STM32 ---- 03 再次学习C8T6加深理解

目录

 计算机存储单位的详细换算关系:

SPI通信

SPI通识概念

软件SPI ---- 代码段 

W25Q64模块及代码段

硬件SPI

硬件SPI ---- 代码段

时间撮 

 关于BKP和RTC时钟

​编辑 BAT和VBAT引脚功能 ---- 接VBAT掉电不丢失

PWR作用 ---- 电压检测器、和低功耗模式

通过BKP备份寄存器和PWR电源管理 实现掉电不丢失数据

BKP备份寄存器 ---- 代码段  掉电时钟不复位

PWR 电源控制

案例一   修改主频

案例二 睡眠模式 任一中断会叫醒睡眠

案例三  停机模式 只有外部中断和复位等才能唤醒 

案例四 待机模式 通过闹钟和外部PA0引脚高电平唤醒

看门狗 DWG 分iwdg独立看门狗wwdg窗口看门狗 

​编辑

独立看门狗 ---- 代码段 

窗口看门狗 

窗口看门狗 ---- 代码段 

修改kile中只读文件 

FLASH闪存 

利用stm32 st-link utinil查看修改代码存储内容并解除保护

flash ---- 代码段 

 volatile关键字 防止该内容被编译器优化


 计算机存储单位的详细换算关系:

  1. 1比特(bit)是二进制数的一位,存放一位二进制数。
  2. 1字节(Byte)等于8比特(位),也就是说,1 Byte = 8 bit。例如,一个英文字母或一个数字或一个字符通常占用一个字节。两个字节可以存放一个中文汉字。
  3. 1千字节(KB)等于1024个字节,即 1 KB = 1024 B。
  4. 1兆字节(MB)等于1024个千字节,即 1 MB = 1024 KB。
  5. 1吉字节(GB)等于1024个兆字节,即 1 GB = 1024 MB。
  6. 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)相关的功能。下面是每个函数的含义:

  1. void BKP_DeInit(void);:该函数用于关闭备份寄存器的所有功能,将备份寄存器完全关闭。

  2. void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);:该函数用于配置备份寄存器的篡改引脚电平。参数BKP_TamperPinLevel指定了篡改引脚的电平。

  3. void BKP_TamperPinCmd(FunctionalState NewState);:该函数用于启用或禁用备份寄存器的篡改引脚功能。当参数NewState为ENABLE时,启用篡改引脚;当参数NewState为DISABLE时,禁用篡改引脚。

  4. void BKP_ITConfig(FunctionalState NewState);:该函数用于启用或禁用备份寄存器的中断功能。当参数NewState为ENABLE时,启用中断;当参数NewState为DISABLE时,禁用中断。

  5. void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);:该函数用于配置备份寄存器的RTC输出源。参数BKP_RTCOutputSource指定了RTC输出源。

  6. void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);:该函数用于设置备份寄存器的RTC校准值。参数CalibrationValue指定了校准值。

  7. void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);:该函数用于向指定的备份寄存器写入数据。参数BKP_DR指定了要写入的备份寄存器,参数Data指定了要写入的数据

  8. uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);:该函数用于从指定的备份寄存器读取数据。参数BKP_DR指定了要读取的备份寄存器,返回值是从备份寄存器中读取的数据。

  9. FlagStatus BKP_GetFlagStatus(void);:该函数用于获取备份寄存器的标志状态。返回值是一个标志状态,表示标志是否被设置。

  10. void BKP_ClearFlag(void);:该函数用于清除备份寄存器的标志。

  11. ITStatus BKP_GetITStatus(void);:该函数用于获取备份寄存器的中断状态。返回值是一个中断状态,表示中断是否被触发。

  12. void BKP_ClearITPendingBit(void);:该函数用于清除备份寄存器的中断挂起位。

PWR作用 ---- 电压检测器、和低功耗模式

STM32的PWR引脚主要是用于电源控制,它负责管理STM32内部电源供电部分。具体来说,PWR可以实现可编程电压监测器和低功耗模式的功能。在系统运行时,PWR能够对电源电压进行监测,当电压低于设定的阈值时,可以自动将单片机转入低功耗模式以节省能源。此外,根据具体的应用需求,PWR还可以通过配置实现其他的电源管理功能。因此,PWR引脚的设计和使用对于确保STM32单片机的稳定运行和有效管理电源消耗具有重要的作用

  1. 这些函数是STM32微控制器的电源管理功能相关的函数。下面是每个函数的含义:

  2. void PWR_DeInit(void);:该函数用于关闭所有与电源管理相关的寄存器和配置,将PWR模块完全关闭。

  3. void PWR_BackupAccessCmd(FunctionalState NewState);:该函数用于控制备份访问权限。当参数NewState为ENABLE时,允许对备份寄存器进行访问;当参数NewState为DISABLE时,禁止对备份寄存器进行访问。

  4. void PWR_PVDCmd(FunctionalState NewState);:该函数用于启用或禁用内部上拉下拉电阻,以保护系统免受过压或欠压的影响。当参数NewState为ENABLE时,启用内部上拉下拉电阻;当参数NewState为DISABLE时,禁用内部上拉下拉电阻。

  5. void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);:该函数用于设置内部上拉下拉电阻的电压阈值。参数PWR_PVDLevel指定了要使用的电压阈值。

  6. void PWR_WakeUpPinCmd(FunctionalState NewState);:该函数用于启用或禁用唤醒引脚的功能。当参数NewState为ENABLE时,启用唤醒引脚;当参数NewState为DISABLE时,禁用唤醒引脚。

  7. void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);:该函数用于使STM32进入低功耗模式(STOP模式)。参数PWR_Regulator指定了使用的电源调整器,参数PWR_STOPEntry指定了STOP模式的配置条目。

  8. void PWR_EnterSTANDBYMode(void);:该函数用于使STM32进入待机模式(STANDBY模式)。

  9. FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);:该函数用于获取指定的电源标志状态。参数PWR_FLAG指定了要查询的标志。返回值是一个标志状态,表示标志是否被设置。

  10. 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是与该系列微控制器的电源管理和备份功能相关的模块和引脚。

  1. BKP (Backup Registers): STM32系列的处理器都有备份寄存器,它们位于备份区域。当VDD电源被切断时,这些备份寄存器仍然由VBAT维持供电。当系统在待机模式下被唤醒,或者系统复位或电源复位时,它们都不会被复位。

  2. RTC (Real-Time Clock): RTC是一个实时时钟模块,它可以用于测量较长的时间段。RTC模块和时钟配置系统 (RCC_BDCR寄存器)处于后备区域,这意味着在系统复位或从待机模式唤醒后,RTC的设置和时间会保持不变。RTC的核心部分 (如RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器)只能由后备域复位。

  3. BAT (Battery)VBAT: 这两个引脚分别用于连接电池和其他电源设备。当VDD电源被切断时,备份寄存器和RTC可以选择VBAT供电。

  4. PWR (Power Management): PWR模块负责管理STM32的电源。例如,执行某些操作可以使得能对BKP和RTC的访问。

  1. RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState):这个函数用于配置RTC中断源。RTC_IT参数指定要配置的中断源,NewState参数指定中断源的新状态(开启或关闭)。

  2. RTC_EnterConfigMode(void):这个函数将RTC切换到配置模式。在配置模式下,可以修改RTC的各种寄存器。

  3. RTC_ExitConfigMode(void):这个函数将RTC切换回正常工作模式。

  4. RTC_GetCounter(void):这个函数返回当前的计数值。计数值是从RTC开始计时到现在的时间。

  5. RTC_SetCounter(uint32_t CounterValue):这个函数设置新的计数值。计数值是从RTC开始计时到现在的时间。

  6. RTC_SetPrescaler(uint32_t PrescalerValue):这个函数设置预分频器的值。预分频器决定了RTC计数的频率。

  7. RTC_SetAlarm(uint32_t AlarmValue):这个函数设置闹钟的值。当计数值达到这个值时,会触发一个中断。

  8. RTC_GetDivider(void):这个函数返回当前的除数值。除数值决定了RTC计数的频率。

  9. RTC_WaitForLastTask(void):这个函数等待最后一个任务完成。这通常用于确保所有的RTC操作都已经完成。

  10. RTC_WaitForSynchro(void):这个函数等待RTC与外部时钟同步。这通常用于确保RTC的时间是正确的。

  11. RTC_GetFlagStatus(uint16_t RTC_FLAG):这个函数返回指定的标志位的状态。标志位可以是各种事件,如闹钟事件、溢出事件等。

  12. RTC_ClearFlag(uint16_t RTC_FLAG):这个函数清除指定的标志位。

  13. RTC_GetITStatus(uint16_t RTC_IT):这个函数返回指定的中断源的状态。中断源可以是各种事件,如闹钟事件、溢出事件等。

  14. 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函数

  1. void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess): 这个函数用于设置写访问权限。参数IWDG_WriteAccess是一个16位无符号整数,用于指定写访问权限。

  2. void IWDG_SetPrescaler(uint8_t IWDG_Prescaler): 这个函数用于设置预分频器的值。参数IWDG_Prescaler是一个8位无符号整数,用于指定预分频器的值。

  3. void IWDG_SetReload(uint16_t Reload): 这个函数用于设置重载寄存器的值。参数Reload是一个16位无符号整数,用于指定重载寄存器的值。

  4. void IWDG_ReloadCounter(void): 这个函数用于重新加载计数器。当调用此函数时,内部看门狗定时器的计数器将被重置为指定的重载值。

  5. void IWDG_Enable(void): 这个函数用于启用内部看门狗定时器。调用此函数后,看门狗定时器将开始计时,并在达到预设的时间间隔后触发中断。

  6. 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);		
	
	}
}

窗口看门狗 

  1. void WWDG_DeInit(void);:这个函数用于初始化看门狗定时器,将其设置为默认状态。

  2. void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);:这个函数用于设置看门狗定时器的预分频值。预分频值决定了看门狗定时器的时间基准,通常设置为系统时钟的某个倍数。

  3. void WWDG_SetWindowValue(uint8_t WindowValue);:这个函数用于设置看门狗定时器的窗口值。窗口值决定了看门狗定时器在超时后是否复位。

  4. void WWDG_EnableIT(void);:这个函数用于使能看门狗定时器的中断。当看门狗定时器超时时,会触发一个中断。

  5. void WWDG_SetCounter(uint8_t Counter);:这个函数用于设置看门狗定时器的计数器值。计数器值决定了看门狗定时器在超时前需要等待多少个时钟周期。

  6. void WWDG_Enable(uint8_t Counter);:这个函数用于启动看门狗定时器。它会将看门狗定时器设置为启用状态,并开始计时。

  7. FlagStatus WWDG_GetFlagStatus(void);:这个函数用于获取看门狗定时器的状态标志。它返回一个标志位,表示看门狗定时器是否已经复位。

  8. 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关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值