普通方式控制SD
1、打开STM32CubeMX新建工程,选择芯片。
2、配置基本外设:
(1)配置SYS,打开调试口。我用Jlink-OB,就选SYS下面的Debug选项中的Trace Asynchronous Sw选项,根据实际自行选择。这一步很重要,如果忘记配置,再刷程序就很麻烦了。将Timebase Source选为TIM6,这一步尽量做,避免以后使用FreeRtos时发生冲突。
(2)配置RCC。展开后HSE选为Crystal/Ceramic Resonator。
(3)启用Uart1。Mode改为Asynchronous 。
(4)启用SDIO总线。在SDMMC1中,配置Mode为SD 4 bits Wide bus。
到此,基本外设IO配置完毕。
3、配置时钟。
时钟源选为HSE,SDMMC1时钟不得超过48MHz,注意图中的3个箭头指向位置。
4、详细配置
在Configuration选项卡中,点击SDMMC1,配置如下图。
注意,分频系数(图中蓝色选中部分)可以使用默认值0,跟卡的体质有关,建议先使用4分频,也就是12M作为时钟,适应大部分sd卡的体质。后期调出来可以改回0来使用高速的读写。
USART1保持默认即可。
5、工程配置与源码生成。
选择project --> settings。输入工程名称,保存。然后生成源码,打开工程。
6、源码修改
打开工程后,打开main.c文件。
我们先添加uart1的printf函数的支持。
(1)先添加头文件。
在/* USER CODE BEGIN Includes */后,添加
#include "stdio.h"
(2)添加支持printf函数
在/* USER CODE BEGIN 0 */后添加如下源码:
//加入以下代码,支持printf函数,使用printf函数从串口输出。
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕
USART1->TDR=(uint8_t)ch;
return ch;
}
#endif
接下来,添加测试函数,接着上一步位置加入代码:
/**
* 函数功能: 检查缓冲区的数据是否为0xff或0
* 输入参数: pBuffer:要比较的缓冲区的指针
* BufferLength:缓冲区长度
* 返 回 值: PASSED:缓冲区的数据全为0xff或0
* FAILED:缓冲区的数据至少有一个不为0xff或0
* 说 明: 无
*/
TestStatus eBuffercmp(uint32_t* pBuffer, uint32_t BufferLength)
{
while (BufferLength--)
{
/* SD卡擦除后的可能值为0xff或0 */
if ((*pBuffer != 0xFFFFFFFF) && (*pBuffer != 0))
{
return FAILED;
}
pBuffer++;
}
return PASSED;
}
/**
* 函数功能: SD卡擦除测试
* 输入参数: 无
* 返 回 值: 无
* 说 明: 无
*/
void SD_EraseTest(void)
{
/* 第1个参数为SD卡句柄,第2个参数为擦除起始地址,第3个参数为擦除结束地址 */
sd_status=HAL_SD_Erase(&hsd1,WRITE_READ_ADDRESS,WRITE_READ_ADDRESS+NUMBER_OF_BLOCKS*4);
printf("erase status:%d\r\n",sd_status);
HAL_Delay(500);
if (sd_status == HAL_OK)
{
/* 读取刚刚擦除的区域 */
sd_status = HAL_SD_ReadBlocks(&hsd1,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
printf("erase read status:%d\r\n",sd_status);
/* 把擦除区域读出来对比 */
test_status = eBuffercmp(Buffer_Block_Rx,BLOCK_SIZE*NUMBER_OF_BLOCKS);
if(test_status == PASSED)
printf("》擦除测试成功!\r\n" );
else
printf("》擦除不成功,数据出错!\r\n" );
}
else
{
printf("》擦除测试失败!部分SD不支持擦除,只要读写测试通过即可\r\n" );
}
}
/**
* 函数功能: 在缓冲区中填写数据
* 输入参数: pBuffer:要填充的缓冲区
* BufferLength:要填充的大小
* Offset:填在缓冲区的第一个值
* 返 回 值: 无
* 说 明: 无
*/
void Fill_Buffer(uint32_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{
uint32_t index = 0;
/* 填充数据 */
for (index = 0; index < BufferLength; index++ )
{
pBuffer[index] = index + Offset;
}
}
/**
* 函数功能: 比较两个缓冲区中的数据是否相等
* 输入参数: pBuffer1:要比较的缓冲区1的指针
* pBuffer2:要比较的缓冲区2的指针
* BufferLength:缓冲区长度
* 返 回 值: PASSED:相等
* FAILED:不等
* 说 明: 无
*/
TestStatus Buffercmp(uint32_t* pBuffer1, uint32_t* pBuffer2, uint32_t BufferLength)
{
while (BufferLength--)
{
if(BufferLength%50==0)
{
printf("buf:0x%08X - 0x%08X\r\n",*pBuffer1,*pBuffer2);
}
if (*pBuffer1 != *pBuffer2)
{
return FAILED;
}
pBuffer1++;
pBuffer2++;
}
return PASSED;
}
/**
* 函数功能: SD卡读写测试
* 输入参数: 无
* 返 回 值: 无
* 说 明: 无
*/
void SD_Write_Read_Test(void)
{
int i,j = 0;
/* 填充数据到写缓存 */
Fill_Buffer(Buffer_Block_Tx,BLOCK_SIZE*NUMBER_OF_BLOCKS, 0x6666);
/* 往SD卡写入数据 */
sd_status = HAL_SD_WriteBlocks(&hsd1,(uint8_t *)Buffer_Block_Tx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
printf("write status:%d\r\n",sd_status);
HAL_Delay(500);
/* 从SD卡读取数据 */
sd_status = HAL_SD_ReadBlocks(&hsd1,(uint8_t *)Buffer_Block_Rx,WRITE_READ_ADDRESS,NUMBER_OF_BLOCKS,0xffff);
printf("read status:%d\r\n",sd_status);
/* 比较数据 */
test_status = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE*NUMBER_OF_BLOCKS/4); //比较
if(test_status == PASSED)
{
printf("》读写测试成功!\r\n" );
for(i=0;i<BLOCK_SIZE*NUMBER_OF_BLOCKS/4;i++)
{
if(j==8)
{
printf("\r\n");
j=0;
}
printf("%08x ",Buffer_Block_Rx[i]);
j++;
}
printf("\r\n");
}
else
printf("》读写测试失败!\r\n " );
}
(4)到文件开头的地方
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
之间加入变量,代码如下:
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;
/* 私有宏定义 ----------------------------------------------------------------*/
#define BLOCK_SIZE 512 // SD卡块大小
#define NUMBER_OF_BLOCKS 8 // 测试块数量(小于15)
#define WRITE_READ_ADDRESS 0x00002000 // 测试读写地址
/* 私有变量 ------------------------------------------------------------------*/
__align(4) uint32_t Buffer_Block_Tx[BLOCK_SIZE*NUMBER_OF_BLOCKS]; // 写数据缓存
__align(4) uint32_t Buffer_Block_Rx[BLOCK_SIZE*NUMBER_OF_BLOCKS]; // 读数据缓存
HAL_StatusTypeDef sd_status; // HAL库函数操作SD卡函数返回值:操作结果
TestStatus test_status; // 数据测试结果
(3)到main函数中,找到
/* USER CODE BEGIN 2 */与 /* USER CODE END 2 */之间的位置,加入代码
SD_EraseTest();
SD_Write_Read_Test();
7、配置工程调试、下载
此部分只放图,不解释。
- 编译工程,打开串口调试助手,接好串口线,将程序烧录到MCU中。
- 测试结果
到此,普通SDIO驱动完成。
如果使用DMA,请在完成本节内容的基础上,做下一节的内容。
附加:
可以将前面设置的时钟分频系数改为0测试。如果能测试成功,可以使用更高的速度获取更好的性能,否则就适当加大分频系数直到能用。