目录
编辑 BAT和VBAT引脚功能 ---- 接VBAT掉电不丢失
利用stm32 st-link utinil查看修改代码存储内容并解除保护
计算机存储单位的详细换算关系:
- 1比特(bit)是二进制数的一位,存放一位二进制数。
- 1字节(Byte)等于8比特(位),也就是说,1 Byte = 8 bit。例如,一个英文字母或一个数字或一个字符通常占用一个字节。两个字节可以存放一个中文汉字。
- 1千字节(KB)等于1024个字节,即 1 KB = 1024 B。
- 1兆字节(MB)等于1024个千字节,即 1 MB = 1024 KB。
- 1吉字节(GB)等于1024个兆字节,即 1 GB = 1024 MB。
- 1太字节(TB)等于1024个吉字节,即 1 TB = 1024 GB。
SPI通信
SPI通识概念
spi为推挽输出 比i2c开漏驱动能力强,且为全双工,通信速率更高
软件SPI ---- 代码段
//SPI.C
#include "stm32f10x.h" // Device header
//SS输出信号控制
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
//CLK输出信号控制
void MySPI_W_CLK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}
//MOSI写入信号控制
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}
//MISO读取信号控制
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
//开始信号
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//结束信号
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//SPI发送和接收数据 模式0 第一个边沿移入数据 第二个边沿移出数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for(i = 0; i < 8; i++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //每次发送1个位 发八次 高位先发
MySPI_W_CLK(1);
if(MySPI_R_MISO() == 1) //从机有应答
{
ByteReceive |= (0x80 >> i);//每次获取最高位 然后往左偏移
}
MySPI_W_CLK(0);
}
return ByteReceive;
}
SPI发送和接收数据 模式0 第一个边沿移入数据 第二个边沿移出数据
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t i, ByteReceive = 0x00;
// for(i = 0; i < 8; i++)
// {
// MySPI_W_MOSI(ByteSend & 0x80); //每次发送1个位 发八次 高位先发
// ByteSend <<= 1;
// MySPI_W_CLK(1);
// if(MySPI_R_MISO() == 1) //从机有反应
// {
// ByteSend |= 0x01;//每次获取最高位 然后往左偏移
// }
// MySPI_W_CLK(0);
// }
//
// return ByteReceive;
//}
//软件模拟SPI CS-PA4 DO-MISO-PA6 DI-MOSI-PA7 CLK-PA5
void MySPI_Init(void)
{
//开启GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;//SS MOSI CLK饿配置成输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//MISO配置成输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化SS拉高 CLK拉低
MySPI_W_SS(1);
MySPI_W_CLK(0);
}
//SPI.H
#ifndef _MYSPI_H
#define _MYSPI_H
//开始信号
void MySPI_Start(void);
//结束信号
void MySPI_Stop(void);
//SPI发送和接收数据
uint8_t MySPI_SwapByte(uint8_t ByteSend);
//软件模拟SPI CS-PA4 DO-MISO-PA6 DI-MOSI-PA7 CLK-PA5
void MySPI_Init(void);
#endif
W25Q64模块及代码段
//W25Q64.C
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"
void W25Q64_Init(void)
{
MySPI_Init();
}
//读取ID 并存放在MID 和DID指向的内存中 便于测试SPI和模块是否配置正确
void W25Q64_ReadID(uint8_t* MID,uint16_t* DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);//主机向从机发送9F
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//发送一个空数据用于置换从机返回地址
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//发送两个空数据 返回DID 通过左移或起来存放到DID
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
//写使能
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送写使能
MySPI_Stop();
}
//检测是否在忙
void W25Q64_WaiBusy(void)
{
uint32_t Timeout = 100000;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //检测寄存器1状态
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)//发送空数据 交换从机信息 实现连续读出寄存器 等待BUSY
{
Timeout--;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
//擦除扇区 发送擦除指令 确定擦除地址(擦除为最小单元)
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();//先写使能
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//主机发送擦除指令 按最小擦除单元4kb
MySPI_SwapByte(Address >> 16);//擦除对应地址
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaiBusy();//事后检测是否在忙
}
//写入页 先主机发送写指令 配置写入对应地址 再写入
void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();//先写使能
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//主机向从机发送写页指令
MySPI_SwapByte(Address >> 16); //发送24位地址 0x00123456 》》 16 0x0012 》》8 0x001234 高位省略
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for(i = 0; i < Count; i++)
{
MySPI_SwapByte(DataArray[i]);//循环写入数据
}
MySPI_Stop();
W25Q64_WaiBusy();//事后检测是否在忙
}
//读取页
void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint32_t Count)
{
uint32_t i;
W25Q64_WriteEnable();//先写使能
W25Q64_WaiBusy();//事前检测是否在忙
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);//主机向从机发送读指令
MySPI_SwapByte(Address >> 16); //发送24位地址 0x00123456 》》 16 0x0012 》》8 0x001234 高位省略
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for(i = 0; i < Count; i++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//循环交换 读取内容到数组中
}
MySPI_Stop();
}
//W25Q64.H
#ifndef _W25Q64_H
#define _W25Q64_H
//初始化SPI
void W25Q64_Init(void);
//读取ID 并存放在MID 和DID指向的内存中 便于测试SPI和模块是否配置正确
void W25Q64_ReadID(uint8_t* MID,uint16_t* DID);
//写使能
void W25Q64_WriteEnable(void);
//检测是否在忙
void W25Q64_WaiBusy(void);
//擦除扇区 发送擦除指令 确定擦除地址(擦除为最小单元)
void W25Q64_SectorErase(uint32_t Address);
//写入页 先主机发送写指令 配置写入对应地址 再写入
void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArray,uint16_t Count);
//读取页 发送读取指令, 读取地址 读取内容存放到指针指向地址
void W25Q64_ReadData(uint32_t Address,uint8_t* DataArray,uint32_t Count);
#endif
//W25Q64寄存器宏配置
#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H
#define W25Q64_WRITE_ENABLE 0x06 //写使能
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05 //检测寄存器1是否在忙
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02 //写入页指令
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20 //最小擦除单元
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F //厂商ID
#define W25Q64_READ_DATA 0x03 //读取数据
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF //发送空 交换指令
#endif
硬件SPI
typedef struct { uint16_t SPI_Direction; uint16_t SPI_Mode; uint16_t SPI_DataSize; uint16_t SPI_CPOL; uint16_t SPI_CPHA; uint16_t SPI_NSS; uint16_t SPI_BaudRatePrescaler; uint16_t SPI_FirstBit; uint16_t SPI_TIMode; uint16_t SPI_CRCCalculation; uint16_t SPI_CRCNext; uint16_t SPI_CRCPolynomial; } SPI_InitTypeDef; 该结构体的各个字段含义如下:
SPI_Direction
: 设置 SPI 的数据传输方向。SPI_Mode
: 设置 SPI 工作模式。SPI_DataSize
: 设置 SPI 数据长度。SPI_CPOL
: 设置 SPI 时钟极性。SPI_CPHA
: 设置 SPI 时钟相位。SPI_NSS
: 设置 NSS 信号源和模式。SPI_BaudRatePrescaler
: 设置 SPI 波特率预分频值。SPI_FirstBit
: 设置数据帧的第一个有效位。SPI_TIMode
: 设置 SPI 是否使用 TI 模式。SPI_CRCCalculation
: 设置是否开启 CRC 校验功能。SPI_CRCNext
: 设置下一个数据帧开始 CRC 计算的位置。SPI_CRCPolynomial
: 设置 CRC 校验多项式。通过以上结构体的各个参数,可以灵活地配置 SPI 的各种工作特性。
硬件SPI ---- 代码段
w25q64和软件部分一样
//spi.c
#include "stm32f10x.h" // Device header
//SS输出信号控制
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
//开始信号
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//结束信号
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//SPI发送和接收数据 模式0 单次发送,检测TXE为1 发送数据 同时检测RTXE为1 接收数据返回
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);//检测SPI输出缓冲区标志位TXE是否为1 不为1循环等待
SPI_I2S_SendData(SPI1,ByteSend);//TXE为1 则发送数据
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);//检测SPI输入缓冲区标志位是否为1 是否为空
return SPI_I2S_ReceiveData(SPI1);//为空 则接收数据返还回去 硬件自动清除标志位
}
//硬件SPI CS-PA4 DO-MISO-PA6 DI-MOSI-PA7 CLK-PA5 注意配置四个引脚模式
void MySPI_Init(void)
{
//开启gpio时钟 和SPI时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//PA4 接ss 通用输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//PA7主机MOSI输出 PA5主机CLK 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//PA6主机MISO输入 配置成上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//SPI初始化
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//SPI工作模式 主机模式
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //设置 SPI 时钟极性 四种工作模式 第一种工作模式0
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //设置 SPI 时钟相位 0
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//数据长度为8 一个字节
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//置 SPI 的数据传输方向 双线全双工
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//先发数据高位
SPI_InitStructure.SPI_CRCPolynomial = 7; //设置 CRC 校验多项式。随机无用配置
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//设置 SPI 波特率预分频值。 不能太快
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//软件控制NSS
SPI_Init(SPI1,&SPI_InitStructure);
//SPI使能
SPI_Cmd(SPI1,ENABLE);
//初始化拉高
MySPI_W_SS(1);
}
#ifndef _MYSPI_H
#define _MYSPI_H
//开始信号
void MySPI_Start(void);
//结束信号
void MySPI_Stop(void);
//SS输出信号控制
void MySPI_W_SS(uint8_t BitValue);
//SPI发送和接收数据 模式0 单次发送,检测TXE为1 发送数据 同时检测RTXE为1 接收数据返回
uint8_t MySPI_SwapByte(uint8_t ByteSend);
//硬件SPI CS-PA4 DO-MISO-PA6 DI-MOSI-PA7 CLK-PA5
void MySPI_Init(void);
#endif
时间撮
#include <stdio.h>
#include <tIme.h>
int main()
{
time_t time_cnt;
struct tm time_data;
struct tm time_now;
char* arry;
time_cnt = time(NULL);//获取系统时钟
printf("%ld\n",time_cnt);
time_cnt = 1672588795;
time_data = *gmtime(&time_cnt); //秒计数器转换成日期时钟 1970开始 月要+1
printf("%ld年%d月%d日%d时%d分%d秒\n",time_data.tm_year+1900,time_data.tm_mon+1,time_data.tm_mday,\
time_data.tm_hour,time_data.tm_min,time_data.tm_sec);
time_cnt = 1672588795;
time_now = *localtime(&time_cnt); //秒计数器转换成日期时钟 加上时区 北京东八区+8小时 1970开始 月要+1
printf("%ld年%d月%d日%d时%d分%d秒\n",time_now.tm_year+1900,time_now.tm_mon+1,time_now.tm_mday,\
time_now.tm_hour,time_now.tm_min,time_now.tm_sec);
// time_t __cdecl mktime(struct tm *_Tm);
time_cnt = mktime(&time_now); //把这个日期转换成格林威治秒时间 1672588795
printf("%ld\n",time_cnt);
//char *__cdecl ctime(const time_t *_Time)
arry = ctime(&time_cnt); //把秒计数器转换成字符串
printf("%s\n",arry);
arry = asctime(&time_now); //把日期时间转换成字符串
printf("%s\n",arry);
return 0;
}
关于BKP和RTC时钟
RTC_PRL,全称为RTC预分频装载寄存器,是一个用于保存RTC预分频器的周期计数值的寄存器。它与RTC_DIV共同工作,RTC_DIV是一个递减的计数器,由RTC_PRL的数据进行装载,每次归零后重新装载。当计数值与预分频寄存器中的值相匹配时,会输出TR_CLK信号,然后重新计数。用户可以通过读取RTC_PRL寄存器,获取当前的分频计数器的当前值而不停止分频计数器的工作。值得注意的是,RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器的特性是,它们仅能通过备份域复位信号复位,而系统复位或电源复位无法对其进行异步复位
对应如下图
BAT和VBAT引脚功能 ---- 接VBAT掉电不丢失
STM32的BAT引脚,当外部电池或其他电源连接到VBAT脚上时,可以使STM32的BAT引脚,当外部电池或其他电源连接到VBAT脚上时,可以使STM32在VDD断电情况下保存备份寄存器的内容和维持RTC的功能。这是因为当VDD断电时,芯片会通过VBAT引脚获取电源。如果应用中没有使用外部电池,建议将VBAT引脚接到VDD引脚上。这是因为在没有电池的时候,Vbat引脚必须连接到VDD引脚,否则在单片机内部,Vbat将通过内部ESD保护二极管供电。所以,对于BAT引脚的使用和连接方式需要根据具体的应用和电路设计来确定。
这些函数是STM32的备份寄存器(Backup Register)相关的功能。下面是每个函数的含义:
void BKP_DeInit(void);
:该函数用于关闭备份寄存器的所有功能,将备份寄存器完全关闭。
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
:该函数用于配置备份寄存器的篡改引脚电平。参数BKP_TamperPinLevel指定了篡改引脚的电平。
void BKP_TamperPinCmd(FunctionalState NewState);
:该函数用于启用或禁用备份寄存器的篡改引脚功能。当参数NewState为ENABLE时,启用篡改引脚;当参数NewState为DISABLE时,禁用篡改引脚。
void BKP_ITConfig(FunctionalState NewState);
:该函数用于启用或禁用备份寄存器的中断功能。当参数NewState为ENABLE时,启用中断;当参数NewState为DISABLE时,禁用中断。
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
:该函数用于配置备份寄存器的RTC输出源。参数BKP_RTCOutputSource指定了RTC输出源。
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);
:该函数用于设置备份寄存器的RTC校准值。参数CalibrationValue指定了校准值。
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
:该函数用于向指定的备份寄存器写入数据。参数BKP_DR指定了要写入的备份寄存器,参数Data指定了要写入的数据。
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
:该函数用于从指定的备份寄存器读取数据。参数BKP_DR指定了要读取的备份寄存器,返回值是从备份寄存器中读取的数据。
FlagStatus BKP_GetFlagStatus(void);
:该函数用于获取备份寄存器的标志状态。返回值是一个标志状态,表示标志是否被设置。
void BKP_ClearFlag(void);
:该函数用于清除备份寄存器的标志。
ITStatus BKP_GetITStatus(void);
:该函数用于获取备份寄存器的中断状态。返回值是一个中断状态,表示中断是否被触发。
void BKP_ClearITPendingBit(void);
:该函数用于清除备份寄存器的中断挂起位。
PWR作用 ---- 电压检测器、和低功耗模式
STM32的PWR引脚主要是用于电源控制,它负责管理STM32内部电源供电部分。具体来说,PWR可以实现可编程电压监测器和低功耗模式的功能。在系统运行时,PWR能够对电源电压进行监测,当电压低于设定的阈值时,可以自动将单片机转入低功耗模式以节省能源。此外,根据具体的应用需求,PWR还可以通过配置实现其他的电源管理功能。因此,PWR引脚的设计和使用对于确保STM32单片机的稳定运行和有效管理电源消耗具有重要的作用
这些函数是STM32微控制器的电源管理功能相关的函数。下面是每个函数的含义:
void PWR_DeInit(void);
:该函数用于关闭所有与电源管理相关的寄存器和配置,将PWR模块完全关闭。
void PWR_BackupAccessCmd(FunctionalState NewState);
:该函数用于控制备份访问权限。当参数NewState为ENABLE时,允许对备份寄存器进行访问;当参数NewState为DISABLE时,禁止对备份寄存器进行访问。
void PWR_PVDCmd(FunctionalState NewState);
:该函数用于启用或禁用内部上拉下拉电阻,以保护系统免受过压或欠压的影响。当参数NewState为ENABLE时,启用内部上拉下拉电阻;当参数NewState为DISABLE时,禁用内部上拉下拉电阻。
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);
:该函数用于设置内部上拉下拉电阻的电压阈值。参数PWR_PVDLevel指定了要使用的电压阈值。
void PWR_WakeUpPinCmd(FunctionalState NewState);
:该函数用于启用或禁用唤醒引脚的功能。当参数NewState为ENABLE时,启用唤醒引脚;当参数NewState为DISABLE时,禁用唤醒引脚。
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);
:该函数用于使STM32进入低功耗模式(STOP模式)。参数PWR_Regulator指定了使用的电源调整器,参数PWR_STOPEntry指定了STOP模式的配置条目。
void PWR_EnterSTANDBYMode(void);
:该函数用于使STM32进入待机模式(STANDBY模式)。
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);
:该函数用于获取指定的电源标志状态。参数PWR_FLAG指定了要查询的标志。返回值是一个标志状态,表示标志是否被设置。
void PWR_ClearFlag(uint32_t PWR_FLAG);
:该函数用于清除指定的电源标志。参数PWR_FLAG指定了要清除的标志。
通过BKP备份寄存器和PWR电源管理 实现掉电不丢失数据
//通过备份寄存器存放数据 实现掉电不丢失功能,还得通过电源管理寄存器使能
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
/*备份寄存器配置好后给VBAT供电 则里面的数据掉电不会丢失,
还有警告、中断、闹钟也可以在里面配置
103系列只存到0-10 BKP_DR1
*/
uint8_t KeyNum;
uint16_t ArrayWrite[] = {0x1234,0x5678};
uint16_t ArrayRead[2];
//通过备份寄存器存放数据 实现掉电不丢失功能,还得通过电源管理寄存器使能
int main(void)
{
//开启备份寄存器时钟 和 电源管理时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
//该函数用于控制备份访问权限。当参数NewState为ENABLE时,允许对备份寄存器进行访问
PWR_BackupAccessCmd(ENABLE);
//OLED初始化 Key初始化
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"W:");
OLED_ShowString(2,1,"R:");
//显示 DR1 DE2存放的数据 读取出来放到数组中
while(1)
{
KeyNum =Key_GetNum();
if(KeyNum == 1) //如果按键按下则将DR12中的数据++ 读取到的数据也会改变并显示出来
{
ArrayWrite[0]++;
ArrayWrite[1]++;
}
//往DR1 2 写入数据并读取数据
BKP_WriteBackupRegister(BKP_DR1,ArrayWrite[0]);
BKP_WriteBackupRegister(BKP_DR2,ArrayWrite[1]);
ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);
ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);
//显示写入数据和读取数据
OLED_ShowHexNum(1,4,ArrayWrite[0],4);
OLED_ShowHexNum(1,9,ArrayWrite[1],4);
OLED_ShowHexNum(2,4,ArrayRead[0],4);
OLED_ShowHexNum(2,9,ArrayRead[1],4);
}
}
STM32的BKP、RTC、BAT、VBAT和PWR是与该系列微控制器的电源管理和备份功能相关的模块和引脚。
BKP (Backup Registers): STM32系列的处理器都有备份寄存器,它们位于备份区域。当VDD电源被切断时,这些备份寄存器仍然由VBAT维持供电。当系统在待机模式下被唤醒,或者系统复位或电源复位时,它们都不会被复位。
RTC (Real-Time Clock): RTC是一个实时时钟模块,它可以用于测量较长的时间段。RTC模块和时钟配置系统 (RCC_BDCR寄存器)处于后备区域,这意味着在系统复位或从待机模式唤醒后,RTC的设置和时间会保持不变。RTC的核心部分 (如RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器)只能由后备域复位。
BAT (Battery) 和 VBAT: 这两个引脚分别用于连接电池和其他电源设备。当VDD电源被切断时,备份寄存器和RTC可以选择VBAT供电。
PWR (Power Management): PWR模块负责管理STM32的电源。例如,执行某些操作可以使得能对BKP和RTC的访问。
RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState)
:这个函数用于配置RTC中断源。RTC_IT参数指定要配置的中断源,NewState参数指定中断源的新状态(开启或关闭)。
RTC_EnterConfigMode(void)
:这个函数将RTC切换到配置模式。在配置模式下,可以修改RTC的各种寄存器。
RTC_ExitConfigMode(void)
:这个函数将RTC切换回正常工作模式。
RTC_GetCounter(void)
:这个函数返回当前的计数值。计数值是从RTC开始计时到现在的时间。
RTC_SetCounter(uint32_t CounterValue)
:这个函数设置新的计数值。计数值是从RTC开始计时到现在的时间。
RTC_SetPrescaler(uint32_t PrescalerValue)
:这个函数设置预分频器的值。预分频器决定了RTC计数的频率。
RTC_SetAlarm(uint32_t AlarmValue)
:这个函数设置闹钟的值。当计数值达到这个值时,会触发一个中断。
RTC_GetDivider(void)
:这个函数返回当前的除数值。除数值决定了RTC计数的频率。
RTC_WaitForLastTask(void)
:这个函数等待最后一个任务完成。这通常用于确保所有的RTC操作都已经完成。
RTC_WaitForSynchro(void)
:这个函数等待RTC与外部时钟同步。这通常用于确保RTC的时间是正确的。
RTC_GetFlagStatus(uint16_t RTC_FLAG)
:这个函数返回指定的标志位的状态。标志位可以是各种事件,如闹钟事件、溢出事件等。
RTC_ClearFlag(uint16_t RTC_FLAG)
:这个函数清除指定的标志位。
RTC_GetITStatus(uint16_t RTC_IT)
:这个函数返回指定的中断源的状态。中断源可以是各种事件,如闹钟事件、溢出事件等。
RTC_ClearITPendingBit(uint16_t RTC_IT)
:这个函数清除指定的中断源的挂起位。
BKP备份寄存器 ---- 代码段 掉电时钟不复位
#include "stm32f10x.h"
#include <time.h>
uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55}; //不能写01 06 这会转换成8进制 会造成误会
//把数组的年月日 赋值到struct tm的结构体中 转用mktime转换成s秒
void MyRTC_SetTime(void)
{
time_t Time_Cnt;
struct tm Time_Data;
Time_Data.tm_year = MyRTC_Time[0] - 1900;
Time_Data.tm_mon = MyRTC_Time[1] - 1;
Time_Data.tm_mday = MyRTC_Time[2];
Time_Data.tm_hour = MyRTC_Time[3];
Time_Data.tm_min = MyRTC_Time[4];
Time_Data.tm_sec = MyRTC_Time[5];
Time_Cnt = mktime(&Time_Data) - (8*60*60);//把年月日时分秒转成 格林威治时间秒
RTC_SetCounter(Time_Cnt);//把这个秒 写到RTC时钟里
}
//通过RRTC_GetCounter获取到秒赋值给time_t 的秒再转换成struct tm的年月日 写到数组中
void MyRTC_ReadTime(void)
{
time_t Time_Cnt;
struct tm Time_Data;
Time_Cnt = RTC_GetCounter() + (8*60*60);//获取到秒 加上时区偏移 存放到time_t秒中
Time_Data = *localtime(&Time_Cnt);//把time_t 秒转换成年月日写到对应数组中
MyRTC_Time[0] = Time_Data.tm_year + 1900;
MyRTC_Time[1] = Time_Data.tm_mon + 1;
MyRTC_Time[2] = Time_Data.tm_mday;
MyRTC_Time[3] = Time_Data.tm_hour;
MyRTC_Time[4] = Time_Data.tm_min;
MyRTC_Time[5] = Time_Data.tm_sec;
}
//RTC基础配置初始化 备份寄存器、电源控制、时钟使能、同步、等待完成、预分频器、计数器
void MyRTC_Init(void)
{
//开启备份寄存器和电源控制寄存器时钟 并使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//备份
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//控制
PWR_BackupAccessCmd(ENABLE); //电源控制 使能备份访问指令
//用备份寄存器来不让时间复位 随便给一个值 除非两个都没电 随便等于后做一个标志位
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
//配置内部低速振荡器 并等待配置状态完成
RCC_LSICmd(ENABLE); //开启LSE外部低速时钟
while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);//等待RCC_LSE时钟准备好 没好则循环等待
//配置RTC时钟 并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
RCC_RTCCLKCmd(ENABLE);
//等待同步 等待上一步完成
RTC_WaitForSynchro(); //用于等待实时时钟(RTC)与外部时钟同步的函数
RTC_WaitForLastTask(); //于等待实时时钟(RTC)完成最后任务的函数
//写入预分频器值
RTC_SetPrescaler(40000 - 1); //4khz / 40000 = 1 频率1 时间为1s
RTC_WaitForLastTask(); //等待完成
//设置要
MyRTC_SetTime(); //用于设置时间
RTC_WaitForLastTask();
BKP_WriteBackupRegister(BKP_DR1,0xA5A5);//该备份寄存器是这个值 标志位 则不会再进这里面让时钟复位
}
else {
//等待同步 等待上一步完成
RTC_WaitForSynchro(); //用于等待实时时钟(RTC)与外部时钟同步的函数
RTC_WaitForLastTask(); //于等待实时时钟(RTC)完成最后任务的函数
}
}
//外部低速振荡器不行 则换内部低速振荡器
/*
void MyRTC_Init(void)
{
//开启备份寄存器和电源控制寄存器时钟 并使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//备份
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//控制
PWR_BackupAccessCmd(ENABLE); //电源控制 使能备份访问指令
//配置外部低速振荡器 并等待配置状态完成
RCC_LSEConfig(RCC_LSE_ON); //开启LSE外部低速时钟
while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) != SET);//等待RCC_LSE时钟准备好 没好则循环等待
//配置RTC时钟 并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
//等待同步 等待上一步完成
RTC_WaitForSynchro(); //用于等待实时时钟(RTC)与外部时钟同步的函数
RTC_WaitForLastTask(); //于等待实时时钟(RTC)完成最后任务的函数
//写入预分频器值
RTC_SetPrescaler(32768 - 1); //32.768khz / 32768 = 1 频率1 时间为1s
RTC_WaitForLastTask(); //等待完成
*/
#ifndef _MYRTC_H
#define _MYRTC_H
extern uint16_t MyRTC_Time[]; //不能写01 06 这会转换成8进制 会造成误会
//把数组的年月日 赋值到struct tm的结构体中 转用mktime转换成s秒
void MyRTC_SetTime(void);
//通过RRTC_GetCounter获取到秒赋值给time_t 的秒再转换成struct tm的年月日 写到数组中
void MyRTC_ReadTime(void);
//RTC基础配置初始化 备份寄存器、电源控制、时钟使能、同步、等待完成、预分频器、计数器
void MyRTC_Init(void);
#endif
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
//通过备份寄存器存放数据 实现掉电不丢失功能,还得通过电源管理寄存器使能
int main(void)
{
OLED_Init();
MyRTC_Init();
OLED_ShowString(1,1,"Data:xxxx-xx-xx");
OLED_ShowString(2,1,"Time:xx:xx:xx");
OLED_ShowString(3,1,"Cnt :");
while(1)
{
//获取时间 获取的秒 通过struct tm 填入结构体,再通过结构体填入到数组中
MyRTC_ReadTime();
OLED_ShowNum(1,6,MyRTC_Time[0],4);
OLED_ShowNum(1,11,MyRTC_Time[1],2);
OLED_ShowNum(1,14,MyRTC_Time[2],2);
OLED_ShowNum(2,6,MyRTC_Time[3],2);
OLED_ShowNum(2,9,MyRTC_Time[4],2);
OLED_ShowNum(2,12,MyRTC_Time[5],2);
OLED_ShowNum(3,6,RTC_GetCounter(),10);
}
}
PWR 电源控制
睡前小故事
叫醒服务
案例一 修改主频
案例二 睡眠模式 任一中断会叫醒睡眠
案例三 停机模式 只有外部中断和复位等才能唤醒
一旦烧录成功则必须按住复位键后点击下载 再松开复位键
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
uint8_t RxData;
//因为为停止模式 已经关闭1.8v区域时钟 所以下载不进去
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"USART:");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启电源管理 时钟
while(1)
{
//必须通过外部中断才能唤醒或则复位才能唤醒
//没加外部中断代码!!
OLED_ShowString(2,1,"Running");
Delay_ms(200);
OLED_ShowString(2,1," ");
Delay_ms(200);
//微控制器进入低功耗模式。在低功耗模式下,微控制器的电源被关闭
PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);//配置成电压调节器开或关都可以、中断或者事件都可以
SystemInit();
}
}
案例四 待机模式 通过闹钟和外部PA0引脚高电平唤醒
在待机模式下 cpu电源控制器断开 寄存器也断电 所以会数据丢失 ,唤醒后冲从头开始执行
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
//因为为停止模式 已经关闭1.8v区域时钟 所以下载不进去
int main(void)
{
OLED_Init();
MyRTC_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启电源管理 时钟
OLED_ShowString(1,1,"Cnt:");//时钟秒
OLED_ShowString(2,1,"ALR:");//闹钟时间
OLED_ShowString(3,1,"ALRF:");//闹钟标志位
uint32_t Alarm = RTC_GetCounter() + 10;//闹钟时间等于 格林威治s + 10s
RTC_SetAlarm(Alarm); //置实时时钟(RTC)的闹钟。它允许用户在指定的时间触发一个中断或执行特定的操作
// PWR_WakeUpPinCmd(ENABLE);//唤醒方法2 使能或禁用微控制器的唤醒引脚。在低功耗模式下,微控制器的电源被关闭
while(1)
{
OLED_ShowNum(1,5,RTC_GetCounter(),10);
OLED_ShowNum(2,5,Alarm,10);
OLED_ShowNum(3,6,RTC_GetFlagStatus(RTC_FLAG_ALR),1);//把获取到的标志位显示出来
Delay_s(2);
OLED_Clear();
//微控制器进入 待机 模式。关闭CPU和寄存器 数据丢失 且从老头开始运行
PWR_EnterSTANDBYMode();//使微控制器进入待机模式。在待机模式下,微控制器的电源被关闭,以节省能源
}
}
看门狗 DWG 分iwdg独立看门狗wwdg窗口看门狗
IWDG函数
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess)
: 这个函数用于设置写访问权限。参数IWDG_WriteAccess
是一个16位无符号整数,用于指定写访问权限。
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler)
: 这个函数用于设置预分频器的值。参数IWDG_Prescaler
是一个8位无符号整数,用于指定预分频器的值。
void IWDG_SetReload(uint16_t Reload)
: 这个函数用于设置重载寄存器的值。参数Reload
是一个16位无符号整数,用于指定重载寄存器的值。
void IWDG_ReloadCounter(void)
: 这个函数用于重新加载计数器。当调用此函数时,内部看门狗定时器的计数器将被重置为指定的重载值。
void IWDG_Enable(void)
: 这个函数用于启用内部看门狗定时器。调用此函数后,看门狗定时器将开始计时,并在达到预设的时间间隔后触发中断。
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG)
: 这个函数用于获取内部看门狗定时器的标志状态。参数IWDG_FLAG
是一个16位无符号整数,用于指定要查询的标志。函数返回一个标志状态,表示指定的标志是否被设置。
独立看门狗 ---- 代码段
接触写保护、根据多久喂狗T时间T 设置预分频器PR和RL计数CNT 开启独立看门狗
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"FLAG");
Key_Init();
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET){ //如果独立看门狗动作 则显示IWDGRESET
OLED_ShowString(2,1,"IWDGRESET");
Delay_ms(500);
OLED_ShowString(2,1," ");
Delay_ms(200);
RCC_ClearFlag();
}else{ //如果有在规定时间喂狗 按下复位则显示SET
OLED_ShowString(3,1,"SET");
Delay_ms(500);
OLED_ShowString(3,1," ");
Delay_ms(200);
}
//解除写保护 使用独立看门狗时系统已经自动设置RCC时钟
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//IWDG_WriteAccess_Enable == 0x5555;解除写保护
//设置PSC预分频值
IWDG_SetPrescaler(IWDG_Prescaler_16);
//设置计数值 喂狗时间 = 1/40KHz * 预分频 * ARR
IWDG_SetReload(2500-1);// 1000ms = 0.025ms * 16 * 2499
//立马先喂一下狗 重新加载计数器
IWDG_ReloadCounter();
//启用独立看门狗 ---- 依据设置时间 在时间内去喂狗 就是重新写入计数值
IWDG_Enable();
while(1)
{
Key_getNum();//按住会阻塞等待 没有喂狗 复位显示IWDGRESET
IWDG_ReloadCounter();//主函数钟每800ms喂狗一次 没有超出约定时间 复位显示SET
OLED_ShowString(4,1,"SHUA XIN");
Delay_ms(600);
OLED_ShowString(4,1," ");
Delay_ms(200);
}
}
窗口看门狗
void WWDG_DeInit(void);
:这个函数用于初始化看门狗定时器,将其设置为默认状态。
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
:这个函数用于设置看门狗定时器的预分频值。预分频值决定了看门狗定时器的时间基准,通常设置为系统时钟的某个倍数。
void WWDG_SetWindowValue(uint8_t WindowValue);
:这个函数用于设置看门狗定时器的窗口值。窗口值决定了看门狗定时器在超时后是否复位。
void WWDG_EnableIT(void);
:这个函数用于使能看门狗定时器的中断。当看门狗定时器超时时,会触发一个中断。
void WWDG_SetCounter(uint8_t Counter);
:这个函数用于设置看门狗定时器的计数器值。计数器值决定了看门狗定时器在超时前需要等待多少个时钟周期。
void WWDG_Enable(uint8_t Counter);
:这个函数用于启动看门狗定时器。它会将看门狗定时器设置为启用状态,并开始计时。
FlagStatus WWDG_GetFlagStatus(void);
:这个函数用于获取看门狗定时器的状态标志。它返回一个标志位,表示看门狗定时器是否已经复位。
void WWDG_ClearFlag(void);
:这个函数用于清除看门狗定时器的状态标志。它将重置看门狗定时器的状态,使其可以再次被复位。
窗口看门狗 ---- 代码段
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
//30-50ms从WWDG_Enable到喂狗间隔30-50ms 则不会复位 超出或者低于这个时间段都会复位
int main(void)
{
OLED_Init();
OLED_ShowString(1,1,"FLAG ----");
if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET){ //如果独立看门狗动作 则显示IWDGRESET
OLED_ShowString(2,1,"WWDGRESET");
Delay_ms(500);
OLED_ShowString(2,1," ");
Delay_ms(200);
RCC_ClearFlag();
}else{ //如果有在规定时间喂狗 按下复位则显示SET
OLED_ShowString(3,1,"SET");
Delay_ms(500);
OLED_ShowString(3,1," ");
Delay_ms(200);
}
//开启wwdg时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
WWDG_SetPrescaler(WWDG_Prescaler_8);//预分频器 设定50ms喂狗 只能用8分频
WWDG_SetWindowValue(0x40 + 21);// 30ms = 1/36000 * 4096 * 8 * (T[5:0]-1)高55 - (T[5:0]-1)低22
WWDG_Enable(0x40 + 54);
while(1)
{
//30-50ms从WWDG_Enable到喂狗间隔30-50ms 则不会复位 超出或者低于这个时间段都会复位
OLED_ShowString(4, 1, "FEED");
Delay_ms(20);
OLED_ShowString(4, 1, " ");
Delay_ms(20);
WWDG_SetCounter(0x40 + 54);// T[5:0]为55 T[6]为1未溢出 所以与上0x40 50ms = 1/36000 * 4096 * 8 * (T[5:0]-1)
}
}
修改kile中只读文件
FLASH闪存
要先解除接口寄存器的两个锁
在写入和修改时 必须先擦除数据、不能频繁通过中断去修改flash区域、因为有时间差
利用stm32 st-link utinil查看修改代码存储内容并解除保护
flash ---- 代码段
#include "stm32f10x.h" // Device header
//根据地址读取字32 并返回
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
return *((__IO uint32_t*)(Address));
}
//根据地址读取半字16 并返回
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
return *((__IO uint16_t*)(Address));
}
//根据地址读取字节8 并返回
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
return *((__IO uint8_t*)(Address));
}
//单页擦除 先解锁 擦除该页 加锁
void MyFLASH_ErasePage(uint32_t Address)
{
FLASH_Unlock();
FLASH_ErasePage(Address);
FLASH_Lock();
}
//擦除全部 先解锁 擦除全部 加锁
void MyFLASH_EraseAllPages(void)
{
FLASH_Unlock();
FLASH_EraseAllPages();
FLASH_Lock();
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyFLASH.h"
#include "Key.h"
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowHexNum(1,1,MyFLASH_ReadWord(0x08000000),8);
OLED_ShowHexNum(2,1,MyFLASH_ReadHalfWord(0x08000000),4);
OLED_ShowHexNum(3,1,MyFLASH_ReadByte(0x08000000),2);
while(1)
{
KeyNum = Key_getNum();
if(KeyNum == 1)
{
MyFLASH_EraseAllPages();//如果按键一按键就擦除全部
}
if(KeyNum == 2)
{
MyFLASH_ErasePage(0x08000400);//如果按键二按键就擦除该地址页
}
}
}
#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(); //保存数据到闪存
}
#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);
}
}
volatile关键字 防止该内容被编译器优化
在无意义加减变量、多线程改变变量时、读写与硬件相关的存储器时 都要volatile关键字