硬件介绍:WeDesign活动是硬禾学堂发起的“一起设计、一起体验”活动。同学们可以通过提交申请来获取芯片、仪器、工具等。如果你能在规定时间内完成相应的任务,就可以获取补贴费用,优质作品更是有丰厚的奖励。第四期是基于基于STM32G031的最小系统模块。主控芯片为STM32G031G8U6,运行Arm Cortex M0+内核,工作频率为64MHz,通过USB供电和配置,最多支持18个输入输出,其中6个可以为模拟输入。之前购买了一些WS2812的LED灯,就想着用这个核心板,制作一个氛围灯,摆在桌面上,给电脑桌添加一点点“颜色”。
任务选择:自己定了个小目标:学习HAL库编程。驱动WS2812灯。扩展目标,学习麦克风的使用。
硬件制作:收到了模块,这是个基于STM32G031G8U6制作好了的最小板子。板子上集成了串口芯片,这样就可以直接使用串口来烧写芯片了。
硬件整体比较简单,WS2812自身就有处理数据的功能,所以多颗WS2812只需要串联在数据线上即可。麦克风不是太会用,参考了网上找的电路图。我的理解是将麦克风和电阻串联在电源中,当麦克风收到信号时,容值会变化,导致麦克风两端的电压会变化,然后通过电容耦合到单片机,单片机通过ADC来读取信号。所以我使用A1作为电源驱动麦克风,A0作为信号输入端,用来读取麦克风信号。可惜实际电路验证不成功,读取的AD的值,和音频信号没有啥关系,读到的AD值,感觉是电子噪音。
PCB设计上,使用了圆形的设计,为了使整改板子更加轻薄,底部使用镂空设计,这样就可以把STM32G031G8U6板子,芯片朝上地焊在扩展板上了。模块芯片高度,正好融入扩展板板子的厚度中。可惜WS2812需要5V供电,但是这个最小板模块没有引出5V电源,所以只能使用跳线,从USB口引出5V电源了。板子下边做了截断,然后设计了两个M2的螺丝孔,这样就可以使用铜柱,斜摆在桌面上了。
软件实现:首先来看看ws2812这个led灯。WS2812B 是一种智能控制 LED 光源,将控制电路和 RGB 芯片集成在一个 5050 个组件的封装中。内部包括智能数字端口数据锁存和信号整形放大驱动电路。还包括精密的内部振荡器和电压可编程恒流控制部分,有效保证像素点的光色高度一致。数据传输协议采用单 NZR 通信模式。像素上电复位后,DIN 端口从控制器接收数据,第一像素采集初始 24位数据,然后发送给内部数据锁存器,其他经过内部信号整形放大电路整形后的数据通过 DO 端口发送给下一个级联像素。每传输一个像素后,信号减少 24 位。像素采用自整形传输技术,使得像素级联数不受信号传输的限制,只取决于信号传输的速度。复位时>280us,中断时不会误复位。刷新频率更新至 2KHz,无闪烁,提高了出色的显示效果。
手册上介绍数据发送速度是: 800Kbps。所以每个数据位的时间是: 1/800000-0.00000125s=1.25us。
这 1.25us 可以表示高位或低位,24 1.25us 就是一个灯的颜色,发完一人灯的颜色后需要发送大于 280us 的低电平让数据从锁存器表现在灯上。
数位位 0:周期 1/3 的高电平,1.25*(1/3)us;周期 2/3 的低电平,1.25*(2/3)us。
数位位 1:周期 2/3 的高电平,1.25*(2/3)us;周期 1/3 的低电平,1.25*(1/3)us。
要使用MCU来生成这些led控制的信号,如果使用定时器来产生这样800k赫兹的信号,单片机的资源基本就会被耗尽;加上 WS2812 对时序要求很高,MCU无法去做其它事情了,一旦有中断时序会立刻被打乱。
这里使用STM32G031产生的PWM波,然后通过DMA传送给外设来控制LED灯。STM32G031产生800Khz的方波,通过控制PWM的高低电平占空比,来产生WS2812能解析的1、0信号。直接存储器存取(DMA) 用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无需 CPU 干涉,数据可以通过 DMA 快速移动,这样可以大大节省 CPU 的资源用来做其它操作。
使用STM32CUBEMX来初始化外设,主频使用64M
在PA8上开启pwm,使用tim1时钟,period设置为79。这样pwm的频率就是800KHz,占空比可以从0~79进行调整。然后开启DMA,设置DMA方向从内存到到外设。
开启串口功能,这个最小板,将串口2与USB口连接起来了,所以这里就开启串口2,用来和上位机交互。PA1,PB8作为普通的输出IO口使用,PA0开启ADC功能。
通过控制PWM的高低电平比例,即可给WS2812输出0和1。输出的数据只需要写到缓存,DMA就会自动搬运给外设。这里做了灯逐个点亮和呼吸灯的效果。
#include "ws2812.h"
#include "tim.h"
//低电平偏移,复位
#define WS2812_RST_NUM 300
//数据位
#define WS2812_SET (53) //1
#define WS2812_RSET (26) //0
//定义发送缓冲区
/*
300:低电平占位
(3*8):每个灯珠有3种颜色,每种颜色8位
*/
uint16_t WS2812_GRB_BUF[WS2812_RST_NUM+WS2812_MAX_NUM*(3*8)]={0};
//设置某一位的颜色
/*
uint16_t num:第多少个灯珠
uint8_t rv:红颜色亮度
uint8_t gv:绿颜色亮度
uint8_t bv:蓝颜色亮度
*/
void ws2812_set(uint16_t num,uint8_t rv,uint8_t gv,uint8_t bv){
uint32_t indexx=WS2812_RST_NUM+(num*(3*8));
for (uint8_t i = 0;i < 8;i++)
{
//填充数组
WS2812_GRB_BUF[indexx+i] = (gv << i) & (0x80)?WS2812_SET:WS2812_RSET;
WS2812_GRB_BUF[indexx+i + 8] = (rv << i) & (0x80)?WS2812_SET:WS2812_RSET;
WS2812_GRB_BUF[indexx+i + 16] = (bv << i) & (0x80)?WS2812_SET:WS2812_RSET;
}
}
//关闭所有
void ws2812_set_all_off(void){
for(uint16_t i=0;i<WS2812_MAX_NUM;i++){
ws2812_set(i,0,0,0);
}
}
//开启所有
void ws2812_set_all_on(void){
for(uint16_t i=0;i<WS2812_MAX_NUM;i++){
ws2812_set(i,255,255,255);
}
}
//初始化
void ws2812_init(void){
//设置关闭所有灯
ws2812_set_all_off();
HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t *)WS2812_GRB_BUF,sizeof(WS2812_GRB_BUF)/sizeof(uint16_t));
}
//--------------------灯动作------------------
void effect(char cl)
{
printf("Effect=%d\r\n",cl%6);
for(int i=0;i<WS2812_MAX_NUM;i++){
HAL_Delay(50);
switch(cl%7){
case 0:
ws2812_set(i,122,122,122);
break;
case 1:
ws2812_set(i,122,0,0);
break;
case 2:
ws2812_set(i,0,122,0);
break;
case 3:
ws2812_set(i,0,0,122);
break;
case 4:
ws2812_set(i,122,0,122);
break;
case 5:
ws2812_set(i,122,122,0);
break;
case 6:
ws2812_set(i,0,122,122);
break;
}
}
HAL_Delay(150);
ws2812_set_all_off();
}
void effect2(void){
int n=10;
while(n--){
for(int i=0;i<WS2812_MAX_NUM;i++){
for(int j=0;j<WS2812_MAX_NUM;j++){
ws2812_set(j,11,11,11);
}
ws2812_set(i,10,10,0);
ws2812_set(i+1,30,30,0);
ws2812_set(i+2,50,50,0);
ws2812_set(i+3,100,0,0);
i+=4;
HAL_Delay(100);
}
}
}
void effect3(void){
int n=10;
uint8_t halv=WS2812_MAX_NUM/2/2;
//开灯
for(uint8_t i=0;i<halv;i++){
ws2812_set(halv+i,n*15,n+i*2,i*5);
ws2812_set(halv-i,n*15,n+i*2,i*5);
ws2812_set(halv*3+i,n*15,n+i*2,i*5);
ws2812_set(halv*3-i,n*15,n+i*2,i*5);
HAL_Delay(1000);
}
//顶部点亮时闪烁
ws2812_set(0,255,255,255);
ws2812_set(halv*2,255,255,255);
HAL_Delay(300);
ws2812_set(0,0,0,0);
ws2812_set(halv*2,0,0,0);
HAL_Delay(300);
ws2812_set(0,255,255,255);
ws2812_set(halv*2,255,255,255);
//延时效果
HAL_Delay(2000);
//关灯
ws2812_set(0,0,0,0);
ws2812_set(halv*2,0,0,0);
for(uint8_t i=halv;i>0;i--){
ws2812_set(halv+i,0,0,0);
ws2812_set(halv-i,0,0,0);
ws2812_set(halv*3+i,0,0,0);
ws2812_set(halv*3-i,0,0,0);
HAL_Delay(1000);
}
}
static float min(float a, float b, float c)
{
float m;
m = a < b ? a : b;
return (m < c ? m : c);
}
static float max(float a, float b, float c)
{
float m;
m = a > b ? a : b;
return (m > c ? m : c);
}
void rgb2hsv(uint8_t r, uint8_t g, uint8_t b, float *h, float *s, float *v)
{
float red, green ,blue;
float cmax, cmin, delta;
red = (float)r / 255;
green = (float)g / 255;
blue = (float)b / 255;
cmax = max(red, green, blue);
cmin = min(red, green, blue);
delta = cmax - cmin;
/* H */
if(delta == 0)
{
*h = 0;
}
else
{
if(cmax == red)
{
if(green >= blue)
{
*h = 60 * ((green - blue) / delta);
}
else
{
*h = 60 * ((green - blue) / delta) + 360;
}
}
else if(cmax == green)
{
*h = 60 * ((blue - red) / delta + 2);
}
else if(cmax == blue)
{
*h = 60 * ((red - green) / delta + 4);
}
}
/* S */
if(cmax == 0)
{
*s = 0;
}
else
{
*s = delta / cmax;
}
/* V */
*v = cmax;
}
void hsv2rgb(float h, float s, float v, uint8_t *r, uint8_t *g, uint8_t *b)
{
int hi = ((int)h / 60) % 6;
float f = h * 1.0 / 60 - hi;
float p = v * (1 - s);
float q = v * (1 - f * s);
float t = v * (1- (1 - f) * s);
switch (hi){
case 0:
*r = 255 * v;
*g = 255 * t;
*b = 255 * p;
break;
case 1:
*r = 255 * q;
*g = 255 * v;
*b = 255 * p;
break;
case 2:
*r = 255 * p;
*g = 255 * v;
*b = 255 * t;
break;
case 3:
*r = 255 * p;
*g = 255 * q;
*b = 255 * v;
break;
case 4:
*r = 255 * t;
*g = 255 * p;
*b = 255 * v;
break;
case 5:
*r = 255 * v;
*g = 255 * p;
*b = 255 * q;
break;
}
}
//呼吸灯
void breath(uint16_t r,uint16_t g,uint16_t b)
{
float h,s,v;
uint8_t c_r, c_g, c_b;
/* 呼吸灯曲线表 */
const uint16_t index_wave[] = {255,255,255,255,255,255,255,255,255,255,255,254,254,254,254,254,254,254,254,
254,254,254,254,254,254,253,253,253,253,253,253,253,253,253,252,252,252,252,252,252,252,251,251,251,251,
251,250,250,250,250,250,249,249,249,249,248,248,248,247,247,247,246,246,246,245,245,244,244,243,243,242,
242,241,241,240,240,239,238,238,237,236,236,235,234,233,232,231,231,230,229,228,226,225,224,223,222,220,
219,218,216,215,213,211,210,208,206,204,202,200,198,196,194,191,189,186,184,181,178,175,172,169,166,162,
159,155,151,147,143,139,134,130,125,120,115,110,104,98,92,86,80,73,66,59,51,43,35,27,18,9,1,1,9,18,27,35,
43,51,59,66,73,80,86,92,98,104,110,115,120,125,130,134,139,143,147,151,155,159,162,166,169,172,175,178,
181,184,186,189,191,194,196,198,200,202,204,206,208,210,211,213,215,216,218,219,220,222,223,224,225,226,
228,229,230,231,231,232,233,234,235,236,236,237,238,238,239,240,240,241,241,242,242,243,243,244,244,245,
245,246,246,246,247,247,247,248,248,248,249,249,249,249,250,250,250,250,250,251,251,251,251,251,252,252,
252,252,252,252,252,253,253,253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254,254,254,254,
254,254,255,255,255,255,255,255,255,255,255,255,255};
//传入的为RGB 颜色,将其转换为hsv
rgb2hsv(r, g, b, &h,&s, &v);
//保持HS不变,v变量亮度表
//printf("HSV %.2f, %.2f %.2f\r\n",h,s,v);
for(uint16_t n=0;n<=300;n++){
hsv2rgb(h,s,index_wave[n],&c_r,&c_g,&c_b);
//printf("%d RGB %d, %d %d \r\n",index_wave[n],c_r, c_g, c_b);
for(uint16_t i=0;i<WS2812_MAX_NUM;i++){
ws2812_set(i,c_r,c_g,c_b);
}
HAL_Delay(10);
}
}
在主函数中开启串口监听,当上位机发送命令字后,根据命令字选择LED灯的效果进行展示。
#define UART2_REC_LEN 1 //定义最大接收字节数
uint8_t UART2_RX_Buffer[UART2_REC_LEN];//接受缓存 最大为UART1_REC_LEN个字节
uint16_t UART2_RX_STA=0;// 接收状态标记
//接收状态
//bit15置1 0x8000 接收到0x0a 即\n转移符 表示接收完成
//bit14置1 0x4000 接收到0x0d 即\r转义符
//bit13~0 表示可以接受到的数据个数最大为 2^13
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2,(uint8_t *)&ch,1,HAL_MAX_DELAY);//阻塞方式打印,串口2
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t ADC_Value=0; //ADC值缓存
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART2_UART_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
ws2812_init(); //初始化WS2812
//进入循环前需调用的函数,有中断使能的作用
HAL_UART_Receive_IT(&huart2, UART2_RX_Buffer, UART2_REC_LEN); //将Res作为UART中断接收数据的变量
ws2812_set_all_on(); //全部开启
//延时一段时间
HAL_Delay(1500);
ws2812_set_all_off(); //全部关闭
HAL_Delay(1500);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//声音控制
//ADC_Value=adc_readval()%1300;//读取声音
//srand(ADC_Value);
//printf("ADC value %d %s\r\n",ADC_Value, message);
//ws2812_set_all_off();//关闭所有
//for(int i=0;i<WS2812_MAX_NUM;i++){
//HAL_Delay(50);
//if(ADC_Value%3==0) ws2812_set(i,ADC_Value,0,0);
//if(ADC_Value%3==1) ws2812_set(i,0,ADC_Value,0);
//if(ADC_Value%3==2) ws2812_set(i,0,0,ADC_Value);
//ws2812_set(i,rand()%250,rand()%2500,ADC_Value);
//}
//printf("hello 2!\t%s\r\n",UART2_RX_Buffer);
if(UART2_RX_Buffer[0]>='0' &&UART2_RX_Buffer[0]<='7') effect(UART2_RX_Buffer[0]);
else if(UART2_RX_Buffer[0]=='8') effect2();
else if(UART2_RX_Buffer[0]=='9') effect3();
else if(UART2_RX_Buffer[0]=='R' || UART2_RX_Buffer[0]=='r') breath(255,0,0); //红色
else if(UART2_RX_Buffer[0]=='G' || UART2_RX_Buffer[0]=='g') breath(0,255,0); //绿色
else if(UART2_RX_Buffer[0]=='B' || UART2_RX_Buffer[0]=='b') breath(0,0,255); //蓝色
else if(UART2_RX_Buffer[0]=='W' || UART2_RX_Buffer[0]=='w') breath(255,255,255); //
else if(UART2_RX_Buffer[0]=='Y' || UART2_RX_Buffer[0]=='y') breath(255,255,0); //黄
else if(UART2_RX_Buffer[0]=='P' || UART2_RX_Buffer[0]=='p') breath(255,0,255); //骚粉
else if(UART2_RX_Buffer[0]=='l' || UART2_RX_Buffer[0]=='l') breath(0,255,255); //湖蓝
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
心得体会:感谢硬禾学堂提供的这次活动。通过这个项目掌握了WS2812这种全彩灯的驱动,可惜麦克风还是没能搞定。期待参加更多有意思的活动。