WS281xUKit评估学习板入门指南
WS281xUKit评估学习板入门指南
第一部分、序
由于作者水平有限,文档和视频中难免有出错和讲得不好的地方,欢迎各位读者和观众善意地提出意见和建议,谢谢!
第二部分、硬件概述
WS2812B简介
-
WS2812B幻彩灯珠是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同,每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路,还包含有高精度的内部振荡器和12V高压可编程定电流控制部分,有效保证了像素点光的颜色高度一致。
-
数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
-
LED具有低电压驱动,环保节能,亮度高,散射角度大,一致性好,超低功率,超长寿命等优点。将控制电路集成于LED上面,电路变得更加简单,体积小,安装更加简便。
时序波形图
数据传输时间 (时序)
评估板原理图
提供PDF,如看不清请查阅PDF文档。
第三部分、基础篇
3.1 配置时序
本节将配置时序波形图,实现0码和1码波形输出
3.1.1硬件设计:
如下图,PC6可以配置SPI的MOSI,如下图所示
同时我们通过拨动开关可选PWM和SPI模式,如下图,并把DIN数据信号引出,方便抓取波形和驱动其他WS2812模块。
3.1.2 软件设计:
3.1.2.1初始化代码配置
如下代码是初始化PC6为SPI DMA模式,并且是SPI_Direction_1Line_Tx,
void ws281x_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //PORTA时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //SPI1时钟使能
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
/* PC6 SPI1_MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第2个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为8
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
DMA_DeInit(DMA1_Channel3); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DATAR); //cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel3, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
ws281x_closeAll(); //关闭全部的灯
Delay_Ms(100); //关闭全部的灯需要一定的时间
}
3.1.2.2关于SPI时钟的配置
首先是系统时钟system_ch32v00x.c中,配置HSI:48Mhz,
那么SPI的8分频为6Mhz,
那么T=1/f=1/6=0.1666666666666667us;
SPI发送1个字节为8位,1位0.1666us,那么根据WS2812的波形图,
-
1码:T1H≈T1L;那么0.1666us*4=0.666us;
-
0码:T0H那么0.1666us*2=0.3333us;
也就是说1码只要发0xF0,而0码发0xC0;
3.1.2.3WS2812驱动代码实现
pixelBuffer是要通过SPI DMA发送出去的缓存区,WS_HIGH和WS_LOW对应1码和0码,ws281x_closeAll是全发1码的,最后直接在main中while调用ws281x_closeAll
#define PIXEL_NUM 16
//硬件spi模拟ws2812时序(用spi的8位数据模拟ws281x的一位数据)
//要将系统时钟设置为48M,分频数设置为8,则SPI的通信频率为6M,传输一位数据的时间约为166纳秒(ns)
//4*166 = 666ns 2*166 = 333ns 符合WS281X芯片的通信时序。
// _____
// | |___| 11110000 high level
// ___
// | |_____| 11000000 low level
#define WS_HIGH 0XF0
#define WS_LOW 0XC0
uint8_t pixelBuffer[PIXEL_NUM][24] ;
void ws281x_closeAll(void)
{
uint16_t i;
uint8_t j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < 24; ++j)
{
pixelBuffer[i][j] = WS_LOW;
}
}
ws281x_show();
}
3.1.3 下载验证:
我们就可以把代码下载到开发板上,可以抓到0码如下,
ws281x_closeAll中WS_LOW改成WS_HIGH的得到1码时序如下,如无意外灯亮全白。
3.2 实现七彩跑马灯
上节实现0码和1码波形输出,成功关闭和点亮(全白)了2812,这节我们实现七彩跑马灯并介绍几个函数。
3.2.1软件设计:
3.2.1.1设置单个像素的颜色
/**
* 设置单个像素的颜色
*
* @param n 像素索引
* @param GRBcolor 颜色值,使用GRB格式
*/
void ws281x_setPixelColor(uint16_t n, uint32_t GRBcolor)
{
uint8_t i;
// 检查像素索引是否在有效范围内
if(n < PIXEL_NUM)
{
// 遍历24位颜色值
for(i = 0; i < 24; ++i)
{
// 提取颜色值的每个位并设置到像素缓冲区
pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
}
}
}
3.2.1.2以指定颜色进行填充动画效果
/**
* 以指定颜色进行填充动画效果
*
* @param c 填充的颜色,使用GRB格式
* @param wait 每个像素之间的延迟时间,以毫秒为单位
*/
void ws281x_colorWipe(uint32_t c, uint8_t wait) {
// 遍历所有像素
for(uint16_t i = 0; i < PIXEL_NUM; i++) {
// 设置像素颜色
ws281x_setPixelColor(i, c);
// 更新LED显示
ws281x_show();
// 延迟指定时间
Delay_Ms(wait);
}
}
3.2.1.2以指定颜色进行填充动画效果
/**
* 更新LED显示,使用DMA传输数据
*/
void ws281x_show(void)
{
// 关闭USART1 TX DMA1 所指示的通道
DMA_Cmd(DMA1_Channel3, DISABLE);
// 清除DMA1通道3传输完成标志
DMA_ClearFlag(DMA1_FLAG_TC3);
// 设置DMA通道3的当前传输数据计数器为总像素数乘以每像素的位数(24位)
DMA_SetCurrDataCounter(DMA1_Channel3, 24 * PIXEL_NUM);
// 使能USART1 TX DMA1 所指示的通道
DMA_Cmd(DMA1_Channel3, ENABLE);
}
3.2.2 下载验证:
我们就可以把代码下载到开发板上,可以看到赤橙黄绿青蓝紫循环跑马灯
3.3 实现theaterChase
上节我们实现七彩跑马灯并介绍几个函数,本节介绍theaterChase风格的闪烁灯光效果
3.3.1软件设计:
3.3.1.1影院风格的闪烁灯光效果
/**
* 影院风格的闪烁灯光效果
*
* @param c 闪烁灯光的颜色,使用GRB格式
* @param wait 每次闪烁之间的延迟时间,以毫秒为单位
*/
void ws281x_theaterChase(uint32_t c, uint8_t wait) {
for (int j = 0; j < 10; j++) { // 执行10个循环的闪烁效果
for (int q = 0; q < 3; q++) {
for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3) {
ws281x_setPixelColor(i + q, c); // 打开每隔三个像素的LED
}
ws281x_show();
Delay_Ms(wait);
for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3) {
ws281x_setPixelColor(i + q, 0); // 关闭每隔三个像素的LED
}
}
}
}
3.3.2 下载验证:
我们就可以把代码下载到开发板上,可以看到影院风格的闪烁灯光效果;
3.4 实现彩虹色环的变化
上节我们实现theaterChase灯,本节实现彩虹色环的变化灯光效果
3.4.1软件设计:
3.4.1.1ws281x_rainbow
/**
* 实现彩虹色环的变化
* @param wait 控制彩虹切换速度
*/
void ws281x_rainbow(uint8_t wait) {
uint16_t i, j;
// 外层循环,控制彩虹色环的变化
for (j = 0; j < 256; j++) {
// 内层循环,遍历每个像素点
for (i = 0; i < PIXEL_NUM; i++) {
// 计算当前像素点的彩虹色索引,并设置对应的颜色
ws281x_setPixelColor(i, ws281x_wheel((i + j) & 255));
}
// 显示当前设置的像素点颜色
ws281x_show();
// 延时一段时间,以控制彩虹切换速度
Delay_Ms(wait);
}
}
3.4.1.2 ws281x_wheel
// 根据给定的彩虹色环位置,返回对应的彩虹颜色值
uint32_t ws281x_wheel(uint8_t wheelPos) {
// 将输入的位置映射为反向的颜色位置,以实现彩虹色环的旋转
wheelPos = 255 - wheelPos;
// 根据位置计算不同的彩虹颜色
if(wheelPos < 85) {
// 当位置小于 85 时,生成从红色到绿色的过渡色
return ws281x_color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if(wheelPos < 170) {
// 当位置在 85 到 169 之间时,生成从绿色到蓝色的过渡色
wheelPos -= 85;
return ws281x_color(0, wheelPos * 3, 255 - wheelPos * 3);
}
// 当位置在 170 到 255 之间时,生成从蓝色到红色的过渡色
wheelPos -= 170;
return ws281x_color(wheelPos * 3, 255 - wheelPos * 3, 0);
}
3.4.2 下载验证:
我们就可以把代码下载到开发板上,可以看到彩虹色环的变化灯光效果;
3.5 实现循环的彩虹效果
上节实现彩虹色环的变化灯光效果,本节循环的彩虹效果;
3.5.1软件设计:
3.5.1.1ws281x_rainbowCycle
// Slightly different, this makes the rainbow equally distributed throughout
void ws281x_rainbowCycle(uint8_t wait) {
static uint16_t j; // 静态变量用于记住彩虹的当前位置
uint16_t i;
// 在彩虹色环上循环,一共进行 256 * 5 次循环
while (j++ < 256 * 5) {
for (i = 0; i < PIXEL_NUM; i++) {
// 计算当前像素点的彩虹颜色,并设置颜色
ws281x_setPixelColor(i, ws281x_wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
// 显示当前设置的像素点颜色
ws281x_show();
// 延时一段时间,控制彩虹循环的速度
Delay_Ms(wait);
}
}
3.5.2 下载验证:
我们就可以把代码下载到开发板上,可以看到循环的彩虹灯光效果;
3.6 实现循环的彩虹效果
上节循环的彩虹效果,本节实现带有彩虹效果的剧院式爬行灯
3.6.1软件设计:
3.6.1.1 ws281x_theaterChaseRainbow
// 此函数使用WS281x RGB LED创建了一个彩虹色的剧院追逐效果。
// 函数接受一个名为 'wait' 的参数,用于确定每个动画步骤之间的延迟。
// 动画循环遍历了彩色轮中的所有256种颜色。
void ws281x_theaterChaseRainbow(uint8_t wait) {
for (int j = 0; j < 256; j++) { // 遍历彩色轮中的所有256种颜色
for (int q = 0; q < 3; q++) { // 在每个像素组中迭代三个位置
for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3) {
ws281x_setPixelColor(i + q, ws281x_wheel((i + j) % 255)); // 将每隔三个像素的颜色设置为彩色轮中的颜色
}
ws281x_show(); // 显示像素的当前状态
Delay_Ms(wait); // 等待指定的延迟时间
for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3) {
ws281x_setPixelColor(i + q, 0); // 关闭每隔三个像素
}
}
}
}
3.6.2 下载验证:
我们就可以把代码下载到开发板上,彩虹效果的剧院式爬行灯效果;