基于STM32外部中断系统的秒表的设计

1.项目简介

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

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

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

2.硬件简介

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

硬件接线图

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

3.软件简介

3.1软件总体代码

#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函数代码

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

3.2按键外部中断

//外部中断初始化函数
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消抖即可,但会降低计时精度。

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

3.3定时器中断

定时器初始化代码如下

	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;
		}
}

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

3.4main函数

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函数中只有显示这一功能,注意显示区域,不要重叠即可。

 4.总结

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

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

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

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

qq:2296449414

好的,关于STM32秒表计时器,你可以使用定时器来实现。具体步骤如下: 1. 配置定时器时钟源和分频系数,使得定时器时钟频率为1MHz(或者其他你需要的频率)。 2. 配置定时器的计数模式为向上计数,并设置自动重载值为999(或者其他你需要的值),这样定时器每计数到999时就会自动清零并产生一个中断。 3. 在中断服务函数中,记录计数器的值,并清除中断标志位。 下面是一个简单的示例代码: ```c #include "stm32f10x.h" volatile uint32_t counter = 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { counter++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } int main(void) { // 初始化时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置GPIO GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置定时器 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 999; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 配置中断 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 启动定时器 TIM_Cmd(TIM2, ENABLE); while (1) { // 在这里可以根据计数器的值来更新显示 if (counter >= 1000) { GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); } else { GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); } } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值