【问题标题】:Ways to divide the high/low byte from a 16bit address?从 16 位地址中划分高/低字节的方法?
【发布时间】:2011-02-02 01:48:59
【问题描述】:

我正在 8051 处理器上开发软件。一个常见的工作是划分一个 16 位地址的高字节和低字节。我想看看有多少种方法可以实现它。到目前为止我提出的方法是:(说 ptr 是一个 16 位指针,而 int 是 16 位 int)[注意 rn 和 arn 是寄存器]

按位运算

ADDH = (unsigned int) ptr >> 8;
ADDL = (unsigned int) ptr & 0x00FF;

SDCC 给出以下汇编代码


;   t.c:32: ADDH = (unsigned int) ptr >> 8;
    mov ar6,r3
    mov ar7,r4
    mov _main_ADDH_1_1,r7
;   t.c:33: ADDL = (unsigned int) ptr & 0x00FF;
    mov _main_ADDL_1_1,r6
Keil C51 给了我:

                                           ; SOURCE LINE # 32
0045 AA00        R     MOV     R2,ptr+01H
0047 A900        R     MOV     R1,ptr+02H
0049 AE02              MOV     R6,AR2
004B EE                MOV     A,R6
004C F500        R     MOV     ADDH,A
                                           ; SOURCE LINE # 33
004E AF01              MOV     R7,AR1
0050 EF                MOV     A,R7
0051 F500        R     MOV     ADDL,A
其中有许多无用的代码恕我直言。

指针技巧


ADDH = ((unsigned char *)&ptr)[0];
ADDL = ((unsigned char *)&ptr)[1];
SDCC 给了我:

;   t.c:37: ADDH = ((unsigned char *)&ptr)[0];
    mov _main_ADDH_1_1,_main_ptr_1_1
;   t.c:38: ADDL = ((unsigned char *)&ptr)[1];
    mov _main_ADDL_1_1,(_main_ptr_1_1 + 0x0001)
Keil C51 给了我:

                                           ; SOURCE LINE # 37
006A 850000      R     MOV     ADDH,ptr
                                           ; SOURCE LINE # 38
006D 850000      R     MOV     ADDL,ptr+01H
这与 SDCC 版本相同。

Andrey 的数学方法


 ADDH = ptr / 256;
 ADDL = ptr % 256;

SDCC 给出:


;   t.c:42: ADDH = (unsigned int)ptr / 256;
    mov ar5,r3
    mov ar6,r4
    mov ar7,r6
    mov _main_ADDH_1_1,r7
;   t.c:43: ADDL = (unsigned int)ptr % 256;
    mov _main_ADDL_1_1,r5
我不知道为什么 sdcc 使用 r7 寄存器... Keil C51 给了我:

                                           ; SOURCE LINE # 42
0079 AE00        R     MOV     R6,ptr
007B AF00        R     MOV     R7,ptr+01H
007D AA06              MOV     R2,AR6
007F EA                MOV     A,R2
0080 F500        R     MOV     ADDH,A
                                           ; SOURCE LINE # 43
0082 8F00        R     MOV     ADDL,R7
我不知道为什么 Keil 也不使用 R2 寄存器...

semaj 的联合方法


typedef union
   {
   unsigned short u16;
   unsigned char u8[2];
   } U16_U8;

U16_U8 ptr;

// Do something to set the variable ptr ptr.u16 = ?;

ADDH = ptr.u8[0]; ADDL = ptr.u8[1];

SDCC 给我


;   t.c:26: ADDH = uptr.u8[0];
    mov _main_ADDH_1_1,_main_uptr_1_1
;   t.c:27: ADDL = uptr.u8[1];
    mov _main_ADDL_1_1,(_main_uptr_1_1 + 0x0001)
Keil C51 给了我:

                                           ; SOURCE LINE # 26
0028 850000      R     MOV     ADDH,uptr
                                           ; SOURCE LINE # 27
002B 850000      R     MOV     ADDL,uptr+01H
这对指针技巧非常微笑。但是,这种方法需要多两个字节的内存来存储联合。

有没有人有其他好的想法? ;)

谁能告诉我哪种方式更有效?

如果有人感兴趣,这里是测试用例:


typedef union
{
    unsigned short u16;
    unsigned char u8[2];
} U16_U8;

// call a function on the ADDs to avoid optimizition void swap(unsigned char *a, unsigned char *b) { unsigned char tm; tm = *a; *a = *b; *b = tm; }

main (void) { char c[] = "hello world."; unsigned char xdata *ptr = (unsigned char xdata *)c; unsigned char ADDH, ADDL; unsigned char i = 0;

U16_U8 uptr;
uptr.u16 = (unsigned short)ptr;

for ( ; i < 4 ; i++, uptr.u16++){
    ADDH = uptr.u8[0];
    ADDL = uptr.u8[1];
    swap(&ADDH, &ADDL);
}

for ( ; i < 4 ; i++, ptr++){
    ADDH = (unsigned int) ptr >> 8;
    ADDL = (unsigned int) ptr & 0x00FF;
    swap(&ADDH, &ADDL);
}
for ( ; i < 4 ; i++, ptr++){
    ADDH = ((unsigned char *)&ptr)[0];
    ADDL = ((unsigned char *)&ptr)[1];
    swap(&ADDH, &ADDL);
}
for ( ; i < 4 ; i++, ptr++){
    ADDH = (unsigned int)ptr / 256;
    ADDL = (unsigned int)ptr % 256;
    swap(&ADDH, &ADDL);
}

}

【问题讨论】:

  • “任何人都可以告诉我哪种方式更有效”。既然你有 8051 编译器,而我没有,那么你如何发布每个选项的反汇编,让其他人有机会评论效率;-)
  • 感谢您的提示。我希望有一种方法可以“从 C 代码中查看 asm 代码”。但正如你所问,我明天会上传asm代码。
  • 为了提高效率,您需要知道所涉及的每条指令的循环计数(每种情况下为 1 或 2)。 8052.com/51mov 有这个列表,但它是一个 PITA 为你的 SDCC 程序集工作,因为它没有列出操作码,所以你必须弄清楚每条指令是 MOV 的哪个变体。在比较使用不同寄存器的代码变体时还要注意,您实际查看的代码块可能会对其他代码位产生连锁反应。正如您所说,某些版本使用的寄存器比您预期的要多,可能是出于实际原因......
  • 所以,总而言之,测试它可能更容易。也许在模拟器上。

标签: c 8051 sdcc keil


【解决方案1】:

最有效的方法是完全依赖编译器。您肯定必须弄清楚如何从编译器中获取 8051 项目的程序集列表。

您可以尝试的一种类似于已经提到的方法是联合:

typedef union
   {
   unsigned short u16;
   unsigned char u8[2];
   } U16_U8;

U16_U8 ptr;

// Do something to set the variable ptr
ptr.u16 = ?;

ADDH = ptr.u8[0];
ADDL = ptr.u8[1];

【讨论】:

  • 我在使用 GCC 处理 AVR 上的字节序问题时使用此方法,而其他方法并不总是生成像样的代码——尤其是对于常量。
【解决方案2】:

另一种不那么聪明的地址分割方式:

 ADDH = ptr / 256;
 ADDL = ptr % 256;

【讨论】:

  • 哥们,你知道像 8051 这样的处理器上 div 指令的成本吗?
  • @Andrey:我不知道 8051 的编译器是什么样的,但至少对于无符号类型,如果 C 编译器(在任何架构上)声称完全优化,我会感到惊讶,为ptr / 256 与ptr &gt;&gt; 8 发出不同的代码。我不能说它使用什么 insns,但编译器发现等价并选择最好的并不难。负值不一定那么简单。
【解决方案3】:

最有效的是第一个,因为它是在单条指令中完成的。

不!对不起,我骗了你。我忘记了 8051 指令集只有 1 位移位指令。第二个应该更快,但是编译器可能会生成愚蠢的代码,所以要小心并检查汇编代码。

【讨论】:

  • 但是有没有其他方法来划分地址?
  • 第二个如何生成多条指令?我的猜测是它们同样有效
  • 取决于编译器。最简单的方法是编译和检查程序集
  • @Anders:我认为一个简单的编译器可能会搞砸 2 的主要方式是,它可能会将ptr 从寄存器中写入堆栈以获取它的地址,然后从堆栈中读回一个字符。所以对两行代码进行一次写入和两次读取(希望快速内存,如缓存,我根本不知道 8051)。相反,代码的第一个版本可能只执行一些整数操作,也可能不执行。我注意到 8051 的大多数寄存器都是 8 位的,因此任何一个选项都可能是空操作:寄存器可以重新分配,因此 ptr 的低位寄存器现在是 ADDH。
  • 首先很慢。请参阅我修改后的答案。 8051 没有缓存,但是片上内存很快,对开发者不透明
【解决方案4】:

我只是创建了两个定义(如下)。

这似乎更直接,更不容易出错。

#define HI(x)  ((x) >> 8)
#define LO(x)  ((x) & 0xFF)

【讨论】:

  • 谢谢!比一直输入所有表达式更清楚。但是按位运算是否比其他方式更有效?
猜你喜欢
  • 1970-01-01
  • 2021-09-13
  • 1970-01-01
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
相关资源
最近更新 更多