打印
[STM32F4]

【猪圈丶嗨情歌的开发分享】SDIO读取SD卡扇区,使用HAL库

[复制链接]
5704|35
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 zhangbolily 于 2015-12-14 22:34 编辑

【猪圈丶嗨情歌的开发分享】
今天给大家带来的是我这几天学习的SDIO,用STM32系列芯片的SDIO读取SD卡的扇区。

我使用的工具
开发平台:正点原子探索者STM32F407开发板
硬件:使用了NUCLEO-F446RE开发板的ST-Link作为调试器、SD卡、数据线、开发板的电源适配器、DELL一体机
软件:STM32CubeMX、Keil V5、串口助手

学习的知识点
1、使用STM32CubeMX配置SDIO
2、在Keil中初始化SDIO
3、读取SD卡状态、卡信息
4、sprintf函数的使用

共享的资源完整的工程文件 http:{}oss-c.oss-cn-hangzhou.aliyuncs.com/bbs/SDIO.zip
原始截图 截图.zip (196.14 KB)

写在前面的话
上次STM32F469I开发板申请的活动吃了不少苦头啊,手里没有什么精华帖,只能跑去申请小鲜肉分块的开发板了。结果这个活动异常火爆,最后1000+的人参与。这次决心做好开发分享系列的帖子,把自己平时学习STM32开发中的干货写成帖子在论坛里分享给大家。希望支持的坛友多多回复,给我顶一个人气。


准备工作
本帖不解决硬件连接问题,如果硬件方面有问题最好自行搜索相关资料。
SD卡的通信方式有的是用SPI,我们这里是SDIO,速度更快更好用。如果你的SD卡模块有MOSI、MISO这些字样,那说明这些是SPI读写用的模块,不适合这篇帖子的分享内容。如果使用的是现成的开发板,请参考开发板的手册确定SD卡的通信方式。
如果你确定了SD卡的通信方式为SDIO无误并且正确连接了SD卡和芯片,然后记录下了SD卡的引脚和芯片的连接方式那么准备工作就基本完成了。

STM32CubeMX中配置SDIO


我们来看看左边的红框,在外设里面找到SDIO、SYS、USART1(看自己的开发板来选择。还有我忘了打开这个分支,USART1我选的是第一个选项)。在下拉框里面选择和上图一样的选项,这样可以保证后面的项目一致。
我们在左边选择好了以后,可以看到右边的芯片引脚有一些是绿色的,就说明这些引脚有配置被激活了。



我们现在来玩一玩这个时钟的配置窗口,这个在Pinout标签页旁边大家自行点开。
那么时钟怎么配置呢?我一般是主频给我来最大,只要保证这里的输入框都是蓝色的就行了,如果是红色的就说明频率高了这个就要自己调整一下了。
剩下的内容就已经确定了,不需要我们来配置。如果你自己有什么别的想法的话,就自己动手来配置一下。

在Keil中初始化SDIO

我们现在要初始化SDIO来驱动我们的SD卡工作,现在贴出我在main函数中的初始化代码。下面的代码都是Cube自动生成的,这些代码就可以初始化我们的SDIO了。
/* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SDIO_SD_Init();
  MX_USART1_UART_Init();


添加一些必要的初始化代码(12月14号追加)

为了保证SDIO正常工作,我们还要添加两个函数来初始化。这两个函数Cube没有事先给我们调用,我们要自己动一次手。在工程的文件树Application/User里面找到sdio.c文件,找到第一个void MX_SDIO_SD_Init(void)函数。我们把我们需要的初始化代码贴进去。
加入的这两行函数才是真正的初始化了SDIO,前面都是在配置一个结构体变量。最后的两行代码需要我们自己添加进来,这两个函数的定义大家可以自行查询。这两行代码没有写在用户代码保护模块里面,所以下一次STM32CubeMX生成代码的时候这里的代码会被删除,请悉知。
void MX_SDIO_SD_Init(void)
{

  hsd.Instance = SDIO;
  hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
  hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
  hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
  hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
  hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
  hsd.Init.ClockDiv = 0;

        HAL_SD_Init(&hsd, &SDCardInfo);
  HAL_SD_WideBusOperation_Config(&hsd, SDIO_BUS_WIDE_4B);
}



下面我贴出我在while循环里面的代码,分析一下这些代码。
首先我是用了HAL_SD_GetStatus函数来读取SD卡的状态,这些状态只有SD_TRANSFER_OK、SD_TRANSFER_BUSY、SD_TRANSFER_ERROR这三种。
State = HAL_SD_GetStatus(&hsd);
接下来的条件语句里面,我用几个if来决定不同的状态应该做些什么。
1、现在SD是OK的状态,我们可以对SD卡进行读写操作
在读取扇区的数据之前,发送一个SD Card OK的消息。然后调用HAL_SD_ReadBlocks函数来读取扇区的内容。这个函数的第四个参数必须是512,因为目前还不支持其他的扇区大小。


该函数的返回值表明了读取操作的是否成功,如果为0表示成功,那么我们就发送一个Sector read is OK的消息。如果没有成功,则发送一个ERROR的消息同时用break来终止此次循环。


在接下来的for循环里面,我们把读取的32位扇区信息转化成8位的扇区信息并且使用sprintf函数格式化成字符串。
关于32位转化为8位这里不做过多的赘述,主要是C语言数据类型转换的时候丢失一部分信息和位运算的知识,大家自己看一下。

sprintf函数的使用
这里要特别提一下这个函数,这是C标准库中的一个函数,在我们的STM32平台上也有实现。这个函数可以把数据格式化为字符串,把数据作为字符的形式显示出来非常的实用。
想了解详细的函数使用方法,请自行百度一下。这里简单的介绍一下。
该函数的第一个参数是我们要转换的内容存储的指针,转换好的字符串存储在这个指针指向的空间里。第二个字符串就是格式化字符串,这个字符串里面要包含转换字符。我在代码里面使用的是%X这个转换字符,可以把数据用大写的16进制显示。后面的参数是和前面的转换字符一一对应的待转换变量,直接传递参数就可以了,不需要指针。


转换好了字符串之后,我就可以挨个发送这些字符。发送完之后就循环一直等待就行了,不用反复读取。
原型

int sprintf( char *buffer, const char *format, [ argument] … );
参数列表

buffer:char型指针,指向将要写入的字符串的缓冲区。
format:格式化字符串。
[argument]...:可选参数,可以是任何类型的数据。
返回值:字符串长度(strlen)


if( State == 0)
                {
                        HAL_UART_Transmit(&huart1, (uint8_t *)"SD Card OK\n", 11, 500);                                             
                        if(HAL_SD_ReadBlocks(&hsd, pReadBuffer, 0x00000000, 512, 1) == 0)
                        HAL_UART_Transmit(&huart1, (uint8_t *)"Sector read is OK\n", 18, 500);
                        else
                        {
                                HAL_UART_Transmit(&huart1, (uint8_t *)"ERROR\n", 6, 500);
                                break;
                        }
               
                        for(int i = 0;i < 128;i++)
                        {
                                for(int j = 0;j < 4;j++)
                                {
                                        SendBuffer = pReadBuffer[i];
                                        pReadBuffer[i] >>= 8;
                                        sprintf((char*)&Char, "%X", SendBuffer);
                                        HAL_UART_Transmit(&huart1, &Char, 1, 500);
                                }
                        
                        }
                        while(1);
                }


2、SD卡处于繁忙的状态
这个时候我们就发送一个Busy的消息就可以了,等待1S钟进行下一次SD卡状态的读取。
else if(State == 1)
                {
                        HAL_UART_Transmit(&huart1, (uint8_t *)"Busy\n", 5, 500);        
                }

3、SD卡状态读取错误
SD状态返回为错误,这基本上就是SD卡没有插入,要么就是卡坏掉了。这个时候如果我们不进行任何操作就进行下一次判断的话,就算是在插入SD卡不管怎么读取都是返回ERROR。所以我们在读取到这个状态的时候,调用一次HAL_SD_Init(&hsd, &SDCardInfo);这个函数来清除之前的状态(这个是我自己想的,不知道有没有卵用)。这样子操作的话,在下一次插入SD卡的时候就可以返回为OK的值并且可以读写。

最后用了一个延时函数来限制扫描SD卡的速度。
else {
                        HAL_UART_Transmit(&huart1, (uint8_t *)"Error\n", 6, 500);
                        HAL_SD_Init(&hsd, &SDCardInfo);
                }
        
                HAL_Delay(1000);

实际工作效果



我预先拔掉了SD卡,过了一会我在插上去,所以在串口上看到的就是这样的效果。

写在后面的话
注意!注意!注意!本人不是什么工程师,只不过是爱好嵌入式开发的学生一枚,如果你发现在这个帖子中的错误请及时提醒我。如果对本帖的内容有什么疑问请在下方留言,我会经常过来逛论坛的。

下一次给大家带来的可能就是FatFs了,这样我们就可以读写文件了。






沙发
zhangbolily|  楼主 | 2015-12-13 21:16 | 只看该作者
赶紧给自己占一个楼

使用特权

评论回复
板凳
309030| | 2015-12-13 22:07 | 只看该作者
SDIO方式比SPI有哪些好处

使用特权

评论回复
地板
zhangbolily|  楼主 | 2015-12-13 22:21 | 只看该作者
309030 发表于 2015-12-13 22:07
SDIO方式比SPI有哪些好处

速度快,其他的我就不知道多少了。主要是STM32的这个HAL库里面有着一系列的函数,开发简单所以这里就用SDIO来开发。

使用特权

评论回复
5
zhangbolily|  楼主 | 2015-12-14 15:39 | 只看该作者
自己的帖子怎么可以沉下去,赶紧顶起来

使用特权

评论回复
6
emyhello| | 2015-12-15 10:10 | 只看该作者
ST的库能直接用来做产品?

使用特权

评论回复
7
zhangbolily|  楼主 | 2015-12-15 10:44 | 只看该作者
emyhello 发表于 2015-12-15 10:10
ST的库能直接用来做产品?

我不知道呃,总之用CubeMX生成的代码直接可以来读写SD卡的扇区啦。今天我在测试自带的FatFS读写文件,等我弄好了又可以分享一篇帖子了。

使用特权

评论回复
8
一木随风| | 2015-12-17 16:03 | 只看该作者
好东西,顶一个

使用特权

评论回复
9
libingqing| | 2016-3-7 14:17 | 只看该作者
very good  今天开始调这个,非常感谢

使用特权

评论回复
10
大果仁儿| | 2016-3-7 14:31 | 只看该作者
好东西!感谢

使用特权

评论回复
11
libingqing| | 2016-3-7 15:29 | 只看该作者
HAL_SD_Init(&hsd, &SDCardInfo);
  HAL_SD_WideBusOperation_Config(&hsd, SDIO_BUS_WIDE_4B);
这两个函数在初始化的时候有的,不知道楼主是用的哪个版本的cubemx,我用的有、我用的4.12.00

使用特权

评论回复
12
libingqing| | 2016-3-7 17:37 | 只看该作者
不知道为什么,一加上read或者write函数的时候就死掉了,初始化不成功!

使用特权

评论回复
13
zhangbolily|  楼主 | 2016-3-7 20:06 | 只看该作者
libingqing 发表于 2016-3-7 17:37
不知道为什么,一加上read或者write函数的时候就死掉了,初始化不成功!

read或者write这个你是调用了FatFs的函数吗?如果是的话,你就要看一看我的另一篇帖子了。

使用特权

评论回复
14
libingqing| | 2016-3-7 21:05 | 只看该作者
楼主在不在啊,今天调试了一天,主要有几个问题想请教下啊
1、我用高速晶振,SDIO的最高时钟也是48MHz,测试容量,状态都没有问题,但是读、写的时候不能返回SD_OK
2、为什么楼主要用SYS时钟呢,难道是SDIO有什么引脚发生冲突了么?我一般都是使能RCC的高速、低速的晶振

使用特权

评论回复
15
libingqing| | 2016-3-7 21:06 | 只看该作者

请问你调试出来了没有呢?

使用特权

评论回复
16
zhangbolily|  楼主 | 2016-3-7 22:24 | 只看该作者
libingqing 发表于 2016-3-7 21:05
楼主在不在啊,今天调试了一天,主要有几个问题想请教下啊
1、我用高速晶振,SDIO的最高时钟也是48MHz,测试 ...

我是用SYS的时钟因为……我不会RCC:L
还有就是你调试了一天都没有成功,这个我还真的就不知道是怎么回事了。不晓得你有没有按照我的步骤一步一步看,我也是试了很多次才成功的。

使用特权

评论回复
17
libingqing| | 2016-3-8 09:26 | 只看该作者
zhangbolily 发表于 2016-3-7 22:24
我是用SYS的时钟因为……我不会RCC
还有就是你调试了一天都没有成功,这个我还真的就不知道是怎么回事 ...

不知版本是否愿意留个联系方式,或者加我qq 353141403没有用fatfs文件,只是SDIO读取,读函数和写函数都不返回OK!望楼主加个Q细聊

使用特权

评论回复
18
libingqing| | 2016-3-8 09:36 | 只看该作者
版本可以把工程文件上传一下么,最前面上传的那个不可以下载呢!

使用特权

评论回复
19
libingqing| | 2016-3-8 18:04 | 只看该作者
楼主用的哪个版本的CUBEMX啊,是不是版本的问题呢

使用特权

评论回复
20
zhangbolily|  楼主 | 2016-3-9 23:17 | 只看该作者
libingqing 发表于 2016-3-8 18:04
楼主用的哪个版本的CUBEMX啊,是不是版本的问题呢

这个和版本没有什么关系,可能是哪一步出了问题吧。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

67

帖子

8

粉丝