Bring the five-panel hex portal online with a browser 3D/schematic preview, Pi SPI backends, and renamed multi-panel Pico UDP firmware. Co-authored-by: Cursor <cursoragent@cursor.com>
363 lines
9.6 KiB
C
363 lines
9.6 KiB
C
/**
|
||
* 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=10.1.1.%u\n",
|
||
MAX_LEDS, MAX_STRIPS, PANEL_ID, (unsigned)PANEL_ID, (unsigned)STATIC_IP_OCT4);
|
||
|
||
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] = {PIN_WS2812, PIN_WS2812_1};
|
||
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);
|
||
}
|
||
}
|