STM32: 使用STM32F030F4P6控制WS2812B

WS2812B是智能外控集成LED光源,主要特点是:

- 控制电路与RGB芯片集成在一个5050封装的无器件中, 构成一个完整的外控像素点。

-内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加。

-内置上电复位和掉电复位电路。

-每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真彩色显示,扫描频率不低于400Hz。

-串行级联接口,能通过一根信号线完成数据的接收与解码。

-当刷新速率30帧/秒中,低速模式联数不小于512点,高速模式不小于1024点。

-数据发送速度可达800Kbps。

这里,主要是使用STM32F030F4P6来控制WS2812B。

  • 原理

WS2812B的datasheet可以从这里下载:https://acrobotic.com/datasheets/WS2812B.pdf

-封装与引脚配置:

2016_06_08_ws2812b_pins

可以看到WS2812B只有四个引脚,除于供电所需的VDD, VSS引脚,只剩下DIN - 控制数据输入端和DOUT -控制数据输出端。

-数据传输时间及时序:

2016_06_08_ws2812b_timing

由于数据发送的速率为800Kbsp, 所以单个数据(0码与1码)的传输时间为1.25us, 而RET码的传输时间为>50us(40 * 1.25us)。

-RGB 24位数据的传输顺序:

2016_06_08_ws2812b_data_order

可以看到RGB数据的排列为GRB, 与我们常见的数据排列方式不太一样,并且传输过程中高位在前,低位在后。

-STM32F030F4: TIM + DMA方式

相关的文档在这里:https://github.com/g4lvanix/0xWS2812/blob/master/README.md

The idea to create 16 parallel 800kBit/s data streams is the following:

– Use a Timer to create an 800kHz time base and a DMA request every 1.25us.

– Use 2 compare modules to create DMA requests at the low bit time (350ns) and the high bit time (900ns)

1. The 1.25us DMA request sets all bits of the GPIO port high

2. The 350ns DMA request transfers the data from the frame buffer to the GPIO port. If the bit is a 0, the GPIO pin will go

3. low, otherwise it will stay high.

4. The 900ns DMA request sets all GPIO pins low.

5. Repeat steps 1 to 3 until all bits have been transmitted.

  • 实现

这里:

-需要一个Pin脚,提供数据给WS2812B,这里使用的是GPIOA,Pin1

-需要3个DMA通道:

1. CH3: 在下一个1.25us开始时,使Pin脚输出高电平(“1″),表明开始发送数据

2. CH2: 在数据开始发送后的0.35us输出数据,如果为“0″,则Pin脚输出低电平(“0″),如果为“1″,则保持不变。

3. CH4: 在数据开始发送后的0.90us,使Pin脚输出低电平(“0”)

-需要2个TIMER, 用于输出PWM信号:

1. TIM1: 作为master, 输出频率为800kHz周期为50ms的PWM信号。TIM1的CH3 -> TIM3

  const PWMConfig pwmc1 = {
    48000000 / 60, /* 800Khz PWM clock frequency. 1/60 of PWMC3   */
    (48000000 / 60) * 0.05, /* Total period is 50ms (20FPS), including leds cycles + reset length for ws2812b and FB writes  */
    NULL,
    {
      { PWM_OUTPUT_ACTIVE_HIGH, NULL },
      { PWM_OUTPUT_DISABLED, NULL },
      { PWM_OUTPUT_ACTIVE_HIGH, pwmc3cb },
      { PWM_OUTPUT_DISABLED, NULL }
    },
    TIM_CR2_MMS_2, /* master mode selection */
    0,
  };

2. TIM3: 作为slave, 输出频率为48MHz, 周期为1.25uS的PWM信号。

  /* master mode selection */
  const PWMConfig pwmc3 = {
    48000000, /* 48Mhz PWM clock frequency.   */
    60, /* 60 cycles period (1.25 uS per period @48Mhz       */
    NULL,
    {
      { PWM_OUTPUT_ACTIVE_HIGH, NULL },
      { PWM_OUTPUT_DISABLED, NULL },
      { PWM_OUTPUT_ACTIVE_HIGH, NULL },
      { PWM_OUTPUT_DISABLED, NULL }
    },
    0,
    0,
  };

1. TIM3_UP -> DMA_CH3

2. TIM3_CH3 -> DMA_CH2

3. TIM3_CH1 -> DMA_CH4

2016_06_08_ws2812b_dma

相关代码实现:

  // DMA stream 2, triggered by channel3 pwm signal. if FB indicates, reset output value early to indicate "0" bit to ws2812
  dmaStreamAllocate(STM32_DMA1_STREAM2, 10, NULL, NULL);
  dmaStreamSetPeripheral(STM32_DMA1_STREAM2, &(port->BSRR.H.clear));
  dmaStreamSetMemory0(STM32_DMA1_STREAM2, fb);
  dmaStreamSetTransactionSize(STM32_DMA1_STREAM2, leds * 24);
  dmaStreamSetMode(
      STM32_DMA1_STREAM2,
      STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_MINC | STM32_DMA_CR_PSIZE_BYTE
      | STM32_DMA_CR_MSIZE_BYTE | STM32_DMA_CR_CIRC | STM32_DMA_CR_PL(2));

  // DMA stream 3, triggered by pwm update event. output high at beginning of signal
  dmaStreamAllocate(STM32_DMA1_STREAM3, 10, NULL, NULL);
  dmaStreamSetPeripheral(STM32_DMA1_STREAM3, &(port->BSRR.H.set));
  dmaStreamSetMemory0(STM32_DMA1_STREAM3, &mask);
  dmaStreamSetTransactionSize(STM32_DMA1_STREAM3, 1);
  dmaStreamSetMode(
      STM32_DMA1_STREAM3, STM32_DMA_CR_TEIE |
      STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_PSIZE_BYTE | STM32_DMA_CR_MSIZE_BYTE
      | STM32_DMA_CR_CIRC | STM32_DMA_CR_PL(3));

  // DMA stream 4, triggered by channel1 update event. reset output value late to indicate "1" bit to ws2812.
  // always triggers but no affect if dma stream 2 already change output value to 0
  dmaStreamAllocate(STM32_DMA1_STREAM4, 10, NULL, NULL);
  dmaStreamSetPeripheral(STM32_DMA1_STREAM4, &(port->BSRR.H.clear));
  dmaStreamSetMemory0(STM32_DMA1_STREAM4, &mask);
  dmaStreamSetTransactionSize(STM32_DMA1_STREAM4, 1);
  dmaStreamSetMode(
      STM32_DMA1_STREAM4,
      STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_PSIZE_BYTE | STM32_DMA_CR_MSIZE_BYTE
      | STM32_DMA_CR_CIRC | STM32_DMA_CR_PL(3));

  pwmStart(&PWMD1, &pwmc1);
  pwmStart(&PWMD3, &pwmc3);
  // set pwm3 as slave, triggerd by pwm1 oc1 event. disables pwmd1 for synchronization.
  PWMD3.tim->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2;
  PWMD1.tim->CR1 &= ~TIM_CR1_CEN;

  // set pwm values.
  // 17 (duty in ticks) / 60 (period in ticks) * 1.25uS (period in S) = 0.354 uS
  pwmEnableChannel(&PWMD3, 2, 17);
  // 43 (duty in ticks) / 60 (period in ticks) * 1.25uS (period in S) = 0.896 uS
  pwmEnableChannel(&PWMD3, 0, 43);
  // active during transfer of 60 cycles * leds * 24 bytes * 1/60 multiplier
  pwmEnableChannel(&PWMD1, 0, 60 * leds * 24 / 60);

  pwmEnableChannel(&PWMD1, 2, 60 * (leds + 2) * 24 / 60);
  pwmEnableChannelNotification(&PWMD1, 2);

  // stop and reset counters for synchronization
  PWMD1.tim->CNT = 0;
  // Slave (TIM3) needs to "update" immediately after master (TIM1) start in order to start in sync.
  // this initial sync is crucial for the stability of the run
  PWMD3.tim->CNT = 59;
  PWMD3.tim->DIER |= TIM_DIER_CC3DE | TIM_DIER_CC1DE | TIM_DIER_UDE;
  dmaStreamEnable(STM32_DMA1_STREAM3);
  dmaStreamEnable(STM32_DMA1_STREAM4);
  dmaStreamEnable(STM32_DMA1_STREAM2);
  // all systems go! both timers and all channels are configured to resonate
  // in complete sync without any need for CPU cycles (only DMA and timers)
  // start pwm1 for system to start resonating
  PWMD1.tim->CR1 |= TIM_CR1_CEN;

NOTE:

相关的文档可以参考DM0091010.pdf -> 14.3.15 Timer synchronization – Using one timer to enable another timer.

参考代码来自https://github.com/omriiluz/WS2812B-LED-Driver-ChibiOS

在设置完Pin Mode/Type/Speed之后,没有设置输出电平,在STM32F030F4P6中由示波器中可以看出是高电平,所以在它输出第一帧时,WS2812B的绿色LED会被点亮,但由于它每20ms更新一次,所以可能看不出来:

2016_06_08_ws2812b_green

需要在设置完Pin脚之后,使其输出低电平>50us,这样第一帧才能正确传输。加入如下代码:

  palSetGroupMode(port, mask, 0, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUPDR_FLOATING);
  port->BSRR.H.clear = mask;
  chThdSleepMilliseconds(1);

2016_06_08_ws2812b_init

可以看到第一帧开始时Pin脚上有一段很长时间的低电平。

同时还将原有代码进行如下改进:

1. 不再进行20Hz更新,当RGB的数值发生改变时才去更新。

2. 可以通过串口(UART1)对RGB的数值进行更新,串口波特率为115200n1,输入led <R> <G> <B>进行更新(输入的数值为16进制),如:

ChibiOS/RT Shell
ch> led 80 80 80
led: r=128, g=128, b=128
ch> led ff ff ff
led: r=255, g=255, b=255
ch>
  • 代码

请从这里下载:

https://github.com/brobwind/chibios_stm32f030f4_dev_v1_ws2812b
  • 相关的参考文档:
  1. https://github.com/omriiluz/WS2812B-LED-Driver-ChibiOS
  2. http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
  3. https://github.com/g4lvanix/0xWS2812/blob/master/README.md
  4. https://acrobotic.com/datasheets/WS2812B.pdf

《STM32: 使用STM32F030F4P6控制WS2812B》有6个想法

  1. HI,我最近也在進行WS2812的codeing,但我是使用STM32Cube搭STM32F051,
    想請問您是否使用過STM32CUBE去調適TIM 驅動WS2812
    我目前只能調適出800KHz
    另外master&slave連動的關係又是如何?
    是使用TIM1去觸發TIM3嗎??

    1. 我没有使用STM32CUBE去调试,感觉那个库不太好用,比较喜欢使用基于RTOS的系统。
      完整的代码给你参考一下:https://github.com/brobwind/chibios_stm32f030f4_dev_v1_ws2812b
      如果还有问题,咱们再看看。

  2. 请问如何使用PWM方式控制多个数量的灯呢,比如128颗,或者256颗,使用STM32F030F4P6

发表评论

电子邮件地址不会被公开。 必填项已用*标注