描述

这篇文章延续上一篇文件,上一篇贴出了我写的两个源码文件,这一篇会介绍一下源码的注释,并介绍如何进行代码移植

  • 研发平台:淘宝上购买的野火店铺的一款单片机,名字叫骄阳开发板,核心芯片是stm32F407IGT6
  • 灯带:WS2812型号的灯带

在这一篇文章的工作之前,我们已经通过上一篇文章,成功的配置了PWM管脚,并成功构建了整个工程。

代码移植

  1. ws2812.c文件添加到整个工程中。添加方式:打开你的工程,右侧有整个工程的文件树,右键“Application/User/Core”选择“Add Existing Files to Group Application/..”
  2. ws2812.h文件所在的文件夹路径,以添加include路径的方式添加到整个工程中。选择上方菜单栏的魔法棒,左键点击后,选择上方的“C/C++”一栏,出现以下界面
  3. 在以上界面中,找到“Include Path”一项,后方跟着一个小“…”按钮,点击这个按钮,可以进到以下界面。

  4. 我们在这个界面,点击长得像虚线框的那个按钮,会出现一个新行,在新行的右侧再次出现“…”按钮,我们点击这个按钮,选择ws2812.h的所在路径,点击确定。以下截图是我添加后的样子,我将ws2812.h放在了Core文件夹下,以“fuxi”为文件夹名。

  5. 以上我们完成了路径的配置,接下来就是添加代码了
    main.c中的include代码部分添加
    #include "ws2812.h"
    

main.c中的while循环开始之前添加以下代码

led_init();
int direction = 6;

uint8_t g_value_r[LED_NUM] = {0xFF};
uint8_t g_value_b[LED_NUM] = {0x00};
uint8_t g_value_g[LED_NUM] = {0x00};

for (uint8_t i = 0 ; i < LED_NUM; i++)
{
    led_set(i, g_value_r[i], g_value_g[i], g_value_b[i]);
}
uint8_t g_value_delta = 0x08;

main.c中的while循环中添加以下代码

for (uint8_t i = 0 ; i < LED_NUM; i++)
{
    led_set(i, g_value_r[i], g_value_g[i], g_value_b[i]);
}
led_on();

HAL_Delay(100);
if (((g_value_r[0] >=  0xFF - g_value_delta) || (g_value_b[0] <= g_value_delta/2) )&& g_value_g[0] == 0x00 && direction == 6)
{
    direction = 1;
    g_value_r[0] = 0xFF;
    g_value_g[0] = 0x00;
    g_value_b[0] = 0x00;
}
else if (g_value_r[0] == 0xFF && g_value_g[0] >= 0xFF - g_value_delta && g_value_b[0] == 0x00 && direction == 1)
{
    g_value_g[0] = 0xFF;
    direction = 2;
}
else if (g_value_r[0] <= g_value_delta && g_value_g[0] == 0xff && g_value_b[0] == 0x00 && direction == 2)
{
    g_value_r[0] = 0x00;
    direction = 3;
}
else if (g_value_r[0] ==  0x00 && g_value_g[0] == 0xff && g_value_b[0] >= (0xFF - g_value_delta) && direction == 3)
{
    g_value_b[0] = 0xff;
    direction = 4;
}
else if (g_value_r[0] ==  0x00 && g_value_g[0] <= g_value_delta && g_value_b[0] == 0xff && direction == 4)
{
    g_value_g[0] = 0x00;
    direction = 5;
}
else if (g_value_r[0] >=  (0x8B - g_value_delta) && g_value_g[0] == 0x00 && g_value_b[0] == 0xff && direction == 5)
{
    direction = 6;
    g_value_r[0] = 0x8B;
    g_value_g[0] = 0x00;
    g_value_b[0] = 0xFF;
}


if (direction == 1)
{
    g_value_g[0] = g_value_g[0] + g_value_delta;
}
else if (direction == 2)
{
    g_value_r[0] = g_value_r[0] - g_value_delta;
}
else if (direction == 3)
{
    g_value_b[0] = g_value_b[0] + g_value_delta;
}
else if (direction == 4)
{
    g_value_g[0] = g_value_g[0] - g_value_delta;
}
else if (direction == 5)
{
    g_value_r[0] = g_value_r[0] + g_value_delta;
}
else if (direction == 6)
{
    g_value_r[0] = g_value_r[0] + g_value_delta/2;
    g_value_b[0] = g_value_b[0] - g_value_delta;

}

for (int i = 29 ; i >0; i-- )
{
    g_value_r[i] = g_value_r[i-1]; 
    g_value_g[i] = g_value_g[i-1]; 
    g_value_b[i] = g_value_b[i-1]; 
}
  1. 以下截图是添加代码的具体位置,可以用于参考。

  2. ws2812.hws2812.c文件,我在上一篇文章中写了,可以参考。
  3. 以上就是全部的代码移植过程,依次完成以上的步骤,我们就可以成功的让灯带进行跑马灯的效果了

代码详解

在这里我们来对之前的代码进行一定的剖析。其中,我们只对一些核心的代码进行了解释。

ws2812.h

#define ONE_PULSE 140
#define ZERO_PULSE 70                        

#define LED_NUM 30
#define LED_DATA_LEN 24
#define RESET_PULSE_LEN 80  
#define DATA_BUFFER_LEN RESET_PULSE_LEN+(LED_DATA_LEN*LED_NUM)
  1. 上一篇我们设置了重装载数是209,显然PWM波的频率为800kHz。什么意思呢,我们在1.25us中计算了210个重载值。
    根据之前的ws2812知识的分析,0码的高电平时间为1/3,1码的高电平时间为2/3。显然210的1/3就是70,210的2/3就是140。这就是ZERO_PULSEONE_PULSE的含义

  2. LED_NUM代表的是我使用的灯带所含的led灯的个数,LED_DATA_LEN是每个灯RGB颜色的24位数据,RESET_PULSE_LEN代表的就是最开始的Trst时间,这里我们设置了80个码元周期,1.25us就是100us的数据reset时间。
    DATA_BUFFER_LEN就是上面三个参数形成的灯带控制数据总长度

  3. ws2812.h文件中的所有参数,你都可以根据自己的实际需要进行相应的调整

ws2812.c

该文件有两个核心函数,分别进行解释

void led_set(uint8_t led_id, uint8_t value_r, uint8_t value_g, uint8_t value_b)
{
    uint16_t* p = (data_buffur + RESET_PULSE_LEN) + (LED_DATA_LEN * led_id);
    for (uint16_t i = 0; i < 8; i++)
    {
            p[i]      = (value_g << i) & (0x80)? ONE_PULSE: ZERO_PULSE;
            p[i + 8]  = (value_r << i) & (0x80)? ONE_PULSE: ZERO_PULSE;
            p[i + 16] = (value_b << i) & (0x80)? ONE_PULSE: ZERO_PULSE;
    }
}

这个函数的功能:将整个控制数据中,属于第i个led灯的24位RGB数据进行赋值
这之中最核心的一句话p[i] = (value_g << i) & (0x80)? ONE_PULSE: ZERO_PULSE;

什么意思呢,value_g代表绿颜色的通道值,是一个uint8_t型的数。我们假设它是255,把它写成二进制就是11111111,(value_g << i) & (0x80)代表左移1位后与二进制数0x80进行按位与运算(每个位上都是1才是1),如果最高位是1的话就赋值为140,否则就赋值为70。

简单来说,这就是一句能将uint8_t数据类型很简单的变成二进制的一句话

void led_on(void)
{
     HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)data_buffur, DATA_BUFFER_LEN);
}

这个函数就一句话,就是将已经准备好的数据,用TIM1_CH1管脚发出去。我们将这个函数命名成了“开灯”

main.c

简单来说,我将30个灯分开设置颜色,每个灯的颜色是不一样且相邻的,从红、橙…到紫再从红重新开始,形成一个跑马灯的效果
主函数的核心部分是以下

for (uint8_t i = 0 ; i < LED_NUM; i++)
{
    led_set(i, g_value_r[i], g_value_g[i], g_value_b[i]);
}
led_on();

将每一个灯分别设置为不同的颜色,最后将数据一起发出去。设置的部分就是后面的一大部分代码,实现跑马灯的效果,简单来说就是如下的样子:

灯号1、2、3、4、5、6、7……30
第一个时间点它们发出的颜色:
颜色a、b、c、d、e、f、g……#、@、%
第二个时间点它们发出的颜色:
颜色Z、a、b、c、d、e、f、g……#、@
第三个时间点它们发出的颜色:
颜色Y、Z、a、b、c、d、e、f、g……#

彩虹每个颜色对应的RGB颜色是这样的

16进制码:
红色 #FF0000
橙色 #FF7F00
黄色 #FFFF00
绿色 #00FF00
青色 #00FFFF
蓝色 #0000FF
紫色 #8B00FF

我的代码中,我使用用direction来表明当前灯的颜色究竟是从哪个颜色转变成哪个颜色,比如direction==1我用来表示从红色到黄色的过程、direction==2我用来表示从黄色到绿色的过程,g_value_delta用来表示两个相邻灯的颜色差值。这就是整个代码的逻辑和思路,我就不具体将我的具体代码了,代码很不优雅,但我没有什么时间用在这上面啦哈哈。当然你有更简单的方法去实现同样的效果,写好后可以发给我,我来学习学习哈哈。

总结

从D4.1到D4.3的三篇文章,我们讲解了WS2812灯带的控制过程,完成了利用CubeMX设置的学习。最终还贴上了可以随时使用的原创代码

写的不容易,欢迎各位朋友点赞并加关注,谢谢!