首页 > 其他分享 >STM32定时器驱动WS2812

STM32定时器驱动WS2812

时间:2023-12-30 20:24:24浏览次数:45  
标签:定时器 TIM4 灯珠 STM32 TIM 寄存器 WS2812 数据

最近在学STM32F103的定时器的标准库驱动,在学到定时器的比较输出功能时发现它可以和DMA配合一起使用产生一连串占空比各不同的PWM波,于是我立刻想到用这个东西来驱动WS2812,手边正好有一串30颗灯珠的WS2812灯带。

WS2812的通信协议

  • 数据格式

WS2812是一种采用单线通信方式的全彩灯珠,它只需要一根线就可以与控制器进行通信。它内置R、G、B三种颜色的光源,每种颜色通过一个字节的数据进行控制,所以每颗灯珠都需要3个字节的数据来控制颜色。数据是通过它的DIN引脚输入,下图是它的数据传输格式:

image

可以看到,它的数据格式是高位在前。并且值得注意的是,虽然我们习惯叫它“RGB灯”,但它的通信格式其实是“GRB”,也就是“G”的数据在第一字节,后面才是“R”和“B”的数据,这对于我们写驱动非常重要。

  • 级联方式

WS2812还支持级联,就是将前面一颗灯珠的DOUT接到后一颗的DIN引脚,这样就可以通过第一颗灯珠的DIN引脚控制这一整串灯条。

image

例如:你要控制的灯带上有3颗灯珠,那么你就要通过第一颗灯珠的DIN引脚发送9字节的数据(每个灯珠需要3字节的数据),当第1颗灯珠接收到这一连串的数据后它会保存首3个字节的数据用作控制它的颜色,然后将剩下6个字节的数据通过它的DOUT引脚转发出去,同样地,第2颗灯珠会将这6字节数据的前3字节保存用作控制自己的颜色,然后将剩下3字节的数据通过DOUT转发给下一颗灯珠...

事实上,当你通过第一颗灯珠的DIN引脚将9字节的数据全部发送出去之后,灯珠并不会立刻将颜色刷新到当前的显示上,因为它还需要一个RESET信号,这个RESET信号是通过将信号线置于低电平保持至少80us的时间表示的。也就是说当你把数据全部发送出去之后需要立刻将信号线拉低至少80us之后灯串才会“响应”你的这些数据。如果你是通过PWM进行控制的话就要格外注意这一点。例如如果你通过PWM将数据发送出去后发现灯的颜色并没有任何变化,可能并不是你的周期和占空比有问题,有可能仅仅是你的PWM波在数据发送结束后还在不停变化着,解决方法就是在数据发送完成后再将PWM的占空比调整为0。

  • 编码方式

下图是它的数据编码方式,可以看到bit1码是通过高电平0.195us+低电平0.595us的方式进行表示,而bit0码刚好相反,实际操作中为了便于计算,我们可以取0.3us和0.6us。最后就是RESET码是通过至少80us的低电平表示的。
image

定时器的配置

  • 配置时基单元

我使用通用定时器TIM4的通道1配合DMA来输出PWM波。首先要做的就是配置定时器的频率。芯片的工作频率是72MHz,经过计算我选择将PSC预分频器的分频系数设置为(12-1),计数器的自动重装载寄存器的值设置为(6-1),经过这样的分频最终PWM的输出频率就是72MHz/12/6 = 1MHz,周期刚好是1us,精度值为1/6 us约为0.16us。

TIM_PrescalerConfig(TIM4, 12-1, TIM_PSCReloadMode_Immediate);//设置预分频器
TIM_CounterModeConfig(TIM4, TIM_CounterMode_Up);//计数模式
TIM_SetAutoreload(TIM4, 6-1);//自动重装载值
TIM_SetClockDivision(TIM4, TIM_CKD_DIV1);//时钟滤波器值

经过这样设置,当要输出bit1时可以设置捕获比较寄存器CCR1的值为4,这样一个PWM波就是高电平0.64us+低电平0.36us;而要输出bit0时可以设置捕获比较寄存器CCR1的值为2,这样一个PWM波就是高电平0.32us+低电平0.68us。当然,我们采用的是DMA的方式,所以后面不需要自己去调用TIM_SetCompare1()手动填写获比较寄存器CCR,而是通过DMA自动填写它的值。时基单元配置好后接下来就是要配置输出通道和DMA了。

  • 配置输出通道

TIM_SetCompare1(TIM4, 0);//设置比较值
TIM_OC1PolarityConfig(TIM4, TIM_OCPolarity_High);//设置输出极性
TIM_SelectOCxM(TIM4, TIM_Channel_1, TIM_OCMode_PWM1);//设置输出通道模式
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);//使能CCR寄存器的影子寄存器
TIM_CCxCmd(TIM4, TIM_Channel_1, TIM_CCx_Enable);//使能输出通道

一开始我并没有特地去调用函数TIM_OC1PreloadConfig()使能CCR寄存器的影子寄存器,但后面测试的时候发现一个很大的问题,当我往灯珠发送数据RGB(255,0,0)时,灯珠能够显示红色,但发送数据RGB(128,0,0)时灯珠却不显示任何颜色,于是我拿出示波器开始观察波形。

image

这是在发送RGB(255,0,0)时从示波器观察到的数据,可以看到G数据的高7位数据无论是周期还是占空比都很正常,周期都是1us,高电平的时间都在0.3us左右。但看到后面就有点奇怪了,G数据的bit0和R数据的bit7似乎有些“纠缠不清”了,正常情况下G数据的bit0的PWM波周期应该和前面的一样都占满一整个格子的宽度(1us),但现在却是和R数据的bit7一起”挤”在同一个格子里面,这就让我有些诧异了。

这也正和前面灯珠颜色显示异常对上了,当写入RGB(255,0,0)时对应发送的二进制数据是“00000000’11111111’00000000”(别忘记灯的实际通信格式是GRB),从上面示波器的数据可以看到R数据的bit7周期是异常的,这会让WS2812无法正常识别出bit7的数据,但后面低7位的数据的周期是正常的,会被正常识别出来,这就会导致原本R=255但WS2812识别成了R=127,于是尽管数据识别出错但好歹还是能让红色的灯亮起来,顶多亮度不对罢了。但如果写入的数据是RGB(128,0,0)时,情况就有些麻烦了。RGB(128,0,0)对应的二进制数据是“00000000’10000000’00000000”,可以看出来,同样是把bit7丢掉,对于前面的数据顶多是亮度不对,但对于这串数据而言将是致命的破环,此时WS2812将得到一连串的“0”,于是将没有任何一种颜色的灯会被点亮。

从上面的分析我得到一个信息,那就是现在的PWM波在改变占空比的时候周期会短暂变短。一开始我怀疑是不是在写CCR寄存器时预分频寄存器的值也会被改变(毕竟提到周期有问题肯定第一时间想到预分频寄存器嘛),但经过我的一系列排查最终打消了我这个想法。于是我又仔细捋了一下我所掌握的信息后怀疑是影子寄存器的问题,于是我立刻打开数据手册查看:
image
image

我发现CCR寄存器的影子寄存器默认是不使能的,这也就意味写入CCR寄存器的数据会被立刻刷新进去,而不是等待此周期结束后的更新事件到来时再刷新。于是我在使能输出通道的语句
TIM_CCxCmd(TIM4, TIM_Channel_1, TIM_CCx_Enable);
前面加了一句
TIM_OC1PreloadConfig(TIM4,TIM_OCPreload_Enable);
重新编译下载后问题立刻得到解决。

  • 配置DMA

写配置DMA的代码的过程没有这么坎坷,我就不贴出来了,有需要可以去看我上传的源代码。

源代码结构

  • 函数原型

void WS2812_Init(void); //初始化相关外设
void WS2812_SetQuantity(uint16_t quantity); //设置控制的灯数
uint8_t WS2812_FullRGB(uint16_t ln, uint8_t r, uint8_t g, uint8_t b); //往第ln颗灯填充rgb数据
uint8_t WS2812_AreaFullRGB(uint16_t lnBegin, uint16_t lnEnd, uint8_t r, uint8_t g, uint8_t b); //对第lnBegin到第lnEnd颗灯填充rgb数据
uint8_t WS2812_FullHSV(uint16_t ln, double h, double s, double v); //往第ln颗灯填充hsv数据
uint8_t WS2812_AreaFullHSV(uint16_t lnBegin, uint16_t lnEnd, double h, double s, double v); //对第lnBegin到第lnEnd颗灯填充hsv数据
uint8_t WS2812_Refresh(void); //将填充完的数据刷新到灯带中去

  • 使用方法

  1. 先调用WS2812_SetQuantity()函数设置要控制的灯的数量。
  2. 再根据需要调用WS2812_FullRGB()WS2812_FullHSV()WS2812_AreaFullRGB()WS2812_AreaFullHSV()对缓存区进行数据填充。
  3. 最后调用WS2812_Refresh()函数将数据发送到WS2812中。
  • 说明

  1. WS2812.h文件中有一个宏“MAX_QUANTITY_GRAIN”是用来定义级联的WS2812数量的。
  2. 本库函数中是用TIM4的通道1配合DMA来实现控制的,如果你要换成别的通道或者是定时器的话也要记得根据下表把DMA相关的函数也重新更改一下。
    image

源代码获取

WS2812库函数

https://files.cnblogs.com/files/blogs/814108/ws2812.zip?t=1703925215&download=true

RGB与HSV互相转换库函数

https://files.cnblogs.com/files/blogs/814108/RGBHSV.zip?t=1703925215&download=true

标签:定时器,TIM4,灯珠,STM32,TIM,寄存器,WS2812,数据
From: https://www.cnblogs.com/ueng/p/17936533.html

相关文章

  • 通过keil内置标准库创建stm32工程
    通过keil内置标准库创建stm32工程.mdkeil如果安装的有对应的标准库的话是可以不通过使用模板工程进行文件创建的。具体操作如下:1.跟51一样选择对应芯片型号,创建工程即可。2.注意!!!这个窗口就是标准库的配置窗口,勾选上便可在工程内使用标准库。3.一个基本的标准库工程至少应......
  • STM32实战之IAP代码升级
    1IAP介绍  IAP(InApplicationProgramming)即在应用编程,IAP是用户自己的程序在运行过程中对UserFlash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信接口对产品中的固件程序进行更新升级。通常实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设......
  • 基于stm32f103c8t6蓝牙连接模块hc-05
    一、蓝牙通信流程最简单实际的的蓝牙通信过程就是单片机——蓝牙——手机蓝牙app蓝牙作为桥梁进行单片机与手机数据的交换蓝牙就如同一个无线的USART一样,将两者连接。二、硬件资料1.管教图 连接图: 实物:连接图    三、软件资料1.电脑蓝牙调试软件网址广州汇承......
  • 定时器原理及使用
    一、引入在进行并发编程时,有时候会需要定时功能,比如监控某个GO程是否会运行过长时间、定时打印日志等等。GO标准库中的定时器主要有两种:Timer定时器、Ticker定时器。Timer计时器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker计时器会一直生效。二、Timer定时器1)实现原......
  • 线程安全&&定时器总结
    总结线程线程:执行的独立代码线程执行是靠cpu分配时间片,同一个时间片上只能执行一个线程线程的状态:新建就绪运行阻塞死亡Thread:多线程的类currentThread()getName(),setName()sleep()实现多线程的方式1.继承Thread,重写run2.实现Runnable,重写run实现线程安全......
  • stm32u5 qspi 读写 w25q128 timeout
    http://ramlife.me/posts/solution/embedded/spi/stm32-use-qspi-write-and-read-w25q128-timeout/背景使用STM32U575主控芯片,使用QSPI读写W25Q128,简单的读写测试没有问题。但是在后面调试中发现,当按照11个字节一组进行读写,从4352这个地址开始写,写入到4605的时候,就超......
  • 002-STM32F103+EC800K(移远4G Cat1)基本控制篇(阿里云物联网平台)--STM32+EC800K使用M
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ZLIOTB/EC800K/aliyun.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p> 说明阿里......
  • 14-STM32F103+ESP8266+EC800K(移远4G Cat1)--STM32+EC800K以SSL单向认证方式连接MQTT
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ZLIOTB/EC800K/my.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p>  说明安装的M......
  • 1-STM32F103+ESP8266+EC800K(移远4G Cat1)--硬件使用说明
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/ZLIOTB/EC800K/my.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p> 实物图  ......
  • [BOM]定时器
    定时调用 setInterval//开启定时器:第一个参数是回调函数(必须),第二个参数是毫秒//返回值表示是这个页面的第几个定时器(从1开始数)vartimer=setInterval(function(){console.log('一秒!');},1000);console.log(timer);//异步语句......