ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第四十八章 WS2812B实验

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

原创 嵌入式/内核开发 作者:正点原子 时间:2025-09-22 09:28:15 0 删除 编辑

第四十八章 WS2812B实验


       本章,我们将介绍如何在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具有如下特点:

       l 外观尺寸:2.0*1.8*0.8mm。

       l 颜色:红光、绿光、蓝光。

       l 胶体:无色透明。

       l EIA规范标准包装。

       l 环保产品,符合ROHS要求。

       l 适用于自动贴片机。

       l 适用于红外线回流焊制程。

       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
 * @note        T1H / T1L > 2 / 1 且T1H + T1L = 1.25us (允许些许偏差)
 * @param       无
 * @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灯熄灭,之后重新开始颜色显示。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3096211/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764723