该项目基于正点原子精英板的模板更改配置再自行编写文件得来
适配C8T6的一个文章是STM32F103ZET6代码移植到F103C8T6上的方法及注意事项-CSDN博客
尤其注意Systeminit();以及对应的三行取消注释,没有这个初始化计延时会不准
主要功能是
1.传统长按、短按长条形小夜灯的所有功能,但是还没找到合适的触摸按键或侧贴按钮
2.使用NEC协议来遥控灯的亮度(按键数字0到9),后续会加上定时
3.使用USB转TTL连接并使用上位机XCOM可以得到按键状态(短按、长按、按下次数)、当前设备时间并可以发送类似于20231113060900 年月日时分秒14位数据来修正设备时间,设备修正后每5s返回一次时间
4.可以使用按键来进行设置,设备的早晨光唤醒时间初始化设置为6.00,可以连按四下进入光唤醒时间设置模式,进入后红色LED灯会亮3s左右后熄灭,之后每次连按3次,(可以通过控制的灯条以及红色LED灯来判断按下的次数以及是否成功,可能是按键消抖没做好有时候会少计算一次如:连按四下实际上记录的是三次)光唤醒设定的时间就延迟10min(还没做提前的),并且每次加10min(连续按下三次)红色LED灯会对应亮灭,连按五下按下设置好时间后会自动写入Flash并退出编辑模式,之后哪怕断电也可以记住设置的时间(目前仅支持设置一个时间因为除了早上其余时间光唤醒似乎没用)
5.光唤醒亮后按一下按键就可以关掉,两个按键目前只做了一个按键的功能,两一个后续做一做或者哪位大佬改改开了也行
代码模板可以套用正点原子的红外遥控模板用来接收红外信号,代码写的很丑陋只是实现了功能,还没优化。
网盘链接宿舍小夜.zip - 蓝奏云 (lanzouj.com)https://wwwq.lanzouj.com/i1Nmz1epo67i
// mian.c文件
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "pwm.h"
#include "remote.h"
#include "rtc.h"
#include "stmflash.h"
// PB9 红外输入 PB0 PB5 LED灯控制 PA1 PA2中断按键 PA10 PA9 串口输入 PC13 LED灯
int main(void)
{
u16 t, len, times=0; //串口接收
u8 key_event=1,key_preevent,key_state=0,keycnt=0,remote; //按键、红外信号存储
u8 add=0,change_add=0;//加减亮度切换
u16 led0pwmval=450,autopwmval=900; //两种亮度控制
u8 open_hour=6,open_min=0,open_sec=0,open_once=1,change_opentime=1;//灯开启的小时分钟 秒 是否开启 是否更换开始时间
u8 changeled=1, change_longled=0,to_change=0;
u16 flashval = 600;
SystemInit();
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
Key_Init();
RTC_Init();
Remote_Init();
TIM3_PWM_Init(899,0); //不分频。PWM频率=72000000/900=80Khz
flashval=Flash_Read();
delay_ms(500);
if(flashval >= 500){
//printf("mainflashval:%d \r\n",flashval);
open_hour = flashval/100;
open_min = flashval%100;
printf("小时%d:分%d",open_hour,open_min);
}
//flash读取光亮的时间
while(1)
{
remote=Remote_Scan();
key_state=Get_State();
keycnt=Get_Cnt();
if(key_state==1){
key_event=Get_Event();
if(key_event==1){
switch(add){
case 0: led0pwmval+=5;
if(led0pwmval > 903 &&led0pwmval < 3000 ){ led0pwmval = 903; }
LED_TIM_SetCompare2(change_longled,led0pwmval); //第一个参数时选择亮的灯,第二个参数是亮度
break;
case 1: led0pwmval-=5;
if(led0pwmval <2 || led0pwmval > 4000 ) led0pwmval = 2;
LED_TIM_SetCompare2(change_longled,led0pwmval); //第一个参数时选择亮的灯,第二个参数是亮度
break;
}
delay_ms(10);
change_add=1;
}
}
else if(key_state==0){
if(key_event==1){ key_event=0; if(change_add==1) add=add==0?1:0; } //如果是长按,切换亮度增减
else if( key_event==2){ //单独按下一次则切换灯,同时可以作为更改闹光时间的一个依据
key_event=0;
change_longled++;
change_longled=change_longled==4?0:change_longled;
LED_TIM_SetCompare2(change_longled,led0pwmval); //第一个参数时选择亮的灯,第二个参数是亮度
printf("open_hour%d:open_min%d",open_hour,open_min);
}
}
switch(remote) //红外接收
{
case 104: TIM_SetCompare2(TIM3,0); TIM_SetCompare3(TIM3,0); LED0=0; break;
case 152: TIM_SetCompare2(TIM3,100); TIM_SetCompare3(TIM3,100); LED0=0; break;
case 176: TIM_SetCompare2(TIM3,200); TIM_SetCompare3(TIM3,200); LED0=0; break;
case 48: TIM_SetCompare2(TIM3,300); TIM_SetCompare3(TIM3,300); LED0=0; break;
case 24: TIM_SetCompare2(TIM3,400); TIM_SetCompare3(TIM3,400); LED0=0; break;
case 122: TIM_SetCompare2(TIM3,500); TIM_SetCompare3(TIM3,500); LED0=0; break;
case 16: TIM_SetCompare2(TIM3,600); TIM_SetCompare3(TIM3,600); LED0=0; break;
case 56: TIM_SetCompare2(TIM3,700); TIM_SetCompare3(TIM3,700); LED0=0; break;
case 90: TIM_SetCompare2(TIM3,800); TIM_SetCompare3(TIM3,800); LED0=0; break;
case 66: TIM_SetCompare2(TIM3,900); TIM_SetCompare3(TIM3,900); LED0=0; break;
}
if(USART_RX_STA&0x8000)
{
oledcom=1;
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
flashTim=1;
USART_RX_STA=0;
Set_NewTime(USART_RX_BUF, len);//接收到串口信息修正时间
}else{
times++;
if(times%500==0) printf("时间为%d %d %d %d %d %d\r\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec);
delay_ms(10);
}
if(rtc_clock.hour==open_hour && rtc_clock.min == open_min && (rtc_clock.sec >=0 && rtc_clock.sec <= 2)) open_once=1; //到时间了可以开灯
if(rtc_clock.hour==open_hour&&open_once==1){
if(rtc_clock.min >= open_min && rtc_clock.min <= (open_min+1) ){
if(open_once == 1){
TIM_SetCompare2(TIM3,0);
TIM_SetCompare3(TIM3,0);
}
if(key_state==1) key_event=Get_Event();//触发按键关灯
if(key_event==2||key_event==3){
key_event=0;
open_once=0;
TIM_SetCompare2(TIM3,905);
TIM_SetCompare3(TIM3,905);
}
if(rtc_clock.min==(open_min+1)&&(rtc_clock.sec>=0&&rtc_clock.sec<=5)){ //过时间了可以关灯
TIM_SetCompare2(TIM3,905);
TIM_SetCompare3(TIM3,905);
open_once=0;
}
}
}
if(keycnt==4 && change_opentime==1){
printf("开始修改时间");
oledcom=1;
change_opentime=0; //不再进入这个判断而是进入修改时间判断
keycnt=Get_Cnt();
}
else if(change_opentime==0){
if(keycnt==3){ //按下三次时间加十分钟灯切换亮暗
oledcom=oledcom==2?3:2;
open_min+=10;
if(open_min>=60){ open_min=0; open_hour++; }
}
if(keycnt==5){ //确认更改时间并退出3
printf("当前狗叫时间为%d时%d分",open_hour,open_min);
oledcom=1;//按下五次开灯6s后关闭
change_opentime=1;//按下五次确认时间并关闭,进入按下四次判断是否修改时间
Flash_Write(open_hour,open_min);//写入数据
printf("写入数据\r\n");
delay_ms(100);
}
}
if(keycnt==7){
printf("已经数据\r\n");
Flash_Read();//读取数据
delay_ms(100);
}
LED_Command(oledcom,times);
}
}
//#define KEY_NULL 0 //无事件
//#define KEY_LONG 1 //长按事件
//#define KEY_SHORT 2 //短按事件
//#define KEY_DOUBLE 3 //连按事件
//led.c文件
#include "led.h"
u8 oledcom=0;
//初始化PB5和PE5为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能PB,PE端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOC,GPIO_Pin_13); //PB.5 输出高
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 端口配置, 推挽输出
// GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
// GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高
}
void LED_Command(u8 com,u8 times){
if(com==1) LED0=0;
if(times%600 == 0 && LED0 == 0 && com==1){oledcom=0; LED0=1; }
if(com==2) LED0=0;
if(com==3) LED0=1;
}
//led.h文件
#ifndef __LED_H
#define __LED_H
#include "sys.h"
#define LED0 PCout(13)// PB5
//#define LED1 PEout(5)// PE5
extern u8 oledcom;
void LED_Init(void);//初始化
#endif
//key.c文件
#include "stm32f10x.h"
#include "key.h"
#include "sys.h"
#include "delay.h"
#include "timer.h"
#include "usart.h"
struct KEY
{
u8 key_prevent; //前一次按键事件
u8 key_event; //当前按键事件
u8 key_state; //按键状态 按下或松开
u8 key_cnt; //按键按下的次数
u8 key_continue; //按键按下的时间
u8 key_idle; //按键松手的时间
u8 key_longflag;
u8 key_flag; //按键状态发生改变的标志
u8 key_event_flag; //产生一次按键事件的标志
u8 key_resault;
}key={KEY_NULL,KEY_NULL,KEY_NONE,0,0,0,1,0,0,0};//结构体初始化
u8 once=0;
void Key_Init(void)
{
Exit_GPIO_Config();
TIM2_Config();
NVIC_Exit_GPIO_Config();
NVIC_TIM2_Config();
TIM_Cmd(TIM2, ENABLE); //使能时钟
}
void EXTI1_IRQHandler()
{
delay_ms(20); //按键消抖
if(KEY_PIN==0) //KEY_PIN==0代表按键按下
{
key.key_flag=1; //代表按键状态发生改变的标志
key.key_resault=1; //可以在松开按键部分得出一次结果
key.key_state=KEY_DOWN; //按键状态
key.key_continue=0; //按键按下的时间清零
}else //else的情况就是按键松手
{
key.key_longflag=1;
key.key_flag=1;
key.key_state=KEY_UP;
key.key_idle=0; // 按键松手的时间清零
}
EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位
}
void EXTI2_IRQHandler()
{
delay_ms(20); //按键消抖
if(KEY2_PIN==0) //KEY_PIN==0代表按键按下
{
key.key_flag=1; //代表按键状态发生改变的标志
key.key_resault=1; //可以在松开按键部分得出一次结果
key.key_state=KEY_DOWN; //按键状态
key.key_continue=0; //按键按下的时间清零
}else //else的情况就是按键松手
{
key.key_longflag=1;
key.key_flag=1;
key.key_state=KEY_UP;
key.key_idle=0; // 按键松手的时间清零
}
EXTI_ClearITPendingBit(EXTI_Line2); // 清除中断标志位
}
void TIM2_IRQHandler(void) //定时器中断每隔10ms进入一次中断
{
Key_Process(); //每隔10ms调用一次
TIM_ClearITPendingBit(TIM2 , TIM_IT_Update);//清除中断标志位
}
u8 Get_Event(){ //其他函数调用按键事件
return key.key_event;
}
u8 Get_State(){ //其他函数调用按键事件
return key.key_state;
once=0;
}
u8 Get_Cnt(){
u8 tmp;
if(key.key_cnt>=2 && key.key_idle >= KEY_IDLE && key.key_event == KEY_DOUBLE){
tmp=key.key_cnt;
key.key_cnt=1;
return tmp;
}
return 0;
}
void Key_Process(void)
{
switch(key.key_state)
{
case KEY_DOWN://按键按下进入
{
if(key.key_continue<KEY_CONTINUE){key.key_continue++;}
//每隔10ms,key.key_continue++,if判断防止它一直增加
//当按下的时间超出了设定的值后,说明此次为长按
if(key.key_continue>=KEY_CONTINUE)
{
if(key.key_cnt>1)//这个if判断是防止连按之后在长按会被判定为一次长按
{
printf("%s","1DOUBLE");//这个if判断把连按之后的长按判断为连按
printf("Cnt:%d\r\n",key.key_cnt);//的一部分
key.key_event=KEY_DOUBLE;
key.key_prevent=key.key_event;
once=1;
}
else if(key.key_cnt<=1 && key.key_longflag==1)//正常的长按
{
key.key_event=KEY_LONG; //当按下的时间超出了设定的值后判断为长按
key.key_longflag = 0; //产生了一次按键事件
key.key_prevent=key.key_event;
printf("%s","2LONG "); //打印到OLED屏上观察
once=1;
}
}
if(key.key_flag)//按键按下就会进入这个if判断
{
key.key_flag=0;//清除按键状态发生改变的标志位
if(key.key_idle<KEY_IDLE){key.key_cnt++;}
//判断松手的时间是否超时,没超时就说明是连按,按键按下次数加一
else{key.key_cnt=1;}//否则已超时,此次只能为单机或长按
//单击和连击的判断放到下面松手的部分通过按键次数来判断
key.key_event=KEY_SHORT;
}
break;
}
//按键松开
case KEY_UP:
{
//清楚标志位,其实这一部分没有这个也一样
if(key.key_idle<KEY_IDLE){key.key_idle++;}//松手时间小于设定值就增加
if(key.key_idle>=KEY_IDLE&&key.key_flag==1)//大于松手时间,代表按键事件产生
{
key.key_flag=0;
once=0;
//这个if判断防止长按被识别为单击,应为这一部分是靠按下次数来判断
//单击或长按,而单击和长按按下次数都为1,但长按的按键事件一定会判定为
//长按,通过key.key_event判断当前是长按还是单击
if(key.key_cnt==1 && key.key_event!=KEY_LONG)
{
key.key_event=KEY_SHORT;
key.key_prevent=key.key_event;
printf("%s","3SHORT ");
printf("Cnt:%d\r\n",key.key_cnt);
once=1;
}
else if(key.key_cnt>1)//按下次数为>1次的情况
{
if(key.key_prevent==KEY_LONG)
//这个if判断防止前一次的长按被当作连按的一部分
key.key_cnt--;//减去长按的一次计数
if(key.key_cnt==1)//一次说明为单击
{
key.key_event=KEY_SHORT;
key.key_prevent=key.key_event;
printf("%s","4SHORT ");
printf("Cnt:%d\r\n",key.key_cnt);
once=1;
}
else//大于1次为连击
{
key.key_event=KEY_DOUBLE;
printf("%s","5DOUBLE");
printf("Cnt:%d\r\n",key.key_cnt);
once=1;
}
}
}
//key.key_state=KEY_NONE;
}
}
}
//key.h文件
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY_NULL 0 //无事件
#define KEY_LONG 1 //长按事件
#define KEY_SHORT 2 //短按事件
#define KEY_DOUBLE 3 //连按事件
#define KEY_DOWN 1 //按键按下状态
#define KEY_UP 0 //按键松手状态
#define KEY_NONE 2 //按键初始状态
#define KEY_CONTINUE 50 //按下的最长时间
#define KEY_IDLE 40 //松手最长时间
#define KEY_PIN PAin(1)//PB10用作按键输入
#define KEY2_PIN PAin(2)//PB11用作按键输入
extern u8 once;
extern u8 resu;
void Key_Init(void);
//int Key_Scan(void);
void Key_Process(void);
#endif
//pwm.c文件
#include "pwm.h"
#include "led.h"
#include "usart.h"
#include "rtc.h"
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000为500ms
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
}
//定时器3中断服务程序
//void TIM3_IRQHandler(void) //TIM3中断
//{
// if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
// {
// TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源
// LED0=!LED0;
// }
//}
//
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_0; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM3 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC3Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
void Open_LED(autopwmval){
if(rtc_clock.hour>=17){
if(rtc_clock.min>=12){
TIM_SetCompare2(TIM3,autopwmval);
}
}
}
void LED_TIM_SetCompare2(u8 c_led,u16 val){
switch(c_led){
printf("c_led:%d",c_led);
case 0:
TIM_SetCompare2(TIM3,900);
TIM_SetCompare3(TIM3,900);
break;
case 1:
TIM_SetCompare2(TIM3,val);
TIM_SetCompare3(TIM3,900);
break;
case 2:
TIM_SetCompare2(TIM3,900);
TIM_SetCompare3(TIM3,val);
break;
case 3:
TIM_SetCompare2(TIM3,val);
TIM_SetCompare3(TIM3,val);
break;
}
}
//pwm.h文件
#ifndef __PWM_H
#define __PWM_H
#include "sys.h"
void TIM3_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_Init(u16 arr,u16 psc);
#endif
//timer.c文件
#include "timer.h"
#include "usart.h"
#include "key.h"
void Exit_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //配置为上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
EXTI_DeInit();
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_Init(&EXTI_InitStructure);
//上升下降沿中断,这样按下或松手就都能触发中断
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_Init(&EXTI_InitStructure);
}
void TIM2_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Prescaler= 71;//预分频
TIM_TimeBaseStructure.TIM_Period=9999; //相当于每10ms进入一次中断
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,向上计数
// 初始化定时器
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE); //使能TIM重载寄存器ARR
TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 清除计数器中断标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 开启计数器中断
TIM_Cmd(TIM2, DISABLE); // 关闭定时器的时钟,等待使用
}
//配置中断
void NVIC_Exit_GPIO_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void NVIC_TIM2_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//timer.h文件
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
#endif
//rtc.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h"
#include "stm32f10x.h"
#include <stdbool.h>
struct RTC_CLOCK rtc_clock; //定义RTC标准结构体
u8 New_RTC_Index =0; //更改时间
u8 flashTim=0; //是否赋值时间
u16 New_RTC[15]; //获取的时间
u16 t;
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(2023,9,19,21,00,00); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return 0; //ok
}
//RTC时钟中断
//每秒触发一次
//extern u16 tcnt;
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_Get(); //更新时间
printf("Alarm Time:%d-%d-%d %d:%d:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec);//输出闹铃时间
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断
RTC_WaitForLastTask();
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0) //必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
RTC_SetCounter(seccount); //设置RTC计数器的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
//初始化闹钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒
//返回值:0,成功;其他:错误代码.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec;//最后的秒钟加上去
//设置时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//上面三步是必须的!
RTC_SetAlarm(seccount);
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}
//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp)//超过一天了
{
daycnt=temp;
temp1=1970; //从1970年开始 这是一个默认规则
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)temp-=366;//闰年的秒钟数
else {temp1++;break;}
}
else temp-=365; //平年
temp1++;
}
rtc_clock.year=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(rtc_clock.year)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
rtc_clock.mon=temp1+1; //得到月份
rtc_clock.day=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
rtc_clock.hour=temp/3600; //小时
rtc_clock.min=(temp%3600)/60; //分钟
rtc_clock.sec=(temp%3600)%60; //秒钟
rtc_clock.week=RTC_Get_Week(rtc_clock.year,rtc_clock.mon,rtc_clock.day);//获取星期
return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100; yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}
void Set_NewTime(u8* USART_RX,u16 len)
{
for(t=0;t<len;t++)
{
switch(USART_RX[t]){
case '0': New_RTC[t]=0; break;
case '1': New_RTC[t]=1; break;
case '2': New_RTC[t]=2; break;
case '3': New_RTC[t]=3; break;
case '4': New_RTC[t]=4; break;
case '5': New_RTC[t]=5; break;
case '6': New_RTC[t]=6; break;
case '7': New_RTC[t]=7; break;
case '8': New_RTC[t]=8; break;
case '9': New_RTC[t]=9; break;
}
}
if(flashTim==1){
if( New_RTC[0]<4000){
New_RTC[0]=New_RTC[0]*1000+New_RTC[1]*100+New_RTC[2]*10+New_RTC[3];
New_RTC[1]=New_RTC[4]*10+New_RTC[5];
New_RTC[2]=New_RTC[6]*10+New_RTC[7];
New_RTC[3]=New_RTC[8]*10+New_RTC[9];
New_RTC[4]=New_RTC[10]*10+New_RTC[11];
New_RTC[5]=New_RTC[12]*10+New_RTC[13];
RTC_Set(New_RTC[0],New_RTC[1],New_RTC[2],New_RTC[3],New_RTC[4],New_RTC[5]);
flashTim=0;
}
}
}
//rtc.h文件
#ifndef __RTC_H
#define __RTC_H
//ALIENTEK 精英STM32开发板
//RTC实时时钟 驱动代码
//正点原子@ALIENTEK
//2010/6/6
//时间结构体
struct RTC_CLOCK
{
u32 year;
u32 mon;
u32 day;
u32 hour;
u32 min;
u32 sec;
u32 week;
};
extern struct RTC_CLOCK rtc_clock;
extern u8 Open_Auto_Air;
extern u8 flashTim;
extern u8 const mon_table[12]; //月份日期数据表
void Disp_Time(u8 x,u8 y,u8 size);//在制定位置开始显示时间
void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
void NewTime(u8 Value);
u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;
u8 Is_Leap_Year(u16 year);//平年,闰年判断
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
#endif
//remote.c
#include "remote.h"
#include "delay.h"
#include "usart.h"
//红外遥控初始化
//设置IO以及定时器4的输入捕获
void Remote_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); //TIM4 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PB9 输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_9); //初始化GPIOB.9
TIM_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大10ms溢出
TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //预分频器,1M的计数频率,1us加1.
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; // 选择输入端 IC4映射到TI4上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波
TIM_ICInit(TIM4, &TIM_ICInitStructure);//初始化定时器输入捕获通道
TIM_Cmd(TIM4,ENABLE ); //使能定时器4
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig( TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE);//允许更新中断 ,允许CC4IE捕获中断
}
//遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
u8 RmtSta=0;
u16 Dval; //下降沿时计数器的值
u32 RmtRec=0; //红外接收到的数据
u8 RmtCnt=0; //按键按下的次数
//定时器4中断服务程序
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
{
if(RmtSta&0x80) //上次有数据被接收到了
{
RmtSta&=~0X10; //取消上升沿已经被捕获标记
if((RmtSta&0X0F)==0X00)RmtSta|=1<<6; //标记已经完成一次按键的键值信息采集
if((RmtSta&0X0F)<14)RmtSta++;
else
{
RmtSta&=~(1<<7); //清空引导标识
RmtSta&=0XF0; //清空计数器
}
}
}
if(TIM_GetITStatus(TIM4,TIM_IT_CC4)!=RESET)
{
if(RDATA)//上升沿捕获
{
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling); //CC4P=1 设置为下降沿捕获
TIM_SetCounter(TIM4,0); //清空定时器值
RmtSta|=0X10; //标记上升沿已经被捕获
}else //下降沿捕获
{
Dval=TIM_GetCapture4(TIM4); //读取CCR4也可以清CC4IF标志位
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising); //CC4P=0 设置为上升沿捕获
if(RmtSta&0X10) //完成一次高电平捕获
{
if(RmtSta&0X80)//接收到了引导码
{
if(Dval>300&&Dval<800) //560为标准值,560us
{
RmtRec<<=1; //左移一位.
RmtRec|=0; //接收到0
}else if(Dval>1400&&Dval<1800) //1680为标准值,1680us
{
RmtRec<<=1; //左移一位.
RmtRec|=1; //接收到1
}else if(Dval>2200&&Dval<2600) //得到按键键值增加的信息 2500为标准值2.5ms
{
RmtCnt++; //按键次数增加1次
RmtSta&=0XF0; //清空计时器
}
}else if(Dval>4200&&Dval<4700) //4500为标准值4.5ms
{
RmtSta|=1<<7; //标记成功接收到了引导码
RmtCnt=0; //清除按键次数计数器
}
}
RmtSta&=~(1<<4);
}
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC4);
}
//处理红外键盘
//返回值:
// 0,没有任何按键按下
//其他,按下的按键键值.
u8 Remote_Scan(void)
{
u8 sta=0;
u8 t1,t2;
if(RmtSta&(1<<6))//得到一个按键的所有信息了
{
t1=RmtRec>>24; //得到地址码
t2=(RmtRec>>16)&0xff; //得到地址反码
if((t1==(u8)~t2)&&t1==REMOTE_ID)//检验遥控识别码(ID)及地址
{
t1=RmtRec>>8;
t2=RmtRec;
if(t1==(u8)~t2)sta=t1;//键值正确
}
if((sta==0)||((RmtSta&0X80)==0))//按键数据错误/遥控已经没有按下了
{
RmtSta&=~(1<<6);//清除接收到有效按键标识
RmtCnt=0; //清除按键次数计数器
}
}
return sta;
}
//remote.h
#ifndef __RED_H
#define __RED_H
#include "sys.h"
#define RDATA PBin(9) //红外数据输入脚
//红外遥控识别码(ID),每款遥控器的该值基本都不一样,但也有一样的.
//我们选用的遥控器识别码为0
#define REMOTE_ID 0
extern u8 RmtCnt; //按键按下的次数
void Remote_Init(void); //红外传感器接收头引脚初始化
u8 Remote_Scan(void);
#endif
//stmflash.c
#include "stmflash.h"
#include "delay.h"
#include "usart.h"
#include "sys.h"
u16 A_Parameter[10],B_Parameter[10],Flash_Parameter[10]; //Flash相关数组
// 读取的数据 写入的数据
//解锁STM32的FLASH
void STMFLASH_Unlock(void)
{
FLASH->KEYR=FLASH_KEY1;//写入解锁序列.
FLASH->KEYR=FLASH_KEY2;
}
//flash上锁
void STMFLASH_Lock(void)
{
FLASH->CR|=1<<7;//上锁
}
//得到FLASH状态
u8 STMFLASH_GetStatus(void)
{
u32 res;
res=FLASH->SR;
if(res&(1<<0))return 1; //忙
else if(res&(1<<2))return 2; //编程错误
else if(res&(1<<4))return 3; //写保护错误
return 0; //操作完成
}
//等待操作完成
//time:要延时的长短
//返回值:状态.
u8 STMFLASH_WaitDone(u16 time)
{
u8 res;
do
{
res=STMFLASH_GetStatus();
if(res!=1)break;//非忙,无需等待了,直接退出.
delay_us(1);
time--;
}while(time);
if(time==0)res=0xff;//TIMEOUT
return res;
}
//擦除页
//paddr:页地址
//返回值:执行情况
u8 STMFLASH_ErasePage(u32 paddr)
{
u8 res=0;
res=STMFLASH_WaitDone(0X5FFF);//等待上次操作结束,>20ms
if(res==0)
{
FLASH->CR|=1<<1;//页擦除
FLASH->AR=paddr;//设置页地址
FLASH->CR|=1<<6;//开始擦除
res=STMFLASH_WaitDone(0X5FFF);//等待操作结束,>20ms
if(res!=1)//非忙
{
FLASH->CR&=~(1<<1);//清除页擦除标志.
}
}
return res;
}
//读取指定地址的半字(16位数据)
//faddr:读地址(此地址必须为2的倍数!!)
//返回值:对应数据.
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr;
}
#if STM32_FLASH_WREN //如果使能了写
//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0;i<NumToWrite;i++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
WriteAddr+=2;//地址增加2.
}
}
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else
#define STM_SECTOR_SIZE 2048
#endif
u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; //扇区地址
u16 secoff; //扇区内偏移地址(16位字计算)
u16 secremain; //扇区内剩余地址(16位字计算)
u16 i;
u32 offaddr; //去掉0X08000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
FLASH_Unlock(); //解锁
offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址.
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)
secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
for(i=0;i<secremain;i++)//校验数据
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
for(i=0;i<secremain;i++)//复制
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.
if(NumToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++; //扇区地址增1
secoff=0; //偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumToWrite-=secremain; //字节(16位)数递减
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
else secremain=NumToWrite;//下一个扇区可以写完了
}
};
FLASH_Lock();//上锁
}
#endif
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
ReadAddr+=2;//偏移2个字节.
}
}
//
//WriteAddr:起始地址
//WriteData:要写入的数据
void Test_Write(u32 WriteAddr,u16 WriteData)
{
STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字
}
/**************************************************************************
函数功能:从Flash读取指定数据
入口参数:无
返回 值:无
**************************************************************************/
u16 Flash_Read(void)
{
STMFLASH_Read(FLASH_SAVE_ADDR,(u16*)A_Parameter,10);
//Flash未写入数据的时候,数据为65535
if(A_Parameter[0]==65535&&A_Parameter[1]==65535)
{
printf("FLASH暂时为空");
}
else//Flash内有数据,进行读取
{
printf("%d,%d\r\n",A_Parameter[0],A_Parameter[1]);
return A_Parameter[0]*100+A_Parameter[1];
}
}
/**************************************************************************
函数功能:向Flash写入数据
入口参数:无
返回 值:无
**************************************************************************/
void Flash_Write(u16 hour,u8 min)
{
if(hour>=5) B_Parameter[0]=hour;
B_Parameter[1]=min;
STMFLASH_Write(FLASH_SAVE_ADDR,(u16*)B_Parameter,4);
}
//stemflash.h
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "sys.h"
extern u16 A_Parameter[10],B_Parameter[10],Flash_Parameter[10]; //Flash相关数组
//用户根据自己的需要设置
#define STM32_FLASH_SIZE 64 //所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN 1 //使能FLASH写入(0,不是能;1,使能)
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
#define FLASH_SAVE_ADDR 0X0800E000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)
//FLASH解锁键值
#define FLASH_KEY1 0X45670123
#define FLASH_KEY2 0XCDEF89AB
void STMFLASH_Unlock(void); //FLASH解锁
void STMFLASH_Lock(void); //FLASH上锁
u8 STMFLASH_GetStatus(void); //获得状态
u8 STMFLASH_WaitDone(u16 time); //等待操作结束
u8 STMFLASH_ErasePage(u32 paddr); //擦除页
u8 STMFLASH_WriteHalfWord(u32 faddr, u16 dat); //写入半字
u16 STMFLASH_ReadHalfWord(u32 faddr); //读出半字
void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len); //指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len); //指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据
void Test_Write(u32 WriteAddr,u16 WriteData);//测试写入
u16 Flash_Read(void); //读取Flash数据
void Flash_Write(u16 hour,u8 min);//写入Flash数据
#endif