• STM32使用PWM+DMA方式驱动WS2812灯珠


    一. 关于WS2812

    WS2812 内部集成了处理芯片和3颗不同颜色的led灯(红,绿,蓝),通过单总线协议分别控制三个灯的亮度强弱,达到全彩的效果。

    WS2812B Datasheet

    在这里插入图片描述

    二. WS2812灯珠的几种驱动方式

    1. 使用延时函数
      直接翻转IO口产生时序,这种方式最为简单易用,只需要控制延时的时间,就可以从产生0和1码,它需要占用系统资源。
      使用 SPI 数据传输产生时序
    2. 通过SPI控制
      只需要控制在合适的波特率,在传输不同数据的时候,可以产生符合要求的0和1码,这种方式需要等同于使用了一个SPI设备
    3. 使用 DMA+Timer 产生时序
      这种方式需要使用一个定时器,其中一个通道固定产生一个周期1.25us的PWM,占空比2/3,接着需要另一个通道,在周期的1/3处搬运数据到IO口,若为1,PWM不变,若为0,PWM则为0码,这种方式有更大的局限性,由于DMA只能搬运至少一个字节,所以每次会同时改变8个IO口的高低电平,或许使用位带操作可以解决这问题
    4. 使用 Timer+PWM+DMA 产生时序
      本文讨论的实现方案,这种方案有2种驱动的方式,一种是直接建立一个大的数组,存放所有灯珠的数据,然后启动DMA传输,第二种是建立2个灯组数据大小的数组,当DMA传输一个灯珠数据时,改变另一个灯组数据,通过不断改变数组的方式,节约内存,相比较而言,第一种方式较为直观,第二种方式则可以解决灯珠较多的情况,本文讨论第一种的原理和程序的实现。

    三. STM32CubeMX 相关配置

    基于 STM32F405RGT6

    由于项目选择的TIM8定时器, 查询Datasheet得知, TIM8挂载于APB2总线
    在这里插入图片描述
    APB2的时钟基准为168MHz
    在这里插入图片描述
    计算自动重装载数值:
    我们要产生一个周期为1.25us的PWM,
    则 自动重装载值 = 0.00000125 * 168000000 = 210
    减一不多说
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    四. 代码实现部分

    /**
    * @file 在 tim.c 文件增加以下内容
    */
    
    void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
    {
        HAL_TIM_PWM_Stop_DMA(&htim8, TIM_CHANNEL_3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    /**
    * @file ws2812.c
    * @brief WS2812 LED driver, use (PC8) TIM8-CH3 PWM mode.
    * @author William
    * @date 2022.5.20
    */
    
    #include "ws2812.h"
    #include "tim.h"
    
    /* WS2812B f=800k, T=1.25us */
    #define ONE_PULSE    (143) //1 码 (2/3*T)
    #define ZERO_PULSE   (67)  //0 码 (1/3*T)
    #define RESET_PULSE  (9000)  //低电平复位信号50us
    #define LED_DATA_LEN (24)  //led 颜色数据长度, 一个灯珠需要24bits
    #define WS2812_DATA_LEN (RESET_PULSE + LED_NUMS * LED_DATA_LEN) //ws2812灯条需要的总数组长度
    
    /* !! 若嵌套循环使用, 须注意变量(i) !! */
    #define LOOP_ALL for(size_t i = 0; i < LED_NUMS; i++) /* 所有灯珠 */
    
    #define DEFAULT_BRIGHTNESS (100) //灯带默认亮度: 0~100
    
    static uint16_t RGB_buffer[WS2812_DATA_LEN] = {0};
    
    static void ws2812_refresh(void);
    static void color_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b);
    
    
    /* ************************************ Public Functions ************************************ */
    
    /**
    * @brief WS2812初始化, 全黑
    */
    void ws2812_init(void)
    {
        ws2812_set_dark(MAX);
    }
    
    /**
    * @brief 设置某个灯珠颜色RGB
    *
    * @param uint8_t R,G,B: RGB色彩格式 红,绿,蓝通道数据
    *        uint16_t num, 指定设置颜色的灯珠位号
    */
    void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num)
    {
        if (num > LED_NUMS)
            return;
    
        uint16_t *p = (RGB_buffer + RESET_PULSE) + (num * LED_DATA_LEN);
    
        for (uint16_t i = 0; i < 8; i++)
        {
            p[i]      = (G << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
            p[i + 8]  = (R << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
            p[i + 16] = (B << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
        }
    }
    
    /**
    * @brief 设置某个灯珠颜色HSV
    *
    * @param uint8_t H,S,V: HSV色彩格式
    *        uint16_t num, 指定设置颜色的灯珠位号
    */
    void ws2812_set_HSV(uint16_t H, uint16_t S, uint16_t V, uint16_t num)
    {
        uint32_t R = 0, G = 0, B = 0;
    
        if (num > LED_NUMS)
            return;
    
        uint16_t *p = (RGB_buffer + RESET_PULSE) + (num * LED_DATA_LEN);
    
        color_hsv2rgb(H, S, V, &R, &G, &B);
    
        for (uint16_t i = 0; i < 8; i++)
        {
            p[i]      = (G << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
            p[i + 8]  = (R << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
            p[i + 16] = (B << i) & (0x80) ? ONE_PULSE : ZERO_PULSE;
        }
    }
    
    /**
    * @brief 灭灯
    */
    void ws2812_set_dark(uint8_t type)
    {
        switch (type)
        {
        default:
            LOOP_ALL
            {
                ws2812_set_RGB(0x00, 0x00, 0x00, i);
            }
            break;
        }
    
        ws2812_refresh();
    }
    
    /**
    * @brief WS2812全彩渐变
    * @note 需放置循环体内
    */
    void ws2812_colorful_shadow(void)
    {
        for (uint16_t color = 0; color < 360; color++)
        {
            for (uint8_t i = 0; i < LED_NUMS; i++)
            {
                ws2812_set_HSV(color, 100, DEFAULT_BRIGHTNESS, i);
            }
            ws2812_refresh();
            osDelay(50);
        }
    }
    
    /* ************************************ Static Functions ************************************ */
    
    /**
    * @brief WS2812颜色数据刷新, 修改颜色值后调用
    */
    static void ws2812_refresh(void)
    {
        HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)RGB_buffer, WS2812_DATA_LEN);
    }
    
    /**
     * @brief 将HSV颜色空间转换为RGB颜色空间
     *
     * @param  h HSV颜色空间的H:色调, 范围0~360
     * @param  s HSV颜色空间的S:饱和度, 范围0~100
     * @param  v HSV颜色空间的V:明度, 范围0~100
     * @param  r 转换后RGB-R值的指针
     * @param  g 转换后RGB-G值的指针
     * @param  b 转换后RGB-B值的指针
     *
     */
    static void color_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
    {
        h %= 360; // h -> [0,360]
        uint32_t rgb_max = v * 2.55f;
        uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
    
        uint32_t i = h / 60;
        uint32_t diff = h % 60;
    
        uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
    
        switch (i)
        {
        case 0:
            *r = rgb_max;
            *g = rgb_min + rgb_adj;
            *b = rgb_min;
            break;
        case 1:
            *r = rgb_max - rgb_adj;
            *g = rgb_max;
            *b = rgb_min;
            break;
        case 2:
            *r = rgb_min;
            *g = rgb_max;
            *b = rgb_min + rgb_adj;
            break;
        case 3:
            *r = rgb_min;
            *g = rgb_max - rgb_adj;
            *b = rgb_max;
            break;
        case 4:
            *r = rgb_min + rgb_adj;
            *g = rgb_min;
            *b = rgb_max;
            break;
        default:
            *r = rgb_max;
            *g = rgb_min;
            *b = rgb_max - rgb_adj;
            break;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    /**
    * @file ws2812.h
    * @brief WS2812 LED driver, use TIM8-CH3 PWM mode.
    * @author William
    * @date 2022.5.20
    */
    
    #ifndef  __WS2812_H
    #define __WS2812_H
    
    #include "stdint.h"
    
    #define LED_NUMS (9) //灯珠数量
    
    /* HSV格式常用色值预设 */
    #define RED 0
    #define YELLOW 60
    #define GREEN 120
    #define CYAN 180
    #define BLUE 240
    
    void ws2812_init(void);
    void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num);
    void ws2812_set_HSV(uint16_t H, uint16_t S, uint16_t V, uint16_t num);
    void ws2812_set_dark(uint8_t type);
    void ws2812_colorful_shadow(void);
    
    #endif  //__WS2812_H
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    声明:
    本文有参考其他博客, 时间久远, 已忘记原文链接
    另外, 本文整合并精简了部分代码, 也增加了常用的颜色格式转换等函数, 更容易使用些
    互相交流学习, 不喜勿喷

  • 相关阅读:
    vulnhub靶机DC1
    HTTP服务器——tomcat的安装和使用
    六、程序员指南:数据平面开发套件
    设计模式个人理解——工厂模式
    【STM32】入门(二):跑马灯-GPIO端口输出控制
    Promethus(普罗米修斯)安装与配置(亲测可用)
    STM32产品介绍
    【Vue】VUE常用的几种import引入方式
    开启智慧之旅,AI与机器学习驱动的微服务设计模式探索
    面试-J.U.C包的梳理
  • 原文地址:https://blog.csdn.net/William_Zhang_CSDN/article/details/126699601