本文章介紹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
}
]
沒有留言:
張貼留言