Replace MicroPython runtime with Pico C + W5500 firmware.
Ship UDP RGB panel firmware (eight WS2812 strips) and drop the ESP32 Wi-Fi/MicroPython stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
89
firmware/CMakeLists.txt
Normal file
89
firmware/CMakeLists.txt
Normal file
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
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 WS2812_PIN_BUILD)
|
||||
target_compile_definitions(panel PRIVATE PIN_WS2812=${WS2812_PIN_BUILD})
|
||||
endif ()
|
||||
|
||||
if (DEFINED WS2812_PIN1_BUILD)
|
||||
target_compile_definitions(panel PRIVATE PIN_WS2812_1=${WS2812_PIN1_BUILD})
|
||||
endif ()
|
||||
|
||||
if (DEFINED STATIC_IP_BUILD)
|
||||
target_compile_definitions(panel PRIVATE "STATIC_IP={${STATIC_IP_BUILD}}")
|
||||
endif ()
|
||||
if (DEFINED STATIC_SN_BUILD)
|
||||
target_compile_definitions(panel PRIVATE "STATIC_SN={${STATIC_SN_BUILD}}")
|
||||
endif ()
|
||||
if (DEFINED STATIC_GW_BUILD)
|
||||
target_compile_definitions(panel PRIVATE "STATIC_GW={${STATIC_GW_BUILD}}")
|
||||
endif ()
|
||||
if (DEFINED STATIC_DNS_BUILD)
|
||||
target_compile_definitions(panel PRIVATE "STATIC_DNS={${STATIC_DNS_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
|
||||
hardware_dma
|
||||
)
|
||||
|
||||
pico_enable_stdio_usb(panel 1)
|
||||
pico_enable_stdio_uart(panel 0)
|
||||
|
||||
pico_add_extra_outputs(panel)
|
||||
147
firmware/Makefile
Normal file
147
firmware/Makefile
Normal file
@@ -0,0 +1,147 @@
|
||||
# Portal panel firmware — build and USB deploy
|
||||
#
|
||||
# make # build only
|
||||
# make deploy # build + flash over USB (picotool)
|
||||
#
|
||||
# Options (also via repo-root .env — see .env.example):
|
||||
# make deploy PANEL_ID=0
|
||||
# make deploy NETWORK=dhcp
|
||||
# make deploy IP=10.1.1.10 GATEWAY=10.1.1.1
|
||||
# make deploy WS2812_PIN=26 WS2812_PIN1=27
|
||||
#
|
||||
# Environment:
|
||||
# PICO_SDK_PATH default: ~/pico-sdk (fallback ~/pico/pico-sdk)
|
||||
|
||||
MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
-include $(MAKEFILE_DIR)../.env
|
||||
-include $(MAKEFILE_DIR).env
|
||||
|
||||
ifeq ($(origin PICO_SDK_PATH), undefined)
|
||||
ifneq ($(wildcard $(HOME)/pico-sdk/pico_sdk_init.cmake),)
|
||||
PICO_SDK_PATH := $(HOME)/pico-sdk
|
||||
else
|
||||
PICO_SDK_PATH := $(HOME)/pico/pico-sdk
|
||||
endif
|
||||
endif
|
||||
|
||||
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
|
||||
NETWORK ?= static
|
||||
|
||||
comma := ,
|
||||
dot_to_csv = $(subst .,$(comma),$(1))
|
||||
|
||||
CMAKE_ARGS :=
|
||||
ifdef PANEL_ID
|
||||
CMAKE_ARGS += -DPANEL_ID_BUILD=$(PANEL_ID)
|
||||
endif
|
||||
ifdef WS2812_PIN
|
||||
CMAKE_ARGS += -DWS2812_PIN_BUILD=$(WS2812_PIN)
|
||||
endif
|
||||
ifdef WS2812_PIN1
|
||||
CMAKE_ARGS += -DWS2812_PIN1_BUILD=$(WS2812_PIN1)
|
||||
endif
|
||||
ifdef IP
|
||||
CMAKE_ARGS += -DSTATIC_IP_BUILD=$(call dot_to_csv,$(IP))
|
||||
endif
|
||||
ifdef NETMASK
|
||||
CMAKE_ARGS += -DSTATIC_SN_BUILD=$(call dot_to_csv,$(NETMASK))
|
||||
endif
|
||||
ifdef GATEWAY
|
||||
CMAKE_ARGS += -DSTATIC_GW_BUILD=$(call dot_to_csv,$(GATEWAY))
|
||||
endif
|
||||
ifdef DNS
|
||||
CMAKE_ARGS += -DSTATIC_DNS_BUILD=$(call dot_to_csv,$(DNS))
|
||||
endif
|
||||
ifeq ($(NETWORK),dhcp)
|
||||
CMAKE_ARGS += -DPORTAL_USE_DHCP=ON
|
||||
else ifeq ($(NO_DHCP),0)
|
||||
CMAKE_ARGS += -DPORTAL_USE_DHCP=ON
|
||||
else
|
||||
CMAKE_ARGS += -DPORTAL_USE_DHCP=OFF
|
||||
endif
|
||||
|
||||
CMAKE_STAMP_BODY := $(strip $(CMAKE_ARGS))
|
||||
|
||||
.PHONY: all build configure reconfigure deploy flash upload reset monitor clean help
|
||||
|
||||
all: build
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " make build Build panel.uf2"
|
||||
@echo " make deploy Build and flash over USB"
|
||||
@echo " make reset USB reboot Pico"
|
||||
@echo " make monitor USB serial console"
|
||||
@echo " make clean Remove build directory"
|
||||
|
||||
build: configure
|
||||
@echo "==> Building panel firmware"
|
||||
# Host GCC 15 needs cstdint for pico-sdk pioasm
|
||||
CXXFLAGS='-include cstdint' 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)" && 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)"; \
|
||||
CXXFLAGS='-include cstdint' PICO_SDK_PATH="$(PICO_SDK_PATH)" cmake -S . -B $(BUILD_DIR) $(CMAKE_ARGS); \
|
||||
echo "$$STAMP" > "$(CMAKE_STAMP)"; \
|
||||
fi
|
||||
|
||||
reconfigure:
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
CXXFLAGS='-include cstdint' 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"; exit 1; }
|
||||
@echo "==> Uploading $(UF2) over USB"
|
||||
@picotool load -x -f "$(UF2)"
|
||||
|
||||
flash: deploy
|
||||
upload: deploy
|
||||
|
||||
reset:
|
||||
@command -v picotool >/dev/null || { echo "Error: picotool not found"; exit 1; }
|
||||
@picotool reboot -f
|
||||
@i=0; last=; \
|
||||
while [ $$i -lt 50 ]; do \
|
||||
cur=$$(ls /dev/ttyACM* 2>/dev/null | head -1); \
|
||||
if [ -n "$$cur" ] && [ "$$cur" = "$$last" ]; then \
|
||||
sleep 0.5; \
|
||||
if [ -e "$$cur" ]; then echo "==> Ready ($$cur)"; exit 0; fi; \
|
||||
fi; \
|
||||
last=$$cur; \
|
||||
i=$$((i + 1)); sleep 0.2; \
|
||||
done; \
|
||||
echo "==> Warning: serial not ready yet"; exit 0
|
||||
|
||||
monitor:
|
||||
@command -v picocom >/dev/null || { echo "Error: picocom not found"; exit 1; }
|
||||
@SERIAL=; i=0; \
|
||||
while [ $$i -lt 40 ]; do \
|
||||
for d in $(SERIAL_PORT) $$(ls /dev/ttyACM* 2>/dev/null); do \
|
||||
[ -n "$$d" ] && [ -e "$$d" ] || continue; \
|
||||
SERIAL=$$d; break; \
|
||||
done; \
|
||||
[ -n "$$SERIAL" ] && [ -e "$$SERIAL" ] && break; \
|
||||
i=$$((i + 1)); sleep 0.25; \
|
||||
done; \
|
||||
if [ -z "$$SERIAL" ] || [ ! -e "$$SERIAL" ]; then echo "Error: no serial port"; exit 1; fi; \
|
||||
echo "==> monitor $$SERIAL"; \
|
||||
exec picocom --baud $(SERIAL_BAUD) --flow n --echo --noreset "$$SERIAL"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
170
firmware/WS2812.cpp
Normal file
170
firmware/WS2812.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/* WS2812 driver — from https://github.com/ForsakenNGS/Pico_WS2812 (BSD-style)
|
||||
* Pixel push uses DMA → PIO TX FIFO (DREQ-paced).
|
||||
*/
|
||||
|
||||
#include "WS2812.hpp"
|
||||
|
||||
#include "WS2812.pio.h"
|
||||
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/gpio.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() {
|
||||
if (dma_chan >= 0) {
|
||||
dma_channel_abort(dma_chan);
|
||||
dma_channel_unclaim((uint)dma_chan);
|
||||
dma_chan = -1;
|
||||
}
|
||||
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;
|
||||
this->bits = (b1 == NONE ? 24 : 32);
|
||||
this->dma_chan = (int)dma_claim_unused_channel(true);
|
||||
/* One copy of the program per PIO block — shared by all strips on that PIO. */
|
||||
static int pio0_offset = -1;
|
||||
static int pio1_offset = -1;
|
||||
int *cached = (pio == pio0) ? &pio0_offset : &pio1_offset;
|
||||
if (*cached < 0) {
|
||||
*cached = (int)pio_add_program(pio, &ws2812_program);
|
||||
}
|
||||
this->offset = (uint)*cached;
|
||||
applyPinConfig();
|
||||
}
|
||||
|
||||
void WS2812::applyPinConfig() {
|
||||
pio_sm_set_enabled(pio, sm, false);
|
||||
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) / (800000.f * (float)cycles_per_bit);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
void WS2812::setPin(uint new_pin) {
|
||||
if (new_pin == pin) {
|
||||
return;
|
||||
}
|
||||
dma_wait();
|
||||
pio_sm_set_enabled(pio, sm, false);
|
||||
gpio_set_function(pin, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(pin, GPIO_IN);
|
||||
pin = new_pin;
|
||||
applyPinConfig();
|
||||
}
|
||||
|
||||
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::dma_wait(void) {
|
||||
if (dma_chan >= 0 && dma_channel_is_busy((uint)dma_chan)) {
|
||||
dma_channel_wait_for_finish_blocking((uint)dma_chan);
|
||||
}
|
||||
}
|
||||
|
||||
void WS2812::show() {
|
||||
show(length);
|
||||
}
|
||||
|
||||
void WS2812::show(uint count) {
|
||||
if (count > length) {
|
||||
count = length;
|
||||
}
|
||||
if (count == 0 || dma_chan < 0 || data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
dma_wait();
|
||||
|
||||
dma_channel_config c = dma_channel_get_default_config((uint)dma_chan);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
||||
channel_config_set_read_increment(&c, true);
|
||||
channel_config_set_write_increment(&c, false);
|
||||
channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true));
|
||||
|
||||
dma_channel_configure((uint)dma_chan, &c,
|
||||
&pio->txf[sm], /* write address */
|
||||
data, /* read address */
|
||||
count, /* transfer count */
|
||||
true); /* start immediately */
|
||||
|
||||
dma_channel_wait_for_finish_blocking((uint)dma_chan);
|
||||
}
|
||||
46
firmware/WS2812.hpp
Normal file
46
firmware/WS2812.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#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();
|
||||
void show(uint count);
|
||||
void setPin(uint pin);
|
||||
uint getPin() const { return pin; }
|
||||
|
||||
private:
|
||||
uint pin;
|
||||
uint length;
|
||||
PIO pio;
|
||||
uint sm;
|
||||
uint offset;
|
||||
uint bits;
|
||||
int dma_chan;
|
||||
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);
|
||||
void applyPinConfig();
|
||||
void dma_wait(void);
|
||||
};
|
||||
|
||||
#endif
|
||||
44
firmware/WS2812.pio
Normal file
44
firmware/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);
|
||||
}
|
||||
%}
|
||||
74
firmware/board_config.h
Normal file
74
firmware/board_config.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef BOARD_CONFIG_H
|
||||
#define BOARD_CONFIG_H
|
||||
|
||||
/* W5500 on SPI1; eight WS2812 strips (portal UDP protocol) */
|
||||
|
||||
#define PIN_LED_STATUS 25
|
||||
|
||||
#define SPI_PORT spi1
|
||||
#define SPI_CLK_MHZ 15
|
||||
|
||||
/* This adapter board: CS=13, RST=9 (some portal PCBs use CS=9 / RST=13) */
|
||||
#define PIN_CS 13
|
||||
#define PIN_SCK 10
|
||||
#define PIN_MOSI 11
|
||||
#define PIN_MISO 12
|
||||
#define PIN_RST 9
|
||||
|
||||
/* Strip 0 / 1 overridable; remaining pins fixed in DEFAULT_STRIP_PINS */
|
||||
#ifndef PIN_WS2812
|
||||
#define PIN_WS2812 18
|
||||
#endif
|
||||
#ifndef PIN_WS2812_1
|
||||
#define PIN_WS2812_1 19
|
||||
#endif
|
||||
|
||||
/* Strip / buffer capacity. Pixel count comes from each UDP frame length. */
|
||||
#ifndef MAX_LEDS
|
||||
#define MAX_LEDS 512
|
||||
#endif
|
||||
|
||||
#define LED_RGB_BYTES (MAX_LEDS * 3)
|
||||
|
||||
/* Eight WS2812 outputs (one PIO SM each on RP2040). */
|
||||
#ifndef MAX_STRIPS
|
||||
#define MAX_STRIPS 8
|
||||
#endif
|
||||
|
||||
#define DEFAULT_STRIP_PINS \
|
||||
{ PIN_WS2812, PIN_WS2812_1, 20, 21, 22, 26, 27, 28 }
|
||||
|
||||
/* W5500 socket assignments */
|
||||
#define SOCKET_DHCP 0
|
||||
#define SOCKET_UDP 1
|
||||
|
||||
/* UDP port (same as CircuitPython adapter / portal) */
|
||||
#define UDP_PORT 50007
|
||||
|
||||
/* Compile-time panel index for filtered frames (0–4). 255 = accept all. */
|
||||
#ifndef PANEL_ID
|
||||
#define PANEL_ID 255
|
||||
#endif
|
||||
|
||||
#ifndef USE_DHCP
|
||||
#define USE_DHCP 0
|
||||
#endif
|
||||
|
||||
#ifndef STATIC_IP
|
||||
#define STATIC_IP_OCT4 ((PANEL_ID) == 255 ? 19u : (10u + (unsigned)(PANEL_ID)))
|
||||
#define STATIC_IP {10, 1, 1, (uint8_t)STATIC_IP_OCT4}
|
||||
#endif
|
||||
#ifndef STATIC_SN
|
||||
#define STATIC_SN {255, 255, 255, 0}
|
||||
#endif
|
||||
#ifndef STATIC_GW
|
||||
#define STATIC_GW {10, 1, 1, 1}
|
||||
#endif
|
||||
#ifndef STATIC_DNS
|
||||
#define STATIC_DNS {10, 1, 1, 1}
|
||||
#endif
|
||||
|
||||
/* Locally administered; last byte = PANEL_ID (unique per panel 0–4). */
|
||||
#define MAC_ADDR {0x02, 0x50, 0x52, 0x54, 0x4C, (uint8_t)PANEL_ID}
|
||||
|
||||
#endif
|
||||
365
firmware/main.c
Normal file
365
firmware/main.c
Normal file
@@ -0,0 +1,365 @@
|
||||
/**
|
||||
* Portal panel firmware — W5500 UDP + WS2812 (Pico SDK).
|
||||
*
|
||||
* UDP protocol (port 50007):
|
||||
* - N×3 bytes: raw RGB → strip 0 (N = 1…MAX_LEDS)
|
||||
* - 1 + N×3 bytes: strip_id (byte 0) + RGB
|
||||
* strip_id 0…MAX_STRIPS-1 → that strip
|
||||
* strip_id 255 → all active strips
|
||||
* - 5 bytes "PIN\\0" + gpio: bind strip 0 to gpio
|
||||
* - 8 bytes "PIN\\0" + strip + gpio + len_lo + len_hi: bind strip (len=0 → unset)
|
||||
* - 4 bytes "SHOW": reserved (frames already show on receive)
|
||||
*
|
||||
* One Pico can drive up to MAX_STRIPS displays on separate GPIOs.
|
||||
* Each strip’s pixel count comes from its own UDP frame length (strips may differ).
|
||||
* MAX_LEDS is per-strip capacity.
|
||||
*/
|
||||
|
||||
#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[MAX_STRIPS][LED_RGB_BYTES];
|
||||
static uint16_t g_frame_pixels[MAX_STRIPS];
|
||||
static uint16_t g_strip_leds[MAX_STRIPS];
|
||||
static uint8_t g_frame_dirty[MAX_STRIPS];
|
||||
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(unsigned int strip) {
|
||||
if (!ws2812_strip_active(strip)) {
|
||||
return;
|
||||
}
|
||||
uint8_t one[3] = {255, 0, 0};
|
||||
ws2812_set_rgb(strip, one, 1);
|
||||
ws2812_show(strip);
|
||||
sleep_ms(150);
|
||||
one[0] = 0;
|
||||
one[1] = 255;
|
||||
ws2812_set_rgb(strip, one, 1);
|
||||
ws2812_show(strip);
|
||||
sleep_ms(150);
|
||||
one[1] = 0;
|
||||
one[2] = 255;
|
||||
ws2812_set_rgb(strip, one, 1);
|
||||
ws2812_show(strip);
|
||||
sleep_ms(150);
|
||||
one[2] = 0;
|
||||
ws2812_set_rgb(strip, one, 1);
|
||||
ws2812_show(strip);
|
||||
}
|
||||
|
||||
static int buffer_frame(unsigned int strip, const uint8_t *rgb, uint16_t nbytes) {
|
||||
if (strip >= MAX_STRIPS || !ws2812_strip_active(strip)) {
|
||||
return -1;
|
||||
}
|
||||
if (nbytes == 0 || (nbytes % 3u) != 0) {
|
||||
return -1;
|
||||
}
|
||||
uint16_t pixels = nbytes / 3u;
|
||||
if (pixels > MAX_LEDS) {
|
||||
pixels = MAX_LEDS;
|
||||
nbytes = (uint16_t)(pixels * 3u);
|
||||
}
|
||||
memcpy(g_pixel_buf[strip], rgb, nbytes);
|
||||
if (nbytes < LED_RGB_BYTES) {
|
||||
memset(g_pixel_buf[strip] + nbytes, 0, LED_RGB_BYTES - nbytes);
|
||||
}
|
||||
g_frame_pixels[strip] = pixels;
|
||||
g_frame_dirty[strip] = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void buffer_frame_all(const uint8_t *rgb, uint16_t nbytes) {
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (ws2812_strip_active(s)) {
|
||||
buffer_frame(s, rgb, nbytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void show_dirty_frames(void) {
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (!g_frame_dirty[s]) {
|
||||
continue;
|
||||
}
|
||||
ws2812_set_rgb(s, g_pixel_buf[s], g_frame_pixels[s]);
|
||||
ws2812_show(s);
|
||||
g_frame_dirty[s] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int pin_is_reserved(uint8_t pin) {
|
||||
switch (pin) {
|
||||
case PIN_CS:
|
||||
case PIN_SCK:
|
||||
case PIN_MOSI:
|
||||
case PIN_MISO:
|
||||
case PIN_RST:
|
||||
case PIN_LED_STATUS:
|
||||
return 1;
|
||||
default:
|
||||
return pin > 29;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_pin_command(uint8_t strip, uint8_t pin, uint16_t leds) {
|
||||
if (strip >= MAX_STRIPS) {
|
||||
printf("PIN strip %u out of range (max %u)\n", (unsigned)strip, MAX_STRIPS);
|
||||
return;
|
||||
}
|
||||
if (pin_is_reserved(pin)) {
|
||||
printf("PIN GP%u rejected (reserved)\n", (unsigned)pin);
|
||||
return;
|
||||
}
|
||||
if (leds > MAX_LEDS) {
|
||||
printf("PIN leds %u clamped to max_leds=%u\n", (unsigned)leds, MAX_LEDS);
|
||||
leds = MAX_LEDS;
|
||||
}
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (s != strip && ws2812_strip_active(s) && ws2812_get_pin(s) == pin) {
|
||||
printf("PIN GP%u: releasing strip %u\n", (unsigned)pin, s);
|
||||
ws2812_release_strip(s);
|
||||
g_strip_leds[s] = 0;
|
||||
g_frame_pixels[s] = 0;
|
||||
g_frame_dirty[s] = 0;
|
||||
}
|
||||
}
|
||||
if (ws2812_set_pin(strip, pin) != 0) {
|
||||
printf("PIN strip %u GP%u failed (no PIO SM?)\n", (unsigned)strip, (unsigned)pin);
|
||||
return;
|
||||
}
|
||||
g_strip_leds[strip] = leds;
|
||||
if (leds) {
|
||||
printf("strip %u ws2812=GP%u leds=%u\n", (unsigned)strip,
|
||||
(unsigned)ws2812_get_pin(strip), (unsigned)leds);
|
||||
} else {
|
||||
printf("strip %u ws2812=GP%u\n", (unsigned)strip, (unsigned)ws2812_get_pin(strip));
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_packet(const uint8_t *data, int16_t len) {
|
||||
if (len == 4 && memcmp(data, "SHOW", 4) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == 5 && data[0] == 'P' && data[1] == 'I' && data[2] == 'N' && data[3] == 0) {
|
||||
handle_pin_command(0, data[4], 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len == 8 && data[0] == 'P' && data[1] == 'I' && data[2] == 'N' && data[3] == 0) {
|
||||
uint16_t leds = (uint16_t)data[6] | ((uint16_t)data[7] << 8);
|
||||
handle_pin_command(data[4], data[5], leds);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len >= 3 && (len % 3) == 0) {
|
||||
buffer_frame(0, data, (uint16_t)len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len >= 4 && ((len - 1) % 3) == 0) {
|
||||
uint8_t id = data[0];
|
||||
uint16_t nbytes = (uint16_t)(len - 1);
|
||||
if (id == 255) {
|
||||
buffer_frame_all(data + 1, nbytes);
|
||||
return;
|
||||
}
|
||||
if (id < MAX_STRIPS) {
|
||||
buffer_frame(id, data + 1, nbytes);
|
||||
return;
|
||||
}
|
||||
if (id == (uint8_t)PANEL_ID) {
|
||||
buffer_frame(0, data + 1, nbytes);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_strip_map(void) {
|
||||
printf("strips=%d", MAX_STRIPS);
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (ws2812_strip_active(s)) {
|
||||
if (g_strip_leds[s]) {
|
||||
printf(" [%u]=GP%u/%u", s, ws2812_get_pin(s), g_strip_leds[s]);
|
||||
} else {
|
||||
printf(" [%u]=GP%u", s, ws2812_get_pin(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\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("max_leds=%d max_strips=%d panel_id=%d mac=02:50:52:54:4C:%02X "
|
||||
"ip=%u.%u.%u.%u %s\n",
|
||||
MAX_LEDS, MAX_STRIPS, PANEL_ID, (unsigned)PANEL_ID, g_net_info.ip[0],
|
||||
g_net_info.ip[1], g_net_info.ip[2], g_net_info.ip[3],
|
||||
USE_DHCP ? "dhcp" : "static");
|
||||
|
||||
wizchip_spi_initialize();
|
||||
wizchip_cris_initialize();
|
||||
wizchip_reset();
|
||||
wizchip_initialize();
|
||||
wizchip_check();
|
||||
|
||||
ws2812_init(MAX_LEDS);
|
||||
memset(g_pixel_buf, 0, sizeof(g_pixel_buf));
|
||||
memset(g_frame_pixels, 0, sizeof(g_frame_pixels));
|
||||
memset(g_strip_leds, 0, sizeof(g_strip_leds));
|
||||
memset(g_frame_dirty, 0, sizeof(g_frame_dirty));
|
||||
|
||||
static const unsigned int default_pins[MAX_STRIPS] = DEFAULT_STRIP_PINS;
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (ws2812_set_pin(s, default_pins[s]) == 0) {
|
||||
led_boot_test(s);
|
||||
} else {
|
||||
printf("default strip %u GP%u failed\n", s, default_pins[s]);
|
||||
}
|
||||
}
|
||||
print_strip_map();
|
||||
|
||||
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();
|
||||
show_dirty_frames();
|
||||
if (++heartbeat >= 500) {
|
||||
gpio_xor_mask(1u << PIN_LED_STATUS);
|
||||
heartbeat = 0;
|
||||
}
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
54
firmware/pico_sdk_import.cmake
Normal file
54
firmware/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/timer.c
Normal file
21
firmware/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/timer.h
Normal file
10
firmware/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/wizchip_spi.c
Normal file
121
firmware/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/wizchip_spi.h
Normal file
14
firmware/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
|
||||
176
firmware/ws2812_led.cpp
Normal file
176
firmware/ws2812_led.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/* Multi-strip WS2812 wrapper around ForsakenNGS/Pico_WS2812 */
|
||||
|
||||
#include "ws2812_led.h"
|
||||
|
||||
#include "board_config.h"
|
||||
#include "WS2812.hpp"
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
typedef struct {
|
||||
WS2812 *strip;
|
||||
PIO pio;
|
||||
uint sm;
|
||||
unsigned int pin;
|
||||
unsigned int lit;
|
||||
unsigned int show_n;
|
||||
uint8_t active;
|
||||
} strip_slot_t;
|
||||
|
||||
static strip_slot_t s_slots[MAX_STRIPS];
|
||||
static unsigned int s_max_leds;
|
||||
|
||||
static int claim_sm(PIO *pio_out, uint *sm_out) {
|
||||
int sm = pio_claim_unused_sm(pio0, false);
|
||||
if (sm >= 0) {
|
||||
*pio_out = pio0;
|
||||
*sm_out = (uint)sm;
|
||||
return 0;
|
||||
}
|
||||
sm = pio_claim_unused_sm(pio1, false);
|
||||
if (sm >= 0) {
|
||||
*pio_out = pio1;
|
||||
*sm_out = (uint)sm;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern "C" void ws2812_init(unsigned int max_leds) {
|
||||
s_max_leds = max_leds;
|
||||
for (unsigned int i = 0; i < MAX_STRIPS; i++) {
|
||||
s_slots[i].strip = nullptr;
|
||||
s_slots[i].pin = 0;
|
||||
s_slots[i].lit = 0;
|
||||
s_slots[i].show_n = 0;
|
||||
s_slots[i].active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ws2812_release_strip(unsigned int strip) {
|
||||
if (strip >= MAX_STRIPS) {
|
||||
return;
|
||||
}
|
||||
strip_slot_t *slot = &s_slots[strip];
|
||||
if (!slot->active) {
|
||||
return;
|
||||
}
|
||||
pio_sm_set_enabled(slot->pio, slot->sm, false);
|
||||
gpio_set_function(slot->pin, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(slot->pin, GPIO_IN);
|
||||
delete slot->strip;
|
||||
slot->strip = nullptr;
|
||||
pio_sm_unclaim(slot->pio, slot->sm);
|
||||
slot->pin = 0;
|
||||
slot->lit = 0;
|
||||
slot->show_n = 0;
|
||||
slot->active = 0;
|
||||
}
|
||||
|
||||
extern "C" int ws2812_set_pin(unsigned int strip, unsigned int pin) {
|
||||
if (strip >= MAX_STRIPS || pin > 29) {
|
||||
return -1;
|
||||
}
|
||||
strip_slot_t *slot = &s_slots[strip];
|
||||
if (slot->active && slot->pin == pin) {
|
||||
return 0;
|
||||
}
|
||||
/* Steal pin from any other strip that already owns it. */
|
||||
for (unsigned int s = 0; s < MAX_STRIPS; s++) {
|
||||
if (s != strip && s_slots[s].active && s_slots[s].pin == pin) {
|
||||
ws2812_release_strip(s);
|
||||
}
|
||||
}
|
||||
if (slot->active) {
|
||||
/* Tear down and rebuild on the same PIO SM — more reliable than setPin alone. */
|
||||
PIO pio = slot->pio;
|
||||
uint sm = slot->sm;
|
||||
unsigned int old_pin = slot->pin;
|
||||
pio_sm_set_enabled(pio, sm, false);
|
||||
gpio_set_function(old_pin, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(old_pin, GPIO_IN);
|
||||
delete slot->strip;
|
||||
slot->strip = new WS2812(pin, s_max_leds, pio, sm, WS2812::FORMAT_GRB);
|
||||
slot->pin = pin;
|
||||
slot->lit = 0;
|
||||
slot->show_n = 0;
|
||||
return 0;
|
||||
}
|
||||
PIO pio;
|
||||
uint sm;
|
||||
if (claim_sm(&pio, &sm) != 0) {
|
||||
return -1;
|
||||
}
|
||||
slot->strip = new WS2812(pin, s_max_leds, pio, sm, WS2812::FORMAT_GRB);
|
||||
slot->pio = pio;
|
||||
slot->sm = sm;
|
||||
slot->pin = pin;
|
||||
slot->lit = 0;
|
||||
slot->show_n = 0;
|
||||
slot->active = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" unsigned int ws2812_get_pin(unsigned int strip) {
|
||||
if (strip >= MAX_STRIPS || !s_slots[strip].active) {
|
||||
return 0;
|
||||
}
|
||||
return s_slots[strip].pin;
|
||||
}
|
||||
|
||||
extern "C" int ws2812_strip_active(unsigned int strip) {
|
||||
return strip < MAX_STRIPS && s_slots[strip].active;
|
||||
}
|
||||
|
||||
extern "C" void ws2812_set_rgb(unsigned int strip, const uint8_t *rgb, unsigned int count) {
|
||||
if (strip >= MAX_STRIPS || !s_slots[strip].active || rgb == nullptr) {
|
||||
return;
|
||||
}
|
||||
strip_slot_t *slot = &s_slots[strip];
|
||||
if (count > s_max_leds) {
|
||||
count = s_max_leds;
|
||||
}
|
||||
unsigned int old = slot->lit;
|
||||
unsigned int i = 0;
|
||||
for (; i < count; i++) {
|
||||
slot->strip->setPixelColor(i, rgb[i * 3 + 0], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
||||
}
|
||||
for (; i < old; i++) {
|
||||
slot->strip->setPixelColor(i, 0, 0, 0);
|
||||
}
|
||||
slot->lit = count;
|
||||
slot->show_n = count > old ? count : old;
|
||||
}
|
||||
|
||||
extern "C" void ws2812_fill(unsigned int strip, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (strip >= MAX_STRIPS || !s_slots[strip].active) {
|
||||
return;
|
||||
}
|
||||
strip_slot_t *slot = &s_slots[strip];
|
||||
slot->strip->fill(WS2812::RGB(r, g, b));
|
||||
slot->lit = s_max_leds;
|
||||
slot->show_n = s_max_leds;
|
||||
}
|
||||
|
||||
extern "C" void ws2812_show(unsigned int strip) {
|
||||
if (strip >= MAX_STRIPS || !s_slots[strip].active) {
|
||||
return;
|
||||
}
|
||||
strip_slot_t *slot = &s_slots[strip];
|
||||
unsigned int n = slot->show_n;
|
||||
if (n == 0) {
|
||||
n = 1;
|
||||
}
|
||||
slot->strip->show(n);
|
||||
sleep_us(300);
|
||||
slot->show_n = slot->lit;
|
||||
}
|
||||
|
||||
extern "C" void ws2812_show_all(void) {
|
||||
for (unsigned int i = 0; i < MAX_STRIPS; i++) {
|
||||
if (s_slots[i].active) {
|
||||
ws2812_show(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
firmware/ws2812_led.h
Normal file
33
firmware/ws2812_led.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef WS2812_LED_H
|
||||
#define WS2812_LED_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MAX_STRIPS
|
||||
#define MAX_STRIPS 8
|
||||
#endif
|
||||
|
||||
/* Create strip slots (none active until ws2812_set_pin). max_leds = capacity each. */
|
||||
void ws2812_init(unsigned int max_leds);
|
||||
|
||||
/* Bind strip to gpio (creates/rebinds PIO SM). strip = 0 … MAX_STRIPS-1. */
|
||||
int ws2812_set_pin(unsigned int strip, unsigned int pin);
|
||||
/* Free PIO SM + GPIO so another strip can take the pin. */
|
||||
void ws2812_release_strip(unsigned int strip);
|
||||
unsigned int ws2812_get_pin(unsigned int strip);
|
||||
int ws2812_strip_active(unsigned int strip);
|
||||
|
||||
void ws2812_set_rgb(unsigned int strip, const uint8_t *rgb, unsigned int count);
|
||||
void ws2812_fill(unsigned int strip, uint8_t r, uint8_t g, uint8_t b);
|
||||
void ws2812_show(unsigned int strip);
|
||||
void ws2812_show_all(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user