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

您所在的位置:网站首页 怎么让定时器清零 STM32 定时器TIM实现跑马灯以及按键暂停(外部中断) 固件库编程 流水灯

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

2024-07-15 12:28| 来源: 网络整理| 查看: 265

目录

项目要求

基础部分:

提高部分:

实现流程

硬件

软件

代码结构

跑马灯

按键中断

main函数

项目要求

先看看具体要求:

流水灯的设计:

基础部分:

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

提高部分:

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

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

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

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

实现流程 硬件

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

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

原理图的中间部分就是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:

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

软件 代码结构

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

跑马灯

首先是最基本的定时器跑马灯,定义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   



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3