前几天发现了一个人工智能学习网站,通俗易懂,幽默幽默,忍不住和大家分享。点击跳转到教程。
实现目标
- 每隔一秒,点亮模块上的一个LED
- 点亮模块上的一切LED
所需的工具和环境
- Keil 5
- STM32F103RET6核心板(本平台自制专用核心板,可随意找到开发板)
- 8位 圆形 WS2812B LED模块
本文源码
微信官方账号后台回复关键词”,获取模块数据、数据手册和工程源代码。
简介
电压:DC4~7V
多位(几个LED就是几位)通过引脚级联接一个到另一个的输出引脚,通过这种级联的方式,只需要使用一个IO口(单片机引脚)可以尽可能多地控制LED,每个LED里面集成了驱动芯片,让我们LED变得智能和寻址,每个内部都有恒流驱动,所以LED颜色非常一致,即使电压有轻微的抖动,电压变化也是如此。
不需要外部电阻-限流电阻LED灯的布局设计变得简单。
单线通信可以最大限度地减少单片机IO口压,另外这个RGB灯使用了WS2812B驱动芯片,让外围电路能够满足电路的需求,使电路最大限度地简单美观。
特点
- 智能反接保护,电源反接不会损坏IC;
- IC控制电路与LED点光源公用一个电源;
- 控制电路与RGB芯片集成在5050包装元件中,形成外部控制像素点;
- 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加;
- 内置上电复位和掉电复位电路;
- 每个像素点的三基色可显示256亮度,1677216种颜色的全真色可显示,扫描频率不低于400Hz;
- 串行级联接口可以通过信号线接收和解码数据;
- 当任何两点传输距离不超过5米时,无需增加额外电路;
- 当刷新率为30帧/秒时,级联数不小于1024点;
- 数据发送速度可达8000Kbps;
- 光的颜色高度一致,性价比高。
引脚图
引脚功能描述:
1 | VDD | LED,Vdd 范围 3.5~ 5.3 V |
2 | DOUT | 控制信号数据输出 |
3 | VSS | 地 |
4 | DIN | 引脚控制信号数据输入 |
典型电路
串联方法
原理图
除了灯珠,只需要额外添加0.1uF电容。
硬件连接
PA6 | DIN |
VCC | 5V |
GND | GND |
驱动原理
数据协议采用单线归零码的通信方式,上电复位后像素点,DIN端接收从控制器传输的数据,首先发送的24bit数据从第一个像素点提取后,发送到像素点内部的数据锁器。剩余数据通过内部整形电路整形放大DOUT端口开始向下一级联的像素点转发输出,每次传输一个像素点,信号减少24bit。
像素点采用自动整形转发技术,使得像素点的级联数不受信号传输的限制,只受信号传输速度的限制。
由于数据被内部锁定,只要颜色值不改变(模块连续供电),颜色就不会改变,设置颜色的脉冲也不需要连续提供(单片机复位无影响),只需在修改颜色值时再次发送。
0和1的区分
Treset:复位时间
从上图可以看出,我们必须发送它 ‘’ ,需要将GPIO引脚置高并持续0.4 us(400 ns),然后GPIO置低并持续0.85 us(850 ns),这个过程就完成了具体代码如下:
void send_0(void) { IN_H; Wait400ns; IN_L; Wait850ns; }
我们要发送 ‘’ ,需要将GPIO引脚位高并持续0.85 us(850 ns),然后GPIO置低并持续0.4 us(400 ns),这个过程就完成了具体代码如下:
void send_1(void) { IN_H; Wait850ns; IN_L; Wait400ns; }
所以这个程序的难点是4000 ns 和 850 ns 延迟时间相对准确。
实现延迟函数
单片机中的延迟函数通常通过执行一些无意义的循环来延迟,例如定义以下函数:
void delay(unsigned char i) { while(--i); }
这里需要的延迟周期很小,只有1.25us,因为函数的调用需要进出栈,如果使用上述延迟函数,一进一出就会接近几百。ns因此,为了精确控制,这里延迟函数的定义如下:
#define Wait10nop {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();} #define Wait250ns {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();} #define Wait400ns {Wait250ns;Wait10nop;} //388 #define Wait850ns {Wait250ns;Wait10nop;Wait10nop;Wait10nop;Wait10nop;__NOP();__NOP();__NOP();__NOP();__NOP();} //860
我们在main该延迟函数采用以下方法测试:
while(1) { IN_H; //Wait250ns; Wait850ns; N_L;
//Wait250ns;
Wait850ns;
}
然后用示波器观察与模块DIN引脚相连的GPIO输出的脉冲信号,查看其高电平是否与咱们预定义的一致,如果不一致,增加或减少空指令进行调整。
注意一个 空指令的耗时大约:1000/72 ≈ 14 ns 的时间,自己可以在上面定义的基础上,根据需要随意增加或者减少 空指令的个数。
注意空指令前面是两个“”。
经过示波器测量,不断调整,上面定义的 Wait250ns 宏定义的耗时如下图所示。
经过示波器测试,上面的 Wait400ns 耗时为 388 ns , Wait850ns 耗时为 860 ns,满足上面"0"和"1"的时间区间范围。
24 bit数据的组成
数据传输顺序按GRB顺序传输,并且高位在前。
void ws2812_rgb(u8 ws_num,u8 ws_r,u8 ws_g,u8 ws_b)
{
ws_data[(ws_num-1)*3]=ws_g;
ws_data[(ws_num-1)*3+1]=ws_r;
ws_data[(ws_num-1)*3+2]=ws_b;
}
数组中用于记录待传输的RGB数据,每一个灯珠的颜色占用三个字节,因为数据传输顺序按GRB的顺序传输,所以赋值的时候注意先后顺序,上面函数是设置某一个灯珠的颜色值。
数组中颜色值设置完毕之后,就要把这个数组的数据发送到模块中,具体的实现函数如下:
void ws2812_refresh(u8 ws_count)
{
u8 ws_ri=0;
for(;ws_ri<ws_count*3;ws_ri++)
{
if((ws_data[ws_ri]&0x80)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x40)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x20)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x10)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x08)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x04)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x02)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x01)==0) send_0(); else send_1();
}
//延时一段时间
ws2812_reset();
}
数组中的每一个字节按位判断发送,因为高位在前,所以先发送每个字节的高位,获取最高位的值的方法为: 。
数据传输方法
N位的模块,一次就要发送 N * 3 字节的数据。
D1的数据是通过单片机发送,D2,D3,D4通过像素内重塑放大传输。
main函数调用
main函数中,每隔1S,点亮一个LED,当8个LED都点亮一次之后,所有LED点亮一次,然后再开启下一次循环。
main函数的具体实现如下所示:
int main(void)
{
int times = 0;
//初始化
//延时函数初始化
delay_init();
uart_init(115200); //串口1:Debug,初始化为115200
ws2812_init();
printf("System Init OK ...\r\n");
while(1)
{
times++;
if(times > 8)
times = 0;
switch(times)
{
case 0:
ws2812_rgb(1, WS_RED);
ws2812_rgb(2, WS_GREEN);
ws2812_rgb(3, WS_BLUE);
ws2812_rgb(4, WS_WHITE);
ws2812_rgb(5, WS_PURPLE);
ws2812_rgb(6, WS_YELLOW);
ws2812_rgb(7, WS_BROWN);
ws2812_rgb(8, WS_BLUE);
ws2812_refresh(8);
break;
case 1:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(1, WS_RED);
ws2812_refresh(8);
break;
case 2:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(2, WS_GREEN);
ws2812_refresh(8);
break;
case 3:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(3, WS_BLUE);
ws2812_refresh(8);
break;
case 4:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(4, WS_WHITE);
ws2812_refresh(8);
break;
case 5:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(5, WS_PURPLE);
ws2812_refresh(8);
break;
case 6:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(6, WS_YELLOW);
ws2812_refresh(8);
break;
case 7:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(7, WS_BROWN);
ws2812_refresh(8);
break;
case 8:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(8, WS_BLUE);
ws2812_refresh(8);
break;
}
delay_ms(1000);
}
}
颜色RGB值查询
颜色的RGB值和名称可以参考下面链接:
https://code.ziqiangxuetang.com/try/color.py
程序中颜色预定义如下:
#define WS_DARK 0,0,0
#define WS_WHITE 255,255,255
#define WS_RED 255,0,0
#define WS_GREEN 0,255,0
#define WS_BLUE 0,0,255
#define WS_YELLOW 255,255,0
#define WS_PURPLE 255,0,255
#define WS_CYAN 0,255,255
#define WS_BROWN 165,42,42
大家可以根据自己的喜欢,随意替换颜色。