Compare commits

...

57 Commits

Author SHA1 Message Date
Philippe G
c5d871f5ee display inline renaming - release 2020-08-11 16:27:58 -07:00
Philippe G
a87066fef6 fix opus early end on long tracks - release 2020-08-10 13:05:51 -07:00
philippe44
683ec77424 Update README.md 2020-08-05 17:21:05 -07:00
philippe44
e5050c9f72 Update README.md 2020-08-05 15:00:59 -07:00
philippe44
daf98c7d50 Update README.md 2020-08-05 14:49:51 -07:00
philippe44
91b0efd741 Update README.md 2020-08-05 14:49:27 -07:00
Philippe G
8820a85112 missing space after command header - release 2020-08-04 10:30:18 -07:00
Philippe G
4a56b55b49 silence DAC do when using spif & sharing IO - release 2020-08-03 19:09:10 -07:00
philippe44
5c79b0c0e8 Update README.md 2020-08-03 17:29:27 -07:00
philippe44
ffd0320e54 Update README.md 2020-08-03 17:27:46 -07:00
Philippe G
80bf63ed2a integrate TAS5713 - release 2020-08-03 13:04:42 -07:00
Philippe G
f9b7d10243 fix vertical VU offset - release 2020-08-02 21:59:17 -07:00
Philippe G
ad2846f50d Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-08-02 21:36:40 -07:00
Philippe G
340a1bd19e audio refactoring done + T-WATCH2020 support 2020-08-02 21:36:36 -07:00
philippe44
1dcd0aeacb Update README.md 2020-08-02 19:15:24 -07:00
philippe44
0bcc2bcecc Update README.md 2020-08-02 18:39:59 -07:00
philippe44
9d7c0eb48f Update README.md 2020-08-02 18:17:13 -07:00
philippe44
a4200b4b85 Update README.md 2020-08-02 18:16:09 -07:00
philippe44
80188e944c Update README.md 2020-08-02 10:57:30 -07:00
philippe44
e6c5c04fd6 Update README.md 2020-08-02 10:56:33 -07:00
Philippe G
78563b20f3 Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-08-01 19:32:01 -07:00
Philippe G
9fd792cf98 refactor DAC handling 2020-08-01 19:31:58 -07:00
philippe44
c7697c31cd Update README.md 2020-08-01 19:03:27 -07:00
Philippe G
77e8c29936 Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-07-31 14:50:59 -07:00
Philippe G
bf4358f340 fix ST7789 + move VFLip/HFlip tp "Layout" option - release 2020-07-31 14:50:56 -07:00
philippe44
3de62b3ad2 Update README.md 2020-07-29 21:56:39 -07:00
Philippe G
4f90002c99 max volume for AP/BT - release 2020-07-28 18:53:10 -07:00
philippe44
f4af5cfda0 Update README.md 2020-07-28 18:38:08 -07:00
philippe44
f4f4570c2a Update README.md 2020-07-28 18:37:31 -07:00
Philippe G
be6bbe1bcc S777xx support + LED brightness - release 2020-07-28 15:57:29 -07:00
Philippe G
dfe468b610 fix opus & vorbis resampling 2020-07-27 14:33:43 -07:00
philippe44
da1aa2e74e Update README.md 2020-07-25 18:40:31 -07:00
Philippe G
c12e0b39f6 add color display support + SSD1351 driver - release 2020-07-25 16:59:26 -07:00
philippe44
b929436f4f Update README.md 2020-07-23 14:11:01 -07:00
philippe44
e39580ad5d Update README.md 2020-07-23 14:10:11 -07:00
philippe44
254459fbf5 Update README.md 2020-07-23 14:07:00 -07:00
philippe44
32a847bf02 Update README.md 2020-07-23 14:04:21 -07:00
philippe44
22b86b3323 Update README.md 2020-07-23 13:56:18 -07:00
philippe44
97856e2f0f Update README.md 2020-07-23 13:40:59 -07:00
philippe44
fafb764120 Update README.md 2020-07-22 17:06:55 -07:00
philippe44
4451aff1c8 Update README.md 2020-07-18 23:45:31 -07:00
Philippe G
af4472dbe9 pause: change stage only when not already off 2020-07-12 12:41:01 -07:00
Philippe G
cf81182dae proper close order on error - release 2020-07-10 23:38:38 -07:00
Philippe G
ba75350455 large mp4 header handling - release 2020-06-18 23:24:19 -07:00
Philippe G
0db1ac38e4 release 2020-06-16 18:41:09 -07:00
Philippe G
fbbe73b1d6 Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-06-16 18:16:38 -07:00
Philippe G
b4c17b02a0 aac channels wrong calculation - release 2020-06-16 18:16:33 -07:00
philippe44
8911d44327 Update README.md 2020-06-15 12:56:18 -07:00
Philippe G
3e39300759 AMPDU must not be defined at all - release 2020-06-11 14:01:06 -07:00
philippe44
dd9018ca28 Update README.md 2020-06-05 17:19:43 -07:00
philippe44
ca5754be5d Update README.md 2020-06-05 17:16:40 -07:00
Philippe G
dc9e235157 knob-only navigation - release 2020-06-05 16:44:29 -07:00
Philippe G
c2c31a191e Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-05-30 00:03:41 -07:00
Philippe G
9f3bc774d4 equalizer sampling message & default squeezelite command line 2020-05-30 00:03:25 -07:00
Sébastien
e87bc7dd83 Trigger build - release 2020-05-29 11:21:49 -04:00
Philippe G
14cc21eb66 Merge branch 'master' of https://github.com/sle118/squeezelite-esp32 2020-05-27 16:58:12 -07:00
Philippe G
4517e9040a log scale 0..5 for brightness - release 2020-05-27 16:58:07 -07:00
69 changed files with 2318 additions and 773 deletions

View File

@@ -1,9 +1,15 @@
# Squeezelite-esp32
## Supported Hardware
### SqueezeAMP
Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP/blob/master/README.md). Add repository https://raw.githubusercontent.com/sle118/squeezelite-esp32/master/plugin/repo.xml to LMS if you want to have a display
Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
Use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
if you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries SqueezeAMP4MBFlash/SqueezeAMP8MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- set_GPIO: 12=green,13=red,34=jack,2=spkfault
- batt_config: channel=7,scale=20.24
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0
- spdif_config: bck=33,ws=25,do=15
### ESP32-A1S
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4000765857347.html?spm=2114.12010615.8148356.11.5d963cd0j669ns) or an external amplifier if you want direct speaker connection.
@@ -18,11 +24,18 @@ The board showed above has the following IO set
So a possible config would be
- set_GPIO: 21=amp,22=green:0,39=jack:0
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
- a button mapping:
```
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
```
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
- dac_config: model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22
- dac_controlset: { "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
- spi_config: dc=27,data=19,clk=18
- display_config: SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip
### ESP32-WROVER + I2S DAC
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
@@ -41,6 +54,10 @@ XMT - 3.3V
Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
### SqueezeAmpToo !
And the super cool project https://github.com/rochuck/squeeze-amp-too
## Configuration
To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters.
@@ -57,8 +74,18 @@ data=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2]
### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. If your DAC also requires i2c, then you must go the re-compile route. Syntax is
```
bck=<gpio>,ws=<gpio>,do=<gpio>
bck=<gpio>,ws=<gpio>,do=<gpio>[,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=gpio[,i2c=<addr>]]
```
if "model" is not set or is not recognized, then default "I2S" is used. I2C parameters are optional an only needed if your dac requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
The parameter "dac_controlset" allows definition of simple commands to be sent over i2c for init, power on and off using a JSON syntax:
```
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ] }
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**
### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
@@ -73,13 +100,24 @@ bck=<gpio>,ws=<gpio>,do=<gpio>
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106]
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
- SSD1322 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
- SSD1327 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
Currently 128x32/64 I2C and SPI display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported
To use the display on LMS, add repository https://raw.githubusercontent.com/sle118/squeezelite-esp32/master/plugin/repo.xml. You will then be able to tweak how the vu-meter and spectrum analyzer are displayed, as well as size of artwork. You can also install the excellent plugin "Music Information Screen" which is super useful to tweak the layout.
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
```
@@ -91,8 +129,6 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
- 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
You can install the excellent plugin "Music Information Screen" which is super useful to tweak the layout for these small displays.
### Infrared
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
@@ -103,11 +139,11 @@ In AirPlay and Bluetooth mode, only these native remotes are supported, I've not
See "set GPIO" below to set the GPIO associated to infrared receiver (option "ir").
### Set GPIO
The parameter "set_GPIO" is use to assign GPIO to various functions.
The parameter "set_GPIO" is used to assign GPIO to various functions.
GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here.
The \<amp\> parameter can use used to assign a GPIO that will be set to 1 when playback starts. It will be reset to 0 when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\>
The \<amp\> parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\>
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
@@ -118,23 +154,35 @@ The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add p
Syntax is:
```
<gpio>=Vcc|GND|amp|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for SqueezeAMP where these are forced at runtime.
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below:
- longpress is 'Play'
- double press is 'Back' (Left in LMS's terminology).
- a quick left-right movement on the encoder is 'Pause'
The speed of double click (or left-right) can be set using the optional parameter of 'knobonly'. This is not a perfect solution, and other ideas are welcome. Be aware that the longer you set double click speed, the less responsive the interface will be. The reason is that I need to wait for that delay before deciding if it's a single or double click. It can also make menu navigation "hesitations" being easoly interpreted as 'Pause'
Use parameter rotary_config with the following syntax:
```
A=<gpio>,B=<gpio>[,SW=gpio>[,volume][,longpress]]
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]
```
HW note: all gpio used for rotary have internal pull-up so normally there is no need to provide Vcc to the encoder. Nevertheless if the encoder board you're using also has its own pull-up that are stronger than ESP32's ones (which is likely the case), then there will be crosstalk between gpio, so you must bring Vcc. Look at your board schematic and you'll understand that these board pull-up create a "winning" pull-down when any other pin is grounded.
See also the "IMPORTANT NOTE" on the "Buttons" section
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
### Buttons
Buttons are described using a JSON string with the following syntax

View File

@@ -26,15 +26,13 @@ CONFIG_SPKFAULT_GPIO=-1
CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=-1
CONFIG_BAT_SCALE="0"
CONFIG_I2S_BCK_IO=27
CONFIG_I2S_WS_IO=26
CONFIG_I2S_DO_IO=25
CONFIG_I2S_DI_IO=35
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=27
CONFIG_SPDIF_WS_IO=26
CONFIG_SPDIF_DO_IO=-1
CONFIG_DAC_CONFIG="model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32"
CONFIG_MUTE_GPIO=-1
CONFIG_MUTE_GPIO_LEVEL=-1
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_TARGET="esp32"
@@ -143,7 +141,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
@@ -523,8 +521,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set

View File

@@ -33,6 +33,8 @@ CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=33
CONFIG_SPDIF_WS_IO=25
CONFIG_SPDIF_DO_IO=-1
CONFIG_MUTE_GPIO=-1
CONFIG_MUTE_GPIO_LEVEL=-1
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_TARGET="esp32"
@@ -144,7 +146,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
@@ -523,8 +525,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set

View File

@@ -524,8 +524,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set

View File

@@ -523,8 +523,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set

View File

@@ -12,7 +12,6 @@ CONFIG_DSP_MAX_FFT_SIZE_512=y
CONFIG_JACK_LOCKED=y
CONFIG_BAT_LOCKED=y
CONFIG_I2C_LOCKED=y
CONFIG_SPDIF_LOCKED=y
CONFIG_LED_LOCKED=y
CONFIG_DISPLAY_CONFIG=""
CONFIG_I2C_CONFIG=""
@@ -30,17 +29,12 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_I2S_BCK_IO=33
CONFIG_I2S_WS_IO=25
CONFIG_I2S_DO_IO=32
CONFIG_I2S_DI_IO=-1
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=33
CONFIG_SPDIF_WS_IO=25
CONFIG_SPDIF_DO_IO=15
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0"
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_TARGET="esp32"
CONFIG_MUTE_GPIO_LEVEL=-1
#
# SDK tool configuration
@@ -143,7 +137,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
@@ -523,8 +517,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set

View File

@@ -12,7 +12,6 @@ CONFIG_DSP_MAX_FFT_SIZE_512=y
CONFIG_JACK_LOCKED=y
CONFIG_BAT_LOCKED=y
CONFIG_I2C_LOCKED=y
CONFIG_SPDIF_LOCKED=y
CONFIG_LED_LOCKED=y
CONFIG_DISPLAY_CONFIG=""
CONFIG_I2C_CONFIG=""
@@ -30,14 +29,10 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_I2S_BCK_IO=33
CONFIG_I2S_WS_IO=25
CONFIG_I2S_DO_IO=32
CONFIG_I2S_DI_IO=-1
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=33
CONFIG_SPDIF_WS_IO=25
CONFIG_SPDIF_DO_IO=15
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14"
CONFIG_MUTE_GPIO_LEVEL=-1
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_TARGET="esp32"
@@ -137,7 +132,7 @@ CONFIG_DEFAULT_AP_GATEWAY="192.168.4.1"
CONFIG_DEFAULT_AP_NETMASK="255.255.255.0"
CONFIG_DEFAULT_AP_MAX_CONNECTIONS=4
CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30"
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
@@ -517,8 +512,8 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0
CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=12
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=n
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=n
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set
CONFIG_ESP32_WIFI_NVS_ENABLED=y

Binary file not shown.

Binary file not shown.

View File

@@ -611,9 +611,21 @@ void config_delete_key(const char *key){
}
config_unlock();
}
void * config_alloc_get(nvs_type_t nvs_type, const char *key) {
return config_alloc_get_default(nvs_type, key, NULL, 0);
}
void * config_alloc_get_str(const char *key, char *lead, char *fallback) {
if (lead && *lead) return strdup(lead);
char *value = config_alloc_get_default(NVS_TYPE_STR, key, NULL, 0);
if ((!value || !*value) && fallback) {
if (value) free(value);
value = strdup(fallback);
}
return value;
}
void * config_alloc_get_default(nvs_type_t nvs_type, const char *key, void * default_value, size_t blob_size) {
void * value = NULL;

View File

@@ -34,6 +34,7 @@ void * config_alloc_get_default(nvs_type_t type, const char *key, void * default
void config_delete_key(const char *key);
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
void * config_alloc_get_str(const char *key, char *lead, char *fallback);
bool wait_for_commit();
char * config_alloc_get_json(bool bFormatted);
esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value);

View File

@@ -73,8 +73,11 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetHFlip( struct GDS_Device* Device, bool On ) { Device->WriteCommand( Device, On ? 0xA1 : 0xA0 ); }
static void SetVFlip( struct GDS_Device *Device, bool On ) { Device->WriteCommand( Device, On ? 0xC8 : 0xC0 ); }
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
Device->WriteCommand( Device, HFlip ? 0xA1 : 0xA0 );
Device->WriteCommand( Device, VFlip ? 0xC8 : 0xC0 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
@@ -117,8 +120,7 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetVFlip( Device, false );
Device->SetHFlip( Device, false );
Device->SetLayout( Device, false, false, false );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks
@@ -135,8 +137,12 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SH1106 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Depth = 1,
#if !defined SHADOW_BUFFER && defined USE_IRAM
.Alloc = GDS_ALLOC_IRAM_SPI;
#endif
};
struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
@@ -144,10 +150,7 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SH1106;
Device->Depth = 1;
#if !defined SHADOW_BUFFER && defined USE_IRAM
Device->Alloc = GDS_ALLOC_IRAM_SPI;
#endif
ESP_LOGI(TAG, "SH1106 driver");
return Device;

View File

@@ -85,8 +85,11 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetHFlip( struct GDS_Device* Device, bool On ) { Device->WriteCommand( Device, On ? 0xA1 : 0xA0 ); }
static void SetVFlip( struct GDS_Device *Device, bool On ) { Device->WriteCommand( Device, On ? 0xC8 : 0xC0 ); }
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
Device->WriteCommand( Device, HFlip ? 0xA1 : 0xA0 );
Device->WriteCommand( Device, VFlip ? 0xC8 : 0xC0 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
@@ -129,8 +132,7 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetVFlip( Device, false );
Device->SetHFlip( Device, false );
Device->SetLayout( Device, false, false, false);
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks
@@ -150,8 +152,12 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SSD1306 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Mode = GDS_MONO, .Depth = 1,
#if !defined SHADOW_BUFFER && defined USE_IRAM
.Alloc = GDS_ALLOC_IRAM_SPI,
#endif
};
struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
@@ -159,10 +165,7 @@ struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SSD1306;
Device->Depth = 1;
#if !defined SHADOW_BUFFER && defined USE_IRAM
Device->Alloc = GDS_ALLOC_IRAM_SPI;
#endif
ESP_LOGI(TAG, "SSD1306 driver");
return Device;

View File

@@ -96,17 +96,10 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetHFlip( struct GDS_Device* Device, bool On ) {
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = On ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Device->WriteCommand( Device, 0xA0 );
Device->WriteData( Device, &Private->ReMap, 1 );
WriteDataByte( Device, 0x11 );
}
static void SetVFlip( struct GDS_Device *Device, bool On ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Private->ReMap = HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Device->WriteCommand( Device, 0xA0 );
Device->WriteData( Device, &Private->ReMap, 1 );
WriteDataByte( Device, 0x11 );
@@ -128,7 +121,7 @@ static bool Init( struct GDS_Device* Device ) {
// find a page size that is not too small is an integer of height
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ;
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
#ifdef SHADOW_BUFFER
Private->Shadowbuffer = malloc( Device->FramebufferSize );
@@ -152,8 +145,7 @@ static bool Init( struct GDS_Device* Device ) {
// set flip modes
Private->ReMap = 0;
Device->SetVFlip( Device, false );
Device->SetHFlip( Device, false );
Device->SetLayout( Device, false, false, false);
// set Clocks
Device->WriteCommand( Device, 0xB3 );
@@ -187,8 +179,9 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SSD1322 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Mode = GDS_GRAYSCALE, .Depth = 4,
};
struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) {
@@ -197,7 +190,6 @@ struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SSD1322;
Device->Depth = 4;
return Device;
}

View File

@@ -168,7 +168,7 @@ static void Update1( struct GDS_Device* Device ) {
}
// in 1 bit mode, SSD1326 has a different memory map than SSD1306 and SH1106
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static void IRAM_ATTR DrawPixel1FastLocal( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t XBit = ( X & 0x07 );
uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width + X ) >> 3 );
@@ -188,12 +188,12 @@ static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int
for (int r = y1; r <= y2; r++) {
int c = x1;
// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
while (c & 0x07 && c <= x2) DrawPixel1FastLocal( Device, c++, r, Color );
// at this point we are aligned on column boundary
int chunk = (x2 - c + 1) >> 3;
memset(optr + Width * r + (c >> 3), _Color, chunk );
c += chunk * 8;
while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
while (c <= x2) DrawPixel1FastLocal( Device, c++, r, Color );
}
}
@@ -222,18 +222,15 @@ static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, i
}
}
static void SetHFlip( struct GDS_Device* Device, bool On ) {
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
else Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
Device->WriteCommand( Device, 0xA0 );
Device->WriteCommand( Device, Private->ReMap );
if (Private->Model == SSD1326) {
Private->ReMap = HFlip ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
Private->ReMap = HFlip ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
} else {
Private->ReMap = VFlip ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
}
static void SetVFlip( struct GDS_Device *Device, bool On ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
else Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Device->WriteCommand( Device, 0xA0 );
Device->WriteCommand( Device, Private->ReMap );
}
@@ -251,7 +248,7 @@ static bool Init( struct GDS_Device* Device ) {
// find a page size that is not too small is an integer of height
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ;
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
#ifdef SHADOW_BUFFER
#ifdef USE_IRAM
@@ -270,7 +267,6 @@ static bool Init( struct GDS_Device* Device ) {
#ifdef USE_IRAM
if (Device->Depth == 4 && Device->IF == GDS_IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
#endif
ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
@@ -292,8 +288,7 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetVFlip( Device, false );
Device->SetHFlip( Device, false );
Device->SetLayout( Device, false, false, false );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks
@@ -317,8 +312,9 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SSD132x = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
.SetLayout = SetLayout,
.Update = Update4, .Init = Init,
.Mode = GDS_GRAYSCALE, .Depth = 4,
};
struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
@@ -335,18 +331,17 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
((struct PrivateSpace*) Device->Private)->Model = Model;
sscanf(Driver, "%*[^:]:%u", &Depth);
Device->Depth = Depth;
if (Model == SSD1326 && Device->Depth == 1) {
if (Model == SSD1326 && Depth == 1) {
Device->Update = Update1;
Device->DrawPixelFast = DrawPixel1Fast;
Device->DrawPixelFast = DrawPixel1FastLocal;
Device->DrawBitmapCBR = DrawBitmapCBR;
Device->ClearWindow = ClearWindow;
Device->Depth = 1;
Device->Mode = GDS_MONO;
#if !defined SHADOW_BUFFER && defined USE_IRAM
Device->Alloc = GDS_ALLOC_IRAM_SPI;
#endif
} else {
Device->Depth = 4;
}
return Device;

View File

@@ -0,0 +1,286 @@
/**
* Copyright (c) 2017-2018 Tara Keeling
* 2020 Philippe G.
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include "gds.h"
#include "gds_private.h"
#define SHADOW_BUFFER
#define USE_IRAM
#define PAGE_BLOCK 2048
#define ENABLE_WRITE 0x5c
#define min(a,b) (((a) < (b)) ? (a) : (b))
static char TAG[] = "SSD1351";
struct PrivateSpace {
uint8_t *iRAM, *Shadowbuffer;
uint8_t ReMap, PageSize;
};
// Functions are not declared to minimize # of lines
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
Device->WriteData( Device, &Data, 1 );
}
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0x15 );
WriteByte( Device, Start );
WriteByte( Device, End );
}
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0x75 );
WriteByte( Device, Start );
WriteByte( Device, End );
}
static void Update16( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
#ifdef SHADOW_BUFFER
uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer;
int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width is always a multiple of 2)
for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) {
if (*optr != *iptr) {
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
}
// wait for a large enough window - careful that window size might increase by more than a line at once !
if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
FirstCol *= 2;
LastCol = LastCol * 2 + 1;
SetRowAddress( Device, FirstRow, LastRow );
SetColumnAddress( Device, FirstCol, LastCol );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 2;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
}
FirstCol = Device->Width / 2; LastCol = 0;
FirstRow = -1;
}
#else
// always update by full lines
SetColumnAddress( Device, 0, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, r, r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
}
}
#endif
}
static void Update24( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width always / by 2)
for (int c = 0; c < (Device->Width * 3) / 2; c++, optr++, iptr++) {
if (*optr != *iptr) {
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
}
// do we have enough to send (cols are divided by 3/2)
if (FirstRow < 0 || ((((LastCol - FirstCol + 1) * 2 + 3 - 1) / 3) * (r - FirstRow + 1) * 3 < PAGE_BLOCK && r != Device->Height - 1)) continue;
FirstCol = (FirstCol * 2) / 3;
LastCol = (LastCol * 2 + 1) / 3;
SetRowAddress( Device, FirstRow, LastRow );
SetColumnAddress( Device, FirstCol, LastCol );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 3;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize );
}
FirstCol = (Device->Width * 3) / 2; LastCol = 0;
FirstRow = -1;
}
#else
// always update by full lines
SetColumnAddress( Device, 0, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, r, r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 3 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
}
}
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Device->WriteCommand( Device, 0xA0 );
WriteByte( Device, Private->ReMap );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, 0xC7 );
WriteByte( Device, Contrast >> 4);
}
static bool Init( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
int Depth = (Device->Depth + 8 - 1) / 8;
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width * Depth));
#ifdef SHADOW_BUFFER
Private->Shadowbuffer = malloc( Device->FramebufferSize );
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
#endif
#ifdef USE_IRAM
Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
ESP_LOGI(TAG, "SSD1351 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
// unlock (specially 0xA2)
Device->WriteCommand( Device, 0xFD);
WriteByte(Device, 0xB1);
// set clocks
/*
Device->WriteCommand( Device, 0xB3 );
WriteByte( Device, ( 0x08 << 4 ) | 0x00 );
*/
// need to be off and disable display RAM
Device->DisplayOff( Device );
// need COM split (5)
Private->ReMap = (1 << 5);
// Display Offset
Device->WriteCommand( Device, 0xA2 );
WriteByte( Device, 0x00 );
// Display Start Line
Device->WriteCommand( Device, 0xA1 );
WriteByte( Device, 0x00 );
// set flip modes & contrast
Device->SetContrast( Device, 0x7F );
Device->SetLayout( Device, false, false, false );
// set Adressing Mode Horizontal
Private->ReMap |= (0 << 2);
// set screen depth (16/18)
if (Device->Depth == 24) Private->ReMap |= (0x02 << 6);
// write ReMap byte
Device->WriteCommand( Device, 0xA0 );
WriteByte( Device, Private->ReMap );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// gone with the wind
Device->DisplayOn( Device );
Device->Update( Device );
return true;
}
static const struct GDS_Device SSD1351 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetLayout = SetLayout,
.Update = Update16, .Init = Init,
.Mode = GDS_RGB565, .Depth = 16,
};
struct GDS_Device* SSD1351_Detect(char *Driver, struct GDS_Device* Device) {
int Depth;
if (!strcasestr(Driver, "SSD1351")) return NULL;
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SSD1351;
sscanf(Driver, "%*[^:]:%u", &Depth);
if (Depth == 18) {
Device->Mode = GDS_RGB666;
Device->Depth = 24;
Device->Update = Update24;
}
return Device;
}

View File

@@ -118,7 +118,7 @@ static void Update( struct GDS_Device* Device ) {
}
// remember that for these ELD drivers W and H are "inverted"
static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixelLocal( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
Y>>= 3;
@@ -129,7 +129,7 @@ static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, in
static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
for (int r = y1; r <= y2; r++) {
for (int c = x1; c <= x2; c++) {
DrawPixelFast( Device, c, r, Color );
DrawPixelLocal( Device, c, r, Color );
}
}
}
@@ -228,8 +228,10 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SSD1675 = {
.DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow,
.DrawPixelFast = DrawPixelFast,
.DrawPixelFast = DrawPixelLocal,
.Update = Update, .Init = Init,
.Mode = GDS_MONO, .Depth = 1,
.Alloc = GDS_ALLOC_NONE,
};
struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
@@ -238,9 +240,6 @@ struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SSD1675;
Device->Depth = 1;
Device->Alloc = GDS_ALLOC_NONE;
char *p;
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->ReadyPin = -1;

297
components/display/ST77xx.c Normal file
View File

@@ -0,0 +1,297 @@
/**
* Copyright (c) 2017-2018 Tara Keeling
* 2020 Philippe G.
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include "gds.h"
#include "gds_private.h"
#define SHADOW_BUFFER
#define USE_IRAM
#define PAGE_BLOCK 2048
#define ENABLE_WRITE 0x2c
#define min(a,b) (((a) < (b)) ? (a) : (b))
static char TAG[] = "ST77xx";
enum { ST7735, ST7789 };
struct PrivateSpace {
uint8_t *iRAM, *Shadowbuffer;
struct {
uint16_t Height, Width;
} Offset;
uint8_t MADCtl, PageSize;
uint8_t Model;
};
// Functions are not declared to minimize # of lines
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
Device->WriteData( Device, &Data, 1 );
}
static void SetColumnAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
Device->WriteCommand( Device, 0x2A );
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
}
static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
Device->WriteCommand( Device, 0x2B );
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
}
static void Update16( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
#ifdef SHADOW_BUFFER
uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer;
int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width is always a multiple of 2)
for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) {
if (*optr != *iptr) {
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
}
// wait for a large enough window - careful that window size might increase by more than a line at once !
if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
FirstCol *= 2;
LastCol = LastCol * 2 + 1;
SetRowAddress( Device, FirstRow + Private->Offset.Height, LastRow + Private->Offset.Height);
SetColumnAddress( Device, FirstCol + Private->Offset.Width, LastCol + Private->Offset.Width );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 2;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
}
FirstCol = Device->Width / 2; LastCol = 0;
FirstRow = -1;
}
#else
// always update by full lines
SetColumnAddress( Device, Private->Offset.Width, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, Private->Offset.Height + r, Private->Offset.Height + r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
}
}
#endif
}
static void Update24( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width always / by 2)
for (int c = 0; c < (Device->Width * 3) / 2; c++, optr++, iptr++) {
if (*optr != *iptr) {
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
}
// do we have enough to send (cols are divided by 3/2)
if (FirstRow < 0 || ((((LastCol - FirstCol + 1) * 2 + 3 - 1) / 3) * (r - FirstRow + 1) * 3 < PAGE_BLOCK && r != Device->Height - 1)) continue;
FirstCol = (FirstCol * 2) / 3;
LastCol = (LastCol * 2 + 1) / 3;
SetRowAddress( Device, FirstRow + Private->Offset.Height, LastRow + Private->Offset.Height);
SetColumnAddress( Device, FirstCol + Private->Offset.Width, LastCol + Private->Offset.Width );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 3;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize );
}
FirstCol = (Device->Width * 3) / 2; LastCol = 0;
FirstRow = -1;
}
#else
// always update by full lines
SetColumnAddress( Device, Private->Offset.Width, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, Private->Offset.Height + r, Private->Offset.Height + r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 3 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
}
}
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->MADCtl = HFlip ? (Private->MADCtl | (1 << 7)) : (Private->MADCtl & ~(1 << 7));
Private->MADCtl = VFlip ? (Private->MADCtl | (1 << 6)) : (Private->MADCtl & ~(1 << 6));
Private->MADCtl = Rotate ? (Private->MADCtl | (1 << 5)) : (Private->MADCtl & ~(1 << 5));
Device->WriteCommand( Device, 0x36 );
WriteByte( Device, Private->MADCtl );
if (Private->Model == ST7789) {
if (Rotate) Private->Offset.Width = HFlip ? 320 - Device->Width : 0;
else Private->Offset.Height = HFlip ? 320 - Device->Height : 0;
}
#ifdef SHADOW_BUFFER
// force a full refresh (almost ...)
memset(Private->Shadowbuffer, 0xAA, Device->FramebufferSize);
#endif
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x29 ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x28 ); }
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, 0x51 );
WriteByte( Device, Contrast );
Device->SetContrast = NULL;
GDS_SetContrast( Device, Contrast );
Device->SetContrast = SetContrast;
}
static bool Init( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
int Depth = (Device->Depth + 8 - 1) / 8;
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width * Depth));
#ifdef SHADOW_BUFFER
Private->Shadowbuffer = malloc( Device->FramebufferSize );
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
#endif
#ifdef USE_IRAM
Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
ESP_LOGI(TAG, "ST77xx with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
// Sleepout + Booster
Device->WriteCommand( Device, 0x11 );
// need BGR & Address Mode
Private->MADCtl = 1 << 3;
Device->WriteCommand( Device, 0x36 );
WriteByte( Device, Private->MADCtl );
// set flip modes & contrast
GDS_SetContrast( Device, 0x7f );
Device->SetLayout( Device, false, false, false );
// set screen depth (16/18)
Device->WriteCommand( Device, 0x3A );
if (Private->Model == ST7789) WriteByte( Device, Device->Depth == 24 ? 0x066 : 0x55 );
else WriteByte( Device, Device->Depth == 24 ? 0x06 : 0x05 );
// no Display Inversion
Device->WriteCommand( Device, Private->Model == ST7735 ? 0x20 : 0x21 );
// gone with the wind
Device->DisplayOn( Device );
Device->Update( Device );
return true;
}
static const struct GDS_Device ST77xx = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff,
.SetLayout = SetLayout,
.Update = Update16, .Init = Init,
.Mode = GDS_RGB565, .Depth = 16,
};
struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
uint8_t Model;
int Depth;
if (strcasestr(Driver, "ST7735")) Model = ST7735;
else if (strcasestr(Driver, "ST7789")) Model = ST7789;
else return NULL;
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = ST77xx;
((struct PrivateSpace*) Device->Private)->Model = Model;
sscanf(Driver, "%*[^:]:%u", &Depth);
if (Depth == 18) {
Device->Mode = GDS_RGB666;
Device->Depth = 24;
Device->Update = Update24;
}
if (Model == ST7789) Device->SetContrast = SetContrast;
return Device;
}

View File

@@ -9,22 +9,37 @@
#include <string.h>
#include <ctype.h>
#include <stdint.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_log.h"
#include "gds.h"
#include "gds_private.h"
static struct GDS_Device Display;
static struct GDS_BacklightPWM PWMConfig;
static char TAG[] = "gds";
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] ) {
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
if (!Driver) return NULL;
if (PWM) PWMConfig = *PWM;
for (int i = 0; DetectFunc[i]; i++) {
if (DetectFunc[i](Driver, &Display)) {
ESP_LOGD(TAG, "Detected driver %p", &Display);
if (PWM && PWM->Init) {
ledc_timer_config_t PWMTimer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_num = PWMConfig.Timer,
};
ledc_timer_config(&PWMTimer);
}
ESP_LOGD(TAG, "Detected driver %p with PWM %d", &Display, PWM ? PWM->Init : 0);
return &Display;
}
}
@@ -53,12 +68,20 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
}
void GDS_Clear( struct GDS_Device* Device, int Color ) {
if (Device->Depth == 1) Color = Color == GDS_COLOR_BLACK ? 0 : 0xff;
else if (Device->Depth == 4) Color = Color | (Color << 4);
memset( Device->Framebuffer, Color, Device->FramebufferSize );
if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize );
else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize );
else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
else if (Device->Depth == 8) memset( Device->Framebuffer, Color, Device->FramebufferSize );
else GDS_ClearWindow(Device, 0, 0, -1, -1, Color);
Device->Dirty = true;
}
#define CLEAR_WINDOW(x1,y1,x2,y2,F,W,C,T,N) \
for (int y = y1; y <= y2; y++) { \
T *Ptr = (T*) F + (y * W + x1)*N; \
for (int c = (x2 - x1)*N; c-- >= 0; *Ptr++ = C); \
}
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
// -1 means up to width/height
if (x2 < 0) x2 = Device->Width - 1;
@@ -80,7 +103,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1;
// for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) {
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
r++;
}
// go fast if we have more than 8 lines to write
@@ -88,7 +111,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8;
} else while (r <= y2) {
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
r++;
}
}
@@ -104,16 +127,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
// try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) {
int c = x1;
if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color);
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
}
}
} else if (Device->Depth == 8) {
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,1);
} else if (Device->Depth == 16) {
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint16_t,1);
} else if (Device->Depth == 24) {
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,3);
} else {
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
GDS_DrawPixelFast( Device, x, y, Color);
DrawPixelFast( Device, x, y, Color);
}
}
}
@@ -138,29 +167,80 @@ bool GDS_Reset( struct GDS_Device* Device ) {
bool GDS_Init( struct GDS_Device* Device ) {
Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
// allocate FB unless explicitely asked not to
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) {
heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
} else {
Device->Framebuffer = calloc( 1, Device->FramebufferSize );
}
NullCheck( Device->Framebuffer, return false );
}
if (Device->Backlight.Pin >= 0) {
Device->Backlight.Channel = PWMConfig.Channel++;
Device->Backlight.PWM = PWMConfig.Max - 1;
ledc_channel_config_t PWMChannel = {
.channel = Device->Backlight.Channel,
.duty = Device->Backlight.PWM,
.gpio_num = Device->Backlight.Pin,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.hpoint = 0,
.timer_sel = PWMConfig.Timer,
};
ledc_channel_config(&PWMChannel);
}
bool Res = Device->Init( Device );
if (!Res) free(Device->Framebuffer);
if (!Res && Device->Framebuffer) free(Device->Framebuffer);
return Res;
}
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); }
void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); }
void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); }
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
switch(Device->Mode) {
case GDS_MONO: return Level;
case GDS_GRAYSCALE: return Level >> (8 - Device->Depth);
case GDS_RGB332:
Level >>= 5;
return (Level << 6) | (Level << 3) | (Level >> 1);
case GDS_RGB444:
Level >>= 4;
return (Level << 8) | (Level << 4) | Level;
case GDS_RGB555:
Level >>= 3;
return (Level << 10) | (Level << 5) | Level;
case GDS_RGB565:
Level >>= 2;
return ((Level & ~0x01) << 10) | (Level << 5) | (Level >> 1);
case GDS_RGB666:
Level >>= 2;
return (Level << 12) | (Level << 6) | Level;
case GDS_RGB888:
return (Level << 16) | (Level << 8) | Level;
}
return -1;
}
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
else if (Device->Backlight.Pin >= 0) {
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
}
}
void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) { if (Device->SetLayout) Device->SetLayout( Device, HFlip, VFlip, Rotate ); }
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; }
int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; }
int GDS_GetDepth( struct GDS_Device* Device ) { return Device->Depth; }
int GDS_GetMode( struct GDS_Device* Device ) { return Device->Mode; }
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }

View File

@@ -5,40 +5,43 @@
#include <stdbool.h>
/* NOTE for drivers:
The build-in DrawPixel(Fast), DrawCBR and ClearWindow are optimized for 1 bit
and 4 bits screen depth. For any other type of screen, DrawCBR and ClearWindow
default to use DrawPixel, which is very sub-optimal. For such other depth, you
must supply the DrawPixelFast. The built-in 1 bit depth function are only for
screen with vertical framing (1 byte = 8 lines). For example SSD1326 in
The build-in DrawPixel(Fast), DrawCBR and ClearWindow have optimized for 1 bit
and 4 bits grayscale screen depth and 8, 16, 24 color. For any other type of screen,
DrawCBR and ClearWindow default to use DrawPixel, which is very sub-optimal. For
other depth, you must supply the DrawPixelFast. The built-in 1 bit depth function
are only for screen with vertical framing (1 byte = 8 lines). For example SSD1326 in
monochrome mode is not such type of screen, SH1106 and SSD1306 are
*/
enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7,
GDS_COLOR_L8, GDS_COLOR_L9, GDS_COLOR_L10, GDS_COLOR_L11, GDS_COLOR_L12, GDS_COLOR_L13, GDS_COLOR_L14, GDS_COLOR_L15,
GDS_COLOR_MAX
};
// this is an ordered enum, do not change!
enum { GDS_MONO = 0, GDS_GRAYSCALE, GDS_RGB332, GDS_RGB444, GDS_RGB555, GDS_RGB565, GDS_RGB666, GDS_RGB888 };
#define GDS_COLOR_BLACK (0)
#define GDS_COLOR_WHITE (-1)
#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1)
#define GDS_COLOR_XOR (256)
struct GDS_Device;
struct GDS_FontDef;
struct GDS_BacklightPWM {
int Channel, Timer, Max;
bool Init;
};
typedef struct GDS_Device* GDS_DetectFunc(char *Driver, struct GDS_Device *Device);
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] );
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
void GDS_DisplayOn( struct GDS_Device* Device );
void GDS_DisplayOff( struct GDS_Device* Device );
void GDS_Update( struct GDS_Device* Device );
void GDS_SetHFlip( struct GDS_Device* Device, bool On );
void GDS_SetVFlip( struct GDS_Device* Device, bool On );
void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
void GDS_SetDirty( struct GDS_Device* Device );
int GDS_GetWidth( struct GDS_Device* Device );
int GDS_GetHeight( struct GDS_Device* Device );
int GDS_GetDepth( struct GDS_Device* Device );
int GDS_GetMode( struct GDS_Device* Device );
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level );
void GDS_ClearExt( struct GDS_Device* Device, bool full, ...);
void GDS_Clear( struct GDS_Device* Device, int Color );
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );

View File

@@ -8,10 +8,10 @@ extern "C" {
struct GDS_Device;
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int speed );
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin );
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin );
bool GDS_SPIInit( int SPI, int DC );
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed );
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin );
#ifdef __cplusplus
}

View File

@@ -5,7 +5,6 @@
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
@@ -45,9 +44,13 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
*a = Temp;
}
// un-comment if need to be instanciated for external callers
extern inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
extern inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixelFast( Device, X, Y, Color );
}
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixel( Device, X, Y, Color );
}
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
int XEnd = x + Width;
@@ -60,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
if (y < 0) y = 0;
else if (y >= Device->Height) y = Device->Height - 1;
for ( ; x < XEnd; x++ ) GDS_DrawPixelFast( Device, x, y, Color );
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
}
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
@@ -74,7 +77,7 @@ void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Col
if (y < 0) y = 0;
else if (YEnd >= Device->Height) YEnd = Device->Height - 1;
for ( ; y < YEnd; y++ ) GDS_DrawPixel( Device, x, y, Color );
for ( ; y < YEnd; y++ ) DrawPixel( Device, x, y, Color );
}
static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
@@ -94,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; x < x1; x++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -123,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; y < y1; y++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -199,7 +202,9 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
if (Device->DrawBitmapCBR) {
Device->DrawBitmapCBR( Device, Data, Width, Height, Color );
} else if (Device->Depth == 1) {
Height >>= 3;
// need to do row/col swap and bit-reverse
for (int r = 0; r < Height; r++) {
uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
@@ -211,8 +216,10 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
} else if (Device->Depth == 4) {
uint8_t *optr = Device->Framebuffer;
int LineLen = Device->Width >> 1;
Height >>= 3;
Color &= 0x0f;
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
@@ -238,33 +245,103 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
}
} else if (Device->Depth == 8) {
uint8_t *optr = Device->Framebuffer;
int LineLen = Device->Width;
Height >>= 3;
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen;
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c; }
}
} else if (Device->Depth == 16) {
uint16_t *optr = (uint16_t*) Device->Framebuffer;
int LineLen = Device->Width;
Height >>= 3;
Color = __builtin_bswap16(Color);
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
*optr = ((Byte & 0x01) * Color); optr += LineLen;
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = (uint16_t*) Device->Framebuffer + c; }
}
} else if (Device->Depth == 24) {
uint8_t *optr = Device->Framebuffer;
int LineLen = Device->Width * 3;
Height >>= 3;
if (Device->Mode == GDS_RGB666) Color = ((Color << 4) & 0xff0000) | ((Color << 2) & 0xff00) | (Color & 0x00ff);
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
#define SET24(O,D) O[0]=(D)>>16; O[1]=(D)>>8; O[2]=(D);
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
SET24(optr,(Byte & 0x01) * Color); optr += LineLen;
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c * 3; }
}
} else {
Height >>= 3;
// don't know bitdepth, use brute-force solution
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = *Data++;
GDS_DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
if (++r == Height) { c++; r = 0; }
}
/* for better understanding, here is the mundane version
for (int x = 0; x < Width; x++) {
for (int y = 0; y < Height; y++) {
uint8_t Byte = *Data++;
GDS_DrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
}
}
*/

View File

@@ -17,15 +17,12 @@
extern "C" {
#endif
#ifndef _GDS_PRIVATE_H_
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
#endif
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0
void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color);

View File

@@ -89,7 +89,7 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
YBit = ( i + OffsetY ) & 0x07;
if ( GlyphData[ YByte ] & BIT( YBit ) ) {
GDS_DrawPixel( Device, x, y, Color );
DrawPixel( Device, x, y, Color );
}
}

View File

@@ -24,8 +24,9 @@ typedef struct {
const unsigned char *InData; // Pointer to jpeg data
int InPos; // Current position in jpeg data
int Width, Height;
uint8_t Mode;
union {
uint16_t *OutData; // Decompress
void *OutData;
struct { // DirectDraw
struct GDS_Device *Device;
int XOfs, YOfs;
@@ -35,6 +36,40 @@ typedef struct {
};
} JpegCtx;
/****************************************************************************************
* RGB conversion (24 bits 888: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
*/
static inline int Scaler332(uint8_t *Pixels) {
return (Pixels[2] & ~0x1f) | ((Pixels[1] & ~0x1f) >> 3) | (Pixels[0] >> 6);
}
static inline int Scaler444(uint8_t *Pixels) {
return ((Pixels[2] & ~0x0f) << 4) | (Pixels[1] & ~0x0f) | (Pixels[0] >> 4);
}
static inline int Scaler555(uint8_t *Pixels) {
return ((Pixels[2] & ~0x07) << 7) | ((Pixels[1] & ~0x07) << 2) | (Pixels[0] >> 3);
}
static inline int Scaler565(uint8_t *Pixels) {
return ((Pixels[2] & ~0x07) << 8) | ((Pixels[1] & ~0x03) << 3) | (Pixels[0] >> 3);
}
static inline int Scaler666(uint8_t *Pixels) {
return ((Pixels[2] & ~0x03) << 10) | ((Pixels[1] & ~0x03) << 4) | (Pixels[0] >> 2);
}
static inline int Scaler888(uint8_t *Pixels) {
return (Pixels[2] << 16) | (Pixels[1] << 8) | Pixels[0];
}
static inline int ScalerGray(uint8_t *Pixels) {
return (Pixels[2] * 14 + Pixels[1] * 76 + Pixels[0] * 38) >> 7;
}
static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
JpegCtx *Context = (JpegCtx*) Decoder->device;
if (Buf) memcpy(Buf, Context->InData + Context->InPos, Len);
@@ -42,43 +77,94 @@ static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
return Len;
}
#define OUTHANDLER(F) \
for (int y = Frame->top; y <= Frame->bottom; y++) { \
for (int x = Frame->left; x <= Frame->right; x++) { \
OutData[Context->Width * y + x] = F(Pixels); \
Pixels += 3; \
} \
}
#define OUTHANDLER24(F) \
for (int y = Frame->top; y <= Frame->bottom; y++) { \
uint8_t *p = OutData + (Context->Width * y + Frame->left) * 3; \
for (int c = Frame->right - Frame->left; c-- >= 0;) { \
uint32_t v = F(Pixels); \
*p++ = v; *p++ = v >> 8; *p++ = v >> 16; \
Pixels += 3; \
} \
}
static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
JpegCtx *Context = (JpegCtx*) Decoder->device;
uint8_t *Pixels = (uint8_t*) Bitmap;
for (int y = Frame->top; y <= Frame->bottom; y++) {
for (int x = Frame->left; x <= Frame->right; x++) {
// Convert the 888 to RGB565
uint16_t Value = (*Pixels++ & ~0x07) << 8;
Value |= (*Pixels++ & ~0x03) << 3;
Value |= *Pixels++ >> 3;
Context->OutData[Context->Width * y + x] = Value;
}
// decoded image is RGB888
if (Context->Mode == GDS_RGB888) {
uint8_t *OutData = (uint8_t*) Context->OutData;
OUTHANDLER24(Scaler888);
} else if (Context->Mode == GDS_RGB666) {
uint8_t *OutData = (uint8_t*) Context->OutData;
OUTHANDLER24(Scaler666);
} else if (Context->Mode == GDS_RGB565) {
uint16_t *OutData = (uint16_t*) Context->OutData;
OUTHANDLER(Scaler565);
} else if (Context->Mode == GDS_RGB555) {
uint16_t *OutData = (uint16_t*) Context->OutData;
OUTHANDLER(Scaler555);
} else if (Context->Mode == GDS_RGB444) {
uint16_t *OutData = (uint16_t*) Context->OutData;
OUTHANDLER(Scaler444);
} else if (Context->Mode == GDS_RGB332) {
uint8_t *OutData = (uint8_t*) Context->OutData;
OUTHANDLER(Scaler332);
} else if (Context->Mode <= GDS_GRAYSCALE) {
uint8_t *OutData = (uint8_t*) Context->OutData;
OUTHANDLER(ScalerGray);
}
return 1;
}
// Convert the RGB888 to destination color plane, use DrawPixel and not "fast"
// version as X,Y may be beyond screen
#define OUTHANDLERDIRECT(F,S) \
for (int y = Frame->top; y <= Frame->bottom; y++) { \
if (y < Context->YMin) continue; \
for (int x = Frame->left; x <= Frame->right; x++) { \
if (x < Context->XMin) continue; \
DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \
Pixels += 3; \
} \
}
static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
JpegCtx *Context = (JpegCtx*) Decoder->device;
uint8_t *Pixels = (uint8_t*) Bitmap;
int Shift = 8 - Context->Depth;
for (int y = Frame->top; y <= Frame->bottom; y++) {
if (y < Context->YMin) continue;
for (int x = Frame->left; x <= Frame->right; x++) {
if (x < Context->XMin) continue;
// Convert the 888 to RGB565
int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift;
Pixels += 3;
// used DrawPixel and not "fast" version as X,Y may be beyond screen
GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value);
}
// decoded image is RGB888, shift only make sense for grayscale
if (Context->Mode == GDS_RGB888) {
OUTHANDLERDIRECT(Scaler888, 0);
} else if (Context->Mode == GDS_RGB666) {
OUTHANDLERDIRECT(Scaler666, 0);
} else if (Context->Mode == GDS_RGB565) {
OUTHANDLERDIRECT(Scaler565, 0);
} else if (Context->Mode == GDS_RGB555) {
OUTHANDLERDIRECT(Scaler555, 0);
} else if (Context->Mode == GDS_RGB444) {
OUTHANDLERDIRECT(Scaler444, 0);
} else if (Context->Mode == GDS_RGB332) {
OUTHANDLERDIRECT(Scaler332, 0);
} else if (Context->Mode <= GDS_GRAYSCALE) {
OUTHANDLERDIRECT(ScalerGray, Shift);
}
return 1;
}
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly) {
static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) {
JDEC Decoder;
JpegCtx Context;
char *Scratch = calloc(SCRATCH_SIZE, 1);
@@ -99,7 +185,9 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
Decoder.scale = Scale;
if (Res == JDR_OK && !SizeOnly) {
Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t));
if (RGB_Mode <= GDS_RGB332) Context.OutData = malloc(Decoder.width * Decoder.height);
else if (RGB_Mode < GDS_RGB666) Context.OutData = malloc(Decoder.width * Decoder.height * 2);
else if (RGB_Mode <= GDS_RGB888) Context.OutData = malloc(Decoder.width * Decoder.height * 3);
// find the scaling factor
uint8_t N = 0, ScaleInt = ceil(1.0 / Scale);
@@ -114,6 +202,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
if (Context.OutData) {
Context.Width = Decoder.width / (1 << N);
Context.Height = Decoder.height / (1 << N);
Context.Mode = RGB_Mode;
if (Width) *Width = Context.Width;
if (Height) *Height = Context.Height;
Res = jd_decomp(&Decoder, OutHandler, N);
@@ -121,7 +210,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
}
} else {
ESP_LOGE(TAG, "Can't allocate bitmap %dx%d", Decoder.width, Decoder.height);
ESP_LOGE(TAG, "Can't allocate bitmap %dx%d or invalid mode %d", Decoder.width, Decoder.height, RGB_Mode);
}
} else if (!SizeOnly) {
ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
@@ -132,138 +221,153 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
return Context.OutData;
}
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale) {
return DecodeJPEG(Source, Width, Height, Scale, false);
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode) {
return DecodeJPEG(Source, Width, Height, Scale, false, RGB_Mode);
}
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
DecodeJPEG(Source, Width, Height, 1, true);
DecodeJPEG(Source, Width, Height, 1, true, -1);
}
/****************************************************************************************
* Simply draw a RGB 16bits image
* RGB conversion (24 bits: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
*/
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
if (Device->DrawRGB16) {
Device->DrawRGB16( Device, Image, x, y, Width, Height, RGB_Mode );
static inline int ToGray888(uint8_t **Pixel) {
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
return (((v & 0xff) * 14) + ((v >> 8) & 0xff) * 76 + ((v >> 16) * 38) + 1) >> 7;
}
static inline int ToGray666(uint8_t **Pixel) {
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
return (((v & 0x3f) * 14) + ((v >> 6) & 0x3f) * 76 + ((v >> 12) * 38) + 1) >> 7;
}
static inline int ToGray565(uint16_t **Pixel) {
uint16_t v = *(*Pixel)++;
return ((((v & 0x1f) * 14) << 1) + ((v >> 5) & 0x3f) * 76 + (((v >> 11) * 38) << 1) + 1) >> 7;
}
static inline int ToGray555(uint16_t **Pixel) {
uint16_t v = *(*Pixel)++;
return ((v & 0x1f) * 14 + ((v >> 5) & 0x1f) * 76 + (v >> 10) * 38) >> 7;
}
static inline int ToGray444(uint16_t **Pixel) {
uint16_t v = *(*Pixel)++;
return ((v & 0x0f) * 14 + ((v >> 4) & 0x0f) * 76 + (v >> 8) * 38) >> 7;
}
static inline int ToGray332(uint8_t **Pixel) {
uint8_t v = *(*Pixel)++;
return ((((v & 0x3) * 14) << 1) + ((v >> 2) & 0x7) * 76 + (v >> 5) * 38 + 1) >> 7;
}
static inline int ToSelf(uint8_t **Pixel) {
return *(*Pixel)++;
}
#define DRAW_GRAYRGB(S,F) \
if (Scale > 0) { \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
DrawPixel( Device, c + x, r + y, F(S) >> Scale); \
} \
} \
} else { \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
DrawPixel( Device, c + x, r + y, F(S) << -Scale); \
} \
} \
}
#define DRAW_RGB(T) \
T *S = (T*) Image; \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
DrawPixel(Device, c + x, r + y, *S++); \
} \
}
#define DRAW_RGB24 \
uint8_t *S = (uint8_t*) Image; \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
uint32_t v = *S++; v |= *S++ << 8; v |= *S++ << 16; \
DrawPixel(Device, c + x, r + y, v); \
} \
}
/****************************************************************************************
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
*/
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
// don't do anything if driver supplies a draw function
if (Device->DrawRGB) {
Device->DrawRGB( Device, Image, x, y, Width, Height, RGB_Mode );
Device->Dirty = true;
return;
}
// RGB type displays
if (Device->Mode > GDS_GRAYSCALE) {
// image must match the display mode!
if (Device->Mode != RGB_Mode) {
ESP_LOGE(TAG, "non-matching display & image mode %u %u", Device->Mode, RGB_Mode);
return;
}
if (RGB_Mode == GDS_RGB332) {
DRAW_RGB(uint8_t);
} else if (RGB_Mode < GDS_RGB666) {
DRAW_RGB(uint16_t);
} else {
switch(RGB_Mode) {
case GDS_RGB565:
// 6 bits pixels to be placed. Use a linearized structure for a bit of optimization
if (Device->Depth < 6) {
int Scale = 6 - Device->Depth;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
}
} else {
int Scale = Device->Depth - 6;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
}
}
}
break;
case GDS_RGB555:
// 5 bits pixels to be placed Use a linearized structure for a bit of optimization
if (Device->Depth < 5) {
int Scale = 5 - Device->Depth;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
}
} else {
int Scale = Device->Depth - 5;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
}
}
}
break;
case GDS_RGB444:
// 4 bits pixels to be placed
if (Device->Depth < 4) {
int Scale = 4 - Device->Depth;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
}
} else {
int Scale = Device->Depth - 4;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30;
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
}
}
}
break;
}
DRAW_RGB24;
}
Device->Dirty = true;
return;
}
/****************************************************************************************
* Simply draw a RGB 8 bits image (R:3,G:3,B:2) or plain grayscale
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
*/
void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
if (Device->DrawRGB8) {
Device->DrawRGB8( Device, Image, x, y, Width, Height, RGB_Mode );
} else if (RGB_Mode == GDS_GRAYSCALE) {
// 8 bits pixels
// set the right scaler when displaying grayscale
if (RGB_Mode <= GDS_GRAYSCALE) {
int Scale = 8 - Device->Depth;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
GDS_DrawPixel( Device, c + x, r + y, *Image++ >> Scale);
}
}
} else if (Device->Depth < 3) {
// 3 bits pixels to be placed
DRAW_GRAYRGB(&Image,ToSelf);
} else if (RGB_Mode == GDS_RGB332) {
int Scale = 3 - Device->Depth;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
}
DRAW_GRAYRGB(&Image,ToGray332);
} else if (RGB_Mode < GDS_RGB666) {
if (RGB_Mode == GDS_RGB565) {
int Scale = 6 - Device->Depth;
DRAW_GRAYRGB((uint16_t**)&Image,ToGray565);
} else if (RGB_Mode == GDS_RGB555) {
int Scale = 5 - Device->Depth;
DRAW_GRAYRGB((uint16_t**)&Image,ToGray555);
} else if (RGB_Mode == GDS_RGB444) {
int Scale = 4 - Device->Depth;
DRAW_GRAYRGB((uint16_t**)&Image,ToGray444)
}
} else {
// 3 bits pixels to be placed
int Scale = Device->Depth - 3;
for (int r = 0; r < Height; r++) {
for (int c = 0; c < Width; c++) {
int pixel = *Image++;
pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100;
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
}
if (RGB_Mode == GDS_RGB666) {
int Scale = 6 - Device->Depth;
DRAW_GRAYRGB(&Image,ToGray666);
} else if (RGB_Mode == GDS_RGB888) {
int Scale = 8 - Device->Depth;
DRAW_GRAYRGB(&Image,ToGray888);
}
}
Device->Dirty = true;
}
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
/****************************************************************************************
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
*/
bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
JDEC Decoder;
JpegCtx Context;
@@ -313,6 +417,7 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int
Context.XMin = x - Context.XOfs;
Context.YMin = y - Context.YOfs;
Context.Mode = Device->Mode;
// do decompress & draw
Res = jd_decomp(&Decoder, OutHandlerDirect, N);

View File

@@ -15,8 +15,6 @@
struct GDS_Device;
enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE };
// Fit options for GDS_DrawJPEG
#define GDS_IMAGE_LEFT 0x00
#define GDS_IMAGE_CENTER_X 0x01
@@ -28,8 +26,7 @@ enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE };
#define GDS_IMAGE_FIT 0x10 // re-scale by a factor of 2^N (up to 3)
// Width and Height can be NULL if you already know them (actual scaling is closest ^2)
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale);
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode); // can be 8, 16 or 24 bits per pixel in return
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );

View File

@@ -72,6 +72,11 @@ typedef struct spi_device_t* spi_device_handle_t;
struct GDS_Device {
uint8_t IF;
int8_t RSTPin;
struct {
int8_t Pin, Channel;
int PWM;
} Backlight;
union {
// I2C Specific
struct {
@@ -80,7 +85,6 @@ struct GDS_Device {
// SPI specific
struct {
spi_device_handle_t SPIHandle;
int8_t RSTPin;
int8_t CSPin;
};
};
@@ -93,11 +97,11 @@ struct GDS_Device {
uint16_t Width;
uint16_t Height;
uint8_t Depth;
uint8_t Depth, Mode;
uint8_t Alloc;
uint8_t* Framebuffer;
uint16_t FramebufferSize;
uint32_t FramebufferSize;
bool Dirty;
// default fonts when using direct draw
@@ -113,28 +117,26 @@ struct GDS_Device {
void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast );
void (*DisplayOn)( struct GDS_Device* Device );
void (*DisplayOff)( struct GDS_Device* Device );
void (*SetHFlip)( struct GDS_Device* Device, bool On );
void (*SetVFlip)( struct GDS_Device* Device, bool On );
void (*SetLayout)( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
// must provide for depth other than 1 (vertical) and 4 (may provide for optimization)
void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color );
void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color );
// may provide for optimization
void (*DrawRGB16)( struct GDS_Device* Device, uint16_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
void (*DrawRGB8)( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
// interface-specific methods
WriteCommandProc WriteCommand;
WriteDataProc WriteData;
// 16 bytes for whatever the driver wants (should be aligned as it's 32 bits)
uint32_t Private[4];
// 32 bytes for whatever the driver wants (should be aligned as it's 32 bits)
uint32_t Private[8];
};
bool GDS_Reset( struct GDS_Device* Device );
bool GDS_Init( struct GDS_Device* Device );
inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
bool Result = (
( x >= 0 ) &&
( x < Device->Width ) &&
@@ -151,9 +153,9 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result;
}
inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset = NULL;
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
@@ -172,22 +174,48 @@ inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int
}
}
inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset;
FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color);
else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color);
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
}
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
}

View File

@@ -99,7 +99,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->Width; c++)
for (int y = Y_min; y < Y_max; y++)
GDS_DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
}
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

View File

@@ -66,13 +66,14 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
*
* Returns true on successful init of display.
*/
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin ) {
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin ) {
NullCheck( Device, return false );
Device->WriteCommand = I2CDefaultWriteCommand;
Device->WriteData = I2CDefaultWriteData;
Device->Address = I2CAddress;
Device->RSTPin = RSTPin;
Device->Backlight.Pin = BacklightPin;
Device->IF = GDS_IF_I2C;
Device->Width = Width;
Device->Height = Height;

View File

@@ -34,7 +34,7 @@ bool GDS_SPIInit( int SPI, int DC ) {
return true;
}
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed ) {
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed ) {
spi_device_interface_config_t SPIDeviceConfig;
spi_device_handle_t SPIDevice;
@@ -50,6 +50,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
SPIDeviceConfig.spics_io_num = CSPin;
SPIDeviceConfig.queue_size = 1;
SPIDeviceConfig.flags = SPI_DEVICE_NO_DUMMY;
ESP_ERROR_CHECK_NONFATAL( spi_bus_add_device( SPIHost, &SPIDeviceConfig, &SPIDevice ), return false );
@@ -58,6 +59,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
Device->SPIHandle = SPIDevice;
Device->RSTPin = RSTPin;
Device->CSPin = CSPin;
Device->Backlight.Pin = BackLightPin;
Device->IF = GDS_IF_SPI;
Device->Width = Width;
Device->Height = Height;

View File

@@ -48,30 +48,31 @@ static EXT_RAM_ATTR struct {
static void displayer_task(void *args);
struct GDS_Device *display;
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, NULL };
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, NULL };
/****************************************************************************************
*
*/
void display_init(char *welcome) {
bool init = false;
char *config = config_alloc_get(NVS_TYPE_STR, "display_config");
char *config = config_alloc_get_str("display_config", CONFIG_DISPLAY_CONFIG, "N/A");
if (!config) {
ESP_LOGI(TAG, "no display");
return false;
}
int width = -1, height = -1;
int width = -1, height = -1, backlight_pin = -1;
char *p, *drivername = strstr(config, "driver");
// query drivers to see if we have a match
ESP_LOGI(TAG, "Trying to configure display with %s", config);
display = GDS_AutoDetect(drivername ? drivername : "SSD1306", drivers);
if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "back")) != NULL) backlight_pin = atoi(strchr(p, '=') + 1);
// query drivers to see if we have a match
ESP_LOGI(TAG, "Trying to configure display with %s", config);
if (backlight_pin >= 0) {
struct GDS_BacklightPWM PWMConfig = { .Channel = pwm_system.base_channel++, .Timer = pwm_system.timer, .Max = pwm_system.max, .Init = false };
display = GDS_AutoDetect(drivername, drivers, &PWMConfig);
} else {
display = GDS_AutoDetect(drivername, drivers, NULL);
}
// so far so good
if (display && width > 0 && height > 0) {
@@ -86,7 +87,7 @@ void display_init(char *welcome) {
init = true;
GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
GDS_I2CAttachDevice( display, width, height, address, RST_pin );
GDS_I2CAttachDevice( display, width, height, address, RST_pin, backlight_pin );
ESP_LOGI(TAG, "Display is I2C on port %u", address);
} else if (strstr(config, "SPI") && spi_system_host != -1) {
@@ -97,7 +98,7 @@ void display_init(char *welcome) {
init = true;
GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, speed );
GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed );
ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin);
} else {
@@ -113,8 +114,7 @@ void display_init(char *welcome) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[DISPLAYER_STACK_SIZE] __attribute__ ((aligned (4)));
GDS_SetHFlip(display, strcasestr(config, "HFlip") ? true : false);
GDS_SetVFlip(display, strcasestr(config, "VFlip") ? true : false);
GDS_SetLayout( display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
GDS_SetFont(display, &Font_droid_sans_fallback_15x17 );
GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, welcome);

View File

@@ -168,8 +168,8 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
addr.sin_port = htons(ctx->port);
#endif
if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
closesocket(ctx->sock);
free(ctx);
return NULL;

View File

@@ -75,7 +75,7 @@ const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
.quadhd_io_num = -1
};
nvs_item = config_alloc_get(NVS_TYPE_STR, "spi_config");
nvs_item = config_alloc_get_str("spi_config", CONFIG_SPI_CONFIG, NULL);
if (nvs_item) {
if ((p = strcasestr(nvs_item, "data")) != NULL) spi.mosi_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "clk")) != NULL) spi.sclk_io_num = atoi(strchr(p, '=') + 1);

View File

@@ -8,10 +8,11 @@
* https://opensource.org/licenses/MIT
*
*/
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "esp_system.h"
#include "esp_log.h"
#include "cJSON.h"
@@ -36,6 +37,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c
static esp_err_t actrls_init_json(const char *profile_name, bool create);
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
static void rotary_timer( TimerHandle_t xTimer );
static const actrls_config_map_t actrls_config_map[] =
{
@@ -71,6 +73,9 @@ static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
static struct {
bool long_state;
bool volume_lock;
TimerHandle_t timer;
bool click_pending;
int left_count;
} rotary;
static const struct ir_action_map_s{
@@ -133,8 +138,16 @@ esp_err_t actrls_init(const char *profile_name) {
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "knobonly")) != NULL) {
p = strchr(p, '=');
int double_press = p ? atoi(p + 1) : 350;
rotary.timer = xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer);
longpress = 500;
ESP_LOGI(TAG, "single knob navigation %d", double_press);
} else {
if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
}
// create rotary (no handling of long press)
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
@@ -223,17 +236,42 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
switch(event) {
case ROTARY_LEFT:
if (rotary.long_state) action = ACTRLS_PREV;
if (rotary.timer) {
if (rotary.left_count) {
action = KNOB_LEFT;
// need to add a left button the first time
if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true);
}
xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
rotary.left_count++;
}
else if (rotary.long_state) action = ACTRLS_PREV;
else if (rotary.volume_lock) action = ACTRLS_VOLDOWN;
else action = KNOB_LEFT;
break;
case ROTARY_RIGHT:
if (rotary.long_state) action = ACTRLS_NEXT;
if (rotary.timer) {
if (rotary.left_count == 1) {
action = ACTRLS_PAUSE;
rotary.left_count = 0;
xTimerStop(rotary.timer, 0);
} else action = KNOB_RIGHT;
}
else if (rotary.long_state) action = ACTRLS_NEXT;
else if (rotary.volume_lock) action = ACTRLS_VOLUP;
else action = KNOB_RIGHT;
break;
case ROTARY_PRESSED:
if (long_press) rotary.long_state = !rotary.long_state;
if (rotary.timer) {
if (long_press) action = ACTRLS_PLAY;
else if (rotary.click_pending) {
action = BCTRLS_LEFT;
xTimerStop(rotary.timer, 0);
}
else xTimerStart(rotary.timer, 20 / portTICK_RATE_MS);
rotary.click_pending = !rotary.click_pending;
}
else if (long_press) rotary.long_state = !rotary.long_state;
else if (rotary.volume_lock) action = ACTRLS_TOGGLE;
else action = KNOB_PUSH;
break;
@@ -244,6 +282,19 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
}
/****************************************************************************************
*
*/
static void rotary_timer( TimerHandle_t xTimer ) {
if (rotary.click_pending) {
(*current_controls[KNOB_PUSH])(true);
rotary.click_pending = false;
} else if (rotary.left_count) {
if (rotary.left_count == 1) (*current_controls[KNOB_LEFT])(true);
rotary.left_count = 0;
}
}
/****************************************************************************************
*
*/

View File

@@ -303,7 +303,7 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
}
}
// huh
// don't know what we are doing here
if (!button) return NULL;
prev_client = button->client;

View File

@@ -18,6 +18,10 @@ extern int i2c_system_speed;
extern int spi_system_host;
extern int spi_system_dc_gpio;
extern bool gpio36_39_used;
typedef struct {
int timer, base_channel, max;
} pwm_system_t;
extern pwm_system_t pwm_system;
#ifdef CONFIG_SQUEEZEAMP
#define ADAC dac_tas57xx

View File

@@ -10,14 +10,18 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "led.h"
#include "globdefs.h"
#include "accessors.h"
#include "config.h"
#define MAX_LED 8
#define BLOCKTIME 10 // up to portMAX_DELAY
@@ -29,6 +33,8 @@ static struct led_s {
bool on;
int onstate;
int ontime, offtime;
int pwm;
int channel;
int pushedon, pushedoff;
bool pushed;
TimerHandle_t timer;
@@ -37,8 +43,22 @@ static struct led_s {
static struct {
int gpio;
int active;
} green = { CONFIG_LED_GREEN_GPIO, 0 },
red = { CONFIG_LED_RED_GPIO, 0 };
int pwm;
} green = { .gpio = CONFIG_LED_GREEN_GPIO, .active = 0, .pwm = -1 },
red = { .gpio = CONFIG_LED_RED_GPIO, .active = 0, .pwm = -1 };
static int led_max = 2;
/****************************************************************************************
*
*/
static void set_level(struct led_s *led, bool on) {
if (led->pwm < 0) gpio_set_level(led->gpio, on ? led->onstate : !led->onstate);
else {
ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
}
}
/****************************************************************************************
*
@@ -49,8 +69,8 @@ static void vCallbackFunction( TimerHandle_t xTimer ) {
if (!led->timer) return;
led->on = !led->on;
ESP_LOGD(TAG,"led vCallbackFunction setting gpio %d level", led->gpio);
gpio_set_level(led->gpio, led->on ? led->onstate : !led->onstate);
ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (pwm:%d)", led->gpio, led->on, led->pwm);
set_level(led, led->on);
// was just on for a while
if (!led->on && led->offtime == -1) return;
@@ -89,22 +109,37 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
if (ontime == 0) {
ESP_LOGD(TAG,"led %d, setting reverse level", idx);
gpio_set_level(leds[idx].gpio, !leds[idx].onstate);
set_level(leds + idx, false);
} else if (offtime == 0) {
ESP_LOGD(TAG,"led %d, setting level", idx);
gpio_set_level(leds[idx].gpio, leds[idx].onstate);
set_level(leds + idx, true);
} else {
if (!leds[idx].timer) {
ESP_LOGD(TAG,"led %d, Creating timer", idx);
leds[idx].timer = xTimerCreate("ledTimer", ontime / portTICK_RATE_MS, pdFALSE, (void *)&leds[idx], vCallbackFunction);
}
leds[idx].on = true;
ESP_LOGD(TAG,"led %d, Setting gpio %d", idx, leds[idx].gpio);
gpio_set_level(leds[idx].gpio, leds[idx].onstate);
ESP_LOGD(TAG,"led %d, Starting timer.", idx);
set_level(leds + idx, true);
ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio);
if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false;
}
ESP_LOGD(TAG,"led %d, led_blink_core_done", idx);
return true;
}
/****************************************************************************************
*
*/
bool led_brightness(int idx, int pwm) {
if (pwm > 100) pwm = 100;
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
if (!leds[idx].onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
ledc_set_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel, leds[idx].pwm);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel);
return true;
}
@@ -123,33 +158,49 @@ bool led_unpush(int idx) {
/****************************************************************************************
*
*/
bool led_config(int idx, gpio_num_t gpio, int onstate) {
if(gpio<0){
ESP_LOGW(TAG,"LED GPIO not configured");
return false;
}
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s", idx, gpio, onstate>0?"On":"Off");
if (idx >= MAX_LED) return false;
leds[idx].gpio = gpio;
leds[idx].onstate = onstate;
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Selecting GPIO pad", idx, gpio, onstate>0?"On":"Off");
gpio_pad_select_gpio(gpio);
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Setting direction to OUTPUT", idx, gpio, onstate>0?"On":"Off");
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Setting State to %d", idx, gpio, onstate>0?"On":"Off", onstate);
gpio_set_level(gpio, !onstate);
ESP_LOGD(TAG,"Done configuring the led");
return true;
int led_allocate(void) {
if (led_max < MAX_LED) return led_max++;
return -1;
}
/****************************************************************************************
*
*/
bool led_unconfig(int idx) {
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
if (gpio < 0) {
ESP_LOGW(TAG,"LED GPIO -1 ignored");
return false;
}
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s", idx, gpio, onstate>0?"On":"Off");
if (idx >= MAX_LED) return false;
if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
leds[idx].timer = NULL;
leds[idx].gpio = gpio;
leds[idx].onstate = onstate;
leds[idx].pwm = -1;
if (pwm < 0) {
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
} else {
leds[idx].channel = pwm_system.base_channel++;
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
if (!onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
ledc_channel_config_t ledc_channel = {
.channel = leds[idx].channel,
.duty = leds[idx].pwm,
.gpio_num = gpio,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.hpoint = 0,
.timer_sel = pwm_system.timer,
};
ledc_channel_config(&ledc_channel);
}
set_level(leds + idx, false);
ESP_LOGD(TAG,"PWM Index %d, GPIO %d, on state %s, pwm %d%%", idx, gpio, onstate > 0 ? "On" : "Off", pwm);
return true;
}
@@ -170,7 +221,6 @@ void set_led_gpio(int gpio, char *value) {
}
void led_svc_init(void) {
#ifdef CONFIG_LED_GREEN_GPIO_LEVEL
green.active = CONFIG_LED_GREEN_GPIO_LEVEL;
#endif
@@ -181,8 +231,15 @@ void led_svc_init(void) {
#ifndef CONFIG_LED_LOCKED
parse_set_GPIO(set_led_gpio);
#endif
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d), red:%d (active:%d)", green.gpio, green.active, red.gpio, red.active);
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, green.gpio, green.active, green.pwm );
led_config(LED_GREEN, green.gpio, green.active);
led_config(LED_RED, red.gpio, red.active);
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p;
if (nvs_item) {
if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1);
free(nvs_item);
}
led_config(LED_GREEN, green.gpio, green.active, green.pwm);
led_config(LED_RED, red.gpio, red.active, red.pwm);
}

View File

@@ -20,9 +20,10 @@ enum { LED_GREEN = 0, LED_RED };
#define led_blink(idx, on, off) led_blink_core(idx, on, off, false)
#define led_blink_pushed(idx, on, off) led_blink_core(idx, on, off, true)
bool led_config(int idx, gpio_num_t gpio, int onstate);
bool led_unconfig(int idx);
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm);
bool led_brightness(int idx, int percent);
bool led_blink_core(int idx, int ontime, int offtime, bool push);
bool led_unpush(int idx);
int led_allocate(void);
#endif

View File

@@ -9,7 +9,8 @@
#include <stdio.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include <driver/i2c.h>
#include "driver/ledc.h"
#include "driver/i2c.h"
#include "config.h"
#include "battery.h"
#include "led.h"
@@ -25,6 +26,11 @@ int i2c_system_port = I2C_SYSTEM_PORT;
int i2c_system_speed = 400000;
int spi_system_host = SPI_SYSTEM_HOST;
int spi_system_dc_gpio = -1;
pwm_system_t pwm_system = {
.timer = LEDC_TIMER_0,
.base_channel = LEDC_CHANNEL_0,
.max = (1 << LEDC_TIMER_13_BIT),
};
static const char *TAG = "services";
@@ -92,6 +98,16 @@ void services_init(void) {
ESP_LOGW(TAG, "no SPI configured");
}
// system-wide PWM timer configuration
ledc_timer_config_t pwm_timer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_num = pwm_system.timer,
};
ledc_timer_config(&pwm_timer);
led_svc_init();
battery_svc_init();
monitor_svc_init();

View File

@@ -33,7 +33,7 @@
#include "adac.h"
#include "ac101.h"
const static char TAG[] = "AC101";
static const char TAG[] = "AC101";
#define SPKOUT_EN ((1 << 9) | (1 << 11) | (1 << 7) | (1 << 5))
#define EAROUT_EN ((1 << 11) | (1 << 12) | (1 << 13))
@@ -48,14 +48,14 @@ const static char TAG[] = "AC101";
return b;\
}
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
static void deinit(void);
static void speaker(bool active);
static void headset(bool active);
static void volume(unsigned left, unsigned right);
static bool volume(unsigned left, unsigned right);
static void power(adac_power_e mode);
struct adac_s dac_a1s = { init, deinit, power, speaker, headset, volume };
const struct adac_s dac_ac101 = { "AC101", init, deinit, power, speaker, headset, volume };
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val);
static uint16_t i2c_read_reg(uint8_t reg);
@@ -70,21 +70,24 @@ static int i2c_port;
/****************************************************************************************
* init
*/
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
esp_err_t res = ESP_OK;
i2c_port = i2c_port_num;
char *p;
// configure i2c
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 33,
.sda_io_num = -1,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = 32,
.scl_io_num = -1,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 250000,
};
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
i2c_port = i2c_port_num;
i2c_param_config(i2c_port, &i2c_config);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
@@ -96,8 +99,6 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
return 0;
}
ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
res = i2c_write_reg(CHIP_AUDIO_RS, 0x123);
// huh?
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -140,13 +141,6 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
i2c_write_reg(OMIXER_SR, BIN(0000,0101,0000,1010)); // source=DAC(R/L) and LINEIN(R/L)
#endif
// configure I2S pins & install driver
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = CONFIG_I2S_DI_IO
};
res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL);
res |= i2s_set_pin(i2s_num, &i2s_pin_config);
// enable earphone & speaker
i2c_write_reg(SPKOUT_CTRL, 0x0220);
i2c_write_reg(HPOUT_CTRL, 0xf801);
@@ -155,7 +149,7 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
ac101_set_spk_volume(100);
ac101_set_earph_volume(100);
ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
ESP_LOGI(TAG, "AC101 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
return (res == ESP_OK);
}
@@ -170,8 +164,9 @@ static void deinit(void) {
/****************************************************************************************
* change volume
*/
static void volume(unsigned left, unsigned right) {
static bool volume(unsigned left, unsigned right) {
// nothing at that point, volume is handled by backend
return false;
}
/****************************************************************************************

View File

@@ -15,14 +15,16 @@
typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
struct adac_s {
bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config);
char *model;
bool (*init)(char *config, int i2c_port_num, i2s_config_t *i2s_config);
void (*deinit)(void);
void (*power)(adac_power_e mode);
void (*speaker)(bool active);
void (*headset)(bool active);
void (*volume)(unsigned left, unsigned right);
bool (*volume)(unsigned left, unsigned right);
};
extern struct adac_s dac_tas57xx;
extern struct adac_s dac_a1s;
extern struct adac_s dac_external;
extern const struct adac_s dac_tas57xx;
extern const struct adac_s dac_tas5713;
extern const struct adac_s dac_ac101;
extern const struct adac_s dac_external;

View File

@@ -292,7 +292,13 @@ static int read_mp4_header(void) {
l->pos += bytes;
l->consume = consume - bytes;
break;
} else if (len > streambuf->size) {
// can't process an atom larger than streambuf!
LOG_ERROR("atom %s too large for buffer %u %u", type, len, streambuf->size);
return -1;
} else {
// make sure there is 'len' contiguous space
_buf_unwrap(streambuf, len);
break;
}
}

View File

@@ -100,6 +100,56 @@ void _buf_resize(struct buffer *buf, size_t size) {
buf->base_size = size;
}
void _buf_unwrap(struct buffer *buf, size_t cont) {
ssize_t len, by = cont - (buf->wrap - buf->readp);
size_t size;
u8_t *scratch;
// do nothing if we have enough space
if (by <= 0 || cont >= buf->size) return;
// buffer already unwrapped, just move it up
if (buf->writep >= buf->readp) {
memmove(buf->readp - by, buf->readp, buf->writep - buf->readp);
buf->readp -= by;
buf->writep -= by;
return;
}
// how much is overlapping
size = by - (buf->readp - buf->writep);
len = buf->writep - buf->buf;
// buffer is wrapped and enough free space to move data up directly
if (size <= 0) {
memmove(buf->readp - by, buf->readp, buf->wrap - buf->readp);
buf->readp -= by;
memcpy(buf->wrap - by, buf->buf, min(len, by));
if (len > by) {
memmove(buf->buf, buf->buf + by, len - by);
buf->writep -= by;
} else buf->writep += buf->size - by;
return;
}
scratch = malloc(size);
// buffer is wrapped but not enough free room => use scratch zone
if (scratch) {
memcpy(scratch, buf->writep - size, size);
memmove(buf->readp - by, buf->readp, buf->wrap - buf->readp);
buf->readp -= by;
memcpy(buf->wrap - by, buf->buf, by);
memmove(buf->buf, buf->buf + by, len - by - size);
buf->writep -= by;
memcpy(buf->writep - size, scratch, size);
free(scratch);
} else {
_buf_unwrap(buf, cont / 2);
_buf_unwrap(buf, cont - cont / 2);
}
}
void buf_init(struct buffer *buf, size_t size) {
buf->buf = malloc(size);
buf->readp = buf->buf;

View File

@@ -20,7 +20,7 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
# -I$(COMPONENT_PATH)/../codecs/inc/faad2
COMPONENT_SRCDIRS := . tas57xx a1s external
COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./a1s
COMPONENT_SRCDIRS := . tas57xx ac101 external
COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./ac101
COMPONENT_EMBED_FILES := vu.data

View File

@@ -49,6 +49,7 @@ struct IR_header {
static in_addr_t server_ip;
static u16_t server_hport;
static u16_t server_cport;
static int cli_sock = -1;
static u8_t mac[6];
static void (*chained_notify)(in_addr_t, u16_t, u16_t);
static bool raw_mode;
@@ -243,38 +244,44 @@ const actrls_t LMS_controls = {
*
*/
static void cli_send_cmd(char *cmd) {
char packet[64];
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int len, sock = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = server_ip;
addr.sin_port = htons(server_cport);
if (connect(sock, (struct sockaddr *) &addr, addrlen) < 0) {
LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport);
return;
}
char packet[96];
int len;
len = sprintf(packet, "%02x:%02x:%02x:%02x:%02x:%02x %s\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], cmd);
LOG_DEBUG("sending command %s at %s:%hu", packet, inet_ntoa(server_ip), server_cport);
if (send(sock, packet, len, 0) < 0) {
if (send(cli_sock, packet, len, MSG_DONTWAIT) < 0) {
LOG_WARN("cannot send CLI %s", packet);
}
closesocket(sock);
// need to empty the RX buffer otherwise we'll lock the TCP/IP stack
len = recv(cli_sock, packet, 96, MSG_DONTWAIT);
}
/****************************************************************************************
* Notification when server changes
*/
static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
server_ip = ip;
server_hport = hport;
server_cport = cport;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = server_ip;
addr.sin_port = htons(server_cport);
// close existing CLI connection and open new one
if (cli_sock >= 0) closesocket(cli_sock);
cli_sock = socket(AF_INET, SOCK_STREAM, 0);
if (connect(cli_sock, (struct sockaddr *) &addr, addrlen) < 0) {
LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport);
cli_sock = -1;
}
LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport);
if (chained_notify) (*chained_notify)(ip, hport, cport);

View File

@@ -28,7 +28,6 @@ extern struct buffer *outputbuf;
// this is the only system-wide loglevel variable
extern log_level loglevel;
static bool enable_bt_sink;
static bool enable_airplay;
@@ -145,7 +144,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
LOG_INFO("Setting BT sample rate %u", output.next_sample_rate);
break;
case BT_SINK_VOLUME: {
u16_t volume = (u16_t) va_arg(args, u32_t);
u32_t volume = va_arg(args, u32_t);
volume = 65536 * powf(volume / 128.0f, 3);
set_volume(volume, volume);
break;
@@ -282,7 +281,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
float volume = va_arg(args, double);
LOG_INFO("Volume[0..1] %0.4f", volume);
volume = 65536 * powf(volume, 3);
set_volume((u16_t) volume, (u16_t) volume);
set_volume(volume, volume);
break;
}
default:

View File

@@ -123,6 +123,8 @@ static struct {
bool owned;
} displayer = { .dirty = true, .owned = true };
static uint32_t *grayMap;
#define LONG_WAKE (10*1000)
#define SB_HEIGHT 32
@@ -296,6 +298,12 @@ bool sb_display_init(void) {
displayer.width = GDS_GetWidth(display);
displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
// allocate gray-color mapping if needed;
if (GDS_GetMode(display) > GDS_GRAYSCALE) {
grayMap = malloc(256*sizeof(*grayMap));
for (int i = 0; i < 256; i++) grayMap[i] = GDS_GrayMap(display, i);
}
// create visu configuration
visu.bar_gap = 1;
visu.speed = 100;
@@ -565,12 +573,14 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
// adjust to current display window
if (width > VU_WIDTH) {
if (rotate) y += (width - VU_WIDTH) / 2;
else x += (width - VU_WIDTH) / 2;
width = VU_WIDTH;
x += (width - VU_WIDTH) / 2;
} else {
data += (VU_WIDTH - width) / 2 * VU_HEIGHT;
}
if (GDS_GetMode(display) <= GDS_GRAYSCALE) {
// this is 8 bits grayscale
int scale = 8 - GDS_GetDepth(display);
@@ -588,6 +598,23 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
}
}
}
} else {
// use "fast" version as we are not beyond screen boundaries
if (visu.rotate) {
for (int r = 0; r < width; r++) {
for (int c = VU_HEIGHT; --c >= 0;) {
GDS_DrawPixelFast(display, c + x, r + y, grayMap[*data++]);
}
}
} else {
for (int r = 0; r < width; r++) {
for (int c = 0; c < VU_HEIGHT; c++) {
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
}
}
}
}
// need to manually set dirty flag as DrawPixel does not do it
GDS_SetDirty(display);
@@ -647,11 +674,12 @@ static void grfb_handler(u8_t *data, int len) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
if (pkt->brightness < 0) {
// LMS driver sends 0..5 value, we assume driver is highly log
if (pkt->brightness <= 0) {
GDS_DisplayOff(display);
} else {
GDS_DisplayOn(display);
GDS_SetContrast(display, pkt->brightness);
GDS_SetContrast(display, 255 * powf(pkt->brightness / 5.0f, 3));
}
xSemaphoreGive(displayer.mutex);
@@ -907,6 +935,7 @@ static void visu_update(void) {
if (mode != VISU_VUMETER || !visu.style) {
// there is much more optimization to be done here, like not redrawing bars unless needed
for (int i = visu.n; --i >= 0;) {
// update maximum
if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current;

View File

@@ -26,13 +26,15 @@ static struct {
* open equalizer
*/
void equalizer_open(u32_t sample_rate) {
// in any case, need to clear update flag
equalizer.update = false;
if (sample_rate != 11025 && sample_rate != 22050 && sample_rate != 44100 && sample_rate != 48000) {
LOG_WARN("equalizer only supports 11025, 22050, 44100 and 48000 sample rates, not %u", sample_rate);
return;
}
equalizer.handle = esp_equalizer_init(2, sample_rate, EQ_BANDS, 0);
equalizer.update = false;
if (equalizer.handle) {
bool active = false;

View File

@@ -12,46 +12,163 @@
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/i2s.h>
#include "driver/i2c.h"
#include "esp_log.h"
#include "cJSON.h"
#include "config.h"
#include "adac.h"
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
static void deinit(void) { };
static void speaker(bool active) { };
static void headset(bool active) { } ;
static void volume(unsigned left, unsigned right) { };
static void power(adac_power_e mode) { };
static const char TAG[] = "DAC external";
struct adac_s dac_external = { init, deinit, power, speaker, headset, volume };
static void deinit(void) { }
static void speaker(bool active) { }
static void headset(bool active) { }
static bool volume(unsigned left, unsigned right) { return false; }
static void power(adac_power_e mode);
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
static char TAG[] = "DAC external";
static bool i2c_json_execute(char *set);
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val);
static uint8_t i2c_read_reg(uint8_t reg);
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) {
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = CONFIG_I2S_DI_IO };
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "dac_config");
const struct adac_s dac_external = { "i2s", init, deinit, power, speaker, headset, volume };
static int i2c_port, i2c_addr;
static cJSON *i2c_json;
if (nvs_item) {
/****************************************************************************************
* init
*/
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
char *p;
if ((p = strcasestr(nvs_item, "bck")) != NULL) i2s_pin_config.bck_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "ws")) != NULL) i2s_pin_config.ws_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "do")) != NULL) i2s_pin_config.data_out_num = atoi(strchr(p, '=') + 1);
free(nvs_item);
i2c_port = i2c_port_num;
// configure i2c
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = -1,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = -1,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 250000,
};
if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);
i2c_json = cJSON_Parse(p);
if (!i2c_addr || !i2c_json || i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
if (p) free(p);
ESP_LOGW(TAG, "No i2c controlset found");
return true;
}
if (i2s_pin_config.bck_io_num != -1 && i2s_pin_config.ws_io_num != -1 && i2s_pin_config.data_out_num != -1) {
i2s_driver_install(i2s_num, config, 0, NULL);
i2s_set_pin(i2s_num, &i2s_pin_config);
ESP_LOGI(TAG, "DAC uses I2C @%d with sda:%d, scl:%d", i2c_addr, i2c_config.sda_io_num, i2c_config.scl_io_num);
ESP_LOGI(TAG, "External DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
// we have an I2C configured
i2c_param_config(i2c_port, &i2c_config);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
return true;
} else {
ESP_LOGI(TAG, "Cannot initialize I2S for DAC bck:%d ws:%d do:%d", i2s_pin_config.bck_io_num,
i2s_pin_config.ws_io_num,
i2s_pin_config.data_out_num);
if (!i2c_json_execute("init")) {
ESP_LOGE(TAG, "could not intialize DAC");
return false;
}
return true;
}
/****************************************************************************************
* power
*/
static void power(adac_power_e mode) {
if (mode == ADAC_STANDBY || mode == ADAC_OFF) i2c_json_execute("poweroff");
else i2c_json_execute("poweron");
}
/****************************************************************************************
*
*/
bool i2c_json_execute(char *set) {
cJSON *json_set = cJSON_GetObjectItemCaseSensitive(i2c_json, set);
cJSON *item;
if (!json_set) return true;
cJSON_ArrayForEach(item, json_set)
{
cJSON *reg = cJSON_GetObjectItemCaseSensitive(item, "reg");
cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
if (!reg || !val) continue;
if (!mode) {
i2c_write_reg(reg->valueint, val->valueint);
} else if (!strcasecmp(mode->valuestring, "or")) {
uint8_t data = i2c_read_reg(reg->valueint);
data |= (uint8_t) val->valueint;
i2c_write_reg(reg->valueint, data);
} else if (!strcasecmp(mode->valuestring, "and")) {
uint8_t data = i2c_read_reg(reg->valueint);
data &= (uint8_t) val->valueint;
i2c_write_reg(reg->valueint, data);
}
}
return true;
}
/****************************************************************************************
*
*/
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) {
esp_err_t ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "I2C write failed");
}
return ret;
}
/****************************************************************************************
*
*/
static uint8_t i2c_read_reg(uint8_t reg) {
esp_err_t ret;
uint8_t data = 0;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_READ, I2C_MASTER_NACK);
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "I2C read failed");
}
return data;
}

View File

@@ -252,7 +252,7 @@ static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t en
}
if ( f->container == 'o' ) {
LOG_DEBUG("ogg/flac container - using init_ogg_stream");
LOG_INFO("ogg/flac container - using init_ogg_stream");
FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
} else {
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);

View File

@@ -156,7 +156,7 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
info.sampRateCore = (*ptr++ & 0x07) << 1;
info.sampRateCore |= (*ptr >> 7) & 0x01;
info.sampRateCore = rates[info.sampRateCore];
info.nChans = *ptr >> 3;
info.nChans = (*ptr & 0x7f) >> 3;
*channels_p = info.nChans;
*samplerate_p = info.sampRateCore;
HAAC(a, SetRawBlockParams, a->hAac, 0, &info);
@@ -314,7 +314,13 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
a->pos += bytes;
a->consume = consume - bytes;
break;
} else if (len > streambuf->size) {
// can't process an atom larger than streambuf!
LOG_ERROR("atom %s too large for buffer %u %u", type, len, streambuf->size);
return -1;
} else {
// make sure there is 'len' contiguous space
_buf_unwrap(streambuf, len);
break;
}
}
@@ -358,7 +364,7 @@ static decode_state helixaac_decode(void) {
if (a->type == '2') {
// adts stream - seek for header
long n = AACFindSyncWord(streambuf->readp, bytes_wrap);
long n = HAAC(a, FindSyncWord, streambuf->readp, bytes_wrap);
LOG_DEBUG("Sync search in %d bytes %d", bytes_wrap, n);

View File

@@ -30,7 +30,9 @@
* thread has a higher priority. Using an interim buffer where opus decoder writes the output is not great from
* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long
*/
#if EMBEDDED
#define FRAME_BUF 2048
#endif
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
@@ -151,16 +153,14 @@ static decode_state opus_decompress(void) {
LOG_INFO("setting track_start");
}
#if !FRAME_BUF
LOCK_O_direct;
#endif
#if FRAME_BUF
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
frames = min(frames, FRAME_BUF);
write_buf = u->write_buf;
);
#else
LOCK_O_direct;
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
write_buf = outputbuf->writep;
@@ -171,10 +171,6 @@ static decode_state opus_decompress(void) {
write_buf = process.inbuf;
);
#if FRAME_BUF
frames = min(frames, FRAME_BUF);
#endif
// write the decoded frames into outputbuf then unpack them (they are 16 bits)
n = OP(u, read, u->of, (opus_int16*) write_buf, frames * channels, NULL);
@@ -190,15 +186,21 @@ static decode_state opus_decompress(void) {
frames = n;
count = frames * channels;
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
optr = (ISAMPLE_T *) write_buf + frames * 2;
if (channels == 2) {
#if BYTES_PER_FRAME == 4
#if FRAME_BUF
// copy needed only when DIRECT and FRAME_BUF
IF_DIRECT(
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
)
#endif
#else
while (count--) {
*--optr = *--iptr << 16;
*--optr = ALIGN(*--iptr);
}
#endif
} else if (channels == 1) {
@@ -298,8 +300,8 @@ struct codec *register_opus(void) {
static struct codec ret = {
'u', // id
"ops", // types
4096, // min read
20480, // min space
4*1024, // min read
32*1024, // min space
opus_open, // open
opus_close, // close
opus_decompress, // decode
@@ -311,7 +313,9 @@ struct codec *register_opus(void) {
}
u->of = NULL;
#if FRAME_BUF
u->write_buf = NULL;
#endif
if (!load_opus()) {
return NULL;

View File

@@ -77,15 +77,14 @@ extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern u8_t *silencebuf;
// by default no DAC selected
struct adac_s *adac = &dac_external;
const struct adac_s *dac_set[] = { &dac_tas57xx, &dac_tas5713, &dac_ac101, NULL };
const struct adac_s *adac = &dac_external;
static log_level loglevel;
static bool jack_mutes_amp;
static bool running, isI2SStarted;
static i2s_config_t i2s_config;
static int bytes_per_frame;
static u8_t *obuf;
static frames_t oframes;
static bool spdif;
@@ -93,7 +92,10 @@ static size_t dma_buf_frames;
static pthread_t thread;
static TaskHandle_t stats_task;
static bool stats;
static int amp_gpio = -1;
static struct {
int gpio, active;
} amp_control = { -1, 1 },
mute_control = { CONFIG_MUTE_GPIO, CONFIG_MUTE_GPIO_LEVEL };
DECLARE_ALL_MIN_MAX;
@@ -129,53 +131,53 @@ static void jack_handler(bool inserted) {
* amp GPIO
*/
static void set_amp_gpio(int gpio, char *value) {
char *p;
if (!strcasecmp(value, "amp")) {
amp_gpio = gpio;
amp_control.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
gpio_pad_select_gpio(amp_gpio);
gpio_set_direction(amp_gpio, GPIO_MODE_OUTPUT);
gpio_set_level(amp_gpio, 0);
gpio_pad_select_gpio(amp_control.gpio);
gpio_set_direction(amp_control.gpio, GPIO_MODE_OUTPUT);
gpio_set_level(amp_control.gpio, !amp_control.active);
LOG_INFO("setting amplifier GPIO %d", amp_gpio);
LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active);
}
}
/****************************************************************************************
* Set pin from config string
*/
static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
char *p;
pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1;
if ((p = strcasestr(config, "bck")) != NULL) pin_config->bck_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "ws")) != NULL) pin_config->ws_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "do")) != NULL) pin_config->data_out_num = atoi(strchr(p, '=') + 1);
}
/****************************************************************************************
* Initialize the DAC output
*/
void output_init_i2s(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
int silent_do = -1;
char *p;
esp_err_t res;
p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
free(p);
#ifdef CONFIG_I2S_BITS_PER_CHANNEL
switch (CONFIG_I2S_BITS_PER_CHANNEL) {
case 24:
output.format = S24_BE;
bytes_per_frame = 2*3;
break;
case 16:
output.format = S16_BE;
bytes_per_frame = 2*2;
break;
case 8:
output.format = S8_BE;
bytes_per_frame = 2*4;
break;
default:
LOG_ERROR("Unsupported bit depth %d",CONFIG_I2S_BITS_PER_CHANNEL);
break;
}
#if BYTES_PER_FRAME == 8
output.format = S32_LE;
#else
output.format = S16_LE;
bytes_per_frame = 2*2;
#endif
output.write_cb = &_i2s_write_frames;
obuf = malloc(FRAME_BLOCK * bytes_per_frame);
obuf = malloc(FRAME_BLOCK * BYTES_PER_FRAME);
if (!obuf) {
LOG_ERROR("Cannot allocate i2s buffer");
return;
@@ -183,6 +185,19 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
running = true;
// get SPDIF configuration from NVS or compile
char *spdif_config = config_alloc_get_str("spdif_config", CONFIG_SPDIF_CONFIG, "bck=" STR(CONFIG_SPDIF_BCK_IO)
",ws=" STR(CONFIG_SPDIF_WS_IO) ",do=" STR(CONFIG_SPDIF_DO_IO));
char *dac_config = config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
",mute" STR(CONFIG_MUTE_GPIO));
i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin;
set_i2s_pin(spdif_config, &i2s_spdif_pin);
set_i2s_pin(dac_config, &i2s_dac_pin);
// common I2S initialization
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
@@ -194,22 +209,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
if (strcasestr(device, "spdif")) {
spdif = true;
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO,
.data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 };
#ifndef CONFIG_SPDIF_LOCKED
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "spdif_config");
if (nvs_item) {
if ((p = strcasestr(nvs_item, "bck")) != NULL) i2s_pin_config.bck_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "ws")) != NULL) i2s_pin_config.ws_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "do")) != NULL) i2s_pin_config.data_out_num = atoi(strchr(p, '=') + 1);
free(nvs_item);
}
#endif
if (i2s_pin_config.bck_io_num == -1 || i2s_pin_config.ws_io_num == -1 || i2s_pin_config.data_out_num == -1) {
LOG_WARN("Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_pin_config.bck_io_num,
i2s_pin_config.ws_io_num,
i2s_pin_config.data_out_num);
if (i2s_spdif_pin.bck_io_num == -1 || i2s_spdif_pin.ws_io_num == -1 || i2s_spdif_pin.data_out_num == -1) {
LOG_WARN("Cannot initialize I2S for SPDIF bck:%d ws:%d do:%d", i2s_spdif_pin.bck_io_num,
i2s_spdif_pin.ws_io_num,
i2s_spdif_pin.data_out_num);
}
i2s_config.sample_rate = output.current_sample_rate * 2;
@@ -223,30 +227,62 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
audio frame. So the real depth is true frames is (LEN * COUNT / 2)
*/
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;
i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(CONFIG_I2S_NUM, &i2s_pin_config);
LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
} else {
#if CONFIG_SPDIF_DO_IO != -1
gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_SPDIF_DO_IO, 0);
#endif
// silence DAC output if sharing the same ws/bck
if (i2s_dac_pin.ws_io_num == i2s_spdif_pin.ws_io_num && i2s_dac_pin.bck_io_num == i2s_spdif_pin.bck_io_num) silent_do = i2s_dac_pin.data_out_num;
res = i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_spdif_pin);
LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_spdif_pin.bck_io_num, i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
} else {
i2s_config.sample_rate = output.current_sample_rate;
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
i2s_config.bits_per_sample = BYTES_PER_FRAME * 8 / 2;
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
i2s_config.dma_buf_len = DMA_BUF_LEN;
i2s_config.dma_buf_count = DMA_BUF_COUNT;
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN;
// finally let DAC driver initialize I2C and I2S
if (dac_tas57xx.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) adac = &dac_tas57xx;
else if (dac_a1s.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) adac = &dac_a1s;
else if (!dac_external.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) {
LOG_WARN("DAC not configured and SPDIF not enabled, I2S will not continue");
// silence SPDIF output
silent_do = i2s_spdif_pin.data_out_num;
char model[32] = "i2s";
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", model);
if ((p = strcasestr(dac_config, "mute")) != NULL) {
char mute[8];
sscanf(p, "%*[^=]=%7[^,]", mute);
mute_control.gpio = atoi(mute);
if ((p = strchr(mute, ':')) != NULL) mute_control.active = atoi(p + 1);
}
for (int i = 0; adac == &dac_external && dac_set[i]; i++) if (strcasestr(dac_set[i]->model, model)) adac = dac_set[i];
res = adac->init(dac_config, I2C_PORT, &i2s_config) ? ESP_OK : ESP_FAIL;
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);
if (res == ESP_OK && mute_control.gpio >= 0) {
gpio_pad_select_gpio(mute_control.gpio);
gpio_set_direction(mute_control.gpio, GPIO_MODE_OUTPUT);
gpio_set_level(mute_control.gpio, mute_control.active);
}
LOG_INFO("%s DAC using I2S bck:%d, ws:%d, do:%d, mute:%d:%d (res:%d)", model, i2s_dac_pin.bck_io_num, i2s_dac_pin.ws_io_num,
i2s_dac_pin.data_out_num, mute_control.gpio, mute_control.active, res);
}
free(dac_config);
free(spdif_config);
if (res != ESP_OK) {
LOG_WARN("no DAC configured");
return;
}
// turn off GPIO than is not used (SPDIF of DAC DO when shared)
if (silent_do >= 0) {
gpio_pad_select_gpio(silent_do);
gpio_set_direction(silent_do, GPIO_MODE_OUTPUT);
gpio_set_level(silent_do, 0);
}
LOG_INFO("Initializing I2S mode %s with rate: %d, bits per sample: %d, buffer frames: %d, number of buffers: %d ",
@@ -314,8 +350,8 @@ void output_close_i2s(void) {
* change volume
*/
bool output_volume_i2s(unsigned left, unsigned right) {
adac->volume(left, right);
return false;
if (mute_control.gpio >= 0) gpio_set_level(mute_control.gpio, (left | right) ? !mute_control.active : mute_control.active);
return adac->volume(left, right);
}
/****************************************************************************************
@@ -337,13 +373,13 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
_apply_gain(outputbuf, out_frames, gainL, gainR);
}
memcpy(obuf + oframes * bytes_per_frame, outputbuf->readp, out_frames * bytes_per_frame);
memcpy(obuf + oframes * BYTES_PER_FRAME, outputbuf->readp, out_frames * BYTES_PER_FRAME);
#else
optr = (s32_t*) outputbuf->readp;
#endif
} else {
#if BYTES_PER_FRAME == 4
memcpy(obuf + oframes * bytes_per_frame, silencebuf, out_frames * bytes_per_frame);
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
#else
optr = (s32_t*) silencebuf;
#endif
@@ -357,10 +393,10 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
dsd_invert((u32_t *) optr, out_frames);
)
_scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format);
_scale_and_pack_frames(obuf + oframes * BYTES_PER_FRAME, optr, out_frames, gainL, gainR, output.format);
#endif
output_visu_export((s16_t*) (obuf + oframes * bytes_per_frame), out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
output_visu_export((s16_t*) (obuf + oframes * BYTES_PER_FRAME), out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
oframes += out_frames;
@@ -396,8 +432,8 @@ static void *output_thread_i2s(void *arg) {
LOG_INFO("Output state is %d", output.state);
if (output.state == OUTPUT_OFF) {
led_blink(LED_GREEN, 100, 2500);
if (amp_gpio != -1) gpio_set_level(amp_gpio, 0);
LOG_INFO("switching off amp GPIO %d", amp_gpio);
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, !amp_control.active);
LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
} else if (output.state == OUTPUT_STOPPED) {
adac->speaker(false);
led_blink(LED_GREEN, 200, 1000);
@@ -460,7 +496,7 @@ static void *output_thread_i2s(void *arg) {
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
i2s_start(CONFIG_I2S_NUM);
adac->power(ADAC_ON);
if (amp_gpio != -1) gpio_set_level(amp_gpio, 1);
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, amp_control.active);
}
// this does not work well as set_sample_rates resets the fifos (and it's too early)
@@ -469,8 +505,8 @@ static void *output_thread_i2s(void *arg) {
/*
if (synced)
// can sleep for a buffer_queue - 1 and then eat a buffer (discard) if we are synced
usleep(((DMA_BUF_COUNT - 1) * DMA_BUF_LEN * bytes_per_frame * 1000) / 44100 * 1000);
discard = DMA_BUF_COUNT * DMA_BUF_LEN * bytes_per_frame;
usleep(((DMA_BUF_COUNT - 1) * DMA_BUF_LEN * BYTES_PER_FRAME * 1000) / 44100 * 1000);
discard = DMA_BUF_COUNT * DMA_BUF_LEN * BYTES_PER_FRAME;
}
*/
i2s_config.sample_rate = output.current_sample_rate;
@@ -483,20 +519,25 @@ static void *output_thread_i2s(void *arg) {
}
// run equalizer
equalizer_process(obuf, oframes * bytes_per_frame, output.current_sample_rate);
equalizer_process(obuf, oframes * BYTES_PER_FRAME, output.current_sample_rate);
// we assume that here we have been able to entirely fill the DMA buffers
if (spdif) {
spdif_convert((ISAMPLE_T*) obuf, oframes, (u32_t*) sbuf, &count);
i2s_write(CONFIG_I2S_NUM, sbuf, oframes * 16, &bytes, portMAX_DELAY);
bytes /= 4;
#if BYTES_PER_FRAME == 4
} else if (i2s_config.bits_per_sample == 32) {
i2s_write_expand(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, 16, 32, &bytes, portMAX_DELAY);
#endif
} else {
i2s_write(CONFIG_I2S_NUM, obuf, oframes * bytes_per_frame, &bytes, portMAX_DELAY);
i2s_write(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, &bytes, portMAX_DELAY);
}
fullness = gettime_ms();
if (bytes != oframes * bytes_per_frame) {
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", oframes * bytes_per_frame, bytes);
if (bytes != oframes * BYTES_PER_FRAME) {
LOG_WARN("I2S DMA Overflow! available bytes: %d, I2S wrote %d bytes", oframes * BYTES_PER_FRAME, bytes);
}
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
@@ -517,7 +558,7 @@ static void *output_thread_i2s_stats(void *arg) {
output_state state = output.state;
if(stats && state>OUTPUT_STOPPED){
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, bytes_per_frame);
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, BYTES_PER_FRAME);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD3);

View File

@@ -204,7 +204,7 @@ static decode_state pcm_decode(void) {
out = process.max_in_frames;
);
if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) {
if ((stream.state <= DISCONNECT && bytes < bytes_per_frame) || (limit && audio_left == 0)) {
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_COMPLETE;

View File

@@ -320,7 +320,7 @@ static void process_strm(u8_t *pkt, int len) {
output.pause_frames = interval * status.current_sample_rate / 1000;
if (interval) {
output.state = OUTPUT_PAUSE_FRAMES;
} else {
} else if (output.state != OUTPUT_OFF) {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}

View File

@@ -387,9 +387,6 @@ typedef BOOL bool;
#endif
typedef u32_t frames_t;
typedef int sockfd;
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
@@ -402,6 +399,9 @@ void logprint(const char *fmt, ...);
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
typedef uint32_t frames_t;
typedef int sockfd;
#if EMBEDDED
#include "embedded.h"
#endif
@@ -544,6 +544,7 @@ void _buf_inc_readp(struct buffer *buf, unsigned by);
void _buf_inc_writep(struct buffer *buf, unsigned by);
void buf_flush(struct buffer *buf);
void _buf_flush(struct buffer *buf);
void _buf_unwrap(struct buffer *buf, size_t cont);
void buf_adjust(struct buffer *buf, size_t mod);
void _buf_resize(struct buffer *buf, size_t size);
void buf_init(struct buffer *buf, size_t size);

View File

@@ -0,0 +1,195 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, philippe_44@outlook.com
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
* (c) C. Rohs 2020 added support for the tas5713 (eg. HiFiBerry AMP+)
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "adac.h"
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array))
#define TAS5713 0x36 /* i2c address of TAS5713 */
// TAS5713 I2C-bus register addresses
#define TAS5713_CLOCK_CTRL 0x00
#define TAS5713_DEVICE_ID 0x01
#define TAS5713_ERROR_STATUS 0x02
#define TAS5713_SYSTEM_CTRL1 0x03
#define TAS5713_SERIAL_DATA_INTERFACE 0x04
#define TAS5713_SYSTEM_CTRL2 0x05
#define TAS5713_SOFT_MUTE 0x06
#define TAS5713_VOL_MASTER 0x07
#define TAS5713_VOL_CH1 0x08
#define TAS5713_VOL_CH2 0x09
#define TAS5713_VOL_HEADPHONE 0x0A
#define TAS5713_OSC_TRIM 0x1B
static const char TAG[] = "TAS5713";
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
static void deinit(void);
static void speaker(bool active) { };
static void headset(bool active) { } ;
static bool volume(unsigned left, unsigned right);
static void power(adac_power_e mode) { };
const struct adac_s dac_tas5713 = {"TAS5713", init, deinit, power, speaker, headset, volume};
struct tas5713_cmd_s {
uint8_t reg;
uint8_t value;
};
// matching orders
typedef enum {
TAS57_ACTIVE = 0,
TAS57_STANDBY,
TAS57_DOWN,
TAS57_ANALOGUE_OFF,
TAS57_ANALOGUE_ON,
TAS57_VOLUME
} dac_cmd_e;
static int i2c_port;
static void tas5713_set(uint8_t reg, uint8_t val);
static uint8_t tas5713_get(uint8_t reg);
/****************************************************************************************
* init
*/
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
char *p;
i2c_port = i2c_port_num;
// configure i2c
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = -1,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = -1,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 250000,
};
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
i2c_param_config(i2c_port, &i2c_config);
esp_err_t res = i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
/* find if there is a tas5713 attached. Reg 0 should read non-zero if so */
if (!tas5713_get(0x00)) {
ESP_LOGW(TAG, "No TAS5713 detected");
i2c_driver_delete(i2c_port);
return 0;
}
ESP_LOGI(TAG, "TAS5713 uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
/* do the init sequence */
tas5713_set(TAS5713_OSC_TRIM, 0x00); /* a delay is required after this */
vTaskDelay(50 / portTICK_PERIOD_MS);
tas5713_set(TAS5713_SERIAL_DATA_INTERFACE, 0x03); /* I2S LJ 16 bit */
tas5713_set(TAS5713_SYSTEM_CTRL2, 0x00); /* exit all channel shutdown */
tas5713_set(TAS5713_SOFT_MUTE, 0x00); /* unmute */
tas5713_set(TAS5713_VOL_MASTER, 0x20);
tas5713_set(TAS5713_VOL_CH1, 0x30);
tas5713_set(TAS5713_VOL_CH2, 0x30);
tas5713_set(TAS5713_VOL_HEADPHONE, 0xFF);
/* The tas5713 typically has the mclk connected to the sclk. In this
configuration, mclk must be a multiple of the sclk. The lowest workable
multiple is 64x. To achieve this, 32 bits per channel on must be sent
over I2S. Reconfigure the I2S for that here, and expand the I2S stream
when it is sent */
i2s_config->bits_per_sample = 32;
if (res != ESP_OK) {
ESP_LOGE(TAG, "could not intialize TAS5713 %d", res);
return false;
}
return true;
}
/****************************************************************************************
* init
*/
static void deinit(void) {
i2c_driver_delete(i2c_port);
}
/****************************************************************************************
* change volume
*/
static bool volume(unsigned left, unsigned right) {
return false;
}
/****************************************************************************************
* DAC specific commands
*/
void tas5713_set(uint8_t reg, uint8_t val) {
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG,"TAS5713 send %x %x", reg, val);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd,
TAS5713 | I2C_MASTER_WRITE,
I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, val, I2C_MASTER_NACK);
i2c_master_stop(i2c_cmd);
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
i2c_cmd_link_delete(i2c_cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Could not send command to TAS5713 %d", ret);
}
}
/*************************************************************************
* Read from i2c for the tas5713. This doubles as tas5713 detect. This function
* returns zero on error, so read register 0x00 for tas detect, which will be
* non-zero in this application.
*/
static uint8_t tas5713_get(uint8_t reg) {
int ret;
uint8_t data = 0;
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, reg, I2C_MASTER_NACK);
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, TAS5713 | I2C_MASTER_READ, I2C_MASTER_NACK);
i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(i2c_cmd);
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
i2c_cmd_link_delete(i2c_cmd);
if (ret == ESP_OK) {
ESP_LOGI(TAG,"TAS5713 reg 0x%x is 0x%x", reg, data);
}
return data;
}

View File

@@ -9,28 +9,28 @@
*
*/
#include "squeezelite.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "adac.h"
// this is the only hard-wired thing
#define VOLUME_GPIO 14
#define TAS575x 0x98
#define TAS578x 0x90
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
static const char TAG[] = "TAS575x/8x";
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
static void deinit(void);
static void speaker(bool active);
static void headset(bool active);
static void volume(unsigned left, unsigned right);
static bool volume(unsigned left, unsigned right);
static void power(adac_power_e mode);
struct adac_s dac_tas57xx = { init, deinit, power, speaker, headset, volume };
const struct adac_s dac_tas57xx = { "TAS57xx", init, deinit, power, speaker, headset, volume };
struct tas57xx_cmd_s {
uint8_t reg;
@@ -59,8 +59,7 @@ static const struct tas57xx_cmd_s tas57xx_cmd[] = {
{ 0x56, 0x00 }, // TAS57_ANALOGUE_ON
};
static log_level loglevel = lINFO;
static u8_t tas57_addr;
static uint8_t tas57_addr;
static int i2c_port;
static void dac_cmd(dac_cmd_e cmd, ...);
@@ -69,19 +68,23 @@ static int tas57_detect(void);
/****************************************************************************************
* init
*/
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
char *p;
i2c_port = i2c_port_num;
// configure i2c
i2c_config_t i2c_config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = 27,
.sda_io_num = -1,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = 26,
.scl_io_num = -1,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
.master.clk_speed = 250000,
};
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
i2c_param_config(i2c_port, &i2c_config);
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
@@ -89,13 +92,11 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
tas57_addr = tas57_detect();
if (!tas57_addr) {
LOG_WARN("No TAS57xx detected");
ESP_LOGW(TAG, "No TAS57xx detected");
i2c_driver_delete(i2c_port);
return 0;
return false;
}
LOG_INFO("TAS57xx DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) {
@@ -103,32 +104,21 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
ESP_LOGD(TAG, "i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
}
i2c_master_stop(i2c_cmd);
esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
i2c_cmd_link_delete(i2c_cmd);
// configure I2S pins & install driver
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = CONFIG_I2S_DI_IO,
};
res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL);
res |= i2s_set_pin(i2s_num, &i2s_pin_config);
LOG_INFO("DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
ESP_LOGI(TAG, "TAS57xx uses I2C sda:%d, scl:%d", i2c_config.sda_io_num, i2c_config.scl_io_num);
if (res == ESP_OK) {
// init volume & mute
gpio_pad_select_gpio(VOLUME_GPIO);
gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(VOLUME_GPIO, 0);
return true;
} else {
LOG_ERROR("could not intialize TAS57xx %d", res);
if (res != ESP_OK) {
ESP_LOGE(TAG, "could not intialize TAS57xx %d", res);
return false;
}
return true;
}
/****************************************************************************************
@@ -141,9 +131,8 @@ static void deinit(void) {
/****************************************************************************************
* change volume
*/
static void volume(unsigned left, unsigned right) {
LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right);
gpio_set_level(VOLUME_GPIO, left || right);
static bool volume(unsigned left, unsigned right) {
return false;
}
/****************************************************************************************
@@ -161,7 +150,7 @@ static void power(adac_power_e mode) {
dac_cmd(TAS57_DOWN);
break;
default:
LOG_WARN("unknown DAC command");
ESP_LOGW(TAG, "unknown DAC command");
break;
}
}
@@ -177,8 +166,7 @@ static void speaker(bool active) {
/****************************************************************************************
* headset
*/
static void headset(bool active) {
}
static void headset(bool active) { }
/****************************************************************************************
* DAC specific commands
@@ -192,7 +180,7 @@ void dac_cmd(dac_cmd_e cmd, ...) {
switch(cmd) {
case TAS57_VOLUME:
LOG_ERROR("DAC volume not handled yet");
ESP_LOGE(TAG, "DAC volume not handled yet");
break;
default:
i2c_master_start(i2c_cmd);
@@ -206,7 +194,7 @@ void dac_cmd(dac_cmd_e cmd, ...) {
i2c_cmd_link_delete(i2c_cmd);
if (ret != ESP_OK) {
LOG_ERROR("could not intialize TAS57xx %d", ret);
ESP_LOGE(TAG, "could not intialize TAS57xx %d", ret);
}
va_end(args);
@@ -216,7 +204,7 @@ void dac_cmd(dac_cmd_e cmd, ...) {
* TAS57 detection
*/
static int tas57_detect(void) {
u8_t data, addr[] = {TAS578x, TAS575x};
uint8_t data, addr[] = {TAS578x, TAS575x};
int ret;
for (int i = 0; i < sizeof(addr); i++) {
@@ -235,7 +223,7 @@ static int tas57_detect(void) {
i2c_cmd_link_delete(i2c_cmd);
if (ret == ESP_OK) {
LOG_INFO("Detected TAS @0x%x", addr[i]);
ESP_LOGI(TAG, "Detected TAS @0x%x", addr[i]);
return addr[i];
}
}

View File

@@ -29,7 +29,9 @@
* thread has a higher priority. Using an interim buffer where vorbis decoder writes the output is not great from
* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long
*/
#if EMBEDDED
#define FRAME_BUF 2048
#endif
#if BYTES_PER_FRAME == 4
#define ALIGN(n) (n)
@@ -183,16 +185,14 @@ static decode_state vorbis_decode(void) {
}
}
#if !FRAME_BUF
LOCK_O_direct;
#endif
#if FRAME_BUF
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
frames = min(frames, FRAME_BUF);
write_buf = v->write_buf;
);
#else
LOCK_O_direct;
IF_DIRECT(
frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
write_buf = outputbuf->writep;
@@ -203,9 +203,6 @@ static decode_state vorbis_decode(void) {
write_buf = process.inbuf;
);
#if FRAME_BUF
frames = min(frames, FRAME_BUF);
#endif
bytes = frames * 2 * channels; // samples returned are 16 bits
// write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them
@@ -237,15 +234,21 @@ static decode_state vorbis_decode(void) {
frames = n / 2 / channels;
count = frames * channels;
// work backward to unpack samples (if needed)
iptr = (s16_t *) write_buf + count;
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
optr = (ISAMPLE_T *) write_buf + frames * 2;
if (channels == 2) {
#if BYTES_PER_FRAME == 4
#if FRAME_BUF
// copy needed only when DIRECT and FRAME_BUF
IF_DIRECT(
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
)
#endif
#else
while (count--) {
*--optr = *--iptr << 16;
*--optr = ALIGN(*--iptr);
}
#endif
} else if (channels == 1) {

View File

@@ -15,7 +15,7 @@ var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases
var recovery = false;
var enableAPTimer = true;
var enableStatusTimer = true;
var commandHeader = 'squeezelite -b 500:2000 -d all=info ';
var commandHeader = 'squeezelite -b 500:2000 -d all=info -C 30 -W';
var pname, ver, otapct, otadsc;
var blockAjax = false;
var blockFlashButton = false;

View File

@@ -235,7 +235,7 @@
<h1>Squeezelite command to run</h1>
<section id="command-list">
<textarea id="autoexec1" maxlength="120">squeezelite -o I2S -b 500:2000 -d all=info -M esp32</textarea>
<textarea id="autoexec1" maxlength="120">squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W</textarea>
</section>
<div class="buttons">
<input id="save-autoexec1" type="button" class="btn btn-success" value="Save" />

View File

@@ -21,6 +21,7 @@ menu "Squeezelite-ESP32"
help
Set logging level info|debug|sdebug
endmenu
config JACK_LOCKED
bool
config BAT_LOCKED
@@ -33,9 +34,38 @@ menu "Squeezelite-ESP32"
bool
config SPKFAULT_LOCKED
bool
menu "Audio Output"
config MUTE_GPIO_LEVEL
int
default 0
# AGGREGATES - begin
# these parameters are "aggregates" that take precedence. The must have a default value
config DAC_CONFIG
string
default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
default "model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32" if A1S
default "model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22" if TWATCH2020
default ""
config SPDIF_CONFIG
string
default "bck=33,ws=25,do=15" if SQUEEZEAMP
default ""
config SPI_CONFIG
string
default "dc=27,data=19,clk=18" if TWATCH2020
default ""
config DISPLAY_CONFIG
string
default "SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip" if TWATCH2020
default ""
config DAC_CONTROLSET
string
default "{ \"init\": [ {\"reg\":41, \"val\":128}, {\"reg\":18, \"val\":255} ], \"poweron\": [ {\"reg\":18, \"val\":64, \"mode\":\"or\"} ], \"poweroff\": [ {\"reg\":18, \"val\":191, \"mode\":\"and\"} ] }" if TWATCH2020
default ""
# AGGREGATES - end
choice OUTPUT_TYPE
prompt "Output Type"
prompt "Main system"
default BASIC_I2C_BT
help
Type of hardware platform
@@ -44,18 +74,22 @@ menu "Squeezelite-ESP32"
select JACK_LOCKED
select BAT_LOCKED
select I2C_LOCKED
select SPDIF_LOCKED
select LED_LOCKED
select SPKFAULT_LOCKED
config A1S
bool "ESP32-A1S module"
select I2C_LOCKED
config TWATCH2020
bool "T-WATCH2020 by LilyGo"
select I2C_LOCKED
config BASIC_I2C_BT
bool "Generic I2S & Bluetooth"
endchoice
menu "DAC I2S settings"
menu "Audio settings"
menu "DAC settings"
visible if BASIC_I2C_BT
menu "I2S settings"
config I2S_NUM
int "I2S channel (0 or 1). "
default 0
@@ -63,29 +97,46 @@ menu "Squeezelite-ESP32"
I2S dma channel to use.
config I2S_BCK_IO
int "I2S Bit clock GPIO number. "
default 33 if !A1S
default 27 if A1S
default 33
help
I2S Bit Clock gpio pin to use.
config I2S_WS_IO
int "I2S Word Select GPIO number. "
default 25 if !A1S
default 26 if A1S
default 25
help
I2S Word Select gpio pin to use.
config I2S_DO_IO
int "I2S Data Output GPIO number. "
default 32 if !A1S
default 25 if A1S
default 32
help
I2S data output gpio pin to use.
config I2S_DI_IO
int "I2S Data Input GPIO number. "
default -1 if !A1S
default 35 if A1S
help
I2S data input gpio pin to use (not used mostly, leave it to -1).
endmenu
menu "I2C settings"
config I2C_SDA
int "I2C SDA GPIO number for DAC control. "
default -1
help
I2C data gpio pin to use with DAC (not used mostly, leave it to -1).
config I2C_SCL
int "I2C SCL GPIO number for DAC control. "
default -1
help
I2C clock gpio pin to use with DAC (not used mostly, leave it to -1).
endmenu
config MUTE_GPIO
int "GPIO for muting DAC"
default -1
help
GPIO used to mute DAC (not used mostly, leave it to -1).
config MUTE_GPIO_LEVEL
int "Mute GPIO active level"
depends on MUTE_GPIO != -1
default 1
endmenu
menu "SPDIF settings"
@@ -107,9 +158,7 @@ menu "Squeezelite-ESP32"
Must be set as SPDIF re-uses I2S but only needs DO (recommendation: set it to I2S Word select value)
config SPDIF_DO_IO
int "SPDIF Data I/O GPIO number"
default 15 if SQUEEZEAMP
default I2S_DO_IO if !A1S
default -1 if A1S
default -1
help
I2S data output IO use to simulate SPDIF
endmenu
@@ -181,16 +230,17 @@ menu "Squeezelite-ESP32"
endmenu
menu "Display Screen"
depends on !TWATCH2020
config DISPLAY_CONFIG
string "Screen configuraton"
default ""
help
Set parameters for display screen, leave empty for no screen
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,HFlip][,VFlip]
I2C,driver=<model>,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][,rotate]
SPI,driver=<model>,width=<pixels>,height=<pixels>,cs=<gpio>[,HFlip][,VFlip][,rotate]
endmenu
menu "Various I/O"
visible if !TWATCH2020
config I2C_CONFIG
string "I2C system configuration"
default ""
@@ -199,7 +249,6 @@ menu "Squeezelite-ESP32"
sda=<gpio>,scl=<gpio>[,speed=<num>][,port=<0|1>]
config SPI_CONFIG
string "SPI system configuration"
default ""
help
Set parameters of shared SPI interface
data=<gpio>,clk=<gpio>[,d/c=<num>][,host=<0|1|2>]
@@ -208,7 +257,7 @@ menu "Squeezelite-ESP32"
default ""
help
Set parameters of shared GPIO with special values.
<gpio_1>=Vcc|GND|amp|jack[:0|1][,<gpio_n>=Vcc|GND|amp|jack[:0|1]]
<gpio_1>=Vcc|GND|amp[:0|1]|jack[:0|1][,<gpio_n>=Vcc|GND|amp[:0|1]|jack[:0|1]]
'amp' => GPIO that is set when playback starts
'jack' => GPIO used for audio jack detection
'green', 'red' => GPIO for status LED
@@ -218,39 +267,39 @@ menu "Squeezelite-ESP32"
default ""
help
Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
A=<gpio>,B=<gpio>[,SW=gpio>[,volume][,longpress]]
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]
endmenu
menu "LED configuration"
visible if !SQUEEZEAMP
visible if !SQUEEZEAMP && !TWATCH2020
config LED_GREEN_GPIO
int "Green led GPIO"
default -1 if !SQUEEZEAMP
default 12 if SQUEEZEAMP
default -1
help
Set to -1 for no LED
config LED_GREEN_GPIO_LEVEL
int "Green led ON level"
depends on LED_GREEN_GPIO != -1
default 0 if SQUEEZEAMP
default 1 if !SQUEEZEAMP
default 1
config LED_RED_GPIO
int "Red led GPIO"
default -1 if !SQUEEZEAMP
default 13 if SQUEEZEAMP
default -1
help
Set to -1 for no LED
config LED_RED_GPIO_LEVEL
int "Red led ON level"
depends on LED_RED_GPIO != -1
default 0 if SQUEEZEAMP
default 1 if !SQUEEZEAMP
default 1
endmenu
menu "Audio JACK"
visible if !SQUEEZEAMP
visible if !SQUEEZEAMP && !TWATCH2020
config JACK_GPIO
int "Jack insertion GPIO"
default -1 if !SQUEEZEAMP
default 34 if SQUEEZEAMP
default -1
help
GPIO to detect speaker jack insertion. Set to -1 for no detection.
config JACK_GPIO_LEVEL
@@ -259,11 +308,11 @@ menu "Squeezelite-ESP32"
default 0
endmenu
menu "Speaker Fault"
visible if !SQUEEZEAMP
visible if !SQUEEZEAMP && !TWATCH2020
config SPKFAULT_GPIO
int "Speaker fault GPIO"
default -1 if !SQUEEZEAMP
default 2 if SQUEEZEAMP
default -1
help
GPIO to detect speaker fault condition. Set to -1 for no detection.
config SPKFAULT_GPIO_LEVEL
@@ -272,18 +321,18 @@ menu "Squeezelite-ESP32"
default 0
endmenu
menu "Battery measure"
visible if !SQUEEZEAMP
visible if !SQUEEZEAMP && !TWATCH2020
config BAT_CHANNEL
int "Set channel (0..7)"
default -1 if !SQUEEZEAMP
default 7 if SQUEEZEAMP
default -1
help
Read a value every 10s on ADC1 on set Channel
config BAT_SCALE
string "Set scaling factor"
depends on BAT_CHANNEL != -1
default "" if !SQUEEZEAMP
default "20.24" if SQUEEZEAMP
default ""
help
Set the scaling factor for this 12 bits ADC
endmenu

View File

@@ -69,10 +69,11 @@ static void * squeezelite_thread(){
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
isRunning=false;
ESP_LOGE(TAG, "Exited from squeezelite thread, something's wrong ... rebooting");
ESP_LOGE(TAG, "Exited from squeezelite thread, something's wrong ... rebooting (wait 30s for user to take action)");
if(!wait_for_commit()){
ESP_LOGW(TAG,"Unable to commit configuration. ");
}
vTaskDelay( pdMS_TO_TICKS( 30*1000 ) );
esp_restart();
return NULL;
}

View File

@@ -321,12 +321,18 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "set_GPIO", CONFIG_SET_GPIO);
config_set_default(NVS_TYPE_STR, "set_GPIO", CONFIG_SET_GPIO, 0);
ESP_LOGD(TAG,"Registering default value for key %s", "led_brightness");
config_set_default(NVS_TYPE_STR, "led_brightness", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "spdif_config");
config_set_default(NVS_TYPE_STR, "spdif_config", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "dac_config");
config_set_default(NVS_TYPE_STR, "dac_config", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);

Binary file not shown.

View File

@@ -88,7 +88,7 @@ sub displayWidth {
}
sub brightnessMap {
return (65535, 10, 50, 100, 200);
return (0 .. 5);
}
=comment

View File

@@ -10,6 +10,6 @@
<name>PLUGIN_SQUEEZEESP32</name>
<description>PLUGIN_SQUEEZEESP32_DESC</description>
<module>Plugins::SqueezeESP32::Plugin</module>
<version>0.93</version>
<version>0.94</version>
<creator>Philippe</creator>
</extensions>

View File

@@ -1,10 +1,10 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<plugins>
<plugin version="0.93" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<plugin version="0.94" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator>
<sha>42e9a5713355c5c7b8b318f4254a183e9bb86b8f</sha>
<sha>a9bf10b47d13508ba051e4067cdabc2c283f4824</sha>
<email>philippe_44@outlook.com</email>
<desc lang="EN">SqueezeESP32 additional player id (100)</desc>
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>