STM32使用PWM+DMA方式驱动WS2812灯珠
一. 关于WS2812
WS2812 内部集成了处理芯片和3颗不同颜色的led灯(红,绿,蓝),通过单总线协议分别控制三个灯的亮度强弱,达到全彩的效果。
WS2812B Datasheet
二. WS2812灯珠的几种驱动方式
- 使用延时函数
直接翻转IO口产生时序,这种方式最为简单易用,只需要控制延时的时间,就可以从产生0和1码,它需要占用系统资源。
使用 SPI 数据传输产生时序 - 通过SPI控制
只需要控制在合适的波特率,在传输不同数据的时候,可以产生符合要求的0和1码,这种方式需要等同于使用了一个SPI设备 - 使用 DMA+Timer 产生时序
这种方式需要使用一个定时器,其中一个通道固定产生一个周期1.25us
的PWM,占空比2/3,接着需要另一个通道,在周期的1/3处搬运数据到IO口,若为1,PWM不变,若为0,PWM则为0码,这种方式有更大的局限性,由于DMA只能搬运至少一个字节,所以每次会同时改变8个IO口的高低电平,或许使用位带操作可以解决这问题 - 使用 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);
}
/**
* @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~100static 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;}
}
/**
* @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 240void 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
声明:
本文有参考其他博客, 时间久远, 已忘记原文链接
另外, 本文整合并精简了部分代码, 也增加了常用的颜色格式转换等函数, 更容易使用些
互相交流学习, 不喜勿喷
STM32使用PWM+DMA方式驱动WS2812灯珠相关推荐
- WS2812灯珠(三)-- STM32 PWM+DMA方式驱动
WS2812灯珠(三)-- STM32 PWM+DMA方式驱动 文章目录 WS2812灯珠(三)-- STM32 PWM+DMA方式驱动 一.理论 二.代码实践 一.理论 PWM输出就是对外输出脉宽( ...
- 树莓派驱动 WS2812 灯珠 不亮的问题
前言 树莓派在控制某些硬件外设上坑还真不少,今天就又踩了一个(其实有两天了).其实越复杂的问题往往是有越简单的解决办法. 树莓派驱动ws2812 网上一搜几乎都是 用的 rpi-ws281x 这个库 ...
- 嵌入式作业STM32采用串口DMA方式发送数据
目录 前言 要求 一.DMA的基本介绍 DMA的基本定义 DMA的主要特征 STM32F411x系列芯片DMA控制器 二.通过CubeMX配置项目 1.创建项目 2.选择芯片STM32F103C8T6 ...
- WS2812灯珠(五)---移植Adafruit_NeoPixel库
将Adafruit_NeoPixel库移植为C版本 Adafruit_NeoPixel库为实现WS2812类似系列的灯珠实现非常酷炫的效果提供了各种接口函数,应用层可以很方便的利用这些接口函数实现各种 ...
- WS2812灯珠(四)---实现全彩呼吸灯效果
WS2812灯珠实现呼吸灯效果主要涉及到呼吸函数及颜色模型两部分的内容.清楚了这两点结合之前的灯珠驱动程序,便可以实现任意颜色的呼吸变换效果了. 呼吸函数 具体的呼吸函数细节这里就不介绍了,感兴趣的可 ...
- Arduino控制1302颗ws2812灯珠显示圣诞树和圣诞老人(附程序源码)
Arduino控制1302颗ws2812灯珠显示圣诞树和圣诞老人 设计者:STCode(公众号同名) 效果直接看视频~ Arduino控制ws2812灯带显示圣诞树和圣诞老人 1)项目介绍 该设计一共 ...
- STM32输出PWM波形并实现呼吸灯
文章目录 一.环境配置 二.PWM简介 三.使用STM32CubeMX配置工程 四.使用Keil配置代码 五.运行效果 六.用Keil自带的逻辑仿真器观察占空比 七.总结 八.参考资料 一.环境配置 ...
- STM32输出PWM波形及LED呼吸灯
目录 一.PWM的介绍 1.概述 2.优点 3.主要产生方法 SPWM法 1.等面积法 2.硬件调制法 3.软件生成法 4.PWM电路图 5.PWM基本原理 二.cubemx项目的建立 1.选择芯片S ...
- 关于RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题
1.开发平台 计算机操作系统:WIN7 64位: 开发环境:Keil MDK 5.14: MCU:STM32F407ZET6: STM32F4xx固件库:STM32F4xx_DSP_StdPeriph ...
最新文章
- 给 Python 初学者推荐的 IDE 哦!
- Java拾遗:001 - 重写 equals 和 hashCode 方法
- C# try catch finally 执行
- 在 Oracle 和 PHP 中使用 LOB
- 【OpenKruise v0.9.0】新增 Pod 重启、删除防护等重磅功能
- BP神经网络——从二次代价函数(Quadratic cost)到交叉熵(cross-entropy cost)代价函数
- linux的基本命令tail,Linux基本命令(示例代码)
- LINUX获得当前用户名
- html设置导背景宽度,calc()实现满屏背景定宽内容
- macbook 安装mysql_mac下安装mysql
- 计分器 java_非常实用的java语言自动答题计时计分器[Java代码]
- 外贸建站自己买主机空间好吗
- 高效能人士的七个习惯读后感与总结概括-(第一章)
- cmd命令行常用指令
- 学计算机u盘多少g合适,U盘设定分配单元的大小多少合适
- 当大数据遇到保险:传统精算模型将被颠覆
- JMM,synchronized
- eps图片太大压缩小
- 关于MAXIMO数据限制代码的理解
- 思科模拟器8.1版本身份验证失败禁用网络解决
热门文章
- python上海交通大学出版社答案网_上海交通大学出版社python答案
- 音乐网站毕业设计html,静态音乐网站设计(毕业论文).doc
- 热插拔能力,热交换,热冗余
- Kali应用——(一)信息收集
- [CTO札记]‘信息/行为外播’--与开放SNS平台的互动
- U盘超级加密(U盘,移动硬盘,共享文件夹加密软件)
- 制作三星I9088 刷机ROM的实践(二)
- Jonny Mo的读书笔记——《完成任务不找借口》之二
- java 实现58热敏票据打印
- 鸿蒙系统平板电脑能安装吗,平板电脑已预装鸿蒙系统,我们来看看效果