一文看懂WS2812呼吸灯实现

1. 相关资料

  WS2812是一个集控制电路与发光电路于一体的智能外控LED光源,外形一般为5050封装,每个LED灯珠为一个像素点,支持RGB无极调色,同时每颗灯珠内部集成有智能数字接口数据锁存信号整形放大驱动电路,还包含有高精度的内部振荡器和可编程定电流控制部分,有效保证了像素点光的颜色高度一致。
  数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,**首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。**像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅受限信号传输速度要求。

  有关此款灯珠的其他详细资料,大家可以看下各自手上的灯珠的对应的数据手册,里面会有详细描述。

  需要注意一点的是,为什么在这里强调大家一定要看自己手上的那款灯珠的数据手册呢?这是因为ws2812这款灯珠型号特别多,如果你随便去网上找的话会发现一个有意思的现象,手册基本上差不多,通讯时序也差不多,但是细看你会发现,不同手册灯珠通讯时序的具体时间要求会大不相同!!!所以大家最好是拿自己买的那颗灯珠的手册仔细阅读下

  以下是我找到的一款ws2812灯珠的数据手册内的通讯时序要求,见下图:
  WS2812是一颗数字LED灯珠,采用单总线通讯,每颗灯珠支持24bit的颜色控制,也即RGB888,信号线通过DIN输入,经过一颗灯珠之后,信号线上前24bit数据会被该灯珠锁存,之后将剩下的数据信号整形之后通过DOUT输出

2. 灯光控制

2.1 点亮一颗WS2812

根据上述数据手册我们可以知道,使用mcu或其他控制器,生成一段特殊的方波即可完成ws2812的驱动,下面先介绍一种最简单的驱动方法:配置IO为推挽输出,软件模拟控制IO产生控制时序

//注意:以下程序需要根据实际情况修改完善void ws2812_sendbit_1(void)
{拉高io;延时800ns;拉低io;延时300ns;拉高io;
}void ws2812_sendbit_0(void)
{拉高io;延时300ns;拉低io;延时800ns;拉高io;
}void ws2812_reset(void)
{拉低io;延时1ms;拉高io;
}void ws2812_set_rgb_for_onelamp(uint8_t r, uint8_t g, uint8_t b)
{ws2812_reset();for(uint8_t i = 0; i < 8; i++) {if(r >> (8-i))ws2812_sendbit_1();elsews2812_sendbit_0();}
}

  对于上述程序产生的时序是否符合ws2812驱动要求,可以通过使用示波器对波形进行测量之后对程序进行调整,以实现一颗灯珠的点亮,当需要点亮多颗灯珠时,重复调用上述ws2812_set_rgb_for_onelamp()函数即可。

  需要注意的是,当点亮多颗灯珠的时候往往容易出现第一颗灯珠或最后一颗灯珠颜色显示不正常,此时需要减少点亮的灯珠数,如点亮三颗灯珠,使用示波器抓取所有波形进行分析,此类故障往往是一些bug导致;此外如果最后一颗灯珠显示不正常,可以在发送完所有灯珠的rgb值之后再次调用一次ws2812_reset();看下问题是否解决

  以上方案是一种最简单,最容易实现的控制方式,但是此实现方式过于简单,点亮灯珠期间占用cpu资源,程序执行效率极低,特别是当灯珠数量多的时候,执行时间会更长,严重浪费cpu,不建议使用!

  在实际项目开发过程中,为了提升效率,往往采用PWM+DMA控制方式或SPI单总线控制方式,接下来本博文将介绍如何使用PWM+DMA方式驱动WS2812

2.2 点灯大师的高级玩法

接下来将向大家介绍如何使用PWM+DMA的方式实现WS12812的驱动。

根据ws2812 datasheet上描述可知,ws2812通讯所采用的0码和1码是周期一致,占空比不相同的PWM波

那么实现对输出PWM的每个波形的占空比控制,并实现对输出pwm个数的精准控制,即可实现ws2812多灯珠的驱动!

如果需要实现pwm的每个波形的占空比控制,那么肯定需要在每个pwm输出完成之后触发一个事件,通知到我们的程序切换下一个pwm输出的占空比,联想到能实现此功能也只有更新中断/事件和DMA了

如果采用更新中断,则需要在中断处理程序内去修改下一个pwm的占空比,也即改变比较寄存器的值,我们查看时序可以发现,无论是发送0码还是1码,周期都是非常短,在1us左右,

如果采用在中断处理,往往难以做到如此快速切换,同时周期1us的中断,也是十分消耗cpu资源的,而DMA则不同,dma相当于第二颗CPU,每当一个PWM输出完成之后即可触发搬运事件,不消耗cpu资源,同时速度极快

综上分析,因此我们可以得出以下方案来完成WS2812的驱动:

  • 采用定时器+DMA的方式
  • 定时器负责完成PWM的输出,通过改变每一个PWM的占空比模拟10 的发送时序
  • 采用定时器的更新事件作为DMA的搬运触发事件,这样每发送完一个PWM波之后,即触发一次DMA搬运,将下个需要发送的1或0的数据进行更新
  • 设计一个缓冲数组,这个里面存放RGB解码之后对应的定时器的CCR的值,也即占空比
  • 定时器每发送完一个波,即一个更新事件触发之后,触发DMA搬运
  • DMA负责把这个缓冲区的数据往定时器的比较寄存器一个个搬就可以了

采用PWM+DMA的方式驱动WS2812理论上来说是一种可行的方案。

以下为实际驱动代码(基于gd32f303 主频120Mhz):

tim.c文件内容如下,主要完成timer 及与timer 有关的 dma有关的初始化配置:

#include "./TIM/tim.h"
#include "./WS2812/ws2812.h"
#include "./TIM/tim_core.h"static void dma_tim_rcu_config(void);
static void dma_tim_config(void);
static void dma_nvic_config(void);/*!\brief      configure the GPIO ports\param[in]  none\param[out] none\retval     none
*/
void timer_pwm_gpio_config(void)
{rcu_periph_clock_enable(PWM_1_GPIO_CLK);#ifdef USE_TIM_REMAPrcu_periph_clock_enable(RCU_AF);gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);gpio_pin_remap_config(TIM_REMAP_CLK, ENABLE);
#endifgpio_init(PWM_1_GPIO_PORT,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,PWM_1_GPIO_PIN);
}/*!\brief      configure the TIMER peripheral\param[in]  none\param[out] none\retval     none
*/
void timer_pwm_config(void)
{timer_oc_parameter_struct timer_ocintpara;timer_parameter_struct timer_initpara;rcu_periph_clock_enable(PWM_CLK);timer_deinit(TIM_PWM);timer_initpara.prescaler         = TIM_PWM_PSC;timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;timer_initpara.counterdirection  = TIMER_COUNTER_UP;timer_initpara.period            = TIM_PWM_CCR;timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;timer_initpara.repetitioncounter = 0;timer_init(TIM_PWM, &timer_initpara);timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_HIGH;timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;timer_channel_output_config(TIM_PWM, TIMER_CH_1, &timer_ocintpara);timer_channel_output_pulse_value_config(TIM_PWM, TIMER_CH_1, 0);timer_channel_output_mode_config(TIM_PWM, TIMER_CH_1, TIMER_OC_MODE_PWM0);timer_channel_output_shadow_config(TIM_PWM, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);/* 设置timer的更新事件作为dma的触发信号,很关键!!! */timer_dma_enable(TIMER3,TIMER_DMA_UPD);/* auto-reload preload enable */timer_auto_reload_shadow_enable(TIM_PWM);/* auto-reload preload enable */timer_enable(TIM_PWM);
}/*** @brief dma inital, config dma of adc.*/
void dma_tim_init(void)
{/* DMA of ADC clocks configuration */dma_tim_rcu_config();/* DMA of ADC configuration */dma_tim_config();dma_nvic_config();
}static void dma_tim_rcu_config(void)
{/* enable DMA0 clock */rcu_periph_clock_enable(RCU_DMA0);
}static void dma_nvic_config(void)
{nvic_irq_enable(DMA0_Channel6_IRQn, 0, 0);
}/*!\brief      configure the DMA peripheral\param[in]  none\param[out] none\retval     none
*/
static void dma_tim_config(void)
{/* ADC_DMA_channel configuration */dma_parameter_struct dma_data_parameter;/* ADC DMA_channel configuration */dma_deinit(DMA0, DMA_CH6);/* initialize DMA single data mode */dma_data_parameter.periph_addr  = (uint32_t)(&TIMER_CH1CV(TIMER3));dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;dma_data_parameter.memory_addr  = (uint32_t)(&ws2812_rgb_buf);dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;  dma_data_parameter.direction    = DMA_MEMORY_TO_PERIPHERAL;dma_data_parameter.number       = (WS2812_NUM+20) * 24;dma_data_parameter.priority     = DMA_PRIORITY_ULTRA_HIGH;dma_init(DMA0, DMA_CH6, &dma_data_parameter);dma_circulation_disable(DMA0, DMA_CH6);dma_interrupt_enable(DMA0, DMA_CH6, DMA_INT_HTF);/* enable DMA channel */dma_channel_enable(DMA0, DMA_CH6);
}void DMA0_Channel6_IRQHandler(void)
{if(dma_interrupt_flag_get(DMA0, DMA_CH6, DMA_INT_HTF)){     dma_interrupt_flag_clear(DMA0, DMA_CH6, DMA_INT_FLAG_G);ws2812_dma_irq_handle();}
}

tim_core.c文件内容如下,此为中间层程序:

#include "./TIM/tim_core.h"
#include "./TIM/tim.h"
#include "./WS2812/ws2812.h"
#include "main.h"void timer_config_init(void)
{timer_pwm_gpio_config();timer_pwm_config();dma_tim_init();
}void ws2812_dma_irq_handle(void)
{rt_sem_release(&ws2812_sync_sem);
}void ws2812_enable(void)
{dma_channel_disable(DMA0, DMA_CH6);dma_transfer_number_config(DMA0, DMA_CH6, (WS2812_NUM+20) * 24);dma_channel_enable(DMA0, DMA_CH6);
}

以下为ws2812.c 程序,为ws2812的应用层实现:

#include "./WS2812/ws2812.h"
#include "./TIM/tim_core.h"
#include <rtthread.h>
#include <string.h>
#include "./LOG/log.h"
#include "main.h"#define TAG     "ws2812b.c"#define BIT_1                   102
#define BIT_0                   48/* +20:前十用于启动时Reset 后十用于停止时Reset */
uint16_t ws2812_rgb_buf[WS2812_NUM+20][24] = {0};void set_ws2812_color(uint8_t red, uint8_t green, uint8_t blue)
{int i = 0, j = 0;memset(ws2812_rgb_buf, 0, sizeof(ws2812_rgb_buf));rt_sem_take(&ws2812_sync_sem, RT_WAITING_FOREVER);for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j] = (green & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j+8]  = (red & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j+16]  = (blue & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}ws2812_enable();
}void set_ws2812_single_color(uint8_t index, uint8_t red, uint8_t green, uint8_t blue)
{int i = 0, j = 0;memset(ws2812_rgb_buf, 0, sizeof(ws2812_rgb_buf));rt_sem_take(&ws2812_sync_sem, RT_WAITING_FOREVER);for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j] = (0x00 & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j+8]  = (0x00 & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}for (i = 0; i < WS2812_NUM; i++) {for (j = 0; j < 8; j++)ws2812_rgb_buf[i+10][j+16]  = (0x00 & (0x01 << (7 - j))) ? BIT_1 : BIT_0;}for (j = 0; j < 8; j++)ws2812_rgb_buf[index+10][j] = (green & (0x01 << (7 - j))) ? BIT_1 : BIT_0;for (j = 0; j < 8; j++)ws2812_rgb_buf[index+10][j+8]  = (red & (0x01 << (7 - j))) ? BIT_1 : BIT_0;for (j = 0; j < 8; j++)ws2812_rgb_buf[index+10][j+16]  = (blue & (0x01 << (7 - j))) ? BIT_1 : BIT_0;ws2812_enable();
}void set_ws2812_breathing(uint8_t index)
{int i = 0;switch (index) {case 0: /* red */for (i = 0; i < 254; i+=2) {set_ws2812_color(i, 0, 0);rt_thread_delay(30);}for (i = 254; i > 0; i-=2) {set_ws2812_color(i, 0, 0);rt_thread_delay(30);}break;case 1: /* green */for (i = 0; i < 254; i+=2) {set_ws2812_color(0, i, 0);rt_thread_delay(30);}for (i = 254; i > 0; i-=2) {set_ws2812_color(0, i, 0);rt_thread_delay(30);}break;case 2: /* green */for (i = 0; i < 254; i+=2) {set_ws2812_color(0, 0, i);rt_thread_delay(30);}for (i = 254; i > 0; i-=2) {set_ws2812_color(0, 0, i);rt_thread_delay(30);}break;}
}void set_ws2812_running_water(uint8_t red, uint8_t green, uint8_t blue)
{int i = 0;for (i = 0; i < WS2812_NUM; i ++) {set_ws2812_single_color(i, red, green, blue);rt_thread_delay(30);}for (i = WS2812_NUM; i >= 0; i --) {set_ws2812_single_color(i, red, green, blue);rt_thread_delay(30);}
}

一文看懂WS2812的呼吸灯实现相关推荐

  1. 护眼灯和白炽灯哪个更保护眼睛视力?一文看懂护眼灯与普通灯区别

    现在的小孩子近视越来越严重,越来越趋近于低龄化,因此很多家长也是非常担心,想尽各种办法保护孩子视力,其中使用护眼台灯改善照明环境,是非常有效的选择.但是护眼灯光源分类较多,其中led灯最为普遍,光线柔 ...

  2. 新基建必看系列——一文看懂爆火的智慧灯杆未来趋势及竞争格局

    新基建必看系列--一文看懂爆火的智慧灯杆未来趋势及竞争格局 智慧灯杆是集照明.视频监控.交通管理.环境监测.通信等多功能于一体的新型信息基础设施,由基础设施及杆体,照明设施,交通.视频监控等其他杆载设 ...

  3. 一文看懂“语音识别ASR” | AI产品经理需要了解的AI技术概念

    原标题:一文看懂"语音识别ASR" | AI产品经理需要了解的AI技术概念 温馨提示:文末有[重大福利]:优惠券(金额很大) for 三节课<产品经理P2(进阶)系列课程&g ...

  4. 一文看懂“声纹识别VPR” | AI产品经理需要了解的AI技术概念_团员分享_@cony

    前言:声纹识别是AI领域中一个看似很小.但其实有机会在近期落地,且比较有意思的细分方向:本文作者是"AI产品经理大本营"团员@cony  ,她总结了AI产品经理"最必要& ...

  5. 2021-11-06一文看懂融合定位技术6种打开方式,深圳核芯物联国产蓝牙aoa融合定位生态合能伙伴方案展示 核芯物联岳毅恒

    一文看懂融合定位技术6种打开方式,深圳核芯物联国产蓝牙aoa融合定位生态合能伙伴方案展示 原创 市大妈 物联传媒 今天 方案素材:属于核芯物联国产蓝牙aoa融合定位生态伙伴所有 本文来源:物联传媒 本 ...

  6. 计算机攒机过程,电脑装机一筹莫展?一文看懂攒机全过程

    原标题:电脑装机一筹莫展?一文看懂攒机全过程 电脑装机一筹莫展?一文看懂攒机全过程 引言 大家好,双十一就要来了,有没有想要组装电脑的网友呀? 距离上一次在年中大促销活动(618)已经过去四个月了,上 ...

  7. 一文看懂 AI 训练集、验证集、测试集(附:分割方法+交叉验证)

    2019-12-20 20:01:00 数据在人工智能技术里是非常重要的!本篇文章将详细给大家介绍3种数据集:训练集.验证集.测试集. 同时还会介绍如何更合理的讲数据划分为3种数据集.最后给大家介绍一 ...

  8. 一文看懂计算机视觉-CV(基本原理+2大挑战+8大任务+4个应用)

    2020-03-06 20:00:00 计算机视觉(Computer Vision)是人工智能领域的一个重要分支.它的目的是:看懂图片里的内容. 本文将介绍计算机视觉的基本概念.实现原理.8 个任务和 ...

  9. 一文看懂人脸识别(4个特点+4个实现步骤+5个难点+算法发展轨迹)

    2020-03-09 20:01:00 人脸识别是身份识别的一种方式,目的就是要判断图片和视频中人脸的身份时什么. 本文将详细介绍人脸识别的4个特点.4个步骤.5个难点及算法的发展轨迹. 什么是人脸识 ...

最新文章

  1. HIbernate的优缺点
  2. 微软论文解读:用于视觉对话的多步双重注意力模型
  3. row_number() OVER(PARTITION BY)函数
  4. Web前端开发笔记——第二章 HTML语言 第七节 表格标签
  5. Spring_Bean配置_生命周期_注解
  6. 数学作图工具_科研论文作图系列-从PPT到AI (一)
  7. bodymovin导出没有html5,AE脚本-导出json格式的Web动画工具 Bodymovin v5.5.3+使用教程
  8. 我在 GitHub 上发现了一个 狗屁不通 的Python开源项目...
  9. 7-128 大于m的最小素数
  10. L298N模块的使用介绍
  11. 消除桌面上的计算机名称,Win10桌面图标有小箭头怎么去掉?Win10去掉桌面图标小箭头的方法...
  12. 计算机毕业设计 python微信公众平台机器人
  13. RTL设计与编码指导
  14. 科技助力东京奥运会:中国装备中国造
  15. LIGO 用 Python 分析引力波数据
  16. 盘点 | 2020大数据十大关键词与趋势新鲜出炉
  17. 《计算机网络自顶向下方法》读书笔记(一):计算机网络和因特网
  18. 阿里副总裁车品觉:无数据不成活
  19. 求好看简洁的PPT模版
  20. 如何把heic格式转换为jpg

热门文章

  1. power query是什么
  2. 聚观早报 | iPhone14 Pro可能是“药丸屏”;SpaceX获14亿美元订单
  3. 区块链之java调用智能合约(二)部署智能合约
  4. 通过PPM计算MHz晶振频率偏差和32.768KHz晶振计时公式
  5. 《口吃者的自我治疗》(8. 缓慢而专注地说话)
  6. zip() 和zip(*)
  7. ESP32使用外设RMT控制WS2812灯条
  8. 微信小程序开发规范文档
  9. 2021福建省安全员官方 单选题题库及答案
  10. 【MQ】【day2】MQ等其他基础概念