本文开发环境:

  • MCU型号:STM32F103C8T6
  • IDE环境: MDK 5.27
  • 代码生成工具:STM32CubeMx 5.6.1
  • HAL库版本:STM32Cube_FW_F1_V1.8.0

本文内容:

  • STM32 使用 DMA+PWM 方式驱动 ws2812(x4)

附件:

  • MDK5 示例工程

一、DMA 的双缓存模式

STM32 系列的 MCU DMA 可以搬运2个源地址的数据,DMA 自动在2个地址A和B中来回切换,可以运用于搬运大数据:当DMA在搬运A数据时,MCU把新数据准备到B中,当DMA在搬运B数据时,MCU把新的数据准备到A中,通过不断的来回错开操作,可以搬运大的数据。
本文使用另一种方式:在DMA传输过程中,不只传输完成时,会产生一个传输完成(TC)中断,在完成一半的时候,也会产生一个半完成传输(HC)中断。

这种方式虽然只有一个源地址,但其效果是一至的。

二、DMA 双缓存在 ws2812 中的应用

1. 框图:

上文(ws2812 程序设计与应用(1))的原理是把所有 ws2812 数据 填充到数组中:

每增加一颗 ws2812 ,[DATA] 的体积就会增加(24x2)字节,在灯珠较多的情况下,会有比较大的内存损耗。
利用DMA的半完成传输机制,无论灯组多少,[DATA] 的数据永远是2个 ws2812 的大小,即(24x2x2)96个字节,同时,它需要建立一个数组,用来保存灯珠的颜色信息:

2. 基础流程


在 DMA 双缓存模式中,程序通过set2812_RGB_set()函数来对灯的颜色进行设置,这些值首先存放在pixel结构体中,由于有多个灯,所以需要一个结构体数组,接着需要一个数据填充函数ws2812_pixel_data_fill(),它可以把原始的数据转换成PWM占空比的值,存在DAM缓存中,最后通过HAL库函数,HAL_TIM_PWM_Start()函数,启动 DMA 传输,就完成了ws2812的驱动。

3. WS2812 时序与 DMA传输

1. ws2812 数据传输


DMA 被设置为循环传输模式,所以会不断的传输[pixel data]这48个字节:

  • MCU 准备好 pixel1 和 pixel2 的数据,启动DMA传输
  • DMA 发送半传输中断,此时DMA开始传输[pixel data]后半部分数据,MCU 将 pixel3 的数据写入到[pixel data + 0]([pixel data + 24]前半部分)中。
  • DMA 发生传输完成中断,此时DMA开始传输[pixel data]前半部分数据,MCU 将 pixel4 的数据写入到[pixel data + 24]([pixel data + 24]后半部分)中。

MCU 是通过 ws2812_pixel_data_fill() 来写入的,该函数的数据是 struct pixel 提供的,而pixel的数据是 ws2812_RGBset() 设置的。

2. 复位信号

对于 ws2812 ,在一帧数据传输之前,需要一个不低于50us的低电平,作为Reset信号,接收到Reset信号以后,前24个PWM就成了第一个灯组的颜色数据,为了程序的统一,本文改进了流程:

在上图中,首先将 [pixel data]的值设置为零,当DMA传输的时候,首先会传输48个0,即产生了为时长约为60us(1.25us x 48) 的低电平,这个信号可以作为Reset信号

3. 更规范的时序

对于 MCU 来说,它响应DMA的中断有一定的滞后性,比如,当DMA发送半传输中断时,内核首先响应这个中断,然后通过HAL库处理一些标志位,最后才调用到回调函数去执行用户程序,这些都需要时间,当用户程序被执行时候,DMA已经传输一些波形了,这就导致一个现象,ws8212 接收到的 PWM 是多出几个波形的,这会导致一个非常奇怪的bug:最后一个灯的最后一个数据,不能是 0x03、0x07、0x1F 等这些bit1为1的值。处理这个问题是简单的,只需要让它多传输的PWM,占空比为0即可,所以需要进一步改进流程(以4灯为例):

虽然滞后性无法避免的,但是通过这种方法,可以让多出来的波形占空比为0,其实就是低电平,所以不影响后续数据的传输。

三、DMA 程序设计与实现

STM32CubeMx 配置

CubeMx 的配置与上文(ws2812 程序设计与应用(1))基本一致,只需要将占空比默认值59改为0,避免首次运行颜色错乱的现象:

这是因为如果不改为0,DMA首次运行的时候,会多产生一个1码(DMA需要定时器溢出作为触发条件,第一次溢出会产生一个PWM,然后触发第一次DMA搬运),接着是一个复位信号,才是灯的颜色数据,根据测试效果,虽然是这个1码在复位信号之前,却依然会影响到灯的显示效果。

程序设计(4灯)

新建一个 ws2812.c 文件:

#include "main.h"
#include "string.h"
#include "stdio.h"extern TIM_HandleTypeDef htim1;                           //声明 htim1 handle
extern DMA_HandleTypeDef hdma_tim1_ch1;                   //声明 hdma_tim1_ch1 handle#define PULSE_1_CODE        (59)                           //1 码计数个数
#define PULSE_0_CODE        (29)                           //0 码计数个数
#define PIXEL_NUM            (4)                           //led 个数
#define PIXEL_DATA_LEN      (24)// ws2812 结构体数组
typedef struct{uint8_t r;uint8_t g;uint8_t b;
}piexel_t;
piexel_t pixel[PIXEL_NUM];// DMA 传输的数据
uint16_t ws2812_DMA_data[PIXEL_DATA_LEN * 2] = {0};/************************ 函数声明 ******************************/
void ws2812_RGB_set(uint8_t R,uint8_t G,uint8_t B,uint8_t pixel_id);                      //设置颜色
uint8_t ws2812_pixel_data_fill(uint8_t pixel_next,uint16_t *addr);                        //传输数据/************************ 函数定义 ******************************/

ws2812_RGB_set()函数需要对参数进行检查,如果传入的值不对,不做处理,避免数组越界:

void ws2812_RGB_set(uint8_t R,uint8_t G,uint8_t B,uint8_t led_id)
{if(led_id >= (PIXEL_NUM))return;pixel[led_id].r = R;pixel[led_id].g = G;pixel[led_id].b = B;return;
}

ws2812_pixel_data_fill()同样需要对参数,避免访问越界数组

uint8_t ws2812_pixel_data_fill(uint8_t pixel_next,uint16_t *addr)
{if(pixel_next >= PIXEL_NUM){return 0;}for (uint8_t i = 0;i < 8;i++){//填充数组addr[i]      = (pixel[pixel_next].g << i) & (0x80)?PULSE_1_CODE:PULSE_0_CODE;addr[i + 8]  = (pixel[pixel_next].r << i) & (0x80)?PULSE_1_CODE:PULSE_0_CODE;addr[i + 16] = (pixel[pixel_next].b << i) & (0x80)?PULSE_1_CODE:PULSE_0_CODE;}
}

回调函数本文实现的比较简陋,请根据实际程序改进。它是根据DMA中断的次数,推算需要将第几个ws2812的数值,写入到[pixel data],传输完成灯光颜色数据后,使用memset(),清零数组,让其传输低电平:

volatile int cnt = 0;
//TC 回调函数
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{ cnt++;if(cnt == 2){ws2812_pixel_data_fill(1,ws2812_DMA_data+24);}if(cnt == 4){ws2812_pixel_data_fill(3,ws2812_DMA_data+24);}if(cnt == 6){memset(ws2812_DMA_data+24,0x00,48);}if(cnt == 8){cnt = 0; HAL_TIM_PWM_Stop_DMA(&htim1,TIM_CHANNEL_1);//__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,0);}
}
//HC 回调函数
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim)
{   cnt++;if(cnt == 1){ws2812_pixel_data_fill(0,ws2812_DMA_data);}if(cnt == 3){ws2812_pixel_data_fill(2,ws2812_DMA_data);}if(cnt == 5){memset(ws2812_DMA_data,0x00,48);}
}

__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,0) 是非必须的,因为最后清零数组,占空比肯定为 0。

最后一个API测试效果:

void ws2812_example_2(void)
{uint8_t static bri1 = 0x0F;//memset(ws2812_DMA_data,0x00,96);ws2812_RGB_set(bri1, bri1, bri1, 0);ws2812_RGB_set(bri1, 0x00, 0x00, 1);ws2812_RGB_set(0x00, bri1, 0x00, 2);ws2812_RGB_set(0x00, 0x00, bri1, 3);HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t *)ws2812_DMA_data,(48));HAL_Delay(100);
}

memset(ws2812_DMA_data,0x00,96); 是非必须的,其目的清零数组,使其可产生复位信号,但是这个数组在最后数据传输阶段,为了避免bug,已经被清零了。

测试

直接在 main.c 调用示例函数即可,注意用 extern 声明外部函数:

//file main.c:
... ...
extern void ws2812_example_2(void);void main(void)
{... ...while(1){ws2812_example_2();}
}

可以看到依次亮了:白,红,绿,蓝灯。

四、附件

链接:ws2812 DMA 双缓存
提取码:1234

ws2812 程序设计与应用(2)DMA 控制 PWM 占空比(双缓存降低内存消耗)相关推荐

  1. STM32F411RE Nucleo笔记-按键控制PWM占空比

    STM32F411RE Nucleo笔记-按键控制PWM占空比 此次用到STM32F411RENucleo开发板,用到Keil MDK5.12和STM32CubeMx软件. 首先用STM32CubeM ...

  2. STM32F103C8T6制作舵机测试仪详细图文教程 | 定时器触发ADC | DMA传输 | PWM输出 | RTC实时时钟 | USART串口输出 | OLED IIC显示

    自主学习STM32已有一周,先实现一个小demo,算是给自己一个动力叭,有目标的学习收获会更多.虽然本科也修了嵌入式课程,但那种走马观花式的学习,最后真正得到的知识实在寥寥无几.个人理解,学习STM3 ...

  3. WS2812灯珠(三)-- STM32 PWM+DMA方式驱动

    WS2812灯珠(三)-- STM32 PWM+DMA方式驱动 文章目录 WS2812灯珠(三)-- STM32 PWM+DMA方式驱动 一.理论 二.代码实践 一.理论 PWM输出就是对外输出脉宽( ...

  4. STM32F427库函数PWM+DMA控制ws2812b灯带

    STM32F427IIHx库函数PWM+DMA控制ws2812b灯带 一.参考资料 查看ws2812b用户手册可知: 二.代码部分 添加文件ws2812b.c,ws2812b.h 配置的F427IIH ...

  5. 单片机C语言PWM程序原理,单片机C语言程序设计:用 ADC0808 控制 PWM 输出

    /* 名称:用 ADC0808 控制 PWM 输出 说明:使用数模转换芯片 ADC0808,通过调节可变电阻 RV1 来调节脉冲宽度, 运行程序时,通过虚拟示波器观察占空比的变化. */ #inclu ...

  6. 精准控制PWM脉冲的频率和数量

    在一些项目中,我们经常要控制PWM脉冲的频率和数量,比如步进电机的控制等,下面分享一个程序是关于这方面的,程序的思想就是通过STM32的定时器来输出PWM波,并开启定时器中断,在中断里面计数脉冲的数量 ...

  7. SPI驱动0.96/1.3寸 OLED屏幕,易修改为DMA控制

    目录 OLED SPI 端口定义 七针OLED引脚定义 六针OLED引脚定义 驱动程序 oled.c oled.h oledfont.h 使用 main.c 实验现象 STC实验箱4 IAP15W4K ...

  8. python控制gpio产生固定数量的脉冲_STM32L151用dma控制GPIO口发出指定的脉冲个数的疑惑!...

    本帖最后由 mon51 于 2015-3-6 16:41 编辑 用DMA控制GPIO的一个IO脚,输出指定脉冲的个数项目,由于要低功耗!MCU主频不能高.采用的定时器联机,还是达不到输出150KHZ的 ...

  9. 【STM32】CubeMX+HAL库之 硬件IIC+DMA控制OLED(兼容SSD1306SH1106驱动)

    [STM32]CubeMX+HAL库之 硬件IIC+DMA控制1.3寸OLED 前言 目前网上大多数驱动OLED屏都采用软件IIC,因为HAL库的升级使得硬件IIC的稳定性得到了保障,所以想采用硬件I ...

最新文章

  1. linux编译安装jpeg,Linux下JPEG库安装脚本(转)
  2. Linux虚拟机创建后如何进行登录(Windows Azure)
  3. 「NLP-语义匹配」详解深度语义匹配模型DSSM
  4. Gson解析JSON数据中动态未知字段key的方法
  5. 《Web前端开发精品课 HTML与CSS进阶教程》——1.4 id和class
  6. 在cmd环境下操作Oracle11g数据库
  7. MMC检测到此管理单元发生一个错误。建议关闭并重新启动MMC
  8. linux apr文件解压失败,Apache编译安装提示configure: error: APR not found错误解决方法...
  9. 程序员面试金典 - 面试题 16.24. 数对和(双指针/哈希map)
  10. swift怎么调用Java,Swift完成UIAlertController的调用
  11. 前端开发 表单控件高级 0303
  12. 怎么用deveco studio升级鸿蒙,华为鸿蒙DevEco studio2.0的安装和hello world运行教程
  13. BUPT 2012复试机考 2T
  14. 学术分享 | 没有导师的指导,研究生如何阅读文献、提出创见、写论文?
  15. 精选|2018年6月R新包推荐
  16. 会计信息系统复习资料
  17. 手机话费充值页面HTMLcss3+html5模板
  18. 聊天的技巧,你是怎样和别人聊天的
  19. 如何将Jenkins基础环境迁移到Docker?
  20. 前端代码为什么会有低代码及无代码

热门文章

  1. linux 查看 操作系统位数
  2. word设置页码不在第一页开始;删除页眉横线的小技巧
  3. el-table表格横竖双表头,表头带斜线
  4. html制作过程总结经验,网页基础制作教程:学习HTML经验总结
  5. web的标准网页设计与php课后,web网页设计尺寸规范
  6. 【机器智能】双十一奇迹背后:机器智能如何构建社会的全新技术设施?
  7. ZZ,春晚零点报时出错揭密
  8. 巨型计算机卡通,动漫史上十大超巨型机体
  9. 2020形式化方法复习笔记
  10. 【兴趣书签】为什么观测之后量子态会坍塌