使用Platformio平台的libopencm3开发框架来开发STM32G030,下面介绍SD卡模块的使用方法。
1 新建项目
- 从仓库 MonkeyPi-STM32G030-PIO_Libopencm3_example/template_libopencm3 at main · makerinchina-iot/MonkeyPi-STM32G030-PIO_Libopencm3_example 下载一个模版;
- 更改名字为spisdcard,然后使用VSCode打开该文件夹;
- 然后更改项目文件platformio.ini的烧写和调试方式,可以选择DAPLink或者串口下载:
;;;; //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 文件,现在目录结构如下:
- 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:
STM32 | SD-Card |
---|---|
PA0 | SCK |
PA4 | MOSI |
PA5 | CS |
PB0 | MISO |
3V3 | 3V3 |
GND | GND |
4 烧写测试
将程序烧写到开发板后,打开日志打印,可以看到测试成功:
注:完整代码查看仓库:MonkeyPi-STM32G030-PIO_Libopencm3_example/spi_sdcard at main · makerinchina-iot/MonkeyPi-STM32G030-PIO_Libopencm3_example
评论