/** * 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 #include #include #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); } }