本文章介紹Raspberry Pi Pico W 使用lwIP MQTT Client 透過手機設定Pico W 上的WS2812 LED燈條展示方式。
- 顯示時鐘。
- 警示閃光。
- 隨機顏色、燈數與方向。
- 固定顏色、燈數與方向。
一、pico-sdk加入MQTT library:
pico-sdk(1.4)未將lwip MQTT client application llibrary加入,為了使用lwIP MQTT Client library修正下列檔案加入MQTT Client library。
(註:Pico SDK 1.5.0 已經加入了)
pico-sdk/src/rp2-common/pico_lwip/CMakeLists.txt:
#MQTT
add_library(pico_lwip_mqtt INTERFACE)
target_sources(pico_lwip_mqtt INTERFACE
${PICO_LWIP_PATH}/src/apps/mqtt/mqtt.c
)
如下圖所示:
二、WS2812 LED燈條:
根據Datasheet每個bit 0 & 1 timer,使用raspberry Pi Pico PIO的時間如下圖:
WS2812的 Send data at speeds of 800Kbps. PIO clock如下設定。
ws2812_program_init(pio, sm, WS2812_PIN, 800000);
Subscribe API:
mqtt_sub_unsub(): subscribe or unsubscribe MQTT topic。
mqtt_set_inpub_callback():設定callback function,接收subscribe的topic&message。
mqtt_incoming_publish_cb_t: callback function 接收topic 與total length。
mqtt_incoming_data_cb_t: callback funcion 接收subscibe data。
Publish API:
mqtt_publish(): publish a topic message。
Connection API:
mqtt_client_connect(): connect to MQTT server。設定connected callback。
mqtt_connection_cb_t: connect callback function,mqtt_connection_status_t 參數為連線狀態。
四、MQTT Server & Node-RED
請參閱下列連結進一步說明:
- MQTT 簡介:使用Mosquitto Broker
- 使用NODE-RED Dashboard 以MQTT訊息驅動來控制MCU上的感應器或設備(Controlling MCU/Sensor/Device with Node-RED Dashboard via MQTT)
- Node-RED 進階使用筆記
Node-RED Flow:
五、成果影片:
六、程式碼:
- Pi Pico W
#include <stdio.h> #include "pico/stdlib.h" #include "hardware/pio.h" #include "pico/cyw43_arch.h" #include "hardware/rtc.h" #include "lwip/apps/mqtt.h" #include "ws2812.pio.h" #include "hardware/clocks.h" #include "ntp_time.h" #include "cJSON.h" #define WIFI_SSID "your_SSID" #define WIFI_PASSWORD "your_PASSWORD" typedef struct MQTT_CLIENT_DATA_T_ { mqtt_client_t* mqtt_client_inst; struct mqtt_connect_client_info_t mqtt_client_info; uint8_t data[MQTT_OUTPUT_RINGBUF_SIZE]; uint8_t topic[100]; uint32_t len; bool playing; bool newTopic; } MQTT_CLIENT_DATA_T; MQTT_CLIENT_DATA_T *mqtt; struct mqtt_connect_client_info_t mqtt_client_info= { "ws2812", NULL, /* user */ NULL, /* pass */ 0, /* keep alive */ NULL, /* will_topic */ NULL, /* will_msg */ 0, /* will_qos */ 0 /* will_retain */ #if LWIP_ALTCP && LWIP_ALTCP_TLS , NULL #endif }; #define NUM_PIXELS 60 #define WS2812_PIN 16 #define green 0xff0000 #define red 0x00ff00 #define blue 0x0000ff uint32_t color_pixel[NUM_PIXELS]; repeating_timer_t colok_timer; static inline void ws2812_program_init(PIO pio, uint sm, uint pin, float freq) { pio_gpio_init(pio, pin); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); uint offset = pio_add_program(pio, &ws2812_program); pio_sm_config c = ws2812_program_get_default_config(offset); sm_config_set_sideset_pins(&c, pin); sm_config_set_out_shift(&c, false, true, 24); sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); int cycles_per_bit = ws2812_T1 + ws2812_T2 *2; float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); sm_config_set_clkdiv(&c, div); pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } static inline void put_pixel(uint32_t pixel_grb) { pio_sm_put_blocking(pio0, 0, pixel_grb << 8u); } static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) { return ((uint32_t) (r) << 8) | ((uint32_t) (g) << 16) | (uint32_t) (b); } void wait_connecting() { for (int i = 0; i < NUM_PIXELS; i++) put_pixel(red); } void clear_pixel() { for (int i = 0; i < NUM_PIXELS; i++) put_pixel(0x000000); } void connected() { for (int i = 0; i < NUM_PIXELS; i++) put_pixel(green); } void random_pixel(uint8_t color_index, uint len, int dir, uint32_t color, uint8_t speed) { static uint t=0; while(mqtt->playing) { if (len > NUM_PIXELS) len = NUM_PIXELS; for (int i=0; i < NUM_PIXELS; i++) color_pixel[i] = 0x000000; // reset all pixel for (int i = t; i < (t+len);++i) { switch (color_index) { case 1: color_pixel[(i)%NUM_PIXELS]=color; break; case 2: color_pixel[(i)%NUM_PIXELS] = (rand()); break; } } for (int i = 0; i < NUM_PIXELS; i++) put_pixel(color_pixel[i]); t = (t+dir+NUM_PIXELS) % NUM_PIXELS; sleep_ms(10*speed); } clear_pixel(); } bool repeat_timer_cb(repeating_timer_t *rt) { static datetime_t dt; rtc_get_datetime(&dt); for (int i=0; i < NUM_PIXELS; i++) color_pixel[i]=0x000000; color_pixel[(dt.hour%12)*5] |= green; color_pixel[dt.min] |= blue; color_pixel[dt.sec] |= red; for (int i=0; i < NUM_PIXELS; i++) put_pixel(color_pixel[i]); return true; } void sparkle() { while(mqtt->playing) { for (int i = 0; i < NUM_PIXELS; ++i) put_pixel(rand() % 32 ? 0 : 0xffffffff); sleep_ms(10); } } void ws2812_action() { cancel_repeating_timer(&colok_timer); cJSON* json_obj = cJSON_CreateObject(); json_obj = cJSON_Parse(mqtt->data); uint8_t *type = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "type")); uint8_t speed = 11-(uint8_t)cJSON_GetNumberValue(cJSON_GetObjectItem(json_obj, "speed")); uint8_t length = (uint8_t)cJSON_GetNumberValue(cJSON_GetObjectItem(json_obj, "length")); int8_t dir = (int8_t)cJSON_GetNumberValue(cJSON_GetObjectItem(json_obj, "dir")); uint8_t *colorstr = (cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "color"))); uint32_t color=strtoul(colorstr,NULL, 16); color = (color&0xff0000) >> 8 | (color&0x00ff00) << 8 | color &0x0000ff; if (strcmp(type, "clock") == 0) { mqtt->playing=true; add_repeating_timer_ms(-1000, repeat_timer_cb, NULL, &colok_timer); } if (strcmp(type, "sparkle") == 0) { mqtt->playing=true; sparkle(); } if (strcmp(type, "randomcolor") == 0) { mqtt->playing=true; random_pixel(2,length,dir,0x000000,speed);//// } if (strcmp(type, "pixelcolor") == 0) { mqtt->playing=true; random_pixel(1,length,dir,color,speed);//// } cJSON_Delete(json_obj); } void ws2812_stop() { mqtt->playing=false; } static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) { MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; LWIP_UNUSED_ARG(data); strncpy(mqtt_client->data, data, len); mqtt_client->len=len; mqtt_client->data[len]='\0'; mqtt_client->newTopic=true; mqtt->playing=false; } static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) { MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; strcpy(mqtt_client->topic, topic); } static void mqtt_request_cb(void *arg, err_t err) { MQTT_CLIENT_DATA_T* mqtt_client = ( MQTT_CLIENT_DATA_T*)arg; LWIP_PLATFORM_DIAG(("MQTT client \"%s\" request cb: err %d\n", mqtt_client->mqtt_client_info.client_id, (int)err)); } static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) { MQTT_CLIENT_DATA_T* mqtt_client = (MQTT_CLIENT_DATA_T*)arg; LWIP_UNUSED_ARG(client); LWIP_PLATFORM_DIAG(("MQTT client \"%s\" connection cb: status %d\n", mqtt_client->mqtt_client_info.client_id, (int)status)); if (status == MQTT_CONNECT_ACCEPTED) { mqtt_sub_unsub(client, "start", 0, mqtt_request_cb, arg, 1); mqtt_sub_unsub(client, "stop", 0, mqtt_request_cb, arg, 1); } } int main() { stdio_init_all(); PIO pio = pio0; int sm = 0; ws2812_program_init(pio, sm, WS2812_PIN, 800000); mqtt=(MQTT_CLIENT_DATA_T*)calloc(1, sizeof(MQTT_CLIENT_DATA_T)); if (!mqtt) { printf("mqtt client instant ini error\n"); return 0; } mqtt->playing=false; mqtt->newTopic=false; mqtt->mqtt_client_info = mqtt_client_info; if (cyw43_arch_init()) { printf("failed to initialise\n"); return 1; } wait_connecting(); cyw43_arch_enable_sta_mode(); if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { printf("failed to connect\n"); return 1; } ntp_time_init(); get_ntp_time(); ip_addr_t addr; if (!ip4addr_aton("your_MQTT_SERVER_IP", &addr)) { printf("ip error\n"); return 0; } mqtt->mqtt_client_inst = mqtt_client_new(); mqtt_set_inpub_callback(mqtt->mqtt_client_inst, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, mqtt); err_t err = mqtt_client_connect(mqtt->mqtt_client_inst, &addr, MQTT_PORT, &mqtt_connection_cb, mqtt, &mqtt->mqtt_client_info); if (err != ERR_OK) { printf("connect error\n"); return 0; } connected(); while(1) { if (mqtt->newTopic) { mqtt->newTopic=false; if (strcmp(mqtt->topic, "start")==0) { ws2812_action(); } if (strcmp(mqtt->topic, "stop")==0) { ws2812_stop(); } } } return 0; }
- Node-RED flow
[ { "id": "6e21089b48879905", "type": "tab", "label": "Flow 1", "disabled": false, "info": "", "env": [] }, { "id": "27103af5cfb31338", "type": "function", "z": "6e21089b48879905", "name": "function 1", "func": "var newmsg={};\n\nif (msg.payload == \"clock\" || msg.payload == \"sparkle\") {\n newmsg.payload = {\n \"group\":\n {\n \"hide\": [\"WS2812_SCROOLCOLOR\", \"WS2812_SLIDEPARAM\"]\n }\n };\n} else {\n if (msg.payload==\"pixelcolor\") {\n newmsg.payload = {\n \"group\":\n {\n \"show\": [\"WS2812_SCROOLCOLOR\",\"WS2812_SLIDEPARAM\"]\n }\n };\n } else {\n newmsg.payload = {\n \"group\":\n {\n \"hide\": [\"WS2812_SCROOLCOLOR\"], \"show\":[\"WS2812_SLIDEPARAM\"]\n }\n };\n }\n}\n\n\nreturn newmsg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 40, "wires": [ [ "700d3dc46fdd7924" ] ] }, { "id": "dbe22a0817d2347d", "type": "ui_dropdown", "z": "6e21089b48879905", "name": "", "label": "Dispaly Type:", "tooltip": "", "place": "Select option", "group": "82089b24e7a5068c", "order": 1, "width": 5, "height": 1, "passthru": false, "multiple": false, "options": [ { "label": "Clock", "value": "clock", "type": "str" }, { "label": "Sparkle", "value": "sparkle", "type": "str" }, { "label": "Pixel Color", "value": "pixelcolor", "type": "str" }, { "label": "Random Color", "value": "randomcolor", "type": "str" } ], "payload": "", "topic": "type", "topicType": "msg", "className": "", "x": 120, "y": 80, "wires": [ [ "27103af5cfb31338", "1272075b2d61d0ce" ] ] }, { "id": "700d3dc46fdd7924", "type": "ui_ui_control", "z": "6e21089b48879905", "name": "", "events": "all", "x": 480, "y": 40, "wires": [ [] ] }, { "id": "5a66ec42f68bd5aa", "type": "ui_slider", "z": "6e21089b48879905", "name": "", "label": "speed", "tooltip": "", "group": "f72a2a2a55f0b438", "order": 4, "width": 0, "height": 0, "passthru": false, "outs": "end", "topic": "topic", "topicType": "msg", "min": "1", "max": 10, "step": 1, "className": "", "x": 90, "y": 220, "wires": [ [ "50b09b338cf91909" ] ] }, { "id": "ca0b6363716ad933", "type": "ui_slider", "z": "6e21089b48879905", "name": "", "label": "Pixels#", "tooltip": "", "group": "f72a2a2a55f0b438", "order": 3, "width": 0, "height": 0, "passthru": false, "outs": "end", "topic": "topic", "topicType": "msg", "min": "1", "max": "59", "step": 1, "className": "", "x": 100, "y": 180, "wires": [ [ "08d9446473a7a2f2" ] ] }, { "id": "ddb42afe82963199", "type": "ui_colour_picker", "z": "6e21089b48879905", "name": "", "label": "", "group": "ff7916db92ed582e", "format": "hex", "outformat": "string", "showSwatch": true, "showPicker": false, "showValue": true, "showHue": false, "showAlpha": false, "showLightness": true, "square": "false", "dynOutput": "false", "order": 2, "width": 0, "height": 0, "passthru": false, "topic": "topic", "topicType": "msg", "className": "", "x": 110, "y": 360, "wires": [ [ "586673b8704f4617" ] ] }, { "id": "d5e936d54e6a819b", "type": "ui_text", "z": "6e21089b48879905", "group": "ff7916db92ed582e", "order": 1, "width": 0, "height": 0, "name": "", "label": "Pick Color", "format": "", "layout": "col-center", "className": "", "x": 110, "y": 320, "wires": [] }, { "id": "3a36f74e374bc0ab", "type": "ui_text", "z": "6e21089b48879905", "group": "f72a2a2a55f0b438", "order": 1, "width": 0, "height": 0, "name": "", "label": "Select the number of Pixels and speed", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "x": 190, "y": 140, "wires": [] }, { "id": "50b09b338cf91909", "type": "change", "z": "6e21089b48879905", "name": "", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "speed", "tot": "flow" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 270, "y": 220, "wires": [ [] ] }, { "id": "08d9446473a7a2f2", "type": "change", "z": "6e21089b48879905", "name": "", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "length", "tot": "flow" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 270, "y": 180, "wires": [ [] ] }, { "id": "586673b8704f4617", "type": "change", "z": "6e21089b48879905", "name": "", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "color", "tot": "flow" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 310, "y": 360, "wires": [ [] ] }, { "id": "08a77a34f56dd819", "type": "ui_button", "z": "6e21089b48879905", "name": "", "group": "e5d38edfd552806e", "order": 1, "width": 2, "height": 1, "passthru": false, "label": "Start", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 90, "y": 420, "wires": [ [ "32bc237d4be9a18c" ] ] }, { "id": "32bc237d4be9a18c", "type": "function", "z": "6e21089b48879905", "name": "function 2", "func": "msg.payload={};\nmsg.payload.type=flow.get(\"type\") || \"\";\nmsg.payload.speed = flow.get(\"speed\") || 1;\nmsg.payload.length = flow.get(\"length\") || 1;\nmsg.payload.color = flow.get('color') || 0x000000;\nmsg.payload.dir = flow.get(\"dir\") || 1;\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 240, "y": 420, "wires": [ [ "03cebb9c7b1959d8" ] ] }, { "id": "1272075b2d61d0ce", "type": "change", "z": "6e21089b48879905", "name": "", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "type", "tot": "flow" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 330, "y": 100, "wires": [ [] ] }, { "id": "d98476ae54d711de", "type": "ui_button", "z": "6e21089b48879905", "name": "", "group": "e5d38edfd552806e", "order": 2, "width": 2, "height": 1, "passthru": false, "label": "Stop", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 90, "y": 480, "wires": [ [ "f82aa1487c39a9cb" ] ] }, { "id": "d84ae8a0e6a9e047", "type": "mqtt out", "z": "6e21089b48879905", "name": "", "topic": "", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "466c902492653c8c", "x": 590, "y": 480, "wires": [] }, { "id": "03cebb9c7b1959d8", "type": "json", "z": "6e21089b48879905", "name": "", "property": "payload", "action": "", "pretty": false, "x": 370, "y": 420, "wires": [ [ "f31a49d4d4727859" ] ] }, { "id": "f82aa1487c39a9cb", "type": "function", "z": "6e21089b48879905", "name": "function 3", "func": "msg.topic=\"stop\";\nmsg.payload=\"stop\";\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 280, "y": 480, "wires": [ [ "d84ae8a0e6a9e047" ] ] }, { "id": "f31a49d4d4727859", "type": "function", "z": "6e21089b48879905", "name": "function 4", "func": "msg.topic=\"start\";\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 420, "wires": [ [ "d84ae8a0e6a9e047" ] ] }, { "id": "9edca29e76cc4a35", "type": "ui_dropdown", "z": "6e21089b48879905", "name": "", "label": "Direction", "tooltip": "", "place": "", "group": "f72a2a2a55f0b438", "order": 2, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "clockwise", "value": 1, "type": "num" }, { "label": "counterclockwise", "value": "-1", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 100, "y": 260, "wires": [ [ "ed2d4290fb55a4bc" ] ] }, { "id": "ed2d4290fb55a4bc", "type": "change", "z": "6e21089b48879905", "name": "", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "dir", "tot": "flow" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 270, "y": 260, "wires": [ [] ] }, { "id": "104b1503d903372c", "type": "ui_spacer", "z": "6e21089b48879905", "name": "spacer", "group": "82089b24e7a5068c", "order": 2, "width": 1, "height": 1 }, { "id": "7783de4639450be7", "type": "ui_spacer", "z": "6e21089b48879905", "name": "spacer", "group": "e5d38edfd552806e", "order": 3, "width": 2, "height": 1 }, { "id": "82089b24e7a5068c", "type": "ui_group", "name": "Default", "tab": "8caf46a96fa73472", "order": 1, "disp": false, "width": 6, "collapse": false, "className": "" }, { "id": "f72a2a2a55f0b438", "type": "ui_group", "name": "SLIDEPARAM", "tab": "8caf46a96fa73472", "order": 2, "disp": false, "width": "5", "collapse": false, "className": "" }, { "id": "ff7916db92ed582e", "type": "ui_group", "name": "SCROOLCOLOR", "tab": "8caf46a96fa73472", "order": 3, "disp": false, "width": "5", "collapse": false, "className": "" }, { "id": "e5d38edfd552806e", "type": "ui_group", "name": "Buttons", "tab": "8caf46a96fa73472", "order": 5, "disp": false, "width": 6, "collapse": false, "className": "" }, { "id": "466c902492653c8c", "type": "mqtt-broker", "name": "mosquitto", "broker": "localhost", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "", "credentials": {} }, { "id": "8caf46a96fa73472", "type": "ui_tab", "name": "WS2812", "icon": "dashboard", "disabled": false, "hidden": true } ]
沒有留言:
張貼留言