找回密码
 立即注册
查看: 117|回复: 6

能工作的 bug 就是好 bug

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:57
  • 最近打卡:2025-05-02 00:21:46

13

主题

43

回帖

136

积分

注册会员

积分
136
发表于 2025-3-11 16:48:09 | 显示全部楼层 |阅读模式
无意中看到一段关于WS2812的代码,看起来那么回事,不过。。。delay_us(0.4);  是什么鬼?
诡异的是代码居然可以工作。祭出示波器看了看波形,还真是可以的。
仔细想了想,delay_us(0.4) 其实并没有真的执行0.4个延时,而是跳过了(进行一次 for 判断,不满足则跳过)。
延时循环等函数都有自己的开销,所以 WS2812_PIN = 0 后面可以不加任何延时语句,从波形看依然有足够的低电平时间。
高电平的短延时用一个 __nop__ 就够了,长延时根据频率 数个 __nop__ 。

void delay_us(unsigned int us) {
    unsigned int i;
    while(us--) {
        for(i = 0; i < 10; i++);
    }
}

void send_bit_1() {                  // 发送逻辑1
    WS2812_PIN = 1;
    delay_us(1);            // 约800ns
    WS2812_PIN = 0;
    delay_us(0.4);          // 约400ns
}

void send_bit_0() {                  // 发送逻辑0
    WS2812_PIN = 1;
    delay_us(0.4);          // 约400ns
    WS2812_PIN = 0;
    delay_us(1);            // 约800ns
}


最后上面的几个函数合并为一个,11.0592MHz  12MHz 能够很好的工作
//------------------------------------------------------------------------------------------------------------------------------------------

void send_byte(unsigned char byte){
    unsigned char i;
    for(i = 0; i < 8; i++) {
        if(byte & 0x80) { WS2812_PIN = 1;_nop_();_nop_();_nop_();_nop_();}        // 如果1: 输出长高电平 (IO拉高,几个__nop__充当延时)
        WS2812_PIN = 1;                                                                                 // 接着不判断继续输出短高电平(即 1 发送 长高电平+短高电平;  0 只发送 短高电平 )
        WS2812_PIN = 0;                                                                                // 低电平时间不用额外加延迟(循环、位移等执行需要时间,刚刚好)
        byte <<= 1;                                                                                        // 位移
    }
}


回复

使用道具 举报 送花

3

主题

1160

回帖

1031

积分

等待验证会员

积分
1031
发表于 2025-3-11 16:53:00 | 显示全部楼层
在嵌入式系统中,尤其是在控制WS2812这类RGB LED灯带时,时序的精确性至关重要。WS2812的通信协议要求严格的高低电平时序,以确保数据能够正确传输。帖子中提到的代码片段涉及到一个延时函数 `delay_us(0.4)`,虽然表面上看起来不合理,但实际却能够工作,这引发了我们对嵌入式系统中延时机制和编译器优化的深入思考。<br><br>首先,`delay_us(0.4)` 的写法在C语言中是不合法的,因为 `delay_us` 函数的参数类型是 `unsigned int`,而 `0.4` 是一个浮点数。编译器在处理这种不匹配的类型时,通常会将其转换为整数 `0`。因此,`delay_us(0.4)` 实际上等同于 `delay_us(0)`,即不执行任何延时操作。<br><br>然而,代码仍然能够正常工作,这可以从以下几个方面来解释:<br><br>1. **延时函数的开销**:在嵌入式系统中,延时函数的执行本身会带来一定的开销。即使 `delay_us(0)` 不执行任何延时循环,函数调用的开销(如压栈、跳转等)仍然会消耗一定的时间。对于WS2812的时序要求来说,这些开销可能已经足够满足低电平时间的需求。<br><br>2. **编译器优化**:现代编译器在编译代码时会进行各种优化,以提高代码的执行效率。在优化过程中,编译器可能会将一些不必要的操作(如空循环)完全移除。因此,`delay_us(0)` 在编译后可能不会产生任何实际的指令,从而进一步减少了延时的影响。<br><br>3. **硬件特性**:WS2812的通信协议虽然对时序有严格要求,但在实际应用中,硬件本身可能对时序有一定的容错能力。即使延时略有偏差,硬件仍能正确解析信号。<br><br>4. **`__nop__` 指令的使用**:`__nop__` 是一种无操作指令,通常用于精确控制延时。在WS2812的通信中,高电平的短延时确实可以通过 `__nop__` 来实现,而长延时则可以通过多个 `__nop__` 指令的组合来满足。<br><br>综上所述,`delay_us(0.4)` 虽然看起来不合理,但在实际应用中却能够正常工作,这得益于延时函数的开销、编译器优化、硬件容错性以及 `__nop__` 指令的使用。然而,从代码的可读性和可维护性角度来看,建议开发者遵循标准的编程规范,避免使用不合法的语法,并明确注释代码的意图,以便他人理解和维护。<br><br>在实际开发中,建议使用更精确的延时方法,如硬件定时器或精确的 `__nop__` 指令组合,以确保代码的可靠性和可移植性。同时,开发者应充分了解目标硬件的特性和通信协议的要求,以便编写出高效、稳定的嵌入式代码。<br><br>[本文内容由国芯人工智能辅助生成,仅供参考]
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:423
  • 最近打卡:2025-05-02 07:55:56
已绑定手机

19

主题

3190

回帖

4870

积分

论坛元老

积分
4870
发表于 2025-3-11 18:21:29 | 显示全部楼层
还能这样玩,unsigned int不是都是整数,0.4不报错或警告吗
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:57
  • 最近打卡:2025-05-02 00:21:46

13

主题

43

回帖

136

积分

注册会员

积分
136
发表于 2025-3-11 19:00:34 | 显示全部楼层
so*** 发表于 2025-3-11 18:21
还能这样玩,unsigned int不是都是整数,0.4不报错或警告吗

可能编译器比较智能吧
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:453
  • 最近打卡:2025-05-02 08:15:40
已绑定手机

13

主题

1257

回帖

2975

积分

金牌会员

积分
2975
发表于 2025-3-11 20:07:52 | 显示全部楼层
; void send_bit_0() {                  // 发送逻辑0

        RSEG  ?PR?send_bit_0?MAIN
send_bit_0:
        USING        0
                        ; SOURCE LINE # 21
;     WS2812_PIN = 1;
                        ; SOURCE LINE # 22
        SETB         WS2812_PIN
;     delay_us(0.4);          // 约400ns
                        ; SOURCE LINE # 23
        CLR          A
        MOV          R7,A
        MOV          R6,A
        LCALL        _delay_us
;     WS2812_PIN = 0;
                        ; SOURCE LINE # 24
        CLR          WS2812_PIN
;     delay_us(1);            // 约800ns
                        ; SOURCE LINE # 25
        MOV          R7,#01H
        MOV          R6,#00H
        LJMP         _delay_us
; END OF send_bit_0

实测9.60版本的C51不会跳过delay_us(0.4)这行啊,默认编译优化等级。
不过确实不报错,传参是小数的话都会取整处理,舍弃小数部分。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:521
  • 最近打卡:2025-05-02 09:38:13

109

主题

1900

回帖

5214

积分

论坛元老

积分
5214
发表于 2025-3-11 20:38:17 | 显示全部楼层
应该是取0
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:57
  • 最近打卡:2025-05-02 00:21:46

13

主题

43

回帖

136

积分

注册会员

积分
136
发表于 2025-3-11 22:16:49 | 显示全部楼层
21cns*** 发表于 2025-3-11 20:07
; void send_bit_0() {                  // 发送逻辑0

        RSEG  ?PR?send_bit_0?MAIN

是的,我没表达清楚,是进行一次for判断后跳过
回复 支持 反对

使用道具 举报 送花

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

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 13:10 , Processed in 0.124360 second(s), 89 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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