LilyGo T-Embed CC1101 Plus & Led Strip Driver (#670)

This commit is contained in:
Shadowtrance 2026-10-04 02:58:55 +10:00 committed by GitHub
parent 5bac1ada82
commit ffab897fe4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 2817 additions and 19 deletions

1
.gitignore vendored
View File

@ -26,5 +26,6 @@ sdkconfig.board.*.dev
.caveman.json
.ai/mcp
.clinerules/
__pycache__

View File

@ -0,0 +1,6 @@
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
REQUIRES TactilityKernel Tactility
)

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,28 @@
general.vendor=LilyGO
general.name=T-Embed CC1101 Plus
apps.launcherAppId=tactility.launcher
hardware.target=ESP32S3
hardware.flashSize=16MB
hardware.spiRam=true
hardware.spiRamMode=OCT
hardware.spiRamSpeed=80M
hardware.spiRamXipDisabled=true
hardware.tinyUsbMsc=true
hardware.esptoolFlashFreq=80M
hardware.bluetooth=true
storage.userDataLocation=SD
display.size=1.9"
display.shape=rectangle
display.dpi=191
lvgl.colorDepth=16
lvgl.fontSize=16
lvgl.uiDensity=compact
sdkconfig.CONFIG_CODEC_DUMMY_SUPPORT=y

View File

@ -0,0 +1,186 @@
/dts-v1/;
#include <tactility/bindings/battery_sense.h>
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_adc_oneshot.h>
#include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_i2s.h>
#include <tactility/bindings/esp32_pwm_ledc.h>
#include <tactility/bindings/esp32_sdspi.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_usbdevice.h>
#include <tactility/bindings/gpio_hog.h>
#include <tactility/bindings/pwm_backlight.h>
#include <bindings/st7789.h>
#include <bindings/dummy_i2s_amp.h>
#include <bindings/pdm_mic.h>
#include <bindings/bq25896.h>
#include <bindings/bq27220.h>
#include <bindings/gpio_encoder.h>
#include <bindings/esp32_led_strip.h>
// Reference:
// https://wiki.lilygo.cc/products/t-embed-series/t-embed-cc1101/
// https://github.com/Xinyuan-LilyGO/T-Embed-CC1101
// https://lilygo.cc/products/t-embed-cc1101-plus
/ {
compatible = "root";
model = "T-Embed CC1101 Plus";
adc0 {
compatible = "espressif,esp32-adc-oneshot";
unit-id = <ADC_UNIT_1>;
clk-src = <ADC_RTC_CLK_SRC_DEFAULT>;
channels = <ADC_CHANNEL_3 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
};
battery-sense {
compatible = "battery-sense";
io-channel = <&adc0 0>;
reference-voltage-mv = <3300>;
multiplier = <2110>;
};
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
};
ble0 {
compatible = "espressif,esp32-ble";
};
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
// Required to enable peripherals like BQ25896
peripheral-power-hog {
compatible = "gpio-hog";
pin = <&gpio0 15 GPIO_FLAG_NONE>;
mode = <GPIO_HOG_MODE_OUTPUT_HIGH>;
};
// Speaker I2S (MAX98357A amplifier)
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
pin-bclk = <&gpio0 46 GPIO_FLAG_NONE>;
pin-ws = <&gpio0 40 GPIO_FLAG_NONE>;
pin-data-out = <&gpio0 7 GPIO_FLAG_NONE>;
pin-data-in = <&gpio0 42 GPIO_FLAG_NONE>;
pin-mclk = <&gpio0 39 GPIO_FLAG_NONE>;
};
// MAX98357A class-D speaker amplifier. No I2C.
speaker0 {
compatible = "maxim,max98357a";
i2s = <&i2s0>;
};
mic0 {
compatible = "generic,spm1423";
i2s = <&i2s0>;
channels = <1>;
};
// WS2812 RGB LED Ring
// TODO - Sync on/off with DisplayIdle?
ledstrip0 {
compatible = "espressif,esp32-led-strip";
pin-data = <&gpio0 14 GPIO_FLAG_NONE>;
led-count = <8>;
};
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c-master";
port = <I2C_NUM_0>;
clock-frequency = <100000>;
pin-sda = <&gpio0 8 GPIO_FLAG_NONE>;
pin-scl = <&gpio0 18 GPIO_FLAG_NONE>;
bq27220 {
compatible = "ti,bq27220";
reg = <0x55>;
};
bq25896 {
compatible = "ti,bq25896";
reg = <0x6B>;
};
// TODO - Add PN532 NFC - IRQ GPIO 17, RES GPIO 45, address 0x24 - driver needed
};
display_backlight_pwm {
compatible = "espressif,esp32-pwm-ledc";
pin = <&gpio0 21 GPIO_FLAG_NONE>;
period-ns = <33333>;
ledc-timer = <0>;
ledc-channel = <0>;
};
display_backlight {
compatible = "pwm-backlight";
// Off by default so dispaly power-on won't show the screen from before the last power loss.
// The display backlight is turned on during the boot process.
status = "disabled";
pwm = <&display_backlight_pwm>;
};
spi0 {
compatible = "espressif,esp32-spi";
host = <SPI2_HOST>;
cs-gpios = <&gpio0 41 GPIO_FLAG_NONE>, // Display
<&gpio0 13 GPIO_FLAG_NONE>, // SD card
<&gpio0 12 GPIO_FLAG_NONE>, // Radio - CC1101
<&gpio0 44 GPIO_FLAG_NONE>; // Radio - nRF24L01
pin-mosi = <&gpio0 9 GPIO_FLAG_NONE>;
pin-miso = <&gpio0 10 GPIO_FLAG_NONE>;
pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>;
display@0 {
compatible = "sitronix,st7789";
horizontal-resolution = <320>;
vertical-resolution = <170>;
gap-x = <35>;
swap-xy;
mirror-y;
invert-color;
pixel-clock-hz = <80000000>;
pin-dc = <&gpio0 16 GPIO_FLAG_NONE>;
pin-reset = <&gpio0 40 GPIO_FLAG_NONE>;
backlight = <&display_backlight>;
};
sdcard@1 {
compatible = "espressif,esp32-sdspi";
status = "disabled";
frequency-khz = <20000>;
};
// TODO - Add CC1101 Radio - CS GPIO 12, SCK GPIO 11, MOSI GPIO 9, MISO GPIO 10, IO2 GPIO 38, IO0 GPIO 3, SW1 GPIO 47, SW0 GPIO 48 - driver needed
// TODO - Add nRF24L01 Radio - CS GPIO 44, CE GPIO 43, IQR -1/NC, SCK GPIO 11, MOSI GPIO 9, MISO GPIO 10 - driver needed
};
encoder {
compatible = "tactility,gpio-encoder";
pin-a = <&gpio0 5 GPIO_FLAG_NONE>;
pin-b = <&gpio0 4 GPIO_FLAG_NONE>;
pin-enter = <&gpio0 0 GPIO_FLAG_NONE>;
pulses-per-detent = <2>;
pending-capacity = <8>;
};
usbdevice0 {
compatible = "espressif,esp32-usbdevice";
usbdevicemsc0 {
compatible = "espressif,esp32-usbdevice-msc";
};
};
};

View File

@ -0,0 +1,11 @@
dependencies:
- Platforms/platform-esp32
- Drivers/bq27220-module
- Drivers/bq25896-module
- Drivers/gpio-encoder-module
- Drivers/st7789-module
- Drivers/dummy-i2s-amp-module
- Drivers/pdm-mic-module
- Drivers/audio-stream-module
- Drivers/esp32-led-strip-module
dts: lilygo,tembed-cc1101-plus.dts

View File

@ -0,0 +1,49 @@
#include <tactility/error.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <tactility/device.h>
#include <tactility/drivers/led_strip.h>
#include <tactility/system_event.h>
#include <Tactility/settings/LedStripSettings.h>
constexpr auto* TAG = "tembed-cc1101-plus";
extern "C" {
static void on_boot_completed(SystemEvent* event, void* context) {
(void)event;
(void)context;
Device* device = nullptr;
if (device_get_first_by_type(&LED_STRIP_TYPE, &device) != ERROR_NONE) {
LOG_W(TAG, "No LED strip found during boot restore");
return;
}
const auto settings = tt::settings::ledstrip::loadOrGetDefault(device->name);
const error_t result = tt::settings::ledstrip::apply(device, settings);
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to restore LED strip %s: %d", device->name, result);
} else {
LOG_I(TAG, "Restored LED strip %s", device->name);
}
device_put(device);
}
static error_t start() {
return system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
}
static error_t stop() {
return system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
}
Module lilygo_tembed_cc1101_plus_module = {
.name = "lilygo-tembed-cc1101-plus",
.start = start,
.stop = stop,
.drivers = nullptr,
.symbols = nullptr,
.internal = nullptr
};
}

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(esp32-led-strip-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_driver_rmt driver
)

View File

@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,88 @@
# esp32-led-strip-module
WS2812-compatible RGB LED strip driver for Tactility. It uses ESP-IDF's RMT peripheral to transmit pixel data and provides per-pixel and range color control, brightness adjustment, and gamma-corrected output.
## Devicetree Configuration
Declare the strip with the compatible string `"espressif,esp32-led-strip"`. `pin-data` references the GPIO device and pin used for the data signal; `led-count` is the number of LEDs on the strip.
```dts
ws2812 {
compatible = "espressif,esp32-led-strip";
pin-data = <&gpio0 14 GPIO_FLAG_NONE>;
led-count = <8>;
};
```
The driver reads these properties into `LedStripConfig`.
## API
Include `<tactility/drivers/led_strip.h>`. The kernel API operates on a `struct Device *` and returns `error_t`; successful calls return `ERROR_NONE`.
| Function | Description |
|---|---|
| `led_strip_set_single_led_color` | Set one LED using global brightness |
| `led_strip_set_single_led_color_and_brightness` | Set one LED with an explicit brightness |
| `led_strip_set_led_range_color` | Set a range from an array of `LedRgb` values using global brightness |
| `led_strip_set_led_range_color_and_brightness` | Set a range from an array with explicit brightness |
| `led_strip_fill_led_range_color` | Fill a range using global brightness |
| `led_strip_fill_led_range_color_and_brightness` | Fill a range with explicit brightness |
| `led_strip_set_brightness` | Set global brightness |
| `led_strip_show` | Transmit buffered pixel values to the strip |
| `led_strip_clear` | Turn off all LEDs |
| `led_strip_get_length` | Get the configured LED count |
LED indices and range offsets are zero-based. Color channels in `struct LedRgb` range from 0 to 255. Global and per-operation brightness also range from 0 (off) to 255 (full); the default global brightness is 255. Call `led_strip_show()` after setting colors to transmit the updated buffer. `led_strip_clear()` clears and refreshes the strip.
## Usage Example
```c
#include <tactility/drivers/led_strip.h>
void led_demo(struct Device *strip) {
uint16_t length = 0;
if (led_strip_get_length(strip, &length) != ERROR_NONE || length == 0) {
return;
}
led_strip_set_brightness(strip, 128); // About 50% brightness
led_strip_fill_led_range_color(strip, 0, length, LED_RGB_COLOR_GREEN);
led_strip_show(strip);
led_strip_set_single_led_color(strip, 0, LED_RGB_COLOR_RED);
led_strip_show(strip);
led_strip_clear(strip);
}
```
## Using Literal RGB Values
Color macros are optional. Initialize `struct LedRgb` directly when a color is calculated or loaded at runtime. This example assumes the strip has at least four LEDs.
```c
#include <tactility/drivers/led_strip.h>
void show_custom_colors(struct Device *strip) {
const struct LedRgb colors[] = {
{ .r = 255, .g = 32, .b = 0 },
{ .r = 0, .g = 180, .b = 64 },
{ .r = 24, .g = 48, .b = 255 },
{ .r = 200, .g = 80, .b = 160 }
};
led_strip_set_led_range_color_and_brightness(strip, 0, 4, colors, 192);
led_strip_show(strip);
}
```
## Color Presets
The kernel header also provides `LED_RGB_COLOR_*` macros such as `LED_RGB_COLOR_RED`, `LED_RGB_COLOR_GREEN`, and `LED_RGB_COLOR_WHITE`, plus `LED_COLOR_RGB_CUSTOM(r, g, b)` for a custom color. These expand to `struct LedRgb` values.
## Implementation Notes
- The driver configures the ESP-IDF RMT backend for WS2812 RGB output in GRB component order.
- An 8-bit gamma correction lookup table is applied after brightness scaling.
- The devicetree compatible string and driver registration are `"espressif,esp32-led-strip"`; the kernel device type is `LED_STRIP_TYPE`.

View File

@ -0,0 +1,13 @@
description: RGB LED strip. ESP-IDF's RMT peripheral to transmit pixel data to drive WS2812-compatible LEDs.
compatible: "espressif,esp32-led-strip"
properties:
pin-data:
type: phandles
required: true
description: GPIO pin for strip data output
led-count:
type: int
required: true
description: Number of LEDs in the strip

View File

@ -0,0 +1,14 @@
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/esp32_led_strip.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_led_strip, struct LedStripConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <tactility/device.h>
#include <tactility/error.h>
#include <tactility/drivers/gpio.h>
#include <tactility/drivers/led_strip.h>
#include <esp32_led_strip_types.h>
struct Device;
#ifdef __cplusplus
extern "C" {
#endif
struct LedStripConfig {
/** Data Pin */
struct GpioPinSpec pin_data;
/** LED Count */
uint16_t led_count;
};
/**
* @brief Sets the color of a single LED on the strip with global brightness.
* @param[in] device the LED strip device
* @param[in] index index of the LED in the strip (0-based)
* @param[in] color the color to set
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color);
/**
* @brief Sets the color of a single LED on the strip with an explicit brightness value.
* @param[in] device the LED strip device
* @param[in] index index of the LED in the strip (0-based)
* @param[in] color the color to set
* @param[in] brightness 0-255
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness);
/**
* @brief Sets colors for a range of LEDs on the strip using the global brightness.
* @param[in] device the LED strip device
* @param[in] first starting LED index (0-based)
* @param[in] count number of LEDs to set
* @param[in] color array of colors to set
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color);
/**
* @brief Sets colors for a range of LEDs on the strip with an explicit brightness value.
* @param[in] device the LED strip device
* @param[in] first starting LED index (0-based)
* @param[in] count number of LEDs to set
* @param[in] color array of colors to set
* @param[in] brightness 0-255
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness);
/**
* @brief Fills a range of LEDs with a single color using the global brightness.
* @param[in] device the LED strip device
* @param[in] first starting LED index (0-based)
* @param[in] count number of LEDs to fill
* @param[in] color the color to fill with
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color);
/**
* @brief Fills a range of LEDs with a single color and an explicit brightness value.
* @param[in] device the LED strip device
* @param[in] first starting LED index (0-based)
* @param[in] count number of LEDs to fill
* @param[in] color the color to fill with
* @param[in] brightness 0-255
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness);
/**
* @brief Sets global brightness for the LED strip.
* @param[in] device the LED strip device
* @param[in] brightness 0-255
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_set_brightness(struct Device* device, uint8_t brightness);
/**
* @brief Refreshes the strip to update the displayed colors.
* @param[in] device the LED strip device
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_show(struct Device* device);
/**
* @brief Clears the strip to black.
* @param[in] device the LED strip device
* @retval ERROR_NONE when the operation was successful
*/
error_t esp32_led_strip_clear(struct Device* device);
/**
* @brief Gets the length of a strip.
* @param[in] device the LED strip device
* @param[out] out_length receives the strip length
* @retval ERROR_NONE when the operation was successful
* @retval ERROR_INVALID_ARGUMENT if out of range
*/
error_t esp32_led_strip_get_length(struct Device* device, uint16_t* out_length);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module esp32_led_strip_module;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "esp32_led_strip_types.h"
#include "esp_idf_version.h"
#include "driver/rmt_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip RMT specific configuration
*/
typedef struct {
rmt_clock_source_t clk_src; /*!< RMT clock source */
uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */
size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */
/*!< Extra RMT specific driver flags */
struct led_strip_rmt_extra_config {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_rmt_config_t;
/**
* @brief Create LED strip based on RMT TX channel
*
* @param led_config LED strip configuration
* @param rmt_config RMT specific configuration
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip);
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_rmt_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t led_strip_rmt_refresh(led_strip_handle_t strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t led_strip_rmt_clear(led_strip_handle_t strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t led_strip_rmt_del(led_strip_handle_t strip);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#include <esp32_led_strip_types.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of led strip encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
led_model_t led_model; /*!< LED model */
} led_strip_encoder_config_t;
/**
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of LED strip handle
*/
typedef struct led_strip_t led_strip_t;
typedef led_strip_t *led_strip_handle_t;
/**
* @brief LED strip model
* @note Different led model may have different timing parameters, so we need to distinguish them.
*/
typedef enum {
LED_MODEL_WS2812, /*!< LED strip model: WS2812 */
LED_MODEL_SK6812, /*!< LED strip model: SK6812 */
LED_MODEL_WS2811, /*!< LED strip model: WS2811 */
LED_MODEL_WS2816, /*!< LED strip model: WS2816 */
LED_MODEL_INVALID /*!< Invalid LED strip model */
} led_model_t;
/**
* @brief LED color component format
* @note The format is used to specify the order of color components in each pixel, also the number of color components.
*/
typedef union {
struct format_layout {
uint32_t r_pos: 2; /*!< Position of the red channel in the color order: 0~3 */
uint32_t g_pos: 2; /*!< Position of the green channel in the color order: 0~3 */
uint32_t b_pos: 2; /*!< Position of the blue channel in the color order: 0~3 */
uint32_t w_pos: 2; /*!< Position of the white channel in the color order: 0~3 */
uint32_t reserved: 19; /*!< Reserved */
uint32_t bytes_per_color: 2; /*!< Bytes per color component: 1 or 2. If set to 0, it will fallback to 1 */
uint32_t num_components: 3; /*!< Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3 */
} format; /*!< Format layout */
uint32_t format_id; /*!< Format ID */
} led_color_component_format_t;
/// Helper macros to set the color component format
#define LED_STRIP_COLOR_COMPONENT_FMT_GRB (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRB_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW_16 (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGB (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGB_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 3}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 1, .num_components = 4}}
#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW_16 (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .bytes_per_color = 2, .num_components = 4}}
/**
* @brief LED Strip common configurations
* The common configurations are not specific to any backend peripheral.
*/
typedef struct {
int strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t max_leds; /*!< Maximum number of LEDs that can be controlled in a single strip */
led_model_t led_model; /*!< Specifies the LED strip model (e.g., WS2812, SK6812) */
led_color_component_format_t color_component_format; /*!< Specifies the order of color components in each pixel.
Use helper macros like `LED_STRIP_COLOR_COMPONENT_FMT_GRB` to set the format */
/*!< LED strip extra driver flags */
struct led_strip_extra_flags {
uint32_t invert_out: 1; /*!< Invert output signal */
} flags; /*!< Extra driver flags */
} led_strip_config_t;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings

View File

@ -0,0 +1,258 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <esp32_led_strip_module.h>
#include <tactility/drivers/led_strip.h>
#include <tactility/drivers/gpio.h>
#include <driver/gpio.h>
#include <esp_log.h>
#include <drivers/esp32_led_strip.h>
#include <cstdlib>
#include <esp32_led_strip_rmt.h>
constexpr auto* TAG = "Esp32LedStrip";
#define GET_CONFIG(device) (static_cast<const LedStripConfig*>((device)->config))
#define GET_INTERNAL(device) (static_cast<LedStripInternal*>(device_get_driver_data(device)))
struct LedStripInternal {
led_strip_handle_t strip;
GpioPinSpec data_pin;
uint16_t led_count;
uint8_t global_brightness;
};
// Gamma correction lookup table for 8-bit brightness/color scaling
static const uint8_t gamma8[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7,
7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13,
13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20,
21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30,
31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75,
76, 77, 78, 79, 81, 82, 83, 84, 86, 87, 88, 90, 91, 92, 94, 95,
96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 119,
121, 122, 124, 125, 127, 129, 130, 132, 134, 135, 137, 139, 141, 142, 144, 146,
148, 150, 151, 153, 155, 157, 159, 161, 163, 165, 166, 168, 170, 172, 174, 176,
178, 180, 182, 184, 186, 188, 191, 193, 195, 197, 199, 201, 204, 206, 208, 210,
213, 215, 217, 220, 222, 224, 227, 229, 231, 234, 236, 239, 241, 244, 246, 249,
251, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};
static error_t create_strip(const LedStripConfig* source, led_strip_handle_t* out_strip) {
led_strip_config_t strip_config_internal = {
.strip_gpio_num = static_cast<gpio_num_t>(source->pin_data.pin),
.max_leds = source->led_count,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {
.invert_out = false,
}
};
led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000,
.mem_block_symbols = 0,
.flags = {
.with_dma = 0,
}
};
led_strip_handle_t strip = nullptr;
esp_err_t err = led_strip_new_rmt_device(&strip_config_internal, &rmt_config, &strip);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
*out_strip = strip;
return ERROR_NONE;
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<LedStripInternal*>(std::calloc(1, sizeof(LedStripInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
internal->data_pin = static_cast<GpioPinSpec>(config->pin_data);
internal->led_count = config->led_count;
internal->global_brightness = 255;
led_strip_handle_t strip = nullptr;
if (create_strip(config, &strip) != ERROR_NONE) {
std::free(internal);
return ERROR_RESOURCE;
}
internal->strip = strip;
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = GET_INTERNAL(device);
led_strip_rmt_del(internal->strip);
std::free(internal);
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}
static error_t apply_single(Device* device, size_t index, const LedRgb& color, uint8_t brightness) {
auto* internal = GET_INTERNAL(device);
if (index >= internal->led_count) return ERROR_INVALID_ARGUMENT;
uint32_t sr = (color.r * brightness) / 255;
uint32_t sg = (color.g * brightness) / 255;
uint32_t sb = (color.b * brightness) / 255;
esp_err_t err = led_strip_rmt_set_pixel(internal->strip, index, gamma8[sr], gamma8[sg], gamma8[sb]);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
return ERROR_NONE;
}
static error_t apply_range(Device* device, size_t first, size_t count, const LedRgb* colors, uint8_t brightness) {
auto* internal = GET_INTERNAL(device);
if (first > internal->led_count) return ERROR_INVALID_ARGUMENT;
if (count > internal->led_count - first) return ERROR_INVALID_ARGUMENT;
if (colors == nullptr) return ERROR_INVALID_ARGUMENT;
for (size_t i = 0; i < count; i++) {
uint32_t sr = (colors[i].r * brightness) / 255;
uint32_t sg = (colors[i].g * brightness) / 255;
uint32_t sb = (colors[i].b * brightness) / 255;
esp_err_t err = led_strip_rmt_set_pixel(internal->strip, first + i, gamma8[sr], gamma8[sg], gamma8[sb]);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
}
return ERROR_NONE;
}
static error_t apply_fill(Device* device, size_t first, size_t count, const LedRgb& color, uint8_t brightness)
{
auto* internal = GET_INTERNAL(device);
if (first > internal->led_count) return ERROR_INVALID_ARGUMENT;
if (count > internal->led_count - first) return ERROR_INVALID_ARGUMENT;
for (size_t i = 0; i < count; i++) {
uint32_t sr = (color.r * brightness) / 255;
uint32_t sg = (color.g * brightness) / 255;
uint32_t sb = (color.b * brightness) / 255;
esp_err_t err = led_strip_rmt_set_pixel(internal->strip, first + i, gamma8[sr], gamma8[sg], gamma8[sb]);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
}
return ERROR_NONE;
}
extern "C" {
error_t esp32_led_strip_set_single_led_color(Device* device, size_t index, const LedRgb color) {
auto* internal = GET_INTERNAL(device);
return apply_single(device, index, color, internal->global_brightness);
}
error_t esp32_led_strip_set_single_led_color_and_brightness(Device* device, size_t index, const LedRgb color, uint8_t brightness) {
return apply_single(device, index, color, brightness);
}
error_t esp32_led_strip_set_led_range_color(Device* device, size_t first, size_t count, const LedRgb* color) {
auto* internal = GET_INTERNAL(device);
return apply_range(device, first, count, color, internal->global_brightness);
}
error_t esp32_led_strip_set_led_range_color_and_brightness(Device* device, size_t first, size_t count, const LedRgb* color, uint8_t brightness) {
return apply_range(device, first, count, color, brightness);
}
error_t esp32_led_strip_fill_led_range_color(Device* device, size_t first, size_t count, const LedRgb color) {
auto* internal = GET_INTERNAL(device);
return apply_fill(device, first, count, color, internal->global_brightness);
}
error_t esp32_led_strip_fill_led_range_color_and_brightness(Device* device, size_t first, size_t count, const LedRgb color, uint8_t brightness) {
return apply_fill(device, first, count, color, brightness);
}
error_t esp32_led_strip_set_brightness(Device* device, uint8_t brightness) {
auto* internal = GET_INTERNAL(device);
internal->global_brightness = brightness;
return ERROR_NONE;
}
error_t esp32_led_strip_show(Device* device) {
auto* internal = GET_INTERNAL(device);
if (internal->strip != nullptr) {
esp_err_t err = led_strip_rmt_refresh(internal->strip);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
}
return ERROR_NONE;
}
error_t esp32_led_strip_clear(Device* device) {
auto* internal = GET_INTERNAL(device);
if (internal->strip != nullptr) {
esp_err_t err = led_strip_rmt_clear(internal->strip);
if (err != ESP_OK) {
return ERROR_RESOURCE;
}
}
return ERROR_NONE;
}
error_t esp32_led_strip_get_length(Device* device, uint16_t* out_length) {
auto* internal = GET_INTERNAL(device);
*out_length = internal->led_count;
return ERROR_NONE;
}
static constexpr LedStripApi LED_STRIP_API = {
.set_single_led_color = esp32_led_strip_set_single_led_color,
.set_single_led_color_and_brightness = esp32_led_strip_set_single_led_color_and_brightness,
.set_led_range_color = esp32_led_strip_set_led_range_color,
.set_led_range_color_and_brightness = esp32_led_strip_set_led_range_color_and_brightness,
.fill_led_range_color = esp32_led_strip_fill_led_range_color,
.fill_led_range_color_and_brightness = esp32_led_strip_fill_led_range_color_and_brightness,
.set_brightness = esp32_led_strip_set_brightness,
.show = esp32_led_strip_show,
.clear = esp32_led_strip_clear,
.get_length = esp32_led_strip_get_length,
};
extern Module esp32_led_strip_module;
Driver esp32_led_strip_driver = {
.name = "esp32_led_strip",
.compatible = (const char*[]) { "espressif,esp32-led-strip", nullptr },
.start_device = start,
.stop_device = stop,
.probe = nullptr,
.api = &LED_STRIP_API,
.device_type = &LED_STRIP_TYPE,
.owner = &esp32_led_strip_module,
.internal = nullptr,
};
} // extern "C"

View File

@ -0,0 +1,170 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt_tx.h"
#include "drivers/esp32_led_strip.h"
#include "esp32_led_strip_rmt_encoder.h"
#include "esp32_led_strip_rmt.h"
#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution
#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4
// the memory size of each RMT channel, in words (4 bytes)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
#else
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
#endif
static const char *TAG = "led_strip_rmt";
typedef struct led_strip_t {
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t bytes_per_pixel;
led_color_component_format_t component_fmt;
uint8_t pixel_buf[];
} led_strip_rmt_obj;
esp_err_t led_strip_rmt_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = strip;
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
struct format_layout format = rmt_strip->component_fmt.format;
uint32_t start = index * rmt_strip->bytes_per_pixel;
uint8_t *pixel_buf = rmt_strip->pixel_buf;
uint8_t pos_bytes = format.bytes_per_color;
for (uint8_t i = 0; i < format.bytes_per_color; i++) {
uint8_t color_shift = 8 * (format.bytes_per_color - 1 - i);
pixel_buf[start + format.r_pos * pos_bytes + i] = (red >> color_shift) & 0xFF;
pixel_buf[start + format.g_pos * pos_bytes + i] = (green >> color_shift) & 0xFF;
pixel_buf[start + format.b_pos * pos_bytes + i] = (blue >> color_shift) & 0xFF;
if (format.num_components > 3) {
pixel_buf[start + format.w_pos * pos_bytes + i] = 0;
}
}
return ESP_OK;
}
esp_err_t led_strip_rmt_refresh(led_strip_handle_t strip)
{
led_strip_rmt_obj *rmt_strip = strip;
rmt_transmit_config_t tx_conf = {
.loop_count = 0,
};
ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf,
rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed");
ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed");
return ESP_OK;
}
esp_err_t led_strip_rmt_clear(led_strip_handle_t strip)
{
led_strip_rmt_obj *rmt_strip = strip;
// Write zero to turn off all leds
memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return led_strip_rmt_refresh(strip);
}
esp_err_t led_strip_rmt_del(led_strip_handle_t strip)
{
led_strip_rmt_obj *rmt_strip = strip;
ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_color_component_format_t component_fmt = led_config->color_component_format;
// If R/G/B order is not specified, set default GRB order as fallback
if (component_fmt.format_id == 0) {
component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
}
if (led_config->led_model == LED_MODEL_WS2816) {
component_fmt.format.bytes_per_color = 2;
}
if (component_fmt.format.bytes_per_color == 0) {
component_fmt.format.bytes_per_color = 1;
}
// check the validation of the color component format
uint8_t mask = 0;
if (component_fmt.format.num_components == 3) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else if (component_fmt.format.num_components == 4) {
mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos);
// Check for invalid values
ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument");
} else {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components);
}
uint8_t bytes_per_pixel = component_fmt.format.num_components;
if (component_fmt.format.bytes_per_color > 1) {
bytes_per_pixel *= component_fmt.format.bytes_per_color;
}
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
// for backward compatibility, if the user does not set the clk_src, use the default value
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
if (rmt_config->clk_src) {
clk_src = rmt_config->clk_src;
}
size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
// override the default value if the user sets it
if (rmt_config->mem_block_symbols) {
mem_block_symbols = rmt_config->mem_block_symbols;
}
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = clk_src,
.gpio_num = led_config->strip_gpio_num,
.mem_block_symbols = mem_block_symbols,
.resolution_hz = resolution,
.trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE,
.flags.with_dma = rmt_config->flags.with_dma,
.flags.invert_out = led_config->flags.invert_out,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = resolution,
.led_model = led_config->led_model
};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
rmt_strip->component_fmt = component_fmt;
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
*ret_strip = rmt_strip;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}

View File

@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "esp_idf_version.h"
#include "esp_check.h"
#include "esp_attr.h"
#include "esp32_led_strip_rmt_encoder.h"
static const char *TAG = "led_rmt_encoder";
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0)
#if CONFIG_RMT_ISR_IRAM_SAFE
#define RMT_ENCODER_FUNC_ATTR IRAM_ATTR
#else
#define RMT_ENCODER_FUNC_ATTR
#endif // CONFIG_RMT_ISR_IRAM_SAFE
#endif // ESP_IDF_VERSION
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int state;
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
RMT_ENCODER_FUNC_ATTR
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
free(led_encoder);
return ESP_OK;
}
RMT_ENCODER_FUNC_ATTR
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model");
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
rmt_bytes_encoder_config_t bytes_encoder_config;
uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accommodate WS2812B-V5
if (config->led_model == LED_MODEL_SK6812) {
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.6 * config->resolution / 1000000, // T1H=0.6us
.level1 = 0,
.duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us
},
.flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0)
};
} else if (config->led_model == LED_MODEL_WS2812) {
// different led strip might have its own timing requirements, following parameter is for WS2812
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
} else if (config->led_model == LED_MODEL_WS2811) {
// different led strip might have its own timing requirements, following parameter is for WS2811
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.5 * config->resolution / 1000000., // T0H=0.5us
.level1 = 0,
.duration1 = 2.0 * config->resolution / 1000000., // T0L=2.0us
},
.bit1 = {
.level0 = 1,
.duration0 = 1.2 * config->resolution / 1000000., // T1H=1.2us
.level1 = 0,
.duration1 = 1.3 * config->resolution / 1000000., // T1L=1.3us
},
.flags.msb_first = 1
};
reset_ticks = config->resolution / 1000000 * 50 / 2; // divide by 2... signal is sent twice
} else if (config->led_model == LED_MODEL_WS2816) {
// different led strip might have its own timing requirements, following parameter is for WS2816
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.95 * config->resolution / 1000000, // T0L=0.95us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.75 * config->resolution / 1000000, // T1H=0.75us
.level1 = 0,
.duration1 = 0.5 * config->resolution / 1000000, // T1L=0.5us
},
.flags.msb_first = 1
};
} else {
assert(false);
}
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View File

@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver esp32_led_strip_driver;
static Driver* const esp32_led_strip_drivers[] = {
&esp32_led_strip_driver,
nullptr
};
Module esp32_led_strip_module = {
.name = "esp32_led_strip",
.start = nullptr,
.stop = nullptr,
.drivers = esp32_led_strip_drivers,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"

View File

@ -114,6 +114,7 @@ shared_symbol_code_point_names = [
"terminal",
"logo_dev",
"camera",
"lightstrip",
]
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded

View File

@ -48,3 +48,4 @@
#define LVGL_ICON_SHARED_TERMINAL "\xEE\xAE\x8E"
#define LVGL_ICON_SHARED_LOGO_DEV "\xEE\xAB\x96"
#define LVGL_ICON_SHARED_CAMERA "\xEE\x8E\xAF"
#define LVGL_ICON_SHARED_LIGHTSTRIP "\xF3\xBF\xBD\xB5"

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 12 px
* Bpp: 2
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@ -258,7 +258,13 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x0, 0x80, 0x0, 0x9, 0x0, 0x70, 0x91, 0xcd,
0x9, 0xd, 0xc0, 0x90, 0xac, 0x4, 0x6, 0xd0,
0x0, 0x97, 0x0, 0x1c, 0x2c, 0x7, 0x0, 0x7f,
0x80
0x80,
/* U+FFF75 "󿽵" */
0xff, 0xff, 0xec, 0x41, 0x13, 0xd5, 0x55, 0x7a,
0xaf, 0xef, 0xb, 0xd1, 0x3b, 0x81, 0x2e, 0xd5,
0x7f, 0x5e, 0xaa, 0xab, 0xc4, 0x11, 0x3b, 0xff,
0xff
};
@ -313,7 +319,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 883, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
{.bitmap_index = 899, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
{.bitmap_index = 923, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
{.bitmap_index = 946, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1}
{.bitmap_index = 946, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
{.bitmap_index = 971, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1}
};
/*---------------------
@ -335,6 +342,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
{
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 1048437, .range_length = 1, .glyph_id_start = 47,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@ -359,7 +370,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.cmap_num = 2,
.bpp = 2,
.kern_classes = 0,
.bitmap_format = 0,

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 16 px
* Bpp: 2
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@ -361,7 +361,16 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xca, 0x0, 0x80, 0xc, 0xa0, 0x0, 0x0, 0xc7,
0x0, 0x0, 0x2c, 0x38, 0x0, 0x3, 0x40, 0xe0,
0x1, 0xe0, 0x3, 0xea, 0xf8, 0x0, 0x6, 0xf8,
0x0
0x0,
/* U+FFF75 "󿽵" */
0x55, 0x55, 0x55, 0xf, 0xff, 0xff, 0xfc, 0xd1,
0x4, 0x4, 0xde, 0x55, 0x55, 0x5d, 0xff, 0xff,
0xff, 0xd0, 0x7, 0xf4, 0xd, 0x1b, 0xe0, 0x21,
0xd7, 0x90, 0x82, 0xf8, 0xe1, 0x7, 0xf8, 0xf,
0xff, 0xff, 0xfd, 0xe5, 0x55, 0x55, 0xdd, 0x20,
0x80, 0x8d, 0xb5, 0x55, 0x55, 0xd2, 0xff, 0xff,
0xfd
};
@ -416,7 +425,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 1630, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
{.bitmap_index = 1666, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
{.bitmap_index = 1714, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
{.bitmap_index = 1756, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1}
{.bitmap_index = 1756, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
{.bitmap_index = 1805, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1}
};
/*---------------------
@ -438,6 +448,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
{
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 1048437, .range_length = 1, .glyph_id_start = 47,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@ -462,7 +476,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.cmap_num = 2,
.bpp = 2,
.kern_classes = 0,
.bitmap_format = 0,

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 20 px
* Bpp: 2
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@ -480,6 +480,19 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x0, 0x7, 0x80, 0xf4, 0x0, 0x0, 0xf0, 0x3,
0xe0, 0x0, 0x7c, 0x0, 0xb, 0xea, 0xbf, 0x0,
0x0, 0x1b, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0,
0x0,
/* U+FFF75 "󿽵" */
0x2a, 0xaa, 0xaa, 0xa9, 0x3, 0xff, 0xff, 0xff,
0xfc, 0x38, 0x0, 0x0, 0x1, 0xd3, 0x89, 0x7,
0x3, 0xe, 0x38, 0x0, 0x0, 0x0, 0xe3, 0xff,
0xff, 0xff, 0xfe, 0x2a, 0xab, 0xff, 0xab, 0xe0,
0x1, 0xff, 0x44, 0xe, 0x6, 0xfe, 0x0, 0x82,
0xd2, 0xf9, 0x2, 0x6, 0xf8, 0x38, 0x30, 0x1b,
0xf8, 0x3, 0xea, 0xaf, 0xfe, 0xa9, 0x3f, 0xff,
0xff, 0xff, 0xe3, 0x80, 0x0, 0x1, 0xe, 0x38,
0x90, 0x20, 0x20, 0xe3, 0xea, 0xaa, 0xaa, 0xae,
0x1f, 0xff, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x0,
0x0
};
@ -535,7 +548,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 2555, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
{.bitmap_index = 2604, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
{.bitmap_index = 2674, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
{.bitmap_index = 2746, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1}
{.bitmap_index = 2746, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
{.bitmap_index = 2827, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1}
};
/*---------------------
@ -557,6 +571,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
{
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 1048437, .range_length = 1, .glyph_id_start = 47,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@ -581,7 +599,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.cmap_num = 2,
.bpp = 2,
.kern_classes = 0,
.bitmap_format = 0,

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 24 px
* Bpp: 2
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@ -582,7 +582,22 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x0, 0x0, 0x7c, 0x1f, 0x0, 0x0, 0x0, 0xf4,
0xb, 0xd0, 0x0, 0x7, 0xe0, 0x2, 0xfd, 0x0,
0x7f, 0x80, 0x0, 0x7f, 0xff, 0xfd, 0x0, 0x0,
0x6, 0xff, 0x90, 0x0
0x6, 0xff, 0x90, 0x0,
/* U+FFF75 "󿽵" */
0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff,
0xff, 0xfe, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0xd0, 0x28, 0x7, 0xf, 0xf0, 0x0, 0x0, 0x0,
0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x0, 0x2, 0xff, 0x80, 0xf,
0x0, 0x7f, 0xf4, 0x28, 0xf, 0xb, 0xfe, 0x0,
0x0, 0x2f, 0x7f, 0x90, 0x28, 0x7, 0xfd, 0xf8,
0x34, 0x1, 0xbf, 0xe0, 0xf0, 0x10, 0x6f, 0xf9,
0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0, 0xf,
0xf0, 0xd0, 0x28, 0x7, 0xf, 0xf0, 0x0, 0x0,
0x0, 0xf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0x2f,
0xff, 0xff, 0xff, 0xff
};
@ -637,7 +652,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 3288, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
{.bitmap_index = 3352, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4},
{.bitmap_index = 3440, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2},
{.bitmap_index = 3530, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2}
{.bitmap_index = 3530, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
{.bitmap_index = 3630, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2}
};
/*---------------------
@ -659,6 +675,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
{
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 1048437, .range_length = 1, .glyph_id_start = 47,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@ -683,7 +703,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.cmap_num = 2,
.bpp = 2,
.kern_classes = 0,
.bitmap_format = 0,

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* Size: 32 px
* Bpp: 2
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
@ -980,6 +980,33 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x0, 0xb, 0xff, 0xaa, 0xbf, 0xf4, 0x0, 0x0,
0x1, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x0,
0x1b, 0xff, 0xe4, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0,
/* U+FFF75 "󿽵" */
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x40, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x3e, 0x0, 0x0,
0x0, 0x0, 0x0, 0xbd, 0x3e, 0x7, 0x0, 0x1c,
0x0, 0x70, 0x3e, 0x3e, 0xb, 0x0, 0x2c, 0x0,
0x70, 0x3e, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3e, 0x3f, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbe,
0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0,
0x6, 0xff, 0xf8, 0x0, 0x7e, 0x0, 0x0, 0xbf,
0xfe, 0x42, 0x40, 0x3e, 0x0, 0x1f, 0xff, 0xd0,
0x3, 0x80, 0x3e, 0x2, 0xff, 0xf8, 0x1c, 0x0,
0x1, 0xfd, 0xf, 0xfe, 0x40, 0x2c, 0x0, 0x6f,
0xf8, 0x3f, 0xe0, 0xd0, 0x0, 0xb, 0xff, 0xe0,
0x3e, 0x1, 0xd0, 0x2, 0xff, 0xfd, 0x0, 0x3f,
0x0, 0x0, 0x7f, 0xff, 0x80, 0x0, 0x3f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0x3f, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xbe, 0x3e, 0x1, 0x0, 0x4, 0x0,
0x10, 0x3e, 0x3e, 0xb, 0x0, 0x2c, 0x0, 0xb4,
0x3e, 0x3e, 0x1, 0x0, 0x4, 0x0, 0x10, 0x3e,
0x3f, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbe, 0x1f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x7, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0
};
@ -1035,7 +1062,8 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 6130, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
{.bitmap_index = 6251, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5},
{.bitmap_index = 6416, .adv_w = 512, .box_w = 25, .box_h = 28, .ofs_x = 4, .ofs_y = 2},
{.bitmap_index = 6591, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2}
{.bitmap_index = 6591, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
{.bitmap_index = 6787, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2}
};
/*---------------------
@ -1057,6 +1085,10 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
{
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 46, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 1048437, .range_length = 1, .glyph_id_start = 47,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
@ -1081,7 +1113,7 @@ static lv_font_fmt_txt_dsc_t font_dsc = {
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.cmap_num = 2,
.bpp = 2,
.kern_classes = 0,
.bitmap_format = 0,

View File

@ -59,6 +59,7 @@ typedef struct {
lv_obj_t* close_button_image;
lv_obj_t* action_container;
uint8_t action_count;
lv_event_cb_t nav_action_callback;
} Toolbar;
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
@ -105,6 +106,7 @@ lv_obj_t* lvgl_toolbar_create(lv_obj_t* parent, const char* title) {
lv_obj_set_height(obj, toolbar_height);
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
toolbar->nav_action_callback = nullptr;
lv_obj_set_width(obj, LV_PCT(100));
lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(obj, 0, LV_STATE_DEFAULT);
@ -176,7 +178,13 @@ void lvgl_toolbar_set_title(lv_obj_t* obj, const char* title) {
void lvgl_toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data) {
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data);
if (toolbar->nav_action_callback != nullptr) {
lv_obj_remove_event_cb(toolbar->close_button, toolbar->nav_action_callback);
}
toolbar->nav_action_callback = callback;
if (callback != nullptr) {
lv_obj_add_event_cb(toolbar->close_button, callback, LV_EVENT_SHORT_CLICKED, user_data);
}
lv_image_set_src(toolbar->close_button_image, icon); // e.g. LV_SYMBOL_CLOSE
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <cstdint>
#include <tactility/drivers/led_strip.h>
namespace tt::settings::ledstrip {
enum class Pattern : uint8_t {
Solid,
Alternating,
Gradient,
};
struct LedStripSettings {
bool enabled = true;
Pattern pattern = Pattern::Alternating;
uint8_t brightness = 77;
uint8_t primaryPreset = 6;
uint8_t secondaryPreset = 7;
struct LedRgb primaryCustom { 255, 165, 0 }; //Orange
struct LedRgb secondaryCustom { 0, 0, 255 }; //Blue
};
bool load(const char* deviceName, LedStripSettings& settings);
LedStripSettings loadOrGetDefault(const char* deviceName);
LedStripSettings getDefault();
bool save(const char* deviceName, const LedStripSettings& settings);
error_t apply(struct Device* device, const LedStripSettings& settings);
}

View File

@ -91,6 +91,7 @@
extern "C" Module tactility_audio_module;
#include <tactility/drivers/grove.h>
#include <tactility/drivers/imu.h>
#include <tactility/drivers/led_strip.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/drivers/rtc.h>
#include <tactility/drivers/trackball.h>
@ -176,6 +177,7 @@ namespace app {
namespace imageviewer { extern const ::AppManifest manifest; }
namespace inputdialog { extern const ::AppManifest manifest; }
namespace launcher { extern const ::AppManifest manifest; }
namespace ledstripsettings { extern const ::AppManifest manifest; }
namespace localesettings { extern const ::AppManifest manifest; }
namespace notes { extern const ::AppManifest manifest; }
namespace power { extern const ::AppManifest manifest; }
@ -237,6 +239,9 @@ static void registerInternalApps() {
if (device_exists_of_type(&DISPLAY_TYPE)) {
app_manager_add(&app::display::manifest);
}
if (device_exists_of_type(&LED_STRIP_TYPE)) {
app_manager_add(&app::ledstripsettings::manifest);
}
app_manager_add(&app::files::manifest);
app_manager_add(&app::fileselection::manifest);
app_manager_add(&app::i2cscanner::manifest);

View File

@ -0,0 +1,474 @@
#include <lvgl/lvgl.h>
#include <tactility/device.h>
#include <tactility/drivers/led_strip.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <Tactility/Tactility.h>
#include <Tactility/settings/LedStripSettings.h>
#include <app/event.h>
#include <app/manifest.h>
#include <app/scheduler.h>
#include <lvgl_window_manager/window_manager.h>
#include <lvgl/widgets/sliderbox.h>
#include <lvgl/widgets/toolbar.h>
#include <cstdint>
#include <string>
namespace tt::app::ledstripsettings {
extern const ::AppManifest manifest;
constexpr auto* TAG = "LedStripSettings";
constexpr uint8_t CUSTOM_PRESET = 12;
constexpr const char* COLOR_NAMES[] = {
"White", "Green", "Red", "Blue",
"Yellow", "Orange", "Cyan", "Magenta",
"Purple", "Pink", "Teal", "Black"
};
namespace {
struct Context {
uint32_t appInstanceId = 0;
Device* device = nullptr;
settings::ledstrip::LedStripSettings settings;
bool updated = false;
enum class Page : uint8_t { Main, Choices, CustomColor } page = Page::Main;
enum class ChoiceKind : uint8_t { None, Pattern, PrimaryColor, SecondaryColor } choiceKind = ChoiceKind::None;
bool editingSecondary = false;
lv_obj_t* toolbar = nullptr;
lv_obj_t* mainPanel = nullptr;
lv_obj_t* choicePanel = nullptr;
lv_obj_t* choiceList = nullptr;
lv_obj_t* customPanel = nullptr;
lv_obj_t* returnFocus = nullptr;
lv_obj_t* modeValue = nullptr;
lv_obj_t* primaryValue = nullptr;
lv_obj_t* secondaryRow = nullptr;
lv_obj_t* secondaryValue = nullptr;
lv_obj_t* primaryEditor = nullptr;
lv_obj_t* secondaryEditor = nullptr;
lv_obj_t* primarySliderBoxes[3] = {};
lv_obj_t* secondarySliderBoxes[3] = {};
lv_obj_t* brightnessSlider = nullptr;
lv_obj_t* brightnessValue = nullptr;
};
const char* patternName(settings::ledstrip::Pattern pattern) {
switch (pattern) {
case settings::ledstrip::Pattern::Solid: return "Solid";
case settings::ledstrip::Pattern::Alternating: return "Alternating";
case settings::ledstrip::Pattern::Gradient: return "Gradient";
}
return "Solid";
}
const char* presetName(uint8_t preset) {
if (preset == CUSTOM_PRESET) return "Custom";
return COLOR_NAMES[preset < CUSTOM_PRESET ? preset : 0];
}
void applyAndLog(Context& ctx) {
const error_t result = settings::ledstrip::apply(ctx.device, ctx.settings);
if (result != ERROR_NONE) {
LOG_W(TAG, "Failed to apply LED strip settings: %d", result);
}
}
void updateVisibility(Context& ctx) {
const bool hasSecondary = ctx.settings.pattern != settings::ledstrip::Pattern::Solid;
if (hasSecondary) {
lv_obj_remove_flag(ctx.secondaryRow, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(ctx.secondaryRow, LV_OBJ_FLAG_HIDDEN);
}
}
void onNavigationPressed(lv_event_t* event);
void returnToMain(Context& ctx) {
ctx.page = Context::Page::Main;
ctx.choiceKind = Context::ChoiceKind::None;
lv_obj_add_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ctx.customPanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_update_layout(ctx.mainPanel);
lvgl_toolbar_set_title(ctx.toolbar, "LED Strip");
lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_CLOSE, onNavigationPressed, &ctx);
if (ctx.returnFocus != nullptr) {
lv_group_focus_obj(ctx.returnFocus);
}
}
void onNavigationPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
if (ctx->page != Context::Page::Main) {
returnToMain(*ctx);
} else {
app_event_emit_close(ctx->appInstanceId);
}
}
void openCustomEditor(Context& ctx, bool secondary) {
ctx.page = Context::Page::CustomColor;
ctx.choiceKind = Context::ChoiceKind::None;
ctx.editingSecondary = secondary;
lv_obj_add_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(ctx.customPanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(secondary ? ctx.primaryEditor : ctx.secondaryEditor, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(secondary ? ctx.secondaryEditor : ctx.primaryEditor, LV_OBJ_FLAG_HIDDEN);
lvgl_toolbar_set_title(ctx.toolbar, secondary ? "Custom second color" : "Custom color");
lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_LEFT, onNavigationPressed, &ctx);
lv_obj_update_layout(ctx.customPanel);
auto* sliderBox = secondary ? ctx.secondarySliderBoxes[0] : ctx.primarySliderBoxes[0];
lv_group_focus_obj(sliderBox);
}
size_t choiceCount(Context::ChoiceKind kind) {
return kind == Context::ChoiceKind::Pattern ? 3 : CUSTOM_PRESET + 1;
}
uint32_t currentChoice(const Context& ctx) {
switch (ctx.choiceKind) {
case Context::ChoiceKind::Pattern: return static_cast<uint32_t>(ctx.settings.pattern);
case Context::ChoiceKind::PrimaryColor: return ctx.settings.primaryPreset;
case Context::ChoiceKind::SecondaryColor: return ctx.settings.secondaryPreset;
case Context::ChoiceKind::None: return 0;
}
return 0;
}
const char* choiceTitle(Context::ChoiceKind kind) {
switch (kind) {
case Context::ChoiceKind::Pattern: return "Choose mode";
case Context::ChoiceKind::PrimaryColor: return "Choose color";
case Context::ChoiceKind::SecondaryColor: return "Choose second color";
case Context::ChoiceKind::None: return "LED Strip";
}
return "LED Strip";
}
const char* choiceName(Context::ChoiceKind kind, size_t index) {
if (kind == Context::ChoiceKind::Pattern) {
return patternName(static_cast<settings::ledstrip::Pattern>(index));
}
return index == CUSTOM_PRESET ? "Custom" : COLOR_NAMES[index];
}
void onChoiceSelected(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
const size_t selected = lv_obj_get_index(static_cast<lv_obj_t*>(lv_event_get_target(event)));
if (selected >= choiceCount(ctx->choiceKind)) return;
const auto choiceKind = ctx->choiceKind;
switch (ctx->choiceKind) {
case Context::ChoiceKind::Pattern:
ctx->settings.pattern = static_cast<settings::ledstrip::Pattern>(selected);
break;
case Context::ChoiceKind::PrimaryColor:
ctx->settings.primaryPreset = static_cast<uint8_t>(selected);
break;
case Context::ChoiceKind::SecondaryColor:
ctx->settings.secondaryPreset = static_cast<uint8_t>(selected);
break;
case Context::ChoiceKind::None:
return;
}
ctx->updated = true;
lv_label_set_text(ctx->modeValue, patternName(ctx->settings.pattern));
lv_label_set_text(ctx->primaryValue, presetName(ctx->settings.primaryPreset));
lv_label_set_text(ctx->secondaryValue, presetName(ctx->settings.secondaryPreset));
updateVisibility(*ctx);
if (selected == CUSTOM_PRESET && choiceKind != Context::ChoiceKind::Pattern) {
openCustomEditor(*ctx, choiceKind == Context::ChoiceKind::SecondaryColor);
applyAndLog(*ctx);
return;
}
returnToMain(*ctx);
applyAndLog(*ctx);
}
void openChoice(Context& ctx, Context::ChoiceKind kind, lv_obj_t* returnFocus) {
ctx.page = Context::Page::Choices;
ctx.choiceKind = kind;
ctx.returnFocus = returnFocus;
lv_obj_clean(ctx.choiceList);
const size_t selected = currentChoice(ctx);
lv_obj_t* selectedButton = nullptr;
for (size_t index = 0; index < choiceCount(kind); index++) {
auto* button = lv_list_add_button(ctx.choiceList, index == selected ? LV_SYMBOL_OK : nullptr, choiceName(kind, index));
lv_obj_add_event_cb(button, onChoiceSelected, LV_EVENT_SHORT_CLICKED, &ctx);
if (index == selected) selectedButton = button;
}
lvgl_toolbar_set_title(ctx.toolbar, choiceTitle(kind));
lvgl_toolbar_set_nav_action(ctx.toolbar, LV_SYMBOL_LEFT, onNavigationPressed, &ctx);
lv_obj_add_flag(ctx.mainPanel, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(ctx.choicePanel, LV_OBJ_FLAG_HIDDEN);
if (selectedButton != nullptr) {
lv_group_focus_obj(selectedButton);
}
}
void onOutputChanged(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
ctx->settings.enabled = lv_obj_has_state(sw, LV_STATE_CHECKED);
ctx->updated = true;
applyAndLog(*ctx);
}
void onPatternPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
openChoice(*ctx, Context::ChoiceKind::Pattern, static_cast<lv_obj_t*>(lv_event_get_target(event)));
}
void onPrimaryColorPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
openChoice(*ctx, Context::ChoiceKind::PrimaryColor, static_cast<lv_obj_t*>(lv_event_get_target(event)));
}
void onSecondaryColorPressed(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
openChoice(*ctx, Context::ChoiceKind::SecondaryColor, static_cast<lv_obj_t*>(lv_event_get_target(event)));
}
void setChannel(LedRgb& color, size_t channel, uint8_t value) {
switch (channel) {
case 0: color.r = value; break;
case 1: color.g = value; break;
case 2: color.b = value; break;
default: break;
}
}
void onColorSliderChanged(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
auto* target = static_cast<lv_obj_t*>(lv_event_get_target(event));
const uint8_t value = static_cast<uint8_t>(lvgl_sliderbox_get_value(target));
for (size_t channel = 0; channel < 3; channel++) {
if (target == ctx->primarySliderBoxes[channel]) {
setChannel(ctx->settings.primaryCustom, channel, value);
ctx->updated = true;
applyAndLog(*ctx);
return;
}
if (target == ctx->secondarySliderBoxes[channel]) {
setChannel(ctx->settings.secondaryCustom, channel, value);
ctx->updated = true;
applyAndLog(*ctx);
return;
}
}
}
void onBrightnessChanged(lv_event_t* event) {
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
const auto value = static_cast<uint8_t>(lvgl_sliderbox_get_value(static_cast<lv_obj_t*>(lv_event_get_target(event))));
ctx->settings.brightness = value;
lv_label_set_text_fmt(ctx->brightnessValue, "%u%%", (static_cast<unsigned>(value) * 100 + 127) / 255);
ctx->updated = true;
applyAndLog(*ctx);
}
lv_obj_t* createRow(lv_obj_t* parent, const char* title) {
auto* row = lv_obj_create(parent);
lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(row, 2, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT);
auto* label = lv_label_create(row);
lv_label_set_text(label, title);
lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
return row;
}
void createRgbEditor(lv_obj_t* parent, const char* title, const LedRgb& color, lv_obj_t** sliderBoxes, Context* ctx, bool primary) {
auto* editor = lv_obj_create(parent);
lv_obj_set_width(editor, LV_PCT(100));
lv_obj_set_flex_flow(editor, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(editor, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(editor, 0, LV_STATE_DEFAULT);
auto* heading = lv_label_create(editor);
lv_label_set_text(heading, title);
constexpr const char* CHANNELS[] = { "R", "G", "B" };
const uint8_t channels[] = { color.r, color.g, color.b };
for (size_t i = 0; i < 3; i++) {
auto* row = lv_obj_create(editor);
lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(row, 1, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT);
auto* label = lv_label_create(row);
lv_label_set_text(label, CHANNELS[i]);
lv_obj_set_width(label, LV_PCT(12));
lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
sliderBoxes[i] = lvgl_sliderbox_create(row, 0, UINT8_MAX, 8, channels[i]);
lv_obj_set_width(sliderBoxes[i], LV_PCT(84));
lv_obj_align(sliderBoxes[i], LV_ALIGN_RIGHT_MID, 0, 0);
lvgl_sliderbox_add_value_changed_cb(sliderBoxes[i], onColorSliderChanged, ctx);
}
if (primary) {
ctx->primaryEditor = editor;
} else {
ctx->secondaryEditor = editor;
}
}
void createWidgets(lv_obj_t* parent, void* userData) {
auto* ctx = static_cast<Context*>(userData);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
ctx->toolbar = lvgl_toolbar_create(parent, "LED Strip");
lvgl_toolbar_set_nav_action(ctx->toolbar, LV_SYMBOL_CLOSE, onNavigationPressed, ctx);
ctx->mainPanel = lv_obj_create(parent);
lv_obj_set_width(ctx->mainPanel, LV_PCT(100));
lv_obj_set_flex_grow(ctx->mainPanel, 1);
lv_obj_set_flex_flow(ctx->mainPanel, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(ctx->mainPanel, 2, LV_STATE_DEFAULT);
lv_obj_add_flag(ctx->mainPanel, LV_OBJ_FLAG_SCROLLABLE);
ctx->customPanel = lv_obj_create(parent);
lv_obj_set_width(ctx->customPanel, LV_PCT(100));
lv_obj_set_flex_grow(ctx->customPanel, 1);
lv_obj_set_flex_flow(ctx->customPanel, LV_FLEX_FLOW_COLUMN);
lv_obj_add_flag(ctx->customPanel, LV_OBJ_FLAG_HIDDEN);
auto* outputRow = createRow(ctx->mainPanel, "Output");
auto* outputSwitch = lv_switch_create(outputRow);
if (ctx->settings.enabled) lv_obj_add_state(outputSwitch, LV_STATE_CHECKED);
lv_obj_align(outputSwitch, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(outputSwitch, onOutputChanged, LV_EVENT_VALUE_CHANGED, ctx);
auto* modeRow = createRow(ctx->mainPanel, "Mode");
auto* modeButton = lv_button_create(modeRow);
lv_obj_set_width(modeButton, LV_PCT(62));
lv_obj_align(modeButton, LV_ALIGN_RIGHT_MID, 0, 0);
ctx->modeValue = lv_label_create(modeButton);
lv_label_set_text(ctx->modeValue, patternName(ctx->settings.pattern));
lv_obj_center(ctx->modeValue);
lv_obj_add_event_cb(modeButton, onPatternPressed, LV_EVENT_SHORT_CLICKED, ctx);
auto* primaryRow = createRow(ctx->mainPanel, "Color");
auto* primaryButton = lv_button_create(primaryRow);
lv_obj_set_width(primaryButton, LV_PCT(62));
lv_obj_align(primaryButton, LV_ALIGN_RIGHT_MID, 0, 0);
ctx->primaryValue = lv_label_create(primaryButton);
lv_label_set_text(ctx->primaryValue, presetName(ctx->settings.primaryPreset));
lv_obj_center(ctx->primaryValue);
lv_obj_add_event_cb(primaryButton, onPrimaryColorPressed, LV_EVENT_SHORT_CLICKED, ctx);
createRgbEditor(ctx->customPanel, "Red / Green / Blue", ctx->settings.primaryCustom, ctx->primarySliderBoxes, ctx, true);
lv_obj_add_flag(ctx->primaryEditor, LV_OBJ_FLAG_HIDDEN);
ctx->secondaryRow = createRow(ctx->mainPanel, "Second color");
auto* secondaryButton = lv_button_create(ctx->secondaryRow);
lv_obj_set_width(secondaryButton, LV_PCT(62));
lv_obj_align(secondaryButton, LV_ALIGN_RIGHT_MID, 0, 0);
ctx->secondaryValue = lv_label_create(secondaryButton);
lv_label_set_text(ctx->secondaryValue, presetName(ctx->settings.secondaryPreset));
lv_obj_center(ctx->secondaryValue);
lv_obj_add_event_cb(secondaryButton, onSecondaryColorPressed, LV_EVENT_SHORT_CLICKED, ctx);
createRgbEditor(ctx->customPanel, "Red / Green / Blue", ctx->settings.secondaryCustom, ctx->secondarySliderBoxes, ctx, false);
lv_obj_add_flag(ctx->secondaryEditor, LV_OBJ_FLAG_HIDDEN);
auto* brightnessRow = createRow(ctx->mainPanel, "Brightness");
ctx->brightnessValue = lv_label_create(brightnessRow);
lv_label_set_text_fmt(ctx->brightnessValue, "%u%%", (static_cast<unsigned>(ctx->settings.brightness) * 100 + 127) / 255);
lv_obj_align(ctx->brightnessValue, LV_ALIGN_RIGHT_MID, 0, 0);
ctx->brightnessSlider = lvgl_sliderbox_create(ctx->mainPanel, 0, UINT8_MAX, 17, ctx->settings.brightness);
lv_obj_set_width(ctx->brightnessSlider, LV_PCT(100));
lvgl_sliderbox_add_value_changed_cb(ctx->brightnessSlider, onBrightnessChanged, ctx);
ctx->choicePanel = lv_obj_create(parent);
lv_obj_set_width(ctx->choicePanel, LV_PCT(100));
lv_obj_set_flex_grow(ctx->choicePanel, 1);
lv_obj_set_flex_flow(ctx->choicePanel, LV_FLEX_FLOW_COLUMN);
lv_obj_add_flag(ctx->choicePanel, LV_OBJ_FLAG_HIDDEN);
ctx->choiceList = lv_list_create(ctx->choicePanel);
lv_obj_set_width(ctx->choiceList, LV_PCT(100));
lv_obj_set_flex_grow(ctx->choiceList, 1);
updateVisibility(*ctx);
}
void persistIfUpdated(const Context& ctx) {
if (!ctx.updated) return;
const auto settingsToSave = ctx.settings;
const std::string deviceName = ctx.device->name;
getMainDispatcher().dispatch([settingsToSave, deviceName] {
if (!settings::ledstrip::save(deviceName.c_str(), settingsToSave)) {
LOG_W(TAG, "Failed to save LED strip settings");
}
});
}
int32_t appMain(int argc, char* argv[]) {
Device* device = nullptr;
if (device_get_first_by_type(&LED_STRIP_TYPE, &device) != ERROR_NONE) {
return 0;
}
Context ctx {};
ctx.appInstanceId = app_scheduler_current_app_id();
ctx.device = device;
ctx.settings = settings::ledstrip::loadOrGetDefault(device->name);
uint16_t ledCount = 0;
if (led_strip_get_length(device, &ledCount) != ERROR_NONE || ledCount == 0) {
device_put(device);
return 0;
}
applyAndLog(ctx);
TaskEventGroup eventGroup {};
task_event_group_construct(&eventGroup);
AppEventSubscription subscription {};
if (app_event_subscribe(&subscription, &eventGroup) != ERROR_NONE) {
task_event_group_destruct(&eventGroup);
device_put(device);
return 0;
}
const WindowId window = window_manager_create(ctx.appInstanceId, createWidgets, &ctx);
bool shouldClose = false;
while (!shouldClose) {
task_event_group_wait_any(&eventGroup, nullptr, portMAX_DELAY);
AppEvent event {};
while (app_event_poll(&subscription, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
persistIfUpdated(ctx);
shouldClose = true;
break;
}
}
}
window_manager_remove(window);
app_event_unsubscribe(&subscription);
task_event_group_destruct(&eventGroup);
device_put(device);
return 0;
}
}
extern const ::AppManifest manifest = {
.id = "tactility.ledstripsettings",
.name = "LED Strip",
.category = APP_CATEGORY_SETTINGS,
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
.flags = 0,
.stack = {}
};
}

View File

@ -43,6 +43,7 @@ constexpr IconEntry ICONS[] = {
{"tactility.gpssettings", LVGL_ICON_SHARED_NAVIGATION},
{"tactility.grovesettings", LVGL_ICON_SHARED_CABLE},
{"tactility.keyboardsettings", LVGL_ICON_SHARED_KEYBOARD_ALT},
{"tactility.ledstripsettings", LVGL_ICON_SHARED_LIGHTSTRIP},
{"tactility.localesettings", LVGL_ICON_SHARED_LANGUAGE},
{"tactility.power", LVGL_ICON_SHARED_POWER_SETTINGS_NEW},
{"tactility.timedatesettings", LVGL_ICON_SHARED_CALENDAR_MONTH},

View File

@ -0,0 +1,194 @@
#include <Tactility/settings/LedStripSettings.h>
#include <Tactility/file/File.h>
#include <Tactility/file/PropertiesFile.h>
#include <app/paths.h>
#include <charconv>
#include <algorithm>
#include <iterator>
#include <map>
#include <string>
namespace tt::settings::ledstrip {
namespace {
struct ColorPreset {
const char* name;
LedRgb color;
};
const ColorPreset COLOR_PRESETS[] = {
{"White", LED_RGB_COLOR_WHITE},
{"Green", LED_RGB_COLOR_GREEN},
{"Red", LED_RGB_COLOR_RED},
{"Blue", LED_RGB_COLOR_BLUE},
{"Yellow", LED_RGB_COLOR_YELLOW},
{"Orange", LED_RGB_COLOR_ORANGE},
{"Cyan", LED_RGB_COLOR_CYAN},
{"Magenta", LED_RGB_COLOR_MAGENTA},
{"Purple", LED_RGB_COLOR_PURPLE},
{"Pink", LED_RGB_COLOR_PINK},
{"Teal", LED_RGB_COLOR_TEAL},
{"Black", LED_RGB_COLOR_BLACK},
};
LedRgb getColor(uint8_t preset, const LedRgb& custom) {
if (preset == 12) return custom;
return COLOR_PRESETS[std::min<size_t>(preset, std::size(COLOR_PRESETS) - 1)].color;
}
uint8_t interpolate(uint8_t first, uint8_t second, size_t position, size_t count) {
if (count <= 1) return first;
const size_t denominator = count - 1;
return static_cast<uint8_t>((first * (denominator - position) + second * position) / denominator);
}
}
static std::string getSettingsFilePath(const char* deviceName) {
char path[256];
const auto fileName = std::string(deviceName) + ".properties";
if (app_paths_get_user_data_path("tactility.ledstripsettings", fileName.c_str(), path, sizeof(path)) != ERROR_NONE) {
return "";
}
return path;
}
static bool readNumber(const std::map<std::string, std::string>& properties, const char* key, unsigned maximum, unsigned& value) {
const auto it = properties.find(key);
if (it == properties.end()) {
return false;
}
unsigned parsed = 0;
const auto result = std::from_chars(it->second.data(), it->second.data() + it->second.size(), parsed);
if (result.ec != std::errc() || result.ptr != it->second.data() + it->second.size() || parsed > maximum) {
return false;
}
value = parsed;
return true;
}
static bool readBool(const std::map<std::string, std::string>& properties, const char* key, bool& value) {
const auto it = properties.find(key);
if (it == properties.end()) {
return false;
}
if (it->second == "1" || it->second == "true") {
value = true;
return true;
}
if (it->second == "0" || it->second == "false") {
value = false;
return true;
}
return false;
}
LedStripSettings getDefault() {
return {};
}
bool load(const char* deviceName, LedStripSettings& settings) {
const auto path = getSettingsFilePath(deviceName);
if (!file::isFile(path)) {
return false;
}
std::map<std::string, std::string> properties;
if (!file::loadPropertiesFile(path, properties)) {
return false;
}
auto loaded = getDefault();
unsigned value = 0;
readBool(properties, "enabled", loaded.enabled);
if (readNumber(properties, "pattern", static_cast<unsigned>(Pattern::Gradient), value)) {
loaded.pattern = static_cast<Pattern>(value);
}
if (readNumber(properties, "brightness", UINT8_MAX, value)) loaded.brightness = static_cast<uint8_t>(value);
if (readNumber(properties, "primaryPreset", 12, value)) loaded.primaryPreset = static_cast<uint8_t>(value);
if (readNumber(properties, "secondaryPreset", 12, value)) loaded.secondaryPreset = static_cast<uint8_t>(value);
if (readNumber(properties, "primaryRed", UINT8_MAX, value)) loaded.primaryCustom.r = static_cast<uint8_t>(value);
if (readNumber(properties, "primaryGreen", UINT8_MAX, value)) loaded.primaryCustom.g = static_cast<uint8_t>(value);
if (readNumber(properties, "primaryBlue", UINT8_MAX, value)) loaded.primaryCustom.b = static_cast<uint8_t>(value);
if (readNumber(properties, "secondaryRed", UINT8_MAX, value)) loaded.secondaryCustom.r = static_cast<uint8_t>(value);
if (readNumber(properties, "secondaryGreen", UINT8_MAX, value)) loaded.secondaryCustom.g = static_cast<uint8_t>(value);
if (readNumber(properties, "secondaryBlue", UINT8_MAX, value)) loaded.secondaryCustom.b = static_cast<uint8_t>(value);
settings = loaded;
return true;
}
LedStripSettings loadOrGetDefault(const char* deviceName) {
auto settings = getDefault();
load(deviceName, settings);
return settings;
}
bool save(const char* deviceName, const LedStripSettings& settings) {
std::map<std::string, std::string> properties;
properties["enabled"] = settings.enabled ? "1" : "0";
properties["pattern"] = std::to_string(static_cast<unsigned>(settings.pattern));
properties["brightness"] = std::to_string(settings.brightness);
properties["primaryPreset"] = std::to_string(settings.primaryPreset);
properties["secondaryPreset"] = std::to_string(settings.secondaryPreset);
properties["primaryRed"] = std::to_string(settings.primaryCustom.r);
properties["primaryGreen"] = std::to_string(settings.primaryCustom.g);
properties["primaryBlue"] = std::to_string(settings.primaryCustom.b);
properties["secondaryRed"] = std::to_string(settings.secondaryCustom.r);
properties["secondaryGreen"] = std::to_string(settings.secondaryCustom.g);
properties["secondaryBlue"] = std::to_string(settings.secondaryCustom.b);
auto settings_path = getSettingsFilePath(deviceName);
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
return false;
}
return file::savePropertiesFile(settings_path, properties);
}
error_t apply(struct Device* device, const LedStripSettings& settings) {
if (!settings.enabled) {
return led_strip_clear(device);
}
uint16_t ledCount = 0;
auto result = led_strip_get_length(device, &ledCount);
if (result != ERROR_NONE) return result;
if (ledCount == 0) return ERROR_INVALID_ARGUMENT;
result = led_strip_set_brightness(device, settings.brightness);
if (result != ERROR_NONE) return result;
const LedRgb primary = getColor(settings.primaryPreset, settings.primaryCustom);
const LedRgb secondary = getColor(settings.secondaryPreset, settings.secondaryCustom);
if (settings.pattern == Pattern::Solid) {
result = led_strip_fill_led_range_color(device, 0, ledCount, primary);
if (result != ERROR_NONE) return result;
} else {
for (size_t index = 0; index < ledCount; index++) {
LedRgb color = primary;
if (settings.pattern == Pattern::Alternating) {
color = (index % 2 == 0) ? primary : secondary;
} else if (settings.pattern == Pattern::Gradient) {
color = {
interpolate(primary.r, secondary.r, index, ledCount),
interpolate(primary.g, secondary.g, index, ledCount),
interpolate(primary.b, secondary.b, index, ledCount),
};
}
result = led_strip_set_single_led_color(device, index, color);
if (result != ERROR_NONE) return result;
}
}
return led_strip_show(device);
}
}

View File

@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <tactility/device.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief An RGB color value.
*/
struct LedRgb {
uint8_t r;
uint8_t g;
uint8_t b;
};
#define LED_RGB_COLOR_WHITE ((struct LedRgb){255, 255, 255})
#define LED_RGB_COLOR_GREEN ((struct LedRgb){0, 255, 0})
#define LED_RGB_COLOR_RED ((struct LedRgb){255, 0, 0})
#define LED_RGB_COLOR_BLUE ((struct LedRgb){0, 0, 255})
#define LED_RGB_COLOR_YELLOW ((struct LedRgb){255, 255, 0})
#define LED_RGB_COLOR_ORANGE ((struct LedRgb){255, 165, 0})
#define LED_RGB_COLOR_CYAN ((struct LedRgb){0, 255, 255})
#define LED_RGB_COLOR_MAGENTA ((struct LedRgb){255, 0, 255})
#define LED_RGB_COLOR_PURPLE ((struct LedRgb){191, 0, 255})
#define LED_RGB_COLOR_PINK ((struct LedRgb){255, 192, 203})
#define LED_RGB_COLOR_TEAL ((struct LedRgb){0, 128, 128})
#define LED_RGB_COLOR_BLACK ((struct LedRgb){0, 0, 0})
#define LED_COLOR_RGB_CUSTOM(r, g, b) ((struct LedRgb){(r), (g), (b)})
struct LedStripApi {
error_t (*set_single_led_color)(struct Device* device, size_t index, const struct LedRgb color);
error_t (*set_single_led_color_and_brightness)(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness);
error_t (*set_led_range_color)(struct Device* device, size_t first, size_t count, const struct LedRgb* color);
error_t (*set_led_range_color_and_brightness)(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness);
error_t (*fill_led_range_color)(struct Device* device, size_t first, size_t count, const struct LedRgb color);
error_t (*fill_led_range_color_and_brightness)(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness);
error_t (*set_brightness)(struct Device* device, uint8_t brightness);
error_t (*show)(struct Device* device);
error_t (*clear)(struct Device* device);
error_t (*get_length)(struct Device* device, uint16_t* out_length);
};
extern const struct DeviceType LED_STRIP_TYPE;
/**
* @brief Sets the color of a single LED using the global brightness.
*/
error_t led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color);
/**
* @brief Sets the color of a single LED with an explicit brightness value.
*/
error_t led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness);
/**
* @brief Sets colors for a range of LEDs using the global brightness.
*/
error_t led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color);
/**
* @brief Sets colors for a range of LEDs with an explicit brightness value.
*/
error_t led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness);
/**
* @brief Fills a range of LEDs with a single color using the global brightness.
*/
error_t led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color);
/**
* @brief Fills a range of LEDs with a single color and an explicit brightness value.
*/
error_t led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness);
/**
* @brief Set global brightness for the LED strip.
*/
error_t led_strip_set_brightness(struct Device* device, uint8_t brightness);
/**
* @brief Refreshes the strip to update the displayed colors.
*/
error_t led_strip_show(struct Device* device);
/**
* @brief Clears the strip to black.
*/
error_t led_strip_clear(struct Device* device);
/**
* @brief Get the LED strip length.
*/
error_t led_strip_get_length(struct Device* device, uint16_t* out_length);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/drivers/led_strip.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/log.h>
#define TAG "LedStrip"
#define LED_STRIP_DRIVER_API(driver) ((struct LedStripApi*)driver->api)
extern "C" {
error_t led_strip_set_single_led_color(struct Device* device, size_t index, const struct LedRgb color) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->set_single_led_color(device, index, color);
}
error_t led_strip_set_single_led_color_and_brightness(struct Device* device, size_t index, const struct LedRgb color, uint8_t brightness) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->set_single_led_color_and_brightness(device, index, color, brightness);
}
error_t led_strip_set_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb* color) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->set_led_range_color(device, first, count, color);
}
error_t led_strip_set_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb* color, uint8_t brightness) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->set_led_range_color_and_brightness(device, first, count, color, brightness);
}
error_t led_strip_fill_led_range_color(struct Device* device, size_t first, size_t count, const struct LedRgb color) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->fill_led_range_color(device, first, count, color);
}
error_t led_strip_fill_led_range_color_and_brightness(struct Device* device, size_t first, size_t count, const struct LedRgb color, uint8_t brightness) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->fill_led_range_color_and_brightness(device, first, count, color, brightness);
}
error_t led_strip_set_brightness(struct Device* device, uint8_t brightness) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->set_brightness(device, brightness);
}
error_t led_strip_show(struct Device* device) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->show(device);
}
error_t led_strip_clear(struct Device* device) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->clear(device);
}
error_t led_strip_get_length(struct Device* device, uint16_t* out_length) {
const auto* driver = device_get_driver(device);
return LED_STRIP_DRIVER_API(driver)->get_length(device, out_length);
}
const struct DeviceType LED_STRIP_TYPE {
.name = "led_strip"
};
}

View File

@ -25,6 +25,7 @@
#include <tactility/drivers/i8080_controller.h>
#include <tactility/drivers/imu.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/drivers/led_strip.h>
#include <tactility/drivers/lora.h>
#include <tactility/drivers/pointer.h>
#include <tactility/drivers/power_supply.h>
@ -257,6 +258,18 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
// drivers/keyboard
DEFINE_MODULE_SYMBOL(keyboard_read_key),
DEFINE_MODULE_SYMBOL(KEYBOARD_TYPE),
// drivers/led_strip
DEFINE_MODULE_SYMBOL(led_strip_set_single_led_color),
DEFINE_MODULE_SYMBOL(led_strip_set_single_led_color_and_brightness),
DEFINE_MODULE_SYMBOL(led_strip_set_led_range_color),
DEFINE_MODULE_SYMBOL(led_strip_set_led_range_color_and_brightness),
DEFINE_MODULE_SYMBOL(led_strip_fill_led_range_color),
DEFINE_MODULE_SYMBOL(led_strip_fill_led_range_color_and_brightness),
DEFINE_MODULE_SYMBOL(led_strip_set_brightness),
DEFINE_MODULE_SYMBOL(led_strip_show),
DEFINE_MODULE_SYMBOL(led_strip_clear),
DEFINE_MODULE_SYMBOL(led_strip_get_length),
DEFINE_MODULE_SYMBOL(LED_STRIP_TYPE),
// drivers/paths
DEFINE_MODULE_SYMBOL(paths_get_data_path),
DEFINE_MODULE_SYMBOL(paths_get_temp_path),