STM32CubeMX-SPI+DMA 驱动 2812 灯带

一、初始准备
1.硬件平台
主控使用正点原子STM32F4探索者:

WS2812 灯环:

WS2812 是一个单总线控制的 RGB 彩灯,,通过单根总线向其写入数据,通过写入的数据控制 RGB 颜色,因为是单总线,所以通过总线上高低电平时间长短的不同来区分 0 和 1,手册中关于 0 和 1 的区分如下:

具体时间对应如下:

数据发送速度可达 800Kbps,就是 1.25us 发送一位数据,因为协议有一定的兼容性,所以实际上一个位的周期在1.25us±300ns之间都能识别到,因为是 us 级延时,所以时间要控制精准很难,因此我们借助 SPI 来控制 WS2812,我们用 SPI 的 MOSI 接口的一个 Byte(8位)模拟 WS2812 的一个位,比如下面的 SPI 我 设置的 5.25M 速率,一个字节约为 1.5us,所以可以通过发送了一个字节的数据控制电平时间,然后模拟 0 和 1 到 WS2812,比如下面发送的 0xF8

高电平时间 950ns,低电平时间我们不用管,只要整体传输速率在 2812 范围内就行就行,2812 识别 0 和 1 主要还是看高电平时间,所以下面这个 SPI 发的数据是 0xF8 代表的就是 2812 的 1

同理,当发送的数据是 0xC0 时这代表 0

到此,我们就可以通过 SPI 发送的不同数据来发送 0 和 1 到 WS2812,SPI 数据发送 0 和 1的原理明白了,WS2812 控制等的颜色需要 24 位,分别对应 G、R、B 的三个颜色的值,当我们发完一个灯的 RGB 值后,继续发送就会切换到下一个灯的 RGB 色彩控制,循环下去,所以 WS2812 可以无线套娃,可 DIY 性很高

但我们仔细想想,发送 24 位控制 1 个灯,相当于 SPI 要发送 24 个字节去控制一个灯,这对资源的占用太大了,严重占用 CPU 资源,所以我们一般用 SPI 控制灯的时候顺便加上 DMA,减小 CPU 压力

二、操作步骤
1.CubeMX生成初始化代码
1.1 建立工程(通用步骤)
芯片选择
打开cube软件,选择从芯片来创建工程,一般开发都是使用这个来开发,有的时候也可能使用另外两个,但不多,第二个基于ST提供的开发板创建工程,针对性高,第三个则选择ST提供的例程来创建工程

F4探索者的主控为STM32F407ZGT6,所以在搜索框找到STM32F407ZG后点击具体芯片,再开始工程

配置时钟源
我们点开SystemCore(系统内核设置),再点击RCC配置HSE和LSE时钟源,这里我都选择使用外部时钟,配置后,我们可以看到右边芯片引脚分配图的两个时钟源引脚点亮,表示时钟配置为外部源

配置时钟树
我们进入ClockConfiguration配置时钟树,使时钟的输入路径和大小符合我们预期,探索者的晶振和时钟倍频如下

一般配置正确时颜色蓝白为主,配置错误时则会出现紫色,提示我们要修改值

具体时钟树的了解可以看我很久之前的文章,有做一些分析

CSDN文章链接-时钟树分析

1.2配置SPI和DMA外设
开启 SPI1,配置为只有主机发送,然后配置 SPI 为 5.25M 跳变沿选第二个(2Edge)
因为跳变沿为 1 的话,MOSI 的空闲电平为高电平,为 2 的话则会延续上次发送的最后电平,我们发送数据的末尾都是低电平,这样 WS2812 不会误判

开启 DMA,参数保持默认


1.3生成代码(通用步骤)
点击进入Project Manager 配置生成工程的名字,存储路径**(不要有中文)**以及编译器,这里我们选MDK-ARM(Keil被收购后改名)

配置生成选项,主要为下面三大块,第一个我们选择只拷贝必要的库,第二个选择为每个外设生成.c和.h文件,保存之前的用户代码,以及删除之前的生成代码,第三个不选择

PS:用户代码段是一下注释之间的代码,只有原始的用户代码段注释才有效,用户自己添加的无效

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */
1
2
3


最后点击生成代码

2.编写代码
在工程目录创建 Hardware 文件夹,下面在创建个 ws2812 文件夹,里面放上如下文件

在 MDK 工程里面导入文件和其路径

然后编写驱动代码,这里我参考了这篇文章,在其基础上进行修改,改动的挺多,完善内存使用,同时将刷新和设置隔离,方便我们可以在 RTOS 中使用

WS2812b幻彩ARGB灯珠的STM32F103的CPU-SPI方式驱动-作者:爱莎女王

先写头文件代码

#ifndef __WS2812_H
#define __WS2812_H

#include "main.h"

typedef struct                //颜色结构体
{
  uint8_t R;
  uint8_t G;
  uint8_t B;
}RGBColor_TypeDef;

#define RGB_NUM    24    // RGB数量

// 复位函数
void RGB_RST(void);
// 颜色设置函数
void RGB_Set_Color(uint8_t LedId, RGBColor_TypeDef Color);
// RGB 刷新函数
void RGB_Reflash(uint8_t reflash_num);
    
// 各种颜色测试
void RGB_RED(uint16_t RGB_LEN);        //红
void RGB_GREEN(uint16_t RGB_LEN);        //绿
void RGB_BLUE(uint16_t RGB_LEN);        //蓝
void RGB_YELLOW(uint16_t RGB_LEN);        //黄
void RGB_MAGENTA(uint16_t RGB_LEN);    //紫
void RGB_BLACK(uint16_t RGB_LEN);        //黑
void RGB_WHITE(uint16_t RGB_LEN);        //白

#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
30
31
然后写源文件代码

#include "ws2812.h"
#include "spi.h"
#include "dma.h"

// 常用的颜色,亮度调的比较低
const RGBColor_TypeDef RED      = {30 ,0  ,  0};
const RGBColor_TypeDef GREEN    = {0  , 30,  0};
const RGBColor_TypeDef BLUE     = {0  ,  0, 30};
const RGBColor_TypeDef YELLOW   = { 30, 30,  0};
const RGBColor_TypeDef MAGENTA  = { 30,  0, 30};
const RGBColor_TypeDef BLACK    = {  0,  0,  0};
const RGBColor_TypeDef WHITE    = { 30, 30, 30};

//模拟bit码:0xC0 为 0,0xF8 为 1
const uint8_t code[]={0xC0,0xF8};
//灯颜色缓存区
RGBColor_TypeDef RGB_DAT[RGB_NUM];

//SPI底层发送接口,一次发24个字节,相当于1个灯
extern DMA_HandleTypeDef hdma_spi1_tx;
static void SPI_Send(uint8_t *SPI_RGB_BUFFER)
{
  /* 判断上次DMA有没有传输完成 */
    while(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY);
  /* 发送一个(24bit)的 RGB 数据到 2812 */
    HAL_SPI_Transmit_DMA(&hspi1,SPI_RGB_BUFFER,24);  
}
//颜色设置函数,传入 ID 和 颜色,进而设置缓存区
void RGB_Set_Color(uint8_t LedId, RGBColor_TypeDef Color)  
{
  if(LedId < RGB_NUM)
    {
        RGB_DAT[LedId].G = Color.G;
        RGB_DAT[LedId].R = Color.R;
        RGB_DAT[LedId].B = Color.B;
    }
}
//刷新函数,将颜色缓存区刷新到WS2812,输入参数是指定的刷新长度
void RGB_Reflash(uint8_t reflash_num)
{
    static uint8_t RGB_BUFFER[24]={0};
    uint8_t dat_b,dat_r,dat_g;
    //将数组颜色转化为 24 个要发送的字节数据
    if(reflash_num>0 && reflash_num<=RGB_NUM)
    {
        for(int i=0;i<reflash_num;i++)
        {
            dat_g = RGB_DAT[i].G;
            dat_r = RGB_DAT[i].R;
            dat_b = RGB_DAT[i].B;
            for(int j=0;j<8;j++)
            {
                RGB_BUFFER[7-j] =code[dat_g & 0x01];
                RGB_BUFFER[15-j]=code[dat_r & 0x01];
                RGB_BUFFER[23-j]=code[dat_b & 0x01];
                dat_g >>=1;
                dat_r >>=1;
                dat_b >>=1;
            }
            SPI_Send(RGB_BUFFER);
        }
    }
}
//复位函数
void RGB_RST(void)
{
    uint8_t dat[100] = {0};
  /* 判断上次DMA有没有传输完成 */
    while(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY);
  /* RGB RESET */
    HAL_SPI_Transmit_DMA(&hspi1,dat,100); 
    HAL_Delay(10);
}

//常用颜色的点亮测试函数
void RGB_RED(uint16_t RGB_LEN)
{
  uint8_t i;
  for(i=0;i<RGB_LEN;i++)  
    RGB_Set_Color(i,RED);
    RGB_Reflash(RGB_LEN);
}
//.........其他测试函数省略...........//


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
代码把刷新和设置隔离,方便在 RTOS 中调用,其实可以进一步修改,让不同硬件的颜色buffer和刷新进一步隔离,通过指针传递共用底层,我后面有时间再修改,可以关注我的仓库链接,后面更新了会推到上面

JeckXu Gitee

3.程序下载,观察现象(通用步骤)
程序下载我一般用两种方式:

第一种是使用MDK自带的下载环境下载程序,我们给单片机连接ST-Link后配置下载,点击魔术棒,选择debug

选择ST-link后,点击setting

添加对应F4的Flash

keil界面点击下载

第二种是使用Stm32Programmer下载软件,该下载软件下载方式多,下载快,下面我使用st-link下载

打开软件,点击connect左边选择stlink后再点击connect连接下载器

点击open file,找到工程路径下MDK文件夹下工程生成的hex文件

之后点击downlod下载,下载结果如下

三、实验现象
灯环刷新
————————————————
版权声明:本文为CSDN博主「Top嵌入式」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45396672/article/details/123687031

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值