注意,本博客适合像我一样的小白,会的不多,但是想快速做些东西,不适合会写驱动的大佬。另外,示例代码中的注释有误(从多个项目中移植过来的,未做更改),请不要被误导!!!
【免费】stm32f103c8t6SD卡驱动(Fatfs)资源-CSDN文库
一、copy源码,移植
我在CSDN上找到了一位大佬用HAL库和fatfs实现stm32f103c8t6对micro SD卡读写的驱动。于是借(抄)鉴(袭)之。
我决定使用SPI2,查阅引脚定义图可得
* CS -》 PB12
* SCK -》 PB13
* MISO -》 PB14
* MOSI -》 PB15
于是编写SPI初始化函数(CubeIDE编辑生成的初始化代码好像在main.c和xxx_msp.c,比较散乱,于是直接上手写)
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //开启SPI1的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA4引脚初始化为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA5和PA7引脚初始化为复用推挽输出
/*SPI初始化*/
SPI_InitTypeDef SPI_InitStructure; //定义结构体变量
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //模式,选择为SPI主模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //方向,选择2线全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据宽度,选择为8位
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //先行位,选择高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频,选择128分频
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI极性,选择低极性
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS,选择由软件控制
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC多项式,暂时用不到,给默认值7
SPI_Init(SPI2, &SPI_InitStructure); //将结构体变量交给SPI_Init,配置SPI1
SPI_Cmd(SPI2, ENABLE); //使能SPI1,开始运行
/*设置默认电平*/
SD_CS(1); //SS默认高电平
从原子哥的讲解来看,我们还需要更改SPI的速度,(也就是SPI_baudratePrescaler)可是 标准库当中并没有给出,我们只好操作寄存器,编写该函数(我比较菜,直接从原子哥的fatfs的SPI源码中copying过来)
函数如下
void SPI_setspeed(uint16_t SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler; //����SPI2�ٶ�
SPI_Cmd(SPI2,ENABLE);
/*设置默认电平*/
SD_CS(1); //SS默认高电平
}
之后出现了一些灵异事件!
main.c中有一个读写文件的实例函数:
void WritetoSD(char SD_FileName[], uint8_t write_buff[],uint8_t bufSize)
// {
// FATFS fs;
// FIL file;
// uint8_t res=0;
// UINT Bw;
// res = SD_init(); //SD卡初始化
// if(res == 1)
// {
// UsartPrintf(USART_DEBUG,"SD卡初始化失败! \r\n");
// OLED_ShowString(1,1,"failed!");
// }
// else
// {
// UsartPrintf(USART_DEBUG,"SD卡初始化成功! \r\n");
// OLED_ShowString(1,1,"init successed!");
// }
// res=f_mount(&fs,"0:",1); //挂载
// // if(test_sd == 0) //用于测试格式化
// if(res == FR_NO_FILESYSTEM) //没有文件系统,格式化
// {
// // test_sd =1; //用于测试格式化
// UsartPrintf(USART_DEBUG,"没有文件系统! \r\n");
// OLED_ShowString(2,1,"no file system!");
// res = f_mkfs("", 0, 0); //格式化sd卡
// if(res == FR_OK)
// {
// UsartPrintf(USART_DEBUG,"格式化成功! \r\n");
// OLED_ShowString(1,1,"geshihua sed!");
// res = f_mount(NULL,"0:",1); //格式化后先取消挂载
// res = f_mount(&fs,"0:",1); //重新挂载
// if(res == FR_OK)
// {
// UsartPrintf(USART_DEBUG,"SD卡已经成功挂载,可以进进行文件写入测试!\r\n");
// OLED_ShowString(3,1,"can test");
// }
// }
// else
// {
// UsartPrintf(USART_DEBUG,"格式化失败! \r\n");
// OLED_ShowString(1,1,"geshihua fad!");
// }
// }
// else if(res == FR_OK)
// {
// UsartPrintf(USART_DEBUG,"挂载成功! \r\n");
// OLED_ShowString(1,1,"guazai sed!");
// }
// else
// {
// UsartPrintf(USART_DEBUG,"挂载失败! \r\n");
// OLED_ShowString(1,1,"guazai fad!");
// }
// res = f_open(&file,SD_FileName,FA_OPEN_ALWAYS |FA_WRITE);
// if((res & FR_DENIED) == FR_DENIED)
// {
// UsartPrintf(USART_DEBUG,"卡存储已满,写入失败!\r\n");
// OLED_ShowString(1,1,"write sed!");
// }
// f_lseek(&file, f_size(&file));//确保写词写入不会覆盖之前的数据
// if(res == FR_OK)
// {
// UsartPrintf(USART_DEBUG,"打开成功/创建文件成功! \r\n");
// OLED_ShowString(1,1,"create sed!");
// res = f_write(&file,write_buff,bufSize,&Bw); //写数据到SD卡
// if(res == FR_OK)
// {
// UsartPrintf(USART_DEBUG,"文件写入成功! \r\n");
// OLED_ShowString(1,1,"write sed!");
// }
// else
// {
// UsartPrintf(USART_DEBUG,"文件写入失败! \r\n");
// OLED_ShowString(1,1,"write fad!");
// }
// }
// else
// {
// OLED_ShowString(1,1,"open fad!");
// UsartPrintf(USART_DEBUG,"打开文件失败!\r\n");
// }
// f_close(&file); //关闭文件
// f_mount(NULL,"0:",1); //取消挂载
// }
这个函数在运行时一直卡住,在函数第一行写了USARTprintf也没反应!检查接线也没问题!我删除以上代码,直接在main的开头写USART_Printf电脑却能收到!因此,我认为应该是乱码的问题(因为vscode中所有中文注释都不能正常识别,应该是编码错误),导致了一些编译上的错误 ,于是把所有的printf中的中文全部重写,然后再次编译,结果正常。
但是,我发现SPI一直卡在等待发送完成标志位上,检查连接没有问题。苦苦debug两天,最后,在一位大佬的博客中看到RCC_APB1PeriphClockCmd写错导致SPI没时序,我瞬间明白!!!
注意!!!SPI2是APB1上的外设,开启时钟要用RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);不要写成RCC_APB2PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);或者RCC_AHBPeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);写代码或者移植代码时一定要仔细查验。
二、分析源码
课件图片+源代码(分析都写在注释里了)
/**
* @brief 向SD卡发送指令,指令共48位
* @param cmd 命令 8 bit
* @param arg 命令参数 32 bit
* @param crc crc校验值 8 bit
*/
int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc){
uint8_t r1;
uint8_t retry;
SD_CS(0);
Delay_ms(20);
SD_CS(1);
//看看SD卡是否准备好
//因为向SD卡发送数据时,MISO会拉低,直到完成写入才会为1
do{
retry=spi_readwrite(DFF);
}while(retry!=0xFF);
spi_readwrite(cmd | 0x40);
//从高位开始发送
spi_readwrite(arg >> 24);
spi_readwrite(arg >> 16);
spi_readwrite(arg >> 8);
spi_readwrite(arg);
spi_readwrite(crc);
if(cmd==CMD12)spi_readwrite(DFF);//跳过无效字节
do
{
r1=spi_readwrite(0xFF);
}while(r1&0X80);
return r1;
}
/**
* @brief 读SD卡
* @param buf 数据缓冲区
* @param sector 扇区
* @param CNT 扇区数
* @retval
*/
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
uint8_t r1;
if(SD_TYPE!=V2HC)sector <<= 9;//转换为字节地址 <<9 就是*=512
if(cnt==1)
{
r1=SD_sendcmd(CMD17,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SD_ReceiveData(buf,512);//接收512个字节
}
}else
{
r1=SD_sendcmd(CMD18,sector,0X01);//连续读命令
do
{
r1=SD_ReceiveData(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
SD_sendcmd(CMD12,0,0X01); //发送停止命令
}
SD_CS(0);//取消片选
return r1;
}
/**
* @brief 写入数据
* @param buf 缓冲区
* @param sector 开始扇区
* @param cnt 扇区数
* @retval 0 成功; 其他 失败
*
*/
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
uint8_t r1;
if(SD_TYPE!=V2HC)sector *= 512;//转换为字节地址
if(cnt==1)
{
r1=SD_sendcmd(CMD24,sector,0X01);//读命令
if(r1==0)//发送成功
{
r1=SD_SendBlock(buf,0xFE);//写512字节
}
}else
{
if(SD_TYPE!=MMC)
{
SD_sendcmd(CMD55,0,0X01);
SD_sendcmd(CMD23,cnt,0X01);//预先擦除
}
r1=SD_sendcmd(CMD25,sector,0X01);//发送连续写命令
if(r1==0)
{
do
{
r1=SD_SendBlock(buf,0xFC);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
r1=SD_SendBlock(0,0xFD);//发送连续写结束命令
}
}
SD_CS(0);//取消片选
return r1;
}
但是试验之后,一直卡在
do
{
r1 = SD_sendcmd(CMD0 ,0, 0x95);
}
while(r1!=0x01);
个人猜测是SD卡不支持SPI读取,可以换用SDIO读取,也可能只是片选和取消片选时没有等待8clk。爆肝一天,实在受不了,剩下的后续会完善。。。