超级版主
 
- 积分
- 5703
- 金钱
- 5703
- 注册时间
- 2019-5-8
- 在线时间
- 1538 小时
|
|
第四十八章 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
本章,我们将介绍如何在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的内部结构如下图所示:
图48.1.1.1 WS2812B内部框图
我们可以看到内部框架是比较简单的,通过4根线与单片机连接,包括:电源输入线、信号线和地线,具体如下表所示:
图48.1.1.1 引脚说明
48.1.2 WS2812B数据传输
WS2812B采用的数据通信协议是单极性归零码,每一个码元都必须有低电平。本协议的每个码元起始为高电平,高电平时间宽度决定“0”码或者“1”码。
图48.1.2.1 归零码数据通信协议图
上图是一个码元周期的时序波形图,每个阶段的数据传输时间如下表所示:
表48.1.2.1 数据传输时间表
我们写程序时,码元周期最低要求为1.2us。0码、1码的高电平时间需按照上表的规定范围,0码、1码的低电平时间要求小于20us。
另外,数据结构是24bit(3*8)灰度数据结构:高位在前,按照OUTR1/2/3的顺序发送,如下图所示:
图48.1.2.2 协议数据结构
连接方法如下图所示:
图48.1.2.3 连接方法
上图是各芯片的连接方法,接下来我们讲解一下数据的传输方法,各芯片输入数据流(以3颗芯片为例)如下图所示:
图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和开发板的连接,如下图所示:
图48.2.1 WS2812B与开发板连接原理图
48.3 程序设计
48.3.1 程序解析
1. WS2812B驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。WS2812B驱动源码包括两个文件:ws2812b.c和ws2812b.h。
首先是ws2812b.h文件,我们把用到的引脚和RGBLED的 数据以及颜色值等等都用宏定义来表示,需要更换引脚或某些参数的数值时也可以通过修改宏实现快速移植,如下所示:
- #define WS2812B_PORT GPIOB
- #define WS2812B_PIN GPIO_PIN_1
- #define WS2812B_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
- #define WS2812B(x) do{ x ? \
- HAL_GPIO_WritePin(WS2812B_PORT, WS2812B_PIN, GPIO_PIN_SET) : \
- HAL_GPIO_WritePin(WS2812B_PORT, WS2812B_PIN, GPIO_PIN_RESET); \
- }while(0)
- /* RGBLED的数据相关宏定义 */
- #define DELAY_15nS() __NOP()
- /* 宏定义空指令的方法不好计算,只能大致计算,并且受编译器优化等级影响 */
- #define DELAY_400nS() for(uint16_t i = 0; i < 106; i++) __NOP()
- /* 400ns 约等于循环次数 * 2 * 1.67ns(一个时钟) + 44ns */
- #define DELAY_800nS() for(uint16_t i = 0; i < 225; i++) __NOP()
- /* 800ns */
- #define LED_NUM 10 /* 灯珠的个数 */
- /* GRB888颜色值 */
- #define GRB888_RED 0x00FF00 /* 红色 */
- #define GRB888_GREEN 0xFF0000 /* 绿色 */
- #define GRB888_BLUE 0x0000FF /* 蓝色 */
- #define GRB888_BLACK 0x000000 /* 黑色 */
- #define GRB888_WHITE 0xFFFFFF /* 白色 */
- #define GRB888_YELLOW 0xFFFF00 /* 黄色 */
- #define GRB888_IRED 0x5CCD5C /* 浅红色 */
- #define GRB888_ORANGE 0xA5FF00 /* 橙色 */
- #define GRB888_PURPLE 0x008080 /* 紫色 */
- #define GRB888_PING 0xB6FFC1 /* 浅粉色 */
- #define GRB888_CYAN 0xFF00FF /* 青色 */
- #define GRB888_PBLUE 0x80008C /* 孔雀蓝 */
- #define GRB888_VIOLET 0x008BFF /* 紫罗兰 */
复制代码 ws2812b.c我们这里介绍用IO模拟的方法来实现本章实验的功能,另一种定时器+DMA的方式可以参考具体源码以及定时器和DMA章节的讲解。我们来讲解以下几个函数:
- /**
- * @brief WS2812B的逻辑1
- * [url=home.php?mod=space&uid=60778]@note[/url] T1H / T1L > 2 / 1 且T1H + T1L = 1.25us (允许些许偏差)
- * [url=home.php?mod=space&uid=271674]@param[/url] 无
- * @retval 无
- */
- static void ws2812b_send_high(void)
- {
- WS2812B(1);
- DELAY_800nS();
- WS2812B(0);
- DELAY_400nS();
- }
- /**
- * @brief WS2812B的逻辑0
- * @note T1H / T1L < 1 / 2 且T1H + T1L = 1.25us (允许些许偏差)
- * @param 无
- * @retval 无
- */
- static void ws2812b_send_low(void)
- {
- WS2812B(1);
- DELAY_400nS();
- WS2812B(0);
- DELAY_800nS();
- }
复制代码 以上这两个函数是WS2812B的逻辑1和0,可以参考我们前面讲过的0码和1码的高低电平的传输时间。
接着是WS2812B的初始化函数,其定义如下:
- /**
- * @brief 初始化WS2812B相关IO口, 并使能时钟
- * @param 无
- * @retval 无
- */
- void ws2812b_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct;
- WS2812B_GPIO_CLK_ENABLE(); /* 时钟使能 */
- gpio_init_struct.Pin = WS2812B_PIN; /* WS2812B引脚 */
- gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开楼输出 */
- gpio_init_struct.Pull = GPIO_NOPULL; /* 无上下拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速 */
- HAL_GPIO_Init(WS2812B_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
-
- ws2812b_reset(); /* 复位WS2812B */
- }
复制代码 在初始化函数中,主要设置了WS2812B的引脚各项参数,在最后要对WS2812B进行一次复位。
接着是对RGBLED灯要显示的颜色亮度进行设置,这里涉及到RGB模型和HSV模型互相转化,其代码如下所示:
- /**
- * @brief 改变所要显示的颜色亮度(RGB->HSV->RGB)
- * @param rgb : GRB888颜色值
- * @param k : 亮度值(0.0 ~ 1.0)
- * @retval 改变亮度后的颜色值
- */
- uint32_t color_change_brigh(uint32_t rgb, float k)
- {
- uint8_t r, g, b;
- float h, s, v;
- uint8_t cmax, cmin, cdes;
- uint32_t color;
- r = (uint8_t) (rgb >> 16);
- g = (uint8_t) (rgb >> 8);
- b = (uint8_t) (rgb);
-
- cmax = r > g ? r : g;
- if (b > cmax)
- {
- cmax = b;
- }
- cmin = r < g ? r : g;
- if (b < cmin)
- {
- cmin = b;
- }
-
- cdes = cmax - cmin;
- v = cmax / 255.0f;
- s = cmax == 0 ? 0 : cdes / (float) cmax;
- h = 0;
- if (cmax == r && g >= b)
- {
- h = ((g - b) * 60.0f / cdes) + 0;
- }
- else if (cmax == r && g < b)
- {
- h = ((g - b) * 60.0f / cdes) + 360;
- }
- else if (cmax == g)
- {
- h = ((b - r) * 60.0f / cdes) + 120;
- }
- else
- {
- h = ((r - g) * 60.0f / cdes) + 240;
- }
-
- v *= k;
- float f, p, q, t;
- float rf, gf, bf;
- int i = ((int) (h / 60) % 6);
- f = (h / 60) - i;
- p = v * (1 - s);
- q = v * (1 - f * s);
- t = v * (1 - (1 - f) * s);
- switch (i)
- {
- case 0:
- {
- rf = v;
- gf = t;
- bf = p;
- break;
- }
- case 1:
- {
- rf = q;
- gf = v;
- bf = p;
- break;
- }
- case 2:
- {
- rf = p;
- gf = v;
- bf = t;
- break;
- }
- case 3:
- {
- rf = p;
- gf = q;
- bf = v;
- break;
- }
- case 4:
- {
- rf = t;
- gf = p;
- bf = v;
- break;
- }
- case 5:
- {
- rf = v;
- gf = p;
- bf = q;
- break;
- }
- default:
- break;
- }
- r = (uint8_t) (rf * 255.0);
- g = (uint8_t) (gf * 255.0);
- b = (uint8_t) (bf * 255.0);
- color = ((uint32_t) r << 16) | ((uint32_t) g << 8) | b;
- return color;
- }
复制代码 以上代码中,首先是RGB模型转换成HSV模型,然后再将HSV模型转换成RGB模型。RGB模型我们知道是指红、绿、蓝三原色。HSV模型的参数分别是色调(H),饱和度(S)和明度(V)。在转换成HSV模型时,需要将R,G,B的值除以255,以更改范围从0~255到0~1,如下所示:
大家可以根据转换公式代入代码中去理解一下,公式如下所示:
H的计算公式:
S的计算公式:
V的计算公式:
接着就是HSV模型转换成RGB模型,当0≤H≤360,0≤S≤1和0≤V≤1时的公式如下:
2.main.c代码
在main.c里面编写如下代码:
- int main(void)
- {
- uint8_t key, t = 0;
- uint8_t k, i, j = 0;
- uint32_t rgbled_buf[10];
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- key_init(); /* 初始化按键 */
- hyperram_init(); /* 初始化HyperRAM */
- ws2812b_init(); /* 初始化W2812B */
- delay_ms(10); /* 需延迟保持数据稳定 */
-
- while (1)
- {
- key = key_scan(0);
- if(t % 100 == 0) /* 间隔段时间换个颜色填充 */
- {
- for(i = 0; i < LED_NUM; i++)
- {
- rgbled_buf[i] = g_grb888_color[j % LED_NUM];/* 颜色填充 */
- }
- for(k = 1; k <= LED_NUM; k++)
- {
- ws2812b_display(k, rgbled_buf);
- /* RGBLED灯光显示(流水灯效果) */
- delay_ms(50);
- }
- j++; /* 颜色数组的下一个成员 */
- t = 0;
- }
- t++;
- if(key == WKUP_PRES)
- {
- ws2812b_reset(); /* 复位所有灯,让其熄灭 */
- }
- delay_ms(10);
- }
- }
复制代码 该部分的代码逻辑很简单,初始化各个外设之后,进入死循环。通过第一个for循环,依次让每个RGBLED灯都亮起。所有灯亮起之后,进入第二个for循环,再依次显示另一个颜色。如果按键KEY_UP按下,所有灯熄灭并进行复位,重新开始颜色显示。
48.4 下载验证
下载代码后,可以看到RGBLED依次亮起同一个颜色,并且每2秒钟换一种颜色进行显示。此时我们按下KEY_UP按键,所有RGBLED灯熄灭,之后重新开始颜色显示。 |
|