还是延续以前文章的风格,先直接上效果图,感兴趣的伙伴,想自己实现此类功能的朋友再继续往下深读文章内容:

这里先插个课外内容:CSDN如何实现把效果图直接放到文章里面来呢?
已知导视频的话只能导入链接,还要借助其他视频平台进行播放,所以这里只能通过导入图片才能在文章里面显示,而我这些效果图实际都是GIF格式的文件,GIF格式可以通过各类视频格式文件转换得到,转换的web工具链接:https://ezgif.com/
由于比较容易上手,这里就不去讲解转换方式了,继续文章内容:
这里用到的一种方便的取色值数据的方法,就是使用我们经常用来取RGB数据显示到TFTLCD显示屏的一个工具:image2LCD

这里最好使用的是BMP格式图片进行转换,因为BMP图片默认是24位彩色图片,刚好满足WS2812的24bit 色值数据格式,同时最好是像素点也是完全匹配RGB灯板的总灯数,比如RGB灯板是20列X10行,那图片最好就是20X10像素点的,然后这里将对比度调整滑动至最高,避免部分黑色不亮的地方(其值为0x00)仍被读出来还有色值数据,导致最终显示出来不该亮的地方微亮了。
但是这个工具有其缺陷的地方,就是无法选择蛇形采集RGB数据,只能都是从同一个方向往另一个方向取采集(比如都是从上到下一列采完,再到第二列的最上方再往下采集数据),但是WS2812的单线级联较简单的走线方式则是蛇形走线。
现在网上贩卖的RGB灯单线级联方式基本都是这种蛇形走线:

同向走线方式则是这样的:

可以看出每走完一行或者一列,都要直接跳转到另一行或另一列的最远一个灯进行级联,所以对走线来说就比较麻烦。
既然色值采集工具跟硬件走线都被限制了,那最好还是从软件算法方面下手,我们可以知道蛇形走线的结果必然就是奇数列(行)的走势为同一个方向,偶数列(行)的走势为同一个方向,所以我们只要区分数据绘制到奇数列(行)还是偶数列(行),再使用算法进行一个正向读取数据还是反向读取数据就可以满足我们需要的显示效果了。
接下来我们用STM32CUBEMX生成一个工程,MCU使用的是STM32F103VET6,其64KB的RAM满足我们使用PWM+DMA的方式一次性显示较多的RGB灯:

时钟使用的是外部8MHz晶振进行9倍频到72MHz。

使用TIM3的channel4进行硬件PWM的输出。

进行PWM对应的DMA配置,然后就可以生成对应的keil-mdk工程了。

这里把最小栈空间设大,以满足我们PWM的DMA一次性操作较多WS2812数据的要求。
然后我们通过image2LCD工具转换得到的16*7像素图片的对应数组如下:
c
const uint16_t gImage_01[351] = { /* 0X11,0X18,0X00,0X10,0X00,0X07,0X00,0X1B, */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XFF,0XFF,
0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0X00,0X00,0X00,
0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,
0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,
0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,
0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0X00,
0X00,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,
0X00,0X00,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,
0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,
0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,
0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,
0XFF,0X00,0X00,0X00,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,0X00,0XFF,0XFF,
0X00,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
};
这里要注意的是:
1.应加const声明使该数组存放到flash空间,避免占用更加宝贵的ram空间,同时要将数组定义为uint16_t已满足我的封装函数形参要求;
2.16*7像素点对应应该是336个RGB数据,但是我这里怎么是351个RGB数据呢?因为我的灯板不是长方形的,左右各还有2列各少了2个灯的灯列,即每列为5个灯,由于我不需要在它们上面显示什么内容,所以我就在数组前面填充了15个0x00数据,使得数组的数据量由原来的336变成351了。
接下来给出封装函数及调用方式的程序:
c
//ws2812 RGB灯基本参数
#define ONE_PULSE (57) //1 码
#define ZERO_PULSE (28) //0 码
#define RESET_PULSE (90) //80 ,复位信号
#define LED_NUMS (117) //led 数量
#define LED_DATA_LEN (24) //led 单个数据长度,单个需要24bits
#define WS2812_DATA_LEN (LED_NUMS*LED_DATA_LEN) //ws2812灯条需要的数组长度
uint16_t RGB_buffur[RESET_PULSE + WS2812_DATA_LEN+RESET_PULSE] = { 0 };
void ws2812_set_RGB(uint8_t R, uint8_t G, uint8_t B, uint16_t num)//只赋值给1个RGB灯,num在这里的作用只是开辟出特定长度的内存空间
{
//
uint16_t *p = (RGB_buffur + RESET_PULSE) + (num * LED_DATA_LEN);
for (uint8_t i = 0;i < 8;++i)
{
//
p[i] = (G << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
p[i + 8] = (R << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
p[i + 16] = (B << i) & (0x80)?ONE_PULSE:ZERO_PULSE;
}
}
void ws2812_init(uint8_t led_nums)
{
level=0;
breath_R=0;
breath_G=0;
breath_B=0;
uint16_t num_data;
num_data = 60 + led_nums * 24+60;
for(uint8_t i = 0; i < led_nums; i++)
{
ws2812_set_RGB(0x00, 0x00, 0x00, i);
}
HAL_TIM_PWM_Start_DMA(&htim3,TIM_CHANNEL_4,(uint32_t *)RGB_buffur,(num_data));
}
void RGB_board_display(uint16_t rgb_num, const uint16_t * data_array )//rgb_num应为data_array数组的三分之一大小,板子同向走线用这个
{
uint16_t num_data;
num_data = 90 + rgb_num * 24+90;
for(uint16_t i = 0; i < rgb_num; i++)
{
ws2812_set_RGB(data_array[i*3], data_array[i*3+1], data_array[i*3+2], i);
}
HAL_TIM_PWM_Start_DMA(&htim3,TIM_CHANNEL_4,(uint32_t *)RGB_buffur,(num_data));
}
void RGB_board_display_serpentine(uint16_t rgb_num, const uint16_t * data_array )//rgb_num应为data_array数组的三分之一大小,板子蛇形走线用这个
{
uint8_t common_factor7;
uint16_t num_data;
num_data = 90 + rgb_num * 24+90;
for(uint16_t i = 0; i < rgb_num; i++)
{
if(i<5)
{
ws2812_set_RGB(data_array[i*3], data_array[i*3+1], data_array[i*3+2], i);
}
if(i>=5)
{
if((i-5)/7%2==0)//若为奇数行,数据要反相,从每行的第21到0,而不是从0到21
{
//ws2812_set_RGB(data_array[i*3+18-(i+2)%7*3], data_array[i*3+19-(i+2)%7*3], data_array[i*3+20-(i+2)%7*3], i);
ws2812_set_RGB(data_array[i*3], data_array[i*3+1], data_array[i*3+2], i);
}
else if((i-5)/7%2==1)//若为偶数行
{
//ws2812_set_RGB(data_array[i*3], data_array[i*3+1], data_array[i*3+2], i);
ws2812_set_RGB(data_array[(i-(i+2)%7)*3+18-(i+2)%7*3], data_array[(i-(i+2)%7)*3+19-(i+2)%7*3], data_array[(i-(i+2)%7)*3+20-(i+2)%7*3], i);
}
}
}
HAL_TIM_PWM_Start_DMA(&htim3,TIM_CHANNEL_4,(uint32_t *)RGB_buffur,(num_data));
}
main函数的内容也很简单:

这样就完成了调用,实现了文章一开始的那种显示效果了。
最后感谢各位阅读文章,希望各位有所收获!