prettyprint

2023年11月1日 星期三

[Raspberry Pi Pico] Audio Recorder USB device using INMP441 I2S microphone

本文章介紹使用Raspberry Pi Pico的PIO功能,擷取INMP441 I2S microphone製作一台錄音機,將檔案存在SD卡上。連接USB Host時,可透過USB MSC device讀取錄音檔案。


一、測試項目:

  1. 根據inmp441 datasheet擷取24 bits的 32 bit frame, LSB 8bit 為0, 2 channels, 32000 sample rate.
  2. 擷取16 bit 的32 bit frame, LSB 16 bits補0, 2 channels, 32000 sample rate.
  3. 擷取16 bit 的16 bit frame, 2 channels, 44100 sample rate.
  4. 擷取16 bit 的16 bit frame, 1 channels, 44100 sample rate.

二、INMP441 datasheet節錄:


  1. The slave serial-data port’s format is I2S, 24-bit, two's complement.
  2. There must be 64 SCK cycles in each WS stereo frame, or 32 SCK cycles per data-word.
  3. The default data format is I2S (two’s complement), MSB-first. In this format, the MSB of each word is delayed by one SCK cycle from the start of each half-frame

三、程式說明:

使用兩個DMA channel擷取資料到8 KB RAM buffer, 每個DMA channel擷取資料後chan_to到另一個DMA channel,繼續擷取資料,此時將第一個DMA擷取的資料寫到SD上。

根據datasheet的clock時序圖,PIO程式碼如下。

實作後測試發現錄音檔音量非常小,檢視原始資料發現MSB均為00 or FF,
因為資料是2's complement,如下圖所示 00=+0, ff=-0,所以音量非常小。

因此,修正擷取資料程式,忽略MSB 8 bit(保留signed bit)只擷取16 bits,測試結果如「成果影片」所示。
詳細程式碼附於文章末尾。

四、成果影片:


五、程式碼:


有關msc_device_disk與spi_sdmmc詳細說明可參考前篇文章說明。
  • CMakeLists.txt(pico_audio_recorder)
add_library(pico_audio_recorder INTERFACE)
pico_generate_pio_header(pico_audio_recorder ${CMAKE_CURRENT_LIST_DIR}/pico_audio_recorder.pio)
target_sources(pico_audio_recorder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/pico_audio_recorder.c
)

target_include_directories(pico_audio_recorder INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(pico_audio_recorder INTERFACE
        hardware_dma
        hardware_pio
        hardware_rtc
        pico_stdlib
)

  • pico_audio_recorder.c
#include "stdio.h"
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "string.h"
#include "inttypes.h"
#include "pico_audio_recorder.pio.h"
#include "pico_audio_recorder.h"
#include "tusb.h"
#include "hardware/clocks.h"
#include "hardware/rtc.h"
#include "spi_sdmmc.h"

static int32_t buff1[PIO_DMA_BUFFER_SIZE/4];
static int32_t buff2[PIO_DMA_BUFFER_SIZE/4];
static int dma_read_buff1_channel;
static int dma_read_buff2_channel;
static int32_t *fw_ptr = buff1;
static bool new_read_data=false;
static uint8_t recorder_state=STATE_STOP;

static uint16_t channels = INMP441_CHANNEL_MONO;
static uint16_t bitPerSample = INMP441_BIT_PER_SAMPLE_16;
static uint32_t sampleRate = INMP441_SAMPLE_RATE_44_1K;


void start_stop_button_cb(uint gpio, uint32_t event_mask) {

  if (gpio == BUTTON_PIN) {
    if (event_mask == GPIO_IRQ_EDGE_RISE) { 
        gpio_acknowledge_irq(BUTTON_PIN, GPIO_IRQ_EDGE_RISE);
        switch (get_recorder_state()) {
          case STATE_STOP:
            set_recorder_state(STATE_START_RECORDING);
          break;
          case STATE_RECORDING:
            set_recorder_state(STATE_STOPING);
          break;
          case STATE_START_RECORDING:
          break;
          case STATE_STOPING:
          break;

        }
    }
  
    
  }
}

void pio_irq_read_data() {
     if (pio_interrupt_get(INMP441_pio, 0)) {
        pio_interrupt_clear(INMP441_pio, 0);
        printf("irq 0:\n");
        //dma_channel_start(dma_read_buff1_channel);
     }
     if (pio_interrupt_get(INMP441_pio, 1)) {
        pio_interrupt_clear(INMP441_pio, 1);
        printf("irq:1\n");
     }
}


void dma_handler() {
   if (dma_channel_get_irq1_status(dma_read_buff1_channel)) {
      dma_channel_acknowledge_irq1(dma_read_buff1_channel);
      dma_channel_set_write_addr(dma_read_buff1_channel, buff1, false);
      fw_ptr = buff1;
      new_read_data=true;
   }
   if (dma_channel_get_irq1_status(dma_read_buff2_channel)) {
      dma_channel_acknowledge_irq1(dma_read_buff2_channel);
      dma_channel_set_write_addr(dma_read_buff2_channel, buff2, false);
      fw_ptr = buff2;
      new_read_data=true;
   }
}

void set_pin_LED(bool green) {
   if (green) {
      gpio_put(GREEN_PIN, true);
      gpio_put(RED_PIN, false);
   } else {
      gpio_put(GREEN_PIN, false);
      gpio_put(RED_PIN, true);
   }
}

/*!
* \brief sdio memory card pio initialize
* \param pio: pio number
* \param sm state machine
* \param cmd_pin command pin
* \param clk_pin CLK pin
* \param data_pin_base data 0~4 pins(D0~D3)
* \param clk_int Integer part of the divisor
* \param clk_frac – Fractional part in 1/256ths
*/
void inmp441_pio_init(PIO pio,  uint sm, uint sd_pin, uint sideset_pin) { // sideset_pin ws_clk
  
    pio_gpio_init(pio, sd_pin);
    pio_gpio_init(pio, sideset_pin);
    pio_gpio_init(pio, sideset_pin+1);
    gpio_pull_down(sd_pin);
    //gpio_pull_up(sideset_pin);
    //gpio_pull_up(sideset_pin+1);
    
    //== INMP441 SM ==
    uint offset = pio_add_program(pio, &inmp441_program);
    pio_sm_config c = inmp441_program_get_default_config(offset);
    pio_sm_set_consecutive_pindirs(pio, sm, sd_pin, 1, false);
    pio_sm_set_consecutive_pindirs(pio, sm, sideset_pin, 2, true); // WS, SCK
    sm_config_set_in_pins(&c, sd_pin);
    sm_config_set_set_pins(&c, sideset_pin,2);
    sm_config_set_sideset_pins(&c, sideset_pin);
    
    sm_config_set_in_shift(&c, false, true, bitPerSample);// 16 or 32
   
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
    float div = clock_get_hz(clk_sys)/(sampleRate*64*2); // 64 clocks, 2 PIO clocks
    sm_config_set_clkdiv(&c, div);
 
    pio_sm_init(pio, sm, offset, &c);

      // initial state machine but not run
    pio_sm_set_enabled(pio, sm, false);

    //** for test only
    uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    //pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    irq_add_shared_handler(pio_irq, pio_irq_read_data, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq, true);
   
    uint pio_base = (pio==pio0)?PIO0_BASE:PIO1_BASE;

    //==== READ DMA ===
    dma_read_buff1_channel = dma_claim_unused_channel(true);
    dma_read_buff2_channel = dma_claim_unused_channel(true);

    dma_channel_config dc1 = dma_channel_get_default_config(dma_read_buff1_channel);
    channel_config_set_write_increment(&dc1, true);
    channel_config_set_read_increment(&dc1, false);
    channel_config_set_dreq(&dc1, pio_get_dreq(pio, sm, false));
    channel_config_set_chain_to(&dc1, dma_read_buff2_channel);  
    //  for 32 bit frame
    if (bitPerSample == INMP441_BIT_PER_SAMPLE_32) {
         channel_config_set_transfer_data_size(&dc1, DMA_SIZE_32); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff1_channel,
                  &dc1, buff1, (void*) (pio_base+PIO_RXF0_OFFSET),  // 
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
    }
  // for 16 bit frame
  if (bitPerSample == INMP441_BIT_PER_SAMPLE_16) {
         channel_config_set_transfer_data_size(&dc1, DMA_SIZE_16); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff1_channel,
               &dc1, buff1, (void*) (pio_base+PIO_RXF0_OFFSET),  // 
               PIO_DMA_BUFFER_SIZE>> DMA_SIZE_16, false); //DMA_SIZE_8 or 16 or 32
  }

   dma_channel_config dc2 = dma_channel_get_default_config(dma_read_buff2_channel);
    channel_config_set_write_increment(&dc2, true);
    channel_config_set_read_increment(&dc2, false);
    //channel_config_set_bswap(&dc2, true);
    channel_config_set_dreq(&dc2, pio_get_dreq(pio, sm, false));
    channel_config_set_chain_to(&dc2, dma_read_buff1_channel); 

   // for 32 bit frame
   if (bitPerSample == INMP441_BIT_PER_SAMPLE_32) {
         channel_config_set_transfer_data_size(&dc2, DMA_SIZE_32); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff2_channel,
                  &dc2, buff2, (void*) (pio_base+PIO_RXF0_OFFSET),  // SDIO_DATA_READ_SM = 1
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
   }
   // for 16 bit frame
   if (bitPerSample == INMP441_BIT_PER_SAMPLE_16) {
         channel_config_set_transfer_data_size(&dc2, DMA_SIZE_16); //DMA_SIZE_8,16,32
         dma_channel_configure(dma_read_buff2_channel,
                  &dc2, buff2, (void*) (pio_base+PIO_RXF0_OFFSET),  // SDIO_DATA_READ_SM = 1
                  PIO_DMA_BUFFER_SIZE>> DMA_SIZE_16, false); //DMA_SIZE_8 or 16 or 32
   }

   dma_channel_set_irq1_enabled(dma_read_buff1_channel, true);
   dma_channel_set_irq1_enabled(dma_read_buff2_channel, true);

    // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
    //irq_set_exclusive_handler(DMA_IRQ_1, dma_handler);
    irq_add_shared_handler(DMA_IRQ_1, dma_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(DMA_IRQ_1, true);
   
   gpio_init(BUTTON_PIN);
   gpio_set_dir(BUTTON_PIN, false);
   gpio_pull_down(BUTTON_PIN);
   gpio_init(RED_PIN);
   gpio_set_dir(RED_PIN, true);
   gpio_init(GREEN_PIN);
   gpio_set_dir(GREEN_PIN, true);
   recorder_state = STATE_STOP;
   set_pin_LED(true);

   gpio_set_irq_enabled_with_callback(BUTTON_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, start_stop_button_cb);
}

bool write_file_wave_header(FIL *fil) {
   UINT bw;
	WAVE_HEADER wave_header;
	wave_header.riff[0] = 'R';wave_header.riff[1] = 'I';
	wave_header.riff[2] = 'F';wave_header.riff[3] = 'F';
	wave_header.size = (uint32_t)0;
	wave_header.wave[0] = 'W';wave_header.wave[1] = 'A';
	wave_header.wave[2] = 'V';wave_header.wave[3] = 'E';
	wave_header.fmt[0] = 'f';wave_header.fmt[1] = 'm';
	wave_header.fmt[2] = 't';wave_header.fmt[3] = ' ';
	wave_header.fmt_size = 16;
	wave_header.format = 1; // PCM
	wave_header.channels = channels; // channels
	wave_header.sampleRate=sampleRate;  // sample rate
	wave_header.rbc = sampleRate*bitPerSample*channels/8;
	wave_header.bc =  bitPerSample*channels/8;
	wave_header.bitsPerSample = bitPerSample; //bitsPerSample
	wave_header.data[0] = 'd'; wave_header.data[1] = 'a';
	wave_header.data[2] = 't'; wave_header.data[3] = 'a';
	wave_header.data_size = 0;
	if (f_write(fil, (uint8_t*)&wave_header, sizeof(wave_header), &bw)!= FR_OK) return false;
   return true;
}
bool modify_file_wave_header(FIL *fil, uint32_t data_length) {
   uint bw;
	uint32_t total_len = data_length+36;
	f_lseek(fil, 4);
	f_write(fil, (uint8_t*)&total_len, 4, &bw);
	f_lseek(fil, 40);
	f_write(fil, (uint8_t*)&data_length, 4, &bw);

   return true;
}

void inmp441_starting_recording_to_file_wav() { 
   uint32_t wav;
   uint bw;
   FIL fil;
   uint8_t filename[100];
   datetime_t t;
   FRESULT res;
   uint32_t data_length;
   FATFS fs;
   
   rtc_get_datetime(&t);
   tud_disconnect();
   sleep_ms(1);
   if (f_mount(&fs, SDMMC_PATH,1) != FR_OK) {
      tud_connect();
      sleep_ms(1);
      return;
   }
   
   sprintf(filename, "%s/V%02d%02d%02d-%02d%02d%02d.wav", SDMMC_PATH,t.year%100,t.month,t.day,t.hour,t.min,t.sec);
   if (f_open(&fil, filename, FA_CREATE_ALWAYS|FA_WRITE) != FR_OK) {
      printf("open file error:%s\n", filename);
      tud_connect();
      recorder_state = STATE_STOP;
      set_pin_LED(true);
      return;
   }

   if (!write_file_wave_header(&fil)) {
      tud_connect();
      recorder_state = STATE_STOP;
      set_pin_LED(true);
      return;
   }

   recorder_state = STATE_RECORDING;
   set_pin_LED(false);
   
   pio_sm_exec(INMP441_pio, INMP441_SM, pio_encode_mov(pio_isr, pio_null)); // enpty ISR
   pio_sm_clear_fifos(INMP441_pio, INMP441_SM);
   pio_sm_exec(INMP441_pio, INMP441_SM, pio_encode_jmp(inmp441_offset_inmp441_start));
   //dma_channel_start(dma_read_buff1_channel);
   dma_channel_set_write_addr(dma_read_buff2_channel, buff2, false);
   dma_channel_set_write_addr(dma_read_buff1_channel, buff1, true);
   pio_sm_set_enabled(INMP441_pio, INMP441_SM, true);

   new_read_data=false;
   
   sleep_ms(300);
   data_length=0;
   while (recorder_state == STATE_RECORDING) { 
      if (new_read_data) {
         new_read_data=false;
         f_write(&fil, (uint8_t*)fw_ptr, PIO_DMA_BUFFER_SIZE, &bw);
         data_length += PIO_DMA_BUFFER_SIZE;
      }
        
   } 
   modify_file_wave_header(&fil, data_length);
   f_close(&fil);
   f_unmount(SDMMC_PATH);

   pio_sm_set_enabled(INMP441_pio, INMP441_SM, false);
      
   dma_channel_abort(dma_read_buff1_channel);
   dma_channel_abort(dma_read_buff2_channel);
   
   
   recorder_state = STATE_STOP;
   set_pin_LED(true);
printf("Stop recording\n");
   tud_connect();
}

uint8_t get_recorder_state() {
   return recorder_state;
}
void    set_recorder_state(uint8_t state) {
   recorder_state = state;
}
  • pico_audio_recorder.h

#ifndef __PICO_AUDIO_RECORDER_
#define __PICO_AUDIO_RECORDER_
#include "hardware/pio.h"
#include "ff.h"

#define INMP441_pio         	pio0
#define INMP441_SM          	0

#define INMP441_SCK          	18
#define INMP441_SD           	20

#define INMP441_CHANNEL_STEREO	2
#define INMP441_CHANNEL_MONO	1
#define INMP441_SAMPLE_RATE_32K  	32000
#define INMP441_SAMPLE_RATE_44_1K  	44100

#define INMP441_BIT_PER_SAMPLE_32	32
#define INMP441_BIT_PER_SAMPLE_16	16

#define BUTTON_PIN      		21
#define RED_PIN					16
#define GREEN_PIN				17

#define PIO_DMA_BUFFER_SIZE 8192

enum RECORDER_STATE{
    STATE_STOP=0,
	STATE_STOPING,
    STATE_START_RECORDING,
    STATE_RECORDING,
};

typedef struct _WaveHeader{
	char riff[4];
	uint32_t size;
	char wave[4];
	char fmt[4];
	uint32_t fmt_size;
	uint16_t format; //1:PCM
	uint16_t channels; // channels
	uint32_t sampleRate;  // sample rate
	uint32_t rbc;//sampleRate*bitsPerSample*channels/8
	uint16_t bc; //bitsPerSample*channels/8
	uint16_t bitsPerSample; //bitsPerSample
	char data[4];
	uint32_t data_size;
} WAVE_HEADER;

void inmp441_pio_init(PIO pio,  uint sm, uint sd_pin, uint sideset_pin);
void inmp441_starting_recording_to_file_wav();
uint8_t get_recorder_state();
void    set_recorder_state(uint8_t state);

#endif

  • pico_audio_recorder.pio

/*
;=======  (stereo) get 16 bit in 16 bit frame =======
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
    set y, 6                    side 0b00  ; ignore 1+8 bit
    nop                         side 0b01  ; one clock delay
    nop                         side 0b00
    in pins, 1                  side 0b01  ; MSB signed bit
left_idle:          
    nop                         side 0b00
    jmp y--, left_idle          side 0b01  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b00 ; other 15 bits
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00  ; 9+15 clocks

    set y, 7                    side 0b01 ; last 8 clock
left_idle_1:          
    nop                         side 0b00
    jmp y--, left_idle_1        side 0b01 ; cycle:9+15+8=32


; right channel
    set y, 6                    side 0b10  ; ignore 1+8 bit
    nop                         side 0b11  ; one clock delay
    nop                         side 0b10
    in pins, 1                  side 0b11  ; MSB signed bit
right_idle:          
    nop                         side 0b10
    jmp y--, right_idle          side 0b11  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b10
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop  side 0b10  ; 9+15 clocks

    set y, 7                    side 0b11 
right_idle_1:          
    nop                         side 0b10
    jmp y--, right_idle_1        side 0b11 ; cycle:9+15+8

.wrap
;=======  (stereo) get 16 bit in 16 bit frame =======
*/




;=======  (mono, left channel) get 16 bit in 16 bit frame =======
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
    set y, 6                    side 0b00  ; ignore 1+8 bit
    nop                         side 0b01  ; one clock delay
    nop                         side 0b00
    in pins, 1                  side 0b01  ; MSB signed bit
left_idle:          
    nop                         side 0b00
    jmp y--, left_idle          side 0b01  ; one clock delay+MSB signed bit+7 ignore bit=9 bits

    set x, 14                   side 0b00
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00  ; 9+15 clocks

    set y, 7                    side 0b01 
left_idle_1:          
    nop                         side 0b00
    jmp y--, left_idle_1        side 0b01 ; cycle:9+15+8

;right channel 
    set y, 30                   side 0b10
right_idle_loop:
    nop                         side 0b11
    jmp y--, right_idle_loop    side 0b10
    nop                         side 0b11
.wrap
;=======  (mono, left channel) =======


/*
;======= shift out 7 bit and get 16 bit in 32 bit frame =======
.define IGNORE_MSB_BITS  (6+1)   ; one bit delay
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:

.wrap_target
; left channel
    set y, (IGNORE_MSB_BITS-2)  side 0b00
left_idle:          
    nop                         side 0b01
    jmp y--, left_idle          side 0b00
    nop                         side 0b01

    set x, 15                   side 0b00 ; ignore msb 8 bits and capture the other 16 bits
left_channel_loop:
    in  pins, 1                 side 0b01
    jmp x--, left_channel_loop  side 0b00
    nop                         side 0b01

    set y, (13-IGNORE_MSB_BITS) side 0b00
left_idle_1:          
    nop                         side 0b01
    jmp y--, left_idle_1          side 0b00
    in NULL, 16                 side 0b01  


; right channel
    set y, (SHIFT_OUT_BITS-2)  side 0b10
right_idle:          
    nop                         side 0b11
    jmp y--, right_idle          side 0b10
    nop                         side 0b11

    set x, 15                   side 0b10
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop  side 0b10
    nop                         side 0b11

    set y, (13-SHIFT_OUT_BITS) side 0b10
right_idle_1:          
    nop                         side 0b11
    jmp y--, right_idle_1          side 0b10
    in NULL, 16                 side 0b11  
.wrap
;======= shift out 7 bit and get 16 bit in 32 bit frame =======
*/


/*
;====== (STEREO) INMP441 I2S 32 bit per sample =======================
.program inmp441
.origin 0
.side_set 2  ; ws/clk
public inmp441_start:
.wrap_target
; left channel
set x, 22                       side 0b00
nop                             side 0b01 ;delay one SCK clock
nop                             side 0b00 
left_channel_loop:
    in  pins, 1                 side 0b01 ; ; 24 bits 
    jmp x--, left_channel_loop  side 0b00
    in pins, 1                  side 0b01

    set y, 5                    side 0b00 ; 7 SCK clocks
left_idle:          
    nop                         side 0b01
    jmp y--, left_idle          side 0b00
    in NULL, 8                  side 0b01 

; right channel
set x, 22                       side 0b10
nop                             side 0b11
nop                             side 0b10 
right_channel_loop:
    in  pins, 1                 side 0b11
    jmp x--, right_channel_loop side 0b10
    in pins, 1                  side 0b11

    set y, 5                    side 0b10
right_idle:          
    nop                         side 0b11
    jmp y--, right_idle         side 0b10
    in NULL, 8                  side 0b11  
.wrap

*/
  • glue.c

#include "stdio.h"
#include "pico/stdlib.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"
#include "msc_storage_driver.h"
#include "spi_sdmmc.h"
#include "hardware/rtc.h"
#include "inttypes.h"
#include "hardware/gpio.h"


#define SDMMC_DRV_0     0
sdmmc_data_t *pSDMMC=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;
        } else {
            return RES_OK;
        }
           
        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;
        
    }
    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;
        
    }
	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;
        
    }
	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;
        
    }
	return RES_PARERR;
}

DWORD get_fattime(void) {
    datetime_t t ;
    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)
    uint32_t yr = t.year - 1980;
    fattime |= (yr & 0b01111111) << 25;
    // bit24:21
    // Month (1..12)
    uint32_t mo = t.month;
    fattime |= ( mo & 0b00001111) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint32_t da = t.day;
    fattime |= (da & 0b00011111) << 16;
    // bit15:11
    // Hour (0..23)
    uint32_t hr = t.hour;
    fattime |= (hr & 0b00011111) << 11;
    // bit10:5
    // Minute (0..59)
    uint32_t mi = t.min;
    fattime |= ( mi&0b00111111) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint32_t sd = t.sec / 2;
    fattime |= (sd&0b00011111);
    return fattime;
}

void led_blinking(void)
{
    static absolute_time_t  t1;
    static bool state=false;
        
    // Blink every interval ms
    if ( absolute_time_diff_us(t1, get_absolute_time()) < 100000) return; // not enough time
    t1 = get_absolute_time();
    gpio_put(LED_BLINKING_PIN, state);
    state = !state;
}

void led_blinking_off(void) {
    gpio_put(LED_BLINKING_PIN, false);
}

  • 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"
#include "msc_storage_driver.h"


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

static uint8_t dummy_block[SDMMC_SECT_SIZE];

void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc);
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc);
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc);
static void sdmmc_init_spi(sdmmc_data_t *sdmmc);

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 */
}

uint64_t sdmmc_get_sector_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint64_t sectorCounter;

	uint8_t src = 0xFF;
	uint8_t sc = sdmmc_send_cmd(CMD9, 0, sdmmc);
	int rd = sdmmc_read_datablock(csd, 16, sdmmc);
	
	if ((sc == 0) && rd)
	{
		if ((csd[0] >> 6) == 1)
		{ /* SDC CSD ver 2 */
			csize = csd[9] + ((uint16_t)csd[8] << 8) + ((uint32_t)(csd[7] & 63) << 16) + 1;
			sectorCounter = 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) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
			sectorCounter = csize << (n - 9);
		}
	
	} else {
		sectorCounter=0;
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint32_t sdmmc_get_block_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint32_t sectorCounter=0;

	uint8_t src = 0xFF;

	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 */
				sectorCounter = 16UL << (csd[10] >> 4);
			}
		}
	}
	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 */
				sectorCounter = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
			}
			else
			{ /* MMC */
				sectorCounter = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
			}

		}
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint8_t  msc_sdmmc_read_sector(uint32_t sector, uint8_t* buff, uint32_t len, sdmmc_data_t *sdmmc) {
	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize) + 1) : (len / sdmmc->sectSize);
	
	if (!count)
		return ret; /* Check parameter */

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

	return ret;
}

uint8_t msc_sdmmc_write_sector(uint32_t sector, uint8_t *buff, uint32_t len, sdmmc_data_t *sdmmc) {

	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize)+1) : (len / sdmmc->sectSize);

	if (!count)
		return ret; /* 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))
		sector *= sdmmc->sectSize; /* LBA ==> BA conversion (byte addressing cards) */

	if (count == 1)
	{												  /* Single sector write */
		if ((sdmmc_send_cmd(CMD24, sector, sdmmc) == 0) /* WRITE_BLOCK */
			&& sdmmc_write_datablock(buff, 0xFE, sdmmc))
		{
			ret = 1;
		}
led_blinking_task();  //// LED_blinking
	}
	else
	{ /* Multiple sector write */
		if (sdmmc->cardType & CT_SDC)
			sdmmc_send_cmd(ACMD23, count, sdmmc); /* Predefine number of sectors */
		if (sdmmc_send_cmd(CMD25, sector, sdmmc) == 0)
		{ /* WRITE_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_write_datablock(buff, 0xFC, sdmmc))
					break;
				buff += sdmmc->sectSize;

led_blinking_task();  //// LED_blinking
			} while (--count);
			  // LED blinking off
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
			ret =1;
		}
	}
led_blinking_task_off();
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd

}

/* sdmmc spi port initialize*/
void sdmmc_spi_port_init(sdmmc_data_t *sdmmc)
{
	sdmmc->spiPort = SDMMC_SPI_PORT;
	sdmmc->csPin = SDMMC_PIN_CS;
	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 < SDMMC_SECT_SIZE; 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 uint8_ts) */
static void sdmmc_read_spi_dma(
	uint8_t *buff, /* Pointer to data buffer */
	uint btr,	/* Number of uint8_ts 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
}


/* Send a sector data (512 uint8_ts) */
static void sdmmc_write_spi_dma(
	const uint8_t *buff, /* Pointer to the data */
	uint btx,		  /* Number of uint8_ts 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
}


/*-----------------------------------------------------------------------*/
/* 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                                         */
/*-----------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Receive a data packet from the MMC                                    */
/*-----------------------------------------------------------------------*/
// static
int sdmmc_read_datablock(			 /* 1:OK, 0:Error */
						 uint8_t *buff, /* Data buffer */
						 uint btr,	 /* Data block length (uint8_t) */
						 sdmmc_data_t *sdmmc)
{
	uint8_t 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 uint8_t *buff, /* Ponter to 512 uint8_t data to be sent */
						  uint8_t token,		/* Token */
						  sdmmc_data_t *sdmmc)
{
	uint8_t 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, sdmmc->sectSize, 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
uint8_t sdmmc_send_cmd(			  /* Return value: R1 resp (bit7==1:Failed to send) */
						   uint8_t cmd,  /* Command index */
						   uint32_t arg, /* Argument */
						   sdmmc_data_t *sdmmc)
{
	uint8_t n, res;
	uint8_t tcmd[5];

	if (cmd & 0x80)
	{ /* Send a CMD55 prior to ACMD */
		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] = (uint8_t)(arg >> 24); // 32 bits argument
	tcmd[2] = (uint8_t)(arg >> 16);
	tcmd[3] = (uint8_t)(arg >> 8);
	tcmd[4] = (uint8_t)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 uint8_t when CMD12 */
	n = 10;												  /* Wait for response (10 uint8_ts max) */
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1);
	} while ((res & 0x80) && --n);

	return res; /* Return received response */
}

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

	sdmmc->Stat = RES_OK;
	// 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 uint8_ts): 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, SDMMC_SECT_SIZE, 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 = SDMMC_SECT_SIZE;
		sdmmc->Stat = RES_OK; /* Clear STA_NOINIT flag */
	}
	else
	{ /* Failed */
		sdmmc->Stat = STA_NOINIT;
	}
	sdmmc->sectCount = sdmmc_get_sector_count(sdmmc);
	return sdmmc->Stat;
}
/////////////////////////////////////////////
/* 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;
		}
		//led_blinking(); //// LED blinking
	}
	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;
				//led_blinking(); //// LED blinking
			} while (--count);
			
			sdmmc_send_cmd(CMD12, 0, sdmmc); /* STOP_TRANSMISSION */
		}
	}
	//led_blinking_off();  //// LED blinking off
	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;
		}
		//led_blinking();  //// LED_blinking
	}
	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;

				//led_blinking();  //// LED_blinking
			} while (--count);
			  // LED blinking off
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
		}
	}
	//led_blinking_off();
	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;
}


  • msc_device_disk.c

/* this file was modified from tinyUSB example: msc_disk_dual.c*/

/* 
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ha Thach (tinyusb.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
#include "bsp/board.h"
#include "tusb.h"
#include "stdlib.h"

#include "spi_sdmmc.h"
#include "hardware/gpio.h"
#include "msc_storage_driver.h"

extern sdmmc_data_t *pSDMMC;

void storage_driver_init() {
  if (pSDMMC == NULL) {
      // SDMMC driver initialize
      pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
      pSDMMC->spiInit=false;
#ifdef __SPI_SDMMC_DMA
      pSDMMC->dmaInit=false;
#endif
      sdmmc_disk_initialize(pSDMMC);
  }
  sleep_ms(10);
  // LED blinking when reading/writing
  gpio_init(LED_BLINKING_PIN);
  gpio_set_dir(LED_BLINKING_PIN, true);
}

#if CFG_TUD_MSC

// Invoked to determine max LUN
uint8_t tud_msc_get_maxlun_cb(void)
{
  return 1; // LUN 0: SDMMC, LUN 1: W25Q Flash
}

// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
  switch (lun) {
    case SDMMC_LUN:
      sprintf(vendor_id  , "SDMMC");
      sprintf(product_id , "Mass Storage");
      sprintf(product_rev, "1.0");
    break;
  }
 
}

// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
  //if ( lun == 1 && board_button_read() ) return false;

  return true; // RAM disk is always ready
}

// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
{
  switch(lun) {
    case SDMMC_LUN:
        *block_count = pSDMMC->sectCount;
        *block_size  = pSDMMC->sectSize;
    break;

  }
}

// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
  
  (void) lun;
  (void) power_condition;

  if ( load_eject )
  {
    if (start)
    {
printf("start\n");
      // load disk storage
    }else
    {
printf("stop\n");
      // unload disk storage
    }
  }

  return true;
}

// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
  switch(lun) {
    case SDMMC_LUN:
      if (!msc_sdmmc_read_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;

  }
  return (int32_t) bufsize;
}

bool tud_msc_is_writable_cb (uint8_t lun)
{
  (void) lun;

#ifdef CFG_EXAMPLE_MSC_READONLY
  return false;
#else
  return true;
#endif
}

// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
{

  switch (lun) {
    case SDMMC_LUN:
        if (!msc_sdmmc_write_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;
  }
  

  return (int32_t) bufsize;
}

// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize)
{
  // read10 & write10 has their own callback and MUST not be handled here

  void const* response = NULL;
  int32_t resplen = 0;

  // most scsi handled is input
  bool in_xfer = true;

  switch (scsi_cmd[0])
  {
    default:
      // Set Sense = Invalid Command Operation
      tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);

      // negative means error -> tinyusb could stall and/or response with failed status
      resplen = -1;
    break;
  }

  // return resplen must not larger than bufsize
  if ( resplen > bufsize ) resplen = bufsize;

  if ( response && (resplen > 0) )
  {
    if(in_xfer)
    {
      memcpy(buffer, response, (size_t) resplen);
    }else
    {
      // SCSI output
    }
  }

  return resplen;
}

#endif

void led_blinking_task(void)
{
  static uint32_t start_ms = 0;
  static bool led_state = false;

  // Blink every interval ms
  if ( board_millis() - start_ms < 50) return; // not enough time
  start_ms += 50;

  gpio_put(LED_BLINKING_PIN,led_state);
  led_state = 1 - led_state; // toggle
}

void led_blinking_task_off(void) {
  gpio_put(LED_BLINKING_PIN,false);
}

  • main.c


#include 
#include 
#include 
#include "pico/stdlib.h"

#include "bsp/board.h"
#include "tusb.h"

#include "msc_storage_driver.h"

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


/*------------- MAIN -------------*/
int main(void)
{
  stdio_init_all();
  rtc_init();

  datetime_t t = {
    .year=2023, .month=10, .day=29, 
    .dotw=6,
    .hour=14, .min=05, .sec=50
  }; 
  
   if (!rtc_set_datetime(&t)) printf("set rtc error\n");
     
  storage_driver_init();
  
  board_init();
  // init device stack on configured roothub port
  tud_init(BOARD_TUD_RHPORT);

  inmp441_pio_init(INMP441_pio, INMP441_SM, INMP441_SD, INMP441_SCK);

  while (1)
  {
      if (get_recorder_state() == STATE_START_RECORDING) {
        printf("Start recording\n");
        inmp441_starting_recording_to_file_wav();
      }
    
             
      tud_task(); // tinyusb device task
      tight_loop_contents();
  }

  return 0;
}

  • CMakeLists.txt(root)

# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

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

set(PICO_BOARD pico CACHE STRING "Board type")

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

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(audio_recorder C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

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

add_executable(audio_recorder 
        main.c 
        usb_descriptors.c)

pico_set_program_name(audio_recorder "pico_audio_recotr")
pico_set_program_version(audio_recorder "0.1")

pico_enable_stdio_uart(audio_recorder 1)
pico_enable_stdio_usb(audio_recorder 0)

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

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

# Add any user requested libraries
target_link_libraries(audio_recorder 
        hardware_dma
        hardware_pio
        pico_multicore
        pico_util
        )
add_subdirectory(storage_driver)
# Add any user requested libraries
target_link_libraries(audio_recorder 
        tinyusb_device 
        tinyusb_board
        storage_driver
        )
add_subdirectory(pico_audio_recorder)
# Add any user requested libraries
target_link_libraries(audio_recorder 
               pico_audio_recorder
        )

pico_add_extra_outputs(audio_recorder)


2023年10月22日 星期日

[Raspberry Pi Pico (c-sdk)] Storage: Ep 6. SD Memory Card 4-bit wide bus and 1-bit data bus

 本文章介紹使用Raspberry Pi Pico PIO程式製作SD memory Card 4-bit wide bus and 1-bit data bus驅動程式。並與FatFs檔案系統結合,測試在SD card上進行檔案讀寫。

SD memory card主要分為Command, data read and data write三個部份。每個部份我們製作一個相對應的State machine:CMD, READ and WRITE。

一、CMD PIO state machine:

command為48bits:第一個byte:01開頭為HOST to Card, 00開頭為Card to Host, 其餘6bits為Command ID。接下來4 bytes為parameters。最後一個byte為CRC7and 1 bit(stop bit)。response分成0,48 and 136 bits三種。

CMD state machine使用1 CMD line and 1 CLK line:
CMD pin:IN/OUT/SET/JMP。 CLK pin:side-set
詳細程式碼附於文章末尾。

二、READ State machine:
Card output(HOST read): 每個data block為512 bytes後面跟著CRC16。4-bit wide bus DAT line分別需要個別的CRC16 data。 
READ state machine: 1 or 4 DAT line, 1 CLK line。
DAT pin(s): IN/SET/JMP, CLK pin:side-set

三、WRITE state machine:

Card input(HOST output):host寫入一個data block後面跟CRC16。Card於3 clocks後在DAT0 (DAT1~3 don't care) 送出CRC status(010為positive, 101為negative)。
WRITE state machine: 1~4 DAT line, 1 CLK line。
DAT pin(s): IN/OUT/SET/JMP, CLK pin:side-set

四、HOST read data:

  1. 啟動CMD state machine, host向card 送出data read command(CMD17 or CMD18),收到rsponse後,停止state machine(disable)。
  2. 啟動READ state machine讀取資料。
  3. CMD與READ state machine使用自己的CLK pin。因此在CMD state machine換成READ state machine時,clock會先暫停。根據SD specification "clock control"章節說明是允許的。

五、HOST DATA write:

  1. 啟動CMD state machine, host向card 送出data write command(CMD24 or CMD25),收到rsponse後,停止state machine(disable)。
  2. 啟動WRITE state machine write data block and crc。接著讀取crc status。

參考資料為:
SD Specifications Part 1 Physical Layer Specification(https://www.sdcard.org/)


成果展示:



程式碼:


  • sdio_mem_card.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "sdio_mem_card.h"
#include "sdio_mem_card.pio.h"
#include "hardware/clocks.h"
#include "crc7.h"
#include "crc-itu-t.h"
#include "string.h"
#include "inttypes.h"

/*
void pio_irq_read_data() {
     if (pio_interrupt_get(SDIO_MEM_PIO, 0)) {
        pio_interrupt_clear(SDIO_MEM_PIO, 0);
        printf("irq 0:\n");
     }
     if (pio_interrupt_get(SDIO_MEM_PIO, 1)) {
        pio_interrupt_clear(SDIO_MEM_PIO, 1);
        printf("irq:1\n");
     }
}
*/

/*!
* \brief sdio memory card pio initialize
* \param pio: pio number
* \param sm state machine
* \param cmd_pin command pin
* \param clk_pin CLK pin
* \param data_pin_base data 0~4 pins(D0~D3)
* \param clk_int Integer part of the divisor
* \param clk_frac – Fractional part in 1/256ths
*/
void sdio_pio_init(sd_memory_card_t* pSD, PIO pio,  uint cmd_pin, uint clk_pin, uint data_pin_base,  uint16_t clk_int, uint8_t clk_frac) {
    uint offset=0;
    pio_sm_config c;
    static uint8_t data_width;

    data_width = (pSD->is_wide_bus ? 4:1);
  
    pio_gpio_init(pio, cmd_pin);
    pio_gpio_init(pio, clk_pin);
    gpio_pull_up(cmd_pin);
    
    for (int i=0; i < 4; i++) {
        pio_gpio_init(pio, data_pin_base+i);
        gpio_pull_up(data_pin_base+i);
    }
    pio_enable_sm_mask_in_sync(pio, (1<<SDIO_CMD_SM)|(1<<SDIO_DATA_READ_SM)|(1<<SDIO_DATA_WRITE_SM));
        
    //== SDIO READ SM ==
    pio_sm_config cr;
    if (data_width == 1) {
        pSD->sd_data_rx_offset = pio_add_program(pio, &sdio_1_bit_rx_program);
        cr = sdio_1_bit_rx_program_get_default_config(pSD->sd_data_rx_offset);
    }
    else {
        pSD->sd_data_rx_offset = pio_add_program(pio, &sdio_4_bit_rx_program);
        cr = sdio_4_bit_rx_program_get_default_config(pSD->sd_data_rx_offset);
    }
    pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_READ_SM, data_pin_base, 4, false);
    pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_READ_SM, clk_pin, 1, true);
    //pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_READ_SM, cmd_pin, 1, false);
    sm_config_set_in_pins(&cr, data_pin_base);
    sm_config_set_sideset_pins(&cr, clk_pin);
    sm_config_set_set_pins(&cr, data_pin_base,data_width);
    sm_config_set_jmp_pin(&cr, data_pin_base);
    sm_config_set_out_shift(&cr, false, true, 32);
    sm_config_set_in_shift(&cr, false, true, 32);
    sm_config_set_clkdiv_int_frac(&cr, clk_int,clk_frac);
    pio_sm_init(pio, SDIO_DATA_READ_SM, pSD->sd_data_rx_offset, &cr);
    
    //== SDIO WRITE SM ==
    pio_sm_config cw;
    if (data_width == 1) {
        pSD->sd_data_tx_offset = pio_add_program(pio, &sdio_1_bit_tx_program);
        cw = sdio_1_bit_rx_program_get_default_config(pSD->sd_data_tx_offset);
    }
    else {
        pSD->sd_data_tx_offset = pio_add_program(pio, &sdio_4_bit_tx_program);
        cw = sdio_4_bit_rx_program_get_default_config(pSD->sd_data_tx_offset);
    }
    pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_WRITE_SM, data_pin_base, 4, true);
    pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_WRITE_SM, clk_pin, 1, true);
    //pio_sm_set_consecutive_pindirs(pio, SDIO_DATA_WRITE_SM, cmd_pin, 1, false);
    sm_config_set_in_pins(&cw, data_pin_base);
    sm_config_set_out_pins(&cw, data_pin_base, data_width);
    sm_config_set_sideset_pins(&cw, clk_pin);
    sm_config_set_set_pins(&cw, data_pin_base,data_width);
    sm_config_set_jmp_pin(&cw, data_pin_base);
    sm_config_set_out_shift(&cw, false, true, 32);
    sm_config_set_in_shift(&cw, false, true, 32);
    sm_config_set_clkdiv_int_frac(&cw, clk_int,clk_frac);
    pio_sm_init(pio, SDIO_DATA_WRITE_SM, pSD->sd_data_tx_offset, &cw);

    //== SDIO command SM ==
    offset = pio_add_program(pio, &sdio_mem_cmd_program);
    c = sdio_mem_cmd_program_get_default_config(offset);
    pio_sm_set_consecutive_pindirs(pio, SDIO_CMD_SM, cmd_pin, 1, false);
    pio_sm_set_consecutive_pindirs(pio, SDIO_CMD_SM, clk_pin, 1, true);
    pio_sm_set_consecutive_pindirs(pio, SDIO_CMD_SM, data_pin_base, 4, false);
    sm_config_set_in_pins(&c, cmd_pin);
    sm_config_set_out_pins(&c, cmd_pin, 1);
    sm_config_set_set_pins(&c, cmd_pin,1);
    sm_config_set_sideset_pins(&c, clk_pin);
    sm_config_set_jmp_pin(&c, cmd_pin);
    sm_config_set_out_shift(&c, false, true, 32);
    sm_config_set_in_shift(&c, false, true, 32);
    sm_config_set_clkdiv_int_frac(&c, clk_int,clk_frac);
    pio_sm_init(pio, SDIO_CMD_SM, offset, &c);

      // initial state machine but not run
    pio_sm_set_enabled(pio, SDIO_CMD_SM, false);
    pio_sm_set_enabled(pio, SDIO_DATA_READ_SM, false);
    pio_sm_set_enabled(pio, SDIO_DATA_WRITE_SM, false);

    //** for test only
    //uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    //pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    //pio_set_irq0_source_enabled(pio, pis_interrupt1, true);
    //irq_add_shared_handler(pio_irq, pio_irq_read_data, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    //irq_set_enabled(pio_irq, true);
   
    #if _DMA_TRANS_
    /*   DMA  */
    uint pio_base = (pio==pio0)?PIO0_BASE:PIO1_BASE;
    // ==== Write DMA ===
    pSD->dma_write_channel = dma_claim_unused_channel(true);
    dma_channel_config dcw = dma_channel_get_default_config(pSD->dma_write_channel);
    channel_config_set_write_increment(&dcw, false);
    channel_config_set_read_increment(&dcw, true);
    channel_config_set_bswap(&dcw, true);
    channel_config_set_dreq(&dcw, pio_get_dreq(pio, SDIO_DATA_WRITE_SM, true));
    channel_config_set_transfer_data_size(&dcw, DMA_SIZE_32); //DMA_SIZE_8,16,32
    dma_channel_configure(pSD->dma_write_channel,
             &dcw, (void*) (pio_base+PIO_TXF2_OFFSET),  // SDIO_DATA_WRITE_SM = 2
             NULL, MAX_RW_BLOCKS_TRANS>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
    
    //==== READ DMA ===
    pSD->dma_read_channel = dma_claim_unused_channel(true);
    dma_channel_config dcr = dma_channel_get_default_config(pSD->dma_read_channel);
    channel_config_set_write_increment(&dcr, true);
    channel_config_set_read_increment(&dcr, false);
    channel_config_set_bswap(&dcr, true);
    channel_config_set_dreq(&dcr, pio_get_dreq(pio, SDIO_DATA_READ_SM, false));
    channel_config_set_transfer_data_size(&dcr, DMA_SIZE_32); //DMA_SIZE_8,16,32
    dma_channel_configure(pSD->dma_read_channel,
             &dcr, NULL, (void*) (pio_base+PIO_RXF1_OFFSET),  // SDIO_DATA_READ_SM = 1
              MAX_RW_BLOCKS_TRANS>> DMA_SIZE_32, false); //DMA_SIZE_8 or 16 or 32
    /*  DMA */
    #endif
}
/*!
* \brief send SDIO CMD and recv RESPONSE
* \param cmd_id command ID
* \param arg args size 4 bytes(32bits)
* \param resp RESPONSE Register (48 bits / 136 bites)
* \param resp_bits response register length
*/
uint8_t sdio_send_recv_cmd(uint8_t cmd_id, uint8_t *arg, uint32_t *resp, uint8_t resp_bits) {
    uint8_t cmd;
    uint32_t cmd_buf[2];
    uint8_t crc = 0;
    cmd = cmd_id | 0x40; // start 0b01 send from host to card

    // crc7 
    crc = crc7_table[crc ^ cmd];
    for (int i=0; i < count_of(arg); i++)
        crc = crc7_table[crc ^ arg[i]];
    // crc7
    if (resp_bits > 0) resp_bits--;
    // put to fifo: 1byte command len,1byte command ID, 4bytes arg ,1byte crc ,1bytes RESP
    cmd_buf[0] = 47 << 24 | cmd << 16 | arg[0] << 8 | arg[1];
    cmd_buf[1] = arg[2] << 24 | arg[3] << 16 | (crc | 0x01) << 8 | (resp_bits);

    memset(resp, 0, sizeof(resp));

    pio_sm_clear_fifos(SDIO_MEM_PIO, SDIO_CMD_SM);
    pio_sm_exec(SDIO_MEM_PIO, SDIO_CMD_SM,pio_encode_set(pio_pins,1));
    pio_sm_exec(SDIO_MEM_PIO, SDIO_CMD_SM,pio_encode_set(pio_pindirs,1));
    //Output shift counter is reset to 0 by this operation, i.e. full)
    pio_sm_exec(SDIO_MEM_PIO, SDIO_CMD_SM, pio_encode_mov(pio_osr, pio_null));
    for (int i = 0; i < count_of(cmd_buf); i++) {
        pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_CMD_SM, cmd_buf[i]);
    }
    pio_sm_exec(SDIO_MEM_PIO, SDIO_CMD_SM, pio_encode_jmp(sdio_mem_cmd_offset_sdio_cmd_start));
    pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_CMD_SM, true);     
  
    switch (resp_bits) {
        case 47:
            resp[0] = pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_CMD_SM);
            resp[1] = pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_CMD_SM);
            
        break;
        case 135:
            for (int i=0; i < 5; i++) {
                resp[i] = pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_CMD_SM);
            }
        break;
        case 0:
            resp[0] = pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_CMD_SM); //dummy
        break;
    }
    pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_CMD_SM, false);
    
    return SD_OK;
}

bool format_sdio_response_136(uint32_t *resp_32, uint32_t *content) {
    if ((resp_32[0]>>24) != 0x3f){ printf("get CID/CSD error\n"); return false;}
    for (int i=0; i < 3;i++) {
        content[i] = resp_32[i] << 8 | resp_32[i+1] >> 24;
    }
    content[3] = (resp_32[3] >> 24) << 8 | resp_32[4]&0xff;
}

bool format_sdio_response_48(uint32_t *resp_32, uint8_t *resp_8) {
    resp_8[0] = resp_32[0] >> 24;
    resp_8[1] = (resp_32[0]&0x00ff0000) >> 16;
    resp_8[2] = (resp_32[0]&0x0000ff00) >> 8;
    resp_8[3] = resp_32[0] &0x000000ff;
    resp_8[4] = (resp_32[1]&0x0000ff00) >> 8;
    resp_8[5] = (resp_32[1] &0x000000ff) ;
    uint8_t crc = 0;
    for (int i=0; i < 5; i++)
        crc = crc7_table[crc ^ resp_8[i]];
    if (crc != (resp_8[5]&0xfe)) return false;
    return true;
}

uint32_t get_sd_card_status(sd_memory_card_t* pSD) {
    uint8_t arg[4];
    uint32_t resp[2];
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    arg[2] = 0;
    arg[3] = 0;

    sdio_send_recv_cmd(CMD13, arg, resp, 48); 
    return (resp[0])<<8 | (resp[1]>>8);  
  
} 

uint32_t format_R1_response(uint32_t *resp) {
   
    return (resp[0]&0x00ffffff)<<8 | (resp[1]>>8); 
}

/*!
* \brief initialize sd card
* \param wide_bus true:4bit, false 1bit
*/
uint8_t sd_memory_card_init(sd_memory_card_t* pSD,  uint8_t wide_bus) {
    uint8_t arg[4];
    uint32_t resp[5];
    uint8_t resp_bytes[17];
    uint8_t ret_val;
    uint32_t CSID[5];
    uint8_t ret = SD_OK;
    
    pSD->SD_initialized=false;

    pio_clear_instruction_memory(SDIO_MEM_PIO);

    pSD->is_wide_bus = (wide_bus)? true:false;
    sdio_pio_init(pSD, SDIO_MEM_PIO,  SDIO_CMD_PIN,  SDIO_CLK_PIN, SDIO_D0_PIN, 1, 0);
 
    sleep_ms(2);
    memset(arg, 0, sizeof(arg));
    sdio_send_recv_cmd(CMD0, arg, resp, 0);  // CMD0: REEST

    for (int i=0; i < 10; i++) {
       
        memset(arg, 0, sizeof(arg));
        arg[2]=1;
        arg[3]=0xaa;
        sdio_send_recv_cmd(CMD8, arg, resp, 48);  //CMD8: SEND_IF_CONF
        
        if(format_sdio_response_48(resp, resp_bytes)) {
            if (resp_bytes[4]== arg[3])
                printf("cmd8 ok:%d\n",i);
                ret = SD_OK;
                break;
        } else {
            ret = SD_INIT_FAILURE;
        }
   }
    absolute_time_t timeout = make_timeout_time_ms(1000);
    do {
        memset(arg, 0, sizeof(arg));
        sdio_send_recv_cmd(CMD55, arg, resp, 48);   // next is APP_CMD

        arg[0] = 0x40; // HCS, XPC, S18R 
        arg[1] = 0x10;
        sdio_send_recv_cmd(ACMD41, arg, resp, 48); // R3
        format_sdio_response_48(resp, resp_bytes); // crc is always 1111111 
        //for (int i=0; i < 6; i++) { printf("%02x ",resp_bytes[i]);}  printf("\n");
        if (resp_bytes[0]==0x3f && (resp_bytes[1] & 0x80)) {
            ret = SD_OK;
            break;
        } 
        else {
            ret = SD_TIME_OUT;
        }

    } while (absolute_time_diff_us(get_absolute_time(), timeout) > 0);  
    // 0x80 initialize completely. R3[1]: Busy CCS UHS-II Reserved(0000) S18A
    if(ret != SD_OK) return ret;

    memset(arg, 0, sizeof(arg));
    if (resp_bytes[1] & 0x01) {
        sdio_send_recv_cmd(CMD11, arg, resp, 0); // voltage switch
    }
  
    sdio_send_recv_cmd(CMD2, arg, resp, 136);  // CID(R2) 

    sdio_send_recv_cmd(CMD3, arg, resp, 48);
    if (format_sdio_response_48(resp, resp_bytes)) {
        pSD->rca[0] = resp_bytes[1];
        pSD->rca[1] = resp_bytes[2];
        printf("rca received:%02x, %02x\n", pSD->rca[0], pSD->rca[1]);
    } else {
        printf("CMD3 response crc error\n");
        for (int i=0; i < 6; i++) {
            printf("%02x ",resp_bytes[i]);
        }
        printf("\n");
        return SD_CRC_ERROR;
    }


     //get CID regisger ===
    memset(arg,0,sizeof(arg));
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    for (int i=0; i<10; i++) {
        if (sdio_send_recv_cmd(CMD10, arg, resp, 136)== SD_OK) {
            ret = SD_OK;
            break;  // CID (R2)
        }
        else {
            ret = SD_INIT_FAILURE;
        }
    }
    format_sdio_response_136(resp, CSID);
  
    pSD->pnm[0]= (uint8_t)(CSID[0]);
    pSD->pnm[1]= (uint8_t)((CSID[1]>>24));
    pSD->pnm[2]= (uint8_t)((CSID[1]>>16));
    pSD->pnm[3]= (uint8_t)((CSID[1])>>8);
    pSD->pnm[4]= (uint8_t)(CSID[1]);
    pSD->pnm[5]= '\0';
    pSD->prv = (uint8_t)(CSID[2]>>24);
    pSD->psn = (CSID[2]) << 8 | CSID[3]>>24;
    // === get CID end
    
    //===get CSD register ===
    memset(arg,0,sizeof(arg));
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    for (int i=0; i<10; i++) {
        if (sdio_send_recv_cmd(CMD9, arg, resp, 136)== SD_OK) { 
            ret = SD_OK;
            break;  // CSD (R2) 
        }else {
            ret = SD_INIT_FAILURE;
        }
        sleep_ms(2);
    }
    format_sdio_response_136(resp, CSID);
    printf("CSD:%08x,%08x,%08x,%08x\n", CSID[0],CSID[1],CSID[2],CSID[3]);
    
    pSD->block_size = 1<<((CSID[1]>>16) & 0xf) ;
    pSD->total_blocks = (CSID[1]&0x1f) << 16 |CSID[2] >> 16;

    printf("block_size:%d, total_blocks:%u, capacity:%d KB\n", 
                    pSD->block_size,
                    pSD->total_blocks,
                    (pSD->total_blocks/(1024/pSD->block_size))
                    );
    // ==get CSD end===

    memset(arg,0,sizeof(arg));
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    sdio_send_recv_cmd(CMD7, arg, resp, 48);  // R1b, SELECT_CARD
    //sdio_send_recv_cmd(CMD42, arg, resp, 48); //R1
    //format_sdio_response_48(resp, resp_bytes);
    
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    sdio_send_recv_cmd(CMD55, arg, resp, 48);   // next is APP_CMD
    if (wide_bus) arg[3]=0x02;   // 4-bit
    sdio_send_recv_cmd(ACMD6, arg, resp, 48);  //R1, set bus width
    //format_sdio_response_48(resp, resp_bytes);
    //printf("status:%0x\n", resp_bytes[4]);
    sdio_send_recv_cmd(CMD13, arg, resp, 48);   
    format_sdio_response_48(resp, resp_bytes);
    if (resp_bytes[3] == 0x09 && resp_bytes[4]==0x00)
    {   
        uint8_t test[512];
        sd_read_single_block(pSD, (uint32_t*)test, pSD->total_blocks);
        if (get_sd_card_status(pSD)==0x0900) {
            pSD->SD_initialized=true;
            printf("SD card initialize successfully\n");
        }
    }

    return ret;
}

uint8_t sd_stop_trans() {
    uint8_t arg[4];
    uint32_t resp[2];
    uint8_t resp_bytes[5];
    memset(arg, 0, sizeof(arg));
    sdio_send_recv_cmd(CMD12, arg, resp, 48);  //R1b, 
     return SD_OK;

}
uint16_t calc_1bit_crc16(uint32_t *buff, uint16_t words_per_block){
    uint16_t crc =0;
    uint32_t temp;
    for (int i= 0; i< words_per_block; i++) {
        temp = __builtin_bswap32(buff[i]);
        crc = crc_itu_t_table[(crc >> 8) ^ (uint8_t)(temp>>24)] ^ (crc << 8);
        crc = crc_itu_t_table[(crc >> 8) ^ (uint8_t)(temp>>16)] ^ (crc << 8);
        crc = crc_itu_t_table[(crc >> 8) ^ (uint8_t)(temp>>8)] ^ (crc << 8);
        crc = crc_itu_t_table[(crc >> 8) ^ (uint8_t)(temp)] ^ (crc << 8);
    }
    return crc;
}

uint64_t calc_4bit_crc16(uint32_t *data, uint32_t num_words)
{
    uint64_t crc = 0;
    uint32_t *end = data + num_words;
    while (data < end)
    {
        for (int unroll = 0; unroll < 4; unroll++)
        {
            // Each 32-bit word contains 8 bits per line.
            // Reverse the bytes because SDIO protocol is big-endian.
            uint32_t data_in = __builtin_bswap32(*data++);    

            // Shift out 8 bits for each line
            uint32_t data_out = crc >> 32;
            crc <<= 32;

            // XOR outgoing data to itself with 4 bit delay
            data_out ^= (data_out >> 16);

            // XOR incoming data to outgoing data with 4 bit delay
            data_out ^= (data_in >> 16);

            // XOR outgoing and incoming data to accumulator at each tap
            uint64_t xorred = data_out ^ data_in;
            crc ^= xorred;
            crc ^= xorred << (5 * 4);
            crc ^= xorred << (12 * 4);
        }
    }

    return crc;
}

uint8_t pio_sd_read_blocks(sd_memory_card_t* pSD, uint32_t *buff, uint32_t count, uint32_t* crc) {
    uint32_t read_cycles;
    uint read_start_addr;
    uint8_t ret=SD_OK;
    uint16_t block_data_bytes;
    uint8_t crc_len;
    

    if (pSD->is_wide_bus) {
        block_data_bytes=(512+2*4);
        read_cycles=block_data_bytes*2-1;
        read_start_addr = pSD->sd_data_rx_offset+sdio_4_bit_rx_offset_rx_start;
        crc_len=2;
    }
    else
    {
        block_data_bytes = (512+2);
        read_cycles = block_data_bytes*8-1;
        read_start_addr = pSD->sd_data_rx_offset+sdio_1_bit_rx_offset_rx_start;
        crc_len=1;
    }

    pio_sm_clear_fifos(SDIO_MEM_PIO, SDIO_DATA_READ_SM);
    pio_sm_restart(SDIO_MEM_PIO, SDIO_DATA_READ_SM);
    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_READ_SM,pio_encode_set(pio_pindirs, 0));
    //pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_READ_SM, pio_encode_mov(pio_isr, pio_null));
    pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_READ_SM, read_cycles);
    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_READ_SM, pio_encode_out(pio_x, 32));
    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_READ_SM, pio_encode_jmp(read_start_addr));
   
    pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_DATA_READ_SM, true);
    for (int block_idx=0; block_idx < count; block_idx++) {
#if _DMA_TRANS_
        dma_channel_set_trans_count(pSD->dma_read_channel, SDIO_WORDS_PER_BLOCK, false);
        dma_channel_set_write_addr(pSD->dma_read_channel, buff+block_idx*SDIO_WORDS_PER_BLOCK, false);
        dma_channel_start(pSD->dma_read_channel);
        dma_channel_wait_for_finish_blocking(pSD->dma_read_channel);
        
        dma_channel_set_trans_count(pSD->dma_read_channel, crc_len, false);
        dma_channel_set_write_addr(pSD->dma_read_channel, crc+crc_len*block_idx, false);
        dma_channel_start(pSD->dma_read_channel);
        dma_channel_wait_for_finish_blocking(pSD->dma_read_channel);
#else
    for (int i=0; i < SDIO_WORDS_PER_BLOCK; i++) { 

        buff[i+SDIO_WORDS_PER_BLOCK*block_idx] = __builtin_bswap32(pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_DATA_READ_SM));

    }
    for (int c=0; c < crc_len; c++) {
        crc[c+crc_len*block_idx] = __builtin_bswap32(pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_DATA_READ_SM));
    }
#endif
    }
    pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_DATA_READ_SM, false);
  
        
    return SD_OK;
}

uint8_t pio_sd_write_blocks(sd_memory_card_t* pSD, uint32_t *buff, uint32_t count) {
    uint32_t write_cycles;
    uint write_start_addr;
    uint8_t ret=SD_OK;
    uint16_t block_data_bytes;
    uint16_t crc;
    uint32_t start, end;
    uint32_t lines=1;
    if (pSD->is_wide_bus) {
        block_data_bytes=(4+512+2*4); // start 0xfffffff0, end 0xffffffff
        write_cycles=block_data_bytes*2;
        write_start_addr = pSD->sd_data_tx_offset+sdio_4_bit_tx_offset_tx_start;
        start=0xfffffff0;
        end = 0xffffffff;
        lines=4;
    }
    else
    {
        block_data_bytes = (4+512+2); // start 0xfffffffe,  
        write_cycles = block_data_bytes*8;
        write_start_addr = pSD->sd_data_tx_offset+sdio_1_bit_tx_offset_tx_start;
        start = 0xfffffffe;
        end =0xffffffff;
        lines=1;
    }
   
    pio_sm_clear_fifos(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM);
    pio_sm_restart(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM);
    // set pins output
    uint pindirs;
    if (pSD->is_wide_bus){
        pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_set(pio_pins, 15));
        pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_set(pio_pindirs, 15));
    } else {
        pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_set(pio_pins, 1));
        pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_set(pio_pindirs, 1));
    }   
    
    pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, write_cycles);
    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_out(pio_x, 32));
    
    pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, 31);//
    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_out(pio_y, 32));

    pio_sm_exec(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, pio_encode_jmp(write_start_addr));

    uint32_t crc_and_stop_bit;
    uint32_t crc32_h, crc32_l;
 
    if (pSD->is_wide_bus) {
        
        uint64_t crc64 = calc_4bit_crc16(buff, SDIO_WORDS_PER_BLOCK);
        crc32_h = (uint32_t)(crc64>>32);
        crc32_l = (uint32_t)crc64;
    } else {
        crc = calc_1bit_crc16(buff, SDIO_WORDS_PER_BLOCK);
        crc_and_stop_bit = crc <<16 | 0xffff;
    } 
    
    pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, start); //start bit
    pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, true);
     
#if _DMA_TRANS_      
        dma_channel_set_trans_count(pSD->dma_write_channel, SDIO_WORDS_PER_BLOCK, false);
        dma_channel_set_read_addr(pSD->dma_write_channel, buff, false);
        dma_channel_start(pSD->dma_write_channel);
        dma_channel_wait_for_finish_blocking(pSD->dma_write_channel);
#else
        for (int i=0; i < SDIO_WORDS_PER_BLOCK; i++) {     
            pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, __builtin_bswap32(buff[i]));
        }
#endif
        if (pSD->is_wide_bus) {
            pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM,crc32_h);
            pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM,crc32_l);
            pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM,end);
        } else {
            pio_sm_put_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM,crc_and_stop_bit);
        }
        uint32_t crc_status;
       
        crc_status= pio_sm_get_blocking(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM);
        pio_sm_set_enabled(SDIO_MEM_PIO, SDIO_DATA_WRITE_SM, false);  

        // card report postive crc:010, neg:101(card status)
         if (crc_status>>24 != 0xe5){   // EZZS...E(pos:e5, net:e9)
            //printf("write CRC Error\n");
            ret = SD_CRC_ERROR;
        }
    
    return ret;
}


uint8_t sd_read_single_block(sd_memory_card_t *pSD, uint32_t *buff, uint32_t address) {
    uint8_t arg[4];
    uint32_t resp[2];
    uint32_t read_crc[2]; 

    uint8_t ret = SD_OK;
    arg[0] = address >> 24;
    arg[1] = (address & 0x00ff0000) >> 16;
    arg[2] = (address & 0x0000ff00) >> 8;
    arg[3] = (address & 0x000000ff);

    sdio_send_recv_cmd(CMD17, arg, resp, 48);  //R1, 

    if (format_R1_response(resp) == 0x0900) 
    {
        ret = pio_sd_read_blocks(pSD, buff, 1, read_crc);
    } else {
        printf("read single bolck error:%08x\n", format_R1_response(resp));
        return SD_STATE_ERROR;
    }

    // check CRC;
    if (pSD->is_wide_bus) {
        uint64_t data_crc;
        uint64_t crc;
        data_crc = (uint64_t)(__builtin_bswap32(read_crc[0]))<<32|(__builtin_bswap32(read_crc[1]));
        crc = calc_4bit_crc16(buff,SDIO_WORDS_PER_BLOCK);
//printf("data_crc:%016llu\n     crc:%016llu\n\n", data_crc, crc);  
//printf("data_crc:%08x,%08x\n     crc:%08x,%08x\n\n", read_crc[0],read_crc[1], (uint32_t)(crc>>32),(uint32_t)crc) ; 
        if (data_crc != crc) {
printf("crc error:wide bus\n");
            return SD_CRC_ERROR;
        }      
    } else {
        uint16_t data_crc;
        uint16_t crc;
     
        data_crc = (uint16_t)(__builtin_bswap32(read_crc[0])>>16);
        crc = calc_1bit_crc16(buff,SDIO_WORDS_PER_BLOCK);
//printf("data_crc:%08x     crc:%08x, dd:%08x\n", data_crc, crc, read_crc[0]);          
        if (data_crc!=crc){
printf("crc error:1 bit\n");
            return SD_CRC_ERROR; 
        }
    }

    return ret;
}

uint8_t sd_read_multiple_block(sd_memory_card_t* pSD, uint32_t *buff, uint32_t address, uint32_t count) {
    uint8_t arg[4];
    uint32_t resp[2];
    uint32_t read_crc[2*count];
    uint8_t ret = SD_OK;
    uint32_t crc_len=(pSD->is_wide_bus)?2:1;  
    arg[0] = address >> 24;
    arg[1] = (address & 0x00ff0000) >> 16;
    arg[2] = (address & 0x0000ff00) >> 8;
    arg[3] = (address & 0x000000ff);

    sdio_send_recv_cmd(CMD18, arg, resp, 48);  //R1, 
    if (format_R1_response(resp) == 0x0900) 
    {                
        ret = pio_sd_read_blocks(pSD, buff, count, read_crc);   
    } else {
        printf("read multiple error:%08x\n", format_R1_response(resp));
        return SD_STATE_ERROR;
    }

    sd_stop_trans();

    // check CRC;
    if (pSD->is_wide_bus) {
        uint64_t data_crc;
        uint64_t crc;
        for (int i=0; i < count; i++)  {
            data_crc = (uint64_t)(__builtin_bswap32(read_crc[2*i]))<<32|(__builtin_bswap32(read_crc[2*i+1]));
            crc = calc_4bit_crc16(buff+(i*SDIO_WORDS_PER_BLOCK),SDIO_WORDS_PER_BLOCK);
//printf("data_crc:%016llu\n     crc:%016llu\n", data_crc, crc);  
            if (data_crc != crc) {
printf("crc error: wide bus\n\n");
                return SD_CRC_ERROR;
            }  
        }
    } else {
        uint16_t data_crc;
        uint16_t crc;
        for (int i=0; i < count; i++)  {
            data_crc = (uint16_t)(__builtin_bswap32(read_crc[i])>>16);
            crc = calc_1bit_crc16(buff+(i*(SDIO_WORDS_PER_BLOCK)),SDIO_WORDS_PER_BLOCK);
 //printf("data_crc:%08x     crc:%08x\n", data_crc, crc);           
            if (data_crc!=crc){
printf("crc error: 1bit\n");
                return SD_CRC_ERROR; 
            }
        }
    }

    return ret ;
}

uint8_t sd_write_single_block(sd_memory_card_t* pSD, uint32_t *buff, uint32_t address) {
    uint8_t arg[4];
    uint32_t resp[2];
    uint8_t resp_bytes[6];
    uint8_t ret=SD_ERROR_CODE;


    //CMD16 SET_BLOCKLEN
    memset(arg,0,sizeof(arg));
    arg[2]=0x02;
    sdio_send_recv_cmd(CMD16, arg, resp, 48);  //R1,


    arg[0] = address >> 24;
    arg[1] = (address & 0x00ff0000) >> 16;
    arg[2] = (address & 0x0000ff00) >> 8;
    arg[3] = (address & 0x000000ff);
    sdio_send_recv_cmd(CMD24, arg, resp, 48);  //R1, 
 // uint32_t count=0;
    if (format_R1_response(resp) == 0x0900) { 
        ret = pio_sd_write_blocks(pSD, buff, 1);
        for (int idx=0; idx< 10000;idx++) {
            uint32_t ss= get_sd_card_status(pSD);
            
            if (ss == 0x0900) break;
            sleep_us(10);
//            count++;
        };
    }
    else {
        printf("write single error :%08x\n",format_R1_response(resp) );
        return SD_STATE_ERROR;
    }
    //printf("error count:%d\n", count);
    return ret;
}

uint8_t sd_write_multiple_block(sd_memory_card_t* pSD, uint32_t *buff, uint32_t address, uint32_t count) {
    uint8_t arg[4];
    uint32_t resp[2];
    uint8_t resp_bytes[6];
    uint8_t ret=SD_OK;

    //CMD16 SET_BLOCKLEN
    memset(arg,0,sizeof(arg));
    arg[2]=0x02;
    sdio_send_recv_cmd(CMD16, arg, resp, 48);  //R1,

    // pre-erase
    arg[0] = pSD->rca[0];
    arg[1] = pSD->rca[1];
    sdio_send_recv_cmd(CMD55, arg, resp, 48);   // next is APP_CMD
    arg[0] = count >> 24;
    arg[1] = (count & 0x00ff0000) >> 16;
    arg[2] = (count & 0x0000ff00) >> 8;
    arg[3] = (count & 0x000000ff);
    sdio_send_recv_cmd(ACMD23, arg, resp, 48);  //R1, pre-erase blocks
    arg[0] = address >> 24;
    arg[1] = (address & 0x00ff0000) >> 16;
    arg[2] = (address & 0x0000ff00) >> 8;
    arg[3] = (address & 0x000000ff);

    sdio_send_recv_cmd(CMD25, arg, resp, 48);  //R1, 
//    uint32_t errors=0;
        for (int i=0; i < count; i++) {
            for (int idx=0; idx < 100000; idx++) { // max 100ms wait for status : READY_FOR_DATA
                if (get_sd_card_status(pSD) & 0x0100) {ret=SD_OK;break;}
                sleep_us(1);
                ret = SD_TIME_OUT; 
        //        errors++;
            }
            if (ret == SD_OK) {
                ret = pio_sd_write_blocks(pSD, buff+i*SDIO_WORDS_PER_BLOCK, 1);
                if (ret != SD_OK) break;
            } else {
                return ret;
            }
    }
  
    sd_stop_trans();
    if (ret != SD_OK) { sleep_ms(1); return ret;}
    uint32_t cardStatus;
//    errors=0;
    for (int timeout=0; timeout<10000;timeout++) { // wait for card ready
        cardStatus = get_sd_card_status(pSD);
        if (cardStatus == 0x0900) break;
//        errors++;
        sleep_us(10);
    }
    //printf("not finish count:%d\n", errors);
    // send ACMD22 to get how many blocks well writen, no arg, R1 32bit+CRC
    return ret;
}

uint32_t sd_get_total_blocks(sd_memory_card_t* pSD) {
    return pSD->total_blocks;
}

uint16_t sd_get_block_size(sd_memory_card_t* pSD) {
    return pSD->block_size;
}

/* sdio_memory_disk_initizlize, sdio_memory_disk_read, sdio_memory_disk_write, sdio_memory_disk_status, sdio_memory_disk_ioctl*/
/* sdmmc_disk_initialize*/
DSTATUS sdio_memory_disk_initialize(sd_memory_card_t* pSD, uint8_t wide_bus)
{
    DSTATUS stat = sd_memory_card_init(pSD, wide_bus);
	return RES_OK;
}
/* sdmmc disk status*/
DSTATUS sdio_memory_disk_status(sd_memory_card_t* pSD)
{
    if (pSD->SD_initialized) 
        return RES_OK;
     else 
        return RES_PARERR;
}

/* sdmmc disk read*/
DSTATUS sdio_memory_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) */
	sd_memory_card_t* pSD)
{
    DSTATUS ret=RES_OK;
	DWORD sect = (DWORD)(sector);
	if (!count)
		return RES_PARERR; /* Check parameter */
    if (!pSD->SD_initialized)
		return RES_NOTRDY; /* Check if drive is ready */
	if (count == 1)
	{	
        for (int retry=0; retry < CRC_ERROR_RETRY_COUNT; retry++) {
            ret = sd_read_single_block(pSD, (uint32_t*)buff, sector);
            if (ret == SD_OK)  break;//crc error try again
            if (ret == SD_CRC_ERROR) continue; else break;
        //led_blinking(); //// LED blinking
        }
	}
	else
	{ /* Multiple sector read */
        for (int retry=0; retry < CRC_ERROR_RETRY_COUNT; retry++) {
            ret = sd_read_multiple_block(pSD, (uint32_t*)buff, sect, count);
            if (ret == SD_OK)  break;//crc error try again
            if (ret == SD_CRC_ERROR) continue; else break;
        //led_blinking(); //// LED blinking
        }
	}
	//led_blinking_off();  //// LED blinking off

	return ret; /* Return result */
}

DSTATUS sdio_memory_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) */
	sd_memory_card_t *pSD)
{
	DWORD sect = (DWORD)sector;
	if (!count)
		return RES_PARERR; /* Check parameter */
	if (!pSD->SD_initialized)
		return RES_NOTRDY; /* Check if drive is ready */

	if (count == 1)
	{												  /* Single block write */
		return sd_write_single_block(pSD, (uint32_t*)buff, sect);
		//led_blinking();  //// LED_blinking
	}
	else
	{ /* Multiple sector write */
		return sd_write_multiple_block(pSD, (uint32_t*)buff, sect, count);
	}
	return RES_OK; /* Return result */
}

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

	BYTE src = 0xFF;

	if (!pSD->SD_initialized)
		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 (get_sd_card_status(pSD) == 0x0900)
			res = RES_OK;
		break;
	case GET_SECTOR_COUNT: /* Get drive capacity in unit of sector (DWORD) */
        *(LBA_t *)buff = *(LBA_t *)sd_get_total_blocks(pSD);	
		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 = pSD->block_size;
		res = RES_OK;
		break;
	case GET_BLOCK_SIZE: /* Get erase block size in unit of sector (DWORD) */
        //printf("get_block_size\n");
        /* ACMD13,CMD9 */
        res = RES_OK;
		break;

	case CTRL_TRIM: /* Erase a block of sectors (used when _USE_ERASE == 1) */
        //printf("CTL_TRIM\n");
        /* CMD32  */
       res = RES_OK;
		/* Following commands are never used by FatFs module */
        break;
	case MMC_GET_TYPE: /* Get MMC/SDC type (BYTE) */
        //printf("MMC_GET_TYPE\n");
       
		res = RES_OK;
		break;

	case MMC_GET_CSD: /* Read CSD (16 bytes) CMD9*/
        //printf("MMC_GET_CSD\n");
        res = RES_OK;
		break;

	case MMC_GET_CID: /* Read CID (16 bytes) CMD10*/
        //printf("MMC_GET_CID\n");
        res = RES_OK;
		break;

	case MMC_GET_OCR: /* Read OCR (4 bytes)  CMD58*/
        //printf("MMC_GET_OCR\n");
        
        res = RES_OK;
		break;

	case MMC_GET_SDSTAT: /* Read SD status (64 bytes) ACMD13*/
        //printf("MMC_GET_SDSTAT\n");
        res = RES_OK;
		break;

	default:
        //printf("default:%d\n",cmd);
		res = RES_PARERR;
	}
	return res;
}


  • sdio_mem_card.h
#ifndef __SDIO_MEM_H_
#define __SDIO_MEM_H_

#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"

#define SDIO_CLK_PIN    16    // PIO sideset pin
#define SDIO_CMD_PIN    17    // 
#define SDIO_D0_PIN     18    // D0~D3 must consecutive and D0 PIN number is minium
#define SDIO_D1_PIN     19
#define SDIO_D2_PIN     20
#define SDIO_D3_PIN     21

#define SDIO_MEM_PIO    pio0
#define SDIO_CMD_SM          0
#define SDIO_DATA_READ_SM    1
#define SDIO_DATA_WRITE_SM   2
#define SDIO_CLK_SM          3

#define _DMA_TRANS_          1

#define SDIO_WORDS_PER_BLOCK 128
#define SDIO_RW_WORDS_PER_BLOCK 130 //128+2
#define MAX_RW_BLOCKS_TRANS    16
#define CRC_ERROR_RETRY_COUNT          3

typedef struct {
    uint8_t rca[2];
    uint8_t SD_initialized;
    int sd_data_rx_offset;
    int sd_data_tx_offset;
    int sd_data_cmd_offset;
    uint8_t is_wide_bus;
    uint8_t pnm[6];   // product name
    uint8_t prv;      // product revision BCD(4.4)
    uint32_t psn;     //product serial number
  
    uint16_t block_size;
    uint32_t total_blocks;
    int dma_write_channel;
    int dma_read_channel;
} sd_memory_card_t;

enum {
    CLK_FREQ_400K=0,
    CLK_FREQ_1M,
    CLK_FREQ_12_5M,
    CLK_FREQ_25M,
    CLK_FREQ_50M,

} SDIO_CLK_FREQ;

enum {
    CMD0 = 0,
    CMD2 = 2,
    CMD3 = 3,       //SEND_RELATIVE_ADDR
    CMD4 = 4,       //SET_DSR
    CMD7 = 7,       //SELECT_CARD
    CMD8 = 8,
    CMD9 = 9,       //SEND_CSD
    CMD10 = 10,     //SEND_CID
    CMD11 = 11,
    CMD12 = 12,     //STOP_TRANSMISSION
    CMD13 = 13,     //SEND_STATUS
    CMD16 = 16,     // BLOCK LENGTH
    CMD17 = 17,     // read single block
    CMD18 = 18,     // read mutiple block  
    CMD24 = 24,     // write single block
    CMD25 = 25,     // write mutiple block
    CMD42 = 42,
    CMD55 = 55,     // APP_CMD, next is app command
    ACMD22 = 22,    // SEND_NUM_WR_BLOCKS
    ACMD23 = 23,    //SET_WR_BLK_ERASE_COUNT
    ACMD6 = 6,      // SET_BUS_WIDTH, param:00b-1 bit, 10b-4 bits
    ACMD41 = 41,    //SD_SEND_OP_COND    
} SDIO_CMD_LIST;

enum {
    IDLE_STATE=             0x01,
    ERASE_RESET=            0x02,
    ILLEAGAL_COMMAND=       0x04,
    CRC_ERROR=              0x08,
    ERASE_SEQUENCE_ERROR=   0x10,
    ADDRESS_ERROR=          0x20,
    PARAMETER_ERROR=        0x40,
} SDIO_R1;

enum {
    SD_OK=0,
    SD_INIT_FAILURE,
    SD_CRC_ERROR,
    SD_TIME_OUT,
    SD_STATE_ERROR,
    SD_READ_ERROR,
    SD_WRITE_ERROR,
} SD_ERROR_CODE;

//void sdio_pio_init(PIO pio, uint cmd_pin, uint clk_pin, uint data_pin_base, uint16_t clk_int, uint8_t clk_frac);
uint8_t sd_memory_card_init(sd_memory_card_t* sd_mem_card, uint8_t wide_bus);
uint8_t sd_read_single_block(sd_memory_card_t* pSD,uint32_t *buff, uint32_t address);
uint8_t sd_read_multiple_block(sd_memory_card_t* pSD,uint32_t *buff, uint32_t address, uint32_t count);
uint8_t sd_write_single_block(sd_memory_card_t* pSD,uint32_t *buff, uint32_t address);
uint8_t sd_write_multiple_block(sd_memory_card_t* pSD,uint32_t *buff, uint32_t address, uint32_t count);
uint32_t sd_get_total_blocks(sd_memory_card_t* pSD);
uint16_t sd_get_block_size(sd_memory_card_t* pSD);
DSTATUS sdio_memory_disk_initialize(sd_memory_card_t* pSD, uint8_t wide_bus);
DSTATUS sdio_memory_disk_status(sd_memory_card_t* pSD);
DSTATUS sdio_memory_disk_read(BYTE *buff, LBA_t sector,	UINT count, sd_memory_card_t *pSD); 
DSTATUS sdio_memory_disk_write(const BYTE *buff, LBA_t sector,UINT count, sd_memory_card_t *pSD);
DSTATUS sdio_memory_disk_ioctl(BYTE cmd, void *buff, sd_memory_card_t* pSD);
#endif

  • sdio_mem_card.pio
; SD clock frequency: 125M/(DL+DH+2). 
; DL=2, DH=1: 25Mhz
; DL=1, DH=1: 31.25Mhz. 
; DL=1, DH=0: 41.67Mhz. 
.define DL 1
.define DH 0
; ===cmd state machine===
.program sdio_mem_cmd
.origin 0
.side_set 1

.wrap_target
public sdio_cmd_start:
    out null, 32                side 0 [1]      ;discard data
    out x, 8                    side 1 [1]      ;command bits
send_cmd_bits:      
    out pins, 1                 side 0 [1]
    jmp x--, send_cmd_bits      side 1 [1]

    set pindirs, 0              side 0 [1]
     
    out x, 8                    side 1 [1] ;response bits
    jmp !x, sdio_cmd_stop       side 0 [1]
  

wait_recv:  
    nop                         side 1 [1]
    jmp pin, wait_recv          side 0 [1]
   
recv_resp:  
    in pins, 1                  side 1 [1] 
    jmp x--, recv_resp          side 0 [1]
sdio_cmd_stop:
    ;nop                         side 1
    set x,7                     side 1 [1]
wait_Ncr_Ncc:
    nop                         side 0 [1]
    jmp x--, wait_Ncr_Ncc       side 1 [1]
    push                        side 0 [1]
wait_shutdown:
    jmp wait_shutdown           side 0 [1]
    ;irq nowait 0 side 1
.wrap

;========== 1 bit READ ============
.program sdio_1_bit_rx
.side_set 1

public rx_start:
.wrap_target
wait_start:
    mov y,x                     side 0 [DL] 
    jmp pin, wait_start         side 1 [DH]

    nop                         side 0 [DL]
rx_data_bit:
    in PINS, 1                  side 1 [DH]        
    jmp y--, rx_data_bit        side 0 [DL]
    IN NULL,16                  side 0 [DL]

.wrap    
    
;========== 4 bit READ ============
.program sdio_4_bit_rx
.side_set 1
.wrap_target
public rx_start:
wait_start:
    mov y,x                     side 0 [DL]
    jmp pin, wait_start         side 1 [DH]
    nop                         side 0 [DL]
    
rx_data_bit:
    in PINS, 4                  side 1 [DH]         
    jmp y--, rx_data_bit        side 0 [DL]
    nop                         side 0 [DL]
   
.wrap    



;========== 1 bit WRITE ============
.program sdio_1_bit_tx
.side_set 1
.wrap_target
public tx_start:
tx_data_bit:
    out PINS, 1                 side 0 [DL]   
    jmp x--, tx_data_bit        side 1 [DH]
    
    set pindirs, 0              side 0 [DL]
response_loop:
    in PINS, 1                  side 1 [DH]  
    jmp Y--, response_loop      side 0 [DL]
    nop                         side 1 [DH]
    push                        side 0 [DL]  
.wrap

;========== 4 bit WRITE ============
.program sdio_4_bit_tx
.side_set 1
.wrap_target
public tx_start:
tx_data_bit:
    out PINS, 4                 side 0 [DL]    
    jmp x--, tx_data_bit        side 1 [DH]
    set pindirs, 0              side 0 [DL]
response_loop:
    in PINS, 1                  side 1 [DH] 
    jmp Y--, response_loop      side 0 [DL]
    nop                         side 1 [DH]
    push                        side 0 [DL]

    ;nop side 1 
 
.wrap




  • CMakeLists.txt(pico_strorage_drv)
add_library(pico_storage_drv INTERFACE)
pico_generate_pio_header(pico_storage_drv ${CMAKE_CURRENT_LIST_DIR}/sdio_mem_card/sdio_mem_card.pio)
target_sources(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/glue.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ff.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffunicode.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffsystem.c
    ${CMAKE_CURRENT_LIST_DIR}/spi_sdmmc/spi_sdmmc.c
    ${CMAKE_CURRENT_LIST_DIR}/flash/W25Q.c
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc/usb_msc.c
    ${CMAKE_CURRENT_LIST_DIR}/sdio_mem_card/sdio_mem_card.c
)

target_include_directories(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/FatFs
    ${CMAKE_CURRENT_LIST_DIR}/flash
    ${CMAKE_CURRENT_LIST_DIR}/spi_sdmmc
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc
    ${CMAKE_CURRENT_LIST_DIR}/sdio_mem_card
)

target_link_libraries(pico_storage_drv INTERFACE
        hardware_spi
        hardware_dma
        hardware_pio
        hardware_rtc
        pico_stdlib
        tinyusb_board
        tinyusb_host
)



  • pico_storage.h
#ifndef _PICO_STORAGE_H_
#define _PICO_STORAGE_H_

/* used by my project */
#define SD_4_BIT_PATH       "0:"
#define SPI_SDMMC_PATH      "1:"
#define SD_1_BIT_PATH       "2:"
#define W25Q_PATH           "3:"
#define USB_MSC_PATH	    "4:"

#define SPI_BAUDRATE_LOW (1000*1000)
#define SPI_BAUDRATE_HIGH (10*1000*1000)
/* =================================  */
#define  LED_BLINKING_PIN     25
void led_blinking(void);
void led_blinking_off(void);
/* =================================  */
#endif
  • glue.c
#include "stdio.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "sdio_mem_card.h"
#include "spi_sdmmc.h"
#include "W25Q.h"
#include "usb_msc.h"
#include "hardware/rtc.h"
#include "inttypes.h"
#include "hardware/gpio.h"

#define SD_4_BIT_DRV        0
#define SPI_SDMMC_DRV       1
#define SD_1_BIT_DRV        2
#define W25Q_DRV            3
#define USB_MSC_DRV         4

sd_memory_card_t *pSDIO_MEM_CARD_4=NULL;
sd_memory_card_t *pSDIO_MEM_CARD_1=NULL;
spi_sdmmc_data_t *pSPI_SDMMC=NULL;
w25q_data_t *pW25Q = NULL;
usb_msc_t    *pUSB_MSC=NULL;


//==================//
DSTATUS disk_initialize (BYTE drv){
    DSTATUS stat;
    switch (drv) {
        case SD_4_BIT_DRV:
            if (pSDIO_MEM_CARD_4 == NULL) {
                pSDIO_MEM_CARD_4 = (sd_memory_card_t*)malloc(sizeof(sd_memory_card_t));
                pSDIO_MEM_CARD_4->SD_initialized=false;
            }
            stat = sdio_memory_disk_initialize(pSDIO_MEM_CARD_4, true);
            return stat;
        break;
       case SD_1_BIT_DRV:
            if (pSDIO_MEM_CARD_1 == NULL) {
                pSDIO_MEM_CARD_1 = (sd_memory_card_t*)malloc(sizeof(sd_memory_card_t));
                pSDIO_MEM_CARD_1->SD_initialized=false;
            }
            stat = sdio_memory_disk_initialize(pSDIO_MEM_CARD_1, false);
            return stat;
        break;
        case SPI_SDMMC_DRV:
            if (pSPI_SDMMC == NULL) {
                pSPI_SDMMC = (spi_sdmmc_data_t*)malloc(sizeof(spi_sdmmc_data_t));
                pSPI_SDMMC->csPin = SDMMC_PIN_CS;
                pSPI_SDMMC->spiPort = SDMMC_SPI_PORT;
                pSPI_SDMMC->spiInit=false;
                pSPI_SDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
                pSPI_SDMMC->dmaInit=false;
#endif
    }
            stat = sdmmc_disk_initialize(pSPI_SDMMC);
            return stat;
        break;
        case W25Q_DRV:
        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;
        case USB_MSC_DRV:
        if (pUSB_MSC == NULL) {
            pUSB_MSC = (usb_msc_t*)malloc(sizeof(usb_msc_t));
        }
        stat = usb_msc_initialize(pUSB_MSC);
        return stat;
        break;
    }
    return STA_NOINIT;
 }
/*-----------------------------------------------------------------------*/
/* Get disk status                                                       */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE drv) {
    DSTATUS stat;
    switch (drv) {
        case SD_4_BIT_DRV:
            stat = sdio_memory_disk_status(pSDIO_MEM_CARD_4);          
            return stat;
        break;
        case SD_1_BIT_DRV:
            stat = sdio_memory_disk_status(pSDIO_MEM_CARD_1);
            return stat;
        break;
        case SPI_SDMMC_DRV:
            stat=  sdmmc_disk_status(pSPI_SDMMC); /* Return disk status */
            return stat;
        break;
        case W25Q_DRV:
            stat = pW25Q->Stat;
            return stat;
        break;
        case USB_MSC_DRV:
            //uint8_t dev_addr = pdrv + 1;
            return tuh_msc_mounted(pUSB_MSC->dev_addr) ? 0 : STA_NODISK;
        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 SD_4_BIT_DRV:       

            stat = sdio_memory_disk_read(buff, sector, count, pSDIO_MEM_CARD_4);       
            return stat;
        break;
        case SD_1_BIT_DRV:
            stat = sdio_memory_disk_read(buff, sector, count, pSDIO_MEM_CARD_1);
            return stat;
        break;
        case SPI_SDMMC_DRV: 
            stat = sdmmc_disk_read(buff, sector, count, pSPI_SDMMC);
            return stat;
        break;
        case W25Q_DRV:
            if (pW25Q->Stat & STA_NOINIT) return RES_NOTRDY;
		    w25q_read_sector((uint32_t)sector, 0, buff, count*pW25Q->sectorSize, pW25Q);
            return pW25Q->Stat;
        break;
        case USB_MSC_DRV:
            stat = usb_msc_disk_read(buff, sector, count, pUSB_MSC);
            return 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 SD_4_BIT_DRV:
            stat = sdio_memory_disk_write(buff, sector, count, pSDIO_MEM_CARD_4);
            return stat;
        break;
        case SD_1_BIT_DRV:
            stat = sdio_memory_disk_write(buff, sector, count, pSDIO_MEM_CARD_1);
            return stat;
        break;
        case SPI_SDMMC_DRV:
            stat = sdmmc_disk_write(buff, sector, count, pSPI_SDMMC);
            return stat;
        break;
        case W25Q_DRV:
            stat = w25q_disk_write(buff, sector, count, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV:
            stat = usb_msc_disk_write(buff, sector, count, pUSB_MSC);
            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 SD_4_BIT_DRV:
            stat = sdio_memory_disk_ioctl(cmd, buff, pSDIO_MEM_CARD_4);
            return stat;
        break;
        case SD_1_BIT_DRV:
            stat = sdio_memory_disk_ioctl(cmd, buff, pSDIO_MEM_CARD_1);
            return stat;
        break;
        case W25Q_DRV:
            stat = w25q_disk_ioctl(cmd, buff, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV:
            stat = usb_msc_ioctl(cmd, buff, pUSB_MSC);
            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)
    uint32_t yr = t.year - 1980;
    fattime |= (0b01111111 & yr) << 25;
    // bit24:21
    // Month (1..12)
    uint32_t mo = t.month;
    fattime |= (0b00001111 & mo) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint32_t da = t.day;
    fattime |= (0b00011111 & da) << 16;
    // bit15:11
    // Hour (0..23)
    uint32_t hr = t.hour;
    fattime |= (0b00011111 & hr) << 11;
    // bit10:5
    // Minute (0..59)
    uint32_t mi = t.min;
    fattime |= (0b00111111 & mi) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint32_t sd = t.sec / 2;
    fattime |= (0b00011111 & sd);
    return fattime;
}

void led_blinking(void)
{
    static absolute_time_t  t1;
    static bool state=false;
        
    // Blink every interval ms
    if ( absolute_time_diff_us(t1, get_absolute_time()) < 100000) return; // not enough time
    t1 = get_absolute_time();
    gpio_put(LED_BLINKING_PIN, state);
    state = !state;
}

void led_blinking_off(void) {
    gpio_put(LED_BLINKING_PIN, false);
}
  • main.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
//#include "sdio_mem_card/sdio_mem_card.h"
#include "string.h"
#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"
#include "hardware/rtc.h"

//sd_memory_card_t sd;

void test_data(uint8_t* buff) {
     // test write data    
    uint8_t ch='A';
    for (int i=0; i < 512;i++){
        buff[i] = ch;
        ch++;
        if (ch > 'Z') ch='A';
    }
    memcpy(buff+512, buff, 512);
    buff[511]='\n';
    buff[0+512] = 'a';
    buff[26+512] = 'b';
    buff[52+512] = 'c';
    buff[512+511]='\n';

    
    memcpy(buff+1024, buff, 512);
    buff[0+1024] = '1';
    buff[26+1024] = '2';
    buff[52+1024] = '3';
    buff[1024+511]='\n';
    for (uint32_t i = 1536; i< 8192; i+=512)
        memcpy(buff+i, buff, 512);
}

uint8_t test_4_bit() {
     FATFS fs;
     FIL fil_r, fil_w;
     FRESULT res;
     uint bw, br;
     uint8_t buff[16384];
    if (f_mount(&fs, SD_4_BIT_PATH, 1) != FR_OK) {
        printf("mount error\n");
        return 0;
     }
     printf("mount ok\n");
     printf("\n ==============================\ntest under 41.66Mhz SD clock frequency\n");
     
     res = f_open(&fil_r, SD_4_BIT_PATH"/pico_storage.mp4", FA_READ);
    if (res != FR_OK) {
        printf("open read error:\n");
        f_unmount(SD_4_BIT_PATH);
        return 0;
     }
    uint64_t r_out=0;
    uint64_t fsize=f_size(&fil_r);
    uint32_t i=0;
    f_close(&fil_r);
    printf("file size:%llu\n",fsize);
    uint16_t buff_size[4] = {1024, 4096, 8192, 12288};

    for (int r=0; r<4; r++) { 
         res = f_open(&fil_r, SD_4_BIT_PATH"/pico_storage.mp4", FA_READ);
        if (res != FR_OK) {
            printf("open read error:\n");
            f_unmount(SD_4_BIT_PATH);
            return 0;
        }
    r_out=0;
    printf("Test file read speed(SD-4bit): using %dK buffer\n", buff_size[r]/1024);
    absolute_time_t tt=get_absolute_time();
    do {
        res = f_read(&fil_r, buff, buff_size[r], &br);
        if (res != FR_OK) {
            printf("read error:%d\n", res);
            break;
        }
        r_out += br;
    } while(r_out < fsize);
    int64_t et=absolute_time_diff_us(tt, get_absolute_time())/1000;
    double dd = (double)(r_out)/et*1000/1024/1024;
    printf("total time:%llu ms, total_read:%d bytes, = %0.2lfMB/s\n", et, r_out, dd);
  
    printf("--------------\n\n");
    f_close(&fil_r);
    }

    printf("\n======================\n");
    printf("Test file write speed(SD-4bit): using 8K buffer\n");
    test_data(buff);
    res = f_open(&fil_w, SD_4_BIT_PATH"/8k_4_buff.txt", FA_CREATE_ALWAYS|FA_WRITE);
     if (res != FR_OK) {
        printf("open file error:\n");
        f_unmount(SD_4_BIT_PATH);
        return 0;
     }
     absolute_time_t tw=get_absolute_time();
     for (int s=0; s < 8192;s++) { // 64MB
        f_write(&fil_w, buff, 8192, &bw);
    //printf("write:%d\n", bw);
     }
     int64_t et=absolute_time_diff_us(tw, get_absolute_time())/1000;
    double dd = (double)(8192*8192)/et*1000/1024/1024;
    printf("total time:%llu ms, total_write:%d bytes, = %0.2lfMB/s\n", et, 8192*8192, dd);
    f_close(&fil_w);


    printf("\n======================\n");
    printf("Test file read in and write out speed(SD-4bit): using 8K buffer\n");
    res = f_open(&fil_r, SD_4_BIT_PATH"/pico_storage.mp4", FA_READ);
        if (res != FR_OK) {
            printf("open read error:\n");
            f_unmount(SD_4_BIT_PATH);
            return 0;
        }
    res = f_open(&fil_w, SD_4_BIT_PATH"/test_out_4.mp4", FA_CREATE_ALWAYS|FA_WRITE);
     if (res != FR_OK) {
        printf("open file error:\n");
        f_unmount(SD_4_BIT_PATH);
        return 0;
     }
    r_out=0;
     absolute_time_t tt=get_absolute_time();
    do {
    res = f_read(&fil_r, buff, 8192, &br);
    if (res != FR_OK) {
        printf("read error:%d\n", res);
        break;
    }
    f_write(&fil_w, buff, br, &bw);
    if (res != FR_OK) {
        printf("write error:%d\n", res);
        break;
    }
    r_out += br;
    //if ((i++%100)==0) { 
    //printf("read bytes:%d\n", br);
   // printf("write  :%llu\n", r_out);
    //}
    } while(r_out < fsize);
    printf("total time:%llu ms, total_read:%d bytes\n", absolute_time_diff_us(tt, get_absolute_time())/1000, r_out);
    f_close(&fil_r);
    f_close(&fil_w);


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


     f_unmount(SD_4_BIT_PATH);
}

uint8_t test_1_bit() {
     FATFS fs;
     FIL fil_r, fil_w;
     FRESULT res;
     uint bw, br;
     uint8_t buff[16384];
    if (f_mount(&fs, SD_1_BIT_PATH, 1) != FR_OK) {
        printf("mount error\n");
        return 0;
     }
     printf("mount ok\n");
     printf("\n ==============================\ntest under 41.66Mhz SD clock frequency\n");
     
     res = f_open(&fil_r, SD_1_BIT_PATH"/pico_storage.mp4", FA_READ);
    if (res != FR_OK) {
        printf("open read error:\n");
        f_unmount(SD_1_BIT_PATH);
        return 0;
     }
    uint64_t r_out=0;
    uint64_t fsize=f_size(&fil_r);
    uint32_t i=0;
    f_close(&fil_r);
    printf("file size:%llu\n",fsize);
    uint16_t buff_size[4] = {1024, 4096, 8192, 12288};

    for (int r=0; r<4; r++) { 
         res = f_open(&fil_r, SD_1_BIT_PATH"/pico_storage.mp4", FA_READ);
        if (res != FR_OK) {
            printf("open read error:\n");
            f_unmount(SD_1_BIT_PATH);
            return 0;
        }
    r_out=0;
    printf("Test file read speed(SD-1bit): using %dK buffer\n", buff_size[r]/1024);
    absolute_time_t tt=get_absolute_time();
    do {
        res = f_read(&fil_r, buff, buff_size[r], &br);
        if (res != FR_OK) {
            printf("read error:%d\n", res);
            break;
        }
        r_out += br;
    } while(r_out < fsize);
    int64_t et=absolute_time_diff_us(tt, get_absolute_time())/1000;
    double dd = (double)(r_out)/et*1000/1024/1024;
    printf("total time:%llu ms, total_read:%d bytes, = %0.2lfMB/s\n", et, r_out, dd);
  
    printf("--------------\n\n");
    f_close(&fil_r);
    }

    printf("\n======================\n");
    printf("Test file write speed(SD-1bit): using 8K buffer\n");
    test_data(buff);
    res = f_open(&fil_w, SD_1_BIT_PATH"/8k_1_buff.txt", FA_CREATE_ALWAYS|FA_WRITE);
     if (res != FR_OK) {
        printf("open file error:\n");
        f_unmount(SD_1_BIT_PATH);
        return 0;
     }
     absolute_time_t tw=get_absolute_time();
     for (int s=0; s < 8192;s++) { // 64MB
        f_write(&fil_w, buff, 8192, &bw);
     }
     int64_t et=absolute_time_diff_us(tw, get_absolute_time())/1000;
    double dd = (double)(8192*8192)/et*1000/1024/1024;
    printf("total time:%llu ms, total_write:%d bytes, = %0.2lfMB/s\n", et, 8192*8192, dd);
    f_close(&fil_w);


    printf("\n======================\n");
    printf("Test file read in and write out speed(SD-1bit): using 8K buffer\n");
    res = f_open(&fil_r, SD_1_BIT_PATH"/pico_storage.mp4", FA_READ);
        if (res != FR_OK) {
            printf("open read error:\n");
            f_unmount(SD_1_BIT_PATH);
            return 0;
        }
    res = f_open(&fil_w, SD_1_BIT_PATH"/test_out_1.mp4", FA_CREATE_ALWAYS|FA_WRITE);
     if (res != FR_OK) {
        printf("open file error:\n");
        f_unmount(SD_1_BIT_PATH);
        return 0;
     }
    r_out=0;
     absolute_time_t tt=get_absolute_time();
    do {
    res = f_read(&fil_r, buff, 8192, &br);
    if (res != FR_OK) {
        printf("read error:%d\n", res);
        break;
    }
    f_write(&fil_w, buff, br, &bw);
    if (res != FR_OK) {
        printf("write error:%d\n", res);
        break;
    }
    r_out += br;
    //if ((i++%100)==0) { 
    //printf("read bytes:%d\n", br);
   // printf("write  :%llu\n", r_out);
    //}
    } while(r_out < fsize);
    printf("total time:%llu ms, total_read:%d bytes\n", absolute_time_diff_us(tt, get_absolute_time())/1000, r_out);
    f_close(&fil_r);
    f_close(&fil_w);

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


     f_unmount(SD_1_BIT_PATH);
}


int main()
{
     stdio_init_all();
     printf("start\n");
     
    

     
 
     datetime_t t = {2023, 10, 20, 5, 3, 0, 0};
    rtc_set_datetime(&t);
    sleep_ms(1000);

    test_1_bit();
    test_4_bit();
        

    while(1);
    
    puts("Hello, world!");

    return 0;
}

  • CMakeListx.txt(root)
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

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

set(PICO_BOARD pico CACHE STRING "Board type")

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

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(SDIO_MEM C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

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

add_executable(SDIO_MEM main.c)

pico_set_program_name(SDIO_MEM "SDIO_MEM")
pico_set_program_version(SDIO_MEM "0.1")

pico_enable_stdio_uart(SDIO_MEM 1)
pico_enable_stdio_usb(SDIO_MEM 0)

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

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


# Add any user requested libraries
#target_link_libraries(SDIO_MEM 
#        hardware_dma
#        hardware_pio
#                )

add_subdirectory(pico_storage_drv)
# Add any user requested libraries
target_link_libraries(SDIO_MEM 
        tinyusb_host 
        tinyusb_board
        pico_storage_drv
        ) 


pico_add_extra_outputs(SDIO_MEM)