mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 19:47:02 +03:00
Compare commits
58 Commits
refactorin
...
v0.5.642-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcb087f9ee | ||
|
|
c5d871f5ee | ||
|
|
a87066fef6 | ||
|
|
683ec77424 | ||
|
|
e5050c9f72 | ||
|
|
daf98c7d50 | ||
|
|
91b0efd741 | ||
|
|
8820a85112 | ||
|
|
4a56b55b49 | ||
|
|
5c79b0c0e8 | ||
|
|
ffd0320e54 | ||
|
|
80bf63ed2a | ||
|
|
f9b7d10243 | ||
|
|
ad2846f50d | ||
|
|
340a1bd19e | ||
|
|
1dcd0aeacb | ||
|
|
0bcc2bcecc | ||
|
|
9d7c0eb48f | ||
|
|
a4200b4b85 | ||
|
|
80188e944c | ||
|
|
e6c5c04fd6 | ||
|
|
78563b20f3 | ||
|
|
9fd792cf98 | ||
|
|
c7697c31cd | ||
|
|
77e8c29936 | ||
|
|
bf4358f340 | ||
|
|
3de62b3ad2 | ||
|
|
4f90002c99 | ||
|
|
f4af5cfda0 | ||
|
|
f4f4570c2a | ||
|
|
be6bbe1bcc | ||
|
|
dfe468b610 | ||
|
|
da1aa2e74e | ||
|
|
c12e0b39f6 | ||
|
|
b929436f4f | ||
|
|
e39580ad5d | ||
|
|
254459fbf5 | ||
|
|
32a847bf02 | ||
|
|
22b86b3323 | ||
|
|
97856e2f0f | ||
|
|
fafb764120 | ||
|
|
4451aff1c8 | ||
|
|
af4472dbe9 | ||
|
|
cf81182dae | ||
|
|
ba75350455 | ||
|
|
0db1ac38e4 | ||
|
|
fbbe73b1d6 | ||
|
|
b4c17b02a0 | ||
|
|
8911d44327 | ||
|
|
3e39300759 | ||
|
|
dd9018ca28 | ||
|
|
ca5754be5d | ||
|
|
dc9e235157 | ||
|
|
c2c31a191e | ||
|
|
9f3bc774d4 | ||
|
|
e87bc7dd83 | ||
|
|
14cc21eb66 | ||
|
|
4517e9040a |
78
README.md
78
README.md
@@ -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.
|
||||
|
||||
@@ -53,12 +70,22 @@ sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
|
||||
The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
|
||||
```
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -96,22 +96,15 @@ 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));
|
||||
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 );
|
||||
}
|
||||
|
||||
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));
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
Device->WriteData( Device, &Private->ReMap, 1 );
|
||||
WriteDataByte( Device, 0x11 );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 _DrawPixel1Fast( 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) _DrawPixel1Fast( 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) _DrawPixel1Fast( Device, c++, r, Color );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,22 +222,19 @@ 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)));
|
||||
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));
|
||||
}
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
Device->WriteCommand( Device, Private->ReMap );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -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,14 +312,15 @@ 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) {
|
||||
uint8_t Model;
|
||||
int Depth;
|
||||
|
||||
|
||||
if (strcasestr(Driver, "SSD1326")) Model = SSD1326;
|
||||
else if (strcasestr(Driver, "SSD1327")) Model = SSD1327;
|
||||
else return NULL;
|
||||
@@ -332,22 +328,22 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
|
||||
*Device = SSD132x;
|
||||
((struct PrivateSpace*) Device->Private)->Model = Model;
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
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 = _DrawPixel1Fast;
|
||||
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;
|
||||
}
|
||||
286
components/display/SSD1351.c
Normal file
286
components/display/SSD1351.c
Normal 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;
|
||||
}
|
||||
@@ -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 _DrawPixel( 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 );
|
||||
_DrawPixel( 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 = _DrawPixel,
|
||||
.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;
|
||||
|
||||
298
components/display/ST77xx.c
Normal file
298
components/display/ST77xx.c
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* 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;
|
||||
sscanf(Driver, "%*[^:]:%u", &Depth);
|
||||
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
|
||||
Private->Model = Model;
|
||||
|
||||
if (Depth == 18) {
|
||||
Device->Mode = GDS_RGB666;
|
||||
Device->Depth = 24;
|
||||
Device->Update = Update24;
|
||||
}
|
||||
|
||||
if (Model == ST7789) Device->SetContrast = SetContrast;
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -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 ); }
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,37 +245,107 @@ 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 );
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,11 @@ 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;
|
||||
struct GDS_Device *Device;
|
||||
int XOfs, YOfs;
|
||||
int XMin, YMin;
|
||||
int Depth;
|
||||
@@ -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,150 +210,165 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
// free scratch area
|
||||
if (Scratch) free(Scratch);
|
||||
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 );
|
||||
} 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;
|
||||
}
|
||||
|
||||
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 {
|
||||
DRAW_RGB24;
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// set the right scaler when displaying grayscale
|
||||
if (RGB_Mode <= GDS_GRAYSCALE) {
|
||||
int Scale = 8 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToSelf);
|
||||
} else if (RGB_Mode == GDS_RGB332) {
|
||||
int Scale = 3 - Device->Depth;
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* 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) )
|
||||
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
*/
|
||||
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
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
//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) {
|
||||
bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
|
||||
JDEC Decoder;
|
||||
JpegCtx Context;
|
||||
bool Ret = false;
|
||||
@@ -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);
|
||||
|
||||
@@ -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 );
|
||||
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);
|
||||
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
|
||||
@@ -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,11 +85,10 @@ struct GDS_Device {
|
||||
// SPI specific
|
||||
struct {
|
||||
spi_device_handle_t SPIHandle;
|
||||
int8_t RSTPin;
|
||||
int8_t CSPin;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// cooked text mode
|
||||
struct {
|
||||
int16_t Y, Space;
|
||||
@@ -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 ) {
|
||||
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = 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) GDS_DrawPixel4Fast( Device, X, Y, Color);
|
||||
else if (Device->Depth == 1) GDS_DrawPixel1Fast( 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 );
|
||||
}
|
||||
|
||||
inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int 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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -44,12 +44,13 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
|
||||
}
|
||||
|
||||
|
||||
memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) );
|
||||
|
||||
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;
|
||||
|
||||
@@ -48,31 +48,32 @@ 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");
|
||||
|
||||
if (!config) {
|
||||
ESP_LOGI(TAG, "no display");
|
||||
return false;
|
||||
}
|
||||
char *config = config_alloc_get_str("display_config", CONFIG_DISPLAY_CONFIG, "N/A");
|
||||
|
||||
int width = -1, height = -1;
|
||||
int width = -1, height = -1, backlight_pin = -1;
|
||||
char *p, *drivername = strstr(config, "driver");
|
||||
|
||||
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);
|
||||
display = GDS_AutoDetect(drivername ? drivername : "SSD1306", drivers);
|
||||
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);
|
||||
}
|
||||
|
||||
if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
|
||||
|
||||
// so far so good
|
||||
if (display && width > 0 && height > 0) {
|
||||
int RST_pin = -1;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -89,7 +89,13 @@ extern struct mdnsd* glmDNSServer;
|
||||
extern log_level raop_loglevel;
|
||||
static log_level *loglevel = &raop_loglevel;
|
||||
|
||||
#ifdef WIN32
|
||||
static void* rtsp_thread(void *arg);
|
||||
static void* search_remote(void *args);
|
||||
#else
|
||||
static void rtsp_thread(void *arg);
|
||||
static void search_remote(void *args);
|
||||
#endif
|
||||
static void cleanup_rtsp(raop_ctx_t *ctx, bool abort);
|
||||
static bool handle_rtsp(raop_ctx_t *ctx, int sock);
|
||||
|
||||
@@ -97,7 +103,7 @@ static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
|
||||
static int base64_pad(char *src, char **padded);
|
||||
static int base64_encode(const void *data, int size, char **str);
|
||||
static int base64_decode(const char *str, void *data);
|
||||
static void* search_remote(void *args);
|
||||
|
||||
|
||||
extern char private_key[];
|
||||
|
||||
@@ -168,8 +174,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)) {
|
||||
LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
|
||||
LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
|
||||
closesocket(ctx->sock);
|
||||
free(ctx);
|
||||
return NULL;
|
||||
@@ -352,7 +358,11 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
|
||||
}
|
||||
|
||||
free(command);
|
||||
closesocket(sock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
#ifdef WIN32
|
||||
static void *rtsp_thread(void *arg) {
|
||||
@@ -397,9 +407,9 @@ static void *rtsp_thread(void *arg) {
|
||||
sock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sock != -1) closesocket(sock);
|
||||
|
||||
if (sock != -1) closesocket(sock);
|
||||
|
||||
#ifndef WIN32
|
||||
xTaskNotifyGive(ctx->joiner);
|
||||
vTaskSuspend(NULL);
|
||||
@@ -708,7 +718,7 @@ static void* search_remote(void *args) {
|
||||
|
||||
query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
|
||||
|
||||
return NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -741,8 +751,6 @@ static void* search_remote(void *args) {
|
||||
}
|
||||
}
|
||||
|
||||
mdns_query_results_free(results);
|
||||
}
|
||||
mdns_query_results_free(results);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,8 +158,12 @@ static void buffer_reset(abuf_t *audio_buffer);
|
||||
static void buffer_release(abuf_t *audio_buffer);
|
||||
static void buffer_reset(abuf_t *audio_buffer);
|
||||
static void buffer_push_packet(rtp_t *ctx);
|
||||
static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
|
||||
static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
|
||||
static bool rtp_request_timing(rtp_t *ctx);
|
||||
static int seq_order(seq_t a, seq_t b);
|
||||
#ifdef WIN32
|
||||
static void *rtp_thread_func(void *arg);
|
||||
#else
|
||||
static void rtp_thread_func(void *arg);
|
||||
#endif
|
||||
|
||||
@@ -565,7 +569,11 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#ifdef WIN32
|
||||
static void *rtp_thread_func(void *arg) {
|
||||
#else
|
||||
static void rtp_thread_func(void *arg) {
|
||||
#endif
|
||||
@@ -620,8 +628,9 @@ static void *rtp_thread_func(void *arg) {
|
||||
unsigned rtptime;
|
||||
|
||||
// re-sent packet
|
||||
case 0x56: {
|
||||
pktp += 4;
|
||||
case 0x56: {
|
||||
pktp += 4;
|
||||
plen -= 4;
|
||||
}
|
||||
// fall through
|
||||
|
||||
@@ -695,7 +704,6 @@ static void *rtp_thread_func(void *arg) {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// NTP timing packet
|
||||
case 0x53: {
|
||||
@@ -712,8 +720,8 @@ static void *rtp_thread_func(void *arg) {
|
||||
}
|
||||
|
||||
/*
|
||||
The expected elapsed remote time should be exactly the same as
|
||||
elapsed local time between the two request, corrected by the
|
||||
elapsed local time between the two request, corrected by the
|
||||
drifting
|
||||
u64_t expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
|
||||
*/
|
||||
@@ -740,9 +748,9 @@ static void *rtp_thread_func(void *arg) {
|
||||
free(packet);
|
||||
LOG_INFO("[%p]: terminating", ctx);
|
||||
|
||||
#ifndef WIN32
|
||||
xTaskNotifyGive(ctx->joiner);
|
||||
#ifndef WIN32
|
||||
xTaskNotifyGive(ctx->joiner);
|
||||
vTaskSuspend(NULL);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, "volume")) != NULL) rotary.volume_lock = true;
|
||||
if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,14 +10,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define I2C_SYSTEM_PORT 1
|
||||
#define SPI_SYSTEM_HOST SPI2_HOST
|
||||
#define I2C_SYSTEM_PORT 1
|
||||
#define SPI_SYSTEM_HOST SPI2_HOST
|
||||
|
||||
extern int i2c_system_port;
|
||||
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
|
||||
|
||||
@@ -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,9 +69,9 @@ 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;
|
||||
|
||||
@@ -63,7 +83,7 @@ static void vCallbackFunction( TimerHandle_t xTimer ) {
|
||||
*
|
||||
*/
|
||||
bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
|
||||
if (!leds[idx].gpio || leds[idx].gpio<0 ) return false;
|
||||
if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false;
|
||||
|
||||
ESP_LOGD(TAG,"led_blink_core");
|
||||
if (leds[idx].timer) {
|
||||
@@ -89,25 +109,40 @@ 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,34 +158,50 @@ 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) {
|
||||
if (idx >= MAX_LED) return false;
|
||||
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;
|
||||
}
|
||||
|
||||
if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
|
||||
leds[idx].timer = NULL;
|
||||
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;
|
||||
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 );
|
||||
|
||||
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);
|
||||
led_config(LED_RED, red.gpio, red.active);
|
||||
led_config(LED_GREEN, green.gpio, green.active, green.pwm);
|
||||
led_config(LED_RED, red.gpio, red.active, red.pwm);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -91,6 +97,16 @@ void services_init(void) {
|
||||
spi_system_host = -1;
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
@@ -63,28 +63,30 @@ static void ac101_start(ac_module_t mode);
|
||||
static void ac101_stop(void);
|
||||
static void ac101_set_earph_volume(uint8_t volume);
|
||||
static void ac101_set_spk_volume(uint8_t volume);
|
||||
static int ac101_get_spk_volume(void);
|
||||
|
||||
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;
|
||||
char *p;
|
||||
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// 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 +98,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 +140,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 +148,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 +163,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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -273,13 +267,6 @@ void set_sample_rate(int rate) {
|
||||
i2c_write_reg(I2S_SR_CTRL, rate);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get normalized (0..100) speaker volume
|
||||
*/
|
||||
static int ac101_get_spk_volume(void) {
|
||||
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Set normalized (0..100) volume
|
||||
*/
|
||||
@@ -290,13 +277,6 @@ static void ac101_set_spk_volume(uint8_t volume) {
|
||||
i2c_write_reg(SPKOUT_CTRL, value);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get normalized (0..100) earphone volume
|
||||
*/
|
||||
static int ac101_get_earph_volume(void) {
|
||||
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Set normalized (0..100) earphone volume
|
||||
*/
|
||||
@@ -307,6 +287,21 @@ static void ac101_set_earph_volume(uint8_t volume) {
|
||||
i2c_write_reg(HPOUT_CTRL, value);
|
||||
}
|
||||
|
||||
#if 0
|
||||
/****************************************************************************************
|
||||
* Get normalized (0..100) speaker volume
|
||||
*/
|
||||
static int ac101_get_spk_volume(void) {
|
||||
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get normalized (0..100) earphone volume
|
||||
*/
|
||||
static int ac101_get_earph_volume(void) {
|
||||
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -335,6 +330,27 @@ static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mi
|
||||
i2c_write_reg(OMIXER_BST1_CTRL,regval);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void ac101_deinit(void) {
|
||||
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Don't know when this one is supposed to be called
|
||||
*/
|
||||
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
|
||||
uint16_t regval=0;
|
||||
regval = i2c_read_reg(I2S1LCK_CTRL);
|
||||
regval &= 0xe03f;
|
||||
regval |= (cfg->bclk_div << 9);
|
||||
regval |= (cfg->lclk_div << 6);
|
||||
i2c_write_reg(I2S1LCK_CTRL, regval);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -367,21 +383,3 @@ static void ac101_stop(void) {
|
||||
i2c_write_reg(PLL_CTRL2, value);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void ac101_deinit(void) {
|
||||
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Don't know when this one is supposed to be called
|
||||
*/
|
||||
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
|
||||
uint16_t regval=0;
|
||||
regval = i2c_read_reg(I2S1LCK_CTRL);
|
||||
regval &= 0xe03f;
|
||||
regval |= (cfg->bclk_div << 9);
|
||||
regval |= (cfg->lclk_div << 6);
|
||||
i2c_write_reg(I2S1LCK_CTRL, regval);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -292,9 +292,15 @@ static int read_mp4_header(void) {
|
||||
l->pos += bytes;
|
||||
l->consume = consume - bytes;
|
||||
break;
|
||||
} else {
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
// fifo bufffers
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "squeezelite.h"
|
||||
|
||||
@@ -100,6 +102,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
char packet[96];
|
||||
int len;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
@@ -406,7 +414,7 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
displayer.dirty = true;
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
|
||||
// inform new LMS server of our capabilities
|
||||
sendSETD(displayer.width, GDS_GetHeight(display));
|
||||
|
||||
@@ -418,7 +426,7 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
*/
|
||||
static bool handler(u8_t *data, int len){
|
||||
bool res = true;
|
||||
|
||||
|
||||
if (!strncmp((char*) data, "vfdc", 4)) {
|
||||
vfdc_handler(data, len);
|
||||
} else if (!strncmp((char*) data, "grfe", 4)) {
|
||||
@@ -436,7 +444,7 @@ static bool handler(u8_t *data, int len){
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
|
||||
|
||||
// chain protocol handlers (bitwise or is fine)
|
||||
if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
|
||||
|
||||
@@ -565,28 +573,47 @@ 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;
|
||||
}
|
||||
|
||||
// this is 8 bits grayscale
|
||||
int scale = 8 - GDS_GetDepth(display);
|
||||
if (GDS_GetMode(display) <= GDS_GRAYSCALE) {
|
||||
// this is 8 bits grayscale
|
||||
int scale = 8 - GDS_GetDepth(display);
|
||||
|
||||
// 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, *data++ >> scale);
|
||||
// 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, *data++ >> scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = 0; c < VU_HEIGHT; c++) {
|
||||
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int r = 0; r < width; r++) {
|
||||
for (int c = 0; c < VU_HEIGHT; c++) {
|
||||
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
|
||||
// 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
|
||||
@@ -646,12 +673,13 @@ static void grfb_handler(u8_t *data, int len) {
|
||||
pkt->brightness = htons(pkt->brightness);
|
||||
|
||||
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,12 +935,13 @@ 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;
|
||||
else if (visu.bars[i].max) visu.bars[i].max--;
|
||||
else if (!clear) continue;
|
||||
|
||||
|
||||
if (visu.rotate) {
|
||||
int x1 = visu.col;
|
||||
int y1 = visu.row + visu.border + visu.bar_border + i*(visu.bar_width + visu.bar_gap);
|
||||
@@ -1094,7 +1123,7 @@ static void visu_handler( u8_t *data, int len) {
|
||||
*/
|
||||
static void displayer_task(void *args) {
|
||||
int sleep;
|
||||
|
||||
|
||||
while (1) {
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
|
||||
@@ -26,14 +26,16 @@ 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;
|
||||
|
||||
|
||||
177
components/squeezelite/external/dac_external.c
vendored
177
components/squeezelite/external/dac_external.c
vendored
@@ -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;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
char *p;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
if (nvs_item) {
|
||||
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);
|
||||
}
|
||||
// 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 (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, "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);
|
||||
|
||||
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;
|
||||
} 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);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "DAC uses I2C @%d with sda:%d, scl:%d", i2c_addr, i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
// we have an I2C configured
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,9 +314,15 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
|
||||
a->pos += bytes;
|
||||
a->consume = consume - bytes;
|
||||
break;
|
||||
} else {
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -99,7 +101,7 @@ static int _read_cb(void *datasource, char *ptr, int size) {
|
||||
|
||||
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
bytes = min(bytes, size);
|
||||
|
||||
|
||||
memcpy(ptr, streambuf->readp, bytes);
|
||||
_buf_inc_readp(streambuf, bytes);
|
||||
|
||||
@@ -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;
|
||||
|
||||
iptr = (s16_t *)write_buf + count;
|
||||
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
|
||||
|
||||
// work backward to unpack samples (if needed)
|
||||
iptr = (s16_t *) write_buf + count;
|
||||
optr = (ISAMPLE_T *) write_buf + frames * 2;
|
||||
|
||||
if (channels == 2) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
|
||||
#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;
|
||||
|
||||
@@ -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,14 +92,17 @@ 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;
|
||||
|
||||
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
|
||||
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
|
||||
static void *output_thread_i2s(void *arg);
|
||||
static void *output_thread_i2s_stats(void *arg);
|
||||
static void output_thread_i2s_stats(void *arg);
|
||||
static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count);
|
||||
static void (*jack_handler_chain)(bool inserted);
|
||||
|
||||
@@ -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;
|
||||
@@ -191,25 +206,14 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
i2s_config.tx_desc_auto_clear = true;
|
||||
i2s_config.use_apll = true;
|
||||
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// 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 {
|
||||
#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
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
// 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,19 +496,19 @@ 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)
|
||||
if (i2s_config.sample_rate != output.current_sample_rate) {
|
||||
LOG_INFO("changing sampling rate %u to %u", i2s_config.sample_rate, output.current_sample_rate);
|
||||
/*
|
||||
if (synced)
|
||||
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;
|
||||
i2s_set_sample_rates(CONFIG_I2S_NUM, spdif ? i2s_config.sample_rate * 2 : i2s_config.sample_rate);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
@@ -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);
|
||||
@@ -511,13 +552,13 @@ static void *output_thread_i2s(void *arg) {
|
||||
/****************************************************************************************
|
||||
* Stats output thread
|
||||
*/
|
||||
static void *output_thread_i2s_stats(void *arg) {
|
||||
static void output_thread_i2s_stats(void *arg) {
|
||||
while (1) {
|
||||
// no need to lock
|
||||
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);
|
||||
@@ -538,7 +579,6 @@ static void *output_thread_i2s_stats(void *arg) {
|
||||
}
|
||||
vTaskDelay( pdMS_TO_TICKS( STATS_PERIOD_MS ) );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -401,7 +398,10 @@ void logprint(const char *fmt, ...);
|
||||
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#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);
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
// stream thread
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "squeezelite.h"
|
||||
|
||||
|
||||
195
components/squeezelite/tas57xx/dac_5713.c
Normal file
195
components/squeezelite/tas57xx/dac_5713.c
Normal 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;
|
||||
}
|
||||
@@ -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,10 +131,9 @@ 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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* power
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
iptr = (s16_t *)write_buf + count;
|
||||
optr = (ISAMPLE_T *) outputbuf->writep + frames * 2;
|
||||
// work backward to unpack samples (if needed)
|
||||
iptr = (s16_t *) write_buf + count;
|
||||
optr = (ISAMPLE_T *) write_buf + frames * 2;
|
||||
|
||||
if (channels == 2) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME);
|
||||
#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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -343,7 +343,7 @@ $(document).ready(function(){
|
||||
});
|
||||
|
||||
$("#generate-command").on("click", function() {
|
||||
var commandLine = commandHeader + '-n "' + $("#player").val() + '"';
|
||||
var commandLine = commandHeader + ' -n "' + $("#player").val() + '"';
|
||||
|
||||
if (output == 'bt') {
|
||||
commandLine += ' -o "BT -n \'' + $("#btsink").val() + '\'" -R -Z 192000';
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -21,6 +21,7 @@ menu "Squeezelite-ESP32"
|
||||
help
|
||||
Set logging level info|debug|sdebug
|
||||
endmenu
|
||||
|
||||
config JACK_LOCKED
|
||||
bool
|
||||
config BAT_LOCKED
|
||||
@@ -33,59 +34,109 @@ menu "Squeezelite-ESP32"
|
||||
bool
|
||||
config SPKFAULT_LOCKED
|
||||
bool
|
||||
menu "Audio Output"
|
||||
choice OUTPUT_TYPE
|
||||
prompt "Output Type"
|
||||
default BASIC_I2C_BT
|
||||
help
|
||||
Type of hardware platform
|
||||
config SQUEEZEAMP
|
||||
bool "SqueezeAMP"
|
||||
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 BASIC_I2C_BT
|
||||
bool "Generic I2S & Bluetooth"
|
||||
endchoice
|
||||
|
||||
menu "DAC I2S settings"
|
||||
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 "Main system"
|
||||
default BASIC_I2C_BT
|
||||
help
|
||||
Type of hardware platform
|
||||
config SQUEEZEAMP
|
||||
bool "SqueezeAMP"
|
||||
select JACK_LOCKED
|
||||
select BAT_LOCKED
|
||||
select I2C_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 "Audio settings"
|
||||
menu "DAC settings"
|
||||
visible if BASIC_I2C_BT
|
||||
config I2S_NUM
|
||||
int "I2S channel (0 or 1). "
|
||||
default 0
|
||||
help
|
||||
I2S dma channel to use.
|
||||
config I2S_BCK_IO
|
||||
int "I2S Bit clock GPIO number. "
|
||||
default 33 if !A1S
|
||||
default 27 if A1S
|
||||
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
|
||||
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
|
||||
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).
|
||||
|
||||
menu "I2S settings"
|
||||
config I2S_NUM
|
||||
int "I2S channel (0 or 1). "
|
||||
default 0
|
||||
help
|
||||
I2S dma channel to use.
|
||||
config I2S_BCK_IO
|
||||
int "I2S Bit clock GPIO number. "
|
||||
default 33
|
||||
help
|
||||
I2S Bit Clock gpio pin to use.
|
||||
config I2S_WS_IO
|
||||
int "I2S Word Select GPIO number. "
|
||||
default 25
|
||||
help
|
||||
I2S Word Select gpio pin to use.
|
||||
config I2S_DO_IO
|
||||
int "I2S Data Output GPIO number. "
|
||||
default 32
|
||||
help
|
||||
I2S data output gpio pin to use.
|
||||
config I2S_DI_IO
|
||||
int "I2S Data Input GPIO number. "
|
||||
default -1 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 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ extern bool bypass_wifi_manager;
|
||||
|
||||
extern EventGroupHandle_t wifi_event_group;
|
||||
extern const int CONNECTED_BIT;
|
||||
static const char * TAG = "cmd_wifi";
|
||||
//static const char * TAG = "cmd_wifi";
|
||||
/** Arguments used by 'join' function */
|
||||
static struct {
|
||||
struct arg_int *timeout;
|
||||
@@ -123,9 +123,8 @@ static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
|
||||
return (bits & CONNECTED_BIT) != 0;
|
||||
}
|
||||
|
||||
|
||||
static int set_auto_connect(int argc, char **argv)
|
||||
{
|
||||
//static int set_auto_connect(int argc, char **argv)
|
||||
//{
|
||||
// int nerrors = arg_parse(argc, argv, (void **) &join_args);
|
||||
// if (nerrors != 0) {
|
||||
// arg_print_errors(stderr, join_args.end, argv[0]);
|
||||
@@ -147,8 +146,9 @@ static int set_auto_connect(int argc, char **argv)
|
||||
// return 1;
|
||||
// }
|
||||
// ESP_LOGI(__func__, "Connected");
|
||||
return 0;
|
||||
}
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
static int connect(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &join_args);
|
||||
|
||||
@@ -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.
@@ -88,7 +88,7 @@ sub displayWidth {
|
||||
}
|
||||
|
||||
sub brightnessMap {
|
||||
return (65535, 10, 50, 100, 200);
|
||||
return (0 .. 5);
|
||||
}
|
||||
|
||||
=comment
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user