目录
1. 异常的基本概念
异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。异常通常可以分成两类:同步异常和异步异常。
同步异常,由内部事件(像处理器指令运行产生的事件)引起的异常称为同步异常。例如造成被零除的算术运算引发一个异常,又如在某些处理器体系结构中,对于确定的数据尺寸必须从内存的偶数地址进行读和写操作。从一个奇数内存地址的读或写操作将引起存储器存取一个错误事件并引起一个异常(称为校准异常)。
异步异常,主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。例如按下设备某个按钮产生的事件。
二者的区别:
事件的来源来看:
- 同步异常事件是由于执行某些指令而从处理器内部产生的;
- 异步异常事件的来源是外部硬件装置。
反应速度:
- 同步异常触发后,系统必须立刻进行处理而不能够依然执行原有的程序指令步骤;
- 异步异常则可以延缓处理甚至是忽略。
2. 中断的基本概念
中断属于异步异常。所谓中断是指中央处理器 CPU 正在处理某件事的时候,外部发生了某一事件,请求 CPU 迅速处理,CPU暂时中断当前的工作,转入处理所发生的事件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。
中断能打断任务的运行,无论该任务具有什么样的优先级,因此中断一般用于处理比较紧急的事件,而且只做简单处理,例如标记该事件,在使用 FreeRTOS 系统时,一般建议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务,处理任务再做具体处理。
通过中断机制,在外设不需要 CPU 介入时,CPU 可以执行其他任务,而当外设需要 CPU 时通过产生中断信号使 CPU 立即停止当前任务转而来响应中断请求。这样可以使 CPU 避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以及执行效率。
与中断相关的硬件可以划分为三类:外设、中断控制器、CPU 本身。
- 外设:当外设需要请求 CPU 时,产生一个中断信号,该信号连接至中断控制器。
- 中断控制器:中断控制器是 CPU 众多外设中的一个,它一方面接收其他外设中断信号的输入,另一方面,它会发出中断信号给 CPU。可以通过对中断控制器编程实现对中断源的优先级、触发方式、打开和关闭源等设置操作。在 Cortex-M 系列控制器中常用的中断控制器是 NVIC(内嵌向量中断控制器 Nested Vectored Interrupt Controller)。
- CPU:CPU 会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。NVIC 最多支持 240个中断,每个中断最多 256 个优先级。
3. 中断相关名词介绍
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
中断请求:“紧急事件”需向 CPU 提出申请,要求 CPU 暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断请求。
中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
中断处理程序:当外设产生中断请求后,CPU 暂停当前的任务,转而响应中断申请,即执行中断处理程序。
中断触发:中断源发出并送给 CPU 控制信号,将中断触发器置“1”,表明该中断源产生了中断,要求 CPU 去响应该中断,CPU 暂停当前任务,执行相应的中断处理程序。
中断触发类型:外部中断申请通过一个物理信号发送到 NVIC,可以是电平触发或边沿触发。
中断向量:中断服务程序的入口地址。
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
临界段:代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
4. 中断运作机制
当中断产生时,处理机将按如下的顺序执行:
- 保存当前处理机状态信息
- 载入异常或中断处理函数到 PC寄存器
- 把控制权转交给处理函数并开始执行
- 当处理函数执行完成时,恢复处理器状态信息
- 从异常或中断中返回到前一个程序执行点
中断使得 CPU 可以在事件发生时才给予处理,而不必让 CPU 连续不断地查询是否有相应的事件发生。通过两条特殊指令:关中断和开中断可以让处理器不响应或响应中断,在关闭中断期间,通常处理器会把新产生的中断挂起,当中断打开时立刻进行响应,所以会有适当的延时响应中断,故用户在进入临界区的时候应快进快出。
中断发生的环境有两种情况:在任务的上下文中,在中断服务函数处理上下文中。
任务在工作的时候,如果此时发生了一个中断,无论中断的优先级是多大,都会打断当前任务的执行,从而转到对应的中断服务函数中执行:
在执行中断服务例程的过程中,如果有更高优先级别的中断源触发中断,由于当前处于中断处理上下文环境中,根据不同的处理器构架可能有不同的处理方式,比如新的中断等待挂起直到当前中断处理离开后再行响应;或新的高优先级中断打断当前中断处理过程,而去直接响应这个更高优先级的新中断源。后面这种情况,称之为中断嵌套。在硬实时环境中,前一种情况是不允许发生的,不能使响应中断的时间尽量的短。而在软件处理(软实时环境)上,FreeRTOS 允许中断嵌套,即在一个中断服务例程期间,处理器可以响应另外一个优先级更高的中断:
5. 中断延迟的概念
中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间。也就是:系统接收到中断信号到操作系统作出响应,并完成换到转入中断服务程序的时间。也可以简单地理解为:(外部)硬件(设备)发生中断,到系统执行中断服务子程序(ISR)的第一条指令的时间。
中断的处理过程是:外界硬件发生了中断后,CPU 到中断处理器读取中断向量,并且查找中断向量表,找到对应的中断服务子程序(ISR)的首地址,然后跳转到对应的 ISR去做相应处理。这部分时间,我称之为:识别中断时间。
在允许中断嵌套的实时操作系统中,中断也是基于优先级的,允许高优先级中断抢断正在处理的低优先级中断,所以,如果当前正在处理更高优先级的中断,即使此时有低优先级的中断,也系统不会立刻响应,而是等到高优先级的中断处理完之后,才会响应。而即使在不支持中断嵌套,即中断是没有优先级的,中断是不允许被中断的,所以,如果当前系统正在处理一个中断,而此时另一个中断到来了,系统也是不会立即响应的,而只是等处理完当前的中断之后,才会处理后来的中断。此部分时间,我称其为:等待中断打开时间。
在操作系统中,很多时候我们会主动进入临界段,系统不允许当前状态被中断打断,故而在临界区发生的中断会被挂起,直到退出临界段时候打开中断。此部分时间,我称其为:关闭中断时间。
6. 中断管理讲解
ARM Cortex-M 系列内核的中断是由硬件管理的,而 FreeRTOS 是软件,它并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由 RTOS 的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应),只支持简单的开关中断等,所以 FreeRTOS 中的中断使用其实跟裸机差不多的,需要我们自己配置中断并且使能中断,编写中断服务函数,在中断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断。
用户在使用过程中可以自己管理中断优先级的宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY , 它是用于配置内核中的basepri 寄存器的,当basepri 设置为某个值的时候,NVIC 不会响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候,中断优先级数值在 0、1、2、3、4 的这些中断是不受 FreeRTOS 屏蔽的,也就是说即使在系统进入临界段的时候,这些中断也能被触发而不是等到退出临界段的时候才被触发,当然,这些中断服务函数中也不能调用 FreeRTOS 提供的 API 函数接口,而中断优先级在 5 到 15 的这些中断是可以被屏蔽的,也能安全调用 FreeRTOS 提供的 API 函数接口。
7. 中断管理实验
在 FreeRTOS 中创建了两个任务分别获取信号量与消息队列,并且定义了两个按键 KEY1 与 KEY2 的触发方式为中断触发,其触发的中断服务函数则跟裸机一样,在中断触发的时候通过消息队列将消息传递给任务,任务接收到消息就将信息通过串口调试助手显示出来。而且中断管理实验也实现了一个串口的 DMA 传输+空闲中断功能,当串口接收完不定长的数据之后产生一个空闲中断,在中断中将信号量传递给任务,任务在收到信号量的时候将串口的数据读取出来并且在串口调试助手中回显。
这里我们还是使用之前的空白工程:
首先我们对按键的触发方式进行更改,更改为中断触发,这里我们用外部中断,首先配备NVIC:
static void NVIC_Configuration(void)
{
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
/* 配置NVIC为优先级组 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ; //配置中断源:按键1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7; //指定NVIC线路的抢占优先级为7
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //指定NVIC线路的响应优先级为0
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ; //配置中断源:按键2
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}
其中宏定义相关的东西如下:
#define KEY1_INT_GPIO_PORT GPIOA
#define KEY1_INT_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource0
#define KEY1_INT_EXTI_LINE EXTI_Line0
#define KEY1_INT_EXTI_IRQ EXTI0_IRQn
#define KEY1_IRQHandler EXTI0_IRQHandler
#define KEY2_INT_GPIO_PORT GPIOC
#define KEY2_INT_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
然后是对GPIO口的初始化:
/*开启时钟*/
RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE); //开启KEY1的时钟
RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_PIN, ENABLE); //开启KEY1的时钟
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
/*KEY1初始化*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/*KEY2初始化*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
以及对外部中断的配置:
/* 配置 NVIC 中断*/
NVIC_Configuration();
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE);
GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE);
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
/*KEY1初始化EXTI*/
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; //选择配置外部中断的14号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //指定外部中断线为上升沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
/*KEY2初始化EXTI*/
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; //选择配置外部中断的14号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
完整的:
//配置 IO为EXTI中断口,并设置中断优先级
void EXTI_Key_Config(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE); //开启KEY1的时钟
RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_PIN, ENABLE); //开启KEY1的时钟
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
/* 配置 NVIC 中断*/
NVIC_Configuration();
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
/*KEY1初始化*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/*KEY2初始化*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE);
GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE);
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
/*KEY1初始化EXTI*/
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE; //选择配置外部中断的14号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //指定外部中断线为上升沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
/*KEY2初始化EXTI*/
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE; //选择配置外部中断的14号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
}
然后是对中断的调整,增加DMA数据转运的功能,首先配备NVIC:
static void NVIC_Configuration(void)
{
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
/* 配置NVIC为优先级组 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 配置USART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 抢断优先级*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
/* 子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
对于串口将一下三句加入:
// 串口中断优先级配置
NVIC_Configuration();
// 开启 串口空闲IDEL 中断
USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
// 开启串口DMA接收
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
完整:
//USART GPIO 配置,工作参数配置
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 打开串口GPIO的时钟
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 打开串口外设的时钟
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 针数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校验位
USART_InitStructure.USART_Parity = USART_Parity_No ;
// 配置硬件流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(DEBUG_USARTx, &USART_InitStructure);
// 串口中断优先级配置
NVIC_Configuration();
// 开启 串口空闲IDEL 中断
USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
// 开启串口DMA接收
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
然后是对DMA的一些配置:
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;//定义结构体变量
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 开启DMA时钟
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADDRESS;// 设置DMA源地址:串口数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Usart_Rx_Buf;//存储器基地址,内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//数据传输方向,选择由外设到存储器
DMA_InitStructure.DMA_BufferSize = USART_RBUFF_SIZE;// 传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据单位
//DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // DMA模式,一次或者循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级:中
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;// 禁止内存到内存的传输
DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure);// 配置DMA通道
DMA_ClearFlag(DMA1_FLAG_TC5);// 清除DMA所有标志
DMA_ITConfig(USART_RX_DMA_CHANNEL, DMA_IT_TE, ENABLE);
// 使能DMA
DMA_Cmd (USART_RX_DMA_CHANNEL,ENABLE);
}
开始:
void Uart_DMA_Rx_Data(void)
{
BaseType_t pxHigherPriorityTaskWoken;
// 关闭DMA ,防止干扰
DMA_Cmd(USART_RX_DMA_CHANNEL, DISABLE);
// 清DMA标志位
DMA_ClearFlag( DMA1_FLAG_TC5 );
// 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
USART_RX_DMA_CHANNEL->CNDTR = USART_RBUFF_SIZE;
DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE);
/*
xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken);
*/
//给出二值信号量 ,发送接收到新数据标志,供前台程序查询
xSemaphoreGiveFromISR(BinarySem_Handle,&pxHigherPriorityTaskWoken); //释放二值信号量
//如果需要的话进行一次任务切换,系统会判断是否需要进行切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
/*
DMA 开启,等待数据。注意,如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,
中断又发来数据的话,这里不能开启,否则数据会被覆盖。有2种方式解决:
1. 在重新开启接收DMA通道之前,将LumMod_Rx_Buf缓冲区里面的数据复制到另外一个数组中,
然后再开启DMA,然后马上处理复制出来的数据。
2. 建立双缓冲,在LumMod_Uart_DMA_Rx_Data函数中,重新配置DMA_MemoryBaseAddr 的缓冲区地址,
那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。
*/
}
然后回到主函数,先把任务创建一下,首先把所需要的头文件包含一下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
/* 硬件外设头文件 */
#include "LED.h"
#include "Usart.h"
#include "Key.h"
#include "Exti.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 标准库头文件 */
#include <string.h>
老样子两个任务,两个任务句柄:
static TaskHandle_t LED_Task_Handle = NULL;/* LED任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY任务句柄 */
中间使用了信号量和消息队列,给他们也创建句柄,方便使用:
QueueHandle_t Test_Queue =NULL;
SemaphoreHandle_t BinarySem_Handle =NULL;
对于消息队列将长度和大小也规定一下:
#define QUEUE_LEN 4 /* 队列的长度,最大可包含多少个消息 */
#define QUEUE_SIZE 4 /* 队列中每个消息大小(字节) */
然后开始对任务主体的编写,二值信号量配置:
//KEY_Task任务主体
static void KEY_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdPASS == xReturn)
{
printf("\r\n收到数据:%s\r\n",Usart_Rx_Buf);
memset(Usart_Rx_Buf,0,USART_RBUFF_SIZE);/* 清零 */
Toggle_LED_R();
}
}
}
消息队列配置:
//LED_Task任务主体
static void LED_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
uint32_t r_queue; /* 定义一个接收消息的变量 */
while (1)
{
/* 队列读取(接收),等待时间为一直等待 */
xReturn = xQueueReceive( Test_Queue, /* 消息队列的句柄 */
&r_queue, /* 发送的消息内容 */
portMAX_DELAY); /* 等待时间 一直等 */
if(pdPASS == xReturn)
{
printf("\r\n触发中断的是 KEY%d\r\n",r_queue);
}
else
{
printf("数据接收出错!\r\n");
}
Toggle_LED_R();
}
}
任务创建:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if(NULL != Test_Queue)
printf("Test_Queue消息队列创建成功!\r\n");
/* 创建 BinarySem */
BinarySem_Handle = xSemaphoreCreateBinary();
if(NULL != BinarySem_Handle)
printf("BinarySem_Handle二值信号量创建成功!\r\n");
/* 创建LED_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LED_Task任务成功!\r\n");
/* 创建KEY_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */
(const char* )"KEY_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建KEY_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
上面我们已经对两个按键进行了中断初始化操作,下面我们开始对其按下后进行配置,首先声明一些函数:
/* 声明引用外部队列 & 二值信号量 */
extern QueueHandle_t Test_Queue;
extern SemaphoreHandle_t BinarySem_Handle;
static uint32_t send_data1 = 1;
static uint32_t send_data2 = 2;
配置按键KEY1的中断服务函数,用来中断的嵌套:
void KEY1_IRQHandler(void)
{
BaseType_t pxHigherPriorityTaskWoken;
//确保是否产生了EXTI Line中断
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
/* 将数据写入(发送)到队列中,等待时间为 0 */
xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
&send_data1,/* 发送的消息内容 */
&pxHigherPriorityTaskWoken);
//如果需要的话进行一次任务切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
KEY2同理:
void KEY2_IRQHandler(void)
{
BaseType_t pxHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
/* 将数据写入(发送)到队列中,等待时间为 0 */
xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
&send_data2,/* 发送的消息内容 */
&pxHigherPriorityTaskWoken);
//如果需要的话进行一次任务切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
然后对中断的服务函数进行配置:
void DEBUG_USART_IRQHandler(void)
{
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
{
Uart_DMA_Rx_Data(); /* 释放一个信号量,表示数据已接收 */
USART_ReceiveData(DEBUG_USARTx); /* 清除标志位 */
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
至此所有配置完成,我们来看一下运行结果:
完整工程: