SEGA星空投影仪太贵买不起?教你用STM32自制星空灯!

女神最近种草了一款星空投影仪——日本SEGA的Homestar。白柴看着少则几百,多则上千的价格,不禁感觉钱包一紧
再看看某宝上几十块的星空灯,功能不少,但图案偏幼稚,一点都不浪漫。这样的东西,如何拿得出手送给女神呢?
看来只能自己动手DIY了。鲁迅曾说过:“如果没有你想要的星空灯,那么就自己做一个。”
事不宜迟,立马打电话给大Z老师,向他学习星空灯的制作。现在白柴就把整理好的制作方法分享给大家,感兴趣的粉丝们可以跟着一起做哟~
看完已心动,自己也想做?
现在报名参加EEPW星空灯DIY内测活动,满足你的愿望!
报名时间:4.23~4.30
参与名额:10名
*内测活动,活跃度高的粉丝更容易被选中
亮点1
100元开发补贴 人人都有
放飞创意 费用我们报销
亮点2
自己设计灯罩 我的灯 我做主
实现星空灯自由
亮点3
记录过程 参与评比 得分最高者
我们将在520当天送上一份花束大礼(价值200元)
带上鲜花和星空灯,去和她告白吧!
如何报名?
添加小助手微信(ID:baichai1025),私信【星空灯报名】。
收到小助手回复后,即为报名成功。
扫一扫,添加小助手
☝星空灯成品展示
效果超nice!绝对能感动女神
本体:
STM32最小系统板(1个)
WS2812灯带(1个)
HC-06蓝牙模块(1个)
杜邦线若干
星空灯本体
灯罩:
裁好的亚克力板(1份)
黑色墙纸(适量)
灯罩裁切图纸
虽然叫星空灯,但本体实际是STM32点流水灯。硬件不复杂,属于入门级,有一点单片机和编程基础的朋友都可尝试制作。本文只给出了基础电灯程序,欢迎技术大神们开发添加新功能。
WS2812彩灯
先简单说下WS2812的工作过程:
1)上电时,WS2812数据IO为低电平保持。
2)空闲时,IO为低电平。
3)数据发送完毕后,保持电平,超过规格书上定义的RESET时间(只有低电平时间超过280us,就可以认为是RESET)。
接下来,我们看下数据的时序,如图1。
图1 数据的时序
如图2,是每24bit的组成。注意,顺序不是RGB888,而是GRB888。一般我们取颜色的数值,都是RGB顺序,所以这里在代码里实现的时候,会需要做一下移位。另外,需要注意的是,需要高位先发(MSB)。
图2 24bit数据结构
图3是时序波形图。关键在于用什么方法去表示Bit的波形,网络上的方法有很多。例如PWM,也有用SPI。今天我们就先用一种简单方法实行吧,IO口模拟是不错的选择。
图3 时序波形图
现在我们开始配置工程吧。打开STM32CubeMX,新建一个工程,图4。
图4
在搜索框内,搜索我们的开发板型号,也就是STM32G070RB,图5。这样工程就新建好了。
图5
配置时钟树,如图6所示。
图6
接下来配置引脚 ,这里我们采用的是PWM+DMA的方式来驱动WS2812。通过WS2812的手册,可以得知驱动需要800KHZ的频率。现在我们来配置定时器,这里以定时器1为例来配置。如图7所示,图中的Pulse是指一个周期的脉冲数;计算方法79=(64M/800K)-1得出。
图7
下面我们开始配置DMA,如图8所示配置。
图8
到这里就可以生成代码了,图9。
图9
我们打开工程,将下面的代码,复制到图10所在位置:
#define ONE_PULSE (59) //1 码计数个数
#define ZERO_PULSE (29) //0 码计数个数
#define RESET_PULSE (80) //80 复位电平个数(不能低于40)
#define LED_NUMS (12) //led 个数
#define LED_DATA_LEN (24) //led 长度,单个需要24个字节
#define WS2812_DATA_LEN (LED_NUMS*LED_DATA_LEN) //ws2812灯条需要的数组长度
uint16_t static RGB_buffur[RESET_PULSE +WS2812_DATA_LEN] = { 0 };
图10
接下来就是DMA传输完成回调函数(根据你使用的定时器配置),以下函数都复制到main.c 的/* USER CODE BEGIN 4 */代码区:
voidHAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim){ HAL_TIM_PWM_Stop_DMA(&htim1,TIM_CHANNEL_1);}
下面就是WS2812的驱动函数了,该函数的作用是根据WS2812的数量将灯的GRB颜色数据写到需要DMA传送的数组中:
void WS281x_SetPixelColor(uint16_t n,uint32_t GRBColor){ uint8_ti; if(n < LED_NUMS) { for(i = 0; i < 24; ++i) RGB_buffur[24* n + i] = (((GRBColor << i) & 0X800000) ? ONE_PULSE : ZERO_PULSE); }}
将三个颜色的数据合并成GRB数据:
uint32_t WS281x_Color(uint8_t red, uint8_tgreen, uint8_t blue){ returngreen << 16 | red << 8 | blue;}
这是一个简单的颜色渐变算法 ,感兴趣的可以研究研究:
uint32_t Wheel(uint8_t WheelPos){ WheelPos= 255 - WheelPos; if(WheelPos < 85) { returnWS281x_Color(255 - WheelPos * 3, 0, WheelPos * 3); } if(WheelPos < 170) { WheelPos-= 85; returnWS281x_Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos-= 170; returnWS281x_Color(WheelPos * 3, 255 - WheelPos * 3, 0);}
这里简单的写了两个演示程序:
void Mode2_LED(uint8_t wait){ uint32_ttimestamp = HAL_GetTick(); uint16_ti; staticuint8_t j; staticuint32_t next_time = 0; uint32_tflag = 0; if(next_time < wait) { if((uint64_t)timestamp + wait - next_time > 0) flag= 1; } elseif (timestamp > next_time) { flag= 1; } if(flag) { j++; next_time= timestamp + wait; for(i = 0; i < LED_NUMS; i++) { WS281x_SetPixelColor(i,Wheel((i + j) & 255)); } } HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t*)RGB_buffur,RESET_PULSE + WS2812_DATA_LEN);}
void Mode1_LED(uint8_t wait)
{
uint32_ttimestamp = HAL_GetTick();
uint16_ti;
staticuint8_t j;
staticuint32_t next_time = 0;
staticuint8_t loop = 0;
if(loop == 0)
next_time= timestamp;
loop= 1; //首次调用初始化
if((timestamp > next_time)) // && (timestamp - next_time < wait*5))
{
j++;
next_time= timestamp + wait;
for(i = 0; i < LED_NUMS; i++)
{
WS281x_SetPixelColor(i,Wheel(((i * 256 / (LED_NUMS)) + j) & 255));
}
}
HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t*)RGB_buffur,RESET_PULSE + WS2812_DATA_LEN);
}
在主函数中直接调用Mode1_LED和Mode2_LED函数即可。
OK,点亮之后相当炫酷。大家可以借鉴,修改出自己独特的风格。
按键模式切换
这里简单的写了几种模式,图11。
图11
通过图12的原理图可以看出来,开发板上的按键连接的是PC13引脚。
图12
好的,知道了按键的引脚,我们打开STM32CubeMX软件来配置引脚,如图13所示。
图13
同时使能中断,如图14。
图14
生成代码,打开工程。
在/* USER CODE BEGIN 4 */
/* USER CODE END 4 */之间重写中断回调函数,代码如下。
void HAL_GPIO_EXTI_Rising_Callback(uint16_tGPIO_Pin){ switch(GPIO_Pin) { caseGPIO_PIN_13:Mode_Led++; if(Mode_Led == 'I') Mode_Led ='A';break; }}
通过主函数判断当前的模式,并运行相对应的模式实现函数,如图15。
图15
通过按键进行模式选择就完成了。
蓝牙控制
接下来,用手机通过蓝牙来控制WS2812的模式和速度调节。
先打开STM32CubeMX配置我们要用到串口(这里以串口2为例,大家根据自己的喜好选择)。
因为蓝牙模块默认是9600Bits/s,所以我们配置时要与蓝牙的波特率一致,如下图16所示。
图16
同时我们使能下串口的收发中断,如图17。
图17
这样串口就配置好了,点击生成代码,打开工程。
图18
我们可以看到串口2的初始化程序,图18。
接下来添加代码,定义一个字符的数据接收,图19。
图19
添加以下代码,打开串口接收中断,图20。
图20
重写串口接收中断回调函数, 代码如下:
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(Buffer>='A' && Buffer <='H') { Mode_Led = Buffer; printf("灯光模式:%c",Mode_Led); } if(Buffer == 'I') { Mode_wait +=10; if(Mode_wait==500) Mode_wait=490; printf("灯光速度:%d",500-Mode_wait); } if(Buffer == 'J') { Mode_wait -=10; if(Mode_wait==40) Mode_wait=50; printf("灯光速度:%d",500-Mode_wait); } HAL_UART_Receive_IT(&huart2,&Buffer, 1);}
这里的printf对串口发送函数进行了重定义,方法如下:
int fputc(int c, FILE *stream) //重写fputc函数{ /* huart1是工具生成代码定义的UART1结构体, 如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。*/ HAL_UART_Transmit(&huart2, (unsigned char *)&c, 1, 1000); return 1;}
将代码复制到
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */之间。
接下来点击Options for Target,把Use MicroLIB打上对勾,图21。
图21
至此就完成了对串口发送函数的重写,可以正常的使用printf函数了。我们可以通过输出一定的信息,显示出当前的状态。
连上蓝牙模块,发送模式对应的字符,就可以实现模式的切换,以及实现调速,图22。
图22
到这里,我们就实现了使用蓝牙控制的效果。
感兴趣粉丝们,欢迎报名参加我们的DIY活动哟~
加入“电子产品世界”粉丝交流群

工程师必备
- 项目客服
- 培训客服
- 平台客服
TOP
