【GD32F427开发板试用】点亮WS2812B炫彩灯环

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动更多开发板试用活动请关注极术社区网站。作者HonestQiao

我有一个WS2812B炫彩灯环搭配精选的背景非常出镜

在玩过的板子上我都要把它点亮。

关于WS2812B的介绍资料网上有很多这里就不细说了只说重点

  1. WS2812B是单总线控制的

控制一颗WS2812B与控制多颗WS2812B方式是一样的不同的只是每批传送的数据的多少。
我上面的这个灯环就是24颗串联在一起第一颗的DIN负责接收控信号然后每一颗的DOUT接下一颗的DIN将控制信号传递过去直到最后一颗。这种方式可以连接上千颗一起控制。
需要注意的是最后一颗的DOUT是留空的。所以上面这个炫彩灯环首尾是不相连的。

  1. WS2812B控制一颗灯珠需要24bits的数据代表着GRB三种颜色值

控制多颗则使用多组连续的24bits数据

每颗WS2812B截取数据中最开始的24bits然后把剩下的传递给后来者直到数据发送完毕。

  1. 控制设备不能直接发送这些bit位的信号而是要按照一定的规则发送信号才被检测为对应的bit位

WS2812B规定了三种信号0码、1码、reset码这三种码通过信号线上的高低电平的特定保持时间来做区分具体如下

也就是

  • 1个码的信号时间长度位1.25us
  • 如果高电平保持0.4us低电平保持0.85us则判定为T0码
  • 如果高电平保持0.8us低电平保持0.45us则判定为T1码
  • 如果低电平持续至少50us则判定位RESET码例如传递第二批控制信号时需要使用该码

上述数据如图表所示都有一定的容错范围经过以往的测验经验最终使用如下的值

  • T0高0.25us低1.00us
  • T1高1.00us低0.25us
  • RESET低50us

要实现0.25us的时间控制精度那么换算成频率位1/(0.25/1000000) = 4M。也就是控制设备需要以4Mbits/s这么快的速度来控制信号的变化才能满足WS2812B控制信号的要求。

要满足上述的要求可以有很多种方法。常用的有GPIO翻转使用SPI的MOSI发送数据等。
有人可能有疑问UART、I2C发送数据也能带来信号线的高低电平变化哪能使用吗
UART的传送速度通常波特率最高位1.5Mbits/sI2C通信超高速的能达到5M但一般都达不到通常可能为400kbits/s快的位3.4Mbits/s。
而SPI通常速度都能达到50Mbits/s。
但使用GPIO翻转来控制信号并非所有的的设备都能达到有的翻转的速率没有这么快。
经过实测咱们的GD32F427开发板完全可以满足。所以这篇分享就直接使用GPIO翻转这种简单的方式来做了。
在实际使用中有很多大佬也研究了各种各样优化的方法大家感兴趣的话可以查找资料了解一下。

通过查看原理图和数据手册可以使用PC1来进行控制该引脚一般情况下没有直接复用

具体接线如下

因为我只打算一次点亮一颗灯珠所以供电部分直接使用板载的3.3V、GND即可。
如果需要点亮多颗灯珠那么应该使用外部电源供电。点亮一颗灯珠的一种颜色需要20mA电流三种颜色都点亮则需要60mA电流24颗全部点亮则需要1440mA电流。一般开发板的GPIO是供不起的全部点亮的。

在前一篇文章Systick系统定时器的使用种探讨了SysTick的基础使用可以达到us级别的精确控制。不过亚us级别的控制就会有一些吃力了。
在MCU的精确时间控制中还有一种使用nop指令来进行控制的。所谓nop就是一条空指令一个最小的机器周期。通过一定数量的nop从而实现亚us级别的控时。

GD32F427开发板的运行频率高低200MHz经过实测每200个nop刚好经过1us也就是每个nop为0.005us。那么要达到0.25us的控时就需要50个nop。

在system_gd32f4xx.c中有系统时钟的定义

在 core_cmInstr.h 中有关于nop的符号定义

那么我们要定义0.25us可以参考使用如下的方式

#define NOP                                         \
    {                                               \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
    }

当然也可以通过nop辅助多重循环用更为简介的代码来实现。这里就先用直接nop的方式更简洁明了。

前面要求的两个时间分别为0.25us1.00us就分别为1个NOP和4个NOP。

经过以上的准备工作就可以编写实际的控制代码了具体代码如下

/*!
    \file    main.c
    \brief   GPIO running WS2812B demo

    \version 2022-11-24, V1.0.0, demo for GD32F4xx
*/


#include "gd32f4xx.h"
#include "gd32f427v_start.h"
#include "systick.h"
#include <stdio.h>

#define NOP                                         \
    {                                               \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
        __NOP();__NOP();__NOP();__NOP();__NOP();    \
    }
    
void GPIO_Init(void)
{
    rcu_periph_clock_enable(RCU_GPIOC);                                                //使能外部时钟

    gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_1);                //配置端口模式
    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);    //输出选项配置
    gpio_bit_reset(GPIOC, GPIO_PIN_1);                                                //PC1复位

}

#define LOW 0
#define HIGH 1
#define DIN PC1
#define NUM 24

//拉低DIN保持50us以上
void ws2812_init() {
  unsigned char i;
  gpio_bit_reset(GPIOC, GPIO_PIN_1);
  for (i = 0; i <= 200; i++) {
    NOP;
  }
}

//高电平0.25us低电平1us
void ws2812_write_0() {
  gpio_bit_set(GPIOC, GPIO_PIN_1);
  NOP;
  gpio_bit_reset(GPIOC, GPIO_PIN_1);
  NOP;
  NOP;
  NOP;
  NOP;
}

//高电平1us低电0.25us
void ws2812_write_1() {
  gpio_bit_set(GPIOC, GPIO_PIN_1);
  NOP;
  NOP;
  NOP;
  NOP;
  gpio_bit_reset(GPIOC, GPIO_PIN_1);
  NOP;
}


// 写入24bits
void ws2812_write_24bits(unsigned long dat) {
  unsigned char t[NUM] = {0};
  unsigned char i;

  for (i = 0; i < NUM; i++) {
    if (dat >> i & 1) {
      t[i] = HIGH;
    } else {
      t[i] = LOW;
    }
  }

  for (i = 0; i < NUM; i++) {
    if (t[i]) {
      ws2812_write_1();
    } else {
      ws2812_write_0();
    }
  }
}


void ws2812_test() {
  unsigned int i = 0;
  unsigned int j = 0;
  unsigned int index = 1;
  unsigned long colors[8] = {0x000000, 0xff0000, 0x00ff00, 0x0000ff,
                             0xffff00, 0xff00ff, 0x00ffff, 0xffffff};

  ws2812_init();

  while (1) {
    for (i = 0; i < NUM; i++) {
      if (index % NUM == i) {
        ws2812_write_24bits(colors[index % 7 + 1]);
      } else {
        ws2812_write_24bits(colors[0]);
      }
    }
    index++;
    if (index >= NUM) {
      index = 0;
    }
    // 延时0.1秒
    for (i = 0; i <= 200*50*100; i++) {
      NOP;
    }
  }
}

int main(void)
{
    systick_config();    //配置系统时钟
    GPIO_Init();
    
    ws2812_test();
}

上述代码中关键调用说明如下

  • gpio_bit_set(GPIOC, GPIO_PIN_1)设置高电平
  • gpio_bit_reset(GPIOC, GPIO_PIN_1)重置为低电平
  • ws2812_init()重置WS2812B控制拉低保持50us
  • ws2812_write_0()输出T0码高0.25us低0.25us
  • ws2812_write_1()输出T1码高1.00us低0.25us
  • ws2812_write_24bits()批量生成和输出T0、T1
  • ws2812_test()使用index自增表示当前需要点亮的灯珠一次发送24组数据每组24bits且使用colors预定义8种颜色colors[0]实际上表示熄灭。

编译以上代码然后下载到开发板中就可以看到炫彩的灯光在灯环上欢快的跳跃了

动图封面

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6