OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 22|回复: 0

《STM32H7R7开发指南 V1.1 》第四十八章 WS2812B实验

[复制链接]

1335

主题

1351

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5703
金钱
5703
注册时间
2019-5-8
在线时间
1538 小时
发表于 1 小时前 | 显示全部楼层 |阅读模式
第四十八章 WS2812B实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

本章,我们将介绍如何在STM32H7R7开发板上使用WS2812B来实现绚烂的灯光效果。WS2812是一种可编程的RGBLED灯,具有RGB显示效果,可显示的颜色数量为224种。
本章分为如下几个小节:
48.1 WS2812B简介
48.2 硬件设计
48.3 程序设计
48.4 下载验证


48.1 WS2812B介绍

48.1.1 WS2812B简介
WS2812B是一款单线传输三通道(RGB)驱动控制电路与发光电路于一体的智能外控LED光源。产品内含有信号解码模块、数据缓冲器、内置恒流电路及RC振荡器;内部集成电流增益控制模块,CMOS制程,低压、低耗电;三通道恒流驱动器默认输出19mA,采用单线输出方式,串接各晶片之输出动作同步;上电默认不亮灯。
数据协议采用单极性归零码的通讯方式,单线传输LED驱动控制专用芯片,同时芯片内置的电流增益调节功能,可设置电流1.75mA~19mA,共16个电流增益等级;PWM信号刷新率高达4KHz,显示更趋细腻平滑,解决拍摄画面暗条纹问题,非常适合高速移动产品的使用。200us以上的复位时间,出现中断也不会引起误复位,可以支持更低频率、价格便宜的MCU。主要应用领域:消费类电子产品、LED全彩发光字灯串、电脑及周边设备、游戏设备以及各种电器设备。
WS2812B具有如下特点:
外观尺寸:2.0*1.8*0.8mm。
颜色:红光、绿光、蓝光。
胶体:无色透明。
EIA规范标准包装。
环保产品,符合ROHS要求。
适用于自动贴片机。
适用于红外线回流焊制程。
WS2812B的内部结构如下图所示:


第四十八章 WS2812B实验698.png
图48.1.1.1 WS2812B内部框图

我们可以看到内部框架是比较简单的,通过4根线与单片机连接,包括:电源输入线、信号线和地线,具体如下表所示:

1.png
图48.1.1.1 引脚说明

48.1.2 WS2812B数据传输
WS2812B采用的数据通信协议是单极性归零码,每一个码元都必须有低电平。本协议的每个码元起始为高电平,高电平时间宽度决定“0”码或者“1”码。

第四十八章 WS2812B实验946.png
图48.1.2.1 归零码数据通信协议图

上图是一个码元周期的时序波形图,每个阶段的数据传输时间如下表所示:

2.png
表48.1.2.1 数据传输时间表

我们写程序时,码元周期最低要求为1.2us。0码、1码的高电平时间需按照上表的规定范围,0码、1码的低电平时间要求小于20us。
另外,数据结构是24bit(3*8)灰度数据结构:高位在前,按照OUTR1/2/3的顺序发送,如下图所示:


第四十八章 WS2812B实验1313.png
图48.1.2.2 协议数据结构

连接方法如下图所示:

第四十八章 WS2812B实验1344.png
图48.1.2.3 连接方法

上图是各芯片的连接方法,接下来我们讲解一下数据的传输方法,各芯片输入数据流(以3颗芯片为例)如下图所示:

第四十八章 WS2812B实验1414.png
图48.1.2.4 数据传输方法

在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个芯片提取后,送到芯片内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DOUT端口开始转发输出给下一个级联的芯片,每经过一个芯片的传输,信号减少24bit。其中,Trst复位时间要大于200us。采用自动整形转发技术,使得该芯片的级联个数不受信号传送的限制,仅受限信号传输速度要求。

48.2 硬件设计

1. 例程功能
板载的WS2812B(RGBLCD灯)10个灯依次显示同一个颜色,并且每2秒钟换一种颜色显示。通过按下按键KEY_UP,所有灯熄灭,之后重新开始颜色显示。本章实验我们使用两种不同的方式去实现以上描述的功能,一种是定时器+DMA的方式,另一种是IO模拟的方式。

2. 硬件资源
1)LED灯  
       LED0 – PD14
2 ) 独立按键  
       KEY0 – PE9
       KEY1 – PE8
       KEY2 –PE7
       KEY_UP –PC13
3)WS2812B –PB1

3. 原理图
我们主要来看看WS2812B和开发板的连接,如下图所示:


第四十八章 WS2812B实验1911.png
图48.2.1 WS2812B与开发板连接原理图

48.3 程序设计

48.3.1 程序解析

1. WS2812B驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。WS2812B驱动源码包括两个文件:ws2812b.c和ws2812b.h。
首先是ws2812b.h文件,我们把用到的引脚和RGBLED的 数据以及颜色值等等都用宏定义来表示,需要更换引脚或某些参数的数值时也可以通过修改宏实现快速移植,如下所示:

  1. #define WS2812B_PORT                    GPIOB
  2. #define WS2812B_PIN                     GPIO_PIN_1
  3. #define WS2812B_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

  4. #define WS2812B(x)                      do{ x ? \
  5.        HAL_GPIO_WritePin(WS2812B_PORT, WS2812B_PIN, GPIO_PIN_SET) :  \
  6.        HAL_GPIO_WritePin(WS2812B_PORT, WS2812B_PIN, GPIO_PIN_RESET); \
  7.                                         }while(0)
  8. /* RGBLED的数据相关宏定义 */
  9. #define DELAY_15nS()         __NOP()                                    
  10. /* 宏定义空指令的方法不好计算,只能大致计算,并且受编译器优化等级影响 */
  11. #define DELAY_400nS()       for(uint16_t i = 0; i < 106; i++) __NOP()  
  12. /* 400ns 约等于循环次数 * 2 * 1.67ns(一个时钟) + 44ns */                  
  13. #define DELAY_800nS()       for(uint16_t i = 0; i < 225; i++) __NOP()   
  14. /* 800ns */                                       
  15. #define LED_NUM             10          /* 灯珠的个数 */

  16. /* GRB888颜色值 */
  17. #define GRB888_RED          0x00FF00        /* 红色 */
  18. #define GRB888_GREEN        0xFF0000        /* 绿色 */
  19. #define GRB888_BLUE         0x0000FF        /* 蓝色 */
  20. #define GRB888_BLACK        0x000000        /* 黑色 */
  21. #define GRB888_WHITE        0xFFFFFF        /* 白色 */
  22. #define GRB888_YELLOW       0xFFFF00        /* 黄色 */
  23. #define GRB888_IRED         0x5CCD5C        /* 浅红色 */
  24. #define GRB888_ORANGE       0xA5FF00        /* 橙色 */
  25. #define GRB888_PURPLE       0x008080        /* 紫色 */        
  26. #define GRB888_PING         0xB6FFC1        /* 浅粉色 */
  27. #define GRB888_CYAN         0xFF00FF        /* 青色 */
  28. #define GRB888_PBLUE        0x80008C        /* 孔雀蓝 */
  29. #define GRB888_VIOLET       0x008BFF        /* 紫罗兰 */
复制代码
ws2812b.c我们这里介绍用IO模拟的方法来实现本章实验的功能,另一种定时器+DMA的方式可以参考具体源码以及定时器和DMA章节的讲解。我们来讲解以下几个函数:
  1. /**
  2. * @brief       WS2812B的逻辑1
  3. * [url=home.php?mod=space&uid=60778]@note[/url]        T1H / T1L > 2 / 1 且T1H + T1L = 1.25us (允许些许偏差)
  4. * [url=home.php?mod=space&uid=271674]@param[/url]       无
  5. * @retval      无
  6. */
  7. static void ws2812b_send_high(void)
  8. {
  9.     WS2812B(1);
  10.     DELAY_800nS();
  11.     WS2812B(0);
  12.     DELAY_400nS();
  13. }

  14. /**
  15. * @brief       WS2812B的逻辑0
  16. * @note        T1H / T1L < 1 / 2 且T1H + T1L = 1.25us (允许些许偏差)
  17. * @param       无
  18. * @retval      无
  19. */
  20. static void ws2812b_send_low(void)
  21. {
  22.     WS2812B(1);
  23.     DELAY_400nS();
  24.     WS2812B(0);
  25.     DELAY_800nS();
  26. }
复制代码
以上这两个函数是WS2812B的逻辑1和0,可以参考我们前面讲过的0码和1码的高低电平的传输时间。
接着是WS2812B的初始化函数,其定义如下:
  1. /**
  2. * @brief       初始化WS2812B相关IO口, 并使能时钟
  3. * @param       无
  4. * @retval      无
  5. */
  6. void ws2812b_init(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct;
  9.     WS2812B_GPIO_CLK_ENABLE();                              /* 时钟使能 */

  10.     gpio_init_struct.Pin = WS2812B_PIN;                     /* WS2812B引脚 */
  11.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;            /* 开楼输出 */
  12.     gpio_init_struct.Pull = GPIO_NOPULL;                    /* 无上下拉 */
  13.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;           /* 低速 */
  14.     HAL_GPIO_Init(WS2812B_PORT, &gpio_init_struct);         /* 初始化LED0引脚 */
  15.    
  16.     ws2812b_reset();                                        /* 复位WS2812B */
  17. }
复制代码
在初始化函数中,主要设置了WS2812B的引脚各项参数,在最后要对WS2812B进行一次复位。
接着是对RGBLED灯要显示的颜色亮度进行设置,这里涉及到RGB模型和HSV模型互相转化,其代码如下所示:
  1. /**
  2. * @brief   改变所要显示的颜色亮度(RGB->HSV->RGB)
  3. * @param   rgb : GRB888颜色值
  4. * @param   k   : 亮度值(0.0 ~ 1.0)
  5. * @retval  改变亮度后的颜色值
  6. */
  7. uint32_t color_change_brigh(uint32_t rgb, float k)
  8. {
  9.     uint8_t r, g, b;
  10.     float h, s, v;
  11.     uint8_t cmax, cmin, cdes;
  12.     uint32_t color;

  13.     r = (uint8_t) (rgb >> 16);
  14.     g = (uint8_t) (rgb >> 8);
  15.     b = (uint8_t) (rgb);
  16.    
  17.     cmax = r > g ? r : g;
  18.     if (b > cmax)
  19.     {
  20.         cmax = b;
  21.     }
  22.     cmin = r < g ? r : g;
  23.     if (b < cmin)
  24.     {
  25.         cmin = b;
  26.     }
  27.    
  28.     cdes = cmax - cmin;

  29.     v = cmax / 255.0f;
  30.     s = cmax == 0 ? 0 : cdes / (float) cmax;
  31.     h = 0;

  32.     if (cmax == r && g >= b)
  33.     {
  34.         h = ((g - b) * 60.0f / cdes) + 0;
  35.     }
  36.     else if (cmax == r && g < b)
  37.     {
  38.         h = ((g - b) * 60.0f / cdes) + 360;
  39.     }
  40.     else if (cmax == g)
  41.     {
  42.         h = ((b - r) * 60.0f / cdes) + 120;
  43.     }
  44.     else
  45.     {
  46.         h = ((r - g) * 60.0f / cdes) + 240;
  47.     }
  48.    
  49.     v *= k;

  50.     float f, p, q, t;
  51.     float rf, gf, bf;
  52.     int i = ((int) (h / 60) % 6);
  53.     f = (h / 60) - i;
  54.     p = v * (1 - s);
  55.     q = v * (1 - f * s);
  56.     t = v * (1 - (1 - f) * s);
  57.     switch (i)
  58.     {
  59.         case 0:
  60.         {
  61.             rf = v;
  62.             gf = t;
  63.             bf = p;
  64.             break;
  65.         }
  66.         case 1:
  67.         {
  68.             rf = q;
  69.             gf = v;
  70.             bf = p;
  71.             break;
  72.         }
  73.         case 2:
  74.         {
  75.             rf = p;
  76.             gf = v;
  77.             bf = t;
  78.             break;
  79.         }
  80.         case 3:
  81.         {
  82.             rf = p;
  83.             gf = q;
  84.             bf = v;
  85.             break;
  86.         }
  87.         case 4:
  88.         {
  89.             rf = t;
  90.             gf = p;
  91.             bf = v;
  92.             break;
  93.         }
  94.         case 5:
  95.         {
  96.             rf = v;
  97.             gf = p;
  98.             bf = q;
  99.             break;
  100.         }
  101.         default:
  102.         break;
  103.     }

  104.     r = (uint8_t) (rf * 255.0);
  105.     g = (uint8_t) (gf * 255.0);
  106.     b = (uint8_t) (bf * 255.0);

  107.     color = ((uint32_t) r << 16) | ((uint32_t) g << 8) | b;

  108.     return color;
  109. }
复制代码
以上代码中,首先是RGB模型转换成HSV模型,然后再将HSV模型转换成RGB模型。RGB模型我们知道是指红、绿、蓝三原色。HSV模型的参数分别是色调(H),饱和度(S)和明度(V)。在转换成HSV模型时,需要将R,G,B的值除以255,以更改范围从0~255到0~1,如下所示:

3.png

大家可以根据转换公式代入代码中去理解一下,公式如下所示:
H的计算公式:


第四十八章 WS2812B实验7415.png

S的计算公式:

第四十八章 WS2812B实验7426.png

V的计算公式:

第四十八章 WS2812B实验7437.png

接着就是HSV模型转换成RGB模型,当0≤H≤360,0≤S≤1和0≤V≤1时的公式如下:

4.png

2.main.c代码
在main.c里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t key, t = 0;
  4.     uint8_t k, i, j = 0;
  5.     uint32_t rgbled_buf[10];
  6.    
  7.     sys_mpu_config();                   /* 配置MPU */
  8.     sys_cache_enable();                 /* 使能Cache */
  9.     HAL_Init();                         /* 初始化HAL库 */
  10.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  11.     delay_init(600);                    /* 初始化延时 */
  12.     usart_init(115200);                 /* 初始化串口 */
  13.     led_init();                         /* 初始化LED */
  14.     key_init();                         /* 初始化按键 */
  15.     hyperram_init();                    /* 初始化HyperRAM */
  16.     ws2812b_init();                     /* 初始化W2812B */
  17.     delay_ms(10);                       /* 需延迟保持数据稳定 */
  18.    
  19.     while (1)
  20.     {
  21.         key = key_scan(0);
  22.         if(t % 100 == 0)                /* 间隔段时间换个颜色填充 */
  23.         {
  24.             for(i = 0; i < LED_NUM; i++)
  25.             {
  26.                 rgbled_buf[i] = g_grb888_color[j % LED_NUM];/* 颜色填充 */
  27.             }
  28.             for(k = 1; k <= LED_NUM; k++)
  29.             {
  30.                 ws2812b_display(k, rgbled_buf);            
  31. /* RGBLED灯光显示(流水灯效果) */
  32.                 delay_ms(50);
  33.             }
  34.             j++;                        /* 颜色数组的下一个成员 */
  35.             t = 0;            
  36.         }

  37.         t++;
  38.         if(key == WKUP_PRES)
  39.         {
  40.             ws2812b_reset();            /* 复位所有灯,让其熄灭 */
  41.         }
  42.         delay_ms(10);
  43.     }
  44. }
复制代码
该部分的代码逻辑很简单,初始化各个外设之后,进入死循环。通过第一个for循环,依次让每个RGBLED灯都亮起。所有灯亮起之后,进入第二个for循环,再依次显示另一个颜色。如果按键KEY_UP按下,所有灯熄灭并进行复位,重新开始颜色显示。

48.4 下载验证
下载代码后,可以看到RGBLED依次亮起同一个颜色,并且每2秒钟换一种颜色进行显示。此时我们按下KEY_UP按键,所有RGBLED灯熄灭,之后重新开始颜色显示。
回复

使用道具 举报

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

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-5-23 11:24

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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