STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯

目录

项目要求

基础部分:

提高部分:

实现流程

硬件

软件

代码结构

跑马灯

按键中断

main函数



项目要求

先看看具体要求:

流水灯的设计:

基础部分:

利用GPIO的四个引脚控制四个发光二极管,第一个灯亮过2秒之后,延时2秒,第二个亮,以此类推,当第四个亮过之后就让四个二极管全亮,保持2秒,然后不断循环。

提高部分:

利用GPIO口的一个管脚作为一个按键信号输入,其作用是启动流水灯的开始和停止。(第一次按启动,第二次停止,第三次启动,以此类推)

①使用定时器TIM实现跑马灯

②使用外部中断来实现按键按下暂停

③在暂停的时候考虑原状态

实现流程

硬件

原理图很简单,拥有一块STM32F103C8T6最小系统板后,只需画一个简易的PCB来代替面包板即可,具体原理图如下:

STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯_第1张图片

最左边是STM32的核心板,根据买到的核心板进行符号和封装的绘制,封装只需根据具体大小画上轮廓以及焊盘,之后打出PCB后焊上排母就可以插上系统板了如下图:

STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯_第2张图片

原理图的中间部分就是5个LED灯,所有的LED是共阳的,一开始对LED的接法有些迷惑,认为LED导通后内阻极小,无论内阻接到左边还是右边,如果GPIO输出的是低电平,LED两端的电压都为0。但实际上不是这样的,实际上没导通时LED为断路,VCC的3.3V全部加在了LED两端,而导通后只需要电流在一定范围内即可。所以只需要计算限流电阻的大小,查看淘宝资料发现蓝色LED的工作电压为2.2-2.4V,我们假定为2.3V,需要达到10mA的电流,不难算出限流电阻大小为1K欧姆。

原理图的右侧是按键部分,按键有4个引脚,左右两边(1、2和3、4)分别接在了一起,按照上图接法,当按键没有按下时,PA6与VCC连接,为高电平;当按键按下时,四个引脚接在了一起,此时PA6为低电平,就可以分清按键的两种状态了。

画完原理图后绘制PCB:

STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯_第3张图片

实物图如下,焊上LED、按键、排母后插上最小系统板:

软件

代码结构

软件代码结构如下图,首先是几个GROUP,STARTUP存放启动文件,CMSIS放内核和系统文件,FWLIB存放固件库文件,USER中存放main文件和中断文件,HARDWARE中存放LED以及中断按键的预定义方法,SYSTEM中存放定时器以及系统的预定义方法。

STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯_第4张图片

跑马灯

首先是最基本的定时器跑马灯,定义LED板级支持包:

bsp_led.h:对所有led的端口进行定义,声明一个流水灯函数LED_RUN()。

#ifndef __LED_H
#define	__LED_H


#include "stm32f10x.h"


/* 定义LED连接的GPIO端口, 用户只需要修改下面的代码即可改变控制的LED引脚 */

#define LED_GPIO_CLK 	    RCC_APB2Periph_GPIOA		/* GPIO端口时钟 */

#define LED1_GPIO_PORT    	GPIOA		              //led1
#define LED1_GPIO_PIN		GPIO_Pin_4			        

#define LED2_GPIO_PORT    	GPIOA		             //led2
#define LED2_GPIO_PIN		GPIO_Pin_3			       

#define LED3_GPIO_PORT    	GPIOA		              //led3
#define LED3_GPIO_PIN		GPIO_Pin_2			        

#define LED4_GPIO_PORT    	GPIOA		            //led4
#define LED4_GPIO_PIN		GPIO_Pin_1			        

#define LED5_GPIO_PORT    	GPIOA		              //led5
#define LED5_GPIO_PIN		GPIO_Pin_0			        


void LED_GPIO_Config(void);
void LED_RUN(u8 index);

#endif /* __LED_H */

接着定义其c文件,bsp_led.c,需要为跑马灯LED_RUN函数定义多个状态,每个灯单独亮,全亮或全灭,参数由每次中断调用此函数时传入:

#include "bsp_led.h"   

void LED_GPIO_Config(void)
{		
	
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;
		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd( LED_GPIO_CLK , ENABLE);
		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	
		//灯1的GPIO测试
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;	
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
	
		//灯2的GPIO测试
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;	
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);	
		
		//灯3的GPIO测试
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;	
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);	
		
		//灯4的GPIO测试
		GPIO_InitStructure.GPIO_Pin = LED4_GPIO_PIN;	
		GPIO_Init(LED4_GPIO_PORT, &GPIO_InitStructure);	
		
		//灯5的GPIO测试
		GPIO_InitStructure.GPIO_Pin = LED5_GPIO_PIN;	
		GPIO_Init(LED5_GPIO_PORT, &GPIO_InitStructure);	


}

void LED_RUN(u8 index){
	
	if(index==1){
		
			//LED1亮
		GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);	
		
	}else if(index==2){
	
			//LED2亮
		GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
		
	}else if(index==3){
		
			//LED3亮
		GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
		
	}else if(index==4){
		
			//LED4亮
		GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_ResetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
	
	}else if(index==5){
	
			//LED5亮
		GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_ResetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
		
	}else if(index ==6) {
			//所有LED亮
		GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_ResetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_ResetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
	
	}else{
		//所有灯灭
		GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);
		GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);
		GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);
		
	}
	
	
}

使用到了原子哥的定时器包timer.c,对其中的端口以及配置进行修改

#include "timer.h"
#include "stm32f10x.h"
 #include "bsp_led.h"


//通用定时器中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!	
u8 a=1;
u8 flag=1;//1表示亮灯 0表示不亮
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(  //使能或者失能指定的TIM中断
		TIM3, //TIM2
		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外设
							 
}

void TIM3_IRQHandler(void)   //TIM3中断
{
	
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
			if(flag==1){
				LED_RUN(a);
				a=(a+1)%7;
				if(a==0) a=1;
			}else{
					LED_RUN(99);
					
			}
			flag=flag==1?0:1;
			
			
			
		}
}

这个文件只修改了定时器TIM3中断函数TIM3_IRQHandler() 中的内容,flag标记是否全灭,a记录哪个灯亮。这里一开始flag定义成了bool类型,程序报错,查找资料发现c语言中并没有bool类型的变量,于是定义成了u8类型。举个栗子:第一次中断时a为1、flag为1,则led1亮,其余灭;接着再次定时器中断,a为2,flag为0,则全灭;接着再次进入,a仍为2,flag为1,则led2亮,其余灭...

此时led跑马灯已经完成了,接着开始按键中断。

按键中断

由于TIM3也是中断定时,如果想要按键也是中断来暂停跑马灯,则需要按键中断的优先级高于TIM3即可,由timer.c代码中可以看出,其中断被设置为了抢占优先级为0,从优先级为3。所以我们只要将按键中断的抢占优先级设置为1即可。以下是按键中断代码。

bsp_ecti.h

#ifndef __EXTI_H
#define	__EXTI_H


#include "stm32f10x.h"


//引脚定义
#define KEY1_INT_GPIO_PORT         GPIOA
#define KEY1_INT_GPIO_CLK          (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN          GPIO_Pin_6
#define KEY1_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE    GPIO_PinSource6
#define KEY1_INT_EXTI_LINE         EXTI_Line6
#define KEY1_INT_EXTI_IRQ          EXTI9_5_IRQn

#define KEY1_IRQHandler            EXTI9_5_IRQHandler



void EXTI_Key_Config(void);


#endif /* __EXTI_H */

修改gpio的端口为PA6,这里有个坑,定义IRQ时,PA0的为 EXTI0_IRQn,但PA6的为 EXTI9_5_IRQn。接着是bsp_exti.c,设置优先级以及下降沿触发:

#include "bsp_exti.h"

 /**
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  无
  * @retval 无
  */
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 配置NVIC为优先级组1 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中断源:按键1 */
  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
  /* 配置抢占优先级 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 配置子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断通道 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  


}

 /**
  * @brief  配置 IO为EXTI中断口,并设置中断优先级
  * @param  无
  * @retval 无
  */
void EXTI_Key_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure; 
	EXTI_InitTypeDef EXTI_InitStructure;

	/*开启按键GPIO口的时钟*/
	RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
												
	/* 配置 NVIC 中断*/
	NVIC_Configuration();
	
/*--------------------------KEY1配置-----------------------------*/
	/* 选择按键用到的GPIO */	
  GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
  /* 配置为浮空输入 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
  GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 下降沿中断 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  /* 使能中断 */	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
}
/*********************************************END OF FILE**********************/

接着要进入到stm32f10x_it.c中进行中断函数的定义,设置全局变量u8 timflag,补充以下函数,进行每次按键点击时定时器TIM3的失能和失能(这里更新一下错误,  我一开始用的是  TIM_ITConfig(TIM3, TIM_IT_Update ,ENABLE );来进行定时器的暂停和开始,后来发现这个函数会更新定时器状态,如果想要定时器保留暂停之前的状态,用TIM_Cmd(TIM3,ENABLE)函数即可):

void KEY1_IRQHandler(void)
{
  //确保是否产生了EXTI Line中断
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 
	{
		//将定时器3暂停
		if(timflag==0){
			//暂停
				TIM_Cmd(TIM3,DISABLE);
		}else{
			//打开
			
            TIM_Cmd(TIM3,ENABLE);
		}
		timflag=timflag==0?1:0;
    //清除中断标志位
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     
	}  
}

main函数

最后是main.c,依次调用方法即可。

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_exti.h"
#include "timer.h"


int main(){
LED_GPIO_Config();//led的gpio初始化
EXTI_Key_Config(); //exti按键中断使能 无需在while(1)中判断按键是否按下
TIM3_Int_Init(19999,7199); //   19999是2000ms


	while(1){
		
			
	}

}

总的程序在以下链接中:

链接:https://pan.baidu.com/s/1sycqVla4QMcp7_uQ8WjDag 
提取码:ok1o 
 

你可能感兴趣的:(嵌入式,单片机,stm32)