Yang.Lian
发表于 2024-5-31 10:29:49
本帖最后由 Yang.Lian 于 2024-5-31 10:31 编辑
楼主,我有一个问题,
我有两个I2C设备挂在同一个I2C总线,两个设备工作模式不一样
A 工作在查询模式,每2秒查询一下
B 工作在中断模式,如果有事件发生,就处理,没有事件发生就做一个安静的美男子
我本来使用两个Task,一个Task负责A,一个Task负责B;
同时,还使用两个Bin,一个负责B的中断,一个负责I2C总线的排他使用;
当前代码工作无误,我一直没有理解 CosyOS 中的 的定时中断和定时任务以及定时中断钩子和定时任务钩子。
我直觉告诉我这个场景可以用定时中断/任务或钩子来简化,正好借这个机会,认真学习一下。
我也不知道第几集视频在讲这个,我怀疑应该比较靠后,正在人肉搜索视频中......
希望楼主,说说思路,指导指导
Yang.Lian
发表于 2024-5-31 13:57:39
Yang.Lian 发表于 2024-5-31 10:29
楼主,我有一个问题,
我有两个I2C设备挂在同一个I2C总线,两个设备工作模式不一样
楼主,你这样搞会出事儿啊,以后谁还用普通任务啊!
我已经用 定时中断/查询 改写完成
start_hook 里面只有启动定时器的代码,都没有普通任务了{:titter:}
并且用任务临界状态来控制I2C的排他独占操作{:titter:},不用 Bin 了
丝滑好用
之前用Bin的时候,如果B中断了,在任务里面不加一个延时,是会卡死的,当时也没有深究。改完以后,没有发现卡死的情况,很好啊
我觉得我不会再爱 uDelay 和 uCreateTask 了{:titter:}
CosyOS
发表于 2024-5-31 14:03:01
Yang.Lian 发表于 2024-5-31 09:07
发现一个小问题,提醒一下大家
本来我需要2000毫秒采集一次数据,到时考虑到各种误差,我就这么写了一下
这个问题很好,我会在下一版优化一下,以后就不用再多写一个()了。
CosyOS
发表于 2024-5-31 14:06:52
Yang.Lian 发表于 2024-5-31 10:29
楼主,我有一个问题,
我有两个I2C设备挂在同一个I2C总线,两个设备工作模式不一样
在第六集。
CosyOS
发表于 2024-5-31 14:09:50
本帖最后由 CosyOS 于 2024-5-31 21:00 编辑
Yang.Lian 发表于 2024-5-31 13:57
楼主,你这样搞会出事儿啊,以后谁还用普通任务啊!
我已经用 定时中断/查询 改写完成
我还有一个有关 定时中断 和 定时查询,在中断中高效应用的补充说明。
等我整理好资料,今天就会发布!
CosyOS
发表于 2024-5-31 14:19:57
只要是 周期运行任务,都可以考虑使用 定时中断/查询任务 来实现,
连 启动任务 都不需要了,易用性更强、操作更方便。
CosyOS
发表于 2024-5-31 18:21:25
本帖最后由 CosyOS 于 2024-5-31 21:08 编辑
CosyOS-II定时中断 / 定时查询在中断中进行定时操作的高效方法
以上一次的 事件标志组 测试例程 为例,进行说明:
这是传统方法:
这是可实现高效操作的新方法:
bit u_sign_timqry_1 = false;
bit u_sign_timint_1 = false;
当在中断中进行定时操作时,新方法要比传统方法(在中断中直接调用 iTimQry_ms、iTimInt_ms),
高效许多,尤其是对于 51 来说,原因是 iTimQry_ms、iTimInt_ms 等,为中断挂起服务,
对于 51来说,入 PendSV_FIFO 是 比较耗时的。
再有,定时操作本就不会直接影响任务调度,而仅与系统滴答直接相关。
我们来看一下还未发布的最新版 CosyOS 源码:
任务管理器的 定时中断 和 定时查询 的定时操作,也是采用的新方法。
而后是 滴答钩子,用户可采用 新方法 在 滴答钩子 中定时。
然后是 定时中断 和 定时查询 定时器 的计数 和 绑定任务/钩子的执行了。
新方法对高速中断中每次都需要定时是非常有利的(如示例串口中断中,每接收一帧都要定时中断),
采用新方法后,即使串口波特率为1M、2M等,都不会有问题,是极为高效的。
它的高效不仅体现在,中断的执行时间仅是增加了一个 SETB bit 的时间;
而是对于高速的频繁定时来说,不用再每次入 FIFO、触发 PendSV、进PendSV中断了,
而是不管定时了多少次,都是等待进入 系统滴答中断 时,定时一次而已。
这极大的促进了整个系统的全面的高效运行。
新方法我们可称为 事件标志法。
事件标志法的适用条件:
1、定时操作与其它的中断挂起服务之间没有时序要求。
2、定时时间为常量(固定的立即数)。
额外说明:
CosyOS 标配的中断挂起服务入FIFO,挂起到PendSV中执行的方法,
就是为了确保各个中断挂起服务之间可能存在的时序关系,
而且还可以保证同一个服务自身的时序关系。
什么是同一个服务自身的时序关系呢?
就是某一项服务,参数是可变的,每次调用该服务时,数值可能都不同;
当存在瞬时的并发调用时,由于 是 先入先出,可确保最终执行结果的正确。
新版 CosyOS-II 即将发布,届时将针对 可行的中断挂起服务 提供 上述 事件标志法 的支持,
减少中断执行时间、提高服务执行效率,届时再做相关说明。
CosyOS
发表于 2024-6-1 00:15:17
奇怪的宏,看的多了就不奇怪了。
重点还是,是否达成了设计目标。
CosyOS 的很多功能都有着良好的易用性,如创建任务、动态创建与静态创建的转换、定时中断/查询。。。
这些功能的实现和易用性,就来源于这些奇怪的宏。
CosyOS
发表于 2024-6-1 17:27:29
本帖最后由 CosyOS 于 2024-6-1 20:40 编辑
CosyOS-II “中断挂起服务FIFO队列” @ “中断挂起服务FLAG队列”
为了实现“非最低优先级中断的零中断延迟”,CosyOS 采用了“中断挂起服务”的方案。
所谓“中断挂起服务”,是指用户在中断中调用的服务,不在本地直接执行,
而是挂起到“服务层临界区”(SysTick 或 PendSV)中 间接执行,
从而实现不用关闭总中断的临界区保护方案,实现零中断延迟。
这种间接执行的中断挂起服务,在具体实施上,可以有两种方案。
方案一:“中断挂起服务FIFO队列”
把服务的相关内容保存在局部的结构体中,再把结构体指针 入 FIFO队列,而后触发PendSV。
在PendSV中,从 FIFO队列 依次取出各指针,而后执行服务。
方案一的优势:
1、FIFO队列 可保证同一中断中或不同中断中,各中断挂起服务之间可能存在的时序逻辑关系。
2、FIFO队列 可保证同一服务自身的时序逻辑关系。
什么是同一服务自身的时序逻辑关系呢?
就是某一项服务,参数是可变的,每次调用该服务时,参数可能会不同。
当存在瞬时的并发调用时,由于是 先入先出,可确保最终执行结果的正确。
3、FIFO队列 可保证同一服务,当存在瞬时的并发调用时,不会丢失执行次数。
如发送一个计数信号量,如果存在瞬时的并发调用,如果丢失了执行次数,将导致计数出错。
方案一的劣势:
入队列、触发PendSV、出队列 等操作是比较耗时的,如果大量的中断挂起服务都经常性的频繁调用,
会对系统实时性造成不利影响,甚至导致任务没有机会运行。
尤其是对于 51 来说,由于先天的缺陷,主频又低,相关操作将更为耗时。
方案二:“中断挂起服务FLAG队列”
1、在中断中,当需要调用一项服务时,仅设置一个标志位即可,再触发PendSV;
而后在 pendsv_hook 中,依次查询各标志位,为真就清零并执行相应服务。
2、在中断中,当需要定时操作时,仅设置一个标志位即可;
而后在 tick_hook 中,依次查询各标志位,为真就清零并执行定时操作。
方案二的特点:
即使出现瞬时的并发调用,在执行时,也只能执行一次服务。
方案二的优势:
1、与方案一相比,调用和执行服务的时间都将明显缩短。
2、当出现瞬时的并发调用时,由于仅执行一次服务,执行效率会更高。
3、对高速中断中每次都需要定时是非常有利的(如示例串口中断中,每接收一帧都要定时中断),
采用该方案后,即使串口波特率为1M、2M等,都不会有问题,是极为高效的。
它的高效不仅体现在,中断的执行时间仅是增加了一个 设置标志位 的时间;
而是对于高速的频繁定时来说,不管定时了多少次,都是等待进入 系统滴答中断 时,定时一次而已,
这极大的促进了整个系统的全面的高效运行。
总结:
CosyOS-II 自 V2.3.0 版本开始,将同时支持上述两种方案。
当在中断中调用一项挂起服务时,
我们可以优先考虑采用“中断挂起服务标志队列”,
如无法满足要求再考虑“中断挂起服务FIFO队列”。
采用“中断挂起服务标志队列”的条件:
1、CosyOS已提供相应的服务支持(API前缀为:p);
2、该服务与其它挂起服务之间不存在时序逻辑关系;
3、即使存在瞬时的并发调用,也仅需执行一次即可;
4、该服务的参数为常量。
以前版本的 sv_int_pend.c, sv_int_pend.h,
自 V2.3.0 版本开始,将替换为:
sv_int_pend_fifo.c,sv_int_pend_fifo.h:“中断挂起服务FIFO队列”,
sv_int_pend_flag.c, sv_int_pend_flag.h:“中断挂起服务标志队列”。
以前版本的API,前缀分为 u、d、t、i、x,
自 V2.3.0 版本开始,新增前缀 p,意为在 pendsv_hook 中调用并执行的中断挂起服务。
CosyOS-II V2.3.0 版本 新特性
1、支持的前缀为 p 的挂起服务:
pResumeTask(task)
pSuspendTask(task)
pDeleteTask(task)
pSetTaskPri(task, pri)
pSetBlock_tc(task, tc)
pSetBlock_ms(task, ms)
pSetBlock_s(task, s)
pSetBlock_m(task, m)
pSetBlock_h(task, h)
pClearBlock(task)
pLockBin(bin)
pGiveBin(bin)
未来可能还会增加其它的挂起服务支持。
2、新增服务:
/* 终止定时中断 */
uTimInt_Cancel(tmid)
tTimInt_Cancel(tmid)
iTimInt_Cancel(tmid)
/* 终止定时查询 */
uTimQry_Cancel(tmid)
tTimQry_Cancel(tmid)
iTimQry_Cancel(tmid)
3、系统配置头文件,进行了适当的结构调整,
新增 定时中断/查询设置,
用户定时查询初始化状态。
CosyOS-II V2.3.0 版本 已发布!
再补充一个示例,大家一看便能懂 两种 挂起服务方案 该如何应用。
如在 TIM2中断 中要恢复一个任务:task_3,
采用 中断挂起服务FIFO队列:
void TIM2_IRQHandler(void)
{
iResumeTask(task_3);
}
采用 中断挂起服务标志队列:
bool i_resume_task3 = false;
void TIM2_IRQHandler(void)
{
i_resume_task3 = true;
mPendSV_Set;
}
void pendsv_hook(void) MCUCFG_USING
{
if(i_resume_task3){
i_resume_task3 = false;
pResumeTask(task_3);
}
}
我们再来比较一下二者的性能差距(51为例):
采用 中断挂起服务FIFO队列:
iResumeTask(task_3); 的反汇编:(仅是入FIFO 并触发PendSV),
从 0xB593 - 0xB5A9,共15句汇编;
还要再加上 void mPendSV_FIFOLoader(s_u16_t sv) 的执行,
是连续的 JBC指令,来抢占 队列位,再 sv 入 队列,
即使能入队列首,至少也需10句汇编。
一共至少25句汇编。
采用 中断挂起服务标志队列:
i_resume_task3 = true;
mPendSV_Set;
这两句的反汇编:
仅两句 SETB。
两种方法,在性能上可谓是 天壤之别。
另外,采用 中断挂起服务标志队列,即使出现 永不停息的高速并发调用,任务仍然有执行的机会。
即使是 ARM,两种方案在性能上也会有明显的差距,只不过 可通过 高主频 来弥补。
但 中断挂起服务FIFO队列 的优势在于,只要能处理过来,在各种情况下应用都确保不会出问题。
而 中断挂起服务标志队列 的应用,则必须满足如下条件:
1、CosyOS已提供相应的服务支持(API前缀为:p);
2、该服务与其它挂起服务之间不存在时序逻辑关系;
3、即使存在瞬时的并发调用,也仅需执行一次即可;
4、该服务的参数为常量。
所以,还是要权衡利弊,针对不同的服务和应用场景,选择适合的方法。
CosyOS
发表于 2024-6-1 20:21:02
其实,早在 CosyOS-I 时,51 所采用的就是 中断挂起服务FLAG队列,
在 CosyOS-II 时,改为了 中断挂起服务FIFO队列。
现在,只不过是两种方案的整合。
让我们来回忆一下 CosyOS-I 时的情景,中断挂起服务是如何调用的。
当时 中断挂起服务 都有个参数 svid,就是用来实现系统自动定义标志位的。
所以,“中断挂起服务FLAG队列”的应用,在技术上是可以实现自动化、智能化的,
不用用户 在 调用处(中断)、执行处(pendsv_hook)两地分别写代码。
但是,最终我还是决定采用 手动实现的方案,让用户自己来完成 FLAG队列、两地分别写代码。
原因是:
1、采用 “中断挂起服务FLAG队列” 就是为了实现高性能,而自动化的方法也是有局部的结构体,也是要结构体指针入数组的,
只不过是入固定的位置 array,这将会导致低性能,而且还浪费内存。如果是这样的话,存在的价值就不大了。
2、让用户自己来完成,还有另外一个优势,那就是 “中断挂起服务FLAG队列” 必须要满足一个条件:服务的参数为常量,
由用户自己来异地(pendsv_hook)调用服务,如果参数不是常量,是无法实现的,这就在客观上保证了 服务的参数 一定为 常量。
为什么参数必须是常量呢?
因为如果参数不是常量,而是变量,就必须采用自动化的方法才能实现,
这正是 1 中所述的问题,会导致低性能,存在的价值就不大了。
综上所述,中断挂起服务FLAG队列,必须由用户自己实现,调用处(中断)、执行处(pendsv_hook)两地分别写代码,而且 参数 只能是常量。