AVR_DIY 发表于 2010-12-9 11:41:27

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

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

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

John_Lee 发表于 2010-12-9 11:58:50

3、keil C51的指针有可能占用:
1字节:存储限定idata
2字节:存储限定xdata, code
3字节:无存储限定,根据右值而变化指向空间。

AVR_DIY 发表于 2010-12-9 12:12:48

回复【1楼】John_Lee
3、keil c51的指针有可能占用:
1字节:存储限定idata
2字节:存储限定xdata, code
3字节:无存储限定,根据右值而变化指向空间。

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

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

AVR_DIY 发表于 2010-12-9 13:02:12

顶上去

John_Lee 发表于 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种情况。

John_Lee 发表于 2010-12-9 13:11:31

无限定区域的指针就是keil C51手册中说的“generic pointer”,这种指针的操作比限定了区域的指针:如“idata pointer”,“xdata pointer”等,要耗费资源一些,如果在程序中能够事先确定数据的存储区域,建议在指针定义时,使用限定了存储区域的指针。

AVR_DIY 发表于 2010-12-9 13:44:07

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

OS_EXTidata OS_TCB   *OSTCBCur;                     /* Pointer to currently running TCB         */

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

AVR_DIY 发表于 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个字节
而这样都是一样!

health 发表于 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字节。
通用指针需要三个字节,第一个字节表示指向那个内存空间,第二和第三字节保存地址。

AVR_DIY 发表于 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;   等效了啊?

AVR_DIY 发表于 2010-12-9 14:55:16

对于其他单片机,比如说AVR 通用指针都是2字节吧?
因为他的内存,比如mega系列,都是大于256字节的 ,1个8位地址指不了那么多空间?
不知道是不是这样呢?对AVR的指针、堆栈不是很清楚,望大家赐教!

John_Lee 发表于 2010-12-9 15:06:29

存储修饰符放在 * 之前和之后是两个不同的意思。

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

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

AVR_DIY 发表于 2010-12-9 15:18:35

你们说的都是针对51这类特殊的单片机吧?其他的单片机应该没有这些区分吧?

AVR_DIY 发表于 2010-12-9 15:20:27

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

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

AVR_DIY 发表于 2010-12-9 15:22:20

对了,你们可以分析分析我提出的前两个问题嘛? 我在网上找了好久都没有搞清楚具体51在调用子函数的时候压栈、出栈顺序,以及压哪些内容?

John_Lee 发表于 2010-12-9 15:25:37

avr也有这个问题。它的存储空间有两个:代码空间和数据空间,访问的指令也是不同的。

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

AVR_DIY 发表于 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字节...

John_Lee 发表于 2010-12-9 15:57:36

avr的指针总是16bits的宽度,如果超过了64K的空间,则要使用辅助寄存器扩展,但编译器不支持。

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

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

John_Lee 发表于 2010-12-9 16:08:00

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

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

AVR_DIY 发表于 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都熟悉啊?

John_Lee 发表于 2010-12-9 16:35:07

回复【19楼】AVR_DIY 苹果的另一半
-----------------------------------------------------------------------

右值!!不是左值。

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

health 发表于 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编译器的写法,目前两种写法都支持,但是将来可能不再支持前者。

AVR_DIY 发表于 2010-12-9 19:16:40

回复【21楼】gamethink
很好奇,这个uc版本能否正常运行呢?
-----------------------------------------------------------------------

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

AVR_DIY 发表于 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 intDelayTime;
        unsigned char TaskStat;
}OS_TCB;
OS_TCB TaskTcb;
unsigned char Task1Stk;
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);                                   //创建一个任务并执行
        return 0;
}
/***************************************
            创建任务并运行
***************************************/
void TaskCreat(void (*Task)(void *Pdata),unsigned char *PtrTask)
{
    SP=&PtrTask;                              //将堆栈指针指向人工堆栈栈顶
    *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);   
                }
      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);   
                }
      Led8 = ~Led8;
          TaskDelay(50);       
        }                                       
}
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_603687FA5FXG.jpg
CX51关于函数调用的说明 (原文件名:q.jpg)

欢迎大家指正!

health 发表于 2010-12-10 10:46:55

c51好像不使用堆栈传递函数参数,
默认情况下,首先使用寄存器传递参数,寄存器不够用时再使用固定存储区。

AVR_DIY 发表于 2010-12-10 13:11:49

回复【25楼】health
c51好像不使用堆栈传递函数参数,
默认情况下,首先使用寄存器传递参数,寄存器不够用时再使用固定存储区。
-----------------------------------------------------------------------

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

health 发表于 2010-12-10 13:35:04

不能这么说,
固定存储区可以是片内data,片外pdata,xdata。
即使是片内data空间,和堆栈也不是一回事。

AVR_DIY 发表于 2010-12-10 13:38:38

回复【27楼】health
不能这么说,
固定存储区可以是片内data,片外pdata,xdata。
即使是片内data空间,和堆栈也不是一回事。
-----------------------------------------------------------------------

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

health 发表于 2010-12-10 14:46:45

没错,是这样。

AVR_DIY 发表于 2010-12-10 15:00:42

MOVR0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据
      INCR0
      MOVDPH,@R0    ;全局变量OSTCBCur在IDATA中
      INCR0
      MOVDPL,@R0

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

health 发表于 2010-12-10 15:24:01

C51为大端模式,即内存低地址保存变量的高位。

AVR_DIY 发表于 2010-12-10 15:25:53

回复【31楼】health
c51为大端模式,即内存低地址保存变量的高位。
-----------------------------------------------------------------------

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

AVR_DIY 发表于 2010-12-10 15:29:35

我明白了,是我自己理解错了!
MOVR0,#LOW (OSTCBCur) 这个命令是获得低地址不是低字节 谢谢!

peavey 发表于 2011-1-17 10:54:15

记号
页: [1]
查看完整版本: 学习UCOS II遇到的一些问题,希望得到大家的帮助和指点