一、基础知识点
知识点一
STM32一共有8个定时器,2-5为基本定时器拥有PWN高级功能,1和8为高级定时器用于电机处理,6和7普通定时器。
CK_CNT = CK_PSC/(PSC + 1)
PSC= CK_PSC/CK_CNT -1
自动装载寄存器的值:CK_CNT/需要配置的值
注:PSC范围0-65535(0对应的是1分频,65535对应的是65536分频,所以要加1)
知识点二
STM32中断系统的核心,即NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器),中断向量嵌套就会有优先级
STM32的中断优先级:1、抢占式优先级 2、响应式优先级3、中断向量表
抢占式优先级的特点是:
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套。
响应式优先级的特点是:
(1)当两个中断源的抢占式优先级相同时,高响应优先级的中断优先被响应,这两个中断将没有嵌套关系;
(2)当两个中断源的抢占式优先级相同时,如果有低响应优先级中断正在执行,那么高响应优先级的中断要等待已被响应的低响应优先级的中断执行结束后才能得到响应。当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。
中断向量表的特点是:
如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序来决定
STM32设置了组(Group)的概念来管理这些优先级。 每一个中断都有一个专门的寄存器来描述该中断的抢占式优先级和响应式优先级。在这个寄存器中STM32使用了4个二进制位来描述优先级。4位的中断优先级可以分成2组,从高位看,前面定义的是抢占式优先级,后面是响应优先级。
第0组:所有4位用于指定响应式优先级;
第1组:最高1位用于指定抢占式优先级,后面3位用于指定响应式优先级;
第2组:最高2位用于指定抢占式优先级,后面2位用于指定响应式优先级;
第3组:最高3位用于指定抢占式优先级,后面1位用于指定响应式优先级;
第4组:所有4位用于指定抢占式优先级。
本实验通过定时器中断简单控制个LED灯,了解整个定时器中断流程。
准备好了吗?开始我的show time。
二、开发环境
1、硬件开发准备
主控:STM32F103ZET6
2、软件开发准备
软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建
三、STM32CubeMX相关配置
1、STM32CubeMX基本配置
本实验基于CubeMX详解构建基本框架 进行开发。
2、配置产生定时器的时钟
配置时钟是非常重要的事!!!上图可看出,定时器没不必要直接通过72M进行定时器分频,这样CPU会非常忙碌。因此后面会基于72M进行分频处理。
3、配置三个LED灯引脚
4、配置定时器
配置事件5ms触发中断,来自RCC的时钟72M(在时钟那配置)
PSC = 72000000/1000000-1 (CK_CNT 1M)
自动装载寄存器的值:1000000/200
这样配置定时器5ms中断一次。
5、定时器相关中断配置
四、Vscode代码讲解
1、构建定时器6相关结构体
// Inc/Timer6.h
typedef struct
{
uint16_t volatile uRun_Timer; //系统运行定时器
void (*Timer6_Start_IT)(void); //定时器6以中断模式启动
} Timer6_t;
extern Timer6_t Timer6;
2、填充结构体
// Src/Timer6.c
static void Timer6_Start_IT(void); //定时器6以中断模式启动
Timer6_t Timer6 =
{
0,
Timer6_Start_IT
};
static void Timer6_Start_IT(void)
{
HAL_TIM_Base_Start_IT(&htim6); //启动定时器6,后面有详解
}
3、重构定时器中断,实现1s LED闪烁(后续详解中断调用)
#define LED1_Toggle HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
#define LED2_Toggle HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
#define LED3_Toggle HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == htim6.Instance)
{
//程序支持运行,指示灯间隔1s闪烁
if(++Timer6.uRun_Timer >= TIMER6_100mS)
{
Timer6.uRun_Timer = 0;
LED1_Toggle;
LED2_Toggle;
LED3_Toggle;
}
}
}
现在开始深入分析相关代码
kernel/Core/Src/tim.c新生成的文件,根据Cube里配置的参数自动生成的初始化定时器函数。
void MX_TIM6_Init(void)
{
/* USER CODE BEGIN TIM6_Init 0 */
/* USER CODE END TIM6_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {
0};
/* USER CODE BEGIN TIM6_Init 1 */
/* USER CODE END TIM6_Init 1 */
htim6.Instance = TIM6;
htim6.Init.Prescaler = 72000000/1000000-1;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000000/200;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM6_Init 2 */
/* USER CODE END TIM6_Init 2 */
}
开启中断HAL_TIM_Base_Start_IT函数
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
uint32_t tmpsmcr;
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance)); // 检查输入定时器
/* Check the TIM state */
if (htim->State != HAL_TIM_STATE_READY) // 当前定时器状态,不是处于准备状态则返回
{
return HAL_ERROR;
}
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY; // 设置当前为忙碌状态
/* Enable the TIM Update interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); // 开启定时器更新中断
/* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
// 启用外设,除非在触发模式,其中启用是自动完成触发
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
__HAL_TIM_ENABLE(htim);
}
}
else
{
__HAL_TIM_ENABLE(htim);
}
/* Return function status */
return HAL_OK;
}
中断流程,中断函数保存在Core/Src/stm32f1xx_it.c中
// 定时器6总中断
void TIM6_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
-> if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET) // 判断是否为更新中断
{
if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET) // 获取中断源也就是timer6
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE); // 清除中断标志位
HAL_TIM_PeriodElapsedCallback(htim); // 调用更新中断处理函数,这函数为__weak弱函数,可重构
}
}
}