STM32G030-Platformio+libopencm3-SPI接口SD卡使用

MCU  ·  2025-04-26

使用Platformio平台的libopencm3开发框架来开发STM32G030,下面介绍SD卡模块的使用方法。

1 新建项目

;;;; //use swd
upload_protocol=cmsis-dap

;;;; //use custom uart isp
; upload_protocol = custom
; upload_port = COM22
; upload_flags = 
;     -f 
;     G0
; upload_command = $PYTHONEXE -m stm32loader -p $UPLOAD_PORT -e -w -v -s $UPLOADERFLAGS $BUILD_DIR/${PROGNAME}.bin

2 编写程序

2.1 加入FATFS库
  • 从网站fatfs http://elm-chan.org/fsw/ff/00index_e.html 下载最新的源码;
  • 工程目录lib下新建fatfs文件夹;
  • 然后将fatfs源码的source目录下所有文件放置到工程的lib\fatfs目录下;
  • 将diskio.c文件移动到src目录下,这个文件是需要我们实现的底层接口;
2.2 实现SPI接口的SD读写

在src目录下新建spi_sd.h 和 spi_sd.c 文件,现在目录结构如下:

image-20220918102647364.png

  • spi_sd.h 头文件
/**
 * @file spi_sd.h
 * @author MakerInChina (makerinchina.cn)
 * @brief 
 * @version 0.01
 * @date 2022-09-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#ifndef _SPI_SD_HEAD_H_
#define _SPI_SD_HEAD_H_

#include <stdint.h>

/**
 * @brief init sd card
 * 
 */
uint8_t spi_sd_init();

/**
 * @brief spi read sd
 * 
 * @param buff 
 * @param sector 
 */
uint8_t spi_sd_read(uint8_t *buff, uint32_t sector);

/**
 * @brief spi write sd
 * 
 * @param buff 
 * @param sector 
 */
uint8_t spi_sd_write(uint8_t *buff, uint32_t sector);

#endif //!_SPI_SD_HEAD_H_
  • spi_sd.c 实现
/**
 * @file spi_sd.c
 * @author MakerInChina (makerinchina.cn)
 * @brief 
 * @version 0.01
 * @date 2022-09-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "spi_sd.h"

#include <libopencm3/stm32/spi.h>
#include <libopencm3/stm32/gpio.h>

#define SDSPI    SPI2
#define SD_CS    GPIO5
#define SD_PORT  GPIOA

#define spi_cs_deselect()   gpio_set(SD_PORT, SD_CS)
#define spi_cs_select()     gpio_clear(SD_PORT, SD_CS)


/* Definitions for MMC/SDC command */
#define CMD0    (0x40+0)    /* GO_IDLE_STATE */
#define CMD1    (0x40+1)    /* SEND_OP_COND (MMC) */
#define ACMD41    (0xC0+41)    /* SEND_OP_COND (SDC) */
#define CMD8    (0x40+8)    /* SEND_IF_COND */
#define CMD9    (0x40+9)    /* SEND_CSD */
#define CMD10    (0x40+10)    /* SEND_CID */
#define CMD12    (0x40+12)    /* STOP_TRANSMISSION */
#define ACMD13    (0xC0+13)    /* SD_STATUS (SDC) */
#define CMD16    (0x40+16)    /* SET_BLOCKLEN */
#define CMD17    (0x40+17)    /* READ_SINGLE_BLOCK */
#define CMD18    (0x40+18)    /* READ_MULTIPLE_BLOCK */
#define CMD23    (0x40+23)    /* SET_BLOCK_COUNT (MMC) */
#define ACMD23    (0xC0+23)    /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24    (0x40+24)    /* WRITE_BLOCK */
#define CMD25    (0x40+25)    /* WRITE_MULTIPLE_BLOCK */
#define CMD55    (0x40+55)    /* APP_CMD */
#define CMD58    (0x40+58)    /* READ_OCR */

#define CT_MMC 0x01 /* MMC ver 3 */
#define CT_SD1 0x02 /* SD ver 1 */
#define CT_SD2 0x04 /* SD ver 2 */
#define CT_SDC (CT_SD1|CT_SD2) /* SD */
#define CT_BLOCK 0x08 /* Block addressing */

static uint8_t spi_read_write8(uint32_t spi, uint8_t tx);
static uint8_t wait_ready(void);
static uint8_t send_cmd (uint8_t cmd,uint32_t arg);
static void set_spi_slow();
static void set_spi_fast();

/**
 * @brief init sd card
 * 
 */
uint8_t spi_sd_init()
{
    uint8_t n, cmd, ty, ocr[4];
    uint16_t i;


    //init with low speed
    // set_spi_slow();

    spi_cs_select();

    for (n = 10; n; n--) spi_read_write8(SDSPI,0xff);    /* 80 dummy clocks */
    
    ty = 0;
    
    /* Enter Idle state */
    ty = send_cmd(CMD0, 0);

    // printf("  > enter idle:%d\n", ty);

    /* Initialization timeout of 1000 milliseconds */
    /* SDHC */

    if(ty == 1){
     
        if (send_cmd(CMD8, 0x1AA) == 1){ /* SDv2? */
        /* Get trailing return value of R7 response */
            for (n = 0; n < 4; n++) ocr[n] = spi_read_write8(SDSPI,0xff);
            /* The card can work at VDD range of 2.7-3.6V */
            if (ocr[2] == 0x01 && ocr[3] == 0xAA){
                /* Wait for leaving idle state (ACMD41 with HCS bit) */
                i=0xfff;
                while (--i && send_cmd(ACMD41, 1UL << 30));
                if (i && send_cmd(CMD58, 0) == 0){
                /* Check CCS bit in the OCR */
                    for (n = 0; n < 4; n++) ocr[n] = spi_read_write8(SDSPI,0xff);
                    ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
                    
                }
            }
        }  else {    /* Not SDv2 card */
            // if (send_cmd(ACMD41, 0) <= 1)     {    /* SDv1 or MMC? */
            //     ty = CT_SD1; cmd = ACMD41;    /* SDv1 (ACMD41(0)) */
            // } else {
            //     ty = CT_MMC; cmd = CMD1;    /* MMCv3 (CMD1(0)) */
            // }

            // while (SPI_Timer_Status() && send_cmd(cmd, 0)) ;        /* Wait for end of initialization */
            // if (!SPI_Timer_Status() || send_cmd(CMD16, 512) != 0)    /* Set block length: 512 */
            //     ty = 0;
        }

    }
    //CardType = ty;
    

    spi_cs_deselect();
    spi_read_write8(SDSPI,0xff);
    while (SPI_SR(SDSPI) & SPI_SR_BSY);

    set_spi_fast();

    return ty;
}

/**
 * @brief spi read sd
 * 
 * @param buff 
 * @param sector 
 */
uint8_t spi_sd_read(uint8_t *buff, uint32_t sector)
{
    uint8_t result;
    uint16_t cnt=0xffff;
    spi_cs_select();    
    result=send_cmd(CMD17, sector); //CMD17 даташит стр 50 и 96
    if (result){spi_cs_deselect(); return 5;} //Выйти, если результат не 0x00
    
    spi_read_write8(SDSPI,0xff);
    cnt=0;
    do result=spi_read_write8(SDSPI,0xff); while ((result!=0xFE)&&--cnt);
    if(!cnt){spi_cs_deselect(); return 5;}
    
    for (cnt=0;cnt<512;cnt++) *buff++=spi_read_write8(SDSPI,0xff); 
    
    spi_read_write8(SDSPI,0xff); 
    spi_read_write8(SDSPI,0xff);
    spi_cs_deselect();
    spi_read_write8(SDSPI,0xff);

    return 0;
}

/**
 * @brief spi write sd
 * 
 * @param buff 
 * @param sector 
 */
uint8_t spi_sd_write(uint8_t *buff, uint32_t sector)
{
    uint8_t result;
    uint16_t cnt=0xffff;
    spi_cs_select();
    result=send_cmd(CMD24,sector); //CMD24 
    if(result){spi_cs_deselect(); return 6;} //
    spi_read_write8(SDSPI,0xff);
    spi_read_write8(SDSPI,0xfe);//
    for (cnt=0;cnt<512;cnt++) spi_read_write8(SDSPI,buff[cnt]); //Данные
    spi_read_write8(SDSPI,0xff);
    spi_read_write8(SDSPI,0xff);
    result=spi_read_write8(SDSPI,0xff);
    //result=wait_ready();
    if((result&0x05)!=0x05){spi_cs_deselect(); return 6;} 
    //spi_read_write8(SDSPI,0xff);
    while (SPI_SR(SDSPI) & SPI_SR_BSY);
    //
    spi_cs_deselect();
    spi_read_write8(SDSPI,0xff);
    return 0;
}


static void set_spi_slow()
{
    // spi_disable(SDSPI);
    spi_set_baudrate_prescaler(SDSPI,SPI_CR1_BAUDRATE_FPCLK_DIV_128);
    // spi_enable(SDSPI);
}

static void set_spi_fast()
{
    // spi_disable(SDSPI);
    spi_set_baudrate_prescaler(SDSPI,SPI_CR1_BAUDRATE_FPCLK_DIV_8);
    // spi_enable(SDSPI);
}

static uint8_t spi_read_write8(uint32_t spi, uint8_t tx)
{
    spi_send8(spi, tx);
    return spi_read8(spi);
}

static uint8_t wait_ready(void)
{
    uint8_t res;
    uint16_t cnt=0xffff;
    spi_read_write8(SDSPI, 0xff);
    do res = spi_read_write8(SDSPI, 0xff); while ((res!=0xFF)&& --cnt );
    return res;
}

static uint8_t send_cmd (uint8_t cmd,uint32_t arg)
{
    uint8_t n, res;

    /* ACMD<n> is the command sequence of CMD55-CMD<n> */
    if (cmd & 0x80){
        cmd &= 0x7F;
        res = send_cmd(CMD55, 0);
        if (res > 1) return res;
    }

    if (wait_ready()!=0xFF) return 0xFF;
    /* Send command packet */
    spi_read_write8(SDSPI, cmd);                    /* Start + Command index */
    spi_read_write8(SDSPI,(uint8_t)(arg >> 24));    /* Argument[31..24] */
    spi_read_write8(SDSPI,(uint8_t)(arg >> 16));    /* Argument[23..16] */
    spi_read_write8(SDSPI,(uint8_t)(arg >> 8));    /* Argument[15..8] */
    spi_read_write8(SDSPI,(uint8_t)arg);            /* Argument[7..0] */
    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_read_write8(SDSPI,n);
    /* Receive command response */
    if (cmd == CMD12) spi_read_write8(SDSPI,0xff);    
    /* Skip a stuff byte when stop reading */
    /* Wait for a valid response in timeout of 10 attempts */
    n = 10;
    do res=spi_read_write8(SDSPI,0xff); while ((res & 0x80) && --n);
    
    while (SPI_SR(SDSPI) & SPI_SR_BSY); //wait if busy

    return res;                       /* Return with the response value */
}

注:这里初始化部分只写了SDv2的判断;

  • diskio.c 调用 spi_sd的读写接口:
/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
    BYTE pdrv,        /* Physical drive nmuber to identify the drive */
    BYTE *buff,        /* Data buffer to store read data */
    LBA_t sector,    /* Start sector in LBA */
    UINT count        /* Number of sectors to read */
)
{
    while(count){
        if (spi_sd_read(buff,sector)) return RES_ERROR;
        --count;
        ++sector;
        buff+=512;
    }
    return RES_OK;
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
    BYTE pdrv,            /* Physical drive nmuber to identify the drive */
    const BYTE *buff,    /* Data to be written */
    LBA_t sector,        /* Start sector in LBA */
    UINT count            /* Number of sectors to write */
)
{
    while(count){
        if(spi_sd_write(buff,sector)) return RES_ERROR;
        --count;
        ++sector;
        buff+=512;
    }
    return RES_OK;
}
2.3 SPI接口配置
static void spi2_init(void)
{
    // spi2 - display
    /* Enable SPI2 Periph and gpio clocks */
    rcc_periph_clock_enable(RCC_SPI2);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_GPIOB);

    /* Configure GPIOs:
     *
     * SCK=PA0
     * MOSI=PA4
     * MISO=PB0/1/2
     *
     * for SD card
     * SDCS PA5
     */

    // MOSI & SCK & MISO
    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO0);
    gpio_set_af(GPIOA, GPIO_AF0, GPIO0);
    gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO0);

    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO4);
    gpio_set_af(GPIOA, GPIO_AF1, GPIO4);
    gpio_set_output_options(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO4);

    gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2);
    gpio_set_af(GPIOB, GPIO_AF1, GPIO2);
    gpio_set_output_options(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, GPIO2);

    // SDCS
    gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5);

    gpio_set(GPIOA, GPIO5);

    /* Reset SPI, SPI_CR1 register cleared, SPI is disabled */
    spi_reset(SPI2);

    /* Set up SPI in Master mode with:
     * Clock baud rate
     * Clock polarity
     * Clock phase
     * Frame format MSB
     */
    spi_init_master(SPI2, SPI_CR1_BAUDRATE_FPCLK_DIV_128,
                    SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE,
                    SPI_CR1_CPHA_CLK_TRANSITION_1,
                    SPI_CR1_MSBFIRST);

    spi_set_data_size(SPI2, SPI_CR2_DS_8BIT);
    spi_set_full_duplex_mode(SPI2);

    spi_fifo_reception_threshold_8bit(SPI2);

    // spi_set_dff_8bit(SPI2);//not

    // SPI_CR2(SPI2) |= SPI_CR2_NSSP; //NSSP, ?? clock continus
    // spi_set_unidirectional_mode(SPI2);
    // SPI_CR2(SPI2) &= (~SPI_CR2_FRF_TI_MODE); //motorala mode
    // SPI_CR2(SPI2) |= SPI_CR2_FRF_TI_MODE;

    /*
     * Set NSS management to software.
     *
     * Note:
     * Setting nss high is very important, even if we are controlling
     * the GPIO
     * ourselves this bit needs to be at least set to 1, otherwise the spi
     * peripheral will not send any data out.
     */
    spi_enable_software_slave_management(SPI2);
    spi_set_nss_high(SPI2);

    /* Enable SPI2 periph. */
    spi_enable(SPI2);
}

SPI使用的SPI2,CS使用软件控制;

2.4 SD卡读写测试

main函数如下:

int main(void)
{
    sys_clock_setup();

    uart_init();

    log_init(LOG_TRACE, false);

    // spi
    spi2_init();

    // sd
    uint8_t sd_type = spi_sd_init();

    // // sdv2: 0x08|0x04 . 0x0c
    // log_debug(" sd_type: %x\n", sd_type);

    FATFS fs;
    FRESULT res;
    res = f_mount(&fs, "", 0);
    if (res != FR_OK)
    {
        log_error("mount fs failed, res = %d\r\n", res);
        return -1;
    }
    else
    {
        log_debug(" mount fs OK.\n");
    }

    FIL fd;

    res = f_open(&fd, "test.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
    if (res != FR_OK)
    {
        log_error("open file failed: %d\n", res);
    }
    else
    {
        log_debug("open file OK.\n");
    }

    char buff[128] = {0};
    sprintf(buff, "test data to write fs at time:%s %s\r\n", __DATE__, __TIME__);
    UINT len = 0;
    res = f_write(&fd, buff, strlen(buff), &len);
    if (res != FR_OK)
    {
        log_error(" write file failed.\n");
    }
    else
    {
        log_debug(" write file OK, write size %d .\n", len);
    }

    res = f_lseek(&fd, 0);
    if (res != FR_OK)
    {
        log_error(" lseek file failed.\n");
    }
    else
    {
        log_debug(" lseek file to 0 OK.\n");
    }

    UINT rd_len = 0;
    char rd_buff[128] = {0};
    res = f_read(&fd, rd_buff, 128, &rd_len);
    if (res != FR_OK)
    {
        log_error(" read file failed.");
    }
    else
    {
        log_debug(" read file OK, read size %d.\n", rd_len);
        log_debug(" read content: %s \n", rd_buff);
    }

    res = f_close(&fd);
    if (res != FR_OK)
    {
        log_error(" close fs failed.\n");
    }
    else
    {
        log_debug(" close fs OK.\n");
    }

    res = f_unmount("");
    if (res != FR_OK)
    {
        log_error("Unmount fs failed, res = %d\r\n", res);
        return -1;
    }
    else
    {
        log_debug("Unmound fs OK.\n");
    }

    while (1)
    {
    }

    return 0;
}

main中测试SD卡挂载、读写;

3 硬件连接

硬件引脚按如下方式连接到开发板的SPI2:

STM32SD-Card
PA0SCK
PA4MOSI
PA5CS
PB0MISO
3V33V3
GNDGND

4 烧写测试

将程序烧写到开发板后,打开日志打印,可以看到测试成功:

1745631246533.png

注:完整代码查看仓库:MonkeyPi-STM32G030-PIO_Libopencm3_example/spi_sdcard at main · makerinchina-iot/MonkeyPi-STM32G030-PIO_Libopencm3_example
评论
MonkeyPi. All Rights Reserved.

ICP粤ICP备19095914号-2