Merge branch 'development' into prerelease-15.2.0

This commit is contained in:
Theo Arends 2025-12-09 09:57:30 +01:00
commit 0c1105b9b7
1189 changed files with 161316 additions and 96795 deletions

View File

@ -7,7 +7,7 @@
- [ ] Only relevant files were touched - [ ] Only relevant files were touched
- [ ] Only one feature/fix was added per PR and the code change compiles without warnings - [ ] Only one feature/fix was added per PR and the code change compiles without warnings
- [ ] The code change is tested and works with Tasmota core ESP8266 V.2.7.8 - [ ] The code change is tested and works with Tasmota core ESP8266 V.2.7.8
- [ ] The code change is tested and works with Tasmota core ESP32 V.3.1.4 - [ ] The code change is tested and works with Tasmota core ESP32 V.3.1.6
- [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla). - [ ] I accept the [CLA](https://github.com/arendst/Tasmota/blob/development/CONTRIBUTING.md#contributor-license-agreement-cla).
_NOTE: The code change must pass CI tests. **Your PR cannot be merged unless tests pass**_ _NOTE: The code change must pass CI tests. **Your PR cannot be merged unless tests pass**_

5
.gitpod.Dockerfile vendored
View File

@ -1,5 +0,0 @@
FROM gitpod/workspace-python-3.13
RUN python -m pip install --break-system-packages uv
USER gitpod

View File

@ -1,9 +0,0 @@
tasks:
- command: pip install -U platformio && pip install --upgrade pip && platformio run -t clean -e tasmota
image:
file: .gitpod.Dockerfile
vscode:
extensions:
- shardulm94.trailing-spaces

View File

@ -3,7 +3,72 @@ All notable changes to this project will be documented in this file.
## [Released] ## [Released]
## [15.1.0] ## [15.2.0]
- Release Steven
## [15.1.0.3] 20251212
### Added
- Support for ESP32-P4 rev.3 (#24146)
- Support for Analog Gauges (#24153)
- Support for MakeSkyBlue Solar Charger Energy Monitor (#24151)
- Berry `tasmota.micros()` to get time in microseconds (#24192)
- Support for AGS02MA TVOC sensor (#24109)
## [15.0.1.5] 20251011
### Changed
- ESP32 Platform from 2025.11.30 to 2025.11.31, Framework (Arduino Core) from v3.1.5 to v3.1.6 and IDF from v5.3.4.251110 to v5.3.4.251110 (#24146)
- Refactored DALI using TasmotaDali library v1.0.0 adding frame receive buffer
### Fixed
- ESP32-P4 Hosted MCU updated to v2.6.6 solving WiFi boot issues (#24146)
- ESP32-Solo1 using pre-compiled Arduino libraries (#24146)
- PCA9685 V2 driver PWMTO fading logic and overflow (#24159)
- RGBW handling in TasmotaLED and xlgt_01_ws2812_esp32 (#24172)
- ArtNet single light color mapping using `ChannelRemap` (#24058)
## [15.1.0.2] 20251122
### Added
- WS2812 and Berry animation support for reverse-order LED strip (#24138)
- DALI persistence for `DaliTarget` and `DaliChannels` if filesystem is present
- DALI DT8 RGBWAF color support using Tasmota light control
### Changed
- ESP32 Platform from 2025.10.30 to 2025.11.30, Framework (Arduino Core) from v3.1.4 to v3.1.5 and IDF from v5.3.4.250826 to v5.3.4.251110 (#24118)
- JPEGDEC library from v1.8.3 to v1.8.4 (#24120)
### Fixed
- DALI protocol errors
## [15.1.0.1] 20251115
### Added
- TLS enabled ECDSA by default for ESP8266 (#24009)
- Berry `cb.free_cb` for extension manager (#24014)
- Berry `light.get()` direct access to values (#24033)
- HostedMCU file update using command `HostedLoad <version>|<filename>`
- Berry `gc_heap` and `gc_time` to `tasmota.memory()` (#24054)
- Scripter array transfer via UFS (#24060)
- ESP8266 GPIOViewer memory map if enabled with `#define GV_USE_ESPINFO`
- Berry `tcp.write()` add `offset` and `len` (#24076)
- NeoPool command `NPReadLSB`, `NPReadMSB`, `NPWriteLSB`, `NWriteMSB` for directly read/write LSB/MSB of 16-bit register (#24083)
- Commands `DaliSend` and `DaliQuery` allow extended commands with prefix for DeviceType defaulting to DT6
### Changed
- Refactored library UDisplay (#24007)
- LVGL library from v9.3.0 to v9.4.0 (#24028)
- Increased filesystem file name size from 48 to 50 characters
- GPIOViewer from v1.6.3 to v1.7.0
### Fixed
- TLS fix ECDSA and add `SetOption165 1` to enable ECDSA in addition to RSA (#24000)
- Extension Manager exception when `OtaUrl` is not defined or invalid
- HASPmota exception in `cpicker` (colorwheel) (#24010)
- Extension Manager Light Theme support and Extensions input field control
- InfluxDb receives IPAddress as a value regression from v15.0.1.3 (#24031)
- Scripter UDP and switch case (#24060)
- TuyaMCU v1 soft lock when WIFI_SELECT / WIFI_RESET is initiated (#24063)
- HASPmota `scale` and `angle` for images (#24089)
## [15.1.0] 20251011
- Release Stella - Release Stella
## [15.0.1.5] 20251011 ## [15.0.1.5] 20251011

View File

@ -102,7 +102,7 @@ In addition to @arendst the following code is mainly owned by:
| xdrv_89_ | | xdrv_89_ |
| xdrv_90_esp32_dingtian_relay | @barbudor | xdrv_90_esp32_dingtian_relay | @barbudor
| xdrv_91_esp32_twai | @arendst | xdrv_91_esp32_twai | @arendst
| xdrv_92_ | | xdrv_92_vid6608 | @petrows
| xdrv_93_ | | xdrv_93_ |
| xdrv_94_ | | xdrv_94_ |
| | | |

View File

@ -133,5 +133,6 @@ Index | Define | Driver | Device | Address(es) | Bus2 | Descrip
92 | USE_PCF85063 | xdrv_56 | PCF85063 | 0x51 | | PCF85063 Real time clock 92 | USE_PCF85063 | xdrv_56 | PCF85063 | 0x51 | | PCF85063 Real time clock
93 | USE_AS33772S | xdrv_119 | AS33772S | 0x52 | Yes | AS33772S USB PD Sink Controller 93 | USE_AS33772S | xdrv_119 | AS33772S | 0x52 | Yes | AS33772S USB PD Sink Controller
94 | USE_RV3028 | xdrv_56 | RV3028 | 0x52 | Yes | RV-3028-C7 RTC Controller 94 | USE_RV3028 | xdrv_56 | RV3028 | 0x52 | Yes | RV-3028-C7 RTC Controller
95 | USE_AGS02MA | xsns_118 | AGS02MA | 0x1A | | TVOC Gas sensor
NOTE: Bus2 supported on ESP32 only. NOTE: Bus2 supported on ESP32 only.

View File

@ -7,7 +7,6 @@ _Written for PlatformIO._
[![GitHub download](https://img.shields.io/github/downloads/arendst/Tasmota/total.svg)](https://github.com/arendst/Tasmota/releases/latest) [![GitHub download](https://img.shields.io/github/downloads/arendst/Tasmota/total.svg)](https://github.com/arendst/Tasmota/releases/latest)
[![License](https://img.shields.io/github/license/arendst/Tasmota.svg)](LICENSE.txt) [![License](https://img.shields.io/github/license/arendst/Tasmota.svg)](LICENSE.txt)
[![Discord](https://img.shields.io/discord/479389167382691863.svg?logo=discord&logoColor=white&color=5865F2&label=Discord)](https://discord.gg/Ks2Kzd4) [![Discord](https://img.shields.io/discord/479389167382691863.svg?logo=discord&logoColor=white&color=5865F2&label=Discord)](https://discord.gg/Ks2Kzd4)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/arendst/Tasmota)
<hr></hr> <hr></hr>
@ -116,6 +115,9 @@ If you're looking for support on **Tasmota** there are some options available:
* [Search in Issues](https://github.com/arendst/Tasmota/issues): You might find an answer to your question by searching current or closed issues. * [Search in Issues](https://github.com/arendst/Tasmota/issues): You might find an answer to your question by searching current or closed issues.
* [Software Problem Report](https://github.com/arendst/Tasmota/issues/new?template=Bug_report.md): For reporting problems of Tasmota Software. * [Software Problem Report](https://github.com/arendst/Tasmota/issues/new?template=Bug_report.md): For reporting problems of Tasmota Software.
### Unofficial Community Resources
* [Tasmota-DE](https://t.me/TasmotaDE): A German-language Telegram group related to Tasmota.
## Contribute ## Contribute
You can contribute to Tasmota by You can contribute to Tasmota by

View File

@ -36,9 +36,9 @@ While fallback or downgrading is common practice it was never supported due to S
This release will be supported from ESP8266/Arduino library Core version **2.7.8** due to reported security and stability issues on previous Core version. This will also support gzipped binaries. This release will be supported from ESP8266/Arduino library Core version **2.7.8** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
This release will be supported from ESP32/Arduino library Core version **v3.1.4**. This release will be supported from ESP32/Arduino library Core version **v3.1.6**.
Support of ESP8266 Core versions before 2.7.8 and ESP32 Core versions before v3.1.4 have been removed. Support of ESP8266 Core versions before 2.7.8 and ESP32 Core versions before v3.1.6 have been removed.
## Initial configuration tools ## Initial configuration tools
@ -71,23 +71,23 @@ Latest released binaries can be downloaded from
- http://ota.tasmota.com/tasmota/release - http://ota.tasmota.com/tasmota/release
Historical binaries can be downloaded from Historical binaries can be downloaded from
- http://ota.tasmota.com/tasmota/release-15.1.0 - http://ota.tasmota.com/tasmota/release-15.2.0
The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin.gz`` The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin.gz``
### ESP32, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-S2, ESP32-S3 and ESP32-P4 based ### ESP32, ESP32-C2, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-P4, ESP32-S2 and ESP32-S3 based
The following binary downloads have been compiled with ESP32/Arduino library core version **v3.1.4**. The following binary downloads have been compiled with ESP32/Arduino library core version **v3.1.6**.
- **tasmota32.bin** = The Tasmota version with most drivers including additional sensors and KNX for 4M+ flash. **RECOMMENDED RELEASE BINARY** - **tasmota32.bin** = The Tasmota version with most drivers including additional sensors and KNX for 4M+ flash. **RECOMMENDED RELEASE BINARY**
- **tasmota32solo1.bin** = The Tasmota version with most drivers including additional sensors and KNX for single core ESP32 and 4M+ flash. - **tasmota32solo1.bin** = The Tasmota version with most drivers including additional sensors and KNX for single core ESP32 and 4M+ flash.
- **tasmota32s2.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S2 with serial and 4M+ flash.
- **tasmota32s2cdc.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S2 with serial over embedded USB CDC only and 4M+ flash.
- **tasmota32s3.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S3 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32c2.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C2 with serial and 4M+ flash. - **tasmota32c2.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C2 with serial and 4M+ flash.
- **tasmota32c3.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C3 with USB HWCDC and fallback to serial and 4M+ flash. - **tasmota32c3.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C3 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32c5.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C5 with USB HWCDC and fallback to serial and 4M+ flash. - **tasmota32c5.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C5 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32c6.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C6 with USB HWCDC and fallback to serial and 4M+ flash. - **tasmota32c6.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-C6 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32p4.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-P4 with USB HWCDC and fallback to serial and 4M+ flash. - **tasmota32p4.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-P4 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32s2.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S2 with serial and 4M+ flash.
- **tasmota32s2cdc.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S2 with serial over embedded USB CDC only and 4M+ flash.
- **tasmota32s3.bin** = The Tasmota version with most drivers including additional sensors and KNX for ESP32-S3 with USB HWCDC and fallback to serial and 4M+ flash.
- **tasmota32-AD.bin** to **tasmota32-VN.bin** = The Tasmota version in different languages for 4M+ flash. - **tasmota32-AD.bin** to **tasmota32-VN.bin** = The Tasmota version in different languages for 4M+ flash.
- **tasmota32-bluetooth.bin** = The Bluetooth version adds BLE support for 4M+ flash. - **tasmota32-bluetooth.bin** = The Bluetooth version adds BLE support for 4M+ flash.
- **tasmota32-display.bin** = The Display version without Energy Monitoring but adds display support for 4M+ flash. - **tasmota32-display.bin** = The Display version without Energy Monitoring but adds display support for 4M+ flash.
@ -102,7 +102,7 @@ Latest released binaries can be downloaded from
- https://ota.tasmota.com/tasmota32/release - https://ota.tasmota.com/tasmota32/release
Historical binaries can be downloaded from Historical binaries can be downloaded from
- https://ota.tasmota.com/tasmota32/release-15.1.0 - https://ota.tasmota.com/tasmota32/release-15.2.0
The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasmota.com/tasmota32/release/tasmota32.bin`` The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasmota.com/tasmota32/release/tasmota32.bin``
@ -112,62 +112,47 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
[Complete list](BUILDS.md) of available feature and sensors. [Complete list](BUILDS.md) of available feature and sensors.
## Changelog v15.1.0 Stella ## Changelog v15.2.0 Steven
### Added ### Added
- Commands `LoRaWanDecoder "` and `LoRaWanName "` to clear name [#23394](https://github.com/arendst/Tasmota/issues/23394) - Support for ESP32-P4 rev.3 [#24146](https://github.com/arendst/Tasmota/issues/24118)
- Command `I2sPause` [#23646](https://github.com/arendst/Tasmota/issues/23646) - Support for Analog Gauges [#24153](https://github.com/arendst/Tasmota/issues/24153)
- Support for RV3028 RTC [#23672](https://github.com/arendst/Tasmota/issues/23672) - Support for MakeSkyBlue Solar Charger Energy Monitor [#24151](https://github.com/arendst/Tasmota/issues/24151)
- Support for RX8030 RTC [#23855](https://github.com/arendst/Tasmota/issues/23855) - Support for AGS02MA TVOC sensor [#24109](https://github.com/arendst/Tasmota/issues/24109)
- Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet - Commands `DaliSend` and `DaliQuery` allow extended commands with prefix for DeviceType defaulting to DT6
- Internal function 'WSContentSendRaw_P' [#23641](https://github.com/arendst/Tasmota/issues/23641) - ESP8266 GPIOViewer memory map if enabled with `#define GV_USE_ESPINFO`
- Universal display driver for ZJY169S0800TG01 ST7789 280x240 [#23638](https://github.com/arendst/Tasmota/issues/23638) - HostedMCU file update using command `HostedLoad <version>|<filename>`
- NeoPool add Redox tank alarm [#19811](https://github.com/arendst/Tasmota/issues/19811) - Scripter array transfer via UFS [#24060](https://github.com/arendst/Tasmota/issues/24060)
- I2S additions [#23543](https://github.com/arendst/Tasmota/issues/23543) - DALI DT8 RGBWAF color support using Tasmota light control
- ESP32 ROM SHA Hardware Acceleration to BearSSL [#23819](https://github.com/arendst/Tasmota/issues/23819) - NeoPool command `NPReadLSB`, `NPReadMSB`, `NPWriteLSB`, `NWriteMSB` for directly read/write LSB/MSB of 16-bit register [#24083](https://github.com/arendst/Tasmota/issues/24083)
- ESP32 Extension Manager, replacing loading of Partition Wizard [#23955](https://github.com/arendst/Tasmota/issues/23955) - TLS enabled ECDSA by default for ESP8266 [#24009](https://github.com/arendst/Tasmota/issues/24009)
- Support for ESP32-P4 [#23663](https://github.com/arendst/Tasmota/issues/23663) - WS2812 and Berry animation support for reverse-order LED strip [#24138](https://github.com/arendst/Tasmota/issues/24138)
- Support for ESP32-C5 [#23804](https://github.com/arendst/Tasmota/issues/23804) - Berry `cb.free_cb` for extension manager [#24014](https://github.com/arendst/Tasmota/issues/24014)
- ESP32-P4 command `HostedOta` [#23675](https://github.com/arendst/Tasmota/issues/23675) - Berry `light.get()` direct access to values [#24033](https://github.com/arendst/Tasmota/issues/24033)
- Berry f-strings now support ':' in expression [#23618](https://github.com/arendst/Tasmota/issues/23618) - Berry `gc_heap` and `gc_time` to `tasmota.memory()` [#24054](https://github.com/arendst/Tasmota/issues/24054)
- Berry preview of animation framework [#23816](https://github.com/arendst/Tasmota/issues/23816) - Berry `tcp.write()` add `offset` and `len` [#24076](https://github.com/arendst/Tasmota/issues/24076)
- Berry `call()` now works for classes [#23744](https://github.com/arendst/Tasmota/issues/23744) - Berry `tasmota.micros()` to get time in microseconds [#24192](https://github.com/arendst/Tasmota/issues/24192)
- Berry multiplication between string and int [#23850](https://github.com/arendst/Tasmota/issues/23850)
- Berry animation framework web ui to compile DSL [#23962](https://github.com/arendst/Tasmota/issues/23962)
### Breaking Changed
- Berry `animate` framework is DEPRECATED, will be replace by `animation` framework [#23854](https://github.com/arendst/Tasmota/issues/23854)
### Changed ### Changed
- ESP8266 platform update from 2025.05.00 to 2025.10.00 [#23971](https://github.com/arendst/Tasmota/issues/23971) - ESP32 Platform from 2025.11.30 to 2025.11.31, Framework (Arduino Core) from v3.1.5 to v3.1.6 and IDF from v5.3.4.250826 to v5.3.4.251110 [#24146](https://github.com/arendst/Tasmota/issues/24118)
- ESP32 Platform from 2025.05.30 to 2025.10.30, Framework (Arduino Core) from v3.1.3.250504 to v3.1.4 and IDF from v5.3.3.250501 to v5.3.4.250826 [#23971](https://github.com/arendst/Tasmota/issues/23971) - LVGL library from v9.3.0 to v9.4.0 [#24028](https://github.com/arendst/Tasmota/issues/24028)
- Epdiy library from v1.0.0 to v2.0.0 - JPEGDEC library from v1.8.3 to v1.8.4 [#24120](https://github.com/arendst/Tasmota/issues/24120)
- OpenTherm library from v0.9.0 to v1.1.5 [#23704](https://github.com/arendst/Tasmota/issues/23704) - GPIOViewer from v1.6.3 to v1.7.0
- JPEGDEC library from v1.5.0 to v1.8.3 [#23883](https://github.com/arendst/Tasmota/issues/23883) - Refactored library UDisplay [#24007](https://github.com/arendst/Tasmota/issues/24007)
- Library names [#23560](https://github.com/arendst/Tasmota/issues/23560) - Refactored DALI using TasmotaDali library v1.0.0 adding frame receive buffer
- Web UI styles and HTML syntax [#23847](https://github.com/arendst/Tasmota/issues/23847) - Increased filesystem file name size from 48 to 50 characters
- Make GUI Timer parameters mobile phone friendly [#23959](https://github.com/arendst/Tasmota/issues/23959)
- CSS uses named colors variables [#23597](https://github.com/arendst/Tasmota/issues/23597)
- VEML6070 and AHT2x device detection [#23581](https://github.com/arendst/Tasmota/issues/23581)
- Domoticz supports persistent settings for all relays, keys and switches when filesystem `#define USE_UFILESYS` is enabled
- ESP32 LoRaWan decoding won't duplicate non-decoded message if `SO147 0`
- Use HAL instead of ROM for SHA HW acceleration as used by TLS [#23902](https://github.com/arendst/Tasmota/issues/23902)
- BLE updates for esp-nimble-cpp v2.x [#23553](https://github.com/arendst/Tasmota/issues/23553)
- Berry raise webserver hooks from 16 to 32 [#23748](https://github.com/arendst/Tasmota/issues/23748)
- Berry add argument to `werbserver.content_send_style` [#23953](https://github.com/arendst/Tasmota/issues/23953)
### Fixed ### Fixed
- Syslog RFC5424 compliance [#23509](https://github.com/arendst/Tasmota/issues/23509) - InfluxDb receives IPAddress as a value regression from v15.0.1.3 [#24031](https://github.com/arendst/Tasmota/issues/24031)
- Unable to use default serial GPIOs by TasmotaSerial regression from v14.5.0 with IDF 5.3.2.250120 [#23775](https://github.com/arendst/Tasmota/issues/23775) - Scripter UDP and switch case [#24060](https://github.com/arendst/Tasmota/issues/24060)
- AHT30 sensor start with null values after deep sleep [#23624](https://github.com/arendst/Tasmota/issues/23624) - TuyaMCU v1 soft lock when WIFI_SELECT / WIFI_RESET is initiated [#24063](https://github.com/arendst/Tasmota/issues/24063)
- NeoPool reset to default settings [#23734](https://github.com/arendst/Tasmota/issues/23734) - PCA9685 V2 driver PWMTO fading logic and overflow [#24159](https://github.com/arendst/Tasmota/issues/24159)
- Berry vulnerability in JSON parsing for unicode [#23603](https://github.com/arendst/Tasmota/issues/23603) - DALI protocol errors
- Berry security issues in `int64` and improve documentation [#23605](https://github.com/arendst/Tasmota/issues/23605) - RGBW handling in TasmotaLED and xlgt_01_ws2812_esp32 [#24172](https://github.com/arendst/Tasmota/issues/24172)
- Berry security issues in `berry_mapping` and improve documentation [#23606](https://github.com/arendst/Tasmota/issues/23606) - ArtNet single light color mapping using `ChannelRemap` [#24058](https://github.com/arendst/Tasmota/issues/24058)
- Berry Hue regression from #23429 [#23623](https://github.com/arendst/Tasmota/issues/23623) - TLS fix ECDSA and add `SetOption165 1` to enable ECDSA in addition to RSA [#24000](https://github.com/arendst/Tasmota/issues/24000)
- Berry calling `setmember` with a function [#23825](https://github.com/arendst/Tasmota/issues/23825) - ESP32-P4 Hosted MCU updated to v2.6.6 solving WiFi boot issues [#24146](https://github.com/arendst/Tasmota/issues/24118)
- Berry fixed 'be_top is non zero' warning when calling C mapped functions [#23989](https://github.com/arendst/Tasmota/issues/23989) - ESP32-Solo1 using pre-compiled Arduino libraries [#24146](https://github.com/arendst/Tasmota/issues/24118)
- Berry fixed 'be_top is non zero' when `Br` command fails [#23990](https://github.com/arendst/Tasmota/issues/23990) - Extension Manager exception when `OtaUrl` is not defined or invalid
- LVGL restore `lv_chart.set_range` removed in LVGL 9.3.0 in favor of `lv_chart.set_axis_range` [#23567](https://github.com/arendst/Tasmota/issues/23567) - Extension Manager Light Theme support and Extensions input field control
- HASPmota exception in `cpicker` (colorwheel) [#24010](https://github.com/arendst/Tasmota/issues/24010)
### Removed - HASPmota `scale` and `angle` for images (#24089)[#24089](https://github.com/arendst/Tasmota/issues/24089)
- `user-scalable=no` from HTTP HEADER [#23798](https://github.com/arendst/Tasmota/issues/23798)

View File

@ -2,10 +2,11 @@
"build": { "build": {
"core": "esp32", "core": "esp32",
"extra_flags": "-DARDUINO_TASMOTA -DESP32_4M -DCORE32SOLO1", "extra_flags": "-DARDUINO_TASMOTA -DESP32_4M -DCORE32SOLO1",
"f_cpu": "240000000L", "f_cpu": "160000000L",
"f_flash": "40000000L", "f_flash": "40000000L",
"flash_mode": "dio", "flash_mode": "dio",
"mcu": "esp32", "mcu": "esp32",
"chip_variant": "esp32u",
"variant": "esp32", "variant": "esp32",
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv" "partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
}, },
@ -41,11 +42,6 @@
"download": { "download": {
"speed": 230400 "speed": 230400
}, },
"espidf": {
"custom_sdkconfig": [
"CONFIG_FREERTOS_UNICORE=y"
]
},
"url": "https://www.espressif.com/sites/default/files/documentation/esp32-solo-1_datasheet_en.pdf", "url": "https://www.espressif.com/sites/default/files/documentation/esp32-solo-1_datasheet_en.pdf",
"vendor": "Espressif" "vendor": "Espressif"
} }

View File

@ -2,14 +2,16 @@
"build": { "build": {
"core": "esp32", "core": "esp32",
"extra_flags": [ "extra_flags": [
"-DARDUINO_TASMOTA -DESP32P4 -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE" "-DARDUINO_TASMOTA -DESP32P4ES -DESP32_16M -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE"
], ],
"f_cpu": "360000000L", "f_cpu": "360000000L",
"f_flash": "80000000L", "f_flash": "80000000L",
"f_psram": "200000000L",
"flash_mode": "qio", "flash_mode": "qio",
"mcu": "esp32p4", "mcu": "esp32p4",
"chip_variant": "esp32p4_es",
"variant": "esp32p4", "variant": "esp32p4",
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv" "partitions": "partitions/esp32_partition_app3904k_fs11584k.csv"
}, },
"connectivity": [ "connectivity": [
"wifi", "wifi",
@ -24,7 +26,7 @@
"arduino", "arduino",
"espidf" "espidf"
], ],
"name": "Espressif Generic ESP32-P4 >= 4M Flash, Tasmota 2880k Code/OTA, >= 320k FS", "name": "Espressif Generic ESP32-P4 ES 16M Flash, Tasmota 3904k Code/OTA, 11584k FS",
"upload": { "upload": {
"arduino": { "arduino": {
"flash_extra_images": [ "flash_extra_images": [
@ -34,9 +36,9 @@
] ]
] ]
}, },
"flash_size": "4MB", "flash_size": "16MB",
"maximum_ram_size": 768000, "maximum_ram_size": 768000,
"maximum_size": 4194304, "maximum_size": 16777216,
"require_upload_port": true, "require_upload_port": true,
"speed": 1500000 "speed": 1500000
}, },

47
boards/esp32p4r3.json Normal file
View File

@ -0,0 +1,47 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DARDUINO_TASMOTA -DESP32P4R3 -DESP32_16M -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE"
],
"f_cpu": "400000000L",
"f_flash": "80000000L",
"f_psram": "200000000L",
"flash_mode": "qio",
"mcu": "esp32p4",
"chip_variant": "esp32p4",
"variant": "esp32p4",
"partitions": "partitions/esp32_partition_app3904k_fs11584k.csv"
},
"connectivity": [
"wifi",
"bluetooth",
"openthread",
"ethernet"
],
"debug": {
"openocd_target": "esp32p4.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif Generic ESP32-P4 rev.3 16M Flash, Tasmota 3904k Code/OTA, 11584k FS",
"upload": {
"arduino": {
"flash_extra_images": [
[
"0x10000",
"tasmota32p4r3-safeboot.bin"
]
]
},
"flash_size": "16MB",
"maximum_ram_size": 768000,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 1500000
},
"url": "https://documentation.espressif.com/esp32-p4_datasheet_en.html",
"vendor": "Espressif"
}

View File

@ -2,14 +2,16 @@
"build": { "build": {
"core": "esp32", "core": "esp32",
"extra_flags": [ "extra_flags": [
"-DARDUINO_TASMOTA -DESP32P4 -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE" "-DARDUINO_TASMOTA -DESP32P4R3 -DESP32_16M -DBOARD_HAS_PSRAM"
], ],
"f_cpu": "360000000L", "f_cpu": "400000000L",
"f_flash": "80000000L", "f_flash": "80000000L",
"f_psram": "200000000L",
"flash_mode": "qio", "flash_mode": "qio",
"mcu": "esp32p4", "mcu": "esp32p4",
"chip_variant": "esp32p4",
"variant": "esp32p4", "variant": "esp32p4",
"partitions": "partitions/esp32_partition_app3904k_fs3392k.csv" "partitions": "partitions/esp32_partition_app3904k_fs11584k.csv"
}, },
"connectivity": [ "connectivity": [
"wifi", "wifi",
@ -24,13 +26,13 @@
"arduino", "arduino",
"espidf" "espidf"
], ],
"name": "Espressif ESP32-P4 Function EV Board", "name": "Espressif Generic ESP32-P4 rev.3 16M Flash, Tasmota 3904k Code/OTA, 11584k FS",
"upload": { "upload": {
"arduino": { "arduino": {
"flash_extra_images": [ "flash_extra_images": [
[ [
"0x10000", "0x10000",
"tasmota32p4-safeboot.bin" "tasmota32p4r3-safeboot.bin"
] ]
] ]
}, },
@ -40,7 +42,6 @@
"require_upload_port": true, "require_upload_port": true,
"speed": 1500000 "speed": 1500000
}, },
"url": "https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32p4/esp32-p4-function-ev-board/index.html", "url": "https://documentation.espressif.com/esp32-p4_datasheet_en.html",
"vendor": "Espressif" "vendor": "Espressif"
} }

View File

@ -2,14 +2,16 @@
"build": { "build": {
"core": "esp32", "core": "esp32",
"extra_flags": [ "extra_flags": [
"-DARDUINO_TASMOTA -DESP32P4 -DBOARD_HAS_PSRAM" "-DARDUINO_TASMOTA -DESP32P4ES -DESP32_16M -DBOARD_HAS_PSRAM"
], ],
"f_cpu": "360000000L", "f_cpu": "360000000L",
"f_flash": "80000000L", "f_flash": "80000000L",
"f_psram": "200000000L",
"flash_mode": "qio", "flash_mode": "qio",
"mcu": "esp32p4", "mcu": "esp32p4",
"chip_variant": "esp32p4_es",
"variant": "esp32p4", "variant": "esp32p4",
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv" "partitions": "partitions/esp32_partition_app3904k_fs11584k.csv"
}, },
"connectivity": [ "connectivity": [
"wifi", "wifi",
@ -24,7 +26,7 @@
"arduino", "arduino",
"espidf" "espidf"
], ],
"name": "Espressif Generic ESP32-P4 >= 4M Flash, Tasmota 2880k Code/OTA, >= 320k FS", "name": "Espressif Generic ESP32-P4 ES 16M Flash, Tasmota 3904k Code/OTA, 11584k FS",
"upload": { "upload": {
"arduino": { "arduino": {
"flash_extra_images": [ "flash_extra_images": [
@ -34,9 +36,9 @@
] ]
] ]
}, },
"flash_size": "4MB", "flash_size": "16MB",
"maximum_ram_size": 768000, "maximum_ram_size": 768000,
"maximum_size": 4194304, "maximum_size": 16777216,
"require_upload_port": true, "require_upload_port": true,
"speed": 1500000 "speed": 1500000
}, },

View File

@ -0,0 +1,26 @@
#######################################
# Syntax Coloring Map for TasmotaDali
# (esp8266 and esp32)
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
TasmotaDali KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
write KEYWORD2
read KEYWORD2
available KEYWORD2
flush KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,17 @@
{
"name": "TasmotaDali",
"version": "1.0.0",
"keywords": [
"serial", "io", "TasmotaDali"
],
"description": "Implementation of DALI bitbang for ESP8266 and ESP32.",
"repository":
{
"type": "git",
"url": "https://github.com/arendst/Tasmota/lib/TasmotaDali"
},
"frameworks": "arduino",
"platforms": [
"espressif8266", "espressif32"
]
}

View File

@ -0,0 +1,9 @@
name=TasmotaDali
version=1.0.0
author=Theo Arends
maintainer=Theo Arends <theo@arends.com>
sentence=Implementation of DALI bitbang for ESP8266 and ESP32.
paragraph=
category=Signal Input/Output
url=
architectures=esp8266,esp32

View File

@ -0,0 +1,974 @@
/*
Dali.h - DALI support for Tasmota
SPDX-FileCopyrightText: 2025 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifndef _DALI_H_
#define _DALI_H_
/*-------------------------------------------------------------------------------------------*\
* DALI Address types - Send as first byte
\*-------------------------------------------------------------------------------------------*/
// Address types - Send as first byte
#define DALI_SHORT_ADDRESS0 0x00 // 0b00000000 0 - First short address
#define DALI_SHORT_ADDRESS1 0x02 // 0b00000010 1 - Next short address
// ...
#define DALI_SHORT_ADDRESS63 0x7E // 0b01111110 63 - Last short address
#define DALI_GROUP_ADDRESS0 0x80 // 0b10000000 0 - First group address
#define DALI_GROUP_ADDRESS1 0x82 // 0b10000010 1 - Next group address
// ...
#define DALI_GROUP_ADDRESS15 0x9E // 0b10011110 15 - Last group address
#define DALI_BROADCAST_DP 0xFE // 0b11111110 254 - Broadcast address
// Address selector bit - Send with first byte
#define DALI_SELECTOR_BIT 0x01 // Mark second byte as standard/extended command
/*-------------------------------------------------------------------------------------------*\
* DALI Commands for IEC62386 part 102 = Control gears - Send as first byte
\*-------------------------------------------------------------------------------------------*/
// Special commands - Send as first byte
#define DALI_102_TERMINATE 0xA1 // 256 - All special mode processes (like DALI_102_INITIALISE) shall be terminated.
#define DALI_102_SET_DTR0 0xA3 // 257 - Stores the data XXXX XXXX to the Data Transfer Register (Data Transfer Register 0).
// This is a broadcast command to set the value of the DTR0 register.
#define DALI_102_INITIALISE 0xA5 // 258 REPEAT - Sets the ballast to the INITIALISE status for 15 minutes. Commands 167 to 189 are enabled only for a ballast in this status.
// This command shall start or re-trigger a timer for 15 minutes; the addressing commands 167..189 shall only be processed within this period.
// All other commands shall still be processed during this period.
// This time period shall be aborted with the "DALI_102_TERMINATE" command.
// Ballasts shall react as follows:
// - if broadcast is True then all ballasts shall react
// - if broadcast is False and address is None then ballasts without a short address shall react
// - if broadcast is False and address is an integer 0..63 then ballasts with the address supplied shall react
#define DALI_102_RANDOMISE 0xA7 // 259 REPEAT - Generates a random address.
// The ballast shall generate a new 24-bit random address. The new random address shall be available within a time period of 100ms.
#define DALI_102_COMPARE 0xA9 // 260 - Is the random address smaller or equal to the search address?
// The ballast shall compare its 24-bit random address with the combined search address stored in SearchAddrH, SearchAddrM and SearchAddrL.
// If its random address is smaller or equal to the search address and the ballast is not withdrawn then the ballast shall generate a query "YES".
#define DALI_102_WITHDRAW 0xAB // 261 - Excludes ballasts for which the random address and search address match from the Compare process.
// The ballast that has a 24-bit random address equal to the combined search address stored in SearchAddrH, SearchAddrM and SearchAddrL
// shall no longer respond to the compare command.
// This ballast shall not be excluded from the initialisation process.
#define DALI_102_PING 0xAD // 262 - DALI-2 Ignores in the ballast.
// Transmitted at 10 minute intervals by single master application controllers (that cannot perform collision detection) to indicate their presence.
// Ignored by control gear.
#define DALI_102_RESERVED263 0xAF // 263 - [Reserved]
#define DALI_102_SEARCHADDRH 0xB1 // 264 - Specifies the higher 8 bits of the search address.
#define DALI_102_SEARCHADDRM 0xB3 // 265 - Specifies the middle 8 bits of the search address.
#define DALI_102_SEARCHADDRL 0xB5 // 266 - Specifies the lower 8 bits of the search address.
#define DALI_102_PROGRAM_SHORT_ADDRESS 0xB7 // 267 - The ballast shall store the received 6-bit address (AAA AAA) as a short address if it is selected.
// It is selected if:
// - the ballast's 24-bit random address is equal to the address in SearchAddrH, SearchAddrM and SearchAddrL
// - physical selection has been detected (the lamp is electrically disconnected after reception of command PhysicalSelection())
// If address is 255 ("MASK") then the short address shall be deleted.
#define DALI_102_VERIFY_SHORT_ADDRESS 0xB9 // 268 - Is the short address AAA AAA?
// The ballast shall give an answer 255 ("YES") if the received short address is equal to its own short address.
#define DALI_102_QUERY_SHORT_ADDRESS 0xBB // 269 - What is the short address of the ballast being selected?
// The ballast shall send the short address if the random address is the same as the search address or the ballast is physically selected.
// The answer will be in the format (address<<1)|1 if the short address is programmed, or "MASK" (0xff) if there is no short address stored.
#define DALI_102_PHYSICAL_SELECTION 0xBD // 270 - not DALI-2 Sets the ballast to Physical Selection Mode and excludes the ballast from the Compare process. (Excluding IEC62386-102ed2.0)
#define DALI_102_RESERVED191 0xBF // 271 - [Reserved]
// Extending special commands - Send as first byte
#define DALI_102_ENABLE_DEVICE_TYPE_X 0xC1 // 272 - Adds the device XXXX (a special device).
// This command shall be sent before an application extended command.
// This command can be processed without the use of the Initialise() command.
// This command shall not be used for device type 0.
#define DALI_102_SET_DTR1 0xC3 // 273 - Stores data XXXX into Data Transfer Register 1.
// This is a broadcast command to set the value of the DTR1 register.
#define DALI_102_SET_DTR2 0xC5 // 274 - Stores data XXXX into Data Transfer Register 2.
// This is a broadcast command to set the value of the DTR2 register.
#define DALI_102_WRITE_MEMORY_LOCATION 0xC7 // 275 - Write data into the specified address of the specified memory bank. (There is BW) (DTR(DTR0)address, DTR1memory bank number)
// This instruction will be ignored if the addressed memory bank is not implemented, or writeEnableState is DISABLED.
// If the instruction is executed then the control gear will write data into the memory location identified by DTR0 in bank DTR1 and return data as an answer.
// If the location is not implemented, above the last accessible location, locked or not writeable, the answer will be NO (-1).
// If the location addressed is below 0xff, then DTR0 will be incremented by 1.
#define DALI_102_WRITE_MEMORY_LOCATION_NO_REPLY 0xC9 // 276 - DALI-2 Write data into the specified address of the specified memory bank. (There is no BW) (DTR(DTR0)address, TR1memory bank number)
// This instruction will be ignored if the addressed memory bank is not implemented, or writeEnableState is DISABLED.
// If the instruction is executed then the control gear will write data into the memory location identified by DTR0 in bank DTR1
// If the location is not implemented, above the last accessible location, locked or not writeable, the answer will be NO (-1).
// If the location addressed is below 0xff, then DTR0 will be incremented by 1.
#define DALI_102_RESERVED277 0xCB // 277 - [Reserved]
// ...
#define DALI_102_RESERVED349 0xFD // 349 - [Reserved]
/*-------------------------------------------------------------------------------------------*\
* DALI Commands for IEC62386 part 102 = Control gears - Send as second byte
\*-------------------------------------------------------------------------------------------*/
// Control commands - Send as second byte without repeat
#define DALI_102_OFF 0x00 // 0 - Turns off lighting (without fade).
#define DALI_102_UP 0x01 // 1 - Increases the lighting control level for 200 ms according to the Fade rate.
// No change if the arc power output is already at the "MAX LEVEL".
// If this command is received again while it is being executed, the execution time shall be re-triggered.
// This command shall only affect ballasts with burning lamps.
// No lamp shall be ignited with this command.
#define DALI_102_DOWN 0x02 // 2 - Decreases the lighting control level for 200 ms according to the Fade rate.
// No change if the arc power output is already at the "MIN LEVEL".
// If this command is received again while it is being executed, the execution time shall be re-triggered.
// Lamp shall not be switched off via this command.
#define DALI_102_STEP_UP 0x03 // 3 - Increments the lighting control level (without fade).
// No change if the arc power output is already at the "MAX LEVEL".
// This command shall only affect ballasts with burning lamps. No lamp shall be ignited with this command.
#define DALI_102_STEP_DOWN 0x04 // 4 - Decrements the lighting control level (without fade).
// No change if the arc power output is already at the "MIN LEVEL".
// Lamps shall not be switched off via this command.
#define DALI_102_RECALL_MAX_LEVEL 0x05 // 5 - Maximizes the lighting control level to "MAX LEVEL" (without fade).
// If the lamp is off it shall be ignited with this command.
#define DALI_102_RECALL_MIN_LEVEL 0x06 // 6 - Minimizes the lighting control level to "MIN LEVEL" (without fade)
// If the lamp is off it shall be ignited with this command.
#define DALI_102_STEP_DOWN_AND_OFF 0x07 // 7 - Decrements the lighting control level and turns off lighting if the level is at the minimum (without fade).
#define DALI_102_ON_AND_STEP_UP 0x08 // 8 - Increments the lighting control level and turns on lighting if lighting is off (with fade).
#define DALI_102_DIRECT_ARC_POWER_CONTROL 0x09 // 9 Deprecated - Enable DAPC Sequence
// Indicates the start of a command iteration of DAPC(level) commands.
// The control gear shall temporarily use a fade time of 200ms while the command iteration is active independent of the actual fade/extended fade time.
// The DAPC sequence shall end if 200ms elapse without the control gear receiving a DAPC(level) command.
// The sequence shall be aborted on reception of an indirect arc power control command.
// value 0 - Off
// value minLevel...maxLevel
// value 255 - Retain current level
#define DALI_102_GO_TO_LAST_ACTIVE_LEVEL 0x0A // 10 - DALI-2 Adjusts the lighting control level to the last light control level according to the Fade time.
#define DALI_102_CONTINUOUS_UP 0x0B // 11 - DALI-2 Dim up using the set fade rate.
// TargetLevel shall be set to maxLevel and a fade shall be started using the set fade rate. The fade shall stop when maxLevel is reached.
#define DALI_102_CONTINUOUS_DOWN 0x0C // 12 - DALI-2 Dim down using the set fade rate.
// targetLevel shall be set to minLevel and a fade shall be started using the set fade rate. The fade shall stop when minLevel is reached.
#define DALI_102_RESERVED13 0x0D // 13 - [Reserved]
#define DALI_102_RESERVED14 0x0E // 14 - [Reserved]
#define DALI_102_RESERVED15 0x0F // 15 - [Reserved]
#define DALI_102_GO_TO_SCENE0 0x10 // 16 - Adjusts the lighting control level for Scene XXXX according to the fade time.
// If the ballast does not belong to this scene, the arc power level remains unchanged.
// If the lamp is off, it shall be ignited with this command.
// If the value stored for this scene is zero and the lamp is lit then the lamp shall be switched off by this command after the fade time.
// ...
#define DALI_102_GO_TO_SCENE15 0x1F // 31 - Adjusts the lighting control level for Scene XXXX according to the fade time.
// Configuration commands - Send as second byte with repeat
#define DALI_102_RESET 0x20 // 32 - The variables in the persistent memory shall be changed to their reset values.
// It is not guaranteed that any commands will be received properly within the next 300ms by a ballast acting on this command.
#define DALI_102_STORE_ACTUAL_LEVEL_IN_DTR0 0x21 // 33 - Store actual arc power level in the DTR (DTR0) without changing the current light intensity.
#define DALI_102_SAVE_PERSISTENT_VARIABLES 0x22 // 34 - DALI-2 Saves a variable in nonvolatile memory (NVM).
// All variables identified as non-volatile shall be stored to non-volatile memory.
// The control gear might not react to commands for up to 300ms after reception of this command.
// This command is recommended to be used typically after commissioning.
#define DALI_102_SET_OPERATING_MODE 0x23 // 35 - DALI-2 Set operatingMode to DTR0.
// If DTR0 does not correspond to an implemented operating mode, the command shall be ignored.
#define DALI_102_RESET_MEMORY_BANK 0x24 // 36 - DALI-2 Reset Memory Bank according to DTR0
// If DTR0 = 0 then all implemented and unlocked memory banks except memory bank 0 shall be reset.
// In all other cases, the memory bank identified by DTR0 will be reset provided it is implemented and unlocked.
// This command may cause control gear to react improperly to commands for up to 10s.
#define DALI_102_IDENTIFY_DEVICE 0x25 // 37 - DALI-2 Starts the identification state of the device. Start or restart a 10s timer.
// While the timer is running the device will run a procedure to enable an observer to distinguish the device from other devices in which it is not running.
// This procedure is manufacturer-dependent.
// Identification will be stopped immediately upon reception of any command other than Initialise, RecallMinLevel, RecallMaxLevel or IdentifyDevice.
#define DALI_102_RESERVED38 0x26 // 38 - [Reserved]
// ...
#define DALI_102_RESERVED41 0x29 // 41 - [Reserved]
#define DALI_102_SET_MAX_LEVEL 0x2A // 42 - Specifies the DTR data as the maximum lighting control level (maxLevel or "MAX LEVEL").
#define DALI_102_SET_MIN_LEVEL 0x2B // 43 - Specifies the DTR data as the minimum lighting control level (minLevel or "MIN LEVEL").
// If this value is lower than the "PHYSICAL MIN LEVEL" of the ballast, then store the "PHYSICAL MIN LEVEL" as the new "MIN LEVEL".
#define DALI_102_SET_SYSTEM_FAILURE_LEVEL 0x2C // 44 - Specifies the DTR data as the "SYSTEM FAILURE LEVEL".
#define DALI_102_SET_POWER_ON_LEVEL 0x2D // 45 - Specifies the DTR data as the "POWER ON LEVEL".
#define DALI_102_SET_FADE_TIME 0x2E // 46 - Specifies the DTR data as the "FADE TIME" in seconds.
// Formula T=0.5(sqrt(pow(2,DTR))) seconds
// With DTR0 in the range 1..15. If DTR0 is 0 then the extended fade time will be used.
// The fade time specifies the time for changing the arc power level from the actual level to the requested level.
// In the case of lamp off, the preheat and ignition time is not included in the fade time.
// The new fade time will be used after the reception of the next arc power command.
// If a new fade time is set during a running fade process, the running fade process is not affected.
// value 0 - < 0.707 s
// value 1 - 0.707 s
// value 2 - 1.000 s
// value 3 - 1.414 s
// value 4 - 2.000 s
// value 5 - 2.828 s
// value 6 - 4.000 s
// value 7 - 5.657 s
// value 8 - 8.000 s
// value 9 - 11.314 s
// value 10 - 16.000 s
// value 11 - 22.627 s
// value 12 - 32.000 s
// value 13 - 45.255 s
// value 14 - 64.000 s
// value 15 - 90.510 s
#define DALI_102_SET_FADE_RATE 0x2F // 47 - Specifies the DTR data as the "FADE RATE" in steps per second.
// Formula F = 506/(sqrt(pow(2,DTR))) steps/s
// With DTR in the range 1..15.
// The new fade time will be used after the reception of the next arc power command.
// If a new fade time is set during a running fade process, the running fade process is not affected.
// value 0 - not permitted
// value 1 - 357.796 steps/s
// value 2 - 253.000 steps/s
// value 3 - 178.898 steps/s
// value 4 - 126.500 steps/s
// value 5 - 89.449 steps/s
// value 6 - 63.250 steps/s
// value 7 - 44.725 steps/s
// value 8 - 31.625 steps/s
// value 9 - 22.362 steps/s
// value 10 - 15.813 steps/s
// value 11 - 11.181 steps/s
// value 12 - 7.906 steps/s
// value 13 - 5.591 steps/s
// value 14 - 3.953 steps/s
// value 15 - 2.795 steps/s
#define DALI_102_SET_EXTENDED_FADE_TIME 0x30 // 48 - DALI-2 Specifies the DTR data as the Extended Fade Time.
// If DTR0 > 0x4f then extendedFadeTimeBase and extendedFadeTimeMultiplier are both set to 0.
// Otherwise, extendedFadeTimeBase will be set to DTR0[3:0] and extendedFadeTimeMultiplier will be set to DTR0[6:4].
// If a new fade time is set during a running fade process, the running fade process is not affected.
// bit 0..3 - value 0..15 (E_DALIExtendedFadeTimeBase)
// bit 4..7 - 0 = Disabled (E_DALIExtendeFadeTimeMultiplier)
// 1 = Multiplier100ms
// 2 = Multiplier1s
// 3 = Multiplier10s
// 4 = Multiplier1min
#define DALI_102_RESERVED49 0x31 // 49 - [Reserved]
// ...
#define DALI_102_RESERVED63 0x3F // 63 - [Reserved]
#define DALI_102_SET_SCENE0 0x40 // 64 - Specifies the DTR data as Scene XXXX.
// The value 255 ("MASK") removes the ballast from the scene.
// ...
#define DALI_102_SET_SCENE15 0x4F // 79 - Specifies the DTR data as Scene XXXX.
#define DALI_102_REMOVE_FROM_SCENE0 0x50 // 80 - Deletes the Scene XXXX setting.
// This stores 255 ("MASK") in the specified scene register.
// ...
#define DALI_102_REMOVE_FROM_SCENE15 0x5F // 95 - Deletes the Scene XXXX setting.
#define DALI_102_ADD_TO_GROUP0 0x60 // 96 - Adds the ballast to Group XXXX.
// ...
#define DALI_102_ADD_TO_GROUP15 0x6F // 111 - Adds the ballast to Group XXXX.
#define DALI_102_REMOVE_FROM_GROUP0 0x70 // 112 - Deletes the ballast from Group XXXX.
// ...
#define DALI_102_REMOVE_FROM_GROUP15 0x7F // 127 - Deletes the ballast from Group XXXX.
#define DALI_102_SET_SHORT_ADDRESS 0x80 // 128 - Specifies the DTR data as a Short Address.
// The DTR must contain either:
// - (address<<1)|1 (i.e. 0AAAAAA1) to set a short address
// - 255 (i.e. 11111111) to remove the short address
#define DALI_102_ENABLE_WRITE_MEMORY 0x81 // 129 - Allows writing of the memory bank.
// writeEnableState shall be set to ENABLED.
// NB there is no command to explicitly disable memory write access; any command that is not directly involved with writing to
// memory banks will set writeEnableState to DISABLED.
// The commands that do not set writeEnableState to DISABLED are:
// - WriteMemoryLocation
// - WriteMemoryLocationNoReply
// - DTR0
// - DTR1
// - DTR2
// - QueryContentDTR0
// - QueryContentDTR1
// - QueryContentDTR2
#define DALI_102_RESERVED130 0x82 // 130 - [Reserved]
// ...
#define DALI_102_RESERVED143 0x8F // 143 - [Reserved]
// Query commands - Send as second byte
#define DALI_102_QUERY_STATUS 0x90 // 144 - Returns "STATUS INFORMATION"
// bit 0 - Status of the control gear. 0: OK.
// bit 1 - Lamp failure. 0: OK.
// bit 2 - Lamp power on. 0: OFF.
// bit 3 - Limit value error. 0: the most recently requested lamp power was either between MIN LEVEL and MAX LEVEL or was OFF.
// bit 4 - Fading completed: 0: fading finished. 1: fading active.
// bit 5 - Reset status. 0: no.
// bit 6 - Short address missing. 0: no.
// bit 7 - Power supply fault. 0: No. A reset or a lamp power control command has been received since the last switch-on.
#define DALI_102_QUERY_CONTROL_GEAR_PRESENT 0x91 // 145 - Returns bit0 - Is there a ballast that can communicate?
#define DALI_102_QUERY_LAMP_FAILURE 0x92 // 146 - Returns bit1 - Is there a lamp problem?
#define DALI_102_QUERY_LAMP_POWER_ON 0x93 // 147 - Returns bit2 - Is a lamp on?
#define DALI_102_QUERY_LIMIT_ERROR 0x94 // 148 - Returns bit3 - Is the specified lighting control level out of the range from the minimum to the maximum values?
#define DALI_102_QUERY_RESET_STATE 0x95 // 149 - Returns bit5 - Is the ballast in 'RESET STATE'?
#define DALI_102_QUERY_MISSING_SHORT_ADDRESS 0x96 // 150 - Returns bit6 - Does the ballast not have a short address?
#define DALI_102_QUERY_VERSION_NUMBER 0x97 // 151 - What is the corresponding IEC standard number?
// The high four bits of the answer represent the version number of the standard.
// IEC-60929 is version number 0; the 2009 version of IEC-62386 is version number 1.
// As of the 2014 version of IEC-62386, the answer shall be the content of memory bank 0 location 0x16.
// bit 0..3 - nMinorVersion
// bit 4..7 - nMajorVersion
#define DALI_102_QUERY_CONTENT_DTR0 0x98 // 152 - What is the DTR content?
#define DALI_102_QUERY_DEVICE_TYPE 0x99 // 153 - What is the device type?
// The device type affects which application extended commands the device will respond to.
// XXX this is updated for IEC 62386-102 and interacts with QueryNextDeviceType. In this case:
// - If the device does not implement any part 2xx device type then the response will be 254;
// - If the device implements one part 2xx device type then the response will be the device type number;
// - If the device implements multiple part 2xx device types then the response will be MASK (0xff).
// value 0 - Part 201: Standard device
// value 1 - Part 202: Device for emergency lighting.
// value 2 - Part 203: Device for discharge lamps.
// value 3 - Part 204: Device for low-voltage halogen lamps.
// value 4 - Part 205: Device for dimming incandescent lamps.
// value 5 - Part 206: Device for converting digital signals into DC signals.
// value 6 - Part 207: Device for light emitting diodes (LEDs).
// value 7 - Part 208: Device for switching functions.
// value 8 - Part 209: Device for color/color temperature control.
// value 9 - Part 210: Sequencer
// value 15 - Part 216: Load referencing
// value 16 - Part 217: Thermal gear protection
// value 17 - Part 218: Dimming curve selection
// value 19 - Part 220: Centrally supplied emergency operation
// value 20 - Part 221: Load shedding
// value 21 - Part 222: Thermal lamp protection
// value 23 - Part 224: Non-replaceable light sources
// value 49 - Part 250: Devices with integrated DALI bus power supply
// value 50 - Part 251: Further information and parameters for DALI control gears in memory bank 1
// value 51 - Part 252: Further parameters for the creation of an energy report
// value 52 - Part 253: Further parameters with diagnostic and maintenance information for DALI control gears
// value 53 - Part 254: Extended information for DALI control gears for emergency lighting
// value 254 - None or end
// value 255 - Multiple
#define DALI_102_QUERY_PHYSICAL_MINIMUM_LEVEL 0x9A // 154 - Return the physical minimum level for this device.
#define DALI_102_QUERY_POWER_FAILURE 0x9B // 155 - Ask whether the device has not received a "RESET" or arc power control command since the last power-on.
#define DALI_102_QUERY_CONTENT_DTR1 0x9C // 156 - Return the contents of DTR1.
#define DALI_102_QUERY_CONTENT_DTR2 0x9D // 157 - Return the contents of DTR2.
#define DALI_102_QUERY_OPERATING_MODE 0x9E // 158 - DALI-2 What is the Operating Mode?
#define DALI_102_QUERY_LIGHT_SOURCE_TYPE 0x9F // 159 - DALI-2 What is the Light source type (E_DALILightSourceType)
// "unknown" will typically be used in case of signal conversion, for example to 1-10v dimming
// "none" will be used where no light source is connected, for example a relay
// When the response is "multiple" then the light source types shall be placed into DTR0, DTR1 and DTR2.
// If there are exactly two light source types, DTR2 shall be "none". If there are more than three then DTR2 shall be MASK.
// value 0 = LowPressureFluorescent
// value 2 = HID
// value 3 = LowVoltageHalogen
// value 4 = Incandescent
// value 6 = LED
// value 7 = OLED
// value 252 = Other
// value 253 = Unknown (typicaly signal conversion like 1-10V dimming)
// value 254 = NoLightSource
// value 255 = Multiple (Type in DTR0, DTR1 and DTR2 (= none if two types, = 255 if > 3))
#define DALI_102_QUERY_ACTUAL_LEVEL 0xA0 // 160 - What is the "ACTUAL LEVEL" (the current lighting control level)?
// During preheating and if a lamp error occurs the answer will be 0xff ("MASK").
#define DALI_102_QUERY_MAX_LEVEL 0xA1 // 161 - What is the maximum lighting "MAX LEVEL" control level?
#define DALI_102_QUERY_MIN_LEVEL 0xA2 // 162 - What is the minimum lighting "MIN LEVEL" control level?
#define DALI_102_QUERY_POWER_ON_LEVEL 0xA3 // 163 - What is the "POWER ON LEVEL" (the lighting control level when the power is turned on)?
#define DALI_102_QUERY_SYSTEM_FAILURE_LEVEL 0xA4 // 164 - What is the "SYSTEM FAILURE LEVEL" (the lighting control level when a failure occurs)?
#define DALI_102_QUERY_FADE_TIME_FADE_RATE 0xA5 // 165 - What are the Fade time and Fade rate? (see 47 and 48)
// The fade time set by "DALI_102_SET_FADE_TIME" is in the upper four bits of the response.
// The fade rate set by "DALI_102_SET_FADE_RATE" is in the lower four bits of the response.
#define DALI_102_QUERY_MANUFACTURER_SPECIFIC_MODE 0xA6 // 166 - DALI-2 Query Manufacturer Specific Mode
// The answer shall be YES when operatingMode is in the range 0x80..0xff and NO otherwise.
#define DALI_102_QUERY_NEXT_DEVICE_TYPE 0xA7 // 167 - DALI-2 What is the next Device Type?
// If directly preceded by DALI_102_QUERY_DEVICE_TYPE and more than one device type is supported, returns the first and lowest device type number.
// If directly preceded by DALI_102_QUERY_NEXT_DEVICE_TYPE and not all device types have been reported, returns the next lowest device type number.
// If directly preceded by DALI_102_QUERY_NEXT_DEVICE_TYPE and all device types have been reported, returns 254.
// In all other cases returns NO (no response).
// Multi-master transmitters shall send the sequence DALI_102_QUERY_DEVICE_TYPE,DALI_102_QUERY_NEXT_DEVICE_TYPE,... as a transaction.
#define DALI_102_QUERY_EXTENDED_FADE_TIME 0xA8 // 168 - DALI-2 What is the Extended Fade Time?
// bit 0..3 - value 0..15 (E_DALIExtendedFadeTimeBase)
// bit 4..7 - 0 = Disabled (E_DALIExtendeFadeTimeMultiplier)
// 1 = Multiplier100ms
// 2 = Multiplier1s
// 3 = Multiplier10s
// 4 = Multiplier1min
#define DALI_102_QUERY_CONTROL_GEAR_FAILURE 0xA9 // 169 - DALI-2 Does a ballast have the abnormality (0 = No, 1 = Yes)?
// The answer shall be YES if controlGearFailure is TRUE and NO otherwise.
#define DALI_102_RESERVED170 0xAA // 170 - [Reserved]
// ...
#define DALI_102_RESERVED175 0xAF // 175 - [Reserved]
#define DALI_102_QUERY_SCENE0_LEVEL 0xB0 // 176 - Return the level set for scene 0, or 255 ("MASK") if the device is not part of the scene.
// ...
#define DALI_102_QUERY_SCENE15_LEVEL 0xBF // 191 - Return the level set for scene 15, or 255 ("MASK") if the device is not part of the scene.
#define DALI_102_QUERY_GROUPS_0_7 0xC0 // 192 - Return the device membership of groups 0-7 with group 0 in the least-significant bit of the response.
#define DALI_102_QUERY_GROUPS_8_15 0xC1 // 193 - Return the device membership of groups 8-15 with group 8 in the least-significant bit of the response.
#define DALI_102_QUERY_RANDOM_ADDRESS_H 0xC2 // 194 - What are the high 8 bits of the random address?
#define DALI_102_QUERY_RANDOM_ADDRESS_M 0xC3 // 195 - What are the middle 8 bits of the random address?
#define DALI_102_QUERY_RANDOM_ADDRESS_L 0xC4 // 196 - What are the low 8 bits of the random address?
#define DALI_102_READ_MEMORY_LOCATION 0xC5 // 197 - What is the memory location content (Uses DTR0 and DTR1)
// The query is ignored if the addressed memory bank is not implemented.
// If executed, the answer will be the content of the memory location identified by DTR0 in bank DTR1.
// If the addressed location is below 0xff, then DTR0 is incremented by 1.
#define DALI_102_RESERVED198 0xC6 // 198 - [Reserved]
// ...
#define DALI_102_RESERVED223 0xDF // 223 - [Reserved]
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 201 = DT0 - Send as second byte
* Standard device like fluorescent lamps
\*-------------------------------------------------------------------------------------------*/
#define DALI_201_DEVICE_TYPE 0
// Application extended query commands - Send as second byte
#define DALI_201_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 202 = DT1 - Send as second byte
* Device for emergency lighting
\*-------------------------------------------------------------------------------------------*/
#define DALI_202_DEVICE_TYPE 1
// Application extended query commands - Send as second byte
#define DALI_202_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 203 = DT2 - Send as second byte
* Device for discharge lamps excluding fluorescent lamps
\*-------------------------------------------------------------------------------------------*/
#define DALI_203_DEVICE_TYPE 2
// Application extended query commands - Send as second byte
#define DALI_203_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 204 = DT3 - Send as second byte
* Device for low-voltage halogen lamps
\*-------------------------------------------------------------------------------------------*/
#define DALI_204_DEVICE_TYPE 3
// Application extended query commands - Send as second byte
#define DALI_204_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 205 = DT4 - Send as second byte
* Device for dimming incandescent lamps
\*-------------------------------------------------------------------------------------------*/
#define DALI_205_DEVICE_TYPE 4
// Application extended configuration commands - Send as second byte with repeat
#define DALI_205_REFERENCE_SYSTEM_POWER 0xE0 // 224 - Reference System Power
// The control gear shall measure and store system power levels in order to detect load increase or load decrease.
// The measurement may take up to 15 minutes.
// Measured power levels will be stored in non-volatile memory, Commands received during the measuring period will be ignored except query commands and Terminate.
// The process will be aborted if DALI_102_TERMINATE is received.
#define DALI_205_SELECT_DIMMING_CURVE 0xE1 // 225 - Select Dimming Curve
// If DTR0 is 0 then selects the standard logarithmic curve
// If DTR0 is 1 then selects a linear dimming curve
// Other values of DTR0 are reserved and will not change the dimming curve.
// The setting is stored in non-volatile memory and is not cleared by the Reset command.
#define DALI_205_RESERVED226 0xE2 // 226 - [Reserved]
// ...
#define DALI_205_RESERVED237 0xED // 237 - [Reserved]
// Application extended query commands - Send as second byte
#define DALI_205_QUERY_DIMMING_CURVE 0xEE // 238 - Returns Dimming curve in use
// value 0 = Standard - Standard logarithmic dimming curve
// value 1 = Linear - Linear dimming curve
// value 255 = Unknown
#define DALI_205_QUERY_DIMMER_STATUS 0xEF // 239 - Returns dimmer status
// bit 0 - leading edge mode running
// bit 1 - trailing edge mode running
// bit 2 - reference measurement running
// bit 3 - None
// bit 4 - non-logarithmic dimming curve active
// bit 5..7 - Not used
#define DALI_205_QUERY_FEATURES 0xF0 // 240 - Return feature bytes. There are three bytes of feature information.
// Byte 1 is the reply to this command.
// bit 0 - load over-current shutdown can be queried
// bit 1 - open circuit detection can be queried
// bit 2 - detection of load decrease can be queried
// bit 3 - detection of load increase can be queried
// bit 4 - None
// bit 5 - thermal shutdown can be queried
// bit 6 - thermal overload with output level reduction can be queried
// bit 7 - physical selection supported
// Byte 2 is transferred to DTR0.
// bit 0 - temperature can be queried
// bit 1 - supply voltage can be queried
// bit 2 - supply frequency can be queried
// bit 3 - load voltage can be queried
// bit 4 - load current can be queried
// bit 5 - real load power can be queried
// bit 6 - load rating can be queried
// bit 7 - load current overload with output level reduction can be queried
// Byte 3 is transferred to DTR1.
// bit 0..1 - Dimming method
// value 0 = leading & trailing
// value 1 = leading only
// value 2 = trailing only
// value 3 = sine wave
// bit 2 - None
// bit 3 - non-logarithmic dimming curve can be selected
// bit 4..6 - None
// bit 7 - load unsuitable can be queried
#define DALI_205_QUERY_FAILURE_STATUS 0xF1 // 241 - Returns failure status. There are two bytes of failure information.
// Responds with byte 1 of the failure status information, and transfers byte 2 of the failure status into DTR1.
// Failure states which cause output level reduction shall only be reset by re-powering the control gear or by any command that causes the output to turn off.
// Failure states which cause shutdown shall only be reset by re-powering the control gear or using an optional reset switch on the control gear.
// Byte 1 is the reply to this command.
// bit 0 - load over-current shutdown
// bit 1 - open circuit detected
// bit 2 - load decrease detected
// bit 3 - load increase detected
// bit 4 - None
// bit 5 - thermal shutdown
// bit 6 - thermal overload with output level reduction
// bit 7 - reference measurement failed
// Byte 2 is transferred to DTR1.
// bit 0 - load not suitable for selected dimming method
// bit 1 - supply voltage out of limits
// bit 2 - supply frequency out of limits
// bit 3 - load voltage out of limits
// bit 4 - load current overload with output level reduction
// bit 5..7 - Not used
#define DALI_205_QUERY_DIMMER_TEMPERATURE 0xF2 // 242 - Returns the temperature of the dimmer with 1 degC resolution.
// Values of 0 to 254 represent temperatures of -40C to +214C.
// Below -40C, 0 is returned. Above 214C, 254 is returned. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_RMS_SUPPLY_VOLTAGE 0xF3 // 243 - Returns the measured supply voltage.
// Values of 0 to 254 represent 0V to 508V RMS.
// Voltages above 508V RMS shall be returned as 254. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_SUPPLY_FREQUENCY 0xF4 // 244 - Returns the supply frequency with 0.5Hz resolution.
// Values of 0 to 254 represent 0Hz to 127Hz.
// Frequencies above 127Hz are returned as 254. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_RMS_LOAD_VOLTAGE 0xF5 // 245 - Returns the measured load voltage.
// Values of 0 to 254 represent 0V to 508V RMS.
// Voltages above 508V RMS shall be returned as 254. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_RMS_LOAD_CURRENT 0xF6 // 246 - Returns the measured load current as a percentage of the rated load current given by the answer to QueryLoadRating, with 0.5% resolution.
// Values of 0 to 254 represent 0% to 127%.
// Higher currents shall be returned as 254. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_REAL_LOAD_POWER 0xF7 // 247 - Returns the high byte of the real power supplied to the load. The low byte is transferred to DTR0.
// Values of 0 to 65534 represent powers from 0W to 16383.5W with a resolution of 0.25W.
// Powers above this range are returned as 65534. A value of 65535 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_LOAD_RATING 0xF8 // 248 - Returns the maximum load current rating with 150mA resolution.
// Values of 0 to 254 represent 0A to 38.1A RMS.
// Currents above 38.1A shall be returned as 254. A value of 255 means "unknown".
// Control gear without this feature shall not react.
#define DALI_205_QUERY_REFERENCE_RUNNING 0xF9 // 249 - Asks if the ReferenceSystemPower measurement is running.
#define DALI_205_QUERY_REFERENCE_FAILED 0xFA // 250 - Asks if the reference measurement started by ReferenceSystemPower failed.
#define DALI_205_RESERVED251 0xFB // 251 - [Reserved]
// ...
#define DALI_205_RESERVED254 0xFE // 254 - [Reserved]
#define DALI_205_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 206 = DT5 - Send as second byte
* Device for converting digital signales into DC signals
\*-------------------------------------------------------------------------------------------*/
#define DALI_206_DEVICE_TYPE 5
// Application extended query commands - Send as second byte
#define DALI_206_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 207 = DT6 - Send as second byte
* Device for Light Emitting Diodes (LEDs)
* Variables
* Name Reset value Scope Size Comment
* --------------- --------------------------- ------------------------------------------- ------ ---------
* minFastFadeTime No change E_DALIFastFadeTime.T100ms 1 byte Read only
* ...
* E_DALIFastFadeTime.T700ms
* fastFadeTime E_DALIFastFadeTime.Disabled E_DALIFastFadeTime.Disabled,minFastFadeTime 1 byte
* ...
* E_DALIFastFadeTime.T700ms
* controlGearType No change 0...255 1 byte Read only
* features No change 0...255 1 byte Read only
* failureStatus No change 0...255 1 byte Read only
* dimmingCurve E_DALIDimmingCurve.Standard E_DALIDimmingCurve.Standard, 1 byte
* E_DALIDimmingCurve.Linear
\*-------------------------------------------------------------------------------------------*/
#define DALI_207_DEVICE_TYPE 6
// Application extended configuration commands - Send as second byte with repeat
#define DALI_207_REFERENCE_SYSTEM_POWER 0xE0 // 224 - The DALI control gear measures and stores the performance level of the system, in order to detect load increase and decrease.
#define DALI_207_ENABLE_CURRENT_PROTECTOR 0xE1 // 225 - Enables the current protection (deleted 2018).
#define DALI_207_DISABLE_CURRENT_PROTECTOR 0xE2 // 226 - Disables the current protection (deleted 2018).
#define DALI_207_SELECT_DIMMING_CURVE 0xE3 // 227 - Selects Dimming curve (see 238).
// If DTR0 is 0 then selects the standard logarithmic curve
// If DTR0 is 1 then selects a linear dimming curve
// Other values of DTR0 are reserved and will not change the dimming curve.
// The setting is stored in non-volatile memory and is not cleared by the Reset command.
#define DALI_207_SET_FAST_FADE_TIME 0xE4 // 228 - Sets the DTR0 of the data as Fast Fade Time (see 253).
#define DALI_207_RESERVED229 0xE5 // 229 - [Reserved]
// ...
#define DALI_207_RESERVED236 0xEC // 236 - [Reserved]
// Application extended query commands - Send as second byte
#define DALI_207_QUERY_GEAR_TYPE 0xED // 237 - Returns GEAR TYPE
// bit 0 - LED power supply integrated
// bit 1 - LED module integrated
// bit 2 - AC supply possible
// bit 3 - DC supply possible
// bit 4..7 - Reserve
#define DALI_207_QUERY_DIMMING_CURVE 0xEE // 238 - Returns Dimming curve in use (E_DALIDimmingCurve)
// value 0 = Standard - Standard logarithmic dimming curve
// value 1 = Linear - Linear dimming curve
// value 255 = Unknown
#define DALI_207_QUERY_POSSIBLE_OPERATING_MODE 0xEF // 239 - Returns POSSIBLE OPERATING MODE (deleted 2018)
// bit 0 - PWM mode possible
// bit 1 - AM mode possible
// bit 2 - Output is current controlled
// bit 3 - High current pulse mode
// bit 4..7 - Reserve
#define DALI_207_QUERY_FEATURES 0xF0 // 240 - Returns FEATURES
// bit 0 - Short circuit detection can be queried
// bit 1 - Open circuit detection can be queried
// bit 2 - Detection of the load decrease can be queried
// bit 3 - Detection of the load increase can be queried
// bit 4 - Current protector is implemented and can be queried
// bit 5 - Thermal shutdown can be queried
// bit 6 - Reduction of the output level due to thermal overload can be queried
// bit 7 - Physical selection supported
#define DALI_207_QUERY_FAILURE_STATUS 0xF1 // 241 - Returns FAILURE STATUS
// bit 0 - Short circuit
// bit 1 - Open circuit
// bit 2 - Load decrease
// bit 3 - Load increase
// bit 4 - Current protector active
// bit 5 - Thermal shutdown
// bit 6 - Thermal overload with output level reduction
// bit 7 - Reference measurement failed
#define DALI_207_QUERY_SHORT_CIRCUIT 0xF2 // 242 - Returns bit0 short circuit of FAILURE STATUS (deleted 2018)
#define DALI_207_QUERY_OPEN_CIRCUIT 0xF3 // 243 - Returns bit1 open circuit of FAILURE STATUS (deleted 2018)
#define DALI_207_QUERY_LOAD_DECREASE 0xF4 // 244 - Returns bit2 load decrease of FAILURE STATUS
#define DALI_207_QUERY_LOAD_INDREASE 0xF5 // 245 - Returns bit3 load increase ofFAILURE STATUS
#define DALI_207_QUERY_CURRENT_PROTECTOR_ACTIVE 0xF6 // 246 - Returns bit4 current protector active of FAILURE STATUS (deleted 2018)
#define DALI_207_QUERY_THERMAL_SHUTDOWN 0xF7 // 247 - Returns bit5 thermal shut down of FAILURE STATUS
#define DALI_207_QUERY_THERMAL_OVERLOAD 0xF8 // 248 - Returns bit6 thermal overload with light level reduction of FAILURE STATUS
#define DALI_207_QUERY_REFERENCE_RUNNING 0xF9 // 249 - Returns whether Reference System Power is in operation.
#define DALI_207_QUERY_REFERENCE_MEASURMENT_FAILED 0xFA // 250 - Returns bit7 reference measurement failed of FAILURE STATUS
#define DALI_207_QUERY_CURRENT_PROTECTOR_ENABLE 0xFB // 251 - Returns state of Curent protector (deleted 2018)
#define DALI_207_QUERY_OPERATING_MODE2 0xFC // 252 - Returns OPERATING MODE (deleted 2018)
// bit 0 - PWM mode active
// bit 1 - AM mode active
// bit 2 - Output is current controlled
// bit 3 - High current pulse mode is active
// bit 4 - non-logarithmic dimming curve active
// bit 5..7 - Reserve
#define DALI_207_QUERY_FAST_FADE_TIME 0xFD // 253 - Returns set Fast fade time (E_DALIFastFadeTime)
// value 0 = Disabled
// value 4 = T100ms
// value 8 = T200ms
// value 9 = T225ms
// value 12 = T300ms
// value 16 = T400ms
// value 20 = T500ms
// value 24 = T600ms
// value 27 = T700ms
// value 255 = Unknown
#define DALI_207_QUERY_MIN_FAST_FADE_TIME 0xFE // 254 - Returns set Minimum fast fade time (variable minFastFadeTime)
#define DALI_207_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 208 = DT7 - Send as second byte
* Device for switching functions
\*-------------------------------------------------------------------------------------------*/
#define DALI_208_DEVICE_TYPE 7
// Application extended query commands - Send as second byte
#define DALI_208_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 209 = DT8 - Send as second byte
* Device for controlling colour and colour temperature
* Tc is expressed in MIREK and can vary from 1 Mirek (1000000 Kelvin) to 65534 Mirek (15.26 Kelvin)
* RGBWAF - Red, Green, Blue, White, Amber, Freely selectable color
\*-------------------------------------------------------------------------------------------*/
#define DALI_209_DEVICE_TYPE 8
// Application extended control commands - Send as second byte with repeat
#define DALI_209_SET_TEMPORARY_X_COORDINATE 0xE0 // 224 - Set temporary x-COORDINATE (Uses DTR0 (LSB) and DTR1 (MSB))
// The value is expressed in units of 1/65536. The maximum x-COORDINATE value is 0,99997
#define DALI_209_SET_TEMPORARY_Y_COORDINATE 0xE1 // 225 - Set temporary y-COORDINATE (Uses DTR0 (LSB) and DTR1 (MSB))
// The value is expressed in units of 1/65536. The maximum y-COORDINATE value is 0,99997.
#define DALI_209_ACTIVATE 0xE2 // 226 - Transfer buffered values of temporary registers to the lamp.
// It ends a running cross-fade and starts a new cross-fade for the respective color/color temperature.
#define DALI_209_X_COORDINATE_STEP_UP 0xE3 // 227 - The x-COORDINATE shall be set 256 steps higher (256/65536) immediately without fading.
// This command shall only be executed when Colour type xy-coordinate active bit, bit 4 of the COLOUR STATUS, is set.
// If the new colour value does not correspond to a colour attainable by the control gear, this shall be indicated by
// the xy-coordinate colour point out of range bit, bit 0 of the COLOUR STATUS.
#define DALI_209_X_COORDINATE_STEP_DOWN 0xE4 // 228 - The x-COORDINATE shall be set 256 steps lower (256/65536) immediately without fading.
// This command shall only be executed when Colour type xy-coordinate active bit, bit 4 of the COLOUR STATUS, is set.
// If the new colour value does not correspond to a colour attainable by the control gear, this shall be indicated by
// the xy-coordinate colour point out of range bit, bit 0 of the COLOUR STATUS.
#define DALI_209_Y_COORDINATE_STEP_UP 0xE5 // 229 - The y-COORDINATE shall be set 256 steps higher (256/65536) immediately without fading.
// This command shall only be executed when Colour type xy-coordinate active bit, bit 4 of the COLOUR STATUS, is set.
// If the new colour value does not correspond to a colour attainable by the control gear, this shall be indicated by
// the xy-coordinate colour point out of range bit, bit 0 of the COLOUR STATUS.
#define DALI_209_Y_COORDINATE_STEP_DOWN 0xE6 // 230 - The y-COORDINATE shall be set 256 steps lower (256/65536) immediately without fading.
// This command shall only be executed when Colour type xy-coordinate active bit, bit 4 of the COLOUR STATUS, is set.
// If the new colour value does not correspond to a colour attainable by the control gear, this shall be indicated by
// the xy-coordinate colour point out of range bit, bit 0 of the COLOUR STATUS.
#define DALI_209_SET_TEMPORARY_COLOUR_TEMP_TC 0xE7 // 231 - Set temporary colour temperature Tc (Uses DTR0 (LSB) and DTR1 (MSB))
// The value is expressed in units of 1 Mirek.
// A value of 0 for Tc shall be ignored and therefore not stored in memory.
// NOTE Colour temperature TC can vary from 1 Mirek (1 000 000 K) to 65 534 Mirek (15,26 K).
#define DALI_209_COLOUR_TEMP_TC_STEP_COOLER 0xE8 // 232 - The COLOUR TEMPERATURE TC shall be set 1 Mirek lower immediately without fading.
// This command shall only be executed when Colour type colour temperature TC active bit, bit 5 of the COLOUR STATUS is set.
// No change shall occur if COLOUR TEMPERATURE TC is already at COLOUR TEMPERATURE TC COOLEST.
// If the new colour value does not correspond to a colour temperature attainable by the control gear, this shall be indicated by
// the Colour temperature TC out of range bit, bit 1 of the COLOUR STATUS.
#define DALI_209_COLOUR_TEMP_TC_STEP_WARMER 0xE9 // 233 - The COLOUR TEMPERATURE TC shall be set 1 Mirek higher immediately without fading.
// This command shall only be executed when Colour type colour temperature TC active bit, bit 5 of the COLOUR STATUS is set.
// No change shall occur if COLOUR TEMPERATURE TC is already at COLOUR TEMPERATURE TC WARMEST.
// If the new colour value does not correspond to a colour temperature attainable by the control gear, this shall be indicated by
// the Colour temperature TC out of range bit, bit 1 of the COLOUR STATUS.
#define DALI_209_SET_TEMPORARY_PRIMARY_N_DIMLEVEL 0xEA // 234 Deprecated - Set temporary primary N dimlevel (Uses DTR0 (LSB), DTR1 (MSB) and DTR2 (N))
// The value is expressed in units of 1/65536.
// The maximum PRIMARY N DIMLEVEL value is 0,99997 and shall be interpreted on a linear scale.
// N depends on DTR2 and shall be in the range from 0 to 5 depending upon the available number of primaries.
// For any other value of DTR2 the command shall be ignored.
#define DALI_209_SET_TEMPORARY_RGB_DIMLEVEL 0xEB // 235 - Set temporary RGB dimlevel
// The data in the DTR shall be set as TEMPORARY RED DIMLEVEL.
// The data in DTR1 shall be set as TEMPORARY GREEN DIMLEVEL.
// The data in DTR2 shall be set as TEMPORARY BLUE DIMLEVEL.
#define DALI_209_SET_TEMPORARY_WAF_DIMLEVEL 0xEC // 236 - Set temporary WAF dimlevel
// The data in the DTR shall be set as TEMPORARY WHITE DIMLEVEL.
// The data in DTR1 shall be set as TEMPORARY AMBER DIMLEVEL.
// The data in DTR2 shall be set as TEMPORARY FREECOLOUR DIMLEVEL.
#define DALI_209_SET_TEMPORARY_RGBWAF_CONTROL 0xED // 237 - Set temporary RGBWAF control via DTR0
// The data in the DTR shall be stored as TEMPORARY RGBWAF CONTROL.
// bit 0 - output channel 0/Red; '0' = Unlinked, '1' = Linked
// bit 1 - output channel 1/Green; '0' = Unlinked, '1' = Linked
// bit 2 - output channel 2/Blue; '0' = Unlinked, '1' = Linked
// bit 3 - output channel 3/White; '0' = Unlinked, '1' = Linked
// bit 4 - output channel 4/Amber; '0' = Unlinked, '1' = Linked
// bit 5 - output channel 5/Freecolour; '0' = Unlinked, '1' = Linked
// bits 6..7 control type
// value 0 = Channel control
// value 1 = Colour control
// value 2 = Normalised colour control
// value 3 = reserved
// Bit 0 to bit 5 sets the appropriate output channel(s)/colour(s) linked or unlinked.
// Bit 6 and bit 7: The control type defines how the gear shall react to Arc Power Commands.
// The linked channels shall all be set to unlinked on any colour activation with colour type xy-coordinate, colour temperature TC or primary N.
// NOTE More than one channel can be linked at the same time
#define DALI_209_COPY_REPORT_TO_TEMPORARY 0xEE // 238 - Copy the contents of the variables with the color settings to the variables for the temporary color settings.
// Application extended configuration commands - Send as second byte with repeat
#define DALI_209_RESERVED239 0xEF // 239 - [Reserved]
#define DALI_209_STORE_TY_PRIMARY_N 0xF0 // 240 Deprecated - Store TY Primary N via DTR0/1/2
// The value is expressed in units of 0,5 lumen resulting in a possible range of TYmin = 0 lumen, to TYmax = 32767 lumen.
// A value of 65535 (“MASK”) means unknown.
// N depends on DTR2 and shall be in the range from 0 to 5 depending upon the available number of primaries.
// For any other value of DTR2 the command shall be ignored.
// A value of “MASK” means that this primary is undefined and calibration is needed.
#define DALI_209_STORE_XY_COORDINATE_PRIMARY_N 0xF1 // 241 Deprecated - Store XY coord primary channel N via DTR2
// The TEMPORARY x-COORDINATE and the TEMPORARY y-COORDINATE, given by command 224 and command 225 shall be stored as x-COORDINATE PRIMARY N
// respectively y-COORDINATE PRIMARY N of primary N given by the value of DTR2, and shall be in the range from 0 to 5 depending upon the available number of primaries.
// For any other value of DTR2 the command shall be ignored.
// A value of “MASK” in one of the “temporary colour value” values shall be stored which means that this primary is undefined and calibration is needed.
// NOTE 1 The intended use of this command is to store the actual xy-coordinate belonging to the primary. Any other use can lead to unexpected results (colours).
// NOTE 2 xy-coordinates outside the CIE 1931 colour space chromaticity diagram are not meaningful.
#define DALI_209_STORE_COLOUR_TEMP_TC_LIMIT 0xF2 // 242 - Set color temperature Tc limit in Mirek (Uses DTR0 (LSB), DTR1 (MSB) and DTR2 (TcLimit type))
// The value shall be stored in a variable defined by DTR2.
// For any other value of DTR2 the command shall be ignored.
// DTR2 = 0 - ColorTemperatureTcCoolest (lowest possible value but always equal to or warmer than the lowest possible physical value)
// DTR2 = 1 - ColorTemperatureTcWarmest (highest possible value but always equal to or cooler than the highest possible physical value)
// DTR2 = 2 - ColorTemperatureTcPhysicalCoolest (lowest possible physical value)
// DTR2 = 3 - ColorTemperatureTcPhysicalWarmest (highest possible physical value)
#define DALI_209_STORE_GEAR_FEATURES_STATUS 0xF3 // 243 - Set gear features / status (See 247) (Uses DTR0)
// The data in the DTR shall be interpreted as follows:
// bit 0 - Automatic Activation; '0' = No
// bit 1..7 - reserved; 0 = No
// If bit 0, the Automatic Activation bit, is set to 1, all arc power control commands except “ENABLE DAPC SEQUENCE” shall automatically trigger a colour transition.
// If the Automatic Activation bit is set, bit 0 of the “GEAR FEATURES/STATUS” byte shall be set.
#define DALI_209_RESERVED244 0xF4 // 244 - [Reserved]
#define DALI_209_ASSIGN_COLOUR_TO_LINKED_COMMAND 0xF5 // 245 - Assign to linked channel via DTR0
// The data held in the DTR in the range 0 to 6 shall be used to assign any/all linked output channel(s) to the given colour.
// DTR0 = 0 - No colour assigned
// DTR0 = 1 - Red
// DTR0 = 2 - Green
// DTR0 = 3 - Blue
// DTR0 = 4 - White
// DTR0 = 5 - Amber
// DTR0 = 6 - Freecolour
// For any other value of the DTR the command shall be ignored.
// The linked channels are given by bit 0 to bit 5 held in “TEMPORARY RGBWAF CONTROL”.
// If “TEMPORARY RGBWAF CONTROL” holds “MASK” the channel assignment shall not be changed.
// The “TEMPORARY COLOUR SETTINGS” are set to “MASK” after use of this command.
#define DALI_209_START_AUTO_CALIBRATION 0xF6 // 246 - Start auto calibration
// The command shall start or re-trigger a 15 min timer. Bit 2 of “COLOUR STATUS” shall be set to “1” whilst this timer is running.
// When the timer stops the most recent colour type, colour value and arc power level shall be restored immediately
// Whilst the timer is running the control gear shall run a calibration procedure in order to measure the x-coordinate, y-coordinate and the TY-value of
// all supported primaries and bit 3 of “COLOUR STATUS” shall be set to “0”.
// While the calibration procedure is running, the control gear shall not react to any command except “TERMINATE”, “QUERY COLOUR STATUS” and “START AUTO CALIBRATION”.
// The "TERMINATE" command shall cause the procedure to be aborted and the timer to be stopped.
// If the calibration is successful then bit 3 of “COLOUR STATUS” shall be set to “1” and the timer shall be stopped.
// If the calibration is not successful (bit 3 of “COLOUR STATUS” is “0”) and the control gear is capable of recovering the
// last successful calibration data it shall do so.
// In this case bit 3 of “COLOUR STATUS” shall bet set to “1”.
// The capability to recover the last successful calibration data is a gear feature; see command 247.
// Auto calibration is a gear feature; see command 247. If this feature is not supported the control gear shall not react in any way.
// NOTE Due to the fact that the calibration process may take longer than 15 min, the control device should check the status of the auto calibration by
// command 248 QUERY COLOUR STATUS periodically and re-trigger the calibration process timer by command 246 (START AUTO CALIBRATION) if necessary
// Application extended query commands - Send as second byte
#define DALI_209_QUERY_GEAR_FEATURES_STATUS 0xF7 // 247 - Returns gear features / status
// bit 0 - Automatic activation
// bit 1..5 - reserved
// bit 6 - Automatic calibration is supported
// bit 7 - Restoration of the automatic calibration is supported
#define DALI_209_QUERY_COLOUR_STATUS 0xF8 // 248 - Returns color status
// bit 0 - xy coordinate color point is outside the valid range.
// bit 1 - Color temperature Tc lies outside the valid range
// bit 2 - Automatic calibration is active.
// bit 3 - Automatic calibration was successful.
// bit 4 - Color representation xy-coordinate active.
// bit 5 - Color representation color temperature Tc active.
// bit 6 - Color representation primary N active.
// bit 7 - Color representation RGBWAF active.
#define DALI_209_QUERY_COLOUR_TYPE_FEATURES 0xF9 // 249 - Returns color type
// bit 0 - Supports color representation through xy coordinates.
// bit 1 - Supports color representation through color temperature Tc.
// bit 2..4 - Number of primary N colors supported. A value of 0 means that this color representation by primary colors is not supported.
// bit 5..7 - Number of RGBWAF channels supported. A value of 0 means that this color representation by RGBWAF is not supported.
#define DALI_209_QUERY_COLOUR_VALUE 0xFA // 250 - Returns color value (DTR0 = E_DALIColourValue) (MSB in response, Uses DTR0 for LSB).
// The answer depends on the DTR0 Value.
// Most responses involve a 16-bit number, in such cases the reponse is the MSB and LSB is loaded into DTR0.
// Answers corresponding to the DTR values related to an active colour type are only valid if the colour type of the requested colour value is
// active (see command 248) or if the control gear is capable of recalculating the requested colour value from the active colour type into a colour
// value of another colour type. If recalculation is not possible this shall be indicated by a value of “MASK” as answer.
// Querying the number of primaries, the x-coordinate, y-coordinate and TY of primary N shall be independent of the implemented colour type.
// If the control gear does not know the coordinates, or the primary is not there, the answer shall be “MASK”.
// E_DALIColourValue (DTR0):
// 0 - XCoordinate
// 1 - YCoordinate
// 2 - ColourTemperatureTC
// 3 - PrimaryNDimLevel0
// 4 - PrimaryNDimLevel1
// 5 - PrimaryNDimLevel2
// 6 - PrimaryNDimLevel3
// 7 - PrimaryNDimLevel4
// 8 - PrimaryNDimLevel5
// 9 - RedDimLevel
// 10 - GreenDimLevel
// 11 - BlueDimLevel
// 12 - WhiteDimLevel
// 13 - AmberDimLevel
// 14 - FreecolourDimLevel
// 15 - RGBWAFControl
// 64 - XCoordinatePrimaryN0
// 65 - YCoordinatePrimaryN0
// 66 - TYPrimaryN0
// 67 - XCoordinatePrimaryN1
// 68 - YCoordinatePrimaryN1
// 69 - TYPrimaryN1
// 70 - XCoordinatePrimaryN2
// 71 - YCoordinatePrimaryN2
// 72 - TYPrimaryN2
// 73 - XCoordinatePrimaryN3
// 74 - YCoordinatePrimaryN3
// 75 - TYPrimaryN3
// 76 - XCoordinatePrimaryN4
// 77 - YCoordinatePrimaryN4
// 78 - TYPrimaryN4
// 79 - XCoordinatePrimaryN5
// 80 - YCoordinatePrimaryN5
// 81 - TYPrimaryN5
// 82 - NumberOfPrimaries
// 128 - ColourTemperatureTcCoolest
// 129 - ColourTemperatureTcPhysicalCoolest
// 130 - ColourTemperatureTcWarmest
// 131 - ColourTemperatureTcPhysicalWarmest
// 192 - TemporaryXCoordinate
// 193 - TemporaryYCoordinate
// 194 - TemporaryColourTemperature
// 195 - TemporaryPrimaryNDimLevel0
// 196 - TemporaryPrimaryNDimLevel1
// 197 - TemporaryPrimaryNDimLevel2
// 198 - TemporaryPrimaryNDimLevel3
// 199 - TemporaryPrimaryNDimLevel4
// 200 - TemporaryPrimaryNDimLevel5
// 201 - TemporaryRedDimLevel
// 202 - TemporaryGreenDimLevel
// 203 - TemporaryBlueDimLevel
// 204 - TemporaryWhiteDimLevel
// 205 - TemporaryAmberDimLevel
// 206 - TemporaryFreecolourDimLevel
// 207 - TemporaryRgbwafControl
// 208 - TemporaryColourType
// 224 - ReportXCoordinate
// 225 - ReportYCoordinate
// 226 - ReportColourTemperatureTc
// 227 - ReportPrimaryNDimLevel0
// 228 - ReportPrimaryNDimLevel1
// 229 - ReportPrimaryNDimLevel2
// 230 - ReportPrimaryNDimLevel3
// 231 - ReportPrimaryNDimLevel4
// 232 - ReportPrimaryNDimLevel5
// 233 - ReportRedDimLevel
// 234 - ReportGreenDimLevel
// 235 - ReportBlueDimLevel
// 236 - ReportWhiteDimLevel
// 237 - ReportAmberDimLevel
// 238 - ReportFreecolourDimLevel
// 239 - ReportRgbwafControl
// 240 - ReportColourType
// If, for colour type RGBWAF, more output channels are assigned to one colour (Red, Green, Blue, White, Amber or Freecolour) and the actual levels
// of these output channels are different the answer to that query shall be “MASK”.
// For all other DTR values and for unsupported colour types no answer shall be sent and neither DTR1 nor the DTR shall be changed.
// NOTE 1 The actual level of an output channel can be queried by linking only this output channel and sending QUERY ACTUAL LEVEL.
// NOTE 2 A control device should always use command 160 (“QUERY ACTUAL LEVEL”) to update the report colour setting before querying it.
// NOTE 3 A value of “MASK” for any of “x-COORDINATE PRIMARY N”, “y-COORDINATE PRIMARY N” or “TY PRIMARY N” means that this primary is
// undefined and calibration is needed.
#define DALI_209_QUERY_RGBWAF_CONTROL 0xFB // 251 - RGBWAFControl contains further information about the assignment between output channel and color.
// bit 0 - Output channel 0 / red
// bit 1 - Output channel 1 / green
// bit 2 - Output channel 2 / blue
// bit 3 - Output channel 3 / white
// bit 4 - Output channel 4 / amber
// bit 5 - Output channel 5 / free selectable color
// bit 6..7 - 0 = channel control
// 1 = color control
// 2 = standardized color control
// 3 = reserved
// If an output channel/colour is not supported the appropriate bit shall read '0'
#define DALI_209_QUERY_ASSIGNED_COLOUR 0xFC // 252 - Returns the color assigned to the specified output channel (DTR0 = 0...5). If a non-existent channel number is specified, 255 (MASK) is returned. (Uses DTR0)
// Assigned colour for a given channel (as specified in DTR0). Refer to Command 252 "QUERY ASSIGNED COLOUR".
// Note that a response may indicate an assigned channel, or could also be "MASK" if the queried channel is not supported or is invalid.
// value 0 - No color assigned
// value 1 - Red
// value 2 - Green
// value 3 - Blue
// value 4 - White
// value 5 - Amber
// value 6 - Freely selectable color
#define DALI_209_RESERVED253 0xFD // 253 - [Reserved]
#define DALI_209_RESERVED254 0xFE // 254 - [Reserved]
#define DALI_209_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
/*-------------------------------------------------------------------------------------------*\
* DALI Application extended commands for IEC62386 part 210 = DT9 - Send as second byte
* Sequencer
\*-------------------------------------------------------------------------------------------*/
#define DALI_210_DEVICE_TYPE 9
// Application extended query commands - Send as second byte
#define DALI_210_QUERY_EXTENDED_VERSION_NUMBER 0xFF // 255 - The version number of the extended support.
// This command must be preceded by an appropriate DALI_102_ENABLE_DEVICE_TYPE_X command; if it is not then it will be ignored.
// Returns the version number of Part 2xx of IEC 62386 for the corresponding device type as an 8-bit number.
// Device type implementations must provide their own implementation of QueryExtendedVersionNumber using this mixin.
#endif // _DALI_H_

View File

@ -0,0 +1,309 @@
/*
TasmotaDali.cpp - DALI support for Tasmota
SPDX-FileCopyrightText: 2025 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#include <TasmotaDali.h>
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
/*-------------------------------------------------------------------------------------------*/
bool TasmotaDali::IsValidGPIOpin(int pin) {
return (pin >= -1 && pin <= 5) || (pin >= 12 && pin <= 15);
}
/*-------------------------------------------------------------------------------------------*/
TasmotaDali::TasmotaDali(int receive_pin, int transmit_pin, bool receive_invert, bool transmit_invert, int buffer_size) {
m_valid = false;
if ((receive_pin < 0) || (transmit_pin < 0)) { return; }
#ifdef ESP8266
if (!((IsValidGPIOpin(receive_pin)) && (IsValidGPIOpin(transmit_pin) || transmit_pin == 16))) {
return;
}
#endif // ESP8266
#ifdef ESP32
if (!GPIO_IS_VALID_GPIO(receive_pin)) { return; }
if (!GPIO_IS_VALID_OUTPUT_GPIO(transmit_pin)) { return; }
#endif // ESP32
m_buffer_size = buffer_size;
m_buffer = (DaliFrame*)malloc(m_buffer_size * sizeof(DaliFrame));
if (m_buffer == NULL) { return; }
m_rx_pin = receive_pin;
m_tx_pin = transmit_pin;
m_rx_invert = receive_invert;
m_tx_invert = transmit_invert;
// Use getCycleCount() loop to get as exact timing as possible
// Manchester twice 1200 bps = 2400 bps = 417 (protocol 416.76 +/- 10%) us = 1Te
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / 2400;
m_last_activity = 0;
m_last_frame.data = 0;
m_last_frame.meta = 0;
pinMode(m_tx_pin, OUTPUT);
digitalWrite(m_tx_pin, (m_tx_invert) ? LOW : HIGH); // Idle
pinMode(m_rx_pin, INPUT);
EnableRxInterrupt();
m_in_pos = 0;
m_out_pos = 0;
m_valid = true;
}
void TasmotaDali::end(void) {
DisableRxInterrupt();
if (m_buffer) {
free(m_buffer);
}
}
TasmotaDali::~TasmotaDali(void) {
if (m_valid) {
end();
}
}
bool TasmotaDali::begin(void) {
return m_valid;
}
void TasmotaDali::flush(void) {
m_in_pos = 0;
m_out_pos = 0;
}
int TasmotaDali::available(void) {
int avail = m_in_pos - m_out_pos;
if (avail < 0) {
avail += m_buffer_size;
}
return avail;
}
void TasmotaDali::write(DaliFrame frame) {
DisableRxInterrupt();
SendData(frame); // Takes 14.7 ms
if (frame.meta & TM_DALI_SEND_TWICE) {
SendData(frame); // Takes 14.7 ms
}
delay(2); // Block response
EnableRxInterrupt();
}
DaliFrame TasmotaDali::read(void) {
DaliFrame frame;
frame.data = 0;
frame.meta = 0;
if (m_in_pos != m_out_pos) {
/*
uint32_t in_pos = m_in_pos;
uint32_t out_pos = m_out_pos;
while (in_pos != out_pos) {
AddLog(LOG_LEVEL_DEBUG, PSTR("TEO: in %d/%d 0x%08X-0x%08X"), out_pos, in_pos, m_buffer[out_pos].data, m_buffer[out_pos].meta);
out_pos = (out_pos +1) % m_buffer_size;
}
*/
frame = m_buffer[m_out_pos];
m_out_pos = (m_out_pos +1) % m_buffer_size;
uint32_t bit_state = frame.meta >> 16;
frame.meta &= 0x000000FF;
if (bit_state != 0) { // Invalid Manchester encoding including start and stop bits
frame.meta | TM_DALI_COLLISION; // Possible collision or invalid reply of repeated frame
}
}
return frame;
}
/*-------------------------------------------------------------------------------------------*\
* DALI send
\*-------------------------------------------------------------------------------------------*/
void TasmotaDali::SendData(DaliFrame frame) {
/*
DALI-2 protocol forward frame
DALI data 0xFE6432 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0
Start and Stop bits 1 1 1
Manchester data 01010101010101011010010110100110101010010110100110
Stop bits 1111
DALI protocol forward frame
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0
Start and Stop bits 1 1 1
Manchester data 0101010101010101101001011010011010
Stop bits 1111
Bit number 012345678901234567890123456789012345678901234567890123
1 2 3 4 5
*/
bool bit_value;
bool pin_value;
bool dali_read;
bool collision;
uint32_t retry = 2;
do {
collision = false;
uint32_t send_data = frame.data;
uint32_t bit_pos = (frame.meta & TM_DALI_BIT_COUNT_MASK) -1;
uint32_t max_bit_number = (bit_pos * 2) + 4;
uint32_t bit_number = 0;
m_last_activity += 14; // As suggested by DALI protocol (>22Te = 9.17 ms) - We need to add 1.1 ms due to not waiting for stop bits
while (((int) (millis() - m_last_activity)) < 0) {
delay(1); // Wait for bus to be free if needed
}
#ifdef ESP32
{portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&mux);
#endif
uint32_t wait = ESP.getCycleCount();
while (bit_number <= max_bit_number) { // 417 * 35 = 35Te = 14.7 ms
if (!collision) {
if (0 == (bit_number &1)) { // Even bit
// Start bit, Stop bit, Data bits
bit_value = (0 == bit_number) ? 1 : (max_bit_number == bit_number) ? 0 : (bool)((send_data >> bit_pos--) &1); // MSB first
} else { // Odd bit
bit_value = !bit_value; // Complement bit
}
pin_value = bit_value ? LOW : HIGH; // Invert bit
} else {
if (max_bit_number == bit_number) {
pin_value = HIGH; // Set to idle
}
}
digitalWrite(m_tx_pin, (m_tx_invert) ? !pin_value : pin_value);
wait += m_bit_time; // Auto roll-over
while (ESP.getCycleCount() < wait);
if (!collision) {
dali_read = (digitalRead(m_rx_pin) != m_rx_invert);
if ((HIGH == pin_value) && (LOW == dali_read)) { // Collision if write is 1 and bus is 0
collision = true;
pin_value = LOW;
bit_number = max_bit_number -5; // Keep bus low for 4 bits - break sequence
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DLI: Tx collision"));
}
}
bit_number++;
}
#ifdef ESP32
portEXIT_CRITICAL(&mux);}
#endif
// delayMicroseconds(1100); // Wait 3Te as sending stop bits - adds to total 15.8 ms
m_last_activity = millis(); // Start Forward Frame delay time (>22Te)
} while (retry-- && collision);
}
/*-------------------------------------------------------------------------------------------*\
* DALI receive
\*-------------------------------------------------------------------------------------------*/
void TasmotaDali::ReceiveData(void) {
/*
Unsupported Forward frame (1 Start bit + 32 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 70 bits
DALI data 0xFE643278 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0 0 1 1 1 1 0 0 0 Forward frame - 30.2 ms
Start and Stop bits 1 1 1
Manchester data 010101010101010110100101101001101010100101101001101001010101101010
Stop bits 1111
DALI-2 Forward frame (1 Start bit + 24 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 54 bits
DALI data 0xFE6432 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 0 0 1 1 0 0 1 0 Forward frame - 23.2 ms
Start and Stop bits 1 1 1
Manchester data 01010101010101011010010110100110101010010110100110
Stop bits 1111
Forward frame (1 Start bit + 16 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 38 bits
DALI data 0xFE64 1 1 1 1 1 1 1 0 0 1 1 0 0 1 0 0 Forward frame - 16.2 ms
Start and Stop bits 1 1 1
Manchester data 0101010101010101101001011010011010
Stop bits 1111
Backward frame (1 Start bit + 8 data bits) * 2 bits/bit (manchester encoding) + 2 * 2 Stop bits = 22 bits
DALI data 0x64 0 1 1 0 0 1 0 0 Backward frame - 10 ms
Start and Stop bits 1 1 1
Manchester data 011001011010011010
Stop bits 1111
Bit number 01234567890123456789012345678901234567890123456789012345678901234567890
1 2 3 4 5 6 7
*/
uint32_t wait = ESP.getCycleCount() + (m_bit_time / 2);
DaliFrame frame;
frame.data = 0; // Received dali data
frame.meta = 0; // Bit count 0..32 bit
int bit_state = 0;
bool dali_read;
uint32_t bit_number = 0;
while (bit_number < 72) {
while (ESP.getCycleCount() < wait);
wait += m_bit_time; // Auto roll-over +1Te
dali_read = (digitalRead(m_rx_pin) != m_rx_invert);
if (bit_number < 68) { // 66 manchester encoded bits
bit_state += (dali_read) ? 1 : -1;
if (0 == bit_state) { // Manchester encoding total 2 bits is always 0
if (bit_number > 2) { // Skip start bit
frame.data <<= 1;
frame.data |= dali_read;
}
}
else if (2 == bit_state) { // Invalid manchester data (might be stop bit)
if (bit_number > 4) { // bn 19 -> 8, 35 -> 16, 51 -> 24, 67 -> 32
frame.meta = (bit_number - 3) / 2; // 1..32 bit
}
bit_state = 0;
bit_number = 69; // Continue receiving stop bits
}
else if (abs(bit_state) > 1) { // Invalid manchester data (too many 0 or 1)
break;
}
} else { // 4 high Stop bits
if (bit_state != 0) { // Invalid manchester data
break;
}
else if (dali_read != 1) { // Invalid level of stop bit
bit_state = 1; // Could be collision
break;
}
}
bit_number++;
}
m_last_activity = millis(); // Start Forward Frame delay time (>22Te)
if (frame.meta > 0) { // Any valid bit received - fix spike interrupts
uint32_t data_size = frame.meta;
frame.meta |= ((bit_state << 16) | (bit_number << 8)); // Possible collision or invalid reply of repeated frame if bit_state > 0
if ((8 == data_size) || // Always allow backward frame
(m_last_frame.data != frame.data) ||
(m_last_frame.meta != frame.meta)) { // Skip duplicate forward frames
m_last_frame = frame;
m_buffer[m_in_pos] = frame;
m_in_pos = (m_in_pos + 1) % m_buffer_size;
}
}
}
void IRAM_ATTR ReceiveDataIrq(void *self) {
((TasmotaDali*)self)->ReceiveData();
};
void TasmotaDali::EnableRxInterrupt(void) {
attachInterruptArg(m_rx_pin, ReceiveDataIrq, this, (m_rx_invert) ? RISING : FALLING);
}
void TasmotaDali::DisableRxInterrupt(void) {
detachInterrupt(m_rx_pin);
}

View File

@ -0,0 +1,69 @@
/*
TasmotaDali.h - DALI support for Tasmota
SPDX-FileCopyrightText: 2025 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifndef TasmotaDali_h
#define TasmotaDali_h
/*********************************************************************************************\
* TasmotaDali (1200bps) using default buffer size of 8
\*********************************************************************************************/
#define TM_DALI_BUFFER_SIZE 8 // Receive buffer size
#define TM_DALI_COLLISION 0x80000000 // Collision data mask
#define TM_DALI_SEND_TWICE 0x40000000 // Send twice mask
#define TM_DALI_BIT_COUNT_MASK 0x0000003F // Bit count mask - 0..63 bits (0..32 supported)
#define TM_DALI_EVENT_FRAME 0x80000000 // DALI-2 24-bit event frame
#include <Arduino.h>
#include <Dali.h>
typedef struct {
uint32_t data;
uint32_t meta;
} DaliFrame;
class TasmotaDali {
public:
TasmotaDali(int receive_pin, int transmit_pin, bool receive_invert = false, bool transmit_invert = false, int buffer_size = TM_DALI_BUFFER_SIZE);
virtual ~TasmotaDali();
bool begin(void);
void end(void);
int available(void);
void flush(void);
void write(DaliFrame frame);
DaliFrame read(void);
inline void ReceiveData(void);
private:
bool IsValidGPIOpin(int pin);
void EnableRxInterrupt(void);
void DisableRxInterrupt(void);
void SendData(DaliFrame frame);
// Member variables
DaliFrame *m_buffer = nullptr;
DaliFrame m_last_frame;
uint32_t m_buffer_size = TM_DALI_BUFFER_SIZE;
uint32_t m_last_activity;
uint32_t m_bit_time;
int m_rx_pin;
int m_tx_pin;
int m_in_pos;
int m_out_pos;
bool m_valid;
bool m_rx_invert;
bool m_tx_invert;
};
#endif // TasmotaDali_h

View File

@ -166,10 +166,6 @@ int WiFiHelper::getPhyMode() {
WIFI_PHY_MODE_HE20, // PHY mode for Bandwidth HE20 (11ax) WIFI_PHY_MODE_HE20, // PHY mode for Bandwidth HE20 (11ax)
} wifi_phy_mode_t; } wifi_phy_mode_t;
*/ */
#ifndef SOC_WIFI_SUPPORTED
// ESP32-P4 does not support PHY modes, return 0
return 0;
#else
int phy_mode = 0; // "low rate|11b|11g|HT20|HT40|HE20" int phy_mode = 0; // "low rate|11b|11g|HT20|HT40|HE20"
wifi_phy_mode_t WiFiMode; wifi_phy_mode_t WiFiMode;
if (esp_wifi_sta_get_negotiated_phymode(&WiFiMode) == ESP_OK) { if (esp_wifi_sta_get_negotiated_phymode(&WiFiMode) == ESP_OK) {
@ -179,23 +175,16 @@ int WiFiHelper::getPhyMode() {
} }
} }
return phy_mode; return phy_mode;
# endif
} }
bool WiFiHelper::setPhyMode(WiFiPhyMode_t mode) { bool WiFiHelper::setPhyMode(WiFiPhyMode_t mode) {
# ifndef SOC_WIFI_SUPPORTED
return false; // ESP32-P4 does not support PHY modes
# else
uint8_t protocol_bitmap = WIFI_PROTOCOL_11B; // 1 uint8_t protocol_bitmap = WIFI_PROTOCOL_11B; // 1
switch (mode) { switch (mode) {
#if ESP_IDF_VERSION_MAJOR >= 5
case 4: protocol_bitmap |= WIFI_PROTOCOL_11AX; // 16 case 4: protocol_bitmap |= WIFI_PROTOCOL_11AX; // 16
#endif
case 3: protocol_bitmap |= WIFI_PROTOCOL_11N; // 4 case 3: protocol_bitmap |= WIFI_PROTOCOL_11N; // 4
case 2: protocol_bitmap |= WIFI_PROTOCOL_11G; // 2 case 2: protocol_bitmap |= WIFI_PROTOCOL_11G; // 2
} }
return (ESP_OK == esp_wifi_set_protocol(WIFI_IF_STA, protocol_bitmap)); return (ESP_OK == esp_wifi_set_protocol(WIFI_IF_STA, protocol_bitmap));
#endif // CONFIG_IDF_TARGET_ESP32P4
} }
void WiFiHelper::setOutputPower(int n) { void WiFiHelper::setOutputPower(int n) {
@ -369,24 +358,20 @@ int WiFiHelper::hostByName(const char* aHostname, IPAddress& aResult)
return WiFiHelper::hostByName(aHostname, aResult, WifiDNSGetTimeout()); return WiFiHelper::hostByName(aHostname, aResult, WifiDNSGetTimeout());
} }
#if (ESP_IDF_VERSION_MAJOR >= 5)
#include "esp_mac.h" #include "esp_mac.h"
#endif
String WiFiHelper::macAddress(void) { String WiFiHelper::macAddress(void) {
#if (ESP_IDF_VERSION_MAJOR < 5)
return WiFi.macAddress();
#else
uint8_t mac[6] = {0,0,0,0,0,0}; uint8_t mac[6] = {0,0,0,0,0,0};
char macStr[18] = { 0 }; char macStr[18] = { 0 };
#ifdef CONFIG_SOC_HAS_WIFI #ifdef CONFIG_SOC_HAS_WIFI
esp_read_mac(mac, ESP_MAC_WIFI_STA); esp_read_mac(mac, ESP_MAC_WIFI_STA); // Local WiFi station MAC address
#else #elif CONFIG_ESP_WIFI_REMOTE_ENABLED
esp_read_mac(mac, ESP_MAC_BASE); WiFi.macAddress(mac); // Remote WiFi station MAC address (devices without WiFi but hostedMCU)
#endif // CONFIG_SOC_HAS_WIFI #else // No CONFIG_SOC_HAS_WIFI
esp_read_mac(mac, ESP_MAC_BASE); // Local hardware base MAC address
#endif // CONFIG_SOC_HAS_WIFI
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(macStr); return String(macStr);
#endif
} }
#endif // ESP32 #endif // ESP32

View File

@ -6,4 +6,4 @@ sentence=A library that makes controlling NeoPixels (APA106, WS2811, WS2812, WS2
paragraph=Supports most Arduino platforms, including async hardware support for Esp8266, Esp32, and Nrf52 (Nano 33 BLE). Support for RGBW pixels and 7 Segment LED direct driven. Includes seperate RgbColor, RgbwColor, Rgb16Color, Rgb48Color, HslColor, and HsbColor objects. Includes an animator class that helps create asyncronous animations. Supports Matrix layout of pixels. Includes Gamma corretion object. For all platforms; there are two methods of sending DotStar data, hardware SPI and software SPI. paragraph=Supports most Arduino platforms, including async hardware support for Esp8266, Esp32, and Nrf52 (Nano 33 BLE). Support for RGBW pixels and 7 Segment LED direct driven. Includes seperate RgbColor, RgbwColor, Rgb16Color, Rgb48Color, HslColor, and HsbColor objects. Includes an animator class that helps create asyncronous animations. Supports Matrix layout of pixels. Includes Gamma corretion object. For all platforms; there are two methods of sending DotStar data, hardware SPI and software SPI.
category=Display category=Display
url=https://github.com/Makuna/NeoPixelBus/wiki url=https://github.com/Makuna/NeoPixelBus/wiki
architectures=* architectures=esp8266

View File

@ -0,0 +1,242 @@
# TasmotaLED
A lightweight, high-performance library for controlling addressable LED strips on ESP32 microcontrollers.
## Overview
TasmotaLED is a streamlined replacement for NeoPixelBus, designed specifically for the Tasmota firmware. It focuses on efficient pixel pushing with minimal memory overhead while supporting multiple hardware acceleration methods.
**Key Features:**
- Hardware-accelerated via RMT (preferred), SPI, or I2S
- Support for WS2812 and SK6812 LED strips
- 3-byte (RGB) and 4-byte (RGBW) pixel formats
- Flexible pixel ordering (GRB, RGB, RBG, BRG, BGR, GBR)
- Minimal memory footprint (2 buffers only)
- ESP32 platform exclusive
## Quick Start
```cpp
#include "TasmotaLED.h"
#include "TasmotaLEDPusher.h"
// Create 60-LED WS2812 strip on GPIO 5
TasmotaLED strip(ws2812_grb, 60);
void setup() {
TasmotaLEDPusher *pusher = TasmotaLEDPusher::Create(TasmotaLed_HW_Default, 5);
strip.SetPusher(pusher);
strip.Begin();
// Set colors and display
strip.ClearTo(0xFF0000); // All red
strip.Show();
}
```
## Architecture
### Class Hierarchy
```
┌─────────────────────────────────────┐
│ TasmotaLED │
│ ┌──────────────┐ ┌──────────────┐│
│ │ _buf_work │ │ _buf_show ││
│ │ (editable) │─▶│ (internal) ││
│ └──────────────┘ └──────────────┘│
└──────────────┬──────────────────────┘
│ uses
┌──────────────────────────────────────┐
│ TasmotaLEDPusher (Abstract) │
└──────────────┬───────────────────────┘
┌───────┼───────┐
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│ RMT │ │ SPI │ │ I2S │
└─────┘ └─────┘ └─────┘
```
### Hardware Support
| Hardware | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | ESP32-C2 | ESP32-C6 |
|----------|-------|----------|----------|----------|----------|----------|
| **RMT** | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| **SPI** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **I2S** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
**Selection Priority:** RMT → I2S → SPI (auto-selected based on SOC capabilities)
## Configuration
### LED Type Encoding
LED types are encoded in a 16-bit value:
```
Bits 15-8: Timing (WS2812=0, SK6812=1)
Bit 7: W Position (0=after RGB, 1=before RGB)
Bits 6-4: Pixel Order (GRB, RGB, BGR, etc.)
Bits 3-0: Bytes/Pixel (3=RGB, 4=RGBW)
```
**Predefined Types:**
```cpp
ws2812_grb // WS2812 with GRB ordering (most common)
sk6812_grbw // SK6812 with GRBW ordering
sk6812_grb // SK6812 with GRB ordering (no white)
```
**Custom Types:**
```cpp
uint16_t custom = TasmotaLed_3_RGB | // 3 bytes per pixel
TasmotaLed_RGB | // RGB ordering
TasmotaLed_WS2812; // WS2812 timing
```
### Pixel Ordering Options
| Enum | Order | Description |
|------|-------|-------------|
| `TasmotaLed_GRB` | G, R, B | Green-Red-Blue (default) |
| `TasmotaLed_RGB` | R, G, B | Red-Green-Blue |
| `TasmotaLed_BGR` | B, G, R | Blue-Green-Red |
| `TasmotaLed_RBG` | R, B, G | Red-Blue-Green |
| `TasmotaLed_BRG` | B, R, G | Blue-Red-Green |
| `TasmotaLed_GBR` | G, B, R | Green-Blue-Red |
## Timing Specifications
### WS2812 Timing
```
T0H: 400ns (bit 0 high time)
T0L: 850ns (bit 0 low time)
T1H: 800ns (bit 1 high time)
T1L: 450ns (bit 1 low time)
Reset: 80µs (reset pulse)
```
### SK6812 Timing
```
T0H: 300ns (bit 0 high time)
T0L: 900ns (bit 0 low time)
T1H: 600ns (bit 1 high time)
T1L: 600ns (bit 1 low time)
Reset: 80µs (reset pulse)
```
### RMT Implementation
- Clock: 40 MHz (25ns resolution)
- Precision: ±25ns per timing parameter
- Memory: 192 symbols per channel
- Non-blocking with DMA
### SPI Implementation
- Clock: 2.5 MHz
- Encoding: 3 SPI bits per LED bit
- Bit 0: `100` pattern
- Bit 1: `110` pattern
- Memory: 3× pixel buffer size
## Memory Usage
| Configuration | Memory Required |
|---------------|-----------------|
| 60 RGB pixels | ~460 bytes |
| 144 RGB pixels | ~964 bytes |
| 300 RGB pixels | ~1,900 bytes |
| 512 RGB pixels | ~3,172 bytes |
| 60 RGBW pixels | ~580 bytes |
**Formula:** `~100 bytes + (2 × pixels × bytes_per_pixel)`
**SPI Additional:** Add `3 × pixels × bytes_per_pixel` for encoding buffer
## Performance
### Transmission Times (WS2812/SK6812)
- 60 pixels: ~1.8 ms (max 555 Hz)
- 144 pixels: ~4.3 ms (max 232 Hz)
- 300 pixels: ~9.0 ms (max 111 Hz)
- 512 pixels: ~15.4 ms (max 65 Hz)
### CPU Overhead
- **RMT:** <1% during transmission (DMA-based)
- **SPI:** <2% during transmission (DMA-based)
- **Format conversion:** ~124 µs for 512 RGB pixels
## API Highlights
### Core Methods
```cpp
// Initialization
bool Begin()
void SetPusher(TasmotaLEDPusher *pusher)
// Pixel manipulation
void SetPixelColor(int32_t index, uint32_t wrgb)
uint32_t GetPixelColor(int32_t index)
void ClearTo(uint32_t rgbw, int32_t first = 0, int32_t last = -1)
// Display
void Show()
bool CanShow()
// Configuration
void SetPixelCount(uint16_t num_leds)
void SetPixelSubType(uint8_t type)
// Query
uint16_t PixelCount() const
uint8_t PixelSize() const
uint8_t * Pixels() const
```
### Color Format
**RGB Strips (3 bytes):**
```cpp
0xRRGGBB
// Example: 0xFF0000 = Red
```
**RGBW Strips (4 bytes):**
```cpp
0xWWRRGGBB
// Example: 0xFF000000 = Pure white (W channel)
// Example: 0x00FF0000 = Red (RGB channels)
```
## Compile-Time Configuration
Enable/disable hardware support:
```cpp
#define TASMOTALED_HARDWARE_RMT 1 // Enable RMT (default: 1)
#define TASMOTALED_HARDWARE_SPI 0 // Enable SPI (default: 0)
#define TASMOTALED_HARDWARE_I2S 0 // Enable I2S (default: 0)
```
**Note:** If no hardware is enabled, SPI is automatically enabled as fallback.
## Documentation
For complete documentation including detailed API reference, integration guide, troubleshooting, and advanced usage, see:
**[TASMOTALED_DOCUMENTATION.md](TASMOTALED_DOCUMENTATION.md)**
## License
GNU General Public License v3.0
Copyright (C) 2024 Stephan Hadinger
## Credits
- **Author:** Stephan Hadinger
- **Project:** Tasmota Firmware
- **Inspired by:** NeoPixelBus library
- **RMT Encoder:** Based on ESP-IDF examples

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,7 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D
TasmotaLED::TasmotaLED(uint16_t type, uint16_t num_leds) : TasmotaLED::TasmotaLED(uint16_t type, uint16_t num_leds) :
_type(type), _type(type),
_pixel_reverse(false),
_timing((type >> 8) & 0xFF), _timing((type >> 8) & 0xFF),
_started(false), _started(false),
_dirty(true), _dirty(true),
@ -111,7 +112,7 @@ TasmotaLED::~TasmotaLED() {
void TasmotaLED::_adjustSubType(void) { void TasmotaLED::_adjustSubType(void) {
_pixel_order = (_type >> 4) & 0x07; _pixel_order = (_type >> 4) & 0x07;
_pixel_matrix = &TASMOTALED_CHANNEL_ORDERS[_pixel_order]; _pixel_matrix = &TASMOTALED_CHANNEL_ORDERS[_pixel_order];
_w_before = _type & 0x08; _w_before = _type & 0x80; // bit 7 sets the position for W channel
} }
void TasmotaLED::SetPixelCount(uint16_t num_leds) { void TasmotaLED::SetPixelCount(uint16_t num_leds) {
@ -175,6 +176,11 @@ void TasmotaLED::Show(void) {
} else { } else {
uint8_t *buf_from = _buf_work; uint8_t *buf_from = _buf_work;
uint8_t *buf_to = _buf_show; uint8_t *buf_to = _buf_show;
int32_t pixel_incr = _pixel_size; // will be set to negative if reverse
if (_pixel_reverse) {
buf_from += (_pixel_count - 1) * _pixel_size;
pixel_incr = -pixel_incr;
}
if (_pixel_size == 3) { if (_pixel_size == 3) {
// copying with swapping 512 pixels (1536 bytes) takes 124 microseconds to copy, so it's negligeable // copying with swapping 512 pixels (1536 bytes) takes 124 microseconds to copy, so it's negligeable
for (uint32_t i = 0; i < _pixel_count; i++) { for (uint32_t i = 0; i < _pixel_count; i++) {
@ -182,17 +188,23 @@ void TasmotaLED::Show(void) {
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B
buf_to += 3; buf_to += 3;
buf_from += 3; buf_from += pixel_incr;
} }
} else if (_pixel_size == 4) { } else if (_pixel_size == 4) {
for (uint32_t i = 0; i < _pixel_count; i++) { for (uint32_t i = 0; i < _pixel_count; i++) {
if (_w_before) { *buf_to++ = buf_from[3]; } if (_w_before) {
buf_to[(*_pixel_matrix)[0]] = buf_from[0]; // R buf_to[0] = buf_from[0]; // W
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G buf_to[1 + (*_pixel_matrix)[0]] = buf_from[1]; // R
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B buf_to[1 + (*_pixel_matrix)[1]] = buf_from[2]; // G
if (!_w_before) { *buf_to++ = buf_from[3]; } buf_to[1 + (*_pixel_matrix)[2]] = buf_from[3]; // B
buf_to += 3; // one increment already happened } else {
buf_from += 4; buf_to[(*_pixel_matrix)[0]] = buf_from[1]; // R
buf_to[(*_pixel_matrix)[1]] = buf_from[2]; // G
buf_to[(*_pixel_matrix)[2]] = buf_from[3]; // B
buf_to[3] = buf_from[0]; // W
}
buf_to += 4;
buf_from += pixel_incr;
} }
} }
} }

View File

@ -96,6 +96,8 @@ public:
void SetPixelSubType(uint8_t type); // change only Pixel order and pixel size void SetPixelSubType(uint8_t type); // change only Pixel order and pixel size
void _adjustSubType(void); void _adjustSubType(void);
inline void SetPixelReverse(bool reverse) { _pixel_reverse = reverse; }
bool Begin(void); bool Begin(void);
void SetPusher(TasmotaLEDPusher *pusher); // needs to be called before `Begin()`, sets the hardware implementation void SetPusher(TasmotaLEDPusher *pusher); // needs to be called before `Begin()`, sets the hardware implementation
void Show(void); // pushes the pixels to the LED strip void Show(void); // pushes the pixels to the LED strip
@ -118,6 +120,7 @@ protected:
uint16_t _type; // the composite type uint16_t _type; // the composite type
uint8_t _pixel_order; // permutation between RGB and position of W uint8_t _pixel_order; // permutation between RGB and position of W
bool _w_before; // true if W channel comes first (4 channels only) bool _w_before; // true if W channel comes first (4 channels only)
bool _pixel_reverse; // display LED strip in reverse order
uint8_t _timing; // timing code for strip, 0=WS2812, 1=SK6812... uint8_t _timing; // timing code for strip, 0=WS2812, 1=SK6812...
bool _started; // true if the hardware implementation is configured bool _started; // true if the hardware implementation is configured
bool _dirty; // for NeoPixelBus compatibility, but ignored by `Push()` bool _dirty; // for NeoPixelBus compatibility, but ignored by `Push()`

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2010 - 2011 Espressif System
*
*/
#ifndef SPI_REGISTER_H_INCLUDED
#define SPI_REGISTER_H_INCLUDED
#define REG_SPI_BASE(i) (0x60000200-i*0x100)
#define SPI_CMD(i) (REG_SPI_BASE(i) + 0x0)
#define SPI_USR (BIT(18))
#define SPI_ADDR(i) (REG_SPI_BASE(i) + 0x4)
#define SPI_CTRL(i) (REG_SPI_BASE(i) + 0x8)
#define SPI_WR_BIT_ORDER (BIT(26))
#define SPI_RD_BIT_ORDER (BIT(25))
#define SPI_QIO_MODE (BIT(24))
#define SPI_DIO_MODE (BIT(23))
#define SPI_QOUT_MODE (BIT(20))
#define SPI_DOUT_MODE (BIT(14))
#define SPI_FASTRD_MODE (BIT(13))
#define SPI_RD_STATUS(i) (REG_SPI_BASE(i) + 0x10)
#define SPI_CTRL2(i) (REG_SPI_BASE(i) + 0x14)
#define SPI_CS_DELAY_NUM 0x0000000F
#define SPI_CS_DELAY_NUM_S 28
#define SPI_CS_DELAY_MODE 0x00000003
#define SPI_CS_DELAY_MODE_S 26
#define SPI_MOSI_DELAY_NUM 0x00000007
#define SPI_MOSI_DELAY_NUM_S 23
#define SPI_MOSI_DELAY_MODE 0x00000003
#define SPI_MOSI_DELAY_MODE_S 21
#define SPI_MISO_DELAY_NUM 0x00000007
#define SPI_MISO_DELAY_NUM_S 18
#define SPI_MISO_DELAY_MODE 0x00000003
#define SPI_MISO_DELAY_MODE_S 16
#define SPI_CK_OUT_HIGH_MODE 0x0000000F
#define SPI_CK_OUT_HIGH_MODE_S 12
#define SPI_CK_OUT_LOW_MODE 0x0000000F
#define SPI_CK_OUT_LOW_MODE_S 8
#define SPI_CLOCK(i) (REG_SPI_BASE(i) + 0x18)
#define SPI_CLK_EQU_SYSCLK (BIT(31))
#define SPI_CLKDIV_PRE 0x00001FFF
#define SPI_CLKDIV_PRE_S 18
#define SPI_CLKCNT_N 0x0000003F
#define SPI_CLKCNT_N_S 12
#define SPI_CLKCNT_H 0x0000003F
#define SPI_CLKCNT_H_S 6
#define SPI_CLKCNT_L 0x0000003F
#define SPI_CLKCNT_L_S 0
#define SPI_USER(i) (REG_SPI_BASE(i) + 0x1C)
#define SPI_USR_COMMAND (BIT(31))
#define SPI_USR_ADDR (BIT(30))
#define SPI_USR_DUMMY (BIT(29))
#define SPI_USR_MISO (BIT(28))
#define SPI_USR_MOSI (BIT(27))
#define SPI_USR_MOSI_HIGHPART (BIT(25))
#define SPI_USR_MISO_HIGHPART (BIT(24))
#define SPI_SIO (BIT(16))
#define SPI_FWRITE_QIO (BIT(15))
#define SPI_FWRITE_DIO (BIT(14))
#define SPI_FWRITE_QUAD (BIT(13))
#define SPI_FWRITE_DUAL (BIT(12))
#define SPI_WR_BYTE_ORDER (BIT(11))
#define SPI_RD_BYTE_ORDER (BIT(10))
#define SPI_CK_OUT_EDGE (BIT(7))
#define SPI_CK_I_EDGE (BIT(6))
#define SPI_CS_SETUP (BIT(5))
#define SPI_CS_HOLD (BIT(4))
#define SPI_FLASH_MODE (BIT(2))
#define SPI_DOUTDIN (BIT(0))
#define SPI_USER1(i) (REG_SPI_BASE(i) + 0x20)
#define SPI_USR_ADDR_BITLEN 0x0000003F
#define SPI_USR_ADDR_BITLEN_S 26
#define SPI_USR_MOSI_BITLEN 0x000001FF
#define SPI_USR_MOSI_BITLEN_S 17
#define SPI_USR_MISO_BITLEN 0x000001FF
#define SPI_USR_MISO_BITLEN_S 8
#define SPI_USR_DUMMY_CYCLELEN 0x000000FF
#define SPI_USR_DUMMY_CYCLELEN_S 0
#define SPI_USER2(i) (REG_SPI_BASE(i) + 0x24)
#define SPI_USR_COMMAND_BITLEN 0x0000000F
#define SPI_USR_COMMAND_BITLEN_S 28
#define SPI_USR_COMMAND_VALUE 0x0000FFFF
#define SPI_USR_COMMAND_VALUE_S 0
#define SPI_WR_STATUS(i) (REG_SPI_BASE(i) + 0x28)
#define SPI_PIN(i) (REG_SPI_BASE(i) + 0x2C)
#define SPI_CS2_DIS (BIT(2))
#define SPI_CS1_DIS (BIT(1))
#define SPI_CS0_DIS (BIT(0))
#define SPI_IDLE_EDGE (BIT(29))
#define SPI_SLAVE(i) (REG_SPI_BASE(i) + 0x30)
#define SPI_SYNC_RESET (BIT(31))
#define SPI_SLAVE_MODE (BIT(30))
#define SPI_SLV_WR_RD_BUF_EN (BIT(29))
#define SPI_SLV_WR_RD_STA_EN (BIT(28))
#define SPI_SLV_CMD_DEFINE (BIT(27))
#define SPI_TRANS_CNT 0x0000000F
#define SPI_TRANS_CNT_S 23
#define SPI_TRANS_DONE_EN (BIT(9))
#define SPI_SLV_WR_STA_DONE_EN (BIT(8))
#define SPI_SLV_RD_STA_DONE_EN (BIT(7))
#define SPI_SLV_WR_BUF_DONE_EN (BIT(6))
#define SPI_SLV_RD_BUF_DONE_EN (BIT(5))
#define SLV_SPI_INT_EN 0x0000001f
#define SLV_SPI_INT_EN_S 5
#define SPI_TRANS_DONE (BIT(4))
#define SPI_SLV_WR_STA_DONE (BIT(3))
#define SPI_SLV_RD_STA_DONE (BIT(2))
#define SPI_SLV_WR_BUF_DONE (BIT(1))
#define SPI_SLV_RD_BUF_DONE (BIT(0))
#define SPI_SLAVE1(i) (REG_SPI_BASE(i) + 0x34)
#define SPI_SLV_STATUS_BITLEN 0x0000001F
#define SPI_SLV_STATUS_BITLEN_S 27
#define SPI_SLV_BUF_BITLEN 0x000001FF
#define SPI_SLV_BUF_BITLEN_S 16
#define SPI_SLV_RD_ADDR_BITLEN 0x0000003F
#define SPI_SLV_RD_ADDR_BITLEN_S 10
#define SPI_SLV_WR_ADDR_BITLEN 0x0000003F
#define SPI_SLV_WR_ADDR_BITLEN_S 4
#define SPI_SLV_WRSTA_DUMMY_EN (BIT(3))
#define SPI_SLV_RDSTA_DUMMY_EN (BIT(2))
#define SPI_SLV_WRBUF_DUMMY_EN (BIT(1))
#define SPI_SLV_RDBUF_DUMMY_EN (BIT(0))
#define SPI_SLAVE2(i) (REG_SPI_BASE(i) + 0x38)
#define SPI_SLV_WRBUF_DUMMY_CYCLELEN 0X000000FF
#define SPI_SLV_WRBUF_DUMMY_CYCLELEN_S 24
#define SPI_SLV_RDBUF_DUMMY_CYCLELEN 0X000000FF
#define SPI_SLV_RDBUF_DUMMY_CYCLELEN_S 16
#define SPI_SLV_WRSTR_DUMMY_CYCLELEN 0X000000FF
#define SPI_SLV_WRSTR_DUMMY_CYCLELEN_S 8
#define SPI_SLV_RDSTR_DUMMY_CYCLELEN 0x000000FF
#define SPI_SLV_RDSTR_DUMMY_CYCLELEN_S 0
#define SPI_SLAVE3(i) (REG_SPI_BASE(i) + 0x3C)
#define SPI_SLV_WRSTA_CMD_VALUE 0x000000FF
#define SPI_SLV_WRSTA_CMD_VALUE_S 24
#define SPI_SLV_RDSTA_CMD_VALUE 0x000000FF
#define SPI_SLV_RDSTA_CMD_VALUE_S 16
#define SPI_SLV_WRBUF_CMD_VALUE 0x000000FF
#define SPI_SLV_WRBUF_CMD_VALUE_S 8
#define SPI_SLV_RDBUF_CMD_VALUE 0x000000FF
#define SPI_SLV_RDBUF_CMD_VALUE_S 0
#define SPI_W0(i) (REG_SPI_BASE(i) +0x40)
#define SPI_W1(i) (REG_SPI_BASE(i) +0x44)
#define SPI_W2(i) (REG_SPI_BASE(i) +0x48)
#define SPI_W3(i) (REG_SPI_BASE(i) +0x4C)
#define SPI_W4(i) (REG_SPI_BASE(i) +0x50)
#define SPI_W5(i) (REG_SPI_BASE(i) +0x54)
#define SPI_W6(i) (REG_SPI_BASE(i) +0x58)
#define SPI_W7(i) (REG_SPI_BASE(i) +0x5C)
#define SPI_W8(i) (REG_SPI_BASE(i) +0x60)
#define SPI_W9(i) (REG_SPI_BASE(i) +0x64)
#define SPI_W10(i) (REG_SPI_BASE(i) +0x68)
#define SPI_W11(i) (REG_SPI_BASE(i) +0x6C)
#define SPI_W12(i) (REG_SPI_BASE(i) +0x70)
#define SPI_W13(i) (REG_SPI_BASE(i) +0x74)
#define SPI_W14(i) (REG_SPI_BASE(i) +0x78)
#define SPI_W15(i) (REG_SPI_BASE(i) +0x7C)
#define SPI_EXT3(i) (REG_SPI_BASE(i) + 0xFC)
#define SPI_INT_HOLD_ENA 0x00000003
#define SPI_INT_HOLD_ENA_S 0
#endif // SPI_REGISTER_H_INCLUDED

View File

@ -0,0 +1,276 @@
#ifndef _UDISP_
#define _UDISP_
#include <Adafruit_GFX.h>
#include <renderer.h>
#include <Wire.h>
#include <SPI.h>
#include "uDisplay_config.h"
#ifdef ESP32
#if __has_include("soc/soc_caps.h")
# include "soc/soc_caps.h"
#else
# error "No ESP capability header found"
#endif
#if (SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS)
#define UDISPLAY_I80
#include "uDisplay_I80_panel.h"
#endif
#if defined(SOC_LCD_RGB_SUPPORTED)
#include "uDisplay_RGB_panel.h"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "uDisplay_DSI_panel.h"
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define USE_ESP32_S3
#endif
#include "soc/gpio_periph.h"
#include <rom/gpio.h>
// #include "driver/spi_master.h"
#endif
#include "uDisplay_SPI_controller.h"
#include "uDisplay_I2C_panel.h"
#include "uDisplay_EPD_panel.h"
#include "uDisplay_SPI_panel.h"
// ===== Panel Config Union =====
// Union to hold any panel configuration type
// Only one config is active at a time based on interface type
union PanelConfigUnion {
SPIPanelConfig spi;
I2CPanelConfig i2c;
EPDPanelConfig epd;
#ifdef UDISPLAY_I80
I80PanelConfig i80;
#endif
#if SOC_LCD_RGB_SUPPORTED
esp_lcd_rgb_panel_config_t rgb; // ESP-IDF native config
#endif
#if SOC_MIPI_DSI_SUPPORTED
DSIPanelConfig dsi;
#endif
};
enum {
UT_RD,UT_RDM,UT_CP,UT_RTF,UT_MV,UT_MVB,UT_RT,UT_RTT,UT_RDW,UT_RDWM,UT_WR,UT_WRW,UT_CPR,UT_AND,UT_SCALE,UT_LIM,UT_DBG,UT_GSRT,UT_XPT,UT_CPM,UT_END
};
#define UDSP_WRITE_16 0xf0
#define UDSP_READ_DATA 0xf1
#define UDSP_READ_STATUS 0xf2
// Simple resistive touch pin mapping (I80 only):
// XP = data_pins_low[1], XM = cs_pin, YP = dc_pin, YM = data_pins_low[0]
#define _UDSP_I2C 1
#define _UDSP_SPI 2
#define _UDSP_PAR8 3
#define _UDSP_PAR16 4
#define _UDSP_RGB 5
#define _UDSP_DSI 6
#define UDISP1_WHITE 1
#define UDISP1_BLACK 0
// #define MAX_LUTS 5
#define DISPLAY_INIT_MODE 0
#define DISPLAY_INIT_PARTIAL 1
#define DISPLAY_INIT_FULL 2
class uDisplay : public Renderer {
public:
uDisplay(char *);
~uDisplay(void);
Renderer *Init(void);
void DisplayInit(int8_t p,int8_t size,int8_t rot,int8_t font);
void Updateframe();
void DisplayOnff(int8_t on);
void Splash(void);
char *devname(void);
uint16_t fgcol(void);
uint16_t bgcol(void);
int8_t color_type(void);
// void dim(uint8_t dim); // original version with 4 bits resolution 0..15
virtual void dim10(uint8_t dim, uint16_t dim_gamma); // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller with 10 bits resolution
uint16_t GetColorFromIndex(uint8_t index);
void setRotation(uint8_t m);
void fillScreen(uint16_t color);
void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
void pushColors(uint16_t *data, uint16_t len, boolean first);
void TS_RotConvert(int16_t *x, int16_t *y);
void invertDisplay(boolean i);
void SetPwrCB(pwr_cb cb) { pwr_cbp = cb; };
void SetDimCB(dim_cb cb) { dim_cbp = cb; };
#ifdef USE_UNIVERSAL_TOUCH
// universal touch driver
bool utouch_Init(char **name);
uint16_t touched(void);
int16_t getPoint_x();
int16_t getPoint_y();
#endif // USE_UNIVERSAL_TOUCH
private:
uint8_t *frame_buffer;
// uint8_t *lut_full; // MOVED to EPDPanelConfig.lut_full_data
// uint8_t *lut_partial; // MOVED to EPDPanelConfig.lut_partial_data
// uint8_t *lut_array[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_array_data
#if SOC_MIPI_DSI_SUPPORTED
uint8_t dsp_cmds[1024]; // for DSI, does not hurt for ESP32
#else
uint8_t dsp_cmds[256];
#endif
char dname[16];
SPIController *spiController;
TwoWire *wire;
UniversalPanel* universal_panel = nullptr;
// ===== Panel Configuration Union =====
// Heap-allocated union holding the active panel config
// Allocated after parsing :H line, populated during INI parsing
PanelConfigUnion* panel_config = nullptr;
uint16_t x_addr_offs[4];
uint16_t y_addr_offs[4];
uint16_t splash_xp;
uint16_t splash_yp;
uint16_t fg_col;
uint16_t bg_col;
uint16_t gxs;
uint16_t gys;
uint16_t dimmer10_gamma;
uint16_t seta_xp1;
uint16_t seta_xp2;
uint16_t seta_yp1;
uint16_t seta_yp2;
// uint16_t lutptime; // MOVED to EPDPanelConfig.lut_partial_time
// uint16_t lut3time; // MOVED to EPDPanelConfig.update_time
uint16_t lut_num;
uint8_t bpp;
uint8_t col_type;
uint8_t interface;
uint8_t i2caddr;
uint8_t i2c_col_start;
uint8_t i2c_col_end;
uint8_t i2c_page_start;
uint8_t i2c_page_end;
uint16_t dsp_ncmds;
uint8_t dsp_on;
uint8_t dsp_off;
uint8_t allcmd_mode;
uint8_t splash_size;
uint8_t dimmer8;
uint8_t spi_speed;
// uint8_t spi_nr;
uint8_t rot[4];
uint8_t rot_t[4];
uint8_t madctrl;
uint8_t startline;
uint8_t saw_1;
uint8_t saw_2;
uint8_t saw_3;
uint8_t cur_rot;
uint8_t col_mode;
uint8_t inv_on;
uint8_t inv_off;
uint8_t sa_mode;
uint8_t dim_op;
// uint8_t lutfsize; // MOVED to EPDPanelConfig.lutfsize
// uint8_t lutpsize; // MOVED to EPDPanelConfig.lutpsize
// uint8_t lut_siz_full; // Local variable only
// uint8_t lut_siz_partial; // Local variable only
// uint8_t epcoffs_full; // MOVED to EPDPanelConfig.epcoffs_full
// uint8_t epc_full_cnt; // MOVED to EPDPanelConfig.epc_full_cnt
// uint8_t epcoffs_part; // MOVED to EPDPanelConfig.epcoffs_part
// uint8_t epc_part_cnt; // MOVED to EPDPanelConfig.epc_part_cnt
// uint8_t lut_cnt[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cnt_data
// uint8_t lut_cmd[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cmd
// uint8_t lut_siz[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_siz
uint8_t ep_mode;
// uint8_t ep_update_mode; // MOVED to EPDPanel.update_mode
uint8_t sspi;
int8_t spec_init;
int8_t wire_n;
int8_t i2c_scl;
int8_t i2c_sda;
int8_t reset;
int8_t splash_font;
int8_t bpmode;
// int8_t spi_cs;
// int8_t spi_clk;
// int8_t spi_mosi;
// int8_t spi_dc;
int8_t bpanel;
// int8_t spi_miso;
// int8_t busy_pin; // MOVED to EPDPanelConfig.busy_pin (EPD-only)
// int16_t lutftime; // MOVED to EPDPanelConfig.lut_full_time
int16_t rotmap_xmin;
int16_t rotmap_xmax;
int16_t rotmap_ymin;
int16_t rotmap_ymax;
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void drawPixel(int16_t x, int16_t y, uint16_t color);
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
uint32_t str2c(char **sp, char *vp, uint32_t len);
void i2c_command(uint8_t val);
uint8_t strlen_ln(char *str);
int32_t next_val(char **sp);
uint32_t next_hex(char **sp);
void setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap = false);
void delay_sync(int32_t time);
void reset_pin(int32_t delayl, int32_t delayh);
void delay_arg(uint32_t arg);
void send_spi_cmds(uint16_t cmd_offset, uint16_t cmd_size);
void send_spi_icmds(uint16_t cmd_size);
#ifdef USE_UNIVERSAL_TOUCH
// universal touch driver
void ut_trans(char **sp, uint8_t **ut_code);
int16_t ut_execute(uint8_t *ut_code);
uint32_t ut_par(char **cp, uint32_t mode);
uint8_t *ut_rd(uint8_t *io, uint32_t len, uint32_t amode);
uint8_t *ut_wr(uint8_t *io, uint32_t amode);
uint16_t ut_XPT2046(uint16_t zh);
int16_t besttwoavg( int16_t x , int16_t y , int16_t z );
uint8_t ut_array[16];
uint8_t ut_i2caddr;
uint8_t ut_spi_cs = -1;
int8_t ut_reset = -1;
int8_t ut_irq = -1;
uint8_t ut_spi_nr;
TwoWire *ut_wire = nullptr;;
SPIClass *ut_spi = nullptr;;
SPISettings ut_spiSettings;
char ut_name[8];
uint8_t *ut_init_code = nullptr;
uint8_t *ut_touch_code = nullptr;
uint8_t *ut_getx_code = nullptr;
uint8_t *ut_gety_code = nullptr;
#endif // USE_UNIVERSAL_TOUCH
};
#endif // _UDISP_

View File

@ -0,0 +1,103 @@
// WIP - NOT REALLY IMPLEMENTED!!!
// ======================================================
// uDisplay_DSI_panel.h - MIPI-DSI Display Panel Implementation
// ======================================================
#pragma once
#ifdef ESP32
#if __has_include("soc/soc_caps.h")
# include "soc/soc_caps.h"
#else
# error "No ESP capability header found"
#endif
#if SOC_MIPI_DSI_SUPPORTED
#include "uDisplay_panel.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_ldo_regulator.h"
struct DSIPanelConfig {
// Basic display info
uint16_t width;
uint16_t height;
uint8_t bpp; // 24
// DSI parameters (from :H line)
uint8_t dsi_lanes; // 2
int8_t te_pin; // -1 (no TE)
int8_t reset_pin; // -1 (no reset control)
int ldo_channel; // 3
int ldo_voltage_mv; // 2500
uint32_t pixel_clock_hz; // 54000000
uint32_t lane_speed_mbps; // 750
uint8_t rgb_order; // 0=RGB, 1=BGR
uint8_t data_endian; // 0=Big, 1=Little
// Video timing (from :V line)
struct {
uint16_t h_front_porch; // 160
uint16_t v_front_porch; // 40
uint16_t h_back_porch; // 160
uint16_t h_sync_pulse; // 12
uint16_t v_sync_pulse; // 10
uint16_t v_back_porch; // 23
} timing;
// Init commands (from :I section)
uint8_t* init_commands;
uint16_t init_commands_count;
// Display on/off commands (from :O and :o lines)
uint8_t cmd_display_on; // 0x29
uint8_t cmd_display_off; // 0x28
};
class DSIPanel : public UniversalPanel {
public:
// Constructor - takes ESP-IDF panel handle (already initialized)
DSIPanel(const DSIPanelConfig& config);
~DSIPanel();
// Core graphics API (must return bool)
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
bool pushColors(uint16_t *data, uint16_t len, bool not_swapped) override;
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
// Control API
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override;
bool updateFrame() override;
// Get direct framebuffer access (for DPI mode)
uint16_t* framebuffer = nullptr;
private:
// ESP-IDF panel handle
esp_lcd_panel_handle_t panel_handle = nullptr;
esp_lcd_panel_io_handle_t io_handle = nullptr;
esp_ldo_channel_handle_t ldo_handle = nullptr;
DSIPanelConfig cfg;
void sendInitCommandsDBI();
// Display parameters
uint8_t rotation = 0;
// Address window tracking
int16_t window_x0 = 0;
int16_t window_y0 = 0;
int16_t window_x1 = 0;
int16_t window_y1 = 0;
size_t framebuffer_size = 0;
uint32_t framebuffer_dirty = false;
};
#endif // SOC_MIPI_DSI_SUPPORTED
#endif // ESP32

View File

@ -0,0 +1,134 @@
// ======================================================
// uDisplay_epd_panel.h - E-Paper Display Panel Implementation
// ======================================================
#pragma once
#include <functional>
#include "uDisplay_panel.h"
#include "uDisplay_SPI_controller.h"
/**
* Configuration for E-Paper displays
*/
struct EPDPanelConfig {
uint16_t width;
uint16_t height;
uint8_t bpp; // Always 1 for EPD
uint8_t ep_mode; // 1=2-LUT, 2=5-LUT, 3=command-based
// Timing
int16_t lut_full_time;
uint16_t lut_partial_time;
uint16_t update_time;
// Pins
int8_t reset_pin;
int8_t busy_pin;
// EPD-specific flags
bool invert_colors; // If true, invert color logic
bool invert_framebuffer; // If true, invert when sending to display
bool busy_invert; // If true, busy pin is active low
// Busy timeout
uint16_t busy_timeout = 3000; // UDSP_BUSY_TIMEOUT
// Command bytes for ep_mode 2 (4.2" displays)
uint8_t saw_1 = 0; // First command for frame update
uint8_t saw_2 = 0; // Second command for frame update
uint8_t saw_3 = 0; // Third command for frame update
// LUT data (for ep_mode 1 - 2-LUT mode)
const uint8_t* lut_full = nullptr;
uint16_t lut_full_len = 0;
const uint8_t* lut_partial = nullptr;
uint16_t lut_partial_len = 0;
// LUT data (for ep_mode 2 - 5-LUT mode)
const uint8_t** lut_array = nullptr; // Array of 5 LUTs
const uint8_t* lut_cnt = nullptr; // Size of each LUT
uint8_t lut_cmd[5] = {0}; // Commands for each LUT
// Additional LUT management (owned by EPD panel)
uint8_t* lut_full_data = nullptr; // Owned pointer to full LUT data
uint8_t* lut_partial_data = nullptr; // Owned pointer to partial LUT data
uint8_t* lut_array_data[5] = {nullptr, nullptr, nullptr, nullptr, nullptr}; // Owned pointers to LUT array data
uint16_t lutfsize = 0; // Filled size of lut_full
uint16_t lutpsize = 0; // Filled size of lut_partial
uint8_t lut_cnt_data[5] = {0}; // Filled sizes of each LUT in array
uint8_t lut_siz[5] = {0}; // Allocated sizes of each LUT in array
// Command offsets for ep_mode 1 and 3
uint16_t epcoffs_full = 0; // Offset to full update commands
uint16_t epcoffs_part = 0; // Offset to partial update commands
uint8_t epc_full_cnt = 0; // Count of full update commands
uint8_t epc_part_cnt = 0; // Count of partial update commands
// Callback to send command sequences from descriptor
std::function<void(uint16_t offset, uint16_t count)> send_cmds_callback;
};
class EPDPanel : public UniversalPanel {
public:
EPDPanel(const EPDPanelConfig& config,
SPIController* spi_ctrl,
uint8_t* framebuffer); // REQUIRED for EPD
~EPDPanel();
// UniversalPanel interface
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override;
bool updateFrame() override;
// EPD-specific public methods (for uDisplay wrapper compatibility)
void resetDisplay();
void setLut(const uint8_t* lut, uint16_t len);
void setLuts(); // For ep_mode 2 (5-LUT mode)
void setMemoryArea(int x_start, int y_start, int x_end, int y_end);
void setMemoryPointer(int x, int y);
void clearFrameMemory(uint8_t color);
void displayFrame();
void delay_sync(int32_t ms);
// ep_mode 2 specific (4.2" displays)
void clearFrame_42();
void displayFrame_42();
// Frame memory management
void setFrameMemory(const uint8_t* image_buffer);
void setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void sendEPData();
// Update mode control (for ep_mode 1 and 3)
void setUpdateMode(uint8_t mode); // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL
EPDPanelConfig cfg;
private:
SPIController* spi;
uint8_t* fb_buffer; // Framebuffer (always used)
uint8_t update_mode; // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL
uint8_t rotation; // Current rotation (0-3)
// Address window for pushColors
int16_t window_x1 = 0;
int16_t window_y1 = 0;
int16_t window_x2 = 0;
int16_t window_y2 = 0;
// Private helpers
void waitBusy();
void drawAbsolutePixel(int x, int y, uint16_t color);
void sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width,
uint16_t rows, uint16_t cols_bytes);
};

View File

@ -0,0 +1,73 @@
#ifndef _UDISPLAY_I2C_PANEL_H_
#define _UDISPLAY_I2C_PANEL_H_
#include <Arduino.h>
#include <Wire.h>
#include "uDisplay_panel.h"
/**
* Configuration for I2C displays (typically OLED like SSD1306)
*/
struct I2CPanelConfig {
// ===== Display Dimensions =====
uint16_t width;
uint16_t height;
uint8_t bpp; // bits per pixel (typically 1 for OLED)
// ===== I2C Configuration =====
uint8_t i2c_address; // I2C device address
TwoWire* wire; // I2C bus instance
// ===== Display Commands =====
uint8_t cmd_set_addr_x; // Set column address command
uint8_t cmd_set_addr_y; // Set page address command
uint8_t cmd_write_ram; // Write data command
// ===== Display Control Commands =====
uint8_t cmd_display_on;
uint8_t cmd_display_off;
uint8_t cmd_invert_on;
uint8_t cmd_invert_off;
// ===== Address Range =====
uint8_t page_start; // Starting page
uint8_t page_end; // Ending page
uint8_t col_start; // Starting column
uint8_t col_end; // Ending column
// ===== Initialization =====
uint8_t* init_commands;
uint16_t init_commands_count;
};
class i2c_panel : public UniversalPanel {
public:
/**
* Constructor - receives configuration struct and framebuffer
*/
i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer);
bool updateFrame() override;
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override { return true; }
bool drawPixel(int16_t x, int16_t y, uint16_t color) override { return false; }
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override { return false; }
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override { return false; }
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override { return false; }
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override { return false; }
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override { return false; }
uint8_t* framebuffer = nullptr;
private:
// ===== Hardware & Configuration =====
I2CPanelConfig cfg; // Copy of config
// ===== Internal Helpers =====
void i2c_command(uint8_t val);
};
#endif // _UDISPLAY_I2C_PANEL_H_

View File

@ -0,0 +1,144 @@
#pragma once
#ifdef ESP32
#if __has_include("soc/soc_caps.h")
# include "soc/soc_caps.h"
#else
# error "No ESP capability header found"
#endif
#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS)
#include "uDisplay_panel.h"
#include "esp_private/gdma.h"
#include <hal/gpio_ll.h>
#include <hal/lcd_hal.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
#include "esp_rom_lldesc.h"
#include "esp_lcd_io_i80.h"
#include "esp_private/gdma.h"
#include <hal/gpio_ll.h>
#include <hal/lcd_hal.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
#include "esp_pm.h"
#include <hal/dma_types.h>
#include <rom/cache.h>
#include "esp_rom_lldesc.h"
#include <rom/gpio.h>
/**
* Configuration for I80 (8080/6800) parallel displays
*/
struct I80PanelConfig {
// ===== Display Dimensions =====
uint16_t width;
uint16_t height;
uint8_t bpp; // bits per pixel (typically 16)
uint8_t color_mode; // color mode (16, 18, etc.)
// ===== Bus Configuration =====
int8_t cs_pin; // Chip select
int8_t dc_pin; // Data/Command
int8_t wr_pin; // Write strobe
int8_t rd_pin; // Read strobe (-1 if not used)
int8_t data_pins_low[8]; // D0-D7 pins
int8_t data_pins_high[8]; // D8-D15 pins (for 16-bit bus)
uint8_t bus_width; // 8 or 16
uint32_t clock_speed_hz; // Bus clock speed
// ===== Display Commands =====
uint8_t cmd_set_addr_x; // Column address command
uint8_t cmd_set_addr_y; // Row/page address command
uint8_t cmd_write_ram; // Write to RAM command
uint8_t cmd_madctl; // Memory access control command (typically 0x36)
uint8_t cmd_startline; // Start line command (for sa_mode == 8)
// ===== Display Modes =====
uint8_t sa_mode; // Set address mode (8 = special, 16 = normal)
uint8_t allcmd_mode; // If true, send data as commands
// ===== Per-Rotation Configuration =====
uint8_t rot_cmd[4]; // MADCTL rotation command value per rotation
uint16_t x_addr_offset[4]; // Address offset per rotation
uint16_t y_addr_offset[4];
// ===== Initialization =====
uint8_t* init_commands;
uint16_t init_commands_count;
};
class I80Panel : public UniversalPanel {
public:
/**
* Constructor - receives configuration struct
*/
I80Panel(const I80PanelConfig& config);
virtual ~I80Panel();
// UniversalPanel interface
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override;
bool updateFrame() override;
// Simple resistive touch hack
uint32_t getSimpleResistiveTouch(uint32_t threshold);
// DMA functionality
bool initDMA();
void deInitDMA();
bool dmaBusy();
void dmaWait();
private:
// ===== Hardware & Configuration =====
I80PanelConfig cfg; // Copy of config
// ===== Display State =====
int16_t _width, _height;
uint8_t _rotation;
// I80 hardware handles
esp_lcd_i80_bus_handle_t _i80_bus;
volatile lcd_cam_dev_t* _dev;
uint32_t _clock_reg_value;
// DMA resources
bool _DMA_Enabled;
gdma_channel_handle_t _dma_chan;
lldesc_t *_dmadesc;
uint32_t _dmadesc_size;
// Current address window
int16_t _addr_x0, _addr_y0, _addr_x1, _addr_y1;
// Low-level I80 functions
void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq);
void _alloc_dmadesc(size_t len);
void _setup_dma_desc_links(const uint8_t *data, int32_t len);
void pb_beginTransaction(void);
void pb_endTransaction(void);
void pb_wait(void);
bool pb_busy(void);
void _pb_init_pin(bool read);
bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length);
void pb_writeData(uint32_t data, uint_fast8_t bit_length);
void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma);
void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma);
void cs_control(bool level);
// Color mode helpers
void writeColor(uint16_t color);
void setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
};
#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES
#endif // ESP32

View File

@ -0,0 +1,55 @@
// ======================================================
// uDisplay_rgb_panel.h - RGB Panel Implementation
// ======================================================
#pragma once
#ifdef ESP32
#if __has_include("soc/soc_caps.h")
# include "soc/soc_caps.h"
#else
# error "No ESP capability header found"
#endif
#endif
#if SOC_LCD_RGB_SUPPORTED
#include "uDisplay_panel.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
class RGBPanel : public UniversalPanel {
public:
// Takes only the ESP-IDF config
RGBPanel(const esp_lcd_rgb_panel_config_t *config);
~RGBPanel();
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override;
bool updateFrame() override;
uint16_t* framebuffer = nullptr;
private:
esp_lcd_panel_handle_t panel_handle = nullptr;
uint8_t rotation = 0;
uint16_t width = 0;
uint16_t height = 0;
int16_t window_x1 = 0;
int16_t window_y1 = 0;
int16_t window_x2 = 1;
int16_t window_y2 = 1;
size_t framebuffer_size = 0;
uint32_t framebuffer_dirty = false;
};
#endif //SOC_LCD_RGB_SUPPORTED

View File

@ -0,0 +1,98 @@
#ifndef _UDISPLAY_SPI_CONTROLLER_H_
#define _UDISPLAY_SPI_CONTROLLER_H_
#include <Arduino.h>
#include <SPI.h>
#ifdef ESP32
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "esp32-hal-spi.h"
#include "driver/spi_master.h"
#include "soc/gpio_periph.h"
#endif
#ifndef ESP32
#include "spi_register.h"
#endif
struct SPIControllerConfig {
uint8_t bus_nr;
int8_t cs;
int8_t clk;
int8_t mosi;
int8_t dc;
int8_t miso;
uint32_t speed;
};
/**
* Minimal SPIController - wraps low-level SPI functions
* Extracted from uDisplay_spi.cpp
*/
class SPIController {
public:
SPIController(const SPIControllerConfig& config);
~SPIController() = default;
// ===== Pin Control =====
void csLow();
void csHigh();
void dcLow();
void dcHigh();
// ===== Transaction Control =====
void beginTransaction();
void endTransaction();
// ===== High-Level Write Functions =====
void writeCommand(uint8_t cmd);
void writeData8(uint8_t data);
void writeData16(uint16_t data);
void writeData32(uint32_t data);
// ===== RA8876 Specific =====
uint8_t writeReg16(uint8_t reg, uint16_t wval);
uint8_t readData(void);
uint8_t readStatus(void);
// ===== Direct Access =====
SPIClass* getSPI() { return spi; }
// SPISettings getSPISettings() { return spi_settings; }
// ===== DMA =====
#ifdef ESP32
bool initDMA(uint16_t width, uint16_t height, uint8_t data);
void dmaWait(void);
bool dmaBusy(void);
void pushPixelsDMA(uint16_t* image, uint32_t len);
void pushPixels3DMA(uint8_t* image, uint32_t len);
#endif
SPIControllerConfig spi_config; // make this private in the future again!
private:
SPIClass* spi;
SPISettings spi_settings;
// ===== Low-Level Write Functions =====
void write8(uint8_t val);
void write8_slow(uint8_t val);
void write9(uint8_t val, uint8_t dc);
void write9_slow(uint8_t val, uint8_t dc);
void write16(uint16_t val);
void write32(uint32_t val);
void hw_write9(uint8_t val, uint8_t dc);
#ifdef ESP32
bool dma_enabled = false;
bool async_dma_enabled = false;
spi_host_device_t spi_host = VSPI_HOST;
bool DMA_Enabled = false;
uint8_t spiBusyCheck;
spi_device_handle_t dmaHAL = nullptr; // For DMA
spi_transaction_t trans;
#endif //ESP32
};
#endif // _UDISPLAY_SPI_CONTROLLER_H_

View File

@ -0,0 +1,116 @@
// WIP
// ======================================================
// uDisplay_spi_panel.h - SPI LCD Panel Implementation
// ======================================================
#pragma once
#include "uDisplay_panel.h"
#include "uDisplay_SPI_controller.h"
typedef struct LVGL_PARAMS_t {
uint16_t flushlines;
union {
uint8_t data;
struct {
uint8_t use_dma : 1;
uint8_t swap_color : 1;
uint8_t async_dma : 1; // force DMA completion before returning, avoid conflict with other devices on same bus. If set you should make sure the display is the only device on the bus
uint8_t busy_invert : 1;
uint8_t invert_bw : 1;
uint8_t resvd_3 : 1;
uint8_t resvd_4 : 1;
uint8_t resvd_5 : 1;
};
};
}LVGL_PARAMS_t;
/**
* Configuration for SPI-based displays
*/
struct SPIPanelConfig {
// ===== Display Dimensions =====
uint16_t width;
uint16_t height;
uint8_t bpp; // bits per pixel (1, 8, 16, etc.)
uint8_t col_mode; // color mode (16, 18, etc.)
// ===== Address Window Protocol =====
uint8_t cmd_set_addr_x; // Command to set X address range
uint8_t cmd_set_addr_y; // Command to set Y address range
uint8_t cmd_write_ram; // Command to write pixel data
// ===== Display Control Commands =====
uint8_t cmd_display_on;
uint8_t cmd_display_off;
uint8_t cmd_invert_on;
uint8_t cmd_invert_off;
uint8_t cmd_memory_access; // For rotation settings
uint8_t cmd_startline; // For vertical scroll offset
// ===== Per-Rotation Configuration =====
uint8_t rot_cmd[4]; // Memory access command variant for each rotation
uint16_t x_addr_offset[4]; // Address offset per rotation
uint16_t y_addr_offset[4];
uint8_t address_mode; // Addressing scheme (8, 16, 32-bit)
// ===== Flags =====
bool all_commands_mode; // If true: send data bytes as commands
// ===== Reset & Power Control =====
int8_t reset_pin; // GPIO for display reset (-1 if none)
// int8_t busy_pin; // REMOVED - busy_pin is EPD-only, moved to EPDPanelConfig
int8_t bpanel; // Backlight GPIO (-1 if none)
};
class SPIPanel : public UniversalPanel {
public:
/**
* Constructor - receives framebuffer from uDisplay if needed
*/
SPIPanel(const SPIPanelConfig& config,
SPIController* spi_ctrl,
uint8_t* framebuffer);
~SPIPanel();
// ===== UniversalPanel Interface =====
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
bool pushColors(uint16_t *data, uint16_t len, bool not_swapped = false) override;
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
bool displayOnff(int8_t on) override;
bool invertDisplay(bool invert) override;
bool setRotation(uint8_t rotation) override;
bool updateFrame() override;
private:
// ===== Hardware & Configuration =====
SPIController* spi; // Not owned by panel
SPIPanelConfig cfg; // Copy of config
// ===== Framebuffer =====
uint8_t* fb_buffer; // Framebuffer (if provided by uDisplay)
// ===== Display State =====
uint8_t rotation; // Current rotation (0-3)
int16_t window_x0, window_y0, window_x1, window_y1;
bool display_on;
bool inverted;
bool use_hw_spi = false;
// ===== Internal Helpers =====
void setAddrWindow_internal(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void sendAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void sendCommand(uint8_t cmd);
void sendData8(uint8_t data);
void sendData16(uint16_t data);
void writeColor(uint16_t color);
void resetDisplay();
void waitBusy();
};

View File

@ -0,0 +1,61 @@
#ifndef UDISPLAY_CONFIG_H
#define UDISPLAY_CONFIG_H
// Logging system interface - only declare if not building within Tasmota
#ifndef _TASMOTA_H_
enum LoggingLevels {
LOG_LEVEL_NONE,
LOG_LEVEL_ERROR,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG,
LOG_LEVEL_DEBUG_MORE
};
// Function declarations - only if not building within Tasmota
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
extern uint32_t ESP_ResetInfoReason(void);
extern bool UsePSRAM(void);
extern float CharToFloat(const char *str);
extern SPIClass *SpiBegin(uint32_t bus);
#endif // _TASMOTA_H_
// Enable universal touch support
#define USE_UNIVERSAL_TOUCH
enum uColorType { uCOLOR_BW, uCOLOR_COLOR };
// Color definitions
constexpr uint16_t UDISP_BLACK = 0x0000; /* 0, 0, 0 */
constexpr uint16_t UDISP_NAVY = 0x000F; /* 0, 0, 128 */
constexpr uint16_t UDISP_DARKGREEN = 0x03E0; /* 0, 128, 0 */
constexpr uint16_t UDISP_DARKCYAN = 0x03EF; /* 0, 128, 128 */
constexpr uint16_t UDISP_MAROON = 0x7800; /* 128, 0, 0 */
constexpr uint16_t UDISP_PURPLE = 0x780F; /* 128, 0, 128 */
constexpr uint16_t UDISP_OLIVE = 0x7BE0; /* 128, 128, 0 */
constexpr uint16_t UDISP_LIGHTGREY = 0xC618; /* 192, 192, 192 */
constexpr uint16_t UDISP_DARKGREY = 0x7BEF; /* 128, 128, 128 */
constexpr uint16_t UDISP_BLUE = 0x001F; /* 0, 0, 255 */
constexpr uint16_t UDISP_GREEN = 0x07E0; /* 0, 255, 0 */
constexpr uint16_t UDISP_CYAN = 0x07FF; /* 0, 255, 255 */
constexpr uint16_t UDISP_RED = 0xF800; /* 255, 0, 0 */
constexpr uint16_t UDISP_MAGENTA = 0xF81F; /* 255, 0, 255 */
constexpr uint16_t UDISP_YELLOW = 0xFFE0; /* 255, 255, 0 */
constexpr uint16_t UDISP_WHITE = 0xFFFF; /* 255, 255, 255 */
constexpr uint16_t UDISP_ORANGE = 0xFD20; /* 255, 165, 0 */
constexpr uint16_t UDISP_GREENYELLOW = 0xAFE5; /* 173, 255, 47 */
constexpr uint16_t UDISP_PINK = 0xFC18; /* 255, 128, 192 */
// epaper pseudo opcodes
constexpr uint8_t EP_RESET = 0x60;
constexpr uint8_t EP_LUT_FULL = 0x61;
constexpr uint8_t EP_LUT_PARTIAL = 0x62;
constexpr uint8_t EP_WAITIDLE = 0x63;
constexpr uint8_t EP_SET_MEM_AREA = 0x64;
constexpr uint8_t EP_SET_MEM_PTR = 0x65;
constexpr uint8_t EP_SEND_DATA = 0x66;
constexpr uint8_t EP_CLR_FRAME = 0x67;
constexpr uint8_t EP_SEND_FRAME = 0x68;
constexpr uint8_t EP_BREAK_RR_EQU = 0x69;
constexpr uint8_t EP_BREAK_RR_NEQ = 0x6a;
#endif

View File

@ -0,0 +1,37 @@
// ======================================================
// uDisplay_panel.h - Base Panel Interface
// ======================================================
#pragma once
#if CONFIG_IDF_TARGET_ESP32P4
#include "esp_cache.h"
#define CACHE_WRITEBACK_ADDR(addr, size) esp_cache_msync((void*)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M)
#else
#define CACHE_WRITEBACK_ADDR(addr, size) Cache_WriteBack_Addr(addr, size)
#endif
#include <Arduino.h>
class UniversalPanel {
public:
virtual ~UniversalPanel() {}
// Core graphics API - return true if handled, false for uDisplay fallback
virtual bool drawPixel(int16_t x, int16_t y, uint16_t color) = 0;
virtual bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0;
virtual bool pushColors(uint16_t *data, uint16_t len, bool first = false) = 0;
virtual bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) = 0;
virtual bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) = 0;
virtual bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) = 0;
// Control API - return true if handled, false for uDisplay fallback
virtual bool displayOnff(int8_t on) = 0;
virtual bool invertDisplay(bool invert) = 0;
virtual bool setRotation(uint8_t rotation) = 0;
// Frame update method for displays that need explicit updates
virtual bool updateFrame() = 0;
// Framebuffer - own or external
uint16_t* framebuffer = nullptr;
};

View File

@ -1,17 +1,33 @@
{ {
"name": "universal display Library", "name": "universal display Library",
"version": "0.1", "version": "0.9.0",
"description": "This is a library a couple of displays.", "description": "General driver library for a couple of display types.",
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/arendst/Tasmota", "homepage": "https://github.com/arendst/Tasmota",
"frameworks": "*", "frameworks": "arduino",
"platforms": "*", "platforms": [
"authors": "espressif8266",
"espressif32"
],
"authors": [
{ {
"name": "Gerhard Mutz", "name": "Gerhard Mutz",
"maintainer": true "maintainer": true
}, },
{
"name": "Christian Baars",
"maintainer": true
}
],
"build": { "build": {
"flags": [ "-I$PROJECT_DIR/include" ] "srcFilter": [
"+<*>",
"+<src/*>"
],
"flags": [
"-I$PROJECT_DIR/include",
"-I$PROJECT_DIR/tasmota/include"
],
"includeDir": "include"
} }
} }

View File

@ -1,9 +0,0 @@
name=universal display Library
version=0.1
author=Gerhard Mutz
maintainer=Gerhard Mutz
sentence=This is a library a couple of displays.
paragraph=This is a library a couple of displays.
category=Display
url=https://github.com/arendst/Tasmota
architectures=*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
// ======================================================
// uDisplay_DSI_panel.cpp - Hardcoded JD9165 MIPI-DSI Implementation
// Based on esp_lcd_jd9165.c from Espressif
// ======================================================
#include "uDisplay_DSI_panel.h"
#if SOC_MIPI_DSI_SUPPORTED
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_ldo_regulator.h"
#include "driver/gpio.h"
#include <rom/cache.h>
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
DSIPanel::DSIPanel(const DSIPanelConfig& config)
: cfg(config), rotation(0)
{
framebuffer_size = cfg.width * cfg.height * 2;
esp_err_t ret;
// Step 1: Initialize LDO for display power (from config)
if (cfg.ldo_channel >= 0 && cfg.ldo_voltage_mv > 0) {
esp_ldo_channel_config_t ldo_config = {
.chan_id = cfg.ldo_channel,
.voltage_mv = cfg.ldo_voltage_mv,
};
ret = esp_ldo_acquire_channel(&ldo_config, &ldo_handle);
if (ret != ESP_OK) {
AddLog(3, "DSI: Failed to acquire LDO: %d", ret);
return;
}
AddLog(3, "DSI: LDO enabled (ch %d @ %dmV)", cfg.ldo_channel, cfg.ldo_voltage_mv);
delay(10);
} else {
AddLog(3, "DSI: No LDO configuration");
}
// Step 2: Create DSI bus (from config)
esp_lcd_dsi_bus_handle_t dsi_bus = nullptr;
esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = cfg.dsi_lanes,
.lane_bit_rate_mbps = cfg.lane_speed_mbps
};
ret = esp_lcd_new_dsi_bus(&bus_config, &dsi_bus);
if (ret != ESP_OK) {
AddLog(3, "DSI: Failed to create DSI bus: %d", ret);
return;
}
AddLog(3, "DSI: DSI bus created");
// Step 3: Create DBI IO for commands
esp_lcd_dbi_io_config_t io_config = {
.virtual_channel = 0,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
ret = esp_lcd_new_panel_io_dbi(dsi_bus, &io_config, &io_handle);
if (ret != ESP_OK) {
AddLog(3, "DSI: Failed to create DBI IO: %d", ret);
return;
}
AddLog(3, "DSI: DBI IO created");
// Step 4: Configure DPI panel (from config)
esp_lcd_dpi_panel_config_t dpi_config = {};
dpi_config.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
dpi_config.dpi_clock_freq_mhz = cfg.pixel_clock_hz / 1000000;
dpi_config.virtual_channel = 0;
dpi_config.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
dpi_config.num_fbs = 1;
dpi_config.video_timing.h_size = cfg.width;
dpi_config.video_timing.v_size = cfg.height;
dpi_config.video_timing.hsync_back_porch = cfg.timing.h_back_porch;
dpi_config.video_timing.hsync_pulse_width = cfg.timing.h_sync_pulse;
dpi_config.video_timing.hsync_front_porch = cfg.timing.h_front_porch;
dpi_config.video_timing.vsync_back_porch = cfg.timing.v_back_porch;
dpi_config.video_timing.vsync_pulse_width = cfg.timing.v_sync_pulse;
dpi_config.video_timing.vsync_front_porch = cfg.timing.v_front_porch;
dpi_config.flags.use_dma2d = 1;
AddLog(3, "DSI: DPI config: clk=%dMHz res=%dx%d", dpi_config.dpi_clock_freq_mhz, cfg.width, cfg.height);
AddLog(3, "DSI: H timing: BP=%d PW=%d FP=%d", cfg.timing.h_back_porch, cfg.timing.h_sync_pulse, cfg.timing.h_front_porch);
AddLog(3, "DSI: V timing: BP=%d PW=%d FP=%d", cfg.timing.v_back_porch, cfg.timing.v_sync_pulse, cfg.timing.v_front_porch);
AddLog(3, "DSI: Expected: clk=54MHz res=1024x600 H:160/40/160 V:23/10/12");
// Step 5: Create DPI panel
ret = esp_lcd_new_panel_dpi(dsi_bus, &dpi_config, &panel_handle);
if (ret != ESP_OK) {
AddLog(3, "DSI: Failed to create DPI panel: %d", ret);
return;
}
AddLog(3, "DSI: DPI panel created");
// Step 6: Reset via GPIO (from config)
if (cfg.reset_pin >= 0) {
gpio_config_t gpio_conf = {
.pin_bit_mask = 1ULL << cfg.reset_pin,
.mode = GPIO_MODE_OUTPUT,
};
gpio_config(&gpio_conf);
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
delay(5);
gpio_set_level((gpio_num_t)cfg.reset_pin, 0);
delay(10);
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
delay(120);
AddLog(3, "DSI: GPIO reset completed (pin %d)", cfg.reset_pin);
} else {
AddLog(3, "DSI: No reset pin configured");
}
// Step 7: Initialize DPI panel
ret = esp_lcd_panel_init(panel_handle);
if (ret != ESP_OK) {
AddLog(3, "DSI: Panel init failed: %d", ret);
return;
}
AddLog(3, "DSI: DPI panel initialized");
// Step 8: Get framebuffer
void* fb_ptr = nullptr;
ret = esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb_ptr);
if (ret == ESP_OK && fb_ptr != nullptr) {
framebuffer = (uint16_t*)fb_ptr;
AddLog(3, "DSI: Framebuffer at %p", framebuffer);
} else {
framebuffer = nullptr;
AddLog(3, "DSI: No framebuffer, using draw_bitmap");
}
// Step 9: Send init commands from INI file
if (cfg.init_commands && cfg.init_commands_count > 0) {
AddLog(3, "DSI: Sending init commands from INI file");
uint16_t index = 0;
uint16_t cmd_num = 0;
while (index < cfg.init_commands_count) {
uint8_t cmd = cfg.init_commands[index++];
uint8_t data_size = cfg.init_commands[index++];
if (data_size > 0) {
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, &cfg.init_commands[index], data_size);
index += data_size;
} else {
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
}
if (ret != ESP_OK) {
AddLog(3, "DSI: Cmd 0x%02x failed: %d", cmd, ret);
}
uint8_t delay_ms = cfg.init_commands[index++];
if (delay_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
cmd_num++;
}
AddLog(3, "DSI: Sent %d commands from INI", cmd_num);
} else {
AddLog(3, "DSI: No init commands in config");
}
}
DSIPanel::~DSIPanel() {
if (panel_handle) {
esp_lcd_panel_del(panel_handle);
}
if (io_handle) {
esp_lcd_panel_io_del(io_handle);
}
if (ldo_handle) {
esp_ldo_release_channel(ldo_handle);
}
}
bool DSIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
if (!framebuffer || x < 0 || y < 0 || x >= cfg.width || y >= cfg.height) return true;
int16_t w = cfg.width, h = cfg.height;
switch (rotation) {
case 1: std::swap(w, h); std::swap(x, y); x = w - x - 1; break;
case 2: x = w - x - 1; y = h - y - 1; break;
case 3: std::swap(w, h); std::swap(x, y); y = h - y - 1; break;
}
uint16_t* p = &framebuffer[y * cfg.width + x];
*p = color;
framebuffer_dirty = true;
return true;
}
bool DSIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
for (int16_t yp = y; yp < y + h; yp++) {
uint16_t* line_start = &framebuffer[yp * cfg.width + x];
for (int16_t i = 0; i < w; i++) {
line_start[i] = color;
}
// CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2);
}
framebuffer_dirty = true;
return true;
}
bool DSIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
return fillRect(x, y, w, 1, color);
}
bool DSIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
return fillRect(x, y, 1, h, color);
}
bool DSIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) {
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, window_x0, window_y0, window_x1, window_y1, data);
return (ret == ESP_OK);
}
bool DSIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
window_x0 = x0;
window_y0 = y0;
window_x1 = x1;
window_y1 = y1;
return true;
}
bool DSIPanel::displayOnff(int8_t on) {
if (!io_handle) return false;
// Use commands from descriptor
uint8_t cmd = on ? cfg.cmd_display_on : cfg.cmd_display_off;
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
return (ret == ESP_OK);
}
bool DSIPanel::invertDisplay(bool invert) {
if (!io_handle) return false;
// Standard MIPI DCS commands for invert
uint8_t cmd = invert ? 0x21 : 0x20; // 0x21 = INVON, 0x20 = INVOFF
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
return (ret == ESP_OK);
}
bool DSIPanel::setRotation(uint8_t rot) {
if (!io_handle) return false;
rotation = rot & 3;
// Standard MIPI DCS MADCTL (0x36) values for rotation
// These are common values but may need adjustment for specific displays
uint8_t madctl_val = 0;
switch (rotation) {
case 0: // Portrait
madctl_val = 0x00; // Normal
break;
case 1: // Landscape (90° clockwise)
madctl_val = 0x60; // MX + MV
break;
case 2: // Portrait inverted (180°)
madctl_val = 0xC0; // MX + MY
break;
case 3: // Landscape inverted (270° clockwise)
madctl_val = 0xA0; // MY + MV
break;
}
// Send MADCTL command (0x36) with rotation value
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl_val, 1);
return false; // pass job to Renderer
}
bool DSIPanel::updateFrame() {
if (!framebuffer_dirty) {
return true;
}
CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough!
framebuffer_dirty = false; // ← RESET
return true;
}
#endif // SOC_MIPI_DSI_SUPPORTED

View File

@ -0,0 +1,514 @@
// ======================================================
// uDisplay_epd_panel.cpp - E-Paper Display Panel Implementation
// ======================================================
#include "uDisplay_EPD_panel.h"
#include <Arduino.h>
// EPD Command Definitions
static constexpr uint8_t DRIVER_OUTPUT_CONTROL = 0x01;
static constexpr uint8_t BOOSTER_SOFT_START_CONTROL = 0x0C;
static constexpr uint8_t GATE_SCAN_START_POSITION = 0x0F;
static constexpr uint8_t DEEP_SLEEP_MODE = 0x10;
static constexpr uint8_t DATA_ENTRY_MODE_SETTING = 0x11;
static constexpr uint8_t SW_RESET = 0x12;
static constexpr uint8_t TEMPERATURE_SENSOR_CONTROL = 0x1A;
static constexpr uint8_t MASTER_ACTIVATION = 0x20;
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_1 = 0x21;
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_2 = 0x22;
static constexpr uint8_t WRITE_RAM = 0x24;
static constexpr uint8_t WRITE_VCOM_REGISTER = 0x2C;
static constexpr uint8_t WRITE_LUT_REGISTER = 0x32;
static constexpr uint8_t SET_DUMMY_LINE_PERIOD = 0x3A;
static constexpr uint8_t SET_GATE_TIME = 0x3B;
static constexpr uint8_t BORDER_WAVEFORM_CONTROL = 0x3C;
static constexpr uint8_t SET_RAM_X_ADDRESS_START_END_POSITION = 0x44;
static constexpr uint8_t SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45;
static constexpr uint8_t SET_RAM_X_ADDRESS_COUNTER = 0x4E;
static constexpr uint8_t SET_RAM_Y_ADDRESS_COUNTER = 0x4F;
static constexpr uint8_t TERMINATE_FRAME_READ_WRITE = 0xFF;
EPDPanel::EPDPanel(const EPDPanelConfig& config,
SPIController* spi_ctrl,
uint8_t* framebuffer)
: spi(spi_ctrl), cfg(config), fb_buffer(framebuffer), update_mode(0), rotation(0)
{
// Don't do automatic initialization here - let the descriptor init commands handle it
// The uDisplay framework will call send_spi_cmds() after panel creation
// which will handle reset, LUT setup, and initial display state
}
EPDPanel::~EPDPanel() {
// Panel doesn't own framebuffer or SPI controller
// Free owned LUT data
if (cfg.lut_full_data) {
free(cfg.lut_full_data);
}
if (cfg.lut_partial_data) {
free(cfg.lut_partial_data);
}
for (uint8_t i = 0; i < 5; i++) {
if (cfg.lut_array_data[i]) {
free(cfg.lut_array_data[i]);
}
}
}
void EPDPanel::delay_sync(int32_t ms) {
uint8_t busy_level = cfg.busy_invert ? LOW : HIGH;
uint32_t time = millis();
if (cfg.busy_pin >= 0) {
while (digitalRead(cfg.busy_pin) == busy_level) {
delay(1);
if ((millis() - time) > cfg.busy_timeout) {
break;
}
}
} else {
delay(ms);
}
}
void EPDPanel::resetDisplay() {
if (cfg.reset_pin < 0) return;
pinMode(cfg.reset_pin, OUTPUT);
digitalWrite(cfg.reset_pin, HIGH);
delay(10);
digitalWrite(cfg.reset_pin, LOW);
delay(10);
digitalWrite(cfg.reset_pin, HIGH);
delay(10);
delay_sync(100); // Use delay_sync instead of waitBusy
}
void EPDPanel::waitBusy() {
// Deprecated - use delay_sync instead
delay_sync(cfg.update_time);
}
void EPDPanel::setLut(const uint8_t* lut, uint16_t len) {
if (!lut || len == 0) return;
spi->beginTransaction();
spi->csLow();
spi->writeCommand(WRITE_LUT_REGISTER);
for (uint16_t i = 0; i < len; i++) {
spi->writeData8(lut[i]);
}
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::setMemoryArea(int x_start, int y_start, int x_end, int y_end) {
int x_start1 = (x_start >> 3) & 0xFF;
int x_end1 = (x_end >> 3) & 0xFF;
int y_start1 = y_start & 0xFF;
int y_start2 = (y_start >> 8) & 0xFF;
int y_end1 = y_end & 0xFF;
int y_end2 = (y_end >> 8) & 0xFF;
spi->beginTransaction();
spi->csLow();
spi->writeCommand(SET_RAM_X_ADDRESS_START_END_POSITION);
spi->writeData8(x_start1);
spi->writeData8(x_end1);
spi->writeCommand(SET_RAM_Y_ADDRESS_START_END_POSITION);
if (cfg.ep_mode == 3) {
// ep_mode 3: reversed Y order
spi->writeData8(y_end1);
spi->writeData8(y_end2);
spi->writeData8(y_start1);
spi->writeData8(y_start2);
} else {
spi->writeData8(y_start1);
spi->writeData8(y_start2);
spi->writeData8(y_end1);
spi->writeData8(y_end2);
}
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::setMemoryPointer(int x, int y) {
int x1, y1, y2;
if (cfg.ep_mode == 3) {
x1 = (x >> 3) & 0xFF;
y--;
y1 = y & 0xFF;
y2 = (y >> 8) & 0xFF;
} else {
x1 = (x >> 3) & 0xFF;
y1 = y & 0xFF;
y2 = (y >> 8) & 0xFF;
}
spi->beginTransaction();
spi->csLow();
spi->writeCommand(SET_RAM_X_ADDRESS_COUNTER);
spi->writeData8(x1);
spi->writeCommand(SET_RAM_Y_ADDRESS_COUNTER);
spi->writeData8(y1);
spi->writeData8(y2);
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::clearFrameMemory(uint8_t color) {
setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1);
setMemoryPointer(0, 0);
spi->beginTransaction();
spi->csLow();
spi->writeCommand(WRITE_RAM);
uint32_t pixel_count = (cfg.width * cfg.height) / 8;
for (uint32_t i = 0; i < pixel_count; i++) {
spi->writeData8(color);
}
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::displayFrame() {
spi->beginTransaction();
spi->csLow();
spi->writeCommand(DISPLAY_UPDATE_CONTROL_2);
spi->writeData8(0xC4);
spi->writeCommand(MASTER_ACTIVATION);
spi->writeData8(TERMINATE_FRAME_READ_WRITE);
spi->csHigh();
spi->endTransaction();
delay_sync(cfg.update_time); // Use delay_sync with proper timing
}
void EPDPanel::drawAbsolutePixel(int x, int y, uint16_t color) {
// Bounds check using physical dimensions
if (x < 0 || x >= cfg.width || y < 0 || y >= cfg.height) {
return;
}
// CRITICAL: Must match Renderer::drawPixel() layout!
//
// Two rendering systems write to the SAME framebuffer:
// 1. Renderer::drawPixel() - used by DrawStringAt() for text (Splash Screen)
// 2. EPDPanel::drawPixel() - used by Adafruit_GFX for graphics (circles, lines)
//
// Both MUST use the same framebuffer layout: Y-column-wise
// Layout: fb[x + (y/8)*width] with bit position (y&7)
// This means 8 vertical pixels are stored in one byte.
//
// setFrameMemory() will convert Y-column to X-row format when sending to hardware.
if (color) {
fb_buffer[x + (y / 8) * cfg.width] |= (1 << (y & 7));
} else {
fb_buffer[x + (y / 8) * cfg.width] &= ~(1 << (y & 7));
}
}
// ===== UniversalPanel Interface Implementation =====
bool EPDPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
if (!fb_buffer) return false;
// Get rotated dimensions for bounds check
int16_t w = cfg.width, h = cfg.height;
if (rotation == 1 || rotation == 3) {
std::swap(w, h);
}
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) {
return false; // Out of bounds
}
// Apply rotation transformation using PHYSICAL dimensions (gxs/gys)
switch (rotation) {
case 1:
std::swap(x, y);
x = cfg.width - x - 1; // gxs
break;
case 2:
x = cfg.width - x - 1; // gxs
y = cfg.height - y - 1; // gys
break;
case 3:
std::swap(x, y);
y = cfg.height - y - 1; // gys
break;
}
// Convert color to monochrome and draw
drawAbsolutePixel(x, y, (color != 0) ? 1 : 0);
return true;
}
bool EPDPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
// Use drawPixel to handle rotation properly
for (int16_t yp = y; yp < y + h; yp++) {
for (int16_t xp = x; xp < x + w; xp++) {
drawPixel(xp, yp, color);
}
}
return true;
}
bool EPDPanel::pushColors(uint16_t *data, uint16_t len, bool first) {
// Convert RGB565 to monochrome and write to framebuffer
// Pixel is white if at least one of the 3 RGB components is above 50%
static constexpr uint16_t RGB16_TO_MONO = 0x8410;
if (!fb_buffer) return false;
// Write pixels to framebuffer based on window coordinates
// IMPORTANT: window coordinates are in LOGICAL (rotated) space,
// so we must use drawPixel (not drawAbsolutePixel) to apply rotation!
for (int16_t y = window_y1; y < window_y2 && len > 0; y++) {
for (int16_t x = window_x1; x < window_x2 && len > 0; x++, len--) {
uint16_t color = *data++;
// Convert to mono: white if any component > 50%
bool pixel = (color & RGB16_TO_MONO) ? true : false;
if (cfg.invert_colors) pixel = !pixel;
drawPixel(x, y, pixel ? 1 : 0);
}
}
return true; // Handled by EPD panel
}
bool EPDPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
// Save window coordinates for pushColors
window_x1 = x0;
window_y1 = y0;
window_x2 = x1;
window_y2 = y1;
return true;
}
bool EPDPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
while (w--) {
drawPixel(x, y, color);
x++;
}
return true;
}
bool EPDPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
while (h--) {
drawPixel(x, y, color);
y++;
}
return true;
}
bool EPDPanel::displayOnff(int8_t on) {
// EPD doesn't have on/off in traditional sense
return true;
}
bool EPDPanel::invertDisplay(bool invert) {
// Toggle color inversion logic
cfg.invert_colors = invert;
// For EPD, we need to redraw the entire display when inversion changes
if (fb_buffer) {
// Invert the entire framebuffer
uint32_t byte_count = (cfg.width * cfg.height) / 8;
for (uint32_t i = 0; i < byte_count; i++) {
fb_buffer[i] = ~fb_buffer[i];
}
updateFrame();
}
return true;
}
bool EPDPanel::setRotation(uint8_t rot) {
rotation = rot & 3; // Store rotation (0-3)
return true;
}
bool EPDPanel::updateFrame() {
if (!fb_buffer) return false;
// Handle different EPD modes
if (cfg.ep_mode == 1 || cfg.ep_mode == 3) {
// Mode 1 (2-LUT) or Mode 3 (command-based): Use descriptor command sequences
switch (update_mode) {
case 1: // DISPLAY_INIT_PARTIAL
if (cfg.epc_part_cnt && cfg.send_cmds_callback) {
cfg.send_cmds_callback(cfg.epcoffs_part, cfg.epc_part_cnt);
}
break;
case 2: // DISPLAY_INIT_FULL
if (cfg.epc_full_cnt && cfg.send_cmds_callback) {
cfg.send_cmds_callback(cfg.epcoffs_full, cfg.epc_full_cnt);
}
break;
default: // DISPLAY_INIT_MODE (0)
// Default: write framebuffer and display
setFrameMemory(fb_buffer, 0, 0, cfg.width, cfg.height);
displayFrame();
}
} else if (cfg.ep_mode == 2) {
// Mode 2 (5-LUT / 4.2" displays): Use internal displayFrame_42
displayFrame_42();
}
return true;
}
// ===== ep_mode 2 Support (5-LUT mode) =====
void EPDPanel::setLuts() {
if (!cfg.lut_array || !cfg.lut_cnt) return;
for (uint8_t index = 0; index < 5; index++) {
if (cfg.lut_cmd[index] == 0 || !cfg.lut_array[index]) continue;
spi->beginTransaction();
spi->csLow();
spi->writeCommand(cfg.lut_cmd[index]);
for (uint8_t count = 0; count < cfg.lut_cnt[index]; count++) {
spi->writeData8(cfg.lut_array[index][count]);
}
spi->csHigh();
spi->endTransaction();
}
}
void EPDPanel::clearFrame_42() {
spi->beginTransaction();
spi->csLow();
spi->writeCommand(cfg.saw_1);
for (uint16_t j = 0; j < cfg.height; j++) {
for (uint16_t i = 0; i < cfg.width; i++) {
spi->writeData8(0xFF);
}
}
spi->writeCommand(cfg.saw_2);
for (uint16_t j = 0; j < cfg.height; j++) {
for (uint16_t i = 0; i < cfg.width; i++) {
spi->writeData8(0xFF);
}
}
spi->writeCommand(cfg.saw_3);
spi->csHigh();
spi->endTransaction();
delay_sync(100);
}
void EPDPanel::displayFrame_42() {
spi->beginTransaction();
spi->csLow();
spi->writeCommand(cfg.saw_1);
for(int i = 0; i < cfg.width / 8 * cfg.height; i++) {
spi->writeData8(0xFF);
}
spi->csHigh();
spi->endTransaction();
delay(2);
spi->beginTransaction();
spi->csLow();
spi->writeCommand(cfg.saw_2);
for(int i = 0; i < cfg.width / 8 * cfg.height; i++) {
spi->writeData8(fb_buffer[i] ^ 0xff);
}
spi->csHigh();
spi->endTransaction();
delay(2);
setLuts();
spi->beginTransaction();
spi->csLow();
spi->writeCommand(cfg.saw_3);
spi->csHigh();
spi->endTransaction();
delay_sync(100);
}
// ===== Frame Memory Management =====
// Helper: Convert Y-column framebuffer to X-row format and send via SPI
// Y-column: fb[x + (y/8)*width] with bit (y&7) - 8 vertical pixels per byte
// X-row: 8 horizontal pixels per byte, MSB = leftmost pixel
void EPDPanel::sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width,
uint16_t rows, uint16_t cols_bytes) {
for (uint16_t row = 0; row < rows; row++) {
for (uint16_t x_byte = 0; x_byte < cols_bytes; x_byte++) {
uint8_t byte_out = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
uint16_t x = x_byte * 8 + bit;
uint8_t pixel = (y_column_buffer[x + (row / 8) * buffer_width] >> (row & 7)) & 1;
if (pixel) byte_out |= (0x80 >> bit);
}
spi->writeData8(byte_out ^ 0xff);
}
}
}
void EPDPanel::setFrameMemory(const uint8_t* image_buffer) {
setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1);
setMemoryPointer(0, 0);
spi->beginTransaction();
spi->csLow();
spi->writeCommand(WRITE_RAM);
sendYColumnAsXRow(image_buffer, cfg.width, cfg.height, cfg.width / 8);
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height) {
if (!image_buffer) return;
// Align to 8-pixel boundary
x &= 0xFFF8;
image_width &= 0xFFF8;
uint16_t x_end = (x + image_width >= cfg.width) ? cfg.width - 1 : x + image_width - 1;
uint16_t y_end = (y + image_height >= cfg.height) ? cfg.height - 1 : y + image_height - 1;
// Full screen optimization
if (!x && !y && image_width == cfg.width && image_height == cfg.height) {
setFrameMemory(image_buffer);
return;
}
setMemoryArea(x, y, x_end, y_end);
setMemoryPointer(x, y);
spi->beginTransaction();
spi->csLow();
spi->writeCommand(WRITE_RAM);
sendYColumnAsXRow(image_buffer, image_width, y_end - y + 1, (x_end - x + 1) / 8);
spi->csHigh();
spi->endTransaction();
}
void EPDPanel::sendEPData() {
// EP_SEND_DATA (0x66) - used by some display.ini files (e.g. v2)
// Must also convert Y-column to X-row format like setFrameMemory()
sendYColumnAsXRow(fb_buffer, cfg.width, cfg.height, cfg.width / 8);
}
// ===== Update Mode Control =====
void EPDPanel::setUpdateMode(uint8_t mode) {
update_mode = mode;
}

View File

@ -0,0 +1,61 @@
#include "uDisplay_I2C_panel.h"
i2c_panel::i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer)
: cfg(config), framebuffer(framebuffer) {
// Execute initialization commands
if (cfg.init_commands && cfg.init_commands_count > 0) {
for (uint16_t i = 0; i < cfg.init_commands_count; i++) {
i2c_command(cfg.init_commands[i]);
}
}
}
bool i2c_panel::updateFrame() {
if (!framebuffer) return false;
i2c_command(cfg.cmd_set_addr_x | 0x0);
i2c_command(cfg.page_start | 0x0);
i2c_command(cfg.page_end | 0x0);
uint8_t ys = cfg.height >> 3;
uint8_t xs = cfg.width >> 3;
uint8_t m_row = cfg.cmd_set_addr_y;
uint8_t m_col = cfg.col_start;
uint16_t p = 0;
uint8_t i, j, k = 0;
for (i = 0; i < ys; i++) {
i2c_command(0xB0 + i + m_row);
i2c_command(m_col & 0xf);
i2c_command(0x10 | (m_col >> 4));
for (j = 0; j < 8; j++) {
cfg.wire->beginTransmission(cfg.i2c_address);
cfg.wire->write(0x40);
for (k = 0; k < xs; k++, p++) {
cfg.wire->write(framebuffer[p]);
}
cfg.wire->endTransmission();
}
}
return true;
}
bool i2c_panel::displayOnff(int8_t on) {
i2c_command(on ? cfg.cmd_display_on : cfg.cmd_display_off);
return true;
}
bool i2c_panel::invertDisplay(bool invert) {
i2c_command(invert ? cfg.cmd_invert_on : cfg.cmd_invert_off);
return true;
}
void i2c_panel::i2c_command(uint8_t val) {
cfg.wire->beginTransmission(cfg.i2c_address);
cfg.wire->write(0);
cfg.wire->write(val);
cfg.wire->endTransmission();
}

View File

@ -0,0 +1,733 @@
#include "uDisplay_I80_panel.h"
#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS )
#ifdef UDSP_DEBUG
extern void AddLog(uint32_t loglevel, const char *formatP, ...);
#define LOG_LEVEL_DEBUG 3
#endif
// Pin control helpers
static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; }
static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; }
static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); }
static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); }
I80Panel::I80Panel(const I80PanelConfig& config)
: cfg(config),
_width(config.width), _height(config.height), _rotation(0),
_i80_bus(nullptr), _dev(nullptr), _dmadesc(nullptr),
_DMA_Enabled(false), _dma_chan(nullptr), _dmadesc_size(0),
_addr_x0(0), _addr_y0(0), _addr_x1(0), _addr_y1(0) {
framebuffer = nullptr;
// Initialize pins manually FIRST (matching old code order)
if (cfg.cs_pin >= 0) {
pinMode(cfg.cs_pin, OUTPUT);
digitalWrite(cfg.cs_pin, HIGH);
}
pinMode(cfg.dc_pin, OUTPUT);
digitalWrite(cfg.dc_pin, HIGH);
pinMode(cfg.wr_pin, OUTPUT);
digitalWrite(cfg.wr_pin, HIGH);
if (cfg.rd_pin >= 0) {
pinMode(cfg.rd_pin, OUTPUT);
digitalWrite(cfg.rd_pin, HIGH);
}
for (int i = 0; i < 8; i++) {
pinMode(cfg.data_pins_low[i], OUTPUT);
}
if (cfg.bus_width == 16) {
for (int i = 0; i < 8; i++) {
pinMode(cfg.data_pins_high[i], OUTPUT);
}
}
// Now create I80 bus config
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = cfg.dc_pin,
.wr_gpio_num = cfg.wr_pin,
.clk_src = LCD_CLK_SRC_DEFAULT,
.bus_width = cfg.bus_width,
.max_transfer_bytes = (size_t)cfg.width * cfg.height * 2
};
// Set data pins
if (cfg.bus_width == 8) {
for (int i = 0; i < 8; i++) {
bus_config.data_gpio_nums[i] = cfg.data_pins_low[i];
}
} else {
for (int i = 0; i < 8; i++) {
bus_config.data_gpio_nums[i] = cfg.data_pins_low[i];
bus_config.data_gpio_nums[i + 8] = cfg.data_pins_high[i];
}
}
// Create I80 bus (this will take over GPIO matrix for DC, WR, and data pins)
esp_lcd_new_i80_bus(&bus_config, &_i80_bus);
// Calculate clock using original algorithm
uint32_t div_a, div_b, div_n, clkcnt;
calcClockDiv(&div_a, &div_b, &div_n, &clkcnt, 240*1000*1000, cfg.clock_speed_hz);
lcd_cam_lcd_clock_reg_t lcd_clock;
lcd_clock.lcd_clkcnt_n = std::max((uint32_t)1u, clkcnt - 1);
lcd_clock.lcd_clk_equ_sysclk = (clkcnt == 1);
lcd_clock.lcd_ck_idle_edge = true;
lcd_clock.lcd_ck_out_edge = false;
lcd_clock.lcd_clkm_div_num = div_n;
lcd_clock.lcd_clkm_div_b = div_b;
lcd_clock.lcd_clkm_div_a = div_a;
lcd_clock.lcd_clk_sel = 2; // 240MHz
lcd_clock.clk_en = true;
_clock_reg_value = lcd_clock.val;
_alloc_dmadesc(1);
_dev = &LCD_CAM;
// EXECUTE INITIALIZATION COMMANDS (from original uDisplay code)
if (cfg.init_commands && cfg.init_commands_count > 0) {
uint16_t index = 0;
pb_beginTransaction();
while (index < cfg.init_commands_count) {
cs_control(false);
uint8_t cmd = cfg.init_commands[index++];
pb_writeCommand(cmd, 8);
if (index < cfg.init_commands_count) {
uint8_t args = cfg.init_commands[index++];
uint8_t arg_count = args & 0x1f;
#ifdef UDSP_DEBUG
AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", cmd, arg_count);
#endif
for (uint32_t cnt = 0; cnt < arg_count && index < cfg.init_commands_count; cnt++) {
uint8_t arg_data = cfg.init_commands[index++];
#ifdef UDSP_DEBUG
AddLog(LOG_LEVEL_DEBUG, "%02x ", arg_data);
#endif
pb_writeData(arg_data, 8);
}
cs_control(true);
// Handle delay after command
if (args & 0x80) {
uint32_t delay_ms = 0;
switch (args & 0xE0) {
case 0x80: delay_ms = 150; break;
case 0xA0: delay_ms = 10; break;
case 0xE0: delay_ms = 500; break;
}
if (delay_ms > 0) {
#ifdef UDSP_DEBUG
AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay %d ms", delay_ms);
#endif
delay(delay_ms);
}
}
} else {
cs_control(true);
}
}
pb_endTransaction();
}
}
I80Panel::~I80Panel() {
deInitDMA();
if (_dmadesc) {
heap_caps_free(_dmadesc);
_dmadesc = nullptr;
}
if (_i80_bus) {
esp_lcd_del_i80_bus(_i80_bus);
}
}
// DMA Implementation
bool I80Panel::initDMA() {
if (_DMA_Enabled) return true;
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX
};
if (gdma_new_channel(&dma_chan_config, &_dma_chan) == ESP_OK) {
gdma_connect(_dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
_alloc_dmadesc(16);
_DMA_Enabled = true;
return true;
}
return false;
}
void I80Panel::deInitDMA() {
if (_dma_chan) {
gdma_disconnect(_dma_chan);
gdma_del_channel(_dma_chan);
_dma_chan = nullptr;
}
_DMA_Enabled = false;
}
bool I80Panel::dmaBusy() {
if (!_DMA_Enabled) return false;
return (_dev->lcd_user.val & LCD_CAM_LCD_START);
}
void I80Panel::dmaWait() {
if (!_DMA_Enabled) return;
while (dmaBusy()) {
delay(1);
}
}
// Graphics implementation
bool I80Panel::drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return true;
pb_beginTransaction();
cs_control(false);
setAddrWindow_int(x, y, 1, 1);
writeColor(color);
cs_control(true);
pb_endTransaction();
return true;
}
bool I80Panel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
if((x >= _width) || (y >= _height)) return true;
if((x + w - 1) >= _width) w = _width - x;
if((y + h - 1) >= _height) h = _height - y;
pb_beginTransaction();
cs_control(false);
setAddrWindow_int(x, y, w, h);
for (int16_t yp = h; yp > 0; yp--) {
for (int16_t xp = w; xp > 0; xp--) {
writeColor(color);
}
}
cs_control(true);
pb_endTransaction();
return true;
}
bool I80Panel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
if((x >= _width) || (y >= _height)) return true;
if((x + w - 1) >= _width) w = _width - x;
pb_beginTransaction();
cs_control(false);
setAddrWindow_int(x, y, w, 1);
while (w--) {
writeColor(color);
}
cs_control(true);
pb_endTransaction();
return true;
}
bool I80Panel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
if ((x >= _width) || (y >= _height)) return true;
if ((y + h - 1) >= _height) h = _height - y;
pb_beginTransaction();
cs_control(false);
setAddrWindow_int(x, y, 1, h);
while (h--) {
writeColor(color);
}
cs_control(true);
pb_endTransaction();
return true;
}
bool I80Panel::pushColors(uint16_t *data, uint16_t len, bool first) {
// Match old code: just push pixels, no transaction management
// Transaction is managed by setAddrWindow()
pb_pushPixels(data, len, true, false); // swap_bytes=true to match old driver
return true;
}
bool I80Panel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
// Match old code behavior exactly
if (!x0 && !y0 && !x1 && !y1) {
// End transaction signal
cs_control(true);
pb_endTransaction();
} else {
// Begin transaction and send address window commands
pb_beginTransaction();
cs_control(false);
setAddrWindow_int(x0, y0, x1 - x0, y1 - y0);
// Leave transaction open for pushColors
}
return true;
}
bool I80Panel::displayOnff(int8_t on) {
return false; // Let uDisplay handle display commands
}
bool I80Panel::invertDisplay(bool invert) {
return false; // Let uDisplay handle inversion commands
}
bool I80Panel::setRotation(uint8_t rotation) {
_rotation = rotation & 3;
// Calculate new dimensions based on rotation FIRST
uint16_t new_width, new_height;
switch (_rotation) {
case 0:
case 2:
new_width = cfg.width;
new_height = cfg.height;
break;
case 1:
case 3:
new_width = cfg.height;
new_height = cfg.width;
break;
}
// Send MADCTL rotation command to display
pb_beginTransaction();
cs_control(false);
// Send rotation command (matches old code behavior)
pb_writeCommand(cfg.cmd_madctl, 8);
if (!cfg.allcmd_mode) {
pb_writeData(cfg.rot_cmd[_rotation], 8);
} else {
pb_writeCommand(cfg.rot_cmd[_rotation], 8);
}
// For sa_mode == 8, also send startline command
if (cfg.sa_mode == 8 && !cfg.allcmd_mode) {
pb_writeCommand(cfg.cmd_startline, 8);
pb_writeData((_rotation < 2) ? new_height : 0, 8);
}
cs_control(true);
pb_endTransaction();
// Update dimensions
_width = new_width;
_height = new_height;
return true;
}
bool I80Panel::updateFrame() {
return true; // I80 updates are immediate
}
uint32_t I80Panel::getSimpleResistiveTouch(uint32_t threshold) {
uint32_t aval = 0;
uint16_t xp, yp;
if (pb_busy()) return 0;
// Disable GPIO matrix routing to use pins as GPIOs
_pb_init_pin(true);
// Temporarily reconfigure I80 pins as GPIOs for analog touch
gpio_matrix_out(cfg.dc_pin, 0x100, 0, 0);
pinMode(cfg.data_pins_low[0], INPUT_PULLUP);
pinMode(cfg.dc_pin, INPUT_PULLUP);
pinMode(cfg.cs_pin, OUTPUT);
pinMode(cfg.data_pins_low[1], OUTPUT);
digitalWrite(cfg.cs_pin, HIGH);
digitalWrite(cfg.data_pins_low[1], LOW);
xp = 4096 - analogRead(cfg.data_pins_low[0]);
pinMode(cfg.cs_pin, INPUT_PULLUP);
pinMode(cfg.data_pins_low[1], INPUT_PULLUP);
pinMode(cfg.data_pins_low[0], OUTPUT);
pinMode(cfg.dc_pin, OUTPUT);
digitalWrite(cfg.data_pins_low[0], HIGH);
digitalWrite(cfg.dc_pin, LOW);
yp = 4096 - analogRead(cfg.data_pins_low[1]);
aval = (xp << 16) | yp;
// Restore pins to I80 function
pinMode(cfg.dc_pin, OUTPUT);
pinMode(cfg.cs_pin, OUTPUT);
pinMode(cfg.data_pins_low[0], OUTPUT);
pinMode(cfg.data_pins_low[1], OUTPUT);
digitalWrite(cfg.dc_pin, HIGH);
digitalWrite(cfg.cs_pin, HIGH);
// Re-enable GPIO matrix routing for I80
_pb_init_pin(false);
gpio_matrix_out(cfg.dc_pin, LCD_DC_IDX, 0, 0);
return aval;
}
// Color mode helper
void I80Panel::writeColor(uint16_t color) {
if (cfg.color_mode == 18) {
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
pb_writeData(r, 8);
pb_writeData(g, 8);
pb_writeData(b, 8);
} else {
pb_writeData(color, 16);
}
}
void I80Panel::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
// Apply rotation-specific offsets (matches old code logic)
x += cfg.x_addr_offset[_rotation];
y += cfg.y_addr_offset[_rotation];
uint16_t x2 = x + w - 1;
uint16_t y2 = y + h - 1;
if (cfg.sa_mode != 8) {
// Normal mode: send 32-bit packed coordinates
uint32_t xa = ((uint32_t)x << 16) | x2;
uint32_t ya = ((uint32_t)y << 16) | y2;
pb_writeCommand(cfg.cmd_set_addr_x, 8);
pb_writeData(xa, 32);
pb_writeCommand(cfg.cmd_set_addr_y, 8);
pb_writeData(ya, 32);
if (cfg.cmd_write_ram != 0xff) {
pb_writeCommand(cfg.cmd_write_ram, 8);
}
} else {
// Special mode 8: swap coordinates if rotation is odd
if (_rotation & 1) {
uint16_t tmp;
tmp = x; x = y; y = tmp;
tmp = x2; x2 = y2; y2 = tmp;
}
pb_writeCommand(cfg.cmd_set_addr_x, 8);
if (cfg.allcmd_mode) {
pb_writeCommand(x, 8);
pb_writeCommand(x2, 8);
} else {
pb_writeData(x, 8);
pb_writeData(x2, 8);
}
pb_writeCommand(cfg.cmd_set_addr_y, 8);
if (cfg.allcmd_mode) {
pb_writeCommand(y, 8);
pb_writeCommand(y2, 8);
} else {
pb_writeData(y, 8);
pb_writeData(y2, 8);
}
if (cfg.cmd_write_ram != 0xff) {
pb_writeCommand(cfg.cmd_write_ram, 8);
}
}
// Store for push operations
_addr_x0 = x;
_addr_y0 = y;
_addr_x1 = x2;
_addr_y1 = y2;
}
// Low-level I80 implementation
void I80Panel::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) {
uint32_t diff = INT32_MAX;
*div_n = 256;
*div_a = 63;
*div_b = 62;
*clkcnt = 64;
uint32_t start_cnt = std::min<uint32_t>(64u, (baseClock / (targetFreq * 2) + 1));
uint32_t end_cnt = std::max<uint32_t>(2u, baseClock / 256u / targetFreq);
if (start_cnt <= 2) { end_cnt = 1; }
for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt) {
float fdiv = (float)baseClock / cnt / targetFreq;
uint32_t n = std::max<uint32_t>(2u, (uint32_t)fdiv);
fdiv -= n;
for (uint32_t a = 63; diff && a > 0; --a) {
uint32_t b = roundf(fdiv * a);
if (a == b && n == 256) break;
uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a);
uint32_t d = abs((int)targetFreq - (int)freq);
if (diff <= d) continue;
diff = d;
*clkcnt = cnt;
*div_n = n;
*div_b = b;
*div_a = a;
if (b == 0 || a == b) break;
}
}
if (*div_a == *div_b) {
*div_b = 0;
*div_n += 1;
}
}
void I80Panel::_alloc_dmadesc(size_t len) {
if (_dmadesc) heap_caps_free(_dmadesc);
_dmadesc_size = len;
_dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA);
}
void I80Panel::_setup_dma_desc_links(const uint8_t *data, int32_t len) {
// ORIGINAL CODE: This function was empty in the original implementation
// DMA descriptor setup is incomplete - transfers larger than pre-allocated
// descriptor count will be silently truncated, causing corrupted data
// This matches the original uDisplay behavior but should be fixed eventually
static constexpr size_t MAX_DMA_LEN = (4096-4);
// TODO: Implement proper DMA descriptor chain setup
// Currently, if len > MAX_DMA_LEN * _dmadesc_size, data will be truncated
// without any error detection or recovery
}
void I80Panel::pb_beginTransaction(void) {
auto dev = _dev;
dev->lcd_clock.val = _clock_reg_value;
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M;
}
void I80Panel::pb_endTransaction(void) {
auto dev = _dev;
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
}
void I80Panel::pb_wait(void) {
auto dev = _dev;
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
}
bool I80Panel::pb_busy(void) {
auto dev = _dev;
return (dev->lcd_user.val & LCD_CAM_LCD_START);
}
void I80Panel::_pb_init_pin(bool read) {
if (read) {
if (cfg.bus_width == 8) {
for (size_t i = 0; i < 8; ++i) {
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]);
}
} else {
for (size_t i = 0; i < 8; ++i) {
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]);
}
for (size_t i = 0; i < 8; ++i) {
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_high[i]);
}
}
} else {
auto idx_base = LCD_DATA_OUT0_IDX;
if (cfg.bus_width == 8) {
for (size_t i = 0; i < 8; ++i) {
gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0);
}
} else {
for (size_t i = 0; i < 8; ++i) {
gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0);
}
for (size_t i = 0; i < 8; ++i) {
gpio_matrix_out(cfg.data_pins_high[i], idx_base + 8 + i, 0, 0);
}
}
}
}
bool I80Panel::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) {
auto dev = _dev;
auto reg_lcd_user = &(dev->lcd_user.val);
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET;
if (cfg.bus_width == 8) {
auto bytes = bit_length >> 3;
do {
dev->lcd_cmd_val.lcd_cmd_value = data;
data >>= 8;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
} while (--bytes);
return true;
} else {
dev->lcd_cmd_val.val = data;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
return true;
}
}
void I80Panel::pb_writeData(uint32_t data, uint_fast8_t bit_length) {
auto dev = _dev;
auto reg_lcd_user = &(dev->lcd_user.val);
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
auto bytes = bit_length >> 3;
if (cfg.bus_width == 8) {
uint8_t shift = (bytes - 1) * 8;
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
shift -= 8;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
}
return;
} else {
if (bytes == 1 || bytes == 4) {
uint8_t shift = (bytes - 1) * 8;
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
shift -= 8;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
}
return;
}
dev->lcd_cmd_val.val = data;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
return;
}
}
void I80Panel::pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma) {
// original code commented out
/*
uint32_t freq = spi_speed * 1000000;
uint32_t slow = (freq< 4000000) ? 2 : (freq < 8000000) ? 1 : 0;
auto dev = _dev;
do {
auto reg_lcd_user = &(dev->lcd_user.val);
dev->lcd_misc.lcd_cd_cmd_set = 0;
dev->lcd_cmd_val.lcd_cmd_value = data[0] | data[1] << 16;
uint32_t cmd_val = data[2] | data[3] << 16;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
if (use_dma) {
if (slow) { ets_delay_us(slow); }
_setup_dma_desc_links(&data[4], length - 4);
gdma_start(_dma_chan, (intptr_t)(_dmadesc));
length = 0;
} else {
size_t len = length;
if (len > CACHE_SIZE) {
len = (((len - 1) % CACHE_SIZE) + 4) & ~3u;
}
memcpy(_cache_flip, &data[4], (len-4+3)&~3);
_setup_dma_desc_links((const uint8_t*)_cache_flip, len-4);
gdma_start(_dma_chan, (intptr_t)(_dmadesc));
length -= len;
data += len;
_cache_flip = _cache[(_cache_flip == _cache[0])];
}
dev->lcd_cmd_val.lcd_cmd_value = cmd_val;
dev->lcd_misc.lcd_cd_data_set = 0;
*reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_START;
} while (length);
*/
}
// FIXED: Byte swap logic was backwards in 8-bit mode
void I80Panel::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) {
auto dev = _dev;
auto reg_lcd_user = &(dev->lcd_user.val);
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
if (cfg.bus_width == 8) {
if (swap_bytes) {
for (uint32_t cnt = 0; cnt < length; cnt++) {
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte first
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte second
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
data++;
}
} else {
for (uint32_t cnt = 0; cnt < length; cnt++) {
dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte first
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte second
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
data++;
}
}
} else {
if (swap_bytes) {
uint16_t iob;
for (uint32_t cnt = 0; cnt < length; cnt++) {
iob = *data++;
iob = (iob << 8) | (iob >> 8);
dev->lcd_cmd_val.lcd_cmd_value = iob;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
}
} else {
for (uint32_t cnt = 0; cnt < length; cnt++) {
dev->lcd_cmd_val.lcd_cmd_value = *data++;
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
}
}
}
}
void I80Panel::cs_control(bool level) {
auto pin = cfg.cs_pin;
if (pin < 0) return;
if (level) {
gpio_hi(pin);
} else {
gpio_lo(pin);
}
}
#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES

View File

@ -0,0 +1,116 @@
// ======================================================
// panel/uDisplay_rgb_panel.cpp - RGB Panel Implementation
// ======================================================
#include "uDisplay_RGB_panel.h"
#if SOC_LCD_RGB_SUPPORTED
#include <cstdint>
#include <algorithm>
#include <rom/cache.h>
RGBPanel::RGBPanel(const esp_lcd_rgb_panel_config_t *config) {
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
width = config->timings.h_res;
height = config->timings.v_res;
framebuffer_size = width * height * 2; // 16 bpp
void* buf = NULL;
esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 1, &buf);
framebuffer = (uint16_t*)buf;
uint16_t color = random(0xffff);
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 1, 1, &color));
}
RGBPanel::~RGBPanel() {
// TODO: Cleanup panel_handle if needed
}
bool RGBPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
int16_t w = width, h = height;
// Apply rotation
switch (rotation) {
case 1: std::swap(w, h); std::swap(x, y); x = w - x - 1; break;
case 2: x = w - x - 1; y = h - y - 1; break;
case 3: std::swap(w, h); std::swap(x, y); y = h - y - 1; break;
}
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return true; // Handled (out of bounds)
framebuffer[y * w + x] = color;
framebuffer_dirty = true;
return true; // Handled by RGB panel
}
bool RGBPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
for (int16_t yp = y; yp < y + h; yp++) {
uint16_t* line_start = &framebuffer[yp * width + x];
for (int16_t i = 0; i < w; i++) {
line_start[i] = color;
}
}
framebuffer_dirty = true;
return true; // Handled by RGB panel
}
bool RGBPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
window_x1 = x0;
window_y1 = y0;
window_x2 = x1;
window_y2 = y1;
return true; // Handled by RGB panel
}
bool RGBPanel::pushColors(uint16_t *data, uint16_t len, bool first) {
esp_lcd_panel_draw_bitmap(panel_handle, window_x1, window_y1, window_x2, window_y2, data);
return true; // Handled by RGB panel
}
bool RGBPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
uint16_t* line_start = &framebuffer[y * width + x];
for (int16_t i = 0; i < w; i++) {
line_start[i] = color;
}
CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2);
return true; // Handled by RGB panel
}
bool RGBPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
for (int16_t j = 0; j < h; j++) {
framebuffer[(y + j) * width + x] = color;
}
CACHE_WRITEBACK_ADDR((uint32_t)&framebuffer[y * width + x], h * 2);
return true; // Handled by RGB panel
}
bool RGBPanel::displayOnff(int8_t on) {
esp_lcd_panel_disp_on_off(panel_handle, on != 0);
return false; // bpanel is controlled from display class
}
bool RGBPanel::invertDisplay(bool invert) {
// TODO: Not supported by RGB panels in ESP-IDF API
return false; // Not handled - let uDisplay handle if possible
}
bool RGBPanel::setRotation(uint8_t rotation) {
this->rotation = rotation & 3;
esp_lcd_panel_mirror(panel_handle, rotation == 1 || rotation == 2, rotation & 2);
esp_lcd_panel_swap_xy(panel_handle, rotation & 1);
return true; // Handled by RGB panel
}
bool RGBPanel::updateFrame() {
if (!framebuffer_dirty) {
return true;
}
CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough!
framebuffer_dirty = false;
return true; // Handled (no-op is still handled)
}
#endif // #if SOC_LCD_RGB_SUPPORTED

View File

@ -0,0 +1,486 @@
#include "uDisplay_SPI_controller.h"
// ===== GPIO Macros =====
#ifdef ESP8266
#define PIN_OUT_SET 0x60000304
#define PIN_OUT_CLEAR 0x60000308
#define GPIO_SET(A) WRITE_PERI_REG(PIN_OUT_SET, 1 << A)
#define GPIO_CLR(A) WRITE_PERI_REG(PIN_OUT_CLEAR, 1 << A)
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
#else // ESP32
#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A)
#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A)
#else // plain ESP32 or S3
#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A)
#define GPIO_SET(A) GPIO.out_w1ts = (1 << A)
#endif
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
#endif
// ===== RA8876 Constants =====
static constexpr uint8_t RA8876_DATA_WRITE = 0x80;
static constexpr uint8_t RA8876_DATA_READ = 0xC0;
static constexpr uint8_t RA8876_CMD_WRITE = 0x00;
static constexpr uint8_t RA8876_STATUS_READ = 0x40;
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
SPIController::SPIController(const SPIControllerConfig& config)
: spi_config(config)
{
if (spi_config.dc >= 0) {
pinMode(spi_config.dc, OUTPUT);
digitalWrite(spi_config.dc, HIGH);
}
if (spi_config.cs >= 0) {
pinMode(spi_config.cs, OUTPUT);
digitalWrite(spi_config.cs, HIGH);
}
#ifdef ESP8266
if (spi_config.bus_nr <= 1) {
SPI.begin();
spi = &SPI;
} else {
pinMode(spi_config.clk, OUTPUT);
digitalWrite(spi_config.clk, LOW);
pinMode(spi_config.mosi, OUTPUT);
digitalWrite(spi_config.mosi, LOW);
if (spi_config.miso >= 0) {
pinMode(spi_config.miso, INPUT_PULLUP);
}
}
#endif // ESP8266
#ifdef ESP32
if (spi_config.bus_nr == 1) {
spi = &SPI;
spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1);
} else if (spi_config.bus_nr == 2) {
spi = new SPIClass(HSPI);
spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1);
} else {
pinMode(spi_config.clk, OUTPUT);
digitalWrite(spi_config.clk, LOW);
pinMode(spi_config.mosi, OUTPUT);
digitalWrite(spi_config.mosi, LOW);
if (spi_config.miso >= 0) {
pinMode(spi_config.miso, INPUT_PULLUP);
}
}
#endif // ESP32
spi_settings = SPISettings((uint32_t)spi_config.speed*1000000, MSBFIRST, SPI_MODE3);
}
// ===== Pin Control =====
void SPIController::csLow() {
if (spi_config.cs >= 0) GPIO_CLR_SLOW(spi_config.cs);
}
void SPIController::csHigh() {
if (spi_config.cs >= 0) GPIO_SET_SLOW(spi_config.cs);
}
void SPIController::dcLow() {
if (spi_config.dc >= 0) GPIO_CLR_SLOW(spi_config.dc);
}
void SPIController::dcHigh() {
if (spi_config.dc >= 0) GPIO_SET_SLOW(spi_config.dc);
}
// ===== Transaction Control =====
void SPIController::beginTransaction() {
if (spi_config.bus_nr <= 2) spi->beginTransaction(spi_settings);
}
void SPIController::endTransaction() {
if (spi_config.bus_nr <= 2) spi->endTransaction();
}
// ===== Low-Level Write Functions =====
void SPIController::writeCommand(uint8_t cmd) {
if (spi_config.dc < 0) {
// 9-bit mode
if (spi_config.bus_nr > 2) {
if (spi_config.bus_nr == 3) write9(cmd, 0);
else write9_slow(cmd, 0);
} else {
hw_write9(cmd, 0);
}
} else {
// 8-bit mode
dcLow();
writeData8(cmd);
dcHigh();
}
}
void SPIController::writeData8(uint8_t data) {
if (spi_config.dc < 0) {
// 9-bit mode
if (spi_config.bus_nr > 2) {
if (spi_config.bus_nr == 3) write9(data, 1);
else write9_slow(data, 1);
} else {
hw_write9(data, 1);
}
} else {
// 8-bit mode
if (spi_config.bus_nr > 2) {
if (spi_config.bus_nr == 3) write8(data);
else write8_slow(data);
} else {
spi->write(data);
}
}
}
void SPIController::writeData16(uint16_t data) {
if (spi_config.dc < 0) {
// 9-bit: break into bytes
writeData8(data >> 8);
writeData8(data);
} else {
// 8-bit mode
if (spi_config.bus_nr > 2) {
if (spi_config.bus_nr == 3) write16(data);
else {
// Slow mode: break into bytes
writeData8(data >> 8);
writeData8(data);
}
} else {
spi->write16(data); // Assume SPI has write16
}
}
}
void SPIController::writeData32(uint32_t data) {
if (spi_config.dc < 0) {
// 9-bit mode: break into bytes
writeData8(data >> 24);
writeData8(data >> 16);
writeData8(data >> 8);
writeData8(data);
} else {
// 8-bit mode
if (spi_config.bus_nr > 2) {
if (spi_config.bus_nr == 3) {
write32(data); // Fast bit-banging
} else {
// Slow mode: break into bytes
writeData8(data >> 24);
writeData8(data >> 16);
writeData8(data >> 8);
writeData8(data);
}
} else {
// Hardware SPI
spi->write32(data); // Assume SPI has write32 on ESP32
}
}
}
// ===== Low-Level Write Functions =====
void SPIController::write8(uint8_t val) {
for (uint8_t bit = 0x80; bit; bit >>= 1) {
GPIO_CLR(spi_config.clk);
if (val & bit) GPIO_SET(spi_config.mosi);
else GPIO_CLR(spi_config.mosi);
GPIO_SET(spi_config.clk);
}
}
void SPIController::write8_slow(uint8_t val) {
for (uint8_t bit = 0x80; bit; bit >>= 1) {
GPIO_CLR_SLOW(spi_config.clk);
if (val & bit) GPIO_SET_SLOW(spi_config.mosi);
else GPIO_CLR_SLOW(spi_config.mosi);
GPIO_SET_SLOW(spi_config.clk);
}
}
void SPIController::write9(uint8_t val, uint8_t dc) {
GPIO_CLR(spi_config.clk);
if (dc) GPIO_SET(spi_config.mosi);
else GPIO_CLR(spi_config.mosi);
GPIO_SET(spi_config.clk);
for (uint8_t bit = 0x80; bit; bit >>= 1) {
GPIO_CLR(spi_config.clk);
if (val & bit) GPIO_SET(spi_config.mosi);
else GPIO_CLR(spi_config.mosi);
GPIO_SET(spi_config.clk);
}
}
void SPIController::write9_slow(uint8_t val, uint8_t dc) {
GPIO_CLR_SLOW(spi_config.clk);
if (dc) GPIO_SET_SLOW(spi_config.mosi);
else GPIO_CLR_SLOW(spi_config.mosi);
GPIO_SET_SLOW(spi_config.clk);
for (uint8_t bit = 0x80; bit; bit >>= 1) {
GPIO_CLR_SLOW(spi_config.clk);
if (val & bit) GPIO_SET_SLOW(spi_config.mosi);
else GPIO_CLR_SLOW(spi_config.mosi);
GPIO_SET_SLOW(spi_config.clk);
}
}
void SPIController::write16(uint16_t val) {
for (uint16_t bit = 0x8000; bit; bit >>= 1) {
GPIO_CLR(spi_config.clk);
if (val & bit) GPIO_SET(spi_config.mosi);
else GPIO_CLR(spi_config.mosi);
GPIO_SET(spi_config.clk);
}
}
void SPIController::write32(uint32_t val) {
for (uint32_t bit = 0x80000000; bit; bit >>= 1) {
GPIO_CLR(spi_config.clk);
if (val & bit) GPIO_SET(spi_config.mosi);
else GPIO_CLR(spi_config.mosi);
GPIO_SET(spi_config.clk);
}
}
// ===== Hardware 9-bit Mode =====
#ifdef ESP32
void SPIController::hw_write9(uint8_t val, uint8_t dc) {
if (spi_config.dc < -1) {
// RA8876 mode
if (!dc) {
spi->write(RA8876_CMD_WRITE);
spi->write(val);
} else {
spi->write(RA8876_DATA_WRITE);
spi->write(val);
}
} else {
uint32_t regvalue = val >> 1;
if (dc) regvalue |= 0x80;
else regvalue &= 0x7f;
if (val & 1) regvalue |= 0x8000;
REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI);
REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1);
uint32_t *dp = (uint32_t*)SPI_W0_REG(3);
*dp = regvalue;
REG_SET_BIT(SPI_CMD_REG(3), SPI_USR);
while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR));
}
}
#else
void SPIController::hw_write9(uint8_t val, uint8_t dc) {
if (spi_config.dc < -1) {
// RA8876 mode
if (!dc) {
spi->write(RA8876_CMD_WRITE);
spi->write(val);
} else {
spi->write(RA8876_DATA_WRITE);
spi->write(val);
}
} else {
uint32_t regvalue;
uint8_t bytetemp;
if (!dc) {
bytetemp = (val >> 1) & 0x7f;
} else {
bytetemp = (val >> 1) | 0x80;
}
regvalue = ((8 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | ((uint32)bytetemp);
if (val & 0x01) regvalue |= BIT15;
while (READ_PERI_REG(SPI_CMD(1)) & SPI_USR);
WRITE_PERI_REG(SPI_USER2(1), regvalue);
SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR);
}
}
#endif
// DMA
#ifdef ESP32
bool SPIController::initDMA(uint16_t width, uint16_t flushlines, uint8_t data) {
AddLog(3,"init dma %u %u %d",flushlines,data, spi_config.cs);
if (!spi && spi_config.cs == -1) return false;
if((data&1) == 0){
AddLog(3,"no dma selected");
return false;
}
if (spi_config.bus_nr == 1){
AddLog(3,"dma spi 1");
} else if (spi_config.bus_nr == 2){
AddLog(3,"dma spi 2");
spi_host = HSPI_HOST;
} else {
return false;
}
esp_err_t ret;
spi_bus_config_t buscfg = {
.mosi_io_num = spi_config.mosi,
.miso_io_num = spi_config.miso,
.sclk_io_num = spi_config.clk,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = width * flushlines * 2 + 8,
.flags = 0,
.intr_flags = 0
};
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = SPI_MODE3,
.duty_cycle_pos = 0,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.clock_speed_hz = (int)spi_config.speed,
.input_delay_ns = 0,
.spics_io_num = spi_config.cs,
.flags = SPI_DEVICE_NO_DUMMY,
.queue_size = 1,
.pre_cb = 0,
.post_cb = 0
};
// spi_host_device_t spi_host = (spi_config.bus_nr == 1) ? VSPI_HOST : HSPI_HOST;
// Try to initialize the bus, but if it's already initialized (by Arduino SPI), that's OK
ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
AddLog(3,"init dma bus init failed: %d", ret);
return false;
}
if (ret == ESP_ERR_INVALID_STATE) {
AddLog(3,"init dma bus already initialized (OK)");
}
ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL);
if (ret == ESP_OK) {
DMA_Enabled = true;
async_dma_enabled = ((data&4) != 0);
dma_enabled = true;
spiBusyCheck = 0;
AddLog(3,"init dma succes");
return true;
}
return false;
}
// just a placeholder
// void SPIController::deInitDMA(void) {
// if (!DMA_Enabled) return;
// spi_bus_remove_device(dmaHAL);
// spi_bus_free(spi_host);
// DMA_Enabled = false;
// }
bool SPIController::dmaBusy(void) {
if (!DMA_Enabled || !spiBusyCheck) return false;
spi_transaction_t *rtrans;
esp_err_t ret;
uint8_t checks = spiBusyCheck;
for (int i = 0; i < checks; ++i) {
ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0);
if (ret == ESP_OK) spiBusyCheck--;
}
if (spiBusyCheck == 0) return false;
return true;
}
void SPIController::dmaWait(void) {
if (!DMA_Enabled || !spiBusyCheck) return;
spi_transaction_t *rtrans;
esp_err_t ret;
for (int i = 0; i < spiBusyCheck; ++i) {
ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY);
assert(ret == ESP_OK);
}
spiBusyCheck = 0;
}
void SPIController::pushPixelsDMA(uint16_t* image, uint32_t len) {
if(!DMA_Enabled){
getSPI()->writePixels(image, len * 2);
return;
}
if (len == 0) return;
dmaWait();
esp_err_t ret;
memset(&trans, 0, sizeof(spi_transaction_t));
trans.user = (void *)1;
trans.tx_buffer = image; //finally send the line data
trans.length = len * 16; //Data length, in bits
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
assert(ret == ESP_OK);
spiBusyCheck++;
if (!async_dma_enabled) {
dmaWait();
}
}
void SPIController::pushPixels3DMA(uint8_t* image, uint32_t len) {
if ((len == 0) || (!DMA_Enabled)) return;
dmaWait();
esp_err_t ret;
memset(&trans, 0, sizeof(spi_transaction_t));
trans.user = (void *)1;
trans.tx_buffer = image; //finally send the line data
trans.length = len * 24; //Data length, in bits
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
assert(ret == ESP_OK);
spiBusyCheck++;
if (!async_dma_enabled) {
dmaWait();
}
}
#endif // ESP32
// ===== RA8876 Specific =====
uint8_t SPIController::writeReg16(uint8_t reg, uint16_t wval) {
hw_write9(reg, 0);
hw_write9(wval, 1);
hw_write9(reg + 1, 0);
hw_write9(wval >> 8, 1);
return 0;
}
uint8_t SPIController::readData(void) {
if (!spi) return 0;
spi->write(RA8876_DATA_READ);
return spi->transfer(0);
}
uint8_t SPIController::readStatus(void) {
if (!spi) return 0;
spi->write(RA8876_STATUS_READ);
return spi->transfer(0);
}

View File

@ -0,0 +1,367 @@
// WIP
// ======================================================
// uDisplay_spi_panel.cpp - SPI LCD Panel Implementation
// ======================================================
#include "uDisplay_SPI_panel.h"
#include <Arduino.h>
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
SPIPanel::SPIPanel(const SPIPanelConfig& config,
SPIController* spi_ctrl,
uint8_t* framebuffer)
: spi(spi_ctrl), cfg(config), fb_buffer(framebuffer),
rotation(0), display_on(true), inverted(false)
{
// Initialize address window state
window_x0 = 0;
window_y0 = 0;
window_x1 = 0;
window_y1 = 0;
use_hw_spi = (spi->spi_config.dc >= 0) && (spi->spi_config.bus_nr <= 2);
}
SPIPanel::~SPIPanel() {
// Panel doesn't own framebuffer or SPI controller
}
// ===== UniversalPanel Interface Implementation =====
bool SPIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
// From original uDisplay::drawPixel - only handle direct SPI drawing for color TFTs
if ((x < 0) || (x >= cfg.width) || (y < 0) || (y >= cfg.height)) return true;
// Only handle direct SPI drawing for color displays without framebuffer
if (!fb_buffer && cfg.bpp >= 16) {
spi->beginTransaction();
spi->csLow();
setAddrWindow_internal(x, y, 1, 1);
spi->writeCommand(cfg.cmd_write_ram);
if (cfg.col_mode == 18) {
// From original WriteColor function
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
spi->writeData8(r);
spi->writeData8(g);
spi->writeData8(b);
} else {
spi->writeData16(color);
}
spi->csHigh();
spi->endTransaction();
return true;
}
return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs)
}
bool SPIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
// From original uDisplay::fillRect
if((x >= cfg.width) || (y >= cfg.height)) return true;
if((x + w - 1) >= cfg.width) w = cfg.width - x;
if((y + h - 1) >= cfg.height) h = cfg.height - y;
// Only handle direct SPI drawing for color displays without framebuffer
if (!fb_buffer && cfg.bpp >= 16) {
spi->beginTransaction();
spi->csLow();
setAddrWindow_internal(x, y, w, h);
spi->writeCommand(cfg.cmd_write_ram);
if (cfg.col_mode == 18) {
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
for (int16_t yp = h; yp > 0; yp--) {
for (int16_t xp = w; xp > 0; xp--) {
spi->writeData8(r);
spi->writeData8(g);
spi->writeData8(b);
}
}
} else {
for (int16_t yp = h; yp > 0; yp--) {
for (int16_t xp = w; xp > 0; xp--) {
spi->writeData16(color);
}
}
}
spi->csHigh();
spi->endTransaction();
return true;
}
return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs)
}
bool SPIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) {
// Only handle direct rendering for color displays
if (cfg.bpp < 16) {
return false;
}
// Handle byte swapping for LVGL (when not_swapped == false)
if (!not_swapped && cfg.col_mode != 18) {
// LVGL data - bytes are already swapped
if (use_hw_spi) {
#ifdef ESP32
spi->pushPixelsDMA(data, len);
#else
spi->getSPI()->writeBytes((uint8_t*)data, len * 2);
#endif
} else {
// Software SPI - write pixel by pixel
for (uint16_t i = 0; i < len; i++) {
spi->writeData16(data[i]);
}
}
return true;
}
// Handle 18-bit color mode
if (cfg.col_mode == 18) {
#ifdef ESP32
if (use_hw_spi) {
uint8_t *line = (uint8_t*)malloc(len * 3);
if (line) {
uint8_t *lp = line;
for (uint32_t cnt = 0; cnt < len; cnt++) {
uint16_t color = data[cnt];
if (!not_swapped) {
color = (color << 8) | (color >> 8);
}
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
*lp++ = r;
*lp++ = g;
*lp++ = b;
}
spi->pushPixels3DMA(line, len);
free(line);
}
} else
#endif
{
// Software SPI or ESP8266
for (uint16_t i = 0; i < len; i++) {
uint16_t color = data[i];
if (!not_swapped) {
color = (color << 8) | (color >> 8);
}
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
spi->writeData8(r);
spi->writeData8(g);
spi->writeData8(b);
}
}
return true;
}
// Handle 16-bit color mode with no byte swapping (not_swapped == true)
if (not_swapped) {
if (use_hw_spi) {
#ifdef ESP32
spi->getSPI()->writePixels(data, len * 2);
#else
// ESP8266: writePixels() doesn't exist, use per-pixel write
for (uint16_t i = 0; i < len; i++) {
spi->writeData16(data[i]);
}
#endif
return true;
}
// Software SPI - write per-pixel
for (uint16_t i = 0; i < len; i++) {
spi->writeData16(data[i]);
}
return true;
}
return false;
}
bool SPIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
// From original uDisplay::setAddrWindow
window_x0 = x0;
window_y0 = y0;
window_x1 = x1;
window_y1 = y1;
if (!x0 && !y0 && !x1 && !y1) {
spi->csHigh();
spi->endTransaction();
} else {
spi->beginTransaction();
spi->csLow();
setAddrWindow_internal(x0, y0, x1 - x0, y1 - y0);
}
return true;
}
void SPIPanel::setAddrWindow_internal(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
// From original uDisplay::setAddrWindow_int
x += cfg.x_addr_offset[rotation];
y += cfg.y_addr_offset[rotation];
uint16_t x2 = x + w - 1;
uint16_t y2 = y + h - 1;
if (cfg.address_mode != 8) {
// 16/32-bit addressing (most TFT displays)
uint32_t xa = ((uint32_t)x << 16) | x2;
uint32_t ya = ((uint32_t)y << 16) | y2;
spi->writeCommand(cfg.cmd_set_addr_x);
spi->writeData32(xa);
spi->writeCommand(cfg.cmd_set_addr_y);
spi->writeData32(ya);
if (cfg.cmd_write_ram != 0xFF) {
spi->writeCommand(cfg.cmd_write_ram);
}
} else {
// 8-bit addressing mode (OLED displays)
if (rotation & 1) {
// Vertical address increment mode
uint16_t temp = x; x = y; y = temp;
temp = x2; x2 = y2; y2 = temp;
}
spi->writeCommand(cfg.cmd_set_addr_x);
if (cfg.all_commands_mode) {
spi->writeData8(x);
spi->writeData8(x2);
} else {
spi->writeCommand(x);
spi->writeCommand(x2);
}
spi->writeCommand(cfg.cmd_set_addr_y);
if (cfg.all_commands_mode) {
spi->writeData8(y);
spi->writeData8(y2);
} else {
spi->writeCommand(y);
spi->writeCommand(y2);
}
if (cfg.cmd_write_ram != 0xFF) {
spi->writeCommand(cfg.cmd_write_ram);
}
}
}
bool SPIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
// From original uDisplay::drawFastHLine
return fillRect(x, y, w, 1, color);
}
bool SPIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
// From original uDisplay::drawFastVLine
return fillRect(x, y, 1, h, color);
}
bool SPIPanel::displayOnff(int8_t on) {
display_on = (on != 0);
spi->beginTransaction();
spi->csLow();
if (display_on && cfg.cmd_display_on != 0xFF) {
spi->writeCommand(cfg.cmd_display_on);
} else if (!display_on && cfg.cmd_display_off != 0xFF) {
spi->writeCommand(cfg.cmd_display_off);
}
spi->csHigh();
spi->endTransaction();
return true;
}
bool SPIPanel::invertDisplay(bool invert) {
inverted = invert;
spi->beginTransaction();
spi->csLow();
if (invert && cfg.cmd_invert_on != 0xFF) {
spi->writeCommand(cfg.cmd_invert_on);
} else if (!invert && cfg.cmd_invert_off != 0xFF) {
spi->writeCommand(cfg.cmd_invert_off);
}
spi->csHigh();
spi->endTransaction();
return true;
}
bool SPIPanel::setRotation(uint8_t rot) {
// From original uDisplay::setRotation
rotation = rot & 3;
spi->beginTransaction();
spi->csLow();
if (cfg.cmd_memory_access != 0xFF && cfg.rot_cmd[rotation] != 0xFF) {
spi->writeCommand(cfg.cmd_memory_access);
if (!cfg.all_commands_mode) {
spi->writeData8(cfg.rot_cmd[rotation]);
} else {
spi->writeCommand(cfg.rot_cmd[rotation]);
}
spi->csHigh();
spi->endTransaction();
return true;
}
spi->csHigh();
spi->endTransaction();
return false;
}
bool SPIPanel::updateFrame() {
// From original uDisplay::Updateframe - only for monochrome SPI OLEDs
// Only handle framebuffer updates for monochrome displays
if (!fb_buffer || cfg.bpp != 1) return false;
// OLED page-based framebuffer update (from original code)
uint8_t ys = cfg.height >> 3;
uint8_t xs = cfg.width >> 3;
uint8_t m_row = cfg.cmd_set_addr_y; // saw_2 in original
uint8_t m_col = 0; // i2c_col_start in original
uint16_t p = 0;
uint8_t i, j, k = 0;
spi->beginTransaction();
spi->csLow();
for (i = 0; i < ys; i++) {
spi->writeCommand(0xB0 + i + m_row); // set page address
spi->writeCommand(m_col & 0xf); // set lower column address
spi->writeCommand(0x10 | (m_col >> 4)); // set higher column address
for (j = 0; j < 8; j++) {
for (k = 0; k < xs; k++, p++) {
spi->writeData8(fb_buffer[p]);
}
}
}
spi->csHigh();
spi->endTransaction();
return true;
}

View File

@ -0,0 +1,31 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
// Color palette definition
static constexpr uint16_t udisp_colors[] = {
UDISP_BLACK, UDISP_WHITE, UDISP_RED, UDISP_GREEN, UDISP_BLUE, UDISP_CYAN,
UDISP_MAGENTA, UDISP_YELLOW, UDISP_NAVY, UDISP_DARKGREEN, UDISP_DARKCYAN,
UDISP_MAROON, UDISP_PURPLE, UDISP_OLIVE, UDISP_LIGHTGREY, UDISP_DARKGREY,
UDISP_ORANGE, UDISP_GREENYELLOW, UDISP_PINK
};
uint16_t uDisplay::GetColorFromIndex(uint8_t index) {
const size_t color_count = sizeof(udisp_colors) / sizeof(udisp_colors[0]);
if (index >= color_count) {
index = 0;
}
return udisp_colors[index];
}
uint16_t uDisplay::fgcol(void) {
return fg_col;
}
uint16_t uDisplay::bgcol(void) {
return bg_col;
}
int8_t uDisplay::color_type(void) {
return col_type;
}

View File

@ -0,0 +1,125 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
void udisp_bpwr(uint8_t on);
void udisp_dimm(uint8_t dim);
// input value is 0..15
// void uDisplay::dim(uint8_t dim) {
// dim8(((uint32_t)dim * 255) / 15);
// }
// ===== Power Management =====
void uDisplay::DisplayOnff(int8_t on) {
if (ep_mode) {
return;
}
if (pwr_cbp) {
pwr_cbp(on);
}
if (universal_panel->displayOnff(on)) {
return;
}
#define AW_PWMRES 1024
if (on) {
if (bpanel >= 0) {
#ifdef ESP32
if (!bpmode) {
analogWrite(bpanel, dimmer10_gamma);
} else {
analogWrite(bpanel, AW_PWMRES - dimmer10_gamma);
}
#else
if (!bpmode) {
digitalWrite(bpanel, HIGH);
} else {
digitalWrite(bpanel, LOW);
}
#endif
}
} else {
if (bpanel >= 0) {
#ifdef ESP32
if (!bpmode) {
analogWrite(bpanel, 0);
} else {
analogWrite(bpanel, AW_PWMRES - 1);
}
#else
if (!bpmode) {
digitalWrite(bpanel, LOW);
} else {
digitalWrite(bpanel, HIGH);
}
#endif
}
}
}
// ===== Brightness/Dimming Control =====
// dim is 0..255
void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) {
dimmer8 = dim;
dimmer10_gamma = dim_gamma;
if (ep_mode) {
return;
}
#ifdef ESP32
if (bpanel >= 0) {
if (!bpmode) {
analogWrite(bpanel, dimmer10_gamma);
} else {
analogWrite(bpanel, AW_PWMRES - dimmer10_gamma);
}
} else if (dim_cbp) {
dim_cbp(dim);
}
#endif
if (interface == _UDSP_SPI) {
if (dim_op != 0xff) {
spiController->beginTransaction();
spiController->csLow();
spiController->writeCommand(dim_op);
spiController->writeData8(dimmer8);
spiController->csHigh();
spiController->endTransaction();
}
}
}
// ===== Display Inversion =====
void uDisplay::invertDisplay(boolean i) {
if (universal_panel) {
universal_panel->invertDisplay(i);
}
}
// ===== Splash Screen =====
void uDisplay::Splash(void) {
if (splash_font < 0) return;
if (ep_mode) {
Updateframe();
if (universal_panel) {
EPDPanel* epd = static_cast<EPDPanel*>(universal_panel);
epd->delay_sync(panel_config->epd.update_time * 10);
}
}
setTextFont(splash_font);
setTextSize(splash_size);
DrawStringAt(splash_xp, splash_yp, dname, fg_col, 0);
Updateframe();
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: draw splash"));
}

View File

@ -0,0 +1,137 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
// ===== Basic Drawing Primitives =====
static constexpr uint16_t RGB16_TO_MONO = 0x8410;
static constexpr uint16_t RGB16_SWAP_TO_MONO = 0x1084;
void uDisplay::drawPixel(int16_t x, int16_t y, uint16_t color) {
if (universal_panel->drawPixel(x, y, color)) {
return; // Handled by universal panel
}
if (framebuffer) {
Renderer::drawPixel(x, y, color);
return;
}
}
void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
// Rudimentary clipping
if((x >= _width) || (y >= _height)) return;
if((x + w - 1) >= _width) w = _width - x;
if (universal_panel->drawFastHLine(x, y, w, color)) {
return;
}
if (framebuffer) {
Renderer::drawFastHLine(x, y, w, color);
return;
}
}
void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
if (framebuffer) {
Renderer::drawFastVLine(x, y, h, color);
return;
}
// Rudimentary clipping
if ((x >= _width) || (y >= _height)) return;
if ((y + h - 1) >= _height) h = _height - y;
if (universal_panel->drawFastVLine(x, y, h, color)) {
return;
}
}
void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
if (universal_panel->fillRect(x, y, w, h, color)) {
return;
}
if (framebuffer) {
Renderer::fillRect(x, y, w, h, color);
return;
}
}
void uDisplay::fillScreen(uint16_t color) {
fillRect(0, 0, width(), height(), color);
}
static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); }
void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) { //not_swapped is always true in call form LVGL driver!!!!
if (lvgl_param.swap_color) {
not_swapped = !not_swapped;
}
universal_panel->pushColors(data, len, not_swapped);
}
// convert to mono, these are framebuffer based
void uDisplay::pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap) {
// pixel is white if at least one of the 3 components is above 50%
// this is tested with a simple mask, swapped if needed
uint16_t rgb16_to_mono_mask = rgb16_swap ? RGB16_SWAP_TO_MONO : RGB16_TO_MONO;
for (uint32_t y = seta_yp1; y < seta_yp2; y++) {
seta_yp1++;
if (lvgl_param.invert_bw) {
for (uint32_t x = seta_xp1; x < seta_xp2; x++) {
uint16_t color = *data++;
if (bpp == 1) color = (color & rgb16_to_mono_mask) ? 0 : 1;
drawPixel(x, y, color); // todo - inline the method to save speed
len--;
if (!len) return; // failsafe - exist if len (pixel number) is exhausted
}
} else {
for (uint32_t x = seta_xp1; x < seta_xp2; x++) {
uint16_t color = *data++;
if (bpp == 1) color = (color & rgb16_to_mono_mask) ? 1 : 0;
drawPixel(x, y, color); // todo - inline the method to save speed
len--;
if (!len) return; // failsafe - exist if len (pixel number) is exhausted
}
}
}
}
void uDisplay::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
universal_panel->setAddrWindow(x0, y0, x1, y1);
}
void uDisplay::setRotation(uint8_t rotation) {
cur_rot = rotation;
if (universal_panel->setRotation(rotation)) {
// Update Renderer dimensions based on rotation
switch (rotation) {
case 0:
case 2:
_width = gxs;
_height = gys;
break;
case 1:
case 3:
_width = gys;
_height = gxs;
break;
}
return;
}
if (framebuffer) {
Renderer::setRotation(cur_rot);
return;
}
}
void uDisplay::Updateframe(void) {
universal_panel->updateFrame();
}

View File

@ -0,0 +1,26 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
// ===== Timing and Delay Functions =====
void uDisplay::delay_arg(uint32_t args) {
uint32_t delay_ms = 0;
switch (args & 0xE0) {
case 0x80: delay_ms = 150; break;
case 0xA0: delay_ms = 10; break;
case 0xE0: delay_ms = 500; break;
}
if (delay_ms > 0) {
delay(delay_ms);
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: delay %d ms"), delay_ms);
}
}
void uDisplay::reset_pin(int32_t msl, int32_t msh) {
if (reset > 0) {
digitalWrite(reset, LOW);
delay(msl);
digitalWrite(reset, HIGH);
delay(msh);
}
}

View File

@ -0,0 +1,576 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
#ifdef USE_UNIVERSAL_TOUCH
// ===== Touch IRQ Handler =====
uint8_t ut_irq_flg;
void IRAM_ATTR ut_touch_irq(void) {
ut_irq_flg = 1;
}
// ===== Touch Initialization =====
bool uDisplay::utouch_Init(char **name) {
*name = ut_name;
if (ut_init_code) {
if (ut_reset >= 0) {
pinMode(ut_reset, OUTPUT);
digitalWrite(ut_reset, HIGH);
delay(10);
digitalWrite(ut_reset, LOW);
delay(10);
digitalWrite(ut_reset, HIGH);
delay(50);
}
if (ut_irq >= 0) {
pinMode(ut_irq, INPUT);
attachInterrupt(ut_irq, ut_touch_irq, FALLING);
}
if (ut_wire) {
// I2C touch - no SPI needed
ut_spi = nullptr;
} else if (spiController && ut_spi_nr == spiController->spi_config.bus_nr) {
// SPI touch using same bus as display
ut_spi = spiController->getSPI();
} else {
// SPI touch using different bus or display doesn't use SPI
#ifdef ESP32
ut_spi = SpiBegin(ut_spi_nr);
#endif
}
return ut_execute(ut_init_code);
}
return false;
}
// ===== Touch Detection =====
uint16_t uDisplay::touched(void) {
if (ut_irq >= 0) {
if (!ut_irq_flg) {
return false;
}
ut_irq_flg = 0;
}
if (ut_touch_code) {
return ut_execute(ut_touch_code);
}
return 0;
}
// ===== Touch Coordinate Reading =====
int16_t uDisplay::getPoint_x(void) {
if (ut_getx_code) {
return ut_execute(ut_getx_code);
}
return 0;
}
int16_t uDisplay::getPoint_y(void) {
if (ut_gety_code) {
return ut_execute(ut_gety_code);
}
return 0;
}
// ===== Touch Command Execution =====
// ===== Touch Code Translation =====
void uDisplay::ut_trans(char **sp, uint8_t **code) {
char *cp = *sp;
uint16_t wval;
uint8_t tmp_code[64];
uint8_t *ut_code = tmp_code;
while (*cp) {
if (*cp == ':' || *cp == '#') {
break;
}
if (*cp == ';') {
// skip comment line
while (*cp) {
if (*cp == '\n') {
cp++;
break;
}
cp++;
}
}
if (!strncmp(cp, "RDWM", 4)) {
// read word many
*ut_code++ = UT_RDWM;
wval = ut_par(&cp, 0);
*ut_code++ = wval>>8;
*ut_code++ = wval;
wval = ut_par(&cp, 1);
if (wval > sizeof(ut_array)) {
wval = sizeof(ut_array);
}
*ut_code++ = wval;
} else if (!strncmp(cp, "RDW", 3)) {
// read word one
*ut_code++ = UT_RDW;
wval = ut_par(&cp, 0);
*ut_code++ = wval>>8;
*ut_code++ = wval;
} else if (!strncmp(cp, "RDM", 3)) {
// read many
*ut_code++ = UT_RDM;
*ut_code++ = ut_par(&cp, 0);
wval = ut_par(&cp, 1);
if (wval > sizeof(ut_array)) {
wval = sizeof(ut_array);
}
*ut_code++ = wval;
} else if (!strncmp(cp, "RD", 2)) {
// read one
*ut_code++ = UT_RD;
*ut_code++ = ut_par(&cp, 0);
} else if (!strncmp(cp, "CPR", 3)) {
// cmp and set
*ut_code++ = UT_CPR;
*ut_code++ = ut_par(&cp, 0);
} else if (!strncmp(cp, "CPM", 3)) {
// cmp multiple and set
*ut_code++ = UT_CPM;
uint8_t num = ut_par(&cp, 0);
*ut_code++ = num;
for (uint32_t cnt = 0; cnt < num; cnt++) {
*ut_code++ = ut_par(&cp, 0);
}
} else if (!strncmp(cp, "CP", 2)) {
// cmp and set
*ut_code++ = UT_CP;
*ut_code++ = ut_par(&cp, 0);
} else if (!strncmp(cp, "RTF", 3)) {
// return when false
*ut_code++ = UT_RTF;
} else if (!strncmp(cp, "RTT", 3)) {
// return when true
*ut_code++ = UT_RTT;
} else if (!strncmp(cp, "MVB", 3)) {
// move
*ut_code++ = UT_MVB;
*ut_code++ = ut_par(&cp, 1);
*ut_code++ = ut_par(&cp, 1);
} else if (!strncmp(cp, "MV", 2)) {
// move
*ut_code++ = UT_MV;
*ut_code++ = ut_par(&cp, 1);
*ut_code++ = ut_par(&cp, 1);
} else if (!strncmp(cp, "RT", 2)) {
// return status
*ut_code++ = UT_RT;
} else if (!strncmp(cp, "WRW", 3)) {
*ut_code++ = UT_WRW;
wval = ut_par(&cp, 0);
*ut_code++ = wval>>8;
*ut_code++ = wval;
wval = ut_par(&cp, 0);
*ut_code++ = wval;
} else if (!strncmp(cp, "WR", 2)) {
*ut_code++ = UT_WR;
wval = ut_par(&cp, 0);
*ut_code++ = wval;
wval = ut_par(&cp, 0);
*ut_code++ = wval;
} else if (!strncmp(cp, "AND", 3)) {
*ut_code++ = UT_AND;
wval = ut_par(&cp, 0);
*ut_code++ = wval >> 8;
*ut_code++ = wval;
} else if (!strncmp(cp, "SCL", 3)) {
*ut_code++ = UT_SCALE;
wval = ut_par(&cp, 1);
*ut_code++ = wval >> 8;
*ut_code++ = wval;
uint32_t lval = ut_par(&cp, 2);
*ut_code++ = lval >> 24;
*ut_code++ = lval >> 16;
*ut_code++ = lval >> 8;
*ut_code++ = lval;
} else if (!strncmp(cp, "LIM", 3)) {
*ut_code++ = UT_LIM;
wval = ut_par(&cp, 1);
*ut_code++ = wval >> 8;
*ut_code++ = wval;
} else if (!strncmp(cp, "GSRT", 4)) {
*ut_code++ = UT_GSRT;
wval = ut_par(&cp, 1);
*ut_code++ = wval >> 8;
*ut_code++ = wval;
} else if (!strncmp(cp, "XPT", 3)) {
*ut_code++ = UT_XPT;
wval = ut_par(&cp, 1);
*ut_code++ = wval >> 8;
*ut_code++ = wval;
} else if (!strncmp(cp, "DBG", 3)) {
*ut_code++ = UT_DBG;
wval = ut_par(&cp, 1);
*ut_code++ = wval;
}
cp++;
}
*ut_code++ = UT_END;
*sp = cp - 1;
uint16_t memsize = (uint32_t)ut_code - (uint32_t)tmp_code;
// allocate memory
uint8_t *mp = (uint8_t*)malloc(memsize + 2);
if (mp) {
memmove(mp, tmp_code, memsize);
*code = mp;
}
}
// ===== Touch Parameter Parsing =====
uint32_t uDisplay::ut_par(char **lp, uint32_t mode) {
char *cp = *lp;
while (*cp != ' ') {
if (!cp) break;
cp++;
}
cp++;
uint32_t result;
if (!mode) {
// hex
result = strtol(cp, &cp, 16);
} else if (mode == 1) {
// word
result = strtol(cp, &cp, 10);
} else {
// float as 32bit integer
float fval = CharToFloat(cp);
result = *(uint32_t*)&fval;
while (*cp) {
if (*cp == ' ' || *cp =='\n') {
break;
}
cp++;
}
}
*lp = cp;
return result;
}
int16_t uDisplay::ut_execute(uint8_t *ut_code) {
int16_t result = 0;
uint8_t iob, len;
uint16_t wval;
while (*ut_code != UT_END) {
iob = *ut_code++;
switch (iob) {
case UT_RD:
// read 1 byte
ut_code = ut_rd(ut_code, 1, 1);
break;
case UT_RDM:
// read multiple bytes
ut_code = ut_rd(ut_code, 2, 1);
break;
case UT_RDW:
// read 1 byte
ut_code = ut_rd(ut_code, 1, 2);
break;
case UT_RDWM:
// read multiple bytes
ut_code = ut_rd(ut_code, 2, 2);
break;
case UT_WR:
ut_code = ut_wr(ut_code, 1);
break;
case UT_WRW:
ut_code = ut_wr(ut_code, 2);
break;
case UT_CP:
// compare
iob = *ut_code++;
result = (iob == ut_array[0]);
break;
case UT_CPM:
// compare multiple
len = *ut_code++;
result = 0;
for (uint32_t cnt = 0; cnt < len; cnt++) {
iob = *ut_code++;
result |= (iob == ut_array[0]);
}
break;
case UT_CPR:
// compare
iob = *ut_code++;
result = (iob == result);
break;
case UT_RTF:
// return when false
if (result == 0) {
return false;
}
break;
case UT_RTT:
// return when true
if (result > 0) {
return false;
}
break;
case UT_MVB:
// move byte from index to high or low result
wval = *ut_code++;
iob = *ut_code++;
if (wval == 0) {
result &= 0xff00;
result |= ut_array[iob];
} else {
result &= 0x00ff;
result |= (ut_array[iob] << 8);
}
break;
case UT_MV:
// move
// source
result = *ut_code++;
iob = *ut_code++;
if (iob == 1) {
result = ut_array[result];
} else if (iob == 2) {
iob = result;
result = ut_array[iob] << 8;
result |= ut_array[iob + 1];
} else {
iob = result;
result = ut_array[iob + 1] << 8;
result |= ut_array[iob];
}
result &= 0xfff;
break;
case UT_AND:
// and
wval = *ut_code++ << 8;
wval |= *ut_code++;
result &= wval;
break;
case UT_SCALE:
{
wval = *ut_code++ << 8;
wval |= *ut_code++;
result -= wval;
uint32_t lval = (uint32_t)*ut_code++ << 24;
lval |= (uint32_t)*ut_code++ << 16;
lval |= (uint32_t)*ut_code++ << 8;
lval |= (uint32_t)*ut_code++;
float fval = *(float*)&lval;
fval *= (float)result;
result = fval;
}
break;
case UT_LIM:
wval = *ut_code++ << 8;
wval |= *ut_code++;
if (result > wval) {
result = wval;
}
break;
case UT_RT:
// result
return result;
break;
case UT_GSRT:
#ifdef UDISPLAY_I80
{
// Simple resistive touch using I80 data pins
uint32_t val = get_sr_touch(panel_config->i80.data_pins_low[1], // XP
panel_config->i80.cs_pin, // XM
panel_config->i80.dc_pin, // YP
panel_config->i80.data_pins_low[0]); // YM
if (val == 0) {
return false;
}
uint16_t xp = val >> 16;
uint16_t yp = val;
wval = *ut_code++ << 8;
wval |= *ut_code++;
if (xp > wval && yp > wval) {
ut_array[0] = val >> 24;
ut_array[1] = val >> 16;
ut_array[2] = val >> 8;
ut_array[3] = val;
return true;
}
return false;
}
#endif // UDISPLAY_I80
break;
case UT_XPT:
wval = *ut_code++ << 8;
wval |= *ut_code++;
result = ut_XPT2046(wval);
break;
case UT_DBG:
// debug show result
wval = *ut_code++;
AddLog(LOG_LEVEL_INFO, PSTR("UTDBG %d: %02x : %02x,%02x,%02x,%02x"), wval, result, ut_array[0], ut_array[1], ut_array[2], ut_array[3]);
break;
case UT_END:
break;
}
}
return result;
}
// ===== Low-Level Touch Communication =====
uint8_t *uDisplay::ut_rd(uint8_t *iop, uint32_t len, uint32_t amode) {
if (ut_wire) {
// i2c mode
ut_wire->beginTransmission(ut_i2caddr);
ut_wire->write(*iop++);
if (amode == 2) {
ut_wire->write(*iop++);
}
ut_wire->endTransmission(false);
if (len > 1) {
len = *iop++;
}
ut_wire->requestFrom(ut_i2caddr, (size_t)len);
uint8_t index = 0;
while (ut_wire->available()) {
ut_array[index++] = ut_wire->read();
}
} else {
// spi mode
if (amode == 1) {
uint16_t val = *iop++;
uint16_t len = *iop++;
if (ut_spi) {
digitalWrite(ut_spi_cs, LOW);
ut_spi->beginTransaction(ut_spiSettings);
ut_spi->transfer(val);
val = ut_spi->transfer16(0);
ut_spi->endTransaction();
ut_array[len] = val << 8;
ut_array[len + 1] = val;
digitalWrite(ut_spi_cs, HIGH);
}
}
}
return iop;
}
uint8_t *uDisplay::ut_wr(uint8_t *iop, uint32_t amode) {
if (ut_wire) {
// i2c mode
ut_wire->beginTransmission(ut_i2caddr);
ut_wire->write(*iop++);
if (amode == 2) {
ut_wire->write(*iop++);
}
ut_wire->write(*iop++);
ut_wire->endTransmission(true);
} else {
// spi mode
}
return iop;
}
// ===== XPT2046 Touch Controller =====
uint16_t uDisplay::ut_XPT2046(uint16_t z_th) {
uint16_t result = 0;
if (ut_spi) {
int16_t data[6];
ut_spi->beginTransaction(ut_spiSettings);
digitalWrite(ut_spi_cs, LOW);
ut_spi->transfer(0xB1 /* Z1 */);
int16_t z1 = ut_spi->transfer16(0xC1 /* Z2 */) >> 3;
int16_t z = z1 + 4095;
int16_t z2 = ut_spi->transfer16(0x91 /* X */) >> 3;
z -= z2;
if (z >= z_th) {
ut_spi->transfer16(0x91 /* X */); // dummy X measure, 1st is always noisy
data[0] = ut_spi->transfer16(0xD1 /* Y */) >> 3;
data[1] = ut_spi->transfer16(0x91 /* X */) >> 3; // make 3 x-y measurements
data[2] = ut_spi->transfer16(0xD1 /* Y */) >> 3;
data[3] = ut_spi->transfer16(0x91 /* X */) >> 3;
result = 1;
} else {
data[0] = data[1] = data[2] = data[3] = 0;
}
data[4] = ut_spi->transfer16(0xD0 /* Y */) >> 3; // Last Y touch power down
data[5] = ut_spi->transfer16(0) >> 3;
digitalWrite(ut_spi_cs, HIGH);
ut_spi->endTransaction();
uint16_t x = besttwoavg(data[0], data[2], data[4]);
uint16_t y = besttwoavg(data[1], data[3], data[5]);
ut_array[0] = x >> 8;
ut_array[1] = x;
ut_array[2] = y >> 8;
ut_array[3] = y;
}
return result;
}
// ===== Touch Data Processing =====
int16_t uDisplay::besttwoavg(int16_t x, int16_t y, int16_t z) {
int16_t da, db, dc;
int16_t reta = 0;
if (x > y) da = x - y; else da = y - x;
if (x > z) db = x - z; else db = z - x;
if (z > y) dc = z - y; else dc = y - z;
if (da <= db && da <= dc) reta = (x + y) >> 1;
else if (db <= da && db <= dc) reta = (x + z) >> 1;
else reta = (y + z) >> 1;
return (reta);
}
#endif // USE_UNIVERSAL_TOUCH

View File

@ -0,0 +1,111 @@
#include "uDisplay.h"
#include "uDisplay_config.h"
// ===== String and Parsing Utilities =====
uint8_t uDisplay::strlen_ln(char *str) {
for (uint32_t cnt = 0; cnt < 256; cnt++) {
if (!str[cnt] || str[cnt] == '\n' || str[cnt] == ' ') return cnt;
}
return 0;
}
char *uDisplay::devname(void) {
return dname;
}
uint32_t uDisplay::str2c(char **sp, char *vp, uint32_t len) {
char *lp = *sp;
if (len) len--;
char *cp = strchr(lp, ',');
if (cp) {
while (1) {
if (*lp == ',') {
*vp = 0;
*sp = lp + 1;
return 0;
}
if (len) {
*vp++ = *lp++;
len--;
} else {
lp++;
}
}
} else {
uint16_t slen = strlen(lp);
if (slen) {
strlcpy(vp, *sp, len);
*sp = lp + slen;
return 0;
}
}
return 1;
}
int32_t uDisplay::next_val(char **sp) {
char ibuff[16];
if (!str2c(sp, ibuff, sizeof(ibuff))) {
return atoi(ibuff);
}
return 0xff;
}
uint32_t uDisplay::next_hex(char **sp) {
char ibuff[16];
if (!str2c(sp, ibuff, sizeof(ibuff))) {
return strtol(ibuff, 0, 16);
}
return 0xff;
}
// ===== Touch Coordinate Conversion =====
// the cases are PSEUDO_OPCODES from MODULE_DESCRIPTOR
// and may be expanded with more opcodes
void uDisplay::TS_RotConvert(int16_t *x, int16_t *y) {
int16_t temp;
if (rot_t[cur_rot] & 0x80) {
temp = *y;
*y = *x;
*x = temp;
}
if (rotmap_xmin >= 0) {
*y = map(*y, rotmap_ymin, rotmap_ymax, 0, gys);
*x = map(*x, rotmap_xmin, rotmap_xmax, 0, gxs);
*x = constrain(*x, 0, gxs);
*y = constrain(*y, 0, gys);
}
switch (rot_t[cur_rot] & 0xf) {
case 0:
break;
case 1:
temp = *y;
*y = height() - *x;
*x = temp;
break;
case 2:
*x = width() - *x;
*y = height() - *y;
break;
case 3:
temp = *y;
*y = *x;
*x = width() - temp;
break;
case 4:
*x = width() - *x;
break;
case 5:
*y = height() - *y;
break;
}
}
// ===== Color Conversion Helper =====
// static inline void lvgl_color_swap(uint16_t *data, uint16_t len) {
// for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8);
// }

File diff suppressed because it is too large Load Diff

View File

@ -1,417 +0,0 @@
#ifndef _UDISP_
#define _UDISP_
#include <Adafruit_GFX.h>
#include <renderer.h>
#include <Wire.h>
#include <SPI.h>
#ifdef ESP32
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define USE_ESP32_S3
#endif
#include "driver/spi_master.h"
#include "soc/gpio_periph.h"
#include <rom/gpio.h>
#endif
enum {
UT_RD,UT_RDM,UT_CP,UT_RTF,UT_MV,UT_MVB,UT_RT,UT_RTT,UT_RDW,UT_RDWM,UT_WR,UT_WRW,UT_CPR,UT_AND,UT_SCALE,UT_LIM,UT_DBG,UT_GSRT,UT_XPT,UT_CPM,UT_END
};
#define RA8876_DATA_WRITE 0x80
#define RA8876_DATA_READ 0xC0
#define RA8876_CMD_WRITE 0x00
#define RA8876_STATUS_READ 0x40
#define UDSP_WRITE_16 0xf0
#define UDSP_READ_DATA 0xf1
#define UDSP_READ_STATUS 0xf2
#define SIMPLERS_XP par_dbl[1]
#define SIMPLERS_XM par_cs
#define SIMPLERS_YP par_rs
#define SIMPLERS_YM par_dbl[0]
#ifdef USE_ESP32_S3
#include <esp_lcd_panel_io.h>
#include "esp_private/gdma.h"
#include <hal/gpio_ll.h>
#include <hal/lcd_hal.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; }
//static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004014 : 0x60004008) ; } // workaround Eratta
static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; }
//static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004018 : 0x6000400C) ; }
static inline bool gpio_in(int_fast8_t pin) { return ((pin & 32) ? GPIO.in1.data : GPIO.in) & (1 << (pin & 31)); }
static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_hi: %d", pin); }
static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_lo: %d", pin); }
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_pm.h"
#include "esp_lcd_panel_ops.h"
#include <hal/dma_types.h>
#include <rom/cache.h>
#include "esp_rom_lldesc.h"
#endif // USE_ESP32_S3
#define _UDSP_I2C 1
#define _UDSP_SPI 2
#define _UDSP_PAR8 3
#define _UDSP_PAR16 4
#define _UDSP_RGB 5
#define UDISP1_WHITE 1
#define UDISP1_BLACK 0
#define MAX_LUTS 5
#define DISPLAY_INIT_MODE 0
#define DISPLAY_INIT_PARTIAL 1
#define DISPLAY_INIT_FULL 2
enum uColorType { uCOLOR_BW, uCOLOR_COLOR };
// Color definitions
#define UDISP_BLACK 0x0000 /* 0, 0, 0 */
#define UDISP_NAVY 0x000F /* 0, 0, 128 */
#define UDISP_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define UDISP_DARKCYAN 0x03EF /* 0, 128, 128 */
#define UDISP_MAROON 0x7800 /* 128, 0, 0 */
#define UDISP_PURPLE 0x780F /* 128, 0, 128 */
#define UDISP_OLIVE 0x7BE0 /* 128, 128, 0 */
#define UDISP_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define UDISP_DARKGREY 0x7BEF /* 128, 128, 128 */
#define UDISP_BLUE 0x001F /* 0, 0, 255 */
#define UDISP_GREEN 0x07E0 /* 0, 255, 0 */
#define UDISP_CYAN 0x07FF /* 0, 255, 255 */
#define UDISP_RED 0xF800 /* 255, 0, 0 */
#define UDISP_MAGENTA 0xF81F /* 255, 0, 255 */
#define UDISP_YELLOW 0xFFE0 /* 255, 255, 0 */
#define UDISP_WHITE 0xFFFF /* 255, 255, 255 */
#define UDISP_ORANGE 0xFD20 /* 255, 165, 0 */
#define UDISP_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define UDISP_PINK 0xFc18 /* 255, 128, 192 */
#ifdef ESP8266
#define PIN_OUT_SET 0x60000304
#define PIN_OUT_CLEAR 0x60000308
#define GPIO_SET(A) WRITE_PERI_REG( PIN_OUT_SET, 1 << A)
#define GPIO_CLR(A) WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << A)
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
#else
#undef GPIO_SET
#undef GPIO_CLR
#undef GPIO_SET_SLOW
#undef GPIO_CLR_SLOW
#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A)
#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A)
#else // plain ESP32
#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A)
#define GPIO_SET(A) GPIO.out_w1ts = (1 << A)
#endif
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
#endif
#define SPI_BEGIN_TRANSACTION if (spi_nr <= 2) beginTransaction(spiSettings);
#define SPI_END_TRANSACTION if (spi_nr <= 2) endTransaction();
#define SPI_CS_LOW if (spi_cs >= 0) GPIO_CLR_SLOW(spi_cs);
#define SPI_CS_HIGH if (spi_cs >= 0) GPIO_SET_SLOW(spi_cs);
#define SPI_DC_LOW if (spi_dc >= 0) GPIO_CLR_SLOW(spi_dc);
#define SPI_DC_HIGH if (spi_dc >= 0) GPIO_SET_SLOW(spi_dc);
class uDisplay : public Renderer {
public:
uDisplay(char *);
~uDisplay(void);
Renderer *Init(void);
void DisplayInit(int8_t p,int8_t size,int8_t rot,int8_t font);
void Updateframe();
void DisplayOnff(int8_t on);
void Splash(void);
char *devname(void);
uint16_t fgcol(void);
uint16_t bgcol(void);
int8_t color_type(void);
// void dim(uint8_t dim); // original version with 4 bits resolution 0..15
virtual void dim10(uint8_t dim, uint16_t dim_gamma); // dimmer with 8 bits resolution, 0..255. Gamma correction must be done by caller with 10 bits resolution
uint16_t GetColorFromIndex(uint8_t index);
void setRotation(uint8_t m);
void fillScreen(uint16_t color);
void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
void pushColors(uint16_t *data, uint16_t len, boolean first);
void TS_RotConvert(int16_t *x, int16_t *y);
void invertDisplay(boolean i);
void SetPwrCB(pwr_cb cb) { pwr_cbp = cb; };
void SetDimCB(dim_cb cb) { dim_cbp = cb; };
#ifdef USE_UNIVERSAL_TOUCH
// universal touch driver
bool utouch_Init(char **name);
uint16_t touched(void);
int16_t getPoint_x();
int16_t getPoint_y();
#endif // USE_UNIVERSAL_TOUCH
private:
void beginTransaction(SPISettings s);
void endTransaction(void);
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void drawPixel(int16_t x, int16_t y, uint16_t color);
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
uint32_t str2c(char **sp, char *vp, uint32_t len);
void i2c_command(uint8_t val);
void ulcd_command_one(uint8_t val);
void ulcd_command(uint8_t val);
void ulcd_data8(uint8_t val);
void ulcd_data16(uint16_t val);
void ulcd_data32(uint32_t val);
void write8(uint8_t val);
void write8_slow(uint8_t val);
void write9(uint8_t val, uint8_t dc);
void write9_slow(uint8_t val, uint8_t dc);
void hw_write9(uint8_t val, uint8_t dc);
void write16(uint16_t val);
void write32(uint32_t val);
void spi_data9(uint8_t d, uint8_t dc);
uint8_t readData(void);
uint8_t readStatus(void);
uint8_t writeReg16(uint8_t reg, uint16_t wval);
void WriteColor(uint16_t color);
void SetLut(const unsigned char* lut);
void SetLuts(void);
void DisplayFrame_29(void);
void Updateframe_EPD();
//void DisplayFrame_42(const unsigned char* frame_buffer);
void SetFrameMemory(const unsigned char* image_buffer);
void SetFrameMemory(const unsigned char* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height);
void SetMemoryArea(int x_start, int y_start, int x_end, int y_end);
void SetMemoryPointer(int x, int y);
void DrawAbsolutePixel(int x, int y, int16_t color);
void drawPixel_EPD(int16_t x, int16_t y, uint16_t color);
void fillRect_EPD(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
void drawFastVLine_EPD(int16_t x, int16_t y, int16_t h, uint16_t color);
void drawFastHLine_EPD(int16_t x, int16_t y, int16_t w, uint16_t color);
void Init_EPD(int8_t p);
void spi_command_EPD(uint8_t val);
void spi_data8_EPD(uint8_t val);
//void SetPartialWindow_42(uint8_t* frame_buffer, int16_t x, int16_t y, int16_t w, int16_t l, int16_t dtm);
void ClearFrameMemory(unsigned char color);
void ClearFrame_42(void);
void DisplayFrame_42(void);
uint8_t strlen_ln(char *str);
int32_t next_val(char **sp);
uint32_t next_hex(char **sp);
void setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
char dname[16];
int8_t bpp;
uint8_t col_type;
uint8_t interface;
uint8_t i2caddr;
int8_t i2c_scl;
int8_t spec_init;
TwoWire *wire;
int8_t wire_n;
int8_t i2c_sda;
uint8_t i2c_col_start;
uint8_t i2c_col_end;
uint8_t i2c_page_start;
uint8_t i2c_page_end;
int8_t reset;
uint8_t dsp_cmds[256];
uint8_t dsp_ncmds;
uint8_t dsp_on;
uint8_t dsp_off;
uint8_t allcmd_mode;
int8_t splash_font;
uint8_t splash_size;
uint16_t splash_xp;
uint16_t splash_yp;
uint16_t fg_col;
uint16_t bg_col;
uint16_t gxs;
uint16_t gys;
int8_t bpmode;
int8_t spi_cs;
int8_t spi_clk;
int8_t spi_mosi;
int8_t spi_dc;
int8_t bpanel; // backbanel GPIO, -1 if none
int8_t spi_miso;
uint8_t dimmer8; // 8 bits resolution, 0..255
uint16_t dimmer10_gamma; // 10 bits resolution, 0..1023, gamma corrected
SPIClass *uspi;
uint8_t sspi;
SPISettings spiSettings;
uint8_t spi_speed;
uint8_t spi_nr = 1;
uint8_t madctrl;
uint8_t startline;
uint8_t rot[4];
uint8_t rot_t[4];
uint16_t x_addr_offs[4];
uint16_t y_addr_offs[4];
uint8_t saw_1;
uint8_t saw_2;
uint8_t saw_3;
uint8_t cur_rot;
uint8_t col_mode;
uint8_t inv_on;
uint8_t inv_off;
uint8_t sa_mode;
uint8_t dim_op;
uint8_t lutfsize;
uint8_t lutpsize;
int16_t lutftime;
int8_t busy_pin;
uint16_t lutptime;
uint16_t lut3time;
uint16_t lut_num;
uint8_t ep_mode;
uint8_t ep_update_mode;
uint8_t *lut_full;
uint8_t lut_siz_full;
uint8_t *lut_partial;
uint8_t lut_siz_partial;
uint8_t *frame_buffer;
uint8_t epcoffs_full;
uint8_t epc_full_cnt;
uint8_t epcoffs_part;
uint8_t epc_part_cnt;
uint8_t *lut_array[MAX_LUTS];
uint8_t lut_cnt[MAX_LUTS];
uint8_t lut_cmd[MAX_LUTS];
uint8_t lut_siz[MAX_LUTS];
uint16_t seta_xp1;
uint16_t seta_xp2;
uint16_t seta_yp1;
uint16_t seta_yp2;
int16_t rotmap_xmin;
int16_t rotmap_xmax;
int16_t rotmap_ymin;
int16_t rotmap_ymax;
void pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap = false);
void delay_sync(int32_t time);
void reset_pin(int32_t delayl, int32_t delayh);
void delay_arg(uint32_t arg);
void Send_EP_Data(void);
void send_spi_cmds(uint16_t cmd_offset, uint16_t cmd_size);
void send_spi_icmds(uint16_t cmd_size);
#ifdef USE_ESP32_S3
int8_t par_cs;
int8_t par_rs;
int8_t par_wr;
int8_t par_rd;
int8_t par_dbl[8];
int8_t par_dbh[8];
int8_t de;
int8_t vsync;
int8_t hsync;
int8_t pclk;
uint16_t hsync_polarity;
uint16_t hsync_front_porch;
uint16_t hsync_pulse_width;
uint16_t hsync_back_porch;
uint16_t vsync_polarity;
uint16_t vsync_front_porch;
uint16_t vsync_pulse_width;
uint16_t vsync_back_porch;
uint16_t pclk_active_neg;
esp_lcd_panel_handle_t _panel_handle = NULL;
esp_lcd_i80_bus_handle_t _i80_bus = nullptr;
gdma_channel_handle_t _dma_chan;
lldesc_t *_dmadesc = nullptr;
uint32_t _dmadesc_size = 0;
uint32_t _clock_reg_value;
void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq);
void _alloc_dmadesc(size_t len);
void _setup_dma_desc_links(const uint8_t *data, int32_t len);
void pb_beginTransaction(void);
void pb_endTransaction(void);
void pb_wait(void);
bool pb_busy(void);
void _pb_init_pin(bool);
bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length);
void pb_writeData(uint32_t data, uint_fast8_t bit_length);
void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma);
void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma);
void _send_align_data(void);
volatile lcd_cam_dev_t* _dev;
uint32_t* _cache_flip;
static constexpr size_t CACHE_SIZE = 256;
uint32_t _cache[2][CACHE_SIZE / sizeof(uint32_t)];
bool _has_align_data;
uint8_t _align_data;
void cs_control(bool level);
uint32_t get_sr_touch(uint32_t xp, uint32_t xm, uint32_t yp, uint32_t ym);
void drawPixel_RGB(int16_t x, int16_t y, uint16_t color);
#endif
#ifdef ESP32
// dma section
bool DMA_Enabled = false;
uint8_t spiBusyCheck = 0;
spi_transaction_t trans;
spi_device_handle_t dmaHAL;
spi_host_device_t spi_host = VSPI_HOST;
// spi_host_device_t spi_host = VSPI_HOST;
bool initDMA(int32_t ctrl_cs);
void deInitDMA(void);
bool dmaBusy(void);
void dmaWait(void);
void pushPixelsDMA(uint16_t* image, uint32_t len);
void pushPixels3DMA(uint8_t* image, uint32_t len);
#endif // ESP32
#ifdef USE_UNIVERSAL_TOUCH
// universal touch driver
void ut_trans(char **sp, uint8_t **ut_code);
int16_t ut_execute(uint8_t *ut_code);
uint32_t ut_par(char **cp, uint32_t mode);
uint8_t *ut_rd(uint8_t *io, uint32_t len, uint32_t amode);
uint8_t *ut_wr(uint8_t *io, uint32_t amode);
uint16_t ut_XPT2046(uint16_t zh);
int16_t besttwoavg( int16_t x , int16_t y , int16_t z );
uint8_t ut_array[16];
uint8_t ut_i2caddr;
uint8_t ut_spi_cs = -1;
int8_t ut_reset = -1;
int8_t ut_irq = -1;
uint8_t ut_spi_nr;
TwoWire *ut_wire = nullptr;;
SPIClass *ut_spi = nullptr;;
SPISettings ut_spiSettings;
char ut_name[8];
uint8_t *ut_init_code = nullptr;
uint8_t *ut_touch_code = nullptr;
uint8_t *ut_getx_code = nullptr;
uint8_t *ut_gety_code = nullptr;
#endif // USE_UNIVERSAL_TOUCH
};
#endif // _UDISP_

View File

@ -0,0 +1,43 @@
# Dockerfile to build platformio
FROM petrows/arduino-vid6608:latest
RUN <<PKG
set -e
apt-get -qq update
apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-venv \
apt-get -qq clean
rm -rf /var/lib/apt/lists/*
PKG
# Python virtual env
ENV VIRTUAL_ENV=/var/venv
RUN mkdir -p -m 777 $VIRTUAL_ENV
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Default env
ENV HOME=/opt/home
RUN mkdir -p -m 777 $HOME
RUN pip install platformio==6.1.18
# Copy config file and enforce deps installation
COPY platformio.ini /tmp/
RUN <<PIO
cd /tmp/
# Install common packages
platformio pkg install
# Install required tools
platformio pkg install -t platformio/tool-cppcheck
platformio pkg install -t platformio/tool-clangtidy
# Cleanup
rm -rf /tmp/*
# Allow this image to run under non-root
chmod -R 777 $HOME
PIO
WORKDIR /app

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -0,0 +1,93 @@
# Gauge stepper motor Switec X25.168 driver for VID6608 and clones
![Moving example](doc/output.gif)
This library implements driver for Arduino framework
for following driver chips for analog automotive gauges (Switec X25.168, X27.168 and clones) with microstepping support:
* VID6606 (2 motors)
* VID6608 (4 motors)
* VT6608S
* AX1201728SG
* BY8920
* many others
Driver chips with microstepping is the recommended way to drive such motors,
they provide much more relailabe and smooth movement with reduced noise and
to avoid skipping steps.
This library is developed by inspiration from [SwitecX25](https://github.com/clearwater/SwitecX25) library, many thanks to author.
This library has following features:
* More precise Datasheet complaince
* Another smoothing method (requires less calculations)
* Optimized homing
* Extended API's
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/petrows/library/vid6608.svg)](https://registry.platformio.org/libraries/petrows/vid6608)
## Chip documentation
See [VID6606 Datasheet (English)](doc/VID6608.pdf).
## Wiring
This library requires that two pins (per drive) are connected to two outputs.
![Datasheet scheme](doc/operation-configuration.png)
* Step pin f(scx): impulse to drive motor to one microstep;
* Direction pin CW/CCW: defines direction ov movement;
* RESET pin: does not controlled by this library. Hold to VDD to enable function (see notes below);
## RESET pin
This library does not control RESET pin, please perform this inside your firmware.
I have problems with some IC's, as they lost function after RESET pin manipulation. [Datasheet](doc/VID6608.pdf) recommends to hold it LOW during boot, and set to HIGH to enable operation, but i recommend just to connect to VDD to be safe.
## Setting zero
Motor is set to zero by moving whole scale and kept bouncing on the one of
dead positions. This library provides optimized way to perform homing: it does
1/2 of scale forward, then full scale backward. This helps to reduce bouncing
like in the classical "full scale back" method.
## Function documentation
See inline documentation in source code: [vid6608.h](src/vid6608.h).
## Basic example
Simple code to activate the library.
```cpp
#include <Arduino.h>
#include <vid6608.h>
// standard X25.168 range 315 degrees at 1/3 degree steps
#define STEPS (320 * 12)
#define PIN_STEP 26 // Pin, connected to f(scx)
#define PIN_DIR 27 // Pin. connected to CW/CCW
vid6608 motor1(PIN_STEP, PIN_DIR, STEPS);
unsigned long nextMoveTime = 0;
uint16_t nextMovePos = 0;
void setup(void)
{
// Run the motor against the stops
motor1.zero();
// Plan next move (in loop())
motor1.moveTo(100);
}
void loop(void)
{
// the motor only moves when you call update
motor1.loop();
}
```

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@ -0,0 +1,78 @@
#include <Arduino.h>
#include <vid6608.h>
// standard X25.168 range 315 degrees at 1/3 degree steps
#define STEPS (320 * 12)
#define PIN_STEP 26 // Pin, connected to f(scx)
#define PIN_DIR 27 // Pin. connected to CW/CCW
// Custom Acceleration table for the X27.168
// Proposed non-linear curve by ChatGPT
static vid6608::AccelTable accelTable[] = {
{30, 3000},
{65, 2920},
{100, 2780},
{135, 2600},
{170, 2380},
{205, 2140},
{240, 1890},
{275, 1650},
{310, 1420},
{345, 1210},
{380, 1020},
{415, 860},
{450, 730},
{485, 620},
{520, 530},
{555, 460},
{590, 410},
{625, 370},
{660, 340},
{695, 320},
{730, 310},
{765, 305},
{800, 300},
};
vid6608 motor1(PIN_STEP, PIN_DIR, STEPS);
unsigned long nextMoveTime = 0;
uint16_t nextMovePos = 0;
void setup(void)
{
// Initalize debug here
Serial.begin(9600);
Serial.println("Setup");
// Set custom curve
motor1.setAccelTable(accelTable);
// run the motor against the stops
motor1.zero();
// We are done
Serial.println("Setup done");
}
void loop(void)
{
// the motor only moves when you call update
motor1.loop();
// Wait for motor to finish
if (motor1.isStopped()) {
// Plan next move here, to have real pause between moves
if (nextMoveTime == 0) {
nextMoveTime = millis() + random(500, 2000);
} else {
// Wait for next move time
if (millis() > nextMoveTime) {
// Plan next move here
nextMoveTime = 0;
nextMovePos = random(0, STEPS);
Serial.print("Moving to ");
Serial.println(nextMovePos);
motor1.moveTo(nextMovePos);
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "vid6608",
"keywords": "driver, motor, control",
"description": "Arduino library for driving IC VID6608 and clones for Switec X25.168 / X27.168 miniature stepper motors",
"homepage": "https://github.com/petrows/arduino-vid6608",
"repository":
{
"type": "git",
"url": "https://github.com/petrows/arduino-vid6608.git"
},
"authors": {
"name": "Petr Golovachev",
"email": "petro@petro.ws",
"url": "https://petro.ws/"
},
"version": "1.0.2",
"frameworks": "arduino",
"platforms": "*"
}

View File

@ -0,0 +1,9 @@
name=vid6608
version=1.0.2
author=Petr Golovachev
maintainer=Petr Golovachev <petro@petro.ws>
sentence=Arduino library for driving IC VID6608 and clones for Switec X25.168 / X27.168 miniature stepper motors
paragraph=This library allows to control automotive gauge stepper motors with microstepping drivers with smooth movement
category=Device Control
url=https://github.com/petrows/arduino-vid6608
architectures=*

View File

@ -0,0 +1,31 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = esp32doit-devkit-v1
[env:uno]
platform = atmelavr
board = uno
framework = arduino
check_tool = cppcheck
check_src_filters =
+<src/*>
+<examples/RandomMove/*>
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
[env:d1_mini_pro]
platform = espressif8266
board = d1_mini_pro
framework = arduino

View File

@ -0,0 +1,172 @@
/**
* VID6608 gauge motor driver library
* by Petr Golovachev <petro@petro.ws>, 2025
*
* https://github.com/petrows/arduino-vid6608
*
* Licensed under the GPL, see LICENSE.txt for details.
*/
#include "vid6608.h"
// This table defines the acceleration curve.
// 1 value: distance from begin / target in steps
// 2 value: speed delay in microseconds
static vid6608::AccelTable defaultAccelTable[] = {
{30, 3000},
{65, 2920},
{100, 2780},
{135, 2600},
{170, 2380},
{205, 2140},
{240, 1890},
{275, 1650},
{310, 1420},
{345, 1210},
{380, 1020},
{415, 860},
{450, 730},
{485, 620},
{520, 530},
{555, 460},
{590, 410},
{625, 370},
{660, 340},
{695, 320},
{730, 310},
{765, 305},
{800, 300},
};
vid6608::vid6608(int stepPin, int dirPin, uint16_t maxSteps /*= VID6608_MAX_STEPS*/) {
// Zero values:
this->maxSteps = maxSteps;
this->stepPin = stepPin;
this->dirPin = dirPin;
this->moveState = STATE_IDLE;
this->moveDirection = MOVE_NONE;
this->dirPinState = MOVE_NONE; // invalid state to force update on first step
this->currentPosition = 0;
this->targetPosition = 0;
this->setAccelTable(defaultAccelTable);
// Setup pins
pinMode(this->stepPin, OUTPUT);
pinMode(this->dirPin, OUTPUT);
digitalWrite(this->stepPin, LOW);
digitalWrite(this->dirPin, LOW);
}
void vid6608::zero(uint16_t delay /*= VID6608_DEFAULT_ZERO_SPEED*/) {
// We have to optimize the zeroing process to avoid bouncing on end-stops
// Drive makes 1/2 move forward and 1/2 backward to reduce bouncing
// This will reduce bouncing, if the last position was not a zero position
uint16_t halfSteps = this->maxSteps / 2;
// Move to halfSteps forward
for (uint16_t x = 0; x < halfSteps; x++) {
step(MOVE_FORWARD, delay);
}
// Move to halfSteps back
for (uint16_t x = 0; x < this->maxSteps; x++) {
step(MOVE_BACKWARD, delay);
}
// Reset values
this->currentPosition = 0;
this->targetPosition = 0;
this->targetPositionNext = 0;
this->moveState = STATE_IDLE;
this->moveDirection = MOVE_NONE;
}
void vid6608::moveTo(uint16_t position) {
// Test for duplicates
if (position == this->targetPositionNext) {
return;
}
// Sanity check
if (position > this->maxSteps - 1) {
position = this->maxSteps - 1;
}
// Calculate new values to move and schedule it for next async call
this->targetPositionNext = position;
}
bool vid6608::isMoving() {
return this->moveState == STATE_MOVING;
}
uint16_t vid6608::getPosition() {
return this->currentPosition;
}
void vid6608::loop() {
// Check if we have a new target position scheduled
if (this->moveState == STATE_IDLE) {
if (this->targetPosition != this->targetPositionNext) {
// New position is scheduled, we have to prepare new values for it
this->targetPosition = this->targetPositionNext;
this->moveState = STATE_MOVING;
this->moveDirection = this->targetPosition > this->currentPosition ? MOVE_FORWARD : MOVE_BACKWARD;
this->moveDone = 0;
// Calculate point values, we have to save 1/2 of way to compare it,
// it is required to decide what distance we have (from begin or to end)
if (this->moveDirection == MOVE_FORWARD) {
this->moveLeft = this->targetPosition - this->currentPosition;
} else {
this->moveLeft = this->currentPosition - this->targetPosition;
}
}
}
// If we are moving -> update to next position
if (this->moveState == STATE_MOVING) {
// Check what we have - close to end or begin?
uint16_t accelDistance = 0;
if (this->moveDone < this->moveLeft) {
// We are in the first half
accelDistance = this->moveDone;
} else {
// We are in the second half
accelDistance = this->moveLeft;
}
// Get the actual speed, depending on distance from/to
uint16_t accelDelay = getDelay(accelDistance);
// Actual move
step(this->moveDirection, accelDelay);
this->moveDone++;
this->moveLeft--;
this->currentPosition += (this->moveDirection == MOVE_FORWARD) ? 1 : -1;
// Check the end of movement
if (this->currentPosition == this->targetPosition) {
this->moveState = STATE_IDLE;
}
}
}
void vid6608::step(vid6608::MoveDirection direction, uint16_t delayUs) {
if (direction != this->dirPinState) {
this->dirPinState = direction;
digitalWrite(this->dirPin, direction == MOVE_FORWARD ? LOW : HIGH);
// Setup time must be > 100ns, we 1us to be safe
delay(1);
}
digitalWrite(this->stepPin, HIGH);
delayMicroseconds(delayUs);
// VID6608 reacts on raising front, so we can lower the pin immediately with lower delay
// to improve max speed. Lower time must be > 100ns, we set 1us to be safe.
digitalWrite(this->stepPin, LOW);
delayMicroseconds(1);
// We should keep resources reserved by others
yield();
}
uint16_t vid6608::getDelay(uint16_t distance) {
if (distance >= this->accelMaxDistance) {
return this->accelMaxDelay;
}
for (uint16_t x = 0; x < this->accelTableSize; x++) {
if (this->accelTable[x].distance > distance) {
return this->accelTable[x].delay;
}
}
// We should never be here, but return to be safe
return this->accelMaxDelay;
}

View File

@ -0,0 +1,154 @@
/**
* VID6608 gauge motor driver library
* by Petr Golovachev <petro@petro.ws>, 2025
*
* https://github.com/petrows/arduino-vid6608
*
* Licensed under the GPL, see LICENSE.txt for details.
*/
#ifndef vid6608_h
#define vid6608_h
#include <Arduino.h>
/**
* @brief Default max steps for VID6608 motor + x27.168 clones
* @note driver has 12 steps per degree normally
* this value is default only, can be changed as argument, @see vid6608::vid6608()
*/
#define VID6608_DEFAULT_MAX_STEPS 320 * 12
/**
* @brief Default speed while homing, in microseconds
* this value is default only, can be changed as argument, @see vid6608::zero()
*/
#define VID6608_DEFAULT_ZERO_SPEED 600
/**
* @brief Driver object class
*
* See example usage in: examples/RandomMove/RandomMove.cpp
*
*/
class vid6608 {
public:
/**
* @brief Object constructor, specify two control pins and overall motor capacity here
*
* @param stepPin pin connected to f(scx)N chip input, controls movement
* @param dirPin pin connected to CW/CCW(N) chip input, controls movement direction
* @param maxSteps full capacity in steps for motor. Note that VID6606 and its clones provides 12 steps per degree. Default capacity is defined in VID6608_DEFAULT_MAX_STEPS (320 degrees * 12 steps)
*/
vid6608(int stepPin, int dirPin, uint16_t maxSteps = VID6608_DEFAULT_MAX_STEPS);
/**
* @brief Resets zero position to actual 0 position
*
* Uses optimized method to reduce bouncing end-stops
* @warning this function is blocking, execution is delayed upon done
*
* @param delay single step delay, controls sthe speed of motor, default is deined in VID6608_DEFAULT_ZERO_SPEED
*/
void zero(uint16_t delay = VID6608_DEFAULT_ZERO_SPEED);
/**
* @brief Shedules movement to defined absolute position
*
* Input is checked for sanity: must be in range 0...maxSteps-1. Values bigger are threated as maxSteps-1.
* @warning this function is asynchronous, actual movement is done in the loop() function.
* @warning next move will be scheduled after current move is done to avoid drive jittering.
*
* @param position absolute position in range 0...maxSteps-1
*/
void moveTo(uint16_t position);
/**
* @brief Test if motor is moving
*
* Return true, if drive still have sheduled steps (that means that next loop() call will result impulse).
*
* @return true if drive is moveemnt
* @return false if drive is stopped
*/
bool isMoving();
/**
* @brief Test if motor is stopped
*
* @return true if drive is stopped
* @return false if drive is moveemnt
*/
bool isStopped() { return !isMoving(); }
/**
* @brief Returns current real absolute position
*
* @return uint16_t current real drive position in steps
*/
uint16_t getPosition();
/**
* @brief Main loop function
*
* This function must be called in main loop to manage actual movement
* and proper signal generation.
* @warning: this function is asynchronous, but may delay up to single step (depends on accelTable, up to 3ms using default one)
*/
void loop();
/**
* @brief Acceleration Table struct
* This struct holds the acceleration curve, to slow down movement close to start/end positions.
*/
typedef struct {
uint16_t distance; /** in steps, from start or to end */
uint16_t delay; /** in microseconds */
} AccelTable;
/**
* @brief Set the Acceleration Table object
*
* This function sets the acceleration curve, to slow down movement close to start/end positions.
* Inspired by: https://github.com/clearwater/SwitecX25/pull/31
* See ``defaultAccelTable`` defenition for example.
*
* @param table acceleration table, must be array of @struct AccelTable
*/
template <typename T, size_t N> void setAccelTable(T (&table)[N]) {
accelTable = table;
accelTableSize = N;
accelMaxDistance = accelTable[accelTableSize - 1].distance;
accelMaxDelay = accelTable[accelTableSize - 1].delay;
};
private:
enum MoveState {
STATE_IDLE = 0,
STATE_MOVING = 1,
};
enum MoveDirection {
MOVE_NONE = -1,
MOVE_FORWARD = 0,
MOVE_BACKWARD = 1,
};
uint16_t maxSteps; // Maximum steps for the motor
uint16_t currentPosition; // Current position in steps
uint16_t targetPosition; // Target position in steps
uint16_t targetPositionNext; // Target position in steps (scheduled for next move)
uint16_t moveLeft; // How long we have to move left
uint16_t moveDone; // How long we have traveled
int stepPin; // Pin connected to step input
int dirPin; // Pin connected to direction input
MoveState moveState; // Current move state
MoveDirection moveDirection; // Current move state
MoveDirection dirPinState; // Direction pin last state, tri-state to force update on first step
AccelTable *accelTable; // Accel table can be modified
uint16_t accelTableSize; // How many rows in the acceleration table
uint16_t accelMaxDistance; // Max distance from the acceleration table
uint16_t accelMaxDelay; // Max delay from the acceleration table
// Actual move
void step(MoveDirection direction, uint16_t delayUs);
// Get current acceleration speed (delay)
uint16_t getDelay(uint16_t distance);
};
#endif // vid6608_h

View File

@ -0,0 +1,28 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
- esp32
- esp8266
# - mega2560
- rpipico

View File

@ -0,0 +1,386 @@
//
// FILE: AGS02MA.cpp
// AUTHOR: Rob Tillaart, Viktor Balint, Beanow
// DATE: 2021-08-12
// VERSION: 0.4.3
// PURPOSE: Arduino library for AGS02MA TVOC sensor
// URL: https://github.com/RobTillaart/AGS02MA
#include "AGS02MA.h"
// REGISTERS
#define AGS02MA_DATA 0x00
#define AGS02MA_CALIBRATION 0x01
#define AGS02MA_VERSION 0x11
#define AGS02MA_SLAVE_ADDRESS 0x21
AGS02MA::AGS02MA(const uint8_t deviceAddress, TwoWire *wire)
{
_address = deviceAddress;
_wire = wire;
reset();
}
bool AGS02MA::begin()
{
_startTime = millis(); // PREHEAT TIMING
return isConnected();
}
bool AGS02MA::isConnected()
{
_setI2CLowSpeed();
_wire->beginTransmission(_address);
bool rv = ( _wire->endTransmission(true) == 0);
_setI2CHighSpeed();
return rv;
}
void AGS02MA::reset()
{
_I2CResetSpeed = 100000;
_startTime = millis();
_lastRead = 0;
_lastPPB = 0;
_mode = 255;
_status = AGS02MA_OK;
_error = AGS02MA_OK;
}
bool AGS02MA::setAddress(const uint8_t deviceAddress)
{
if ((deviceAddress < 10) or (deviceAddress > 119)) return false;
_buffer[2] = _buffer[0] = deviceAddress;
_buffer[3] = _buffer[1] = 0xFF ^ deviceAddress;
_buffer[4] = _CRC8(_buffer, 4);
if (_writeRegister(AGS02MA_SLAVE_ADDRESS))
{
_address = deviceAddress;
}
return isConnected();
}
uint8_t AGS02MA::getSensorVersion()
{
uint8_t version = 0xFF;
if (_readRegister(AGS02MA_VERSION))
{
// for (int i = 0; i < 5; i++)
// {
// Serial.print(_buffer[i]);
// Serial.print('\t');
// }
// Serial.println();
// unclear what the other bytes have for information.
// datasheet names these 3 bytes as KEEP.
// BUFFER VALUE MEANING
// buffer [0] == 20 year ?
// buffer [1] == 07 month ?
// buffer [2] == 28 day ?
// buffer [3] == 117 VERSION
// buffer [4] == CRC
version = _buffer[3];
if (_CRC8(_buffer, 5) != 0)
{
_error = AGS02MA_ERROR_CRC;
}
}
return version;
}
uint32_t AGS02MA::getSensorDate()
{
uint32_t date = 0xFFFFFFFF;
if (_readRegister(AGS02MA_VERSION))
{
date = 0x20;
date <<= 8;
date += _bin2bcd(_buffer[0]);
date <<= 8;
date += _bin2bcd(_buffer[1]);
date <<= 8;
date += _bin2bcd(_buffer[2]);
// version = _buffer[3];
if (_CRC8(_buffer, 5) != 0)
{
_error = AGS02MA_ERROR_CRC;
}
}
return date;
}
bool AGS02MA::setPPBMode()
{
_buffer[0] = 0x00;
_buffer[1] = 0xFF;
_buffer[2] = 0x00;
_buffer[3] = 0xFF;
_buffer[4] = 0x30;
if (_writeRegister(AGS02MA_DATA))
{
_mode = 0;
return true;
}
return false;
}
bool AGS02MA::setUGM3Mode()
{
_buffer[0] = 0x02;
_buffer[1] = 0xFD;
_buffer[2] = 0x02;
_buffer[3] = 0xFD;
_buffer[4] = 0x00;
if (_writeRegister(AGS02MA_DATA))
{
_mode = 1;
return true;
}
return false;
}
uint32_t AGS02MA::readPPB()
{
uint32_t value = _readSensor();
if (_error == AGS02MA_OK)
{
_lastRead = millis();
_lastPPB = value;
}
else
{
value = _lastPPB;
}
return value;
}
uint32_t AGS02MA::readUGM3()
{
uint32_t value = _readSensor();
if (_error == AGS02MA_OK)
{
_lastRead = millis();
_lastUGM3 = value;
}
else
{
value = _lastUGM3;
}
return value;
}
bool AGS02MA::manualZeroCalibration(uint16_t value)
{
_buffer[0] = 0x00;
_buffer[1] = 0x0C;
_buffer[2] = (uint8_t) (value >> 8);
_buffer[3] = (uint8_t) (value & 0x00FF);
_buffer[4] = _CRC8(_buffer, 4);
return _writeRegister(AGS02MA_CALIBRATION);
}
bool AGS02MA::getZeroCalibrationData(AGS02MA::ZeroCalibrationData &data) {
if (!_readRegister(AGS02MA_CALIBRATION))
{
return false;
}
if (_CRC8(_buffer, 5) != 0)
{
_error = AGS02MA_ERROR_CRC;
return false;
}
_error = AGS02MA_OK;
// Don't pollute the struct given to us, until we've handled all error cases.
data.status = _getDataMSB();
data.value = _getDataLSB();
return true;
}
int AGS02MA::lastError()
{
int e = _error;
_error = AGS02MA_OK; // reset error after read
return e;
}
bool AGS02MA::readRegister(uint8_t address, AGS02MA::RegisterData &reg) {
if (!_readRegister(address))
{
return false;
}
if (_CRC8(_buffer, 5) != 0)
{
_error = AGS02MA_ERROR_CRC;
return false;
}
_error = AGS02MA_OK;
// Don't pollute the struct given to us, until we've handled all error cases.
reg.data[0] = _buffer[0];
reg.data[1] = _buffer[1];
reg.data[2] = _buffer[2];
reg.data[3] = _buffer[3];
reg.crc = _buffer[4];
reg.crcValid = true; // checked above.
return true;
}
/////////////////////////////////////////////////////////
//
// PRIVATE
//
uint32_t AGS02MA::_readSensor()
{
uint32_t value = 0;
if (_readRegister(AGS02MA_DATA))
{
_error = AGS02MA_OK;
_status = _buffer[0];
if (_status & 0x01)
{
_error = AGS02MA_ERROR_NOT_READY;
}
value = _buffer[1] * 65536UL;
value += _buffer[2] * 256;
value += _buffer[3];
if (_CRC8(_buffer, 5) != 0)
{
_error = AGS02MA_ERROR_CRC;
}
}
return value;
}
bool AGS02MA::_readRegister(uint8_t reg)
{
while (millis() - _lastRegTime < 30) yield();
_setI2CLowSpeed();
_wire->beginTransmission(_address);
_wire->write(reg);
_error = _wire->endTransmission(true);
if (_error != 0)
{
// _error will be I2C error code
_setI2CHighSpeed();
return false;
}
// TODO investigate async interface
delay(30);
if (_wire->requestFrom(_address, (uint8_t)5) != 5)
{
_error = AGS02MA_ERROR_READ;
_setI2CHighSpeed();
return false;
}
for (uint8_t i = 0; i < 5; i++)
{
_buffer[i] = _wire->read();
}
_error = AGS02MA_OK;
_setI2CHighSpeed();
return true;
}
bool AGS02MA::_writeRegister(uint8_t reg)
{
while (millis() - _lastRegTime < 30) yield();
_lastRegTime = millis();
_setI2CLowSpeed();
_wire->beginTransmission(_address);
_wire->write(reg);
for (uint8_t i = 0; i < 5; i++)
{
_wire->write(_buffer[i]);
}
_error = _wire->endTransmission(true);
_setI2CHighSpeed();
return (_error == 0);
}
void AGS02MA::_setI2CLowSpeed()
{
#if defined (__AVR__)
// TWBR = 255; // == 30.4 KHz with TWSR = 0x00
TWBR = 78; // == 25.0 KHZ
TWSR = 0x01; // pre-scaler = 4
#else
_wire->setClock(AGS02MA_I2C_CLOCK);
#endif
}
void AGS02MA::_setI2CHighSpeed()
{
#if defined (__AVR__)
TWSR = 0x00;
#endif
_wire->setClock(_I2CResetSpeed);
}
uint16_t AGS02MA::_getDataMSB()
{
return (_buffer[0] << 8) + _buffer[1];
}
uint16_t AGS02MA::_getDataLSB()
{
return (_buffer[2] << 8) + _buffer[3];
}
uint8_t AGS02MA::_CRC8(uint8_t * buf, uint8_t size)
{
uint8_t crc = 0xFF; // start value
for (uint8_t b = 0; b < size; b++)
{
crc ^= buf[b];
for (uint8_t i = 0; i < 8; i++)
{
if (crc & 0x80) crc = (crc << 1) ^ 0x31;
else crc = (crc << 1);
}
}
return crc;
}
uint8_t AGS02MA::_bin2bcd (uint8_t value)
{
return value + 6 * (value / 10);
}
// -- END OF FILE --

Some files were not shown because too many files have changed in this diff Show More