当前位置:   article > 正文

基于STM32外部中断系统的秒表的设计_stm32定时器做秒时钟

stm32定时器做秒时钟

1.项目简介

硬件平台:基于某STM32F103开发板。

软件平台:KEIL5 MDK V5.38,基于LCD屏幕库函数例程库为模板,添加了外部中断,定时器中断相关代码,稍加修改而成。

功能简介:有三个功能按键,启动,暂停,归零;一块LCD屏幕上显示数据数据,理论精度0.01秒

2.硬件简介

按键模块硬件接线图如图所示。

硬件接线图

需要注意的是三个按键中,KEY0,KEY1一端接的是GND。我们在初始化时应设置上拉输入,即没有按下按下按键,IO口读取电压为高,当按键按下,IO口读取的电压会变低,可正确完成按键按下判断;WK_UP按键同理,应设置下拉输入。

3.软件简介

3.1软件总体代码

  1. #include "delay.h"
  2. #include "usart.h"
  3. #include "timer.h"
  4. #include "exti.h"
  5. #include "lcd.h"
  6. #include "led.h"
  7. #include "sys.h"
  8. #include "key.h"
  9. volatile u8 m=0; //分钟
  10. volatile u8 s=0; //秒位
  11. volatile u8 ms=0;//毫秒位,1个ms代表10毫秒,如果显示毫秒,屏幕刷不过来
  12. int main(void)
  13. {
  14. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
  15. LED_Init(); //LED初始化
  16. delay_init(); //延时函数初始化
  17. uart_init(115200);//LCD与主控通过串口通信进行初始化,不可省略
  18. LCD_Init(); //LCD初始化
  19. EXTIX_Init(); //外部中断初始化
  20. POINT_COLOR=RED; //设置颜色
  21. LCD_ShowString(65,25,12*9,24,24,"stopwatch");//显示标题
  22. LCD_ShowChar(60+3*12,80,'m',24,0);//字体大小设置为12像素宽,24像素长
  23. LCD_ShowChar(60+6*12,80,'s',24,0);
  24. TIM3_Int_Init(100,7199);//时钟周期72M/(7199+1)=10k,10Khz的计数频率,计数到计数到100,为10ms
  25. while(1)
  26. {
  27. LCD_ShowxNum(60+0*12,80,m,2,24,0);//显示
  28. LCD_ShowxNum(60+4*12,80,s,2,24,0);
  29. LCD_ShowxNum(60+7*12,80,ms,2,24,0);
  30. }
  31. return 0;
  32. }

初始化以及main函数代码

下面我们按照顺序依次讲解。

3.2按键外部中断

  1. //外部中断初始化函数
  2. void EXTIX_Init(void)
  3. {
  4. EXTI_InitTypeDef EXTI_InitStructure;
  5. NVIC_InitTypeDef NVIC_InitStructure;
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//外部中断,需要使能AFIO时钟
  7. KEY_Init();//初始化按键对应io模式
  8. //GPIOC.5 中断线以及中断初始化配置
  9. GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
  10. EXTI_InitStructure.EXTI_Line=EXTI_Line5;
  11. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  12. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
  13. EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  14. EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  15. //GPIOA.15 中断线以及中断初始化配置
  16. GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource15);
  17. EXTI_InitStructure.EXTI_Line=EXTI_Line15;
  18. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  19. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  20. EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  21. EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  22. //GPIOA.0 中断线以及中断初始化配置
  23. GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
  24. EXTI_InitStructure.EXTI_Line=EXTI_Line0;
  25. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  26. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  27. EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  28. EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  29. NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键所在的外部中断通道
  30. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
  31. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级1
  32. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  33. NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  34. NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; //使能按键所在的外部中断通道
  35. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
  36. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
  37. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  38. NVIC_Init(&NVIC_InitStructure);
  39. NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //使能按键所在的外部中断通道
  40. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
  41. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级1
  42. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  43. NVIC_Init(&NVIC_InitStructure);
  44. }

我们设置的是按键触发是外部中断,所以先进行按键的初始化,在初始化时注意按键的硬件连接,如果按键另一端连接的3.3V,那就设置为下拉输入,按键连接的GND那就设置上拉输入。

对于外部中断,STM32的中断主控制支持19个外部中断请求:

线0~15:对应外部IO口的输入中断。

线16:连接到PVD输出。

线17:连接到RTC闹钟事件。

线18:连接到USB唤醒事件。

每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。

我们使用的是第一类中断。STM32的IO每组最多只有16个(如PA0~PA15),所以根据IO口序号选择对应的外部中断线即可。例如:我们的按键的硬件IO口别是分PA0,PA15,PC5,我就需要配置为

  1. PA0 -EXTI_Line0
  2. PA15-EXTI_Line15
  3. PC5 -EXTI_Line5
  4. (根据IO口序号选择即可)

(如果选择PA0与PB0来触发外部中断,将无法正常工作,因为他们共用一条中断线)

再配置触发方式(上升沿,下降沿,双边沿)

选择对应的中断向量,对应的选择如下图所示

同样根据IO口序号选择即可,如果是0~4号口,选择EXTI0~4即可,如果是5~9号口,那么只能选择EXTI9_5这个中断向量,10~15号口同理只能选择EXTI15_10。

(这就意味着如果同时选择PC9,PB7作为外部中断触发,那么他们将无法同时正常工作,因为他们位于同样的中断向量EXTI9_5)

  1. //io口对应外部中断关系如下
  2. io口 外部中断线 中断向量 中断服务函数
  3. PA0 -EXTI_Line0 -EXTI0_IRQn -EXTI0_IRQHandler
  4. PA15-EXTI_Line15-EXTI15_10_IRQn-EXTI15_10_IRQHandler
  5. PC5 -EXTI_Line5 -EXTI9_5_IRQn -EXTI9_5_IRQHandler

选好了中断向量,就在对应的中断服务函数中去按照要求编写代码即可。

外部中断服务函数代码如下。

  1. volatile u8 suspend_flag=0;//暂停标
  2. volatile u8 start_flag=0; //开始标
  3. volatile u8 zero_flag=0; //清零
  4. void EXTI0_IRQHandler(void)//开始
  5. {
  6. if(WK_UP==1)
  7. {
  8. //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
  9. start_flag=1;//开始
  10. zero_flag=0;
  11. suspend_flag=0;
  12. LED0=!LED0;
  13. LED1=!LED1;
  14. }
  15. EXTI_ClearITPendingBit(EXTI_Line0); //清除EXTI0线路挂起位
  16. }
  17. void EXTI15_10_IRQHandler(void)//暂停
  18. {
  19. if(KEY1==0)
  20. {
  21. //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
  22. if(start_flag==1&&suspend_flag==0)//正常运行情况下,才暂停
  23. {
  24. suspend_flag=1;
  25. }
  26. LED0=!LED0;
  27. LED1=!LED1;
  28. }
  29. EXTI_ClearITPendingBit(EXTI_Line15);//清除LINE15线路挂起位
  30. }
  31. void EXTI9_5_IRQHandler(void)//清零
  32. {
  33. //delay_ms(10); //消抖,会提高按键识别率,但是会影响秒表精度,自行选择
  34. if(KEY0==0)
  35. {
  36. if(suspend_flag==1)//只暂停状态下清零
  37. {
  38. zero_flag=1; //清零
  39. }
  40. LED0=!LED0;
  41. LED1=!LED1;
  42. }
  43. EXTI_ClearITPendingBit(EXTI_Line5); //清除LINE5上的中断标志位
  44. }

在进入中断后可以选择打开10ms消抖,减少误判率,但是本设计是秒表,为了提高计时精准性,选择将它关闭,所以实际测试时,会有误判现象发生,如误判较多,开启10ms消抖即可,但会降低计时精度。

进入到每个按键的中断服务函数后,可以根据情况,选择将标志置位或者清除,在定时器中断函数中读取相关标志位并做出相应操作。

3.3定时器中断

定时器初始化代码如下

	TIM3_Int_Init(100,7199);//时钟周期72M/(7199+1)=10k,10Khz的计数频率,计数到计数到100,为10ms  
  1. void TIM3_Int_Init(u16 arr,u16 psc)
  2. {
  3. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  4. NVIC_InitTypeDef NVIC_InitStructure;
  5. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
  6. TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
  7. TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率
  8. TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
  9. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
  10. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  11. //使能或者失能指定的TIM中断//TIM3//使能
  12. TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
  13. NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
  14. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
  15. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
  16. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  17. NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  18. TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
  19. }

定时器中断中需注意分频系数与装载值,这两个参数决定了计数频率,具体公式在注释中有写。

将定时器中断初始化好,就可在定时器中断服务函数中编写代码了。

(注意:一般情况下,定时器初始化都在最后配置,因为一旦配置完成,定时器自动执行中断,可能会影响其他外设的初始化,造成初始化失败)

定时器中断服务函数如下

  1. extern volatile u8 start_flag; //开始标
  2. extern volatile u8 suspend_flag;//暂停标
  3. extern volatile u8 zero_flag; //清零标
  4. extern volatile u8 m; //分钟
  5. extern volatile u8 s; //秒位
  6. extern volatile u8 ms;//毫秒位
  7. void TIM3_IRQHandler(void) //TIM3中断
  8. {
  9. if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
  10. {
  11. if(zero_flag==1)//数值清零
  12. {
  13. s=0;//清零
  14. m=0;
  15. ms=0;
  16. zero_flag=0;//清零后把自己置零,保证清一次,不然数值永远是0
  17. }
  18. if(start_flag==1)//只要在开始模式下
  19. {
  20. if(suspend_flag==1)
  21. {
  22. ;//暂停按下,数值不再自加
  23. }
  24. else//正常计时
  25. {
  26. ms++; //毫秒位
  27. if(ms>=100)
  28. {
  29. s++;//进位操作
  30. ms=0;
  31. }
  32. if(s>=60)
  33. {
  34. m++;//进位操作
  35. s=0;
  36. }
  37. }
  38. }
  39. TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源
  40. LED1=!LED1;
  41. }
  42. }

根据在定时器中断设置的几个标志位,来决定时间是自增,还是空跑。

3.4main函数

  1. while(1)
  2. {
  3. LCD_ShowxNum(60+0*12,80,m,2,24,0);//显示
  4. LCD_ShowxNum(60+4*12,80,s,2,24,0);
  5. LCD_ShowxNum(60+7*12,80,ms,2,24,0);
  6. }

在main函数中只有显示这一功能,注意显示区域,不要重叠即可。

 4.总结

设计了一个简易秒表,通过按键外部中断来控制开始,暂停,清零等功能,计时功能通过10ms的定时器中断,60一进位,完成时分秒的显示,在main函数调用屏幕显示函数将将关数据显示出来。

计时精度其实不高,估计STM32的72M的晶振产生的10ms中断未必是真的10ms,或者屏幕显示占用了一定的时间,这些误差随着时间是积累,误差会越来越大,实测每一分钟大概会慢0.5秒左右,所以仅供学习参考,不建议实际使用。

开源代码如下:基于STM32的秒表项目: 基于STM32的秒表项目,有三个按键,启动,暂停,归零,时间数据在LCD屏幕上显示,显示到0.01秒https://gitee.com/xu-shuhao/stopwatch.git

欢迎支持,有问题欢迎交流讨论。

qq:2296449414

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/767920
推荐阅读
相关标签
  

闽ICP备14008679号