数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 1113|回复: 18

[C51] KEIL C51的ldivt实现

[复制链接]
发表于 2024-9-27 12:20:49 | 显示全部楼层 |阅读模式

爱科技、爱创意、爱折腾、爱极致,我们都是技术控

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x
KEIL C51中没有实现ldivt,所以有时做BCD码处理时的除法和取模效率不高,要无谓的计算两次,研究了一下,用了在线汇编把结果直接取出来,比较方便,实现如下:

typedef struct {
        long quot;
        long rem;
} ldiv_t;       

ldiv_t ldiv(long numer, long denom) {
        ldiv_t result;
        result.quot = numer / denom;
//        result.rem = numer % denom;  //此行用于校验result的地址
        __asm MOV        ?_ldiv?BYTE+8+4,R0
        __asm MOV        ?_ldiv?BYTE+8+5,R1
        __asm MOV        ?_ldiv?BYTE+8+6,R2
        __asm MOV        ?_ldiv?BYTE+8+7,R3
        return result;
}


使用:
ldiv_t  test = ldiv(0x12345678, 0x98765);

以上文件可以单独放在一个C文件里实现,这样变量一般不会出错,否则要自行修改以上的汇编中的局部变量引用.
最后C文件属性中注意勾选src两项!

打赏

参与人数 1家元 +15 收起 理由
不长叶子的树 + 15

查看全部打赏

 楼主| 发表于 2024-9-27 12:31:48 | 显示全部楼层
此操作不局限KEIL C51,理论上适用于所有非标C(只需遵守各自嵌入汇编标准即可),因为底层调用除法子程序后,商和余数肯定是都存在的,只需取出即可.
回复 支持 反对

使用道具 举报

发表于 2024-9-27 15:02:21 | 显示全部楼层
qd0426 发表于 2024-9-27 12:31
此操作不局限KEIL C51,理论上适用于所有非标C(只需遵守各自嵌入汇编标准即可),因为底层调用除法子程序后,商 ...

用C51测试,这种做法虽然可以避免2次除法,但效率还可以进一步提高。
Keil C51对余数的处理是这样的:8位余数用B返回,16位余数用(R4,R5返回),32位余数用(R0,R1,R2,R3)返回,对于16位、32位余数,可以用汇编语言编写返回2字节、4字节的函数,内容如下:
2字节余数:
  1. Return_Remainder_2Byte:
  2.         MOV        A,        R4
  3.         MOV        R6,        A
  4.         MOV        A,        R5
  5.         MOV        R7,        A

  6.         RET
复制代码
4字节余数:
  1. Return_Remainder_4Byte:
  2.         MOV        A,        R0
  3.         MOV        R4,        A
  4.         MOV        A,        R1
  5.         MOV        R5,        A
  6.         MOV        A,        R2
  7.         MOV        R6,        A
  8.         MOV        A,        R3
  9.         MOV        R7,        A

  10.         RET
复制代码
需要使用余数时,紧接着除法调用保存余数函数。
如:

unsigned long data result = ldiv(0x12345678, 0x98765);
unsigned long remainder=Return_Remainder_4Byte();

这种做法有个局限,就是商保存需要使用data区变量,否则,会因为间接寻址修改R0、R1的值。

回复 支持 反对

使用道具 举报

发表于 2024-9-27 15:17:00 | 显示全部楼层
本帖最后由 inthsunshine 于 2024-9-27 15:38 编辑

我在stm32上测试分别求商和求余数,如果优化级别选o3, 将只有1次除法,编译器会自动优化

回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 16:07:41 | 显示全部楼层
mmxx2015 发表于 2024-9-27 15:02
用C51测试,这种做法虽然可以避免2次除法,但效率还可以进一步提高。
Keil C51对余数的处理是这样的:8位 ...

这个写法是实现标准ldiv_t结构及其函数ldiv(),这个是标准C库里面已有的,不过C51里面就没有这个
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 16:09:27 | 显示全部楼层
inthsunshine 发表于 2024-9-27 15:17
我在stm32上测试分别求商和求余数,如果优化级别选o3, 将只有1次除法,编译器会自动优化

...

ARM上的话不存在这个问题,它支持ldiv_t结构
回复 支持 反对

使用道具 举报

发表于 2024-9-27 16:20:40 | 显示全部楼层
qd0426 发表于 2024-9-27 16:09
ARM上的话不存在这个问题,它支持ldiv_t结构

改玩stm32吧,g0系列也很便宜,不比51贵多少,却好用很多
回复 支持 反对

使用道具 举报

发表于 2024-9-27 17:54:44 | 显示全部楼层
实际使用的时候,结构体寻址会不会更影响效率
回复 支持 反对

使用道具 举报

发表于 2024-9-27 20:21:55 | 显示全部楼层
inthsunshine 发表于 2024-9-27 15:17
我在stm32上测试分别求商和求余数,如果优化级别选o3, 将只有1次除法,编译器会自动优化

...

我测试的结果还是执行2次除法。
C程序:
  1. unsigned int Var32_1,Var32_2,Var32_3,Var32_4;

  2. void Ldiv_Test(void)
  3. {
  4.         Var32_1=0x12345678;
  5.         Var32_2=0x98765;

  6.         Var32_3=(Var32_1/Var32_2);
  7.         Var32_4=(Var32_1%Var32_2);
  8. }
复制代码
汇编程序:
  1. Ldiv_Test PROC
  2.         LDR      r1,|L0.28|
  3.         LDR      r2,|L0.36|
  4.         LDR      r0,|L0.32|
  5.         UDIV     r3,r1,r2        ;r3=r1/r2
  6.         STRD     r1,r2,[r0,#0]        ;r1,r2-->[r0,#0]
  7.         STR      r3,[r0,#8]        ;Var32_3=r3
  8.         UDIV     r3,r1,r2        ;r3=r1/r2
  9.         MLS      r1,r2,r3,r1        ;r1=r1-(r2*r3)
  10.         STR      r1,[r0,#0xc]  ; Var32_4=r1
  11.         BX       lr
  12.         ENDP

  13. |L0.28|
  14.         DCD      0x12345678
  15. |L0.32|
  16.         DCD      ||.data||
  17. |L0.36|
  18.         DCD      0x00098765

  19.         AREA ||.arm_vfe_header||, DATA, READONLY, NOALLOC, ALIGN=2

  20.         DCD      0x00000000

  21.         AREA ||.data||, DATA, ALIGN=2

  22. Var32_1
  23.         DCD      0x00000000
  24. Var32_2
  25.         DCD      0x00000000
  26. Var32_3
  27.         DCD      0x00000000
  28. Var32_4
  29.         DCD      0x00000000
复制代码

回复 支持 反对

使用道具 举报

发表于 2024-9-27 20:59:39 | 显示全部楼层
qd0426 发表于 2024-9-27 16:09
ARM上的话不存在这个问题,它支持ldiv_t结构

在Keil MDK上测试发现实现的效率很低。
C程序:
  1. unsigned int Var32_1,Var32_2;
  2. ldiv_t ldiv_test;

  3. void Ldiv_Test(void)
  4. {
  5.         Var32_1=0x12345678;
  6.         Var32_2=0x98765;

  7.         ldiv_test = ldiv(Var32_1, Var32_2);
  8. }
复制代码
汇编程序:
  1. Ldiv_Test PROC
  2.         PUSH     {r2-r4,lr}
  3.         LDR      r4,|L0.32|
  4.         LDR      r2,|L0.36|
  5.         LDR      r1,|L0.28|
  6.         STRD     r1,r2,[r4,#0]
  7.         MOV      r0,sp
  8.         BL       ldiv
  9.         LDRD     r0,r1,[sp,#0]
  10.         STRD     r0,r1,[r4,#8]
  11.         POP      {r2-r4,pc}
  12.         ENDP

  13. |L0.28|
  14.         DCD      0x12345678
  15. |L0.32|
  16.         DCD      ||.data||
  17. |L0.36|
  18.         DCD      0x00098765

  19.         AREA ||.arm_vfe_header||, DATA, READONLY, NOALLOC, ALIGN=2

  20.         DCD      0x00000000

  21.         AREA ||.data||, DATA, ALIGN=2

  22. Var32_1
  23.         DCD      0x00000000
  24. Var32_2
  25.         DCD      0x00000000
  26. ldiv_test
  27.         %        8
复制代码
看起来汇编程序行数很少,但是其中的ldiv子程序很长:
  1. 0x00000154 B510      PUSH     {r4,lr}
  2. 0x00000156 460B      MOV      r3,r1
  3. 0x00000158 4604      MOV      r4,r0
  4. 0x0000015A 4611      MOV      r1,r2
  5. 0x0000015C 4618      MOV      r0,r3
  6. 0x0000015E F000F803  BL.W     0x00000168 __aeabi_idiv
  7. 0x00000162 E9C40100  STRD     r0,r1,[r4,#0]
  8. 0x00000166 BD10      POP      {r4,pc}
  9. 0x00000168 B570      PUSH     {r4-r6,lr}
  10. 0x0000016A 2400      MOVS     r4,#0x00
  11. 0x0000016C 4625      MOV      r5,r4
  12. 0x0000016E 2800      CMP      r0,#0x00
  13. 0x00000170 DA01      BGE      0x00000176
  14. 0x00000172 2401      MOVS     r4,#0x01
  15. 0x00000174 4240      RSBS     r0,r0,#0
  16. 0x00000176 2900      CMP      r1,#0x00
  17. 0x00000178 DA01      BGE      0x0000017E
  18. 0x0000017A 2501      MOVS     r5,#0x01
  19. 0x0000017C 4249      RSBS     r1,r1,#0
  20. 0x0000017E F000F819  BL.W     0x000001B4 __aeabi_uidiv
  21. 0x00000182 42AC      CMP      r4,r5
  22. 0x00000184 D000      BEQ      0x00000188
  23. 0x00000186 4240      RSBS     r0,r0,#0
  24. 0x00000188 2C00      CMP      r4,#0x00
  25. 0x0000018A D000      BEQ      0x0000018E
  26. 0x0000018C 4249      RSBS     r1,r1,#0
  27. 0x0000018E BD70      POP      {r4-r6,pc}
  28. 0x00000190 4C06      LDR      r4,[pc,#24]  ; @0x000001AC
  29. 0x00000192 4D07      LDR      r5,[pc,#28]  ; @0x000001B0
  30. 0x00000194 E006      B        0x000001A4
  31. 0x00000196 68E0      LDR      r0,[r4,#0x0C]
  32. 0x00000198 F0400301  ORR      r3,r0,#0x01
  33. 0x0000019C E8940007  LDM      r4,{r0-r2}
  34. 0x000001A0 4798      BLX      r3
  35. 0x000001A2 3410      ADDS     r4,r4,#0x10
  36. 0x000001A4 42AC      CMP      r4,r5
  37. 0x000001A6 D3F6      BCC      0x00000196
  38. 0x000001A8 F7FFFFB8  BL.W     0x0000011C __main_after_scatterload
  39. 0x000001AC 5B6C      DCW      0x5B6C
  40. 0x000001AE 0000      DCW      0x0000
  41. 0x000001B0 5B8C      DCW      0x5B8C
  42. 0x000001B2 0000      DCW      0x0000
  43. 0x000001B4 B530      PUSH     {r4-r5,lr}
  44. 0x000001B6 460B      MOV      r3,r1
  45. 0x000001B8 4601      MOV      r1,r0
  46. 0x000001BA 2000      MOVS     r0,#0x00
  47. 0x000001BC 2220      MOVS     r2,#0x20
  48. 0x000001BE 2401      MOVS     r4,#0x01
  49. 0x000001C0 E009      B        0x000001D6
  50. 0x000001C2 FA21F502  LSR      r5,r1,r2
  51. 0x000001C6 429D      CMP      r5,r3
  52. 0x000001C8 D305      BCC      0x000001D6
  53. 0x000001CA FA03F502  LSL      r5,r3,r2
  54. 0x000001CE 1B49      SUBS     r1,r1,r5
  55. 0x000001D0 FA04F502  LSL      r5,r4,r2
  56. 0x000001D4 4428      ADD      r0,r0,r5
  57. 0x000001D6 1E15      SUBS     r5,r2,#0
  58. 0x000001D8 F1A20201  SUB      r2,r2,#0x01
  59. 0x000001DC DCF1      BGT      0x000001C2
  60. 0x000001DE BD30      POP      {r4-r5,pc}
复制代码
这可能与ldiv的参数是两个long型变量有关,实际上,我们常用的是unsigned int变量(32位无符号变量)。



回复 支持 反对

使用道具 举报

发表于 2024-9-27 21:09:46 | 显示全部楼层
mmxx2015 发表于 2024-9-27 20:21
我测试的结果还是执行2次除法。
C程序:
汇编程序:

刚才回看确实还需要2次除法,下午我把测试代码直接插到程序里,编译后源代码和汇编分割错位,第2个除法被划到后面的语句,漏看,又看了下SDIV, UDIV汇编指令,没有保存余数的功能
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 21:13:12 | 显示全部楼层
inthsunshine 发表于 2024-9-27 16:20
改玩stm32吧,g0系列也很便宜,不比51贵多少,却好用很多

玩和做项目区别很大!!!做项目,客户对一个bit的资源都会计较,一个uA的功耗可能就丢了单子
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 21:16:42 | 显示全部楼层
mmxx2015 发表于 2024-9-27 20:59
在Keil MDK上测试发现实现的效率很低。
C程序:
汇编程序:

视平台而定,long型一般是4bytes长度,unsigned int有可能编译成4bytes长,有可能2bytes长,如果要确保2bytes,你要的是短整型short int
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 21:19:28 | 显示全部楼层
mmxx2015 发表于 2024-9-27 20:59
在Keil MDK上测试发现实现的效率很低。
C程序:
汇编程序:

代码看起来长,但是都是执行一次即可,这个并不会占用太多运算时间,耗时间的是乘除运算里面的循环,在ARM上,带硬件除法指令那这个时间无所谓,如果不带硬件除法器,那就很耗时间了
回复 支持 反对

使用道具 举报

发表于 2024-9-27 21:35:57 | 显示全部楼层
qd0426 发表于 2024-9-27 21:19
代码看起来长,但是都是执行一次即可,这个并不会占用太多运算时间,耗时间的是乘除运算里面的循环,在AR ...

我想说的是在STM32平台依次做32位无符号数除法、取余数运算用ldiv()实现比直接做一次32位无符号数除法和一次32位无符号数取余数效率低。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 21:49:58 | 显示全部楼层
mmxx2015 发表于 2024-9-27 21:35
我想说的是在STM32平台依次做32位无符号数除法、取余数运算用ldiv()实现比直接做一次32位无符号数除法和 ...

如果芯片带硬件除法器,效率应该是低一些,视情况来用吧
回复 支持 反对

使用道具 举报

发表于 2024-9-27 22:03:16 | 显示全部楼层
qd0426 发表于 2024-9-27 21:49
如果芯片带硬件除法器,效率应该是低一些,视情况来用吧

我是用M3内核芯片试的,M3内核是有硬件除法器的,不熟悉ARM汇编,没看明白编译器怎么算的。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-9-27 22:04:45 | 显示全部楼层
mmxx2015 发表于 2024-9-27 22:03
我是用M3内核芯片试的,M3内核是有硬件除法器的,不熟悉ARM汇编,没看明白编译器怎么算的。
...

我看了下你上面的汇编,你这个M3核应该是性能很强的,mls指令应该是扩展的DSP指令,一般的M3可没有这些指令
回复 支持 反对

使用道具 举报

发表于 2024-9-27 22:24:06 | 显示全部楼层
qd0426 发表于 2024-9-27 22:04
我看了下你上面的汇编,你这个M3核应该是性能很强的,mls指令应该是扩展的DSP指令,一般的M3可没有这些指 ...

是中颖SH32F284,器件手册上只说“使用ARM CortexTM-M3最新的r2p1版本内核”,没有什么特别的描述,指令描述里对MLS的描述与其它指令一样。把Keil工程的芯片改为STM32F103C8,编译结果一样,也是有MLS指令的。
  1. MLS.W <Rd>, <Rn>, <Rm>, <Racc>        将两个带符号或无符号的寄存器值相乘,并将低32位与寄存
  2. 器值相减
复制代码



回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-7-13 00:32 , Processed in 0.218400 second(s), 11 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

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