mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
LilyGo T-Embed CC1101 Plus & Led Strip Driver (#670)
This commit is contained in:
parent
5bac1ada82
commit
ffab897fe4
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,5 +26,6 @@ sdkconfig.board.*.dev
|
||||
|
||||
.caveman.json
|
||||
.ai/mcp
|
||||
.clinerules/
|
||||
|
||||
__pycache__
|
||||
6
Devices/lilygo-tembed-cc1101-plus/CMakeLists.txt
Normal file
6
Devices/lilygo-tembed-cc1101-plus/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
file(GLOB_RECURSE SOURCE_FILES source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCE_FILES}
|
||||
REQUIRES TactilityKernel Tactility
|
||||
)
|
||||
195
Devices/lilygo-tembed-cc1101-plus/LICENSE-Apache-2.0.md
Normal file
195
Devices/lilygo-tembed-cc1101-plus/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### 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.
|
||||
|
||||
28
Devices/lilygo-tembed-cc1101-plus/device.properties
Normal file
28
Devices/lilygo-tembed-cc1101-plus/device.properties
Normal 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
|
||||
|
||||
|
||||
186
Devices/lilygo-tembed-cc1101-plus/lilygo,tembed-cc1101-plus.dts
Normal file
186
Devices/lilygo-tembed-cc1101-plus/lilygo,tembed-cc1101-plus.dts
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
11
Devices/lilygo-tembed-cc1101-plus/module.yaml
Normal file
11
Devices/lilygo-tembed-cc1101-plus/module.yaml
Normal 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
|
||||
49
Devices/lilygo-tembed-cc1101-plus/source/module.cpp
Normal file
49
Devices/lilygo-tembed-cc1101-plus/source/module.cpp
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
11
Drivers/esp32-led-strip-module/CMakeLists.txt
Normal file
11
Drivers/esp32-led-strip-module/CMakeLists.txt
Normal 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
|
||||
)
|
||||
195
Drivers/esp32-led-strip-module/LICENSE-Apache-2.0.md
Normal file
195
Drivers/esp32-led-strip-module/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
Apache License
|
||||
==============
|
||||
|
||||
_Version 2.0, January 2004_
|
||||
_<<http://www.apache.org/licenses/>>_
|
||||
|
||||
### 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.
|
||||
|
||||
88
Drivers/esp32-led-strip-module/README.md
Normal file
88
Drivers/esp32-led-strip-module/README.md
Normal 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`.
|
||||
@ -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
|
||||
@ -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
|
||||
122
Drivers/esp32-led-strip-module/include/drivers/esp32_led_strip.h
Normal file
122
Drivers/esp32-led-strip-module/include/drivers/esp32_led_strip.h
Normal 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
|
||||
@ -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
|
||||
99
Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt.h
Normal file
99
Drivers/esp32-led-strip-module/include/esp32_led_strip_rmt.h
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
3
Drivers/esp32-led-strip-module/module.yaml
Normal file
3
Drivers/esp32-led-strip-module/module.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
258
Drivers/esp32-led-strip-module/source/esp32_led_strip.cpp
Normal file
258
Drivers/esp32-led-strip-module/source/esp32_led_strip.cpp
Normal 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"
|
||||
170
Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_dev.c
Normal file
170
Drivers/esp32-led-strip-module/source/esp32_led_strip_rmt_dev.c
Normal 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;
|
||||
}
|
||||
@ -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(©_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;
|
||||
}
|
||||
23
Drivers/esp32-led-strip-module/source/module.cpp
Normal file
23
Drivers/esp32-led-strip-module/source/module.cpp
Normal 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"
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
34
Tactility/Include/Tactility/settings/LedStripSettings.h
Normal file
34
Tactility/Include/Tactility/settings/LedStripSettings.h
Normal 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);
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
474
Tactility/Source/app/ledstripsettings/LedStripSettings.cpp
Normal file
474
Tactility/Source/app/ledstripsettings/LedStripSettings.cpp
Normal 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 = {}
|
||||
};
|
||||
|
||||
}
|
||||
@ -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},
|
||||
|
||||
194
Tactility/Source/settings/LedStripSettings.cpp
Normal file
194
Tactility/Source/settings/LedStripSettings.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
110
TactilityKernel/include/tactility/drivers/led_strip.h
Normal file
110
TactilityKernel/include/tactility/drivers/led_strip.h
Normal 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
|
||||
67
TactilityKernel/source/drivers/led_strip.cpp
Normal file
67
TactilityKernel/source/drivers/led_strip.cpp
Normal 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"
|
||||
};
|
||||
|
||||
}
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user