本文章介紹使用Raspberry Pi Pico的PIO功能,擷取INMP441 I2S microphone製作一台錄音機,將檔案存在SD卡上。連接USB Host時,可透過USB MSC device讀取錄音檔案。
一、測試項目:
- 根據inmp441 datasheet擷取24 bits的 32 bit frame, LSB 8bit 為0, 2 channels, 32000 sample rate.
- 擷取16 bit 的32 bit frame, LSB 16 bits補0, 2 channels, 32000 sample rate.
- 擷取16 bit 的16 bit frame, 2 channels, 44100 sample rate.
- 擷取16 bit 的16 bit frame, 1 channels, 44100 sample rate.
二、INMP441 datasheet節錄:
- The slave serial-data port’s format is I2S, 24-bit, two's complement.
- There must be 64 SCK cycles in each WS stereo frame, or 32 SCK cycles per data-word.
- 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上。
因為資料是2's complement,如下圖所示 00=+0, ff=-0,所以音量非常小。
詳細程式碼附於文章末尾。
四、成果影片:
五、程式碼:
- 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)
Hi! Greetings from Ensenada, Mexico. I found your entry very interesting and would like to know if you are available to share your code in a zip file or GitHub page. I am working on a personal project to create multiple audio processing filters using RP2040 and probably make use of the pio code, and would like to use your code as a reference. Thanks!
回覆刪除