搜索
bottom↓
回复: 34

学习UCOS II遇到的一些问题,希望得到大家的帮助和指点

[复制链接]

出0入0汤圆

发表于 2010-12-9 11:41:27 | 显示全部楼层 |阅读模式
最近在学习UCOS ii,平台是STC51+KEIL,网上流传的杨屹的移植版本。
问题如下:
1、51单片机调用函数的时候,比如A调用B,那么在进行保护现场也就是将A用到的CPU寄存器和临时变量压入堆栈的过程是怎样的?先后顺序?用汇编和用C有什么不同?A的PC地址是何时压入堆栈的?
2、在OS_CPU_A.ASM文件中有如下一段代码:
?STACK  SEGMENT IDATA
        RSEG ?STACK
OSStack:
        DS 40H
OSStkStart IDATA OSStack-1
这一段是表示堆栈需要重新定位,其预留空间是64字节?OSStack-1为什么这里要减一?编译器是怎么知道用户这里定义是给堆栈的?
3、
;OSTCBCur ===> DPTR  获得当前TCB指针,详见C51.PDF第178页
        MOV  R0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据
        INC  R0
        MOV  DPH,@R0    ;全局变量OSTCBCur在IDATA中
        INC  R0
        MOV  DPL,@R0

上面的代码说指针是3字节?为什么很多教材或者网上的资料都说 8位机下 指针是2字节?
提的问题有点多,呵呵,希望得到大家的帮助!!先谢谢了!

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)

出0入0汤圆

发表于 2010-12-9 11:58:50 | 显示全部楼层
3、keil C51的指针有可能占用:
1字节:存储限定idata
2字节:存储限定xdata, code
3字节:无存储限定,根据右值而变化指向空间。

出0入0汤圆

 楼主| 发表于 2010-12-9 12:12:48 | 显示全部楼层
回复【1楼】John_Lee
3、keil c51的指针有可能占用:
1字节:存储限定idata
2字节:存储限定xdata, code
3字节:无存储限定,根据右值而变化指向空间。

-----------------------------------------------------------------------

楼上说的无存储限定的是指void *ptr这种吧?
但是 OSTCBCur在定义的时候限定它在idata区,那不应该是1字节吗?

出0入0汤圆

 楼主| 发表于 2010-12-9 13:02:12 | 显示全部楼层
顶上去

出0入0汤圆

发表于 2010-12-9 13:05:01 | 显示全部楼层
回复【2楼】AVR_DIY 苹果的另一半
-----------------------------------------------------------------------

并不是说指针本身存储在哪个区域,而是指指针指向的区域限定。
例如:
OS_TCB* idata OSTCBCur; // 指针占用3字节,可以指向任意区域,本身存储在idata。
OS_TCB idata* OSTCBCur; // 指针占用1字节,限定指向idata区域,本身存储由编译器分配。
OS_TCB idata* idata OSTCBCur; // 指针占用1字节,限定指向idata区域,本身存储在idata。

你给的代码里是第1种情况。

出0入0汤圆

发表于 2010-12-9 13:11:31 | 显示全部楼层
无限定区域的指针就是keil C51手册中说的“generic pointer”,这种指针的操作比限定了区域的指针:如“idata pointer”,“xdata pointer”等,要耗费资源一些,如果在程序中能够事先确定数据的存储区域,建议在指针定义时,使用限定了存储区域的指针。

出0入0汤圆

 楼主| 发表于 2010-12-9 13:44:07 | 显示全部楼层
回复【5楼】John_Lee
无限定区域的指针就是keil c51手册中说的“generic pointer”,这种指针的操作比限定了区域的指针:如“idata pointer”,“xdata pointer”等,要耗费资源一些,如果在程序中能够事先确定数据的存储区域,建议在指针定义时,使用限定了存储区域的指针。
-----------------------------------------------------------------------

OS_EXT  idata OS_TCB     *OSTCBCur;                     /* Pointer to currently running TCB         */

代码里面是这样定义的,貌似是你说的第二种哦

出0入0汤圆

 楼主| 发表于 2010-12-9 14:04:03 | 显示全部楼层
data unsigned char    *ptr;   3个字节
unsigned char data    *ptr;   1个字节
奇怪了,为什么上面的两种形式所占空间不一致?我在keil下测试了,关键字data放在前面,占3字节,放在后面占1个字节(这个好理解)。
data unsigned char    ptr;   1个字节
unsigned char data    ptr;   1个字节
而这样都是一样!

出0入0汤圆

发表于 2010-12-9 14:39:20 | 显示全部楼层
回楼上,是正常的现象。
data unsigned char    ptr;   1个字节  
unsigned char data    ptr;   1个字节
这两条语句是相同意义,即定义一个unsigned char型变量ptr。

data unsigned char    *ptr;   3个字节  
unsigned char data    *ptr;   1个字节
这两语句含义不同,
第一个是定义一个通用指针变量,此指针变量存放于data空间,可指向任意内存空间。
第二个是定义一个指向data空间的指针,此指针变量存放于默认空间,指向data内存空间。

因data空间只需一个字节即可描述内存地址,所以指针占用1字节。
通用指针需要三个字节,第一个字节表示指向那个内存空间,第二和第三字节保存地址。

出0入0汤圆

 楼主| 发表于 2010-12-9 14:48:30 | 显示全部楼层
回复【8楼】health
回楼上,是正常的现象。
data unsigned char    ptr;   1个字节   
unsigned char data    ptr;   1个字节
这两条语句是相同意义,即定义一个unsigned char型变量ptr。
data unsigned char    *ptr;   3个字节   
unsigned char data    *ptr;   1个字节  
这两语句含义不同,
第一个是定义一个通用指针变量,此指针变量存放于data空间,可指向任意内存空间。
第二个是定义一个指向data空间的指针,此指针变量存放于默认空间,指向data内存空间。
因data空间只需一个字节即可描述内存地址,所以指针占用1字节。
通用指针需要三个字节,第一个字节表示指向那个内存空间,第二和第三字节保存地址。

-----------------------------------------------------------------------

data unsigned char    *ptr;   和这个unsigned char    *data ptr;   等效了啊?

出0入0汤圆

 楼主| 发表于 2010-12-9 14:55:16 | 显示全部楼层
对于其他单片机,比如说AVR 通用指针都是2字节吧?
因为他的内存,比如mega系列,都是大于256字节的 ,1个8位地址指不了那么多空间?
不知道是不是这样呢?对AVR的指针、堆栈不是很清楚,望大家赐教!

出0入0汤圆

发表于 2010-12-9 15:06:29 | 显示全部楼层
存储修饰符放在 * 之前和之后是两个不同的意思。

放在 * 之前表示修饰的是指向存储区域,这直接影响指针的宽度。
放在 * 之后表示修饰的是指针本身的存储区域,这不影响指针的宽度,但影响访问指针本身的操作。

像51类的CPU就是麻烦,各存储空间的最优访问方法是不同的,且效率差异较大,如果编译器全部采用统一方法(如“generic pointer”)的访问方法,运行效率肯定糟糕,如果只使用某一种(如“idata pointer”),而放弃其它数据空间,又不现实,折中的方案就是“增加方言”,搞几个存储修饰关键字(“idata, data, code, xdata”)让用户自己定义数据访问方式,但造成的问题,就是与标准C不兼容了。所谓“有利就有弊”。

出0入0汤圆

 楼主| 发表于 2010-12-9 15:18:35 | 显示全部楼层
你们说的都是针对51这类特殊的单片机吧?其他的单片机应该没有这些区分吧?

出0入0汤圆

 楼主| 发表于 2010-12-9 15:20:27 | 显示全部楼层
回复【11楼】John_Lee
存储修饰符放在 * 之前和之后是两个不同的意思。
放在 * 之前表示修饰的是指向存储区域,这直接影响指针的宽度。
放在 * 之后表示修饰的是指针本身的存储区域,这不影响指针的宽度,但影响访问指针本身的操作。
像51类的cpu就是麻烦,各存储空间的最优访问方法是不同的,且效率差异较大,如果编译器全部采用统一方法(如“generic pointer”)的访问方法,运行效率肯定糟糕,如果只使用某一种(如“idata pointer”),而放弃其它数据空间,又不现实,折中的方案就是“增加方言”,搞几个存储修饰关键字(“idata, data, code, xdata”)让用户自己定义数据访问方式,但造成的问题,就是与标准c不兼容了。所谓“有利就有弊”。
-----------------------------------------------------------------------

51 虽然比较麻烦,但是比较经典!呵呵 谢谢你的分析!

出0入0汤圆

 楼主| 发表于 2010-12-9 15:22:20 | 显示全部楼层
对了,你们可以分析分析我提出的前两个问题嘛? 我在网上找了好久都没有搞清楚具体51在调用子函数的时候压栈、出栈顺序,以及压哪些内容?

出0入0汤圆

发表于 2010-12-9 15:25:37 | 显示全部楼层
avr也有这个问题。它的存储空间有两个:代码空间和数据空间,访问的指令也是不同的。

编译器方面,iar, icc, cv这几款都作了方言扩充。而gcc则是放弃了代码空间的访问。

出0入0汤圆

 楼主| 发表于 2010-12-9 15:39:47 | 显示全部楼层
回复【15楼】John_Lee
avr也有这个问题。它的存储空间有两个:代码空间和数据空间,访问的指令也是不同的。
编译器方面,iar, icc, cv这几款都作了方言扩充。而gcc则是放弃了代码空间的访问。

-----------------------------------------------------------------------

GCC也可以定义常量在CODE然后读取CODE里面的内容啊
我的意思是说,AVR有没有像51这样指针宽度会随着指向的空间不同而变化,像51指向data、idata就是1字节,指向xdata、code就是两字节,通用指针宽度为3字节...

出0入0汤圆

发表于 2010-12-9 15:57:36 | 显示全部楼层
avr的指针总是16bits的宽度,如果超过了64K的空间,则要使用辅助寄存器扩展,但编译器不支持。

gcc可以访问代码空间?那不是在编译器层面实现的,不是编译器本身的功能,例如:你能定义一个整数在代码空间然后直接用右值表达式访问吗?

我们看到的所谓访问,那是在编译器之外,用inline汇编完成的,编译器本身不知道这个汇编指令的实际意义。

出0入0汤圆

发表于 2010-12-9 16:08:00 | 显示全部楼层
回复【14楼】AVR_DIY 苹果的另一半
对了,你们可以分析分析我提出的前两个问题嘛? 我在网上找了好久都没有搞清楚具体51在调用子函数的时候压栈、出栈顺序,以及压哪些内容?
-----------------------------------------------------------------------

没有做过51的ucos,无法回答你,如果你问avr 或者cortex,倒可以解答你的所有问题。

出0入0汤圆

 楼主| 发表于 2010-12-9 16:20:31 | 显示全部楼层
回复【17楼】John_Lee
avr的指针总是16bits的宽度,如果超过了64k的空间,则要使用辅助寄存器扩展,但编译器不支持。
gcc可以访问代码空间?那不是在编译器层面实现的,不是编译器本身的功能,例如:你能定义一个整数在代码空间然后直接用右值表达式访问吗?
我们看到的所谓访问,那是在编译器之外,用inline汇编完成的,编译器本身不知道这个汇编指令的实际意义。

-----------------------------------------------------------------------

赋值当然不行啊 ,keil也一样不能右值,我说的访问是只读访问,ReadOnly!

回复【18楼】John_Lee
回复【14楼】avr_diy 苹果的另一半
对了,你们可以分析分析我提出的前两个问题嘛? 我在网上找了好久都没有搞清楚具体51在调用子函数的时候压栈、出栈顺序,以及压哪些内容?
-----------------------------------------------------------------------
没有做过51的ucos,无法回答你,如果你问avr 或者cortex,倒可以解答你的所有问题。
-----------------------------------------------------------------------

谢谢你!以后移植UCOS到AVR 或者STM32的时候再找你!呵呵
对了你说的cortex是指M0 、M3都熟悉啊?

出0入0汤圆

发表于 2010-12-9 16:35:07 | 显示全部楼层
回复【19楼】AVR_DIY 苹果的另一半
-----------------------------------------------------------------------

右值!!不是左值。

Cortex-M,各型号差别不大。

出0入0汤圆

发表于 2010-12-9 19:02:39 | 显示全部楼层
回复【9楼】AVR_DIY 苹果的另一半
data unsigned char    *ptr;   和这个unsigned char    *data ptr;   等效了啊?
-----------------------------------------------------------------------
是这样的,按照keil c51手册的叙述,
data char *x; 等效于 char *data x;
推荐使用后者,前者是早期版本C51编译器的写法,目前两种写法都支持,但是将来可能不再支持前者。

出0入0汤圆

 楼主| 发表于 2010-12-9 19:16:40 | 显示全部楼层
回复【21楼】gamethink
很好奇,这个uc版本能否正常运行呢?
-----------------------------------------------------------------------

怎么不能正常运行啊?跑起来了

出0入0汤圆

 楼主| 发表于 2010-12-10 09:36:59 | 显示全部楼层
第一个问题已经找到了答案并在keil下测试了!
1、51单片机调用函数的时候,比如A调用B,那么在进行保护现场也就是将A用到的CPU寄存器和临时变量压入堆栈的过程是怎样的?先后顺序?用汇编和用C有什么不同?A的PC地址是何时压入堆栈的
以下面两个函数为例:
void A(a,b,c)
{
    INT8U a=2,b=3,c=4,d=5;
    B(a,b,c);
}
void B(a,b,c)
{
    a=b+c;
}
A调用B时,入栈情况是这样的:将三个参数a,b,c用通用寄存器保存下来,而局部变量d移入到堆栈空间,如果参数大于3个的话就利用堆栈空间来传递参数。但是这个过程中,是没有操作堆栈指针的! 在调用B之前,硬件是自动将A调用B的下一条PC地址压入了堆栈,顺序是先SP+1(到这里,第二个问题的为什么那个地址要减一已经很清楚了),压地址低字节,SP+1,再地址压高字节。这个过程,即使反汇编,用户是看不见的!
在调用B完后,需要返回A,执行RET指令的时候,硬件又自动从SP指向的栈顶位置弹出那两字节的PC,顺序是:弹出高8位地址,SP-1,再弹出低8位地址,SP-1,到这时候,栈顶栈底重合!指向OSStkStart。一般,keil编译器是把SP堆栈指针放在定义的变量之后的。
    但是中断与子程序调用就不同,它除了压PC的时候需要操作SP指针,其他CPU寄存器也要操作SP指针压入堆栈!
因此,可以编写以下简单代码,执行任务切换(已测试):
#include"STC89C51RC_RD_PLUS.H"
#include "serial.h"
#define OFFSETOF(TYPE,MEMBER)  (unsigned char)(&((TYPE*)0)->MEMBER)                //或得结构体成员的偏移量
sbit Led1=P2^0;
sbit Led2=P2^1;
sbit Led3=P2^2;
sbit Led4=P2^3;
sbit Led5=P2^4;
sbit Led6=P2^5;
sbit Led7=P2^6;
sbit Led8=P2^7;
typedef struct
{
    unsigned char Prio;
        unsigned int  DelayTime;
        unsigned char TaskStat;
}OS_TCB;
OS_TCB TaskTcb;
unsigned char Task1Stk[16];
void Task1(void *Msg );
void Task2(void *Msg );
void TaskCreat(void (*Task)(void *Pdata),unsigned char *PtrTask);
void TaskDelay(unsigned int n );
/***************************************
              主函数
***************************************/
int main(void)
{
    unsigned char Offset=0;
    UartInit();
        SendStr("OS_Test:Usart Is Ok!\r\n",22);
    TaskCreat(Task1,&Task1Stk[0]);                                   //创建一个任务并执行
        return 0;
}
/***************************************
              创建任务并运行
***************************************/
void TaskCreat(void (*Task)(void *Pdata),unsigned char *PtrTask)
{
    SP=&PtrTask[1];                                //将堆栈指针指向人工堆栈栈顶
    *PtrTask++=(unsigned int)Task;                               //将任务地址压入人工堆栈
        *PtrTask++=(unsigned int)Task>>8;      
}
/***************************************
              任务延时
***************************************/
void TaskDelay(unsigned int n )
{
    unsigned int i=0;
    while(n--)
        {
            for(i=250;i>0;i--);
        }                                                  
}
/***************************************
              任务1
***************************************/
void Task1(void *Msg )
{
    void *Message;
        unsigned char i;
        Message=Msg;
    while(1)
        {
            if(i++>25)
                {  
                    i=0;
                    TaskCreat(Task2,&Task1Stk[0]);   
                }
        Led1 = ~Led1;
            TaskDelay(50);                       
        }                                         
}
/***************************************
              任务2
***************************************/
void Task2(void *Msg )
{
    void *Message;
        unsigned char i;
        Message=Msg;
    while(1)
        {
            if(i++>15)
                {  
                    i=0;
                    TaskCreat(Task1,&Task1Stk[0]);   
                }
        Led8 = ~Led8;
            TaskDelay(50);       
        }                                         
}

CX51关于函数调用的说明 (原文件名:q.jpg)

欢迎大家指正!

出0入0汤圆

发表于 2010-12-10 10:46:55 | 显示全部楼层
c51好像不使用堆栈传递函数参数,
默认情况下,首先使用寄存器传递参数,寄存器不够用时再使用固定存储区。

出0入0汤圆

 楼主| 发表于 2010-12-10 13:11:49 | 显示全部楼层
回复【25楼】health
c51好像不使用堆栈传递函数参数,
默认情况下,首先使用寄存器传递参数,寄存器不够用时再使用固定存储区。
-----------------------------------------------------------------------

固定存储区也属于堆栈空间啊

出0入0汤圆

发表于 2010-12-10 13:35:04 | 显示全部楼层
不能这么说,
固定存储区可以是片内data,片外pdata,xdata。
即使是片内data空间,和堆栈也不是一回事。

出0入0汤圆

 楼主| 发表于 2010-12-10 13:38:38 | 显示全部楼层
回复【27楼】health
不能这么说,
固定存储区可以是片内data,片外pdata,xdata。
即使是片内data空间,和堆栈也不是一回事。
-----------------------------------------------------------------------

定义完所有的变量后,剩余的空间不就是堆栈空间吗?内存不设这么分配的?

出0入0汤圆

发表于 2010-12-10 14:46:45 | 显示全部楼层
没错,是这样。

出0入0汤圆

 楼主| 发表于 2010-12-10 15:00:42 | 显示全部楼层
MOV  R0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据
        INC  R0
        MOV  DPH,@R0    ;全局变量OSTCBCur在IDATA中
        INC  R0
        MOV  DPL,@R0

楼上的技术不错 ,问一下上面的那段代码,C51不是大端模式吗?假如一个三字节的指针,最高字节应该是类型,次高字节是高8地址,低字节是低8位地址,那上面的那段怎么解释?
MOV  R0,#LOW (OSTCBCur) 这句是获得低8位地址
INC R0;地址加一岂不是超越了这个指针的位置??

出0入0汤圆

发表于 2010-12-10 15:24:01 | 显示全部楼层
C51为大端模式,即内存低地址保存变量的高位。

出0入0汤圆

 楼主| 发表于 2010-12-10 15:25:53 | 显示全部楼层
回复【31楼】health
c51为大端模式,即内存低地址保存变量的高位。
-----------------------------------------------------------------------

是啊  ,那上面那个汇编怎么好像不是那样呢?

出0入0汤圆

 楼主| 发表于 2010-12-10 15:29:35 | 显示全部楼层
我明白了,是我自己理解错了!
MOV  R0,#LOW (OSTCBCur) 这个命令是获得低地址  不是低字节 谢谢!

出0入0汤圆

发表于 2011-1-17 10:54:15 | 显示全部楼层
记号
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-23 15:30

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表