13-CubeMx+Keil+Proteus仿真STM32 - Flash ROM

发布于 2022年 05月 19日 13:09

本文例子参考《STM32单片机开发实例——基于Proteus虚拟仿真与HAL/LL库》
源代码:https://github.com/LanLinnet/STM33F103R6

项目要求

单片机将由串口收到的1字节数据存入Flash ROM的指定地址;按下按钮BTN,单片机将存储在Flash ROM指定地址的字节数据通过串口发送。串口通信参数:波特率为19200bit/s,无校验。

硬件设计

  1. 第一节的基础上,在Proteus中添加电路如下图所示。其中我们添加了:串口组件COMPIM,用于连接计算机虚拟串口;

    调试过程也可以添加一个虚拟仪器VIRTUAL TERMINAL,用来查看单片机收到的串口数据,具体参考第11节
    由于要实现串口通信,我们要将其波特率、字长、校验方式、停止位等都设置一下,具体参数如下图所示
    COMPIM设置

  2. 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数据读取没有繁琐的步骤,直接读取即可。

  3. 打开CubeMX,建立工程。
    首先,设置PA5为GPIO_Input
    然后,点击“Connectivity”列表中的“USART”进行串口配置。将Mode设置为Asynchronous(异步),波特率设为19200Bits/s,字长设为8Bits,校验设为None,停止位设为1,数据传送设为Receive and Transmit(接收与发送)。设置完成后,会看到右侧的PA9和PA10引脚被自动设置为USART1_TXUSART1_RX,即USART1的发送端和接收端。

    随后,再点击“NVIC Settings”,选中USART global interrupt,使能Enabled串口1的中断功能。

  4. 点击“Generator Code”生成Keil工程。

软件编写

  1. 本次我们需要实现串口助手发送单字节数据,单片机收到数据后存入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个宏定义

  2. 点击“Open Project”在Keil中打开工程,双击“main.c”文件。

  3. 首先我们需要在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 */
    

联合调试

  1. 点击运行,生成HEX文件。

  2. 在Proteus中加载相应HEX文件,点击运行。

  3. 打开串口调试助手“XCOM”,选择COM4,设置相应的波特率、停止位、数据位、奇偶校验等,勾选“16进制显示”和“16进制发送”,点击“打开串口”。在发送框输入“00”,点击“发送”。在Proteus中我们可以看到“VIRTUAL TERMINAL”接收到数据“00”。按下按键,同时再观察串口调试助手“XCOM”,可以看到接收窗口收到数据“00”。同理,发送“AA”和“BB”也能得到相应的结果。

推荐文章