RGB三色灯珠WS2812/15B控制嵌入式开发

本篇文章介绍了RGB三色灯珠从原理分析到嵌入式代码完整的嵌入式开发过程,提供给读者在驱动三色灯带的一种思路,它用到了STM32平台提供的DMA功能以防止在驱动等待亮灭时被中断打断导致驱动时许上出现问题。

开发原理分析

WS2812B/WS2815B均为RGB三色灯珠,WS2815B是WS2812B的升级版,区别在于两点,首先是供电电压由5V变为了12V供电,有效的降低了整个像素点的工作电流,降低线路板压降,最大限度保证像素点在很远距离传输时达到良好的混光一致性。其次是额外增加了一路信号线,在单个像素点损坏的情况下,不影响整体显示效果。

每个灯珠控制需要24位数据,8Bit绿色亮度+8Bit红色亮度+8Bit蓝色亮度,每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。 典型电路我们由WS2812B说起,灯带上的灯珠供电并联,信号线“串联”。信号由DI进到灯珠,灯珠在“吃掉”(锁存)24Bit数据后,将剩余数据整形放大后通过DO端口输出给下一个级联的灯珠,每经过一个灯珠的传输,信号减少24bit。当灯珠接收到280μs以上的RESET数据,灯珠根据自己锁存的数据完成对RGB三色灯的控制。24位数据采用归零码编码,Bit数据为高时,发送1码,Bit数据为低时,发送0码。

WS2815B多了一个BIN引脚,这个引脚接前一个灯珠的DI脚(灯带第一个灯珠接地)。BIN端接收到数据信号丢弃24bit数据后,再将DIN接收的数据信号与BIN断进行比较,若DIN端无信号,BIN端有接收到信号,切换到BIN端接收输入信号,这种措施可以确保在单个灯珠损坏时不至于影响到其余的灯珠,但是如果连续两个灯珠损坏,依然会导致后边的灯珠不受控制。
两种灯珠需要不同的灯板(灯珠封装不同),但是两种灯珠需要的嵌入式软件是一样的(数据的定义以及归零码的码制可以是一样的)

嵌入式源码分析

在嵌入式传输代码的实现上。一般都存在两种方式,一种为IO口模拟,这种方式一般见以前玩51单片机的嵌入式工程师,诸如I2C,SPI等常见的通信协议总线都习惯用IO口去模拟时序。对于WS281XB的通信协议,没有像SPI这种硬件帮我们实现的通信接口,这么看来用IO口去模拟是一个摆在桌面的实现方式。但是IO口模拟存在一个致命弱点,那就中断会打断你的时序模拟。以10个灯珠的控制为例,当你的代码正在模拟时序发到控制第10个灯还没有发的时候,中断来了,这个时候IO口正好被模拟程序控制为低,然后芯片去执行相应的中断处理程序,执行了超过280μs(RESET),执行完再回来继续发送第十个灯珠的数据,这时你会发现,你发送控制的第10个灯珠的数据其实发送给了第一个灯珠,因为两个数据之间因为中断的原因夹杂了一个RESET码。
我们采取的是SPI+DMA的方法来实现,使用芯片内部的SPI控制器去发送灯珠的控制数据,又因为我们采用的是DMA发送,能够保证中断来的时候芯片能依然准确的按照时序发送我们控制灯珠的数据。
我的实现是在STM32F10x系列的MCU,时钟的情况如下:
时钟
我使用的是SPI3,初始化代码如下:

void SPI3_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    SPI_InitTypeDef   SPI_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);;//初始化SPI发送IO口

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3,ENABLE);
    SPI_I2S_DeInit(SPI3);
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;//SPI单线发送
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//发送数据宽为8Bit
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//36/4=9M,则传输1Bit时间=111ns
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 10;
    SPI_Init(SPI3, &SPI_InitStructure);
	SPI3->CR1 &= ((uint16_t)0xDFFF);//禁止CRC发送
		
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
	DMA_DeInit(DMA2_Channel2); 	
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI3->DR;  //外设地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //DMA传输方向
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)PixelBuffer;
	DMA_InitStructure.DMA_BufferSize = 0;  //需要发送的大小为0,初始不执行发送操作	
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //外设为发送数据8bit宽
	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //Ram存储数据8Bit宽
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High ; 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  
	DMA_Init(DMA2_Channel2, &DMA_InitStructure); 	
	
	SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE); //使能SPI使用DMA通道发送
	SPI_Cmd(SPI3, ENABLE);//使能SPI控制器
}

当我们发送使用上述配置的SPI发送0xe0(11100000)时,SPI发送引脚高电平持续时间为111ns3=333ns,低电平时间持续的时间为111ns5=550,因为SPI为逐字节发送,用示波器量得SPI发送字节之间的间隙时间大约是100ns左右,则连续发送8字节数,则低电平持续的时间为550ns+100ns=650ns,正符合WS281xB对0码的要求。
于是,我们用发送一字节数据0xe0来模拟发送一个0码。同理可得用0xFC发送1码。用连续310个0x00来模拟发送RESET码。代码如下:

unsigned char PixelBuffer[PixelNumber*24+310] = {0};

void DMA2_Star_SPI_TX()
{
		DMA2_Channel2->CNDTR=(PixelNumber*24+310); 
		DMA2_Channel2->CMAR=(uint32_t)PixelBuffer;	
	
		DMA_Cmd(DMA2_Channel2,ENABLE); //使能SPI3的DMA发送	
	    while(!DMA_GetFlagStatus(DMA2_FLAG_TC2)); //循环等待发送完成,此时如果被中断打断,并不影响发送
		DMA_Cmd(DMA2_Channel2,DISABLE); 
		DMA_ClearFlag(DMA2_FLAG_TC2); 	  
		return;
} 

void Set_All_Pixel_Color(uint8_t r, uint8_t g, uint8_t b)
{
	int i=0;
	for (i = 0; i < 64; i++)//灯带上有64个灯珠
    {
        Ws281x_Set_Pixel(Color_Show(r,g,b),i);
    }
}

void Ws281x_Set_Pixel(uint32_t color,uint32_t position)//
{
	unsigned int positionin=position*3;//一个灯珠3种颜色
	uint8_t Red, Green, Blue; 
	
	Red   = color>>16;
	Green = color>>8;
	Blue  = color;
	
	Ws281x_Set_Bits(Green,positionin);
	Ws281x_Set_Bits(Red,positionin+1);
    Ws281x_Set_Bits(Blue,positionin+2);
}

void Ws281x_Set_Bits(uint8_t bits,uint32_t position)
{
	unsigned int positionin=0;	
	int zero = 0xe0; //11100000
	int one = 0xfC;  //11111100
    int i = 0x00;
	int j = 0x00;	
	positionin=position*8;//一个灯珠上的一种颜色,需要8位数表示亮度
    for (i = 0x80; i >= 0x01; i >>= 1)
    {		
    	PixelBuffer[position+j]=((bits & i) ? one : zero);
		j++;
    }
}
uint32_t Color_Show(uint8_t r, uint8_t g, uint8_t b)
{
  return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b;
}

下面的代码片是我们测试灯带的主函数,主要实现的是三色循环点亮。

int main(void)
{
	int i=0;
	unsigned int PixColorDa=0;
	uint8_t Red, Green, Blue;
	
	SPI3_Init();//初始化SPI3
	while(1)
	{
		for(i=0;i<3;i++{
			PixColorDa=(0xff<<(8*i));//逐次点亮红绿蓝
			
			Blue=(PixColorDa>>16)&0xff;
			Green=(PixColorDa>>8)&0xff;
			Red=(PixColorDa)&0xff;
			Set_All_Pixel_Color(Red,Green,Blue);//设置灯带上所有灯的颜色为红色
			DMA2_Star_SPI_TX();//发送数据
			sleep(1);//睡眠1秒,自己实现此函数
		}				
	}
}

十六宿舍 原创作品,转载必须标注原文链接。
©2023 Yang Li. All rights reserved.
欢迎关注『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。

### 关于WS2815灯带编程方法 #### 使用Arduino控制WS2815灯带 对于使用Arduino控制WS2815灯带,推荐采用`FastLED`库。此库不仅支持多种类型的LED灯带,而且具有丰富的功能集,能够实现复杂的光效果。 ```cpp #include <FastLED.h> #define DATA_PIN 6 #define COLOR_ORDER GRB #define CHIPSET WS2815 #define NUM_LEDS 30 CRGB leds[NUM_LEDS]; void setup() { FastLED.addLeds<CHIPSET, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS); } void loop() { static uint8_t hue = 0; // Fill the LEDs with a rainbow effect fill_rainbow(leds, NUM_LEDS, hue++); // Show the color on all pixels FastLED.show(); } ``` 这段代码展示了如何初始化并设置一个简单的彩虹渐变效果给WS2815灯带[^1]。 #### 使用Raspberry Pi控制WS2815灯带 当利用Raspberry Pi作为控制器时,则可借助`rpi-ws281x`库来操作WS2815灯带。安装完成后,可通过Python脚本来定义各种显示模式。 首先需要确保已经按照官方指南正确安装了`rpi-ws281x-python`包: ```bash sudo pip3 install rpi_ws281x adafruit-circuitpython-neopixel ``` 接着,在Python环境中编写如下所示的例子以展示基本的颜色变化逻辑: ```python import time from rpi_ws281x import PixelStrip, Color # LED strip configuration: LED_COUNT = 30 # Number of LED pixels. LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!). LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) LED_DMA = 10 # DMA channel to use for generating signal (try 10) LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS) strip.begin() def wheel(pos): """Generate rainbow colors across 0-255 positions.""" if pos < 85: return Color(pos * 3, 255 - pos * 3, 0) elif pos < 170: pos -= 85 return Color(255 - pos * 3, 0, pos * 3) else: pos -= 170 return Color(0, pos * 3, 255 - pos * 3) for i in range(strip.numPixels()): strip.setPixelColor(i, wheel((int)(i*256/strip.numPixels()))) strip.show() time.sleep(2) ``` 上述实例实现了整个灯带上颜色由一端向另一端过渡的效果[^4]。
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值