Merge branch 'development' into prerelease-15.2.0
This commit is contained in:
commit
0c1105b9b7
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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
5
.gitpod.Dockerfile
vendored
@ -1,5 +0,0 @@
|
|||||||
FROM gitpod/workspace-python-3.13
|
|
||||||
|
|
||||||
RUN python -m pip install --break-system-packages uv
|
|
||||||
|
|
||||||
USER gitpod
|
|
||||||
@ -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
|
|
||||||
67
CHANGELOG.md
67
CHANGELOG.md
@ -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
|
||||||
|
|||||||
@ -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_ |
|
||||||
| |
|
| |
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -7,7 +7,6 @@ _Written for PlatformIO._
|
|||||||
[](https://github.com/arendst/Tasmota/releases/latest)
|
[](https://github.com/arendst/Tasmota/releases/latest)
|
||||||
[](LICENSE.txt)
|
[](LICENSE.txt)
|
||||||
[](https://discord.gg/Ks2Kzd4)
|
[](https://discord.gg/Ks2Kzd4)
|
||||||
[](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
|
||||||
|
|||||||
111
RELEASENOTES.md
111
RELEASENOTES.md
@ -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)
|
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
47
boards/esp32p4r3.json
Normal 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"
|
||||||
|
}
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
26
lib/default/TasmotaDali/keywords.txt
Normal file
26
lib/default/TasmotaDali/keywords.txt
Normal 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)
|
||||||
|
#######################################
|
||||||
|
|
||||||
17
lib/default/TasmotaDali/library.json
Normal file
17
lib/default/TasmotaDali/library.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
9
lib/default/TasmotaDali/library.properties
Normal file
9
lib/default/TasmotaDali/library.properties
Normal 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
|
||||||
974
lib/default/TasmotaDali/src/Dali.h
Normal file
974
lib/default/TasmotaDali/src/Dali.h
Normal 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, DTR1:memory 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, TR1:memory 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 of‘FAILURE 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_
|
||||||
309
lib/default/TasmotaDali/src/TasmotaDali.cpp
Normal file
309
lib/default/TasmotaDali/src/TasmotaDali.cpp
Normal 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);
|
||||||
|
}
|
||||||
69
lib/default/TasmotaDali/src/TasmotaDali.h
Normal file
69
lib/default/TasmotaDali/src/TasmotaDali.h
Normal 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
|
||||||
@ -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)
|
||||||
|
#else // No CONFIG_SOC_HAS_WIFI
|
||||||
|
esp_read_mac(mac, ESP_MAC_BASE); // Local hardware base MAC address
|
||||||
#endif // CONFIG_SOC_HAS_WIFI
|
#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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
242
lib/lib_basic/TasmotaLED/README.md
Normal file
242
lib/lib_basic/TasmotaLED/README.md
Normal 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
|
||||||
1495
lib/lib_basic/TasmotaLED/TASMOTALED_DOCUMENTATION.md
Normal file
1495
lib/lib_basic/TasmotaLED/TASMOTALED_DOCUMENTATION.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()`
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
189
lib/lib_display/UDisplay/include/spi_register.h
Normal file
189
lib/lib_display/UDisplay/include/spi_register.h
Normal 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
|
||||||
276
lib/lib_display/UDisplay/include/uDisplay.h
Normal file
276
lib/lib_display/UDisplay/include/uDisplay.h
Normal 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_
|
||||||
103
lib/lib_display/UDisplay/include/uDisplay_DSI_panel.h
Normal file
103
lib/lib_display/UDisplay/include/uDisplay_DSI_panel.h
Normal 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
|
||||||
134
lib/lib_display/UDisplay/include/uDisplay_EPD_panel.h
Normal file
134
lib/lib_display/UDisplay/include/uDisplay_EPD_panel.h
Normal 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);
|
||||||
|
};
|
||||||
73
lib/lib_display/UDisplay/include/uDisplay_I2C_panel.h
Normal file
73
lib/lib_display/UDisplay/include/uDisplay_I2C_panel.h
Normal 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_
|
||||||
144
lib/lib_display/UDisplay/include/uDisplay_I80_panel.h
Normal file
144
lib/lib_display/UDisplay/include/uDisplay_I80_panel.h
Normal 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
|
||||||
55
lib/lib_display/UDisplay/include/uDisplay_RGB_panel.h
Normal file
55
lib/lib_display/UDisplay/include/uDisplay_RGB_panel.h
Normal 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
|
||||||
98
lib/lib_display/UDisplay/include/uDisplay_SPI_controller.h
Normal file
98
lib/lib_display/UDisplay/include/uDisplay_SPI_controller.h
Normal 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_
|
||||||
116
lib/lib_display/UDisplay/include/uDisplay_SPI_panel.h
Normal file
116
lib/lib_display/UDisplay/include/uDisplay_SPI_panel.h
Normal 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();
|
||||||
|
};
|
||||||
61
lib/lib_display/UDisplay/include/uDisplay_config.h
Normal file
61
lib/lib_display/UDisplay/include/uDisplay_config.h
Normal 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
|
||||||
37
lib/lib_display/UDisplay/include/uDisplay_panel.h
Normal file
37
lib/lib_display/UDisplay/include/uDisplay_panel.h
Normal 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;
|
||||||
|
};
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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=*
|
|
||||||
1419
lib/lib_display/UDisplay/src/uDisplay.cpp
Normal file
1419
lib/lib_display/UDisplay/src/uDisplay.cpp
Normal file
File diff suppressed because it is too large
Load Diff
288
lib/lib_display/UDisplay/src/uDisplay_DSI_panel.cpp
Normal file
288
lib/lib_display/UDisplay/src/uDisplay_DSI_panel.cpp
Normal 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
|
||||||
514
lib/lib_display/UDisplay/src/uDisplay_EPD_panel.cpp
Normal file
514
lib/lib_display/UDisplay/src/uDisplay_EPD_panel.cpp
Normal 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;
|
||||||
|
}
|
||||||
61
lib/lib_display/UDisplay/src/uDisplay_I2C_panel.cpp
Normal file
61
lib/lib_display/UDisplay/src/uDisplay_I2C_panel.cpp
Normal 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();
|
||||||
|
}
|
||||||
733
lib/lib_display/UDisplay/src/uDisplay_I80_panel.cpp
Normal file
733
lib/lib_display/UDisplay/src/uDisplay_I80_panel.cpp
Normal 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
|
||||||
116
lib/lib_display/UDisplay/src/uDisplay_RGB_panel.cpp
Normal file
116
lib/lib_display/UDisplay/src/uDisplay_RGB_panel.cpp
Normal 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
|
||||||
486
lib/lib_display/UDisplay/src/uDisplay_SPI_controller.cpp
Normal file
486
lib/lib_display/UDisplay/src/uDisplay_SPI_controller.cpp
Normal 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);
|
||||||
|
}
|
||||||
367
lib/lib_display/UDisplay/src/uDisplay_SPI_panel.cpp
Normal file
367
lib/lib_display/UDisplay/src/uDisplay_SPI_panel.cpp
Normal 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;
|
||||||
|
}
|
||||||
31
lib/lib_display/UDisplay/src/uDisplay_colors.cpp
Normal file
31
lib/lib_display/UDisplay/src/uDisplay_colors.cpp
Normal 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;
|
||||||
|
}
|
||||||
125
lib/lib_display/UDisplay/src/uDisplay_control.cpp
Normal file
125
lib/lib_display/UDisplay/src/uDisplay_control.cpp
Normal 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"));
|
||||||
|
}
|
||||||
137
lib/lib_display/UDisplay/src/uDisplay_graphics.cpp
Normal file
137
lib/lib_display/UDisplay/src/uDisplay_graphics.cpp
Normal 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();
|
||||||
|
}
|
||||||
26
lib/lib_display/UDisplay/src/uDisplay_timing.cpp
Normal file
26
lib/lib_display/UDisplay/src/uDisplay_timing.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
576
lib/lib_display/UDisplay/src/uDisplay_touch.cpp
Normal file
576
lib/lib_display/UDisplay/src/uDisplay_touch.cpp
Normal 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
|
||||||
111
lib/lib_display/UDisplay/src/uDisplay_utils.cpp
Normal file
111
lib/lib_display/UDisplay/src/uDisplay_utils.cpp
Normal 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
@ -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_
|
|
||||||
43
lib/lib_div/arduino-vid6608/Dockerfile
Normal file
43
lib/lib_div/arduino-vid6608/Dockerfile
Normal 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
|
||||||
674
lib/lib_div/arduino-vid6608/LICENSE.txt
Normal file
674
lib/lib_div/arduino-vid6608/LICENSE.txt
Normal 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>.
|
||||||
93
lib/lib_div/arduino-vid6608/README.md
Executable file
93
lib/lib_div/arduino-vid6608/README.md
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
# Gauge stepper motor Switec X25.168 driver for VID6608 and clones
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
[](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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
lib/lib_div/arduino-vid6608/doc/VID6608.pdf
Normal file
BIN
lib/lib_div/arduino-vid6608/doc/VID6608.pdf
Normal file
Binary file not shown.
BIN
lib/lib_div/arduino-vid6608/doc/operation-configuration.png
Normal file
BIN
lib/lib_div/arduino-vid6608/doc/operation-configuration.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
lib/lib_div/arduino-vid6608/doc/output.gif
Normal file
BIN
lib/lib_div/arduino-vid6608/doc/output.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/lib_div/arduino-vid6608/library.json
Normal file
19
lib/lib_div/arduino-vid6608/library.json
Normal 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": "*"
|
||||||
|
}
|
||||||
9
lib/lib_div/arduino-vid6608/library.properties
Normal file
9
lib/lib_div/arduino-vid6608/library.properties
Normal 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=*
|
||||||
31
lib/lib_div/arduino-vid6608/platformio.ini
Normal file
31
lib/lib_div/arduino-vid6608/platformio.ini
Normal 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
|
||||||
172
lib/lib_div/arduino-vid6608/src/vid6608.cpp
Normal file
172
lib/lib_div/arduino-vid6608/src/vid6608.cpp
Normal 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;
|
||||||
|
}
|
||||||
154
lib/lib_div/arduino-vid6608/src/vid6608.h
Normal file
154
lib/lib_div/arduino-vid6608/src/vid6608.h
Normal 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
|
||||||
28
lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml
Normal file
28
lib/lib_i2c/AGS02MA-0.4.3/.arduino-ci.yml
Normal 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
|
||||||
|
|
||||||
386
lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp
Normal file
386
lib/lib_i2c/AGS02MA-0.4.3/AGS02MA.cpp
Normal 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 ®) {
|
||||||
|
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
Loading…
Reference in New Issue
Block a user