From 14465abcd8f8199eddd7c0febd8d811f372e118e Mon Sep 17 00:00:00 2001 From: jackw01 Date: Wed, 2 Aug 2023 21:36:16 -0700 Subject: [PATCH] proof of concept - support wifi streaming control with pi pico w --- CMakeLists.txt | 9 +- firmware/CMakeLists.txt | 5 +- firmware/backend.c | 282 +++++++++++++++++++++++++ firmware/backend.h | 20 ++ firmware/lwipopts.h | 90 ++++++++ firmware/main.c | 335 +++--------------------------- firmware/wifi.c | 57 +++++ firmware/wifi.h | 4 + firmware/wifi_config.h | 6 + ledcontrol/animationcontroller.py | 49 ++--- ledcontrol/ledcontroller.py | 91 ++++---- pico_sdk_import.cmake | 72 ------- 12 files changed, 570 insertions(+), 450 deletions(-) create mode 100644 firmware/backend.c create mode 100644 firmware/backend.h create mode 100644 firmware/lwipopts.h create mode 100644 firmware/wifi.c create mode 100644 firmware/wifi.h create mode 100644 firmware/wifi_config.h delete mode 100644 pico_sdk_import.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fea8c..1f05fbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,18 +4,19 @@ set(CMAKE_C_COMPILER_FORCED TRUE) set(CMAKE_CXX_COMPILER_FORCED TRUE) # Pull in SDK (must be before project) -include(pico_sdk_import.cmake) +include( C:/Users/jrwhi/dev/oss/pico-sdk/external/pico_sdk_import.cmake ) +include( $ENV{PICO_EXTRAS_PATH}/external/pico_extras_import.cmake ) set(OUTPUT_NAME "ledcontrol") -#set(PICO_BOARD "adafruit_feather_rp2040") +set(PICO_BOARD "pico_w") project(${OUTPUT_NAME} C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) -if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") - message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") +if (PICO_SDK_VERSION_STRING VERSION_LESS "1.5.0") + message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.5.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() # Initialize the SDK diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index e014982..60f5fcf 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -14,13 +14,16 @@ pico_enable_stdio_uart(${OUTPUT_NAME} 0) target_compile_definitions(${OUTPUT_NAME} PRIVATE PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1) target_sources(${OUTPUT_NAME} - PRIVATE main.c + PRIVATE main.c backend.c wifi.c ) +target_include_directories(${OUTPUT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + target_link_libraries(${OUTPUT_NAME} pico_stdlib pico_multicore hardware_pio + pico_cyw43_arch_lwip_threadsafe_background ) pico_enable_stdio_usb(${OUTPUT_NAME} 1) diff --git a/firmware/backend.c b/firmware/backend.c new file mode 100644 index 0000000..103ac21 --- /dev/null +++ b/firmware/backend.c @@ -0,0 +1,282 @@ +#include "ws2812b.pio.h" +#include "backend.h" + +// PIO + +const PIO PIOBlock = pio0; +const uint PIOStateMachine = 0; + +// LEDs + +uint shift_r, shift_g, shift_b, shift_w; +uint32_t frame_buffer[LEDCount]; +bool has_white; + +uint8_t backend_incoming_data_buffer[BackendIncomingDataBufferSize]; + +uint32_t pack_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + return (w << shift_w) | (r << shift_r) | (g << shift_g) | (b << shift_b); +} + +uint8_t scale_8(uint8_t a, uint8_t b) { + return ((uint16_t)a * (uint16_t)b) >> 8; +} + +void backend_write_frame_buffer() { + for (uint i = 0; i < LEDCount; i++) { + pio_sm_put_blocking(PIOBlock, PIOStateMachine, frame_buffer[i]); + } +} + +uint32_t render_hsv2rgb_rainbow(uint8_t h, uint8_t s, uint8_t v, + uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, + uint8_t saturation, uint8_t brightness) { + uint8_t hue = h; + uint8_t sat = scale_8(s, saturation); + uint8_t val = scale_8(v, v); + if (val > 0 && val < 255) val += 1; + val = scale_8(val, brightness); + uint8_t r, g, b, w; + + w = 0; + + uint8_t offset = hue & 0x1F; // 0..31 + uint8_t offset8 = offset << 3; + uint8_t third = offset8 / 3; + + if (!(hue & 0x80)) { + if (!(hue & 0x40)) { + // section 0-1 + if (!(hue & 0x20)) { + // case 0: // R -> O + r = 255 - third; + g = third; + b = 0; + } else { + // case 1: // O -> Y + r = 171; + g = 85 + third; + b = 0; + } + } else { + // section 2-3 + if (!(hue & 0x20)) { + // case 2: // Y -> G + r = 171 - third * 2; + g = 170 + third; + b = 0; + } else { + // case 3: // G -> A + r = 0; + g = 255 - third; + b = third; + } + } + } else { + // section 4-7 + if (!(hue & 0x40)) { + if (!(hue & 0x20)) { + // case 4: // A -> B + r = 0; + uint8_t twothirds = third * 2; + g = 171 - twothirds; // K170? + b = 85 + twothirds; + } else { + // case 5: // B -> P + r = third; + g = 0; + b = 255 - third; + } + } else { + if (!(hue & 0x20)) { + // case 6: // P -- K + r = 85 + third; + g = 0; + b = 171 - third; + } else { + // case 7: // K -> R + r = 170 + third; + g = 0; + b = 85 - third; + } + } + } + + // Scale down colors if we're desaturated at all + // and add the brightness_floor to r, g, and b. + if (has_white) { + if (sat != 255) { + if (sat == 0) { + r = 0; + b = 0; + g = 0; + w = 255; + } else { + uint8_t desat = 255 - sat; + desat = scale_8(desat, desat); + r = scale_8(r, sat); + g = scale_8(g, sat); + b = scale_8(b, sat); + w = desat; + } + } + } else { + if (sat != 255) { + if (sat == 0) { + r = 255; + b = 255; + g = 255; + } else { + uint8_t desat = 255 - sat; + desat = scale_8(desat, desat); + r = scale_8(r, sat) + desat; + g = scale_8(g, sat) + desat; + b = scale_8(b, sat) + desat; + } + } + } + + // Now scale everything down if we're at value < 255. + if (val != 255) { + if (val == 0) { + r = 0; + g = 0; + b = 0; + w = 0; + } else { + r = scale_8(r, val); + g = scale_8(g, val); + b = scale_8(b, val); + w = scale_8(w, val); + } + } + + r = scale_8(r, corr_r); + g = scale_8(g, corr_g); + b = scale_8(b, corr_b); + + return pack_rgbw(r, g, b, w); +} + +uint32_t render_rgb(uint8_t r, uint8_t g, uint8_t b, + uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, + uint8_t saturation, uint8_t brightness) { + uint8_t w = 0; + + if (has_white) { + uint8_t max = r > g ? (r > b ? r : b) : (g > b ? g : b); + uint8_t min; + if (saturation == 0) { + r = 0; + g = 0; + b = 0; + min = max; + } else { + r = (int)r - max * (int)saturation / 255 + max; + g = (int)g - max * (int)saturation / 255 + max; + b = (int)b - max * (int)saturation / 255 + max; + min = r < g ? (r < b ? r : b) : (g < b ? g : b); + r -= min; + g -= min; + b -= min; + } + w = scale_8(min, min); + } else { + // If saturation is not 1, desaturate the color + // Moves r/g/b values closer to their average + // Not sure if this is the technically correct way but it seems to work? + if (saturation != 255) { + uint8_t v = (r + g + b) / 3; + if (saturation == 0) { + r = v; + b = v; + g = v; + } else { + r = scale_8(r - v, saturation) + v; + g = scale_8(g - v, saturation) + v; + b = scale_8(b - v, saturation) + v; + } + } + } + + r = ((uint32_t)r * (uint32_t)brightness * (uint32_t)corr_r) >> 16; + g = ((uint32_t)g * (uint32_t)brightness * (uint32_t)corr_g) >> 16; + b = ((uint32_t)b * (uint32_t)brightness * (uint32_t)corr_b) >> 16; + w = scale_8(w, brightness); + + return pack_rgbw(r, g, b, w); +} + +void rgb_render_calibration(uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, + uint8_t brightness) { + uint8_t r = scale_8(corr_r, brightness); + uint8_t g = scale_8(corr_g, brightness); + uint8_t b = scale_8(corr_b, brightness); + uint32_t c = pack_rgbw(r, g, b, 0); + for (int i = 0; i < LEDCount; i++) { + frame_buffer[i] = c; + } +} + +void backend_handle_command() { + if (backend_incoming_data_buffer[0] != PacketStartByte) return; + uint8_t command_type = backend_incoming_data_buffer[1]; + if (command_type == CmdTypeCalibration) { + rgb_render_calibration(backend_incoming_data_buffer[4], + backend_incoming_data_buffer[5], + backend_incoming_data_buffer[6], + backend_incoming_data_buffer[7]); + backend_write_frame_buffer(); + } else if (command_type == CmdTypeRenderRGB || + command_type == CmdTypeRenderHSV) { + uint start = backend_incoming_data_buffer[9] << 8 | backend_incoming_data_buffer[10]; + uint end = backend_incoming_data_buffer[11] << 8 | backend_incoming_data_buffer[12]; + if (end > LEDCount) return; + if (command_type == CmdTypeRenderRGB) { + for (int i = start; i < end; i++) { + uint pos = (i - start) * 3 + 13; + frame_buffer[i] = render_rgb(backend_incoming_data_buffer[pos], + backend_incoming_data_buffer[pos + 1], + backend_incoming_data_buffer[pos + 2], + backend_incoming_data_buffer[4], + backend_incoming_data_buffer[5], + backend_incoming_data_buffer[6], + backend_incoming_data_buffer[7], + backend_incoming_data_buffer[8]); + } + } else if (command_type == CmdTypeRenderHSV) { + for (int i = start; i < end; i++) { + uint pos = (i - start) * 3 + 13; + frame_buffer[i] = render_hsv2rgb_rainbow(backend_incoming_data_buffer[pos], + backend_incoming_data_buffer[pos + 1], + backend_incoming_data_buffer[pos + 2], + backend_incoming_data_buffer[4], + backend_incoming_data_buffer[5], + backend_incoming_data_buffer[6], + backend_incoming_data_buffer[7], + backend_incoming_data_buffer[8]); + } + } + } else if (command_type == CmdTypeWriteLEDs) { + backend_write_frame_buffer(); + } +} + +void backend_init() { + uint offset = pio_add_program(PIOBlock, &WS2812B_program); + has_white = StripType & 0xf0000000; + WS2812B_program_init(PIOBlock, PIOStateMachine, + offset, LEDPin, + 800000, has_white ? 32 : 24); + + shift_w = 0; + shift_r = ((StripType >> 16) & 0xff) + (has_white ? 8 : 0); + shift_g = ((StripType >> 8) & 0xff) + (has_white ? 8 : 0); + shift_b = ((StripType >> 0) & 0xff) + (has_white ? 8 : 0); + + for (uint i = 0; i < LEDCount; i++) { + frame_buffer[i] = 0x00000000; + } + + backend_write_frame_buffer(); +} diff --git a/firmware/backend.h b/firmware/backend.h new file mode 100644 index 0000000..97eb997 --- /dev/null +++ b/firmware/backend.h @@ -0,0 +1,20 @@ +#pragma once + +#include "config.h" + +enum CmdType { + CmdTypeCalibration = 0, + CmdTypeRenderRGB, + CmdTypeRenderHSV, + CmdTypeWriteLEDs, + CmdTypeMax +}; + +#define PacketStartByte 0 + +#define BackendIncomingDataBufferSize (LEDCount * 3 + 64) +extern uint8_t backend_incoming_data_buffer[BackendIncomingDataBufferSize]; + +void backend_write_frame_buffer(); +void backend_handle_command(); +void backend_init(); diff --git a/firmware/lwipopts.h b/firmware/lwipopts.h new file mode 100644 index 0000000..ea6148f --- /dev/null +++ b/firmware/lwipopts.h @@ -0,0 +1,90 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +// allow override in some examples +#ifndef NO_SYS +#define NO_SYS 1 +#endif +// allow override in some examples +#ifndef LWIP_SOCKET +#define LWIP_SOCKET 0 +#endif +#if PICO_CYW43_ARCH_POLL +#define MEM_LIBC_MALLOC 1 +#else +// MEM_LIBC_MALLOC is incompatible with non polling versions +#define MEM_LIBC_MALLOC 0 +#endif +#define MEM_ALIGNMENT 4 +#define MEM_SIZE 4000 +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +#endif diff --git a/firmware/main.c b/firmware/main.c index 1a2cb1b..8257d9e 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -4,313 +4,44 @@ #include "pico/stdlib.h" #include "hardware/pio.h" -#include "config.h" -#include "ws2812b.pio.h" - -// PIO - -const PIO PIOBlock = pio0; -const uint PIOStateMachine = 0; - -// LEDs - -uint shift_r, shift_g, shift_b, shift_w; -uint32_t frame_buffer[LEDCount]; -bool has_white; - -uint32_t pack_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - return (w << shift_w) | (r << shift_r) | (g << shift_g) | (b << shift_b); -} - -uint8_t scale_8(uint8_t a, uint8_t b) { - return ((uint16_t)a * (uint16_t)b) >> 8; -} - -void write_frame_buffer() { - for (uint i = 0; i < LEDCount; i++) { - pio_sm_put_blocking(PIOBlock, PIOStateMachine, frame_buffer[i]); - } -} - -uint32_t render_hsv2rgb_rainbow(uint8_t h, uint8_t s, uint8_t v, - uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, - uint8_t saturation, uint8_t brightness) { - uint8_t hue = h; - uint8_t sat = scale_8(s, saturation); - uint8_t val = scale_8(v, v); - if (val > 0 && val < 255) val += 1; - val = scale_8(val, brightness); - uint8_t r, g, b, w; - - w = 0; - - uint8_t offset = hue & 0x1F; // 0..31 - uint8_t offset8 = offset << 3; - uint8_t third = offset8 / 3; - - if (!(hue & 0x80)) { - if (!(hue & 0x40)) { - // section 0-1 - if (!(hue & 0x20)) { - // case 0: // R -> O - r = 255 - third; - g = third; - b = 0; - } else { - // case 1: // O -> Y - r = 171; - g = 85 + third; - b = 0; - } - } else { - // section 2-3 - if (!(hue & 0x20)) { - // case 2: // Y -> G - r = 171 - third * 2; - g = 170 + third; - b = 0; - } else { - // case 3: // G -> A - r = 0; - g = 255 - third; - b = third; - } - } - } else { - // section 4-7 - if (!(hue & 0x40)) { - if (!(hue & 0x20)) { - // case 4: // A -> B - r = 0; - uint8_t twothirds = third * 2; - g = 171 - twothirds; // K170? - b = 85 + twothirds; - } else { - // case 5: // B -> P - r = third; - g = 0; - b = 255 - third; - } - } else { - if (!(hue & 0x20)) { - // case 6: // P -- K - r = 85 + third; - g = 0; - b = 171 - third; - } else { - // case 7: // K -> R - r = 170 + third; - g = 0; - b = 85 - third; - } - } - } - - // Scale down colors if we're desaturated at all - // and add the brightness_floor to r, g, and b. - if (has_white) { - if (sat != 255) { - if (sat == 0) { - r = 0; - b = 0; - g = 0; - w = 255; - } else { - uint8_t desat = 255 - sat; - desat = scale_8(desat, desat); - r = scale_8(r, sat); - g = scale_8(g, sat); - b = scale_8(b, sat); - w = desat; - } - } - } else { - if (sat != 255) { - if (sat == 0) { - r = 255; - b = 255; - g = 255; - } else { - uint8_t desat = 255 - sat; - desat = scale_8(desat, desat); - r = scale_8(r, sat) + desat; - g = scale_8(g, sat) + desat; - b = scale_8(b, sat) + desat; - } - } - } - - // Now scale everything down if we're at value < 255. - if (val != 255) { - if (val == 0) { - r = 0; - g = 0; - b = 0; - w = 0; - } else { - r = scale_8(r, val); - g = scale_8(g, val); - b = scale_8(b, val); - w = scale_8(w, val); - } - } - - r = scale_8(r, corr_r); - g = scale_8(g, corr_g); - b = scale_8(b, corr_b); - - return pack_rgbw(r, g, b, w); -} - -uint32_t render_rgb(uint8_t r, uint8_t g, uint8_t b, - uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, - uint8_t saturation, uint8_t brightness) { - uint8_t w = 0; - - if (has_white) { - uint8_t max = r > g ? (r > b ? r : b) : (g > b ? g : b); - uint8_t min; - if (saturation == 0) { - r = 0; - g = 0; - b = 0; - min = max; - } else { - r = (int)r - max * (int)saturation / 255 + max; - g = (int)g - max * (int)saturation / 255 + max; - b = (int)b - max * (int)saturation / 255 + max; - min = r < g ? (r < b ? r : b) : (g < b ? g : b); - r -= min; - g -= min; - b -= min; - } - w = scale_8(min, min); - } else { - // If saturation is not 1, desaturate the color - // Moves r/g/b values closer to their average - // Not sure if this is the technically correct way but it seems to work? - if (saturation != 255) { - uint8_t v = (r + g + b) / 3; - if (saturation == 0) { - r = v; - b = v; - g = v; - } else { - r = scale_8(r - v, saturation) + v; - g = scale_8(g - v, saturation) + v; - b = scale_8(b - v, saturation) + v; - } - } - } - - r = ((uint32_t)r * (uint32_t)brightness * (uint32_t)corr_r) >> 16; - g = ((uint32_t)g * (uint32_t)brightness * (uint32_t)corr_g) >> 16; - b = ((uint32_t)b * (uint32_t)brightness * (uint32_t)corr_b) >> 16; - w = scale_8(w, brightness); - - return pack_rgbw(r, g, b, w); -} - -void rgb_render_calibration(uint8_t corr_r, uint8_t corr_g, uint8_t corr_b, - uint8_t brightness) { - uint8_t r = scale_8(corr_r, brightness); - uint8_t g = scale_8(corr_g, brightness); - uint8_t b = scale_8(corr_b, brightness); - uint32_t c = pack_rgbw(r, g, b, 0); - for (int i = 0; i < LEDCount; i++) { - frame_buffer[i] = c; - } - write_frame_buffer(); -} +#include "backend.h" +#include "wifi.h" // UART -const uint8_t PacketStartByte = 0; - -enum CmdType { - CmdTypeCalibration = 0, - CmdTypeRenderRGB, - CmdTypeRenderHSV, - CmdTypeWriteLEDs, - CmdTypeMax -}; - -uint8_t packet_type; -uint packet_index = 0; -uint packet_length; -uint8_t incoming_data_buffer[LEDCount * 3 + 64]; - -void handle_uart_packet() { - if (packet_type == CmdTypeCalibration) { - rgb_render_calibration(incoming_data_buffer[0], - incoming_data_buffer[1], - incoming_data_buffer[2], - incoming_data_buffer[3]); - } else if (packet_type == CmdTypeRenderRGB || packet_type == CmdTypeRenderHSV) { - uint start = incoming_data_buffer[5] << 8 | incoming_data_buffer[6]; - uint end = incoming_data_buffer[7] << 8 | incoming_data_buffer[8]; - if (end > LEDCount) return; - if (packet_type == CmdTypeRenderRGB) { - for (int i = start; i < end; i++) { - uint pos = (i - start) * 3 + 9; - frame_buffer[i] = render_rgb(incoming_data_buffer[pos], - incoming_data_buffer[pos + 1], - incoming_data_buffer[pos + 2], - incoming_data_buffer[0], - incoming_data_buffer[1], - incoming_data_buffer[2], - incoming_data_buffer[3], - incoming_data_buffer[4]); - } - } else if (packet_type == CmdTypeRenderHSV) { - for (int i = start; i < end; i++) { - uint pos = (i - start) * 3 + 9; - frame_buffer[i] = render_hsv2rgb_rainbow(incoming_data_buffer[pos], - incoming_data_buffer[pos + 1], - incoming_data_buffer[pos + 2], - incoming_data_buffer[0], - incoming_data_buffer[1], - incoming_data_buffer[2], - incoming_data_buffer[3], - incoming_data_buffer[4]); - } - } - } else if (packet_type == CmdTypeWriteLEDs) { - write_frame_buffer(); - } -} +uint uart_packet_index = 0; +uint uart_packet_length; void handle_uart_input(char c) { - if (packet_index == 0 && c == PacketStartByte) { + backend_incoming_data_buffer[uart_packet_index] = c; + if (uart_packet_index == 0 && c == PacketStartByte) { // Packet start - packet_index++; - packet_length = 0; - } else if (packet_index == 1) { + uart_packet_index++; + uart_packet_length = 0; + } else if (uart_packet_index == 1) { // Packet type if (c < CmdTypeMax) { - packet_type = c; - packet_index++; - } else packet_index = 0; - } else if (packet_index == 2) { + uart_packet_index++; + } else uart_packet_index = 0; + } else if (uart_packet_index == 2) { // Size byte 1 - packet_length = c << 8; - packet_index++; - } else if (packet_index == 3) { + uart_packet_length = c << 8; + uart_packet_index++; + } else if (uart_packet_index == 3) { // Size byte 2 - packet_length |= c; - packet_index++; - } else if (packet_index >= 4) { + uart_packet_length |= c; + uart_packet_index++; + } else if (uart_packet_index >= 4) { // Data start - incoming_data_buffer[packet_index - 4] = c; - packet_index++; - if (packet_index == packet_length || - packet_index == sizeof(incoming_data_buffer) + 3) { - handle_uart_packet(); - packet_index = 0; + uart_packet_index++; + if (uart_packet_index == uart_packet_length || + uart_packet_index == BackendIncomingDataBufferSize + 2) { + backend_handle_command(); + uart_packet_index = 0; } } else { // End - packet_index = 0; + uart_packet_index = 0; } } @@ -321,22 +52,8 @@ int main() { stdio_init_all(); - uint offset = pio_add_program(PIOBlock, &WS2812B_program); - has_white = StripType & 0xf0000000; - WS2812B_program_init(PIOBlock, PIOStateMachine, - offset, LEDPin, - 800000, has_white ? 32 : 24); - - shift_w = 0; - shift_r = ((StripType >> 16) & 0xff) + (has_white ? 8 : 0); - shift_g = ((StripType >> 8) & 0xff) + (has_white ? 8 : 0); - shift_b = ((StripType >> 0) & 0xff) + (has_white ? 8 : 0); - - for (uint i = 0; i < LEDCount; i++) { - frame_buffer[i] = 0x00000000; - } - - write_frame_buffer(); + backend_init(); + wifi_init(); while (true) { int32_t c = getchar_timeout_us(0); diff --git a/firmware/wifi.c b/firmware/wifi.c new file mode 100644 index 0000000..5486385 --- /dev/null +++ b/firmware/wifi.c @@ -0,0 +1,57 @@ +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" + +#include "lwip/netif.h" +#include "lwip/ip4_addr.h" +#include "lwip/pbuf.h" +#include "lwip/udp.h" + +#include "backend.h" +#include "wifi.h" +#include "wifi_config.h" + +#define CYW43_LWIP 1 + +struct udp_pcb *pcb; + +static void udp_receive(void *arg, struct udp_pcb *pcb, struct pbuf *p, const struct ip4_addr *addr, unsigned short port) { + if(p == NULL) return; + + uint8_t *req_data = (uint8_t *)p->payload; + memcpy(backend_incoming_data_buffer, req_data, MIN(p->len, BackendIncomingDataBufferSize)); + backend_handle_command(); + + pbuf_free(p); +} + +void wifi_init() { + if (cyw43_arch_init()) { + printf("cyw43_arch failed to initialize.\n"); + return; + } + + cyw43_arch_enable_sta_mode(); + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("Failed to connect.\n"); + return; + } else { + printf("Connected.\n"); + } + + cyw43_arch_lwip_begin(); + + printf("IP address: %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list))); + + pcb = udp_new(); + udp_bind(pcb, IP_ADDR_ANY, PORT); + udp_recv(pcb, udp_receive, 0); + + cyw43_arch_lwip_end(); +} + +void wifi_deinit() { + cyw43_arch_lwip_begin(); + cyw43_arch_deinit(); + cyw43_arch_lwip_end(); +} diff --git a/firmware/wifi.h b/firmware/wifi.h new file mode 100644 index 0000000..82c8d80 --- /dev/null +++ b/firmware/wifi.h @@ -0,0 +1,4 @@ +#pragma once + +void wifi_init(); +void wifi_deinit(); diff --git a/firmware/wifi_config.h b/firmware/wifi_config.h new file mode 100644 index 0000000..936b9c4 --- /dev/null +++ b/firmware/wifi_config.h @@ -0,0 +1,6 @@ +#pragma once + +#define WIFI_SSID "test network" +#define WIFI_PASSWORD "*9b3fr@!aaJqZ5Vn" + +#define PORT 8888 diff --git a/ledcontrol/animationcontroller.py b/ledcontrol/animationcontroller.py index ab2135f..6da4f98 100644 --- a/ledcontrol/animationcontroller.py +++ b/ledcontrol/animationcontroller.py @@ -373,27 +373,22 @@ def update_leds(self): for i in range(range_start, range_end)] self._prev_state[range_start:range_end] = state - # Write colors to LEDs - if mode == animfunctions.ColorMode.hsv: - self._led_controller.set_range_hsv(state, range_start, range_end, - self._correction, - computed_saturation, - computed_brightness) - elif mode == animfunctions.ColorMode.rgb: - self._led_controller.set_range_rgb(state, range_start, range_end, - self._correction, - computed_saturation, - computed_brightness) + self._led_controller.set_range(state, range_start, range_end, + self._correction, + computed_saturation, + computed_brightness, + mode) except Exception as e: msg = traceback.format_exception(type(e), e, e.__traceback__) print(f'Animation execution: {msg}') r = 0.1 * driver.wave_pulse(time_fix, 0.5) - self._led_controller.set_range_rgb([(r, 0, 0) for i in range(self._led_count)], - 0, self._led_count, - self._correction, - 1.0, - 1.0) + self._led_controller.set_range([(r, 0, 0) for i in range(self._led_count)], + 0, self._led_count, + self._correction, + 1.0, + 1.0, + animfunctions.ColorMode.rgb) self._led_controller.render() return @@ -419,20 +414,22 @@ def _sacn_callback(self, packet): self._sacn_perf_avg = 0 data = [x / 255.0 for x in packet.dmxData[:self._led_count * 3]] - self._led_controller.set_range_rgb(list(zip_longest(*(iter(data),) * 3)), - 0, self._led_count, - self._correction, - 1.0, - self._settings['global_brightness']) + self._led_controller.set_range(list(zip_longest(*(iter(data),) * 3)), + 0, self._led_count, + self._correction, + 1.0, + self._settings['global_brightness'], + animfunctions.ColorMode.rgb) self._led_controller.render() def clear_leds(self): 'Turn all LEDs off' - self._led_controller.set_range_rgb([(0, 0, 0) for i in range(self._led_count)], - 0, self._led_count, - self._correction, - 1.0, - 1.0) + self._led_controller.set_range([(0, 0, 0) for i in range(self._led_count)], + 0, self._led_count, + self._correction, + 1.0, + 1.0, + animfunctions.ColorMode.rgb) self._led_controller.render() def end_animation(self): diff --git a/ledcontrol/ledcontroller.py b/ledcontrol/ledcontroller.py index 2dd0002..0403cf4 100644 --- a/ledcontrol/ledcontroller.py +++ b/ledcontrol/ledcontroller.py @@ -5,10 +5,16 @@ import serial import numpy as np import itertools +import socket +from enum import Enum + +import ledcontrol.animationfunctions as animfunctions import ledcontrol.driver as driver import ledcontrol.utils as utils +TargetMode = Enum('TargetMode', ['serial', 'udp']) + class LEDController: def __init__(self, led_count, @@ -83,7 +89,15 @@ def __init__(self, else: self._where_hue = np.zeros((led_count * 3,),dtype=bool) self._where_hue[0::3] = True - self._ser = serial.Serial(serial_port, 115200, timeout=0.01, write_timeout=0) + + self._target_mode = TargetMode.udp + + if self._target_mode == TargetMode.serial: + self._serial = serial.Serial(serial_port, 115200, timeout=0.01, write_timeout=0) + elif self._target_mode == TargetMode.udp: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._udp_target = "192.168.8.230" + self._udp_port = 8888 def _cleanup(self): # Clean up memory used by the library when not needed anymore @@ -92,55 +106,56 @@ def _cleanup(self): self._leds = None self._channel = None - def set_range_hsv(self, pixels, start, end, correction, saturation, brightness): - if driver.is_raspberrypi(): - driver.ws2811_hsv_render_range_float(self._channel, pixels, start, end, - correction, saturation, brightness, 1.0, - self._has_white) - else: - data = np.fromiter(itertools.chain.from_iterable(pixels), np.float32) - np.fmod(data, 1.0, where=self._where_hue[0:(end - start) * 3], out=data) - data = data * 255.0 - data = data.astype(np.uint8) - self._ser.write(b'\x00\x02' - + int((end - start) * 3 + 13).to_bytes(2, 'big') - + correction.to_bytes(3, 'big') - + int(saturation * 255).to_bytes(1, 'big') - + int(brightness * 255).to_bytes(1, 'big') - + start.to_bytes(2, 'big') - + end.to_bytes(2, 'big') - + data.tobytes()) - - def set_range_rgb(self, pixels, start, end, correction, saturation, brightness): + def set_range(self, pixels, start, end, correction, saturation, brightness, color_mode): if driver.is_raspberrypi(): - driver.ws2811_rgb_render_range_float(self._channel, pixels, start, end, - correction, saturation, brightness, 1.0, - self._has_white) + if color_mode == animfunctions.ColorMode.hsv: + driver.ws2811_hsv_render_range_float(self._channel, pixels, start, end, + correction, saturation, brightness, 1.0, + self._has_white) + else: + driver.ws2811_rgb_render_range_float(self._channel, pixels, start, end, + correction, saturation, brightness, 1.0, + self._has_white) + else: data = np.fromiter(itertools.chain.from_iterable(pixels), np.float32) - data = data * 255.0 - data = np.clip(data, 0.0, 255.0) + if color_mode == animfunctions.ColorMode.hsv: + np.fmod(data, 1.0, where=self._where_hue[0:(end - start) * 3], out=data) + data = data * 255.0 + else: + data = data * 255.0 + data = np.clip(data, 0.0, 255.0) data = data.astype(np.uint8) - self._ser.write(b'\x00\x01' - + int((end - start) * 3 + 13).to_bytes(2, 'big') - + correction.to_bytes(3, 'big') - + int(saturation * 255).to_bytes(1, 'big') - + int(brightness * 255).to_bytes(1, 'big') - + start.to_bytes(2, 'big') - + end.to_bytes(2, 'big') - + data.tobytes()) + packet = (b'\x00' + + (b'\x02' if color_mode == animfunctions.ColorMode.hsv else b'\x01') + + int((end - start) * 3 + 13).to_bytes(2, 'big') + + correction.to_bytes(3, 'big') + + int(saturation * 255).to_bytes(1, 'big') + + int(brightness * 255).to_bytes(1, 'big') + + start.to_bytes(2, 'big') + + end.to_bytes(2, 'big') + + data.tobytes()) + self._send(packet) def show_calibration_color(self, count, correction, brightness): if driver.is_raspberrypi(): driver.ws2811_rgb_render_calibration(self._leds, self._channel, count, correction, brightness) else: - self._ser.write(b'\x00\x00\x00\x08' - + correction.to_bytes(3, 'big') - + int(brightness * 255).to_bytes(1, 'big')) + packet = (b'\x00\x00\x00\x08' + + correction.to_bytes(3, 'big') + + int(brightness * 255).to_bytes(1, 'big')) + self._send(packet) def render(self): if driver.is_raspberrypi(): driver.ws2811_render(self._leds) else: - self._ser.write(b'\x00\x03\x00\x05\x00') + packet = b'\x00\x03\x00\x05\x00' + self._send(packet) + + def _send(self, packet): + if self._target_mode == TargetMode.serial: + self._serial.write(packet) + elif self._target_mode == TargetMode.udp: + self._socket.sendto(packet, (self._udp_target, self._udp_port)) diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake deleted file mode 100644 index db94ec5..0000000 --- a/pico_sdk_import.cmake +++ /dev/null @@ -1,72 +0,0 @@ -# This is a copy of /external/pico_sdk_import.cmake - -# This can be dropped into an external project to help locate this SDK -# It should be include()ed prior to project() - -if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) - set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) - message("Using PICO_SDK_PATH from environment ('${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}) - message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${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}) - message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${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 copy of SDK from git if not otherwise locatable") -set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download 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 BASE_DIR "${CMAKE_SOURCE_DIR}") - endif () - # GIT_SUBMODULES_RECURSE was added in 3.17 - if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - GIT_SUBMODULES_RECURSE FALSE - ) - else () - FetchContent_Declare( - pico_sdk - GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk - GIT_TAG master - ) - endif () - - if (NOT pico_sdk) - message("Downloading Raspberry Pi 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 - "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." - ) - 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 "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") -endif () - -set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) -include(${PICO_SDK_INIT_CMAKE_FILE})