目录
项目要求
基础部分:
提高部分:
实现流程
硬件
软件
代码结构
跑马灯
按键中断
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.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