Codejock.Xtreme.Toolkit.Pro V15.0.1 VC6下的编译问题
1. Dialog type “Hello word!” application.
2. Fatal Error message:
Compiling…StdAfx.cppAutomatically linking with ToolkitPro1501vc60SD.libc:\program files\microsoft visual studio\vc98\mfc\include\afxtempl.h(61) : fatal error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit c:\program files\microsoft visual studio\vc98\mfc\include\afxtempl.h(331) : see reference to function template instantiation ‘void __stdcall ConstructElements(class CXTPReportRecordItemControl ** ,int)’ being compiledError executing cl.exe.
3. 解决办法:
添加定义
#define _XTP_EXCLUDE_CALENDAR
#define _XTP_EXCLUDE_REPORTCONTROL
还不知VC6下为什么不能包含CALENDAR和REPORTCONTROL功能。
为uCOS51增加Shell界面
为uCOS51增加Shell界面
巨龙公司系统集成开发部 杨屹 asdjf@163.com 2002/10/13
引言
自从发表《uCOS51移植心得》以来,我收到了很多朋友们的来信,大家对公开源码表示鼓励,谢谢大家的支持!很多人对于编写自己的操作系统很感兴趣,uCOS51是个不错的选择。它的优点是简单易懂,学习成本低,有利于向32位CPU过渡。目前,嵌入式BBS上的热点是:嵌入式实时多任务操作系统、单片机上网、32bitCPU(如ARM等)。其实通过uCOS51学习完全可以掌握这些热门技术的精髓,而且学习成本低廉。为此我会陆续将我在研发过程中的经验体会写出来与大家交流,共同进步。
我准备讨论以下内容:uCOS51高效内核、OS人机界面SHELL的编写、51机开发板的硬件设计、RTL8019AS网卡驱动程序、51TCP/IP协议栈设计、应用协议FTP、PPP、HTTP、SMTP、SNMP……在51上的实现技术、51OS任务划分和应用程序实例、由51软件系统向ARM的移植以及其他想到的题目。欢迎大家积极参与。
注:开发板原理图、PCB图、GAL烧录文件、芯片手册、全部源程序可以来信索取,在整理好后会共享在网上。
讨论2—-OS人机界面SHELL的编写
uCOSII只提供了操作系统内核,用户要自己添加文件处理、人机界面、网络接口等重要部分。其中Shell(人机界面)提供了人与机器交互的界面,是机器服务于人的体现,是系统必不可少的重要组成部分。现代的很多OS如UNIX、DOS、VxWorks都提供了友好的命令行界面。Windows更是提供了GUI。大部分人认识OS都是从这里开始的。uCOS51同样拥有Shell,它是我从以前写的前后台程序中移植过来的。
命令行Shell的工作原理比较简单,主要思路就是单片机接收用户键盘输入的字符存入命令缓冲区,并回显到屏幕,当用户按下回车键,触发软件状态机状态变迁,从输入态转移到命令解释态,然后根据用户命令调用相关子程序执行相应操作,执行完毕后重新回到输入态。
我感觉原理很好掌握,程序也不长,但是细节部分要反复调试多次才能稳定工作。比如:命令行左右边界的保护、退格键的处理、词表的设计等等。
Shell程序由词表、取词子程序、状态机框架程序(输入回显和命令解释执行)、命令相关子程序组成(详见源程序清单)。
词表结构如程序清单所示,由词数目,左括号数,右括号数,每个词的具体信息(长度,字符串)构成。左右括号数用于括号匹配检查;词数目用于程序循环;词的具体信息作为解释/执行程序的输入参数。
取词子程序从命令行语句中提取单词并存入词表同时进行匹配检查和词法分析。默认字符为:0-9、a-z、A-Z、’.’;定界符为:空格、逗号,左/右括号。建议用户补充默认字符集(? / \ -)以便实现更灵活的语法。注意:现在版本的Shell只检查左右括号数量的匹配,无优先级和语法含义。
输入回显程序循环检查用户键盘输入。如果输入回车,程序状态转入解释执行态;如果输入退格(8)则回显退格、空格、退格,模拟删除字符,同时输入缓冲区清除相应字节,清除前先检查左边界是否越界。如越界则鸣响报警且不执行清除操作;其他字符输入直接存入输入缓冲区并回显,此前检查右边界是否溢出,如果溢出则鸣响报警且抛弃刚输入的字符。
命令解释程序调用取词子程序分析用户命令行输入,根据词表第一个单词在散转表中的位置调用相应执行子程序处理命令,如果散转表中无此单词,则打印“Bad command!”。取词子程序返回错误指示时也打印此句。
命令解释程序向相应的命令相关子程序传入词表指针,具体执行由用户自行决定。由命令相关子程序返回后重新回到命令输入态,完成一次输入执行全过程。此过程周而复始地循环执行。
Shell界面的命令按功能分为以下几组:
1。操作系统相关命令:
查看就绪任务lt / 中止任务kill / 恢复任务执行call / CPU利用率usage / 版本查询ver / 查某个任务信息(TCB、堆栈内容)lt
查看切换次数和时间lts
2。网络相关命令:
显示配置MAC地址macadr / 显示配置主机IP地址host / 显示配置子网掩码mask / 显示配置缺省网关gateway
显示网络配置总情况lc / 连通测试命令ping / 用户数据报发送命令udp / telnet命令tel / 相关应用命令**
显示ARP高速缓冲区地址对ls / 显示发送缓冲区信息lti
3。屏幕显示相关命令:
清屏clr / 帮助help / 功能键F3、F7处理 / 组合键Ctrl+C、Ctrl+B处理
4。外设(闪盘X5045和I/O口)相关命令:
读闪盘rdx / 读I/O口rdp / 写闪盘wdx
5。安全相关命令:
身份认证密码权限usr、pass
6。应用相关命令:
用户自行定义
7。调试相关命令:
单步、断点、运行、寄存器等命令,类似68K的TUTOR和ARM的驻留监控程序Angel。
怎么样,像不像VxWorks的命令行Shell!
用户命令大小写不敏感,程序将命令字符串统一成小写形式。程序中各种参数(如:最大词长度、词数量……)定义成宏放在一个头文件中,随时可修改配置,很方便。Shell作为一个任务工作于内核之外,占用一个任务号。
源程序:
词表
typedef struct{
int Num;
int LeftCurveNum,RightCurveNum;
struct{
int Length;
unsigned char Str[MaxLenWord+1]; /*for ”*/
} wt[MaxLenWordTable];
} WORDTABLE;
取词
bit GetWord(unsigned char *ComBuf,WORDTABLE *WordTable)
{
int i=0; /*ComBuf String pointer*/
int j=0; /*Length of Word */
int k=-1; /*The number of WordTable*/
int StrFlag=0; /*There is “0-9/a-z/A-Z” before ” ,()”*/
int SentenceEndFlag=0; /*Sentence end*/
char ch;
WordTable->Num=0;
WordTable->LeftCurveNum=0;
WordTable->RightCurveNum=0;
ch=ComBuf[0];
while(!SentenceEndFlag&&i<MaxLenComBuf){
if((ch>=’0’&&ch<=’9′)||(ch>=’a’&&ch<=’z’)||(ch>=’A’&&ch<=’Z’)||(ch==’.’)){
if(StrFlag==0){
StrFlag=1;k=k+1;j=0;
if(k>=MaxLenWordTable) return 0;
WordTable->wt[k].Str[j]=ch;
WordTable->Num=k+1;
}
else{
j=j+1;
if(j>=MaxLenWord) return 0;
WordTable->wt[k].Str[j]=ch;
}
}
else if(ch==’ ‘||ch==’,’||ch=='(‘||ch==’)’||ch==”){
if(ch=='(‘) WordTable->LeftCurveNum++;
if(ch==’)’) WordTable->RightCurveNum++;
if(StrFlag==1){
StrFlag=0;j=j+1;
WordTable->wt[k].Str[j]=”;
WordTable->wt[k].Length=j;
}
if(ch==”) SentenceEndFlag=1;
}
else{
return 0;
}
i=i+1;
ch=ComBuf[i];
}
if(i<MaxLenComBuf||ComBuf[MaxLenComBuf]==”){
if(WordTable->LeftCurveNum==WordTable->RightCurveNum) return 1;
else return 0;
}
else{
return 0;
}
}
输入回显和命令解释执行
void yyshell(void *yydata) reentrant
{
yydata=yydata;
clrscr();
PrintStr(“\t\t***********************************************\n”);
PrintStr(“\t\t* Welcom to use this program *\n”);
PrintStr(“\t\t* Author:YangYi 20020715 *\n”);
PrintStr(“\t\t***********************************************\n\n\n”);
/*Login & Password*/
PrintStr(“% “);
while(!ShellEnd){
switch(State){
case StatInputCom:{
if(yygetch(&ch)){
if(ch==13) /*Enter return key*/
{
PrintStr(“\n”);
ComBuf[i+1]=”;
if(i+1==0) PrintStr(“% “);
else
State=StatExeCom;
}
else{
i=i+1;
if((i>=MaxLenComBuf)&&(ch!=8)){
PrintChar(7);
i=MaxLenComBuf-1;
}
else{
if(ch==8){
i=i-2;
if(i<-1) {i=-1;PrintChar(7);}
else{
PrintChar(8);
PrintChar(‘ ‘);
PrintChar(8);
}
}
else{
PrintChar(ch);
ComBuf[i]=ch;
}
}
}
break;
}
else{
//OSTimeDly(10);
break;
}
}
case StatExeCom:{
if(GetWord(ComBuf,&WordTable)==1&&WordTable.Num!=0){
yystrlwr(WordTable.wt[0].Str);
for(tem=0;tem<MaxComNum&&!ComMatchFlag;tem++)
if(yystrcmp(WordTable.wt[0].Str,ComTable[tem])==0) ComMatchFlag=1;
if(ComMatchFlag){
tem–;
switch(tem){
case 0:{DisplayTask(&WordTable);break;}
case 1:{Kill(&WordTable);break;}
case 2:{PingCommand(&WordTable);break;}
case 3:{UDPCommand(&WordTable);break;}
case 4:{CfgHost(&WordTable);break;}
case 5:{CfgMask(&WordTable);break;}
case 6:{CfgGateway(&WordTable);break;}
case 7:{
//ShellEnd=1;
PrintStr(“\n\tThis Command is limited!\n\n”);
break;
}
case 8:{PrintConfig(&WordTable);break;}
case 9:{clrscr();break;}
case 10:{DisplayHelpMenu(&WordTable);break;}
}
}
else
PrintStr(” Bad command!\n\n”);
}
else{
if(WordTable.Num) PrintStr(” Bad command!\n\n”);
}
ComMatchFlag=0;
State=StatInputCom;
if(ShellEnd) {PrintStr(“\n\n”);}
else PrintStr(“% “);
i=-1;
break;
}
default:{
//ShellEnd=1;
PrintStr(“System fatal error!\n”);
PrintChar(7);PrintChar(7);PrintChar(7);
}
}
}
}
命令使用说明:
一、关于93LC66的命令
1.rd66 [起址] [显示行数]
例如:rd66 0x100 3 显示从0x100开始的数据,显示3行(每行16字节)。
注:显示行数最大为ROMSIZE/16;省略起址和显示行数,则缺省显示0x00开始的一行,省略显示行数,则缺省显示1行。另有一rdb66指令,格式与此同,只是它是按字节显示的。
2.wd66 地址 [x] 数据0-n
例如:wd66 all 0xad 将0xad写入93LC66全部存储单元。
wd66 0xaa 0xff 将0xff写入0xaa存储单元。
wd66 0xcd x aa bb cc dd 将十六进制数AA、BB、CC、DD写入0xcd单元。
wd66 0xcd 12 34 56 78 将十进制数12、34、56、78写入0xcd单元。
注:地址和数据不可省略;标记x用于数据进制的指定。
3.erase66 地址
例如:erase66 all 擦除93LC66全部存储单元。
erase66 0x20 擦除0x20存储单元。
注:地址不可省略。全擦命令要求确认一次(按y/Y确认)。
二、关于X5045的命令
1.rxr
例如:rxr 读X5045状态寄存器。
注:状态寄存器格式 X X WD1 WD0 BL1 BL0 WEL WIP
WD1 WD0 BL1 BL0
0 0 1.4s 0 0 None
0 1 600ms 0 1 $180-$1FF
1 0 200ms 1 0 $100-$1FF
1 1 Disable 1 1 $000-$1FF
WIP=0 空闲;WIP=1 正在写;WEL=0 不可写;WEL=1 可写。
2.wxr
例如:wxr 写X5045状态寄存器。
注:见rxr注。
3.rdx [起址] [显示行数]
例如::rdx 0x100 3 显示从0x100开始的数据,显示3行(每行16字节)。
注:显示行数最大为ROMSIZE/16;省略起址和显示行数,则缺省显示0x00开始的一行,省略显示行数,则缺省显示1行。ROMSIZE按字节为单位计算。
4.wdx 地址 [x] 数据0-n
例如:wdx 0xaa 0xff 将0xff写入0xaa存储单元。
wdx 0xcd x aa bb cc dd 将十六进制数AA、BB、CC、DD写入0xcd单元。
wdx 0xcd 12 34 56 78 将十进制数12、34、56、78写入0xcd单元。
注:地址和数据不可省略;标记x用于数据进制的指定。
三、关于端口的命令
1.rdp
例如:rdp 读端口值。
注:背面 JACK …BA3 BA2 BA1 BA0 POW2 POW1
正面 ISIN LINKOK TXSPEED … JACK
2.wrbs
例如:wrbs 写板选(0-15)。
注:见rdp注。
四、关于shell的命令
1.clr
例如:clr 清屏幕。
2.exit
例如:exit 退出Shell。
注:此命令受限制,此处不响应。
3.help
例如:help 屏幕打印帮助菜单。
五、关于TCPIP的命令
1.lc
例如:lc 显示TCPIP配置信息(MAC地址、主机地址、子网掩码、网关地址、SNMP主机地址)。
2.ls
例如:ls 显示ARP缓存信息(寿命、MAC地址、IP地址)。
3.lt
例如:lt 显示发包缓存信息(目的IP地址、重发次数)。
4.host
例如:host 显示主机IP地址。
host 172.18.92.86 更改IP地址为172.18.92.86。
5.mask
例如:mask 显示子网掩码。
mask 255.255.0.0 更改子网掩码为255.255.0.0。
6.gateway
例如:gateway 显示网关地址。
gateway 172.18.80.1 更改网关地址为172.18.80.1。
7.snmphost
例如:snmphost 显示SNMP主机地址。
snmphost 172.18.92.66 更改SNMP主机地址为172.18.92.66。
8.macadr
例如:macadr 显示MAC地址。
macadr 0000 1234 5678 更改MAC地址为0000 1234 5678。
9.ping
例如:ping 172.18.92.87 检查目的主机连通情况。
10.udp
例如:udp 172.18.92.87 Hello. 向172.18.92.87发送UDP包,包内数据为字符串。
X5045数据结构
地址类型 对应全局变量 位置 大小
MAC my_ethernet_address 0-5 6
IP my_ip_address 6-9 4
MASK mask_ip_address 10-13 4
GateWay IP gateway_ip_address 14-17 4
SNMP IP SNMP_ip_address 18-21 4
uC/OS-II uCOS51 问题
关于uC/OS-II
1。uC/OS-II源码里有的地方好象漏掉了编译预处理,导致裁减失效。是作者故意这样做的吗?还是有技巧?
2。推荐的中断处理流程:
void OSTickISR(void)
{
保存处理器寄存器的值;
调用OSIntEnter()或是将OSIntNesting加1;
调用OSTimeTick();
调用OSIntExit();
恢复处理器寄存器的值;
执行中断返回指令;
}
似乎有问题。我认为“OSIntEnter()或OSIntNesting+1”应该放到第1句,否则中断嵌套发生后,不知道前面已经中断过,在中断退出处理时就可能提前任务切换,导致混乱。我建议进入中断后立即关中断执行OSIntNesting+1或如果CPU支持OSIntNesting+1原语直接加1。
关于uCOS51
1。51支持4个寄存器区(R0-R7),在C里也可以调用USING选择,你只保存1个寄存器区,是有问题的。
2。通过使用bank技术,51可以支持2M的存储,对于使用字库等的应用很有价值,uCOS51如何实现?
3。任务参数传递问题的解决使用了R1-R3,据我所知,不使用堆栈传值,无法重入,如果我想递归调用,如何实现?51仿真堆栈的格式是如何安排的?
问题越来越多,目前发现这些,有些解决了,有些正在想,请继续查错,也请帮忙指点和改正。
在其他MCU上移植,请注意:
1。任务实现了重入了吗?
2。任务参数传递是否真正用堆栈实现。
3。中断处理流程有无BUG。
4。原作者给的源码是否完整,有无经过技术处理。
ucosii工作原理,移植和应用程序的接口
ucosii工作原理
ucosii 移植
ucosii 和应用程序的接口(用户程序如何嵌入到操作系统中运行)
首先ucossii 的工作核心原理是:让最高优先级的任务处于运行状态。这需要操作系统在某个时间得运行任务调度的一个算法(操作系统提供了这个函数),她将自动完成调度运算并完成任务的切换。调度操作在下面情况中将被运行:调用api函数(用户主动调度),中断(系统、用户的)
我现在不能理解的是调度算法的api函数到底是怎么实现的(对于任务的优先级运算(查表)这个我知道),她怎么实现调度前运行的任务的数据(特别是pc地址)保存?如果任务从运行到就绪再到运行,它是否将从新开始运行任务程序,还是从调度以前的断点处运行?任务为什么需要被写成无限循环方式?还有,中断结束后,系统也要进行调度操作,那么她怎么实现的?因为按照常理,中断结束后,系统应该直接回到原先任务的断点处运行,除非她在中断函数中将调度函数用软件方式入栈了,这样reti退出后,系统运行调度函数。。。如果是这样,用户自己的中断函数是否也需要这样编写(用软件将调度函数入栈)才能实现调度运算呢?否则系统就不执行调度算法,直到系统的定时中断到了才执行调度算法(系统的中断函数是否有这样的特殊处理),当然系统可能还有其他的什么方式来实现调度算法(我是想不出来了),请赐教~~~谢谢~~~~~~~~~~~
TO:XXX
uCOSII工作核心原理是:近似地让最高优先级的就绪任务处于运行状态。
操作系统将在下面情况中进行任务调度:调用API函数(用户主动调用),中断(系统占用的时间片中断OsTimeTick(),用户使用的中断)。
调度算法书上讲得很清楚,我主要讲一下整体思路。
(1)在调用API函数时,有可能引起阻塞,如果系统API函数察觉到运行条件不满足,需要切换就调用OSSched()调度函数,这个过程是系统自动完成的,用户没有参与。OSSched()判断是否切换,如果需要切换,则此函数调用OS_TASK_SW()。这个函数模拟一次中断(在51里没有软中断,我用子程序调用模拟,效果相同),好象程序被中断打断了,其实是OS故意制造的假象,目的是为了任务切换。既然是中断,那么返回地址(即紧邻OS_TASK_SW()的下一条汇编指令的PC地址)就被自动压入堆栈,接着在中断程序里保存CPU寄存器(PUSHALL)……。堆栈结构不是任意的,而是严格按照uCOSII规范处理。OS每次切换都会保存和恢复全部现场信息(POPALL),然后用RETI回到任务断点继续执行。这个断点就是OSSched()函数里的紧邻OS_TASK_SW()的下一条汇编指令的PC地址。切换的整个过程就是,用户任务程序调用系统API函数,API调用OSSched(),OSSched()调用软中断OS_TASK_SW()即OSCtxSw,返回地址(PC值)压栈,进入OSCtxSw中断处理子程序内部。反之,切换程序调用RETI返回紧邻OS_TASK_SW()的下一条汇编指令的PC地址,进而返回OSSched()下一句,再返回API下一句,即用户程序断点。因此,如果任务从运行到就绪再到运行,它是从调度前的断点处运行。
(2)中断会引发条件变化,在退出前必须进行任务调度。uCOSII要求中断的堆栈结构符合规范,以便正确协调中断退出和任务切换。前面已经说到任务切换实际是模拟一次中断事件,而在真正的中断里省去了模拟(本身就是中断嘛)。只要规定中断堆栈结构和uCOSII模拟的堆栈结构一样,就能保证在中断里进行正确的切换。任务切换发生在中断退出前,此时还没有返回中断断点。仔细观察中断程序和切换程序最后两句,它们是一模一样的,POPALL+RETI。即要么直接从中断程序退出,返回断点;要么先保存现场到TCB,等到恢复现场时再从切换函数返回原来的中断断点(由于中断和切换函数遵循共同的堆栈结构,所以退出操作相同,效果也相同)。用户编写的中断子程序必须按照uCOSII规范书写。任务调度发生在中断退出前,是非常及时的,不会等到下一时间片才处理。OSIntCtxSw()函数对堆栈指针做了简单调整,以保证所有挂起任务的栈结构看起来是一样的。
(3)在uCOSII里,任务必须写成两种形式之一(《uCOSII中文版》p99页)。在有些RTOS开发环境里没有要求显式调用OSTaskDel(),这是因为开发环境自动做了处理,实际原理都是一样的。uCOSII的开发依赖于编译器,目前没有专用开发环境,所以出现这些不便之处是可以理解的。
在Keil C中定义成const的变量,编译器会在RAM(XDATA)中为其分配空间;而定义成code的变量,编译器则在ROM(CODE)中分配空间。例如:
INT8U const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
改为:
INT8U code OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
可以减少对RAM空间的占用。
关于中断处理的系列问题
关于中断处理的系列问题
中断的任务被调度后,再度获得运行时是从任务被中断处执行。
中断的任务被调度后,再度获得运行时是从任务被中断处执行。
在中断ISR中调用OSIntExit(), 在OSIntExit()中又调用OSIntCtxSW(),此时堆栈结构如下:
栈顶–>OSIntCtxSW()返回地址 2字节
OSIntExit()返回地址 2字节
SP-4–>保存寄存器PUSHALL
中断断点(任务返回地址)
。。。
可见SP-4就是寄存器信息和中断返回地址,OSCtxSw()弹栈后就回到了任务断点。
KEIL调用函数时压入堆栈的就是2字节的返回地址。
中断剩余部分没有被跳过,准确的说是被OSCtxSw借用了。它的内容只有POPALL和RETI,此处没有错误。
中断完成后有可能使某些资源满足,导致阻塞任务就绪,因此要进行任务调度,这样中断返回后的任务就不一定是这个被中断的任务了。
***************************************************************************************************
在UCOS 中典型的中断处理被建议成如下:
void OSTickISR(void)
{
保存处理器寄存器的值;
调用OSIntEnter()或是将OSIntNesting加1;
调用OSTimeTick();
调用OSIntExit();
恢复处理器寄存器的值;
执行中断返回指令;
}
你的程序也是这么做的:
OSTickISR:
USING 0
PUSHALL
CLR TR0
MOV TH0,#70H ;定义Tick=50次/秒(即0.02秒/次)
MOV TL0,#00H ;OS_CPU_C.C 和 OS_TICKS_PER_SEC
SETB TR0
LCALL _?OSIntEnter
LCALL _?OSTimeTick
LCALL _?OSIntExit
POPALL
RETI
这样好象有问题,我觉得“ 调用OSIntEnter()或是将OSIntNesting加1;”应放在中断的第一条语句来执行,以保证在被更高级别中断打断之前将OSIntNesting加1,以避免在高级别中断中发生任务切换。
如果在“保存处理器寄存器的值”时被打断;发生任务切换,何时再返回到被打断的中断程序很难讲了,从切换的原理,好象当被中断打断了的任务再次运行时,应是从被高级别中断打断的低级别中断程序里面的断点处继续运行,即回到中断程序把其余下的部分执行完,再返回任务。( 这样理解请杨大师仔细批评指正一下), 这样,中断处理的时间太长了。中断失去了意义。
对!“ 调用OSIntEnter()或是将OSIntNesting加1;”应放在中断的第一条语句来执行。我当时没有考虑清楚,这是又一个BUG。
你的理解正确。
中断处理延迟在OS里是很常见的现象,最大中断处理延迟是评价OS性能的重要指标。这说明CPU的负荷能力是有限的,工程上以最大中断处理延迟为指标选择CPU速度,只要延迟在允许的范围内就可以接受。如果“中断处理的时间太长了,中断失去了意义”,那么说明所选CPU的能力不够。
在理解OS工作原理的时候,不要想得太理想化,否则总是不得要领,你的思维定势会误导你的思考。注意计算机是个离散数字系统,它不能连续运行。有些观念应该确立,例如:单CPU系统在微观上串行,在宏观上并发;实时指在一定时间范围内完成任务;中断处理必然有延迟等等。
不是你没考虑清楚,而是UCOS 邵贝贝翻译的UCOS 书里也是这么写的,不知UCOS 源码是依据此而产生一些BUGS?
而且您设计的调度方案,当程序任务再度运行时,是直接返回到任务。而不是返回到中断里, 所以当任务再度运行时是回到任务去的,中断剩余部分就被跳过去了。如果中断剩余部分有重要的内容,那就有BUG了。
这样理解对不对呢?
uC/OS-II一进入中断,理论上应立即原语执行OSIntNesting+1,以便后续中断了解中断嵌套情况,不至于在中断嵌套里切换任务。我认为原作此处是个BUG。有些CPU要求中断后必须至少执行一条指令,这期间不会嵌套中断,那么这条指令就可以是OSIntNesting+1原语操作或者关中断(以便原语操作),这样中断嵌套就被严密监视了,不会漏掉导致判断错误。
任务切换是通过模拟一次中断实现的,它和硬件中断产生的堆栈样式相同。在中断嵌套里不会执行任务切换,所有嵌套中断都使用被中断任务的堆栈空间,在最后一个中断快要退出时进行任务调度。
此时中断剩余部分只有两个内容:POPALL和RETI,再没有其他内容,更不用说重要内容了。如果不需调度,中断正常完成,否则,保存现场到TCB,切换任务到高优先级。现场内容包括了断点返回地址,中断剩余部分没有被跳过,它保存在此任务的TCB里,下次调度到此任务时,OSCtxSw借用这个中断的剩余部分,直接返回到任务断点继续执行任务。因为OSCtxSw也是模拟一次中断,栈与这个中断的剩余部分一模一样,可以直接借用。
中断的重要工作均被完成后才会切换任务,最后一个中断剩余的退出部分让给OSCtxSw,假想成发生了一次切换中断(切换中断模拟的就是硬件中断),而不是硬件中断。
第一次任务切换时没有中断栈,就人工模拟一个。在硬件中断里本身就有一个栈,那就直接拿过来用。
***************************************************************************************************
看了您的大作,对其中无任务切换中断的处理好象有点想不通。你在串口中断程序中不作任务切换,所以不调用OSIntEnter(),OSIntNesting没增加;当有更高级别的中断嵌套发生时,则有可能发生任务切换。这样串口中断的堆栈压入和弹出分别在不同任务堆栈中,这样是否会造成堆栈混乱?
是不是即使无任务切换中断处理都应成对调用OSIntEnter()和 OSIntExit()?如果调用会有什么坏影响吗?
SerialISR:
USING 0
PUSHALL
CLR EA
LCALL _?serial
SETB EA
POPALL
RETI
在以上串口中断程序中,从SETB EA 之后到RETI之间,仍然可被高级别中断打端,有可能发生任务切换。即使任务的堆栈不乱,那串口中断程序何时能得以继续运行也是问题,相当于中端程序中调了一个花费很长时间的子程序, 这样理解对吗?
同意你的观点!
我主要是想节省一个任务号,同时提高效率,任务切换太费时间了,中断的标准处理也比较烦琐,所以当时采用了关闭中断的临界资源处理方式,现在看来这么做是有问题的,这是个BUG。你把它按标准中断的处理方式做就可以解决这个问题。本来想偷机取巧来着,没想到漏了破绽,呵呵。
关于串口发送程序的BUG修正
关于串口发送程序的BUG修正
asdjf@163.com 2004/04/09
网友windysons在2004-3-24 14:12:02来信询问如下问题:
——————————————————————————-
你好~我看到你写的serial.c的文件中,有一个问题有点不明白
我把你的serial.c单独拿出来用,没有使用UCOS2,只想输出一个字符PrintChar(‘b’);
但是怎么也出不来,但是我连续调用两次这个函数,就能输出两个b,
但是调用一次,什么也没有显示,这是为什么样?还是你这个文件中的小BUG,谢谢!
——————————————————————————-
2004-3-26 11:33:20网友windysons修改程序如下:
——————————————————————————-
问题我已经解决,稍微改动了一下你的子程序
你的原程序是:
void PrintChar(unsigned char ch) reentrant//显示字符
{
unsigned char *t;
//ES=0;
if( TIflag){
TIflag=0;
TI=1;
}
t=inTxBuf;t++;
if(t==TxBuf+LenTxBuf) t=TxBuf;
if(t==outTxBuf) {/*ES=1;*/return;} //TxBuf Full
*inTxBuf=ch;
inTxBuf=t;
//ES=1;
}
我改动后好使的现程序,只是把标志位判断调到子程序最后就OK了
void PrintChar(unsigned char ch) reentrant//显示字符
{
unsigned char *t;
//ES=0;
t=inTxBuf;t++;
if(t==TxBuf+LenTxBuf) t=TxBuf;
if(t==outTxBuf) {/*ES=1;*/return;} //TxBuf Full
*inTxBuf=ch;
inTxBuf=t;
//ES=1;
if( TIflag){
TIflag=0;
TI=1;
}
}
——————————————————————————-
2004-3-27 21:14:14我发现这是一个BUG
——————————————————————————-
在执行到PrintChar函数里TI=1一句时立即引发串口中断发送过程,若此时显示缓冲区为空,则串口发送中断立即返回,导致自动连续发送过程结束,除非再次被启动。第一次发送时,还没有在显示缓冲区里准备好数据就启动发送过程,导致发送过程停止,即使随后缓冲区里写入数据,也要等下次启动时才能被发出。第二次发送时,显示缓冲区里有数据,发送中断程序被启动后将发出字符,发送完毕后再次引发TI中断,因为串口速率比CPU慢,所以在下次中断到来前CPU有时间把第二个字符写入缓冲区。只要缓冲区里有数据,一经PrintChar首次启动,发送中断就能自己维持连续发送过程,直到缓冲区空。这样,第一次发送时什么也不显示,而第二次发送时显示两个字符。
在ucos51shell程序里也存在这个问题。在人机界面下,输入help,敲入h将不显示,继续敲入e,显示he,接着敲入l不显示,再敲入p,接着显示lp。当时百思不得其解(2002年12月),以为是任务切换导致的错误,又不知道如何写内嵌汇编,就放下了。2004/02/20我在PrintChar里加入关中断的内嵌汇编,同样解决了这个问题。这更使我误以为是任务切换不当造成的。现在看来,这是因为在TI=1前关中断保证了在缓冲区里没有存入数据前,发送中断程序不会被执行,从而避免了中断连续发送过程被打断,自持过程停止需要重新启动的问题。按你所说的方法修改serial.c,shell将正常回显键盘输入字符。
总之,在显示缓冲区为空时不应启动发送过程,要在准备好显示数据后再启动自持发送过程。