第一节:STM32中断处理体系结构:
1. 中断处理路径:
2.NVIC中断控制器的中断优先级:
2.1 中断号:
在NVIC中对于硬件产生的任何一个中断都分配了一个中断号,中断号是一个唯一的标识符,用于识别每个外设设备的中断。NVIC使用中断号来配置中断的优先级和使用的状态。如在F103数据手册上的介绍:
(在向量表中列出的 "位置"(Position)实际上就是中断号(IRQ number)。这些中断号用于在嵌套向量中断控制器(NVIC)中配置和管理中断)
2.2 中断优先级组成:
NVIC中对中断优先级进行了分组:优先级分别有两部分组成:抢占优先级+ 子优先级
抢占优先级:决定了是否可以抢占。
子优先级:决定了优先级的处理顺序。
(中断的分组优先级的抢占优先级与子优先级大部分可由用户自行配置。如果未配置则使用默认值,数据越小,优先级越高。)
例如:当两个相同的分组优先相同,子优先不同的中断,不会发生抢占,而是按子优先级顺序依次执行。
3.中断处理例程ISR:
对中断进行响应的函数,又称中断服务例程,是CPU中断当前任务保存现场后去执行的函数。
在中断处理例程中要执行一些实时性高的操作,不可以出现阻塞。也不允许在ISR使用可能出现阻塞的操作。
对于每一个中断号,MCU的启动文件startup_stmf103c8t6.s的汇编启动文中已经定义好了相应的ISR中断全程函数的句柄。
在启动文件中定义两部分中断的ISR,一部分是系统内置的中断服务例程的句柄,另一部则是外部中断源的中例ISR,大多也都是一些外设中断源的ISR句柄。
4.__weak弱函数,可实现的函数,是一种函数回调的方式。
__weak函数修饰符是ARM编译器中的一种语法形式。此函数可以实新实现,类似于C++虚函数。
本质是个函数回调。在代码中重新实现即可。编译时会直接编译重新实现的函数。
重点一句话:以上讲解了很多的概念,但是在开发中,只需要搞清楚与中断事件对应的ISR的回调的__weak函数,重新实现相关的ISR逻辑即可,注册ISR的回调的函数中不可以有阻塞。
5.HAL库快速配置GPIO引脚为中断复用引脚:
1. 看电路图:
使用cubeMX快速配置引脚复用,使用EXTI14的NVIC中的中断功能。
6.重写__weak函数,实现ISR逻辑:实现按键响应的功能:
在小彩灯的驱动文件中,重写相关中断句柄的回调的_weak函数即可实现中断控制的逻辑:
#include "color_led_driver.h"
#include "main.h"
//全采led灯的驱动:
void color_led_set(uint8_t red, uint8_t green, uint8_t blue)
{
//红灯:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, (GPIO_PinState)!red);
//绿灯:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, (GPIO_PinState)!green);
//蓝灯:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, (GPIO_PinState)!blue);
}
//外部按键通过中断控制小彩灯驱动:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//实现功能:就是通过按键key是否按下,来控制小彩灯:
if(GPIO_Pin == GPIO_PIN_14)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14))//抬起
{
color_led_set(0,0,0);
}else{//按下:
color_led_set(1,0,1);
}
}
}
第二节、FreeRTOS中对中断上下文的处理:
由于材料的物理特性及电学特性,当按键按下的瞬间,会短时间出现电平的高低变化,如图所示:
1. FreeRTOS软件定时器进行延时消抖:
1. 简述FreeRTOS定时器:
在RTOS调度启动时,会自动创建一个空闲idle任务,与一个定时器守护任务。那么定时器的功能就是由这个定时器守护任务执行相应的回调函数实现的。
软件定时器有一个定时周期,还有一个回调函数。根据回调函数执行的频度,有两种类型的软件定时器
单次定时器(one-shot timer),即回调函数被执行一次后,定时器停止工作。
周期定时器(periodic timer),回调函数会被循环执行,定时器一直工作。
2.定时器有休眠与运行两种状态:
休眠状态:
定时器使用xTimerCreate创建后就处于休眠状态,单次定时器执行一次回调函数后进入休眠状态。定时器使用函数xTimerStop停止后进行休眠状态。
运行状态:
处于运行状态的定时器在超时时间到达后就会执行其回调函数,不定单次定时器,还是周期定时器。
定时器在以下几种状态下处于运行状态。
使用函数xTimerStart启动后,定时器进行运行状态。
定时器在运行状态时,被执行函数xTimerReset复位起始时间后继续处于运行状态。
软件定时器的各种操作实际上是在FreeRTOS系统的定时器守护任务中完成的。
3.软件定时器的机制与使用配置:
在用户任务里执行的各种定时器操作函数都是通过一个队列发送给这个定时器守护任务的,所以这个执行同步的中间的队列,也被称为定时器指令队列,定时器守护任务读取定时器指令队列中的指令,然后执行相庆的操作:如图所示:
为了保证守护定时器任务可以得到时间片,最好配置守护任务的调度优先级高于用户任务的优先级。
同时,如果要使用软件定时器,需要在进行如下配置(官方文档介绍)
即需要在FreeRTOSConfig.h中进行以上宏定义,并指定合适的值。
4. 软件定时器常用API接口函数:
1. 动态创建定时器
timers.h
TimerHandle_t xTimerCreate
( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
功能:创建一个新的软件定时器实例, 并返回一个可以引用定时器的句柄。
参数:
pcTimerName 分配给定时器的可读文本名称。 这样做纯粹是为了协助 调试。
xTimerPeriod
定时器的周期。 以 tick 为单位指定此周期,
宏 pdMS_TO_TICKS() 可用于将以毫秒为单位指定的时间转换为以 tick 为单位指定的时间。
uxAutoReload : pdTRUE重新加载,pdFALSE执行一次结束。
pvTimerID 分配给正在创建的定时器的标识符。传递给回调函数的参数。
pxCallbackFunction 定时器到期时调用的函数
返回值:
如果定时器创建成功, 则返回新创建的定时器的句柄。失败返回NULL;
当于此动态创建定时器,也有静态创建定时器,详阅官方文档。
2.启动定时器阻塞版xTimerStart与非阻塞版xTimerStartFromISR
1. BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xBlockTime );
功能:启动定时器,在队列满后会阻塞当前调用任务。
参数:
xTimer 正在启动/重新启动的定时器的句柄。
xBlockTime 在调用 xTimerStart() 时队列已满的情况下,
指定调用任务应保持阻塞状态 以等待启动命令成功发送到定时器命令队列的时间 (以 tick 为单位)。
如果在 启动 RTOS 调度器之前调用 xTimerStop(),则会忽略 xBlockTime
成功返回pdPASS,失败返回pdFAIL.
2. BaseType_t xTimerStartFromISR(TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken);
功能:可从中断服务例程调用的 xTimerStart() 的非阻塞版本
参数:
xTimer 正在启动/重新启动的定时器的句柄
pxHigherPriorityTaskWoken : 如果守护进程的优先级高于当前任务会返回pdTRUE,如果为pdTRUE,
那么应在中断退出前执行上下文切换,这样可以让高优先级任度马上得到执行。
可以调用内核函数portYIELD_FROM_ISR(xHigherPriorityTaskWoken);出让时间片。
成功返回pdPASS,失败返回pdFAIL
3.定时器运行中重置定时器函数xTimerReset 与xTimerResetFromISR
timers.h
1.BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xBlockTime );
功能介绍:xTimerReset() 重新启动先前使用 xTimerCreate() API 函数创建的定时器。
如果定时器已经启动且已处于活动状态, 那么 xTimerReset() 会使定时器 重新评估其到期时间,
因此到期时间与 xTimerReset() 的调用时间有关。
如果定时器处于休眠状态,则 xTimerReset() 具有与 xTimerStart() API 函数等效的功能。
参数:
xTimer 正在重置/启动/重新启动的定时器的句柄。
xBlockTime 队列满中的阻塞时间
返回值:
成功返回pdPASS, 失败返回pdFAIL
2.BaseType_t xTimerResetFromISR(TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken);
功能:与之reset阻塞版相同。
参数:与start一样的参数功能
返回值:与start一样的返回值。
4.删除定时器:xTimerDelete:
BaseType_t xTimerDelete( TimerHandle_t xTimer,
TickType_t xBlockTime );
功能:删除正在使用的定时器,并回收内存。
成功:返回pdPASS,失败返回pdTRUE
5.定时器消抖实验实例演示:
配置FreeRTOSConfig.h:
#define configUSE_QUEUE_SETS 1
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY configMAX_PRIORITIES-1
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 256
freeRTOS.c:
//添加定时器对象,并动态创建定时器:
TimerHandle_t mykeyTimer;
mykeyTimer = xTimerCreate("mykeytimer",10, pdFALSE, NULL, mykeyTimer_function);
configASSERT(mykeyTimer);
key_app.c:
#include "key_app.h"
#include "timers.h"
extern TimerHandle_t mykeyTimer;
//外部按键通过中断控制小彩灯驱动:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//实现功能:就是通过按键key是否按下,来控制小彩灯:
if(GPIO_Pin == GPIO_PIN_14)
{
BaseType_t status = pdFALSE;
//复位定时器:
xTimerResetFromISR(mykeyTimer,&status);
if(status == pdTRUE)
{
portYIELD_FROM_ISR(status);
}
}
}
//超时处理函数:
void mykeyTimer_function( TimerHandle_t xTimer )
{
if(isKeyPress())
{
color_led_set(1,0,1);
}
else{
color_led_set(0,0,0);
}
}