13-CubeMx+Keil+Proteus仿真STM32 - Flash ROM
本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM32F103R6
项目要求
单片机将由串口收到的1字节数据存入Flash ROM的指定地址;按下按钮BTN,单片机将存储在Flash ROM指定地址的字节数据通过串口发送。串口通信参数:波特率为19200bit/s,无校验。
硬件设计
-
在第一节的基础上,在Proteus中添加电路如下图所示。其中我们添加了:串口组件
COMPIM
,用于连接计算机虚拟串口;
调试过程也可以添加一个虚拟仪器VIRTUAL TERMINAL
,用来查看单片机收到的串口数据,具体参考第11节
由于要实现串口通信,我们要将其波特率、字长、校验方式、停止位等都设置一下,具体参数如下图所示
COMPIM设置
-
Flash ROM简介:STM32单片机Flash ROM(程序存储器)的作用是存放用户编写的单片机程序(机器码),但是其除了用来存放单片机的程序外,也可以用来存储一些既可以修改又能断电保存的数据,如设备或模块的设定参数。但是在实际中,由于STM32单片机的Flash ROM擦除次数有限,因此不建议在Flash ROM擦写,可以通过外扩\(E^2 PROM\)、FRAM、存储卡等方式实现保护产品设定参数的目的。不过为了熟悉Flash ROM操作,本节我们使用Flash ROM来存储数据。
1)STM32F103R6单片机具有32KB的FlashROM,地址为0x0800 0000 ~ 0x0800 7FFF,每KB为一页,共32页。
2)Flash ROM数据写入步骤:Flash ROM解锁 → 擦除扇区 → 向指定地址写入数据 → Flash ROM锁定。
3)Flash ROM数据读取没有繁琐的步骤,直接读取即可。 -
打开CubeMX,建立工程。
首先,设置PA5为GPIO_Input
。
然后,点击“Connectivity”列表中的“USART”进行串口配置。将Mode设置为Asynchronous
(异步),波特率设为19200Bits/s
,字长设为8Bits
,校验设为None
,停止位设为1
,数据传送设为Receive and Transmit
(接收与发送)。设置完成后,会看到右侧的PA9和PA10引脚被自动设置为USART1_TX
和USART1_RX
,即USART1的发送端和接收端。
随后,再点击“NVIC Settings”,选中USART global interrupt
,使能Enabled
串口1的中断功能。
-
点击“Generator Code”生成Keil工程。
软件编写
-
本次我们需要实现串口助手发送单字节数据,单片机收到数据后存入Flash ROM,按键按下后将存储的数据通过串口发回串口助手,需要用到Flash ROM相关函数其API文档如下:
HAL_FLASH_Unlock 解锁Flash ROM函数
HAL_FLASH_Lock 锁定Flash ROM函数
HAL_FLASHEx_Erase 擦除Flash ROM指定部分函数
HAL_FLASH_Program 将数据写入Flash ROM函数
其中,
TypeErase
形参有以下2个宏定义
TypeProgram
有以下3个宏定义
-
点击“Open Project”在Keil中打开工程,双击“main.c”文件。
-
首先我们需要在main.c文件中的最前面设置全局变量、声明自定义函数。
/* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ #define _FLASH_ADD 0x08006400 //写入Flash ROM首地址(Page 25) /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t rf = 0; //自定义串口接收完毕标志 uint8_t RcvBuf[1]; //接收缓冲 uint8_t SndBuf[1]; //发送缓冲 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ void FlashErase(uint32_t Add); //声明自定义Flash ROM擦除函数 void FlashWrite(uint32_t Add, uint16_t Dat); //声明自定义Flash ROM写函数 uint16_t FlashRead(uint32_t Add); //声明自定义Flash ROM读函数 /* USER CODE END PFP */
然后,在
main
函数中中插入代码如下,定义中间变量,打开串口1接收中断/* USER CODE BEGIN 1 */ uint16_t flash_wdat; //写入Flash数据存储变量 /* USER CODE END 1 */
/* USER CODE BEGIN 2 */ //打开串口1接收中断,接收数据存入RcvBuf数组,数组长度为1 HAL_UART_Receive_IT(&huart1,RcvBuf,1); /* USER CODE END 2 */
随后,在
/* USER CODE BEGIN 4 */
和/* USER CODE END 4 */
中插入接收完毕回调函数、自定义的Flash页擦除函数、Flash写函数、Flash读函数代码如下/* USER CODE BEGIN 4 */ //串口接收完毕回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart==&huart1) //如果串口1接收完毕 { rf = 1; //标志位置1 } } /*Flash页擦除 *-Add表示待擦除页的首地址 *-Flash必须整页擦除,也就是整页的每个地址单元内容都为FFH才能写入新数据 */ void FlashErase(uint32_t Add) { uint32_t page_error = 0; //错误指针 FLASH_EraseInitTypeDef erase_initstruct = { .TypeErase = FLASH_TYPEERASE_PAGES, //擦除方式为页擦除 .NbPages = 1, //页数量为1页 .PageAddress = Add //擦除页起始地址 }; HAL_FLASH_Unlock(); //解锁Flash ROM HAL_FLASHEx_Erase(&erase_initstruct, &page_error); //擦除 HAL_FLASH_Lock(); //锁定Flash ROM } /*Flash写函数 *-写入一个Half Word(16位)型数据 *-Add表示Flash ROM地址 *-Dat表示写入数据(16位) *-注意:写入时,高字节在高地址 */ void FlashWrite(uint32_t Add, uint16_t Dat) { HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, Add, Dat); //将数据写入Flash HAL_FLASH_Lock(); } /*Flash读函数 *-返回一个Half Word(16位)型数据 *-Add表示Flash ROM地址 */ uint16_t FlashRead(uint32_t Add) { uint16_t dat; dat = *(uint16_t *)Add; return dat; } /* USER CODE END 4 */
最后,在
while(1)
中插入代码如下,进行Flash和串口相关操作/* USER CODE BEGIN WHILE */ while (1) { if(rf == 1) //串口接收完毕 { rf = 0; //标志位清0 flash_wdat = RcvBuf[0]; //将接收到的数据存入写Flash变量中 FlashErase(_FLASH_ADD); //擦除指定部分 FlashWrite(_FLASH_ADD, flash_wdat); //写入Flash HAL_UART_Receive_IT(&huart1, RcvBuf, 1); //每次接收前都需要调用一次 } if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET) { HAL_Delay(25); //消抖 if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET) //如果按键按下 { SndBuf[0] = (uint8_t)FlashRead(_FLASH_ADD); //读Flash中值并存入发送缓冲 HAL_UART_Transmit(&huart1, SndBuf, 1, 10); //由串口1发送缓冲中的值 while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_RESET); //等待按键松开 } } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
联合调试
-
点击运行,生成HEX文件。
-
在Proteus中加载相应HEX文件,点击运行。
-
打开串口调试助手“XCOM”,选择
COM4
,设置相应的波特率、停止位、数据位、奇偶校验等,勾选“16进制显示”和“16进制发送”,点击“打开串口”。在发送框输入“00”,点击“发送”。在Proteus中我们可以看到“VIRTUAL TERMINAL”接收到数据“00”。按下按键,同时再观察串口调试助手“XCOM”,可以看到接收窗口收到数据“00”。同理,发送“AA”和“BB”也能得到相应的结果。