一、项目介绍
1、简介
此项目是一个可远程控制的WS2812彩灯控制器,经调查市面上基本上都是由红外遥控器或者蓝牙去控制,所以本项目集成了红外接收模块和蓝牙透传模块。
2、WS2812简介
WS2812集成了红、绿、蓝灯珠和恒流控制单元。主控制器可以通过特殊的编码对其进行编程。此项目使用的WS2812是经典的5050封装,有四个引脚:电源、地、数据输入和数据输出。WS2812最大的特点就是可以级联,官方标称在刷新率30fps时,最大可级联1024个。
3、红外模块简介
此项目红外接收器采用一体式的接收头,不需要额外的外围电路,方便、稳定。红外编码采用标准的NEC编码,可以使用外部中断+定时器的方式去解码。
4、蓝牙模块简介
此项目采用的蓝牙模块是BT-06,用这款模块的原因是我手头有一个,而且还有调试经验和代码。此模块使用非常简单,进入透传模式后就按照普通串口去操作。
5、主控模块简介
主控模块就是本次WeDesign活动的核心模块,STM32G031最小系统。模块采用的是STM32G031G8U6,UFQFN28封装,拥有64KiB的Flash和8KiB的RAM,对于一个小封装的芯片来说足够多了;除此之外,它的主频也能达到64MHz,可以应付绝大部分的工作。
除了主控芯片外,模块还板载了一个CH340E串口芯片,配合MicroUSB接口可以做串口调试,再配合板载的RST按键、BOOT按键,还可以做程序下载。板载两颗LED,一颗用来指示电源,一颗是用户LED,由用户控制。
二、项目设计思路
1、硬件
硬件设计包含四个部分:一是红外、蓝牙模块;二是主控模块;三是WS2812的连接和摆放;四是电源部分。
红外、蓝牙模块:由于红外和蓝牙使用集成度较高的方案,此次设计采用排母接插件的方式去连接,外加两颗电容保证稳定性。
如上图,红外模块的IO串联一个电阻,可以一定程度上保护单片机的IO。
主控模块:
上图是STM32CubeMX的图形化引脚配置界面。如图,PB8控制板载LED;UART1(PB6、PB7)控制蓝牙;SWD(PA13、PA14)用来在线调试和下载;PA12用来读取扩展板的按键;PB1连接到红外模块;UART2(PA2、PA3)连接到板载的CH340E;SPI1的MOSI(PA7)用来产生控制WS2812的时序。
由于STM32G031模块没有通过排针引出SWD接口,只是预留了两个小焊盘,上图就是通过飞线引出SWCLK和SWDIO供调试使用。
WS2812的连接和摆放:这里重点介绍下WS2812的摆放,由于WS2812的数据输入和输出引脚是斜对着的,所以这里采用”之“字形摆放和布线,如下图:
如上图,一排的WS2812串联起来后,没有跳转到另一行的开始去走,而是就近连接,这样的优点是方便电源走线,且可以避免信号线过长导致的干扰问题;缺点就是软件层面驱动的时候需要多加处理。
电源:由于STM32G031模块没有引出5V电源的引脚,这里扩展板采用的Type-C单独供电,可以保证WS2812在最高亮度时也可以正常工作。
2、软件
先看设计框图
这里的流程图只是一个整体的流程,具体的控制原理见下面详细的介绍。简单说就是上电初始化完成之后,不断检测有没有红外遥控器触发或者收到串口消息,如果有一个触发了,就根据不同的键值操作WS2812显示特定的内容或者对其进行显示颜色等参数的控制。
3、原理图和PCB
以上是完整的原理图和PCB截图。供参考。
三、搜集素材的思路
1、硬件
硬件上搜集素材的途径主要是:
①、官方资料。比如STM32G031模块的原理图,就是在电子森林官网上查找。
②、之前的项目经验。WS2812之前做过16*16的大点阵,有丰富的布局经验。
2、软件
软件上搜集素材的途径主要是
①、官方资料及工具。本次使用的主控STM32G031可以参考ST官方的CubeMX工具,引脚配置、初始化代码都可以很方便的完成。
②、之前的项目经验。蓝牙模块不止一次使用,踩过很多坑,也有Demo例程。
③、搜索引擎。本次调试红外模块就是靠搜索引擎了解到NEC编码,后编写解码驱动。
四、实现结果展示
1、WS2812点阵显示字符
如上图所示,点阵显示了字符‘1’,字符可以通过红外遥控器或者蓝牙进行控制。
2、WS2812点阵切换颜色
如上图,按下特定的按键或者发送特定的字符,会进行显示的颜色切换,变化顺序为白(RGB)-> 红 -> 绿 -> 蓝 -> 黄(RG) -> 品红(RB)-> 青(GB)。例子是由白色切换至红色。
3、WS2812点阵简易流水灯
上图是切换至流水灯模式,流水灯按照”之“字形循环点亮,具体见视频。
4、调试过程中碰到的问题
碰到最大的问题就是点阵字库的问题,常用的字库生成器只能生成常见字体,比如宋体、微软雅黑的字库,对于此扩展板5x8的像素数量,显示的效果非常差,辨识度很低。自己去设计一套字库费时费力,好在很快用搜索引擎找到了一篇文章,上面有5x8点阵的字库,拷贝下来测试发现显示效果很完美。
其次就是ws2812驱动的问题,ws2812的时序要求很严格,所以这里采用的是SPI硬件模拟时序,网上SPI驱动的通用方式是将点阵数据转换成缓存,再利用DMA传输。这样的优势是CPU占用低,时序严格;但缺点也很明显,对RAM的消耗量很大,哪怕使用ping-pong操作,驱动1024个级联的ws2812,对于STM32G031来说也无法实现。
除了上面的软件问题,BT-06蓝牙模块在使用过程中也出现了问题,第一次连接到扩展板上时接反了,排查了好久才发现,等到发现并更正连接后,发现已经无法使用,可能是时间太长老化,也可能是接反的时候烧掉了io,导致视频演示只能采用串口模拟。
五、关键代码及说明
1、红外驱动代码
__STATIC_INLINE void IR_DelayUs() // 延迟140us
{
SysTick->LOAD = 7465;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk;
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk))
{
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
__STATIC_INLINE void IR_DelayMs() // 延迟9.8ms
{
SysTick->LOAD = 522000; //522665;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk;
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk))
{
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
#define READ_IR_PIN() (IR_GPIO_Port->IDR & IR_Pin)
static int irTrigger = 0;
static uint32_t time = 0;
uint8_t irValue[6];
// 1: 有误
// 2: 验证失败
// 3: 成功接收
// 4: 有误
/*
* 电源:0x45 -> P
* Mode:0x46 -> M
* 静音:0x47 -> S
* 暂停:0x44 -> T
* 快退:0x40
* 快进:0x43
* EQ :0x07
* 音减:0x15
* 音加:0x09
* 0-9 :0x16、0x0C、0x18、0x5E、0x08、0x1C、0x5A、0x42、0x52、0x4A
* RPT :0x19
* USD :0x0D
*/
char IR_Update()
{
char val = 0;
if (irTrigger)
{
printf("ir Trigger Type: %d\r\n", irTrigger);
printf("ir Value: 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n\r\n", irValue[0], irValue[1], irValue[2], irValue[3]);
if (irTrigger == 3)
{
switch (irValue[2])
{
case 0x16: val = '0'; break;
case 0x0C: val = '1'; break;
case 0x18: val = '2'; break;
case 0x5E: val = '3'; break;
case 0x08: val = '4'; break;
case 0x1C: val = '5'; break;
case 0x5A: val = '6'; break;
case 0x42: val = '7'; break;
case 0x52: val = '8'; break;
case 0x4A: val = '9'; break;
case 0x45: val = 'C'; break;
case 0x46: val = 'S'; break;
case 0x07: break;
case 0x47: val = 'A'; break;
default: break;
}
}
irTrigger = 0;
irValue[2] = 0;
}
return val;
}
#define ERR_VAL 100000
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
uint32_t i, j, err;
__disable_irq();
time = 0;
IR_DelayMs();
if (!READ_IR_PIN())
{
err = ERR_VAL;
while ((!READ_IR_PIN()) && (err > 0))
{
IR_DelayUs();
err--;
}
if (READ_IR_PIN())
{
err = ERR_VAL / 2;
while (READ_IR_PIN() && (err > 0))
{
IR_DelayUs();
err--;
}
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 8; ++j)
{
err = ERR_VAL / 16;
while ((!READ_IR_PIN()) && (err > 0))
{
IR_DelayUs();
err--;
}
err = ERR_VAL / 2;
while (READ_IR_PIN() && (err > 0))
{
IR_DelayUs();
time++;
err--;
if (time > 30)
{
__HAL_GPIO_EXTI_CLEAR_FALLING_IT(IR_Pin);
__enable_irq();
irTrigger = 1;
return;
}
}
irValue[i] >>= 1;
if (time >= 8)
{
irValue[i] |= 0x80;
}
time = 0;
}
}
}
if (irValue[2] != (uint8_t)~irValue[3])
irTrigger = 2;
else
irTrigger = 3;
}
if (!irTrigger)
irTrigger = 4;
__HAL_GPIO_EXTI_CLEAR_FALLING_IT(IR_Pin);
__enable_irq();
}
以上是红外遥控接收模块的代码,精准延迟采用的是SysTick系统定时器,利用IO中断捕获红外接收的数据,并将数据存入变量中,最后在轮询检测函数IR_Update中处理接收到的数据,并返回设置好的字符。
2、串口驱动代码
串口驱动是CubeMX生成的代码,这里只需要调用HAL库中中断接收的函数。下面是简单的示例。
HAL_UART_Receive_IT(&huart2, uart2_RxBuffer, 1);
while (1)
{
if (uart2_Rx)
{
if (uart2_Rx)
{
uart2_Rx = 0;
HAL_UART_Receive_IT(&huart2, uart2_RxBuffer, 1);
}
}
}
3、WS2812驱动代码
#define SPI_WRITE(DATA) do { \
while (!(SPI1->SR & SPI_FLAG_TXE)) \
{ \
} \
*((__IO uint8_t *)(&SPI1->DR)) = (DATA); \
} while (0)
void WS2812_WriteColors(uint32_t leds, uint32_t colors[])
{
__HAL_SPI_ENABLE(&hspi1);
for (register int i = 0; i < leds; ++i)
{
register uint32_t color = colors[i];
for (register int j = 22; j >= 0; j -= 2) // 22 20 18 16 14 12 10 8 6 4 2 0
{
register uint32_t data;
switch ((color >> j) & 0x03)
{
case 0x00:
data = 0x88;
break;
case 0x01:
data = 0x8E;
break;
case 0x02:
data = 0xE8;
break;
case 0x03:
data = 0xEE;
break;
default:
data = 0x88;
break;
}
SPI_WRITE(data);
}
}
HAL_Delay(1);
}
以上是WS2812刷新的函数,这里采用的是阻塞的方式进行。因为此扩展板的WS2812数量较少,阻塞方式效率相对较高。
void WS2812_Show(uint8_t dot[5], uint32_t color, int dir)
{
uint32_t buffer[40];
for (int i = 0; i < 5; ++i)
{
uint8_t data = dot[i];
for (int j = 0; j < 8; ++j)
{
int tmp = dir ? (data & (1 << j)) : (data & (1 << (7 - j)));
if (tmp)
buffer[8 * i + j] = color;
else
buffer[8 * i + j] = 0;
}
}
WS2812_WriteColors(40, buffer);
}
void WS2812_ShowChar(uint8_t font_8x5[95][5], int ch, uint32_t color)
{
static const uint8_t asciiRrror[1][5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
static const uint8_t asciiClear[1][5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
if (ch == 0)
{
font_8x5 = (uint8_t (*)[5])asciiClear;
ch = ' ';
}
else if (ch < ' ' || ch > '~')
{
font_8x5 = (uint8_t (*)[5])asciiRrror;
ch = ' ';
}
WS2812_Show(font_8x5[ch - ' '], color, 0);
}
上面是WS2812显示字符的代码,WS2812_Show负责显示一个5*8的点阵数据,WS2812_ShowChar则从字库中选择一个合适的点阵数据传给WS2812_Show。
void WS2812_Demo(int enable, uint32_t color)
{
static uint32_t lastTick = 0;
static uint32_t buffer[40] = { 0 };
static uint32_t index = 0;
if (enable)
{
if (HAL_GetTick() - lastTick > 100)
{
lastTick = HAL_GetTick();
buffer[index++] = color;
WS2812_WriteColors(40, buffer);
if (index > 39)
{
index = 0;
memset(buffer, 0, 40 * 4);
}
}
}
}
上面是WS2812流水灯的代码,流水灯每隔100毫秒更新一次显示。
4、功能实现代码
static int demoEnable = 0;
const uint32_t colorTable[] = { 0x000500, 0x050000, 0x000005, 0x050500, 0x000505, 0x050005, 0x050505 };
static uint32_t colorIndex = 6;
while (1)
{
const char irVal = IR_Update();
if (uart2_Rx || irVal)
{
if (uart2_Rx)
{
uart2_Rx = 0;
HAL_UART_Receive_IT(&huart2, uart2_RxBuffer, 1);
}
char ch = irVal ? irVal : uart2_RxBuffer[0];
if (ch == 'A')
{
demoEnable = !demoEnable;
WS2812_ShowChar(ascii_8x5, 0xFF, colorTable[colorIndex]);
}
else if (ch == 'C')
{
WS2812_ShowChar(ascii_8x5, 0, colorTable[colorIndex]);
}
else if (ch == 'S')
{
colorIndex++;
if (colorIndex > 6)
colorIndex = 0;
WS2812_ShowChar(ascii_8x5, 0xFF, colorTable[colorIndex]);
}
if (!demoEnable)
{
if (ch >= '0' && ch <= '9')
{
WS2812_ShowChar(ascii_8x5, ch, colorTable[colorIndex]);
}
}
}
WS2812_Demo(demoEnable, colorTable[colorIndex]);
上面是实现操作逻辑的展示代码,这里将红外接收到的键值和串口接收到的数据做了统一,方便处理。接收到字符‘A’,则在字符显示模式和流水灯中切换;接收到字符‘C'清屏;接收到字符’S‘切换颜色。在字符显示模式下,接收到’0‘~’9‘则显示对应的字符。
六、STM32G031核心模块的优势和局限
1、优势
本次活动完成后,感觉此核心模块的优势是:
①、体积小巧,方便嵌入;主控性能强,资源多。
②、板载串口及下载电路,方便调试,可以精简扩展板电路。
③、有邮票孔,适合多种板对板连接方式。
2、局限
①、没有使用排针引出SWD调试接口,在线调试下载比串口下载更便捷。
②、MicroUSB母座可以换成Type-C,供电能力、连接稳定性都会有一定的提升。
③、没有引出5V VBUS电源。如果有引出,做一些小型扩展板会很方便。
七、原理图、PCB图、源代码
见附件。