【TOLIN】第六章|移植WS2812FX库(上)

2024-04-13 00:32

本文主要是介绍【TOLIN】第六章|移植WS2812FX库(上),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【TOLIN】第六章|移植WS2812FX库(上)

        ↑ 点击上方,关注“Tkwer望远镜”

作者:Tkwer

公众号:Tkwer望远镜

 

WS2812B彩灯介绍

    在上个推送中我们使用PWM+DMA驱动WS2812B。并且成功移植了Adafruit_NeoPixel库。最近在github上关注了WS2812FX库,这个库的实现函数要比Adafruit_NeoPixel库丰富的多。

    很可惜,这个库是基于Arduino和ESP8266、ESP32的,是用C++写的,移植到我们的TOIN核心板上,需要很大的工作量。不过有这个想法,肯定不止我一个!所以:

    Thank lamik !这个是他基于STM32F103写的,我们只需要配置底层代码,即配置SPI+DMA,就能在我们的TOIN核心板跑啦。由于WS2812B发送一位有时钟要求,所以我们先回顾一下它的时序图吧。

    数据线低电平保持时间大于50us时,为复位信号。复位后,每个LED读取“DIN”线上开始的24bit(绿:红:蓝为8:8:8)数据到驱动芯片内部缓存。除了开始的24bit数据,后面的数据都通过“DOUT”脚传递到下一个LED,即每经过一个像素点的传输,信号减少24bit。内部缓存数据在下一个复位脉冲后被写入PWM控制器。一个bit为1.25us±0.15us,一个LED有3*8bits=24bits,传输完大概需要24*1.25us=30us。(注:在spi中,我们用spi一个字节(8位)来表示这里的一"bit",0 code :11000000 1 code :11111000)

所以在ws2812_spi.c文件中做以下定义了:

1//  ___         
2// |   |_____|   11000000  low level
3
4//  _____   
5// |     |___|   11111000  high level
6#define zero 192U  
7#define one 248U

 

 

使用STM32CubeMX生成工程模板

   

    本例程我们需要用到SPI和DMA,所以我们在CuBeMX中添加配置一下。

    首先选中SPI--->Mode 选择Transmit only master--->配置 Parameter Settings 

    注意这里的圈出来的时钟范围一定要满足WS2812时钟要求,如果不满足我们需要进行时钟树配置,SPI1是挂在APB2 Peripheral clocks(MHz)时钟上的.(ps:1/1.25us/8 = 6.4MHz,输入误差公式,时钟范围可以是6.4±0.66MHz。6.4MHz/8=800khz,用SPI一个字节传输代表LED的一个“bit”)

   DMA配置,一定是Memory to Peripheral 且Mode是circular。作者是通过circular 这个模式节省很多内存资源,稍后再介绍。

 

    或许有的同学在第一步就有这个困惑,为啥我配置的SPI1口在的是PB3,PB5,而自己配置图显示的是PA5,PA7,这个不是很大问题。cubeMX默认是SPI1映射到PA5,PA7,但是我们可以通过选pin脚让它映射到PB3,PB5。

以上就算配置好需要的SPI驱动了,下面我来回答几个问题:

  • 为什么要用DMA?

    WS2812灯用作一些动态刷新的时候需要传输大量数据,如果不使用DMA,可能我们在用中断的时候破坏了传输数据。这个库也确切用到了SysTick计时器的中断。

 

  • 为什么DMA Mode是Circular 而不是Normal?

    从内存方面思考,每个LED消耗24字节,100颗就消耗2.4KB内存(这很不利于我们做Bad apple!),但是我们利用DMA的HAL_SPI_TxHalfCpltCallback回调函数,可以先传送一半存储区的内容,一半用于装载数据,注意装载数据所需时间应该是小于传送出去的时间的。circular就可以实现两个半缓存区交替。缓存区大小不受灯数量影响!

    从时间层面,通过DMA发送一半缓冲区(24字节)所需的时间为31 µs。 接下来的24个字节的数据准备仅花费MCU 7 µs。一个LED可以为CPU节省26 µs的时间,这可以做其他事情。如果有100个LED,则为2.6毫秒,而有1000个LED,则CPU需要26毫秒的时间。

 

数据缓冲区将缩短为48个字节,即它将适合两个二极管的数据。它将很好地包装。操作图如下:

  1. 加载2 * 24字节的复位信号并开始循环DMA传输。

  2. 半传输触发器-将另外24个字节加载到前半缓冲区中。

  3. 完全传输的触发器-第一个LED的数据到缓冲器的后半部分。

  4. 半传输触发-第二个(偶数)LED的数据传输到缓冲器的前半部分。

  5. 完全传输的触发器-第三个(奇数)二LED的数据到缓冲器的后半部分。

  6. 重复4和5,直到所有LED均已发送。

  7. 完成。

 

 

 1void WS2812B_Refresh()

 2{
 3  CurrentLed = 0;
 4  ResetSignal = 0;
 5
 6  for(uint8_t i = 0; i < 48; i++)
 7    buffer[i] = 0x00;
 8
 9  HAL_SPI_Transmit_DMA(hspi_ws2812b, buffer, 48); // Additional 3 for reset signal
10  while(HAL_DMA_STATE_READY != HAL_DMA_GetState(hspi_ws2812b->hdmatx));
11}
12
13void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi)
14{
15  if(hspi == hspi_ws2812b)
16  {
17    if(!ResetSignal)
18    {
19      for(uint8_t k = 0; k < 24; k++) // To 72 impulses of reset
20      {
21        buffer[k] = 0x00;
22      }
23      ResetSignal = 1; // End reset signal
24    }
25    else // LEDs Odd 1,3,5,7...
26    {
27      if(CurrentLed > WS2812B_LEDS)
28      {
29        HAL_SPI_DMAStop(hspi_ws2812b);
30      }
31      else
32      {
33        uint8_t j = 0;
34        //GREEN
35        for(int8_t k=7; k>=0; k--)
36        {
37          if((ws2812b_array[CurrentLed].green & (1<<k)) == 0)
38            buffer[j] = zero;
39          else
40            buffer[j] = one;
41          j++;
42        }
43
44        //RED
45        for(int8_t k=7; k>=0; k--)
46        {
47          if((ws2812b_array[CurrentLed].red & (1<<k)) == 0)
48            buffer[j] = zero;
49          else
50            buffer[j] = one;
51          j++;
52        }
53
54        //BLUE
55        for(int8_t k=7; k>=0; k--)
56        {
57          if((ws2812b_array[CurrentLed].blue & (1<<k)) == 0)
58            buffer[j] = zero;
59          else
60            buffer[j] = one;
61          j++;
62        }
63        CurrentLed++;
64      }
65    }
66  }
67}
68
69void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
70{
71  if(hspi == hspi_ws2812b)
72  {
73    if(CurrentLed > WS2812B_LEDS)
74    {
75      HAL_SPI_DMAStop(hspi_ws2812b);
76    }
77    else
78    {
79      // Even LEDs 0,2,0
80      uint8_t j = 24;
81      //GREEN
82      for(int8_t k=7; k>=0; k--)
83      {
84        if((ws2812b_array[CurrentLed].green & (1<<k)) == 0)
85          buffer[j] = zero;
86        else
87          buffer[j] = one;
88        j++;
89      }
90
91      //RED
92      for(int8_t k=7; k>=0; k--)
93      {
94        if((ws2812b_array[CurrentLed].red & (1<<k)) == 0)
95          buffer[j] = zero;
96        else
97          buffer[j] = one;
98        j++;
99      }
100
101      //BLUE
102      for(int8_t k=7; k>=0; k--)
103      {
104        if((ws2812b_array[CurrentLed].blue & (1<<k)) == 0)
105          buffer[j] = zero;
106        else
107          buffer[j] = one;
108        j++;
109      }
110      CurrentLed++;
111    }
112  }
113}

 

传送门回顾:【TOLIN】第四章|驱动WS2812B彩灯

 

注:由于我们修改了时钟参数,之前的PWM+DMA移植的代码在这个工程可能不起作用,需要重新计算数值。

这篇关于【TOLIN】第六章|移植WS2812FX库(上)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/898664

相关文章

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

第六章习题11.输出以下图形

🌏个人博客:尹蓝锐的博客 希望文章能够给到初学的你一些启发~ 如果觉得文章对你有帮助的话,点赞 + 关注+ 收藏支持一下笔者吧~ 1、题目要求: 输出以下图形

RT-Thread(Nano版本)的快速移植(基于NUCLEO-F446RE)

目录 概述 1 RT-Thread 1.1 RT-Thread的版本  1.2 认识Nano版本 2 STM32F446U上移植RT-Thread  2.1 STM32Cube创建工程 2.2 移植RT-Thread 2.2.1 安装RT-Thread Packet  2.2.2 加载RT-Thread 2.2.3 匹配相关接口 2.2.3.1 初次编译代码  2.2.3.

Go语言设计与实现 学习笔记 第六章 并发编程(3)

系统调用 系统调用对于Go语言调度器的调度也有比较大的影响,为了处理这些特殊的系统调用,我们甚至专门在Goroutine中加入了_Gsyscall这一状态,Go语言通过Syscall和Rawsyscall等使用汇编语言编写的方法封装了操作系统提供的所有系统调用,其中Syscall在Linux 386上的实现如下: // 定义名为.Syscall的函数,该函数不允许栈分割,栈帧大小为0,有28字

libmad音频解码库-Linux交叉编译移植

下载并解压libmad-0.15.1b.tar.gz 下载链接:https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz $tar -xvf libmad-0.15.1b.tar.gz$cd libmad-0.15.1b 1、先执行下面的命令:这条命令是为了适配高版本的gcc,因为高版本的gcc已经将-fforce-mem去除了:

arm linux lua移植

lua: lua home 1.下载lua源码 lua下载 lua-5.3.4.tar.gz 2.解压: tar xvf lua-5.3.4.tar.gz 3.修改makefile and luaconf.h $修改 lua-5.3.4/Makefile #INSTALL_TOP= /usr/local INSTALL_TOP= $(shell pwd)/out #修改安装目录(当前目录/o

计算机三级网络技术总结 第六章交换机及其配置

采用直通交换模式的交换机开始转发数据帧时已经接收到的帧长度时14字节建立VALN的命令格式: vlan <vlan_ID> name <vlan_name> 为端口分配VLAN的命令格式为: switchport access vlan <vlan_num>  不给定名字的VLAN,系统自动按缺省的VLAN名(VLAN00xxx)配置交换机Catalyst 6500管理IP地址命令格式: (ena

第六章 详细设计简记

第六章  详细设计       详细设计不是具体的编程,而是要设计出程序的“蓝图”,详细设计不仅仅是逻辑上正确的实现每个模块的功能,更重要的是设计出来的处理工程应该简明易懂。       详细设计的目的:为软件结构图中的每一个模块确定使用的算法和块内的数据结构,并用某种选定的表达工具给出清晰的描述。       详细设计的任务:           1.为每

Java学习Day37:HTML 第六章:黄金国(项目思路梳理)

第一天:后端思路梳理及代码实现 1.数据库设计 数据库设计使用一张表(course)设计 CREATE TABLE course(id INT PRIMARY KEY AUTO_INCREMENT COMMENT '课程ID',cname VARCHAR(255) NOT NULL COMMENT '课程名称',price DOUBLE NOT NULL COMMENT '售卖价格

s3c2440---PWM使用之蜂鸣器驱动移植

一、蜂鸣器驱动介绍 1.1.什么是蜂鸣器               蜂鸣器是一种简单的声响发生器,常用于电子产品中作为警示或提醒作用。其基本原理是通过交替改变直流电的电压方向来产生声音,一般使用交替电流产生声音会比较稳定。 1.2.蜂鸣器的类别 1.有源蜂鸣器 1)结构原理 有源蜂鸣器内部自带振荡源,只需接通电源即可发声。内部电路会自动产生一定频率的振荡信号,从而驱动蜂鸣器发声。