WDG看门狗 - STM32 - FLASH闪存


至此落下帷幕,但学习并未停止

09 - STM32 - WDG看门狗 & FLASH闪存

WDG看门狗

WDG简介

  • WDG(Watchdog)看门狗。
  • 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性。
  • 看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号。
  • STM32内置两个看门狗:

    • 独立看门狗(IWDG):独立工作,对时间精度要求较低
    • 窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用

IWDG框图

IWDG键寄存器

  • 键寄存器本质上是控制寄存器,用于控制硬件电路的工作。
  • 在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率。
写入键寄存器的值作用
0xCCCC启用独立看门狗
0xAAAAIWDG_RLR中的值重新加载到计数器(喂狗)
0x5555解除IWDG_PR和IWDG_RLR的写保护
0x5555之外的其他值启用IWDG_PR和IWDG_RLR的写保护

IWDG超时时间

WWDG框图

WWDG工作特性

WWDG超时时间

IWDG和WWDG对比

IWDG独立看门狗WWDG窗口看门狗
复位计数器减到0后计数器T[5:0]减到0后、过早重装计数器
中断早期唤醒中断
时钟源LSI(40KHz)PCLK1(36MHz)
预分频系数4、8、32、64、128、2561、2、4、8
计数器12位6位(有效计数)
超时时间0.1ms~26214.4ms113us~58.25ms
喂狗方式写入键寄存器,重装固定值RLR直接写入计数器,写多少重装多少
防误操作键寄存器和写保护
用途独立工作,对时间精度要求较低要求看门狗在精确计时窗口起作用

独立看门狗(程序)

接线图

代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"

int main(void)
{
    /*模块初始化*/
    OLED_Init();                        //OLED初始化
    Key_Init();                            //按键初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "IWDG TEST");
    
    /*判断复位信号来源*/
    if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET)    //如果是独立看门狗复位
    {
        OLED_ShowString(2, 1, "IWDGRST");            //OLED闪烁IWDGRST字符串
        Delay_ms(500);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(100);
        
        RCC_ClearFlag();                            //清除标志位
    }
    else                                            //否则,即为其他复位
    {
        OLED_ShowString(3, 1, "RST");                //OLED闪烁RST字符串
        Delay_ms(500);
        OLED_ShowString(3, 1, "   ");
        Delay_ms(100);
    }
    
    /*IWDG初始化*/
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);    //独立看门狗写使能
    IWDG_SetPrescaler(IWDG_Prescaler_16);            //设置预分频为16
    IWDG_SetReload(2499);                            //设置重装值为2499,独立看门狗的超时时间为1000ms
    IWDG_ReloadCounter();                            //重装计数器,喂狗
    IWDG_Enable();                                    //独立看门狗使能
    
    while (1)
    {
        Key_GetNum();                                //调用阻塞式的按键扫描函数,模拟主循环卡死
        
        IWDG_ReloadCounter();                        //重装计数器,喂狗
        
        OLED_ShowString(4, 1, "FEED");                //OLED闪烁FEED字符串
        Delay_ms(200);                                //喂狗间隔为200+600=800ms
        OLED_ShowString(4, 1, "    ");
        Delay_ms(600);
    }
}

测试

窗口看门狗(程序)

接线图

代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"

int main(void)
{
    /*模块初始化*/
    OLED_Init();                        //OLED初始化
    Key_Init();                            //按键初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "WWDG TEST");
    
    /*判断复位信号来源*/
    if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET)    //如果是窗口看门狗复位
    {
        OLED_ShowString(2, 1, "WWDGRST");            //OLED闪烁WWDGRST字符串
        Delay_ms(500);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(100);
        
        RCC_ClearFlag();                            //清除标志位
    }
    else                                            //否则,即为其他复位
    {
        OLED_ShowString(3, 1, "RST");                //OLED闪烁RST字符串
        Delay_ms(500);
        OLED_ShowString(3, 1, "   ");
        Delay_ms(100);
    }
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);    //开启WWDG的时钟
    
    /*WWDG初始化*/
    WWDG_SetPrescaler(WWDG_Prescaler_8);            //设置预分频为8
    WWDG_SetWindowValue(0x40 | 21);                    //设置窗口值,窗口时间为30ms
    WWDG_Enable(0x40 | 54);                            //使能并第一次喂狗,超时时间为50ms
    
    while (1)
    {
        Key_GetNum();                                //调用阻塞式的按键扫描函数,模拟主循环卡死
        
        OLED_ShowString(4, 1, "FEED");                //OLED闪烁FEED字符串
        Delay_ms(20);                                //喂狗间隔为20+20=40ms
        OLED_ShowString(4, 1, "    ");
        Delay_ms(20);
        
        WWDG_SetCounter(0x40 | 54);                    //重装计数器,喂狗
    }
}

测试

FLASH闪存

FLASH简介

  • STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程。
  • 读写FLASH的用途:

    • 利用程序存储器的剩余空间来保存掉电不丢失的用户数据;
    • 通过在程序中编程(IAP),实现程序的自我更新;
  • 在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序。
  • 在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序。

闪存模块组织

FLASH基本结构

FLASH解锁

使用指针访问存储器

程序存储器编程

程序存储器页擦除

程序存储器全擦除

选项字节

选项字节编程

  • 检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作
  • 解锁FLASH_CR的OPTWRE位
  • 设置FLASH_CR的OPTPG位为1
  • 写入要编程的半字到指定的地址
  • 等待BSY位变为0
  • 读出写入的地址并验证数据

选项字节擦除

  • 检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作
  • 解锁FLASH_CR的OPTWRE位
  • 设置FLASH_CR的OPTER位为1
  • 设置FLASH_CR的STRT位为1
  • 等待BSY位变为0
  • 读出被擦除的选择字节并做验证

器件电子签名

  • 电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名。
  • 闪存容量寄存器:

    • 基地址:0x1FFF F7E0
    • 大小:16位
  • 产品唯一身份标识寄存器

    • 基地址:0x1FFF F7E8
    • 大小:96位

读写内部FLASH(程序)

接线图

代码

System - MyFLASH.c - 实现闪存最基本的3个功能 - 读取、擦除和编程

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:FLASH读取一个32位的字
  * 参    数:Address 要读取数据的字地址
  * 返 回 值:指定地址下的数据
  */
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
    return *((__IO uint32_t *)(Address));    //使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个16位的半字
  * 参    数:Address 要读取数据的半字地址
  * 返 回 值:指定地址下的数据
  */
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
    return *((__IO uint16_t *)(Address));    //使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个8位的字节
  * 参    数:Address 要读取数据的字节地址
  * 返 回 值:指定地址下的数据
  */
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
    return *((__IO uint8_t *)(Address));    //使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH全擦除
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在
  */
void MyFLASH_EraseAllPages(void)
{
    FLASH_Unlock();                    //解锁
    FLASH_EraseAllPages();            //全擦除
    FLASH_Lock();                    //加锁
}

/**
  * 函    数:FLASH页擦除
  * 参    数:PageAddress 要擦除页的页地址
  * 返 回 值:无
  */
void MyFLASH_ErasePage(uint32_t PageAddress)
{
    FLASH_Unlock();                    //解锁
    FLASH_ErasePage(PageAddress);    //页擦除
    FLASH_Lock();                    //加锁
}

/**
  * 函    数:FLASH编程字
  * 参    数:Address 要写入数据的字地址
  * 参    数:Data 要写入的32位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
    FLASH_Unlock();                            //解锁
    FLASH_ProgramWord(Address, Data);        //编程字
    FLASH_Lock();                            //加锁
}

/**
  * 函    数:FLASH编程半字
  * 参    数:Address 要写入数据的半字地址
  * 参    数:Data 要写入的16位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
    FLASH_Unlock();                            //解锁
    FLASH_ProgramHalfWord(Address, Data);    //编程半字
    FLASH_Lock();                            //加锁
}

System - MyFLASH.h

#ifndef __MYFLASH_H
#define __MYFLASH_H

uint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);

void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePage(uint32_t PageAddress);

void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

#endif

System - Store.c - 实现参数数据的读写和存储管理

#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#define STORE_START_ADDRESS        0x0800FC00        //存储的起始地址
#define STORE_COUNT                512                //存储数据的个数

uint16_t Store_Data[STORE_COUNT];                //定义SRAM数组

/**
  * 函    数:参数存储模块初始化
  * 参    数:无
  * 返 回 值:无
  */
void Store_Init(void)
{
    /*判断是不是第一次使用*/
    if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)    //读取第一个半字的标志位,if成立,则执行第一次使用的初始化
    {
        MyFLASH_ErasePage(STORE_START_ADDRESS);                    //擦除指定页
        MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);    //在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
        for (uint16_t i = 1; i < STORE_COUNT; i ++)                //循环STORE_COUNT次,除了第一个标志位
        {
            MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);        //除了标志位的有效数据全部清0
        }
    }
    
    /*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/
    for (uint16_t i = 0; i < STORE_COUNT; i ++)                    //循环STORE_COUNT次,包括第一个标志位
    {
        Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);        //将闪存的数据加载回SRAM数组
    }
}

/**
  * 函    数:参数存储模块保存数据到闪存
  * 参    数:无
  * 返 回 值:无
  */
void Store_Save(void)
{
    MyFLASH_ErasePage(STORE_START_ADDRESS);                //擦除指定页
    for (uint16_t i = 0; i < STORE_COUNT; i ++)            //循环STORE_COUNT次,包括第一个标志位
    {
        MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);    //将SRAM数组的数据备份保存到闪存
    }
}

/**
  * 函    数:参数存储模块将所有有效数据清0
  * 参    数:无
  * 返 回 值:无
  */
void Store_Clear(void)
{
    for (uint16_t i = 1; i < STORE_COUNT; i ++)            //循环STORE_COUNT次,除了第一个标志位
    {
        Store_Data[i] = 0x0000;                            //SRAM数组有效数据清0
    }
    Store_Save();                                        //保存数据到闪存
}

System - Store.h

#ifndef __STORE_H
#define __STORE_H

extern uint16_t Store_Data[];

void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"

uint8_t KeyNum;                    //定义用于接收按键键码的变量

int main(void)
{
    /*模块初始化*/
    OLED_Init();                //OLED初始化
    Key_Init();                    //按键初始化
    Store_Init();                //参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Flag:");
    OLED_ShowString(2, 1, "Data:");
    
    while (1)
    {
        KeyNum = Key_GetNum();        //获取按键键码
        
        if (KeyNum == 1)            //按键1按下
        {
            Store_Data[1] ++;        //变换测试数据
            Store_Data[2] += 2;
            Store_Data[3] += 3;
            Store_Data[4] += 4;
            Store_Save();            //将Store_Data的数据备份保存到闪存,实现掉电不丢失
        }
        
        if (KeyNum == 2)            //按键2按下
        {
            Store_Clear();            //将Store_Data的数据全部清0
        }
        
        OLED_ShowHexNum(1, 6, Store_Data[0], 4);    //显示Store_Data的第一位标志位
        OLED_ShowHexNum(3, 1, Store_Data[1], 4);    //显示Store_Data的有效存储数据
        OLED_ShowHexNum(3, 6, Store_Data[2], 4);
        OLED_ShowHexNum(4, 1, Store_Data[3], 4);
        OLED_ShowHexNum(4, 6, Store_Data[4], 4);
    }
}

测试

  • key1变换数据并存储到FLASH,即使断电重启后也会存在;
  • key2重置数据。

读取芯片ID(程序)

接线图

代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main(void)
{
    OLED_Init();                        //OLED初始化
    
    OLED_ShowString(1, 1, "F_SIZE:");    //显示静态字符串
    OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);        //使用指针读取指定地址下的闪存容量寄存器
    
    OLED_ShowString(2, 1, "U_ID:");        //显示静态字符串
    OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);        //使用指针读取指定地址下的产品唯一身份标识寄存器
    OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
    OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
    OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
    
    while (1)
    {
        
    }
}

测试

  • F_SIZE:芯片FLASH的容量,单位KB,需手动转换为十进制;
  • U_ID:96位的芯片唯一ID号。

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - WDG看门狗 - STM32 - FLASH闪存


三二一的一的二