prettyprint

2022年12月26日 星期一

[Raspberry Pi Pico (c-sdk)] PIO I2S audio player

 本實驗整合

  1. [Raspberry Pi Pico (c-sdk)] Display: Ep 2 : PIO TFT LCD ILI9341 8-bit parallel C-code Driver
  2. [Raspberry Pi Pico (c-sdk)] Storage: Ep 3. Using SD/MMC Devices and Flash Devices Together
  3. Raspberry Pi Pico PIO(Programmable IO) Episode 4: I2S audio (Play WAVE File In SDCard from MAX98357A DAC)
再加入XPT2046 touch screen模組,製作一台聲音播放器。
各模組接到Raspberry Pi Pico接線腳位如下圖所示:


XPT2046 touch screen controller:

使用SPI界面,連接到SPI0。XPT2046 T_PEN腳位,平時為HIGH,按下時轉為LOW。使用GPIO EDGE_FALL 產生Interrupt。讀取觸控位置坐標,以便執行相關動作。

讀取X與Y座標的指令碼,分別為0xD0與0x90。透過spi送出指令後接著讀取讀取2 Bytes座標值。
連續取樣16次後平均後,再對應到實際TFT螢幕座標。

詳細程式碼如文末所附。

成果影片:


程式碼:



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

#define XPT2046_PEN_GPIO    26
#define XPT2046_MOSI        19
#define XPT2046_MISO        16
#define XPT2046_CS          17
#define XPT2046_CLK         18
#define XPT2046_SPI         spi0

#define XPT2046_MIN_RAW_X 2000
#define XPT2046_MAX_RAW_X 30000
#define XPT2046_MIN_RAW_Y 1500
#define XPT2046_MAX_RAW_Y 29000

/* user define actions begin*/
enum {
    ACTION_FILE=1,
    ACTION_PLAY_PAUSE,
    ACTION_STOP,
    ACTION_IDLE
};

extern uint8_t user_action;
extern uint8_t selected_filename[60];
void main_menu();
/* user define actions end*/

void xpt2046_init();
bool xpt2046_getXY(uint16_t *x, uint16_t *y);
#endif

xpt2046.c
 #include "xpt2046.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "pico/stdlib.h"
#include "stdio.h"
#include "ili9341.h"
#include "ff.h"
#include "spi_sdmmc.h"
#include "fonts/font_fixedsys_mono_24.h"
#include "i2s_pio_dma.h"
#include "string.h"
#include "fonts/play.h"
#include "fonts/stop.h"
#include "fonts/pause.h"
#include "fonts/resume.h"

uint32_t xpt2046_event=0;
uint8_t READ_X = 0xD0;
uint8_t READ_Y = 0x90;

static uint8_t top_index=0;
static uint16_t total_files=0;

/* user define action function*/
uint8_t user_action;            //export
uint8_t selected_filename[60];  // export

static int selected_item=-1;
static uint16_t sx=5, sy = 4;
static uint16_t item_height=30;

static uint8_t audio_files[30][30];
/* action region */
static uint16_t text_rgn_x0=2,text_rgn_x1=216,text_rgn_y0=2,text_rgn_y1=273;
static uint16_t menu_rgn_x0=2,menu_rgn_x1=238,menu_rgn_y0=275,menu_rgn_y1=318;
static uint16_t play_rgn_x0=8,play_rgn_x1=48,play_rgn_y0=276,play_rgn_y1=318;
static uint16_t stop_rgn_x0=100,stop_rgn_x1=140,stop_rgn_y0=276,stop_rgn_y1=318;
static uint16_t su_rgn_x0=220,su_rgn_x1=238,su_rgn_y0=5,su_rgn_y1=25;
static uint16_t sd_rgn_x0=220,sd_rgn_x1=238,sd_rgn_y0=262,sd_rgn_y1=272;

void draw_menu_item() {
    ili9341_fill_rect(2,2,215, 272,0x0000);
    for (int i = 0; i < 9; i++) {
        if (i+top_index < total_files)
            ili9341_draw_string(sx, sy+i*item_height,audio_files[i+top_index], 0xffff, &font_fixedsys_mono_24);
    }
}
void main_menu() {
    FRESULT fr;
    FIL file;
    FATFS fs;
    DIR dir;
    FILINFO finfo;
    uint8_t findex=0;
    

    fr = f_opendir(&dir, SDMMC_PATH"/");
    if (fr != FR_OK) {
        printf("open dir error\n");
        return;
    }
    uint8_t bc;
    fr = f_readdir(&dir, &finfo);
    total_files = 0;
    top_index = 0;
    while (fr == FR_OK && strlen(finfo.fname) > 0) { 
        
        if ((uint8_t)(finfo.fname[0]) != '.') {
            strcpy(audio_files[findex++], finfo.fname);
            total_files++;
       }   
        
        fr = f_readdir(&dir, &finfo);
    }

    ili9341_fill_rect(0,0, TFT_WIDTH, TFT_HEIGHT, 0x0000);
    ili9341_draw_rect(1,1,238, 318,0xffff);
    ili9341_draw_line(217,1,217,274,0xffff);
    ili9341_draw_line(1,274,238,274,0xffff);
    
    ili9341_draw_bitmap(10,277, &play);
    ili9341_draw_bitmap(102, 277, &stop);

    ili9341_fill_rect(222, 5, 10,10, 0xffff);
    ili9341_fill_rect(222, 262, 10,10, 0xffff);
    /* files */
    draw_menu_item();
      
}
/* user define action function*/
void touch_action() {
    uint16_t x, y;
    int temp_select;
    if (xpt2046_event & GPIO_IRQ_EDGE_FALL) {
            if (xpt2046_getXY(&x,&y)) { 
                if (x >= text_rgn_x0 && x <= text_rgn_x1 && y >= text_rgn_y0 && y <= text_rgn_y1) { 
                    temp_select = (int)((y-4)/item_height);
                    ili9341_fill_rect(2,sy+(temp_select)*item_height,215, 30, 0x001f);
                    ili9341_draw_string(sx, sy+(temp_select)*item_height,audio_files[temp_select+top_index], 0xffff, &font_fixedsys_mono_24);
                    ili9341_fill_rect(2,sy+(selected_item)*item_height,215, 30, 0x0000);
                    ili9341_draw_string(sx, sy+(selected_item)*item_height,audio_files[selected_item+top_index], 0xffff, &font_fixedsys_mono_24);
                    selected_item=temp_select;
                    user_action = ACTION_FILE;
                    strcpy(selected_filename, audio_files[selected_item+top_index]);
                }
                if (x >= play_rgn_x0 && x <= play_rgn_x1 && y >= play_rgn_y0 && y <= play_rgn_y1) {
                    user_action = ACTION_PLAY_PAUSE;
                    switch(i2s_play_state) {
                        case I2S_PLAYING:
                            i2s_play_state = I2S_PAUSE;
                            ili9341_draw_bitmap(10,277, &resume);
                        break;
                        case I2S_PAUSED:
                            i2s_play_state = I2S_RESUME;
                            ili9341_draw_bitmap(10,277, &pause);
                        break;
                        case I2S_STOP:
                            i2s_play_state = I2S_PLAY;
                        break;
                    }
                }
                if (x >= stop_rgn_x0 && x <= stop_rgn_x1 && y >= stop_rgn_y0 && y <= stop_rgn_y1) {
                    user_action = ACTION_STOP;
                    i2s_play_state=I2S_STOP;
                }
                if (x >= su_rgn_x0 && x <= su_rgn_x1 && y >= su_rgn_y0 && y <= su_rgn_y1) {
                    if (top_index > 0 && total_files > 9) {
                        top_index--;
                        draw_menu_item();
                    }
                }
                if (x >= sd_rgn_x0 && x <= sd_rgn_x1 && y >= sd_rgn_y0 && y <= sd_rgn_y1) { 
                    if (top_index+9 < total_files) {
                        top_index++;
                        draw_menu_item();
                    }
                }
            }
            xpt2046_event &= (!GPIO_IRQ_EDGE_FALL);
            gpio_set_irq_enabled(XPT2046_PEN_GPIO, GPIO_IRQ_EDGE_FALL, true);

        }
        if (xpt2046_event & GPIO_IRQ_EDGE_FALL) {
            xpt2046_event &= (!GPIO_IRQ_EDGE_RISE);
            gpio_set_irq_enabled(XPT2046_PEN_GPIO, GPIO_IRQ_EDGE_RISE, true);
        }

}

void touch_irq_handle(uint gpio, uint32_t event) {
    static uint32_t cnt=0;
    gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL&event, false);
    gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_RISE&event, false);
    xpt2046_event |= event;
    if(event & GPIO_IRQ_EDGE_FALL && gpio == XPT2046_PEN_GPIO) { 
        touch_action();
    }
    if(event & GPIO_IRQ_EDGE_RISE  && gpio == XPT2046_PEN_GPIO) {

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

     gpio_put(XPT2046_CS, false);

    for(uint8_t i = 0; i < SAMPLES; i++, nsamples++)
    {
        if(gpio_get(XPT2046_PEN_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;
    }

    gpio_put(XPT2046_CS, true);

    if(nsamples < SAMPLES)
        return false;

    raw_x = (avg_x / SAMPLES);
    raw_y = (avg_y / SAMPLES);

    if(raw_x < XPT2046_MIN_RAW_X) raw_x = XPT2046_MIN_RAW_X;
    if(raw_x > XPT2046_MAX_RAW_X) raw_x = XPT2046_MAX_RAW_X;

    if(raw_y < XPT2046_MIN_RAW_Y) raw_y = XPT2046_MIN_RAW_Y;
    if(raw_y > XPT2046_MAX_RAW_Y) raw_y = XPT2046_MAX_RAW_Y;

    *x = (raw_x - XPT2046_MIN_RAW_X) * TFT_WIDTH  / (XPT2046_MAX_RAW_X - XPT2046_MIN_RAW_X);
    *y = (raw_y - XPT2046_MIN_RAW_Y) * TFT_HEIGHT / (XPT2046_MAX_RAW_Y - XPT2046_MIN_RAW_Y);
    return true;
    
}

void xpt2046_init() {
    gpio_init(XPT2046_PEN_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_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);

   gpio_set_irq_enabled_with_callback(XPT2046_PEN_GPIO, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, touch_irq_handle);
   

}





2022年12月19日 星期一

[Raspberry Pi Pico (c-sdk)] Display: Ep 3: ILI9341 8-bit Parallel -- PIO Pixel Read/Write and Animation

 本文章延續前一篇[Raspberry Pi Pico (c-sdk)] Display: Ep 2 : PIO TFT LCD ILI9341 8-bit parallel C-code Driver只有由MCU輸出到TFT,加入從TFT Graphic RAM讀取pixel資料,以便達成簡易動畫功能,例如popup message與物件軌跡運動等。

在ili9341_cmd加入新的參數dir以決定out pins或in pins的方向。


在Raspberry Pi Pico PIO程式加入 out pindirs, 8指令改變DB0~DB7 pin OUT/IN。
Memory Write的Command為2Ch,parameter為pixel color的high byte與low byte。
Memory Read的Command code 為2Eh,要讀取4個bytes,第一個byte為dummy byte,第二至第四byte分別R,G,B如下公式轉成uint16_t RGB565格式: 
(((uint16_t)buff[1])>>3) << 11 | (((uint16_t)buff[2])>>2) << 5 | (((uint16_t)buff[3])>>3)

詳細程式碼列於文末。

成果影片展示:

程式碼:

  • ili9341.pio
.program ili9341_pio_cmd
; CSX, D/CX, WRX, RDX --> gpio 15, 14, 13, 12  (set pins)
.wrap_target
start:
set y,0       ; set pins out
mov osr, !y
out pindirs 8
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
; input or output
pull                    ; dir
mov y, osr
out pindirs, 8        ; if 0x00 set to input
jmp x--, param_data         
param_data:
set pins, 0b1111
jmp !y read
; write command parameters
write:
pull                    ; write data
set pins, 0b0101 
out pins, 8      ;[1] 
set pins, 0b0111 ;[1]
jmp x--, write
jmp finish
; === 
read:
set pins, 0b0110 [3]
in pins, 8 
set pins, 0b0111
push 
jmp x--, read
finish:
set pins, 0b1111
.wrap
  • 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"


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

void ili9341_cmd(uint8_t cmd, uint32_t count, uint8_t dir, uint8_t *param) {/*dir:write:0xFF, read:0x00*/
    if (dir != 0xFF && dir != 0x00) return;
    //pio_sm_restart(ili9341_pio,ili9341_sm);
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, cmd); // command code
    pio_sm_put_blocking(ili9341_pio, ili9341_sm, count); // how many parameters
    if (count != 0) {
        pio_sm_put_blocking(ili9341_pio, ili9341_sm, dir); // write out or read in
        if (dir == 0xFF) {
            for (int i = 0; i < count; i++) {
                pio_sm_put_blocking(ili9341_pio, ili9341_sm, param[i]); // write out parameters
            }
        } else {
            for (int i = 0; i < count; i++) {
                param[i] = (uint8_t)(pio_sm_get_blocking(ili9341_pio, ili9341_sm)); // read in data
            }
        }
    }
}

/* ================  */
void pio_irq_read_data() {
     if (pio_interrupt_get(ili9341_pio, ili9341_sm)) {
        pio_interrupt_clear(ili9341_pio, ili9341_sm);
     }
}

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

    //uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    //pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    //irq_add_shared_handler(pio_irq, pio_irq_read_data, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    //irq_set_enabled(pio_irq, true);

    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,  0xFF, 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,  0xFF, addr );

    ili9341_cmd(ILI9341_MEMORYWRITE, 0, 0xFF, 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,  0xFF, 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,  0xFF, 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, 0xFF, (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}
/* read a RGB565 pixel from tft device
read 4 bytes:
0: dummy
1: Red byte
2: Green byte
3: Blue byte
*/
uint16_t ili9341_read_pixel(uint16_t x, uint16_t y)
{
    if ( x < 0 || x > TFT_WIDTH-1 || y < 0 || y > TFT_HEIGHT-1) return 0;
	uint8_t buff[4];
 
    ili9341_set_address_window(x, y, x,y);
    ili9341_cmd(ILI9341_MEMORYREAD, 4, 0x00, buff);
    return ((((uint16_t)buff[1])>>3) << 11 | (((uint16_t)buff[2])>>2) << 5 | (((uint16_t)buff[3])>>3));
}
void ili9341_invert_display(bool invert) {
  if (invert) 
    ili9341_cmd(ILI9341_INVERTON, 0, 0xFF, NULL);
  else 
    ili9341_cmd(ILI9341_INVERTOFF, 0, 0xFF, NULL);
}
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, 0xFF, (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)(color&0xff)});
}

void ili9341_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) {
	/* algorithm from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
    int dx = abs(x1 - x0);
    int sx = x0 < x1 ? 1 : -1;
    int dy = -abs(y1 - y0);
    int sy = y0 < y1 ? 1 : -1;
    int error = dx + dy;
    int e2;
    while(1) {
        //plot(x0, y0)
        ili9341_draw_pixel(x0, y0, color);
        if ( x0 == x1 && y0 == y1 ) break;
        e2 = 2 * error;
        if (e2 >= dy) {  
            if (x0 == x1) break;
            error = error + dy;
            x0 = x0 + sx;
        }
        if (e2 <= dx) { 
            if (y0 == y1) break;
            error = error + dx;
            y0 = y0 + sy;
        }
    }
}

void ili9341_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
	int x;
	int y;
	int error;
	int old_error;

	x=0;
	y=-r;
	error=2-2*r;
	do{
		ili9341_draw_pixel(x0-x,  y0+y, color);
		ili9341_draw_pixel(x0-y,  y0-x, color);
		ili9341_draw_pixel(x0+x,  y0-y, color);
		ili9341_draw_pixel(x0+y,  y0+x, color);
		if ((old_error=error)<=x)	error+=++x*2+1;
		if (old_error>y || error>x) error+=++y*2+1;	 
	} while(y<0);
}

// Draw circle of filling
// x0:Central X coordinate
// y0:Central Y coordinate
// r:radius
// color:color
void ili9341_draw_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
	int x;
	int y;
	int error;
	int old_error;
	int ChangeX;

	x=0;
	y=-r;
	error=2-2*r;
	ChangeX=1;
	do{
		if(ChangeX) {
			ili9341_draw_line(x0-x, y0-y,x0-x, y0+y, color);
			ili9341_draw_line(x0+x, y0-y, x0+x, y0+y, color);
		} // endif
		ChangeX=(old_error=error)<=x;
		if (ChangeX)			error+=++x*2+1;
		if (old_error>y || error>x) error+=++y*2+1;
	} while(y<=0);
} 
void ili9341_draw_string(uint16_t x, uint16_t y, uint8_t* str, uint16_t color, const tFont *font) {
	ili_draw_string(x,y, str, color, font);
}
void ili9341_draw_string_withbg(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font) {
	ili_draw_string_withbg(x, y, str, fore_color, back_color, font);
}
void ili9341_draw_char(uint16_t x, uint16_t y, uint8_t character, uint16_t color, const tFont *font) {
    ili_draw_char(x, y, character, color, 0, font, 0);
}

void ili9341_popMessage(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *message, uint8_t seconds, const tFont *font) {
    if (x < 0 || y < 0 || x+width >= TFT_WIDTH || y+height >= TFT_HEIGHT) return;
    uint8_t tmpbuf[width*height*2];
    uint16_t font_width = font->chars->image->width;
    uint16_t font_height = font->chars->image->height;
    ili9341_read_area(x, y, width, height, tmpbuf);
    ili9341_fill_rect(x, y,width,height, 0xffff);
    ili9341_draw_line(x, y, x+width-1, y, 0x001f);
    ili9341_draw_line(x+width-1, y, x+width-1, y+height-1, 0x001f);
    ili9341_draw_line(x+width-1, y+height-1, x, y+height-1, 0x001f);
    ili9341_draw_line(x, y+height-1, x, y, 0x001f);

    ili9341_draw_line(x+1, y+1, x+width-2, y+1, 0x001f);
    ili9341_draw_line(x+width-2, y+1, x+width-2, y+height-2, 0x001f);
    ili9341_draw_line(x+width-2, y+height-2, x+1, y+height-2, 0x001f);
    ili9341_draw_line(x+1, y+height-2, x+1, y+1, 0x001f);

    uint16_t sx, sy, index;
    sx = x+5;
    sy = y+5;
    index =0;
    while (index < strlen(message)) {
        ili9341_draw_char(sx, sy,message[index++], 0xf800,font);
        sx+=font_width;
        if (sx > x+width-1-font_width) {sy+=(font_height+2); sx=25;}
        if (sy > y+height-1 - font_height) break;
    }
 
    sleep_ms(seconds*1000);
    ili9341_write_area(x, y, width, height, tmpbuf);
}

void ili9341_vertical_scroll_def(uint16_t top, uint16_t bottom) {
    uint16_t scroll=TFT_HEIGHT - top - bottom;
    ili9341_cmd(ILI9341_VSCROLLDEF, 6, 0xFF, (uint8_t[6]){(uint8_t)(top>>8),(uint8_t)(top&0xff),
    (uint8_t)(scroll>>8),(uint8_t)(scroll&0xff),(uint8_t)(bottom>>8),(uint8_t)(bottom&0xff)});
}
void ili9341_vertical_scrolling(uint16_t addr) {
    ili9341_cmd(ILI9341_VSCROLLADDR, 2, 0xFF, (uint8_t[2]){(uint8_t)(addr>>8),(uint8_t)(addr&0xff),});
}

void ili9341_read_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels) {
    if (x < 0 || y < 0 || x >= TFT_WIDTH || y >= TFT_HEIGHT) return;
    if (x+width > TFT_WIDTH) width = TFT_WIDTH - x;
    if (y+height > TFT_HEIGHT) height = TFT_HEIGHT - y;
    uint32_t total_pixels = width*height;
    uint8_t tmpbuf[4];
    ili9341_set_address_window(x,y,x+width-1, y+height-1);
    ili9341_cmd(ILI9341_MEMORYREAD, 1, 0x00, tmpbuf);
    for (int i=0; i < total_pixels; i++) {
        ili9341_cmd(ILI9341_NOP, 3, 0x00, tmpbuf);
        pixels[i*2] = (tmpbuf[0]& 0xf8) | (tmpbuf[1] >> 5);
        pixels[i*2+1] = ((tmpbuf[1] << 3) & 0xe0) | (tmpbuf[2] >> 3);
    }
    

}
void ili9341_write_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels) {
    if (x < 0 || y < 0 || x >= TFT_WIDTH || y >= TFT_HEIGHT) return;
    if (x+width > TFT_WIDTH) width = TFT_WIDTH - x;
    if (y+height > TFT_HEIGHT) height = TFT_HEIGHT - y;
    uint32_t total_bytes = width*height*2;
    ili9341_set_address_window(x,y,x+width-1, y+height-1);
    ili9341_cmd(ILI9341_MEMORYWRITE, total_bytes, 0xFF, pixels);

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

	uint16_t total_pixels = width * height;

	ili9341_set_address_window (x, y, x + width-1, y + height-1);
    ili9341_cmd(ILI9341_MEMORYWRITE, total_pixels*2, 0xFF, (uint8_t*)(bitmap->data));
   
}
void ili9431_init_config() {
	ili9341_cmd(ILI9341_SOFTRESET, 0, 0xFF, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYOFF, 0, 0xFF, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_PIXELFORMAT, 1, 0xFF, (uint8_t[1]){0x55}); //0x55
    ili9341_cmd(ILI9341_POWERCONTROL1, 1, 0xFF, (uint8_t[1]){0x05}); // 0x05 :3.3V
    ili9341_cmd(ILI9341_POWERCONTROL2, 1, 0xFF, (uint8_t[1]){0x10});
    ili9341_cmd(ILI9341_VCOMCONTROL1, 2, 0xFF, (uint8_t[2]){0x3E, 0x28});
    ili9341_cmd(ILI9341_VCOMCONTROL2, 1, 0xFF, (uint8_t[1]){0x86});
    ili9341_cmd(ILI9341_MADCTL, 1, 0xFF, (uint8_t[1]){0x00}); //MY,MX,MV,ML,BRG,MH,0,0(40)
    ili9341_cmd(ILI9341_FRAMECONTROL, 2, 0xFF, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    ili9341_cmd(ILI9341_DISPLAYFUNC, 4, 0xFF, (uint8_t[4]){0x0A, 0x82, 0x27, 0x04}); //0a,a2,27,04
    ili9341_cmd(ILI9341_GAMMASET, 1, 0xFF, (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, 0xFF, NULL);
    sleep_ms(150);
    ili9341_cmd(ILI9341_DISPLAYON, 0, 0xFF, NULL);
    sleep_ms(500);
    
}
void ili9341_init() {
    
    ili9431_pio_cmd_init(ili9341_pio, ili9341_sm, in_out_base_pin, set_base_pin, 35000000); 
	ili9431_init_config();
    
}
  • ili9341.h
#ifndef  _ILI9431_H_
#define _ILI9431_H_

#define TFT_WIDTH   240
#define TFT_HEIGHT   320

#include "pico/stdlib.h"
#include "tft_string_lib/ili_string.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);
uint16_t ili9341_read_pixel(uint16_t x, uint16_t y);
void ili9341_set_address_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void ili9341_memory_write_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void ili9341_cmd(uint8_t cmd, uint32_t count, uint8_t dir, 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_invert_display(bool invert);
void ili9341_draw_line(uint16_t start_x, uint16_t start_y, uint16_t end_x, uint16_t end_y, uint16_t color);
void ili9341_draw_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_string(uint16_t x, uint16_t y, uint8_t* str, uint16_t color, const tFont *font);
void ili9341_draw_string_withbg(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font);
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);
void ili9341_vertical_scroll_def(uint16_t top, uint16_t bottom);
void ili9341_vertical_scrolling(uint16_t addr);
void ili9341_read_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels);
void ili9341_write_area(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t * pixels);
void ili9341_draw_char(uint16_t x, uint16_t y, uint8_t character, uint16_t color, const tFont *font);
void ili9341_popMessage(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *message, uint8_t seconds, const tFont *font);
#endif






2022年12月13日 星期二

[Raspberry Pi Pico (c-sdk)] Display: Ep 2 : PIO TFT LCD ILI9341 8-bit parallel C-code Driver

本文章介紹使用Raspberry Pi Pico PIO(Programmable IO)功能來寫作ILI3941 8-bit parallel模式的驅動程式。並且試著只改變StateMachine執行的Frequency,來比較LCD TLF ILI9341的顯示效能。


一、接腳:

  1. GPIO 11,10,9,8,7,6,5,4分別接DB7,DB6,DB5,DB4,DB3,DB2,DB1,DB0。並設定成StateMachine的out pins。
  2. GPIO 15,14,13,12分別接CS(CSX), RS(D/CX),WR(WRX),RD(RDX)。並設定成StateMachine的set pins。

二、ILI9341 Datasheet write sequence 與 PIO程式碼比對。


三、重要指令說明:

  1. 相對於PIO的ili9341_cmd:
    依序put 指定(cmd),多少參數(count),與資料(params)到TX_FIFO中,若指令為0x00(NOP)則只有輸入資料,count為0則指令不帶參數。

  2. 定址:2A, 2B, 2C
    column address 與page address分別需要4個參數。
    ili9341_draw_pixel 寫入一個RGB565格式的顏色點。
  3. 其他劃線、圓、方則由上面兩個函數衍生。

四、成果影片


五、程式碼

  •     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
  • 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"

PIO ili9341_pio = pio1;
uint ili9341_sm = 0;
uint out_base_pin = 4;
uint in_base_pin = 4;
uint set_base_pin = 12;

static uint8_t p_count = 0;
static uint8_t d_count = 0;
static uint8_t data_recv = 0;
static uint8_t params[8];
static uint32_t data[8];

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 ili9431_pio_cmd_init(PIO pio, uint sm, uint 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, out_base + i);
    for (int i = 0; i < 4; i++)
        pio_gpio_init(pio, set_base + i);

    pio_sm_set_consecutive_pindirs(pio, sm, out_base, 8, true);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);

    sm_config_set_out_pins(&c, out_base, 8);
    sm_config_set_set_pins(&c, set_base, 4);

    sm_config_set_out_shift(&c, true, false, 32);

    float div = clock_get_hz(clk_sys) / freq;
    sm_config_set_clkdiv(&c, div);

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

    ili9341_cmd(ILI9341_MEMORYWRITE, 0, NULL);
}

void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
    if (x < 0 || x > SCREEN_WIDTH || y < 0 || y > SCREEN_HEIGHT)
        return;
    ili9341_set_address_window(x, y, x, y);
    ili9341_cmd(ILI9341_NOP, 2, (uint8_t[2]){(uint8_t)(color >> 8), (uint8_t)color});
}
void ili9341_invert_display(bool invert)
{
    if (invert)
        ili9341_cmd(ILI9341_INVERTON, 0, NULL);
    else
        ili9341_cmd(ILI9341_INVERTOFF, 0, NULL);
}
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 > SCREEN_WIDTH)
        width = SCREEN_WIDTH - x;
    if (y + height > SCREEN_HEIGHT)
        height = SCREEN_HEIGHT - y;
    ili9341_set_address_window(x, y, x + width, y + height);
    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 & 0x00ff)});
}

void ili9341_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
    /* algorithm from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
    int dx = abs(x1 - x0);
    int sx = x0 < x1 ? 1 : -1;
    int dy = -abs(y1 - y0);
    int sy = y0 < y1 ? 1 : -1;
    int error = dx + dy;
    int e2;
    while (1)
    {
        // plot(x0, y0)
        ili9341_draw_pixel(x0, y0, color);
        if (x0 == x1 && y0 == y1)
            break;
        e2 = 2 * error;
        if (e2 >= dy)
        {
            if (x0 == x1)
                break;
            error = error + dy;
            x0 = x0 + sx;
        }
        if (e2 <= dx)
        {
            if (y0 == y1)
                break;
            error = error + dx;
            y0 = y0 + sy;
        }
    }
}

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

    uint16_t total_pixels = width * height;

    ili9341_set_address_window(x, y, x + width - 1, y + height - 1);

    for (uint16_t pixels = 0; pixels < total_pixels; pixels++)
    {
        ili9341_cmd(ILI9341_NOP, 1, (uint8_t[1]){(uint8_t)(bitmap->data[2 * pixels])});
        ili9341_cmd(ILI9341_NOP, 1, (uint8_t[1]){(uint8_t)(bitmap->data[2 * pixels + 1])});
    }
}
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});
    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]){0x40});             // MY,MX,MV,ML,BRG,MH,0,0
    ili9341_cmd(ILI9341_FRAMECONTROL, 2, (uint8_t[2]){0x00, 0x1B}); // Default 70Hz
    ili9341_cmd(ILI9341_DISPLAYFUNC, 4, (uint8_t[4]){0x0A, 0xA2, 0x27, 0x04});
    ili9341_cmd(ILI9341_GAMMASET, 1, (uint8_t[1]){0x01});

    ili9341_cmd(ILI9341_PGAMCOR, 15, (uint8_t[15]){0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09, 0x00});
    ili9341_cmd(ILI9341_NGAMCOR, 15, (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, out_base_pin, set_base_pin, 70 * 1000000); // 70*
    ili9431_init_config();
}
  • ili9341.h

#ifndef  _ILI9431_H_
#define _ILI9431_H_

#define SCREEN_WIDTH   240
#define SCREEN_HEIGHT   320

#include "pico/stdlib.h"
#include "tft_string_lib/ili_string.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);
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_invert_display(bool invert);
void ili9341_draw_line(uint16_t start_x, uint16_t start_y, uint16_t end_x, uint16_t end_y, uint16_t color);
void ili9341_draw_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void ili9341_draw_string(uint16_t x, uint16_t y, uint8_t* str, uint16_t color, const tFont *font);
void ili9341_draw_string_withbg(uint16_t x, uint16_t y, char *str, uint16_t fore_color, uint16_t back_color, const tFont *font);
void ili9341_draw_bitmap(uint16_t x, uint16_t y, const tImage *bitmap);
void pio_sm_reinit(uint32_t freq);
#endif
  • main.c
#include <stdio.h>
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "ili9341.h"
#include "pico_img.h"
#include "string.h"

void demo() {
    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0x0000);
    for (int j = 0; j < 320; j+=5) ili9341_draw_line(0, 0, 239, j, 0xffff);
    for (int i = 0; i < 240; i+=5) ili9341_draw_line(0, 0, i, 319, 0xffff);
    for (int i = 10;i < 150; i+=10) {
        ili9341_fill_rect(i, i, 100, 100, ili9341_color_565RGB(0x60+i, 0x70+i*2, 0x30+i));
    }
    ili9341_draw_bitmap(50,0, &pico_img);
    ili9341_invert_display(true);
    ili9341_invert_display(false);
}
int main()
{
    stdio_init_all();
    
    ili9341_init();

    ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0x0000);
    for (int j = 0; j < 320; j+=5) ili9341_draw_line(0, 0, 239, j, 0xffff);
    sleep_ms(2000);
    for (int i = 0; i < 240; i+=5) ili9341_draw_line(0, 0, i, 319, 0xffff);
    sleep_ms(2000);
    for (int i = 10;i < 150; i+=10) {
        ili9341_fill_rect(i, i, 100, 100, ili9341_color_565RGB(0x60+i, 0x70+i*2, 0x30+i));
    }
    sleep_ms(2000);
    ili9341_draw_bitmap(50,0, &pico_img);
    sleep_ms(2000);
    ili9341_invert_display(true);
    sleep_ms(2000);
    ili9341_invert_display(false);
    sleep_ms(2000);

    char buf[60];
    uint32_t freqs[4] = {1000000,2500000,  50000000, 70000000};
    for (int i = 0 ; i < 4; i++) {
        float div =125000000/freqs[i]; 
        //
        pio_sm_set_clkdiv(pio1, 0, div);
        pio_sm_clkdiv_restart(pio1, 0);
        
        ili9341_fill_rect(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, 0x0000);
        absolute_time_t t1 = get_absolute_time();
        demo();
        absolute_time_t t2 = get_absolute_time();
        printf("Freq=%d\n",freqs[i]);
        printf("%d ms", absolute_time_diff_us(t1,t2)/1000);
        sleep_ms(5000);
    }

}

2022年12月7日 星期三

[Raspberry Pi Pico (c-sdk)] Display: Ep 1. PIO LCD1602A C-Code Driver & 4x4 keypad

本實驗依據 LCD1602A datasheet 撰寫以Raspberry Pi Pico PIO為基底的 C Driver。


實驗項目:

  1. 輸入啟用密碼後,控制伺服馬達轉動。
  2. 更改密碼,存在開發板上的Flash Memory。
  3. 展示LCD1602 CGRAM,Cursor Blink,Text Scroll 功能。

接線:



LCD1602指令表:

使用4 bit模式,因此每個指令須依序送出4 High bit與 4 Low bits。

例如Display clear指令為0000000001(RS,RW D7~D0),則依序送出00 0000(0x00, RS,RW D7~D4)與0x00 0001(0x01)相對Pi Pico PIO指令為:


 成果影片:



程式碼:


4x4 matrix keypad請參閱:

Raspberry Pi Pico PIO(Programmable IO) Episode 1: 4x4 matrix keypad

  • lcd1602_4b.c

#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "lcd1602_pio.h"
#include "lcd1602.h"
#include "hardware/clocks.h"
#include "string.h"
PIO lcd_pio = pio0;
uint lcd_sm = 0;
uint lcd_out_base=16; //rs, rw, db7, db6, db5, db4
uint lcd_en_pin = 22; // En

static uint8_t CGRAM[8][8] = {
    {0x00,0x0a,0x1f,0x1f,0x1f,0x0e,0x04,0x00},
    {0x1f,0x15,0x00,0x00,0x00,0x11,0x1b,0x1f},
    {0x04,0x0e,0x0a,0x0a,0x0a,0x0a,0x0e,0x00},
    {0x04,0x0e,0x0a,0x0a,0x0e,0x0e,0x0e,0x00},
    {0x04,0x0e,0x0e,0x0e,0x0e,0x0e,0x0e,0x00},
    {0x07,0x05,0x07,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}
};



void lcd1602_4b_pio_init(PIO pio, uint sm, uint out_base,  uint en_pin, uint freq) {
    uint offset=0;
    pio_sm_config c;
    offset = pio_add_program(pio, &lcd1602_4b_program);
    c = lcd1602_4b_program_get_default_config(offset);
    
    for (int i=0; i < 6; i++) pio_gpio_init(pio, out_base+i);
    pio_gpio_init(pio, en_pin);

    pio_sm_set_consecutive_pindirs(pio, sm, out_base, 6, true);
    pio_sm_set_consecutive_pindirs(pio, sm, en_pin, 1, true);

    sm_config_set_out_pins(&c, out_base, 6);
    sm_config_set_set_pins(&c, en_pin, 1);
   
    sm_config_set_jmp_pin(&c, out_base+3);
    
    sm_config_set_out_shift(&c, true, false, 32);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
   
}

void lcd1602_set_cursor(uint8_t line, uint8_t pos) {
    if (line > 1 || line < 0 || pos < 0 || pos > 15) return;
    pio_sm_put_blocking(pio0, 0, 0x08 | line << 2);
    pio_sm_put_blocking(pio0, 0, 0x00 | pos);
}
void lcd1602_display_cursor_on_off(uint8_t on_off, uint8_t cursor_on_off, uint8_t cursor_blink) {
    uint8_t stat = 0x8;
    if (on_off) stat = stat | (on_off << 2);
    if (cursor_on_off) {
        stat = stat | (cursor_on_off << 1);
        if (cursor_blink) stat = stat | (cursor_blink &0x01);
    }
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00);
    pio_sm_put_blocking(lcd_pio, lcd_sm, stat);

    
}
void lcd1602_cursor_shift(uint8_t right) {
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x01); // cursor shift left
    if (right)
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x06);
     else 
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x04);
    
}
void lcd1602_clear() {
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00); //display clear
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x01);
    sleep_ms(2);
    
}

void lcd1602_print_ch(uint8_t l, uint8_t p, uint8_t c) {
    if (l > 1 || l < 0 || p < 0 || p > 15) return;
    pio_sm_put_blocking(pio0, 0, 0x08 | l << 2);
    pio_sm_put_blocking(pio0, 0, 0x00 | p);
    pio_sm_put_blocking(pio0, 0, 0x20 | (c & 0xff) >> 4);
    pio_sm_put_blocking(pio0, 0, 0x20 | (c & 0x0f));
}
void lcd1602_print(uint8_t l, uint8_t p, uint8_t * buff) {
    if (l > 1 || l < 0 || p < 0 || p > 15) return;
    uint slen= strlen(buff);
 
    if (!slen) return;
    slen = (slen+p)>16?16-p:slen;   // max positon is 15

    for (uint8_t i = p;  i < p + slen; i++) {
        /* set address*/
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x08 | l << 2);
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00 | i);
        /* put data */
        pio_sm_put_blocking(lcd_pio, lcd_sm, ((buff[i-p] & 0xff) >> 4) | 0x20);
        pio_sm_put_blocking(lcd_pio, lcd_sm, (buff[i-p]&0x0f) | 0x20);

    }
    
}
void lcd1602_scroll(uint8_t line, uint8_t *buff, bool left, uint16_t delay_ms, int loop_count) {
    uint8_t shift_out_char;
    uint8_t len = strlen(buff);
    uint8_t *l_buff = (uint8_t*) malloc(len+1);
    strcpy(l_buff, buff);
    lcd1602_print(line, 0, l_buff);
    for (int l = 0; l < loop_count; l++) {
        if (left) {
            sleep_ms(delay_ms);
            shift_out_char = l_buff[0];
            for (int i = 0; i < len-1; i++) {
                l_buff[i] = l_buff[i+1];
            }
            l_buff[len-1]=shift_out_char;

            lcd1602_print(line, 0, l_buff);
        

        } else {

            sleep_ms(delay_ms);
            shift_out_char = l_buff[len-1];
            for (int i = len-1; i > 0; i--) l_buff[i] = l_buff[i-1];
            l_buff[0]=shift_out_char;
            lcd1602_print(line, 0, l_buff);

        }
    }
    free(l_buff);

}
void lcd1602_pw_on_init() {
    sleep_ms(16);
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x03);
     pio_sm_put_blocking(lcd_pio, lcd_sm, 0x03);
 
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x03 );
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x02);

    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x02 );  // 2 line, font 5*8 
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x08);

    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00);  // display on, cursor off
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x0c);

    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00);  // entry mode
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x06);
  

    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00);
    pio_sm_put_blocking(lcd_pio, lcd_sm, 0x01 );  // display clear
    sleep_ms(1000);
}
void lcd1602_write_to_CGRAM() {
    for (uint8_t i = 0; i < 8; i++) {
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x04 | ((i << 3) & 0xf0) >> 4 ); //CGRAM address i
        pio_sm_put_blocking(lcd_pio, lcd_sm, 0x00 | (i << 3)&0x0f);
        for (uint8_t j =0; j < 8; j++) {
            pio_sm_put_blocking(lcd_pio, lcd_sm, 0x20 | (CGRAM[i][j] & 0xff)>>4); //CGRAM data j
            pio_sm_put_blocking(lcd_pio, lcd_sm, 0x20 |  CGRAM[i][j] & 0x0f);
        }
    }
    
    
}
void lcd1602_init() {
    /* bits */
    lcd1602_4b_pio_init(lcd_pio, lcd_sm, lcd_out_base,  lcd_en_pin, 100000);
    
    lcd1602_pw_on_init();

    lcd1602_write_to_CGRAM();
    
}

  • lcd1602_4b.pio

.program lcd1602_4b
; RS,R/W, DB7,DB6, Db5, DB4   : bits order
.wrap_target
pull
check_busy:
set x, 0x10
mov pins, x
set pins, 0
set pins, 1 [2]
set pins, 0 [1]
jmp pin check_busy

set pins,0
out pins, 6 
set pins,1 [3]
set pins,0 [2]

  • lcd1602.h

#ifndef __LCD1602_H
#define __LCD1602_H

void lcd1602_init();
void lcd1602_print(uint8_t l, uint8_t p, uint8_t * buff);
void lcd1602_print_ch(uint8_t l, uint8_t p, uint8_t c);
void lcd1602_clear();
void lcd1602_display_cursor_on_off(uint8_t on_off, uint8_t cursor_on_off, uint8_t cursor_blink);
void lcd1602_cursor_shift(uint8_t right);
void lcd1602_set_cursor(uint8_t line, uint8_t pos);
void lcd1602_scroll(uint8_t line, uint8_t *buff, bool left, uint16_t delay_ms, int loop_count);

#endif

  • main.c

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/pwm.h"
#include "hardware/flash.h"
#include "lcd1602.h"
#include "keypad.h"
#include "string.h"

#define PWM_PIN 28
#define XIP_OFFSET (0x153600)

uint8_t test_passwd[7]="123456";
uint8_t passwd[7], apasswd[7];
uint8_t passLen=0;
uint8_t passRow=0;
int slice, pwm_ch;

enum {
    MENU=1,
    ENTER_PWD,
    VALIFIED,
    CHANGE_PWD
} State;

void setServoAngle(int ang) {
    uint16_t top_count = (uint16_t)(ang/0.09 + 1500);  // angle 0: 1.5 ms duty
    pwm_set_chan_level(slice, pwm_ch, top_count);

}
void show_menu() {
    lcd1602_clear();
    lcd1602_display_cursor_on_off(1,0,0);
    lcd1602_print(0,4, "Hellow!");
    lcd1602_print(1,0, "*:Enter Password");
    State = MENU;
}
void function_A() {
    setServoAngle(90);
}
void function_B() {
    setServoAngle(0);

}
void save_password() {
    char tempbuff[256];
    lcd1602_clear();
    lcd1602_display_cursor_on_off(1,0,0);
    lcd1602_print(0,0, "PWD Changed!");
    strcpy(test_passwd,passwd);
    memset(tempbuff, 0, 256);
    sprintf(tempbuff, "Password:%s", passwd);
    flash_range_erase(XIP_OFFSET, 4096);
    flash_range_program(XIP_OFFSET, tempbuff, 256);
    sleep_ms(1000);
    show_menu();
}
void load_password() {
    char tempbuff[256];
    memset(tempbuff, 0, 256);
    strncpy(tempbuff, (uint8_t*)(XIP_BASE+XIP_OFFSET), 15);
    if (strstr(tempbuff, "Password:")) {
        memset(test_passwd, 0, 6);
        for (int i =0; i < 6; i++) {
            test_passwd[i]=*(uint8_t*)(XIP_BASE+XIP_OFFSET+9+i);
        }
    }
}
void servo_init() {
    // set Servo pwm
    gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);
    slice = pwm_gpio_to_slice_num(PWM_PIN);
    pwm_ch = pwm_gpio_to_channel(PWM_PIN);
    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(slice, &c, true);    
    
    setServoAngle(0);
}
void show_change_pwd() {
    lcd1602_clear();
    lcd1602_display_cursor_on_off(1,1,1);
    lcd1602_print(0,0, "Enter PWD:");
    lcd1602_print(1,0, "PWD AGAIN:");
    passRow=0;
    lcd1602_set_cursor(passRow,10);
    memset(passwd,0,6);
    memset(apasswd,0,6);
    passLen=0;
    State=CHANGE_PWD;
}

void demo() {
    lcd1602_clear();
    lcd1602_print(0, 0, "Some Demos");
    sleep_ms(1000);
    lcd1602_print(0, 0, "Print CGRAM Char");
    lcd1602_print_ch(1,0,0);
    lcd1602_print_ch(1,1,1);
    lcd1602_print_ch(1,2,2);
    lcd1602_print_ch(1,3,3);
    lcd1602_print_ch(1,4,4);
    lcd1602_print_ch(1,5,5);
    sleep_ms(3000);
    lcd1602_clear();
    lcd1602_print(0, 0, "Cursor On     ");
    lcd1602_display_cursor_on_off(1, 1, 0);
    lcd1602_print(1, 0, "              ");
    lcd1602_set_cursor(1,5);
    sleep_ms(3000);
    lcd1602_print(0, 0, "Cursor Blink   ");
    lcd1602_display_cursor_on_off(1, 1, 1);
    lcd1602_print(1, 0, "               ");
    lcd1602_set_cursor(1,5);
    sleep_ms(4000);
    lcd1602_print(0, 0, "Cursor Off    ");
    lcd1602_display_cursor_on_off(1, 0, 0);
    lcd1602_print(1, 0, "              ");
    lcd1602_set_cursor(1,5);
    sleep_ms(2000);
    lcd1602_print(0, 0, "Scroll left   ");
    lcd1602_scroll(1, "This is a scrolling messages.",true, 500, 20);
    lcd1602_clear();
     sleep_ms(1000);
    lcd1602_print(0, 0, "Scroll Right   ");
    lcd1602_scroll(1, "This is a scrolling messages.",false, 500, 20);
 
 show_menu();

}

int main()
{
    uint8_t key;
    
    stdio_init_all();
    keypad_init();
    lcd1602_init();
    load_password();
    servo_init();
    
    show_menu();
    while (1) {
        key = get_new_keypad_value();
        switch (key) {
            case 0:
            break;
            case '*':
                lcd1602_clear();
                lcd1602_display_cursor_on_off(1,1,1);
                lcd1602_print(0,0, "Enter PWD:");
                lcd1602_print(1,0, "C:Menu, #:Enter ");
                lcd1602_set_cursor(0,10);
                State=ENTER_PWD;
                memset(passwd,0,6);
                passLen=0;
                passRow=0;
            break;
            case '#':
                if (State == ENTER_PWD) {
                    if (strcmp(passwd, test_passwd)==0) {
                        State = VALIFIED;
                        lcd1602_display_cursor_on_off(1,0,0);
                        lcd1602_print(0,0, "A: UP, B:Down   ");
                        lcd1602_print(1,0, "Confirm!       ");
                        sleep_ms(1000);      
                        lcd1602_print(1,0, "C:Menu, D:CH PWD");     
                    }
                    else {
                        lcd1602_print(1,0, "Password Error! ");
                        lcd1602_print(0,10,"      ");
                        lcd1602_display_cursor_on_off(1,1,1);
                        lcd1602_set_cursor(0,10);
                        memset(passwd,0,6);
                        passLen=0;
                    }
                } 
                if (State == CHANGE_PWD) {
                    if (passRow == 0) {
                        lcd1602_set_cursor(++passRow, 10);
                        passLen=0;
                    } else {
                        if (strcmp(passwd, apasswd)!=0) {
                            lcd1602_clear();
                            lcd1602_print(1,0, "Mismatch!");
                            sleep_ms(1000); 
                            show_change_pwd();
                        } else {
                            save_password();
                        }
                    }


                }
            break;
            case 'A':
                if (State == VALIFIED) {
                    
                    function_A();
                }
            break;
            case 'B':
                if (State == VALIFIED) {
                    
                    function_B();
                }
            break;
            case 'C':
                show_menu();
                demo();

            break;
            case 'D':
                if (State == VALIFIED) {
                    show_change_pwd();
                }
            break;
            default:
                if ((State==ENTER_PWD || State == CHANGE_PWD) && passLen < 6) {
                    lcd1602_print_ch(passRow,10+passLen, '*');
                    if (passRow == 0) 
                        passwd[passLen++] = key;
                    else 
                        apasswd[passLen++] = key;
                }
            break;
        }
        sleep_ms(1);
    }
    return 0;
}





2022年12月1日 星期四

[Raspberry Pi Pico (c-sdk)] Storage: Ep 3. Using SD/MMC Devices and Flash Devices Together

 本實驗 整合SD/MMC Card與Flash Memory device一起使用。


  • 測試項目:

  1. SD/MMC Low Level Disk I/O,沒有使用檔案系統,直接進行Sector Address I/O
  2. SD/MMC 在有沒有使用DMA時,I/O效能比較。
  3. 同時掛載SD/MMC Card與Flash memory device進行I/O測試。
SD/MMC driver程式寫作參考網址 FatFs - Generic FAT Filesystem Module,Flash memory device driver程式碼請參閱上篇文章:[Raspberry Pi Pico (c-sdk)] Storage: Ep 2. Multi external flash memory devices with FatFs filesystem

  • SPI 腳位連接:
  • ffconf.h修改項目:


展示影片:



程式碼:

  • glue.c
#include "stdio.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "spi_sdmmc.h"
#include "W25Q.h"
#include "hardware/rtc.h"
#include "inttypes.h"

#define SDMMC_DRV_0     0
#define W25Q_DRV_1      1

sdmmc_data_t *pSDMMC=NULL;
w25q_data_t *pW25Q = NULL;

//==================//
DSTATUS disk_initialize (BYTE drv){
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            if (pSDMMC == NULL) {
                pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
                pSDMMC->csPin = SDMMC_PIN_CS;
                pSDMMC->spiPort = SDMMC_SPI_PORT;
                pSDMMC->spiInit=false;
                pSDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
                pSDMMC->dmaInit=false;
#endif
    }
            stat = sdmmc_disk_initialize(pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
        if (pW25Q == NULL) {
            pW25Q = (w25q_data_t*)malloc(sizeof(w25q_data_t));
            pW25Q->spiInit=false;
            pW25Q->Stat=STA_NOINIT;
        }
		stat = w25q_disk_initialize(W25Q_SPI_PORT, W25Q_PIN_CS, pW25Q); 
		return stat;
		
	    break;
    }
    return STA_NOINIT;
 }
/*-----------------------------------------------------------------------*/
/* Get disk status                                                       */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE drv) {
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat=  sdmmc_disk_status(pSDMMC); /* Return disk status */
            return stat;
        break;
        case W25Q_DRV_1:
            stat = pW25Q->Stat;
            return stat;
        break;
    }
    return RES_PARERR;
	
}

/*-----------------------------------------------------------------------*/
/* Read sector(s)                                                        */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE drv,		/* Physical drive number (0) */
	BYTE *buff,		/* Pointer to the data buffer to store read data */
	LBA_t sector,	/* Start sector number (LBA) */
	UINT count		/* Number of sectors to read (1..128) */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_read(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            if (pW25Q->Stat & STA_NOINIT) return RES_NOTRDY;
		    w25q_read_sector((uint32_t)sector, 0, buff, count*pW25Q->sectorSize, pW25Q);
            return pW25Q->Stat;
        break;
    }
	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write sector(s)                                                       */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
	BYTE drv,			/* Physical drive number (0) */
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count			/* Number of sectors to write (1..128) */
)
{
    DSTATUS stat = STA_NODISK;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_write(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_write(buff, sector, count, pW25Q);
            return stat;
        break;
    }
	return RES_PARERR;

}
#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous drive controls other than data read/write               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE drv,		/* Physical drive number (0) */
	BYTE cmd,		/* Control command code */
	void *buff		/* Pointer to the conrtol data */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_ioctl(cmd, buff, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_ioctl(cmd, buff, pW25Q);
            return stat;
        break;
    }
	return RES_PARERR;
}

DWORD get_fattime(void) {
    datetime_t t = {0, 0, 0, 0, 0, 0, 0};
    bool rc = rtc_get_datetime(&t);
    if (!rc) return 0;

    DWORD fattime = 0;
    // bit31:25
    // Year origin from the 1980 (0..127, e.g. 37 for 2017)
    uint8_t yr = t.year - 1980;
    fattime |= (0b01111111 & yr) << 25;
    // bit24:21
    // Month (1..12)
    uint8_t mo = t.month;
    fattime |= (0b00001111 & mo) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint8_t da = t.day;
    fattime |= (0b00011111 & da) << 16;
    // bit15:11
    // Hour (0..23)
    uint8_t hr = t.hour;
    fattime |= (0b00011111 & hr) << 11;
    // bit10:5
    // Minute (0..59)
    uint8_t mi = t.min;
    fattime |= (0b00111111 & mi) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint8_t sd = t.sec / 2;
    fattime |= (0b00011111 & sd);
    return fattime;
}
  • W25Q.c
#include "stdio.h"
#include "stdlib.h"
#include "W25Q.h"

uint8_t rxbuf[10];
uint8_t txbuf[10];

/*=================*/

const uint8_t i_uniqueid=0x4b;
const uint8_t i_page_program=0x02;
const uint8_t i_read_data=0x03;
const uint8_t i_fast_read_data=0x0b;
const uint8_t i_write_disable=0x04;
const uint8_t i_read_status_r1=0x05;
const uint8_t i_read_status_r2=0x35;
const uint8_t i_read_status_r3=0x15;
const uint8_t i_write_status_r1=0x01;
const uint8_t i_write_status_r2=0x31;
const uint8_t i_write_status_r3=0x11;
const uint8_t i_sector_erase=0x20;
const uint8_t i_block_erase_32k=0x52;
const uint8_t i_block_erase_64k=0xd8;
const uint8_t i_write_enable=0x06;
const uint8_t i_erase_chip=0xc7;

const uint8_t i_device_id=0x90;
const uint8_t i_JEDEC_ID=0x9f;

void w25q_spi_port_init(w25q_data_t *w25q) {
    gpio_set_dir(w25q->cs_pin, GPIO_OUT);
    gpio_put(w25q->cs_pin, 1);
    gpio_set_function(w25q->cs_pin,   GPIO_FUNC_SIO);
    gpio_set_function(W25Q_PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(W25Q_PIN_SCK,  GPIO_FUNC_SPI);
    gpio_set_function(W25Q_PIN_MOSI, GPIO_FUNC_SPI);
        
    printf("\nThe actual baudrate(W25Q):%d\n",spi_init(w25q->spi, SPI_BAUDRATE_HIGH));

    w25q->spiInit=true;
}

void w25q_spi_cs_low(w25q_data_t *w25q) {
    gpio_put(w25q->cs_pin,0);
}
void w25q_spi_cs_high(w25q_data_t *w25q){
    gpio_put(w25q->cs_pin,1);
}
void w25q_send_cmd_read(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, bool is_fast, w25q_data_t *w25q) {
    uint8_t addr[4];
    int addr_len=3;
    addr[3] = 0x00;
    if (is_fast) addr_len=4;
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, addr_len);
    spi_read_blocking(w25q->spi, 0x00, buf, len);
    w25q_spi_cs_high(w25q);
}

void w25q_send_cmd_write(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    uint8_t addr[3];
    
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_write_enable(w25q);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, 3);
    spi_write_blocking(w25q->spi, buf, len);
    w25q_spi_cs_high(w25q);

}

void w25q_send_cmd_addr(uint8_t cmd, uint32_t address, w25q_data_t *w25q) {
    uint8_t addr[3];
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, 3);
    w25q_spi_cs_high(w25q);
}

void w25q_send_cmd(uint8_t cmd, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_read_blocking(w25q->spi, 0x00, buf, len);
    w25q_spi_cs_high(w25q);
}

void w25q_send_simple_cmd(uint8_t cmd, w25q_data_t *w25q) {
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    w25q_spi_cs_high(w25q);
}

void w25q_write_enable(w25q_data_t *w25q) {
    w25q_send_simple_cmd(i_write_enable, w25q);
    sleep_ms(1);
}
void w25q_write_disable(w25q_data_t *w25q) {
    w25q_send_simple_cmd(i_write_disable, w25q);
    sleep_ms(1);
}

/*==================*/
DRESULT w25q_disk_initialize(spi_inst_t *spi, uint cs_pin, w25q_data_t *w25q) {
    w25q->spi = spi;
    w25q->cs_pin = cs_pin;

    if (!w25q->spiInit) w25q_spi_port_init(w25q);

    w25q_get_JEDEC_ID(w25q);
    w25q->lock = 1;
	sleep_ms(100);
	switch (w25q->jedec_id & 0x000000FF)
	{
	    case 0x20: // 	w25q512
		    w25q->blockCount = 1024;
		break;
	    case 0x19: // 	w25q256
		    w25q->blockCount = 512;
		break;
	    case 0x18: // 	w25q128
		    w25q->blockCount = 256;
		break;
	    case 0x17: //	w25q64
		    w25q->blockCount = 128;
		break;
	    case 0x16: //	w25q32
		    w25q->blockCount = 64;
		break;
        case 0x15: //	w25q16
            w25q->blockCount = 32;
            break;
        case 0x14: //	w25q80
            w25q->blockCount = 16;
            break;
        case 0x13: //	w25q40
            w25q->blockCount = 8;
        case 0x12: //	w25q20
            w25q->blockCount = 4;
            break;
        case 0x11: //	w25q10
            w25q->blockCount = 2;
            break;
        default:
            w25q->lock = 0;
            return false;
    }
	w25q->pageSize = 256;
	w25q->sectorSize = 0x1000;
	w25q->sectorCount = w25q->blockCount * 16;
	w25q->pageCount = (w25q->sectorCount * w25q->sectorSize) / w25q->pageSize;
	w25q->blockSize = w25q->sectorSize * 16;
	w25q->capacityKB = (w25q->sectorCount * w25q->sectorSize) / 1024;
	w25q_get_uid(w25q);
    w25q_read_status_register_1(w25q);
    w25q_read_status_register_2(w25q);
    w25q_read_status_register_3(w25q);
	w25q->lock = 0;
    w25q->Stat &= ~STA_NOINIT;
	return w25q->Stat;
}


void w25q_read_status_register_1(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r1, &w25q->statusRegister1, 1, w25q);
}
void w25q_read_status_register_2(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r2, &w25q->statusRegister2, 1, w25q);
}
void w25q_read_status_register_3(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r3, &w25q->statusRegister3, 1, w25q);
}

void w25q_write_status_register_1(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r1, &w25q->statusRegister1, 1, w25q);
}
void w25q_write_status_register_2(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r2, &w25q->statusRegister2, 1, w25q);
}
void w25q_write_status_register_3(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r3, &w25q->statusRegister3, 1, w25q);
}

void w25q_wait_for_write_end(w25q_data_t *w25q)
{
	sleep_ms(1);
	w25q_spi_cs_low(w25q);
	spi_write_blocking(w25q->spi, &i_read_status_r1,1);
	do
	{
		spi_read_blocking(w25q->spi, 0x00, &w25q->statusRegister1,1);
		sleep_ms(1);
	} while ((w25q->statusRegister1 & 0x01) == 0x01);
	w25q_spi_cs_high(w25q);
}

void w25q_erase_chip(w25q_data_t *w25q) {
    while (w25q->lock) sleep_ms(1);
    w25q->lock=1;
    w25q_write_enable(w25q);
    w25q_send_simple_cmd(i_erase_chip, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(10);
    w25q->lock=0;
}

void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    while (w25q->lock) sleep_ms(1);
    w25q->lock=1;
    if (offset + len > w25q->pageSize) {
        len = w25q->pageSize - offset;
    }
    page_addr = (page_addr * w25q->pageSize) + offset;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_write(i_page_program, page_addr, buf, len, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
/*===========================*/
uint32_t w25_page_to_sector_address(uint32_t pageAddress, w25q_data_t *w25q)
{
	return ((pageAddress * w25q->pageSize) / w25q->sectorSize);
}
uint32_t w25q_page_to_block_address(uint32_t pageAddress, w25q_data_t *w25q)
{
	return ((pageAddress * w25q->pageSize) / w25q->blockSize);
}
uint32_t w25q_data_sector_to_block_address(uint32_t sectorAddress, w25q_data_t *w25q)
{
	return ((sectorAddress * w25q->sectorSize) / w25q->blockSize);
}
uint32_t w25q_sector_to_page_address(uint32_t sectorAddress, w25q_data_t *w25q)
{
	return (sectorAddress * w25q->sectorSize) / w25q->pageSize;
}
uint32_t w25q_block_to_page_address(uint32_t blockAddress, w25q_data_t *w25q)
{
	return (blockAddress * w25q->blockSize) / w25q->pageSize;
}
/*============================*/

void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	if (offset >= w25q->sectorSize) return;
    if (offset + len  > w25q->sectorSize) 
		len = w25q->sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;


    startPage = w25q_sector_to_page_address(sect_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
    bytesToWrite = len;

	do
	{
        w25q_page_program(startPage, localOffset, buf, bytesToWrite, w25q);
		startPage++;
		bytesToWrite -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToWrite > 0);

}

void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	if ((len > w25q->blockSize) || (len == 0))
		len = w25q->blockSize;
	if (offset >= w25q->blockSize)
		return;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;
	if ((offset + len) > w25q->blockSize)
		bytesToWrite = w25q->blockSize - offset;
	else
		bytesToWrite = len;
	startPage = w25q_block_to_page_address(blk_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_page_program(startPage, localOffset, buf, len, w25q);
		startPage++;
		bytesToWrite -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToWrite > 0);
   
}

DRESULT w25q_disk_write(
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count, 			/* Number of sectors to write (1..128) */
    w25q_data_t *w25q
) 
{
    BYTE *tbuf=(BYTE*)buff;
    while(count > 1)
    {
        w25q_sector_erase(sector, w25q);
        w25q_write_sector(sector, 0, tbuf, w25q->sectorSize, w25q);
        count--;
        tbuf += w25q->sectorSize;
        sector++;
    }
    if (count == 1)
    {
        w25q_sector_erase(sector, w25q);
        w25q_write_sector(sector, 0, tbuf, w25q->sectorSize, w25q);
        count--;
    }
	
	return count? RES_ERROR: RES_OK;
}

void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
	while (w25q->lock == 1) sleep_ms(1);
	w25q->lock = 1;
    w25q_send_cmd_read(i_fast_read_data, address, buf, len, true, w25q);
	sleep_ms(1);
	w25q->lock = 0;
}

void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	while (w25q->lock == 1) sleep_ms(1);
	w25q->lock = 1;
    if (offset >= w25q->pageSize) return;
	if ((offset + len) >= w25q->pageSize)
		len = w25q->pageSize - offset;
	page_addr = page_addr * w25q->pageSize + offset;
    w25q_send_cmd_read(i_fast_read_data, page_addr, buf, len, true, w25q);
	
	sleep_ms(1);
	w25q->lock = 0;
}

DRESULT w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	
    if (offset >= w25q->sectorSize) return RES_ERROR;
    if (offset + len > w25q->sectorSize)
		len = w25q->sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;
	
    startPage = w25q_sector_to_page_address(sect_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead, w25q);
    
		startPage++;
		bytesToRead -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToRead > 0);
    return RES_OK;

}
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
	if (offset+len > w25q->blockSize)
		len = w25q->blockSize-offset;

	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;

	startPage = w25q_block_to_page_address(blk_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead, w25q);
		startPage++;
		bytesToRead -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToRead > 0);

}


void w25q_sector_erase(uint32_t sect_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    sect_addr = sect_addr * w25q->sectorSize;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_sector_erase, sect_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_block_erase_32k(uint32_t blk_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    blk_addr = blk_addr * w25q->sectorSize * 8;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_block_erase_32k, blk_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_block_erase_64k(uint32_t blk_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    blk_addr = blk_addr * w25q->sectorSize * 16;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_block_erase_64k, blk_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_get_manufacter_device_id(uint8_t *mid, w25q_data_t *w25q){
    assert(w25q->spi);
    w25q_send_cmd_read(i_device_id, 0x000000, mid, 2, false, w25q);
}



void w25q_get_JEDEC_ID(w25q_data_t *w25q) {
    uint8_t temp[3];
    w25q_send_cmd(i_JEDEC_ID, temp, 3, w25q);
    w25q->jedec_id = ((uint32_t)temp[0] << 16) | ((uint32_t)temp[1] << 8) | (uint32_t)temp[2];
}
void w25q_get_uid(w25q_data_t *w25q) {
    assert(w25q->spi);
    txbuf[0]= 0x4b;
    txbuf[1] = 0x00; txbuf[2] = 0x00; txbuf[3] = 0x00;txbuf[4]=0x00;
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, txbuf, 5);
    spi_read_blocking(w25q->spi, 0x00, w25q->uuid, 8);
    w25q_spi_cs_high(w25q);
}

DRESULT w25q_disk_ioctl (
	BYTE cmd,		/* Control code */
	void *buff,		/* Buffer to send/receive control data */
    w25q_data_t *w25q
)
{
	DRESULT res = RES_ERROR;
	
    switch(cmd) {
    case CTRL_SYNC:
    	res = RES_OK;
    	break;
    case GET_SECTOR_SIZE:
		*(WORD*)buff = (WORD)w25q->sectorSize; // in f_mkfs() [WORD ss] 
        res = RES_OK;
		break;
    case GET_BLOCK_SIZE:
		
        *(DWORD*)buff = w25q->blockSize;
        res = RES_OK;
		
    	break;
    case GET_SECTOR_COUNT:
		
        *(DWORD*)buff = w25q->sectorCount;
        res = RES_OK;
		
    	break;
    default:
    	res = RES_PARERR;
    	break;
    }
    return res;
}
  • W25q.h
#ifndef W25Q_H
#define W25Q_H
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "ff.h"
#include "ffconf.h"
#include "diskio.h"


/* W25Q SPI pins*/
#define W25Q_SPI_PORT spi0
#define W25Q_PIN_MISO 16
#define W25Q_PIN_SCK  18
#define W25Q_PIN_MOSI 19
#define W25Q_PIN_CS 17
/* ====================== */

typedef struct{
    spi_inst_t *spi;
    uint        cs_pin;
    uint8_t     uuid[8];
    uint32_t    jedec_id;
    uint32_t    blockCount;
    uint32_t    pageCount;
    uint32_t    sectorCount;
    uint16_t    pageSize;
    uint32_t    sectorSize;
    uint32_t    blockSize;
    uint8_t     statusRegister1;
    uint8_t     statusRegister2;
    uint8_t     statusRegister3;
    uint32_t    capacityKB;
    uint8_t     lock;
    bool        spiInit;
    DRESULT     Stat;
}w25q_data_t;


DRESULT w25q_disk_initialize(spi_inst_t *spi, uint cs_pin, w25q_data_t *w25q);
void w25q_get_manufacter_device_id(uint8_t *mid, w25q_data_t *w25q);
void w25q_get_JEDEC_ID(w25q_data_t *w25q);
void w25q_erase_chip(w25q_data_t *w25q);
void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
DRESULT w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
//void w25q_read_data(uint32_t address, uint8_t *buf, uint32_t len);
//void w25q_fast_read_data(uint32_t address, uint8_t *buf, uint32_t len);
void w25q_read_status_register_1(w25q_data_t *w25q);
void w25q_read_status_register_2(w25q_data_t *w25q);
void w25q_read_status_register_3(w25q_data_t *w25q);
void w25q_write_status_register_1(w25q_data_t *w25q);
void w25q_write_status_register_2(w25q_data_t *w25q);
void w25q_write_status_register_3(w25q_data_t *w25q);
void w25q_sector_erase(uint32_t sect_addr, w25q_data_t *w25q);
void w25q_block_erase_32k(uint32_t blk_addr,w25q_data_t *w25q);
void w25q_block_erase_64k(uint32_t blk_addr, w25q_data_t *w25q);
void w25q_get_uid(w25q_data_t *w25q);
void w25q_write_enable(w25q_data_t *w25q);
void w25q_write_diable(w25q_data_t *w25q);
DRESULT w25q_disk_write(const BYTE *buff, LBA_t sector, UINT count,w25q_data_t *w25q);
DRESULT w25q_disk_ioctl(BYTE cmd, void *buff, w25q_data_t *w25q);

#endif

  • spi_sdmmc.c
/*
This library is derived from ChaN's FatFs - Generic FAT Filesystem Module.
*/
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "spi_sdmmc.h"


#define SDMMC_CD 0 // card detect
#define SDMMC_WP 0 // write protected

static uint8_t dummy_block[512];


/* sdmmc spi port initialize*/
void sdmmc_spi_port_init(sdmmc_data_t *sdmmc)
{
	spi_init(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	gpio_set_function(SDMMC_PIN_MISO, GPIO_FUNC_SPI);
	gpio_set_function(sdmmc->csPin, GPIO_FUNC_SIO);
	gpio_set_function(SDMMC_PIN_SCK, GPIO_FUNC_SPI);
	gpio_set_function(SDMMC_PIN_MOSI, GPIO_FUNC_SPI);
	gpio_set_dir(sdmmc->csPin, GPIO_OUT);
	gpio_put(sdmmc->csPin, 1); // deselect

	sdmmc->spiInit = true; // alreadily initialized
}

/* config spi dma*/
#ifdef __SPI_SDMMC_DMA
void config_spi_dma(sdmmc_data_t *sdmmc)
{
	sdmmc->read_dma_ch = dma_claim_unused_channel(true);
	sdmmc->write_dma_ch = dma_claim_unused_channel(true);
	sdmmc->dma_rc = dma_channel_get_default_config(sdmmc->read_dma_ch);
	sdmmc->dma_wc = dma_channel_get_default_config(sdmmc->write_dma_ch);
	channel_config_set_transfer_data_size(&(sdmmc->dma_rc), DMA_SIZE_8);
	channel_config_set_transfer_data_size(&(sdmmc->dma_wc), DMA_SIZE_8);
	channel_config_set_read_increment(&(sdmmc->dma_rc), false);
	channel_config_set_write_increment(&(sdmmc->dma_rc), true);
	channel_config_set_read_increment(&(sdmmc->dma_wc), true);
	channel_config_set_write_increment(&(sdmmc->dma_wc), false);
	channel_config_set_dreq(&(sdmmc->dma_rc), spi_get_dreq(sdmmc->spiPort, false));
	channel_config_set_dreq(&(sdmmc->dma_wc), spi_get_dreq(sdmmc->spiPort, true));

	for (int i = 0; i < 512; i++)
		dummy_block[i] = 0xFF;

	dma_channel_configure(sdmmc->read_dma_ch,
						  &(sdmmc->dma_rc),
						  NULL,
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  sdmmc->sectSize, false);
	dma_channel_configure(sdmmc->write_dma_ch,
						  &(sdmmc->dma_wc),
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  NULL,
						  sdmmc->sectSize, false);
	sdmmc->dmaInit = true;
}
#endif

/* set spi cs low (select)*/
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 0);
}
/* set spi cs high (deselect)*/
void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 1);
}
/* Initialize SDMMC SPI interface */
static void sdmmc_init_spi(sdmmc_data_t *sdmmc)
{
	sdmmc_spi_port_init(sdmmc); // if not initialized, init it
#ifdef __SPI_SDMMC_DMA
	if (!sdmmc->dmaInit)
		config_spi_dma(sdmmc);
#endif

	sleep_ms(10);
}

/* Receive a sector data (512 bytes) */
static void sdmmc_read_spi_dma(
	BYTE *buff, /* Pointer to data buffer */
	UINT btr,	/* Number of bytes to receive (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, dummy_block, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btr, false);

	dma_channel_set_write_addr(sdmmc->read_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->read_dma_ch, btr, false);

	dma_start_channel_mask((1u << (sdmmc->read_dma_ch)) | (1u << (sdmmc->write_dma_ch)));
	dma_channel_wait_for_finish_blocking(sdmmc->read_dma_ch);
#else
	spi_read_blocking(sdmmc->spiPort, 0xFF, buff, btr);
#endif
}

#if FF_FS_READONLY == 0
/* Send a sector data (512 bytes) */
static void sdmmc_write_spi_dma(
	const BYTE *buff, /* Pointer to the data */
	UINT btx,		  /* Number of bytes to send (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btx, false);
	dma_channel_start(sdmmc->write_dma_ch);
	dma_channel_wait_for_finish_blocking(sdmmc->write_dma_ch);
#else
	spi_write_blocking(sdmmc->spiPort, buff, btx);
#endif
}
#endif

/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc)
{
	uint8_t dst;
	absolute_time_t timeout_time = make_timeout_time_ms(timeout);
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &dst, 1);
	} while (dst != 0xFF && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time)); /* Wait for card goes ready or timeout */

	return (dst == 0xFF) ? 1 : 0;
}

/*-----------------------------------------------------------------------*/
/* Deselect card and release SPI                                         */
/*-----------------------------------------------------------------------*/
static void sdmmc_deselect(sdmmc_data_t *sdmmc)
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_high(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
}

/*-----------------------------------------------------------------------*/
/* Select card and wait for ready                                        */
/*-----------------------------------------------------------------------*/
static int sdmmc_select(sdmmc_data_t *sdmmc) /* 1:OK, 0:Timeout */
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
	if (sdmmc_wait_ready(500, sdmmc))
		return 1; /* Wait for card ready */
	sdmmc_deselect(sdmmc);
	return 0; /* Timeout */
}

/*-----------------------------------------------------------------------*/
/* Receive a data packet from the MMC                                    */
/*-----------------------------------------------------------------------*/
// static
int sdmmc_read_datablock(			 /* 1:OK, 0:Error */
						 BYTE *buff, /* Data buffer */
						 UINT btr,	 /* Data block length (byte) */
						 sdmmc_data_t *sdmmc)
{
	BYTE token;
	absolute_time_t timeout_time = make_timeout_time_ms(200);
	do
	{ /* Wait for DataStart token in timeout of 200ms */
		spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	} while ((token == 0xFF) && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time));
	if (token != 0xFE)
		return 0; /* Function fails if invalid DataStart token or timeout */

	sdmmc_read_spi_dma(buff, btr, sdmmc);
	// Discard CRC
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	return 1; // Function succeeded
}

/*-----------------------------------------------------------------------*/
/* Send a data packet to the MMC                                         */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
// static
int sdmmc_write_datablock(					/* 1:OK, 0:Failed */
						  const BYTE *buff, /* Ponter to 512 byte data to be sent */
						  BYTE token,		/* Token */
						  sdmmc_data_t *sdmmc)
{
	BYTE resp;
	if (!sdmmc_wait_ready(500, sdmmc))
		return 0; /* Wait for card ready */
	// Send token : 0xFE--single block, 0xFC -- multiple block write start, 0xFD -- StopTrans
	spi_write_blocking(sdmmc->spiPort, &token, 1);
	if (token != 0xFD)
	{										   /* Send data if token is other than StopTran */
		sdmmc_write_spi_dma(buff, 512, sdmmc); /* Data */

		token = 0xFF;
		spi_write_blocking(sdmmc->spiPort, &token, 1); // Dummy CRC
		spi_write_blocking(sdmmc->spiPort, &token, 1);

		spi_read_blocking(sdmmc->spiPort, 0xFF, &resp, 1);
		// receive response token: 0x05 -- accepted, 0x0B -- CRC error, 0x0C -- Write Error
		if ((resp & 0x1F) != 0x05)
			return 0; /* Function fails if the data packet was not accepted */
	}
	return 1;
}
#endif

/*-----------------------------------------------------------------------*/
/* Send a command packet to the MMC                                      */
/*-----------------------------------------------------------------------*/
//static
BYTE sdmmc_send_cmd(			  /* Return value: R1 resp (bit7==1:Failed to send) */
						   BYTE cmd,  /* Command index */
						   DWORD arg, /* Argument */
						   sdmmc_data_t *sdmmc)
{
	BYTE n, res;
	BYTE tcmd[5];

	if (cmd & 0x80)
	{ /* Send a CMD55 prior to ACMD<n> */
		cmd &= 0x7F;
		res = sdmmc_send_cmd(CMD55, 0, sdmmc);
		if (res > 1)
			return res;
	}

	/* Select the card and wait for ready except to stop multiple block read */
	if (cmd != CMD12)
	{
		sdmmc_deselect(sdmmc);
		if (!sdmmc_select(sdmmc))
			return 0xFF;
	}

	/* Send command packet */
	tcmd[0] = 0x40 | cmd;		 // 0 1 cmd-index(6) --> 01xxxxxx(b)
	tcmd[1] = (BYTE)(arg >> 24); // 32 bits argument
	tcmd[2] = (BYTE)(arg >> 16);
	tcmd[3] = (BYTE)(arg >> 8);
	tcmd[4] = (BYTE)arg;
	spi_write_blocking(sdmmc->spiPort, tcmd, 5);
	n = 0x01; /* Dummy CRC + Stop */
	if (cmd == CMD0)
		n = 0x95; /* Valid CRC for CMD0(0) */
	if (cmd == CMD8)
		n = 0x87; /* Valid CRC for CMD8(0x1AA) */

	spi_write_blocking(sdmmc->spiPort, &n, 1);

	/* Receive command resp */
	if (cmd == CMD12)
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1); /* Diacard following one byte when CMD12 */
	n = 10;												  /* Wait for response (10 bytes max) */
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1);
	} while ((res & 0x80) && --n);

	return res; /* Return received response */
}

/*-----------------------------------------------------------------------*/
/* Initialize disk drive                                                 */
/*-----------------------------------------------------------------------*/
DSTATUS sdmmc_init(sdmmc_data_t *sdmmc)
{
	BYTE n, cmd, ty, src, ocr[4];

	sdmmc->Stat = STA_NOINIT;
	// low baudrate
	spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	for (n = 10; n; n--)
		spi_write_blocking(sdmmc->spiPort, &src, 1); // Send 80 dummy clocks
	sdmmc_spi_cs_high(sdmmc);

	ty = 0;
	if (sdmmc_send_cmd(CMD0, 0, sdmmc) == 1)
	{ /* Put the card SPI/Idle state, R1 bit0=1*/
		absolute_time_t timeout_time = make_timeout_time_ms(1000);
		if (sdmmc_send_cmd(CMD8, 0x1AA, sdmmc) == 1)
		{													 /* SDv2? */
			spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4); // R7(5 bytes): R1 read by sdmmc_send_cmd, Get the other 32 bit return value of R7 resp
			if (ocr[2] == 0x01 && ocr[3] == 0xAA)
			{ /* Is the card supports vcc of 2.7-3.6V? */
				while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(ACMD41, 1UL << 30, sdmmc))
					; /* Wait for end of initialization with ACMD41(HCS) */
				if ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(CMD58, 0, sdmmc) == 0)
				{ /* Check CCS bit in the OCR */
					spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4);
					ty = (ocr[0] & 0x40) ? CT_SDC2 | CT_BLOCK : CT_SDC2; /* Card id SDv2 */
				}
			}
		}
		else
		{ /* Not SDv2 card */
			if (sdmmc_send_cmd(ACMD41, 0, sdmmc) <= 1)
			{ /* SDv1 or MMC? */
				ty = CT_SDC1;
				cmd = ACMD41; /* SDv1 (ACMD41(0)) */
			}
			else
			{
				ty = CT_MMC3;
				cmd = CMD1; /* MMCv3 (CMD1(0)) */
			}
			while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(cmd, 0, sdmmc))
				;																										   /* Wait for end of initialization */
			if (!(0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) || sdmmc_send_cmd(CMD16, 512, sdmmc) != 0) /* Set block length: 512 */
				ty = 0;
		}
	}
	sdmmc->cardType = ty; /* Card type */
	sdmmc_deselect(sdmmc);
	if (ty)
	{ /* OK */
		// high baudrate
		printf("\nThe actual baudrate(SD/MMC):%d\n",spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_HIGH)); // speed high
		sdmmc->sectSize = 512;
		sdmmc->Stat &= ~STA_NOINIT; /* Clear STA_NOINIT flag */
	}
	else
	{ /* Failed */
		sdmmc->Stat = STA_NOINIT;
	}

	return sdmmc->Stat;
}

/* sdmmc_disk_initizlize, sdmmc_disk_read, sdmmc_disk_write, sdmmc_disk_status, sdmmc_disk_ioctl*/

/* sdmmc_disk_initialize*/
DSTATUS sdmmc_disk_initialize(sdmmc_data_t *sdmmc)
{
	if (!sdmmc->spiInit)
		sdmmc_init_spi(sdmmc); /* Initialize SPI */
	DSTATUS stat = sdmmc_init(sdmmc);

	return stat;
}
/* sdmmc disk status*/
DSTATUS sdmmc_disk_status(sdmmc_data_t *sdmmc)
{
	return sdmmc->Stat;
}

/* sdmmc disk read*/
DSTATUS sdmmc_disk_read(
	BYTE *buff,	  /* Pointer to the data buffer to store read data */
	LBA_t sector, /* Start sector number (LBA) */
	UINT count,	  /* Number of sectors to read (1..128) */
	sdmmc_data_t *sdmmc)
{
	DWORD sect = (DWORD)(sector);

	if (!count)
		return RES_PARERR; /* Check parameter */
	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check if drive is ready */

	if (!(sdmmc->cardType & CT_BLOCK))
		sect *= 512; /* LBA ot BA conversion (byte addressing cards) */
	if (count == 1)
	{												  /* Single sector read */
		if ((sdmmc_send_cmd(CMD17, sect, sdmmc) == 0) /* READ_SINGLE_BLOCK */
			&& sdmmc_read_datablock(buff, 512, sdmmc))
		{
			count = 0;
		}
	}
	else
	{ /* Multiple sector read */
		if (sdmmc_send_cmd(CMD18, sect, sdmmc) == 0)
		{ /* READ_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_read_datablock(buff, 512, sdmmc))
					break;
				buff += 512;
			} while (--count);
			sdmmc_send_cmd(CMD12, 0, sdmmc); /* STOP_TRANSMISSION */
		}
	}
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return count ? RES_ERROR : RES_OK; /* Return result */
}

DSTATUS sdmmc_disk_write(
	const BYTE *buff, /* Ponter to the data to write */
	LBA_t sector,	  /* Start sector number (LBA) */
	UINT count,		  /* Number of sectors to write (1..128) */
	sdmmc_data_t *sdmmc)
{
	DWORD sect = (DWORD)sector;
	if (!count)
		return RES_PARERR; /* Check parameter */
	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check drive status */
	if (sdmmc->Stat & STA_PROTECT)
		return RES_WRPRT; /* Check write protect */

	if (!(sdmmc->cardType & CT_BLOCK))
		sect *= 512; /* LBA ==> BA conversion (byte addressing cards) */

	if (count == 1)
	{												  /* Single sector write */
		if ((sdmmc_send_cmd(CMD24, sect, sdmmc) == 0) /* WRITE_BLOCK */
			&& sdmmc_write_datablock(buff, 0xFE, sdmmc))
		{
			count = 0;
		}
	}
	else
	{ /* Multiple sector write */
		if (sdmmc->cardType & CT_SDC)
			sdmmc_send_cmd(ACMD23, count, sdmmc); /* Predefine number of sectors */
		if (sdmmc_send_cmd(CMD25, sect, sdmmc) == 0)
		{ /* WRITE_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_write_datablock(buff, 0xFC, sdmmc))
					break;
				buff += 512;
			} while (--count);
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
		}
	}
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd

	return count ? RES_ERROR : RES_OK; /* Return result */
}

/* sdmmc disk ioctl*/
DSTATUS sdmmc_disk_ioctl(
	BYTE cmd,	/* Control command code */
	void *buff, /* Pointer to the conrtol data */
	sdmmc_data_t *sdmmc)
{
	DRESULT res;
	BYTE n, csd[16];
	DWORD st, ed, csize;
	LBA_t *dp;

	BYTE src = 0xFF;

	if (sdmmc->Stat & STA_NOINIT)
		return RES_NOTRDY; /* Check if drive is ready */

	res = RES_ERROR;
	switch (cmd)
	{
	case CTRL_SYNC: /* Wait for end of internal write process of the drive */
		if (sdmmc_select(sdmmc))
			res = RES_OK;
		break;
	case GET_SECTOR_COUNT: /* Get drive capacity in unit of sector (DWORD) */
		if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
		{
			if ((csd[0] >> 6) == 1)
			{ /* SDC CSD ver 2 */
				csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;
				*(LBA_t *)buff = csize << 10;
			}
			else
			{ /* SDC CSD ver 1 or MMC */
				n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
				csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
				*(LBA_t *)buff = csize << (n - 9);
			}
			res = RES_OK;
		}
		break;
	case GET_SECTOR_SIZE: // FF_MAX_SS != FX_MIN_SS
		//*(WORD*)buff=512; // SDHC, SDXC sector size is 512
		*(WORD *)buff = sdmmc->sectSize;

		res = RES_OK;
		break;
	case GET_BLOCK_SIZE: /* Get erase block size in unit of sector (DWORD) */
		if (sdmmc->cardType & CT_SDC2)
		{ /* SDC ver 2+ */
			if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
			{ /* Read SD status */
				spi_write_blocking(sdmmc->spiPort, &src, 1);
				if (sdmmc_read_datablock(csd, 16, sdmmc))
				{ /* Read partial block */
					for (n = 64 - 16; n; n--)
						spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);	/* Purge trailing data */
					*(DWORD *)buff = 16UL << (csd[10] >> 4);
					res = RES_OK;
				}
			}
		}
		else
		{ /* SDC ver 1 or MMC */
			if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
			{ /* Read CSD */
				if (sdmmc->cardType & CT_SDC1)
				{ /* SDC ver 1.XX */
					*(DWORD *)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
				}
				else
				{ /* MMC */
					*(DWORD *)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
				}
				res = RES_OK;
			}
		}
		break;

	case CTRL_TRIM: /* Erase a block of sectors (used when _USE_ERASE == 1) */
		if (!(sdmmc->cardType & CT_SDC))
			break; /* Check if the card is SDC */
		if (sdmmc_disk_ioctl(MMC_GET_CSD, csd, sdmmc))
			break; /* Get CSD */
		if (!(csd[10] & 0x40))
			break; /* Check if ERASE_BLK_EN = 1 */
		dp = buff;
		st = (DWORD)dp[0];
		ed = (DWORD)dp[1]; /* Load sector block */
		if (!(sdmmc->cardType & CT_BLOCK))
		{
			st *= 512;
			ed *= 512;
		}
		if (sdmmc_send_cmd(CMD32, st, sdmmc) == 0 && sdmmc_send_cmd(CMD33, ed, sdmmc) == 0 && sdmmc_send_cmd(CMD38, 0, sdmmc) == 0 && sdmmc_wait_ready(30000, sdmmc))
		{				  /* Erase sector block */
			res = RES_OK; /* FatFs does not check result of this command */
		}
		break;

		/* Following commands are never used by FatFs module */

	case MMC_GET_TYPE: /* Get MMC/SDC type (BYTE) */
		*(BYTE *)buff = sdmmc->cardType;
		res = RES_OK;
		break;

	case MMC_GET_CSD: /* Read CSD (16 bytes) */
		if (sdmmc_send_cmd(CMD9, 0, sdmmc) == 0 && sdmmc_read_datablock((BYTE *)buff, 16, sdmmc))
		{ /* READ_CSD */
			res = RES_OK;
		}
		break;

	case MMC_GET_CID: /* Read CID (16 bytes) */
		if (sdmmc_send_cmd(CMD10, 0, sdmmc) == 0 && sdmmc_read_datablock((BYTE *)buff, 16, sdmmc))
		{ /* READ_CID */
			res = RES_OK;
		}
		break;

	case MMC_GET_OCR: /* Read OCR (4 bytes) */
		if (sdmmc_send_cmd(CMD58, 0, sdmmc) == 0)
		{ /* READ_OCR */
			for (n = 0; n < 4; n++)
				*(((BYTE *)buff) + n) = spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);
			res = RES_OK;
		}
		break;

	case MMC_GET_SDSTAT: /* Read SD status (64 bytes) */
		if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
		{ /* SD_STATUS */
			spi_write_blocking(sdmmc->spiPort, &src, 1);
			if (sdmmc_read_datablock((BYTE *)buff, 64, sdmmc))
				res = RES_OK;
		}
		break;

	default:
		res = RES_PARERR;
	}

	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return res;
}

  • spi_sdmmc.h
/*
This library is derived from ChaN's FatFs - Generic FAT Filesystem Module.
*/
#ifndef SPI_SDMMC_H
#define SPI_SDMMC_H
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "ff.h"
#include "diskio.h"

//#define __SPI_SDMMC_DMA

/* SDMMC SPI pins*/
#define SDMMC_SPI_PORT spi1
#define SDMMC_PIN_MISO 12
#define SDMMC_PIN_CS   13
#define SDMMC_PIN_SCK  14
#define SDMMC_PIN_MOSI 15
/* ====================== */

/* MMC/SD command */
#define CMD0 (0)		   /* GO_IDLE_STATE */
#define CMD1 (1)		   /* SEND_OP_COND (MMC) */
#define ACMD41 (0x80 + 41) /* SEND_OP_COND (SDC) */
#define CMD8 (8)		   /* SEND_IF_COND */
#define CMD9 (9)		   /* SEND_CSD */
#define CMD10 (10)		   /* SEND_CID */
#define CMD12 (12)		   /* STOP_TRANSMISSION */
#define ACMD13 (0x80 + 13) /* SD_STATUS (SDC) */
#define CMD16 (16)		   /* SET_BLOCKLEN */
#define CMD17 (17)		   /* READ_SINGLE_BLOCK */
#define CMD18 (18)		   /* READ_MULTIPLE_BLOCK */
#define CMD23 (23)		   /* SET_BLOCK_COUNT (MMC) */
#define ACMD23 (0x80 + 23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24 (24)		   /* WRITE_BLOCK */
#define CMD25 (25)		   /* WRITE_MULTIPLE_BLOCK */
#define CMD32 (32)		   /* ERASE_ER_BLK_START */
#define CMD33 (33)		   /* ERASE_ER_BLK_END */
#define CMD38 (38)		   /* ERASE */
#define CMD55 (55)		   /* APP_CMD */
#define CMD58 (58)		   /* READ_OCR */


typedef struct {
    spi_inst_t *spiPort;
    bool spiInit;
    uint csPin;
    BYTE cardType;
    uint16_t sectSize;
#ifdef __SPI_SDMMC_DMA
    uint read_dma_ch;
    uint write_dma_ch;
    dma_channel_config dma_rc;
    dma_channel_config dma_wc;
    bool dmaInit;
#endif
    DRESULT Stat;
}sdmmc_data_t;


//BYTE sdmmc_init(sdmmc_data_t *sdmmc);
DSTATUS sdmmc_disk_initialize(sdmmc_data_t *sdmmc);
DSTATUS sdmmc_disk_status(sdmmc_data_t *sdmmc);
DSTATUS sdmmc_disk_read(BYTE *buff, LBA_t sector,	UINT count, sdmmc_data_t *sdmmc); 
DSTATUS sdmmc_disk_write(const BYTE *buff, LBA_t sector, UINT count, sdmmc_data_t *sdmmc);
DSTATUS sdmmc_disk_ioctl ( BYTE cmd, void *buff, sdmmc_data_t *sdmmc);

//static 
int sdmmc_read_datablock (BYTE *buff, UINT btr, sdmmc_data_t *sdmmc);
//static 
int sdmmc_write_datablock (const BYTE *buff, BYTE token, sdmmc_data_t *sdmmc);
//static 
BYTE sdmmc_send_cmd(BYTE cmd,  DWORD arg, sdmmc_data_t *sdmmc);

/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC3		0x01		/* MMC ver 3 */
#define CT_MMC4		0x02		/* MMC ver 4+ */
#define CT_MMC		0x03		/* MMC */
#define CT_SDC1		0x02		/* SDC ver 1 */
#define CT_SDC2		0x04		/* SDC ver 2+ */
#define CT_SDC		0x0C		/* SDC */
#define CT_BLOCK	0x10		/* Block addressing */
#endif 
  • 主程式碼:
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "stdlib.h"
#include "stdio.h"
#include "ff.h"
#include "diskio.h"
#include "string.h"
#include "spi_sdmmc.h"

#include "inttypes.h"

extern sdmmc_data_t *pSDMMC;
void test_sdmmc_raw_rw(uint8_t *buff) {
    if (pSDMMC == NULL){
       pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
        pSDMMC->csPin = SDMMC_PIN_CS;
        pSDMMC->spiPort = SDMMC_SPI_PORT;
        pSDMMC->spiInit=false;
        pSDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
        pSDMMC->dmaInit=false;
#endif
        printf("sdmmc_init:%0x\n", sdmmc_disk_initialize(pSDMMC));
    }
    printf("-------------------------------------------------------\n");
    printf("Being testing...\n");
    // WRITE SINGLE B:OCL
    printf("\n===============\n");
    printf("Directly write a single block of data to the SDMMC card, the address is 51200. \n");
#ifdef __SPI_SDMMC_DMA
    printf("with DMA:  ");
#endif
    printf("data is written:\n");
    printf("\n--------------");
    for (int i = 0; i < 512; i++){
        if (i % 80==0) printf("\n");
        printf("%c", buff[i]);
    } 
    if ((sdmmc_send_cmd(CMD24, 51200, pSDMMC) == 0)) {
                sdmmc_write_datablock(buff, 0xFE, pSDMMC);
    }
    printf("\n===============\n");
    memset(buff, 0xFF, 512);
    printf("clear buffer\n");
    // READ_SINGLE_BLOCK 
    printf("\n===============\n");
    printf("Directly read a single block of data from  the SDMMC card at address is 51200\n");
#ifdef __SPI_SDMMC_DMA
    printf("with DMA:  ");
#endif
    printf("data is read:\n");
    printf("\n--------------");
    if ((sdmmc_send_cmd(CMD17, 51200, pSDMMC) == 0)) {
            sdmmc_read_datablock(buff, 512, pSDMMC);
            for (int i = 0; i < 512; i++){
                if (i % 80==0) printf("\n");
                printf("%c", buff[i]);
            }        
                
    }
    printf("\n");

}

void test_sdmmc_flash_rw_fatfs(uint8_t *buff) {
    FRESULT res;
    FATFS fs0, fs1;
    FIL fil0, fil1;
    
    uint br;
    uint loop_count=100;

    absolute_time_t time1, time2;
   
   printf("-------------------------------------------------------\n");
    printf("Begin testing(read data from sdmmc and write to flash memory device)...\n");
    res = f_mount(&fs0, SDMMC_PATH, 1);
    if (res != FR_OK) {
        printf(" mount error\n"); return;
    }
    printf("mount SDMMC_PATH ok\n");

    res = f_mount(&fs1, W25Q_PATH, 1);
    if (res != FR_OK) {
        printf(" mount error\n"); return;
    }
    printf("mount W25Q_PATH ok\n");
 
    printf("=================\n");
#ifdef __SPI_SDMMC_DMA
    printf("With DMA:  ");
#else 
    printf("Without DMA:  ");
#endif 
    printf(" write 0.8MB data to SD/MMC card.\n");
    time1=get_absolute_time();
    res= f_open(&fil0, SDMMC_PATH"/tfile1.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open write error\n");
        return;
    }
    for (int i = 0; i < loop_count; i++)
        res = f_write(&fil0, buff, 8192, &br);
    f_close(&fil0);
    time2=get_absolute_time();
    printf("\nwrite total Time:%"PRId64"(us)\n\n", absolute_time_diff_us(time1,time2));

printf("=================\n");
#ifdef __SPI_SDMMC_DMA
    printf("With DMA:  ");
#else 
    printf("Without DMA:  ");
#endif 
    printf(" read 0.8MB data from SD/MMC card and write to Flash memory device.\n");
    time2=get_absolute_time();
    res = f_open(&fil1, W25Q_PATH"/tfile1.txt", FA_CREATE_ALWAYS|FA_WRITE);
    res = f_open(&fil0, SDMMC_PATH"/tfile1.txt",FA_READ );
    if (res != FR_OK) {
        printf("error\n");
        return;
    }
    for (int i = 0; i < loop_count; i++)
        f_read(&fil0, buff, 8192, &br);
        f_write(&fil1, buff, 8192, &br);
    f_close(&fil0);
    f_close(&fil1);
    time1=get_absolute_time();
    printf("\ntotal Time:%"PRId64"(us)\n\n", absolute_time_diff_us(time2,time1));

    f_unmount(SDMMC_PATH);
    f_unmount(W25Q_PATH);
    
}
void test_sdmmc_rw_with_fatfs(uint8_t *buff) {
    FRESULT res;
    FATFS fs0;
    FIL fil0;    
    uint br;
    uint loop_count=4096;

    res = f_mount(&fs0, SDMMC_PATH, 1);
    if (res != FR_OK) {
        printf(" mount error\n"); return;
    }
    printf("-------------------------------------------------------\n");
    printf("Begin testing(SD/MMC card read/write with filesystem support)...\n");
    printf("mount SDMMC_PATH ok\n");

    absolute_time_t time1, time2;

    printf("\n==================\n");
#ifdef __SPI_SDMMC_DMA
    printf("With DMA:  ");
#else 
    printf("Without DMA:  ");
#endif 
    printf("Write 32MB data to sd/mmc card.\n");

    time1=get_absolute_time();
    res= f_open(&fil0, SDMMC_PATH"/testfile.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open write error\n");
        return;
    }
    
    for (int i = 0; i < loop_count; i++) {
        if (i%10==0) {
            printf(".");
            gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
        }
        res = f_write(&fil0, buff, 8192, &br);
    }
        
    f_close(&fil0);

    time2=get_absolute_time();
    printf("\nwrite total Time:%"PRId64"(us)\n", absolute_time_diff_us(time1,time2));

    printf("\n==================\n");
#ifdef __SPI_SDMMC_DMA
    printf("With DMA:  ");
#else 
    printf("Without DMA:  ");
#endif 
    printf("Read 32MB data from sd/mmc card.\n");
    res = f_open(&fil0, SDMMC_PATH"/testfile.txt",FA_READ );
    if (res != FR_OK) {
        printf("error\n");
        return;
    }
    for (int i = 0; i < loop_count; i++) {
        if (i%10==0) {
            printf(".");
            gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
        } 
        
        f_read(&fil0, buff, 8192, &br);
    }
        

f_close(&fil0);

    time1=get_absolute_time();
    printf("\nread total Time:%"PRId64"(us)\n\n", absolute_time_diff_us(time2,time1));


    f_unmount(SDMMC_PATH);

    
}

void test_flash_rw_with_fatfs(uint8_t *buff) {
    FRESULT res;
    FATFS fs0;
    FIL fil0;    
    uint br;
    uint loop_count=256;

    res = f_mount(&fs0, W25Q_PATH, 1);
    if (res != FR_OK) {
        printf(" mount error\n"); return;
    }
    printf("-------------------------------------------------------\n");
    printf("Begin testing(Flash memory device read/write with filesystem support)...\n");
    printf("mount W25Q_PATH ok\n");

    absolute_time_t time1, time2;

    printf("\n==================\n");

    printf("Write 2MB data to Flash Memory device.\n");

    time1=get_absolute_time();
    res= f_open(&fil0, W25Q_PATH"/tfile2.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open write error\n");
        return;
    }
    
    for (int i = 0; i < loop_count; i++) {
        printf(".");
        gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
        res = f_write(&fil0, buff, 8192, &br);
    }
    f_close(&fil0);

    time2=get_absolute_time();
    printf("\nwrite total Time:%"PRId64"(us)\n", absolute_time_diff_us(time1,time2));

    printf("\n==================\n");

    printf("Read 2MB data from  Flash Memory device.\n");
    res = f_open(&fil0, W25Q_PATH"/tfile2.txt",FA_READ );
    if (res != FR_OK) {
        printf("error\n");
        return;
    }
    for (int i = 0; i < loop_count; i++) {
        printf(".");
        gpio_put(PICO_DEFAULT_LED_PIN, !gpio_get(PICO_DEFAULT_LED_PIN));
        f_read(&fil0, buff, 8192, &br);
    }
        

    f_close(&fil0);

    time1=get_absolute_time();
    printf("\nread total Time:%"PRId64"(us)\n\n", absolute_time_diff_us(time2,time1));


    f_unmount(W25Q_PATH);

    
}
int main()
{
    stdio_init_all();
    uint8_t buff[8192];
    uint8_t r;
    for (int i=0; i< 8192; i++) {
        r = rand()%126;
        if (r < 32) r+=32;
        buff[i] = r;
    }
    gpio_init(PICO_DEFAULT_LED_PIN);
    gpio_set_dir(PICO_DEFAULT_LED_PIN, true);

    //test_sdmmc_raw_rw(buff);

    test_sdmmc_rw_with_fatfs(buff);
    test_sdmmc_flash_rw_fatfs(buff);
    test_flash_rw_with_fatfs(buff);
    

    gpio_put(PICO_DEFAULT_LED_PIN, 0);

    
//puts("Hello, world!");

    return 0;
}