档案

Archive for the ‘C51-uCOS’ Category

为uCOS51增加Shell界面

2011/03/14 留下评论

为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

分类:C51-uCOS

uC/OS-II uCOS51 问题

2011/03/14 留下评论

关于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。原作者给的源码是否完整,有无经过技术处理。

分类:C51-uCOS

ucosii工作原理,移植和应用程序的接口

2011/03/14 留下评论

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空间的占用。

分类:C51-uCOS

关于中断处理的系列问题

2011/03/14 留下评论

关于中断处理的系列问题

中断的任务被调度后,再度获得运行时是从任务被中断处执行。

中断的任务被调度后,再度获得运行时是从任务被中断处执行。
在中断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修正

2011/03/14 留下评论

关于串口发送程序的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将正常回显键盘输入字符。
总之,在显示缓冲区为空时不应启动发送过程,要在准备好显示数据后再启动自持发送过程。

关于keilc51入出临界区的内嵌汇编

2011/03/14 留下评论

关于keilc51入出临界区的内嵌汇编
2004/02/20  asdjf@163.com
====
背景
====
2004/02/18收到陈学章网友来信,报告ucos51的BUG,内容如下:
Wednesday, February 18, 2004 1:43 PM

> 在OS_CPU_A.ASM中的OSStartHighRdy函数中你注释
> “;上电后51自动关中断,此处不必用CLR EA指令,因为到此处还未开中断,本程序退出后,开中断.”
> 小弟感觉不妥,因为在系统调用OSInit()时会自动创建一个优先级最低的系统任务,创建过程中会调用OS_EXIT_CRITICAL()打开EA。

经查情况属实。当时移植的时候我也隐隐觉得有些不妥,但考虑到要用内嵌汇编等我不熟悉的技术时,感到困难太大(当时还有很多更重要的技术难点需要解决),就没有深入思考。后来,在调试ucos51shell程序时,发现按键不能及时显示在超级终端上,初步判断是由于在显示程序中使用了不合理的临界区保护代码,但当时没有解决。从现在分析来看,这两个问题都与临界区保护有关,实践的结果也确实如此。

===============================
1.ucos51临界区BUG的保守解决方案
===============================
ucos的作者给出两种临界区保护方案:
方法一:
执行这两个宏的第一个也是最简单的方法是在OS_ENTER_CRITICAL()中调用处理器指令来禁止中断,以及在OS_EXIT_CRITICAL()中调用允许中断指令。
缺点:在这个过程中还存在着小小的问题。如果用户在禁止中断的情况下调用μC/OS-Ⅱ函数,在从μC/OS-Ⅱ返回的时候,中断可能会变成是允许的了!如果用户禁止中断就表明用户想在从μC/OS-Ⅱ函数返回的时候中断还是禁止的。在这种情况下,光靠这种执行方法可能是不够的。嵌套调用OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对时,会引发错误。
优点:(1)速度快;(2)避免关闭中断后调用PEND阻塞类API函数引起死机。

方法二:
执行OS_ENTER_CRITICAL()时先将中断禁止状态保存到堆栈中,然后禁止中断。而执行OS_EXIT_CRITICAL()时只是从堆栈中恢复中断状态。
缺点:(1)总指令周期长,速度慢;
(2)如果用户在中断禁止的时候调用μC/OS-Ⅱ服务,其实用户是在延长应用程序的中断响应时间。
(3)用户的应用程序还可以用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来保护代码的临界段。但是,用户在使用这种方法的时候还得十分小心,因为如果用户在调用象OSTimeDly()之类的服务之前就禁止中断,很有可能用户的应用程序会崩溃。发生这种情况的原因是任务被挂起直到时间期满,而中断是禁止的,因而用户不可能获得节拍中断!很明显,所有的PEND调用都会涉及到这个问题,用户得十分小心。一个通用的办法是用户应该在中断允许的情况下调用μC/OS-Ⅱ的系统服务!
优点:如果用这个方法的话,不管用户是在中断禁止还是允许的情况下调用μC/OS-Ⅱ服务,在整个调用过程中都不会改变中断状态。

结论:
哪种方法更好一点?这就得看用户想牺牲些什么。如果用户并不关心在调用μC/OS-Ⅱ服务后用户的应用程序中中断是否是允许的,那么用户应该选择第一种方法执行。如果用户想在调用μC/OS-Ⅱ服务过程中保持中断禁止状态,那么很明显用户应该选择第二种方法。

ucos书上给出的例子是基于PC机的,它运行的环境是Windows下的DOS仿真。由于此时CPU工作在保护模式,程序运行在V86模式,中断被Windows操作系统接管了,ucos无法实际关闭中断。而且在ucos启动前(OSStart),就有时钟中断存在。作者给出的ucos在DOS环境下的演示方案是:所有例子只提供一个时钟中断,没有其他任何IO中断存在,开关中断只影响tickISR,用户根本不用关心中断是否是允许的,用方法一或方法二入出临界区都可以。“用户必须在开始多任务调度后(即调用OSStart()后)允许时钟节拍中断。换句话说,就是用户应该在OSStart()运行后,μC/OS-Ⅱ启动运行的第一个任务中初始化节拍中断。通常所犯的错误是在调用OSInit()和OSStart()之间允许时钟节拍中断。”对此,作者给出的方案是,创建第一个任务TaskStart ,在这个任务里把ucos的tickISR挂接到硬时钟中断上,然后创建需要的各种任务,接下来死循环周期采样判断是否有按键退出。这样满足了允许时钟节拍中断的时机要求。
对于51上的ucos,由于有多个IO中断,OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对有时嵌套调用,所以,我保守地建议使用第二种方法入出临界区。虽然执行速度慢,但稳定可靠。只要用户遵循“在中断允许的情况下调用μC/OS-Ⅱ的系统服务!”原则,就不会死机。
1。由于#pragma关键字不能出现在H头文件里,所以必须手工修改所有的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()为“4.入出临界区标准代码”所示的样子。用户程序使用入出临界区保护代码时也需手工粘贴此代码,然后按“3.KEILC51内嵌汇编的方法”进行编译。虽然不如宏定义方便,但也不是很麻烦。

2。使用第二种方法,需要在OSIntCtxSw中调整SP指针去掉在调用OSIntExit(),OS_ENTER_CRITICAL(),OSIntCtxSw()过程中压入堆栈的多余内容(SP=SP-5,2+1+2),如下所示:
OS_CORE.C文件:51堆栈由下至上
OSIntExit  //2字节返回地址
OS_ENTER_CRITICAL()  //PUSH IE; 1字节
OSIntCtxSw()  //2字节返回地址

3。上电后51自动关中断,在什么地方打开中断最合适呢?在OSStartHighRdy退出时SETB EA开中断最合适!
为了减少代码量,OSCtxSw、OSIntCtxSw和OSStartHighRdy合用退出代码段,会不会有问题呢?不会!
任务切换只会发生在1、复位;2、tick中断;3、中断;4、系统API调用四个阶段。复位后打开中断,允许系统正常运行是必然的,在OSStartHighRdy退出时必须打开中断。如果是2、3情况引发的调度,中断本来就是打开的,OSIntCtxSw退出时打开中断毫无影响。按照“在中断允许的情况下调用μC/OS-Ⅱ的系统服务!”原则,用户必须在打开中断的情况下调用系统API,所以OSCtxSw退出时打开中断也毫无影响。虽然此时打开中断多此一举,浪费了代码空间和执行时间,但强制退出时开中断,可以稍微起到保护系统不死机的作用(例如:调用系统API前关中断的错误情况)。

通过上面三步,入出临界区的BUG得以完整解决,我仔细审查了一下,觉得这个思路比较严密,应该是比较健壮了,希望网友们继续提出意见和建议,让我们一起把它做得更完善。

=======================================
2.ucos51shell固化后显示不正常的解决方法
=======================================
显示函数(serial.c)在多任务环境下需要临界保护,因为共享同一个输出设备,资源是独占的,否则会出现混乱。比较好的方法是使用信号量互斥,但那样实在是太慢了,为了提高效率,一个简单的办法就是关中断(前提是关中断时间不能太长,以免影响中断响应时间)。
开关中断的方法如“4.入出临界区标准代码”所示,在需要保护的关键段两头粘贴此段代码,然后按照“3.KEILC51内嵌汇编的方法”所示编译即可。
详见ucos51shellv2代码。

=======================
3.KEILC51内嵌汇编的方法
=======================
有时在C51程序中需要嵌入一些汇编代码,这时当然可以用通常的作法:按照 C51 与汇编的接口写一个汇编函数,然后在 C51 程序中调用该函数。(此种方法可在论坛里搜索(www.c51bbs.com),以前有很多帖子讲到,不再重复)
下面介绍直接嵌入汇编代码的方法:
1、在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码:
#pragma ASM
; Assembler Code Here
#pragma ENDASM
例如:
#pragma ASM
PUSH IE;
CLR EA;
#pragma ENDASM
2、在 Project 窗口中包含汇编代码的 C 文件上单击右键,选择“Options for …”,点击右边的“Generate Assembler SRC File”和“Assemble SRC File”,使检查框由灰色变成黑色(有效)状态;
3、根据选择的编译模式,把相应的库文件(如 Small 模式时,是 Keil\C51\Lib\C51S.Lib;Large 模式时,是 Keil\C51\Lib\C51L.Lib)加入工程中,该文件必须作为工程的最后文件(切记是放在最后位置);
4、编译,即可生成目标代码。
问题解答:
问:为什么要把库文件加入工程?是BUG吗?
答:因为实际已经处于汇编连接,所以要加LIB。C连接是自动加的。
建议:
把SRC文件也加入项目中,不然连接的OBJ只是前次生成的。

====================
4.入出临界区标准代码
====================
入临界区时首先保存EA值,然后令EA=0关中断,出临界区时恢复保存的EA值。采用这种方法,在嵌套调用OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()对时,不会发生错误(EA进入临界区前是什么值,退出后还是什么值)。

//OS_ENTER_CRITICAL()
//入临界区
#pragma ASM
PUSH IE;
CLR EA;
#pragma ENDASM

//OS_EXIT_CRITICAL()
//出临界区
#pragma ASM
POP IE;
#pragma ENDASM

关于uCOS51V1.0版本不支持参数传递BUG的修正

2011/03/14 留下评论

关于uCOS51V1.0版本不支持参数传递BUG的修正
2003/05/16   asdjf@163.com  http://www.hjhj.com

uCOS51V1.0版本有一个严重BUG,不支持参数传递(在uCOS51V1.1版本中已经修正)。 网友YAYACOMNET指出此问题,经过检查是由于pdata没有入栈造成的。KEIL编译器对于函数参数的传递种类繁多,有时用寄存器,有时用仿真堆栈,还有的寄存器和堆栈混用,这下处理参数传递好象变得很复杂,似乎无法实现。幸运的是uC/OS-II的任务参数只有一个void *pdata,通过这个空指针,可以传递任意的结构体变量,用户参数安排在结构体里,使用灵活。经过查C51.PDF知,此种情况下,任务的void *ppdata参数恰好是用R3、R2、R1传递,而不通过虚拟堆栈。R3、R2、R1用于传递任务参数ppdata,其中R3代表存储器类型,R2为高字节偏移,R1为低字节位移。因为我用的全是XDATA,所以存储器类型固定为1即R3=1,见C51.PDF第178页说明。修改OS_CPU_C.C的部分代码如下:

void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt) reentrant
{
OS_STK *stk;

ppdata = ppdata;
opt    = opt;                               //opt没被用到,保留此语句防止告警产生
stk    = (OS_STK *)ptos;                    //用户堆栈最低有效地址
*stk++ = 15;                                //用户堆栈长度
*stk++ = (INT16U)task & 0xFF;               //任务地址低8位
*stk++ = (INT16U)task >> 8;                 //任务地址高8位
*stk++ = 0x00;                              //PSW
*stk++ = 0x0A;                              //ACC
*stk++ = 0x0B;                              //B
*stk++ = 0x00;                              //DPL
*stk++ = 0x00;                              //DPH
*stk++ = 0x00;                              //R0

//R3、R2、R1用于传递任务参数ppdata,其中R3代表存储器类型,R2为高字节偏移,R1为低字节位移。
//通过分析KEIL汇编,了解到任务的void *ppdata参数恰好是用R3、R2、R1传递,不是通过虚拟堆栈。
*stk++ = (INT16U)ppdata & 0xFF;             //R1
*stk++ = (INT16U)ppdata >> 8;               //R2
*stk++ = 0x01;                              //R3  因为我用的全是XDATA,所以存储器类型固定为1,见C51.PDF第178页说明。

*stk++ = 0x04;                              //R4
*stk++ = 0x05;                              //R5
*stk++ = 0x06;                              //R6
*stk++ = 0x07;                              //R7
//不用保存SP,任务切换时根据用户堆栈长度计算得出。
*stk++ = (INT16U) (ptos+MaxStkSize) >> 8;   //?C_XBP 仿真堆栈指针高8位
*stk++ = (INT16U) (ptos+MaxStkSize) & 0xFF; //?C_XBP 仿真堆栈指针低8位

return ((void *)ptos);
}

ROM和RAM测试总结

2011/03/14 留下评论

ROM和RAM测试总结
asdjf@163.com  2003/10/17

在硬件系统出厂前要进行产品测试;在嵌入式系统工作之前,一般也要进行自检,其中ROM和RAM检测必不可少,可是有不少人对于测试目的、原因和方法存在错误理解。
为什么要测试ROM和RAM,怎么测试呢?普遍的看法是:由于担心ROM和RAM芯片损坏,在出厂和使用前应该校验这两种芯片的好坏。测试RAM的方法是写读各个内存单元,检查是否能够正确写入;测试ROM的方法是累加各存储单元数值并与校验和比较。这种认识不能说错,但有些肤浅,照此编出的测试程序不完备。一般来说,ROM和RAM芯片本身不大会被损坏,用到次品的概率也比较小,真正出问题的,大都是其他硬件部分,因此,测试ROM和RAM往往是醉翁之意不在酒。

ROM测试
测试ROM的真正目的是保证程序完整性。
嵌入式软件和启动代码存放在ROM里,不能保证长期稳定可靠,因为硬件注定是不可靠的。以flash ROM为例,它会由于以下两种主要原因导致程序挥发:
1。受到辐射。本身工作在辐射环境里/运输过程中受到辐射(如过海关时被X光机检查)。
2。长时间存放导致存储失效,某些0、1位自行翻转。
无论如何,在硬件上存放的程序都是不可靠的。如果完全不能运行,那到也不会造成太大的损失。怕就怕程序可以运行,但某些关键数据/关键代码段被破坏,引发致命错误。为此,必须在程序正常工作前,在软件层面上保证所运行的程序100%没有被破坏,保证现在要运行的程序就是当初写入的。
保证程序完整性的方法很多,例如对全部程序进行CRC校验(-16和-32)/累加和校验(移位累加),只要能在数学上确保出错概率极低,工程上就可以认为程序完整。
程序完整性测试通过,捎带着也就证明了ROM没有被损坏。即测试ROM是否损坏只是测试的副产品,不是主要目的。

RAM测试
测试RAM的真正目的是保证硬件系统的可靠性。
RAM真的是太不容易坏了,我至今还没有看见过一起因为RAM损坏导致的系统不正常现象。不过大部分问题却可以通过RAM测试反映出来。仔细想想,当硬件被生产出来/被插到背板上究竟会发生什么错误呢!是不是感到自己做的板子出问题的可能性更大!请考虑如下几点:
1。生产工艺不过关,过孔打歪了,与临近信号线距离不满足线规甚至打在了线上。
2。由于搭锡引起的信号线粘连。
3。虚焊/漏焊引起的接触不良。
4。不按规程操作,把手印儿印在了高频线上。
5。板子脏了也不吹,覆盖了一层灰尘(内含金属微粒)。
……
这些现象比较有趣,试举几例:
1。地址线A0和A1粘连。读出XXX00、XXX01、XXX10三个字节的数据完全一样。
2。数据线D0和D1粘连。D0和D1只要有一个为0,那么两条线都为0。
3。接触不良。时好时坏。
4。器件表面处理不干净,有助焊剂残留。低速访问正常,大负荷高速访问频繁死机。
总之,我们做的板子在生产中和使用中都会有出错机会,所以出厂前必须测试,使用前必须自检。(当然如果你做的不是实际产品而是实验室样品的话,可以简化步骤。)
如何测试RAM呢?写一个数然后读出来判断显然测不出所有问题,单个测试数据不易覆盖全部测试内容,更不用说定位错误原因了(RAM坏、地址/数据线粘连、接触不良)。好的测试应尽可能测出粘连、RAM坏、单板高频特性。
我总结的方法是这样的:(如测试一个FFH字节的RAM)
首先,测试地址线,
1。’0’滑动,随机选择一个数如55、AA之类,依次写到FEH、FDH、FBH、F7H、EFH、DFH、BFH、7FH地址单元里去,把地址写成二进制数,可以看到比特0在地址总线上从低到高滑动,谓之’0’滑动。目的是测试这些地址线在依次变0时是否稳定正常。当每一根线由1变0,会产生下冲,如果下冲控制不好,在高频时会引起错误。单板上地址线不一定一样长,下冲也就不会完全一样,因此,每一根线都单独测一下下冲性能。
2。’1’滑动,随机选择一个数如55、AA之类,依次写到1H、2H、4H、8H、10H、20H、40H、80H地址单元里去,把地址写成二进制数,可以看到比特1在地址总线上从低到高滑动,谓之’1’滑动。,目的是测试这些地址线在依次变1时是否稳定正常。当每一根线由0变1,会产生上冲,如果上冲控制不好,在高频时会引起错误。单板上地址线不一定一样长,上冲也就不会完全一样,因此,每一根线都单独测一下上冲性能。上冲和下冲是不同的指标,要分别测一下。
3。”全0变全1″,随机选择一个数如55、AA之类,写到FFH单元,再写到00H单元,然后写到FFH单元。把地址写成二进制数,可以看到地址线从全’0’变到全’1’。由信号处理理论知,在电压阶跃跳变时包含无限宽频谱,其中高频部分对外产生辐射,这些辐射信号是干扰源,对临近线路产生较大影响。地址线一般集束布线,同时跳变会引起最大干扰。地址线从全’0’变到全’1’,干扰、上冲、扇出电流影响最大。
4。”全1变全0″,紧接上一步,随机选择一个数如55、AA之类,写到00H单元。把地址写成二进制数,可以看到地址线从全’1’变到全’0’,产生最大下冲干扰。
5。”粘连测试”。依次向不同地址单元写入不同数据并读出判断,如:1、2、3、4……此步骤捎带测试了RAM好坏。注意,千万别用相同数据测试,否则测不出粘连。
6。可选”全0全1连续高速变化”。目的是模拟最恶劣情况(大扇出电流、强干扰、上/下冲)。
然后,测试数据线,(原理与测试地址线相同,1、2两步顺带测试了数据线粘连)
1。’0’滑动,向某一固定地址依次写入FEH、FDH、FBH、F7H、EFH、DFH、BFH、7FH并读出判断。
2。’1’滑动,向某一固定地址依次写入1H、2H、4H、8H、10H、20H、40H、80H并读出判断。
3。”全0变全1″,所有单元置1(先清零再置1并读出判断)。
4。”全1变全0″,所有单元清零(清零并读出判断)。
5。可选”全0全1连续高速变化”。向某一单元高速交替写入若干全’0’和全’1’,最后以全’0’结束。
至此,RAM测试完毕,同时全部存储单元清零。
对于出厂检测程序,有较大发挥余地,如可以加入错误定位代码,自动指出错误原因和错误位置。
每一块单板的高频特性都会因为生产工艺误差(制板、材料、焊接、组装等)和使用情况而各不相同。同一块板子的高频特性在不同情况下表现也不相同。
综上所述,除了测试RAM好坏,大部分代码测的是单板硬件可靠性。
如果不关心高频特性,用原来的测试方法就差不多了(如果测试数据没选好,可能测不出数据线粘连),但应该认识到,测试RAM的主要对象不是RAM本身的好坏,而是连接RAM的单板硬件和线路。

以上是我实际工作经验的一些总结,写出来与大家交流,如有不对之处恳请指正!

源程序(伪代码)
//TEST ROM
TestROM()
{//用移位累加和校验
sum=0;
for(i=0;i<MAXRAMSize;i++){
sum=sum+ram[i];
sum=sum>>1;
}
if(sum==CHECKSUM) printf(“ROM test OK!\n”);
else printf(“ROM test ERROR!\n”);
}

//TEST RAM
TestRAM()
{
//地址线测试
‘0’滑动;
‘1’滑动;
“全0变全1”;
“全1变全0”;
“粘连测试”;
可选”全0全1连续高速变化”;

//数据线测试
‘0’滑动;
‘1’滑动;
“全0变全1”;
“全1变全0″;
可选”全0全1连续高速变化”
}

uCOS51一种低成本的开发方法—ISP

2011/03/14 留下评论

uCOS51一种低成本的开发方法—ISP
asdjf@163.com  2003/10/29

许多网友想通过uCOS51学习RTOS,但苦于没有烧写器和仿真器,无法进行实际的固化和调试,严重影响了学习效果。为此,我再提供一种低成本的开发技术,帮助更多自学的朋友们进入嵌入式开发领域。
如何在没有编程器和仿真器的环境下实现程序的固化和调试呢?其实很简单,随着技术的进步,现在的MCU一般带有BDM/ISP/IAP功能,能够通过JTAG/串口实现程序的写入和调试,根本不需要昂贵的写入调试设备。例如:我使用的P89C51RD2HBP,具有ISP和IAP功能,可以通过串口线下载程序。具体做法如下(参照51上网卡PCB电路):
1。从www.zlgmcu.com上免费下载ZLGISP软件并安装。
2。短接51的第20和29引脚,即:使PSEN接地(当上电时PSEN被拉低,由此强行进入ISP模式),满足ISP硬件激活方式—/PSEN拉低,ALE悬空。
3。通过串口线连接PC机和51。
4。接通电源。
5。运行ZLGISP。使用方法:1、先檫片子;2、然后选择相应HEX文件编程烧录(不用校验);3、点“设置”,将STATUS改为“00”,点写入,就可以了。
6。关闭ZLGISP,关闭电源,断开51第20和29引脚的短接线。
7。打开超级终端,接通电源,就可以看到51程序运行了。
(原来以为很复杂,没想到这么容易,可把我乐坏了,省掉编程器和仿真器,终于可以在家做实验喽。哎,早知道,应该做个ISP编程跳线,也不用象现在这样飞线了。另,在www.zlgmcu.com查“ISP”关键字可以找到很多相关文档。)

因为大部分程序使用C编程,出现低级错误的机会不多,只要在程序关键位置设置打印语句,打印出需要的状态和数据,就能了解程序运行的各种情况和状态,丝毫不比单步、断点调试效果差,最多只是在写打印语句上多花了些时间。利用yyprintf、条件编译、注释等手段,不借助仿真器也能实现调试,而且因为直接在芯片上全速运行,效果可能会更好。这种调试方法简单说就是:烧录–调试–修改–烧录…,往复循环。

好象MON51提供硬件仿真手段,可惜目前还不会用,我想,如果再用上它,真就构成一个完整的低成本开发环境了。

朋友们,还有什么理由不去动手实践一下呢!现在,就DIY吧!

在OSStart前开中断引起的莫名其妙错误

2011/03/14 留下评论

在OSStart前开中断引起的莫名其妙错误
巨龙公司VPN部 杨屹 asdjf@163.com   2004/03/09

2004/03/07网友方吕ladderls来电询问以下问题:

你的ucos-ii在51的移植为啥不能超过11个应用任务?更改任务数量设置无用。
我在keil v623软环境下模拟,建立的任务与您的三个范例任务类同,修改配制文件的最大任务数和最小优先级数,超过11个任务即死机。不知还要修改那些配制?

经检查是ucos51的BUG。我的OS_EXIT_CRITICAL()宏定义为EA=1;,上电后51自动关中断(EA=0,ET0=0),因为在系统调用OSInit()时会自动创建一个优先级最低的系统任务,创建过程中会调用OS_EXIT_CRITICAL()打开EA。而我在InitTimer0函数里使能了T0中断,这个函数在OSStart()前执行,导致在OSStart()前产生中断,致使系统崩溃。

按照我在《关于keilc51入出临界区的内嵌汇编》一文里的方法改正,可以解决这个问题。

或者这样改:(ucos51V2最终采用这种方法)
//OS_CPU_C.C
//初始化定时器0
void InitTimer0(void) reentrant
{
TMOD=TMOD&0xF0;
TMOD=TMOD|0x01;    //模式1(16位定时器),仅受TR0控制
TH0=0x70;    //定义Tick=50次/秒(即0.02秒/次)
TL0=0x00;    //OS_CPU_A.ASM  和  OS_TICKS_PER_SEC
//ET0=1;       //允许T0中断,此时EA=0(51上电缺省值),中断还不会发生,满足在OSStart()前不产生中断的要求。
TR0=1;
}
注释掉InitTimer0函数里的ET0=1,保证在OSStart()前不开时钟中断。
在最高优先级任务里开T0中断:(切记是最高优先级任务)
void TaskStartyya(void *yydata) reentrant
{
yydata=yydata;

//注意!在最高优先级任务循环前打开定时器中断,以满足在OSStart()前不产生中断的要求。
//在系统调用OSInit()时会自动创建一个优先级最低的系统任务,创建过程中会调用OS_EXIT_CRITICAL()打开EA。
//若在InitTimer0()里打开T0中断,则违反了在OSStart()前不产生中断的要求。
//切记将ET0=1;放在最高优先级任务里,OSStart()将调用OSStartHighRdy()第一个运行最高优先级任务,这样ET0=1总能被第一个执行。
ET0=1;

for(;;){

PrintStr(“\t01\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);

}
}

具体到ladderls网友的问题,因为在OSStart()前开了中断,不符合作者要求,会导致系统崩溃。在创建12个以下任务时,时机上的巧合,不会出问题,这些创建函数在12个以下数量时所用时间恰好在T0时钟中断前完成,不会引起崩溃。

改动后的程序如下:

#include <includes.h>

void TaskStartyya(void *yydata) reentrant;
void TaskStartyyb(void *yydata) reentrant;
void TaskStartyyc(void *yydata) reentrant;
void TaskStartyyd(void *yydata) reentrant;
void TaskStartyye(void *yydata) reentrant;
void TaskStartyyf(void *yydata) reentrant;
void TaskStartyyg(void *yydata) reentrant;
void TaskStartyyh(void *yydata) reentrant;
void TaskStartyyi(void *yydata) reentrant;
void TaskStartyyj(void *yydata) reentrant;
void TaskStartyyk(void *yydata) reentrant;
void TaskStartyyl(void *yydata) reentrant;
void TaskStartyym(void *yydata) reentrant;

//注意:我在ASM文件中设置?STACK空间为40H?
OS_STK TaskStartStkyya[MaxStkSize];
OS_STK TaskStartStkyyb[MaxStkSize];
OS_STK TaskStartStkyyc[MaxStkSize];
OS_STK TaskStartStkyyd[MaxStkSize];
OS_STK TaskStartStkyye[MaxStkSize];
OS_STK TaskStartStkyyf[MaxStkSize];
OS_STK TaskStartStkyyg[MaxStkSize];
OS_STK TaskStartStkyyh[MaxStkSize];
OS_STK TaskStartStkyyi[MaxStkSize];
OS_STK TaskStartStkyyj[MaxStkSize];
OS_STK TaskStartStkyyk[MaxStkSize];
OS_STK TaskStartStkyyl[MaxStkSize];
OS_STK TaskStartStkyym[MaxStkSize];

void main(void)
{
OSInit();

InitTimer0();
InitSerial();
InitSerialBuffer();

OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],1);
OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],2);
OSTaskCreate(TaskStartyyc, (void *)0, &TaskStartStkyyc[0],3);
OSTaskCreate(TaskStartyyd, (void *)0, &TaskStartStkyyd[0],4);
OSTaskCreate(TaskStartyye, (void *)0, &TaskStartStkyye[0],5);
OSTaskCreate(TaskStartyyf, (void *)0, &TaskStartStkyyf[0],6);
OSTaskCreate(TaskStartyyg, (void *)0, &TaskStartStkyyg[0],7);
OSTaskCreate(TaskStartyyh, (void *)0, &TaskStartStkyyh[0],8);
OSTaskCreate(TaskStartyyi, (void *)0, &TaskStartStkyyi[0],9);
OSTaskCreate(TaskStartyyj, (void *)0, &TaskStartStkyyj[0],10);
OSTaskCreate(TaskStartyyk, (void *)0, &TaskStartStkyyk[0],11);
OSTaskCreate(TaskStartyyl, (void *)0, &TaskStartStkyyl[0],14);
OSTaskCreate(TaskStartyym, (void *)0, &TaskStartStkyym[0],15);

OSStart();
}

void TaskStartyya(void *yydata) reentrant
{
yydata=yydata;
clrscr();
ET0=1;
for(;;){

PrintStr(“\t01\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);

}
}

void TaskStartyyb(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t02\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);
}
}

void TaskStartyyc(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t03\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);
}
}

void TaskStartyyd(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t04\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);
}
}

void TaskStartyye(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t05\n”);

OSTimeDly(10*OS_TICKS_PER_SEC);
}
}

void TaskStartyyf(void *yydata) reentrant
{
yydata=yydata;

for(;;){
PrintStr(“\t06\n”);

OSTimeDly(20*OS_TICKS_PER_SEC);
}
}

void TaskStartyyg(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t07\n”);

OSTimeDly(20*OS_TICKS_PER_SEC);
}
}

void TaskStartyyh(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t08\n”);

OSTimeDly(20*OS_TICKS_PER_SEC);
}
}

void TaskStartyyi(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t09\n”);

OSTimeDly(20*OS_TICKS_PER_SEC);
}
}

void TaskStartyyj(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t10\n”);

OSTimeDly(20*OS_TICKS_PER_SEC);
}
}

void TaskStartyyk(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t11\n”);

OSTimeDly(30*OS_TICKS_PER_SEC);
}
}

void TaskStartyyl(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t12\n”);

OSTimeDly(30*OS_TICKS_PER_SEC);
}
}

void TaskStartyym(void *yydata) reentrant
{
yydata=yydata;

for(;;){

PrintStr(“\t13\n”);

OSTimeDly(30*OS_TICKS_PER_SEC);
}
}