STM32F103电脑屏幕氛围灯

想法来源:笔者之前逛某宝时候,发现有个贴在电脑屏幕四周的灯带,并且灯带会随着电脑屏幕颜色变化而变化,当时就在心中就埋下了DIY的想法,直到后来帮老师做东西,得到了一些边角料,于是下决心干它!

软件

下位机

本次采用WS2812灯带,所以首先要将其驱动点亮。
于是查阅WS2812的数据手册,其驱动时序如下图。
在这里插入图片描述
网上有很多驱动方法,有SPI、PWM、定时器等,但是本次笔者采用最简单的IO翻转来驱动它,上面提到其他方法请读者自行搜索查阅。

#ifndef __WS2812_H
#define __WS2812_H 			   
#include "sys.h"

#define LED_Nums 60

#define		RGB_LED_HIGH	PAout(8)=1
#define 	RGB_LED_LOW		PAout(8)=0

void RGB_LED_Init(void);
void RGB_LED_Write0(void);
void RGB_LED_Write1(void);
void RGB_LED_Reset(void);
void RGB_LED_Write_Byte(uint8_t byte);
void RGB_LED_Write_24Bits(uint8_t red, uint8_t green, uint8_t blue);
void RGB_LED_Show(uint8_t dat[]);

#endif

```c
#include "WS2812.h"
#include "delay.h"

void RGB_LED_Init(void)
{
    
    
	GPIO_InitTypeDef  GPIO_InitStructure;
 	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		
	GPIO_Init(GPIOA, &GPIO_InitStructure);					
	GPIO_SetBits(GPIOA, GPIO_Pin_8);						 
}

void RGB_LED_Write0(void)//WS2812写0码
{
    
    
	RGB_LED_HIGH;
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
    __nop();__nop();
	RGB_LED_LOW;
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
}


void RGB_LED_Write1(void)//WS2812写1码
{
    
    
	RGB_LED_HIGH;
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	RGB_LED_LOW;
	__nop();__nop();
}

void RGB_LED_Reset(void)//WS2812复位操作
{
    
    
	RGB_LED_LOW;
	delay_us(80);
}

void RGB_LED_Write_Byte(uint8_t byte)//WS2812写1字节
{
    
    
	uint8_t i;
	for(i=0;i<8;i++)
	{
    
    
		if(byte&0x80)
		{
    
    
			RGB_LED_Write1();
		}
		else
		{
    
    
			RGB_LED_Write0();
		}
		byte <<= 1;
	}
}

void RGB_LED_Write_24Bits(uint8_t red, uint8_t green, uint8_t blue)//WS2812写1个像素点
{
    
    
    RGB_LED_Write_Byte(green);
    RGB_LED_Write_Byte(red);
	RGB_LED_Write_Byte(blue);
}

void RGB_LED_Show(uint8_t dat[])//WS2812写多个个像素点
{
    
    
	int16_t i;
	RGB_LED_Reset();
	for(i=0; i<LED_Nums; i++)
	{
    
    
		RGB_LED_Write_24Bits(dat[3*i], dat[3*i+1], dat[3*i+2]);
	}
	RGB_LED_HIGH;
}

在源文件中,为了提高STM32的IO翻转速度,采用了位带输出,然后再使用_nop();来调整延时,使其满足WS2812驱动信号的要求。有条件的可以使用逻辑分析仪或示波器来调整延时。这个笔者当时调整了有一会时间,需要点耐心。
在这里插入图片描述
在这里插入图片描述

本次的电脑屏幕氛围灯,单片机采用串口与PC通信。
接下来是串口接收程序。

void USART1_IRQHandler(void)//串口1中断服务程序
	{
    
    
	static uint8_t k=0,i=0,rebuf[3*LED_Nums+2]={
    
    0};
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
		{
    
    
		 rebuf[k++] = USART_ReceiveData(USART1);//读取接收到的数据
		 if(!(rebuf[0]==0xaa))//如果帧头错误,清缓存
		 {
    
    
			 k = 0;
			 rebuf[0]=0;
			 printf("err!!!");
		 }
		 if(k == 3*LED_Nums + 2)//数据接收完毕
		 {
    
    
			if(rebuf[3*LED_Nums + 1] == 0x55)//判断帧尾
			{
    
    	
				for(i = 1; i<3*LED_Nums + 1; i++)
				{
    
    
					  LED_data[i-1] = rebuf[i];
						show_flag = 1;
				}	
			}
			k=0;//清缓存
		 }
    } 
} 

串口程序,是随便找个串口中断接收的例程修改的,只需修改串口中断服务函数即可。可以看到,本次笔者自定义了一个简单的通信协议,其为0xaa,R,G,B,R,G,B…,0x55。

主程序也很简单,如下。

while(1)
{
    
    
	if(show_flag==1) 
	{
    
    
		RGB_LED_Show(LED_data);
		show_flag=0;
		printf("show ok!");
	}
}

自此,下位机的程序编写完毕。接下来,我们只需要让电脑通过串口向单片机发送相应像素点的颜色值即可。

上位机

本次上位机采用C#开发,这次也算我自学C#编程的一个练习项目。笔者主要是想学成后,可以自己做电脑与单片机的基础通信,其他东西都是需要用时,参考大佬的程序,一点一点修改成自己想要的样子。

上位机主要是串口通信部分和屏幕颜色获取部分。
颜色获取程序如下。

	int len = 60; //设置像素点
	Byte lightness = Convert.ToByte(textBox1.Text); //获取亮度设置
	int x = 0, y = 200; //截图起始点
	Bitmap RGB_Led = new Bitmap(1920, 1); 
	Graphics g = Graphics.FromImage(RGB_Led);
	
	Byte[] fram_data = new Byte[3];
	Byte[] GRB_data_last = new Byte[3 * len];
	Byte[] GRB_data_curr = new Byte[3 * len];
	Byte RGB_r = 0, RGB_g = 0, RGB_b = 0;
	fram_data[0] = 0xAA;
	fram_data[1] = lightness;
	fram_data[2] = 0x55;
	
	//g.CopyFromScreen(new Point(x, y), new Point(0, 0), new Size(1, 120));
	g.CopyFromScreen(x, y, 0, 0, new Size(1920, 1));//起始坐标想x,y,取一个1920*1的屏幕图像
	for (int i = 0; i < len; i++)
	{
    
    
		Color color_start = RGB_Led.GetPixel(16 * 2 * i, 0); //隔每16点取颜色
		RGB_r = color_start.R;
		RGB_g = color_start.G;
		RGB_b = color_start.B;
		GRB_data_last[3 * i] = RGB_r;
		GRB_data_last[3 * i + 1] = RGB_g;
		GRB_data_last[3 * i + 2] = RGB_b;
	}
	serialPort1.Write(fram_data, 0, 1);
	//serialPort1.Write(fram_data, 1, 1);
	serialPort1.Write(GRB_data_curr, 0, 3 * len);
	serialPort1.Write(fram_data, 2, 1);

程序的思路很简单,就是定时获取屏幕固定像素点的颜色的RGB分量,然后按之前设计通信协议发送给单片机。具体的也不细讲了,上位机还有很多没有完善的地方,比如现在实现获取一个像素点,更好的是获取一块区域的像素点的颜色平均值,另外为了灯条显示效果更加顺滑,对获取到颜色数据也需要一些算法来处理。
不过,网上专门的取色软件Prismatik。它是我做完这个练习项目后才看到的。我也下载该软件测试过,发现其有一点不稳定,串口数据经常中断。不过,看其他人配合Arduino做出来效果还不错,不知道是我哪里出现了问题。

上位机运行结果如下。
在这里插入图片描述

硬件

硬件部分,我打了一块小驱动板,顺便也是为了验证自己的一些想法,方便以后设计电路的时候参考。
在这里插入图片描述
一是,USB转串口芯片采用CH340E,它不需要外部晶振,体积也很小。
二是,串口和SWD下载接口共用Type-C接口,通过拨码开关进行选择。

整体演示效果

在这里插入图片描述
总结一下,最后的效果并不是很理想,程序算法还需要优化,有机会我也再尝试Prismatik软件,因为它的效果确实可人。

猜你喜欢

转载自blog.csdn.net/weixin_44625313/article/details/113248494