赞
踩
本章内容使用 STM32CubeMX 软件配置 STM32F407ZGT6 开发板,使用 SPI + DMA 方式来驱动 WS2812 彩灯
教程包含通用步骤以及专用步骤,其中,通用步骤为STM32CubeMX配置其他外设工程的通用操作,STM32CubeMX系列教程基本通用,专用操作则是针对当前工程进行的配置
主控使用正点原子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 压力
打开cube软件,选择从芯片来创建工程,一般开发都是使用这个来开发,有的时候也可能使用另外两个,但不多,第二个基于ST提供的开发板创建工程,针对性高,第三个则选择ST提供的例程来创建工程
F4探索者的主控为STM32F407ZGT6,所以在搜索框找到STM32F407ZG后点击具体芯片,再开始工程
我们点开SystemCore(系统内核设置),再点击RCC配置HSE和LSE时钟源,这里我都选择使用外部时钟,配置后,我们可以看到右边芯片引脚分配图的两个时钟源引脚点亮,表示时钟配置为外部源
我们进入ClockConfiguration配置时钟树,使时钟的输入路径和大小符合我们预期,探索者的晶振和时钟倍频如下
一般配置正确时颜色蓝白为主,配置错误时则会出现紫色,提示我们要修改值
具体时钟树的了解可以看我很久之前的文章,有做一些分析
因为跳变沿为 1 的话,MOSI 的空闲电平为高电平,为 2 的话则会延续上次发送的最后电平,我们发送数据的末尾都是低电平,这样 WS2812 不会误判
点击进入Project Manager 配置生成工程的名字,存储路径**(不要有中文)**以及编译器,这里我们选MDK-ARM(Keil被收购后改名)
配置生成选项,主要为下面三大块,第一个我们选择只拷贝必要的库,第二个选择为每个外设生成.c和.h文件,保存之前的用户代码,以及删除之前的生成代码,第三个不选择
PS:用户代码段是一下注释之间的代码,只有原始的用户代码段注释才有效,用户自己添加的无效
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
最后点击生成代码
在工程目录创建 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 */
然后写源文件代码
#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);
}
//.........其他测试函数省略...........//
代码把刷新和设置隔离,方便在 RTOS 中调用,其实可以进一步修改,让不同硬件的颜色buffer和刷新进一步隔离,通过指针传递共用底层,我后面有时间再修改,可以关注我的仓库链接,后面更新了会推到上面
程序下载我一般用两种方式:
第一种是使用MDK自带的下载环境下载程序,我们给单片机连接ST-Link后配置下载,点击魔术棒,选择debug
选择ST-link后,点击setting
添加对应F4的Flash
keil界面点击下载
第二种是使用Stm32Programmer下载软件,该下载软件下载方式多,下载快,下面我使用st-link下载
打开软件,点击connect左边选择stlink后再点击connect连接下载器
点击open file,找到工程路径下MDK文件夹下工程生成的hex文件
之后点击downlod下载,下载结果如下
灯环刷新
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。