Add ArtNetDMX Pico W firmware with dual DMX and docs.

Art-Net to two RS-485 outputs over Wi-Fi (AP or STA with SoftAP fallback), web UI, mDNS, and QLC+/ArtPoll notes for unicast on STA.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-11 00:00:23 +12:00
co-authored by Cursor
commit fea6dffa74
26 changed files with 3013 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
#ifndef ARTNET_H
#define ARTNET_H
#include <stdbool.h>
bool artnet_start(void);
/** Re-bind listeners to current AP/STA IPs (call after SoftAP re-assert or STA join). */
void artnet_refresh(void);
/** Broadcast ArtPollReply (call after interface comes up). */
void artnet_announce(void);
/** Periodic discovery announces (call from main loop). */
void artnet_poll(void);
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef BOARD_H
#define BOARD_H
#include <stdint.h>
/** DMX universe A (port 0) TX pin → RS-485 DI */
#define BOARD_DMX_A_PIN 0
/** DMX universe B (port 1) TX pin → RS-485 DI */
#define BOARD_DMX_B_PIN 1
#define BOARD_NUM_UNIVERSES 2
#define BOARD_DMX_CHANNELS 512
#define BOARD_DMX_FRAME_LEN (1 + BOARD_DMX_CHANNELS) /* start code + 512 */
#define BOARD_DMX_HZ 40
#define BOARD_DMX_PERIOD_MS (1000 / BOARD_DMX_HZ)
#define BOARD_DEFAULT_UNIVERSE_A 0
#define BOARD_DEFAULT_UNIVERSE_B 1
#define BOARD_AP_SSID_DEFAULT "ArtNetDMX"
#define BOARD_AP_PASS_DEFAULT "artnetdmx"
#define BOARD_HOSTNAME "artnetdmx"
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef CHANNELS_H
#define CHANNELS_H
#include <stdint.h>
#include <stddef.h>
#include "board.h"
void channels_init(void);
/** Returns pointer to 512 channel values for universe index 0 or 1 (no start code). */
uint8_t *channels_get(unsigned universe);
/** Build a 513-byte DMX frame (start code 0 + 512 channels) into out. */
void channels_build_frame(unsigned universe, uint8_t out[BOARD_DMX_FRAME_LEN]);
void channels_set(unsigned universe, unsigned channel, uint8_t value);
void channels_set_range(unsigned universe, const uint8_t *values, size_t len);
void channels_clear(unsigned universe);
void channels_note_artnet(unsigned universe);
uint32_t channels_artnet_packets(void);
uint32_t channels_ms_since_artnet(unsigned universe);
#endif
+57
View File
@@ -0,0 +1,57 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "board.h"
#define CONFIG_MAGIC 0x414E4458u /* 'ANDX' */
#define CONFIG_VERSION 3
#define CONFIG_SSID_MAX 32
#define CONFIG_PASS_MAX 64
#define CONFIG_HOSTNAME_MAX 32
typedef enum {
WIFI_MODE_AP = 0,
WIFI_MODE_STA = 1,
/** Legacy flash value; treated as STA (with AP fallback on failure). */
WIFI_MODE_AP_STA = 2,
} wifi_mode_t;
typedef struct {
uint32_t magic;
uint32_t version;
uint32_t crc;
wifi_mode_t wifi_mode;
char sta_ssid[CONFIG_SSID_MAX];
char sta_pass[CONFIG_PASS_MAX];
char ap_ssid[CONFIG_SSID_MAX];
char ap_pass[CONFIG_PASS_MAX];
uint16_t universe_a;
uint16_t universe_b;
/* v3: mDNS hostname without ".local" (e.g. "artnetdmx") */
char mdns_host[CONFIG_HOSTNAME_MAX];
} config_t;
void config_set_defaults(config_t *cfg);
bool config_load(config_t *cfg);
bool config_save(const config_t *cfg);
const config_t *config_get(void);
void config_init(void);
/** Lowercase DNS-label sanitize into out; returns false if empty/invalid → uses default. */
bool config_sanitize_hostname(const char *in, char *out, size_t out_sz);
static inline const char *wifi_mode_name(wifi_mode_t m) {
switch (m) {
case WIFI_MODE_STA:
case WIFI_MODE_AP_STA:
return "STA";
case WIFI_MODE_AP:
default:
return "AP";
}
}
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef DMX_OUT_H
#define DMX_OUT_H
#include <stdbool.h>
#include <stdint.h>
#include "hardware/pio.h"
typedef struct {
PIO pio;
uint sm;
uint dma;
uint pin;
uint prgm_offset;
bool inited;
} dmx_out_t;
bool dmx_out_init(dmx_out_t *out, uint pin, PIO pio);
void dmx_out_write(dmx_out_t *out, const uint8_t *frame, uint length);
bool dmx_out_busy(const dmx_out_t *out);
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef HTTP_SERVER_H
#define HTTP_SERVER_H
#include <stdbool.h>
bool http_server_start(void);
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef WIFI_MGR_H
#define WIFI_MGR_H
#include <stdbool.h>
#include "config.h"
/**
* AP mode: SoftAP only.
* STA mode: join LAN; if that fails, SoftAP comes up for recovery.
*/
bool wifi_mgr_start(const config_t *cfg);
/** Call from main loop — runs deferred STA join (STA mode only). */
void wifi_mgr_poll(void);
wifi_mode_t wifi_mgr_active_mode(void);
/** Primary IP for UI (STA if up, else AP). */
const char *wifi_mgr_ip_str(void);
const char *wifi_mgr_sta_ip_str(void);
const char *wifi_mgr_ap_ip_str(void);
const char *wifi_mgr_mdns_name(void);
bool wifi_mgr_sta_up(void);
bool wifi_mgr_ap_up(void);
bool wifi_mgr_is_up(void);
const char *wifi_mgr_debug(void);
#endif