CosyOS
发表于 2023-7-27 13:47:49
本帖最后由 CosyOS 于 2023-7-27 14:17 编辑
@leonling,你的应用是串口吗?串口可考虑采用定时中断任务来解析报文。
一般裸机的处理方法是在串口接收中断中会设定一个定时器的定时时间(如30ms),而后在定时器中断中解析。
CosyOS提供的定时中断定时器可替代硬件定时器,实现相同的功能。这就是定时中断任务。
定时中断定时器,相当于硬件定时器;
定时中断任务,相当于对应的定时器中断。
如果你考虑这种方式,我可以提供示例代码。
CosyOS
发表于 2023-7-27 15:27:28
@leonling,我还要补充说明一下,你提到的情况还是有机率会出问题。
uSuspendTasking刚开始时(还未进入任务临界区),中断来了,在中断中恢复这个任务iResumeTask,最终的结果就是先恢复再挂起,导致漏掉一次解析。
所以,必须还得有可靠的机制保护才行。如合理的分包间隔时间、提前解析、解析任务高优先级等。
CosyOS
发表于 2023-7-27 16:56:46
本帖最后由 CosyOS 于 2023-8-3 01:32 编辑
CosyOS - 定时中断与定时查询
我们知道,MCU都有定时器和定时器中断,当定时器溢出时,CPU可以响应中断并调用相应的定时器中断服务程序。
然而,硬件定时器的数量是有限的,在较复杂的应用中往往是不够用的。所以,CosyOS提供了软件定时器,以弥补硬件定时器数量的不足,
同时也支持“定时服务”功能,来模拟定时器中断。
软件定时器
CosyOS的软件定时器分为三类,延时定时器、定时中断定时器、定时查询定时器。
延时定时器:每个任务各有一个,用来在此任务中实现阻塞延时或超时计数。
定时中断定时器:每个定时中断任务/钩子绑定一个定时中断定时器,至多可以有64个,定时器ID从0开始,用户通过调用API进行定时操作后,定时器将自动开始计数。
定时查询定时器:每个定时查询任务/钩子绑定一个定时查询定时器,至多可以有64个,定时器ID从0开始,用户通过调用API进行定时操作后,定时器将自动开始计数。
定时中断定时器、定时查询定时器,相当于是硬件定时器;
定时中断任务/钩子、定时查询任务/钩子,相当于是定时器中断服务程序。
MCU:
调用
中断响应 ——> 定时器中断服务程序
CosyOS:
调用
系统滴答 ——> 定时中断/查询钩子
恢复
系统滴答 ——> 定时中断/查询任务
定时中断任务/钩子:由用户设定定时时间,当定时器溢出时,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。
定时查询任务/钩子:由用户设定定时时间,当定时器溢出后,系统在每个滴答周期都会查询用户定义的事件,若事件为真,系统将自动恢复/调用与其绑定的任务/钩子并自动重复定时(如果您已开启定时器的自动重装载)。
系统初始化后,所有定时查询定时器的值均为零,相当于已经溢出,系统已经开始查询用户定义的事件了。
任务与钩子的区别:由于定时中断钩子、定时查询钩子都是在SysTick_Handler中被调用,所以只有极为精简的代码才适合创建钩子,耗时长的代码应创建为任务。再有,钩子相当于比任务具有更高的优先级,可更及时的被执行。
应用示例
定时查询任务应用于串口发送,定时中断任务应用于串口接收解析(裸机时一般用定时器中断),
下面以串口4为例加以说明(通讯数据为字符串):
/* 声明标志组:UART4_Send_Flag */
uExternFlagGroup
(
UART4_Send_Flag,
uDefFlagBit(f1);
uDefFlagBit(f2);
uDefFlagBit(f3);
uDefFlagBit(f4);
uDefVoidBits(4);
);
/* 创建标志组:UART4_Send_Flag */
uCreateFlagGroup(UART4_Send_Flag) = {0};
/* UART4中断 */
void UART4_Isr(void) interrupt 18 using 2
{
if(S4CON & S4TI)
{
S4CON &= ~S4TI;
if(UART4.Send.Temp) S4BUF = UART4.Send.Temp;
else
{
UART4.Send.i = 0;
iTimQry_ms(tmid_uart4_send_task, 50, 4); // UART4发送分包间隔50ms,4为svid
}
}
if(S4CON & S4RI)
{
S4CON &= ~S4RI;
if(UART4.Recv.i < sizeof_UART4_Recv_Temp-1) UART4.Recv.Temp = S4BUF;
iTimInt_ms(tmid_uart4_recv_task, 25, 5); // 25ms分包提前解析,外部设备发送至串口4的分包间隔要大于25ms+解析时间,5为svid
}
}
/*
* UART4发送任务
* 定时器ID:tmid_uart4_send_task
* 定时查询事件:滴答中查询标志组(UART4_Send_Flag)
* 定时器自动重装载:禁用
* 任务名称:uart4_send_task
* 任务优先级:200
* 任务栈size:0
* 安全运行时:0
* 私信参数:0
* 按自定义的发送优先级依次查询各标志位,为真则清除标志位并发送对应数据。
*/
uCreateTask_TimQry(tmid_uart4_send_task, tQueryFlagGroup(UART4_Send_Flag), false, uart4_send_task, 200, 0, 0, 0)
{
if(UART4_Send_Flag.f1)
{
uClearFlagBit(UART4_Send_Flag, f1);
/* 串口发送对应数据 */
}
else if(UART4_Send_Flag.f2)
{
uClearFlagBit(UART4_Send_Flag, f2);
/* 串口发送对应数据 */
}
else if(UART4_Send_Flag.f3)
{
uClearFlagBit(UART4_Send_Flag, f3);
/* 串口发送对应数据 */
}
else if(UART4_Send_Flag.f4)
{
uClearFlagBit(UART4_Send_Flag, f4);
/* 串口发送对应数据 */
}
uSuspendTasking;
uEndTasking;
}
/*
* UART4接收解析任务
* 定时器ID:tmid_uart4_recv_task
* 定时器自动重装载:禁用
* 任务名称:uart4_recv_task
* 任务优先级:210
* 任务栈size:0
* 安全运行时:0
* 私信参数:0
*/
uCreateTask_TimInt(tmid_uart4_recv_task, false, uart4_recv_task, 210, 0, 0, 0)
{
UART4.Recv.Temp = '\0';
UART4_RECV_ANALYSIS(); // 调用解析函数
UART4.Recv.i = 0;
uSuspendTasking;
uEndTasking;
}
0、UART4发送用定时查询任务就实现了发送分包间隔时间最短为50ms,当有发送标志位为真时,系统将自动恢复任务。
1、UART4接收解析任务的优先级一定要高,并高于UART4发送任务。而后,在各发送处设置对应的标志位(并缓存数据,如果需要的话)。
2、当然对于本示例,还可有另外一个方法,就是双方都发送字符串尾0,接收中断时查0,为0就解析,就不需要定时中断任务了。
3、这个发送方法适用于复杂应用,如在多个中断或任务中都有相应的发送项,这时就需要在一个任务中统一处理各发送项,而不能在各处直接发送。
4、所有系统任务、定时中断任务、定时查询任务均由CosyOS自动启动,无需用户启动。
定时中断任务/钩子、定时查询任务/钩子的应用不仅限于此,在于自己发挥。总之,它们都是软件定时器中断,当你需要定时器中断时就可以考虑它们了。
本示例也充分展示了CosyOS事件标志组的应用,相较传统RTOS,其易用性不可描述。
leonling
发表于 2023-7-27 21:12:09
CosyOS 发表于 2023-7-27 15:27
@leonling,我还要补充说明一下,你提到的情况还是有机率会出问题。
uSuspendTasking刚开始时(还未进入任 ...
我想了一下,按照我原来设计的机制,只有最后一个报文出问题时才是致命的,因为这会导致最后的报文一直不被解析处理。而恰好系统中有一个定时器是监控多长时间没有收到报文的,我在其中加入一个1ms timeout,即1ms没有收到报文就再iResumeTask一次。而解析任务先判断报文长度,确实有报文再解析。总之解析任务如果不能被报文到达中断iResume,就会被超时未收到报文中断iResume。是不是这样就没问题了?
CosyOS
发表于 2023-7-27 21:56:33
本帖最后由 CosyOS 于 2023-7-27 23:53 编辑
leonling 发表于 2023-7-27 21:12
我想了一下,按照我原来设计的机制,只有最后一个报文出问题时才是致命的,因为这会导致最后的报文一直不 ...
是的,这样的确会更加可靠,但还是会有恢复失败的机率,虽然可能一辈子也碰不到。
我不了解你的具体应用,不能给出具体的建议,你可以多想一些方法相比较,看看哪种方法更为可靠。
不过我认为控制发送分包间隔时间是有必要的,控制好时序,实现这一包解析完成并挂起后,下一包才会发过来。
比如约定双方发送分包间隔时间最短为50ms,然后提前解析,比如定时20ms就解析,然后解析时间要小于30ms。
这样还可解决在解析过程中又收到下一包数据而导致解析错误或失败的问题。
解析任务优先级也一定要高,代码也要更为高效,尽早完成解析。
或者可以用二值信号量,解析任务获取二值信号量成功后再解析,接收中断中再释放它。
这样,即使是在解析过程中,接收中断中释放了二值信号量也不会丢掉下一次解析。
leonling
发表于 2023-7-28 15:32:32
目前Debug信息从uart1输出,但在把uart1的中断优先级提高1级之后,出现以下现象:
1. 本来每秒输出taskmgr信息,现在输出5、6后,会停顿几秒钟再输出。
2. 显示信息时而正常时而不正常
接收←CosyOS Taskmgr 0.43%
Name TID TPL STA CPU RAM
Taskmgr 1 7 RDY 0.42% 13B/s23B
Debugger 4 7 SPD 0% 7B/s23B
task_C 8 4 DLY 0% 7B/s128B
taskUart2 5 2 BIN 0% 9B/s128B
taskUart3 6 2 BIN 0% 9B/s128B
taskUart4 7 2 BIN 0% 9B/s128B
Sysidle 3 0 RDY 99.57% 7B/s23B
Task-PC: 104E.
SysTick: 485.38us.
接收←CosyOS Taskmgr 399.80%
Name TID TPL STA CPU RAM
Taskmgr 1 7 RDY 0.43% 13B/s23B
Debugger 4 7 SPD 0% 7B/s23B
task_C 8 4 DLY 0% 7B/s128B
taskUart2 5 2 BIN 0% 9B/s128B
taskUart3 6 2 BIN 0% 9B/s128B
taskUart4 7 2 BIN 0% 9B/s128B
Sysidle 3 0 RDY355.56% 7B/s23B
Task-PC: 104E.
SysTick: 371.08us.
3.3.0版本和3.3.3版本现象一致。感觉
uCreateHook_TimQry(SYSCFG_USERTIMQRYTOTAL, vDebugSendFlag, false, debug_hook)
{
if(vDebugSendFlag & CmdlineSendFlag)
{
vDebugSendFlag &= ~CmdlineSendFlag;
vDebugSendPtr = vCmdLineSendBuff;
SYSCFG_DEBUGSEND;
}
else if(vDebugSendFlag & TaskmgrSendFlag)
{
vDebugSendFlag &= ~TaskmgrSendFlag;
vDebugSendPtr = vTaskmgrSendBuff;
SYSCFG_DEBUGSEND;
}
}
在调度中执行这段时,高优先级的情况下直接就能进中断,然后中断中又iDebugSend(0)了,是不是影响了调度?
CosyOS
发表于 2023-7-28 16:14:21
本帖最后由 CosyOS 于 2023-7-28 16:39 编辑
leonling 发表于 2023-7-28 15:32
目前Debug信息从uart1输出,但在把uart1的中断优先级提高1级之后,出现以下现象:
1. 本来每秒输出taskmgr ...
过一会我测试一下看看。
iDebugSend、iDebugRecv,括号里面的数字是svid,所以其它的中断异步服务调用中的svid不要与它们重复。
你加我qq吧,有事沟通能更及时,2146166599.
CosyOS
发表于 2023-8-1 13:56:19
本帖最后由 CosyOS 于 2023-8-1 22:46 编辑
Keil C51 寄存器库 / 寄存器访问 / using
鉴于很多朋友可能对Keil C51的寄存器访问、using的使用原则等不甚了解,而这些对于51来说又是极为重要的,
特把自己所掌握的这方面的知识分享给大家,共同进步。不足之处在所难免,望谅解。
一、寄存器库(REG BANK)
8051共有四个寄存器库,分别为
REG BANK 0:0x00~0x07
REG BANK 1:0x08~0x0F
REG BANK 2:0x10~0x17
REG BANK 3:0x18~0x1F
当前使用哪个REG BANK,由PSW中的RS0、RS1决定。
关于REG BANK不再多说,因为这方面资料非常丰富,不懂的请自行查阅。
二、寄存器访问
Keil C51的寄存器访问方式分为绝对寄存器访问(absolute register accesses)和相对寄存器访问(no absolute register accesses)。
1、Keil C51在默认情况下采用绝对寄存器访问方式来编译代码。
2、用户也可调整为全局相对寄存器访问,如下图。但通常不建议这么做。
3、用户也可使用预编译指令来单独设置某个自定义函数的寄存器访问方式:
#pragma AREGS // 绝对寄存器访问
#pragma NOAREGS // 相对寄存器访问
如下方示例:
#pragma NOAREGS
void demo_function1(...)
{
}
#pragma AREGS
void demo_function2(...)
{
}
demo_function1采用相对寄存器访问,demo_function2采用绝对寄存器访问。
绝对寄存器访问与相对寄存器访问
首先要弄懂工作寄存器的相对寄存器地址和绝对寄存器地址。
相对寄存器地址:R0、R1、R2、R3、R4、R5、R6、R7,由于共有四个REG BANK,每个REG BANK都有R0~R7,所以寄存器地址不是绝对的。
绝对寄存器地址:AR0、AR1、AR2、AR3、AR4、AR5、AR6、AR7,或工作寄存器的直接内存地址。
示例:
————————————————————————————————————
想做的操作 相对寄存器访问 绝对寄存器访问(REG BANK 0)
————————————————————————————————————
PUSH R0 MOV A, R0 PUSH 0x00
PUSH ACC
------------------------------------------------------------------------------------------
MOV R5, R7 MOV A, R7 MOV R5, 0x07
MOV R5, A
————————————————————————————————————
注:想做的操作是用户想做的操作,但对于51来说,这样的汇编指令是非法的。
可见,绝对寄存器访问方式生成的代码更为高效,拥有比相对寄存器访问方式更高的性能并减少代码量。
但即便采用了绝对寄存器访问方式,也必然同时存在相对寄存器访问;如果采用相对寄存器访问方式,就不会有绝对寄存器访问。
也就是绝对中有相对,相对中无绝对。
既然绝对寄存器访问方式各方面都非常优秀,为什么还需要相对寄存器访问呢?
因为相对寄存器访问方式,仅采用相对寄存器地址,不采用绝对寄存器地址,可生成不依赖REG BANK的代码。
调用者使用哪个REG BANK,被调用者都将继承调用者的REG BANK,使得整个调用过程(调用前传参、调用后、返回值、返回后)工作寄存器都协同一致,所以不会出错。
在绝对寄存器访问时,编译器必须确定当前函数所使用的REG BANK,以此生成绝对寄存器地址。
如果实际使用中,该函数被使用了不同REG BANK的函数所调用,
1、该函数未使用using,被调用时将继承调用者的REG BANK,但该函数的绝对寄存器地址已按REG BANK 0生成,这将导致绝对寄存器地址与实际寄存器地址不符,最终导致数据或运算出错。
2、该函数使用了using,被调用后将使用自己的REG BANK,但调用者传参用的可是调用者的REG BANK,该函数返回值时用的是自己的REG BANK,调用者使用返回值时用的是调用者的REG BANK,可见寄存器的使用已经完全乱套了。
这时,就需要相对寄存器访问才行。
当一个自定义函数,可能被使用了不同REG BANK的函数所调用时,必须声明为相对寄存器访问。这也正是C51标准库的套路。
三、using
1、未使用using的函数(包括中断),在绝对寄存器访问时,编译器一律认为该函数使用了REG BANK 0,并生成部分绝对寄存器地址。
2、使用了using的函数(包括中断),即为绝对寄存器访问,using后面的数字为REG BANK。如 using 3:该函数使用了REG BANK 3。
using的使用原则:
1、建议只有中断和被中断调用的自定义函数,才考虑使用using。
2、所有中断均不能使用using 0,仅能使用using 1、2、3。因为中断使用using后,相应的REG BANK就不会PUSH/POP STACK,当中断using 0时,REG BANK 0未入栈,但中断确使用了REG BANK 0,导致REG BANK 0 中的数据被覆盖。
3、相同优先级的中断可以使用相同的REG BANK,不同优先级的中断绝对不能使用相同的REG BANK。
4、被中断调用的自定义函数,如果该函数仅被该中断或与该中断相同优先级的各中断调用(各调用者使用了相同的REG BANK,非REG BANK 0),
该函数也应using为相同的REG BANK,或声明为相对寄存器访问,两种方法必选其一。(可参考CosyOS/System/sv_tick.c)
5、被中断调用的自定义函数,如果该函数会被不同优先级的中断或任务调用(各调用者使用了不同的REG BANK),该函数必须声明为相对寄存器访问。(可参考CosyOS/System/sv_int_loc.c)
using的使用,可明显提升中断的进出速度,如果能把控好using的使用原则建议采用,否则不要用,因为一但用错会导致数据出错,出现各种奇怪的现象。
四、Keil C251
80251部分遗传了8051的特性,也存在相同的四个寄存器库。
Keil C251编译器仅支持相对寄存器访问方式,不存在绝对寄存器访问方式,也不必使用相应的预处理指令。
using的使用原则与51基本相同,对于中断来说,不使用using有时比使用using更为高效。
自定义函数如果有形参或返回值则不能使用using,否则编译器会报错。
一般来说,80251没必要使用using。但如果您使用了CosyOS,并且任务栈模式为MSP模式,建议最低优先级中断寄存器库选择为bank1、bank2、bank3,这将有助于减小任务栈。
关于Keil C51、C251的寄存器访问方式及using的使用原则就介绍到这,如果谁有疑问或异议都可以提出来,大家共同探讨。
熊仔
发表于 2023-8-3 12:33:13
我认为全部使用using 0,其实不差几个指令周期。中断寄存器组0 全部入栈,绝对地址访问,速度杠杆的,也就8个时钟周期。如果用相对地址R0到R7出入栈要16个周期,需要通过ACC。
而且剩余的3个bank,24字节还可以用来做data区的变量,多好呀。
杨为民
发表于 2023-8-3 12:41:01
熊仔 发表于 2023-8-3 12:33
我认为全部使用using 0,其实不差几个指令周期。中断寄存器组0 全部入栈,绝对地址访问,速度杠杆的,也就8 ...
你的这个看法是正确的,对于C51和C251编译器,你的这个看法符合8051指令集设计的初衷,符合编译器的设计宗旨,也是编译器自己缺省的寄存器使用方案