下探索者 STM32F4 开发板板 载的 SD 卡接口和 STM32F4 的连接关系如图:
源码讲解:
sdio_sdcard.c 里面的SD_Init 函数:
该函数先实现 SDIO 时钟及相关 IO 口的初始化,然后开始 SD 卡的初始化流程。首先,通过 SD_PowerON 函数,我们将完成 SD 卡的上电,并获得 SD 卡的类型(SDHC/SDSC/SDV1.x/MMC),然 后,调用 SD_InitializeCards 函数,完成 SD 卡的初始化。
SD_InitializeCards 函数:
SD_InitializeCards 函数主要发送 CMD2 和 CMD3,获得 CID 寄存器内容和 SD 卡的相对地 址(RCA),并通过 CMD9,获取 CSD 寄存器内容。到这里,实际上 SD 卡的初始化就已经完 成了。 随后,SD_Init 函数又通过调用 SD_GetCardInfo 函数,获取 SD 卡相关信息,之后调用 SD_SelectDeselect 函数,选择要操作的卡(CMD7+RCA),通过 SD_EnableWideBusOperation 函数设置 SDIO 的数据位宽为 4 位(但 MMC 卡只能支持 1 位模式!)。最后设置 SDIO_CK 时钟 的频率,并设置工作模式(DMA/轮询)。
SD 卡读块函数:SD_ReadBlock,该函数用于从 SD 卡指定地址读出一 个块(扇区)数据:
该函数先发送 CMD16,用于设置块大小,然后配置 SDIO 控制器读数据的长度,这里我们 用到函数 convert_from_bytes_to_power_of_two 求出 blksize 以 2 为底的指数,用于 SDIO 读数据 长度设置。然后发送 CMD17(带地址参数 addr),从指定地址读取一块数据。最后,根据我们 所设置的模式(查询模式/DMA 模式),从 SDIO_FIFO 读出数据。
该函数有两个注意的地方:1,addr 参数类型为 long long,以支持大于 4G 的卡,否则操作 大于 4G 的卡,可能有问题!!!2,轮询方式,读写 FIFO 时,严禁任何中断打断,否则可能导 致读写数据出错!!所以使用了 INTX_DISABLE 函数关闭总中断,在 FIFO 读写操作结束后, 才打开总中断(INTX_ENABLE 函数设置)。
另外,还有三个底层读写函数:SD_ReadMultiBlocks, 用于多块读;SD_WriteBlock,用于单块写;SD_WriteMultiBlocks,用于多块写;再次提醒:无 论哪个函数,其数据 buf 的地址都必须是 4 字节对齐的!
SDIO 与文件系统的两个接口函数:SD_ReadDisk 和 SD_WriteDisk:
//读 SD 卡
//buf:读数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态;0,正常;其他,错误代码;
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 sta=SD_OK;
long long lsector=sector;
u8 n;
lsector<<=9;
if((u32)buf%4!=0)
{
for(n=0;n<cnt;n++)
{
sta=SD_ReadBlock(SDIO_DATA_BUFFER,lsector+512*n,512);//单扇区读操作
memcpy(buf,SDIO_DATA_BUFFER,512);
buf+=512;
}
}else
{
if(cnt==1)sta=SD_ReadBlock(buf,lsector,512); //单个 sector 的读操作
else sta=SD_ReadMultiBlocks(buf,lsector,512,cnt);//多个 sector
}
return sta;
}
//写 SD 卡
//buf:写数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态;0,正常;其他,错误代码;
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
u8 sta=SD_OK;
u8 n;
long long lsector=sector;
lsector<<=9;
if((u32)buf%4!=0)
{
for(n=0;n<cnt;n++)
{
memcpy(SDIO_DATA_BUFFER,buf,512);
sta=SD_WriteBlock(SDIO_DATA_BUFFER,lsector+512*n,512);//单扇区写
buf+=512;
}
}else
{
if(cnt==1)sta=SD_WriteBlock(buf,lsector,512); //单个 sector 的写操作
else sta=SD_WriteMultiBlocks(buf,lsector,512,cnt); //多个 sector
}
return sta;
}
SD_ReadDisk 用于读数据,通过调用 SD_ReadBlock 和 SD_ReadMultiBlocks 实现。SD_WriteDisk 用于写数据, 通过调用 SD_WriteBlock 和 SD_WriteMultiBlocks 实现。注意,因为 FATFS 提供给 SD_ReadDisk 或者 SD_WriteDisk 的数据缓存区地址不一定是 4 字节对齐的,所以我们在这两个函数里面做了 4 字节对齐判断,如果不是 4 字节对齐的,则通过一个 4 字节对齐缓存(SDIO_DATA_BUFFER) 作为数据过度,以确保传递给底层读写函数的 buf 是 4 字节对齐的。
main.c 文件:
//通过串口打印 SD 卡相关信息
void show_sdcard_info(void)
{
switch(SDCardInfo.CardType)
{
case SDIO_STD_CAPACITY_SD_CARD_V1_1:
printf("Card Type:SDSC V1.1\r\n");break;
case SDIO_STD_CAPACITY_SD_CARD_V2_0:
printf("Card Type:SDSC V2.0\r\n");break;
case SDIO_HIGH_CAPACITY_SD_CARD:
printf("Card Type:SDHC V2.0\r\n");break;
case SDIO_MULTIMEDIA_CARD:
printf("Card Type:MMC Card\r\n");break;
}
printf("Card ManufacturerID:%d\r\n",SDCardInfo.SD_cid.ManufacturerID);//制造商 ID
printf("Card RCA:%d\r\n",SDCardInfo.RCA);//卡相对地址
printf("Card Capacity:%d MB\r\n",(u32)(SDCardInfo.CardCapacity>>20));//显示容量
printf("Card BlockSize:%d\r\n\r\n",SDCardInfo.CardBlockSize);//显示块大小
}
int main(void)
{
u8 key; u8 t=0; u8 *buf;
u32 sd_size;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为 115200
LED_Init(); //初始化 LED
LCD_Init(); //LCD 初始化
KEY_Init(); //按键初始化
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMCCM); //初始化 CCM 内存池
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"SD CARD TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/5/15");
LCD_ShowString(30,130,200,16,16,"KEY0:Read Sector 0");
while(SD_Init())//检测不到 SD 卡
{
LCD_ShowString(30,150,200,16,16,"SD Card Error!"); delay_ms(500);
LCD_ShowString(30,150,200,16,16,"Please Check! "); delay_ms(500);
LED0=!LED0;//DS0 闪烁
}
show_sdcard_info(); //打印 SD 卡相关信息
POINT_COLOR=BLUE; //设置字体为蓝色
//检测 SD 卡成功
LCD_ShowString(30,150,200,16,16,"SD Card OK ");
LCD_ShowString(30,170,200,16,16,"SD Card Size: MB");
LCD_ShowNum(30+13*8,170,SDCardInfo.CardCapacity>>20,5,16);//显示 SD 卡容量
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)//KEY0 按下了
{
buf=mymalloc(0,512); //申请内存
if(SD_ReadDisk(buf,0,1)==0) //读取 0 扇区的内容
{
LCD_ShowString(30,190,200,16,16,"USART1 Sending Data...");
printf("SECTOR 0 DATA:\r\n");
for(sd_size=0;sd_size<512;sd_size++)printf("%x ",buf[sd_size]);//扇区数据
printf("\r\nDATA ENDED\r\n");
LCD_ShowString(30,190,200,16,16,"USART1 Send Data Over!");
}
myfree(0,buf);//释放内存
}
t++;
delay_ms(10);
if(t==20) { LED0=!LED0; t=0;}
}
}
这里总共 2 个函数,show_sdcard_info 函数用于从串口输出 SD 卡相关信息。而 main 函数, 则先初化 SD 卡,初始化成功,则调用 show_sdcard_info 函数,输出 SD 卡相关信息,并在 LCD 上面显示 SD 卡容量。然后进入死循环,如果有按键 KEY0 按下,则通过 SD_ReadDisk 读取 SD 卡的扇区 0(物理磁盘,扇区 0),并将数据通过串口打印出来。