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:
2026-06-28 22:46:08 +12:00
parent d8323bb9a3
commit d5cab2efdf
52 changed files with 4677 additions and 1 deletions

219
firmware/panel/main.c Normal file
View 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);
}
}