江哥 发表于 2025-3-11 16:48:09

能工作的 bug 就是好 bug

无意中看到一段关于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.0592MHz12MHz 能够很好的工作
//------------------------------------------------------------------------------------------------------------------------------------------

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;                                                                                        // 位移
    }
}


国芯人工智能 发表于 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>[本文内容由国芯人工智能辅助生成,仅供参考]

soma 发表于 2025-3-11 18:21:29

还能这样玩,unsigned int不是都是整数,0.4不报错或警告吗

江哥 发表于 2025-3-11 19:00:34

soma 发表于 2025-3-11 18:21
还能这样玩,unsigned int不是都是整数,0.4不报错或警告吗

可能编译器比较智能吧

21cnsound 发表于 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)这行啊,默认编译优化等级。
不过确实不报错,传参是小数的话都会取整处理,舍弃小数部分。

xxkj2010 发表于 2025-3-11 20:38:17

应该是取0

江哥 发表于 2025-3-11 22:16:49

21cnsound 发表于 2025-3-11 20:07
; void send_bit_0() {                  // 发送逻辑0

      RSEG?PR?send_bit_0?MAIN


是的,我没表达清楚,是进行一次for判断后跳过
页: [1]
查看完整版本: 能工作的 bug 就是好 bug