IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32嵌入式编程——LED控制篇 -> 正文阅读

[嵌入式]STM32嵌入式编程——LED控制篇

? ? ? ? 我们要实现的是使得STM32开发板上的LED灯实现三种不同的闪烁功能,分别是呼吸灯,三档灯和自定义周期闪烁灯。该实验的关键在于如何控制 STM32 的 IO 口输出。以下为本人学习过程中的一些积累,有些许错误是不可避免的,其中为了使内容更加连贯,以及加深读者对内容的了解,引用了STM官方的一些源代码和相关解释,但主要功能函数均为本人原创,大家可以借鉴学习,若发现错误也欢迎指出。本文章未设目录,希望感兴趣的话能从头看到尾,我是按照自己理解学习的顺序写的。

? ? ? ? STM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口 可以由软件配置成如下 8 种模式:

1、输入浮空? ? ? ? ? ? ? ? ? 2、输入上拉

3、输入下拉? ? ? ? ? ? ? ? ? 4、模拟输入

5、开漏输出? ? ? ? ? ? ? ? ? 6、推挽输出

7、推挽式复用功能? ? ? ?8、开漏复用功能

? ? ? ?GPIO的配置使用大概分以下三个过程:

1) 使能 IO 口时钟。

2) 初始化 IO 参数。

3) 操作 IO。

下面这个led.h 以及 led.c文件为实现主要功能的代码,所以我将围绕这个文件的代码讲解。

/*
led.h
*/

#ifndef _LED_E
#define _LED_E

#include "sys.h"
#include "delay.h"
#include "timer.h"
#include "key.h"
#include "exti.h"
#include "beep.h"


#define LED0 PBout(5)
#define LED1 PEout(5) 

void LED_Init(void);

void paomadeng(void); //跑马灯实验

void huxideng(void); //呼吸灯

void sandangdeng(void);  //三档灯

void shanshuodeng(u16 time_continues);  //闪烁灯

#endif
/*
 led.c
*/

#include "led.h"
#include "stm32f10x.h"
#include "delay.h"


void LED_Init()
{
	GPIO_InitTypeDef GPIO_InitStr;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE); //时钟使能
	
	/*
	RCC->APB2ENR |= 1<<3;       //相关寄存器位带操作 	 
	RCC->APB2ENR |= 1<<6;   
	*/
	
	GPIO_InitStr.GPIO_Mode = GPIO_Mode_Out_PP;   //推挽输出模式
	GPIO_InitStr.GPIO_Pin = GPIO_Pin_5;          //GPIO.5
	GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;     //50MHz
	
	GPIO_Init(GPIOB,&GPIO_InitStr);     //GPIO相关参数配置
	GPIO_Init(GPIOE,&GPIO_InitStr);
	
	/*   	 
	GPIOB->CRL &= 0XFF0FFFFF;   //相关寄存器位带操作
	GPIOB->CRL |= 0X00300000; 	 
     											  
	GPIOE->CRL &= 0XFF0FFFFF;
	GPIOE->CRL |= 0X00300000;	  
   */  
	
	GPIO_SetBits(GPIOB,GPIO_Pin_5);    //操作IO口
	GPIO_SetBits(GPIOE,GPIO_Pin_5);
	
	/*
	GPIOB->ODR |= 1<<5;    //相关寄存器位带操作
	GPIOE->ODR |= 1<<5; 
	*/
	
	
}


void paomadeng(void)   
{
	delay_init();   //延时函数初始化
	LED_Init();    //初始化与 LED 连接的硬件接口
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //中断优先级设置
    uart_init(115200);      //串口设置
	
	//LED0->GPIOB.5   
	//LED1->GPIOE.5
	
	TIM3_Int_Init(7199,9999);    //LED1     //定时器初始化
	
	while(1)
	{
		
		LED0=0;
		delay_ms(300);	
		LED0=1;
		delay_ms(300);	
		
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);   
		delay_ms(300);  		   
		GPIO_SetBits(GPIOB,GPIO_Pin_5);	  
		delay_ms(300);    
		
		GPIOB->BRR=GPIO_Pin_5;
		delay_ms(300);
        GPIOB->BSRR=GPIO_Pin_5;
		delay_ms(300);   		
	}

}

void huxideng(void)
{
	u16 led_dt = 0;
	u8 led_st = 1;	
	
	delay_init();	    	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	
	uart_init(115200);	 
 	LED_Init();			    
 	TIM3_PWM_Init(600,0);	   
   	while(1)
	{
 		delay_ms(10);	 
		if(led_st) led_dt++;
		else led_dt--;

 		if(led_dt>400) led_st = 0;
		if(led_dt==0) led_st = 1;										 
		TIM_SetCompare2(TIM3,led_dt);		   
	}	 
}

void sandangdeng(void)
{
	u8 key = 0;
  u8 led_dt = 0;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200);	
	LED_Init();
	KEY_Init();
	BEEP_Init();
	delay_init();
	TIM3_PWM_Init(900,0);   
	
	  while(1)
	{
		delay_ms(10);	
		key = KEY_Scan(0);
		if(key)
		{			 
     			BEEP =! BEEP;
				  delay_ms(50);
				  BEEP =! BEEP;
			
			switch(key)
			{
				case KEY0_PRES:					
				  break;
				case KEY1_PRES:
					
							  
				  LED0 = 0;
				  LED1 = 1;
				
				  if(led_dt > 3)
						led_dt = 0;
					
					led_dt++;
										
					if(led_dt == 1)
					{
						TIM_SetCompare2(TIM3,100);
					}
					else if(led_dt == 2)
					{
						TIM_SetCompare2(TIM3,400);
					}
					else if(led_dt == 3)
					{
						TIM_SetCompare2(TIM3,800);
					}
					else
					{
						TIM_SetCompare2(TIM3,0);
					}
					break;
					
			}
		}
	}
}

void shanshuodeng(u16 time_continues)
{
	u8 key =0;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200);
	
	delay_init();
	LED_Init();
	KEY_Init();
	
	TIM3_PWM_Init(100,0);
	
	
	while(1)
	{
		delay_ms(10);
		key = KEY_Scan(0);
		if(key == KEY0_PRES)
		  break;
      					
		TIM_SetCompare2(TIM3,50);
		delay_ms(time_continues);		
		TIM_SetCompare2(TIM3,0);
		delay_ms(time_continues);
	}
}
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

这个函数有两个参数,第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。 第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。

typedef struct
{ 
 uint16_t GPIO_Pin; 
 GPIOSpeed_TypeDef GPIO_Speed; 
 GPIOMode_TypeDef GPIO_Mode; 
}GPIO_InitTypeDef;

通过初始化结构体初始化 GPIO 的常用格式是:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO

上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。从上面初始 化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化 哪个或者哪些 IO 口;第二个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式, 这些模式是上面我们讲解的 8 个模式,在 MDK 中是通过一个枚举类型定义的:

typedef enum
{ GPIO_Mode_AIN = 0x0, //模拟输入
 GPIO_Mode_IN_FLOATING = 0x04, //浮空输入
 GPIO_Mode_IPD = 0x28, //下拉输入
 GPIO_Mode_IPU = 0x48, //上拉输入
 GPIO_Mode_Out_OD = 0x14, //开漏输出
 GPIO_Mode_Out_PP = 0x10, //通用推挽输出
 GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
 GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;

第三个参数是 IO 口速度设置,有三个可选值,在 MDK 中同样是通过枚举类型定义:

typedef enum
{ 
 GPIO_Speed_10MHz = 1,
 GPIO_Speed_2MHz, 
 GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

在 STM32 固件库中,通过 BSRR 和 BRR 寄存器设置 GPIO 端口输出是通过函数 GPIO_SetBits()和函数 GPIO_ResetBits()来完成的。

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

在多数情况下,我们都是采用这两个函数来设置 GPIO 端口的输入和输出状态。比如我们要设置 GPIOB.5 输出位1,那么方法为:

GPIO_SetBits(GPIOB, GPIO_Pin_5);

反之如果要设置 GPIOB.5 输出位 0,方法为:

GPIO_ResetBits (GPIOB, GPIO_Pin_5);

以上就是GPIO操作的相关函数。通过按键输入操作IO口就可以实现简单的LED灯的亮灭。但是要实现呼吸灯就必须应用另一个知识点,那就是PWM输出。

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽 度的控制。 STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定 时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出!这里我们仅利用 TIM3 的 CH2 产生一路 PWM 输出。

我就以下timer.h 以及 timer.c 文件讲解STM32的定时器及PWM输出。

/* 
timer.h
*/

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"


void TIM3_Int_Init(u16 arr,u16 psc);
void TIM4_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_Init(u16 arr,u16 psc);


void TIM3_IRQHandler(void);
void TIM4_IRQHandler(void);
#endif
/*
timer.c
*/


#include "timer.h"
#include "led.h"
#include "usart.h"
#include "key.h"


//Tout =((arr+1)*(psc+1))/Tclk;

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; 
	
	
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
	
	
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
	
	
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); 

	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure);  

	TIM_Cmd(TIM3, ENABLE); 
							 
}

void TIM3_IRQHandler(void) 
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update); 
		LED1 =! LED1;
		}			
}





void TIM3_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);  
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); 
 
   //éè????òy???a?′ó?ê?3?1|?ü,ê?3?TIM3 CH2μ?PWM??3?2¨D?	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//3?ê??ˉGPIOB
 
   
	TIM_TimeBaseStructure.TIM_Period = arr; 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 
	
	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  

	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  
 
	TIM_Cmd(TIM3, ENABLE); 
	
}

void TIM4_Int_Init(u16 arr,u16 psc)
{
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); 

	TIM_TimeBaseStructure.TIM_Period = arr; 
	TIM_TimeBaseStructure.TIM_Prescaler =psc;  
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); 
 
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); 

	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure);  

	TIM_Cmd(TIM4, ENABLE); 
							 
}




void TIM4_IRQHandler(void)   
{
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) 
  {
		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);  
		switch(key.flag.key_state){
		  case KEY_STATE_PRESS :
				
			  if(key.time_continus < KEY_TIME_OUT){key.time_continus++;} 
				
				
				
			  if(key.time_continus > KEY_TIME_CONTINUS){
				  if(key.event_current_type != EVENT_NONE_CLICK){ 
					  if(key.press_cnt > 1){key.press_cnt--;}
					  key.flag.once_event = 1;
				  }
				  else{
					key.flag.press_time = 1;			
					key.flag.key_state = KEY_STATE_IDLE;		
				
					key.event_current_type = EVENT_LONG_CLICK;	
					key.flag.once_event = 1;				  
					key.press_cnt = 1;                 
					key.time_idle = KEY_TIME_OUT;			  
				  }
			  }  

			 
			  if(key.flag.check){
				  key.flag.check = 0;
				  if(!key.flag.press_time){				 
						
					  if(key.time_idle < KEY_TIME_IDLE){		
						  key.press_cnt++;
					  }else{key.press_cnt = 1;}
				  }
				  key.flag.press_time = 0;				
			  }
				
		  break;
			
		  case KEY_STATE_RELEASE : 
			
			  if(key.time_idle < KEY_TIME_OUT){key.time_idle++;}   

				
			  if(key.flag.check){
				  key.flag.check = 0;
								
				  if(!key.flag.press_time){							
					  if(key.press_cnt > 1){							
						key.event_current_type = EVENT_DOUBLE_CLICK;	
					  }
					  else{												
						key.event_current_type = EVENT_SHORT_CLICK;		
						key.press_cnt = 1;								
					  }
				  }
			  }
			
			 
			  if(key.time_idle > KEY_TIME_IDLE){			
				  if(!key.flag.press_time){				     
					  key.flag.once_event = 1;			      
					  key.flag.key_state = KEY_STATE_IDLE;    
				  }
			  }
				
		    break;
	
		    default : 
		    break;
	  }
		
		
    if(key.flag.once_event){       
		  key.flag.once_event = 0;
		
		  switch(key.event_current_type)
			{
			  case EVENT_SHORT_CLICK : 
					huxideng();
					break;
			  case EVENT_DOUBLE_CLICK : 
					sandangdeng();
				  break;
			  case EVENT_LONG_CLICK : 
					shanshuodeng(key.time_continus);
					break;
			  default: 
				  break;
		  }
		
		  key.event_previous_type = key.event_current_type;
		  key.event_current_type = EVENT_NONE_CLICK;
	  }
		
  }
			
}	


注:定时器4的中断服务函数运行有问题,但由于能力有限未能调试出错误,望大佬能给予帮助。

STM32?的通用定时器是一个通过可编程预分频器(PSC)驱动的 16 位自动装载计数器 (CNT)构成。STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产 生输出波形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的, 没有互相共享的任何资源。

STM3F1 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能包括:

1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。

2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~ 65535 之间的任意数值。

3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:

A.输入捕获 B.输出比较 C.PWM 生成(边缘或中间对齐模式) D.单脉冲模式输出

4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外 一个定时器)的同步电路。

5)如下事件发生时产生中断/DMA:

A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)

B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

C.输入捕获

D.输出比较

E.支持针对定位的增量(正交)编码器和霍尔传感器电路

F.触发输入作为外部时钟或者按周期的电流管理

定时器的时钟来源有 4 个:

1)内部时钟(CK_INT)

2)外部时钟模式 1:外部输入脚(TIx)

3)外部时钟模式 2:外部触发输入(ETR)

4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。

这些时钟,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置。这里的 CK_INT 时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1,否则通用定时器 TIMx 的时钟 是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1 的时钟。这里还要注意的就是高级定时器的时钟不是来自 APB1,而是来自 APB2 的。

以下为使用定时器产生中断的配置过程:

1)TIM3 时钟使能。 TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调 用的函数是:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。 在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的:

voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

第一个参数是确定是哪个定时器,这个比较容易理解。第二个参数是定时器初始化参数结 构体指针,结构体类型为 TIM_TimeBaseInitTypeDef,下面我们看看这个结构体的定义:

typedef struct
{
 uint16_t TIM_Prescaler; 
 uint16_t TIM_CounterMode; 
 uint16_t TIM_Period; 
 uint16_t TIM_ClockDivision; 
 uint8_t TIM_RepetitionCounter; 
} TIM_TimeBaseInitTypeDef; 

这个结构体一共有 5 个成员变量,要说明的是,对于通用定时器只有前面四个参数有用, 最后一个参数 TIM_RepetitionCounter 是高级定时器才有用的,这里不多解释。

第一个参数 TIM_Prescaler 是用来设置分频系数的。

第二个参数 TIM_CounterMode 是用来设置计数方式,可以设置为向上计数, 向下计数方式还有中央对齐计数方式,比较常用的是向上计数模式 TIM_CounterMode_Up 和向 下计数模式 TIM_CounterMode_Down。

第三个参数是设置自动重载计数周期值,这在前面也已经讲解过。

第四个参数是用来设置时钟分频因子。

3)设置 TIM3_DIER 允许更新中断。 因为我们要使用 TIM3 的更新中断,寄存器的相应位便可使能更新中断。在库函数里面定 时器中断使能是通过 TIM_ITConfig 函数来实现的:

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

第一个参数是选择定时器号,这个容易理解,取值为 TIM1~TIM17。

第二个参数非常关键,是用来指明我们使能的定时器中断的类型,定时器中断的类型有很 多种,包括更新中断 TIM_IT_Update,触发中断 TIM_IT_Trigger,以及输入捕获中断等等。

第三个参数就很简单了,就是失能还是使能。

例如我们要使能 TIM4?的更新中断,格式为:

TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE );

4)TIM3 中断优先级设置。 在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中 断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置。

5)允许 TIM3 工作,也就是使能 TIM3。 光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器, 通过 TIM3_CR1 的 CEN 位来设置。在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实 现的:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)

6)编写中断服务函数。 在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。在 中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作, 我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处理完中断之后应 该向 TIM3_SR 的最低位写 0,来清除该中断标志。

在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)

该函数的作用是,判断定时器 TIMx 的中断类型 TIM_IT 是否发生中断。比如,我们要判断定 时器 3 是否发生更新(溢出)中断,方法为:

if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}

固件库中清除中断标志位的函数是:

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)

该函数的作用是,清除定时器 TIMx 的中断 TIM_IT 标志位。

通过以上几个步骤,我们就可以达到我们的目的了,使用通用定时器的更新中断,来控制 LED?的亮灭。

以下为PWM输出的配置过程:

1)开启 TIM3 时钟以及复用功能时钟,配置 PB5 为复用输出。

要使用 TIM3,我们必须先开启 TIM3 的时钟。?这里我们还要配置 PB5 为复用输出,这是因为 TIM3_CH2 通道将重映射到 PB5 上,此时,PB5 属于复用功能输出。库函数使能 TIM3 时钟的方法是:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器 3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟使能
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

2)设置 TIM3_CH2 重映射到 PB5 上。

因为 TIM3_CH2 默认是接在 PA7 上的,所以我们需要设置 TIM3_REMAP 为部分重映射(通 过 AFIO_MAPR 配置),让 TIM3_CH2 重映射到 PB5 上面。在库函数函数里面设置重映射的函 数是:

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

STM32 重映射只能重映射到特定的端口。第一个 入口参数可以理解为设置重映射的类型,比如 TIM3 部分重映射入口参数为 GPIO_PartialRemap_TIM3,这点可以顾名思义了。所以 TIM3 部分重映射的库函数实现方法是:

GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

3)初始化 TIM3,设置 TIM3 的 ARR 和 PSC。

在开启了 TIM3 的时钟之后,我们要设置 ARR 和 PSC 两个寄存器的值来控制输出 PWM 的 周期。当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM 周 期在这里不宜设置的太小。这在库函数是通过 TIM_TimeBaseInit 函数实现的,调用的格式为:

TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化 TIMx 的

4)设置 TIM3_CH2 的 PWM 模式,使能 TIM3 的 CH2 输出。

接下来,我们要设置 TIM3_CH2 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电 平亮,而我们希望当 CCR2 的值小的时候,DS0 就暗,CCR2 值大的时候,DS0 就亮,所以我 们要通过配置 TIM3_CCMR1 的相关位来控制 TIM3_CH2 的模式。在库函数中,PWM 通道设 置是通过函数 TIM_OC1Init()~TIM_OC4Init()来设置的,不同的通道的设置函数不一样,这里我 们使用的是通道 2,所以使用的函数是 TIM_OC2Init()。

void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

又是和上面GPIO初始化一样的结构体初始化,我们来看看结构体的定义

typedef struct
{
 uint16_t TIM_OCMode; 
 uint16_t TIM_OutputState; 
 uint16_t TIM_OutputNState; */
 uint16_t TIM_Pulse; 
 uint16_t TIM_OCPolarity; 
 uint16_t TIM_OCNPolarity; 
 uint16_t TIM_OCIdleState; 
 uint16_t TIM_OCNIdleState; 
} TIM_OCInitTypeDef;

参数 TIM_OCMode 设置模式是 PWM 还是输出比较,这里我们是 PWM 模式。 参数 TIM_OutputState 用来设置比较输出使能,也就是使能 PWM 输出到端口。 参数 TIM_OCPolarity 用来设置极性是高还是低。 其他的参数 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是 高级定时器 TIM1 和 TIM8 才用到的。 要实现我们上面提到的场景,方法是:

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //初始化 TIM3 OC2

5)使能 TIM3。 在完成以上设置了之后,我们需要使能 TIM3。

TIM_Cmd(TIM3, ENABLE); //使能定时器3

6)修改 TIM3_CCR2 来控制占空比。

最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定 的,而我们通过修改 TIM3_CCR2 则可以控制 CH2 的输出占空比。继而控制 DS0 的亮度。 在库函数中,修改 TIM3_CCR2 占空比的函数是:

void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);

当然,对于其他通道,分别有一个函数名字,函数格式为 TIM_SetComparex(x=1,2,3,4)。 通过以上 6 个步骤,我们就可以控制 TIM3 的 CH2 输出 PWM 波了。通过渐进设置不同的占空比可以使LED灯实现呼吸灯的效果。包括三档灯也是通过设置单一占空比来实现的,所以我们可以知道PWM输出的重要性。

由以上内容可以实现LED灯的各种闪烁功能了,但在实际中我们更希望使用按键来控制LED的各种功能,所以外部中断和按键的操作是必不可少的,所以我就先从按键的操作函数来讲解吧。

/*
led.h
*/

#ifndef _KEY_H
#define _KEY_H

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "exti.h"
#include "led.h"
#include "beep.h"
#include "timer.h"
#include "stm32f10x.h"



#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)

#define KEY0_PRES 1
#define KEY1_PRES 2
#define WKUP_PRES 3

#define KEY_PIN		GPIO_Pin_4	  //KEY0


#define	KEY_TIME_IDLE		400		 
#define	KEY_TIME_CONTINUS	500		
#define	KEY_TIME_OUT		2000	  


#define EVENT_NONE_CLICK	0x00	
#define EVENT_SHORT_CLICK	0x01	
#define EVENT_DOUBLE_CLICK	0x02	
#define EVENT_LONG_CLICK	0x03	 
 

#define KEY_STATE_IDLE		0x00	
#define KEY_STATE_PRESS		0x01	 
#define KEY_STATE_RELEASE	0x02	 


typedef struct{
	struct{
		u8 check		  : 1;		 
		u8 key_state	: 2;		
		u8 once_event	: 1;		
		u8 press_time	: 1;		
	}flag;                                       

	u8 event_current_type	  : 4;	 
	u8 event_previous_type	: 4;	 

	u8	press_cnt;			 
	
	u16 time_idle;				
	u16 time_continus;	
	
}KEY_PROCESS_TypeDef;

static KEY_PROCESS_TypeDef key;

void KEY_Init(void);     

void KEY_Config(void);   

u8 KEY_Scan(u8);       

void anjianshuru(void);  
/*
key.c
*/

#include "key.h"


void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStr;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
	
	GPIO_InitStr.GPIO_Mode = GPIO_Mode_IPD;    
	GPIO_InitStr.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA,&GPIO_InitStr);  //WK_UP
	
	
	GPIO_InitStr.GPIO_Mode = GPIO_Mode_IPU;    
	GPIO_InitStr.GPIO_Pin = GPIO_Pin_3;
	GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOE,&GPIO_InitStr);   //KEY1
	
	GPIO_InitStr.GPIO_Mode = GPIO_Mode_IPU;    
	GPIO_InitStr.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStr.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOE,&GPIO_InitStr);  //KEY0
	
}


u8 KEY_Scan(u8 mode)
{
	static u8 key_up=1;
	
	if(mode)      
		key_up=1;  	  
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	} else if(KEY0==1 && KEY1==1 && WK_UP==0) key_up=1; 	    
 	return 0; 
}

void KEY_Config(void)   
{
	EXTIX_Init();
	TIM3_Int_Init(1000,71);
	
	key.flag.check = 0;
	key.flag.key_state = KEY_STATE_IDLE;
	key.flag.once_event = 0;
	key.flag.press_time = 0;
	
	key.event_current_type = EVENT_NONE_CLICK;
	key.event_previous_type = EVENT_NONE_CLICK;
	key.press_cnt = 1;

	key.time_continus = 0;
	key.time_idle = KEY_TIME_OUT;
	
	
	TIM3->CR1 |= 0x0001;

}

void anjianshuru(void)
{
	u8 key=0;	
	
	delay_init();	    	  
	LED_Init();		  	
	BEEP_Init();         
	KEY_Init();   
  	
	LED0=0;				//LED0 
	
	while(1)
	{
 		key = KEY_Scan(0);	
	  if(key)
		{						   
			switch(key)
			{				 
				case WKUP_PRES:	
					BEEP =! BEEP;
					break; 
				case KEY0_PRES:	
					LED1 =! LED1;
					break;
				case KEY1_PRES:	
					LED0 =! LED0;
					LED1 =! LED1;
					break;
			}
		}else delay_ms(10); 
	}	 

}
	

这段代码中的?2 个函数,void KEY_Init(void) 和 u8 KEY_Scan(u8 mode)

KEY_Init()是用来 初始化按键输入的 IO 口的。首先使能 GPIOA 和 GPIOE 时钟,然后实现 PA0、PE3 和 PE4 的 输入设置。?KEY_Scan()函数,则是用来扫描这 3 个 IO 口是否有按键按下。

KEY_Scan()函数,支持两 种扫描方式,通过 mode 参数来设置。 当 mode 为 0 的时候,KEY_Scan()函数将不支持连续按,扫描某个按键,该按键按下之后 必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多 次触发,而坏处就是在需要长按的时候比较不合适。 当 mode 为 1 的时候,KEY_Scan()函数将支持连续按,如果某个按键一直按下,则会一直 返回这个按键的键值,这样可以方便的实现长按检测。 有了 mode 这个参数,大家就可以根据自己的需要,选择不同的方式。 因为该函数里面有 static 变量,所以该函数不是一个可重入函数,在有 OS 的情况下,这个大家 要留意下。同时还有一点要注意的就是,该函数的按键扫描是有优先级的,最优先的是 KEY0, 第二优先的是 KEY1,最后是 WK_UP 按键。该函数有返回值,如果有按键按下,则返回非 0 值,如果没有或者按键不正确,则返回 0。

函数void KEY_Config(void) 为设置按键初始状态的函数,此操作函数在定时器4的中断服务函数中,分别实现按键短按,长按,连击的不同响应功能。

接下来就是关于外部中断,这对我来说是个比较难的点,我就以下代码讲述:

/*
exti.h
*/

#ifndef __EXTI_H
#define __EXTI_H	 
#include "sys.h"

void EXTIX_Init(void);//外部中断初始化	 	

void EXTI4_IRQHandler(void);
#endif
/*
exti.c
*/

#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"

 

void EXTIX_Init(void)
{
 
   	EXTI_InitTypeDef EXTI_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;

    KEY_Init();	 

  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	
	
	

   //GPIOE.3	 //KEY1
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
  	EXTI_InitStructure.EXTI_Line=EXTI_Line3;
  	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  	EXTI_Init(&EXTI_InitStructure);	  	

   //GPIOE.4	 	//KEY0
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4); 
  	EXTI_InitStructure.EXTI_Line=EXTI_Line4;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; 
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  	EXTI_Init(&EXTI_InitStructure);	  	


   //GPIOA.0	 // WK_UP
 	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); 
  	EXTI_InitStructure.EXTI_Line=EXTI_Line0;
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  	EXTI_Init(&EXTI_InitStructure);		

  	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;		
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;					
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								
  	NVIC_Init(&NVIC_InitStructure); 

  	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;		
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;					
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;						
  	NVIC_Init(&NVIC_InitStructure);  	  

  	NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;			
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;					
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								
  	NVIC_Init(&NVIC_InitStructure);  	 
 
}



void EXTI0_IRQHandler(void)
{
	delay_ms(10);
	if(WK_UP == 1)	 	 
	{				 
		huxideng();	
	}
	EXTI_ClearITPendingBit(EXTI_Line0); 
}

 

void EXTI3_IRQHandler(void)
{
	delay_ms(10); 
	if(KEY1 == 0)	  
	{				 
		sandangdeng();
	}		 
	EXTI_ClearITPendingBit(EXTI_Line3); 
}



void EXTI4_IRQHandler(void)
{
	delay_ms(10);			
	
	//(GPIOE->IDR & KEY_PIN) == 0
	if(KEY0 == 0){
		key.flag.key_state = KEY_STATE_PRESS;	
		key.flag.check = 1;
		key.time_continus = 0;		
	}
	else{
		key.flag.key_state = KEY_STATE_RELEASE;	 
		key.flag.check = 1;
		key.time_idle = 0;			
	}
	EXTI_ClearITPendingBit(EXTI_Line4);	
}
 

在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)

将中断线 2 与 GPIOE 映射起来,那么很显然是 GPIOE.2 与 EXTI2 中断线连接了。设置好中断 线映射之后,那么到底来自这个 IO 口的中断是通过什么方式触发的呢?接下来我们就要设置 该中断线上中断的初始化参数了。

中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

下面我们用一个使用范例来说明这个函数的使用:

EXTI_InitTypeDef EXTI_InitStructure;
 EXTI_InitStructure.EXTI_Line=EXTI_Line4;
 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 寄存器

上面的例子设置中断线 4 上的中断为下降沿触发。STM32 的外设的初始化都是通过结构体来设 置初始值的,这里就不罗嗦结构体初始化的过程了。我们来看看结构体 EXTI_InitTypeDef 的成员变量:

typedef struct
{
 uint32_t EXTI_Line; 
 EXTIMode_TypeDef EXTI_Mode; 
 EXTITrigger_TypeDef EXTI_Trigger; 
 FunctionalState EXTI_LineCmd; 
}EXTI_InitTypeDef;

从定义可以看出,有 4 个参数需要设置。第一个参数是中断线的标号,取值范围为 EXTI_Line0~EXTI_Line15。这个在上面已经讲过中断线的概念。也就是说,这个函数配置的是 某个中断线上的中断参数。第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上 升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发 EXTI_Trigger_Rising_Falling。最后一个参数就是使能中断线 了。 我们设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既 然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。这个在前面已经讲解过,这 里我们就接着上面的设置中断线 2 的中断优先级。

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化

我们配置完中断优先级之后,接着我们要做的就是编写中断服务函数。中断服务函数的名 字是在 MDK 中事先有定义的。这里需要说明一下,STM32 的 IO 口外部中断函数只有 6 个, 分别为:

EXPORT EXTI0_IRQHandler 
EXPORT EXTI1_IRQHandler 
EXPORT EXTI2_IRQHandler 
EXPORT EXTI3_IRQHandler 
EXPORT EXTI4_IRQHandler 
EXPORT EXTI9_5_IRQHandler 
EXPORT EXTI15_10_IRQHandler 

中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中 断线 10-15 共用中断函数 EXTI15_10_IRQHandler。在编写中断服务函数的时候会经常使用到两 个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):

ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

这个函数一般使用在中断服务函数的开头判断中断是否发生。另一个函数是清除某个中断线上 的中断标志位:

void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

这个函数一般应用在中断服务函数结束之前,清除中断标志位。 常用的中断服务函数格式为:

void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生 
{
中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位 
}
}

在这里需要说明一下,固件库还提供了两个函数用来判断外部中断状态以及清除外部状态 标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。 只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而 EXTI_GetFlagStatus 直接用来判断状态标志位。 讲到这里,相信大家对于 STM32 的 IO 口外部中断已经有了一定了了解。下面我们再总结一 下使用 IO 口外部中断的一般步骤:

1)初始化 IO 口为输入。

2)开启 AFIO 时钟

3)设置 IO 口与中断线的映射关系。

4)初始化线上中断,设置触发条件等。

5)配置中断分组(NVIC),并使能中断。

6)编写中断服务函数。

通过以上几个步骤的设置,我们就可以正常使用外部中断了。

以上就是相关的知识点了,下面是工程主函数,通过STM官方文件建立工程就可以实现LED的简单编程控制了。

/*
main.c
*/

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "beep.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "exti.h"
#include "timer.h"



int main()
{
	delay_init();
	LED_Init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
  uart_init(115200);
	EXTIX_Init();
	TIM4_Int_Init(899,0);
	
	KEY_Config();
	
	while(1)
	{
		delay_ms(100);
	
	}
}
	

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-23 11:37:35  更:2021-09-23 11:39:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 2:01:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码