Compare commits

..

7 Commits

61 changed files with 156413 additions and 41 deletions

View File

@@ -5,7 +5,7 @@
"idf.openOcdConfigs": [
"board/esp32s3-builtin.cfg"
],
"idf.port": "/dev/tty.BLTH",
"idf.port": "/dev/tty.usbmodem14301",
"idf.toolsPath": "/Users/tarassivas/.espressif",
"idf.customExtraVars": {
"IDF_TARGET": "esp32s3"
@@ -15,5 +15,6 @@
"--background-index",
"--query-driver=/Users/tarassivas/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
"--compile-commands-dir=${workspaceFolder}/build"
]
],
"idf.flashType": "UART"
}

11
.vscode/sftp.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "My Server",
"host": "localhost",
"protocol": "sftp",
"port": 22,
"username": "username",
"remotePath": "/",
"uploadOnSave": false,
"useTempFile": false,
"openSsh": false
}

View File

@@ -4,19 +4,33 @@ watch-watch — вбудована система на ESP32-S3 для нагл
## Основні можливості
- **Керування каналами живлення**: 5 незалежних ліній `EN` (GPIO 2, 4, 5, 18, 19), які можна увімкнути, вимкнути або перемкнути з коду чи CLI.
- **Послідовний автотест**: у `app_main` реалізовано базову логіку — канали вмикаються по черзі з інтервалом 4 с, що дозволяє перевірити всі DC/DC.
- **Послідовний автотест**: у `app_main` реалізовано базову логіку — канали вмикаються по черзі з інтервалом 3 с, що дозволяє перевірити всі DC/DC без стрибків споживання.
- **Світлодіодний індикатор стану**: п’ять WS2812 (GPIO 8) світяться зеленим під час стартової затримки, після чого кожен канал сигналізує лише про дві події — відсутність VPN (два червоних блимання) та падіння APP (три жовтих блимання).
- **Моніторинг навантаження**: датчики INA226 вимірюють напругу, струм та потужність кожного каналу, інформація потрапляє в лог і CLI.
- **UART взаємодія з Raspberry Pi**: один UART через мультиплексор (A0/A1/A2) ділиться між п’ятьма Pi, дозволяючи надсилати службові повідомлення або обмінюватися даними.
- **Нативний USB-CLI**: ESP32-S3 підключається до Raspberry Pi 5 по USB і стає CDC ACM пристроєм; командний інтерфейс дозволяє керувати каналами та дивитись стан у реальному часі.
- **Модульна архітектура**: окремі компоненти `dcdc_controller` і `usb_cdc_cli` спрощують розширення (телеметрія, автоматизація, протоколи зв’язку).
## Структура проєкту
```
├── CMakeLists.txt
├── Kconfig.projbuild # меню і параметри WATCH_WS2812_*
├── README.md
├── sdkconfig # збережені налаштування menuconfig
├── dependencies.lock
├── main
│ ├── main.c // базова логіка, ініціалізація модулів
│ ├── dcdc_controller.c/.h // API керування DC/DC каналами
│ ├── usb_cdc_cli.c/.h // TinyUSB CLI для Raspberry Pi 5
── idf_component.yml // залежність на espressif/esp_tinyusb
├── sdkconfig // конфігурація (опції TinyUSB увімкнені)
└── README.md // цей файл
│ ├── CMakeLists.txt
│ ├── idf_component.yml # залежності: esp_tinyusb, led_strip
│ ├── main.c # головний цикл, тестова логіка DC/DC
── dcdc_controller.c/.h # керування GPIO EN
│ ├── usb_cdc_cli.c/.h # CLI по USB CDC
│ ├── ws2812_status.c/.h # індикація стану на WS2812
│ ├── ina226_monitor.c/.h # вимірювання напруги/струму/потужності
│ └── uart_mux.c/.h # UART взаємодія з 5 Raspberry Pi
├── managed_components
│ ├── espressif__esp_tinyusb # бібліотека TinyUSB від Espressif
│ └── espressif__led_strip # драйвер керування WS2812 (RMT/SPI)
└── .vscode / .devcontainer / .clangd # допоміжні файли середовища розробки
```
## GPIO-призначення каналів
@@ -31,6 +45,35 @@ watch-watch — вбудована система на ESP32-S3 для нагл
> Піни можна змінити в `main/dcdc_controller.c`, масив `s_dcdc_gpio_map`.
## Світлодіоди стану
- IC WS2812 підключений до GPIO 8 (один ланцюг із 5 діодів).
- Після старту всі п’ять діодів світяться сталим зеленим протягом затримки `heartbeat_start_delay_sec`, щоб показати фазу ініціалізації.
- Після завершення затримки вся стрічка гасне, а кожен канал індикує лише два типи подій:
- VPN=0 — два червоних блимання по 200 мс із паузою між циклами;
- APP=0 — три жовтих блимання по 200 мс.
Якщо активні обидва попередження, вони програються послідовно (спочатку VPN, потім APP) із паузою 2 с між послідовностями.
- При критичній помилці (наприклад, DCDC не ініціалізувався) всі індикатори залишаються червоними.
- GPIO, кількість діодів та тактову частоту RMT можна змінити через `idf.py menuconfig` (розділ *Налаштування watch-watch*).
## Моніторинг живлення (INA226)
- Один INA226 підключений до загальної шини живлення (I2C порт 0, GPIO 6/7; адреса налаштовується параметром `WATCH_INA226_ADDR`).
- Модуль `ina226_monitor` вимірює сумарну напругу, струм і розраховує потужність — ці значення фіксуються в логах і використовуються для телеметрії.
- Команда CLI `sense` показує поточні показники для всієї системи (канал не вказується, бо датчик один).
- Конфігурація I2C, адреси, шунта та кроку струму знаходиться в `menuconfig → INA226 моніторинг`.
## UART взаємодія та heartbeat
- Загальний UART (типово UART1, GPIO17/16) підключений до аналогового мультиплексора, лінії адреси `A0/A1/A2` (GPIO 9/10/11) вибирають одну з Raspberry Pi.
- Модуль `uart_mux` серіалізує доступ до UART, надає API для `uart_mux_write/read` і періодично опитує UART на наявність heartbeat.
- Після кожного вимірювання INA226 ESP32-S3 відправляє поточну телеметрію (`PWR <V>V <I>A`) до активної Raspberry Pi.
- Якщо heartbeat від Pi не надходить протягом `CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC` (за замовчуванням 60 с), відповідний канал живлення вимикається й знову вмикається для примусового перезапуску.
- Команди CLI `uart send` / `uart read` дозволяють вручну надсилати/читати повідомлення, а в `app_main` можна реалізувати власні протоколи синхронізації.
## UART взаємодія з Raspberry Pi
- Шина UART (типово UART1, TX=GPIO17, RX=GPIO16) підключена до аналогового мультиплексора з адресними лініями A0/A1/A2 (GPIO 9/10/11), що дозволяє вибирати одну з 5 Raspberry Pi.
- Модуль `uart_mux` гарантує серійний доступ: перед операцією він виставляє двійковий код каналу на A0-A2 та блокує UART м’ютексом.
- У `app_main` після вимірювань кожному Pi відправляється телеметрія (`CHx <V>V <I>A`), а через CLI можна виконати `uart send <n> <msg>` або `uart read <n> [len]`.
- Усі параметри (порт, швидкість, GPIO) доступні в `menuconfig → UART мультиплексор`.
## USB CDC CLI
Після підключення ESP32-S3 до Raspberry Pi 5 з’являється USB-пристрій (CDC ACM). У CLI доступні команди:
@@ -41,9 +84,32 @@ watch-watch — вбудована система на ESP32-S3 для нагл
| `enable <n>` | увімкнути канал `n` (0..4) |
| `disable <n>` | вимкнути канал `n` |
| `toggle <n>` | перемкнути канал `n` |
| `sense` | виміряти загальну напругу/струм/потужність |
| `uart send <n> <msg>` | надіслати повідомлення у Raspberry Pi `n` |
| `uart read <n> [len]` | прочитати відповідь від Raspberry Pi `n` |
| `config show` | переглянути таймінги heartbeat/DCDC |
| `config set …` | змінити та зберегти таймінги / моніторинг |
| `reset` | м’яке перезавантаження ESP32-S3 |
| `bootloader` | перезавантажити ESP32-S3 у ROM bootloader для `esptool.py` |
CLI з’являється після того, як Raspberry Pi встановить DTR (наприклад, через `screen`, `picocom` або власний скрипт).
### Керування таймінгами heartbeat
Команда `config` дозволяє зберігати параметри роботи watchdog та циклу heartbeat у NVS, і вони одразу набувають чинності без перезбирання прошивки:
1. `hb_period` — інтервал (сек) між опитуваннями/heartbeat у головному циклі.
2. `dcdc_off` — тривалість вимкнення каналу DCDC при автоматичному перезапуску (сек).
3. `hb_start` — затримка перед стартом опитування після завантаження (сек); застосовується як у головному циклі, так і у watchdog-завданні.
4. `hb_monitor` — значення `1` вмикає моніторинг heartbeat (за замовчуванням), `0` вимикає лише контроль/рестарти каналів, але тестові повідомлення heartbeat продовжують відправлятися.
5. `hb_miss` — кількість послідовних запитів без відповіді перед автоматичним перезапуском каналу (за замовчуванням 3). Значення зберігає watchdog та використовується в CLI для відображення статистики пропусків/рестартів.
### Прошивка без натискання BOOT
1. Під’єднайтесь до CLI та виконайте команду `bootloader`. ESP32-S3 перезавантажиться у ROM bootloader, а на хості з’явиться USB-пристрій `USB JTAG/serial`.
2. На Raspberry Pi запустіть `esptool.py` або `idf.py flash` і вкажіть новий порт (`/dev/ttyACM*` або `/dev/cu.usbmodem*`). Наприклад:
`esptool.py --chip esp32s3 --port /dev/ttyACM0 --before usb_reset --after no_reset write_flash 0x0 build/watch-watch.bin`
3. Після завершення прошивки виконайте `esptool.py --after hard_reset reset` або просто перезавантажте живлення — пристрій вийде з bootloader і повернеться до нормальної роботи.
Таким чином процедура оновлення доступна без натискання кнопки BOOT і може виконуватись безпосередньо з підключеної Raspberry Pi.
## Збірка та конфігурація
1. Встановіть ESP-IDF v5.5.1 (шлях `IDF_PATH` має вказувати на `/Users/tarassivas/esp/v5.5.1/esp-idf`).
2. Один раз виконайте `idf.py reconfigure`, щоб підвантажити залежності з `idf_component.yml`.

View File

@@ -13,6 +13,16 @@ dependencies:
registry_url: https://components.espressif.com/
type: service
version: 1.7.6~2
espressif/led_strip:
component_hash: 28c6509a727ef74925b372ed404772aeedf11cce10b78c3f69b3c66799095e2d
dependencies:
- name: idf
require: private
version: '>=4.4'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.5.5
espressif/tinyusb:
component_hash: 5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa
dependencies:
@@ -34,6 +44,7 @@ dependencies:
version: 5.5.1
direct_dependencies:
- espressif/esp_tinyusb
manifest_hash: 5a05c0273068b434734dee09d2c2ea1da5ef6181b054b4a9d51531ebb931282d
- espressif/led_strip
manifest_hash: 584163379689100c49748a32c2503844d55d9168901c12c7eec84929b4541e28
target: esp32s3
version: 2.0.0

24
kicad/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Temporary and local KiCad artifacts
*-backups/
*.kicad_prl
*.kicad_pro-bak
*.kicad_sch-bak
*.kicad_pcb-bak
*.sch-bak
*.pcb-bak
*.bak
# Cache and automatically generated tables
sym-lib-table-bak
fp-lib-table-bak
*-cache.lib
*-cache.dcm
# Generated fabrication outputs (regenerate as needed)
*.net
*.xml
*.csv
*.tsv
*.pos
*.ipr
*.rpt

View File

@@ -0,0 +1 @@
{"ARCHIVE_NAME": "", "EXTRA_LAYERS": "", "ALL_ACTIVE_LAYERS": false, "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false, "OPEN BROWSER": true, "NO_BACKUP_OPT": false}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,506 @@
P CODE 00
P UNITS CUST 0
P arrayDim N
317GND VIA MD0118PA00X+052000Y-017250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+051500Y-017250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066063Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066063Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068875Y-025750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070125Y-026750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071813Y-029063X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+069188Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072875Y-030125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071625Y-030125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+048938Y-028500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+048938Y-026000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+048938Y-018500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+048875Y-021000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+048938Y-023500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+053563Y-020313X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054188Y-019813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-020625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-020125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057438Y-020063X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058125Y-019813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063063Y-016313X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063063Y-017563X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063063Y-018688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063063Y-019625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063063Y-020563X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064313Y-021000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064875Y-021688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066188Y-025188X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065750Y-025188X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065938Y-027625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066625Y-027625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066625Y-026813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065875Y-026813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063938Y-026875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060688Y-026813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062875Y-028063X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061000Y-028000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+059938Y-029000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057813Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+056625Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054188Y-028750X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+052625Y-017249X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+053313Y-018188X0236Y0000R000S1526506179
317ET-(J14-PIN_2) VIA MD0118PA00X+055750Y-019813X0236Y0000R000S1526506179
317ET-(J14-PIN_2) VIA MD0118PA00X+064188Y-024625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068500Y-016125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+073813Y-016063X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+073313Y-017688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068563Y-017563X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072438Y-019438X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071813Y-019438X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070375Y-019313X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070125Y-019750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072813Y-024188X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071000Y-026750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065688Y-030000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065625Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065000Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063875Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063250Y-030813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057750Y-029750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060813Y-029688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058188Y-029688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060250Y-029688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058813Y-029313X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+059813Y-029688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058813Y-029688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052750Y-024750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052875Y-027500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054125Y-027813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055125Y-027813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058500Y-020875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058813Y-017881X0236Y0000R000S1526506179
317+5V VIA MD0118PA00X+060063Y-019000X0236Y0000R000S1526506179
317+5V VIA MD0118PA00X+058188Y-019000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055500Y-025625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057000Y-025375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057750Y-025250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+059509Y-026962X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057750Y-025750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+056563Y-020250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057250Y-030500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057250Y-029750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+047250Y-030000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+047250Y-030625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054000Y-029875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054000Y-030500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062000Y-017500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060000Y-017125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058375Y-017125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+056250Y-017125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054875Y-018500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054188Y-019375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055750Y-018938X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+056250Y-018500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+056125Y-020250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055438Y-020563X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060750Y-021250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061125Y-025750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062000Y-026875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+067875Y-026750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066625Y-025188X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066250Y-026813X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066250Y-027625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072875Y-025875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070125Y-025625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+069000Y-025250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068250Y-025875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071000Y-024250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070000Y-024250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+067250Y-021875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+071000Y-020750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065875Y-016250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065125Y-016250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065875Y-018500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065250Y-018500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062063Y-018688X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062000Y-016313X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060000Y-016125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058250Y-016125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+047000Y-016875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+047000Y-016125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052000Y-018000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052000Y-020500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052000Y-023000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052000Y-025500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+052000Y-028000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062500Y-030875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062500Y-030375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062500Y-030000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061250Y-030750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061250Y-030375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061250Y-030000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064750Y-029125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064750Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064750Y-028375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063875Y-029125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063875Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063875Y-028375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063375Y-029125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063375Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+063375Y-028375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065250Y-029125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065250Y-028750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+065250Y-028375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+067375Y-029375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070375Y-030625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+069500Y-030625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+076000Y-030625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+075375Y-030625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+075125Y-027750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+076125Y-026375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+075500Y-026375X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072875Y-025000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+072875Y-023500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+075375Y-022500X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+074875Y-020125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+070000Y-022125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+069000Y-022125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068000Y-020875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+069000Y-028125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068500Y-028125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+068000Y-028125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066938Y-024563X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+064875Y-022625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+066750Y-022625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062063Y-019625X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+060125Y-020125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+058063Y-020750X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+061875Y-025875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055375Y-026250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+057625Y-027000X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+053375Y-025875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+053750Y-023125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+054000Y-020875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-021250X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-022125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-022875X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-023438X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-024125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+055000Y-025125X0236Y0000R000S1526506179
317GND VIA MD0118PA00X+062750Y-022625X0236Y0000R000S1526506179
317NET-(U3-GPIO0) VIA MD0118PA00X+069375Y-019250X0236Y0000R000S1526506179
317NET-(U3-GPIO0) VIA MD0118PA00X+060750Y-027250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+062375Y-025750X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+064750Y-023250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+064750Y-024875X0236Y0000R000S1526506179
317+5V VIA MD0118PA00X+060625Y-028500X0236Y0000R000S1526506179
317RX VIA MD0118PA00X+068500Y-026375X0236Y0000R000S1526506179
317TX VIA MD0118PA00X+068750Y-020625X0236Y0000R000S1526506179
317A0 VIA MD0118PA00X+069000Y-022875X0236Y0000R000S1526506179
317A1 VIA MD0118PA00X+068500Y-022875X0236Y0000R000S1526506179
317A2 VIA MD0118PA00X+068000Y-022875X0236Y0000R000S1526506179
317POWER_RAIL_IN VIA MD0118PA00X+067375Y-020250X0236Y0000R000S1526506179
317POWER_RAIL_IN VIA MD0118PA00X+065250Y-022000X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+062125Y-022000X0236Y0000R000S1526506179
317EN0 VIA MD0118PA00X+059000Y-027375X0236Y0000R000S1526506179
317EN1 VIA MD0118PA00X+058494Y-027625X0236Y0000R000S1526506179
317EN2 VIA MD0118PA00X+059500Y-027375X0236Y0000R000S1526506179
317EN3 VIA MD0118PA00X+059921Y-027375X0236Y0000R000S1526506179
317EN4 VIA MD0118PA00X+060250Y-027750X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+057000Y-022250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+057539Y-024173X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+069625Y-022500X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+072000Y-020250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+072750Y-029250X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+072750Y-027500X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+072000Y-026000X0236Y0000R000S1526506179
317+3.3V VIA MD0118PA00X+072000Y-024750X0236Y0000R000S1526506179
317EN0 VIA MD0118PA00X+058986Y-025688X0236Y0000R000S1526506179
317RX VIA MD0118PA00X+056929Y-023051X0236Y0000R000S1526506179
317TX VIA MD0118PA00X+057323Y-023051X0236Y0000R000S1526506179
317EN3 VIA MD0118PA00X+059921Y-025669X0236Y0000R000S1526506179
317EN2 VIA MD0118PA00X+059606Y-025669X0236Y0000R000S1526506179
317EN1 VIA MD0118PA00X+059155Y-025296X0236Y0000R000S1526506179
317A2 VIA MD0118PA00X+062205Y-023858X0236Y0000R000S1526506179
317A1 VIA MD0118PA00X+061811Y-023858X0236Y0000R000S1526506179
317A0 VIA MD0118PA00X+061417Y-023858X0236Y0000R000S1526506179
317EN4 VIA MD0118PA00X+060236Y-025669X0236Y0000R000S1526506179
317TX3 VIA MD0118PA00X+069750Y-028474X0236Y0000R000S1526506179
317TX0 VIA MD0118PA00X+070500Y-028474X0236Y0000R000S1526506179
317TX1 VIA MD0118PA00X+071250Y-028724X0236Y0000R000S1526506179
317TX2 VIA MD0118PA00X+071313Y-030438X0236Y0000R000S1526506179
317RX4 VIA MD0118PA00X+072250Y-021474X0236Y0000R000S1526506179
317RX4 VIA MD0118PA00X+074119Y-022474X0236Y0000R000S1526506179
327EN0.0 U9 -4 A01X+048905Y-019000X0719Y0256R180S2
327EN0.1 U9 -3 A01X+048905Y-018000X0719Y0256R180S2
327GND U9 -2 A01X+052595Y-018000X0719Y0256R180S2
327NET-(R1-PAD1) U9 -1 A01X+052595Y-019000X0719Y0256R180S2
317GND J14 -1 D0394PA00X+049813Y-016563X0669Y0669R270S0
317ET-(J14-PIN_2) J14 -2 D0394PA00X+050813Y-016563X0669Y0000R270S0
317+3.3V J14 -3 D0394PA00X+051813Y-016563X0669Y0000R270S0
327+3.3V C1 -1 A01X+053063Y-017249X0394Y0571R270S2
327GND C1 -2 A01X+053063Y-016501X0394Y0571R270S2
327GND J1 -S1 A01X+057122Y-016805X0858Y0787R180S2
327GND J1 -S1 A01X+057122Y-018352X0858Y0787R180S2
327GND J1 -S1 A01X+061146Y-016805X0858Y0787R180S2
327GND J1 -S1 A01X+061146Y-018352X0858Y0787R180S2
327GND J1 -B12 A01X+060394Y-018579X0236Y0453R180S2
327+5V J1 -B9 A01X+060079Y-018579X0236Y0453R180S2
327J1-SBU2-PADB8) J1 -B8 A01X+059823Y-018579X0118Y0453R180S2
327-(J1-D--PADA7) J1 -B7 A01X+059429Y-018579X0118Y0453R180S2
327-(J1-D+-PADA6) J1 -B6 A01X+058839Y-018579X0118Y0453R180S2
327NET-(J1-CC2) J1 -B5 A01X+058445Y-018579X0118Y0453R180S2
327+5V J1 -B4 A01X+058189Y-018579X0236Y0453R180S2
327GND J1 -B1 A01X+057874Y-018579X0236Y0453R180S2
327GND J1 -A12 A01X+057874Y-018579X0236Y0453R180S2
327+5V J1 -A9 A01X+058189Y-018579X0236Y0453R180S2
327J1-SBU1-PADA8) J1 -A8 A01X+058642Y-018579X0118Y0453R180S2
327-(J1-D--PADA7) J1 -A7 A01X+059035Y-018579X0118Y0453R180S2
327-(J1-D+-PADA6) J1 -A6 A01X+059232Y-018579X0118Y0453R180S2
327NET-(J1-CC1) J1 -A5 A01X+059626Y-018579X0118Y0453R180S2
327+5V J1 -A4 A01X+060079Y-018579X0236Y0453R180S2
327GND J1 -A1 A01X+060394Y-018579X0236Y0453R180S2
367N/C J1 D0256UA00X+057996Y-018156X0256Y0000R180S0
367N/C J1 D0256UA00X+060272Y-018156X0256Y0000R180S0
327TX TP4 -1 A01X+069500Y-020250X0787Y0000R000S2
317GND U3 -57 D0079PA00X+059823Y-022047X0197Y0000R270S3
317GND U3 -57 D0079PA00X+059134Y-022047X0197Y0000R270S3
317GND U3 -57 D0079PA00X+058445Y-022047X0197Y0000R270S3
317GND U3 -57 D0079PA00X+059823Y-022736X0197Y0000R270S3
327GND U3 -57 A02X+059134Y-022736X1575Y1575R270S3
327GND U3 -57 A01X+059134Y-022736X1575Y1575R270S2
317GND U3 -57 D0079PA00X+059134Y-022736X0197Y0000R270S3
317GND U3 -57 D0079PA00X+058445Y-022736X0197Y0000R270S3
317GND U3 -57 D0079PA00X+059823Y-023425X0197Y0000R270S3
317GND U3 -57 D0079PA00X+059134Y-023425X0197Y0000R270S3
317GND U3 -57 D0079PA00X+058445Y-023425X0197Y0000R270S3
327+3.3V U3 -56 A01X+057776Y-023760X0079Y0315R270S2
327+3.3V U3 -55 A01X+057776Y-023602X0079Y0315R270S2
327ET-(U3-XTAL_P) U3 -54 A01X+057776Y-023445X0079Y0315R270S2
327ET-(U3-XTAL_N) U3 -53 A01X+057776Y-023287X0079Y0315R270S2
327-GPIO46-PAD52) U3 -52 A01X+057776Y-023130X0079Y0315R270S2
327-GPIO45-PAD51) U3 -51 A01X+057776Y-022972X0079Y0315R270S2
327TX U3 -50 A01X+057776Y-022815X0079Y0315R270S2
327RX U3 -49 A01X+057776Y-022657X0079Y0315R270S2
327U3-MTMS-PAD48) U3 -48 A01X+057776Y-022500X0079Y0315R270S2
327U3-MTDI-PAD47) U3 -47 A01X+057776Y-022343X0079Y0315R270S2
327+3.3V U3 -46 A01X+057776Y-022185X0079Y0315R270S2
327U3-MTDO-PAD45) U3 -45 A01X+057776Y-022028X0079Y0315R270S2
327U3-MTCK-PAD44) U3 -44 A01X+057776Y-021870X0079Y0315R270S2
327-GPIO38-PAD43) U3 -43 A01X+057776Y-021713X0079Y0315R270S2
327-GPIO37-PAD42) U3 -42 A01X+058110Y-021378X0315Y0079R270S2
327-GPIO36-PAD41) U3 -41 A01X+058268Y-021378X0315Y0079R270S2
327-GPIO35-PAD40) U3 -40 A01X+058425Y-021378X0315Y0079R270S2
327-GPIO34-PAD39) U3 -39 A01X+058583Y-021378X0315Y0079R270S2
327-GPIO33-PAD38) U3 -38 A01X+058740Y-021378X0315Y0079R270S2
327PICLK_P-PAD37) U3 -37 A01X+058898Y-021378X0315Y0079R270S2
327PICLK_N-PAD36) U3 -36 A01X+059055Y-021378X0315Y0079R270S2
327U3-SPID-PAD35) U3 -35 A01X+059213Y-021378X0315Y0079R270S2
327U3-SPIQ-PAD34) U3 -34 A01X+059370Y-021378X0315Y0079R270S2
327-SPICLK-PAD33) U3 -33 A01X+059528Y-021378X0315Y0079R270S2
327-SPICS0-PAD32) U3 -32 A01X+059685Y-021378X0315Y0079R270S2
3273-SPIWP-PAD31) U3 -31 A01X+059843Y-021378X0315Y0079R270S2
3273-SPIHD-PAD30) U3 -30 A01X+060000Y-021378X0315Y0079R270S2
327VDD_SPI-PAD29) U3 -29 A01X+060157Y-021378X0315Y0079R270S2
327-SPICS1-PAD28) U3 -28 A01X+060492Y-021713X0079Y0315R270S2
327-GPIO21-PAD27) U3 -27 A01X+060492Y-021870X0079Y0315R270S2
327DP U3 -26 A01X+060492Y-022028X0079Y0315R270S2
327DM U3 -25 A01X+060492Y-022185X0079Y0315R270S2
327ET-(U3-GPIO18) U3 -24 A01X+060492Y-022343X0079Y0315R270S2
327ET-(U3-GPIO17) U3 -23 A01X+060492Y-022500X0079Y0315R270S2
327L_32K_N-PAD22) U3 -22 A01X+060492Y-022657X0079Y0315R270S2
327L_32K_P-PAD21) U3 -21 A01X+060492Y-022815X0079Y0315R270S2
327+3.3V U3 -20 A01X+060492Y-022972X0079Y0315R270S2
327ET-(U3-GPIO14) U3 -19 A01X+060492Y-023130X0079Y0315R270S2
327A2 U3 -18 A01X+060492Y-023287X0079Y0315R270S2
327A1 U3 -17 A01X+060492Y-023445X0079Y0315R270S2
327A0 U3 -16 A01X+060492Y-023602X0079Y0315R270S2
327SCL U3 -15 A01X+060492Y-023760X0079Y0315R270S2
327SDA U3 -14 A01X+060157Y-024094X0315Y0079R270S2
327RGB U3 -13 A01X+060000Y-024094X0315Y0079R270S2
327NET-(U3-GPIO7) U3 -12 A01X+059843Y-024094X0315Y0079R270S2
327NET-(U3-GPIO6) U3 -11 A01X+059685Y-024094X0315Y0079R270S2
327EN4 U3 -10 A01X+059528Y-024094X0315Y0079R270S2
327EN3 U3 -9 A01X+059370Y-024094X0315Y0079R270S2
327EN2 U3 -8 A01X+059213Y-024094X0315Y0079R270S2
327EN1 U3 -7 A01X+059055Y-024094X0315Y0079R270S2
327EN0 U3 -6 A01X+058898Y-024094X0315Y0079R270S2
327NET-(U3-GPIO0) U3 -5 A01X+058740Y-024094X0315Y0079R270S2
327T-(U3-CHIP_PU) U3 -4 A01X+058583Y-024094X0315Y0079R270S2
327+3.3V U3 -3 A01X+058425Y-024094X0315Y0079R270S2
327+3.3V U3 -2 A01X+058268Y-024094X0315Y0079R270S2
327ET-(U3-LNA_IN) U3 -1 A01X+058110Y-024094X0315Y0079R270S2
327ET-(U3-LNA_IN) TP1 -1 A01X+056375Y-026500X0787Y0000R000S2
327GND C15 -2 A01X+057087Y-021122X0394Y0571R270S2
327+3.3V C15 -1 A01X+057087Y-021870X0394Y0571R270S2
327ET-(U3-XTAL_N) Y1 -1 A01X+056250Y-022874X0453Y0394R180S2
327GND Y1 -2 A01X+055561Y-022874X0453Y0394R180S2
327GND Y1 -3 A01X+055561Y-023425X0453Y0394R180S2
327ET-(U3-XTAL_P) Y1 -4 A01X+056250Y-023425X0453Y0394R180S2
327GND C10 -2 A01X+055531Y-024173X0394Y0571R180S2
327ET-(U3-XTAL_P) C10 -1 A01X+056280Y-024173X0394Y0571R180S2
327GND C11 -2 A01X+055531Y-022126X0394Y0571R180S2
327ET-(U3-XTAL_N) C11 -1 A01X+056280Y-022126X0394Y0571R180S2
327DP R13 -2 A01X+058740Y-020202X0404Y0551R090S2
327-(J1-D+-PADA6) R13 -1 A01X+058740Y-019483X0404Y0551R090S2
327GND C13 -2 A01X+062374Y-022625X0394Y0571R000S2
327+3.3V C13 -1 A01X+061626Y-022625X0394Y0571R000S2
327GND C12 -2 A01X+057087Y-024921X0394Y0571R090S2
327+3.3V C12 -1 A01X+057087Y-024173X0394Y0571R090S2
327POWER_RAIL_IN U2 -10 A01X+065366Y-022801X0571Y0118R270S2
327POWER_RAIL_OUT U2 -9 A01X+065563Y-022801X0571Y0118R270S2
327POWER_RAIL_IN U2 -8 A01X+065760Y-022801X0571Y0118R270S2
327GND U2 -7 A01X+065957Y-022801X0571Y0118R270S2
327+3.3V U2 -6 A01X+066154Y-022801X0571Y0118R270S2
327SCL U2 -5 A01X+066154Y-024533X0571Y0118R270S2
327SDA U2 -4 A01X+065957Y-024533X0571Y0118R270S2
327~{ALERT}-PAD3) U2 -3 A01X+065760Y-024533X0571Y0118R270S2
327D-(U2-A0-PAD2) U2 -2 A01X+065563Y-024533X0571Y0118R270S2
327D-(U2-A1-PAD1) U2 -1 A01X+065366Y-024533X0571Y0118R270S2
327GND C16 -2 A01X+058110Y-025492X0394Y0571R090S2
327+3.3V C16 -1 A01X+058110Y-024744X0394Y0571R090S2
327ET-(U3-GPIO17) TP9 -1 A01X+063625Y-021875X0787Y0000R000S2
327POWER_RAIL_OUT R9 -2 A01X+064715Y-020000X0482Y1043R180S2
327POWER_RAIL_IN R9 -1 A01X+066535Y-020000X0482Y1043R180S2
327RX TP5 -1 A01X+069500Y-026250X0787Y0000R000S2
327ET-(J14-PIN_2) R6 -2 A01X+061855Y-025197X0404Y0551R000S2
327RGB R6 -1 A01X+061137Y-025197X0404Y0551R000S2
327ET-(U3-GPIO14) TP8 -1 A01X+063625Y-023500X0787Y0000R000S2
327GND C9 -2 A01X+066929Y-024075X0394Y0571R090S2
327+3.3V C9 -1 A01X+066929Y-023327X0394Y0571R090S2
327DM R12 -2 A01X+059528Y-020202X0404Y0551R090S2
327-(J1-D--PADA7) R12 -1 A01X+059528Y-019483X0404Y0551R090S2
327ET-(U3-GPIO18) TP10 -1 A01X+062125Y-020625X0787Y0000R000S2
317RX3 J3 -3 D0394PA00X+073750Y-025474X0669Y0000R000S0
317TX3 J3 -2 D0394PA00X+073750Y-024474X0669Y0000R000S0
317GND J3 -1 D0394PA00X+073750Y-023474X0669Y0669R000S0
327NET-(U3-GPIO6) TP6 -1 A01X+060079Y-026457X0787Y0000R000S2
327RX4 U10 -1 A01X+071500Y-021500X0768Y0236R090S2
327-(U10-A6-PAD2) U10 -2 A01X+071000Y-021500X0768Y0236R090S2
327TX U10 -3 A01X+070500Y-021500X0768Y0236R090S2
327-(U10-A7-PAD4) U10 -4 A01X+070000Y-021500X0768Y0236R090S2
327-(U10-A5-PAD5) U10 -5 A01X+069500Y-021500X0768Y0236R090S2
327GND U10 -6 A01X+069000Y-021500X0768Y0236R090S2
327GND U10 -7 A01X+068500Y-021500X0768Y0236R090S2
327GND U10 -8 A01X+068000Y-021500X0768Y0236R090S2
327A2 U10 -9 A01X+068000Y-023449X0768Y0236R090S2
327A1 U10 -10 A01X+068500Y-023449X0768Y0236R090S2
327A0 U10 -11 A01X+069000Y-023449X0768Y0236R090S2
327RX3 U10 -12 A01X+069500Y-023449X0768Y0236R090S2
327RX0 U10 -13 A01X+070000Y-023449X0768Y0236R090S2
327RX1 U10 -14 A01X+070500Y-023449X0768Y0236R090S2
327RX2 U10 -15 A01X+071000Y-023449X0768Y0236R090S2
327+3.3V U10 -16 A01X+071500Y-023449X0768Y0236R090S2
327+5V D6 -1 A01X+060625Y-029000X0354Y0472R180S2
327NET-(D6-A) D6 -2 A01X+059326Y-029000X0354Y0472R180S2
327NET-(J1-CC2) R11 -1 A01X+057493Y-019442X0404Y0551R180S2
327GND R11 -2 A01X+056775Y-019442X0404Y0551R180S2
327+5V C19 -1 A01X+062374Y-028500X0394Y0571R180S2
327GND C19 -2 A01X+061626Y-028500X0394Y0571R180S2
327T-(U3-CHIP_PU) R15 -1 A01X+058189Y-026176X0404Y0551R090S2
327+3.3V R15 -2 A01X+058189Y-026895X0404Y0551R090S2
327TX R7 -1 A01X+070641Y-020250X0404Y0551R000S2
327+3.3V R7 -2 A01X+071359Y-020250X0404Y0551R000S2
317GND H4 -1 D1063PA00X+075250Y-017125X2126Y0000R000S0
327N/C FID3 A01X+066750Y-030125X0591Y0000R000S2
327+3.3V C17 -1 A01X+064376Y-026500X0394Y0571R000S2
327GND C17 -2 A01X+065124Y-026500X0394Y0571R000S2
317GND J5 -1 D0394PA00X+075750Y-023474X0669Y0669R000S0
317TX1 J5 -2 D0394PA00X+075750Y-024474X0669Y0000R000S0
317RX1 J5 -3 D0394PA00X+075750Y-025474X0669Y0000R000S0
327TX4 U11 -1 A01X+071500Y-027500X0768Y0236R090S2
327-(U11-A6-PAD2) U11 -2 A01X+071000Y-027500X0768Y0236R090S2
327RX U11 -3 A01X+070500Y-027500X0768Y0236R090S2
327-(U11-A7-PAD4) U11 -4 A01X+070000Y-027500X0768Y0236R090S2
327-(U11-A5-PAD5) U11 -5 A01X+069500Y-027500X0768Y0236R090S2
327GND U11 -6 A01X+069000Y-027500X0768Y0236R090S2
327GND U11 -7 A01X+068500Y-027500X0768Y0236R090S2
327GND U11 -8 A01X+068000Y-027500X0768Y0236R090S2
327A2 U11 -9 A01X+068000Y-029449X0768Y0236R090S2
327A1 U11 -10 A01X+068500Y-029449X0768Y0236R090S2
327A0 U11 -11 A01X+069000Y-029449X0768Y0236R090S2
327TX3 U11 -12 A01X+069500Y-029449X0768Y0236R090S2
327TX0 U11 -13 A01X+070000Y-029449X0768Y0236R090S2
327TX1 U11 -14 A01X+070500Y-029449X0768Y0236R090S2
327TX2 U11 -15 A01X+071000Y-029449X0768Y0236R090S2
327+3.3V U11 -16 A01X+071500Y-029449X0768Y0236R090S2
327+3.3V TP3 -1 A01X+063375Y-025250X0787Y0000R000S2
317EN0.0 J7 -1 D0394PA00X+047250Y-018992X0669Y0669R180S0
317EN0.1 J7 -2 D0394PA00X+047250Y-017992X0669Y0000R180S0
327+3.3V C4 -1 A01X+056249Y-025125X0394Y0571R180S2
327GND C4 -2 A01X+055501Y-025125X0394Y0571R180S2
327GND SW1 -1 A01X+069300Y-016739X0630Y0551R180S2
327GND SW1 -1 A01X+072450Y-016739X0630Y0551R180S2
327NET-(U3-GPIO0) SW1 -2 A01X+069300Y-018511X0630Y0551R180S2
327NET-(U3-GPIO0) SW1 -2 A01X+072450Y-018511X0630Y0551R180S2
327NET-(R3-PAD1) U6 -1 A01X+052595Y-024000X0719Y0256R180S2
327GND U6 -2 A01X+052595Y-023000X0719Y0256R180S2
327EN2.1 U6 -3 A01X+048905Y-023000X0719Y0256R180S2
327EN2.0 U6 -4 A01X+048905Y-024000X0719Y0256R180S2
327NET-(R5-PAD1) U8 -1 A01X+052595Y-029000X0719Y0256R180S2
327GND U8 -2 A01X+052595Y-028000X0719Y0256R180S2
327EN4.1 U8 -3 A01X+048905Y-028000X0719Y0256R180S2
327EN4.0 U8 -4 A01X+048905Y-029000X0719Y0256R180S2
327+3.3V C7 -1 A01X+072250Y-029376X0394Y0571R090S2
327GND C7 -2 A01X+072250Y-030124X0394Y0571R090S2
327NET-(R4-PAD1) U7 -1 A01X+052595Y-026500X0719Y0256R180S2
327GND U7 -2 A01X+052595Y-025500X0719Y0256R180S2
327EN3.1 U7 -3 A01X+048905Y-025500X0719Y0256R180S2
327EN3.0 U7 -4 A01X+048905Y-026500X0719Y0256R180S2
327NET-(U3-GPIO0) R14 -1 A01X+059055Y-026176X0404Y0551R090S2
327+3.3V R14 -2 A01X+059055Y-026895X0404Y0551R090S2
327NET-(J1-CC1) R10 -1 A01X+060775Y-019442X0404Y0551R000S2
327GND R10 -2 A01X+061493Y-019442X0404Y0551R000S2
317EN1.0 J8 -1 D0394PA00X+047250Y-021500X0669Y0669R180S0
317EN1.1 J8 -2 D0394PA00X+047250Y-020500X0669Y0000R180S0
327NET-(U3-GPIO7) TP7 -1 A01X+061339Y-026457X0787Y0000R000S2
327+3.3V C8 -1 A01X+071374Y-024724X0394Y0571R180S2
327GND C8 -2 A01X+070626Y-024724X0394Y0571R180S2
327RX R8 -1 A01X+070641Y-026220X0404Y0551R000S2
327+3.3V R8 -2 A01X+071359Y-026220X0404Y0551R000S2
327NET-(R1-PAD1) R1 -1 A01X+053625Y-019016X0404Y0551R090S2
327EN0 R1 -2 A01X+053625Y-019734X0404Y0551R090S2
327+5V C20 -1 A01X+062375Y-027625X0394Y0571R180S2
327GND C20 -2 A01X+061627Y-027625X0394Y0571R180S2
317GND J2 -1 D0394PA00X+073750Y-027724X0669Y0669R000S0
317TX4 J2 -2 D0394PA00X+073750Y-028724X0669Y0000R000S0
317RX4 J2 -3 D0394PA00X+073750Y-029724X0669Y0000R000S0
327+3.3V C14 -1 A01X+056124Y-021250X0394Y0571R180S2
327GND C14 -2 A01X+055376Y-021250X0394Y0571R180S2
317GND J6 -1 D0394PA00X+075750Y-019474X0669Y0669R000S0
317TX0 J6 -2 D0394PA00X+075750Y-020474X0669Y0000R000S0
317RX0 J6 -3 D0394PA00X+075750Y-021474X0669Y0000R000S0
327N/C FID2 A01X+047750Y-016500X0591Y0000R000S2
327GND U1 -1 A01X+065281Y-027510X0787Y0591R090S2
327+3.3V U1 -2 A01X+064375Y-027510X0787Y0591R090S2
327+3.3V U1 -2 A01X+064375Y-029990X0787Y1496R090S2
327+5V U1 -3 A01X+063469Y-027510X0787Y0591R090S2
317EN3.0 J10 -1 D0394PA00X+047250Y-026500X0669Y0669R180S0
317EN3.1 J10 -2 D0394PA00X+047250Y-025500X0669Y0000R180S0
327NET-(R4-PAD1) R4 -1 A01X+053625Y-026516X0404Y0551R090S2
327EN3 R4 -2 A01X+053625Y-027234X0404Y0551R090S2
327NET-(R5-PAD1) R5 -1 A01X+053625Y-028984X0404Y0551R270S2
327EN4 R5 -2 A01X+053625Y-028266X0404Y0551R270S2
327NET-(R3-PAD1) R3 -1 A01X+053625Y-024016X0404Y0551R090S2
327EN2 R3 -2 A01X+053625Y-024734X0404Y0551R090S2
317GND H3 -1 D1063PA00X+054750Y-017125X2126Y0000R000S0
317EN4.0 J11 -1 D0394PA00X+047250Y-029000X0669Y0669R180S0
317EN4.1 J11 -2 D0394PA00X+047250Y-028000X0669Y0000R180S0
327+5V TP2 -1 A01X+062625Y-026500X0787Y0000R000S2
327+3.3V C18 -1 A01X+064376Y-025625X0394Y0571R000S2
327GND C18 -2 A01X+065124Y-025625X0394Y0571R000S2
317GND H1 -1 D1063PA00X+055625Y-029750X2126Y0000R000S0
317NET-(D6-A) J13 -1 D0394PA00X+059326Y-030486X0669Y0669R270S0
317GND J13 -2 D0394PA00X+060326Y-030486X0669Y0000R270S0
317POWER_RAIL_IN J12 -1 D0630PA00X+066937Y-017414X0787Y1024R180S0
317GND J12 -2 D0630PA00X+065559Y-017414X0787Y1024R180S0
317POWER_RAIL_OUT J12 -3 D0630PA00X+064181Y-017414X0787Y1024R180S0
317GND J4 -1 D0394PA00X+075750Y-027724X0669Y0669R000S0
317TX2 J4 -2 D0394PA00X+075750Y-028724X0669Y0000R000S0
317RX2 J4 -3 D0394PA00X+075750Y-029724X0669Y0000R000S0
327NET-(R2-PAD1) R2 -1 A01X+053625Y-021500X0404Y0551R090S2
327EN1 R2 -2 A01X+053625Y-022219X0404Y0551R090S2
327N/C FID1 A01X+073500Y-020000X0591Y0000R000S2
327NET-(R2-PAD1) U5 -1 A01X+052595Y-021500X0719Y0256R180S2
327GND U5 -2 A01X+052595Y-020500X0719Y0256R180S2
327EN1.1 U5 -3 A01X+048905Y-020500X0719Y0256R180S2
327EN1.0 U5 -4 A01X+048905Y-021500X0719Y0256R180S2
317EN2.0 J9 -1 D0394PA00X+047250Y-024000X0669Y0669R180S0
317EN2.1 J9 -2 D0394PA00X+047250Y-023000X0669Y0000R180S0
999

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,669 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 1.0,
"height": 1.7,
"width": 1.7
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.4
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "watch-watchkicad_pro.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_width": 0.2,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "DigitalSig",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.3,
"via_diameter": 0.6,
"via_drill": 0.3
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "постачальник",
"name": "постачальник",
"show": false
},
{
"group_by": false,
"label": "Постачальник",
"name": "Постачальник",
"show": false
},
{
"group_by": false,
"label": "Опис",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Sim.Device",
"name": "Sim.Device",
"show": false
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"e717a214-aafd-4c38-81fe-477ca8e64d07",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,4 +1,8 @@
idf_component_register(SRCS "main.c"
idf_component_register(SRCS "ws2812_status.c" "watch_config.c" "usb_cdc_cli.c" "main.c"
"dcdc_controller.c"
"usb_cdc_cli.c"
INCLUDE_DIRS ".")
"usb_cdc_log.c"
"ina226_monitor.c"
"uart_mux.c"
INCLUDE_DIRS "."
REQUIRES nvs_flash driver esp_timer)

156
main/Kconfig.projbuild Normal file
View File

@@ -0,0 +1,156 @@
menu "Налаштування watch-watch"
menu "DC/DC контролер"
config WATCH_DCDC_EN_GPIO_0
int "GPIO EN каналу 0"
range 0 48
default 2
config WATCH_DCDC_EN_GPIO_1
int "GPIO EN каналу 1"
range 0 48
default 4
config WATCH_DCDC_EN_GPIO_2
int "GPIO EN каналу 2"
range 0 48
default 5
config WATCH_DCDC_EN_GPIO_3
int "GPIO EN каналу 3"
range 0 48
default 18
config WATCH_DCDC_EN_GPIO_4
int "GPIO EN каналу 4"
range 0 48
default 19
endmenu
config WATCH_WS2812_LED_COUNT
int "Кількість статусних світлодіодів WS2812"
range 1 30
default 5
help
Визначає кількість послідовно з’єднаних WS2812 індикаторів,
що показують стан каналів DC/DC.
config WATCH_WS2812_GPIO
int "GPIO для WS2812"
range 0 48
default 8
help
Встановіть номер GPIO, до якого підключено стрічку WS2812
(за замовчуванням GPIO8).
config WATCH_WS2812_RMT_RESOLUTION
int "RMT роздільна здатність (Гц)"
default 10000000
help
Тактова частота RMT драйвера для управління WS2812.
10 МГц = 0.1 мкс на імпульс.
endmenu
menu "INA226 моніторинг"
config WATCH_INA226_ENABLED
bool "Увімкнути моніторинг INA226"
default y
help
Якщо увімкнено, ESP32-S3 опитує датчики INA226 для вимірювання
напруги, струму та потужності кожного каналу.
config WATCH_INA226_I2C_PORT
int "I2C порт"
range 0 1
default 0
config WATCH_INA226_I2C_SDA
int "GPIO SDA"
range 0 48
default 6
config WATCH_INA226_I2C_SCL
int "GPIO SCL"
range 0 48
default 7
config WATCH_INA226_I2C_FREQ_HZ
int "Швидкість I2C (Гц)"
range 10000 1000000
default 400000
config WATCH_INA226_SHUNT_MILLIOHM
int "Опір шунта (мОм)"
range 1 500
default 10
config WATCH_INA226_CURRENT_LSB_uA
int "Крок струму (мкА/LSB)"
range 10 10000
default 100
config WATCH_INA226_SAMPLE_INTERVAL_MS
int "Інтервал опитування (мс)"
range 50 5000
default 500
config WATCH_INA226_ADDR
hex "Адреса INA226 (загальна шина)"
default 0x40
endmenu
menu "UART мультиплексор"
config WATCH_UART_MUX_ENABLED
bool "Увімкнути взаємодію з Raspberry Pi через UART"
default y
config WATCH_UART_MUX_CHANNELS
int "Кількість каналів (Raspberry Pi)"
range 1 8
default 5
config WATCH_UART_PORT
int "Номер UART"
range 0 2
default 1
config WATCH_UART_BAUD
int "Швидкість UART, біт/с"
default 115200
config WATCH_UART_TX_GPIO
int "GPIO TX"
default 17
config WATCH_UART_RX_GPIO
int "GPIO RX"
default 16
config WATCH_UART_MUX_SEL_A0
int "GPIO A0"
default 9
config WATCH_UART_MUX_SEL_A1
int "GPIO A1"
default 10
config WATCH_UART_MUX_SEL_A2
int "GPIO A2"
default 11
config WATCH_UART_MUX_DEFAULT_READ_LEN
int "Типова довжина читання (байт)"
default 128
config WATCH_UART_HEARTBEAT_TIMEOUT_SEC
int "Тайм-аут heartbeat (сек.)"
range 5 600
default 60
endmenu

View File

@@ -2,15 +2,32 @@
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"
static const char *TAG = "dcdc";
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_0
#define CONFIG_WATCH_DCDC_EN_GPIO_0 2
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_1
#define CONFIG_WATCH_DCDC_EN_GPIO_1 4
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_2
#define CONFIG_WATCH_DCDC_EN_GPIO_2 5
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_3
#define CONFIG_WATCH_DCDC_EN_GPIO_3 18
#endif
#ifndef CONFIG_WATCH_DCDC_EN_GPIO_4
#define CONFIG_WATCH_DCDC_EN_GPIO_4 19
#endif
static const gpio_num_t s_dcdc_gpio_map[DCDC_CHANNEL_COUNT] = {
GPIO_NUM_2,
GPIO_NUM_4,
GPIO_NUM_5,
GPIO_NUM_18,
GPIO_NUM_19,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_0,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_1,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_2,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_3,
(gpio_num_t)CONFIG_WATCH_DCDC_EN_GPIO_4,
};
static bool s_initialized;

View File

@@ -1,3 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "^1"
espressif/led_strip: "^2"

185
main/ina226_monitor.c Normal file
View File

@@ -0,0 +1,185 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include "ina226_monitor.h"
#include <math.h>
#include "driver/i2c.h"
#include "esp_check.h"
#include "esp_log.h"
#include "sdkconfig.h"
#ifndef CONFIG_WATCH_INA226_ENABLED
#define CONFIG_WATCH_INA226_ENABLED 0
#endif
#ifndef CONFIG_WATCH_INA226_I2C_PORT
#define CONFIG_WATCH_INA226_I2C_PORT 0
#endif
#ifndef CONFIG_WATCH_INA226_I2C_SDA
#define CONFIG_WATCH_INA226_I2C_SDA 6
#endif
#ifndef CONFIG_WATCH_INA226_I2C_SCL
#define CONFIG_WATCH_INA226_I2C_SCL 7
#endif
#ifndef CONFIG_WATCH_INA226_I2C_FREQ_HZ
#define CONFIG_WATCH_INA226_I2C_FREQ_HZ 400000
#endif
#ifndef CONFIG_WATCH_INA226_CURRENT_LSB_uA
#define CONFIG_WATCH_INA226_CURRENT_LSB_uA 50
#endif
#ifndef CONFIG_WATCH_INA226_SHUNT_MILLIOHM
#define CONFIG_WATCH_INA226_SHUNT_MILLIOHM 100
#endif
#ifndef CONFIG_WATCH_INA226_ADDR
#define CONFIG_WATCH_INA226_ADDR 0x40
#endif
#define INA226_REG_CONFIG 0x00
#define INA226_REG_BUS 0x02
#define INA226_REG_CURRENT 0x04
#define INA226_REG_CALIBRATION 0x05
#define INA226_CONFIG_AVG_16 (0x04 << 9)
#define INA226_CONFIG_VBUS_1100US (0x04 << 6)
#define INA226_CONFIG_VSH_1100US (0x04 << 3)
#define INA226_CONFIG_MODE_SHUNT_BUS_CONT 0x07
#if CONFIG_WATCH_INA226_ENABLED
static const char *TAG = "ina226";
static bool s_initialized;
static float s_current_lsb_a;
static uint8_t s_address = CONFIG_WATCH_INA226_ADDR;
static ina226_reading_t s_last_reading;
#endif
// Ініціалізує контролер INA226: налаштовує I2C, записує конфігурацію та
// калібрувальні значення, а також зберігає початковий стан вимірювань.
esp_err_t ina226_monitor_init(void)
{
#if !CONFIG_WATCH_INA226_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (s_initialized) {
return ESP_OK;
}
i2c_config_t config = {
.mode = I2C_MODE_MASTER,
.sda_io_num = CONFIG_WATCH_INA226_I2C_SDA,
.scl_io_num = CONFIG_WATCH_INA226_I2C_SCL,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = CONFIG_WATCH_INA226_I2C_FREQ_HZ,
};
ESP_RETURN_ON_ERROR(i2c_param_config(CONFIG_WATCH_INA226_I2C_PORT, &config), TAG, "i2c config failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(CONFIG_WATCH_INA226_I2C_PORT, config.mode, 0, 0, 0),
TAG, "i2c driver install failed");
double current_lsb_a = ((double)CONFIG_WATCH_INA226_CURRENT_LSB_uA) / 1000000.0;
double shunt_ohms = ((double)CONFIG_WATCH_INA226_SHUNT_MILLIOHM) / 1000.0;
double calibration = 0.00512 / (current_lsb_a * shunt_ohms);
if (calibration > 0xFFFF) calibration = 0xFFFF;
uint16_t calibration_value = (uint16_t)calibration;
s_current_lsb_a = (float)current_lsb_a;
const uint16_t config_value = INA226_CONFIG_AVG_16 |
INA226_CONFIG_VBUS_1100US |
INA226_CONFIG_VSH_1100US |
INA226_CONFIG_MODE_SHUNT_BUS_CONT;
uint8_t payload_cfg[3] = { INA226_REG_CONFIG, (uint8_t)(config_value >> 8), (uint8_t)(config_value & 0xFF) };
ESP_RETURN_ON_ERROR(i2c_master_write_to_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, payload_cfg, sizeof(payload_cfg),
pdMS_TO_TICKS(100)),
TAG, "config write failed");
uint8_t payload_cal[3] = { INA226_REG_CALIBRATION, (uint8_t)(calibration_value >> 8),
(uint8_t)(calibration_value & 0xFF) };
ESP_RETURN_ON_ERROR(i2c_master_write_to_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, payload_cal, sizeof(payload_cal),
pdMS_TO_TICKS(100)),
TAG, "calibration write failed");
s_last_reading = (ina226_reading_t){0};
s_initialized = true;
ESP_LOGI(TAG, "INA226 ініціалізовано (адреса 0x%02X)", s_address);
return ESP_OK;
#endif
}
// Дозволяє швидко перевірити, чи був модуль INA226 успішно ініціалізований.
bool ina226_monitor_ready(void)
{
#if CONFIG_WATCH_INA226_ENABLED
return s_initialized;
#else
return false;
#endif
}
// INA226 вимірює єдиний канал живлення, тому повертаємо 1.
size_t ina226_monitor_channel_count(void)
{
return 1;
}
#if CONFIG_WATCH_INA226_ENABLED
// Зчитує 16-бітний регістр INA226, використовуючи транзакцію write-then-read.
static esp_err_t ina226_read_register(uint8_t reg, uint16_t *out_value)
{
uint8_t value[2];
esp_err_t err = i2c_master_write_read_device(CONFIG_WATCH_INA226_I2C_PORT,
s_address, &reg, 1, value, sizeof(value),
pdMS_TO_TICKS(100));
if (err != ESP_OK) {
return err;
}
*out_value = ((uint16_t)value[0] << 8) | value[1];
return ESP_OK;
}
#endif
// Виконує одне вимірювання напруги/струму через INA226 та кешує результат.
esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading)
{
#if !CONFIG_WATCH_INA226_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
uint16_t bus_raw = 0;
uint16_t current_raw = 0;
ESP_RETURN_ON_ERROR(ina226_read_register(INA226_REG_BUS, &bus_raw), TAG, "bus read failed");
ESP_RETURN_ON_ERROR(ina226_read_register(INA226_REG_CURRENT, &current_raw), TAG, "current read failed");
float voltage_v = (float)bus_raw * 1.25f / 1000.0f;
float current_a = (int16_t)current_raw * s_current_lsb_a;
float power_w = voltage_v * current_a;
s_last_reading = (ina226_reading_t){
.voltage_v = voltage_v,
.current_a = current_a,
.power_w = power_w,
};
if (out_reading) {
*out_reading = s_last_reading;
}
return ESP_OK;
#endif
}
// Повертає останній виміряний набір даних без нового звернення до шини I2C.
const ina226_reading_t *ina226_monitor_get_last(void)
{
#if CONFIG_WATCH_INA226_ENABLED
return &s_last_reading;
#else
return NULL;
#endif
}

29
main/ina226_monitor.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "esp_err.h"
typedef struct {
float voltage_v;
float current_a;
float power_w;
} ina226_reading_t;
// Ініціалізує INA226: конфігурує I2C та калібрує вимірювач.
esp_err_t ina226_monitor_init(void);
// true, якщо драйвер вже ініціалізований.
bool ina226_monitor_ready(void);
// INA226 підтримує один канал, але інтерфейс залишається узагальненим.
size_t ina226_monitor_channel_count(void);
// Зчитує нові дані; out_reading може бути NULL, якщо дані не потрібні.
esp_err_t ina226_monitor_sample(ina226_reading_t *out_reading);
// Повертає кеш останнього вимірювання або NULL, якщо модуль неактивний.
const ina226_reading_t *ina226_monitor_get_last(void);

View File

@@ -1,42 +1,190 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include <stddef.h>
#include <stdint.h>
#include <stdarg.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "dcdc_controller.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#include "usb_cdc_cli.h"
#include "usb_cdc_log.h"
#include "watch_config.h"
#include "ws2812_status.h"
static const char *TAG = "watch-watch";
static const char HB_MESSAGE[] = "{\"cmd\":\"status\"}\r\n";
static int noop_vprintf(const char *fmt, va_list args)
{
(void)fmt;
(void)args;
return 0;
}
void app_main(void)
{
esp_log_set_vprintf(noop_vprintf);
if (watch_config_init() != ESP_OK) {
ESP_LOGE(TAG, "Не вдалося ініціалізувати конфігурацію");
} else {
ESP_LOGI(TAG, "Конфігурацію завантажено");
}
if (usb_cdc_cli_init() == ESP_OK) {
ESP_LOGI(TAG, "USB CDC CLI активовано");
} else {
ESP_LOGW(TAG, "Не вдалося запустити USB CLI");
}
bool ws_ready = false;
if (ws2812_status_init() == ESP_OK) {
ws_ready = true;
} else {
ESP_LOGW(TAG, "WS2812 статусний індикатор недоступний");
}
const watch_config_t *cfg = watch_config_get();
const uint32_t start_delay_ms = cfg->heartbeat_start_delay_sec * 1000U;
TickType_t start_delay = pdMS_TO_TICKS(start_delay_ms);
if (start_delay > 0) {
ESP_LOGI(TAG, "Очікування %u с перед стартом опитування", cfg->heartbeat_start_delay_sec);
if (ws_ready) {
ws2812_status_set_startup_hold(start_delay_ms);
}
vTaskDelay(start_delay);
if (ws_ready) {
ws2812_status_set_startup_hold(0);
}
}
ESP_LOGI(TAG, "Запуск watch-watch systems");
if (dcdc_init() != ESP_OK) {
ESP_LOGE(TAG, "Помилка ініціалізації DCDC контролера");
if (ws_ready) {
ws2812_status_set_error(true);
}
return;
}
if (usb_cdc_cli_init() != ESP_OK) {
ESP_LOGW(TAG, "USB CDC CLI недоступний");
} else {
ESP_LOGI(TAG, "USB CDC CLI запущено");
if (ws_ready) {
ws2812_status_refresh_from_dcdc();
}
ESP_LOGI(TAG, "Початок послідовного ввімкнення каналів з інтервалом 4 с");
const TickType_t on_time = pdMS_TO_TICKS(4000);
if (ina226_monitor_init() == ESP_OK) {
ESP_LOGI(TAG, "INA226 моніторинг активовано");
ina226_monitor_sample(NULL);
} else {
ESP_LOGW(TAG, "Моніторинг навантаження недоступний");
}
if (uart_mux_init() == ESP_OK) {
ESP_LOGI(TAG, "UART мультиплексор активовано");
} else {
ESP_LOGW(TAG, "UART мультиплексор недоступний");
}
const TickType_t mux_timeout = pdMS_TO_TICKS(600);
TickType_t channel_next_hb[DCDC_CHANNEL_COUNT] = {0};
bool channel_powered[DCDC_CHANNEL_COUNT] = {false};
const TickType_t power_on_stagger = pdMS_TO_TICKS(3000);
ESP_LOGI(TAG, "Початок циклічного опитування всіх каналів");
size_t prev_channel = DCDC_CHANNEL_COUNT - 1;
while (true) {
for (size_t ch = 0; ch < dcdc_channel_count(); ++ch) {
if (prev_channel != ch) {
dcdc_disable(prev_channel);
cfg = watch_config_get();
TickType_t hb_period = pdMS_TO_TICKS(cfg->heartbeat_period_sec * 1000U);
if (hb_period == 0) {
hb_period = 1;
}
TickType_t now = xTaskGetTickCount();
size_t channels = dcdc_channel_count();
for (size_t ch = 0; ch < channels; ++ch) {
bool powered = dcdc_get_state(ch);
if (!powered) {
channel_powered[ch] = false;
ESP_LOGI(TAG, "-> Ввімкнення каналу %d", (int)ch);
if (dcdc_enable(ch) == ESP_OK) {
powered = true;
channel_powered[ch] = true;
channel_next_hb[ch] = now + hb_period;
if (ws_ready) {
ws2812_status_set_channel_state(ch, true);
}
vTaskDelay(power_on_stagger);
} else {
ESP_LOGE(TAG, "Не вдалося ввімкнути канал %d", (int)ch);
continue;
}
}
ESP_LOGI(TAG, "-> Ввімкнення каналу %d", (int)ch);
dcdc_enable(ch);
vTaskDelay(on_time);
if (!channel_powered[ch]) {
channel_powered[ch] = true;
channel_next_hb[ch] = now + hb_period;
continue;
}
prev_channel = ch;
now = xTaskGetTickCount();
if (now < channel_next_hb[ch]) {
continue;
}
ina226_reading_t reading = {0};
if (ina226_monitor_ready() && ina226_monitor_sample(&reading) == ESP_OK) {
ESP_LOGI(TAG, "Живлення: %.2f В, %.2f А, %.2f Вт",
reading.voltage_v, reading.current_a, reading.power_w);
}
if (uart_mux_ready()) {
esp_err_t tx_err = uart_mux_write(ch,
(const uint8_t *)HB_MESSAGE,
sizeof(HB_MESSAGE) - 1,
mux_timeout);
if (tx_err != ESP_OK) {
ESP_LOGW(TAG, "Не вдалося надіслати heartbeat на канал %d: %s",
(int)ch, esp_err_to_name(tx_err));
} else {
uint8_t rx_buffer[CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN];
size_t received = 0;
esp_err_t rx_err = uart_mux_read(ch,
rx_buffer,
sizeof(rx_buffer),
&received,
mux_timeout);
if (rx_err == ESP_OK) {
if (received > 0) {
ESP_LOGI(TAG, "RX CH%u (%u байт після heartbeat)", (unsigned)ch, (unsigned)received);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, rx_buffer, received, ESP_LOG_INFO);
uart_mux_process_rx(ch, rx_buffer, received);
} else {
ESP_LOGW(TAG, "Канал %d не відповів даними на heartbeat", (int)ch);
uart_mux_report_miss(ch);
}
} else if (rx_err == ESP_ERR_TIMEOUT) {
ESP_LOGW(TAG, "Час очікування відповіді з каналу %d перевищено", (int)ch);
uart_mux_report_miss(ch);
} else {
ESP_LOGW(TAG, "Помилка читання відповіді CH%u: %s",
(unsigned)ch, esp_err_to_name(rx_err));
uart_mux_report_miss(ch);
}
}
} else {
ESP_LOGW(TAG, "UART мультиплексор недоступний, очікування...");
}
channel_next_hb[ch] = now + hb_period;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}

520
main/uart_mux.c Normal file
View File

@@ -0,0 +1,520 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#include "uart_mux.h"
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include "dcdc_controller.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "ws2812_status.h"
#include "watch_config.h"
#ifndef CONFIG_WATCH_UART_MUX_CHANNELS
#define CONFIG_WATCH_UART_MUX_CHANNELS 5
#endif
#ifndef CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC
#define CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC 60
#endif
#if CONFIG_WATCH_UART_MUX_ENABLED
#define UART_MUX_MAX_CHANNELS CONFIG_WATCH_UART_MUX_CHANNELS
static const char *TAG = "uart_mux";
static const gpio_num_t s_select_pins[3] = {
CONFIG_WATCH_UART_MUX_SEL_A0,
CONFIG_WATCH_UART_MUX_SEL_A1,
CONFIG_WATCH_UART_MUX_SEL_A2,
};
static SemaphoreHandle_t s_mutex;
static size_t s_active_channel = SIZE_MAX;
static bool s_initialized;
static int64_t s_last_heartbeat_us[UART_MUX_MAX_CHANNELS];
static TaskHandle_t s_watchdog_task;
static uint8_t s_consecutive_miss[UART_MUX_MAX_CHANNELS];
static bool s_watchdog_armed[UART_MUX_MAX_CHANNELS];
static uint32_t s_total_miss_count[UART_MUX_MAX_CHANNELS];
static uint32_t s_restart_count[UART_MUX_MAX_CHANNELS];
static uint8_t uart_mux_get_miss_limit_from_config(void);
static void uart_mux_restart_channel(size_t channel);
static void uart_mux_record_miss(size_t channel, uint8_t miss_limit);
static TickType_t uart_mux_restart_delay_ticks(void)
{
const watch_config_t *cfg = watch_config_get();
uint32_t sec = cfg ? cfg->dcdc_restart_off_sec : 2U;
if (sec == 0) {
sec = 1;
}
return pdMS_TO_TICKS(sec * 1000U);
}
static uint8_t uart_mux_get_miss_limit_from_config(void)
{
const watch_config_t *cfg = watch_config_get();
uint32_t limit = cfg ? cfg->heartbeat_miss_limit : 3U;
if (limit == 0) {
limit = 3U;
}
if (limit > UINT8_MAX) {
limit = UINT8_MAX;
}
return (uint8_t)limit;
}
static void uart_mux_restart_channel(size_t channel)
{
if (channel >= UART_MUX_MAX_CHANNELS) {
return;
}
ESP_LOGW(TAG, "CH%u: перезапуск живлення після відсутності відповіді", (unsigned)channel);
dcdc_disable(channel);
vTaskDelay(uart_mux_restart_delay_ticks());
dcdc_enable(channel);
s_consecutive_miss[channel] = 0;
s_watchdog_armed[channel] = false;
if (s_restart_count[channel] < UINT32_MAX) {
++s_restart_count[channel];
}
s_last_heartbeat_us[channel] = esp_timer_get_time();
}
static void uart_mux_record_miss(size_t channel, uint8_t miss_limit)
{
if (channel >= UART_MUX_MAX_CHANNELS) {
return;
}
if (!dcdc_get_state(channel)) {
return;
}
if (!s_watchdog_armed[channel]) {
s_watchdog_armed[channel] = true;
s_consecutive_miss[channel] = 0;
}
if (s_total_miss_count[channel] < UINT32_MAX) {
++s_total_miss_count[channel];
}
if (s_consecutive_miss[channel] < UINT8_MAX) {
++s_consecutive_miss[channel];
}
if (miss_limit == 0) {
miss_limit = 1;
}
if (s_consecutive_miss[channel] >= miss_limit) {
uart_mux_restart_channel(channel);
}
}
static bool uart_mux_extract_numeric_field(const uint8_t *data,
size_t length,
const char *key,
int *out_value)
{
if (!data || !key || !out_value) {
return false;
}
const size_t key_len = strlen(key);
if (key_len == 0) {
return false;
}
for (size_t i = 0; i < length; ++i) {
if (data[i] != '"') {
continue;
}
size_t j = i + 1;
if (j + key_len >= length) {
break;
}
if (memcmp(&data[j], key, key_len) != 0) {
continue;
}
j += key_len;
if (j >= length || data[j] != '"') {
continue;
}
++j;
while (j < length && isspace((unsigned char)data[j])) {
++j;
}
if (j >= length || data[j] != ':') {
continue;
}
++j;
while (j < length && isspace((unsigned char)data[j])) {
++j;
}
bool negative = false;
if (j < length && (data[j] == '-' || data[j] == '+')) {
negative = (data[j] == '-');
++j;
}
bool has_digit = false;
int value = 0;
while (j < length && isdigit((unsigned char)data[j])) {
has_digit = true;
value = value * 10 + (data[j] - '0');
++j;
}
if (has_digit) {
*out_value = negative ? -value : value;
return true;
}
}
return false;
}
static void uart_mux_decode_status_payload(const uint8_t *data,
size_t length,
bool *hb_ack,
bool *vpn_ok,
bool *app_ok)
{
int value = 0;
if (hb_ack) {
bool found = uart_mux_extract_numeric_field(data, length, "hb", &value);
*hb_ack = found && value == 2;
}
if (vpn_ok) {
bool found = uart_mux_extract_numeric_field(data, length, "VPN", &value);
*vpn_ok = found && value != 0;
}
if (app_ok) {
bool found = uart_mux_extract_numeric_field(data, length, "APP", &value);
*app_ok = found && value != 0;
}
}
// Перевіряє, чи містить буфер відповідь {"hb":2} від Raspberry Pi.
static bool uart_mux_contains_hb_ack(const uint8_t *data, size_t length)
{
bool ack = false;
uart_mux_decode_status_payload(data, length, &ack, NULL, NULL);
return ack;
}
// Перемикає апаратний мультиплексор на вказаний канал під захистом мьютекса,
// оновлюючи таймстемп останнього heartbeat для контролю watchdog.
static esp_err_t uart_mux_select_locked(size_t channel)
{
if (channel >= UART_MUX_MAX_CHANNELS) {
return ESP_ERR_INVALID_ARG;
}
if (channel == s_active_channel) {
return ESP_OK;
}
for (int bit = 0; bit < 3; ++bit) {
int level = (channel >> bit) & 0x1;
ESP_RETURN_ON_ERROR(gpio_set_level(s_select_pins[bit], level), TAG,
"GPIO set failed");
}
s_active_channel = channel;
s_last_heartbeat_us[channel] = esp_timer_get_time();
return ESP_OK;
}
// Періодично опитує всі канали, щоб зчитати heartbeat та перезапускає DCDC,
// якщо канал «мовчить» довше за CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC.
static void uart_mux_watchdog_task(void *arg)
{
const TickType_t poll_interval = pdMS_TO_TICKS(10000);
const TickType_t read_timeout = pdMS_TO_TICKS(300);
const int64_t timeout_us = (int64_t)CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC * 1000000LL;
uint8_t buffer[CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN];
const watch_config_t *cfg = watch_config_get();
TickType_t start_delay = pdMS_TO_TICKS(cfg->heartbeat_start_delay_sec * 1000U);
if (start_delay > 0) {
vTaskDelay(start_delay);
}
while (true) {
vTaskDelay(poll_interval);
const watch_config_t *cfg = watch_config_get();
if (!cfg->heartbeat_monitor_enabled) {
continue;
}
uint8_t miss_limit = uart_mux_get_miss_limit_from_config();
int64_t now = esp_timer_get_time();
for (size_t ch = 0; ch < UART_MUX_MAX_CHANNELS; ++ch) {
if (!dcdc_get_state(ch)) {
s_watchdog_armed[ch] = false;
s_consecutive_miss[ch] = 0;
continue;
}
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(20)) == pdTRUE) {
if (uart_mux_select_locked(ch) == ESP_OK) {
int read = uart_read_bytes(CONFIG_WATCH_UART_PORT,
buffer,
sizeof(buffer),
read_timeout);
if (read > 0) {
bool ack = false;
bool vpn_ok = false;
bool app_ok = false;
uart_mux_decode_status_payload(buffer, read, &ack, &vpn_ok, &app_ok);
ESP_LOGI(TAG, "UART0 RX CH%u (%d байт)%s",
(unsigned)ch, read, ack ? " [HB ACK]" : "");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, read, ESP_LOG_INFO);
if (ack) {
s_last_heartbeat_us[ch] = now;
s_consecutive_miss[ch] = 0;
s_watchdog_armed[ch] = true;
ws2812_status_set_service_state(ch, vpn_ok, app_ok);
} else {
uart_mux_record_miss(ch, miss_limit);
}
} else {
uart_mux_record_miss(ch, miss_limit);
ESP_LOGD(TAG, "UART0 RX CH%u: немає відповіді у watchdog (%u)",
(unsigned)ch, (unsigned)s_consecutive_miss[ch]);
}
}
xSemaphoreGive(s_mutex);
}
if (dcdc_get_state(ch) && s_watchdog_armed[ch] && s_consecutive_miss[ch] >= miss_limit) {
uart_mux_restart_channel(ch);
}
if (dcdc_get_state(ch) && s_last_heartbeat_us[ch] > 0 &&
(now - s_last_heartbeat_us[ch]) > timeout_us) {
uart_mux_restart_channel(ch);
}
}
}
}
#endif // CONFIG_WATCH_UART_MUX_ENABLED
// Налаштовує GPIO-вибірники, драйвер UART та створює watchdog-задачу для
// мультиплексора; повторний виклик просто повертає ESP_OK.
esp_err_t uart_mux_init(void)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (s_initialized) {
return ESP_OK;
}
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pin_bit_mask = 0,
};
for (int i = 0; i < 3; ++i) {
io_conf.pin_bit_mask = 1ULL << s_select_pins[i];
ESP_RETURN_ON_ERROR(gpio_config(&io_conf), TAG, "GPIO config failed");
ESP_RETURN_ON_ERROR(gpio_set_level(s_select_pins[i], 0), TAG, "GPIO init level failed");
}
s_active_channel = 0;
uart_config_t uart_cfg = {
.baud_rate = CONFIG_WATCH_UART_BAUD,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
ESP_RETURN_ON_ERROR(uart_param_config(CONFIG_WATCH_UART_PORT, &uart_cfg), TAG, "UART config failed");
ESP_RETURN_ON_ERROR(uart_set_pin(CONFIG_WATCH_UART_PORT,
CONFIG_WATCH_UART_TX_GPIO,
CONFIG_WATCH_UART_RX_GPIO,
UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE),
TAG, "UART pin assign failed");
ESP_RETURN_ON_ERROR(uart_driver_install(CONFIG_WATCH_UART_PORT,
CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN * 2,
CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN * 2,
0, NULL, 0),
TAG, "UART driver install failed");
s_mutex = xSemaphoreCreateMutex();
ESP_RETURN_ON_FALSE(s_mutex, ESP_ERR_NO_MEM, TAG, "mutex alloc failed");
int64_t now = esp_timer_get_time();
for (size_t ch = 0; ch < UART_MUX_MAX_CHANNELS; ++ch) {
s_last_heartbeat_us[ch] = now;
s_consecutive_miss[ch] = 0;
s_watchdog_armed[ch] = false;
s_total_miss_count[ch] = 0;
s_restart_count[ch] = 0;
}
if (xTaskCreate(uart_mux_watchdog_task, "uart_mux_wd", 4096, NULL, 5, &s_watchdog_task) != pdPASS) {
return ESP_ERR_NO_MEM;
}
s_initialized = true;
ESP_LOGI(TAG, "UART мультиплексор активовано, каналів: %d", UART_MUX_MAX_CHANNELS);
return ESP_OK;
#endif
}
// Повертає ознаку ініціалізації модулю UART мультиплексора.
bool uart_mux_ready(void)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
return s_initialized;
#else
return false;
#endif
}
// Кількість доступних каналів, визначених у конфігурації.
size_t uart_mux_channel_count(void)
{
return CONFIG_WATCH_UART_MUX_CHANNELS;
}
// Перемикається на канал, передає буфер даних через загальний UART та
// захищає доступ до шини мьютексом, щоб уникнути гонок між задачами.
esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (channel >= UART_MUX_MAX_CHANNELS) {
return ESP_ERR_INVALID_ARG;
}
if (!data || length == 0) {
return ESP_OK;
}
if (xSemaphoreTake(s_mutex, timeout) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t err = uart_mux_select_locked(channel);
if (err == ESP_OK) {
int written = uart_write_bytes(CONFIG_WATCH_UART_PORT, (const char *)data, length);
if (written < 0 || (size_t)written != length) {
err = ESP_FAIL;
} else {
if (uart_wait_tx_done(CONFIG_WATCH_UART_PORT, timeout) != ESP_OK) {
err = ESP_ERR_TIMEOUT;
}
}
}
xSemaphoreGive(s_mutex);
return err;
#endif
}
// Читає дані з вказаного каналу, оновлюючи heartbeat під час успішного
// зчитування, і повертає кількість байтів через out_length.
esp_err_t uart_mux_read(size_t channel, uint8_t *buffer, size_t buffer_size, size_t *out_length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
return ESP_ERR_NOT_SUPPORTED;
#else
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (channel >= UART_MUX_MAX_CHANNELS || !buffer || buffer_size == 0) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_mutex, timeout) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t err = uart_mux_select_locked(channel);
if (err == ESP_OK) {
int read = uart_read_bytes(CONFIG_WATCH_UART_PORT, buffer, buffer_size, timeout);
if (read < 0) {
err = ESP_FAIL;
} else {
if (out_length) {
*out_length = (size_t)read;
}
if (read > 0) {
s_last_heartbeat_us[channel] = esp_timer_get_time();
}
}
}
xSemaphoreGive(s_mutex);
return err;
#endif
}
void uart_mux_process_rx(size_t channel, const uint8_t *data, size_t length)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
if (!s_initialized || channel >= UART_MUX_MAX_CHANNELS || !data || length == 0) {
return;
}
bool ack = false;
bool vpn_ok = false;
bool app_ok = false;
uart_mux_decode_status_payload(data, length, &ack, &vpn_ok, &app_ok);
int64_t now = esp_timer_get_time();
if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
s_last_heartbeat_us[channel] = now;
if (ack) {
s_consecutive_miss[channel] = 0;
s_watchdog_armed[channel] = true;
}
xSemaphoreGive(s_mutex);
}
if (ack) {
ws2812_status_set_service_state(channel, vpn_ok, app_ok);
} else {
uart_mux_record_miss(channel, uart_mux_get_miss_limit_from_config());
}
#else
(void)channel;
(void)data;
(void)length;
#endif
}
void uart_mux_report_miss(size_t channel)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
if (!s_initialized) {
return;
}
uart_mux_record_miss(channel, uart_mux_get_miss_limit_from_config());
#else
(void)channel;
#endif
}
void uart_mux_get_channel_stats(size_t channel, uart_mux_channel_stats_t *out_stats)
{
if (!out_stats) {
return;
}
#if CONFIG_WATCH_UART_MUX_ENABLED
if (!s_initialized || channel >= UART_MUX_MAX_CHANNELS) {
out_stats->missed_heartbeats = 0;
out_stats->restart_count = 0;
return;
}
out_stats->missed_heartbeats = s_total_miss_count[channel];
out_stats->restart_count = s_restart_count[channel];
#else
(void)channel;
out_stats->missed_heartbeats = 0;
out_stats->restart_count = 0;
#endif
}

35
main/uart_mux.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* Developed by TComLab
* Version: v0.1
* Date: 2025-12-15
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
typedef struct {
uint32_t missed_heartbeats;
uint32_t restart_count;
} uart_mux_channel_stats_t;
// Ініціалізує апаратний мультиплексор UART: GPIO, UART драйвер та watchdog.
esp_err_t uart_mux_init(void);
// Повертає true, якщо драйвер готовий приймати виклики.
bool uart_mux_ready(void);
// Кількість доступних каналів мультиплексора.
size_t uart_mux_channel_count(void);
// Відправляє буфер даних на вказаний канал з тайм-аутом очікування мьютекса.
esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, TickType_t timeout);
// Зчитує дані з каналу, повертаючи кількість байтів через out_length.
esp_err_t uart_mux_read(size_t channel, uint8_t *buffer, size_t buffer_size, size_t *out_length, TickType_t timeout);
// Повідомляє драйверу про дані, зчитані поза watchdog-ом (для оновлення станів).
void uart_mux_process_rx(size_t channel, const uint8_t *data, size_t length);
// Повідомляє про пропущений heartbeat (використовується основним циклом).
void uart_mux_report_miss(size_t channel);
// Повертає статистику по каналу (кількість пропущених відповідей та рестартів).
void uart_mux_get_channel_stats(size_t channel, uart_mux_channel_stats_t *out_stats);

View File

@@ -11,12 +11,20 @@
#include "dcdc_controller.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "ws2812_status.h"
#include "watch_config.h"
#include "esp_system.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/soc.h"
#define CLI_LINE_MAX_LEN 128
#define CLI_QUEUE_LEN 8
@@ -24,6 +32,9 @@
#ifndef CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CONFIG_TINYUSB_CDC_RX_BUFSIZE 64
#endif
#ifndef CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN
#define CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN 128
#endif
typedef struct {
size_t length;
@@ -39,6 +50,10 @@ static bool s_host_ready;
static void usb_cli_task(void *arg);
static void usb_cli_process_line(char *line);
static void usb_cli_prompt(void);
static void usb_cli_enter_bootloader(void);
static void usb_cli_handle_config(char *args);
static void usb_cli_print_config(void);
static void usb_cli_handle_reset(void);
static void usb_cli_write_raw(const char *data, size_t len)
{
@@ -106,10 +121,16 @@ static void usb_cli_print_status(void)
usb_cli_printf("\r\nСтан каналів:\r\n");
for (size_t i = 0; i < dcdc_channel_count(); ++i) {
dcdc_channel_t ch = (dcdc_channel_t)i;
usb_cli_printf(" - CH%u: %s (GPIO %d)\r\n",
uart_mux_channel_stats_t stats = {0};
if (uart_mux_ready()) {
uart_mux_get_channel_stats(i, &stats);
}
usb_cli_printf(" - CH%u: %s (GPIO %d) | miss=%u restart=%u\r\n",
(unsigned)i,
dcdc_get_state(ch) ? "ON" : "OFF",
(int)dcdc_get_gpio(ch));
(int)dcdc_get_gpio(ch),
(unsigned)stats.missed_heartbeats,
(unsigned)stats.restart_count);
}
}
@@ -118,6 +139,18 @@ static void usb_cli_prompt(void)
usb_cli_write_raw(PROMPT, strlen(PROMPT));
}
static void usb_cli_enter_bootloader(void)
{
usb_cli_printf("\r\nПерехід у режим прошивки esptool...\r\n"
"Після перезавантаження з’явиться ROM-порт USB CDC/Serial.\r\n"
"Запустіть esptool.py або idf.py flash та прошийте пристрій. "
"Для виходу з bootloader виконайте 'esptool.py --after hard_reset reset' або перезавантажте живлення.\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
tinyusb_driver_uninstall();
REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
esp_restart();
}
static void usb_cli_handle_switch(const char *cmd, char *args)
{
dcdc_channel_t channel;
@@ -140,8 +173,87 @@ static void usb_cli_handle_switch(const char *cmd, char *args)
if (err != ESP_OK) {
usb_cli_printf("\r\nПомилка керування каналом: %s\r\n", esp_err_to_name(err));
} else {
usb_cli_printf("\r\nКанал %d -> %s\r\n", channel,
dcdc_get_state(channel) ? "ON" : "OFF");
bool state = dcdc_get_state(channel);
usb_cli_printf("\r\nКанал %d -> %s\r\n", channel, state ? "ON" : "OFF");
ws2812_status_set_channel_state(channel, state);
}
}
static void usb_cli_print_measurement(size_t channel, const ina226_reading_t *reading)
{
usb_cli_printf("\r\nCH%u: %.2f В, %.3f А, %.3f Вт",
(unsigned)channel, reading->voltage_v, reading->current_a, reading->power_w);
}
static void usb_cli_handle_sense(char *args)
{
if (!ina226_monitor_ready()) {
usb_cli_printf("\r\nМоніторинг INA226 недоступний\r\n");
return;
}
ina226_reading_t reading = {0};
if (ina226_monitor_sample(&reading) == ESP_OK) {
usb_cli_print_measurement(0, &reading);
} else {
usb_cli_printf("\r\nНе вдалося зчитати дані з INA226\r\n");
}
}
static void usb_cli_handle_uart(char *args)
{
if (!uart_mux_ready()) {
usb_cli_printf("\r\nUART мультиплексор недоступний\r\n");
return;
}
char *ctx = NULL;
char *action = strtok_r(args, " ", &ctx);
if (!action) {
usb_cli_printf("\r\nUART usage: uart send <channel> <text> | uart read <channel> [len]\r\n");
return;
}
char *channel_str = strtok_r(NULL, " ", &ctx);
dcdc_channel_t channel;
if (!usb_cli_parse_channel(channel_str, &channel)) {
usb_cli_printf("\r\nНевірний номер каналу\r\n");
return;
}
if (strcasecmp(action, "send") == 0) {
char *payload = ctx;
if (!payload || *payload == '\0') {
usb_cli_printf("\r\nНемає даних для відправлення\r\n");
return;
}
size_t len = strlen(payload);
if (uart_mux_write(channel, (const uint8_t *)payload, len, pdMS_TO_TICKS(200)) == ESP_OK) {
usb_cli_printf("\r\nВідправлено %u байт\r\n", (unsigned)len);
} else {
usb_cli_printf("\r\nПомилка відправлення UART\r\n");
}
} else if (strcasecmp(action, "read") == 0) {
char *len_str = ctx ? strtok_r(NULL, " ", &ctx) : NULL;
size_t req_len = len_str ? strtoul(len_str, NULL, 10) : CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
if (req_len == 0) {
req_len = CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
}
if (req_len > 256) {
req_len = 256;
}
uint8_t buffer[256];
size_t received = 0;
if (uart_mux_read(channel, buffer, req_len, &received, pdMS_TO_TICKS(200)) == ESP_OK && received > 0) {
usb_cli_printf("\r\nCH%u UART (%u байт): ", (unsigned)channel, (unsigned)received);
for (size_t i = 0; i < received; ++i) {
usb_cli_write_raw((char *)&buffer[i], 1);
}
} else {
usb_cli_printf("\r\nДані відсутні\r\n");
}
} else {
usb_cli_printf("\r\nНевірна дія '%s'\r\n", action);
}
}
@@ -161,22 +273,135 @@ static void usb_cli_process_line(char *line)
if (strcasecmp(cmd, "help") == 0) {
usb_cli_printf(
"\r\nДоступні команди:\r\n"
" help - показати довідку\r\n"
" status - стан всіх каналів\r\n"
" enable <n> - увімкнути канал n\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n");
" help - показати цю довідку\r\n"
" status - показати стан усіх каналів DCDC\r\n"
" enable <n> - увімкнути канал n (0..4)\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n"
" sense - виміряти напругу/струм/потужність INA226\r\n"
" uart send <n> <msg> - надіслати текстове повідомлення в Raspberry Pi n\r\n"
" uart read <n> [len] - прочитати до [len] байт відповіді від Raspberry Pi n\r\n"
" config show - показати збережені таймінги heartbeat/DCDC\r\n"
" config set hb_period <сек> - змінити інтервал відправки heartbeat\r\n"
" config set dcdc_off <сек> - задати тривалість вимкнення DCDC при рестарті\r\n"
" config set hb_start <сек> - налаштувати затримку перед стартом опитування після boot\r\n"
" config set hb_monitor <0|1> - увімкнути/вимкнути контроль відповіді heartbeat\r\n"
" config set hb_miss <шт> - кількість пропусків відповіді до рестарту каналу\r\n"
" reset - м'яко перезавантажити ESP32-S3\r\n"
" bootloader - перезавантажити ESP32-S3 у ROM bootloader для esptool\r\n");
} else if (strcasecmp(cmd, "status") == 0) {
usb_cli_print_status();
} else if (strcasecmp(cmd, "enable") == 0 ||
strcasecmp(cmd, "disable") == 0 ||
strcasecmp(cmd, "toggle") == 0) {
usb_cli_handle_switch(cmd, save_ptr);
} else if (strcasecmp(cmd, "sense") == 0) {
usb_cli_handle_sense(save_ptr);
} else if (strcasecmp(cmd, "uart") == 0) {
usb_cli_handle_uart(save_ptr);
} else if (strcasecmp(cmd, "config") == 0) {
usb_cli_handle_config(save_ptr);
} else if (strcasecmp(cmd, "reset") == 0) {
usb_cli_handle_reset();
} else if (strcasecmp(cmd, "bootloader") == 0) {
usb_cli_enter_bootloader();
} else {
usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
}
}
static void usb_cli_print_config(void)
{
const watch_config_t *cfg = watch_config_get();
usb_cli_printf(
"\r\nПоточні налаштування:\r\n"
" hb_period: %u с\r\n"
" dcdc_off: %u с\r\n"
" hb_start_delay: %u с\r\n"
" hb_monitor: %s\r\n"
" hb_miss_limit: %u зап.\r\n",
(unsigned)cfg->heartbeat_period_sec,
(unsigned)cfg->dcdc_restart_off_sec,
(unsigned)cfg->heartbeat_start_delay_sec,
cfg->heartbeat_monitor_enabled ? "on" : "off",
(unsigned)cfg->heartbeat_miss_limit);
}
static void usb_cli_handle_config(char *args)
{
if (!args || *args == '\0') {
usb_cli_print_config();
return;
}
char *ctx = NULL;
char *action = strtok_r(args, " ", &ctx);
if (!action || strcasecmp(action, "show") == 0) {
usb_cli_print_config();
return;
}
if (strcasecmp(action, "set") != 0) {
usb_cli_printf("\r\nВикористання: config show | config set <hb_period|dcdc_off|hb_start|hb_monitor|hb_miss> <знач>\r\n");
return;
}
char *param = strtok_r(NULL, " ", &ctx);
char *value_str = strtok_r(NULL, " ", &ctx);
if (!param || !value_str) {
usb_cli_printf("\r\nВкажіть параметр і значення в секундах\r\n");
return;
}
uint32_t value = (uint32_t)strtoul(value_str, NULL, 10);
watch_config_t new_cfg = *watch_config_get();
if (strcasecmp(param, "hb_period") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_period_sec = value;
} else if (strcasecmp(param, "dcdc_off") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.dcdc_restart_off_sec = value;
} else if (strcasecmp(param, "hb_start") == 0 ||
strcasecmp(param, "hb_start_delay") == 0) {
if (value == 0) {
usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_start_delay_sec = value;
} else if (strcasecmp(param, "hb_monitor") == 0) {
if (value != 0 && value != 1) {
usb_cli_printf("\r\nhb_monitor приймає 0 або 1\r\n");
return;
}
new_cfg.heartbeat_monitor_enabled = (value != 0);
} else if (strcasecmp(param, "hb_miss") == 0 ||
strcasecmp(param, "hb_miss_limit") == 0) {
if (value == 0) {
usb_cli_printf("\r\nhb_miss має бути більше нуля\r\n");
return;
}
new_cfg.heartbeat_miss_limit = value;
} else {
usb_cli_printf("\r\nНевідомий параметр '%s'\r\n", param);
return;
}
esp_err_t err = watch_config_save(&new_cfg);
if (err == ESP_OK) {
usb_cli_printf("\r\nПараметр оновлено\r\n");
usb_cli_print_config();
} else {
usb_cli_printf("\r\nПомилка збереження конфігурації: %s\r\n", esp_err_to_name(err));
}
}
static void usb_cli_task(void *arg)
{
usb_cli_msg_t msg;
@@ -193,14 +418,17 @@ static void usb_cli_task(void *arg)
usb_cli_process_line(line);
line_len = 0;
}
usb_cli_write_raw("\r\n", 2);
usb_cli_prompt();
} else if (ch == 0x7F || ch == '\b') {
if (line_len > 0) {
line_len--;
usb_cli_write_raw("\b \b", 3);
}
} else if (isprint((unsigned char)ch)) {
if (line_len < CLI_LINE_MAX_LEN - 1) {
line[line_len++] = ch;
usb_cli_write_raw(&ch, 1);
} else {
usb_cli_printf("\r\nРядок занадто довгий\r\n");
line_len = 0;
@@ -294,3 +522,9 @@ esp_err_t usb_cdc_cli_init(void)
ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
return ESP_OK;
}
static void usb_cli_handle_reset(void)
{
usb_cli_printf("\r\nПерезавантаження пристрою...\r\n");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
}

66
main/usb_cdc_log.c Normal file
View File

@@ -0,0 +1,66 @@
#include "usb_cdc_log.h"
#include <stdio.h>
#include "esp_check.h"
#include "esp_log.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
static const char *TAG = "usb_log";
static bool s_initialized;
static bool s_host_ready;
static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
(void)itf;
s_host_ready = event->line_state_changed_data.dtr;
if (s_host_ready) {
const char banner[] = "\r\nwatch-watch log over USB CDC\r\n";
tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (const uint8_t *)banner, sizeof(banner) - 1);
tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
}
}
bool usb_cdc_log_ready(void)
{
return s_host_ready;
}
esp_err_t usb_cdc_log_init(void)
{
if (s_initialized) {
return ESP_OK;
}
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
ESP_RETURN_ON_ERROR(tinyusb_driver_install(&tusb_cfg), TAG, "TinyUSB init failed");
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = usb_cdc_line_state_changed_callback,
.callback_line_coding_changed = NULL,
};
ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
s_initialized = true;
ESP_LOGI(TAG, "USB CDC лог ініціалізовано (перенаправлення ESP_LOG вимкнено)");
return ESP_OK;
}

15
main/usb_cdc_log.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include "esp_err.h"
/**
* @brief Ініціалізує TinyUSB CDC та перенаправляє ESP_LOG у USB.
*/
esp_err_t usb_cdc_log_init(void);
/**
* @brief Чи встановив хост DTR і готовий приймати лог.
*/
bool usb_cdc_log_ready(void);

141
main/watch_config.c Normal file
View File

@@ -0,0 +1,141 @@
#include "watch_config.h"
#include <stdbool.h>
#include "esp_check.h"
#include "esp_log.h"
#include "nvs.h"
#include "nvs_flash.h"
#define WATCH_CONFIG_NAMESPACE "watchcfg"
#define KEY_HB_PERIOD "hb_period"
#define KEY_DCDC_OFF "dcdc_off"
#define KEY_HB_START "hb_start"
#define KEY_HB_MON "hb_mon"
#define KEY_HB_MISS "hb_miss"
#define DEFAULT_HB_PERIOD_SEC 4U
#define DEFAULT_DCDC_OFF_SEC 2U
#define DEFAULT_HB_START_SEC 2U
#define DEFAULT_HB_MISS_LIMIT 3U
static watch_config_t s_config = {
.heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC,
.dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC,
.heartbeat_start_delay_sec = DEFAULT_HB_START_SEC,
.heartbeat_monitor_enabled = true,
.heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT,
};
static const char *TAG = "watch_cfg";
static bool s_initialized;
static void watch_config_apply_defaults(void)
{
s_config.heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC;
s_config.dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC;
s_config.heartbeat_start_delay_sec = DEFAULT_HB_START_SEC;
s_config.heartbeat_monitor_enabled = true;
s_config.heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT;
}
static void watch_config_clamp(watch_config_t *cfg)
{
if (!cfg->heartbeat_period_sec) {
cfg->heartbeat_period_sec = DEFAULT_HB_PERIOD_SEC;
}
if (!cfg->dcdc_restart_off_sec) {
cfg->dcdc_restart_off_sec = DEFAULT_DCDC_OFF_SEC;
}
if (!cfg->heartbeat_start_delay_sec) {
cfg->heartbeat_start_delay_sec = DEFAULT_HB_START_SEC;
}
cfg->heartbeat_monitor_enabled = cfg->heartbeat_monitor_enabled ? true : false;
if (!cfg->heartbeat_miss_limit) {
cfg->heartbeat_miss_limit = DEFAULT_HB_MISS_LIMIT;
}
}
esp_err_t watch_config_init(void)
{
if (s_initialized) {
return ESP_OK;
}
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_RETURN_ON_ERROR(nvs_flash_erase(), TAG, "NVS erase failed");
err = nvs_flash_init();
}
ESP_RETURN_ON_ERROR(err, TAG, "NVS init failed");
watch_config_apply_defaults();
nvs_handle_t handle = 0;
err = nvs_open(WATCH_CONFIG_NAMESPACE, NVS_READWRITE, &handle);
ESP_RETURN_ON_ERROR(err, TAG, "NVS open failed");
uint32_t value = 0;
if (nvs_get_u32(handle, KEY_HB_PERIOD, &value) == ESP_OK && value > 0) {
s_config.heartbeat_period_sec = value;
}
if (nvs_get_u32(handle, KEY_DCDC_OFF, &value) == ESP_OK && value > 0) {
s_config.dcdc_restart_off_sec = value;
}
if (nvs_get_u32(handle, KEY_HB_START, &value) == ESP_OK && value > 0) {
s_config.heartbeat_start_delay_sec = value;
}
uint8_t hb_mon = 1;
if (nvs_get_u8(handle, KEY_HB_MON, &hb_mon) == ESP_OK) {
s_config.heartbeat_monitor_enabled = (hb_mon != 0);
}
if (nvs_get_u32(handle, KEY_HB_MISS, &value) == ESP_OK && value > 0) {
s_config.heartbeat_miss_limit = value;
}
nvs_close(handle);
s_initialized = true;
return ESP_OK;
}
const watch_config_t *watch_config_get(void)
{
return &s_config;
}
esp_err_t watch_config_save(const watch_config_t *cfg)
{
if (!cfg) {
return ESP_ERR_INVALID_ARG;
}
watch_config_t tmp = *cfg;
watch_config_clamp(&tmp);
nvs_handle_t handle = 0;
ESP_RETURN_ON_ERROR(nvs_open(WATCH_CONFIG_NAMESPACE, NVS_READWRITE, &handle),
TAG, "NVS open failed");
esp_err_t err = nvs_set_u32(handle, KEY_HB_PERIOD, tmp.heartbeat_period_sec);
if (err == ESP_OK) {
err = nvs_set_u32(handle, KEY_DCDC_OFF, tmp.dcdc_restart_off_sec);
}
if (err == ESP_OK) {
err = nvs_set_u32(handle, KEY_HB_START, tmp.heartbeat_start_delay_sec);
}
if (err == ESP_OK) {
err = nvs_set_u8(handle, KEY_HB_MON, tmp.heartbeat_monitor_enabled ? 1 : 0);
}
if (err == ESP_OK) {
err = nvs_set_u32(handle, KEY_HB_MISS, tmp.heartbeat_miss_limit);
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
if (err != ESP_OK) {
return err;
}
s_config = tmp;
return ESP_OK;
}

18
main/watch_config.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
typedef struct {
uint32_t heartbeat_period_sec;
uint32_t dcdc_restart_off_sec;
uint32_t heartbeat_start_delay_sec;
bool heartbeat_monitor_enabled;
uint32_t heartbeat_miss_limit;
} watch_config_t;
esp_err_t watch_config_init(void);
const watch_config_t *watch_config_get(void);
esp_err_t watch_config_save(const watch_config_t *cfg);

367
main/ws2812_status.c Normal file
View File

@@ -0,0 +1,367 @@
#include "ws2812_status.h"
#include "dcdc_controller.h"
#include "led_strip.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/semphr.h"
#include "sdkconfig.h"
#ifndef CONFIG_WATCH_WS2812_LED_COUNT
#define CONFIG_WATCH_WS2812_LED_COUNT 5
#endif
#ifndef CONFIG_WATCH_WS2812_GPIO
#define CONFIG_WATCH_WS2812_GPIO 8
#endif
#ifndef CONFIG_WATCH_WS2812_RMT_RESOLUTION
#define CONFIG_WATCH_WS2812_RMT_RESOLUTION (10 * 1000 * 1000)
#endif
#define WS2812_STATUS_GPIO ((gpio_num_t)CONFIG_WATCH_WS2812_GPIO)
#define WS2812_STATUS_RESOLUTION_HZ CONFIG_WATCH_WS2812_RMT_RESOLUTION
#define WS2812_ANIM_REFRESH pdMS_TO_TICKS(100)
#define WS2812_ALERT_BLINK_PERIOD pdMS_TO_TICKS(200)
#define WS2812_ALERT_GAP_PERIOD pdMS_TO_TICKS(2000)
#define WS2812_VPN_ALERT_BLINKS 2
#define WS2812_APP_ALERT_BLINKS 3
#define WS2812_BRIGHTNESS_PERCENT 5
#define WS2812_VPN_SECTION_TICKS (WS2812_ALERT_BLINK_PERIOD * 2U * WS2812_VPN_ALERT_BLINKS)
#define WS2812_APP_SECTION_TICKS (WS2812_ALERT_BLINK_PERIOD * 2U * WS2812_APP_ALERT_BLINKS)
static const char *TAG = "ws2812";
static led_strip_handle_t s_strip;
static bool s_channel_enabled[WS2812_STATUS_LED_COUNT];
static bool s_vpn_state[WS2812_STATUS_LED_COUNT];
static bool s_app_state[WS2812_STATUS_LED_COUNT];
static SemaphoreHandle_t s_ws_lock;
static TimerHandle_t s_animation_timer;
static bool s_error_state;
static bool s_startup_hold;
static TickType_t s_startup_expire_tick;
static TickType_t s_alert_cycle_epoch;
static size_t s_active_vpn_alerts;
static size_t s_active_app_alerts;
static esp_err_t ws2812_status_apply(void);
static void ws2812_animation_timer_cb(TimerHandle_t timer);
static void ws2812_status_compute_color(size_t index,
TickType_t now_ticks,
uint8_t *r,
uint8_t *g,
uint8_t *b);
static bool ws2812_recalculate_alert_counts(void);
static uint8_t ws2812_apply_brightness(uint8_t component);
static void ws2812_animation_timer_cb(TimerHandle_t timer)
{
(void)timer;
ws2812_status_apply();
}
static bool ws2812_startup_hold_active(TickType_t now_ticks)
{
if (!s_startup_hold) {
return false;
}
if (now_ticks >= s_startup_expire_tick) {
s_startup_hold = false;
return false;
}
return true;
}
static void ws2812_status_compute_color(size_t index,
TickType_t now_ticks,
uint8_t *r,
uint8_t *g,
uint8_t *b)
{
if (!r || !g || !b || index >= WS2812_STATUS_LED_COUNT) {
return;
}
*r = 0;
*g = 0;
*b = 0;
if (s_error_state) {
*r = 40;
return;
}
if (ws2812_startup_hold_active(now_ticks)) {
*g = 40;
return;
}
if (!s_channel_enabled[index]) {
return;
}
const bool vpn_alert = !s_vpn_state[index];
const bool app_alert = !s_app_state[index];
if (!vpn_alert && !app_alert) {
return;
}
const bool vpn_global = (s_active_vpn_alerts > 0);
const bool app_global = (s_active_app_alerts > 0);
TickType_t cycle_ticks = 0;
if (vpn_global) {
cycle_ticks += WS2812_VPN_SECTION_TICKS + WS2812_ALERT_GAP_PERIOD;
}
if (app_global) {
cycle_ticks += WS2812_APP_SECTION_TICKS + WS2812_ALERT_GAP_PERIOD;
}
if (cycle_ticks == 0) {
return;
}
TickType_t blink_period = WS2812_ALERT_BLINK_PERIOD ? WS2812_ALERT_BLINK_PERIOD : 1;
TickType_t cycle_pos = (now_ticks - s_alert_cycle_epoch) % cycle_ticks;
if (vpn_global) {
if (cycle_pos < WS2812_VPN_SECTION_TICKS) {
if (vpn_alert) {
const bool on = ((cycle_pos / blink_period) % 2U) == 0U;
if (on) {
*r = 90;
}
}
return;
}
cycle_pos -= WS2812_VPN_SECTION_TICKS;
if (cycle_pos < WS2812_ALERT_GAP_PERIOD) {
return;
}
cycle_pos -= WS2812_ALERT_GAP_PERIOD;
}
if (app_global) {
if (cycle_pos < WS2812_APP_SECTION_TICKS) {
if (app_alert) {
const bool on = ((cycle_pos / blink_period) % 2U) == 0U;
if (on) {
*r = 80;
*g = 80;
}
}
return;
}
cycle_pos -= WS2812_APP_SECTION_TICKS;
if (cycle_pos < WS2812_ALERT_GAP_PERIOD) {
return;
}
}
}
static esp_err_t ws2812_status_apply(void)
{
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
if (!s_ws_lock) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_ws_lock, portMAX_DELAY) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
const TickType_t now_ticks = xTaskGetTickCount();
esp_err_t err = ESP_OK;
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT && err == ESP_OK; ++i) {
uint8_t r = 0, g = 0, b = 0;
ws2812_status_compute_color(i, now_ticks, &r, &g, &b);
err = led_strip_set_pixel(s_strip,
i,
ws2812_apply_brightness(g),
ws2812_apply_brightness(r),
ws2812_apply_brightness(b));
}
if (err == ESP_OK) {
err = led_strip_refresh(s_strip);
}
xSemaphoreGive(s_ws_lock);
return err;
}
esp_err_t ws2812_status_init(void)
{
if (s_strip) {
return ESP_OK;
}
led_strip_config_t strip_config = {
.strip_gpio_num = WS2812_STATUS_GPIO,
.max_leds = WS2812_STATUS_LED_COUNT,
.led_model = LED_MODEL_WS2812,
.flags.invert_out = false,
};
led_strip_rmt_config_t rmt_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = WS2812_STATUS_RESOLUTION_HZ,
.flags.with_dma = false,
};
ESP_RETURN_ON_ERROR(
led_strip_new_rmt_device(&strip_config, &rmt_cfg, &s_strip),
TAG,
"Не вдалося створити RMT пристрій для WS2812");
ESP_RETURN_ON_ERROR(led_strip_clear(s_strip), TAG, "clear fail");
s_ws_lock = xSemaphoreCreateMutex();
ESP_RETURN_ON_FALSE(s_ws_lock, ESP_ERR_NO_MEM, TAG, "mutex alloc failed");
const TickType_t now = xTaskGetTickCount();
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
s_channel_enabled[i] = false;
s_vpn_state[i] = true;
s_app_state[i] = true;
}
s_error_state = false;
s_startup_hold = false;
s_startup_expire_tick = 0;
s_alert_cycle_epoch = now;
(void)ws2812_recalculate_alert_counts();
if (!s_animation_timer) {
s_animation_timer = xTimerCreate("ws2812_anim",
WS2812_ANIM_REFRESH,
pdTRUE,
NULL,
ws2812_animation_timer_cb);
ESP_RETURN_ON_FALSE(s_animation_timer, ESP_ERR_NO_MEM, TAG, "anim timer alloc failed");
ESP_RETURN_ON_FALSE(xTimerStart(s_animation_timer, 0) == pdPASS,
ESP_FAIL,
TAG,
"anim timer start failed");
}
(void)ws2812_recalculate_alert_counts();
return ws2812_status_apply();
}
esp_err_t ws2812_status_set_channel_state(size_t channel, bool enabled)
{
if (channel >= WS2812_STATUS_LED_COUNT) {
return ESP_ERR_INVALID_ARG;
}
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
s_channel_enabled[channel] = enabled;
if (!enabled) {
s_vpn_state[channel] = true;
s_app_state[channel] = true;
}
return ws2812_status_apply();
}
esp_err_t ws2812_status_set_error(bool has_error)
{
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
s_error_state = has_error;
return ws2812_status_apply();
}
esp_err_t ws2812_status_set_service_state(size_t channel, bool vpn_ok, bool app_ok)
{
if (channel >= WS2812_STATUS_LED_COUNT) {
return ESP_ERR_INVALID_ARG;
}
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
bool changed = false;
if (s_vpn_state[channel] != vpn_ok) {
s_vpn_state[channel] = vpn_ok;
changed = true;
}
if (s_app_state[channel] != app_ok) {
s_app_state[channel] = app_ok;
changed = true;
}
if (changed) {
(void)ws2812_recalculate_alert_counts();
s_alert_cycle_epoch = xTaskGetTickCount();
}
return ws2812_status_apply();
}
esp_err_t ws2812_status_refresh_from_dcdc(void)
{
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
const size_t available_channels = dcdc_channel_count();
const size_t count = available_channels < WS2812_STATUS_LED_COUNT
? available_channels
: WS2812_STATUS_LED_COUNT;
for (size_t i = 0; i < count; ++i) {
s_channel_enabled[i] = dcdc_get_state(i);
}
for (size_t i = count; i < WS2812_STATUS_LED_COUNT; ++i) {
s_channel_enabled[i] = false;
s_vpn_state[i] = true;
s_app_state[i] = true;
}
(void)ws2812_recalculate_alert_counts();
s_alert_cycle_epoch = xTaskGetTickCount();
return ws2812_status_apply();
}
esp_err_t ws2812_status_set_startup_hold(uint32_t duration_ms)
{
if (!s_strip) {
return ESP_ERR_INVALID_STATE;
}
if (duration_ms == 0) {
s_startup_hold = false;
s_startup_expire_tick = 0;
return ws2812_status_apply();
}
TickType_t duration_ticks = pdMS_TO_TICKS(duration_ms);
if (duration_ticks == 0) {
duration_ticks = 1;
}
s_startup_hold = true;
s_startup_expire_tick = xTaskGetTickCount() + duration_ticks;
return ws2812_status_apply();
}
static bool ws2812_recalculate_alert_counts(void)
{
size_t vpn = 0;
size_t app = 0;
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
if (!s_channel_enabled[i]) {
continue;
}
if (!s_vpn_state[i]) {
++vpn;
}
if (!s_app_state[i]) {
++app;
}
}
const bool changed = (vpn != s_active_vpn_alerts) || (app != s_active_app_alerts);
s_active_vpn_alerts = vpn;
s_active_app_alerts = app;
return changed;
}
static uint8_t ws2812_apply_brightness(uint8_t component)
{
// Scale brightness down (component * 20%) with rounding to nearest.
const uint16_t scaled = (component * WS2812_BRIGHTNESS_PERCENT + 50U) / 100U;
return (uint8_t)scaled;
}

21
main/ws2812_status.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#include "sdkconfig.h"
#ifndef CONFIG_WATCH_WS2812_LED_COUNT
#define CONFIG_WATCH_WS2812_LED_COUNT 5
#endif
#define WS2812_STATUS_LED_COUNT CONFIG_WATCH_WS2812_LED_COUNT
esp_err_t ws2812_status_init(void);
esp_err_t ws2812_status_set_channel_state(size_t channel, bool enabled);
esp_err_t ws2812_status_set_error(bool has_error);
esp_err_t ws2812_status_set_service_state(size_t channel, bool vpn_ok, bool app_ok);
esp_err_t ws2812_status_set_startup_hold(uint32_t duration_ms);
esp_err_t ws2812_status_refresh_from_dcdc(void);

View File

@@ -0,0 +1 @@
28c6509a727ef74925b372ed404772aeedf11cce10b78c3f69b3c66799095e2d

View File

@@ -0,0 +1,54 @@
## 2.5.5
- Simplified the led_strip component dependency, the time of full build with ESP-IDF v5.3 can now be shorter.
## 2.5.4
- Inserted extra delay when initialize the SPI LED device, to ensure all LEDs are in the reset state correctly
## 2.5.3
- Extend reset time (280us) to support WS2812B-V5
## 2.5.2
- Added API reference doc (api.md)
## 2.5.0
- Enabled support for IDF4.4 and above
- with RMT backend only
- Added API `led_strip_set_pixel_hsv`
## 2.4.0
- Support configurable SPI mode to control leds
- recommend enabling DMA when using SPI mode
## 2.3.0
- Support configurable RMT channel size by setting `mem_block_symbols`
## 2.2.0
- Support for 4 components RGBW leds (SK6812):
- in led_strip_config_t new fields
led_pixel_format, controlling byte format (LED_PIXEL_FORMAT_GRB, LED_PIXEL_FORMAT_GRBW)
led_model, used to configure bit timing (LED_MODEL_WS2812, LED_MODEL_SK6812)
- new API led_strip_set_pixel_rgbw
- new interface type set_pixel_rgbw
## 2.1.0
- Support DMA feature, which offloads the CPU by a lot when it comes to drive a bunch of LEDs
- Support various RMT clock sources
- Acquire and release the power management lock before and after each refresh
- New driver flag: `invert_out` which can invert the led control signal by hardware
## 2.0.0
- Reimplemented the driver using the new RMT driver (`driver/rmt_tx.h`)
## 1.0.0
- Initial driver version, based on the legacy RMT driver (`driver/rmt.h`)

View File

@@ -0,0 +1 @@
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-21T16:14:59.641729+00:00", "files": [{"path": "CMakeLists.txt", "size": 1131, "hash": "527e1c93841dcf98e4e30767c0b55ec117e1af52638eb72a7aec23d25a31074b"}, {"path": "api.md", "size": 12932, "hash": "fa49f00d63cab790d2e3ffb1cc2436ec337fab336f55a8c2730aa1d923b655e4"}, {"path": "LICENSE", "size": 11358, "hash": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"}, {"path": "CHANGELOG.md", "size": 1469, "hash": "8f323462bb2f1c0159b2e2c5fef08628d47a7ff55ef9829221889c2486bbfad8"}, {"path": "idf_component.yml", "size": 335, "hash": "12ff580cba4f58c8c41cff96d0c81bc001d40d99ad15ba16afabce64261d4b8a"}, {"path": "README.md", "size": 4920, "hash": "f94c5b61f704ec6247450b3252e35b6130b5350edff560dd172f3a7345fac637"}, {"path": "Doxyfile", "size": 684, "hash": "f05e35d4108be9b46398fbd7c0cef9880c0e802abbe36a36a49c7a0ac4126826"}, {"path": "interface/led_strip_interface.h", "size": 2934, "hash": "5b7d0c326d0d0d9748830d4aec46d765400e1446055d4a1197c83111e937d74c"}, {"path": "include/led_strip_spi.h", "size": 1596, "hash": "d79f3a803834ff28930a32380a48afe0977de167d31d673589e98c2a3cf66e5f"}, {"path": "include/led_strip_types.h", "size": 1352, "hash": "13fb81ed62bcaa6d8686c5b095c45adede526bcba3980ad0f9341c38b4bedc96"}, {"path": "include/led_strip_rmt.h", "size": 1876, "hash": "d3d6f0880f7443c21b48c1715e524282688ce9743085b4d7358322f6714b82d2"}, {"path": "include/led_strip.h", "size": 3586, "hash": "f01e0f282fe3042b03381495c616eae211d0b75d04c64e29b3dc92721fbadc4f"}, {"path": "src/led_strip_spi_dev.c", "size": 9346, "hash": "bff2b2e1dc3240d21b1679fba42f0f4395bf9449b21ee3c2408bbc1ae1b9c211"}, {"path": "src/led_strip_rmt_encoder.h", "size": 977, "hash": "690381c35ace2703a5c7156f6547a8524f4cbfe5bef40be619e2097960120a40"}, {"path": "src/led_strip_rmt_dev_idf4.c", "size": 7143, "hash": "81b4a239b554f1ed3326b5380490316a9602bc606a00691a5f325adcbdf2145c"}, {"path": "src/led_strip_api.c", "size": 2575, "hash": "2a8be1284ed6b2ad000907bca3829f71a499aa0d06d47f078d6306392dcc4f4c"}, {"path": "src/led_strip_rmt_encoder.c", "size": 6097, "hash": "7e349a50bd3a161932200dd3da24a6e7316438e0e31615aed6d20deeb3917558"}, {"path": "src/led_strip_rmt_dev.c", "size": 6885, "hash": "3a41df64f2582f3abedd4bddf3111a6bba4a1f886d58dd8391ac2b903ea82e9f"}, {"path": "examples/led_strip_rmt_ws2812/CMakeLists.txt", "size": 381, "hash": "26a31ebd66ad328361d8b69d7f99b9250685932d077dc300dc96280ff6623bc4"}, {"path": "examples/led_strip_rmt_ws2812/README.md", "size": 1200, "hash": "a5f39b31c5f7cbf548ee31b61ab22e430a6c823404c0ddb113703512bcb3ad3c"}, {"path": "examples/led_strip_spi_ws2812/CMakeLists.txt", "size": 381, "hash": "3096c4f9363c9ccf5733c19542163dcb4497764435fb996156552b81c55c6e14"}, {"path": "examples/led_strip_spi_ws2812/README.md", "size": 1201, "hash": "2c02a29197cd1f2d4af4c4c9cd44677e303b0e168a1773eef9fc3fdb39377d27"}, {"path": "examples/led_strip_spi_ws2812/main/CMakeLists.txt", "size": 99, "hash": "34e7f83d26bca924c629ea2012e6f200b415d486907863fe936d94872ff739eb"}, {"path": "examples/led_strip_spi_ws2812/main/idf_component.yml", "size": 142, "hash": "fe89054a1b7988a7068a2952d01bfc6ae40dc59a33bc7dcc8a98cbb231d472fb"}, {"path": "examples/led_strip_spi_ws2812/main/led_strip_spi_ws2812_main.c", "size": 2462, "hash": "8e052cbfd7e27d5e3382f5f115db23ea884db25f4a73fb69d34ed4867dfbf80a"}, {"path": "examples/led_strip_rmt_ws2812/main/CMakeLists.txt", "size": 99, "hash": "8960b68811805d3aa40e1a7f44ddf7400c0d0731829b6d2b3b1584d8dcd3b392"}, {"path": "examples/led_strip_rmt_ws2812/main/idf_component.yml", "size": 125, "hash": "05a57e3d6808bac9e1a2f41cbcfa26d90b5a3427d518b619a1ff4946e62d791d"}, {"path": "examples/led_strip_rmt_ws2812/main/led_strip_rmt_ws2812_main.c", "size": 2705, "hash": "497f3b88387865c41f7a30c7ab5e0086066a344bf16f250d973bf0af907fa78f"}]}

View File

@@ -0,0 +1,31 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
set(srcs "src/led_strip_api.c")
set(public_requires)
# Starting from esp-idf v5.x, the RMT driver is rewritten
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
if(CONFIG_SOC_RMT_SUPPORTED)
list(APPEND srcs "src/led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c")
endif()
else()
list(APPEND srcs "src/led_strip_rmt_dev_idf4.c")
endif()
# the SPI backend driver relies on some feature that was available in IDF 5.1
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.1")
if(CONFIG_SOC_GPSPI_SUPPORTED)
list(APPEND srcs "src/led_strip_spi_dev.c")
endif()
endif()
# Starting from esp-idf v5.3, the RMT and SPI drivers are moved to separate components
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3")
list(APPEND public_requires "esp_driver_rmt" "esp_driver_spi")
else()
list(APPEND public_requires "driver")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include" "interface"
REQUIRES ${public_requires})

View File

@@ -0,0 +1,28 @@
# Set this to the header file you want
INPUT = \
include/ \
interface/
# Output goes into doxygen directory, which is added to gitignore
OUTPUT_DIRECTORY = doxygen
# Warning-related settings, it's recommended to keep them enabled
WARN_IF_UNDOC_ENUM_VAL = YES
WARN_AS_ERROR = YES
# Other common settings
FULL_PATH_NAMES = YES
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
OPTIMIZE_OUTPUT_FOR_C = YES
EXPAND_ONLY_PREDEF = YES
EXTRACT_ALL = YES
PREDEFINED = $(ENV_DOXYGEN_DEFINES)
HAVE_DOT = NO
GENERATE_XML = YES
XML_OUTPUT = xml
GENERATE_HTML = NO
HAVE_DOT = NO
GENERATE_LATEX = NO
QUIET = YES
MARKDOWN_SUPPORT = YES

View File

@@ -0,0 +1,202 @@
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.

View File

@@ -0,0 +1,97 @@
# LED Strip Driver
[![Component Registry](https://components.espressif.com/components/espressif/led_strip/badge.svg)](https://components.espressif.com/components/espressif/led_strip)
This driver is designed for addressable LEDs like [WS2812](http://www.world-semi.com/Certifications/WS2812B.html), where each LED is controlled by a single data line.
## Backend Controllers
### The [RMT](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html) Peripheral
This is the most economical way to drive the LEDs because it only consumes one RMT channel, leaving other channels free to use. However, the memory usage increases dramatically with the number of LEDs. If the RMT hardware can't be assist by DMA, the driver will going into interrupt very frequently, thus result in a high CPU usage. What's worse, if the RMT interrupt is delayed or not serviced in time (e.g. if Wi-Fi interrupt happens on the same CPU core), the RMT transaction will be corrupted and the LEDs will display incorrect colors. If you want to use RMT to drive a large number of LEDs, you'd better to enable the DMA feature if possible [^1].
#### Allocate LED Strip Object with RMT Backend
```c
#define BLINK_GPIO 0
led_strip_handle_t led_strip;
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal (useful when your hardware has a level inverter)
};
led_strip_rmt_config_t rmt_config = {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.rmt_channel = 0,
#else
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = 10 * 1000 * 1000, // 10MHz
.flags.with_dma = false, // whether to enable the DMA feature
#endif
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
```
You can create multiple LED strip objects with different GPIOs and pixel numbers. The backend driver will automatically allocate the RMT channel for you if there is more available.
### The [SPI](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) Peripheral
SPI peripheral can also be used to generate the timing required by the LED strip. However this backend is not as economical as the RMT one, because it will take up the whole **bus**, unlike the RMT just takes one **channel**. You **CANT** connect other devices to the same SPI bus if it's been used by the led_strip, because the led_strip doesn't have the concept of "Chip Select".
Please note, the SPI backend has a dependency of **ESP-IDF >= 5.1**
#### Allocate LED Strip Object with SPI Backend
```c
#define BLINK_GPIO 0
led_strip_handle_t led_strip;
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal (useful when your hardware has a level inverter)
};
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.flags.with_dma = true, // Using DMA can improve performance and help drive more LEDs
.spi_bus = SPI2_HOST, // SPI bus ID
};
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
```
The number of LED strip objects can be created depends on how many free SPI buses are free to use in your project.
## FAQ
* Which led_strip backend should I choose?
* It depends on your application requirement and target chip's ability.
```mermaid
flowchart LR
A{Is RMT supported?}
A --> |No| B[SPI backend]
B --> C{Does the led strip has \n a larger number of LEDs?}
C --> |No| D[Don't have to enable the DMA of the backend]
C --> |Yes| E[Enable the DMA of the backend]
A --> |Yes| F{Does the led strip has \n a larger number of LEDs?}
F --> |Yes| G{Does RMT support DMA?}
G --> |Yes| E
G --> |No| B
F --> |No| H[RMT backend] --> D
```
* How to set the brightness of the LED strip?
* You can tune the brightness by scaling the value of each R-G-B element with a **same** factor. But pay attention to the overflow of the value.
[^1]: The RMT DMA feature is not available on all ESP chips. Please check the data sheet before using it.

View File

@@ -0,0 +1,454 @@
# API Reference
## Header files
- [include/led_strip.h](#file-includeled_striph)
- [include/led_strip_rmt.h](#file-includeled_strip_rmth)
- [include/led_strip_spi.h](#file-includeled_strip_spih)
- [include/led_strip_types.h](#file-includeled_strip_typesh)
- [interface/led_strip_interface.h](#file-interfaceled_strip_interfaceh)
## File include/led_strip.h
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_clear**](#function-led_strip_clear) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip) <br>_Clear LED strip (turn off all LEDs)_ |
| esp\_err\_t | [**led\_strip\_del**](#function-led_strip_del) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip) <br>_Free LED strip resources._ |
| esp\_err\_t | [**led\_strip\_refresh**](#function-led_strip_refresh) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip) <br>_Refresh memory colors to LEDs._ |
| esp\_err\_t | [**led\_strip\_set\_pixel**](#function-led_strip_set_pixel) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip, uint32\_t index, uint32\_t red, uint32\_t green, uint32\_t blue) <br>_Set RGB for a specific pixel._ |
| esp\_err\_t | [**led\_strip\_set\_pixel\_hsv**](#function-led_strip_set_pixel_hsv) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip, uint32\_t index, uint16\_t hue, uint8\_t saturation, uint8\_t value) <br>_Set HSV for a specific pixel._ |
| esp\_err\_t | [**led\_strip\_set\_pixel\_rgbw**](#function-led_strip_set_pixel_rgbw) ([**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) strip, uint32\_t index, uint32\_t red, uint32\_t green, uint32\_t blue, uint32\_t white) <br>_Set RGBW for a specific pixel._ |
## Functions Documentation
### function `led_strip_clear`
_Clear LED strip (turn off all LEDs)_
```c
esp_err_t led_strip_clear (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Clear LEDs successfully
- ESP\_FAIL: Clear LEDs failed because some other error occurred
### function `led_strip_del`
_Free LED strip resources._
```c
esp_err_t led_strip_del (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Free resources successfully
- ESP\_FAIL: Free resources failed because error occurred
### function `led_strip_refresh`
_Refresh memory colors to LEDs._
```c
esp_err_t led_strip_refresh (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- 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.
### function `led_strip_set_pixel`
_Set RGB for a specific pixel._
```c
esp_err_t led_strip_set_pixel (
led_strip_handle_t strip,
uint32_t index,
uint32_t red,
uint32_t green,
uint32_t blue
)
```
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
**Returns:**
- 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
### function `led_strip_set_pixel_hsv`
_Set HSV for a specific pixel._
```c
esp_err_t led_strip_set_pixel_hsv (
led_strip_handle_t strip,
uint32_t index,
uint16_t hue,
uint8_t saturation,
uint8_t value
)
```
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `hue` hue part of color (0 - 360)
- `saturation` saturation part of color (0 - 255, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 127)
- `value` value part of color (0 - 255, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 127)
**Returns:**
- ESP\_OK: Set HSV color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set HSV color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set HSV color for a specific pixel failed because other error occurred
### function `led_strip_set_pixel_rgbw`
_Set RGBW for a specific pixel._
```c
esp_err_t led_strip_set_pixel_rgbw (
led_strip_handle_t strip,
uint32_t index,
uint32_t red,
uint32_t green,
uint32_t blue,
uint32_t white
)
```
**Note:**
Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
**Note:**
Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
- `white` separate white component
**Returns:**
- ESP\_OK: Set RGBW color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set RGBW color for a specific pixel failed because other error occurred
## File include/led_strip_rmt.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_rmt\_config\_t**](#struct-led_strip_rmt_config_t) <br>_LED Strip RMT specific configuration._ |
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_new\_rmt\_device**](#function-led_strip_new_rmt_device) (const [**led\_strip\_config\_t**](#struct-led_strip_config_t) \*led\_config, const [**led\_strip\_rmt\_config\_t**](#struct-led_strip_rmt_config_t) \*rmt\_config, [**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) \*ret\_strip) <br>_Create LED strip based on RMT TX channel._ |
## Structures and Types Documentation
### struct `led_strip_rmt_config_t`
_LED Strip RMT specific configuration._
Variables:
- rmt\_clock\_source\_t clk_src <br>RMT clock source
- struct led\_strip\_rmt\_config\_t::@0 flags <br>Extra driver flags
- size\_t mem_block_symbols <br>How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size.
- uint32\_t resolution_hz <br>RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied
- uint32\_t with_dma <br>Use DMA to transmit data
## Functions Documentation
### function `led_strip_new_rmt_device`
_Create LED strip based on RMT TX channel._
```c
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
)
```
**Parameters:**
- `led_config` LED strip configuration
- `rmt_config` RMT specific configuration
- `ret_strip` Returned LED strip handle
**Returns:**
- 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
## File include/led_strip_spi.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_spi\_config\_t**](#struct-led_strip_spi_config_t) <br>_LED Strip SPI specific configuration._ |
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_new\_spi\_device**](#function-led_strip_new_spi_device) (const [**led\_strip\_config\_t**](#struct-led_strip_config_t) \*led\_config, const [**led\_strip\_spi\_config\_t**](#struct-led_strip_spi_config_t) \*spi\_config, [**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) \*ret\_strip) <br>_Create LED strip based on SPI MOSI channel._ |
## Structures and Types Documentation
### struct `led_strip_spi_config_t`
_LED Strip SPI specific configuration._
Variables:
- spi\_clock\_source\_t clk_src <br>SPI clock source
- struct led\_strip\_spi\_config\_t::@1 flags <br>Extra driver flags
- spi\_host\_device\_t spi_bus <br>SPI bus ID. Which buses are available depends on the specific chip
- uint32\_t with_dma <br>Use DMA to transmit data
## Functions Documentation
### function `led_strip_new_spi_device`
_Create LED strip based on SPI MOSI channel._
```c
esp_err_t led_strip_new_spi_device (
const led_strip_config_t *led_config,
const led_strip_spi_config_t *spi_config,
led_strip_handle_t *ret_strip
)
```
**Note:**
Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes.
**Parameters:**
- `led_config` LED strip configuration
- `spi_config` SPI specific configuration
- `ret_strip` Returned LED strip handle
**Returns:**
- ESP\_OK: create LED strip handle successfully
- ESP\_ERR\_INVALID\_ARG: create LED strip handle failed because of invalid argument
- ESP\_ERR\_NOT\_SUPPORTED: create LED strip handle failed because of unsupported configuration
- 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
## File include/led_strip_types.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| enum | [**led\_model\_t**](#enum-led_model_t) <br>_LED strip model._ |
| enum | [**led\_pixel\_format\_t**](#enum-led_pixel_format_t) <br>_LED strip pixel format._ |
| struct | [**led\_strip\_config\_t**](#struct-led_strip_config_t) <br>_LED Strip Configuration._ |
| typedef struct [**led\_strip\_t**](#struct-led_strip_t) \* | [**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) <br>_LED strip handle._ |
## Structures and Types Documentation
### enum `led_model_t`
_LED strip model._
```c
enum led_model_t {
LED_MODEL_WS2812,
LED_MODEL_SK6812,
LED_MODEL_INVALID
};
```
**Note:**
Different led model may have different timing parameters, so we need to distinguish them.
### enum `led_pixel_format_t`
_LED strip pixel format._
```c
enum led_pixel_format_t {
LED_PIXEL_FORMAT_GRB,
LED_PIXEL_FORMAT_GRBW,
LED_PIXEL_FORMAT_INVALID
};
```
### struct `led_strip_config_t`
_LED Strip Configuration._
Variables:
- struct led\_strip\_config\_t::@2 flags <br>Extra driver flags
- uint32\_t invert_out <br>Invert output signal
- [**led\_model\_t**](#enum-led_model_t) led_model <br>LED model
- [**led\_pixel\_format\_t**](#enum-led_pixel_format_t) led_pixel_format <br>LED pixel format
- uint32\_t max_leds <br>Maximum LEDs in a single strip
- int strip_gpio_num <br>GPIO number that used by LED strip
### typedef `led_strip_handle_t`
_LED strip handle._
```c
typedef struct led_strip_t* led_strip_handle_t;
```
## File interface/led_strip_interface.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_t**](#struct-led_strip_t) <br>_LED strip interface definition._ |
| typedef struct [**led\_strip\_t**](#struct-led_strip_t) | [**led\_strip\_t**](#typedef-led_strip_t) <br> |
## Structures and Types Documentation
### struct `led_strip_t`
_LED strip interface definition._
Variables:
- esp\_err\_t(\* clear <br>_Clear LED strip (turn off all LEDs)_<br>**Parameters:**
- `strip` LED strip
- `timeout_ms` timeout value for clearing task
**Returns:**
- ESP\_OK: Clear LEDs successfully
- ESP\_FAIL: Clear LEDs failed because some other error occurred
- esp\_err\_t(\* del <br>_Free LED strip resources._<br>**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Free resources successfully
- ESP\_FAIL: Free resources failed because error occurred
- esp\_err\_t(\* refresh <br>_Refresh memory colors to LEDs._<br>**Parameters:**
- `strip` LED strip
- `timeout_ms` timeout value for refreshing task
**Returns:**
- 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(\* set_pixel <br>_Set RGB for a specific pixel._<br>**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
**Returns:**
- 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(\* set_pixel_rgbw <br>_Set RGBW for a specific pixel. Similar to_ `set_pixel`_but also set the white component._<br>**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
- `white` separate white component
**Returns:**
- ESP\_OK: Set RGBW color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set RGBW color for a specific pixel failed because other error occurred
### typedef `led_strip_t`
```c
typedef struct led_strip_t led_strip_t;
```
Type of LED strip

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_rmt_ws2812)

View File

@@ -0,0 +1,31 @@
# LED Strip Example (RMT backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_rmt_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[8]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with RMT backend
I (309) example: Start blinking LED strip
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_rmt_ws2812_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip:
version: '^2'
override_path: '../../../'

View File

@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// GPIO assignment
#define LED_STRIP_BLINK_GPIO 2
// Numbers of the LED in the strip
#define LED_STRIP_LED_NUMBERS 24
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_NUMBERS, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal
};
// LED strip backend configuration: RMT
led_strip_rmt_config_t rmt_config = {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.rmt_channel = 0,
#else
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = LED_STRIP_RMT_RES_HZ, // RMT counter clock frequency
.flags.with_dma = false, // DMA feature is available on ESP target like ESP32-S3
#endif
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with RMT backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_spi_ws2812)

View File

@@ -0,0 +1,31 @@
# LED Strip Example (SPI backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_spi_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with SPI backend
I (309) example: Start blinking LED strip
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_spi_ws2812_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip:
version: '^2.4'
override_path: '../../../'
idf: ">=5.1"

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// GPIO assignment
#define LED_STRIP_BLINK_GPIO 2
// Numbers of the LED in the strip
#define LED_STRIP_LED_NUMBERS 24
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_NUMBERS, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal
};
// LED strip backend configuration: SPI
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.flags.with_dma = true, // Using DMA can improve performance and help drive more LEDs
.spi_bus = SPI2_HOST, // SPI bus ID
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with SPI backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View File

@@ -0,0 +1,9 @@
dependencies:
idf: '>=4.4'
description: Driver for Addressable LED Strip (WS2812, etc)
repository: git://github.com/espressif/idf-extra-components.git
repository_info:
commit_sha: 60c14263f3b69ac6e98ecae79beecbe5c18d5596
path: led_strip
url: https://github.com/espressif/idf-extra-components/tree/master/led_strip
version: 2.5.5

View File

@@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_rmt.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include "led_strip_spi.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @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_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel
*
* @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
* @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
*
* @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
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Set HSV for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param hue: hue part of color (0 - 360)
* @param saturation: saturation part of color (0 - 255, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 127)
* @param value: value part of color (0 - 255, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 127)
*
* @return
* - ESP_OK: Set HSV color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value);
/**
* @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_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_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_del(led_strip_handle_t strip);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_types.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/rmt_types.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip RMT specific configuration
*/
typedef struct {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
uint8_t rmt_channel; /*!< Specify the channel number, the legacy RMT driver doesn't support channel allocator */
#else // new driver supports specify the clock source and clock resolution
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 */
#endif
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. */
struct {
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);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "driver/spi_master.h"
#include "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip SPI specific configuration
*/
typedef struct {
spi_clock_source_t clk_src; /*!< SPI clock source */
spi_host_device_t spi_bus; /*!< SPI bus ID. Which buses are available depends on the specific chip */
struct {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_spi_config_t;
/**
* @brief Create LED strip based on SPI MOSI channel
* @note Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes.
*
* @param led_config LED strip configuration
* @param spi_config SPI 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_NOT_SUPPORTED: create LED strip handle failed because of unsupported configuration
* - 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_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED strip pixel format
*/
typedef enum {
LED_PIXEL_FORMAT_GRB, /*!< Pixel format: GRB */
LED_PIXEL_FORMAT_GRBW, /*!< Pixel format: GRBW */
LED_PIXEL_FORMAT_INVALID /*!< Invalid pixel format */
} led_pixel_format_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_INVALID /*!< Invalid LED strip model */
} led_model_t;
/**
* @brief LED strip handle
*/
typedef struct led_strip_t *led_strip_handle_t;
/**
* @brief LED Strip Configuration
*/
typedef struct {
int strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t max_leds; /*!< Maximum LEDs in a single strip */
led_pixel_format_t led_pixel_format; /*!< LED pixel format */
led_model_t led_model; /*!< LED model */
struct {
uint32_t invert_out: 1; /*!< Invert output signal */
} flags; /*!< Extra driver flags */
} led_strip_config_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */
/**
* @brief LED strip interface definition
*/
struct led_strip_t {
/**
* @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 (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component
*
* @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
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel_rgbw)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param timeout_ms: timeout value for refreshing task
*
* @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 (*refresh)(led_strip_t *strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param timeout_ms: timeout value for clearing task
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(led_strip_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 (*del)(led_strip_t *strip);
};
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_check.h"
#include "led_strip.h"
#include "led_strip_interface.h"
static const char *TAG = "led_strip";
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
uint32_t rgb_max = value;
uint32_t rgb_min = rgb_max * (255 - saturation) / 255.0f;
uint32_t i = hue / 60;
uint32_t diff = hue % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
red = rgb_max;
green = rgb_min + rgb_adj;
blue = rgb_min;
break;
case 1:
red = rgb_max - rgb_adj;
green = rgb_max;
blue = rgb_min;
break;
case 2:
red = rgb_min;
green = rgb_max;
blue = rgb_min + rgb_adj;
break;
case 3:
red = rgb_min;
green = rgb_max - rgb_adj;
blue = rgb_max;
break;
case 4:
red = rgb_min + rgb_adj;
green = rgb_min;
blue = rgb_max;
break;
default:
red = rgb_max;
green = rgb_min;
blue = rgb_max - rgb_adj;
break;
}
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel_rgbw(strip, index, red, green, blue, white);
}
esp_err_t led_strip_refresh(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->refresh(strip);
}
esp_err_t led_strip_clear(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->clear(strip);
}
esp_err_t led_strip_del(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->del(strip);
}

View File

@@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2022-2023 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 "led_strip.h"
#include "led_strip_interface.h"
#include "led_strip_rmt_encoder.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 base;
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t pixel_buf[];
} led_strip_rmt_obj;
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
uint32_t start = index * rmt_strip->bytes_per_pixel;
// In thr order of GRB, as LED strip like WS2812 sends out pixels in this order
rmt_strip->pixel_buf[start + 0] = green & 0xFF;
rmt_strip->pixel_buf[start + 1] = red & 0xFF;
rmt_strip->pixel_buf[start + 2] = blue & 0xFF;
if (rmt_strip->bytes_per_pixel > 3) {
rmt_strip->pixel_buf[start + 3] = 0;
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(rmt_strip->bytes_per_pixel == 4, ESP_ERR_INVALID_ARG, TAG, "wrong LED pixel format, expected 4 bytes per pixel");
uint8_t *buf_start = rmt_strip->pixel_buf + index * 4;
// SK6812 component order is GRBW
*buf_start = green & 0xFF;
*++buf_start = red & 0xFF;
*++buf_start = blue & 0xFF;
*++buf_start = white & 0xFF;
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
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;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// 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);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
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");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
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->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}

View File

@@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2022-2023 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.h"
#include "led_strip.h"
#include "led_strip_interface.h"
static const char *TAG = "led_strip_rmt";
#define WS2812_T0H_NS (300)
#define WS2812_T0L_NS (900)
#define WS2812_T1H_NS (900)
#define WS2812_T1L_NS (300)
#define SK6812_T0H_NS (300)
#define SK6812_T0L_NS (900)
#define SK6812_T1H_NS (600)
#define SK6812_T1L_NS (600)
#define LED_STRIP_RESET_MS (10)
// 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 uint32_t led_t0h_ticks = 0;
static uint32_t led_t1h_ticks = 0;
static uint32_t led_t0l_ticks = 0;
static uint32_t led_t1l_ticks = 0;
typedef struct {
led_strip_t base;
rmt_channel_t rmt_channel;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t buffer[0];
} led_strip_rmt_obj;
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num)
{
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ led_t0h_ticks, 1, led_t0l_ticks, 0 }}}; //Logical 0
const rmt_item32_t bit1 = {{{ led_t1h_ticks, 1, led_t1l_ticks, 0 }}}; //Logical 1
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < src_size && num < wanted_num) {
for (int i = 0; i < 8; i++) {
// MSB first
if (*psrc & (1 << (7 - i))) {
pdest->val = bit1.val;
} else {
pdest->val = bit0.val;
}
num++;
pdest++;
}
size++;
psrc++;
}
*translated_size = size;
*item_num = num;
}
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of the maximum number of leds");
uint32_t start = index * rmt_strip->bytes_per_pixel;
// In thr order of GRB
rmt_strip->buffer[start + 0] = green & 0xFF;
rmt_strip->buffer[start + 1] = red & 0xFF;
rmt_strip->buffer[start + 2] = blue & 0xFF;
if (rmt_strip->bytes_per_pixel > 3) {
rmt_strip->buffer[start + 3] = 0;
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_write_sample(rmt_strip->rmt_channel, rmt_strip->buffer, rmt_strip->strip_len * rmt_strip->bytes_per_pixel, true), TAG,
"transmit RMT samples failed");
vTaskDelay(pdMS_TO_TICKS(LED_STRIP_RESET_MS));
return ESP_OK;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// Write zero to turn off all LEDs
memset(rmt_strip->buffer, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return led_strip_rmt_refresh(strip);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_driver_uninstall(rmt_strip->rmt_channel), TAG, "uninstall RMT driver 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 *dev_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(led_config && dev_config && ret_strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, TAG, "invalid led_pixel_format");
ESP_RETURN_ON_FALSE(dev_config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, TAG, "DMA is not supported");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
// allocate memory for led_strip object
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_RETURN_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, TAG, "request memory for les_strip failed");
// install RMT channel driver
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(led_config->strip_gpio_num, dev_config->rmt_channel);
// set the minimal clock division because the LED strip needs a high clock resolution
config.clk_div = 2;
uint8_t mem_block_num = 2;
// override the default value if the user specify the mem block size
if (dev_config->mem_block_symbols) {
mem_block_num = (dev_config->mem_block_symbols + LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS / 2) / LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
}
config.mem_block_num = mem_block_num;
ESP_GOTO_ON_ERROR(rmt_config(&config), err, TAG, "RMT config failed");
ESP_GOTO_ON_ERROR(rmt_driver_install(config.channel, 0, 0), err, TAG, "RMT install failed");
uint32_t counter_clk_hz = 0;
rmt_get_counter_clock((rmt_channel_t)dev_config->rmt_channel, &counter_clk_hz);
// ns -> ticks
float ratio = (float)counter_clk_hz / 1e9;
if (led_config->led_model == LED_MODEL_WS2812) {
led_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
led_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
led_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
led_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
} else if (led_config->led_model == LED_MODEL_SK6812) {
led_t0h_ticks = (uint32_t)(ratio * SK6812_T0H_NS);
led_t0l_ticks = (uint32_t)(ratio * SK6812_T0L_NS);
led_t1h_ticks = (uint32_t)(ratio * SK6812_T1H_NS);
led_t1l_ticks = (uint32_t)(ratio * SK6812_T1L_NS);
} else {
assert(false);
}
// adapter to translates the LES strip date frame into RMT symbols
rmt_translator_init((rmt_channel_t)dev_config->rmt_channel, ws2812_rmt_adapter);
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->rmt_channel = (rmt_channel_t)dev_config->rmt_channel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
free(rmt_strip);
}
return ret;
}

View File

@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "led_strip_rmt_encoder.h"
static const char *TAG = "led_rmt_encoder";
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;
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;
}
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;
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 {
assert(false);
}
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accomodate WS2812B-V5
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View File

@@ -0,0 +1,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 "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of led strip encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
led_model_t led_model; /*!< LED model */
} led_strip_encoder_config_t;
/**
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: 2022-2023 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 "esp_rom_gpio.h"
#include "soc/spi_periph.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "hal/spi_hal.h"
#define LED_STRIP_SPI_DEFAULT_RESOLUTION (2.5 * 1000 * 1000) // 2.5MHz resolution
#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4
#define SPI_BYTES_PER_COLOR_BYTE 3
#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8)
static const char *TAG = "led_strip_spi";
typedef struct {
led_strip_t base;
spi_host_device_t spi_host;
spi_device_handle_t spi_device;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t pixel_buf[];
} led_strip_spi_obj;
// please make sure to zero-initialize the buf before calling this function
static void __led_strip_spi_bit(uint8_t data, uint8_t *buf)
{
// Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110
// So a color byte occupies 3 bytes of SPI.
*(buf + 2) |= data & BIT(0) ? BIT(2) | BIT(1) : BIT(2);
*(buf + 2) |= data & BIT(1) ? BIT(5) | BIT(4) : BIT(5);
*(buf + 2) |= data & BIT(2) ? BIT(7) : 0x00;
*(buf + 1) |= BIT(0);
*(buf + 1) |= data & BIT(3) ? BIT(3) | BIT(2) : BIT(3);
*(buf + 1) |= data & BIT(4) ? BIT(6) | BIT(5) : BIT(6);
*(buf + 0) |= data & BIT(5) ? BIT(1) | BIT(0) : BIT(1);
*(buf + 0) |= data & BIT(6) ? BIT(4) | BIT(3) : BIT(4);
*(buf + 0) |= data & BIT(7) ? BIT(7) | BIT(6) : BIT(7);
}
static esp_err_t led_strip_spi_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
// LED_PIXEL_FORMAT_GRB takes 72bits(9bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
memset(spi_strip->pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
__led_strip_spi_bit(green, &spi_strip->pixel_buf[start]);
__led_strip_spi_bit(red, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE]);
__led_strip_spi_bit(blue, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 2]);
if (spi_strip->bytes_per_pixel > 3) {
__led_strip_spi_bit(0, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 3]);
}
return ESP_OK;
}
static esp_err_t led_strip_spi_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(spi_strip->bytes_per_pixel == 4, ESP_ERR_INVALID_ARG, TAG, "wrong LED pixel format, expected 4 bytes per pixel");
// LED_PIXEL_FORMAT_GRBW takes 96bits(12bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
// SK6812 component order is GRBW
memset(spi_strip->pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
__led_strip_spi_bit(green, &spi_strip->pixel_buf[start]);
__led_strip_spi_bit(red, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE]);
__led_strip_spi_bit(blue, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 2]);
__led_strip_spi_bit(white, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 3]);
return ESP_OK;
}
static esp_err_t led_strip_spi_refresh(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
spi_transaction_t tx_conf;
memset(&tx_conf, 0, sizeof(tx_conf));
tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE;
tx_conf.tx_buffer = spi_strip->pixel_buf;
tx_conf.rx_buffer = NULL;
ESP_RETURN_ON_ERROR(spi_device_transmit(spi_strip->spi_device, &tx_conf), TAG, "transmit pixels by SPI failed");
return ESP_OK;
}
static esp_err_t led_strip_spi_clear(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
//Write zero to turn off all leds
memset(spi_strip->pixel_buf, 0, spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
uint8_t *buf = spi_strip->pixel_buf;
for (int index = 0; index < spi_strip->strip_len * spi_strip->bytes_per_pixel; index++) {
__led_strip_spi_bit(0, buf);
buf += SPI_BYTES_PER_COLOR_BYTE;
}
return led_strip_spi_refresh(strip);
}
static esp_err_t led_strip_spi_del(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_ERROR(spi_bus_remove_device(spi_strip->spi_device), TAG, "delete spi device failed");
ESP_RETURN_ON_ERROR(spi_bus_free(spi_strip->spi_host), TAG, "free spi bus failed");
free(spi_strip);
return ESP_OK;
}
esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip)
{
led_strip_spi_obj *spi_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && spi_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
if (spi_config->flags.with_dma) {
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
spi_strip = heap_caps_calloc(1, sizeof(led_strip_spi_obj) + led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
ESP_GOTO_ON_FALSE(spi_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for spi strip");
spi_strip->spi_host = spi_config->spi_bus;
// for backward compatibility, if the user does not set the clk_src, use the default value
spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
if (spi_config->clk_src) {
clk_src = spi_config->clk_src;
}
spi_bus_config_t spi_bus_cfg = {
.mosi_io_num = led_config->strip_gpio_num,
//Only use MOSI to generate the signal, set -1 when other pins are not used.
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE,
};
ESP_GOTO_ON_ERROR(spi_bus_initialize(spi_strip->spi_host, &spi_bus_cfg, spi_config->flags.with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED), err, TAG, "create SPI bus failed");
if (led_config->flags.invert_out == true) {
esp_rom_gpio_connect_out_signal(led_config->strip_gpio_num, spi_periph_signal[spi_strip->spi_host].spid_out, true, false);
}
spi_device_interface_config_t spi_dev_cfg = {
.clock_source = clk_src,
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.mode = 0,
//set -1 when CS is not used
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
};
ESP_GOTO_ON_ERROR(spi_bus_add_device(spi_strip->spi_host, &spi_dev_cfg, &spi_strip->spi_device), err, TAG, "Failed to add spi device");
//ensure the reset time is enough
esp_rom_delay_us(10);
int clock_resolution_khz = 0;
spi_device_get_actual_freq(spi_strip->spi_device, &clock_resolution_khz);
// TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution
// But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION
// clock_resolution between 2.2MHz to 2.8MHz is supported
ESP_GOTO_ON_FALSE((clock_resolution_khz < LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 + 300) && (clock_resolution_khz > LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 - 300), ESP_ERR_NOT_SUPPORTED, err,
TAG, "unsupported clock resolution:%dKHz", clock_resolution_khz);
spi_strip->bytes_per_pixel = bytes_per_pixel;
spi_strip->strip_len = led_config->max_leds;
spi_strip->base.set_pixel = led_strip_spi_set_pixel;
spi_strip->base.set_pixel_rgbw = led_strip_spi_set_pixel_rgbw;
spi_strip->base.refresh = led_strip_spi_refresh;
spi_strip->base.clear = led_strip_spi_clear;
spi_strip->base.del = led_strip_spi_del;
*ret_strip = &spi_strip->base;
return ESP_OK;
err:
if (spi_strip) {
if (spi_strip->spi_device) {
spi_bus_remove_device(spi_strip->spi_device);
}
if (spi_strip->spi_host) {
spi_bus_free(spi_strip->spi_host);
}
free(spi_strip);
}
return ret;
}