Add multi-panel Pico UDP firmware and Python LED control.
Pico panels get static IPs on 10.1.1.10–14 with per-panel LED counts, Makefile deploy targets, and Python examples for animations, sync tests, and direct Pi SPI control. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
74
firmware/panel/CMakeLists.txt
Normal file
74
firmware/panel/CMakeLists.txt
Normal file
@@ -0,0 +1,74 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(PICO_BOARD pico CACHE STRING "Board type")
|
||||
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(portal_panel C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
pico_sdk_init()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
iolibrary
|
||||
GIT_REPOSITORY https://github.com/Wiznet/ioLibrary_Driver.git
|
||||
GIT_TAG master
|
||||
)
|
||||
FetchContent_MakeAvailable(iolibrary)
|
||||
|
||||
set(IOLIB_DIR ${iolibrary_SOURCE_DIR})
|
||||
|
||||
add_executable(panel
|
||||
main.c
|
||||
wizchip_spi.c
|
||||
timer.c
|
||||
ws2812_led.cpp
|
||||
WS2812.cpp
|
||||
${IOLIB_DIR}/Ethernet/socket.c
|
||||
${IOLIB_DIR}/Ethernet/wizchip_conf.c
|
||||
${IOLIB_DIR}/Ethernet/W5500/w5500.c
|
||||
${IOLIB_DIR}/Internet/DHCP/dhcp.c
|
||||
)
|
||||
|
||||
pico_generate_pio_header(panel ${CMAKE_CURRENT_LIST_DIR}/WS2812.pio)
|
||||
|
||||
target_include_directories(panel PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${IOLIB_DIR}/Ethernet
|
||||
${IOLIB_DIR}/Internet/DHCP
|
||||
)
|
||||
|
||||
target_compile_definitions(panel PRIVATE
|
||||
_WIZCHIP_=W5500
|
||||
)
|
||||
|
||||
if (DEFINED PANEL_ID_BUILD)
|
||||
target_compile_definitions(panel PRIVATE PANEL_ID=${PANEL_ID_BUILD})
|
||||
endif ()
|
||||
|
||||
if (DEFINED NUM_LEDS_BUILD)
|
||||
target_compile_definitions(panel PRIVATE NUM_LEDS=${NUM_LEDS_BUILD})
|
||||
endif ()
|
||||
|
||||
if (DEFINED WS2812_PIN_BUILD)
|
||||
target_compile_definitions(panel PRIVATE PIN_WS2812=${WS2812_PIN_BUILD})
|
||||
endif ()
|
||||
|
||||
option(PORTAL_USE_DHCP "Use DHCP for W5500" OFF)
|
||||
if (PORTAL_USE_DHCP)
|
||||
target_compile_definitions(panel PRIVATE USE_DHCP=1)
|
||||
else ()
|
||||
target_compile_definitions(panel PRIVATE USE_DHCP=0)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(panel
|
||||
pico_stdlib
|
||||
hardware_spi
|
||||
hardware_pio
|
||||
)
|
||||
|
||||
pico_enable_stdio_usb(panel 1)
|
||||
pico_enable_stdio_uart(panel 0)
|
||||
|
||||
pico_add_extra_outputs(panel)
|
||||
113
firmware/panel/Makefile
Normal file
113
firmware/panel/Makefile
Normal file
@@ -0,0 +1,113 @@
|
||||
# Portal panel firmware — build and USB deploy
|
||||
#
|
||||
# Usage:
|
||||
# make # build only
|
||||
# make deploy # build + flash over USB (picotool)
|
||||
# make clean
|
||||
#
|
||||
# Options (passed to cmake on configure/reconfigure):
|
||||
# make deploy PANEL_ID=0
|
||||
# make deploy NO_DHCP=1
|
||||
# make deploy WS2812_PIN=28 # if data wire is on GP28 not GP27
|
||||
#
|
||||
# Environment:
|
||||
# PICO_SDK_PATH default: ~/pico/pico-sdk
|
||||
# SERIAL_PORT default: first /dev/ttyACM* or /dev/ttyACM0
|
||||
|
||||
PICO_SDK_PATH ?= $(HOME)/pico/pico-sdk
|
||||
BUILD_DIR := build
|
||||
UF2 := $(BUILD_DIR)/panel.uf2
|
||||
CMAKE_STAMP := $(BUILD_DIR)/.cmake_stamp
|
||||
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
||||
SERIAL_PORT ?= $(shell ls /dev/ttyACM* 2>/dev/null | head -1)
|
||||
ifeq ($(SERIAL_PORT),)
|
||||
SERIAL_PORT := /dev/ttyACM0
|
||||
endif
|
||||
SERIAL_BAUD ?= 115200
|
||||
|
||||
CMAKE_ARGS :=
|
||||
ifdef PANEL_ID
|
||||
CMAKE_ARGS += -DPANEL_ID_BUILD=$(PANEL_ID)
|
||||
# Panels 0 and 4 are 9×39 (351); others are 9×45 (405). Always pass NUM_LEDS so
|
||||
# cmake cache cannot keep a stale count from a previous PANEL_ID flash.
|
||||
ifeq ($(PANEL_ID),0)
|
||||
NUM_LEDS ?= 351
|
||||
else ifeq ($(PANEL_ID),4)
|
||||
NUM_LEDS ?= 351
|
||||
else
|
||||
NUM_LEDS ?= 405
|
||||
endif
|
||||
CMAKE_ARGS += -DNUM_LEDS_BUILD=$(NUM_LEDS)
|
||||
else
|
||||
ifdef NUM_LEDS
|
||||
CMAKE_ARGS += -DNUM_LEDS_BUILD=$(NUM_LEDS)
|
||||
endif
|
||||
endif
|
||||
ifdef WS2812_PIN
|
||||
CMAKE_ARGS += -DWS2812_PIN_BUILD=$(WS2812_PIN)
|
||||
endif
|
||||
ifeq ($(NO_DHCP),0)
|
||||
CMAKE_ARGS += -DPORTAL_USE_DHCP=ON
|
||||
else
|
||||
CMAKE_ARGS += -DPORTAL_USE_DHCP=OFF
|
||||
endif
|
||||
|
||||
CMAKE_STAMP_BODY := $(strip $(CMAKE_ARGS))
|
||||
|
||||
SOURCES := $(wildcard *.c *.cpp *.h) WS2812.pio CMakeLists.txt pico_sdk_import.cmake
|
||||
|
||||
.PHONY: all build configure reconfigure deploy flash upload monitor clean help
|
||||
|
||||
all: build
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " make build Build panel.uf2"
|
||||
@echo " make deploy Build and flash over USB"
|
||||
@echo " make flash Alias for deploy"
|
||||
@echo " make monitor USB serial console (picocom, Ctrl+A Ctrl+X to quit)"
|
||||
@echo " make clean Remove build directory"
|
||||
@echo " make reconfigure Re-run cmake (e.g. after changing PANEL_ID)"
|
||||
|
||||
build: configure
|
||||
@echo "==> Building panel firmware"
|
||||
PICO_SDK_PATH="$(PICO_SDK_PATH)" cmake --build $(BUILD_DIR) -j$(JOBS)
|
||||
@test -f "$(UF2)" || (echo "Error: build did not produce panel.uf2" && exit 1)
|
||||
@echo "==> Build OK: $(UF2)"
|
||||
|
||||
configure:
|
||||
@test -d "$(PICO_SDK_PATH)" || (echo "Error: pico-sdk not found at $(PICO_SDK_PATH) (set PICO_SDK_PATH)" && exit 1)
|
||||
@command -v cmake >/dev/null || (echo "Error: cmake is required" && exit 1)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@STAMP='$(CMAKE_STAMP_BODY)'; \
|
||||
if [ ! -f "$(BUILD_DIR)/CMakeCache.txt" ] || [ ! -f "$(CMAKE_STAMP)" ] || [ "$$(cat $(CMAKE_STAMP))" != "$$STAMP" ]; then \
|
||||
echo "==> Configuring cmake in $(BUILD_DIR) $(CMAKE_ARGS)"; \
|
||||
PICO_SDK_PATH="$(PICO_SDK_PATH)" cmake -S . -B $(BUILD_DIR) $(CMAKE_ARGS); \
|
||||
echo "$$STAMP" > "$(CMAKE_STAMP)"; \
|
||||
fi
|
||||
|
||||
reconfigure:
|
||||
@test -d "$(PICO_SDK_PATH)" || (echo "Error: pico-sdk not found at $(PICO_SDK_PATH) (set PICO_SDK_PATH)" && exit 1)
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@echo "==> Reconfiguring cmake in $(BUILD_DIR) $(CMAKE_ARGS)"
|
||||
PICO_SDK_PATH="$(PICO_SDK_PATH)" cmake -S . -B $(BUILD_DIR) $(CMAKE_ARGS)
|
||||
@echo '$(CMAKE_STAMP_BODY)' > $(CMAKE_STAMP)
|
||||
|
||||
deploy: build
|
||||
@command -v picotool >/dev/null || (echo "Error: picotool not found (https://github.com/raspberrypi/picotool)" && exit 1)
|
||||
@picotool version 2>&1 | grep -q 'without USB support' && \
|
||||
(echo "Error: picotool was built without USB support" && exit 1) || true
|
||||
@echo "==> Uploading $(UF2) over USB"
|
||||
@picotool load -x -f "$(UF2)"
|
||||
|
||||
flash: deploy
|
||||
upload: deploy
|
||||
|
||||
monitor:
|
||||
@command -v picocom >/dev/null || (echo "Error: picocom not found (sudo apt install picocom)" && exit 1)
|
||||
@test -e "$(SERIAL_PORT)" || (echo "Error: $(SERIAL_PORT) not found (plug in Pico USB, or set SERIAL_PORT=...)" && exit 1)
|
||||
@echo "==> Serial monitor on $(SERIAL_PORT) ($(SERIAL_BAUD) baud, Ctrl+A Ctrl+X to quit)"
|
||||
picocom --baud $(SERIAL_BAUD) --flow n --echo $(SERIAL_PORT)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
125
firmware/panel/README.md
Normal file
125
firmware/panel/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Portal panel firmware (Pico SDK)
|
||||
|
||||
C firmware for a **Pico + W5500 + WS2812** panel adapter. Replaces the slow CircuitPython `adapter/code.py` path with native UDP receive and PIO WS2812 output.
|
||||
|
||||
## Hardware
|
||||
|
||||
Matches `adapter/code.py`:
|
||||
|
||||
| Signal | GPIO |
|
||||
|--------|------|
|
||||
| W5500 CS | GP9 |
|
||||
| SPI1 SCK | GP10 |
|
||||
| SPI1 MOSI | GP11 |
|
||||
| SPI1 MISO | GP12 |
|
||||
| W5500 RST | GP13 |
|
||||
| Status LED | GP25 |
|
||||
| WS2812 data | GP27 |
|
||||
|
||||
Default: **405 LEDs** (45×9 matrix chain), one data pin per panel.
|
||||
|
||||
WS2812 output uses [ForsakenNGS/Pico_WS2812](https://github.com/ForsakenNGS/Pico_WS2812) (FORMAT_GRB on GP27).
|
||||
|
||||
## Build
|
||||
|
||||
Requires [pico-sdk](https://github.com/raspberrypi/pico-sdk) and the ARM GCC toolchain (`arm-none-eabi-gcc`).
|
||||
|
||||
```bash
|
||||
# From repo root
|
||||
make deploy # build + flash over USB
|
||||
make build # build only
|
||||
|
||||
# Or from this directory
|
||||
make deploy
|
||||
```
|
||||
|
||||
Manual cmake (one-time pico-sdk setup):
|
||||
|
||||
```bash
|
||||
git clone https://github.com/raspberrypi/pico-sdk.git ~/pico/pico-sdk
|
||||
cd ~/pico/pico-sdk && git submodule update --init
|
||||
export PICO_SDK_PATH=~/pico/pico-sdk
|
||||
|
||||
mkdir -p build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
UF2 output: `build/panel.uf2` — hold BOOTSEL and copy to the Pico.
|
||||
|
||||
### Options
|
||||
|
||||
```bash
|
||||
# Panel 0 of 5 → 10.1.1.10, unique MAC, UDP panel_id filter
|
||||
make deploy PANEL_ID=0 # 10.1.1.10
|
||||
make deploy PANEL_ID=1 # 10.1.1.11
|
||||
|
||||
# DHCP instead of static 10.1.1.10–14
|
||||
make deploy PANEL_ID=0 NO_DHCP=0
|
||||
|
||||
# Fewer LEDs for bench test
|
||||
make deploy NUM_LEDS=2
|
||||
```
|
||||
|
||||
Static IP and MAC are derived from `PANEL_ID`:
|
||||
|
||||
| Panel | IP | MAC |
|
||||
|-------|-----|-----|
|
||||
| 0 | 10.1.1.10 | 02:50:52:54:4C:00 |
|
||||
| 1 | 10.1.1.11 | 02:50:52:54:4C:01 |
|
||||
| … | … | … |
|
||||
| 4 | 10.1.1.14 | 02:50:52:54:4C:04 |
|
||||
| 255 (bench) | 10.1.1.19 | 02:50:52:54:4C:FF |
|
||||
|
||||
Edit `board_config.h` for gateway, subnet, and pin changes.
|
||||
|
||||
## UDP protocol (port 50007)
|
||||
|
||||
| Payload | Action |
|
||||
|---------|--------|
|
||||
| **1215 bytes** | Raw RGB (`405 × 3`), show immediately |
|
||||
| **1216 bytes** | `panel_id` (byte 0) + RGB; `255` = accept on any panel |
|
||||
| **4 bytes `SHOW`** | Push buffered pixels to the strip (sync helper) |
|
||||
|
||||
Pi sends **RGB** order; firmware converts to WS2812 **GRB**.
|
||||
|
||||
Target throughput: ~30–50 fps per panel (vs ~10–20 fps on CircuitPython).
|
||||
|
||||
## Pi test sender
|
||||
|
||||
```bash
|
||||
pipenv run python examples/panel_udp_send.py --host 192.168.2.111 --animation rainbow
|
||||
```
|
||||
|
||||
## Flashing
|
||||
|
||||
Requires [picotool](https://github.com/raspberrypi/picotool) with libusb support:
|
||||
|
||||
```bash
|
||||
make deploy
|
||||
```
|
||||
|
||||
Or `./scripts/panel_deploy.sh` (same thing).
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
picotool load -x -f build/panel.uf2
|
||||
```
|
||||
|
||||
`-f` forces a USB reboot into BOOTSEL when the panel is already running.
|
||||
|
||||
Legacy UF2 drag-and-drop also works: hold BOOTSEL, plug USB, copy `panel.uf2` to `RPI-RP2`.
|
||||
|
||||
Serial debug:
|
||||
|
||||
```bash
|
||||
make monitor
|
||||
# or: picocom /dev/ttyACM0
|
||||
```
|
||||
|
||||
Ctrl+A then Ctrl+X to exit picocom.
|
||||
|
||||
## Multi-panel (5 Picos)
|
||||
|
||||
Flash each Pico with `PANEL_ID` 0–4 (`make deploy PANEL_ID=N`). Each gets `10.1.1.1N` (10–14), a unique MAC, and UDP filtering. Send frames from the Pi to each panel’s IP, or use a leading `panel_id` byte with `255` broadcast filtering.
|
||||
92
firmware/panel/WS2812.cpp
Normal file
92
firmware/panel/WS2812.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* WS2812 driver — from https://github.com/ForsakenNGS/Pico_WS2812 (BSD-style) */
|
||||
|
||||
#include "WS2812.hpp"
|
||||
|
||||
#include "WS2812.pio.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
WS2812::WS2812(uint pin, uint length, PIO pio, uint sm) {
|
||||
initialize(pin, length, pio, sm, NONE, GREEN, RED, BLUE);
|
||||
}
|
||||
|
||||
WS2812::WS2812(uint pin, uint length, PIO pio, uint sm, DataFormat format) {
|
||||
switch (format) {
|
||||
case FORMAT_RGB:
|
||||
initialize(pin, length, pio, sm, NONE, RED, GREEN, BLUE);
|
||||
break;
|
||||
case FORMAT_GRB:
|
||||
initialize(pin, length, pio, sm, NONE, GREEN, RED, BLUE);
|
||||
break;
|
||||
case FORMAT_WRGB:
|
||||
initialize(pin, length, pio, sm, WHITE, RED, GREEN, BLUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WS2812::~WS2812() {
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
void WS2812::initialize(uint pin, uint length, PIO pio, uint sm, DataByte b1, DataByte b2,
|
||||
DataByte b3, DataByte b4) {
|
||||
this->pin = pin;
|
||||
this->length = length;
|
||||
this->pio = pio;
|
||||
this->sm = sm;
|
||||
this->data = new uint32_t[length];
|
||||
this->bytes[0] = b1;
|
||||
this->bytes[1] = b2;
|
||||
this->bytes[2] = b3;
|
||||
this->bytes[3] = b4;
|
||||
uint offset = pio_add_program(pio, &ws2812_program);
|
||||
uint bits = (b1 == NONE ? 24 : 32);
|
||||
ws2812_program_init(pio, sm, offset, pin, 800000, bits);
|
||||
}
|
||||
|
||||
uint32_t WS2812::convertData(uint32_t rgbw) {
|
||||
uint32_t result = 0;
|
||||
for (uint b = 0; b < 4; b++) {
|
||||
switch (bytes[b]) {
|
||||
case RED:
|
||||
result |= (rgbw & 0xFF);
|
||||
break;
|
||||
case GREEN:
|
||||
result |= (rgbw & 0xFF00) >> 8;
|
||||
break;
|
||||
case BLUE:
|
||||
result |= (rgbw & 0xFF0000) >> 16;
|
||||
break;
|
||||
case WHITE:
|
||||
result |= (rgbw & 0xFF000000) >> 24;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
result <<= 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void WS2812::setPixelColor(uint index, uint32_t color) {
|
||||
if (index < length) {
|
||||
data[index] = convertData(color);
|
||||
}
|
||||
}
|
||||
|
||||
void WS2812::setPixelColor(uint index, uint8_t red, uint8_t green, uint8_t blue) {
|
||||
setPixelColor(index, RGB(red, green, blue));
|
||||
}
|
||||
|
||||
void WS2812::fill(uint32_t color) {
|
||||
color = convertData(color);
|
||||
for (uint i = 0; i < length; i++) {
|
||||
data[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void WS2812::show() {
|
||||
for (uint i = 0; i < length; i++) {
|
||||
pio_sm_put_blocking(pio, sm, data[i]);
|
||||
}
|
||||
}
|
||||
38
firmware/panel/WS2812.hpp
Normal file
38
firmware/panel/WS2812.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef WS2812_H
|
||||
#define WS2812_H
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico/types.h"
|
||||
|
||||
class WS2812 {
|
||||
public:
|
||||
enum DataByte { NONE = 0, RED = 1, GREEN = 2, BLUE = 3, WHITE = 4 };
|
||||
enum DataFormat { FORMAT_RGB = 0, FORMAT_GRB = 1, FORMAT_WRGB = 2 };
|
||||
|
||||
WS2812(uint pin, uint length, PIO pio, uint sm);
|
||||
WS2812(uint pin, uint length, PIO pio, uint sm, DataFormat format);
|
||||
~WS2812();
|
||||
|
||||
static uint32_t RGB(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
return (uint32_t)(blue) << 16 | (uint32_t)(green) << 8 | (uint32_t)(red);
|
||||
}
|
||||
|
||||
void setPixelColor(uint index, uint32_t color);
|
||||
void setPixelColor(uint index, uint8_t red, uint8_t green, uint8_t blue);
|
||||
void fill(uint32_t color);
|
||||
void show();
|
||||
|
||||
private:
|
||||
uint pin;
|
||||
uint length;
|
||||
PIO pio;
|
||||
uint sm;
|
||||
DataByte bytes[4];
|
||||
uint32_t *data;
|
||||
|
||||
void initialize(uint pin, uint length, PIO pio, uint sm, DataByte b1, DataByte b2,
|
||||
DataByte b3, DataByte b4);
|
||||
uint32_t convertData(uint32_t rgbw);
|
||||
};
|
||||
|
||||
#endif
|
||||
44
firmware/panel/WS2812.pio
Normal file
44
firmware/panel/WS2812.pio
Normal file
@@ -0,0 +1,44 @@
|
||||
;
|
||||
; WS2812 PIO — from https://github.com/ForsakenNGS/Pico_WS2812
|
||||
;
|
||||
.program ws2812
|
||||
.side_set 1
|
||||
|
||||
.define public T1 2
|
||||
.define public T2 5
|
||||
.define public T3 3
|
||||
|
||||
.lang_opt python sideset_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_shiftdir = 1
|
||||
|
||||
.wrap_target
|
||||
bitloop:
|
||||
out x, 1 side 0 [T3 - 1]
|
||||
jmp !x send_zero side 1 [T1 - 1]
|
||||
send_one:
|
||||
jmp bitloop side 1 [T2 - 1]
|
||||
send_zero:
|
||||
nop side 0 [T2 - 1]
|
||||
.wrap
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, uint bits) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
|
||||
pio_sm_config c = ws2812_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
sm_config_set_out_shift(&c, false, true, bits);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
|
||||
float div = (float)clock_get_hz(clk_sys) / (freq * (float)cycles_per_bit);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
53
firmware/panel/board_config.h
Normal file
53
firmware/panel/board_config.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef BOARD_CONFIG_H
|
||||
#define BOARD_CONFIG_H
|
||||
|
||||
/* Matches adapter/code.py — W5500 on SPI1, WS2812 on GP27 */
|
||||
|
||||
#define PIN_LED_STATUS 25
|
||||
|
||||
#define SPI_PORT spi1
|
||||
#define SPI_CLK_MHZ 20
|
||||
|
||||
#define PIN_CS 9
|
||||
#define PIN_SCK 10
|
||||
#define PIN_MOSI 11
|
||||
#define PIN_MISO 12
|
||||
#define PIN_RST 13
|
||||
|
||||
#ifndef PIN_WS2812
|
||||
#define PIN_WS2812 27
|
||||
#endif
|
||||
|
||||
#ifndef NUM_LEDS
|
||||
#define NUM_LEDS 405
|
||||
#endif
|
||||
|
||||
#define LED_RGB_BYTES (NUM_LEDS * 3)
|
||||
|
||||
/* W5500 socket assignments */
|
||||
#define SOCKET_DHCP 0
|
||||
#define SOCKET_UDP 1
|
||||
|
||||
/* UDP port (same as CircuitPython adapter) */
|
||||
#define UDP_PORT 50007
|
||||
|
||||
/* Compile-time panel index for filtered frames (0–4). 255 = accept all. */
|
||||
#ifndef PANEL_ID
|
||||
#define PANEL_ID 255
|
||||
#endif
|
||||
|
||||
/* 1 = DHCP, 0 = static IP below */
|
||||
#ifndef USE_DHCP
|
||||
#define USE_DHCP 0
|
||||
#endif
|
||||
|
||||
#define STATIC_IP_OCT4 ((PANEL_ID) == 255 ? 19u : (10u + (unsigned)(PANEL_ID)))
|
||||
#define STATIC_IP {10, 1, 1, (uint8_t)STATIC_IP_OCT4}
|
||||
#define STATIC_SN {255, 255, 255, 0}
|
||||
#define STATIC_GW {10, 1, 1, 1}
|
||||
#define STATIC_DNS {10, 1, 1, 1}
|
||||
|
||||
/* Locally administered; last byte = PANEL_ID (unique per panel 0–4). */
|
||||
#define MAC_ADDR {0x02, 0x50, 0x52, 0x54, 0x4C, (uint8_t)PANEL_ID}
|
||||
|
||||
#endif
|
||||
219
firmware/panel/main.c
Normal file
219
firmware/panel/main.c
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Portal panel firmware — W5500 UDP + WS2812 (Pico SDK).
|
||||
*
|
||||
* UDP protocol (port 50007):
|
||||
* - N×3 bytes: raw RGB (N = NUM_LEDS), show immediately
|
||||
* - N×3+1 bytes: panel_id (byte 0) + RGB; panel_id 255 = any panel
|
||||
* - 4 bytes "SHOW": refresh buffered pixels (for split frame/show sync)
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "board_config.h"
|
||||
#include "wizchip_conf.h"
|
||||
#include "dhcp.h"
|
||||
#include "socket.h"
|
||||
#include "timer.h"
|
||||
#include "wizchip_spi.h"
|
||||
#include "ws2812_led.h"
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#define SOCKET_UDP 1
|
||||
#define SOCKET_DHCP 0
|
||||
#define ETH_BUF_SIZE 2048
|
||||
#define DHCP_RETRY_MAX 10
|
||||
|
||||
static wiz_NetInfo g_net_info = {
|
||||
.mac = MAC_ADDR,
|
||||
.ip = STATIC_IP,
|
||||
.sn = STATIC_SN,
|
||||
.gw = STATIC_GW,
|
||||
.dns = STATIC_DNS,
|
||||
#if USE_DHCP
|
||||
.dhcp = NETINFO_DHCP,
|
||||
#else
|
||||
.dhcp = NETINFO_STATIC,
|
||||
#endif
|
||||
};
|
||||
|
||||
static uint8_t g_eth_buf[ETH_BUF_SIZE];
|
||||
static uint8_t g_pixel_buf[LED_RGB_BYTES];
|
||||
static volatile uint16_t g_ms_tick;
|
||||
static uint8_t g_dhcp_ready;
|
||||
|
||||
static void dhcp_timer_cb(void) {
|
||||
g_ms_tick++;
|
||||
if (g_ms_tick >= 999) {
|
||||
g_ms_tick = 0;
|
||||
DHCP_time_handler();
|
||||
}
|
||||
}
|
||||
|
||||
static void dhcp_assign(void) {
|
||||
getIPfromDHCP(g_net_info.ip);
|
||||
getGWfromDHCP(g_net_info.gw);
|
||||
getSNfromDHCP(g_net_info.sn);
|
||||
getDNSfromDHCP(g_net_info.dns);
|
||||
g_net_info.dhcp = NETINFO_DHCP;
|
||||
network_initialize(g_net_info);
|
||||
print_network_information(g_net_info);
|
||||
g_dhcp_ready = 1;
|
||||
}
|
||||
|
||||
static void dhcp_conflict(void) {
|
||||
printf("DHCP conflict\n");
|
||||
}
|
||||
|
||||
static int network_bring_up(void) {
|
||||
#if USE_DHCP
|
||||
uint8_t retries = 0;
|
||||
DHCP_init(SOCKET_DHCP, g_eth_buf);
|
||||
reg_dhcp_cbfunc(dhcp_assign, dhcp_assign, dhcp_conflict);
|
||||
wizchip_1ms_timer_initialize(dhcp_timer_cb);
|
||||
|
||||
while (!g_dhcp_ready && retries < DHCP_RETRY_MAX) {
|
||||
int8_t rv = DHCP_run();
|
||||
if (rv == DHCP_IP_LEASED) {
|
||||
g_dhcp_ready = 1;
|
||||
break;
|
||||
}
|
||||
if (rv == DHCP_FAILED) {
|
||||
retries++;
|
||||
}
|
||||
wizchip_delay_ms(250);
|
||||
}
|
||||
if (!g_dhcp_ready) {
|
||||
printf("DHCP failed, using static IP\n");
|
||||
g_net_info.dhcp = NETINFO_STATIC;
|
||||
network_initialize(g_net_info);
|
||||
print_network_information(g_net_info);
|
||||
}
|
||||
#else
|
||||
network_initialize(g_net_info);
|
||||
print_network_information(g_net_info);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udp_socket_open(void) {
|
||||
int8_t sn = socket(SOCKET_UDP, Sn_MR_UDP, UDP_PORT, 0);
|
||||
if (sn != SOCKET_UDP) {
|
||||
printf("UDP socket open failed: %d\n", sn);
|
||||
return -1;
|
||||
}
|
||||
printf("UDP listening on port %d\n", UDP_PORT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void led_boot_test(void) {
|
||||
printf("LED boot test on GP%d (%d pixels)\n", PIN_WS2812, NUM_LEDS);
|
||||
ws2812_fill(255, 0, 0);
|
||||
ws2812_show();
|
||||
sleep_ms(500);
|
||||
ws2812_fill(0, 255, 0);
|
||||
ws2812_show();
|
||||
sleep_ms(500);
|
||||
ws2812_fill(0, 0, 0);
|
||||
ws2812_show();
|
||||
}
|
||||
|
||||
static int handle_frame(const uint8_t *rgb, uint16_t len) {
|
||||
if (len != LED_RGB_BYTES) {
|
||||
printf("frame reject len=%u want=%u\n", (unsigned)len, (unsigned)LED_RGB_BYTES);
|
||||
return -1;
|
||||
}
|
||||
ws2812_set_rgb(rgb, NUM_LEDS);
|
||||
ws2812_show();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handle_packet(const uint8_t *data, int16_t len) {
|
||||
if (len == 4 && memcmp(data, "SHOW", 4) == 0) {
|
||||
ws2812_set_rgb(g_pixel_buf, NUM_LEDS);
|
||||
ws2812_show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == LED_RGB_BYTES) {
|
||||
memcpy(g_pixel_buf, data, LED_RGB_BYTES);
|
||||
handle_frame(data, (uint16_t)len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == LED_RGB_BYTES + 1) {
|
||||
uint8_t panel = data[0];
|
||||
if (panel != 255 && panel != (uint8_t)PANEL_ID) {
|
||||
return;
|
||||
}
|
||||
memcpy(g_pixel_buf, data + 1, LED_RGB_BYTES);
|
||||
handle_frame(data + 1, LED_RGB_BYTES);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void poll_udp(void) {
|
||||
while (1) {
|
||||
uint16_t rx = getSn_RX_RSR(SOCKET_UDP);
|
||||
if (rx == 0) {
|
||||
return;
|
||||
}
|
||||
if (rx > ETH_BUF_SIZE) {
|
||||
rx = ETH_BUF_SIZE;
|
||||
}
|
||||
|
||||
uint8_t src_ip[4];
|
||||
uint16_t src_port;
|
||||
int16_t n = recvfrom(SOCKET_UDP, g_eth_buf, rx, src_ip, &src_port);
|
||||
if (n <= 0) {
|
||||
return;
|
||||
}
|
||||
handle_packet(g_eth_buf, n);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
stdio_init_all();
|
||||
sleep_ms(2000);
|
||||
|
||||
gpio_init(PIN_LED_STATUS);
|
||||
gpio_set_dir(PIN_LED_STATUS, GPIO_OUT);
|
||||
|
||||
printf("portal panel firmware (Pico SDK)\n");
|
||||
printf("LEDs=%d panel_id=%d mac=02:50:52:54:4C:%02X ip=10.1.1.%u\n",
|
||||
NUM_LEDS, PANEL_ID, (unsigned)PANEL_ID, (unsigned)STATIC_IP_OCT4);
|
||||
|
||||
wizchip_spi_initialize();
|
||||
wizchip_cris_initialize();
|
||||
wizchip_reset();
|
||||
wizchip_initialize();
|
||||
wizchip_check();
|
||||
|
||||
ws2812_init(PIN_WS2812, NUM_LEDS);
|
||||
memset(g_pixel_buf, 0, sizeof(g_pixel_buf));
|
||||
led_boot_test();
|
||||
|
||||
network_bring_up();
|
||||
if (udp_socket_open() != 0) {
|
||||
while (1) {
|
||||
gpio_xor_mask(1u << PIN_LED_STATUS);
|
||||
sleep_ms(100);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t heartbeat = 0;
|
||||
while (1) {
|
||||
#if USE_DHCP
|
||||
DHCP_run();
|
||||
#endif
|
||||
poll_udp();
|
||||
if (++heartbeat >= 500) {
|
||||
gpio_xor_mask(1u << PIN_LED_STATUS);
|
||||
heartbeat = 0;
|
||||
}
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
54
firmware/panel/pico_sdk_import.cmake
Normal file
54
firmware/panel/pico_sdk_import.cmake
Normal file
@@ -0,0 +1,54 @@
|
||||
# This can be dropped into any project as a standalone CMake file
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch Pico SDK from git")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "Location to download Pico SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH)
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
)
|
||||
if (NOT pico_sdk)
|
||||
FetchContent_Populate(pico_sdk)
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"PICO_SDK_PATH is not set. Clone pico-sdk and export PICO_SDK_PATH, "
|
||||
"or set PICO_SDK_FETCH_FROM_GIT=ON.")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "pico_sdk_init.cmake not found in ${PICO_SDK_PATH}")
|
||||
endif ()
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
21
firmware/panel/timer.c
Normal file
21
firmware/panel/timer.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "timer.h"
|
||||
|
||||
static struct repeating_timer g_timer;
|
||||
static void (*g_callback)(void);
|
||||
|
||||
void wizchip_1ms_timer_initialize(void (*callback)(void)) {
|
||||
g_callback = callback;
|
||||
add_repeating_timer_us(-1000, wizchip_1ms_timer_callback, NULL, &g_timer);
|
||||
}
|
||||
|
||||
bool wizchip_1ms_timer_callback(struct repeating_timer *t) {
|
||||
(void)t;
|
||||
if (g_callback != NULL) {
|
||||
g_callback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void wizchip_delay_ms(uint32_t ms) {
|
||||
sleep_ms(ms);
|
||||
}
|
||||
10
firmware/panel/timer.h
Normal file
10
firmware/panel/timer.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef PANEL_TIMER_H
|
||||
#define PANEL_TIMER_H
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
void wizchip_1ms_timer_initialize(void (*callback)(void));
|
||||
bool wizchip_1ms_timer_callback(struct repeating_timer *t);
|
||||
void wizchip_delay_ms(uint32_t ms);
|
||||
|
||||
#endif
|
||||
121
firmware/panel/wizchip_spi.c
Normal file
121
firmware/panel/wizchip_spi.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* W5500 SPI port for portal panel adapter (SPI1, GP9–13).
|
||||
* Derived from WIZnet-PICO-C port/ioLibrary_Driver (BSD-3-Clause).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "board_config.h"
|
||||
#include "wizchip_conf.h"
|
||||
#include "wizchip_spi.h"
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "pico/binary_info.h"
|
||||
#include "pico/critical_section.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
static critical_section_t g_wizchip_cri_sec;
|
||||
|
||||
static inline void wizchip_select(void) {
|
||||
gpio_put(PIN_CS, 0);
|
||||
}
|
||||
|
||||
static inline void wizchip_deselect(void) {
|
||||
gpio_put(PIN_CS, 1);
|
||||
}
|
||||
|
||||
static uint8_t wizchip_read(void) {
|
||||
uint8_t rx = 0;
|
||||
uint8_t tx = 0xff;
|
||||
spi_read_blocking(SPI_PORT, tx, &rx, 1);
|
||||
return rx;
|
||||
}
|
||||
|
||||
static void wizchip_write(uint8_t tx) {
|
||||
spi_write_blocking(SPI_PORT, &tx, 1);
|
||||
}
|
||||
|
||||
static void wizchip_critical_section_lock(void) {
|
||||
critical_section_enter_blocking(&g_wizchip_cri_sec);
|
||||
}
|
||||
|
||||
static void wizchip_critical_section_unlock(void) {
|
||||
critical_section_exit(&g_wizchip_cri_sec);
|
||||
}
|
||||
|
||||
void wizchip_reset(void) {
|
||||
gpio_init(PIN_RST);
|
||||
gpio_set_dir(PIN_RST, GPIO_OUT);
|
||||
gpio_put(PIN_RST, 0);
|
||||
sleep_ms(100);
|
||||
gpio_put(PIN_RST, 1);
|
||||
sleep_ms(100);
|
||||
bi_decl(bi_1pin_with_name(PIN_RST, "W5500 RESET"));
|
||||
}
|
||||
|
||||
void wizchip_spi_initialize(void) {
|
||||
spi_init(SPI_PORT, SPI_CLK_MHZ * 1000 * 1000);
|
||||
|
||||
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
|
||||
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
|
||||
|
||||
bi_decl(bi_3pins_with_func(PIN_MISO, PIN_MOSI, PIN_SCK, GPIO_FUNC_SPI));
|
||||
|
||||
gpio_init(PIN_CS);
|
||||
gpio_set_dir(PIN_CS, GPIO_OUT);
|
||||
gpio_put(PIN_CS, 1);
|
||||
bi_decl(bi_1pin_with_name(PIN_CS, "W5500 CS"));
|
||||
}
|
||||
|
||||
void wizchip_cris_initialize(void) {
|
||||
critical_section_init(&g_wizchip_cri_sec);
|
||||
reg_wizchip_cris_cbfunc(wizchip_critical_section_lock, wizchip_critical_section_unlock);
|
||||
}
|
||||
|
||||
void wizchip_initialize(void) {
|
||||
wizchip_deselect();
|
||||
reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect);
|
||||
reg_wizchip_spi_cbfunc(wizchip_read, wizchip_write);
|
||||
|
||||
uint8_t memsize[2][8] = {
|
||||
{2, 2, 2, 2, 2, 2, 2, 2},
|
||||
{2, 2, 2, 2, 2, 2, 2, 2},
|
||||
};
|
||||
|
||||
if (ctlwizchip(CW_INIT_WIZCHIP, (void *)memsize) == -1) {
|
||||
printf("W5500 init failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t link = PHY_LINK_OFF;
|
||||
do {
|
||||
if (ctlwizchip(CW_GET_PHYLINK, (void *)&link) == -1) {
|
||||
printf("PHY link unknown\n");
|
||||
return;
|
||||
}
|
||||
} while (link == PHY_LINK_OFF);
|
||||
}
|
||||
|
||||
void wizchip_check(void) {
|
||||
if (getVERSIONR() != 0x04) {
|
||||
printf("W5500 version mismatch: 0x%02x\n", getVERSIONR());
|
||||
while (1) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void network_initialize(wiz_NetInfo net_info) {
|
||||
ctlnetwork(CN_SET_NETINFO, (void *)&net_info);
|
||||
}
|
||||
|
||||
void print_network_information(wiz_NetInfo net_info) {
|
||||
ctlnetwork(CN_GET_NETINFO, (void *)&net_info);
|
||||
printf("MAC %02X:%02X:%02X:%02X:%02X:%02X\n",
|
||||
net_info.mac[0], net_info.mac[1], net_info.mac[2],
|
||||
net_info.mac[3], net_info.mac[4], net_info.mac[5]);
|
||||
printf("IP %d.%d.%d.%d\n",
|
||||
net_info.ip[0], net_info.ip[1], net_info.ip[2], net_info.ip[3]);
|
||||
}
|
||||
14
firmware/panel/wizchip_spi.h
Normal file
14
firmware/panel/wizchip_spi.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef WIZCHIP_SPI_H
|
||||
#define WIZCHIP_SPI_H
|
||||
|
||||
#include "wizchip_conf.h"
|
||||
|
||||
void wizchip_spi_initialize(void);
|
||||
void wizchip_cris_initialize(void);
|
||||
void wizchip_reset(void);
|
||||
void wizchip_initialize(void);
|
||||
void wizchip_check(void);
|
||||
void network_initialize(wiz_NetInfo net_info);
|
||||
void print_network_information(wiz_NetInfo net_info);
|
||||
|
||||
#endif
|
||||
39
firmware/panel/ws2812_led.cpp
Normal file
39
firmware/panel/ws2812_led.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/* C API wrapper around ForsakenNGS/Pico_WS2812 */
|
||||
|
||||
#include "ws2812_led.h"
|
||||
|
||||
#include "WS2812.hpp"
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
static WS2812 *s_strip;
|
||||
|
||||
extern "C" void ws2812_init(unsigned int pin, unsigned int num_leds) {
|
||||
uint sm = pio_claim_unused_sm(pio0, true);
|
||||
s_strip = new WS2812(pin, num_leds, pio0, sm, WS2812::FORMAT_GRB);
|
||||
}
|
||||
|
||||
extern "C" void ws2812_set_rgb(const uint8_t *rgb, unsigned int count) {
|
||||
if (s_strip == nullptr || rgb == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
s_strip->setPixelColor(i, rgb[i * 3 + 0], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ws2812_fill(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (s_strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_strip->fill(WS2812::RGB(r, g, b));
|
||||
}
|
||||
|
||||
extern "C" void ws2812_show(void) {
|
||||
if (s_strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_strip->show();
|
||||
sleep_us(300);
|
||||
}
|
||||
19
firmware/panel/ws2812_led.h
Normal file
19
firmware/panel/ws2812_led.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef WS2812_LED_H
|
||||
#define WS2812_LED_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void ws2812_init(unsigned int pin, unsigned int num_leds);
|
||||
void ws2812_set_rgb(const uint8_t *rgb, unsigned int count);
|
||||
void ws2812_fill(uint8_t r, uint8_t g, uint8_t b);
|
||||
void ws2812_show(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user