STM32 的定时器分为高级定时器、 通用定时器 、基本定时器三种。
这三个定时器成上下级的关系,即基本定时器有的功能通用定时器都有,而且还增加了向下、向上/向下计数器、PWM生成、输出比较、输入捕获等功能;而高级定时器又包含了通用定时器的所有功能,另外还增加了死区互补输出、刹车信号。
例如本文采用的STM32L496VGT6芯片中,高级定时器(TIM1、TIM8)、 通用定时器(TIM2、TIM3、TIM4、TIM5、TIM15,另外TIM5、TIM15亦有些许差异) 、基本定时器(TIM6、TIM7、TIM16、TIM17),高级定时器、 通用定时器 、基本定时器在STM32CubeMX配置界面中是不一样的:
高级定时器(TIM1):
通用定时器(TIM2):
或通用定时器(TIM5):
基本定时器(TIM6):
在通用定时器,每个定时器都有一个16位的自动加载递增/递减计数器,一个16位的预分频器和4个独立通道,每个通道可用于输入捕获、输出比较、PWM和单脉冲模式输出。而TIM1、TIM8高级定时,其通道更是支持到6个通道,在一些高级芯片中支持到的通道会更多。
每个定时器都支持外部中断机制,还可以结合计数功能或时钟功能,实现轮询策略;每个定时器也支持独立的DMA请求机制。
通用定时器可以利用GPIO引脚进行脉冲输出,在配置为比较输出、PWM输出功能时,捕获/比较寄存器TIMx_CCR被用作比较功能。
定时器输出时间间隔(us)=(分频系数+1)*(计数周期+1)÷输入定时器时钟频率(MHz,APB*),如下图所示。
通常STM32芯片支持所有GPIO设置为外部中断输入,外部IO可以由电平上升、电平下降、高低电平三种方式的中断触发或事件触发。定时器作为外设之一,同样支持到外部中断或事件触发。
本文基于STM32L496VGT6芯片创建工程,创建时钟、开启lpuart1串口,以及三个LED灯和按键KEY,配置如下:
【1】系统配置
【2】RCC功能开启外部高速时钟(默认参数配置)及时钟树设置
时钟树设置,STM32L496VGT6支持最大输出频率80MHz,本文直接拉满
【3】lpuart1串口开启
开启lpuart1中断功能及按开发板引脚说明调整引脚:
【4】配置三个按键及LED
完成基本配置后,生成输出代码(每个外设独立的.h/.c文件)
【1】禁用syscalls.c源文件
【2】在工程目录下,创建源文件夹ICore文件目录,
【2】并在ICore目录下创建led、key、print、usart目录。
【3】在print目录创建print.h和print.c源文件,将用来实现将printf标准函数输出映射到lpuart1串口的驱动程序。
print.h
#ifndef INC_RETARGET_H_
#define INC_RETARGET_H_
#include "stm32l4xx_hal.h"
#include "stdio.h"//用于printf函数串口重映射
#include
void ResetPrintInit(UART_HandleTypeDef *huart);
int _isatty(int fd);
int _write(int fd, char* ptr, int len);
int _close(int fd);
int _lseek(int fd, int ptr, int dir);
int _read(int fd, char* ptr, int len);
int _fstat(int fd, struct stat* st);
#endif /* INC_RETARGET_H_ */
print.c
#include <_ansi.h>
#include <_syslist.h>
#include
#include
#include
#include
#include
#include
#include
#include "print.h"
#if !defined(OS_USE_SEMIHOSTING)
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
UART_HandleTypeDef *gHuart;
void ResetPrintInit(UART_HandleTypeDef *huart) {
gHuart = huart;
/* Disable I/O buffering for STDOUT stream, so that
* chars are sent out as soon as they are printed. */
setvbuf(stdout, NULL, _IONBF, 0);
}
int _isatty(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 1;
errno = EBADF;
return 0;
}
int _write(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return len;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _close(int fd) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
return 0;
errno = EBADF;
return -1;
}
int _lseek(int fd, int ptr, int dir) {
(void) fd;
(void) ptr;
(void) dir;
errno = EBADF;
return -1;
}
int _read(int fd, char* ptr, int len) {
HAL_StatusTypeDef hstatus;
if (fd == STDIN_FILENO) {
hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
if (hstatus == HAL_OK)
return 1;
else
return EIO;
}
errno = EBADF;
return -1;
}
int _fstat(int fd, struct stat* st) {
if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
st->st_mode = S_IFCHR;
return 0;
}
errno = EBADF;
return 0;
}
#endif //#if !defined(OS_USE_SEMIHOSTING)
【3】在led目录创建led.h和led.c源文件,将用来实现LED等状态控制的驱动程序。
led.h
#ifndef LED_H_
#define LED_H_
#include "main.h"
#include "gpio.h"
void Toggle_led0();
void Toggle_led1();
void Toggle_led2();
void set_led0_val(GPIO_PinState PinState);
void set_led1_val(GPIO_PinState PinState);
void set_led2_val(GPIO_PinState PinState);
#endif /* LED_H_ */
led.c
#include "led.h"
void Toggle_led0()
{
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
void Toggle_led1()
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
}
void Toggle_led2()
{
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
}
void set_led0_val(GPIO_PinState PinState)
{
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,PinState);
};
void set_led1_val(GPIO_PinState PinState)
{
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,PinState);
};
void set_led2_val(GPIO_PinState PinState)
{
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,PinState);
};
【4】在key目录创建key.h和key.c源文件,将用来实现KEY控制的驱动程序。
key.h
#ifndef KEY_H_
#define KEY_H_
#include "main.h"
#include "gpio.h"
GPIO_PinState get_key0_val();
GPIO_PinState get_key1_val();
GPIO_PinState get_key2_val();
uint8_t KEY_0(void);
uint8_t KEY_1(void);
uint8_t KEY_2(void);
#endif /* KEY_H_ */
key.c
#include "key.h"
GPIO_PinState get_key0_val()
{
return HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin);
};
GPIO_PinState get_key1_val()
{
return HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);
};
GPIO_PinState get_key2_val()
{
return HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin);
};
uint8_t KEY_0(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
uint8_t KEY_1(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
uint8_t KEY_2(void)
{
uint8_t a;
a=0;//如果未进入按键处理,则返回0
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET){//读按键接口的电平
HAL_Delay(20);//延时去抖动
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET){ //读按键接口的电平
a=1;//进入按键处理,返回1
}
}
while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET); //等待按键松开
return a;
}
【4】在usart目录创建usart.h和usart.c源文件,将用来实现lpuart1驱动程序。
usart.h
#ifndef INC_USART_H_
#define INC_USART_H_
#include "stm32l4xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include "../print/print.h"//用于printf函数串口重映射
extern UART_HandleTypeDef hlpuart1;//声明LPUSART的HAL库结构体
#define HLPUSART_REC_LEN 256//定义LPUSART最大接收字节数
extern uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
extern uint16_t HLPUSART_RX_STA;//接收状态标记
extern uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口中断回调函数声明
#endif /* INC_USART_H_ */
usart.c
#include "usart.h"
uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
/*
* bit15:接收到回车(0x0d)时设置HLPUSART_RX_STA|=0x8000;
* bit14:接收溢出标志,数据超出缓存长度时,设置HLPUSART_RX_STA|=0x4000;
* bit13:预留
* bit12:预留
* bit11~0:接收到的有效字节数目(0~4095)
*/
uint16_t HLPUSART_RX_STA=0;接收状态标记//bit15:接收完成标志,bit14:接收到回车(0x0d),bit13~0:接收到的有效字节数目
uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart ==&hlpuart1)//判断中断来源(串口1:USB转串口)
{
if(HLPUSART_NewData==0x0d){//回车标记
HLPUSART_RX_STA|=0x8000;//标记接到回车
}else{
if((HLPUSART_RX_STA&0X0FFF)
【5】在main.c文件中加入各驱动文件的头文件
/* USER CODE BEGIN Includes */
#include "../../ICore/key/key.h"
#include "../../ICore/led/led.h"
#include "../../ICore/print/print.h"
#include "../../ICore/usart/usart.h"
/* USER CODE END Includes */
在主函数中,初始化lpuart1
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
HLPUSART_RX_STA = 0;
/* USER CODE END 2 */
在主函数循环体中,实现lpuart1输入返回测试及按键时LED灯状态反转。
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
printf("receive:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收错误,重新开始
HAL_Delay(100);//等待
}
if(KEY_0())
{
Toggle_led0();//LED0等状态反转一次
}
if(KEY_1())
{
Toggle_led1();//LED1等状态反转一次
}
if(KEY_2())
{
Toggle_led2();//LED2等状态反转一次
}
/* USER CODE END WHILE */
【6】配置输出文件格式支持,然后编译代码
【7】配置调试及下载支持
运行下载烧写程序到开发板,完成基本功能开发。
打开.ioc进入cubeMX配置界面,将KEY0按钮调整为GPIO_EXTI类型
并开启KEY0(PE11)的中断支持
添加TIM2通用定时器功能实现定时轮询功能
并开启 TIM2的中断功能
添加TIM3通用定时器功能实现间隔计时,辅助lpuart1串口在没有结束标记情况下实现超时(100ms)通知
并开启TIM3的中断功能支持
添加TIM4通用定时器功能,开启其通道4对PWM输出功能支持,将KEY2(PD15)调整为TIM_CH4类型。PWM是定时器扩展出来的一个功能(本质上是使用一个比较计数器的功能),配置过程一般为选定定时器、复用GPIO口、选择通道(传入比较值)、使能相应系统时钟、设定相应的预分频、计数周期、PWM模式(有两种)、电平极性等。
并开启TIM4的中断功能支持
配置KEY2(PD15)GPIO引脚为交替推免GPIO模式,如下图。
上述配置完成后输出生成代码。
已经配置了KEY0(PE11)为GPIO_EXTI模式,并开启其中断支持,现需要重新实现stm32l4xx_hal_gpio.c文件中的HAL_GPIO_EXTI_Callback函数来实现GPIO外部中断回调功能。
在main.c文件中,重新实现HAL_GPIO_EXTI_Callback函数,覆盖stm32l4xx_hal_gpio.c文件中HAL_GPIO_EXTI_Callback弱函数。
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY0_Pin)
{
static uint32_t key0_count = 0;
printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);
Toggle_led0();//LED0等状态反转一次
}
}
/* USER CODE END 0 */
已经配置了TIM2,并开启其中断支持,现需要重新实现stm32l4xx_hal_tim.c文件中的当计数溢出时调用的回调函数HAL_TIM_PeriodElapsedCallback。
在mian.c文件中,重新实现HAL_TIM_PeriodElapsedCallback函数,覆盖stm32l4xx_hal_tim.c文件中的同名弱函数。
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY0_Pin)
{
static uint32_t key0_count = 0;
printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);
Toggle_led0();//LED0等状态反转一次
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim2)
{
static uint32_t key1_count = 0;
printf("this is %lu count for HAL_TIM_PeriodElapsedCallback!\r\n",key1_count++);
Toggle_led1();//LED1等状态反转一次
}
}
/* USER CODE END 0 */
同样已经配置了TIM4,并开启其中断支持,现需要重新实现stm32l4xx_hal_tim.c文件中的当计数溢出时调用的回调函数HAL_TIM_PeriodElapsedCallback,在计数结束时,改写lpuart1的接收标记。
/* USER CODE BEGIN 0 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY0_Pin)
{
static uint32_t key0_count = 0;
printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);
Toggle_led0();//LED0等状态反转一次
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim2)
{
static uint32_t key1_count = 0;
printf("this is %lu count for HAL_TIM_PeriodElapsedCallback!\r\n",key1_count++);
Toggle_led1();//LED1等状态反转一次
}
if(htim ==&htim3)//判断是否是定时器3中断(定时器到时表示一组字符串接收结束)
{
HLPUSART_RX_BUF[HLPUSART_RX_STA&0X0FFF]=0;//添加结束符
HLPUSART_RX_STA|=0x8000;//接收标志位最高位置1表示接收完成
__HAL_TIM_CLEAR_FLAG(&htim3,TIM_EVENTSOURCE_UPDATE );//清除TIM3更新中断标志
__HAL_TIM_DISABLE(&htim3);//关闭定时器3
}
}
/* USER CODE END 0 */
同时在usart.h增加对TIM3的引入
#ifndef INC_USART_H_
#define INC_USART_H_
#include "stm32l4xx_hal.h" //HAL库文件声明
#include //用于字符串处理的库
#include "../print/print.h"//用于printf函数串口重映射
extern TIM_HandleTypeDef htim3; //定时器3辅助串口接收数据
extern UART_HandleTypeDef hlpuart1;//声明LPUSART的HAL库结构体
#define HLPUSART_REC_LEN 256//定义LPUSART最大接收字节数
extern uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
extern uint16_t HLPUSART_RX_STA;//接收状态标记
extern uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口中断回调函数声明
#endif /* INC_USART_H_ */
在usart.c中串口回调函数的lpuart1接收实现方式,通过htim3(TIM3)计数周期通知来告知lpuart1结束数据接收。
#include "usart.h"
uint8_t HLPUSART_RX_BUF[HLPUSART_REC_LEN];//接收缓冲,最大HLPUSART_REC_LEN个字节.末字节为换行符
/*
* bit15:接收到回车(0x0d)时设置HLPUSART_RX_STA|=0x8000;
* bit14:接收溢出标志,数据超出缓存长度时,设置HLPUSART_RX_STA|=0x4000;
* bit13:预留
* bit12:预留
* bit11~0:接收到的有效字节数目(0~4095)
*/
uint16_t HLPUSART_RX_STA=0;接收状态标记//bit15:接收完成标志,bit14:接收到回车(0x0d),bit13~0:接收到的有效字节数目
uint8_t HLPUSART_NewData;//当前串口中断接收的1个字节数据的缓存
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
// if(huart ==&hlpuart1)//判断中断来源(串口1:USB转串口)
// {
// if(HLPUSART_NewData==0x0d){//回车标记
// HLPUSART_RX_STA|=0x8000;//标记接到回车
// }else{
// if((HLPUSART_RX_STA&0X0FFF)
TIM4定时器已经开启了通道4,向LED2(PD15)交替输出递减电压实现LED灯状态由亮变暗直至熄灭的循环,首选需要通过HAL_TIM_PWM_Start开启PWM功能,然后通过__HAL_TIM_SetCompare来设置占空比来实现输出信号调节。
PWM输出工作过程:若配置脉冲计数器TIMx_CNT为向上计数,而重载寄存器TIMx_ARR被配置为N,即TIMx_CNT的当前计数值数值X在TIMxCLK时钟源的驱动下不断累加,当TIMx_CNT的数值X(对应下面代码的a)大于N(对应cubeMX配置的ARR值,即99)时,会重置TIMx_CNT数值为0重新计数 。
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4);
uint8_t a = ;
while(1){
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_4,a);
a++;//占空比值加1
if(a>=99)a=0;
HAL_Delay(10);
}
在main.c函数中,初始化及启动TIM定时器
/* USER CODE BEGIN 2 */
ResetPrintInit(&hlpuart1);
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
HLPUSART_RX_STA = 0;
HAL_TIM_Base_Start_IT(&htim2);//开启定时器2中断(必须开启才能进入中断处理回调函数)
HAL_TIM_Base_Start_IT(&htim3);//开启定时器3中断(必须开启才能进入中断处理回调函数),辅助lpuart1接收数据
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4);
uint8_t memu = 0; //用来标记按键KEY2按下,来开启或关闭TIM4的PWM功能
uint16_t a = 0; //占空比值
/* USER CODE END 2 */
在main函数循环中,实现如下:
/* USER CODE BEGIN WHILE */
while (1)
{
if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
printf("receive:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
HLPUSART_RX_STA=0;//接收错误,重新开始
HAL_Delay(100);//等待
}
if(KEY_1())
{
}
if(KEY_2())
{
memu=!memu;
printf("TIM4_PWM is runing!\r\n");
}
if(memu)
{
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_4,a);
a++;//占空比值加1
if(a>=99)
{
printf("TIM4_PWM finish cycle!\r\n");
a=0;
}
HAL_Delay(10);//等待
}
/* USER CODE END WHILE */
【1】测试log输出
【2】注释掉相关打印输出,测试lpuart1输入数据无回车时(lpuart1的HAL_UART_Receive_IT调用后,TIM3每100毫秒通知其结束接收),能成功收到返回数据,如下图。
【3】LED等状态情况
按键KEY0顺利切换LED0的状态,LED1灯周期切换状态(TIM2),按键KEY2开启TIM4的PWM输出功能,LED2灯周期由亮到灭切换状态(TIM4)。
https://live.csdn.net/v/263027