prettyprint

2024年7月19日 星期五

[Raspberry Pi Pico (c-sdk)] LVGL Graphics Library & Pico PIO TFT display driver(Serial or Parallel)

 本文章介紹LVGL繪圖函式庫如何結合本網站先前介紹的Raspberry Pi Pico 利用PIO製作的驅動程式。

  1. [Raspberry Pi Pico (c-sdk)] Display: Ep 5 :TFT LCD 4-lines Serial(SPI) Driver
  2. [Raspberry Pi Pico (c-sdk)] Display: Ep 4 : ILI9341 TFT LCD 8-bit parallel
本文章以LVGL 8.4.0為例:

一、LVGL Graphic library所需要的檔案

  1. src資料夾、
  2. lvgl.h 
  3. lv_conf.h(由lv_conf_template.h複製)
修改lv_conf.h的內容:
  1. /* clang-format off */
    #if 1 /*Set it to "1" to enable content*/
  2. #define LV_COLOR_DEPTH 16
    #define LV_COLOR_16_SWAP 1
CMakeLists.txt內容:

二、週期性呼叫 lv_tick_inc(x)

bool lv_inc_timer_cb(repeating_timer_t *rt) {
lv_tick_inc(*((uint8_t*)rt->user_data));
return true;
}

void pico_lvgl_tick_inc_timer_init(uint8_t tick_inc) {
static repeating_timer_t rt;
static uint8_t _tick_inc;
_tick_inc = tick_inc;
add_repeating_timer_ms(tick_inc, lv_inc_timer_cb, (void*) (&_tick_inc), &rt);
}


三、呼叫 lv_init()

四、製作draw buffer flush callback function, 將一個block資料寫到TFT Display memory中
void tft_lvgl_draw_bitmap(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *bitmap)
{
uint32_t total_pixels = (x2-x1+1) * (y2-y1+1)*2;

tft_set_address_window (x1, y1, x2, y2);
tft_cmd_dma(TFT_MEMORYWRITE, total_pixels, bitmap);
}

void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
tft_lvgl_draw_bitmap(
(uint16_t)(area->x1),
(uint16_t)(area->y1),
(uint16_t)(area->x2),
(uint16_t)(area->y2), (uint8_t*)color_p
);
lv_disp_flush_ready(disp); /* Indicate you are ready with the flushing*/
}
其中
tft_lvgl_draw_bitmap(...)為前篇文章介紹PIO TFT Display Driver的function。

五、以製作一組音樂播放器為展示範例



六、程式碼:

  • 更改 pico_tft.h:
/*=== according to TFT===*/
#define TFT_WIDTH           320 
#define TFT_HEIGHT          480 

#define PICO_TFT_SERIAL     1
#define PICO_TFT_PARALLEL   0
#define PICO_TFT_DMA        1

#define TFT_ST7735          0
#define TFT_ST7796          1
#define TFT_ILI9342         0
#define TFT_ILI9341         0
/*=== according to TFT===*/
以符合硬體

  • 更改 xpt2046.h:
//== Set the following values ​​according to the hardware ==
#define XPT2046_IRQ_GPIO    11 
#define XPT2046_MOSI        15 
#define XPT2046_MISO        12 
#define XPT2046_CS          13 
#define XPT2046_CLK         14 
#define XPT2046_SPI         spi1

#define XPT2046_MIN_RAW_X 1350  
#define XPT2046_MAX_RAW_X 31000 
#define XPT2046_MIN_RAW_Y 2050  
#define XPT2046_MAX_RAW_Y 31500 
//== Set the following values ​​according to the hardware ==
以符合硬體

  1. lvgl
CmakeList.txt
option(LV_LVGL_H_INCLUDE_SIMPLE
       "Use #include \"lvgl.h\" instead of #include \"../../lvgl.h\"" ON)

# Option to define LV_CONF_INCLUDE_SIMPLE, default: ON
option(LV_CONF_INCLUDE_SIMPLE
       "Use #include \"lv_conf.h\" instead of #include \"../../lv_conf.h\"" ON)


add_library(lvgl INTERFACE)


file(GLOB_RECURSE SOURCES src/*.c src/*.S)

target_sources(lvgl INTERFACE
    ${SOURCES}
)

target_include_directories(lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..
)

  
  • pico_tft
CMakeLists.txt
add_library(pico_tft INTERFACE)
pico_generate_pio_header(pico_tft ${CMAKE_CURRENT_LIST_DIR}/pico_tft.pio)

target_sources(pico_tft INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft.c
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft_lvgl.c

)

target_include_directories(pico_tft INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..

)

target_link_libraries(pico_tft INTERFACE
        hardware_pio
        hardware_dma
)

  

pico_tft.h
#ifndef  _TFT_H_
#define _TFT_H_

/*=== according to TFT===*/
#define TFT_WIDTH           320 
#define TFT_HEIGHT          480 

#define PICO_TFT_SERIAL     1
#define PICO_TFT_PARALLEL   0
#define PICO_TFT_DMA        1

#define TFT_ST7735          0
#define TFT_ST7796          1
#define TFT_ILI9342         0
#define TFT_ILI9341         0
/*=== according to TFT===*/

enum {
    TFT_ORIENTATION_PORTRAIT=0,
    TFT_ORIENTATION_LANDSCAPE,
    TFT_ORIENTATION_PORTRAIT_MIRROR,
    TFT_ORIENTATION_LANDSCAPE_MIRROR,
};     

#include "pico/stdlib.h"

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


void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin);
void tft_init_config();
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
void tft_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void tft_cmd(uint32_t cmd, uint32_t count,  uint8_t *param);
void tft_cmd_dma(uint32_t cmd, uint32_t count,  uint8_t *param);
uint16_t tft_color_565RGB(uint8_t R, uint8_t G, uint8_t B);
//void tft_lv_draw_bitmap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *bitmap);
void tft_pio_cmd_init(PIO pio, uint sm, uint out_base,  uint set_base, uint32_t freq);

uint16_t tft_get_width();
uint16_t tft_get_height();
uint8_t tft_get_orientation();
void tft_set_orientation(uint8_t orientation);

void tft_fill_rect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);

#endif

pico_tft.c
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "string.h"

#include "registers.h"
#include "pico_tft.pio.h"
#include "pico_tft.h"
#include "hardware/dma.h"

#define MAX_BYTE_TRANS (TFT_WIDTH*TFT_HEIGHT*2)

// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
#if TFT_ST7735
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b11001000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b10101000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b00001000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b01101000;
#endif

#if TFT_ST7796 
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b01001000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b00101000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b10001000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b11101000;
#endif 

#if TFT_ILI9341
// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b00000000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b01100000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b11000000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b10100000;
#endif

#if TFT_ILI9342
// MADCTL register: 			MY,MX,MV,ML,BGR,MH,x,x
static uint8_t TFT_MADCTL_PORTRAIT  		=	0b00000000;
static uint8_t TFT_MADCTL_LANDSCAPE  		=	0b01100000;
static uint8_t TFT_MADCTL_PORTRAIT_MIRROR  =	0b11000000;
static uint8_t TFT_MADCTL_LANDSCAPE_MIRROR = 	0b10100000;

#endif

static uint16_t tft_width;
static uint16_t tft_height;
static uint8_t tft_orientation;

PIO tft_pio = pio1;
uint tft_sm=0;
uint in_out_base_pin=4;
uint set_base_pin=12;
uint sideset_base_pin=16; 
uint s_in_out_base_pin=19; 

int tft_dma_channel;

void tft_cmd(uint32_t cmd, uint32_t count, uint8_t *param)
{
    #if PICO_TFT_SERIAL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd << 24);
    #endif
    #if PICO_TFT_PARALLEL
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    #endif
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    for (int i = 0; i < count; i++)
    {
        #if PICO_TFT_SERIAL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]<<24);
        #endif
        #if PICO_TFT_PARALLEL
        pio_sm_put_blocking(tft_pio, tft_sm, param[i]);
        #endif
    }
}
//#ifdef PICO_TFT_DMA
void tft_cmd_dma(uint32_t cmd, uint32_t count, uint8_t *param)
{
    #if PICO_TFT_SERIAL
    tft_cmd(cmd, count, param);
    return;
    #endif 
    #if PICO_TFT_PARALLEL
    #if PICO_TFT_DMA
    pio_sm_put_blocking(tft_pio, tft_sm, cmd);
    pio_sm_put_blocking(tft_pio, tft_sm, count);
    dma_channel_set_trans_count(tft_dma_channel, count >> DMA_SIZE_8, false);
    dma_channel_set_read_addr(tft_dma_channel, param, false);
    dma_channel_start(tft_dma_channel);
    dma_channel_wait_for_finish_blocking(tft_dma_channel);
    #else
    tft_cmd(cmd, count, param);
    #endif
    #endif
    
}
//#endif
void tft_pio_cmd_init(PIO pio, uint sm, uint in_out_base,  uint set_sideset, uint32_t freq) {
    uint offset=0;
    pio_sm_config c;
    #if PICO_TFT_PARALLEL
    offset = pio_add_program(pio, &tft_pio_parallel_program);
    c = tft_pio_parallel_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_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 4, true);
    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_sideset, 4);
    sm_config_set_out_shift(&c, true, false, 8);
    sm_config_set_in_shift(&c, false, false, 8);
    #endif
    #if PICO_TFT_SERIAL
    offset = pio_add_program(pio, &tft_pio_serial_program);
    c = tft_pio_serial_program_get_default_config(offset);
    pio_gpio_init(pio, in_out_base);
    for (int i=0; i < 3; i++) pio_gpio_init(pio, set_sideset+i);
    pio_sm_set_consecutive_pindirs(pio, sm, in_out_base, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_sideset, 3, true);
    sm_config_set_in_pins(&c, in_out_base);
    sm_config_set_out_pins(&c, in_out_base, 1);
    sm_config_set_sideset_pins(&c, set_sideset);
    sm_config_set_out_shift(&c, false, false, 8);
    sm_config_set_in_shift(&c, true, false, 8);
    #endif
       
    //sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
    
    float div = (float)clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    //sm_config_set_clkdiv(&c, 1.25);
    
    #if PICO_TFT_DMA
    /*   DMA  */
    tft_dma_channel = dma_claim_unused_channel(true);
    dma_channel_config dc = dma_channel_get_default_config(tft_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
    
    uint32_t pio_base, tx_offset;
    pio_base = (pio == pio0) ? PIO0_BASE: PIO1_BASE;
    tx_offset = PIO_TXF0_OFFSET + sm*4;

    dma_channel_configure(tft_dma_channel, &dc, (void*) (pio_base + tx_offset), 
             NULL, MAX_BYTE_TRANS>> DMA_SIZE_8, false); //DMA_SIZE_8 or 16 or 32
    /*  DMA */
    #endif 
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

/* tft draw functions*/
uint16_t tft_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 tft_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);
    tft_cmd(TFT_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);
	tft_cmd(TFT_PAGEADDRSET, 4,   addr );

    tft_cmd(TFT_MEMORYWRITE, 0, NULL);
}

void tft_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);
    tft_cmd(TFT_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);
	tft_cmd(TFT_PAGEADDRSET, 4,  addr );
}

/* put color at point*/
void tft_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
    if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) {
        printf("over range,x,y\n");
        return;
    }
	tft_set_address_window(x,y,x,y);
    tft_cmd(TFT_MEMORYWRITE, 2,  (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}

uint16_t tft_get_width() {
    return tft_width;
}

uint16_t tft_get_height() {
    return tft_height;
}

uint8_t tft_get_orientation() {
    return tft_orientation;
}
void tft_set_orientation(uint8_t orientation) {
    tft_orientation = orientation;
    switch (orientation) {
        case TFT_ORIENTATION_PORTRAIT:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_PORTRAIT});
            tft_width = TFT_WIDTH;
            tft_height = TFT_HEIGHT;
        break;
        case TFT_ORIENTATION_PORTRAIT_MIRROR:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_PORTRAIT_MIRROR});
            tft_width = TFT_WIDTH;
            tft_height = TFT_HEIGHT;
        break;
        case TFT_ORIENTATION_LANDSCAPE:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_LANDSCAPE});
            tft_width = TFT_HEIGHT;
            tft_height = TFT_WIDTH;
        break;
        case TFT_ORIENTATION_LANDSCAPE_MIRROR:
            tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){TFT_MADCTL_LANDSCAPE_MIRROR});
            tft_width = TFT_HEIGHT;
            tft_height = TFT_WIDTH;
        break;
    }

}

#if TFT_ST7735
void tft_init_config() {
	tft_cmd(TFT_SOFTRESET, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYOFF, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_PIXELFORMAT, 1,  (uint8_t[1]){0x55}); //0x55
    tft_cmd(TFT_POWERCONTROL1, 1,  (uint8_t[1]){0x05}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL2, 1,  (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2,  (uint8_t[2]){0x3E, 0x28});
    tft_cmd(TFT_VCOMCONTROL2, 1,  (uint8_t[1]){0x86});
    //tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){0x08}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_FRAMECONTROL, 2,  (uint8_t[2]){0b00, 0x1B}); // Default 70Hz:0x1B
    tft_cmd(TFT_DISPLAYFUNC, 4,  (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    tft_cmd(TFT_GAMMASET, 1,  (uint8_t[1]){0x01});
  
  	//tft_cmd(tft_PGAMCOR, 15, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    //tft_cmd(tft_NGAMCOR,  15,0xFF, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    tft_cmd(TFT_SLEEPOUT, 0,  NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
#endif

#if TFT_ST7796
void tft_init_config() {
	//tft_cmd(TFT_SOFTRESET, 1,  NULL);
    tft_cmd(TFT_SOFTRESET, 0,  NULL);
    sleep_ms(120);
    tft_cmd(TFT_SLEEPOUT, 0,  NULL);
    sleep_ms(120);

    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0xC3}); //enable part 1
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x96}); //enable part 2
    //tft_cmd(TFT_MADCTL, 1,  (uint8_t[1]){0x88}); //MY,MX,MV,ML,BRG,MH,0,0(24), 0:RGB
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_PIXELFORMAT, 1,  (uint8_t[1]){0x05}); //0x05:RGB565, 0x06 RGB666
    tft_cmd(TFT_DSIPLAY_INVER, 1,  (uint8_t[1]){0x01}); // 1-dot
    #if TFT_ILI9342
    tft_cmd(TFT_DISPLAYFUNC, 3,  (uint8_t[3]){0x0A, 0x82, 0x27});  // ILI9342
    #endif
    #if TFT_ST7796
    tft_cmd(TFT_DISPLAYFUNC, 3,  (uint8_t[3]){0x80, 0x02, 0x3B}); // ST7796
    #endif
    tft_cmd(TFT_DISP_OUTPUT_CTRL_ADJUST, 8,  (uint8_t[8])
                {0x40,
                 0x8A,
                 0x00,
                 0x00,
                 0x29,  //Source eqaulizing period time= 22.5 us
                 0x19,  //Timing for "Gate start"=25 (Tclk)
                 0xA5,  //Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON
                 0x33}); // ST7796

    tft_cmd(TFT_POWERCONTROL2, 1,  (uint8_t[1]){0x06}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL3, 1,  (uint8_t[1]){0xA7});
    tft_cmd(TFT_VCOMCONTROL1, 1,  (uint8_t[1]){0x18});
    sleep_ms(120);
    tft_cmd(TFT_PGAMCOR, 14, (uint8_t[14]){ 0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b});
    tft_cmd(TFT_NGAMCOR, 14, (uint8_t[14]){ 0xe0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2b, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}); 
    sleep_ms(120);
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x3C}); // disable part 1
    tft_cmd(TFT_COMMANDSET, 1,  (uint8_t[1]){0x69}); // disable part 2

    tft_cmd(TFT_DISPLAYOFF, 0,  NULL);
    sleep_ms(120);    
    tft_cmd(TFT_DISPLAYON, 0,  NULL);
    sleep_ms(500);
    
}
#endif 

#if TFT_ILI9342
void tft_init_config() {
    tft_cmd(TFT_SOFTRESET, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYOFF, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_PIXELFORMAT, 1, (uint8_t[1]){0x55}); //0x55
    tft_cmd(TFT_POWERCONTROL1, 1, (uint8_t[1]){0x05}); // 0x05 :3.3V
    tft_cmd(TFT_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2, (uint8_t[2]){0x3E, 0x28});
    tft_cmd(TFT_VCOMCONTROL2, 1, (uint8_t[1]){0x86});
    tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){0x60}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_FRAMECONTROL, 2, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    tft_cmd(TFT_DISPLAYFUNC, 4, (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    tft_cmd(TFT_GAMMASET, 1, (uint8_t[1]){0x01});
  
  	tft_cmd(TFT_PGAMCOR, 15, (uint8_t[15]){ 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00 });
    tft_cmd(TFT_NGAMCOR, 15, (uint8_t[15]){ 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36, 0x0f }); 
    
    tft_cmd(TFT_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0, NULL);
    sleep_ms(500);
    
    
}
#endif

#if TFT_ILI9341
void tft_init_config() {
    
    tft_cmd(TFT_SOFTRESET, 0, NULL);
    sleep_ms(50);
    tft_cmd(TFT_DISPLAYOFF, 0, NULL);

    tft_cmd(TFT_POWERCONTROL1, 1, (uint8_t[1]){0x23});
    tft_cmd(TFT_POWERCONTROL2, 1, (uint8_t[1]){0x10});
    tft_cmd(TFT_VCOMCONTROL1, 2, (uint8_t[2]){0x2B, 0x2B});
    tft_cmd(TFT_VCOMCONTROL2, 1, (uint8_t[1]){0xC0});
    tft_cmd(TFT_MADCTL, 1, (uint8_t[1]){0x88}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    tft_set_orientation(TFT_ORIENTATION_PORTRAIT);
    tft_cmd(TFT_PIXELFORMAT, 1, (uint8_t[1]){0x55});
    tft_cmd(TFT_FRAMECONTROL, 2, (uint8_t[2]){0x00, 0x1B});

    tft_cmd(TFT_ENTRYMODE, 1, (uint8_t[1]){0x07});
    //tft_cmd(TFT_DISPLAYFUNC, 4, (uint8_t[4]){0x0A, 0x82, 0x27, 0x00});

    tft_cmd(TFT_SLEEPOUT, 0, NULL);
    sleep_ms(150);
    tft_cmd(TFT_DISPLAYON, 0, NULL);
    sleep_ms(500);
}
#endif

void tft_init(PIO pio, uint sm, uint din_base, uint csx_dcx_sck_side_base_pin) {
    tft_pio = pio;
    tft_sm = sm;
    #if PICO_TFT_PARALLEL
        in_out_base_pin = din_base;
        set_base_pin = csx_dcx_sck_side_base_pin;
        tft_pio_cmd_init(tft_pio, tft_sm, in_out_base_pin, set_base_pin, 70000000/*70000000*/);  //pio freq
    #endif
    #if PICO_TFT_SERIAL
        s_in_out_base_pin = din_base;
        sideset_base_pin = csx_dcx_sck_side_base_pin;
        tft_pio_cmd_init(tft_pio, tft_sm, s_in_out_base_pin, sideset_base_pin, 90000000/* 62.5M baud rate for SPI*/);  //pio freq  
    #endif 

    tft_init_config();

    
}
  

pico_tft_lvgl.h
#ifndef __PICO_TFT_LVGL__
#define __PICO_TFT_LVGL__
#include "pico_tft.h"
#include "lvgl.h"

void tft_lvgl_draw_bitmap(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *bitmap);
void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p);
uint32_t pico_lvgl_get_tick_cb();
#endif
  

pico_tft_lvgl.c
#include "pico_tft.h"
#include "pico_tft_lvgl.h"
#include "registers.h"
#include "stdio.h"

void tft_lvgl_draw_bitmap(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t *bitmap)
{
	uint32_t total_pixels = (x2-x1+1) * (y2-y1+1)*2;

	tft_set_address_window (x1, y1, x2, y2);
    tft_cmd_dma(TFT_MEMORYWRITE, total_pixels,  bitmap);
   
}

void tft_lvgl_disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
    tft_lvgl_draw_bitmap(
            (uint16_t)(area->x1), 
            (uint16_t)(area->y1), 
            (uint16_t)(area->x2), 
            (uint16_t)(area->y2), (uint8_t*)color_p
            );
 
    lv_disp_flush_ready(disp);         /* Indicate you are ready with the flushing*/
}

/*  tell how many milliseconds have elapsed since start up
    for lvgl 9.x.x
*/
uint32_t pico_lvgl_get_tick_cb() 
{
    return (time_us_32()/1000);    
}

  

pico_tft.pio
.program tft_pio_parallel
.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
.wrap


.program tft_pio_serial
; CSX, D/CX(A0), SCL --> gpio 13, 12, 11  (side-set pins)
.side_set 3
.wrap_target
start:
pull                        side 0b100
mov x, osr                  side 0b000           ;command code, if 0x0, command nop, only data
jmp !x param                side 0b000
set y,7                     side 0b000
cmd_bit_loop:
out pins, 1                 side 0b000  [1]
jmp y--, cmd_bit_loop       side 0b001  [1]

param:
pull                        side 0b010
mov x, osr                  side 0b010            ;how many parameters
jmp !x, start               side 0b010           ;no parameter return start
jmp x--, param_data         side 0b010        
param_data:
pull                        side 0b010                ; write data
set y,7                     side 0b010
data_bit_loop:
out pins, 1                 side 0b010  [1]    
jmp y--, data_bit_loop      side 0b011  [1]
jmp x--, param_data         side 0b010
jmp start                   side 0b100
  • registers.h
#define TFT_NOP             0x00
#define TFT_SOFTRESET       0x01
#define TFT_SLEEPIN         0x10
#define TFT_SLEEPOUT        0x11
#define TFT_NORMALDISP      0x13
#define TFT_INVERTOFF       0x20
#define TFT_INVERTON        0x21
#define TFT_GAMMASET        0x26
#define TFT_DISPLAYOFF      0x28
#define TFT_DISPLAYON       0x29
#define TFT_COLADDRSET      0x2A
#define TFT_PAGEADDRSET     0x2B
#define TFT_MEMORYWRITE     0x2C
#define TFT_MEMORYREAD      0x2E
#define TFT_PIXELFORMAT     0x3A
#define TFT_MEMORYWRITECONT  0x3C
#define TFT_MEMORYREADCONT  0x3E
#define TFT_FRAMECONTROL    0xB1
#define TFT_DSIPLAY_INVER   0xB4
#define TFT_DISPLAYFUNC     0xB6
#define TFT_ENTRYMODE       0xB7
#define TFT_POWERCONTROL1   0xC0
#define TFT_POWERCONTROL2   0xC1
#define TFT_POWERCONTROL3   0xC2
#define TFT_VCOMCONTROL1    0xC5
#define TFT_VCOMCONTROL2    0xC7
#define TFT_COMMANDSET      0xF0
#define TFT_VSCROLLDEF      0x33
#define TFT_VSCROLLADDR     0x37
#define TFT_MEMCONTROL      0x36
#define TFT_MADCTL          0x36

#define TFT_PGAMCOR         0xE0
#define TFT_NGAMCOR         0xE1
#define TFT_DISP_OUTPUT_CTRL_ADJUST                 0xE8

#define TFT_MADCTL_MY       0x80
#define TFT_MADCTL_MX       0x40
#define TFT_MADCTL_MV       0x20
#define TFT_MADCTL_ML       0x10
#define TFT_MADCTL_RGB      0x00
#define TFT_MADCTL_BGR      0x08
#define TFT_MADCTL_MH       0x04
  • XPT2046_touch
CMakeLists.txt
add_library(xpt2046_touch INTERFACE)
target_sources(xpt2046_touch INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/xpt2046.c
)

target_include_directories(xpt2046_touch INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/..
)

target_link_libraries(xpt2046_touch INTERFACE
        hardware_spi
        hardware_dma
)
  

xpt2046.h
#ifndef _XPT2046_H_
#define _XPT2046_H_
#include "pico/stdlib.h"
#include "lvgl.h"

//== Set the following values ​​according to the hardware ==
#define XPT2046_IRQ_GPIO    11 
#define XPT2046_MOSI        15 
#define XPT2046_MISO        12 
#define XPT2046_CS          13 
#define XPT2046_CLK         14 
#define XPT2046_SPI         spi1

#define XPT2046_MIN_RAW_X 1350  
#define XPT2046_MAX_RAW_X 31000 
#define XPT2046_MIN_RAW_Y 2050  
#define XPT2046_MAX_RAW_Y 31500 
//== Set the following values ​​according to the hardware ==


void xpt2046_init();
bool xpt2046_getXY(uint16_t *x, uint16_t *y);
bool xpt2046_TouchPressed();
void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data);

#endif
  

xpt2046.c
#include "stdio.h"
#include "stdlib.h"
#include "xpt2046.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "pico/stdlib.h"
#include "pico_tft.h"

uint32_t xpt2046_event=0;
uint8_t READ_X = 0xD0;
uint8_t READ_Y = 0x90;
#define XPT_WIDTH  320
#define XPT_HEIGHT 480

bool xpt2046_getXY(uint16_t *x, uint16_t *y) {
   
    uint8_t temp[2];
    uint16_t raw_x, raw_y;
    uint16_t est_raw_x, est_raw_y;
    uint32_t avg_x = 0;
    uint32_t avg_y = 0;
     uint8_t nsamples = 0;
    uint8_t SAMPLES=10;

     gpio_put(XPT2046_CS, false);  
     if(gpio_get(XPT2046_IRQ_GPIO)) return false; 
     busy_wait_ms(10);
    //first pass
    SAMPLES=20; // get first average;
    for(uint8_t i = 0; i < SAMPLES; i++, nsamples++)
    {
        if(gpio_get(XPT2046_IRQ_GPIO)) {
            break;
        }
        spi_write_blocking(XPT2046_SPI, &READ_X, 1);
        spi_read_blocking(XPT2046_SPI, 0x00, temp, 2);
        raw_x = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1];

        spi_write_blocking(XPT2046_SPI, &READ_Y, 1);
        spi_read_blocking(XPT2046_SPI, 0x00, temp, 2);
        raw_y = ((uint16_t)temp[0]) << 8 | (uint16_t)temp[1];

        avg_x += raw_x;
        avg_y += raw_y;
    }  

    if(nsamples < SAMPLES)
        return false;

    gpio_put(XPT2046_CS, true);
    raw_x = (avg_x / SAMPLES);
    raw_y = (avg_y / SAMPLES);

    if(raw_x < XPT2046_MIN_RAW_X || raw_x > XPT2046_MAX_RAW_X) return false;
    if(raw_y < XPT2046_MIN_RAW_Y || raw_y > XPT2046_MAX_RAW_Y)  return false;  
    
    uint16_t tx,ty;
    tx = (raw_x - XPT2046_MIN_RAW_X) * XPT_WIDTH  / (XPT2046_MAX_RAW_X - XPT2046_MIN_RAW_X);
    ty = (raw_y - XPT2046_MIN_RAW_Y) * XPT_HEIGHT / (XPT2046_MAX_RAW_Y - XPT2046_MIN_RAW_Y);
   
    // adjust for TFT orientation
    uint8_t lot = tft_get_orientation();
		switch (lot)
		{
		case TFT_ORIENTATION_PORTRAIT:
			*x=tx;
			*y=TFT_HEIGHT-ty;
			break;
		case TFT_ORIENTATION_LANDSCAPE:
			*x=TFT_HEIGHT-ty;
			*y=TFT_WIDTH-tx;
					break;
		case TFT_ORIENTATION_PORTRAIT_MIRROR:
			*x=TFT_WIDTH-tx;
			*y=ty;
					break;
		case TFT_ORIENTATION_LANDSCAPE_MIRROR:
			*x=ty;
			*y=tx;
			break;
		}
    return true;   
}


bool xpt2046_TouchPressed()
{
    return !gpio_get(XPT2046_IRQ_GPIO);
}

void xpt2046_init() {
    gpio_init(XPT2046_IRQ_GPIO);
    gpio_init(XPT2046_MISO);
    gpio_init(XPT2046_MOSI);
    gpio_init(XPT2046_CLK);
    gpio_init(XPT2046_CS);
    gpio_set_dir(XPT2046_CS, GPIO_OUT);
    gpio_set_dir(XPT2046_IRQ_GPIO, GPIO_OUT);
    gpio_set_function(XPT2046_CLK,GPIO_FUNC_SPI);
    gpio_set_function(XPT2046_CS,GPIO_FUNC_SIO);
    gpio_set_function(XPT2046_MOSI,GPIO_FUNC_SPI);
    gpio_set_function(XPT2046_MISO,GPIO_FUNC_SPI);
    //spi_init(XPT2046_SPI,250000);
    spi_init(XPT2046_SPI,3125000); //3125000

}

extern lv_obj_t *mouse_cursor;
void xpt2046_lvgl_read_cb(struct _lv_indev_drv_t * indev, lv_indev_data_t* data) {
    uint16_t x,y;
    
    if (xpt2046_TouchPressed()) { 
        if (xpt2046_getXY(&x,&y)) {
            data->point.x = x;
            data->point.y = y;
            data->state = LV_INDEV_STATE_PRESSED; 
        } else {
            data->state = LV_INDEV_STATE_RELEASED;
        }          
    }  else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}
  • pico_lvgl
CMakeLists.txt

add_library(pico_lvgl INTERFACE)

target_sources(pico_lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_lvgl.c
)

add_subdirectory(pico_tft)
add_subdirectory(xpt2046_touch)
add_subdirectory(lvgl)

target_include_directories(pico_lvgl INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/pico_tft
    ${CMAKE_CURRENT_LIST_DIR}/xpt2046_touch
    ${CMAKE_CURRENT_LIST_DIR}/lvgl
)

target_link_libraries(pico_lvgl INTERFACE
        hardware_pio
        hardware_dma
        pico_tft
        xpt2046_touch
        lvgl
)

pico_lvgl.c

#include "pico_lvgl.h"

bool lv_inc_timer_cb(repeating_timer_t *rt) {
    lv_tick_inc(*((uint8_t*)rt->user_data));
    return true;
}

/*
TFT_SERIAL:
@param sdi_gpio: SDI(MOSI)
@param csx_dcx_sck_gpio: csx:GPIO_n+2,dcx:GPIO_n+1, sck:GPIO_n 

TFT_PARALLEL:
@param sdi_gpio: LCD_D0(DB0): GPIO_dn, LCD_D1(DB1):GPIO_dn+1,...LCD_D7(DB7):GPIO_dn+7 
@param csx_dcx_sck_gpio: CS:GPIO_n+3,RS:GPIO_n+2, WR:GPIO_n+1, RD:GPIO_n 
*/
void pico_lvgl_tft_init(PIO pio, uint sm, uint sdi_gpio, uint csx_dcx_sck_gpio) {

    tft_init(pio , sm, sdi_gpio, csx_dcx_sck_gpio);


}


void pico_lvgl_tick_inc_timer_init(uint8_t tick_inc) {
    static repeating_timer_t rt;
    static uint8_t _tick_inc;
    _tick_inc = tick_inc;
    add_repeating_timer_ms(tick_inc, lv_inc_timer_cb, (void*) (&_tick_inc), &rt);
}

/*
tick_inc: to call the lv_tick_inc(x) function periodically in (x) milliseconds.
*/
void pico_lvgl_display_init(uint8_t tick_inc) {
    // init timer
    if (tick_inc > 10) tick_inc = 10;
    if (tick_inc < 1) tick_inc = 1;
    pico_lvgl_tick_inc_timer_init(tick_inc);

    // lv_init
    lv_init();

    // set draw buffer
    static lv_disp_draw_buf_t draw_buf;
    static lv_color_t buf1[TFT_WIDTH * TFT_HEIGHT / 10];                        /*Declare a buffer for 1/10 screen size*/
    lv_disp_draw_buf_init(&draw_buf, buf1, NULL, TFT_WIDTH * TFT_HEIGHT / 10);  /*Initialize the display buffer.*/
    
    //setup display
    static lv_disp_drv_t disp_drv;        /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);          /*Basic initialization*/
    disp_drv.flush_cb = tft_lvgl_disp_flush;    /*Set your driver function*/
    disp_drv.draw_buf = &draw_buf;        /*Assign the buffer to the display*/
    disp_drv.hor_res = tft_get_width();   /*Set the horizontal resolution of the display*/
    disp_drv.ver_res = tft_get_height();   /*Set the vertical resolution of the display*/
    lv_disp_drv_register(&disp_drv);      /*Finally register the driver*/

}


void pico_lvgl_xpt2046_init() {
    // init xpt2046 hardware
    xpt2046_init();

    // setup touch screen input device
    static lv_indev_drv_t indev_drv;           /*Descriptor of a input device driver*/
    lv_indev_drv_init(&indev_drv);             /*Basic initialization*/
    indev_drv.type = LV_INDEV_TYPE_POINTER;    /*Touch pad is a pointer-like device*/
    indev_drv.read_cb = xpt2046_lvgl_read_cb;      /*Set your driver function*/
    lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
}


lv_indev_t *encoder_indev;
lv_group_t *encoder_group;
void pico_lvgl_encoder_init(bool pio_mode) {
    // init rotary encoder hardware
    if (pio_mode) {
        lvgl_pio_encoder_init();
    } else 
    {
        gpio_encoder_init();
    }

    // setup rotary encoder input device
    static lv_indev_drv_t indev_drv;           /*Descriptor of a input device driver*/
    lv_indev_drv_init(&indev_drv);             /*Basic initialization*/
    indev_drv.type = LV_INDEV_TYPE_ENCODER;    /*Rotary Encoder device*/
    if (pio_mode) {
        indev_drv.read_cb = lvgl_encoder_read_cb;      /*Set your driver function*/
    
    } else {
        indev_drv.read_cb = gpio_encoder_read_cb;
    }
    //indev_drv.long_press_time=1000;
    indev_drv.long_press_repeat_time=2000;
    encoder_indev = lv_indev_drv_register(&indev_drv);         /*Finally register the driver*/
    encoder_group = lv_group_create();
    
    //lv_group_set_default(kg);
    lv_indev_set_group(encoder_indev, encoder_group);

}

    
    
    
     
    
pico_lvgl.h

#ifndef __PICO_LVGL_H__
#define __PICO_LVGL_H__
#include "stdio.h"
#include "pico/stdio.h"
#include "hardware/pio.h"
#include "pico_tft.h"
#include "xpt2046.h"
#include "lvgl.h"

void pico_lvgl_tft_init(PIO pio, uint sm, uint sdi_gpio, uint csx_dcx_sck_gpio);
void pico_lvgl_xpt2046_init();

void pico_lvgl_display_init(uint8_t tick_inc);



#endif

  • root 
CMakeLists.txt

add_subdirectory(pico_lvgl)
target_link_libraries(project_name
        pico_lvgl
)





2024年6月2日 星期日

[Raspberry Pi Pico W] 7-segment ws2812 LED digital clock | Lwip httpd, mdns, sntp application

 本文章介紹使用ws2812製作一個大型的7段顯示器的數位時鐘。

使用Raspberry Pi Pico W微處理器開發板。

使用Lwip httpd, mdns與sntp application。



  • lwipopts.h依照使用的lwip application需要增加的設定值:
  • Lwip httpd:
    網頁檔案儲存在Flash中,因此需要將檔案編譯成程式碼的一部分,使用makefsdata perl程式將網頁檔編譯成_fsdata.c。makefsdata程式碼參閱路徑:pico-sdk/lib/lwip/src/apps/http/makefsdata。
  • Lwip mdns:
    將網站網址定義成picow_led_clock.local,方便存取網站。MEMP_NUM_SYS_TIMEOUT需要依據使用services的數量增加。
  • Lwip sntp:
    使用Lwip sntp取得NTP時間,定期更新微處理器系統時間。自訂的更新系統時間函式定義在



  • 系統組態設定值儲存在Flash最後3個4k位置:

成果展示:
    

程式碼:


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.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(picow_ws2812_clock C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

add_definitions(
        
        -DSNTP_SET_SYSTEM_TIME=sntp_set_system_time

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

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

pico_set_program_name(picow_ws2812_clock "picow_ws2812_clock")
pico_set_program_version(picow_ws2812_clock "0.1")

pico_enable_stdio_uart(picow_ws2812_clock 1)
pico_enable_stdio_usb(picow_ws2812_clock 0)

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

# Add the standard include files to the build
target_include_directories(picow_ws2812_clock 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_ws2812_clock 
        hardware_pio
        hardware_timer
        hardware_clocks
        pico_cyw43_arch_lwip_poll
        pico_lwip_http
        pico_lwip_mdns
        pico_lwip_sntp
        hardware_flash
        hardware_watchdog
        hardware_rtc
        )

add_subdirectory(ws2812)
target_link_libraries(picow_ws2812_clock 
      ws2812
)

pico_add_extra_outputs(picow_ws2812_clock)


  

lwipopts.h:
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)

// allow override in some examples
#ifndef NO_SYS
#define NO_SYS                      1
#endif
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET                 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC             1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC             0
#endif
#define MEM_ALIGNMENT               4
#define MEM_SIZE                    4000
#define MEMP_NUM_TCP_SEG            32
#define MEMP_NUM_ARP_QUEUE          10
#define PBUF_POOL_SIZE              24
#define LWIP_ARP                    1
#define LWIP_ETHERNET               1
#define LWIP_ICMP                   1
#define LWIP_RAW                    1
#define TCP_WND                     (8 * TCP_MSS)
#define TCP_MSS                     1460
#define TCP_SND_BUF                 (8 * TCP_MSS)
#define TCP_SND_QUEUELEN            ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK  1
#define LWIP_NETIF_LINK_CALLBACK    1
#define LWIP_NETIF_HOSTNAME         1
#define LWIP_NETCONN                0
#define MEM_STATS                   0
#define SYS_STATS                   0
#define MEMP_STATS                  0
#define LINK_STATS                  0
// #define ETH_PAD_SIZE                2
#define LWIP_CHKSUM_ALGORITHM       3
#define LWIP_DHCP                   1
#define LWIP_IPV4                   1
#define LWIP_TCP                    1
#define LWIP_UDP                    1
#define LWIP_DNS                    1
#define LWIP_TCP_KEEPALIVE          1
#define LWIP_NETIF_TX_SINGLE_PBUF   1
#define DHCP_DOES_ARP_CHECK         0
#define LWIP_DHCP_DOES_ACD_CHECK    0

#ifndef NDEBUG
#define LWIP_DEBUG                  1
#define LWIP_STATS                  1
#define LWIP_STATS_DISPLAY          1
#endif

#define ETHARP_DEBUG                LWIP_DBG_OFF
#define NETIF_DEBUG                 LWIP_DBG_OFF
#define PBUF_DEBUG                  LWIP_DBG_OFF
#define API_LIB_DEBUG               LWIP_DBG_OFF
#define API_MSG_DEBUG               LWIP_DBG_OFF
#define SOCKETS_DEBUG               LWIP_DBG_OFF
#define ICMP_DEBUG                  LWIP_DBG_OFF
#define INET_DEBUG                  LWIP_DBG_OFF
#define IP_DEBUG                    LWIP_DBG_OFF
#define IP_REASS_DEBUG              LWIP_DBG_OFF
#define RAW_DEBUG                   LWIP_DBG_OFF
#define MEM_DEBUG                   LWIP_DBG_OFF
#define MEMP_DEBUG                  LWIP_DBG_OFF
#define SYS_DEBUG                   LWIP_DBG_OFF
#define TCP_DEBUG                   LWIP_DBG_OFF
#define TCP_INPUT_DEBUG             LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG            LWIP_DBG_OFF
#define TCP_RTO_DEBUG               LWIP_DBG_OFF
#define TCP_CWND_DEBUG              LWIP_DBG_OFF
#define TCP_WND_DEBUG               LWIP_DBG_OFF
#define TCP_FR_DEBUG                LWIP_DBG_OFF
#define TCP_QLEN_DEBUG              LWIP_DBG_OFF
#define TCP_RST_DEBUG               LWIP_DBG_OFF
#define UDP_DEBUG                   LWIP_DBG_OFF
#define TCPIP_DEBUG                 LWIP_DBG_OFF
#define PPP_DEBUG                   LWIP_DBG_OFF
#define SLIP_DEBUG                  LWIP_DBG_OFF
#define DHCP_DEBUG                  LWIP_DBG_OFF


// httpd
#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"

// mDNS
#define LWIP_MDNS_RESPONDER     1
#define LWIP_IGMP               1
#define LWIP_AUTOIP             1
#define MEMP_NUM_UDP_PCB        (4+1)
#define LWIP_NUM_NETIF_CLIENT_DATA  1
#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 4)

// SNTP
#define SNTP_SUPPORT      1
#define SNTP_SERVER_DNS   1
//#define SNTP_UPDATE_DELAY 86400
#define  SNTP_STARTUP_DELAY 0

#endif /* __LWIPOPTS_H__ */

  

picow_ws2812_clock.c:
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/timer.h"
#include "hardware/clocks.h"
#include "pico/cyw43_arch.h"
#include "ws2812.h"
#include "hardware/rtc.h"
// Lwip application: httpd, mdns and sntp
#include "lwip/apps/httpd.h"
#include "lwip/apps/mdns.h"
#include "lwip/apps/sntp.h"

#include "ap_http_server.h"
#include "cJSON.h"

#include "picow_ws2812_clock.h"

// 7-segment digit
uint8_t digit[10][7] = {
    {1,1,1,1,1,1,0}, //0
    {1,0,0,0,0,1,0}, //1
    {0,1,1,0,1,1,1}, //2
    {1,1,0,0,1,1,1}, //3
    {1,0,0,1,0,1,1}, //4
    {1,1,0,1,1,0,1}, //5
    {1,1,1,1,1,0,1}, //6
    {1,0,0,0,1,1,0}, //7
    {1,1,1,1,1,1,1}, //8
    {1,0,0,1,1,1,1}, //9
};

PIO pio = pio0;
uint8_t sm_digit=0;
uint8_t sm_colon=1;

uint32_t status_colon[4] = {0x00ff00, 0x0000ff, 0xffffff,POWER_SAVE_COLOR}; 
struct clock_config_t  clock_config;

uint8_t pre_min=0;

bool connect_to_wifi_ssid() {  
    if (!clock_config.flash_data) return false;

    cyw43_arch_enable_sta_mode();
    printf("\n\n==========================\n"
        "Connecting to WiFi:%s\n"
            "==============================\n", clock_config.ssid);
    if (cyw43_arch_wifi_connect_timeout_ms(clock_config.ssid, clock_config.pass, CYW43_AUTH_WPA2_AES_PSK, 10000)) { 
        printf("wifi sta connect error ssid:%s\n", clock_config.ssid);
        //cyw43_arch_deinit();
        return false;
    }
    
    ip_addr_t addr = cyw43_state.netif->ip_addr;
    printf("connect successfully. get IP: %s\n", ipaddr_ntoa(&addr));
    return true;
}

void display_digit(uint8_t number, uint32_t color) {
   
    for (int i=0; i < 7; i++) {
        if (digit[number][i] == 1) {
            for (int k=0; k < 3;k++)
                ws2812_put_grb_pixel(pio, sm_digit,color);
        }
        else {
            for (int k=0; k < 3;k++)
                ws2812_put_grb_pixel(pio, sm_digit,0x000000);
        }
    }
}

void set_led_clock_color() {
    uint32_t color;
    sscanf(clock_config.led_color, "#%x", &color);
    
    clock_config.display_color = (color&0xff0000) >> 8 | (color&0x00ff00) << 8 | color&0x0000ff;
    printf("color:%x, %s\n", color,clock_config.led_color); 
}

void display_clock_digits(bool force) {
    datetime_t date_time;
    uint8_t h1, h0, min1, min0, h,min;
    if (rtc_get_datetime(&date_time)) {
        if (date_time.sec == 0 || pre_min != date_time.min || force) {
            pre_min = date_time.min;
        } else {
            return ;
        }
        min = date_time.min;
        h = date_time.hour;
        min0 = min%10;
        min1 = min/10;
        h0 = h %10;
        h1 = h/10;
        
        display_digit(min0, clock_config.display_color);
        display_digit(min1, clock_config.display_color);
        display_digit(h0, clock_config.display_color);
        display_digit(h1, clock_config.display_color);
    }

}

bool timer_callback(repeating_timer_t* rt) {
    display_clock_digits(false);
    return true;
}

bool colon_timer_callback(repeating_timer_t* rt) {
    static uint8_t s=0;
    if (!s) {
        for (int i=0; i <2; i++)
            ws2812_put_grb_pixel(pio, sm_colon, status_colon[clock_config.colon_state]);
    }
    else { 
        for (int i=0; i <2; i++)
            ws2812_put_grb_pixel(pio, sm_colon, 0x000000);
    }
    s =(s+1)%2;
    return true;
}

#if LWIP_MDNS_RESPONDER
static void srv_txt(struct mdns_service *service, void *txt_userdata)
{
  err_t res;
  LWIP_UNUSED_ARG(txt_userdata);
  
  res = mdns_resp_add_service_txtitem(service, "path=/", 6);
  LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}

static void mdns_example_report(struct netif* netif, u8_t result, s8_t service)
{
  LWIP_PLATFORM_DIAG(("mdns status[netif %d][service %d]: %d\n", netif->num, service, result));
}
#endif


//SNTP
void sntp_set_system_time(uint32_t sec, uint32_t us)
{
    char buf[32];
    struct tm current_time_val;

    time_t current_time = (sec+atoi(clock_config.timezone)*60*60);
    struct tm* p= gmtime(&current_time);

    datetime_t rtc_time;
    rtc_time.year = p->tm_year+1900;
    rtc_time.month = p->tm_mon+1;
    rtc_time.day = p->tm_mday;
    rtc_time.hour = p->tm_hour;
    rtc_time.min = p->tm_min;
    rtc_time.sec = p->tm_sec;
    rtc_time.dotw = p->tm_wday;

    rtc_set_datetime(&rtc_time);

}

int main()
{
    stdio_init_all();

    if (cyw43_arch_init()) {
        printf("failed to initialise\n");
        return 1;
    }

    // pio state machine 0 for ws2812 digit, 1 for colom
    ws2812_init(pio, sm_digit, 16); //GPIO pin 16
    ws2812_init(pio, sm_colon, 17); //GPIO pin 17

    // rtc initialize value
    rtc_init();
    datetime_t rtc_time;

    rtc_time.year=2024;
    rtc_time.month= 1;
    rtc_time.day = 1;
    rtc_time.hour = 1;
    rtc_time.min = 1;
    rtc_time.sec = 1;
    rtc_time.dotw=1;

    rtc_set_datetime(&rtc_time);
    clock_config.colon_state=SNTP_ERROR;

    // initialize clock configuration. 
    if (!read_config_from_flash()) {
        strcpy(clock_config.ssid,"");   //default values if can not read from flash.
        strcpy(clock_config.pass,"");;
        strcpy(clock_config.led_color, "#1f1f1f");
        strcpy(clock_config.ntp_server, "pool.ntp.org");
        strcpy(clock_config.ntp_interval,"3");
        strcpy(clock_config.timezone,"+0");
        clock_config.flash_data=0;
    } else {
        clock_config.flash_data=1;
    }

    set_led_clock_color();

    repeating_timer_t rt, rt1;
    add_repeating_timer_ms(-1000, timer_callback, NULL, &rt);  // for 7-segment timer
    add_repeating_timer_ms(-500, colon_timer_callback, NULL, &rt1);

    if (!connect_to_wifi_ssid()) {
        clock_config.wifi_connected=0;  // change to AP mode if not connect to WIFI lan 
        ap_http_server_start();
        clock_config.colon_state=WIFI_NOT_CONNECT;
    } else {
        clock_config.wifi_connected=1;
        http_server_start();
        clock_config.colon_state=WIFI_OK;
    }

    // mDNS: picow_led_clock.local
#if LWIP_MDNS_RESPONDER
    mdns_resp_register_name_result_cb(mdns_example_report);
    mdns_resp_init();
    if (mdns_resp_add_netif(netif_default, "picow_led_clock")== ERR_OK) {
        printf("mDNS add successfully\n");
    } else {
        printf("mDNS failure\n");
    }
    mdns_resp_add_service(netif_default, "myweb", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL);
    mdns_resp_announce(netif_default);
#endif

// enable SNTP
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, clock_config.ntp_server);
    sntp_init();
    if (!sntp_enabled()) {
        clock_config.colon_state=SNTP_ERROR;
        printf("sntp not enable\n");
    }
    
   clock_config.prev_colon_state = clock_config.colon_state;
    while (1) {
        cyw43_arch_poll();
        sleep_ms(1);
        
    }


    return 0;
}

  

picow_ws2812_clock.h:
#ifndef __PICOW_WS2812_CLOCK_H_
#define __PROJ__PICOW_WS2812_CLOCK_H_ECT_H_


#define POWER_SAVE_COLOR (0x050505)
/* colon color indicator
    red: wifi not connected to lan
    blue: sntp not enabled
    white : correct;
*/
enum {
    WIFI_NOT_CONNECT=0,
    SNTP_ERROR=1,
    WIFI_OK=2,
    POWER_SAVE=3,
};

struct clock_config_t {
  char ssid[50];
  char pass[50];
  char led_color[10]; //0x000000
  char ntp_server[100];
  char ntp_interval[3]; //3~12
  char timezone[4]; // -12~+14
  uint8_t wifi_connected;
  uint8_t flash_data; //init
  uint32_t display_color;
  uint8_t colon_state;
  uint8_t prev_colon_state;
};

#endif

  
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/sync.h"
#include "hardware/watchdog.h"

#include "hardware/rtc.h"
#include "lwip/apps/sntp.h"

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

#include "picow_ws2812_clock.h"

enum {
    LED_COLOR_PAGE=1,
    NTP_SERVER_PAGE,
    LOCAL_TIME_PAGE,
    WIFI_CONN_PAGE,
} POST_PAGE;


//struct HTTP_POST_SERVER_T 
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;
    uint8_t post_page;
 } HTTP_POST_SERVER_T;

HTTP_POST_SERVER_T *server=NULL;
// struct HTTP_POST_SERVER_T 

SCAN_APS_T *aps;

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

struct clock_config_t clock_config;

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;
}

bool read_config_from_flash() {
    char flash_buff[256*FLASH_STORAGE_PAGES];
    memset(flash_buff,0,256*FLASH_STORAGE_PAGES);
    snprintf(flash_buff, 256*FLASH_STORAGE_PAGES, "%s",(uint8_t*)(XIP_BASE+FLASH_OFFSET));
    if (!flash_buff) return false;

    printf("ff:%s\n", flash_buff);
    cJSON *config_flash = cJSON_CreateObject();
    config_flash = cJSON_Parse(flash_buff);

    if (config_flash) {
        strcpy(clock_config.ssid, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "ssid")));
        strcpy(clock_config.pass, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "pass")));
        strcpy(clock_config.led_color, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "ledcolor")));
        strcpy(clock_config.ntp_server, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "ntpserver")));
        strcpy(clock_config.ntp_interval, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "ntpinterval")));
        strcpy(clock_config.timezone, cJSON_GetStringValue(cJSON_GetObjectItem(config_flash, "timezone")));
    } else {
        return false;
    }
    cJSON_Delete(config_flash);
    
    printf("read configuration from flash oK\n");
    return true;
}

void save_to_flash(bool reboot) {
  char flash_buff[256*FLASH_STORAGE_PAGES];
  
  memset(flash_buff, 0, 256*FLASH_STORAGE_PAGES);
  sprintf(flash_buff, "{\"ssid\":\"%s\",\"pass\":\"%s\", \"ledcolor\":\"%s\", \"ntpserver\":\"%s\", \"ntpinterval\":\"%s\", \"timezone\":\"%s\"}", 
      clock_config.ssid, clock_config.pass, clock_config.led_color, clock_config.ntp_server, clock_config.ntp_interval, clock_config.timezone);
  
  uint32_t ints = save_and_disable_interrupts();
  flash_range_erase(FLASH_OFFSET, 4096); // one sector
  flash_range_program(FLASH_OFFSET, flash_buff, 256*FLASH_STORAGE_PAGES);   
  restore_interrupts(ints);

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

// set_led_clock_color() and display_clock_digits() define at picow_ws2812_clock.c
extern void set_led_clock_color();
extern void display_clock_digits(bool force);

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

}

const char *
cgi_handler_led_color(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
    printf("led_cgi\n");
    
    for (int i = 0; i < iNumParams; i++) {    
      if (strcmp(pcParam[i], "psave") == 0) {
          if (strcmp(pcValue[i], "on") == 0) {
      
            clock_config.display_color = POWER_SAVE_COLOR;
            clock_config.prev_colon_state=clock_config.colon_state;
            clock_config.colon_state=POWER_SAVE; //POWER_SAVE
          } else {
            set_led_clock_color();
            clock_config.colon_state=clock_config.prev_colon_state;      
          }
          display_clock_digits(true);
      }
    }
    return "/index.shtml";  //return to index.shtml

}

const char *
cgi_handler_set_local_time(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
    
      printf("local time\n");
    
    return "/index.shtml";  //return to index.shtml

}

const char *
cgi_handler_ntp_server(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
    
      printf("ntp_server\n");
      return "/index.shtml";  //return to 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
    },
    {
        "/led_color.cgi", cgi_handler_led_color
    },
    {
        "/set_local_time.cgi", cgi_handler_set_local_time
    },
    {
        "/ntp_server.cgi", cgi_handler_ntp_server
    },
};
/* ==== cgi end ======*/

/*===== post begin =====*/
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;

  server->post_page=0;
  if (!memcmp(uri, "/wifi_conn.shtml", 17)) {
    server->post_page = WIFI_CONN_PAGE;
  }
  if (!memcmp(uri, "/led_color.cgi", 15)) {
    server->post_page = LED_COLOR_PAGE;
  }
  if (!memcmp(uri, "/ntp_server.cgi", 16)) {
    server->post_page = NTP_SERVER_PAGE;
  }
  if (!memcmp(uri, "/set_local_time.cgi", 20)) {
    server->post_page = LOCAL_TIME_PAGE;
  }

  if (server->post_page) {
    if (server->current_connection != connection) {
      server->current_connection = connection;
      snprintf(response_uri, response_uri_len, "/index.shtml"); // default : return main page
      *post_auto_wnd = 1;
      return ERR_OK;
    }
    
  }
  return ERR_VAL;
}

bool get_post_data_param(struct pbuf *p, char* param_name, char* param_value) {
    u16_t token, value_offset, len;
    char param_name_eq[100];
    sprintf(param_name_eq, "%s=", param_name);
    token = pbuf_memfind(p, param_name_eq, strlen(param_name_eq), 0);
   
    if ((token != 0xFFFF)) {
      u16_t value_offset = token + strlen(param_name_eq);
      u16_t len = 0;
      u16_t tmp;
      
      /* find  len */
      tmp = pbuf_memfind(p, "&", 1, value_offset);
      if (tmp != 0xFFFF) {
        len = tmp - value_offset;
      } else {
        len = p->tot_len - value_offset;
      }
      
      if ((len > 0)) {
        char* tmpstr= (char*)pbuf_get_contiguous(p, &param_name_eq, sizeof(param_name_eq), len, value_offset);
        tmpstr[len]=0;
        strcpy(param_value, urldecode(tmpstr));
      } else {
        return false;
      }
    } else {
      return false;
    }
    return true;
}

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) {
      if (server->post_page ==  WIFI_CONN_PAGE){
        if (get_post_data_param(p, "ssid", server->ssid) && 
                    get_post_data_param(p, "pass", server->pass)) {
            strcpy(clock_config.ssid, server->ssid);
            strcpy(clock_config.pass, server->pass);
            server->post_recv=true;
        }
        
        }
        //// LED Color
        if (server->post_page ==  LED_COLOR_PAGE){
            if (get_post_data_param(p, "ledcolor", clock_config.led_color)) {
              server->post_recv=true;
            }
          
        }
        if (server->post_page ==  NTP_SERVER_PAGE) {
            if (get_post_data_param(p, "ntp_server", clock_config.ntp_server) &&
                    get_post_data_param(p, "ntp_interval", clock_config.ntp_interval) &&
                    get_post_data_param(p, "timezone_offset", clock_config.timezone) ) {
                server->post_recv=true;
            }     
        }
        if (server->post_page ==  LOCAL_TIME_PAGE){
          char tmpstr[21], buff[21];
          if (get_post_data_param(p, "date_time", tmpstr)) {
              int y,m,d,h,min;
              datetime_t dt;
              strcpy(buff, urldecode(tmpstr));
              sscanf(buff, "%d-%d-%dT%d:%d", &y, &m,&d,&h,&min);
              dt.year=y;
              dt.month=m;
              dt.day=d;
              dt.hour=h;
              dt.min=min;
              dt.dotw=1;
              if (!rtc_set_datetime(&dt)) printf("set local time error\n");
              server->post_recv=true;
          }
          
        }
        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) {
           switch (server->post_page) {
            case WIFI_CONN_PAGE:
              save_to_flash(true);
              snprintf(response_uri, response_uri_len, "/wifi_conn.shtml");
            break;
            case NTP_SERVER_PAGE:
                sntp_stop();
                sntp_init();
                display_clock_digits(true);
                save_to_flash(false);
                snprintf(response_uri, response_uri_len, "/index.shtml");
              break;
            case LED_COLOR_PAGE:
                set_led_clock_color();
            case LOCAL_TIME_PAGE:
              display_clock_digits(true);
              save_to_flash(false);
              snprintf(response_uri, response_uri_len, "/index.shtml");
            break;
           }
       
        } 
    //}
    server->current_connection = NULL;
    //server->valid_connection = NULL;
  }
}
/*===== post end =====*/

/*  === ssi begin =====*/
const char* __not_in_flash("httpd") ssi_tags[] = {
    "scanwifi", //0
    "ssid",     //1
    "ledcolor", //2 
    "wifistat", //3
    "ltime",    //4
    "ntpserv",  //5
    "ntpint",   //6
    "timezone", //7
};

/* 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";
    datetime_t dt;
    int rssi=1;
    switch (iIndex) { 
        case 0: // for scanwifi in select_wifi.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;
        case 2: // for led_color
            printed = snprintf(pcInsert, iInsertLen,clock_config.led_color);
            break;
        case 3: // for wifi_state
            if (clock_config.wifi_connected)
              printed = snprintf(pcInsert, iInsertLen,"img/lan_connected.png");
            else
              printed = snprintf(pcInsert, iInsertLen,"img/lan_disconnected.png");
            
            break;
          case 4: // localtime
              rtc_get_datetime(&dt);
              sprintf(buff, "%04d-%02d-%02dT%02d:%02d", dt.year, dt.month,dt.day, dt.hour, dt.min);
              printf("%s\n", buff);
              printed = snprintf(pcInsert, iInsertLen,buff);
            
            break;
          case 5: // ntp server
            printed = snprintf(pcInsert, iInsertLen,clock_config.ntp_server);
          break;
          case 6: //ntp interval
            printed = snprintf(pcInsert, iInsertLen,clock_config.ntp_interval);
          break;
          case 7: //timezone
            printed = snprintf(pcInsert, iInsertLen,clock_config.timezone);
          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;
    }

    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, 20000);
  
    //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();
}

void 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;
    }

    scan_aps(aps, 20000);
    //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();
 
}


  
ap_http_server.h:
#ifndef __HTTP_SERVER_H_
#define __HTTP_SERVER_H_

#define FLASH_OFFSET  0x1FD000    //last 3*4k
#define FLASH_STORAGE_PAGES 2

#define WIFI_PASS_BUFSIZE 91
void ap_http_server_start();
void http_server_start();
void ap_http_server_stop();
bool read_config_from_flash();
char* urldecode(char* str);

#endif
  






2024年5月14日 星期二

Build your own video surveillance system with ZoneMinder. Raspberry Pi 3B+ attached USB webcam, ESP32-CAM & ONVIF Compliant IP CAM.

 本文章紀錄在MSI Mini PC上建立一套surveillance system,軟體使用ZoneMinder,監視攝影機分別使用

  1. USB webcam 附加在Rapberrry Pi 3B+上,
  2. ESP-32-CAM
  3. D-Link DCS-8300LHV2 符合ONVIF規範。
架構如下圖所示:


一、Raspberry Pi 上USB webcam串流影片(MJPEG)設定:

  • 安裝v4l-utils, video for linux 工具。
    $ sudo apt install v4l-utils

  • 使用v4l2-ctl指令:
    $ v4l2-ctl --list-devices

    列出video device:

    $ v4l2-ctl -d /dev/video0 --list-formats-ext

     列出usb webcam支援的格式。


  • 安裝ustreamer工具。
    $ sudo apt install ustreamer

  • 串流webcam影音:
    ustreamer -d /dev/video0 --format MJPEG -s raspi-usbcam.local -p 8080 --encoder HW -r 1920x1080 --us
    er admin --passwd usbcampass

二、安裝ESP32-CAM:

  • 選用AI-Thinker ESP32-CAM:



  • 使用CameraWebServer範例程式:


  • 設定解析度,翻轉與鏡射與固定IP等:


// set fixed camera resolution, flip and mirror
  s->set_framesize(s, FRAMESIZE_VGA);
  s->set_vflip(s,1);
  s->set_hmirror(s,1);
//set static IP 
// Set your Static IP address
  IPAddress local_IP(192, 168, 1, 79);
  // Set your Gateway IP address
  IPAddress gateway(192, 168, 1, 1);
  IPAddress subnet(255, 255, 255, 0);
  IPAddress primaryDNS(192, 168, 1, 1); //optional
  IPAddress secondaryDNS(8, 8, 8, 8); //optional
  if(!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
  

三、安裝ZoneMinder:
安裝步驟在ZoneMinder中有詳細說明:




其他詳細步驟請參閱成果影片。

成果影片






2024年2月15日 星期四

TinyML(Edge Impulse) : Voice remote control car || RP2040 || Cortex-M0+

 本篇文章介紹 使用Edge Impulse 語音機器學習,在Raspberry Pi Pico(RP2040) MCU上執行 Voice recognition,用來控制一輛遙控小車。

使用元件:

一. 控制端:

  1. Raspberry Pi Pico 
  2. INMP441 I2S microphone
  3. SD card module
  4. nRF24L01


二、受控端:
  1. Raspberry Pi Pico
  2. TT馬達+65mm 車輪 X 4
  3. L298N 直流馬達驅動板
  4. nRF24L01


本專案使用7個語音來控制小車的動作:
  1. 向前進(forward)
  2. 往後退(backward)
  3. 停在原地(stop)
  4. 方向左轉(turn left)
  5. 方向右轉(turn right)
  6. 速度加快(speed up)
  7. 速度減慢(slow down)
Edge Impulse 本專案使用參數:
  • Create Impulse
  • MFCC

  • Classifier

  • Performance calibration

機器學習訓練過程請參閱成果影片的動態說明。


成果影片:





程式碼:

 控制端:


有關nRF24L01, pico_audio_recorder, storage_driver等library使用起參閱前一篇文章:MCU(RP2040) voice recognition/voice commands using TinyML(Edge Impulse)

  • pico_tinyML_voice.cpp


#include <stdlib.h>
#include <stdio.h>
#include <sttring.h>
#include "pico/stdlib.h"
#include "bsp/board.h"
#include "tusb.h"

#include "pico_storage.h"
#include "hardware/rtc.h"
#include "pico_audio_recorder.h"
#include "hardware/dma.h"
#include "nRF24L01.h"

#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
#include "edge-impulse-sdk/classifier/ei_classifier_smooth.h"
extern bool new_read_data;
extern int16_t *fw_ptr;

#if DATA_ACQUISITION
#else
int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) {
    numpy::int16_to_float(fw_ptr+offset, out_ptr, length);
    return 0;
}
#endif

//=== nRF24L01 irq
char const *addr[5] = {"0node", "1node", "2node","3node","4node"};
#define TX_PIN 17

void irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint32_t ack_payload=0;
    uint8_t ack_buff[15];
      switch(event_type) {
        case EVENT_RX_DR:
            data[width]='\0';
            printf("RECV== pipe:%d, width:%d, %s\n", datapipe, width, data);
        break;
        case EVENT_TX_DS:
            gpio_put(TX_PIN, !gpio_get(TX_PIN));
            //printf("event_data_sent:%d\n", datapipe);
        break;
        case EVENT_MAX_RT:
        printf("event_max_rt:%d, \n", datapipe);
        break;
    }

}

/*------------- MAIN -------------*/
int main(void)
{
  stdio_init_all();
#if DATA_ACQUISITION 
      rtc_init();
      datetime_t t = {
        .year=2024, .month=02, .day=12, 
        .dotw=6,
        .hour=10, .min=32, .sec=50
      };
     
      if (!rtc_set_datetime(&t)) printf("set rtc error\n");
        
      storage_driver_init();
      
      board_init();
      // init device stack on configured roothub port
      tud_init(BOARD_TUD_RHPORT);

#else 
    gpio_init(TX_PIN);
    gpio_set_dir(TX_PIN,true);
    // == nRF24L01 init
    nRF24_spi_default_init(8, 9, irq_callback);
    nRF24_config_mode(TRANSMITTER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_TX_addr((uint8_t*)addr[0], 5);
    nRF24_set_RX_addr(0, (uint8_t*)addr[0], 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);
    //== edge impulse
      run_classifier_init();
    
      EI_IMPULSE_ERROR res;
      ei_impulse_result_t result = {nullptr};
      signal_t features_signal;
      features_signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
      features_signal.get_data = &raw_feature_get_data;

      ei_classifier_smooth_t smooth;
      ei_classifier_smooth_init(&smooth, 4, 3, 0.9, 0.3);
#endif

  inmp441_pio_init(INMP441_pio, INMP441_SM, INMP441_SD, INMP441_SCK, INMP441_SAMPLE_RATE);

  uint8_t f_idx=0;

  while (1)
  {
#if DATA_ACQUISITION
      if (get_recorder_state() == STATE_START_RECORDING) {
        inmp441_starting_recording_to_file_wav();
      }
         
      tud_task(); // tinyusb device task
#else

    if (new_read_data) {
          new_read_data = false;
//absolute_time_t t1 = get_absolute_time();
          res = run_classifier_continuous(&features_signal, &result, false,true );

          if (res != EI_IMPULSE_OK) {
                  ei_printf("ERR: Failed to run classifier (%d)\n", res);
          }
         
        //display_results(&result); printf("\n"); // only for debug
          ei_classifier_smooth_update(&smooth, &result);
          for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
            //result.classification[i].value = run_moving_average_filter(&classifier_maf[ix], result.classification[i].value);
            if (result.classification[i].value > 0.9f) 
            { 
              // for debug
              //ei_printf("  %s: ", ei_classifier_inferencing_categories[i]);
              //ei_printf("%.5f\r\n", result.classification[i].value);
              // for running
              nRF24_write_payload((uint8_t*)ei_classifier_inferencing_categories[i], strlen(ei_classifier_inferencing_categories[i]));
              if (strcmp(ei_classifier_inferencing_categories[i], "noise")!=0)
                run_classifier_init();
              break;
            } 
          }

        //absolute_time_t t2 = get_absolute_time();
        //printf("time:%lld\n", absolute_time_diff_us(t1,t2));
        //printf("\n");
   
    }
   
#endif
      tight_loop_contents();
  }

  return 0;
}

受控端(小車)


  • pico_tinyML_voice_RC_CAR.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "nRF24L01.h"
#include "string.h"
#include "hardware/pwm.h"
#include "ws2812.h"

#define CAR_PIN1A   16
#define CAR_PIN1B   17
#define CAR_PIN2A   18
#define CAR_PIN2B   19
#define SPEED_MAX   5
uint slice1a, slice1b, slice2a, slice2b;
uint chan_1a,chan_1b,chan_2a,chan_2b;

uint8_t addr[5][5] = {"0node", "1node", "2node","3node","4node"};

enum {
    NOISE=0,
    FORWARD,
    BACKWARD,
    STOP,
    TURN_LEFT,
    TURN_RIGHT,
    SPEED_UP,
    SLOW_DOWN,
    ACTION_MAX,
};

int8_t speed=0;
uint16_t pwm_top_count[SPEED_MAX+1] = {0, 4000,8000,12000,16000,20000};
uint32_t ws2812_color[SPEED_MAX+1] = {0x0, 0xFF0000, 0xFF0000, 0x8FFF00, 0x8FFF00, 0xFF00};
uint8_t voice_state = 0;
uint8_t current_direction=STOP;

#define NUM_PIXELS 5
void play_ws2812() {
    for (int j=0; j < speed; j++) { 
        put_grb_pixel(ws2812_color[j+1]);
    }
    for (int j=speed; j < SPEED_MAX; j++) { 
        put_grb_pixel(ws2812_color[0]);
    }
    //sleep_ms(100);
    //printf("speed:%d\n", speed);
}
//nRF24L01 irq callback
void irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint32_t ack_payload=0;
    uint8_t ack_buff[15];
      switch(event_type) {
        case EVENT_RX_DR:
            data[width]='\0';
            
            if (strcmp(data, "noise")==0) voice_state=NOISE;
            if (strcmp(data, "forward")==0) voice_state=FORWARD;
            if (strcmp(data, "backward")==0) voice_state=BACKWARD;
            if (strcmp(data, "stop")==0) voice_state=STOP;
            if (strcmp(data, "turn_left")==0) voice_state=TURN_LEFT;
            if (strcmp(data, "turn_right")==0) voice_state=TURN_RIGHT;
            if (strcmp(data, "speed_up")==0) voice_state=SPEED_UP;
            if (strcmp(data, "slow_down")==0) voice_state=SLOW_DOWN;

            printf("RECV== stat:%d, width:%d, %s\n", voice_state, width, data);
        break;
        case EVENT_TX_DS:
            printf("event_data_sent:%d\n", datapipe);
        break;
        case EVENT_MAX_RT:
            printf("EVENT_MAX_RT:%d\n", datapipe);
        break;
    }
}


void voice_action(uint8_t stat) {
    switch(stat) {
        case FORWARD:
            pwm_set_chan_level(slice1a, chan_1a, pwm_top_count[speed]);
            pwm_set_chan_level(slice2a, chan_2a, pwm_top_count[speed]);
            pwm_set_chan_level(slice1b, chan_1b, 0);
            pwm_set_chan_level(slice2b, chan_2b, 0);
            current_direction = FORWARD;
        break;
        case BACKWARD:
            pwm_set_chan_level(slice1a, chan_1a, 0);
            pwm_set_chan_level(slice2a, chan_2a, 0);
            pwm_set_chan_level(slice1b, chan_1b, pwm_top_count[speed]);
            pwm_set_chan_level(slice2b, chan_2b, pwm_top_count[speed]);
            current_direction = BACKWARD;
        break;
        case STOP:
            pwm_set_chan_level(slice1a, chan_1a, 0);
            pwm_set_chan_level(slice2a, chan_2a, 0);
            pwm_set_chan_level(slice1b, chan_1b, 0);
            pwm_set_chan_level(slice2b, chan_2b, 0);
            current_direction=STOP;
        break;
        case TURN_LEFT:
            pwm_set_chan_level(slice1a, chan_1a, 0);         
            pwm_set_chan_level(slice2a, chan_2a, 0);
            pwm_set_chan_level(slice1b, chan_1b, 0);
            pwm_set_chan_level(slice2b, chan_2b, 0);
            pwm_set_chan_level(slice1a, chan_1a, pwm_top_count[speed]);
            sleep_ms(500);
        break;
        case TURN_RIGHT:
            pwm_set_chan_level(slice1a, chan_1a, 0);
            pwm_set_chan_level(slice2a, chan_2a, 0);
            pwm_set_chan_level(slice1b, chan_1b, 0);
            pwm_set_chan_level(slice2b, chan_2b, 0);
            pwm_set_chan_level(slice2a, chan_2a, pwm_top_count[speed]);
            sleep_ms(500);
        break;
        case SPEED_UP:
           speed++;
           if (speed > SPEED_MAX) speed=SPEED_MAX;   
           play_ws2812();      
        break;
        case SLOW_DOWN:
           speed--;
           if (speed < 0) speed=0; 
           play_ws2812();
        break;
        default:
        break;
    }
}

int main()
{
    
    stdio_init_all(); 

    ws2812_init(pio0, 0, 20);

    gpio_set_function(CAR_PIN1A, GPIO_FUNC_PWM);
    gpio_set_function(CAR_PIN1B, GPIO_FUNC_PWM);
    gpio_set_function(CAR_PIN2A, GPIO_FUNC_PWM);
    gpio_set_function(CAR_PIN2B, GPIO_FUNC_PWM);

    slice1a = pwm_gpio_to_slice_num(CAR_PIN1A);
    slice1b = pwm_gpio_to_slice_num(CAR_PIN1B);
    slice2a = pwm_gpio_to_slice_num(CAR_PIN2A);
    slice2b = pwm_gpio_to_slice_num(CAR_PIN2B);

    chan_1a = pwm_gpio_to_channel(CAR_PIN1A);
    chan_1b = pwm_gpio_to_channel(CAR_PIN1B);
    chan_2a = pwm_gpio_to_channel(CAR_PIN2A);
    chan_2b = pwm_gpio_to_channel(CAR_PIN2B);

    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(slice1a, &c, true);
    pwm_init(slice1b, &c, true);
    pwm_init(slice2a, &c, true);
    pwm_init(slice2b, &c, true);
    
    uint32_t count=0;
    uint8_t status;

    nRF24_spi_default_init(8, 9, irq_callback);
    nRF24_config_mode(RECEIVER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_RX_addr(0, addr[0], 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);

   
    while(1) {
        if (voice_state > 0 && voice_state < ACTION_MAX) {
            voice_action(voice_state);
            voice_action(current_direction);
            voice_state = NOISE;
        }
       
        tight_loop_contents();
    }
    
    return 0;
}

  • CMakeLists.txt


# 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 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.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(pico_tinyML_voice_RC_CAR 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(pico_tinyML_voice_RC_CAR pico_tinyML_voice_RC_CAR.c )

pico_set_program_name(pico_tinyML_voice_RC_CAR "pico_guesture_ws2812")
pico_set_program_version(pico_tinyML_voice_RC_CAR "0.1")

pico_enable_stdio_uart(pico_tinyML_voice_RC_CAR 0)
pico_enable_stdio_usb(pico_tinyML_voice_RC_CAR 1)

# Add the standard library to the build
target_link_libraries(pico_tinyML_voice_RC_CAR
        pico_stdlib
        hardware_pwm)

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

# Add any user requested libraries
add_subdirectory(nRF24L01)
target_link_libraries(pico_tinyML_voice_RC_CAR 
        nRF24L01_drv
)

add_subdirectory(ws2812)
target_link_libraries(pico_tinyML_voice_RC_CAR 
      ws2812
)

pico_add_extra_outputs(pico_tinyML_voice_RC_CAR)


ws2812:

  • ws2812.c


#include "ws2812.h"
#include "stdlib.h"
#include "string.h"

#define NUM_PIXELS 12


#define green 0x1f0000
#define red 0x001f00
#define blue 0x00001f

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);
}

void put_grb_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 clear_pixel() {
    for (int i = 0; i < NUM_PIXELS; i++) put_grb_pixel(0x000000);
}

void ws2812_init(PIO pio, uint sm, uint ws2812_pin) {

    ws2812_program_init(pio, sm,  ws2812_pin, 800000);
}



  • ws2812.h



#ifndef _WS2812_H_
#define _WS2812_H_
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "ws2812.pio.h"
#include "hardware/clocks.h"



void ws2812_init(PIO pio, uint sm, uint ws2812_pin);
void put_grb_pixel(uint32_t pixel_grb);

#endif 

  • ws2812.pio


;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;

.program ws2812
.side_set 1

.define public T1 2
.define public T2 2


.lang_opt python sideset_init = pico.PIO.OUT_HIGH
.lang_opt python out_init     = pico.PIO.OUT_HIGH
.lang_opt python out_shiftdir = 1

.wrap_target
bitloop:
    out x, 1       side 0 [T1 - 1] ; Side-set still takes place when instruction stalls
    jmp !x do_zero side 1 [T2 - 1] ; Branch on the bit we shifted out. Positive pulse
do_one:
    jmp  bitloop   side 1 [T2 - 1] ; Continue driving high, for a long pulse
do_zero:
    nop            side 0 [T2 - 1] ; Or drive low, for a short pulse
.wrap

% c-sdk {

%}

  • CMakeLists.txt(ws2812)


add_library(ws2812 INTERFACE)
pico_generate_pio_header(ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
target_sources(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/ws2812.c
)

target_include_directories(ws2812 INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(ws2812 INTERFACE
        hardware_pio
)