prettyprint

2023年2月25日 星期六

[Raspberry Pi Pico (c-sdk)] Display: Ep 4 : ILI9341 TFT LCD 8-bit parallel - Frame(320*240) Rate above 120fps

 在[Raspberry Pi Pico (c-sdk)] Display: Ep 2 : PIO TFT LCD ILI9341 8-bit parallel C-code Driver介紹使用PIO製作ILI9341 C Code驅動程式。本篇文章加入PIO DMA功能。比較在使用DMA與frame buffer的條件下,能達到多快的frame rate。

在原來的PIO init funtion中加入DMA。

加入一個新的function ili9341_cmd_dma()透夠DMA輸出整個frame(320*240)資料。

詳細成果請參考成果影片,詳細程式碼附於文末。
  • 成果影片


  • 程式碼:

測試主程式
  • tft_frame.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "ili9341.h"
#include "fonts/pico.h"

#include "registers.h"
#include "inttypes.h"
#include "string.h"
#include "stdlib.h"
#include "time.h"

int main()
{
    stdio_init_all();
  
    ili9341_init();
    //sleep_ms(5000);
    ili9341_fill_rect(0,0,320,240, 0xf800);
    sleep_ms(1000);
    
    time_t t;
    srand((unsigned) time(&t));

    
    uint8_t *spt;
    int x,y,width,color;
    int boxs[100][4]; //(x,y,width, color-h, cololr-l)
    uint8_t *buff = (uint8_t*) calloc(320*240*2, sizeof(uint8_t));  //frame buffer
    if (!buff) {
        printf("alloc memory error\n");
        return 0;
    }

    //==== 100 random rectangle
    for (int i=0; i < 100; i++) {
        y=rand()%240;
        x=rand()%320;
        width=rand()%60;
        if (y+width >=240) y=240-y;
        if (x+width >=320) x=320-x;
        boxs[i][0]=x;
        boxs[i][1]=y;
        boxs[i][2]=width;
        boxs[i][3]=rand()%0xff;
    } 
    int p;
    absolute_time_t t1 ,t2;
    int i; // repeat test times
//=======
    printf("\n50 rect in a frame\nStarting... \n");
    for (int rep=0; rep < 5; rep++) { 
    t1= get_absolute_time();
    for (i=0; i < 500; i++) {  
        memset(buff, 0, 320*240*2);
        for (int j=0; j < 50; j++) { 
            p = rand()%100;
            spt= (buff+boxs[p][0]*2+boxs[p][1]*640);
            for (int k =0; k < boxs[p][2]; k++){
                memset((spt+k*640), boxs[p][3], boxs[p][2]*2); 
            }
        }
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  buff);
    }
    t2=get_absolute_time();
    printf("\n%d frames(50 Boxs) in RAM) --  time:%"PRIu64"us\n",i, absolute_time_diff_us(t1, t2));
     printf("Frame Rate: %ffps, pause 1 second to start next test\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
    sleep_ms(1000);
    }
//==============================



    printf("starting(with DMA)...\n");
//===============================
    
    memcpy(buff, image_data_pico, 320*240*2);
    width=20;
    
    t1 = get_absolute_time();
    for (i=0; i < 1000; i++) { 
        if (i%50==0) 
        memcpy(buff, image_data_pico, 320*240*2);
        color = rand()&0xff;
        x = rand()%300; y=rand()%220;

        spt= (buff+x*2+y*640);
        for (int k =0; k < 20; k++){
            memset((spt+k*640),color, 40); 
        }
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  buff);
    }
    t2=get_absolute_time();
    printf("\n%d (image frames in RAM) --  time:%"PRIu64"us\n",i,absolute_time_diff_us(t1, t2));
     printf("Frame Rate: %ffps\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
//==============================
     t1 = get_absolute_time();
    for (i=0; i < 60; i++) { 
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  (uint8_t*)image_data_pico);
    }
    t2=get_absolute_time();
    printf("\n%d image frames in XIP Flash--time:%"PRIu64"us\n",i,absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
//====================================== 
    printf("\nrepeat 5 times\n");   
    for (int i=0; i< 5; i++) {
        memcpy(buff, image_data_pico, 320*240*2);
        t1 = get_absolute_time();
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  buff);
        t2=get_absolute_time();
        printf("1 frame(in RAM)              --time:%"PRIu64"us",absolute_time_diff_us(t1, t2));
        printf("     ,sleep 1 second\n");
        sleep_ms(1000);
        
        t1 = get_absolute_time();
        memset(buff, 0xf71f, 320*240*2);
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  buff);
        t2=get_absolute_time();
        printf("1 frame(memset color)        --time:%"PRIu64"us",absolute_time_diff_us(t1, t2));
        printf("     ,sleep 1 second\n");
        sleep_ms(1000);
    }
    memcpy(buff, image_data_pico, 320*240*2);
    t1 = get_absolute_time();
    for (i=0; i < 120; i++) { 
    ili9341_set_address_window (0, 0, 319, 239);
    ili9341_cmd_dma(ILI9341_MEMORYWRITE, 320*240*2,  buff);
    }
    t2=get_absolute_time();
    printf("\n%d 320*240 image frame in RAM-time:%"PRIu64"us\n",1, absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
    printf("\ncomplete\n");

//========================================================================
    printf("starting(without DMA)...\n");
//===============================

    memcpy(buff, image_data_pico, 320*240*2);
   
    t1 = get_absolute_time();
    for (i=0; i < 1000; i++) { 
        if (i%50==0) 
        memcpy(buff, image_data_pico, 320*240*2);
        color = rand()&0xff;
        x = rand()%300; y=rand()%220;

        spt= (buff+x*2+y*640);
        for (int k =0; k < 20; k++){
            memset((spt+k*640),color, 40); 
        }
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd(ILI9341_MEMORYWRITE, 320*240*2,  buff);
    }
     t2=get_absolute_time();
    printf("\n%d (image frames in RAM) --  time:%"PRIu64"us\n",i,absolute_time_diff_us(t1, t2));
     printf("Frame Rate: %ffps\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
//==============================
     t1 = get_absolute_time();
    for (i=0; i < 60; i++) { 
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd(ILI9341_MEMORYWRITE, 320*240*2,  (uint8_t*)image_data_pico);
    }
    t2=get_absolute_time();
    printf("\n%d image frames in XIP Flash--time:%"PRIu64"us\n",i,absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n", (float)i/(absolute_time_diff_us(t1, t2))*1000000);
//====================================== 
    printf("\nrepeat 5 times\n");   
    for (int i=0; i< 5; i++) {
        memcpy(buff, image_data_pico, 320*240*2);
        t1 = get_absolute_time();
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd(ILI9341_MEMORYWRITE, 320*240*2,  buff);
        t2=get_absolute_time();
        printf("1 frame(in RAM)              --time:%"PRIu64"us",absolute_time_diff_us(t1, t2));
        printf("     ,sleep 1 second\n");
        sleep_ms(1000);
        
        t1 = get_absolute_time();
        memset(buff, 0xf71f, 320*240*2);
        ili9341_set_address_window (0, 0, 319, 239);
        ili9341_cmd(ILI9341_MEMORYWRITE, 320*240*2,  buff);
        t2=get_absolute_time();
        printf("1 frame(memset color)        --time:%"PRIu64"us",absolute_time_diff_us(t1, t2));
        printf("     ,sleep 1 second\n");
        sleep_ms(1000);
    }
    memcpy(buff, image_data_pico, 320*240*2);
    t1 = get_absolute_time();
    for (i=0; i < 120; i++) { 
    ili9341_set_address_window (0, 0, 319, 239);
    ili9341_cmd(ILI9341_MEMORYWRITE, 320*240*2,  buff);
    }
    t2=get_absolute_time();
    printf("\n%d 320*240 image frame in RAM-time:%"PRIu64"us\n",i, absolute_time_diff_us(t1, t2));
    printf("Frame Rate: %ffps\n", (float)i/(float)(absolute_time_diff_us(t1, t2))*1000000);
    printf("\ncomplete\n");

    while(1) {
        tight_loop_contents();
    }



    return 0;
}


  • ili9341.c
 #include "stdio.h"
#include "stdlib.h"
#include "ili9341.pio.h"
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "string.h"
#include "registers.h"
#include "ili9341.h"
#include "hardware/dma.h"

#define MAX_BYTE_TRANS (320*240*2)

PIO ili9341_pio = pio1;
uint ili9341_sm=0;
uint in_out_base_pin=4;
uint set_base_pin=12;

int ili9341_dma_channel;

void ili9341_cmd(uint32_t cmd, uint32_t count, uint8_t *param)
{
    pio_sm_restart(ili9341_pio, ili9341_sm);

    pio_sm_put_blocking(ili9341_pio, ili9341_sm, cmd);
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, count);
    for (int i = 0; i < count; i++)
    {
        pio_sm_put_blocking(ili9341_pio, ili9341_sm, param[i]);
    }
}

void ili9341_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param)
{
    pio_sm_restart(ili9341_pio, ili9341_sm);
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, cmd);
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, count);
    dma_channel_set_trans_count(ili9341_dma_channel, count >> DMA_SIZE_8, false);
    dma_channel_set_read_addr(ili9341_dma_channel, param, false);
   dma_channel_start(ili9341_dma_channel);
   dma_channel_wait_for_finish_blocking(ili9341_dma_channel);
    
}

void ili9431_pio_cmd_init(PIO pio, uint sm, uint in_out_base,  uint set_base, uint32_t freq) {
    uint offset=0;
    pio_sm_config c;
    offset = pio_add_program(pio, &ili9341_pio_cmd_program);
    c = ili9341_pio_cmd_program_get_default_config(offset);
    
    for (int i=0; i < 8; i++) pio_gpio_init(pio, in_out_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_base+i);
    
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);
    
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
    
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 8);
    sm_config_set_set_pins(&c, set_base, 4);
   
    sm_config_set_out_shift(&c, true, false, 8);
    sm_config_set_in_shift(&c, false, false, 8);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    
    /*   DMA  */
    ili9341_dma_channel = dma_claim_unused_channel(true);
    dma_channel_config dc = dma_channel_get_default_config(ili9341_dma_channel);
    channel_config_set_write_increment(&dc, false);
    channel_config_set_read_increment(&dc, true);
    channel_config_set_dreq(&dc, pio_get_dreq(pio, sm, true));
    channel_config_set_transfer_data_size(&dc, DMA_SIZE_8); //DMA_SIZE_8,16,32
    
    dma_channel_configure(ili9341_dma_channel, &dc, (void*) (PIO1_BASE+PIO_TXF0_OFFSET), 
             NULL, MAX_BYTE_TRANS>> DMA_SIZE_8, false); //DMA_SIZE_8 or 16 or 32
    /*  DMA */
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

/* ili3941 draw functions*/
uint16_t ili9341_color_565RGB(uint8_t R, uint8_t G, uint8_t B) {
    uint16_t c;
    c = (((uint16_t)R)>>3)<<11 | (((uint16_t)G)>>2) << 5 | ((uint16_t)B)>>3;
    return c;
}
void ili9341_memory_write_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	uint8_t addr[4];
    addr[0]=(uint8_t)(x1 >> 8);
    addr[1]= (uint8_t)(x1 & 0xff);
    addr[2]= (uint8_t)(x2 >> 8);
    addr[3]= (uint8_t)(x2 & 0xff);
    ili9341_cmd(ILI9341_COLADDRSET, 4,   addr);

    addr[0]=(uint8_t)(y1 >> 8);
    addr[1]= (uint8_t)(y1 & 0xff);
    addr[2]= (uint8_t)(y2 >> 8);
    addr[3]= (uint8_t)(y2 & 0xff);
	ili9341_cmd(ILI9341_PAGEADDRSET, 4,   addr );

    ili9341_cmd(ILI9341_MEMORYWRITE, 0, NULL);
}

void ili9341_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
	uint8_t addr[4];
    addr[0]=(uint8_t)(x1 >> 8);
    addr[1]= (uint8_t)(x1 & 0xff);
    addr[2]= (uint8_t)(x2 >> 8);
    addr[3]= (uint8_t)(x2 & 0xff);
    ili9341_cmd(ILI9341_COLADDRSET, 4,  addr);

    addr[0]=(uint8_t)(y1 >> 8);
    addr[1]= (uint8_t)(y1 & 0xff);
    addr[2]= (uint8_t)(y2 >> 8);
    addr[3]= (uint8_t)(y2 & 0xff);
	ili9341_cmd(ILI9341_PAGEADDRSET, 4,  addr );
}

/* put color at point*/
void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
  if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) return;
	ili9341_set_address_window(x,y,x,y);
    ili9341_cmd(ILI9341_MEMORYWRITE, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}

void ili9341_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) {
  if (x < 0) x=0;
  if (y < 0) y=0;
  if (x+width > TFT_WIDTH) width = TFT_WIDTH-x;
  if (y+height > TFT_HEIGHT) height = TFT_HEIGHT-y;
  ili9341_memory_write_window(x,y, x+width-1, y+height-1);
  for (int j = y; j < y+height; j++) 
    for (int i = x; i< x+width;i++)
      ili9341_cmd(ILI9341_NOP, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)(color&0xff)});
}


void ili9341_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap)
{
 
	uint32_t width = 0, height = 0;
	width = bitmap->width;
	height = bitmap->height;

	uint32_t total_pixels = width * height*2;

	ili9341_set_address_window (x, y, x + width-1, y + height-1);
    ili9341_cmd_dma(ILI9341_MEMORYWRITE, total_pixels,  (uint8_t*)(bitmap->data));
   
}
void ili9431_init_config() {
	ili9341_cmd(ILI9341_SOFTRESET, 0,  NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYOFF, 0,  NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_PIXELFORMAT, 1,  (uint8_t[1]){0x55}); //0x55
    ili9341_cmd(ILI9341_POWERCONTROL1, 1,  (uint8_t[1]){0x05}); // 0x05 :3.3V
    ili9341_cmd(ILI9341_POWERCONTROL2, 1,  (uint8_t[1]){0x10});
    ili9341_cmd(ILI9341_VCOMCONTROL1, 2,  (uint8_t[2]){0x3E, 0x28});
    ili9341_cmd(ILI9341_VCOMCONTROL2, 1,  (uint8_t[1]){0x86});
    ili9341_cmd(ILI9341_MADCTL, 1,  (uint8_t[1]){0x60}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    ili9341_cmd(ILI9341_FRAMECONTROL, 2,  (uint8_t[2]){0b00, 0x1B}); // Default 70Hz:0x1B
    ili9341_cmd(ILI9341_DISPLAYFUNC, 4,  (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    ili9341_cmd(ILI9341_GAMMASET, 1,  (uint8_t[1]){0x01});
  
  	//ili9341_cmd(ILI9341_PGAMCOR, 15, 0xFF, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    //ili9341_cmd(ILI9341_NGAMCOR,  15,0xFF, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    ili9341_cmd(ILI9341_SLEEPOUT, 0,  NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
void ili9341_init() {
    
    ili9431_pio_cmd_init(ili9341_pio, ili9341_sm, in_out_base_pin, set_base_pin, 70000000);  //pio freq
	ili9431_init_config();
    
}


  • ili9341.h
 #ifndef  _ILI9431_H_
#define _ILI9431_H_

#define TFT_WIDTH   320
#define TFT_HEIGHT   240

#include "pico/stdlib.h"

#include "bitmap_typedefs.h"
#include "hardware/pio.h"

void ili9341_init();
void ili9431_init_config();
void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
void ili9341_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void ili9341_cmd(uint32_t cmd, uint32_t count,  uint8_t *param);
void ili9341_cmd_dma(uint32_t cmd, uint32_t count,  uint8_t *param);
uint16_t ili9341_color_565RGB(uint8_t R, uint8_t G, uint8_t B);
void ili9341_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);
void ili9341_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap);
void ili9431_pio_cmd_init(PIO pio, uint sm, uint out_base,  uint set_base, uint32_t freq);
#endif

  • ili9341.pio
 .program ili9341_pio_cmd
; CSX, D/CX, WRX, RDX --> gpio 15, 14, 13, 12  (set pins)
.wrap_target
start:
pull
set pins, 0b0011
mov x, osr             ;command code, if 0x0, command nop, only data
jmp !x param
set pins, 0b0001
out pins, 8      ;[1]
set pins, 0b0011 ;[1]
param:
set pins, 0b0111
pull
mov x, osr              ;how many parameters
jmp !x, start            ;no parameter return start
jmp x--, param_data         
param_data:
pull                    ; write data
set pins, 0b0101 
out pins, 8      ;[1] 
set pins, 0b0111 ;[1]
jmp x--, param_data
set pins, 0b1111
jmp start

register.h
 #define ILI9341_NOP             0x00
#define ILI9341_SOFTRESET       0x01
#define ILI9341_SLEEPIN         0x10
#define ILI9341_SLEEPOUT        0x11
#define ILI9341_NORMALDISP      0x13
#define ILI9341_INVERTOFF       0x20
#define ILI9341_INVERTON        0x21
#define ILI9341_GAMMASET        0x26
#define ILI9341_DISPLAYOFF      0x28
#define ILI9341_DISPLAYON       0x29
#define ILI9341_COLADDRSET      0x2A
#define ILI9341_PAGEADDRSET     0x2B
#define ILI9341_MEMORYWRITE     0x2C
#define ILI9341_MEMORYREAD      0x2E
#define ILI9341_PIXELFORMAT     0x3A
#define ILI9341_MEMORYWRITECONT  0x3C
#define ILI9341_MEMORYREADCONT  0x3E
#define ILI9341_FRAMECONTROL    0xB1
#define ILI9341_DISPLAYFUNC     0xB6
#define ILI9341_ENTRYMODE       0xB7
#define ILI9341_POWERCONTROL1   0xC0
#define ILI9341_POWERCONTROL2   0xC1
#define ILI9341_VCOMCONTROL1    0xC5
#define ILI9341_VCOMCONTROL2    0xC7
#define ILI9341_VSCROLLDEF      0x33
#define ILI9341_VSCROLLADDR     0x37
#define ILI9341_MEMCONTROL      0x36
#define ILI9341_MADCTL          0x36

#define ILI9341_PGAMCOR         0xE0
#define ILI9341_NGAMCOR         0xE1

#define ILI9341_MADCTL_MY       0x80
#define ILI9341_MADCTL_MX       0x40
#define ILI9341_MADCTL_MV       0x20
#define ILI9341_MADCTL_ML       0x10
#define ILI9341_MADCTL_RGB      0x00
#define ILI9341_MADCTL_BGR      0x08
#define ILI9341_MADCTL_MH       0x04




2023年2月20日 星期一

[Raspberry Pi Pico (c-sdk)] Rotary Encoder: Multicore vs. PIO

 本文章說明Raspberry Pi Pico 分別使用Multicore與PIO(Programmable I/O)功能建立一個Rotary Encoder的驅動程式,除了測試KY-040 knob外,另外使用IR LED(發射與接收)建立一組IR LED Rotary Encoder用來偵測物體移動的方向。

展示項目:

KY-040 knob

  1. 旋鈕順時針轉動時,每轉一格WS2812燈條LED亮的數目增加一顆。
  2. 旋鈕逆時針轉動時,每轉一格WS2812燈條LED亮的數目減少一顆。
  3. 按下按鈕時,LED燈條以現有亮的數目作流動顯示。
IR LED Rotary Encoder:
  1. 偵測移動的方向,WS2812燈條依移動方向,以流動方式顯示。
  2. 五秒鐘沒有偵測到物體移動過,LED燈條熄滅。

一、Rotary Encoder簡述:

KY-040共有五的pins:

CLK(or A), DT (or B), SW(switch), +(VCC) and GND。


  • 當旋轉時CLK與DT輸出變化如下圖所示。


  • SW按鈕:
按鈕按下時連接,SW Pin連接GND,所以Raspberry Pi Pico連接SW Pin的腳位須先PULL-UP。

二、自製IR LED Rotary Encoder:

線路圖如下:




 三、程式說明:

KY-040 knob停止時的狀態(CLK, DT)分別為(0,0) or (1,1),自製IR LED Rotary Encoder停止狀態為(1,1)。旋鈕旋轉一格為從(0,0)->(1,1) or (1,1)->(0,0)。完全移動經過IR LED Rotary Encoder,輸出為(1,1)->(0,1)->(0,0)->(1,0)->(1,1), or (1,1)->(1,0)->(0,0)->(0,1)->(1,1)四個狀態全經過。

分別製作Pi Pico 的驅動程式在core1與PIO上執行。當偵測到狀態變化時,主動呼叫Callback function,主程式中斷處理callback function。
程式動態說明請參閱成果影片,詳細程式碼附於文末。

四、成果影片:




五、程式碼。

  • pio_rotary_encoder.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"

#include "hardware/clocks.h"
#include "pico/util/queue.h"

#include "pio_rotary_encoder.h"
#include "rotary_encoder.pio.h"



queue_t pio_rotary_encoder_queue;

void (*pio_callback)(queue_t *q);

void rotary_encoder_irq_handler() {
    PIO_RE_QUEUE_T q;
    q.action=PIO_RE_UNKNOW;
    if (pio_interrupt_get(pio0, 0)) { // 1: clockwise, 2: countclockwise
        pio_interrupt_clear(pio0, 0); 
        uint32_t act = pio_sm_get_blocking(pio0, 0);
        if (act == 1) {
            q.action = PIO_RE_CLOCKWISE;
            queue_try_add(&pio_rotary_encoder_queue, &q);
            pio_callback(&pio_rotary_encoder_queue);
        }
        if (act == 2) {
            q.action = PIO_RE_COUNTERCLOCKWISE;
            queue_try_add(&pio_rotary_encoder_queue, &q);
            pio_callback(&pio_rotary_encoder_queue);
        }
         
    }
    if (pio_interrupt_get(pio0, 1)) {
        pio_interrupt_clear(pio0, 1);
        q.action = PIO_RE_SWPRESS;
        queue_try_add(&pio_rotary_encoder_queue, &q);
        pio_callback(&pio_rotary_encoder_queue);
    }
    
    
}

void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq) {
    gpio_pull_up(in_base);  // software pull-up

    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_switch_program);

    c = rotary_switch_program_get_default_config(offset);
    
    pio_gpio_init(pio, in_base);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  1,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 1);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq) {
    pio_sm_config c;
    uint offset = pio_add_program(pio, &rotary_encoder_program);

    c = rotary_encoder_program_get_default_config(offset);
    
    for (int i=0; i < 2; i++) pio_gpio_init(pio, in_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base,  2,false);
     
    sm_config_set_in_pins(&c, in_base);
    sm_config_set_in_shift(&c, false, false, 2);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    
    uint pio_irq_num = (pio==pio0) ? PIO0_IRQ_0: PIO1_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
  
    irq_add_shared_handler(pio_irq_num, rotary_encoder_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq_num, true);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void pio_rotary_encoder_queue_init() {
    queue_init(&pio_rotary_encoder_queue, sizeof(PIO_RE_QUEUE_T), 5);
}

void pio_rotary_encoder_default_init() {
    pio_rotary_encoder_queue_init();
    pio_rotary_switch_init(RE_PIO, RE_SM1, RE_SW_PIN, 2000);
    pio_rotary_encoder_init(RE_PIO, RE_SM0, RE_CLK_DT_PIN, RE_SM_FREQ);
}
void pio_rotary_encoder_cb(void (*cb)(queue_t *q)) {
    pio_callback=cb;
}

  • pio_rotary_encoder.h
 #ifndef _PIO_ROTARY_ENCODER_H_
#define _PIO_ROTARY_ENCODER_H_

#include "pico/util/queue.h"

#define RE_CLK_DT_PIN  16
#define RE_SW_PIN      18
#define RE_PIO      pio0
#define RE_SM0      (0)
#define RE_SM1      (1)
#define RE_SM_FREQ  100000
enum PIO_RE_ACTION{
    PIO_RE_CLOCKWISE=1,
    PIO_RE_COUNTERCLOCKWISE=2,
    PIO_RE_SWPRESS=3,
    PIO_RE_UNKNOW=4
};

typedef struct _PIO_RE_QUEUE_T {
    enum PIO_RE_ACTION action;
}PIO_RE_QUEUE_T;


void pio_rotary_switch_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_init(PIO pio, uint sm, uint in_base, uint freq);
void pio_rotary_encoder_default_init();
void pio_rotary_encoder_cb(void (*cb)(queue_t *q));


#endif 
  • rotary_encoder.pio
 .program rotary_encoder
start:
in pins, 2 [31]
mov x, isr          ; CLK, DT present state
mov y, osr          ; CLK, DT previous state
jmp x!=y, rotary    ; not eaqual, rotary occure
jmp saveoldvalue    ; clear ISR
rotary:
set y, 0b01         ;
jmp x!=y, pos10
jmp check_dir
pos10:
set y, 0b10
jmp x!=y, saveoldvalue
jmp check_dir
saveoldvalue:
mov osr, isr 
in NULL, 32  ;; imporant to clear ISR
jmp start 

check_dir:
out y,1     ; previous state DT bit
mov osr,x   ; save oldvalue
in NULL, 32 ; clear ISR
in x, 1     ; shift in present state DT bit 
mov x, isr  ; present state DT bit
jmp x!=y counterclockwise
jmp clockwise

clockwise:
set y,1         ; clockwise: value = 1
jmp output
counterclockwise:
set y,2         ; counterclockwise: value = 2
output:
mov isr, y
push
irq wait 0
jmp start  

.program rotary_switch
wait 0 pin 0 [31]       ; Switch PIN is initially pulled up 
irq wait 1              ; [31] wait for button bounce
wait 1 pin 0 [31]

  • core1_rotary_encoder.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "core1_rotary_encoder.h"


queue_t core1_rotary_encoder_queue;

void (*core1_rotary_encoder_cb_t)(queue_t *q);
void core1_rotary_encoder_queue_init() {
    queue_init(&core1_rotary_encoder_queue, sizeof(CORE1_RE_QUEUE_T), 5);
}

void core1_rotary_encoder() {
    core1_rotary_encoder_queue_init();
    gpio_init_mask(0b111<<(BASE_PIN-1));
    gpio_set_dir_in_masked(0b111<<(BASE_PIN-1));
 
    gpio_pull_up(SW_PIN);
    bool clk = gpio_get(CLK_PIN);
    bool oldclk=clk;
    bool dt = gpio_get(DT_PIN);
    bool olddt = dt;
    CORE1_RE_QUEUE_T q;
    while(1) {
        clk=gpio_get(CLK_PIN);
        dt=gpio_get(DT_PIN);
        
        if (clk != dt) {
       
            if(clk != oldclk) { 
                q.action = CORE1_RE_CLOCKWISE;
                queue_add_blocking(&core1_rotary_encoder_queue, &q); 
                core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            }
            if (dt != olddt) {
                q.action = CORE1_RE_COUNTERCLOCKWISE;
                queue_add_blocking(&core1_rotary_encoder_queue, &q);
                core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            }
   
      
        }
        oldclk=clk;olddt=dt;
        
        if (!gpio_get(SW_PIN)) {
            q.action=CORE1_RE_SWPRESS;
            queue_add_blocking(&core1_rotary_encoder_queue, &q);
            core1_rotary_encoder_cb_t(&core1_rotary_encoder_queue);
            while(!gpio_get(SW_PIN)){
                tight_loop_contents();
            }
        }
               
        sleep_ms(1);

    }
}



void core1_rotary_encoder_cb(void (*rotary_encoder_cb_t)(queue_t *q)) {
    core1_rotary_encoder_cb_t=rotary_encoder_cb_t;
}

  • core1_rotary_encoder.h
 #ifndef _CORE1_ROTARY_ENCODER_H_
#define _CORE1_ROTARY_ENCODER_H_

#define CLK_PIN 17
#define DT_PIN  16
#define SW_PIN  18
#define BASE_PIN 16

enum CORE1_RE_ACTION{
    CORE1_RE_CLOCKWISE=1,
    CORE1_RE_COUNTERCLOCKWISE=2,
    CORE1_RE_SWPRESS=3,
    CORE1_RE_UNKNOW=4
};

typedef struct _CORE1_RE_QUEUE_T {
    enum CORE1_RE_ACTION action;
}CORE1_RE_QUEUE_T;



void core1_rotary_encoder_cb(void (*rotary_encoder_cb_t)(queue_t *q));
void core1_rotary_encoder();

#endif 

  • rotary_encoder.c
 #include <stdio.h>
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "pio_rotary_encoder.h"
#include "core1_rotary_encoder.h"
#include "ws2812.h"

#include "pio_rotary_encoder.h"
#include "core1_rotary_encoder.h"

#define PIO_ROTARY_ENCODER  1
#define IR_DETECT           0

#define TOTAL_PIXEL 10
#define LED 25
int total_pixel=0;
bool playing=false;
uint8_t current_dir=0;
bool new_dir=false;

void rotary_encoder_cb(queue_t* q ) {
    #if PIO_ROTARY_ENCODER
    PIO_RE_QUEUE_T data;
    #else
    CORE1_RE_QUEUE_T data;
    #endif 
    if (!queue_is_empty(q)) { 
        queue_remove_blocking(q, &data);
        printf("action:%d\n",data.action);
        #if PIO_ROTARY_ENCODER
        printf("PIO Rotary Encoder:%d\n", data.action);
        #else
        printf("CORE1 Rotary Encoder:%d\n", data.action);
        #endif
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_CLOCKWISE) 
        #else
        if (data.action == CORE1_RE_CLOCKWISE) 
        #endif
        {
            current_dir = data.action;
            new_dir=true;
            total_pixel++;
            if (total_pixel > 10) total_pixel = 10;
        }
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_COUNTERCLOCKWISE) 
        #else
        if (data.action == CORE1_RE_COUNTERCLOCKWISE) 
        #endif
        {
            current_dir = data.action;
            new_dir=true;
            total_pixel--;
            if (total_pixel < 0) total_pixel = 0;
        }
        #if PIO_ROTARY_ENCODER
        if (data.action == PIO_RE_SWPRESS) 
        #else
        if (data.action == CORE1_RE_SWPRESS) 
        #endif
        {
              playing = !playing;
        }
    }
}
uint32_t pixels[TOTAL_PIXEL];

void ir_detect_led_dispaly() {

    absolute_time_t timeout;
    
    while(1) {
        #if PIO_ROTARY_ENCODER
        if (current_dir == PIO_RE_CLOCKWISE)
        #else 
        if (current_dir == CORE1_RE_CLOCKWISE)
        #endif
        {
            if (new_dir) {
                timeout = make_timeout_time_ms(5000);
                new_dir=false;;
            }
           
            for (int i = 0 ; i < TOTAL_PIXEL; i++) {
                for (int j=0; j <= i; j++) {
                    ws2812_put_pixel(0x00ff00);
                }
                for (int k=i+1; k < TOTAL_PIXEL; k++) {
                    ws2812_put_pixel(0x000000);
                }
                sleep_ms(50);
            }

        }
        #if PIO_ROTARY_ENCODER
        if (current_dir == PIO_RE_COUNTERCLOCKWISE)
        #else 
        if (current_dir == CORE1_RE_COUNTERCLOCKWISE)
        #endif
        {
            if (new_dir) {
                timeout = make_timeout_time_ms(5000);
                new_dir=false;
            }
         
            for (int i = 0 ; i < TOTAL_PIXEL; i++) {
                for (int k=0; k < TOTAL_PIXEL-i-1; k++) {
                    ws2812_put_pixel(0x000000);
                }
                for (int j=TOTAL_PIXEL-i-1; j < TOTAL_PIXEL; j++) {
                    ws2812_put_pixel(0x0000ff);
                }
                
                sleep_ms(50);
            }
        }
        
        if (!new_dir & current_dir > 0) {
            if (absolute_time_diff_us(get_absolute_time(), timeout) < 0) {
                current_dir = 0;
                for (int j=0; j < TOTAL_PIXEL; j++) {
                    ws2812_put_pixel(0x000000);
                }
                sleep_ms(50);
            }
        }

    }
}

void Rotary_led_display() {
    int i, j, k;
    while(1) {
            if (playing) {
                
                for (i=0; i < total_pixel; i++) {
                    for (k = 0; k <= i; k++)
                        //ws2812_put_pixel(pixels[k]);
                        ws2812_put_pixel(rand());
                    for (j=i+1; j < TOTAL_PIXEL; j++) 
                        ws2812_put_pixel(0x000000);
                        
                    sleep_ms(50);
                 }
                 
                
                
            } else { // setting
                for (int i=0; i < TOTAL_PIXEL; i++) {
                ws2812_put_pixel(0x000000);
                }
                sleep_ms(1);
                for (int i=0; i < total_pixel; i++) {
                    ws2812_put_pixel(0x5f5f5f);
                } 
                sleep_ms(10);
            }
        
            

    }
}

int main()
{
    stdio_init_all();
    
    ws2812_pio_init(WS2812_PIO, WS2812_SM, WS2812_PIN, WS2812_PIO_FREQ);
    gpio_init(LED);
    gpio_set_dir(LED,true);
    #if PIO_ROTARY_ENCODER
    //pio_rotary_switch_init(RE_PIO, RE_SM1, RE_SW_PIN, RE_SM_FREQ);
    //pio_rotary_encoder_init(RE_PIO, RE_SM0, RE_CLK_DT_PIN, RE_SM_FREQ);   
    pio_rotary_encoder_default_init();
    pio_rotary_encoder_cb(rotary_encoder_cb);
    gpio_put(LED,true);
    #else 
    multicore_launch_core1(core1_rotary_encoder);
    core1_rotary_encoder_cb(rotary_encoder_cb);
    gpio_put(LED,false);
    #endif
    uint32_t color;
    for (int i=0; i < TOTAL_PIXEL; i++) {
        color = (0xff0000)/(TOTAL_PIXEL-1)*i+(0x0000ff)/(TOTAL_PIXEL-1)*(TOTAL_PIXEL-1-i);
        color = (color&0xff0000)>>8 | (color&0x00ff00)<<8 | (color&0x0000ff);
        pixels[i] = color;
    }
    
    #if  IR_DETECT
    ir_detect_led_dispaly();
    #else
    Rotary_led_display();
    #endif


    puts("Hello, world!");

    return 0;
}



2023年2月14日 星期二

[Raspberry Pi Pico W (c-sdk)] lwIP: Ep 5. HTTP Server & WiFiManager

本文章介紹Raspberry Pi Pico W使用lwIP HTTP application建立WiFi Manager,當Pico W沒有儲存WiFi連線的SSID與Password時,啟用AP模式,並啟用HTTP與DHCP server,透過網頁設定要連線的WiFi SSID與Password,並將設定檔存在flash memory中。如下流程圖。
一、HTTP server:
lwIP的http server(HTTPD)支援簡易SSI(server-side-include)與CGI功能,另外有支援POST功能。
  • SSI ; 相對應的API:
    http_set_ssi_handler():Set the SSI handler function.
    tSSIHandler: 處理SSI tag的callback function,可使用multi part處理較長的內容。
    const char ** tags: SSI tags。
  • CGI: 相對應的API:
    http_set_cgi_handlers():Set an array of CGI filenames/handler functions。
    tCGI StructURL):指定URL與相對應的function。
  • POST: 相對應的API:
    httpd_post_begin():
    httpd_post_receive_data():每收到一個pbuf就呼叫一次。
    httpd_post_data_recved():
    httpd_post_finished():資料收完或connection close。
  • 啟用HTTPD:
    httpd_init()
  • 在lwipots.h加入下列:
    #define LWIP_HTTPD 1
    #define LWIP_HTTPD_SSI 1
    #define LWIP_HTTPD_CGI 1
    #define LWIP_HTTPD_SSI_MULTIPART 1
    #define LWIP_HTTPD_SUPPORT_POST 1
    #define LWIP_HTTPD_SSI_INCLUDE_TAG 0
    #define HTTPD_FSDATA_FILE "_fsdata.c"  //網頁檔案存放的flash memory image檔。
  • 在pico-sdk/lib/lwip/src/apps/http/makefsdata的perl檔案makefsdata用來產生fsdata.c
  • 修改makefsdata perl檔案,約在24行處,將
    if($file =~ /\.html) {
    改成
    if($file =~ /\.html$/ or $file =~ /\.shtml$/ or $file =~ /\.htm$/ or $file =~ /\.shtm$/) {
    讓.shtml副檔名的檔案,加入Content-type: text/html\r\n檔頭,成為網頁檔案。
  • 在CMakeLists.txt加入下列執行產生fsdata.c
二、WiFi scan:
須先啟用為STA或AP mode才可執行wifi scan功能。
相對應API:
cyw43_arch_enable_sta_mode() or cyw43_arch_enable_ap_mode()
  • cyw43_wifi_scan(&cyw43_state, &scan_options, aps, scan_result):啟用wifi scan並且呼叫scan_result callback function取得scan到的資料。
  • cyw43_wifi_scan_active(&cyw43_state):查看wifi scan是否以完成。
其他進一步解說,請觀看「成果影片」連結。

三、成果影片


四、程式碼:
  • cSJON: https://github.com/DaveGamble/cJSON
  • dhcpserver: pico_examples/pico_w/wifi/access_point/dhcp_server
  • makefsdata: pico-sdk/lib/lwip/src/apps/http/makefsdata
  • ap_http_server.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/httpd.h"
#include "hardware/flash.h"
#include "hardware/watchdog.h"

#include "ap_http_server.h"
#include "wifi_scan.h"
#include "dhcpserver.h"

SCAN_APS_T *aps;

dhcp_server_t dhcp_server;
#define WIFI_AP_SSID "PicoW"
#define WIFI_AP_PASSWORD "picowpwd#"

/* ==== cgi begin ======*/
const char *
cgi_handler_wifi_refresh(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
    
    scan_aps(aps, 20000);
    return "/index.shtml";

}

static const tCGI cgi_handlers[] = {
    {
        //* Html request for "/wifi_refresh.cgi" will start cgi_handler_wifi_refresh 
        "/wifi_refresh.cgi", cgi_handler_wifi_refresh
    },
};
/* ==== cgi end ======*/

/*===== post begin =====*/
typedef struct HTTP_POST_SERVER_T_ {
    void *current_connection;
    //void *valid_connection;
    char ssid[WIFI_PASS_BUFSIZE];
    char pass[WIFI_PASS_BUFSIZE];
    bool post_recv;
 } HTTP_POST_SERVER_T;

HTTP_POST_SERVER_T *server=NULL;

char* urldecode(char* str) {
  char tmpstr[WIFI_PASS_BUFSIZE];
  int j=0;
  int i=0;
  char tmpval[5];
  while(i < strlen(str)) {
    if (str[i] == '%') {
        sprintf(tmpval, "%s%c%c", "0x",str[i+1], str[i+2]);
        tmpstr[j] = strtol(tmpval, NULL, 16);
        i+=3;
    } else {
      tmpstr[j]=str[i];
      i++;
    }
    j++;
  }
  tmpstr[j]='\0';
  strcpy(str, tmpstr);
  return str;
}

void save_to_flash() {
  char flash_buff[256];
  flash_range_erase(FLASH_OFFSET, 4096); // one sector

  memset(flash_buff, 0, 256);
  sprintf(flash_buff, "{\"ssid\":\"%s\",\"pass\":\"%s\"}", urldecode(server->ssid), urldecode(server->pass));
  flash_range_program(FLASH_OFFSET, flash_buff, 256);   // one page

  watchdog_enable(5000, false); // after save data to flash, reboot in 5 seconds
}

err_t httpd_post_begin(void *connection, const char *uri, const char *http_request,
                 u16_t http_request_len, int content_len, char *response_uri,
                 u16_t response_uri_len, u8_t *post_auto_wnd)  {
  LWIP_UNUSED_ARG(connection);
  LWIP_UNUSED_ARG(http_request);
  LWIP_UNUSED_ARG(http_request_len);
  LWIP_UNUSED_ARG(content_len);
  LWIP_UNUSED_ARG(post_auto_wnd);
  server->post_recv=false;
  if (!memcmp(uri, "/wifi_conn.shtml", 17)) {
    if (server->current_connection != connection) {
      server->current_connection = connection;
      //server->valid_connection = NULL;
      snprintf(response_uri, response_uri_len, "/index.shtml"); // default : return main page
      /* e.g. for large uploads to slow flash over a fast connection, you should
         manually update the rx window. That way, a sender can only send a full
         tcp window at a time. If this is required, set 'post_aut_wnd' to 0.
         We do not need to throttle upload speed here, so: */
      *post_auto_wnd = 1;

      return ERR_OK;
    }
  }
  return ERR_VAL;
}

err_t httpd_post_receive_data(void *connection, struct pbuf *p) {
  err_t ret;

  LWIP_ASSERT("NULL pbuf", p != NULL);

  if (server->current_connection == connection) {
    u16_t token_ssid = pbuf_memfind(p, "ssid=", 5, 0);
    u16_t token_pass = pbuf_memfind(p, "pass=", 5, 0);
 
    if ((token_ssid != 0xFFFF) && (token_pass != 0xFFFF)) {
      u16_t value_ssid = token_ssid + 5;
      u16_t value_pass = token_pass + 5;
    
      u16_t len_ssid = 0;
      u16_t len_pass = 0;
      
      u16_t tmp;
      
      /* find ssid len */
      tmp = pbuf_memfind(p, "&", 1, value_ssid);
      if (tmp != 0xFFFF) {
        len_ssid = tmp - value_ssid;
      } else {
        len_ssid = p->tot_len - value_ssid;
      }
      /* find pass len */
      tmp = pbuf_memfind(p, "&", 1, value_pass);
      if (tmp != 0xFFFF) {
        len_pass = tmp - value_pass;
      } else {
        len_pass = p->tot_len - value_pass;
      }
      
      if ((len_ssid > 0) && (len_ssid < WIFI_PASS_BUFSIZE) &&
          (len_pass > 0) && (len_pass < WIFI_PASS_BUFSIZE) ) {
        
        char* tmpstr= (char*)pbuf_get_contiguous(p, &server->ssid, sizeof(server->ssid), len_ssid, value_ssid);
        tmpstr[len_ssid]=0;
        strcpy(server->ssid, tmpstr);
        tmpstr = (char*)pbuf_get_contiguous(p, &server->pass, sizeof(server->pass), len_pass, value_pass);
        tmpstr[len_pass]=0;
        strcpy(server->pass, tmpstr);
        server->post_recv=true;
      }
    }
    //server->valid_connection = connection;
   
    ret = ERR_OK;
  } else {
    ret = ERR_VAL;
  }

  /* this function must ALWAYS free the pbuf it is passed or it will leak memory */
  pbuf_free(p);

  return ret;
}

void httpd_post_finished(void *connection, char *response_uri, u16_t response_uri_len) {
  if (server->current_connection == connection) {
    //if (server->valid_connection == connection) {
        if (server->post_recv) {
           //save ssid & pass to fresh memory
            save_to_flash();
            snprintf(response_uri, response_uri_len, "/wifi_conn.shtml");
        } 
    //}
    server->current_connection = NULL;
    //server->valid_connection = NULL;
  }
}
/*===== post end =====*/

/*  === ssi begin =====*/
const char* __not_in_flash("httpd") ssi_tags[] = {
    "scanwifi",
    "ssid",
};

/* for scan wifi, multipart: every part for one scaned AP*/
u16_t __time_critical_func(ssi_handler)(int iIndex, char *pcInsert, int iInsertLen, u16_t current_tag_part, u16_t *next_tag_part)
{
    size_t printed;
    static char buff[500];
    char keyimg[]="<img src='img/key.png'>";
    char checked[8]="checked";
    int rssi=1;
    switch (iIndex) { 
        case 0: // for scanwifi in index.shtml
        if (aps) {
            if (current_tag_part < aps->len) {
                if (((aps->AP)+current_tag_part)->rssi > -90) rssi=2;
                if (((aps->AP)+current_tag_part)->rssi > -80) rssi=3;
                if (((aps->AP)+current_tag_part)->rssi > -70) rssi=4;
                if (((aps->AP)+current_tag_part)->auth_mode == CYW43_AUTH_OPEN) strcpy(keyimg,"");
                if (current_tag_part == 0) strcpy(checked, "checked"); else strcpy(checked, "");
                sprintf(buff, "<tr>"
                "<td>"
                "<input  type='radio' name='ssid' %s value='%s'>%s"
                "</td>"
                "<td>"
                "<img src='img/wifi%d.png'>"
                "</td>"
                "<td>%s</td></tr>", 
                checked, ((aps->AP)+current_tag_part)->ssid,((aps->AP)+current_tag_part)->ssid, rssi, keyimg);
                *next_tag_part=current_tag_part+1;
                printed = snprintf(pcInsert, iInsertLen, buff);

            } else {
                printed = snprintf(pcInsert, iInsertLen, "");
            }
        }
        break;
        case 1: // for ssid in wifi_conn.shtml
            printed = snprintf(pcInsert, iInsertLen,server->ssid);
            break;
        default: 
          printed = snprintf(pcInsert, iInsertLen, "");
    }
    return printed;
}
/*  === ssi end =====*/

void ap_http_server_start() {
     aps = (SCAN_APS_T*)calloc(1, sizeof(SCAN_APS_T));
     if (!aps) {
        printf("cannot alloc scan ap memory\n");
        return;
    }
    
    server = (HTTP_POST_SERVER_T*) calloc(1, sizeof(HTTP_POST_SERVER_T));
    if (!server) {
        printf("cannot alloc server object\n");
        return;
    }
    if (cyw43_arch_init()) {
      printf("http server cyw43_arch init error\n");
      return;
    }
    printf("Starting AP Mode: local IP:192.168.4.1\n");
    cyw43_arch_enable_ap_mode(WIFI_AP_SSID, WIFI_AP_PASSWORD, CYW43_AUTH_WPA2_AES_PSK);
    /* start dhcp server*/
    ip_addr_t gw, mask;
    IP4_ADDR(ip_2_ip4(&gw), 192, 168, 4, 1);
    IP4_ADDR(ip_2_ip4(&mask), 255, 255, 255, 0); 
    dhcp_server_init(&dhcp_server, &gw, &mask);

    scan_aps(aps, 2000);
  
    //check tag length
    size_t i;
    for (i = 0; i < LWIP_ARRAYSIZE(ssi_tags); i++) {
        LWIP_ASSERT("tag too long for LWIP_HTTPD_MAX_TAG_NAME_LEN",
                    strlen(ssi_tags[i]) <= LWIP_HTTPD_MAX_TAG_NAME_LEN);
    }
    
    http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
    http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers));

    httpd_init();
 
}

void ap_http_server_stop() {
    dhcp_server_deinit(&dhcp_server);
    free(aps);
    cyw43_arch_deinit();
}

  • ap_http_server.h
 #ifndef __HTTP_SERVER_H_
#define __HTTP_SERVER_H_

#define FLASH_OFFSET  0x180000    //1.5M
#define WIFI_PASS_BUFSIZE 91
void ap_http_server_start();
void ap_http_server_stop();
char* urldecode(char* str);

#endif

  • wifi_scan.c
#include <stdio.h>

#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "string.h"
#include "wifi_scan.h"

static int scan_result(void *env, const cyw43_ev_scan_result_t *result) {
    SCAN_APS_T *res_APs = (SCAN_APS_T*) env;
    if (result) {
        for (int i =0; i < res_APs->len; i++) {
            if (strcmp(result->ssid, ((res_APs->AP)+i)->ssid)==0) {
                if (result->rssi > ((res_APs->AP)+i)->rssi) 
                    *((res_APs->AP)+i) = *result; 
                return 0;
            }
        }
        cyw43_ev_scan_result_t *tmp = (cyw43_ev_scan_result_t*) realloc(res_APs->AP, sizeof(cyw43_ev_scan_result_t)*(res_APs->len+1));
        if (tmp) {
            res_APs->len += 1;
            res_APs->AP = tmp;
            *(res_APs->AP+res_APs->len-1) = *result;
        }
    } 
   
    return res_APs->len;
}

bool scan_aps(SCAN_APS_T* aps, uint32_t timeout) { // timeout: ms
    //cyw43_arch_enable_sta_mode() or cyw43_arch_enable_ap_mode() must be called before this function
    bool ret = false;
    aps = realloc(aps,0);
    cyw43_wifi_scan_options_t scan_options = {0};
    int err = cyw43_wifi_scan(&cyw43_state, &scan_options, aps, scan_result);
    if (err == 0) {
        printf("\nPerforming wifi scan\n");
        absolute_time_t scan_timeout = make_timeout_time_ms(timeout); // timeout
        while(absolute_time_diff_us(get_absolute_time(), scan_timeout) > 0) {
            if (!cyw43_wifi_scan_active(&cyw43_state)) {
                    //print out all scaned APs for debug
                for (int i =0; i < aps->len; i++) {
                    printf("%d.. ssid: %-32s rssi: %4d chan: %3d mac: %02x:%02x:%02x:%02x:%02x:%02x sec: %u\n",
                        i, (aps->AP+i)->ssid, (aps->AP+i)->rssi, (aps->AP+i)->channel,
                        (aps->AP+i)->bssid[0], (aps->AP+i)->bssid[1], (aps->AP+i)->bssid[2], (aps->AP+i)->bssid[3], (aps->AP+i)->bssid[4], (aps->AP+i)->bssid[5],
                        (aps->AP+i)->auth_mode);
                }
                ret = true;
                break;
            }
            cyw43_arch_poll();
            sleep_ms(1);
        }
        ret=false;
    } else {
        printf("Failed to start scan: %d\n", err);
        ret = false;
    }
    
    return ret;

}


  • wifi_scan.h
#ifndef __WIFI_SCAN_H_
#define _WIFI_SCAN_H_

#include "pico/stdlib.h"
typedef struct SCAN_APS_T_ {
    uint16_t len;
    cyw43_ev_scan_result_t *AP;
} SCAN_APS_T;

bool scan_aps(SCAN_APS_T* aps, uint32_t timeout);
#endif 

CMakeLists.txt
 # perl makefsdata 
find_package(Perl)
if(NOT PERL_FOUND)
    message(FATAL_ERROR "Perl is needed for generating the fsdata.c file")
endif()

set(MAKE_FS_DATA_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/mkfsdata/makefsdata)

if (EXISTS ${MAKE_FS_DATA_SCRIPT})
    message("Find makefsdata script")
    message("Running makefsdata script")
      execute_process(COMMAND
          perl ${MAKE_FS_DATA_SCRIPT}
          WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
          ECHO_OUTPUT_VARIABLE
          ECHO_ERROR_VARIABLE
        )
    file(RENAME fsdata.c _fsdata.c)
endif()

# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico_w CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

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()

project(picow_wifimanager C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(picow_wifimanager 
  picow_wifimanager.c 
    wifi_scan/wifi_scan.c 
    ap_http_server/ap_http_server.c 
    cJSON/cJSON.c
    dhcpserver/dhcpserver.c
    )

pico_set_program_name(picow_wifimanager "picow_wifimanager")
pico_set_program_version(picow_wifimanager "0.1")

pico_enable_stdio_uart(picow_wifimanager 1)
pico_enable_stdio_usb(picow_wifimanager 0)

# Add the standard library to the build
target_link_libraries(picow_wifimanager
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(picow_wifimanager PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
  ${CMAKE_CURRENT_LIST_DIR}/dhcpserver
  ${CMAKE_CURRENT_LIST_DIR}/cJSON 
  ${CMAKE_CURRENT_LIST_DIR}/ap_http_server
  ${CMAKE_CURRENT_LIST_DIR}/wifi_scan

)

# Add any user requested libraries
target_link_libraries(picow_wifimanager
        pico_cyw43_arch_lwip_poll
        pico_lwip_http
        hardware_flash
        hardware_watchdog
        )

pico_add_extra_outputs(picow_wifimanager)
  • picow_wifimanager.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/httpd.h"
#include "ap_http_server.h"
#include "cJSON.h"


bool connect_to_wifi_ssid() {

    char flash_buff[256];
    memset(flash_buff,0,256);
    snprintf(flash_buff, 256, "%s",(uint8_t*)(XIP_BASE+FLASH_OFFSET));
    if (!flash_buff) return false;
    cJSON *ssid_pass = cJSON_CreateObject();
    char *ssid;
    char *pass;
    ssid_pass = cJSON_Parse(flash_buff);
    if (ssid_pass) {
        ssid = cJSON_GetStringValue(cJSON_GetObjectItem(ssid_pass, "ssid"));
        pass = cJSON_GetStringValue(cJSON_GetObjectItem(ssid_pass, "pass"));
        
        if (cyw43_arch_init()) {
            printf("cyw43_arch init error\n");
            return false;
        }
        cyw43_arch_enable_sta_mode();
        printf("\n\n==========================\n"
            "Connecting to WiFi:%s\n"
                "==============================\n", ssid);
        if (cyw43_arch_wifi_connect_timeout_ms(ssid, pass, CYW43_AUTH_WPA2_AES_PSK, 10000)) { 
            printf("wifi sta connect error ssid:%s\n", ssid);
            cyw43_arch_deinit();
            return false;
        }
    } else {
        return false;
    }
    cJSON_Delete(ssid_pass);
    
    ip_addr_t addr = cyw43_state.netif->ip_addr;
    printf("connect successfully. get IP: %s\n", ipaddr_ntoa(&addr));
    return true;
}

int main()
{
    stdio_init_all();
    bool ap_mode=false;
    if (!connect_to_wifi_ssid()) {
        ap_mode=true;
        ap_http_server_start();
    }

    while(1) {
        static absolute_time_t led_time;
        static int led_on = true;
        if (absolute_time_diff_us(get_absolute_time(), led_time) < 0) {
            if (ap_mode) { 
                led_on = !led_on;
            }
            cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on);
            led_time = make_timeout_time_ms(1000);
        }
        cyw43_arch_poll();
        sleep_ms(1);
    }
   
    ap_http_server_stop();
    return 0;
}
  • index.shtml
<!DOCTYPE html>
<html>
    <head> <title>Pico W WiFi Manager</title> 
    <style>
        h1 { font-size: 70px;}
        table {width:600px; margin:auto}
        img {height:35px;}
        td {padding:8px;font-size:40px;}
        p {font-size: 50px;}
        tr:hover {background-color: coral;}
        input[type="radio"] {
            height:40px;
            width:40px;
        }
                
    </style>
    </head>
    <body> <h1>Pico W WiFi Manager</h1>
        <p>Please select a SSID:</p>
        <form method="post" action="/wifi_conn.shtml">

        <table>
            <!--#scanwifi-->
        </table>  
        <p align="center">        
            Password:<input style="height:40px;width:300px;font-size: 35px;" type="password" name="pass" required maxlength="30"> </p>
        <p align="center"> 
            <input style="height:70px;width:250px;font-size: 40px;" type="submit" name="button" value="Save">&nbsp&nbsp&nbsp&nbsp
            <input style="height:70px;width:250px;font-size: 40px;" type="button" name="refresh" value="Refresh" onclick="window.location.href='wifi_refresh.cgi'">
        </p>
        
        </form>       
   </body>
</html>
  • wifi_conn.shtml
<!DOCTYPE html>
<html>
    <head>
        <title>
            Pico W WiFi Manager
        </title>
    </head>
    <body>
        <p style="font-size: 45px;">Connecting to ssid: <!--#ssid--> </p>
        <p style="font-size: 35px;color:red;">The device will reboot in 5 seconds ...  </p>
    </body>
</html>
  • 404.shmtl
<html>
<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
<body bgcolor="white" text="black">

    <table width="100%">
      <tr valign="top"><td width="80">	  
	  
	</td><td width="500">	  
	  <h1>lwIP - A Lightweight TCP/IP Stack</h1>
	  <h2>404 - Page not found</h2>
	  <p>
	    Sorry, the page you are requesting was not found on this
	    server. 
	  </p>
	</td><td>
	  &nbsp;
	</td></tr>
      </table>
</body>
</html>

2023年2月7日 星期二

[Raspberry Pi Pico W (c-sdk)] lwIP: Ep 4. MQTT Client & Node-RED & Mosquitto & WS2812

 本文章介紹Raspberry Pi Pico W 使用lwIP MQTT Client 透過手機設定Pico W 上的WS2812 LED燈條展示方式。

  1. 顯示時鐘。
  2. 警示閃光。
  3. 隨機顏色、燈數與方向。
  4. 固定顏色、燈數與方向。




一、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
            )

如下圖所示:

在project CMakeLists.txt加入pico_lwip_mqtt library。


二、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);


三、lwIP MQTT Clinet Appliction:
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

    請參閱下列連結進一步說明:



五、成果影片:




六、程式碼:

  • 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
    }
]