目录
写在前面
这次实验涉及到Flash、sd卡读取数据的知识,对初次接触到这类词汇的朋友会比较难上手,建议大家先了解一下相关知识后再上手实验。
一、Flash读取
(一)实验要求
Flash地址空间的数据读取。stm32f103c8t6只有20KB 内存(RAM)供程序代码和数组变量存放,因此,针对内部Flash的总计64KB存储空间(地址从0x08000000开始)①运行一次写入8KB数据,总计复位运行代码8次,将64KB数据写入Flash,并验证写入数据的正确性和读写速率。②此外,继续往后续地址写入数据,检验stm32f103c8t6 实际FlashROM是否超过64KB。
(二)Flash的简单说明
不同型号的 STM32,其 FLASH 容量也有所不同,最小的只有 16K 字节,最大的则达到了 1024K 字节。市面上 STM32F1 开发板使用的芯片是 STM32F103系列,其 FLASH 容量一般为 512K 字节,属于大容量芯片。咱们这次实验的单片机内部flash为64kb,实验的时候也要去实验是否只有64kb,是否存在不公开部分。
- 扇区划分方式
在 STM32F1 系列中,以典型的 64KB Flash 为例,它是被划分为多个扇区的。一般来说,每个扇区的大小为 1KB。这种划分使得在进行擦除操作时可以以扇区为单位进行。例如,如果你只需要更新一小部分程序代码,而这部分代码位于某个特定的扇区内,那么你就可以只擦除和重新编程这个扇区,而不用对整个 64KB 的 Flash 进行操作。
这种扇区划分有利于提高操作的灵活性和效率。假设你有一个包含多个功能模块的程序,每个模块存储在不同的扇区中。当你需要更新其中一个模块时,只需要针对该模块所在扇区进行操作,减少了对其他无关模块的影响,也节省了擦除和写入的时间。
- 存储单元层次
从更微观的角度看,Flash 的存储单元是以字节为基本单位组成的。在编程(写入)操作时,可以按字节、半字(16 位)或字(32 位)进行。不过,在写入之前通常需要先擦除扇区,因为 Flash 的写入操作是基于已擦除状态(全 1 状态)的,只有这样才能将 0 写入存储单元。
例如,如果你想将一个 32 位的整数写入 Flash 的某个位置,你需要确保这个位置所在的扇区已经被擦除,并且要按照正确的地址对齐(字地址对齐)进行写入操作。这种层次结构使得在存储不同类型的数据时可以根据实际需求灵活选择写入方式,同时也需要开发者注意写入操作的规则和要求,以确保数据能够正确存储。
再次注意!Flash的编程原理都是只能将1写为0,而不能将0写为1,所以在进行Flash编程前,必须将对应的块擦除!即将该块的每一位都变为1,块内所有字节变为0xFF。
STM32F1 的闪存(Flash)模块:主存储器、信息块、闪存存储器接口寄存器
①主存储器
主存储器是 STM32F1 闪存模块的核心部分,主要用于存储用户程序代码。当芯片上电启动后,处理器会从主存储器中读取程序指令并开始执行。例如,你编写的一个控制 LED 闪烁的 C 语言程序,经过编译后生成的二进制机器码就存储在主存储器中。它还可以存储常量数据,比如定义为const
类型的数组或者字符串。例如,const char message[]="Hello World";
这样的字符串常量就存放在主存储器中。
②信息块
该部分分为 2 个小部分,其中启动程序代码,是用来存储 ST 自带的启动程序,用于串口下载代码,当 BOOT0 接 V3.3, BOOT1 接 GND 的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。
③闪存存储器接口寄存器
该部分用于控制闪存读写等,是整个闪存模块的控制机构。对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。
在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作。
(三)实验操作
1、cubemx配置
①老两样,sys和rcc配置
② 时钟树
③工程配置(这里有点跟之前的不一样,注意一下)
配置过程很简单,要是不懂可以看我之前的博客。要是你想验证程序是否正常运行,可以设置一个PC13的led灯进行验证。
2、keil完善代码
工程源码链接(主:flash.h文件需要自己重新加入到路径中)
链接:百度网盘 请输入提取码
提取码:pmvn
链接里的代码是报错的,只需要用到其中用到的flash文件而已。还得自己手动配置和添加喔~
将上述工程里的的flash.c 及flash.h加入到本次工程中
在main.c文件中添加部分代码
剖析一下主函数:
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t i;
uint8_t FlashTest[] ="Hello World hello guys";
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
FlashWriteBuff(DEVICE_INFO_ADDRESS, FlashTest, sizeof(FlashTest));//写入数据到Flash
for(i=0;i<255;i++)
FlashWBuff[i]=i;
FlashWriteBuff(DEVICE_INFO_ADDRESS + sizeof(FlashTest), FlashWBuff, 255);//
FlashReadBuff(DEVICE_INFO_ADDRESS + sizeof(FlashTest), FlashRBuff, 255);
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
其中
这一块就是要写入的数据
这一块使用FlashWriteBuff和FlashReadBuff函数对flash进行写入和读取操作。
这个部分为变量的定义,这个部分可以好好读一读。
可以看到咱们主函数写入的地址为:DEVICE_INFO_ADDRESS 0x0800C000。咱们就去看这个地址的相关信息,以及写入的两个变量的数据信息,来对比flash写入和读取情况。
然后再修改其中的地址,改到官方说明的64kb结束的位置,再对其进行写入然后读取,看看会发生什么。
明确实验验证方法后,进行编译,无误,进行调试
3、调试过程
用的stlink,注意连接即可
第一次使用stlink的朋友们可能会遇到一些关于stlink的下载报错。多多上网查找资料解决,资源很丰富。
烧录过后进行仿真验证:
还记得咱们的目的不,查看
0x0800C000的数据
以及写入的两个变量的数据信息,来对比flash写入和读取情况
进入仿真界面:
点击View->memory windows->memory 1打开内存观察窗口,并在地址栏中输入:0x800c000,观察将要修改的flash区间区容:
可以看到咱们写入的数据是写入成功的。
下一步观察两个变量,View->Watch windows->Watch 1打开一个变量观察窗口,将变量FlashWBuff 和 FlashRBuff分别加到 Watch 1 和Watch 2观察窗口,对比观察:
可以观察到两个变量一样,说明写入和读取都是正常的。
最后一个验证是关于写入数据的,这里我试着把刚才的变量定义中的结束地址
FMC_FLASH_END 0x08010000
写入 FlashWriteBuff函数,但是通过仿真观察什么都没有,然后就看是不是程序的问题,我试着改了写入的地址为0x0800C100,然后发现是正常的
那么就猜想是因为程序函数设定了限制的问题,于是找到了FlashWriteBuff函数
果不其然函数对其进行了限制,超过最大kb的地址写入不进去,于是我注释了这标蓝代码,进行尝试。
下面是我的主函数:
进行仿真验证:
哈哈果然成功。
看来官方给的flash大小还是有所保守哈哈。
二、SD卡的数据读取
(一)实验要求
掌握SD卡协议原理,用STM32F103完成对SD卡的数据读取(fat文件模式)。
(二)SD卡的简单说明
SD卡(Secure Digital Memory Card)在我们的生活中已经非常普遍了,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI接口,另外一种就是 SDIO接口。SDIO 全称是 安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡 都有 SDIO接口。STM32F103系列控制器有一个 SDIO主机接口,它可以与 MMC卡、SD卡、SD I/O卡 以及 CE-ATA 设备进行数据传输。
首先学习其物理结构:
一般SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5个部分。
- 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
- 电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;
- 卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器; 接口驱动器控制SD卡引脚的输入输出。
其次学习其八个寄存器:
SD卡总共有8个寄存器,用于设定或表示SD卡信息。
这些寄存器只能通过对应的命令访问,SDIO定义64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。
再学习一下相关的命令:
SD卡一般都支持 SDIO 和 SPI 这两种接口。
其中SD卡模式的信号线有:CLK、CMD、DAT0-DAT3,6根线。
SPI模式的信号线有:CS、CLK、MISO(DATAOUT)、MOSI(DATAIN),4根线。
SD卡的命令格式:命令CMD0就是0,CMD16就是16,以此类推。
SD卡的命令总共有12类,下表为几个比较重要的命令:
最后学习一下相关的初始化和读写流程:
SD卡初始化(SPI模式)
SPI操作模式下:在SD卡收到复位命令时,CS为有效电平(低电平),则SPI模式被启用,在发送CMD之前要先发送74个时钟,64个为内部供电上升时间,10个用于SD卡同步;之后才能开始CMD操作,在初始化时CLK时钟不能超过400KHz。
1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、上电延时(>74个CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等);
6、取消片选,发多8个CLK,结束初始化
这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。
SD卡读取与写入(SPI模式)
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌0XFE;
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;
以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
以上就是一个典型的写SD卡过程。
会比较晦涩,建议用用这段文字去盘问AI哈哈。
(三)实验操作
完整工程代码如下(hal库版本)
链接:百度网盘 请输入提取码
提取码:276d
1、cubemx配置
①老两样,sys和rcc配置
②配置spi(sd卡模块连接)
④配置串口(输出给上位机)
③时钟树
之后导出即可。
2、keil完善代码
对主函数进行分析:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration---------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_FATFS_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1,&aRxBuffer1,1); //enable uart
printf(" main \r\n");
Get_SDCard_Capacity(); //得到使用内存并选择格式化
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
WritetoSD(WriteBuffer,sizeof(WriteBuffer));
HAL_Delay(500);
WriteBuffer[0] = WriteBuffer[0] +10;
WriteBuffer[1] = WriteBuffer[1] +10;
write_cnt ++;
while(write_cnt > 10)
{
printf(" while \r\n");
HAL_Delay(500);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
其中:
HAL_Delay(500);
WriteBuffer[0] = WriteBuffer[0] +10;
WriteBuffer[1] = WriteBuffer[1] +10;
write_cnt ++;
这段代码应该是想改变其输出数据WriteBuffer数组的序号,让每次输出不一样,可以看出书写是由问题的,要是想观察序号需要重新修改代码。
但是做完实验后,把sd卡借给同学了,没来得及修改实验。
3、调试过程
使用到的器材:
要使用左边的u盘对闪迪16GB的SD卡进行格式化:
然后将SD卡插进读写器(SD卡模块CH376S SPI接口 迷你TF卡读写器):
硬件连接:
注:这里要注意,就是sd卡模块需要5v电压驱动,所以用的另外一个usb转ttl的5v进行连接的。我之前用的单片机上面的3.3v,串口没有一点动静,搜索资料,发现要接5v。但是我看有些博主这样也不行,好像是单片机也要供电5v,大家都尝试吧,可以就行。
用usb转ttl烧录代码,BOOT1置0,BOOT0置1
然后用这个进行串口显示,BOOT1置1,BOOT0置0
将sd卡取下,插进u盘,看里边的生成的文件:
我连续复位了好几次所以有好几次的重复信息,可以看出是成功写入的。
这里序号乱码就是我上面提到的代码编写的问题,祝大家好运。
最后想说
作者都会觉得自己阐述的已经很清楚了,但是每个人脑子里的背景知识都不一样,老师看我的文章一看就知道是什么或者说哪里有问题,但是如果研究不是很深入脑子里的背景知识没那么多的话就会觉得看不懂也看不进去。别慌张,抓住不清楚的那些想法,一点点地顺藤摸瓜,慢慢找到那些你缺失的背景知识,多问问ai,把ai当作学习的工具,多上网查找资料,也要自己多多想想自己学习的方案是什么,怎么才能快速搞懂这些知识。
觉得脑袋疼是非常非常好的一件事情,这说明你的知识能力进行一个质的提升的机会来了,我喜欢这种感觉。
单片机有点玄学,有时候不知道是硬件还是软件的问题,建议自动控制某个影响的变量查找问题。比如说多按一按单片机,或者拔插一下,或者复位看看,是不是硬件连接问题什么的,,或者电源供电问题,,或者硬件本身出现问题需要换一下。要是这些都不行,那就大概率是代码的问题了。祝大家成功。