Blogs

微控制器简介 - 驾驶WS2812 RGB LED

麦克风 Silva.2013年11月14日31评论

快速链接

本教程章节是一个绕道的一点,但我认为一个有趣和有用的曲线。 它引入了一些汇编语言编程,并演示了一个紧密的串行数据协议。 它涉及RGB LED,这在自己的权利中非常有趣,特别是这些新部分。 所以我以为我会发布这个 为读者时间提供一些假期照明试验。

回到未来

请记住,我们如何使用简单的闪烁LED启动本教程系列? 那么块上有一个相当新的和非常酷的RGB LED,称为WS2812(WS2812B是最新型号)。 这是一个带内置WS2811驱动器芯片的表面安装RGB LED,在LED封装中,您可以随时看到它,以及R,G和B LED,当您看芯片面时。 BTW,RGB代表红色,绿色,蓝色,如果你不知道。 通过RGB的组合,您可以在任何颜色中重现任何颜色,因此在某种意义上,可控RGB LED是通用LED。

本文以PDF格式提供,便于打印

我可以在网上找到的大多数图片显示较旧的WS2812部分,内部结构略有不同,其具有6个引脚而不是WS2812B的4个引脚。 这是来自好人的WS2812的漂亮照片 www.sparkfun.com.. 您可以清楚地看到3个LED模具以及板载WS2811控制器。

WS2812 RGB LED

WS2812B就是关于 从硬件角度来看,您可以想象的最简单的设备。  Here is a 数据表的副本. 它有4个引脚:VDD(3.5至5.3VDC),GND,DIN和DOUT。 DIN和DOUT引脚是魔法发生的地方。 表示RGB亮度的数据位被串行送入 进入DIN引脚,并且芯片剥离了前24位(每个用于R,G,B)的8位,并将其余位发送出DOUT引脚。 通过将LED连接到一个字符串中,通过从一个LED到下一个LED的DIN的DOUT,每个LED依次从数据流的前部关闭它所需的位,并将其余的数据流发送出来到下一个LED。 理论上,您可以使用单个数据线驱动的LED数量没有限制 - 唯一的限制是更新字符串中的所有LED所需的时间线性地随着字符串中的LED数而增加。 这使得一种非常聪明和有效的方案,用于解决唯一的RGB数据,以在字符串中连接的任何数量的LED。

自拍串行数据

所有串行数据协议都需要时钟来重新组装接收的数据。 该时钟可以是诸如SPI设备上的SCK线的显式时钟信号,或者时钟可以是隐式的,预先商定的时钟,例如UART设备上的波特率设置(具有用于同步的开始和停止位数据到预定时钟的数据或时钟可以内置于串行数据流中。 WS2812B使用这种第三种方法的形式,由此每个位由“1”组成,后跟“0”,并且只能通过'1'间隔是否更长或短于'0'间隔来确定位值。 对于WS2812B,每个位定义为:

  • 0位:0.40us hi,0.85us lo
  • 1位:0.80us hi,0.45us lo

这些时序中的每一个都具有+/- 0.15us的容差,因此有一个相当数量的锯齿空间给出了串行数据时序。 这与自盘序列数据很常见,其中只有在广泛的时序限制内只有“1”到“0”的比率。

您可以从这些数字中看到,单个数据位需要1.25us。 这意味着单个字节需要10us,所有3个字节的RGB数据需要30us。 在以驾驶芯片的方式上升时,必须记住这些时代非常重要。 此外,请记住,位或字节或字节三态之间没有延迟或添加时间。 我们添加到数据流的唯一时间延迟是“重置”延迟至少为50us(但它可以是任何持续时间长)。 因此,至少50us的数据输出线上的空闲'0'重置用于下一批传入数据的所有芯片。 

挑选微控制器

在两个微控制器系列之间,我们在本教程中使用,我们将使用AVR来驱动WS2812B芯片。 这种选择的主要原因之一是WS2812B基本上是5V芯片,而AVR可以在5V处运行,与大多数ARM皮质M3不同。 这使我们不得不从5V LED芯片电压推导3.3V UC电压,并且必须从3.3V UC到5V LED芯片提供水平换档栅极。

做数学

许多AVR可以在8MHz的内部振荡器上运行,或0.125us /时钟。 这意味着1.25us数据位仅为10个UC时钟。 那不是很多! 事实证明,可以使用8MHz AVR编写可以正确驱动WS2812B的AVR装配代码。 这是一个紧密的契合,但它可以完成。 我们无法获得上面指定的确切时间,但我们可以获得适合定时限制内的时序。 具体而言,我们将瞄准'1'位 7个时钟的1'输出,后跟3个时钟的0'输出,以及3个时钟的1'输出的“0”位,然后是7个时钟的0'输出。 此外,我们需要将此定时保持在一串N个字节(或相反,对于N个LED的3 * N字节)。

我几乎没有写过汇编语言(虽然我读过一个相当于看编译输出文件的金额),所以我认为这将是一个有趣的回归到我的练习。 它也是现实世界的证据,即使用汇编语言,虽然这些天稀有, is by no means dead. 我可以想到任何可以使用C的8MHz AVR完成。

你肯定需要一份副本 AVR指令集 手动遵循此代码,除非您已经让它非常致力于内存。

编码

以下是我想出的汇编语言代码来驱动ws2812bs。 它采取了相当数量的循环计数和摆弄,但最终我很高兴发现我击中了规格。 我将首先列出代码,然后解释它。 顺便说一下,该函数称为Output_GRB,因为对于一些无法解释的原因,WS2812B需要顺序G-R-B而不是通用R-G-B顺序的串行数据。  Go figure. 对于每个字节,数据从MSB,位7开始移除。

 #define __SFR_OFFSET 0 
#include <avr/io.h> 

;extern void output_grb(u8 * ptr, u16 count)
;
; r18 = data byte
; r19 = 7-bit count
; r20 = 1 output
; r21 = 0 output
; r22 = SREG save
; r24:25 = 16-bit count
; r26:27 (X) = data pointer

.equ      OUTBIT,   0


.global output_grb
output_grb:
         movw   r26, r24      ;r26:27 = X = p_buf
         movw   r24, r22      ;r24:25 = count
         in     r22, SREG     ;save SREG (global int state)
         cli                  ;no interrupts from here on, we're cycle-counting
         in     r20, PORTB
         ori    r20, (1<<OUTBIT)         ;our '1' output
         in     r21, PORTB
         andi   r21, ~(1<<OUTBIT)        ;our '0' output
         ldi    r19, 7        ;7 bit counter (8th bit is different)
         ld     r18,X+        ;get first data byte
loop1:
         out    PORTB, r20    ; 1   +0 start of a bit pulse
         lsl    r18           ; 1   +1 next bit into C, MSB first
         brcs   L1            ; 1/2 +2 branch if 1
         out    PORTB, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte?
         breq   bit8          ; 1/2 +7 last bit, do differently
         rjmp   loop1         ; 2   +8, 10 total for 0 bit
L1:
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte
         out    PORTB, r21    ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2/1 +8 10 total for 1 bit (fall thru if last bit)
bit8:
         ldi    r19, 7        ; 1   +9 bit count for next byte
         out    PORTB, r20    ; 1   +0 start of a bit pulse
         brts   L2            ; 1/2 +1 branch if last bit is a 1
         nop                  ; 1   +2
         out    PORTB, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         ld     r18, X+       ; 2   +4 fetch next byte
         sbiw   r24, 1        ; 2   +6 dec byte counter
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret
L2:
         ld     r18, X+       ; 2   +3 fetch next byte
         sbiw   r24, 1        ; 2   +5 dec byte counter
         out     PORTB, r21   ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret

此代码是C的函数可调用。  在c中,函数声明是

extern void输出_grb(U8 * PTR,U16计数);

因此,它需要两个参数,指向8位的指针(16位) 数据阵列,以及数组中的字节数的16位计数。 由于我们正在编写由C调用的ASM函数,因此我们需要了解一些AVR-GCC详细信息,例如如何声明这样的功能,并使其可见C代码,如何访问传递参数,以及什么AVR寄存器,我们可以在不担心返回时恢复它们。 我们可以找到所有这些信息 AVR-GCC Wiki. 例如,我们了解到第1参数在R24:R25中传递,R22:R23中的第二参数,寄存器R18–R27,R30,R31可以在没有恢复的情况下使用。

基于此信息,我们可以将数据指针分配给R26:27(“X'寄存器对)和我们的数据计数器到R24:25。 我们必须移动我们的数据计数器,因为SBIW指令仅适用于以R24开始的寄存器对。 现在我们依次查看代码的每个部分,所以这里是我们函数的初始化部分:

初始化

 .global output_grb
output_grb:
         movw   r26, r24      ;r26:27 = X = p_buf
         movw   r24, r22      ;r24:25 = count
         in     r22, SREG     ;save SREG (global int state)
         cli                  ;no interrupts from here on, we're cycle-counting
         in     r20, PORTB
         ori    r20, (1<<OUTBIT)         ;our '1' output
         in     r21, PORTB
         andi   r21, ~(1<<OUTBIT)        ;our '0' output
         ldi    r19, 7        ;7 bit counter (8th bit is different)
         ld     r18,X+        ;get first data byte

在这里,我们如上所述移动两个16位参数,将SREG(带全局中断标志)保存到现在的自由R22并禁用中断(任何中断都是由于时序而完全爆炸此代码),然后我们阅读我们的输出端口(PORTB)并在串行数据输出引脚上创建一个带有“0”输出值的端口数据的2份,以及输出引脚上的“1”输出值。 使用这两个值保存,我们可以快速向串行数据输出行写入“0”或“1”(并且快速是每位10个周期的游戏名称!)。

接下来,我们将计数器寄存器装入数字7。 我们的算法使用每个字节的前7位的不同代码而不是第8位,因为在第8位,我们需要获取数据的下一个字节,如果我们在0时递减字节计数器并退出。 当我们移出7个数据位时,该位计数器会告诉我们。 最后,我们将第一个数据字节并丢进到串行输出循环中。

'0'数据位,前7位

loop1:
         out    PORTB, r20    ; 1   +0 start of a bit pulse
         lsl    r18           ; 1   +1 next bit into C, MSB first
         brcs   l1            ; 1/2 +2 branch if 1
         out    PORTB, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte?
         breq   bit8          ; 1/2 +7 last bit, do differently
         rjmp   loop1         ; 2   +8, 10 total for 0 bit

在每个数据位的开头,我们发送出'1'并将要发送的位(R18的MSB)移入携带标志。 然后我们分支到标签 'L1'如果该位是'1',或者如果它是'0',则掉球。 在这里,我们将重点关注秋季或“0”状态。 每条指令后注意评论中有2个数字。 首先是指令的CPU时钟计数,第二个是由于输出位的开头以来的总时钟计数。 分支指令如果分支未采用1个时钟,如果拍摄2,则为2,因此1/2表示法。 第二编号是自比特输出开始以来的时钟总数。 因此,我们看到,对于“0”输出位,当我们通过BRC指令并将“0”落下并输出到输出线时,我们的“1”脉冲已为3个时钟或375ns,然后在3个时钟后设置输出到'0'。

将输出线设置为“0”后,我们有一个NOP,然后我们移动R18的MSB(这是要输出的下一个位,而不是当前位)进入T标志。  我们为所有7位进行(通过循环7次)这样做,但我们只关心最后一个或第8位。 对于其他位,T标志被忽略。 然后我们递减我们的位计数器,如果为0,则分支到'bit8'以输出第8位。 如果我们的位计数器不是0,我们将循环回“Loop1”以输出另一个位。 请注意,在循环回到“Loop1”的情况下,我们的总环路已经拍摄了10个时钟以输出'0'数据位。 我们将在稍后查看第8位的情况。

接下来我们将看看第一个之一的情况 7 bits is a '1':

'1'数据位,前7位

L1:
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte
         out    PORTB, r21    ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2/1 +8, 10 total for 1 bit (fall thru if last bit)

 当我们到达'l1'时,我们是+4时钟而不是+3,因为所花的分支是2个时钟,而不是1。 我们再次有一个NOP,然后将位7复制到T中,然后按比特计数器计数。 然后我们将输出线设置为“0”(使“1”输出为7个时钟),然后测试递减比特计数器的结果(我们可以执行此操作,因为“OUT”指令没有改变 任何CPU标志)。 如果我们不在第8位,我们会根据需要使用分支(另一个2个时钟)总共10个时钟。 如果我们不采取分支,我们将通过“bit8”掉线

'0'数据位,第8位

bit8:
         ldi    r19, 7        ; 1   +9 bit count for next byte
         out    PORTB, r20    ; 1   +0 start of a bit pulse
         brts   L2            ; 1/2 +1 branch if last bit is a 1
         nop                  ; 1   +2
         out    PORTB, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         ld     r18, X+       ; 2   +4 fetch next byte
         sbiw   r24, 1        ; 2   +6 dec byte counter
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret

请记住,当我们分支或跌倒Thu到'Bit8'时,我们只执行了9个时钟。 我们现在将使用第10个时钟加载位计数器 with 7 for the next byte. 现在,在10个时钟,我们将输出线设置为“1”以进行第8个数据位的开始。 由于我们已经将第8个数据位移动到T中,我们不需要将位移入携带,因此我们保存了一个时钟,我们稍后需要。 对于“0”位,我们通过“BRCS”分支,并在将数据排序线设置为“0”之前进行NOP(以便为“0”位为3时钟HI)。 现在我们将下一个数据字节加载到R18中并递减字节计数器。 如果我们有更多字节,我们将循环回到下一个字节的“循环1”。 否则,我们还原全局中断并从子程序呼叫返回。

 

'1'数据位,第8位

L2:
         ld     r18, X+       ; 2   +3 fetch next byte
         sbiw   r24, 1        ; 2   +5 dec byte counter
         out     PORTB, r21   ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret

如果我们的第8个数据位是'1',我们跳过NOP(因为我们拍摄了BRG,添加了一个额外的时钟),然后加载下一个字节并递减字节计数器,就像'0'位情况一样。 在递减计数器之后,我们将输出线设置为“0”,然后如果输出有更多的数据字节,则将分支回“Loop1”。 否则,我们还原全局中断并返回,就像“0”位案例一样。

一个警告

每个WS2812B可以每LED消耗最多18.5mA,如果芯片上的所有3个LED完全驱动,则为55.5mA(这将是明亮的白色)。  在5V下,每台LED约为92mW或每芯片高达277MW。 它非常容易最终有一条WS2812BS,要求许多电流AMP - 一条带60 /米的单个1米条,可以达到3.3安培和16.5瓦。 因此,首先执行数学,并确保您的电源和电线和连接器可以处理所设置可以绘制的当前电流。

一个简单的例子

这是一个简单的C代码示例,它驱动了6个LED串的WS2812BS。 购买这些LED的一种非常常见的方式是每米的30,60或144个LED的柔性条带,您可以根据需要和线路,VDD和GND线路的“前端”(它们具有指示数据流方向的箭头或文本)。 所以我们已经切断了一个6 LED的条带,我们将为我们的例子使用。

对于6个RGB LED,我们需要一个数据缓冲区 6*3 or 18 bytes. 我们将通过红色,绿色,蓝色,黄色(红色+绿色),水色(绿色+蓝色)和紫色(红色+蓝色)模式运行每个LED。 请记住,WS2812B的数据顺序是GRB,例如,BUF [0]将保持1个WS2812B的绿色值,BUF [1]将保持红色值,BUF [2]将保持蓝色值, Buf [3]将为第2 Ws2812b等持有绿色值,等等。

此示例中的串行数据的输出位是PB0。 此代码还显示了使用AVR-GCC延​​迟函数_delay_ms()的使用,我不推荐一般(使用计时器和中断),但在这种情况下,它很快且简单,不会导致任何并发症。

//
// AVR_2812
// 6 WS2812B LEDs
// 8MHz internal osc
//

#define F_CPU   8000000

#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>

typedef uint8_t   u8;
typedef uint16_t  u16;

#define NUM_WS2812    6
#define NUM_LEDS      (NUM_WS2812*3)

enum {S_R, S_G, S_B, S_Y, S_V, S_T};
  
#define MAX   50

// declaration of our ASM function
extern void输出_grb(U8 * PTR,U16计数);

void set_color(u8 * p_buf, u8 led, u8 r, u8 g, u8 b)
{
  u16 index = 3*led;
  p_buf[index++] = g;
  p_buf[index++] = r;
  p_buf[index] = b;  
}

int main(void)
{
  u8 buf[NUM_LEDS];
  int count = 0;
  
    DDRB = 1;   // bit 0 is our output
    
    memset(buf, 0, sizeof(buf));

    u8 state = S_R;
    u8 val = 0;
    u8 first_time = 1;
        
    while(1)
    {
      output_grb(buf, sizeof(buf));
      
      switch (state)
      {
      case S_R:
        if (++val <= MAX)
        {
          if (!first_time)
          {
            set_color(buf, 5, val, MAX-val, MAX-val);
          }  
          set_color(buf, 0, val, 0, 0);
        }
        else
        {
          first_time = 0;
          state = S_G;
          val = 0;
        }
        break;
        
      case S_G:
        if (++val <= MAX)
        {
          set_color(buf, 0, MAX-val, val, 0);
          set_color(buf, 1, 0, val, 0);
        }
        else
        {
          state = S_B;
          val = 0;
        }
        break;
      
      case S_B:
        if (++val <= MAX)
        {
          set_color(buf, 1, 0, MAX-val, val);
          set_color(buf, 2, 0, 0, val);
        }
        else
        {
          state = S_Y;
          val = 0;
        }
        break;
      
      case S_Y:
        if (++val <= MAX)
        {
          set_color(buf, 2, val, 0, MAX-val);
          set_color(buf, 3, val, val, 0);
        }
        else
        {
          state = S_V;
          val = 0;
        }
        break;
      
      case S_V:
        if (++val <= MAX)
        {
          set_color(buf, 3, MAX-val, MAX-val, val);
          set_color(buf, 4, val, 0, val);
        }
        else
        {
          state = S_T;
          val = 0;
        }
        break;
      
      case S_T:
        if (++val <= MAX)
        {
          set_color(buf, 4, MAX-val, val, MAX-val);
          set_color(buf, 5, 0, val, val);
        }
        else
        {
          state = S_R;
          val = 0;
        }
        break;
      
      default:
        state = S_R;
        break;
      }

      _delay_ms(100);
    }
}

 我发现难度的RGB LED的良好视频很难,至少与我拥有的廉价相机。 这是一个视频,示出了上面的操作中的代码。 不幸的是,即使我使LED亮度低至50(可能255),也是如此,即使我使LED亮度为50的颜色也是如此。 还有一些有趣的PWM伪像在每个LED帖子上垂直爬上屏幕。 尽管视频差,但在实际观看WS2812B的颜色富有丰富和明亮(以完全强度,否则太亮,否则除非来自相当遥远)。


[]
评论 梅利维2015年3月30日
嗨,我正在使用STM32F100RB,我确实将时钟配置为24MHz,我需要帮助转动16环RGB WS2812,我想知道你是否可以帮助我,我为PWM写了代码。
[]
评论 Gavin69.2016年6月4日
一个很好的解释。如果您包含该项目的工作版本,这可能会更好,这也可能熨烫其他问题,例如“String.h”的缺失声明。

我对微量控制器相对较新的是,无法在Atmel Studio中获得你的榜样 - 这是一个链接问题,毫无疑问在我可以考虑如何考虑你的代码时,毫无疑问会在互联网上花费一堆时间代替工作。
[]
评论 Saravananece82.2016年8月21日
WS2811编码有什么不同& WS2812Bs
现在我使用了8MHz的Atmega162,但仍然无法正常工作
[]
评论 DNYANESH012018年3月1日

作者删除的评论

[]
评论 sysshad.2014年1月1日
伟大的指南!
我多年来一直在使用AVR的众多,但我仍然每天学习东西。喜欢你的asm :)
[]
评论 jacek.82.2014年1月12日
这么伟大的教程,但不知道如何将日食拼凑起来
[]
评论 jacek.82.2014年1月12日
这么伟大的教程,但不知道如何移动日食
[]
评论 jacek.82.2014年1月13日
构建文件:../main.c
调用:AVR编译器
avr-gcc -wall -os -fpack-struct -fshort-enums -std = gnu99 -funsigned-char -funsigned-bitfields -mmcu = Atmega8 -df_cpu = 8000000UL -mmd -mp -mf“main.d”-mt“main .d“-c -o”main.o“”../main.c“
在文件中包含../main.c:12:
../defs.h:7:警告:内置函数'calloc'的冲突类型
../main.c:22:错误:预期'*'*'令牌之前'
../main.c:24:错误:预期')''*'令牌之前
../main.c:在功能'main'中:
../main.c:34:错误:'U8'未销毁(在此功能中首次使用)
../main.c:34:错误:(每个未释录的标识符仅报告一次
../main.c:34:错误:对于它出现的每个函数。)
../main.c:34:错误:预期';'在'buf'之前
../main.c:39:警告:隐式函数'memset'声明
../main.c:39:警告:内置函数'memset'的不兼容隐式声明
../main.c:39:错误:'buf'未删除(第一次使用此功能)
../main.c:41:错误:预期';'在“州”之前
../main.c:42:错误:预期';'在'val'之前
../main.c:43:错误:预期';'在'first_time'之前
../main.c:47:警告:隐式声明函数'output_grb'
../main.c:49:错误:'state'undeclared(首次在此功能中使用)
../main.c:52:错误:'val'未删除(在此功能中首次使用)
../main.c:54:错误:'first_time'未申报(本函数首次使用)
../main.c:56:警告:函数'set_color'的隐式声明
../main.c:35:警告:未使用的变量'count'

UPSS这是问题嗯......
[]
评论 mjsilva.2014年1月13日
确实是!我使用帮助文件defs.h用于各种类型的类型和其他我喜欢在项目之间传递的东西,当然我没有包括那个!我已经删除了defs.h,只需包括U8和U16所需的两个类型键盘。对不起!
[]
评论 jacek.82.2014年1月14日
非常感谢您的帮助。我已经附加了一个二极管WS2812,没有B,很卑微。从红色变为绿色。我有另一个控制这个LED的代码点亮,而且有紧张。我不知道是什么原因。
问候
jacek.
[]
评论 jacek.82.2014年1月14日
我已经附加了一个二极管WS2812,没有B,很卑微。从红色变为绿色。我有另一个控制这个LED的代码是点亮的,那里有很多。我不知道是什么原因。
问候
jacek.
[]
评论 mjsilva.2014年1月14日
您的保险丝是否设置为8MHz(内部或外部)?您是否有正确的输出端口和位.s文件和.c文件(设置ddr设置)?

尝试为您的主要尝试(1)循环:

而(1)
{
set_color(buf,0,0,0,0); // 离开
output_grb(buf, 3);
_delay_ms(1000);
set_color(buf,0,255,0,0); // 红色的
output_grb(buf, 3);
_delay_ms(1000);
set_color(buf,0,0,255,0); // 绿色
output_grb(buf, 3);
_delay_ms(1000);
set_color(buf,0,0,0,255); // 蓝色的
output_grb(buf, 3);
_delay_ms(1000);
}

这将为您提供1秒的OFF,1秒的红色,1秒的绿色,1秒的蓝色,重复。我可以想到的最简单的测试。
[]
评论 jacek.82.2014年1月15日
你的最后一段代码很棒。在前一个不幸的是,我无法应付。在循环中发光并迅速熄灭另一个不亮起。再次感谢您的兴趣和支持。
你可能有什么建议吗?
[]
评论 mjsilva.2014年1月15日
如果最后一个代码有效,那么它意味着您的output_grb函数正常工作,这是目标。从那时起开始自己的实验。我给出的C代码只是如何使用output_grb函数的特定示例(而不是非常令人兴奋的一个)。
[]
评论 jacek.82.2014年1月16日
好的。谢谢你的帮助。来自格丁尼亚,波兰的问候:)
[]
评论 gr3ggo2014年1月18日
你好迈克尔,

只需使用WS2812 RGB LEDSTRIP阅读您的教程。好一个 :)
控制LEDSTRIPS我面临着一些问题。

拥有
- 6个LED 5V条带连接到PC源;测量电压,它在周围 5.1V
- 带有Attiny2313的PB0的地带DI
- 从您的教程编译和下载到Attiny2313的代码

Analyzed the PB0 output, it looks gound: http://postimg.org/image/9wssr3q9t/
Pic with setup: http://postimg.org/image/6ele8gerl/
Movie with the behavior: //www.youtube.com/watch?v=h02SE6PQB3Y

欢迎任何帮助/提示。

谢谢,
亚当
[]
评论 mjsilva.2014年1月18日
亚当,

从硬件开始,你需要一个 0.1uF 帽子跨越UC PWR / GND引脚。对于具有多个PWR / GND对的芯片,a 0.1uF 在每对上。我还在并行推荐一些像10uf的东西。您可能还可以尝试在控制信号线的LED条带端添加电阻〜200欧姆。

在软件上,时间看起来很完美。首先尝试仅控制1个LED,然后一次添加LED 1。从1个LED开始执行两件事 - 将RAM用于最小,并从PS中汲取最小电流(并且可能会在电源线上产生最小LED噪声)。在您在较小的芯片上了解之前,很容易使用所有的公羊。

让我知道你发现了什么。

麦克风
[]
评论 matt792014年3月11日
WS2812B的布局(我得到了一些 http://www.ledlightinghut.com/ws2812-5050-rgb-led.html )在包装的角落有一个小凹口,指示引脚3而不是引脚1!当用手焊接时,我们需要提高注意力,这样我们就不会在典型的IC(或WS2812的情况下,我们没有定向模块。
[]
评论 彼得59.2015年6月11日
好工作兄弟'
在150个LED条纹上完美工作,至少20 Hz!
[]
评论 Karthick342016年8月1日
我使用带有WS2812的内部振荡器8MHz的Atmega 8A,它可以正常工作
如果我更改为Atmega328,它就会有效
我怎么解决这个问题
我可以改变任何熔丝位设置吗?
[]
评论 Saravananece82.2016年8月10日
嗨,我正在使用带有Atmega162的CodevisionAvr 11.0592MHz. & WS2811
1.,如果我将LED数据线连接到GND,则没有LED
2.如果我将LED数据线连接到UC端口,则会发生随机颜色
3.如果使LED数据线保持低至超过50us。 LED未重置。
引导我
[]
评论 mjsilva.2016年8月10日
代码是为8 MHz运行的AVR编写的代码。要以11 MHz运行,您需要将nops添加到每个例程中。最接近的您可以获得指定的定时是将2个NOP添加到每个'0'输出部分,2个nops到每个'1'输出部分,每个位输出总共4个nops(其中包括代码'0'位以及“1”位的代码)。也就是说,每个位输出现在将采用14个周期,而不是10个周期。我会让你锻炼细节,但这是一个非常简单的变化。只需确保不要错过将所需的NOP添加到代码的任何分支中。
[]
评论 Saravananece82.2016年8月10日
谢谢,
但我没有关于ASM代码的想法。
引导我在哪里添加nop
我编译了上面的代码:没有错误。
[]
评论 Saifulkoh.2017年11月13日

谢谢......教程..
没有位撞击方法的16MHz的任何教程或示例..?

我希望在C中更好地理解,因为 

我不知道很多。

[]
评论 risc.2017年11月22日

你好,

驱动WS2812B的另一种方法是使用特殊外设和DMA组合,从而显着降低CPU负载。

转移纯粹是由HW完成的

此示例显示了如何使用PIC18F25K42驱动4x4 RGB点击板(Mikroe-1881)由16个RGB WS2812的RGB LED组成:

//github.com/sponkytoo/Moving-Colors

小视频: //vimeo.com/224635389

问候

[]
评论 DNYANESH012018年3月15日

嗨,我编译了这个论坛中给出的代码,它运作良好。现在,它在它在AVR工作室编译时,现在我将其更改为与Codevision AVR一起工作,我已成功编译我检查了它的脉冲,看起来像是好的。脉冲持续时间为1.25us。但LED上没有输出。

代码是


// AVR_2812
// 6 WS2812B LEDs
// 8MHz internal osc

#include <mega32.h>
#include <delay.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef uint8_t   u8;
typedef uint16_t  u16;


 #define __SFR_OFFSET 0 
 
#define NUM_WS2812    12
#define NUM_LEDS      (NUM_WS2812*3)


enum {S_R, S_G, S_B, S_Y, S_V, S_T};
  
#define MAX  50
#asm 
.equ LED_PORT =0x18;
.equ  OUTBIT=0 ;
.equ LED_DDR = LED_PORT-1; 
#endasm
// declaration of our ASM function
extern void输出_grb(U8 * PTR,U16计数);


extern void output_grb(u8 * ptr, u16 count)
{
#asm
; r18 = data byte
; r19 = 7-bit count
; r20 = 1 output
; r21 = 0 output
; r22 = SREG save
; r24:25 = 16-bit count
; r26:27 (X) = data pointer


output_grb:
         movw   r26, r24      ;r26:27 = X = p_buf
         movw   r24, r22      ;r24:25 = count
         in     r22, SREG     ;save SREG (global int state)
         cli                  ;no interrupts from here on, we're cycle-counting
         in     r20, LED_PORT
         ori    r20, (1<<OUTBIT)         ;our '1' output
         in     r21, LED_PORT
         andi   r21, ~(1<<OUTBIT)        ;our '0' output
         ldi    r19, 7        ;7 bit counter (8th bit is different)
         ld     r18,X+        ;get first data byte
loop1:
         out    LED_PORT, r20    ; 1   +0 start of a bit pulse
         lsl    r18           ; 1   +1 next bit into C, MSB first
         brcs   L1            ; 1/2 +2 branch if 1
         out    LED_PORT, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte?
         breq   bit8          ; 1/2 +7 last bit, do differently
         rjmp   loop1         ; 2   +8, 10 total for 0 bit
L1:
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte
         out    LED_PORT, r21    ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2/1 +8 10 total for 1 bit (fall thru if last bit)
bit8:
         ldi    r19, 7        ; 1   +9 bit count for next byte
         out    LED_PORT, r20    ; 1   +0 start of a bit pulse
         brts   L2            ; 1/2 +1 branch if last bit is a 1
         nop                  ; 1   +2
         out    LED_PORT, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         ld     r18, X+       ; 2   +4 fetch next byte
         sbiw   r24, 1        ; 2   +6 dec byte counter
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret
L2:
         ld     r18, X+       ; 2   +3 fetch next byte
         sbiw   r24, 1        ; 2   +5 dec byte counter
         out     LED_PORT, r21   ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret
#endasm
}


void set_color(u8 * p_buf, u8 led, u8 r, u8 g, u8 b)
{
  u16 index = 3*led;
  p_buf[index++] = g;
  p_buf[index++] = r;
  p_buf[index] = b;  
}


void main(void)
{
    u8 buf[NUM_LEDS];
    u8 state = S_R;
    u8 val = 0;
    u8 first_time = 1;
  //   memset(buf, 0, sizeof(buf));  
      #asm
    ori  r26,0xff                 ;set as output
    out    LED_DDR,r26  ; bit 0 is our output
    #endasm 
  




        
    while(1)
    {
      output_grb(buf, sizeof(buf));
      
      switch (state)
      {
      case S_R:
        if (++val <= MAX)
        {
          if (!first_time)
          {
            set_color(buf, 5, val, MAX-val, MAX-val);
          }  
          set_color(buf, 0, val, 0, 0);
        }
        else
        {
          first_time = 0;
          state = S_G;
          val = 0;
        }
        break;
        
      case S_G:
        if (++val <= MAX)
        {
          set_color(buf, 0, MAX-val, val, 0);
          set_color(buf, 1, 0, val, 0);
        }
        else
        {
          state = S_B;
          val = 0;
        }
        break;
      
      case S_B:
        if (++val <= MAX)
        {
          set_color(buf, 1, 0, MAX-val, val);
          set_color(buf, 2, 0, 0, val);
        }
        else
        {
          state = S_Y;
          val = 0;
        }
        break;
      
      case S_Y:
        if (++val <= MAX)
        {
          set_color(buf, 2, val, 0, MAX-val);
          set_color(buf, 3, val, val, 0);
        }
        else
        {
          state = S_V;
          val = 0;
        }
        break;
      
      case S_V:
        if (++val <= MAX)
        {
          set_color(buf, 3, MAX-val, MAX-val, val);
          set_color(buf, 4, val, 0, val);
        }
        else
        {
          state = S_T;
          val = 0;
        }
        break;
      
      case S_T:
        if (++val <= MAX)
        {
          set_color(buf, 4, MAX-val, val, MAX-val);
          set_color(buf, 5, 0, val, val);
        }
        else
        {
          state = S_R;
          val = 0;
        }
        break;
      
      default:
        state = S_R;
        break;
      }


     delay_ms(100);     
     
      };
}

这没有LED发光出了什么问题。

[]
评论 DNYANESH012018年3月15日

嗨,我编制了这个论坛中给出的代码,它运作良好。现在,它在它在AVR工作室编译时,现在我将其更改为与Codevision AVR一起工作,我已成功编译我检查了它的脉冲,看起来像是好的。脉冲持续时间为1.25us。但LED上没有输出。

代码是


// AVR_2812
// 6 WS2812B LEDs
// 8MHz internal osc

#include <mega32.h>
#include <delay.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef uint8_t   u8;
typedef uint16_t  u16;


 #define __SFR_OFFSET 0 
 
#define NUM_WS2812    12
#define NUM_LEDS      (NUM_WS2812*3)


enum {S_R, S_G, S_B, S_Y, S_V, S_T};
  
#define MAX  50
#asm 
.equ LED_PORT =0x18;
.equ  OUTBIT=0 ;
.equ LED_DDR = LED_PORT-1; 
#endasm
// declaration of our ASM function
extern void输出_grb(U8 * PTR,U16计数);


extern void output_grb(u8 * ptr, u16 count)
{
#asm
; r18 = data byte
; r19 = 7-bit count
; r20 = 1 output
; r21 = 0 output
; r22 = SREG save
; r24:25 = 16-bit count
; r26:27 (X) = data pointer


output_grb:
         movw   r26, r24      ;r26:27 = X = p_buf
         movw   r24, r22      ;r24:25 = count
         in     r22, SREG     ;save SREG (global int state)
         cli                  ;no interrupts from here on, we're cycle-counting
         in     r20, LED_PORT
         ori    r20, (1<<OUTBIT)         ;our '1' output
         in     r21, LED_PORT
         andi   r21, ~(1<<OUTBIT)        ;our '0' output
         ldi    r19, 7        ;7 bit counter (8th bit is different)
         ld     r18,X+        ;get first data byte
loop1:
         out    LED_PORT, r20    ; 1   +0 start of a bit pulse
         lsl    r18           ; 1   +1 next bit into C, MSB first
         brcs   L1            ; 1/2 +2 branch if 1
         out    LED_PORT, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte?
         breq   bit8          ; 1/2 +7 last bit, do differently
         rjmp   loop1         ; 2   +8, 10 total for 0 bit
L1:
         nop                  ; 1   +4
         bst    r18, 7        ; 1   +5 save last bit of data for fast branching
         subi   r19, 1        ; 1   +6 how many more bits for this byte
         out    LED_PORT, r21    ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2/1 +8 10 total for 1 bit (fall thru if last bit)
bit8:
         ldi    r19, 7        ; 1   +9 bit count for next byte
         out    LED_PORT, r20    ; 1   +0 start of a bit pulse
         brts   L2            ; 1/2 +1 branch if last bit is a 1
         nop                  ; 1   +2
         out    LED_PORT, r21    ; 1   +3 end hi for '0' bit (3 clocks hi)
         ld     r18, X+       ; 2   +4 fetch next byte
         sbiw   r24, 1        ; 2   +6 dec byte counter
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret
L2:
         ld     r18, X+       ; 2   +3 fetch next byte
         sbiw   r24, 1        ; 2   +5 dec byte counter
         out     LED_PORT, r21   ; 1   +7 end hi for '1' bit (7 clocks hi)
         brne   loop1         ; 2   +8 loop back or return
         out    SREG, r22     ; restore global int flag
         ret
#endasm
}


void set_color(u8 * p_buf, u8 led, u8 r, u8 g, u8 b)
{
  u16 index = 3*led;
  p_buf[index++] = g;
  p_buf[index++] = r;
  p_buf[index] = b;  
}


void main(void)
{
    u8 buf[NUM_LEDS];
    u8 state = S_R;
    u8 val = 0;
    u8 first_time = 1;
  //   memset(buf, 0, sizeof(buf));  
      #asm
    ori  r26,0xff                 ;set as output
    out    LED_DDR,r26  ; bit 0 is our output
    #endasm 
  




        
    while(1)
    {
      output_grb(buf, sizeof(buf));
      
      switch (state)
      {
      case S_R:
        if (++val <= MAX)
        {
          if (!first_time)
          {
            set_color(buf, 5, val, MAX-val, MAX-val);
          }  
          set_color(buf, 0, val, 0, 0);
        }
        else
        {
          first_time = 0;
          state = S_G;
          val = 0;
        }
        break;
        
      case S_G:
        if (++val <= MAX)
        {
          set_color(buf, 0, MAX-val, val, 0);
          set_color(buf, 1, 0, val, 0);
        }
        else
        {
          state = S_B;
          val = 0;
        }
        break;
      
      case S_B:
        if (++val <= MAX)
        {
          set_color(buf, 1, 0, MAX-val, val);
          set_color(buf, 2, 0, 0, val);
        }
        else
        {
          state = S_Y;
          val = 0;
        }
        break;
      
      case S_Y:
        if (++val <= MAX)
        {
          set_color(buf, 2, val, 0, MAX-val);
          set_color(buf, 3, val, val, 0);
        }
        else
        {
          state = S_V;
          val = 0;
        }
        break;
      
      case S_V:
        if (++val <= MAX)
        {
          set_color(buf, 3, MAX-val, MAX-val, val);
          set_color(buf, 4, val, 0, val);
        }
        else
        {
          state = S_T;
          val = 0;
        }
        break;
      
      case S_T:
        if (++val <= MAX)
        {
          set_color(buf, 4, MAX-val, val, MAX-val);
          set_color(buf, 5, 0, val, val);
        }
        else
        {
          state = S_R;
          val = 0;
        }
        break;
      
      default:
        state = S_R;
        break;
      }


     delay_ms(100);     
     
      };
}

这没有LED发光出了什么问题。

[]
评论 Muneshcm.2019年6月29日

嗨,谢谢你的精彩装配WS2812代码。我们一直在使用与Atmega8a的代码为我们的商业产品,过去1年,它很棒。 

[]
评论 manbuttan.5月25日,2020年

你好迈克,

感谢您对这篇文章。我成功地在8MHz上的Attiny816上运行这个问题。但是,我需要在20MHz上运行这个,似乎无法弄清楚汇编代码中的更改。

如果您可以将20MHz更改为汇编代码,我会真诚感谢它。我应该把更多的人放在哪里?

问候,

man

[]
评论 Ekaprits2.3月17日,2021年

您好,非常感谢您的代码!我过去使用过它,它就在盒子里工作了。但是,我现在正在尝试使用它,我得到了一个奇怪的行为:无论我在BUF中设置了什么颜色,LED都将变为白色。如果我呼叫输出_grb(buf,3 * k),那么第一个K LED转为白色,其余部分仍然关闭。你有什么可能导致这个的想法吗?

谢谢!

更新:我想出来了。对于具有同一问题的其他人来说,事实证明,融合了保险丝上的CKDIV8位,我猜是弄乱了时间。我重置它,现在它有效!再次感谢!

[]
评论 Phil12345678991011.2021年4月8日

这么多错误

如何将ASM文件添加到项目中,以便将其链接到主程序?

要发布回复评论,请单击连接到每个注释的“回复”按钮。发布新的评论(不是回复评论),请在评论的顶部查看“写评论”选项卡。

注册将允许您参加所有相关网站的论坛,并为您提供所有PDF下载。

注册

我同意 使用条款隐私政策.

尝试我们偶尔但流行的时事通讯。非常容易取消订阅。
或登录