基于STM32G031核心模块制作WS2812彩灯控制板
基于STM32G031核心模块制作WS2812彩灯控制板,可以通过红外遥控或者蓝牙控制WS2812的显示(Demo板为5*8点阵)
标签
蓝牙
红外
STM32G031核心模块
WeDesign第四期
WS2812彩灯控制器
Snapdragon
更新2023-09-08
1670

一、项目介绍

    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的连接和摆放;四是电源部分。

        红外、蓝牙模块:由于红外和蓝牙使用集成度较高的方案,此次设计采用排母接插件的方式去连接,外加两颗电容保证稳定性。

FoqK4-Ah-pDGy0Mr212NS17jYMLx

        如上图,红外模块的IO串联一个电阻,可以一定程度上保护单片机的IO。

        主控模块:

FrwyY3TOSUqEaFYNiqmHRGNa4OtW

        上图是STM32CubeMX的图形化引脚配置界面。如图,PB8控制板载LED;UART1(PB6、PB7)控制蓝牙;SWD(PA13、PA14)用来在线调试和下载;PA12用来读取扩展板的按键;PB1连接到红外模块;UART2(PA2、PA3)连接到板载的CH340E;SPI1的MOSI(PA7)用来产生控制WS2812的时序。

Fgkw_bJmTa8tWvtco79bXZJCrkpf

        由于STM32G031模块没有通过排针引出SWD接口,只是预留了两个小焊盘,上图就是通过飞线引出SWCLK和SWDIO供调试使用。

        WS2812的连接和摆放:这里重点介绍下WS2812的摆放,由于WS2812的数据输入和输出引脚是斜对着的,所以这里采用”之“字形摆放和布线,如下图:

FtWYx2-0iF9hs1VvwdpSddyXIobm

        如上图,一排的WS2812串联起来后,没有跳转到另一行的开始去走,而是就近连接,这样的优点是方便电源走线,且可以避免信号线过长导致的干扰问题;缺点就是软件层面驱动的时候需要多加处理。

        电源:由于STM32G031模块没有引出5V电源的引脚,这里扩展板采用的Type-C单独供电,可以保证WS2812在最高亮度时也可以正常工作。

Fv9_8wsnyawerK-26AYkWffs4A6u

    2、软件

        先看设计框图

Fn0_eWTsg5WC68MpnbyOgPtEHsEp

        这里的流程图只是一个整体的流程,具体的控制原理见下面详细的介绍。简单说就是上电初始化完成之后,不断检测有没有红外遥控器触发或者收到串口消息,如果有一个触发了,就根据不同的键值操作WS2812显示特定的内容或者对其进行显示颜色等参数的控制。

    3、原理图和PCB

FlmqCKSols-Cy5jEdVhiUnImUFNb

Fr-2GRtF64VSZCr_79f81GWdSSI4

        以上是完整的原理图和PCB截图。供参考。

三、搜集素材的思路

    1、硬件

        硬件上搜集素材的途径主要是:

        ①、官方资料。比如STM32G031模块的原理图,就是在电子森林官网上查找。

        ②、之前的项目经验。WS2812之前做过16*16的大点阵,有丰富的布局经验。

    2、软件

        软件上搜集素材的途径主要是

        ①、官方资料及工具。本次使用的主控STM32G031可以参考ST官方的CubeMX工具,引脚配置、初始化代码都可以很方便的完成。

        ②、之前的项目经验。蓝牙模块不止一次使用,踩过很多坑,也有Demo例程。

        ③、搜索引擎。本次调试红外模块就是靠搜索引擎了解到NEC编码,后编写解码驱动。

四、实现结果展示

    1、WS2812点阵显示字符

FtLOYT_N3fr6yU8FteIhVXpm4-CX

        如上图所示,点阵显示了字符‘1’,字符可以通过红外遥控器或者蓝牙进行控制。

    2、WS2812点阵切换颜色

FuTaUwnY2oS7qMFXczpAvZMIR3OA

        如上图,按下特定的按键或者发送特定的字符,会进行显示的颜色切换,变化顺序为白(RGB)-> 红 -> 绿 -> 蓝 -> 黄(RG) -> 品红(RB)-> 青(GB)。例子是由白色切换至红色。

    3、WS2812点阵简易流水灯

Fhkh_xEVFDLMgntJLXHuGwtQvrEA

        上图是切换至流水灯模式,流水灯按照”之“字形循环点亮,具体见视频。

    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图、源代码

    见附件。

附件下载
WeDesign4_KiCAD.zip
原理图、PCB源文件
WeDesign4_ws2812_v0.2.7z
源代码
团队介绍
陈海
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号