赞
踩
硬件平台:基于某STM32F103开发板。
软件平台:KEIL5 MDK V5.38,基于LCD屏幕库函数例程库为模板,添加了外部中断,定时器中断相关代码,稍加修改而成。
功能简介:有三个功能按键,启动,暂停,归零;一块LCD屏幕上显示数据数据,理论精度0.01秒
按键模块硬件接线图如图所示。
硬件接线图
需要注意的是三个按键中,KEY0,KEY1一端接的是GND。我们在初始化时应设置上拉输入,即没有按下按下按键,IO口读取电压为高,当按键按下,IO口读取的电压会变低,可正确完成按键按下判断;WK_UP按键同理,应设置下拉输入。
- #include "delay.h"
- #include "usart.h"
- #include "timer.h"
- #include "exti.h"
- #include "lcd.h"
- #include "led.h"
- #include "sys.h"
- #include "key.h"
-
- volatile u8 m=0; //分钟
- volatile u8 s=0; //秒位
- volatile u8 ms=0;//毫秒位,1个ms代表10毫秒,如果显示毫秒,屏幕刷不过来
-
- int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
- LED_Init(); //LED初始化
- delay_init(); //延时函数初始化
- uart_init(115200);//LCD与主控通过串口通信进行初始化,不可省略
- LCD_Init(); //LCD初始化
- EXTIX_Init(); //外部中断初始化
- POINT_COLOR=RED; //设置颜色
- LCD_ShowString(65,25,12*9,24,24,"stopwatch");//显示标题
- LCD_ShowChar(60+3*12,80,'m',24,0);//字体大小设置为12像素宽,24像素长
- LCD_ShowChar(60+6*12,80,'s',24,0);
- TIM3_Int_Init(100,7199);//时钟周期72M/(7199+1)=10k,10Khz的计数频率,计数到计数到100,为10ms
- while(1)
- {
- LCD_ShowxNum(60+0*12,80,m,2,24,0);//显示
- LCD_ShowxNum(60+4*12,80,s,2,24,0);
- LCD_ShowxNum(60+7*12,80,ms,2,24,0);
- }
- return 0;
- }
初始化以及main函数代码
下面我们按照顺序依次讲解。
- //外部中断初始化函数
- void EXTIX_Init(void)
- {
- EXTI_InitTypeDef EXTI_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
-
- KEY_Init();//初始化按键对应io模式
-
- //GPIOC.5 中断线以及中断初始化配置
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
-
- EXTI_InitStructure.EXTI_Line=EXTI_Line5;
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
-
- //GPIOA.15 中断线以及中断初始化配置
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource15);
-
- EXTI_InitStructure.EXTI_Line=EXTI_Line15;
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
-
- //GPIOA.0 中断线以及中断初始化配置
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
-
- EXTI_InitStructure.EXTI_Line=EXTI_Line0;
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
-
- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键所在的外部中断通道
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级1
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
- NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
-
- NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能按键所在的外部中断通道
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
- NVIC_Init(&NVIC_InitStructure);
-
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键所在的外部中断通道
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级1
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
- NVIC_Init(&NVIC_InitStructure);
- }
我们设置的是按键触发是外部中断,所以先进行按键的初始化,在初始化时注意按键的硬件连接,如果按键另一端连接的3.3V,那就设置为下拉输入,按键连接的GND那就设置上拉输入。
对于外部中断,STM32的中断主控制支持19个外部中断请求:
线0~15:对应外部IO口的输入中断。
线16:连接到PVD输出。
线17:连接到RTC闹钟事件。
线18:连接到USB唤醒事件。
每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。
我们使用的是第一类中断。STM32的IO每组最多只有16个(如PA0~PA15),所以根据IO口序号选择对应的外部中断线即可。例如:我们的按键的硬件IO口别是分PA0,PA15,PC5,我就需要配置为
- PA0 -EXTI_Line0
-
- PA15-EXTI_Line15
-
- PC5 -EXTI_Line5
- (根据IO口序号选择即可)
(如果选择PA0与PB0来触发外部中断,将无法正常工作,因为他们共用一条中断线)
再配置触发方式(上升沿,下降沿,双边沿)
选择对应的中断向量,对应的选择如下图所示
同样根据IO口序号选择即可,如果是0~4号口,选择EXTI0~4即可,如果是5~9号口,那么只能选择EXTI9_5这个中断向量,10~15号口同理只能选择EXTI15_10。
(这就意味着如果同时选择PC9,PB7作为外部中断触发,那么他们将无法同时正常工作,因为他们位于同样的中断向量EXTI9_5)
- //io口对应外部中断关系如下
- io口 外部中断线 中断向量 中断服务函数
- PA0 -EXTI_Line0 -EXTI0_IRQn -EXTI0_IRQHandler
-
- PA15-EXTI_Line15-EXTI15_10_IRQn-EXTI15_10_IRQHandler
-
- PC5 -EXTI_Line5 -EXTI9_5_IRQn -EXTI9_5_IRQHandler
选好了中断向量,就在对应的中断服务函数中去按照要求编写代码即可。
外部中断服务函数代码如下。
- volatile u8 suspend_flag=0;//暂停标
- volatile u8 start_flag=0; //开始标
- volatile u8 zero_flag=0; //清零
-
- void EXTI0_IRQHandler(void)//开始
- {
- if(WK_UP==1)
- {
- //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
- start_flag=1;//开始
- zero_flag=0;
- suspend_flag=0;
- LED0=!LED0;
- LED1=!LED1;
- }
- EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位
- }
-
- void EXTI15_10_IRQHandler(void)//暂停
- {
- if(KEY1==0)
- {
- //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
- if(start_flag==1&&suspend_flag==0)//正常运行情况下,才暂停
- {
- suspend_flag=1;
- }
- LED0=!LED0;
- LED1=!LED1;
- }
- EXTI_ClearITPendingBit(EXTI_Line15);//清除LINE15线路挂起位
- }
-
- void EXTI9_5_IRQHandler(void)//清零
- {
- //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
- if(KEY0==0)
- {
- if(suspend_flag==1)//只暂停状态下清零
- {
- zero_flag=1; //清零
- }
- LED0=!LED0;
- LED1=!LED1;
- }
- EXTI_ClearITPendingBit(EXTI_Line5); //清除LINE5上的中断标志位
- }
在进入中断后可以选择打开10ms消抖,减少误判率,但是本设计是秒表,为了提高计时精准性,选择将它关闭,所以实际测试时,会有误判现象发生,如误判较多,开启10ms消抖即可,但会降低计时精度。
进入到每个按键的中断服务函数后,可以根据情况,选择将标志置位或者清除,在定时器中断函数中读取相关标志位并做出相应操作。
定时器初始化代码如下
TIM3_Int_Init(100,7199);//时钟周期72M/(7199+1)=10k,10Khz的计数频率,计数到计数到100,为10ms
- 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; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
- 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中断//TIM3//使能
- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
- 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外设
- }
定时器中断中需注意分频系数与装载值,这两个参数决定了计数频率,具体公式在注释中有写。
将定时器中断初始化好,就可在定时器中断服务函数中编写代码了。
(注意:一般情况下,定时器初始化都在最后配置,因为一旦配置完成,定时器自动执行中断,可能会影响其他外设的初始化,造成初始化失败)
定时器中断服务函数如下
- extern volatile u8 start_flag; //开始标
- extern volatile u8 suspend_flag;//暂停标
- extern volatile u8 zero_flag; //清零标
-
- extern volatile u8 m; //分钟
- extern volatile u8 s; //秒位
- extern volatile u8 ms;//毫秒位
-
- void TIM3_IRQHandler(void) //TIM3中断
- {
- if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
- {
- if(zero_flag==1)//数值清零
- {
- s=0;//清零
- m=0;
- ms=0;
- zero_flag=0;//清零后把自己置零,保证清一次,不然数值永远是0
- }
-
- if(start_flag==1)//只要在开始模式下
- {
- if(suspend_flag==1)
- {
- ;//暂停按下,数值不再自加
- }
- else//正常计时
- {
- ms++; //毫秒位
- if(ms>=100)
- {
- s++;//进位操作
- ms=0;
- }
- if(s>=60)
- {
- m++;//进位操作
- s=0;
- }
- }
- }
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源
- LED1=!LED1;
- }
- }
根据在定时器中断设置的几个标志位,来决定时间是自增,还是空跑。
- while(1)
- {
- LCD_ShowxNum(60+0*12,80,m,2,24,0);//显示
- LCD_ShowxNum(60+4*12,80,s,2,24,0);
- LCD_ShowxNum(60+7*12,80,ms,2,24,0);
- }
在main函数中只有显示这一功能,注意显示区域,不要重叠即可。
设计了一个简易秒表,通过按键外部中断来控制开始,暂停,清零等功能,计时功能通过10ms的定时器中断,60一进位,完成时分秒的显示,在main函数调用屏幕显示函数将将关数据显示出来。
计时精度其实不高,估计STM32的72M的晶振产生的10ms中断未必是真的10ms,或者屏幕显示占用了一定的时间,这些误差随着时间是积累,误差会越来越大,实测每一分钟大概会慢0.5秒左右,所以仅供学习参考,不建议实际使用。
欢迎支持,有问题欢迎交流讨论。
qq:2296449414
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。