一个IO控制很多个LED,这个技能你Get到了吗

系统
多位(几个LED就是几位)通过引脚级联,接一个LED的DOUT引脚到另一个LED的DIN引脚,通过这种级联的方式,只需要使用一个IO口(单片机引脚)就能控制尽可能多的LED。

[[377324]]

简介

多位(几个LED就是几位)通过引脚级联,接一个LED的DOUT引脚到另一个LED的DIN引脚,通过这种级联的方式,只需要使用一个IO口(单片机引脚)就能控制尽可能多的LED。

每个LED都集成了一颗驱动芯片在里面,让我们的LED变得智能和寻址,每一个内部都有恒流驱动,所以LED颜色非常一致,即使电压有小幅抖动,电压变化也是一样的。

不需要外部电阻——限流电阻,使LED灯的布局设计变得简单。

单线通信,能够最大限度的减少单片机IO口的压力,另外这款RGB灯使用了WS2812B驱动芯片,让外围电路只需要一颗电容就能够满足电路需求,从而最大可能的让电路变得简单优美。

特点

  1. 智能反接保护,电源反接不会损坏IC;
  2. IC控制电路与LED点光源公用一个电源;
  3. 控制电路与RGB芯片集成在一个5050封装的元器件中,构成一个外控像素点;
  4. 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加;
  5. 内置上电复位和掉电复位电路;
  6. 每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示,扫描频率不低于400Hz;
  7. 串行级联接口,能通过一根信号线完成数据的接收与解码;
  8. 任意两点传输距离在不超过5米时,无需增加额外电路;
  9. 当刷新速率30帧/秒时,级联数不小于1024点;
  10. 数据发送速度可达800Kbps;
  11. 光的颜色高度一致,性价比高。

注意: 800Kbps,相当于1.25us传输一比特数据。

引脚图

引脚功能描述:

NO. Symbol 功能描述
1 VDD LED的供电电源,Vdd 范围 +3.5~+5.3 V
2 DOUT 控制信号数据输出引脚
3 VSS
4 DIN 控制信号数据输入引脚

典型电路

串联方法

原理图

除了灯珠以外只需要额外增加一个0.1uF的电容即可。

硬件连接

STM32F103RET6核心板 WS2812B模块
PA6 DIN
VCC +5V
GND GND

驱动原理

数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DOUT端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。

像素点采用自动整形转发技术,使得像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。

因为数据被内部锁存,所以只要不改变颜色值(模块持续供电),颜色是不会发生改变的,设置颜色的脉冲也不需要持续提供(单片机发生复位也无影响),只需要在修改颜色值的时候,发送一遍即可。

0和1的区分

Treset:复位时间

由上图可知,我们要发送 '0' ,需要将GPIO引脚置高并持续0.4 us(400 ns),然后GPIO置低并持续0.85 us(850 ns),此过程即完成0 code的发送,具体代码实现如下:

  1. void send_0(void) 
  2.     IN_H; 
  3.     Wait400ns; 
  4.     IN_L; 
  5.     Wait850ns; 

我们要发送 '1' ,需要将GPIO引脚置高并持续0.85 us(850 ns),然后GPIO置低并持续0.4 us(400 ns),此过程即完成1 code的发送,具体代码实现如下:

  1. void send_1(void) 
  2.     IN_H; 
  3.     Wait850ns; 
  4.     IN_L; 
  5.     Wait400ns; 

所以本程序的难点即是求取400 ns 和 850 ns 相对精确的延时时间。

延时函数的实现

单片机里的延时函数一般通过执行一些无意义的循环进行延时,比如定义如下函数:

  1. void delay(unsigned char i) 
  2.     while(--i); 

我们这里需要的延时周期很小,才1.25us,因为函数的调用,需要入栈和出栈,所以如果使用上面的延时函数的方式的话,那么一进一出就接近几百ns的时间就没了,所以为了精确控制,我们这里延时函数的定义如下:

  1. #define    Wait10nop        {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();} 
  2. #define    Wait250ns        {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();} 
  3. #define    Wait400ns        {Wait250ns;Wait10nop;}    //388 
  4. #define    Wait850ns        {Wait250ns;Wait10nop;Wait10nop;Wait10nop;Wait10nop;__NOP();__NOP();__NOP();__NOP();__NOP();}    //860 

我们在main函数中使用如下方式测试这个延时函数:

  1. while(1) 
  2.     IN_H; 
  3.     //Wait250ns; 
  4.     Wait850ns; 
  5.     IN_L; 
  6.     //Wait250ns; 
  7.     Wait850ns; 

然后用示波器观察与模块DIN引脚相连的GPIO输出的脉冲信号,查看其高电平是否与咱们预定义的一致,如果不一致,增加或减少空指令进行调整。

注意:一个 __NOP(); 空指令的耗时大约:1000/72 ≈ 14 ns 的时间,自己可以在上面定义的基础上,根据需要随意增加或者减少 __NOP(); 空指令的个数。

“注意空指令前面是两个“_”。

经过示波器测量,不断调整,上面定义的 Wait250ns 宏定义的耗时如下图所示。

经过示波器测试,上面的 Wait400ns 耗时为 388 ns , Wait850ns 耗时为 860 ns,满足上面"0"和"1"的时间区间范围。

24 bit数据的组成

注意: 数据传输顺序按GRB顺序传输,并且高位在前。

  1. void ws2812_rgb(u8 ws_num,u8 ws_r,u8 ws_g,u8 ws_b)  
  2.     ws_data[(ws_num-1)*3]=ws_g; 
  3.     ws_data[(ws_num-1)*3+1]=ws_r; 
  4.     ws_data[(ws_num-1)*3+2]=ws_b; 

ws_data[] 数组用于记录待传输的RGB数据,每一个灯珠的颜色占用三个字节,因为数据传输顺序按GRB的顺序传输,所以赋值的时候注意先后顺序,上面函数是设置某一个灯珠的颜色值。

ws_data[] 数组中颜色值设置完毕之后,就要把这个数组的数据发送到模块中,具体的实现函数如下:

  1. void ws2812_refresh(u8 ws_count) 
  2.     u8 ws_ri=0; 
  3.      
  4.     for(;ws_ri<ws_count*3;ws_ri++) 
  5.     { 
  6.         if((ws_data[ws_ri]&0x80)==0) send_0(); else send_1(); 
  7.         if((ws_data[ws_ri]&0x40)==0) send_0(); else send_1(); 
  8.         if((ws_data[ws_ri]&0x20)==0) send_0(); else send_1(); 
  9.         if((ws_data[ws_ri]&0x10)==0) send_0(); else send_1(); 
  10.         if((ws_data[ws_ri]&0x08)==0) send_0(); else send_1(); 
  11.         if((ws_data[ws_ri]&0x04)==0) send_0(); else send_1(); 
  12.         if((ws_data[ws_ri]&0x02)==0) send_0(); else send_1(); 
  13.         if((ws_data[ws_ri]&0x01)==0) send_0(); else send_1(); 
  14.     } 
  15.      
  16.     //延时一段时间 
  17.     ws2812_reset(); 

ws_data[] 数组中的每一个字节按位发送,因为高位在前,所以先发送每个字节的高位,获取最高位的值的方法为:ws_data[ws_ri]&0x80 。

数据传输方法

N位的模块,一次就要发送 N * 3 字节的数据。

注意: 上图中D1的数据是通过单片机发送,D2,D3,D4通过像素内重塑放大传输。

main函数实现

main函数中,每隔1S,点亮一个LED,当8个LED都点亮一次之后,所有LED点亮一次,然后再开启下一次循环。

main函数的具体实现如下所示:

  1. int main(void)   
  2. {  
  3.     int times = 0;  
  4.      
  5.     //初始化 
  6.     //延时函数初始化    
  7.     delay_init(); 
  8.      
  9.     uart_init(115200);    //串口1:Debug,初始化为115200  
  10.  
  11.     ws2812_init(); 
  12.      
  13.     printf("System Init OK ...\r\n"); 
  14.   
  15.     while(1)  
  16.     {   
  17.         times++;  
  18.  
  19.         if(times > 8) 
  20.             times = 0; 
  21.          
  22.         switch(times) 
  23.         { 
  24.             case 0: 
  25.                 ws2812_rgb(1, WS_RED); 
  26.                 ws2812_rgb(2, WS_GREEN); 
  27.                 ws2812_rgb(3, WS_BLUE); 
  28.                 ws2812_rgb(4, WS_WHITE); 
  29.                 ws2812_rgb(5, WS_PURPLE); 
  30.                 ws2812_rgb(6, WS_YELLOW); 
  31.                 ws2812_rgb(7, WS_BROWN); 
  32.                 ws2812_rgb(8, WS_BLUE); 
  33.                 ws2812_refresh(8); 
  34.                 break; 
  35.             case 1: 
  36.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  37.                 ws2812_rgb(1, WS_RED); 
  38.                 ws2812_refresh(8); 
  39.                 break; 
  40.             case 2: 
  41.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  42.                 ws2812_rgb(2, WS_GREEN); 
  43.                 ws2812_refresh(8); 
  44.                 break; 
  45.             case 3: 
  46.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  47.                 ws2812_rgb(3, WS_BLUE); 
  48.                 ws2812_refresh(8); 
  49.                 break; 
  50.             case 4: 
  51.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  52.                 ws2812_rgb(4, WS_WHITE); 
  53.                 ws2812_refresh(8); 
  54.                 break; 
  55.             case 5: 
  56.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  57.                 ws2812_rgb(5, WS_PURPLE); 
  58.                 ws2812_refresh(8); 
  59.                 break; 
  60.             case 6: 
  61.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  62.                 ws2812_rgb(6, WS_YELLOW); 
  63.                 ws2812_refresh(8); 
  64.                 break; 
  65.             case 7: 
  66.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  67.                 ws2812_rgb(7, WS_BROWN); 
  68.                 ws2812_refresh(8); 
  69.                 break; 
  70.             case 8: 
  71.                 memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));  
  72.                 ws2812_rgb(8, WS_BLUE); 
  73.                 ws2812_refresh(8); 
  74.                 break; 
  75.         } 
  76.          
  77.         delay_ms(1000);         
  78.     }   

颜色RGB值查询

颜色的RGB值和名称可以参考下面链接:

https://code.ziqiangxuetang.com/try/color.py

程序中颜色预定义如下:

  1. #define WS_DARK  0,0,0 
  2. #define WS_WHITE  255,255,255 
  3. #define WS_RED   255,0,0 
  4. #define WS_GREEN  0,255,0 
  5. #define WS_BLUE  0,0,255 
  6. #define WS_YELLOW  255,255,0 
  7. #define WS_PURPLE   255,0,255 
  8. #define WS_CYAN  0,255,255 
  9. #define WS_BROWN    165,42,42 

大家可以根据自己的喜欢,随意替换颜色。

本文转载自微信公众号「 嵌入式从0到1」,可以通过以下二维码关注。转载本文请联系 嵌入式从0到1公众号。

 

责任编辑:武晓燕 来源: 嵌入式从0到1
相关推荐

2022-09-27 08:28:54

云平台云主机私有云

2021-05-12 13:38:47

云计算

2021-01-12 11:37:09

Python编程语言开发

2017-04-29 11:28:57

人工智能机器学习AI

2023-10-28 09:41:12

Next.js函数配置选项

2018-10-25 15:04:22

编程程序员陷阱

2016-11-09 15:46:43

数据中心大数据数据备份

2019-11-15 14:45:10

开发者技能工具

2021-05-19 14:22:46

代码开发项目

2021-10-04 09:29:41

对象池线程池

2019-04-25 13:10:04

Java 8Stream API编程语言

2018-06-29 15:07:13

代码工程师软件开发

2023-03-26 22:02:53

APMPR监控

2023-12-06 07:28:47

阻塞IO异步IO

2023-09-19 08:03:50

rebase​merge

2022-05-27 09:02:31

Openbase开源前端

2015-05-26 09:35:29

运维运维危机云计算应用

2011-06-28 09:16:11

切克签到

2022-07-11 08:48:52

业务转型CIO

2021-09-03 06:46:34

MyBatis缓存后端
点赞
收藏

51CTO技术栈公众号