STM32应用开发——使用PWM+DMA驱动WS2812

STM32应用开发——使用PWM+DMA驱动WS2812

目录

  • STM32应用开发——使用PWM+DMA驱动WS2812
    • 前言
    • 1 硬件介绍
      • 1.1 WS2812介绍
        • 1.1.1 芯片简介
        • 1.1.2 引脚描述
        • 1.1.3 工作原理
        • 1.1.4 时序
        • 1.1.5 传输协议
      • 1.2 电路设计
    • 2 软件编程
      • 2.1 软件原理
      • 2.2 测试代码
        • 2.2.1 底层驱动
        • 2.2.2 灯效应用
      • 2.3 运行测试
        • 2.3.1 时序测试
        • 2.3.2 实际效果
    • 结束语

前言

串行灯带的应用十分广泛,其中以WS2812最为经典,这种灯带一般都是通过单总线的方式来驱动,也就是由一根数据线按照特定的时序输出,继而驱动灯带。这种方式在硬件和软件上都非常简单,但是如果软件用GPIO模拟时序的话比较占用主线程的资源,因此,如果能用硬件外设(比如PWM、SPI、串口)来模拟出这个时序,就能节省MCU的资源。
本文以PWM+DMA为例介绍如何驱动WS2812。

1 硬件介绍

1.1 WS2812介绍

1.1.1 芯片简介

WS2812是一款智能控制LED光源,其外观采用最新的MOLDING封装技术、控制电路和RGB芯片集成在2020组件的封装中。其内部包括智能数字端口数据锁存和信号整形放大驱动电路。还包括精密内部振荡器和电压可编程恒流控制部分,有效保证像素点光源的颜色。

1.1.2 引脚描述
引脚名称描述
DO数据输出控制数据输出到下一个芯片
GND电源负极
DI数据输入控制数据输入
VDD电源电源正极
1.1.3 工作原理

通过级联法把每个灯的DI和DO引脚首尾相连,数据可以从第一个IC开始,不断的传输到后面每一个IC,从而实现整条灯带的控制。
在这里插入图片描述

1.1.4 时序

WS2812通过不同的时序来表示0码1码复位码,如下图所示:
在这里插入图片描述
其中各信号的电平如下图所示:
在这里插入图片描述
注:不同型号的芯片在时序上会有点差异,具体以芯片数据手册为准。

1.1.5 传输协议

传输过程如下图所示:
在这里插入图片描述

每一个灯珠的RGB数据排列如下:
在这里插入图片描述

1.2 电路设计

WS2812的控制方法很简单,每个灯珠首尾相接进行级联即可,如下图所示:
在这里插入图片描述
其中,第一个灯珠的DI引脚接入到MCU的一个GPIO上面。

我这里使用STM32F103来作为主控MCU,引脚接线如下:

MCU引脚灯带引脚描述
PA0DI由MCU发送控制信号输入到灯带

2 软件编程

2.1 软件原理

通过DMA可以精确控制PWM输出的每一个方波,然后通过调整占空比,就可以输出0码1码复位码,从而实现灯珠的驱动。
举个例子:按照上面的手册的时序要求,每一个逻辑电平周期在1.25us左右,也就是800kHz,那么PWM输出的频率就可以设置为800kHz。此时改变PWM的占空比,就可以区分编码“0”和编码“1”,比如编码“0”的高电平脉宽和低电平脉宽分别为0.4us和0.85us,那么对应的PWM占空比就是32%和68%,然后通过DMNA连续传输RGB数据就可以实现灯带的颜色和亮度控制。

测试电平时序如下:

逻辑电平脉宽PWM占空比
逻辑0高电平0.40±0.15us32%
逻辑0低电平0.85±0.15us68%
逻辑1高电平0.85±0.15us68%
逻辑1低电平0.40±0.15us32%
复位低电平1.25±0.60us0%

2.2 测试代码

根据上述原理,编写测试代码。

2.2.1 底层驱动

ws2812_driver.h :

#ifndef __WS2812_DRIVER_H
#define __WS2812_DRIVER_H#include "stm32f10x.h"
#include "stm32f10x_conf.h"#define TIM2_CCR1_Address 0x40000034  // stm32 tim2 base address offset 0x34#define LED_NUM     8    // LED的数量
#define RGB_BIT     24   // 每个灯有24bit的RGB数据,依次按G R B排列#define RESET_WORD  5    // 在传输RGB数据前保持一段低电平
#define DUMMY_WORD  5    // 在传输RGB数据后保持一段低电平#define TIMING_0    29   // T0H(32%) = 1.25us * (29 / 90) = 0.40us, T0L(68%) = 1.25 - 0.40 = 0.85us 
#define TIMING_1    61   // T1H(68%) = 1.25us * (61 / 90) = 0.85us, T1L(32%) = 1.25 - 0.85 = 0.40us void led_display(uint8_t (*led_buf)[3], uint8_t led_num);
void ws2812_init(void);#endif

ws2812_driver.c :

#include "ws2812_driver.h"
#include "string.h"uint16_t pwm_dma_buf[RESET_WORD + RGB_BIT * LED_NUM + DUMMY_WORD];void pwm_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_ResetBits(GPIOA, GPIO_Pin_0);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_TimeBaseStructure.TIM_Period = 90 - 1;     // 72MHz / 90 = 800kHzTIM_TimeBaseStructure.TIM_Prescaler = 0;TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);/* PWM2 Mode configuration: Channel1 */TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 50;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;TIM_OC1Init(TIM2, &TIM_OCInitStructure);TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);// TIM_ARRPreloadConfig(TIM2, ENABLE);/* TIM2 enable counter */TIM_Cmd(TIM2, ENABLE);
}void pwm_dma_init(void)
{/* configure DMA */DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体/* DMA clock enable */RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA时钟(用于SPI的数据传输)memset((uint8_t*)&pwm_dma_buf, 0, sizeof(pwm_dma_buf));/* DMA1 Channel5 Config for PWM2 by TIM2_CH1*/DMA_DeInit(DMA1_Channel5);DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM2_CCR1_Address;	// physical address of Timer 3 CCR1DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&pwm_dma_buf;		// this is the buffer memory DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						// data shifted from memory to peripheralDMA_InitStructure.DMA_BufferSize = sizeof(pwm_dma_buf)/2;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					// automatically increase buffer indexDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							// stop DMA feed after buffer size is reachedDMA_InitStructure.DMA_Priority = DMA_Priority_Medium;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel5, &DMA_InitStructure);/* TIM2 DMA Request enable */TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
}void pwm_dma_send(void)
{DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(pwm_dma_buf)/2); 	// load number of bytes to be transferredDMA_Cmd(DMA1_Channel5, ENABLE); 			// enable DMA channel 5TIM_Cmd(TIM2, ENABLE); 						// enable Timer 2while(!DMA_GetFlagStatus(DMA1_FLAG_TC5)) ; 	// wait until transfer completeDMA_Cmd(DMA1_Channel5, DISABLE); 			// disable DMA channel 5DMA_ClearFlag(DMA1_FLAG_TC5); 				// clear DMA1 Channel 5 transfer complete flagTIM_Cmd(TIM2, DISABLE); 	// disable Timer 2
}void led_display(uint8_t (*led_buf)[3], uint8_t led_num)
{uint8_t i, j;// led_buf -> pwm_dma_buffor(i = 0; i < led_num; i++){// N ledfor(j = 0; j < 8; j++){// 1 color -> 8bit// gpwm_dma_buf[RESET_WORD+RGB_BIT*i+j] = ((led_buf[i][1] << j) & 0x80) ? TIMING_1 : TIMING_0;// rpwm_dma_buf[RESET_WORD+RGB_BIT*i+j+8] = ((led_buf[i][0] << j) & 0x80) ? TIMING_1 : TIMING_0;// bpwm_dma_buf[RESET_WORD+RGB_BIT*i+j+16] = ((led_buf[i][2] << j) & 0x80) ? TIMING_1 : TIMING_0;}}// pwm startpwm_dma_send();
}void ws2812_init(void)
{pwm_init();pwm_dma_init();
}
2.2.2 灯效应用

ws2812_app.h :

#ifndef __WS2812_APP_H
#define __WS2812_APP_H#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "ws2812_driver.h"typedef enum 
{LED_MODE_OFF,LED_MODE_ALL_ON,	LED_MODE_BREATHE,	LED_MODE_GRADIENT,LED_MODE_FLOW,	
}led_mode_t;typedef struct
{led_mode_t mode;  uint8_t g;                uint8_t r;              uint8_t b;              uint8_t brightness;  
}led_t;void led_init(void);
void led_handle(void);#endif

ws2812_app.c :

#include "ws2812_app.h"led_t led;
uint8_t rgb_buf[LED_NUM][3];void led_init(void)
{ws2812_init();led.mode = LED_MODE_ALL_ON;led.r = 0x00;led.g = 0xE0;led.b = 0x80;
}void led_handle(void)
{uint8_t i;switch (led.mode){case LED_MODE_OFF:for (i = 0; i < LED_NUM; i++){rgb_buf[i][0] = 0;  // rrgb_buf[i][1] = 0;  // grgb_buf[i][2] = 0;  // b}break;case LED_MODE_ALL_ON:for (i = 0; i < LED_NUM; i++){rgb_buf[i][0] = led.r;  // rrgb_buf[i][1] = led.g;  // grgb_buf[i][2] = led.b;  // b}break;// ......可以自己加入更多的灯效default:break;}led_display(rgb_buf, LED_NUM);
}

main.c :

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "ws2812_app.h"int main(void)
{SystemInit();delay_init();led_init();while(1){led_handle();delay_ms(5);}
}

2.3 运行测试

2.3.1 时序测试

使用逻辑分析仪抓取信号,得到的结果如下:

  1. 8个LED连续写入RGB值:
    在这里插入图片描述

  2. 编码1高电平(T1H)850ns:
    在这里插入图片描述

  3. 编码1低电平(T1L)400ns:
    在这里插入图片描述

  4. 编码1周期1.25us:
    在这里插入图片描述

  5. 编码0高电平(T0H)400ns:
    在这里插入图片描述

  6. 编码0高电平(T0H)850ns:
    在这里插入图片描述

  7. 编码0周期1.25us:
    在这里插入图片描述

结论:实际输出的波形和理论一致。

2.3.2 实际效果

用在线颜色选取器把代码设置的颜色值输入进去,得到该颜色,然后和实际灯带点亮的颜色比对。

  1. 颜色拾取器显示如下:
    在这里插入图片描述
  2. 实际灯带颜色如下:
    在这里插入图片描述

结论:灯带实际显示的颜色准确无误。

结束语

关于stm32如何使用PWM+DMA驱动WS2812的讲解就到这里,如果还有什么问题,欢迎在评论区留言。

源码下载链接

如果这篇文章能够帮到你,就…你懂的。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/295481.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux_进程的优先级环境变量上下文切换

文章目录 一、进程的优先级二、进程的四个重要概念三、上下文切换四、环境变量4.1 查看当前shell环境下的环境变量与内容 一、进程的优先级 什么是优先级&#xff1f; 指定一个进程获取某种资源的先后顺序本质是进程获取cpu资源的优先顺序 为什么要有优先级 进程访问的资源&am…

【Entity Framework】EF日志-简单日志记录

【Entity Framework】EF日志-简单日志记录 文章目录 【Entity Framework】EF日志-简单日志记录一、概述二、EF日志分类三、简单的日志记录3.1 配置3.2 日志记录到控制台3.3 记录到凋试窗口3.4 记录到文件3.5 敏感数据处理3.6 详细查询异常3.6 日志级别3.7 消息内容和格式设置 一…

基于Scala开发Spark ML的ALS推荐模型实战

推荐系统&#xff0c;广泛应用到电商&#xff0c;营销行业。本文通过Scala&#xff0c;开发Spark ML的ALS算法训练推荐模型&#xff0c;用于电影评分预测推荐。 算法简介 ALS算法是Spark ML中实现协同过滤的矩阵分解方法。 ALS&#xff0c;即交替最小二乘法&#xff08;Alte…

Java SpringBoot中优雅地判断一个对象是否为空

在Java中&#xff0c;可以使用以下方法优雅地判断一个对象是否为空&#xff1a; 使用Objects.isNull()方法判断对象是否为空&#xff1a; import java.util.Objects;if (Objects.isNull(obj)) {// obj为空的处理逻辑 }使用Optional类优雅地处理可能为空的对象&#xff1a; impo…

Centos8/linux/虚拟机安装docker

docker分为ce版和ee版&#xff0c;个人使用ce版就行了&#xff0c;别问为什么&#xff0c;问就是ee版收费。 这是在线版的&#xff0c;离线版的请参考Centos8离线下载安装docker 1.首先切换到root用户 2.为确保安装时出现不必要的问题&#xff0c;先更新一下yum包 sudo yum…

Mac上Matlab_R2023b ARM 版 启动闪退(意外退出)解决方法

安装好后&#xff0c;使用 "libmwlmgrimpl.dylib" 文件替换掉"/Applications/Matlab_R2023b.app/bin/maca64/matlab_startup_plugins/lmgrimpl"文件夹下的同名文件 在终端下执行如下命令&#xff1a; codesign --verbose --force --deep -s - /Applicat…

大数据学习第十二天(hadoop概念)

1、服务器之间数据文件传递 1&#xff09;服务器之间传递数据&#xff0c;依赖ssh协议 2&#xff09;http协议是web网站之间的通讯协议&#xff0c;用户可已通过http网址访问到对应网站数据 3&#xff09;ssh协议是服务器之间&#xff0c;或windos和服务器之间传递的数据的协议…

golang和Java的简单介绍和对比

一、golang 1、Golang简介 Golang&#xff0c;也称为Go&#xff0c;是由Google公司在2009年推出的开源编程语言&#xff0c;由罗伯特格瑞史莫(Rob Pike)、肯汤普逊(Ken Thompson)、罗勃派克(Robert Griesemer)等人设计。Go语言的目标是在保持简单高效的编程模型的同时&#xf…

Linux系统----------探索mysql数据库MHA高可用

目录 一、MHA概述 1.1 什么是 MHA 1.2MHA 的组成 1.2.1MHA Node&#xff08;数据节点&#xff09; 1.2.2MHA Manager&#xff08;管理节点&#xff09; 1.3MHA 的特点 1.4MHA工作原理 1.5数据同步的方式 1.5.1同步复制 1.5.2异步复制 1.5.3半同步复制 二、搭建 MySQ…

ios 之 netty版本swiftNio(TLSHandler 创建)

SwiftNio 简介 用于高性能协议服务器和客户端的事件驱动、无阻塞的网络应用程序框架。 SwiftNIO是一个跨平台异步事件驱动的网络应用程序框架&#xff0c;用于快速开发可维护的高性能协议服务器和客户端。 这就像Netty&#xff0c;但是为Swift写的。 Xcode引入swiftNio 在实…

蓝桥杯第八届c++大学B组详解

目录 1.购物单 2.等差素数列 3.承压计算 4.方格分割 5.日期问题 6.包子凑数 7.全球变暖 8.k倍区间 1.购物单 题目解析&#xff1a;就是将折扣字符串转化为数字&#xff0c;进行相加求和。 #include<iostream> #include<string> #include<cmath> usin…

git源码泄露

Git 源码泄露 开发人员会使用 git 进行版本控制&#xff0c;对站点自动部署。但如果配置不当&#xff0c;可能会将 .git 文件夹直接部署到线上环境&#xff0c;这就引起了 git 泄露漏洞&#xff0c;我们可以利用这个漏洞直接获得网页源码。 确定是否存在泄漏 &#xff08;1&…

自动驾驶的世界模型:综述

自动驾驶的世界模型&#xff1a;综述 附赠自动驾驶学习资料和量产经验&#xff1a;链接 24年3月澳门大学和夏威夷大学的论文“World Models for Autonomous Driving: An Initial Survey”。 在快速发展的自动驾驶领域&#xff0c;准确预测未来事件并评估其影响的能力对安全性…

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…

从零开始,构建智慧企业:人事管理软件新升级全攻略

本文从智能化人事管理的六大核心要素探讨如何打造一个适应现代企业需求的智能化人事管理系统&#xff0c;并介绍几款市场上表现优秀的人事管理软件。 随着我国经济的发展&#xff0c;企业全球化是大势所趋&#xff0c;难免会出现跨国员工数量增加、办公地点分散、跨部门协作等…

重学SpringBoot3-SpringBoot可执行JAR的原因

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-SpringBoot可执行JAR的原因 Spring Boot可执行JAR的结构打包运行JAR 包内部结构 工作原理优点总结 Spring Boot 的一个核心特性是它的可执行 JAR&#x…

Canvas实现数字电子时钟(带粒子掉落效果)

前置知识 Canvas实现简易数字电子时钟 效果 逻辑代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>粒子时钟</title><style>body {margin: 0;overflow: hidden}</style> </…

Redis开源协议调整,我们怎么办?

2024年3月20日, Redis官方宣布&#xff0c;从 Redis 7.4版本开始&#xff0c;Redis将获得源可用许可证 ( RSALv2 ) 和服务器端公共许可证 ( SSPLv1 ) 的双重许可&#xff0c;时间点恰逢刚刚完成最新一轮融资&#xff0c;宣布的时机耐人寻味。 Redis协议调整&#xff0c;对云计算…

Nginx三大常用功能“反向代理,负载均衡,动静分离”

注意&#xff1a;以下案例在Windows系统计算机作为宿主机&#xff0c;Linux CentOS 作为虚拟机的环境中实现 一&#xff0c;Nginx配置实例-反向代理 1.反向代理 案例一 实现效果&#xff1a;使用nginx反向代理&#xff0c;访问 www.123.com 直接跳转到127.0.0.1:8080 准备工…

Golang Context是什么

一、这篇文章我们简要讨论Golang的Context有什么用 1、首先说一下Context的基本作用&#xff0c;然后在讨论他的实现 (1)数据传递&#xff0c;子Context只能看到自己的和父Context的数据&#xff0c;子Context是不能看到孙Context添加的数据。 (2)父子协程的协同&#xff0c;比…