目录
3、中断延迟发布任务初始化 OS_IntQTaskInit()
一、异常与中断的基本概念
异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出错,重则会导致系统毁灭性瘫痪。所以正确地处理异常,避免错误的发生是提高软件鲁棒性(稳定性)非常重要的一环,对于实时系统更是如此。
异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。 异常通常可以分成两类:同步异常和异步异常。由内部事件(像处理器指令运行产生的事件)引起的异常称为同步异常,例如造成被零除的算术运算引发一个异常,又如在某些处理器体系结构中,对于确定的数据尺寸必须从内存的偶数地址进行读和写操作。从一个奇数内存地址的读或写操作将引起存储器存取一个错误事件并引起一个异常( 称为校准异常) 。
异步异常主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。 同步异常不同于异步异常的地方是事件的来源,同步异常事件是由于执行某些指令而从处理器内部产生的, 而异步异常事件的来源是外部硬件装置。 例如按下设备某个按钮产生的事件。同步异常与异步异常的区别还在于,同步异常触发后,系统必须立刻进行处理而不能够依然执行原有的程序指令步骤;而异步异常则可以延缓处理甚至是忽略,例如按键中断异常,虽然中断异常触发了,但是系统可以忽略它继续运行(同样也忽略了相应的按键事件)。
中断,中断属于异步异常。 所谓中断是指中央处理器 CPU 正在处理某件事的时候,外部发生了某一事件,请求 CPU 迅速处理, CPU 暂时中断当前的工作,转入处理所发生的事件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。
中断能打断任务的运行,无论该任务具有什么样的优先级,因此中断一般用于处理比较紧急的事件,而且只做简单处理,例如标记该事件, 在使用 uCOS 系统时,一般建议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务,处理任务再做具体处理。
通过中断机制,在外设不需要 CPU 介入时, CPU 可以执行其他任务,而当外设需要CPU 时通过产生中断信号使 CPU 立即停止当前任务转而来响应中断请求。这样可以使CPU 避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以及执行效率。
此处读者要知道一点, uCOS 源码中有许多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时,任何使用了操作系统的中断响应都不会比裸机快。比如,某个时候有一个任务在运行中,并且该任务部分程序将中断屏蔽掉,也就是进入临界段中,这个时候如果有一个紧急的中断事件被触发,这个中断就会被挂起,不能得到及时响应,必须等到中断开启才可以得到响应,如果屏蔽中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。操作系统的中断在某些时候会产生必要的中断延迟,因此调用中断屏蔽函数进入临界段的时候,也需快进快出。
UCOS 的中断管理支持:
- 开/关中断。
- 恢复中断。
- 中断使能。
- 中断屏蔽。
- 中断嵌套。
- 中断延迟发布。
1、中断的介绍
与中断相关的硬件可以划分为三类:外设、中断控制器、 CPU 本身。
外设:当外设需要请求 CPU 时,产生一个中断信号,该信号连接至中断控制器。
中断控制器:中断控制器是 CPU 众多外设中的一个,它一方面接收其他外设中断信号的输入,另一方面,它会发出中断信号给 CPU。可以通过对中断控制器编程实现对中断源的优先级、触发方式、打开和关闭源等设置操作。在 Cortex-M 系列控制器中常用的中断控制器是 NVIC(内嵌向量中断控制器 Nested Vectored Interrupt Controller) 。
CPU: CPU 会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。NVIC 最多支持 240 个中断,每个中断最多 256 个优先级。
2、和中断相关的名词解释
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
中断请求:“紧急事件”需向 CPU 提出申请,要求 CPU 暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断请求。
中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
中断处理程序:当外设产生中断请求后, CPU 暂停当前的任务,转而响应中断申请,即执行中断处理程序。
中断触发:中断源发出并送给 CPU 控制信号,将中断触发器置“1”,表明该中断源产生了中断,要求 CPU 去响应该中断, CPU 暂停当前任务,执行相应的中断处理程序。
中断触发类型:外部中断申请通过一个物理信号发送到 NVIC,可以是电平触发或边沿触发。
中断向量:中断服务程序的入口地址。
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
临界段:代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
二、中断的运作机制
当中断产生时,处理机将按如下的顺序执行:
- 保存当前处理机状态信息
- 载入异常或中断处理函数到 PC 寄存器
- 把控制权转交给处理函数并开始执行
- 当处理函数执行完成时,恢复处理器状态信息
- 从异常或中断中返回到前一个程序执行点
中断使得 CPU 可以在事件发生时才给予处理,而不必让 CPU 连续不断地查询是否有相应的事件发生。通过两条特殊指令:关中断和开中断可以让处理器不响应或响应中断,在关闭中断期间,通常处理器会把新产生的中断挂起,当中断打开时立刻进行响应,所以会有适当的延时响应中断,故用户在进入临界区的时候应快进快出。
中断发生的环境有两种情况:在任务的上下文中,在中断服务函数处理上下文中。
- 任务在工作的时候,如果此时发生了一个中断,无论中断的优先级是多大,都会打断当前任务的执行,从而转到对应的中断服务函数中执行。
- 在执行中断服务例程的过程中,如果有更高优先级别的中断源触发中断,由于当前处于中断处理上下文环境中,根据不同的处理器构架可能有不同的处理方式,比如新的中断等待挂起直到当前中断处理离开后再行响应;或新的高优先级中断打断当前中断处理过程,而去直接响应这个更高优先级的新中断源。后面这种情况,称之为中断嵌套。在硬实时环境中,前一种情况是不允许发生的,不能使响应中断的时间尽量的短。而在软件处理(软实时环境)上, uCOS 允许中断嵌套,即在一个中断服务例程期间,处理器可以响应另外一个优先级更高的中断。
三、中断延迟的概念
即使操作系统的响应很快了,但对于中断的处理仍然存在着中断延迟响应的问题,我们称之为中断延迟(Interrupt Latency) 。
中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间。也就是:系统接收到中断信号到操作系统作出响应,并完成换到转入中断服务程序的时间。也可以简单地理解为:(外部)硬件(设备)发生中断,到系统执行中断服务子程序(ISR)的第一条指令的时间。
中断的处理过程是:外界硬件发生了中断后, CPU 到中断处理器读取中断向量,并且查找中断向量表,找到对应的中断服务子程序( ISR)的首地址,然后跳转到对应的 ISR去做相应处理。这部分时间,我称之为:识别中断时间。
在允许中断嵌套的实时操作系统中,中断也是基于优先级的,允许高优先级中断抢断正在处理的低优先级中断,所以,如果当前正在处理更高优先级的中断,即使此时有低优先级的中断,也系统不会立刻响应,而是等到高优先级的中断处理完之后,才会响应。而即使在不支持中断嵌套,即中断是没有优先级的,中断是不允许被中断的,所以,如果当前系统正在处理一个中断,而此时另一个中断到来了,系统也是不会立即响应的,而只是等处理完当前的中断之后,才会处理后来的中断。此部分时间,我称其为:等待中断打开时间。
在操作系统中,很多时候我们会主动进入临界段,系统不允许当前状态被中断打断,故而在临界区发生的中断会被挂起,直到退出临界段时候打开中断。此部分时间,我称其为: 关闭中断时间。
中断延迟可以定义为,从中断开始的时刻到中断服务例程开始执行的时刻之间的时间段。中断延迟 = 识别中断时间 + [等待中断打开时间] + [关闭中断时间]。
注意:“[ ]”的时间是不一定都存在的,此处为最大可能的中断延迟时间。
此外, 中断恢复时间定义为:执行完 ISR 中最后一句代码后到恢复到任务级代码的这段时间。任务延迟时间定义为:中断发生到恢复到任务级代码的这段时间。
四、中断的应用场景
中断在嵌入式处理器中应用非常之多,没有中断的系统不是一个好系统,因为有中断,才能启动或者停止某件事情,从而转去做另一间事情。 我们可以举一个日常生活中的例子来说明,假如你正在给朋友写信,电话铃响了,这时你放下手中的笔去接电话,通话完毕再继续写信。这个例子就表现了中断及其处理的过程:电话铃声使你暂时中止当前的工作,而去处理更为急需处理的事情——接电话,当把急需处理的事情处理完毕之后,再回过头来继续原来的事情。在这个例子中,电话铃声就可以称为“中断请求”,而你暂停写信去接电话就叫作“中断响应”,那么接电话的过程就是“中断处理”。由此我们可以看出,在计算机执行程序的过程中,由于出现某个特殊情况(或称为“特殊事件” ),使得系统暂时中止现行程序,而转去执行处理这一特殊事件的程序,处理完毕之后再回到原来程序的中断点继续向下执行。
为什么说吗没有中断的系统不是好系统呢? 我们可以再举一个例子来说明中断的作用。假设有一个朋友来拜访你,但是由于不知何时到达,你只能在门口等待,于是什么事情也干不了;但如果在门口装一个门铃,你就不必在门口等待而可以在家里去做其他的工作,朋友来了按门铃通知你,这时你才中断手中的工作去开门,这就避免了不必要的等待。CPU 也是一样,如果时间都浪费在查询的事情上,那这个 CPU 啥也干不了,要他何用。在嵌入式系统中合理利用中断,能更好利用 CPU 的资源。
五、中断管理讲解
ARM Cortex-M 系列内核的中断是由硬件管理的,而 uCOS 是软件,它并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由 RTOS 的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应),只支持简单的开关中断等,所以 uCOS 中的中断使用其实跟裸机差不多的,需要我们自己配置中断,并且使能中断,编写中断服务函数,在中断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断,当然 uCOS 为了能让系统更快退出中断,它支持中断延迟发布,将中断级的发布变成任务级(在后文讲解)。
ARM Cortex-M NVIC 支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR, R0, R1, R2, R3 以及 R12 寄存器。当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样的会打断当前运行的中断服务例程,然后把老的中断服务例程上下文的 PSR, R0, R1, R2, R3 和 R12 寄存器自动保存到中断栈中。这些部分上下文寄存器保存到中断栈的行为完全是硬件行为,这一点是与其他 ARM 处理器最大的区别(以往都需要依赖于软件保存上下文)。
另外,在 ARM Cortex-M 系列处理器上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理。而在 ARM7、 ARM9 中,一般是先跳转进入 IRQ 入口,然后再由软件进行判断是哪个中断源触发,获得了相对应的中断服务例程入口地址后,再进行后续的中断处理。ARM7、 ARM9 的好处在于,所有中断它们都有统一的入口地址,便于 OS 的统一管理。而ARM Cortex-M 系列处理器则恰恰相反,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义(或在起始代码中给出)。
uCOS 在 Cortex-M 系列处理器上也遵循与裸机中断一致的方法,当用户需要使用自定义的中断服务例程时,只需要定义相同名称的函数覆盖弱化符号即可。所以, uCOS 在Cortex-M 系列处理器的中断控制其实与裸机没什么差别,不过在进入中断与退出中断的时候需要调用一下 OSIntEnter()函数与 OSIntExit()函数,方便中断嵌套管理。
六、中断延迟发布
1、中断延迟发布的概念
uC/OS-III 有两种方法处理来自于中断的事件, 直接发布(或者称为释放) 和延迟发布。通过 os_cfg.h 中的 OS_CFG_ISR_POST_DEFERRED_EN 来选择, 当设置为 0 时, uCOS 使用直接发布的方法。当设置为 1 时,使用延迟发布方法, 用户可以根据自己设计系统的应用选择其中一种方法即可。
使能中断延时发布,可以将中断级发布转换成任务级发布,而且在进入临界段时也可以使用锁调度器代替关中断,这就大大减小了关中断时间,有利于提高系统的实时性(能实 时 响 应中 断 而不 受 中 断屏 蔽 导致 响 应延 迟 ) 。 在 前面 提 到的 OSTimeTick() 、OSSemPost() 、 OSQPost() 、 OSFlagPost() 、 OSTaskSemPost() 、 OSTaskQPost() 、OSTaskSuspend()和 OSTaskResume() 等这些函数, 如果没有使用中断延迟发布, 那么调用这些函数意味着进入一段很长的临界段,也就要关中断很长时间。 在使能中断延时发布后,如果在中断中调用这些函数,系统就会将这些 post 提交函数必要的信息保存到中断延迟提交的变量中去,为了配合中断延迟, μCOS 还将创建了优先级最高(优先级为 0)的任务— —中断发布函数 OS_IntQTask,退出中断后根据之前保存的参数, 在任务中再次进行 post相关操作。这个过程其实就是把中断中的临界段放到任务中来实现,这个时候进入临界段就可以用锁住调度器的方式代替了关中断,因此大大减少了关中断的时间, 系统将 post 操作延迟了,中断延迟就是这么来的。
进入临界段的方式可以是关中断或者锁住调度器, 系统中有些变量不可能在中断中被访问,所以只要保证其他任务不要使用这些变量即可,这个时候就可以用锁调度启动的方式, 用锁住调度代替关中断,大大减少了关中断的时间, 也能达到进入临界段的目的。 中断延迟就是利用这种思想, 让本该在中断中完成的事情切换到任务中完成, 而且进入临界段的方式是锁定调度器, 这样子中断就不会被屏蔽, 系统能随时响应中断, 并且, 整个中断延迟发布的过程是不影响 post 的效果, 因为 uCOS 已经设定中断发布任务的优先级为最高,在退出中断后会马上进行 post 操作,这与在中断中直接进行 post 操作的时间基本一致。
注: 操作系统内核相关函数一般为了保证其操作的完整性,一般都会进入或长或短的临界段,所以在中断的要尽量少调用内核函数,部分 μCOS 提供的函数是不允许在中断中调用的。
在直接发布方式中, uCOS 访问临界段时是采用关中断方式。 然而,在延迟提交方式中, uCOS 访问临界段时是采用锁调度器方式。在延迟提交方式中,访问中断队列时 uCOS仍需要关中断进入临界段, 但是这段关中断时间是非常短的且是固定的。
从两个图中我们可以看出,很明显,采用中断延迟发布的效果更好,将本该在中断中的处理转变成为在任务中处理,系统关中断的时间大大降低,使得系统能很好地响应外部中断, 如果在应用中关中断时间是关键性的,应用中有非常频繁的中断源, 且应用不能接受直接发布方式那样较长的关中断时间, 推荐使用中断延迟发布方式。
2、中断队列控制块
如果使能中断延迟发布,在中断中调用内核对象发布(释放)函数,系统会将发布的内容存放在中断队列中控制块中。
3、中断延迟发布任务初始化 OS_IntQTaskInit()
在系统初始化的时候,如果我们使能了中断延迟发布,那么系统会根据我们自定义配置中断延迟发布任务的宏定义OS_CFG_INT_Q_SIZE 与OS_CFG_INT_Q_TASK_STK_SIZE 进行相关初始化。
4、中断延迟发布过程 OS_IntQPost()
如果使能了中断延迟发布,并且发送消息的函数是在中断中被调用,此时就不该立即发送消息,而是将消息的发送放在指定发布任务中,此时系统就将消息发布到租单消息队列中,等待到中断发布任务唤醒再发送消息。
5、中断延迟发布任务 OS_IntQTask()
在中断中将消息放入中断队列,那么接下来又怎么样进行发布内核对象呢?原来uCOS 在中断中只是将要提交的内核对象的信息都暂时保存起来,然后就绪优先级最高的中断延迟发布任务,接着继续执行中断,在退出所有中断嵌套后,第一个执行的任务就是延迟发布任务。
七、实现
#include <includes.h>
#include <string.h>
static OS_TCB AppTaskStartTCB; //任务控制块
OS_TCB AppTaskUsartTCB;
OS_TCB AppTaskKeyTCB;
static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; //任务堆栈
static CPU_STK AppTaskUsartStk [ APP_TASK_USART_STK_SIZE ];
static CPU_STK AppTaskKeyStk [ APP_TASK_KEY_STK_SIZE ];
extern char Usart_Rx_Buf[USART_RBUFF_SIZE];
static void AppTaskStart (void *p_arg); //任务函数声明
static void AppTaskUsart ( void * p_arg );
static void AppTaskKey ( void * p_arg );
int main (void)
{
OS_ERR err;
OSInit(&err); //初始化 uC/OS-III
/* 创建起始任务 */
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, //任务控制块地址
(CPU_CHAR *)"App Task Start", //任务名称
(OS_TASK_PTR ) AppTaskStart, //任务函数
(void *) 0, //传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_START_PRIO, //任务的优先级
(CPU_STK *)&AppTaskStartStk[0], //任务堆栈的基地址
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10, //任务堆栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE, //任务堆栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 5u, //任务可接收的最大消息数
(OS_TICK ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0, //任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
(OS_ERR *)&err); //返回错误类型
OSStart(&err); //启动多任务管理(交由uC/OS-III控制)
}
static void AppTaskStart (void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
BSP_Init(); //板级初始化
CPU_Init(); //初始化 CPU 组件(时间戳、关中断时间测量和主机名)
cpu_clk_freq = BSP_CPU_ClkFreq(); //获取 CPU 内核时钟频率(SysTick 工作时钟)
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; //根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值
OS_CPU_SysTickInit(cnts); //调用 SysTick 初始化函数,设置定时器计数值和启动定时器
Mem_Init(); //初始化内存管理组件(堆内存池和内存池表)
#if OS_CFG_STAT_TASK_EN > 0u //如果使能(默认使能)了统计任务
OSStatTaskCPUUsageInit(&err); //计算没有应用任务(只有空闲任务)运行时 CPU 的(最大)
#endif //容量(决定 OS_Stat_IdleCtrMax 的值,为后面计算 CPU
//使用率使用)。
CPU_IntDisMeasMaxCurReset(); //复位(清零)当前最大关中断时间
/* 配置时间片轮转调度 */
OSSchedRoundRobinCfg((CPU_BOOLEAN )DEF_ENABLED, //使能时间片轮转调度
(OS_TICK )0, //把 OSCfg_TickRate_Hz / 10 设为默认时间片值
(OS_ERR *)&err ); //返回错误类型
/* 创建 AppTaskUsart 任务 */
OSTaskCreate((OS_TCB *)&AppTaskUsartTCB, //任务控制块地址
(CPU_CHAR *)"App Task Usart", //任务名称
(OS_TASK_PTR ) AppTaskUsart, //任务函数
(void *) 0, //传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_USART_PRIO, //任务的优先级
(CPU_STK *)&AppTaskUsartStk[0], //任务堆栈的基地址
(CPU_STK_SIZE) APP_TASK_USART_STK_SIZE / 10, //任务堆栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_USART_STK_SIZE, //任务堆栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 50u, //任务可接收的最大消息数
(OS_TICK ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0, //任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
(OS_ERR *)&err); //返回错误类型
/* 创建 AppTaskKey 任务 */
OSTaskCreate((OS_TCB *)&AppTaskKeyTCB, //任务控制块地址
(CPU_CHAR *)"App Task Key", //任务名称
(OS_TASK_PTR ) AppTaskKey, //任务函数
(void *) 0, //传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_KEY_PRIO, //任务的优先级
(CPU_STK *)&AppTaskKeyStk[0], //任务堆栈的基地址
(CPU_STK_SIZE) APP_TASK_KEY_STK_SIZE / 10, //任务堆栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_KEY_STK_SIZE, //任务堆栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 50u, //任务可接收的最大消息数
(OS_TICK ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0, //任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
(OS_ERR *)&err); //返回错误类型
OSTaskDel ( 0, & err ); //删除起始任务本身,该任务不再运行
}
static void AppTaskUsart ( void * p_arg )
{
OS_ERR err;
CPU_SR_ALLOC();
(void)p_arg;
while (DEF_TRUE) { //任务体
OSTaskSemPend ((OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果信号量不可用就等待
(CPU_TS *)0, //获取信号量被发布的时间戳
(OS_ERR *)&err); //返回错误类型
OS_CRITICAL_ENTER(); //进入临界段,避免串口打印被打断
printf("收到数据:%s\n",Usart_Rx_Buf);
memset(Usart_Rx_Buf,0,USART_RBUFF_SIZE);/* 清零 */
OS_CRITICAL_EXIT(); //退出临界段
}
}
static void AppTaskKey ( void * p_arg )
{
OS_ERR err;
CPU_TS_TMR ts_int;
CPU_INT32U cpu_clk_freq;
CPU_SR_ALLOC();
(void)p_arg;
cpu_clk_freq = BSP_CPU_ClkFreq(); //获取CPU时钟,时间戳是以该时钟计数
while (DEF_TRUE) { //任务体
/* 阻塞任务,直到KEY1被按下 */
OSTaskSemPend ((OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果信号量不可用就等待
(CPU_TS *)0, //获取信号量被发布的时间戳
(OS_ERR *)&err); //返回错误类型
ts_int = CPU_IntDisMeasMaxGet (); //获取最大关中断时间
OS_CRITICAL_ENTER(); //进入临界段,避免串口打印被打断
printf ( "触发按键中断,最大中断时间是%dus\r\n",
ts_int / ( cpu_clk_freq / 1000000 ) );
OS_CRITICAL_EXIT(); //退出临界段
}
}
/*********************************************************************************
* @ 函数名 : KEY1_IRQHandler
* @ 功能说明: 中断服务函数
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
void KEY1_IRQHandler(void)
{
OS_ERR err;
OSIntEnter(); //进入中断
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
/* 发送任务信号量到任务 AppTaskKey */
OSTaskSemPost((OS_TCB *)&AppTaskKeyTCB, //目标任务
(OS_OPT )OS_OPT_POST_NONE, //没选项要求
(OS_ERR *)&err); //返回错误类型
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
OSIntExit();
}
/*********************************************************************************
* @ 函数名 : KEY2_IRQHandler
* @ 功能说明: 中断服务函数
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
void KEY2_IRQHandler(void)
{
OS_ERR err;
OSIntEnter(); //进入中断
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
/* 发送任务信号量到任务 AppTaskKey */
OSTaskSemPost((OS_TCB *)&AppTaskKeyTCB, //目标任务
(OS_OPT )OS_OPT_POST_NONE, //没选项要求
(OS_ERR *)&err); //返回错误类型
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
OSIntExit(); //退出中断
}
/*********************************************************************************
* @ 函数名 : DEBUG_USART_IRQHandler
* @ 功能说明: 串口中断服务函数
* @ 参数 : 无
* @ 返回值 : 无
********************************************************************************/
void DEBUG_USART_IRQHandler(void)
{
OS_ERR err;
OSIntEnter(); //进入中断
if(USART_GetITStatus(DEBUG_USART,USART_IT_IDLE)!=RESET)
{
// 关闭DMA ,防止干扰
DMA_Cmd(DEBUG_USART_DMA_STREAM, DISABLE);
// 清DMA标志位
DMA_ClearFlag(DEBUG_USART_DMA_STREAM,DMA_FLAG_TCIF2);
// 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
DMA_SetCurrDataCounter(DEBUG_USART_DMA_STREAM,USART_RBUFF_SIZE);
DMA_Cmd(DEBUG_USART_DMA_STREAM, ENABLE);
//给出信号量 ,发送接收到新数据标志,供前台程序查询
/* 发送任务信号量到任务 AppTaskKey */
OSTaskSemPost((OS_TCB *)&AppTaskUsartTCB, //目标任务
(OS_OPT )OS_OPT_POST_NONE, //没选项要求
(OS_ERR *)&err); //返回错误类型
USART_ReceiveData(DEBUG_USART); /* 清除标志位 */
}
OSIntExit(); //退出中断
}