基于stm32的流水灯实现
一、开发环境介绍
主控芯片: 正点原子STM32F103ZET6精英开发板
oled:中景园七针脚0.96寸oled
代码编程软件: keil5
代码下载地址: stm32流水灯项目
希望大家仔细看一看是否符合自己的需求,实际上本文中写到的很多东西已经能为课设所用,理清逻辑后自己编程也很方便的,但如果想直接copy我的项目则需要购买板子、oled、项目文件(或许也可以直接proteus仿真,但本人未做尝试),总之请仔细斟酌再做打算。
二、项目要求
流水灯的设计:
芯片:LPC2000系列或者STM32,建议大家用STM32
基础部分:
(1)利用P0口的四个引脚控制四个发光二极管,第一个灯亮过2秒之后,延时2秒,第二个亮,以此类推,当第四个亮过之后就让四个二极管全亮,保持2秒,然后不断循环。
(2)利用Keil uVision4软件作为交叉编译环境。
(3)利用Proteus 8 Professional 软件作为程序的仿真测试。
提高部分:
(1)利用P0口的一个管脚作为一个按键信号输入,其作用是启动流水灯的开始和停止。(第一次按启动,第二次停止,第三次启动,以此类推)
(2)利用P0口的一个管脚作为一个按键信号输入,其作用是设置灯亮的时间,分三档1秒,2秒,3秒(第一次按1,第二次2秒,第三次3秒,第四次1秒,以此类推)
(3)基于uc/os-II操作系统,完成以上程序设计。
整个任务分为四个工作阶段:
1.安装开发环境,熟悉开发环境的基本操作。(开发环境的功能掌握)
2.查阅相关资料,编制程序代码。(程序的流程图)
3.调试相关程序,在调试过程中,修改相关程序。(调试过程中的问题)
4.对整个设计过程分析总结,提交设计报告。(总的设计报告)
作业包括:
第一个(1、2、3)操作报告(2页纸正反面);
第二个总的设计报告;
第三个程序打包(工程源文件打包)
第四个仿真程序(若有实物,直接视频更好,鼓励采用实物方式,但不是必须)
课程设计提交形式 :
电子版:
1、2、3操作报告(合计3页)、 总的设计报告、程序打包(工程源文件)、仿真程序(若有实物,直接视频更好,鼓励采用实物方式,但不是必须)、仿真程序或者实物实际运行讲解视频(这个必须有)、自己对自己报告完成情况评价,并讲明自己课设内容的优缺点(优、良、中、及格、不及格)。
报告格式:学号 姓名 班级
总的设计报告要求:
题目(加粗四号居中宋体)
班级 姓名 学号(加粗四号居中宋体)
1、流水灯的设计原理。(小四宋体)
2、程序流程图及代码分析。(小四宋体)
3、设计结果分析及改进之处。(小四宋体)
4、设计体会。(小四宋体)
三、实际实现效果
(1)、能够实现流水灯效果,但只有两个LED,如果想要华丽的效果可以尝试网上购买LED
(2)、通过控制定时器的打开与关闭,能够实现流水灯暂停/启动效果
(3)、通过外部中断程序设置定时器自动重装载初值,能够完成流水灯闪烁时间档位的切换(第一次按1,第二次2秒,第三次3秒,第四次1秒)
(4)、通过开启pvd掉电检测,能够实现简单的掉电数据保存,在检测到电压低于2.9v时,会进入掉电中断函数,将重要信息写入flash,并在重新上电后读出flash数据并初始化。
(5)、每次按下按键都会有蜂鸣器响,并且在oled上提示相关信息。
(6)、基于uc/os-II操作系统的移植没有做!!!!!!
四、核心代码
4.1 LED流水灯
该部分通过extern关键字引用了全局变量led_flag,并根据这个变量进行led展示
void LED_show()
{led_flag==6 ? led_flag=1:led_flag++;//六次一循环if(led_flag==1){LED0=0;LED1=1;}else if(led_flag==3){LED0=1;LED1=0;}else if(led_flag==5){LED0=0;LED1=0;}else{LED0=1;LED1=1;}
}
4.2 定时器
需要注意的是调用初始化定时器函数后还需要一个 TIM3->CR1|=0x01; 语句来使能定时器 3,这样才开始自动重装载计时。实际上,这句代码通常是在定时器初始化函数中的,但为了更好地实现其他功能,故而提取出来。
void TIM3_IRQHandler(void)
{ if(TIM3->SR&0X0001) //溢出中断{LED_show();} TIM3->SR&=~(1<<0); //清除中断标志位
}
//通用定时器 3 中断初始化
//这里时钟选择为 APB1 的 2 倍,而 APB1 为 36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器 3!
void TIM3_Int_Init(u16 arr,u16 psc)
{RCC->APB1ENR|=1<<1; //TIM3 时钟使能 TIM3->ARR=arr; //设定计数器自动重装值//刚好 1ms TIM3->PSC=psc; //预分频器 7200,得到 10Khz 的计数时钟 TIM3->DIER|=1<<0; //允许更新中断 //TIM3->CR1|=0x01; //使能定时器 3TIM3->EGR|=1<<0; //产生更新事件(这两句话是正点原子stm32相关bug,如不加,初始化后立刻进入中断)TIM3->SR=0; //清所有标志MY_NVIC_Init(2,3,TIM3_IRQn,2);//抢占 2,子优先级 3,组 2 }
}
4.3 pvd掉电检测并保存数据
开启pvd掉电检测,设置阈值为2.9v,当掉电后电压低于2.9v时触发中断,进行数据保存
void PWR_PVD_Init(void)
{ RCC->APB1ENR|=1<<28; //使能PORTB时钟PWR->CR |= 7<<5; //2.9V 电压阀值PWR->CR |= 1<<4; //PVD使能 EXTI->IMR|=1<<16;//EXTI->FTSR|=1<<16;//这里写错了 这样写进入不了中断,注意上升沿和下降沿产生的中断刚好是翻过来的,这里感觉是资料翻译错误的原因造成的//改为下面这样就正常了EXTI->RTSR|=1<<16;//配置这个寄存器才是断电瞬间保存数据EXTI->EMR |=1<<16;EXTI->SWIER |= 1<<16;MY_NVIC_Init(0,0,PVD_IRQn,2);//组2,最低优先级
}
void PVD_IRQHandler(void)
{EXTI->PR |=1<<16;//清中断 if (((PWR->CSR)&(1<<2))!=0)//当电压低于阈值{u16 msg[2];msg[0]=led_flag;msg[1]=time_gear;STMFLASH_Write(FLASH_SAVE_ADDR,msg,2);
// OLED_ShowString(8,16,"OK",16,1);//经过测试,掉电中断确实执行,但是可能数据写入速度不够 }
}
4.4 外部中断(换挡、暂停/启动、掉电保存模式)
//外部中断 0 服务程序
//函数功能,进入掉电模式
void EXTI0_IRQHandler(void)
{
delay_ms(10); //消抖
if(WK_UP==1) //WK_UP 按键
{BEEP=1;OLED_Clear();PWR_PVD_Init();OLED_ShowString(8,0,"save mode",16,1);BEEP=0;
}
EXTI->PR=1<<0; //清除 LINE0 上的中断标志位
}
//外部中断 3 服务程序
//函数功能,暂停,开启流水灯
void EXTI3_IRQHandler(void)
{delay_ms(10); //消抖if(KEY1==0) {BEEP=1;led_on=!led_on;if(led_on==1){TIM2->CR1 |= (0x01);TIM3->CR1 |= (0x01);OLED_Clear();OLED_ShowString(8,16,"Turn on",16,1); }else{TIM2->CR1 &= ~(0x01);//TIM3 时钟使能 TIM3->CR1 &= ~(0x01);OLED_Clear();OLED_ShowString(8,16,"Turn off",16,1);}}BEEP=0;EXTI->PR=1<<3; //清除 LINE3 上的中断标志位
}
//外部中断 4 服务程序
//函数功能:档位切换
void EXTI4_IRQHandler(void)
{delay_ms(10); //消抖if(KEY0==0) //按键 KEY0{BEEP=1;time_gear++;//档位切换time_gear%=3;OLED_Clear();OLED_ShowString(8,0,"Gear changed!",16,1);sprintf(OLED_ShowBuff,"Gear: %d",time_gear);OLED_ShowString(8,48,OLED_ShowBuff,16,1); TIM2->CR1&=~(0x01); TIM3->CR1&=~(0x01); //先关闭定时器3中断TIM2->SR&=~(1<<0);TIM3->SR&=~(1<<0); //清除中断标志位,从而可以快速切换if(time_gear>=1){//进行了档位切换,且由低档变为高档TIM2_Int_Init((time_init[time_gear]-TIM3->CNT),7199);//根据当前按下按键次数进行定时器的初始化 }else{if(TIM3->CNT>=9999){//3档由29999记到0,如果小于19999需要立马进入中断TIM2_Int_Init(50,7199);//给予定时器2一个很小的记数值,计时满立刻进行LED状态翻转}else {TIM2_Int_Init((time_init[time_gear]-TIM3->CNT),7199);//根据当前按下按键次数进行定时器的初始化}}if(led_on==1) {TIM2->CR1 |= (0x01);TIM3->CR1 |= (0x01);}else {TIM2->CR1&=~(0x01); TIM3->CR1&=~(0x01); } }BEEP=0;EXTI->PR=1<<4; //清除 LINE4 上的中断标志位
}
4.5 主函数
主函数首先进行相关外设初始化,而后读取flash数据,若读取正确数据则根据数据进行复位流水灯,否则进行零状态初始化。需要注意的是,读取flash数据后需要进行擦除,否则pvd掉电的短暂时间难以写入数据,从而造成保存数据失败。
int main(void)
{ u16 datatemp[2]={0,0};//接收上次掉电保存的数据u16 i=0;//用于flash数据擦除u16 secremain=1024-(FLASH_SAVE_ADDR%2048)/2;u32 offaddr=FLASH_SAVE_ADDR-STM32_FLASH_BASE;u32 secpos=offaddr/2048;Stm32_Clock_Init(9); //系统时钟设置uart_init(72,115200); //串口初始化为 115200delay_init(72); //延时初始化OLED_Init();//OLED初始化BEEP_Init();//蜂鸣器初始化EXTIX_Init();//外部中断初始化LED_Init(); //初始化与 LED 连接的硬件接口//相关信息展示OLED_ShowString(8,0,"init OK!",16,1);OLED_ShowString(8,16,"Turn on",16,1);STMFLASH_Read(FLASH_SAVE_ADDR,datatemp,2);//原来此处是SIZEif(datatemp[1]==0||datatemp[1]==1||datatemp[1]==2){led_flag=datatemp[0]-1; time_gear=datatemp[1];}else{//flash没有正确数据,直接初始化led_flag=0; time_gear=0;}sprintf(OLED_ShowBuff,"LED: %d",led_flag);OLED_ShowString(8,32,OLED_ShowBuff,16,1); sprintf(OLED_ShowBuff,"Gear: %d",time_gear);OLED_ShowString(8,48,OLED_ShowBuff,16,1); //擦除flash数据,否则掉电时间内难以写入STMFLASH_Unlock(); //解锁 if(i++<2)STMFLASH_ErasePage(secpos*2048+STM32_FLASH_BASE);//擦除这个扇区STMFLASH_Lock();//上锁//根据掉电储存信息进行复位LED_show();//LED展示TIM3_Int_Init(time_init[time_gear],7199); //定时器初始化TIM3->CR1|=0x01; //使能定时器 3 while(1);
}
五、效果演示
5.1 初始化
初始化时,会从flash读数据,如果读出正确数据,则会根据数据进行初始化,否则进行零状态初始化,并且在oled上显示相关信息。

5.2 暂停、启动
实现流水灯的暂停、启动功能,并且会在oled上显示Turn on/Turn off信息。

5.3 换挡
能够实现流水灯闪烁时间档位切换,当Gear=0时,流水灯变换时间为1s,Gear=2时,流水灯变换时间位2s。
5.4 数据保存模式
按下Key_up键,进入数据保存模式,当设备掉电后,会自动读取led的状态以及时间档位并且存储到flash,以供重新上电时复位。

5.5 保存后重新上电
进入保存模式后掉电再上电,会根据flash信息重新初始化,重新上电时,会从上次闪烁的LED开始亮,比如上次掉电时是两个灯一起亮,这次上电则是从两个灯一起亮开始;上次掉电时,闪烁频率位2s一次变换,则重新上电也是2s一次。若不进入保存模式,发生掉电再上电只会进入零状态初始化。

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

