数码之家

 找回密码
 立即注册
搜索
查看: 1029|回复: 15

[C51] 学习C51编程遇到一个令人困惑的问题

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

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

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

x
本帖最后由 fireflying 于 2024-12-28 00:07 编辑

用的开发板是STC89C52单片机。
P0.0到P0.7上面接了7个LED一字排列。

练习程序如下,程序的功能是让P0.0到P0.7上面接的8枚LED从左到右依次点亮,到了最右边之后再从右向左依次点亮,这么来回循环的流水灯效果:


void main()
{
        unsigned int i = 0;                //定义延时用途自加变量
        unsigned char cnt = 0;        //定义移位计数变量
       
       
        while(1)        //主循环
        {
                for(cnt=0;cnt<8;cnt++)                //移位计数变量归零,判断移位次数是否小于等于8,计数变量自加
                {
                        P0 = ~(0x80 >> cnt);                //先点亮最左侧LED,然后依次右移
                        for(i=0;i<32767;i++);                //延时
                }
                for(cnt=0;cnt<8;cnt++)                //移位计数变量归零,判断移位次数是否小于等于8,计数变量自加
                {
                        P0 = ~(0x01 << cnt);                //先点亮最右侧LED,然后依次左移
                        for(i=0;i<32767;i++);                //延时
                }
        }
}


其中for语句部分是个延时功能,用来调整相邻两个LED依次点亮的间隔时间。
我为了观察程序延时效果,就调整for语句中间那个条件表达式的数值,当我调整到32767的时候,觉得LED灯的流动速度还是太快了,然后我把那数值调整到32768,数值仅仅加了1,结果流动速度断崖式的变得非常慢。
我在Keil里面Debug跟踪for语句的执行时间,发现32767的时候,执行时间是178毫秒,而32768的时候是1.3秒!
变量i的数据类型是unsigned int,查找资料,unsigned int数据类型的最大值是65535,我这个并没有溢出,但是程序运行的表现有点像超过了32767之后溢出了。
然后我又试着把变量i的数据类型改为unsigned long,在那数值仍然是32767的情况下,流水灯的流动速度就明显变慢了,这下把我彻底搞晕了。

实在检查不出问题原因,反复试验也没找到原因。哪位老师能帮我分析一下这个问题的原因?


发表于 2024-12-28 12:30:43 | 显示全部楼层
不妨试试这样

32768U
32768UL

等形式的
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-12-28 13:42:52 来自手机浏览器 | 显示全部楼层
devcang 发表于 2024-12-28 12:30
不妨试试这样

32768U

没看懂,U和UL啥意思?
回复 支持 反对

使用道具 举报

发表于 2024-12-28 16:39:03 | 显示全部楼层
本帖最后由 mmxx2015 于 2024-12-28 16:40 编辑

是超过32767后16位数据比较变成32数据比较,单次循环运行用时变长导致的,因为不带修饰的整数默认是有符号整数,16位有符号数最大只能表示32767,32768就需要扩展为32数表示。

循环比较<=32767时的编译结果:
  1. ;                         for(i=0;i<32767;i++);        //延时
  2.                         ; SOURCE LINE # 27
  3.         MOV          i?040,#00H
  4.         MOV          i?040+01H,#00H
  5. ?C0006:
  6.         CLR          C
  7.         MOV          A,i?040+01H
  8.         SUBB         A,#0FFH
  9.         MOV          A,i?040
  10.         SUBB         A,#07FH
  11.         JNC          ?C0005
  12. ?C0008:
  13.         INC          i?040+01H
  14.         MOV          A,i?040+01H
  15.         JNZ          ?C0018
  16.         INC          i?040
  17. ?C0018:
  18.         SJMP         ?C0006
  19. ?C0007:
  20. ;                         //for(i=0;i<32768;i++);        //延时
  21. ;                         //for(i=0;i<32768U;i++);        //延时
  22. ;                 }
  23.                         ; SOURCE LINE # 30
  24. ?C0005:
复制代码
循环比较>=32768时的编译结果:
  1. ;                         for(i=0;i<32768;i++);        //延时
  2.                         ; SOURCE LINE # 28
  3.         MOV          i?040,#00H
  4.         MOV          i?040+01H,#00H
  5. ?C0006:
  6.         MOV          R6,i?040
  7.         MOV          R7,i?040+01H
  8.         CLR          A
  9.         MOV          R4,A
  10.         MOV          R5,A
  11.         MOV          R3,#00H
  12.         MOV          R2,#080H
  13.         MOV          R1,#00H
  14.         MOV          R0,#00H
  15.         SETB         C
  16.         LCALL        ?C?SLCMP
  17.         JC           ?C0005
  18. ?C0008:
  19.         INC          i?040+01H
  20.         MOV          A,i?040+01H
  21.         JNZ          ?C0018
  22.         INC          i?040
  23. ?C0018:
  24.         SJMP         ?C0006
  25. ?C0007:
  26. ;                         //for(i=0;i<32768U;i++);        //延时
  27. ;                 }
  28.                         ; SOURCE LINE # 30
  29. ?C0005:
复制代码
C?SLCMP执行的指令:
  1.                  C?SLCMP:
  2. C:0x00C3    EB       MOV      A,R3
  3. C:0x00C4    9F       SUBB     A,R7
  4. C:0x00C5    F5F0     MOV      B(0xF0),A
  5. C:0x00C7    EA       MOV      A,R2
  6. C:0x00C8    9E       SUBB     A,R6
  7. C:0x00C9    42F0     ORL      B(0xF0),A
  8. C:0x00CB    E9       MOV      A,R1
  9. C:0x00CC    9D       SUBB     A,R5
  10. C:0x00CD    42F0     ORL      B(0xF0),A
  11. C:0x00CF    EC       MOV      A,R4
  12. C:0x00D0    6480     XRL      A,#P0(0x80)
  13. C:0x00D2    C8       XCH      A,R0
  14. C:0x00D3    6480     XRL      A,#P0(0x80)
  15. C:0x00D5    98       SUBB     A,R0
  16. C:0x00D6    45F0     ORL      A,B(0xF0)
  17. C:0x00D8    22       RET
复制代码

这就好比跑1000米,一圈200米只需跑5圈就够了,如果换个操场,一圈变成500米,如果还跑5圈,用时就长很多了。


解决办法:
(1)在立即数后面加U后缀指明这是无符号整数;
(2)改成递减计数,如:
  1.                         i=32768;        //延时
  2.                         while(--i);
复制代码
不过,无论哪种修改方法都不推荐,因为一直占用CPU时间,推荐用定时器定时。

打赏

参与人数 1家元 +6 收起 理由
fireflying + 6 感谢解答问题!

查看全部打赏

回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-12-28 17:02:31 | 显示全部楼层
mmxx2015 发表于 2024-12-28 16:39
是超过32767后16位数据比较变成32数据比较,单次循环运行用时变长导致的,因为不带修饰的整数默认是有符号 ...

非常感谢!虽然我看不懂汇编,但是大致明白原因了。
那么,在C51编程当中,我定义数据类型时候用的unsigned没有起到控制该变量为无符号数的作用?
我知道定时器,目前是按照教程练习,如果到了正式应用的时候,我将使用定时器。
回复 支持 反对

使用道具 举报

发表于 2024-12-28 20:08:08 | 显示全部楼层
fireflying 发表于 2024-12-28 17:02
非常感谢!虽然我看不懂汇编,但是大致明白原因了。
那么,在C51编程当中,我定义数据类型时候用的unsign ...

当然,变量、常量是有符号还是无符号数完全由编程者控制,当不指定属性时,编译器按默认属性设置,比如定义变量时不指定位宽、整数/浮点数、有/无符号时,Keil C51按16为有符号整数处理,如:
Var1;

signed int Var1;
两种定义方式都是有效的,而且结果相同。
常数也有默认属性,所以,为了安全,最好在把常数定义成一个宏并末尾加属性,如:
#define    C_MY_DELAY_TIME        32768U        //定义为无符号整数


for(i=0;i<C_MY_DELAY_TIME;i++);    //延时

回复 支持 反对

使用道具 举报

发表于 2024-12-28 22:08:14 | 显示全部楼层
我定义数据类型时候用的unsigned没有起到控制该变量为无符号数的作用?
//----------------------------------------------------------------------------------------
在不同类型的混合运算中,编译器会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。
在判断i<32768是否成立时,i 自动转换为32位
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-12-28 22:34:40 | 显示全部楼层
xixia001 发表于 2024-12-28 22:08
我定义数据类型时候用的unsigned没有起到控制该变量为无符号数的作用?
//------------------------------- ...

在我的程序中,只有两个变量,i和cnt,我理解二者并没有进行混合运算,而是各自独立使用。而i这个变量,我在定义的时候已经明确了它是无符号数,还需要转换一下?

或者,这个意思是不是说,数据类型除了有没有符号,还有位长的不同?
编译器默认情况把unsigned int作为16位数来对待,当我赋值为32768及以上之后,编译器就把它转换为32位?
那么,赋值语句里面写成32768U的作用是啥?强制不让它转换到32位,仍然作为16位对待?

回复 支持 反对

使用道具 举报

发表于 2024-12-29 10:26:23 | 显示全部楼层
本帖最后由 xixia001 于 2024-12-29 10:40 编辑
fireflying 发表于 2024-12-28 22:34
在我的程序中,只有两个变量,i和cnt,我理解二者并没有进行混合运算,而是各自独立使用。而i这个变量, ...

在不同类型的混合运算中,编译器会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算
----这句话是我从网上搜索的,这里的混合运算实际上就是运算的意思,判断 i<32767或者i<32768是否成立就是通过运算获得的。
编译后,通过比较代码大小,也能看出问题。


******将i强制转换为long型,代码量大小一样************************


*************************************************


本帖子中包含更多资源

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

x
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-12-29 11:33:29 | 显示全部楼层
xixia001 发表于 2024-12-29 10:26
在不同类型的混合运算中,编译器会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再 ...

那么,这是不是Keil编译器优化造成的结果?如果数值小,它优先使用位数较小的类型,数值大了之后,它就把数据位数加大?
回复 支持 反对

使用道具 举报

发表于 2024-12-29 19:02:20 | 显示全部楼层
fireflying 发表于 2024-12-29 11:33
那么,这是不是Keil编译器优化造成的结果?如果数值小,它优先使用位数较小的类型,数值大了之后,它就把 ...

这算不算Keil编译器的优化,我说不好,你自己研究吧。
回复 支持 反对

使用道具 举报

发表于 2024-12-30 14:11:52 | 显示全部楼层
mmxx2015 发表于 2024-12-28 16:39
是超过32767后16位数据比较变成32数据比较,单次循环运行用时变长导致的,因为不带修饰的整数默认是有符号 ...

正解。不过对于单核裸机编程,使用定时器基本只是起到精确延时的作用,大部分人不会在使用延时的时候进入低功耗模式,也就不存在占不占用CPU时间的问题了
回复 支持 反对

使用道具 举报

发表于 2024-12-31 20:42:54 | 显示全部楼层
南天音乐 发表于 2024-12-30 14:11
正解。不过对于单核裸机编程,使用定时器基本只是起到精确延时的作用,大部分人不会在使用延时的时候进入 ...

用状态机来判定延时结束与否,不可能一直死等的
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-1-1 10:05:09 来自手机浏览器 | 显示全部楼层
sz1988 发表于 2024-12-31 20:42
用状态机来判定延时结束与否,不可能一直死等的

之前练习按键去抖编程,有网友告知了使用状态机去抖动的办法,彼时只是照猫画虎抄例程实现了功能,但是具体原理并没有搞懂。
我先把按我这教程的顺序把现阶段知识点掌握吧,后续要搞懂状态机。
回复 支持 反对

使用道具 举报

发表于 2025-1-2 10:13:00 | 显示全部楼层
sz1988 发表于 2024-12-31 20:42
用状态机来判定延时结束与否,不可能一直死等的

这个分情况吧,业务逻辑层面的可以使用状态机做异步延时,但同步延时也是有存在的必要的,比如业务逻辑嵌套较深或涉及底层设备驱动时序延时,就不宜再使用异步延时方式,要再提高MCU效率那就要上RTOS了
回复 支持 反对

使用道具 举报

发表于 2025-1-2 11:35:26 | 显示全部楼层
fireflying 发表于 2025-1-1 10:05
之前练习按键去抖编程,有网友告知了使用状态机去抖动的办法,彼时只是照猫画虎抄例程实现了功能,但是具 ...

基本编程循环延时是够用的,不要一下追求复杂的.后续可以玩中断和串口打印,这个是一定要有的.
回复 支持 反对

使用道具 举报

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

本版积分规则

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

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-2 04:02 , Processed in 0.156000 second(s), 11 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

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