STM32单片机uCOS-Ⅲ系统11 中断管理
目录
一、异常与中断的基本概念
1、中断的介绍
2、和中断相关的名词解释
二、中断的运作机制
三、中断延迟的概念
四、中断的应用场景
五、中断管理讲解
六、中断延迟发布
1、中断延迟发布的概念
2、中断队列控制块
3、中断延迟发布任务初始化 OS_IntQTaskInit()
4、中断延迟发布过程 OS_IntQPost()
5、中断延迟发布任务 OS_IntQTask()
七、实现
一、异常与中断的基本概念
异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出错,重则会导致系统毁灭性瘫痪。所以正确地处理异常,避免错误的发生是提高软件鲁棒性(稳定性)非常重要的一环,对于实时系统更是如此。
异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。 异常通常可以分成两类:同步异常和异步异常。由内部事件(像处理器指令运行产生的事件)引起的异常称为同步异常,例如造成被零除的算术运算引发一个异常,又如在某些处理器体系结构中,对于确定的数据尺寸必须从内存的偶数地址进行读和写操作。从一个奇数内存地址的读或写操作将引起存储器存取一个错误事件并引起一个异常( 称为校准异常) 。
异步异常主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。 同步异常不同于异步异常的地方是事件的来源,同步异常事件是由于执行某些指令而从处理器内部产生的, 而异步异常事件的来源是外部硬件装置。 例如按下设备某个按钮产生的事件。同步异常与异步异常的区别还在于,同步异常触发后,系统必须立刻进行处理而不能够依然执行原有的程序指令步骤;而异步异常则可以延缓处理甚至是忽略,例如按键中断异常,虽然中断异常触发了,但是系统可以忽略它继续运行(同样也忽略了相应的按键事件)。
中断,中断属于异步异常。 所谓中断是指中央处理器 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(); //退出中断}
相关文章:
STM32单片机uCOS-Ⅲ系统11 中断管理
目录 一、异常与中断的基本概念 1、中断的介绍 2、和中断相关的名词解释 二、中断的运作机制 三、中断延迟的概念 四、中断的应用场景 五、中断管理讲解 六、中断延迟发布 1、中断延迟发布的概念 2、中断队列控制块 3、中断延迟发布任务初始化 OS_IntQTaskInit() 4…...
CTF【WEB】学习笔记1号刊
Kali的小工具箱 curl www.xxx.com:查看服务器响应返回的信息 curl -I www.xxx.com:查看响应的文件头 一、cmd执行命令 ipconfig:ip地址配置等; 二、 Kali操作 1.sudo su; 2.msfconsole 3.search ms17_010 永恒之蓝ÿ…...
cpp-友元
理解 C 中的友元(Friend) 在 C 语言中,封装(Encapsulation) 是面向对象编程的重要特性之一。它允许类将数据隐藏在私有(private)或受保护(protected)成员中,…...
Spring AOP 核心概念与实践指南
第一章:AOP 核心概念与基础应用 1.1 AOP 核心思想 面向切面编程:通过横向抽取机制解决代码重复问题(如日志、事务、安全等)核心优势:不修改源代码增强功能,提高代码复用性和可维护性 1.2 基础环境搭…...
利用ffmpeg库实现音频Opus编解码
一、编译与环境配置 libopus库集成 需在编译FFmpeg时添加--enable-libopus参数,编译前需先安装libopus源码并配置动态库路径。最新FFmpeg 7.1版本默认支持Opus的浮点运算优化和VBR/CVBR模式。 多平台兼容性 Opus支持Windows/Linux/macOS平台࿰…...
深入理解指针(1)(C语言版)
文章目录 前言一、内存和地址1.1 内存1.2 究竟该如何理解编址 二、指针变量和地址2.1 取地址操作符&2.2 指针变量和解引用操作符*2.2.1 指针变量2.2.2 如何拆解指针类型2.2.3 解引用操作符 2.3 指针变量的大小 三、指针变量类型的意义3.1 指针的解引用3.2 指针-整数3.3 voi…...
计算机网络——通信基础和传输介质
物理层任务:实现相邻节点之间比特(0或1)的传输 到了数据链路层之后,它会以帧为单位,把若干个比特交给物理层,物理层需要把这些比特信息转化成信号,在物理传输媒体上进行传输 通信基础基本概念 信…...
【橘子网络】关于网络分层以及协议的全局讲解
一、网络设备 1、硬件网络设备 1.1、主机(host) 主机的定义比较广泛,所有的接收流量或者发送流量的设备都可以被称之为主机。可以是电脑,手机,服务器。在当今云服务大行其道的局面下,各种云设备也可以被称之为主机。 基于这个…...
macOS 使用 enca 识别 文件编码类型(比 file 命令准确)
文章目录 macOS 上安装 enca基本使用起因 - iconv关于 enca安装 Encaenca & enconv 其它用法 macOS 上安装 enca brew install enca基本使用 enca filepath.txt示例 $ enca 动态规划算法.txt [0] Simplified Chinese National Standard; GB2312CRLF line terminat…...
MySQL 字符集
目录 字符集的基本概念 常见MySQL字符集 ascii(单字节字符集) latin1(单字节字符集) utf8(多字节字符集) utf8mb4(多字节字符集) MySQL默认字符集 MySQL字符集的层次级别 服务器级别 数据库级别 表级别 列级别 连接字符集 字符集是计算机科学中的一个重要概念&…...
Linux shell脚本3-if语句、case语句、for语句、while语句、until语句、break语句、continue语句,格式说明及程序验证
目录 1.if 控制语句 1.1 if 语句格式 1.2 程序验证 2.case语句 2.1case语句格式 2.2程序验证 2.2.1 终端先执行程序,在输入一个数 2.2.2 终端执行程序时同时输入一个预设变量 2.2.3 case带有按位或运算和通配符匹配 3.for语句 3.1for语句格式 3.2程序验…...
基于虚拟知识图谱的语义化决策引擎
在数字化转型浪潮中,企业数据资产的价值释放面临两大挑战:海量异构数据的整合困局与业务-技术语义鸿沟。本文解析飞速创软灵燕智能体平台的创新解决方案——通过构建业务语义驱动的虚拟知识图谱系统,实现企业数据的智能认知与决策赋能。 一、…...
Unity Shader 的编程流程和结构
Unity Shader 的编程流程和结构 Unity Shader 的编程主要由以下三个核心部分组成:Properties(属性)、SubShader(子着色器) 和 Fallback(回退)。下面是它们的具体作用和结构: 1. Pr…...
C++ 继承
目录 一、继承的概念与定义 1.1 继承的概念 1.2 继承的定义 1.2.1 语法 1.2.2 继承关系和访问限定符 1.2.3 继承基类成员访问方式的变化 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派生类的默认成员函数 五、C11 final 六、继承与友元 七、继承与静态成…...
XSS Game(DOM型) 靶场 通关
目录 靶场网址 Ma Spaghet! 分析 解题 Jefff 分析 解题 方法一 方法二 Ugandan Knuckles 分析 解题 Ricardo Milos 分析 解题 Ah Thats Hawt 分析 解题 方法一 方法二 Ligma 分析 解题 Mafia 分析 解题 方法一:构造函数 方法二…...
XSS基础靶场练习
目录 1. 准备靶场 2. PASS 1. Level 1:无过滤 源码: 2. level2:转HTML实体 htmlspecialchars简介: 源码 PASS 3. level3:转HTML深入 源码: PASS 4. level4:过滤<> 源码: PASS: 5. level5:过滤on 源码…...
leetcode-200.岛屿数量
首先,想要找岛,肯定是要逐个遍历的,否则肯定会漏岛。 其次,我怎么知道两个点是否属于一个岛?只有一个方法,我踏上一个岛的某个点时,我就分别往四周走,且把当前地块毁掉,就…...
Linux | ubuntu安装 SSH 软件及测试工具
01 windows 要怎么和 ubuntu 互传文件呢,我们可以使用 ssh 软件。 终端输入 sudo apt-get install openssh-server ,输入登录 Ubuntu 用户的密码,这里我们输入 y 确认安装。如下图所示。 接着继续改 ssh 配置文件,因为 ssh 默认…...
组件日志——etcd
目录 一、简介 二、安装【Ubuntu】 安装etcd 安装CAPI 三、写一个示例 3.0写一个示例代码 3.1获取一个etcd服务 3.2获取租约(写端操作) 3.3使用租约(写端操作) 3.4销毁租约(写端操作) 3.5获取etcd服务中的服务列表(读端操作) 3.6监听状态变化(读端操作) 一、简介 Et…...
search_fields与filterset_fields的使用
在Django中,search_fields 和 filterset_fields 可以在视图类中使用,尤其是在 Django REST Framework (DRF) 中。它们分别用于实现搜索和过滤功能。以下是它们在视图类中的具体使用方法。 1. search_fields 在视图类中的使用 search_fields 是 DRF 中 S…...
SQLite Delete 语句详解
SQLite Delete 语句详解 SQLite 是一种轻量级的数据库管理系统,广泛应用于移动设备、嵌入式系统和服务器端应用。在数据库管理中,删除数据是一项基本操作。SQLite 提供了强大的删除功能,本文将详细介绍 SQLite 的 Delete 语句及其用法。 1.…...
通往自主智能之路:探索自我成长的AI
1. 引言:当前AI范式与自我成长智能的愿景 当前的人工智能领域在很大程度上由大型语言模型(LLM)的卓越能力所定义。这些模型,例如OpenAI的GPT系列和谷歌的BERT,已经展示了前所未有的理解和生成类人文本的能力。LLM通常…...
[自动化] 【八爪鱼】使用八爪鱼实现CSDN文章自动阅读脚本
在CSDN上,文章的阅读量往往是衡量内容影响力的一个重要指标。为了测试自动化手段能否提高阅读数,我尝试使用网页自动化工具来模拟人工阅读某个ID的文章。 1. 网页自动化的常见方案 谈到网页自动化,Selenium 是一个最常见的选择。它可以通过…...
Kafka消息序列化深度革命:构建高性能、高安全的自定义编码体系
一、突破默认序列化的桎梏 1.1 原生序列化器的致命缺陷 Kafka默认提供的StringSerializer/ByteArraySerializer在复杂场景下暴露三大痛点: 类型安全黑洞:字节流缺乏元数据描述,消费端解析如履薄冰版本兼容困境:数据结构变更导致…...
2025年如何避免使用验证码求解器时被IP封禁
引言 2025年,验证码求解器已成为自动化网络抓取和其他在线流程的关键工具。然而,自动化用户面临的一个常见挑战是IP封禁。当网站检测到自动化活动时,通常会阻止发出请求的IP地址,导致验证码挑战无法解决。本文将探讨使用验证码求…...
前端 AI IDE应用优缺点
嗯,用户问了三个前端面试的问题,我需要仔细分析每个问题,并结合提供的搜索结果来给出全面且准确的回答。 第一个问题是关于开发工具Cursor或Vim的Windurf在开发中的占比以及它们的缺点。从搜索结果里看,网页3提到了Trae对标Curso…...
银河麒麟桌面版包管理器(二)
APT包管理器 APT是Debian及其派生系统的包管理器,构建在dpkg之上,以其强大的依赖性处理能力和丰富的软件仓库而闻名。APT具有自动解决依赖关系、提供易于使用的命令行工具(如apt-get、apt-cache等),以及稳定的软件更新…...
Leetcode 3494. Find the Minimum Amount of Time to Brew Potions
Leetcode 3494. Find the Minimum Amount of Time to Brew Potions 1. 解题思路2. 代码实现 题目链接:3494. Find the Minimum Amount of Time to Brew Potions 1. 解题思路 这道题虽说算是搞定了,通过了全部的测试样例,不过还是很暴力的求…...
制作Oracle11g Docker 镜像
基于Linux系统,宿主主机要设置如下环境变量,oracle为64位版本 dockerfile中需要的数据库安装包可从csdn下载内找到 #!/bin/bash # 在宿主机上运行以设置Oracle所需的内核参数 # 这些命令需要root权限cat > /etc/sysctl.d/99-oracle.conf << EO…...
rocky linux下载软件
一、配置国内镜像源加速下载 Rocky Linux 默认使用国外软件源,国内用户可通过替换为阿里云镜像提升下载速度: 备份原配置文件: cp -r /etc/yum.repos.d /etc/yum.repos.d.backup 修改镜像源: sed -e s|^mirrorlist|#mirrorli…...
JVM的组成--运行时数据区
JVM的组成 1、类加载器(ClassLoader) 类加载器负责将字节码文件从文件系统中加载到JVM中,分为:加载、链接(验证、准备、解析)、和初始化三个阶段 2、运行时数据区 运行时数据区包括:程序计数…...
SpringBoot中安全的设置阿里云日志SLS的accessKey
众所周知,阿里云的服务都是基于accesskeyId和accesskeySecret来进行身份鉴权的,但唯独日志因为需要写入到.xml文件里对于accesskeyId和accesskeySecret需要进行一定程度的改进,尤其是使用了jasypt进行加密的参数传递进去logback.xml更是会遇到需要对参数进行解密的问题,而官网只…...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例11,TableView15_11带分页的导出表格示例
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
C++多线程编程:从创建到管理的终极指南
在高性能计算时代,掌握多线程编程是提升程序效率的必修课!本文将手把手教你如何用C++11标准库轻松创建和管理线程,告别单线程的“龟速”,让代码跑出多核CPU的性能! 一、多线程为何重要? 充分利用多核CPU:现代计算机普遍支持多核并行,多线程可让程序性能指数级提升。提升…...
人工智能算法基础
基础算法 排序查找线性结构树散列图堆栈 机器学习算法 定义:数据算法 流程:数据收集与预处理、特征选择、训练和测试模型、模型的评估。 监督学习 定义:是从给定的训练数据集中学习出一个函数,当新的数据到来时,可…...
Normal distribution (正态分布)
Normal distribution {正态分布} 1. Normal distribution (正态分布) Gaussian distribution (高斯分布)1.1. Probability density function (概率密度函数)1.2. Standard normal distribution (标准正态分布)1.3. Cumulative distribution function (累积分布函数) 2. 正态分…...
企业级前端架构设计与实战
一、架构设计核心原则 1.1 模块化分层架构 典型目录结构: src/├── assets/ # 静态资源├── components/ # 通用组件├── pages/ # 页面模块├── services/ # API服务层├── store/ # 全局状态管理├── uti…...
数据模型,数据建模,组件,核心价值,使用,意义
数据模型 一组由符号,文本组成的集合, 用以准确表达信息景观, 达到有效交流,沟通的目的 数据建模 是发现,分析和确定数据需求的过程,是一种称为数据模型的精确形式表示和传递这些需求 数据模型的组件 实体, 关系, 属性和域 数据模型的核心价值 交流性 精确性 数据模型的…...
JavaScript 比较运算符
JavaScript 比较运算符 一、基础比较运算符类型 运算符名称示例核心特性==宽松相等"5" == 5 → true隐式类型转换===严格相等"5" === 5 → false类型+值双重校验!=宽松不等null != 0 → true等效于 !(a == b)!==严格不等5 !== "5" → true类型或…...
AI Agent战国时代:Manus挑战者的破局之道与技术博弈
随着Manus引爆通用型AI Agent的"手脑协同"革命,全球AI Agent赛道进入技术竞速期。Flowith、UI-TARS、LangManus等新势力通过差异化路径重构市场格局,背后折射出开源生态、本土化创新与跨模态交互的深层技术博弈。本文结合行业权威报告与公开技…...
linux--时区查看和修改
查看当前时间和时区: 打开终端,输入以下命令查看当前的日期和时间设置: timedatectl修改时区: 使用 timedatectl 命令来修改时区: sudo timedatectl set-timezone <时区>例如,设置时区为北京时间(中国标准时间&a…...
个人博客系统 --- 测试报告
一、项目功能介绍 该项目由:登录模块、博客首页模块、博客详情页模块、博客编辑页模块四个功能模块组成。 该系统实现了个人博客的保存以及记录了发布日期、时间、发布人等信息。 二、测试内容与测试用例 我们需要对该系统进行功能测试,界面测试&…...
ESP32S3基于FreeRTOS实时操作系统控制舵机
这段代码是一个基于ESP32的舵机控制示例,通过MCPWM模块配置定时器、操作符、比较器和发生器,生成特定脉冲宽度的PWM信号,控制舵机在 -60度到60度之间以2度为步长往复转动。 1. 源码部分 #include "freertos/FreeRTOS.h" #include…...
【vue的some和filter】
在 Vue 中,some 和 filter 是两种不同的数组方法,分别用于处理数据筛选和条件判断。以下是它们在 Vue 中的具体用法和区别: 一、filter 方法 作用:对数组进行过滤,返回符合条件的新数组。 使用场景:常用于…...
【LC插件开发】基于Java实现FSRS(自由间隔重复调度算法)
😊你好,我是小航,一个正在变秃、变强的文艺倾年。 🔔本文讲解【LC插件开发】基于Java实现FSRS(自由间隔重复调度算法),期待与你一同探索、学习、进步,一起卷起来叭! 目录…...
jupyter 操作相关内容
1、jupyter界面美化 pip3 install jupyterthemes查看可用主题 jt -l推荐两个 jt -t grade3 -f consolamono -fs 140 -tfs 13 -nfs 115 -ofs 14 -cellw 90% -Tjt -t chesterish -f consolamono -fs 140 -altp -tfs 13 -nfs 115 -ofs 14 -cellw 90% -T...
【数据结构】单链表
目录 一、什么是链表?1、 定义2、链表的分类 二、无头单向非循环链表1、结构2、单链表数据的打印3、创建结点并初始化4、尾插5、头插6、尾删7、头删8、查找9、在指定位置pos之前插入数据10、在指定位置pos之后插入数据11、删除pos结点12、删除pos之后的结点13、销毁…...
UnoCSS极速入门:下一代原子化CSS引擎实战指南
文章目录 一、什么是UnoCSS?二、环境配置(Vite示例)三、核心使用模式3.1 自定义规则3.2 原子化类应用3.3 使用RegExp匹配器 四、高级功能解析4.1 主题系统4.2 响应式与深色模式 五、实战案例:构建现代按钮组件六、性能优化技巧七、…...
Es6进阶
class里的get和set 在 ES6 中,class 里的 get 和 set 是用于定义访问器属性的关键字。get 方法可在访问对象属性时调用,set 方法则在设置对象属性时调用。下面通过一个简单示例来介绍它们的用法: javascript class Person {constructor(nam…...
可发1区的创新思路:基于K-means聚类的EMD-BiLSTM-CNN-Attention时间序列预测模型(功率预测、寿命预测、流量预测、故障诊断)
首先声明,该模型为原创!原创!原创! 一、应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、交通流量预测、电机故障检测等等。 二、模型整体介绍(本文以光伏功率预测为例) 核心架构 数据聚类:K-means对风电场机组分组,降低异质性干扰。…...