在好几年前,我好像就看到了焊武帝 jiripraus在纪念结婚五周年时,制作的一个心跳跟随心形灯,感觉太浪漫了,于是在假期的时候,我也仿照做了一个,虽然还有很多需要完善的地方,但是大致功能已经实现了,下面开源讲讲开源的项目。
心脏的外壳采用紫铜丝或黄铜丝焊接,1mm的铜丝较硬,适合完成外部框架的搭建,0.7mm的铜丝可塑性较好,适合焊接内部的WS2812,整个过程中吗,焊接难度较大,电子器件的固定难度也很大,我内部借助了一些热熔胶固定,有些丑,可以参考 jiripraus的固定方法。
原作者采用的Arduino主控,且没有借助PCB,纯飞铜线完成内部电子器件的连接。
因为我想利用FreeRTOS学习一下实际项目编写,所以将主控更换成了STM32F103C8T6,当然编写了两套代码,先编写了一套裸机开发,测试能够完成所有功能、之后移植了一套FreeRTOS的,都可以运行。
jiripraus项目地址(Arduino):https://www.instructables.com/Beating-LED-Heart/
裸机开发软件下载(STM32版本):https://download.csdn.net/download/zerokingwang/88173336
FreeRTOS开发软件下载(STM32版本):https://download.csdn.net/download/zerokingwang/88173410
硬件PCB(立创开源)下载链接:https://oshwhub.com/zeroking/my_heart_
所有文件上传到github了,也可以到github下载:https://github.com/VioletJA/MY_HEART
绘制了包括STM32F103C8T6的最小系统、蜂鸣器、TP4059锂电池充电电路、OLED接口、MAX30102接口等。
在没有手指触碰到MAX30102模块时,WS2812进行彩虹灯变换,当手指触碰到时,跟随检测到的心跳进行红色闪烁及蜂鸣器模拟心跳。
在3D打印的器件上进行焊接,我先用胶带固定好铜丝,然后再各个连接点进行焊接。
心形3D打印件:https://www.cgtrader.com/items/865899/download-page
WS2812焊接:最好黏在胶带上,固定好大致位置后再进行焊接,我没有找到其他好方法,焊接极其耗时。
当发送一串数据时,第一个24bits的数据给D1,第n个24bits的数据给第n。(24bit对应三原色,每一色八位)
大多数采用的是PWM+DMA的模式,通过传输一串数据,控制不同占空比的PWM完成数据传输
HAL_TIM_PWM_Start_DMA(&ws2812_TIM, ws2812_CHANNEL, (uint32_t *)Pixel_Buf,(Pixel_NUM+1)*24);
参考:STM32系列(HAL库)——F103C8T6驱动WS2812全彩RGB模块(PWM+DMA方式)
但有一点需要注意,必须在PWM的完成回调函数中进行手动关闭DMA传输,否则WS2812颜色不对(应该就是DMA传输出错了)
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==ws2812_TIM.Instance)
HAL_TIM_PWM_Stop_DMA(&ws2812_TIM, ws2812_CHANNEL);
}
其他详细的代码控制都在开源代码中了
参考:【stm32】手把手用cubemx配置血氧传感器(MAX30102)
采用的是中断处理,每次模块发送一个下降沿中断,STM32接收到后进行数据处理。
模块采用IIC进行控制与数据读取,普通采用HAL配置硬件IIC就能够实现,但是在我用STM32F103C6T6进行测试的时候,HAl生成的代码中,IIC引脚不会自动设置为高速,导致运行会出现HAL_ERROR的问题,然而在STM32F103C8T6就已经纠正了这个错误。
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C1)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_RCC_I2C1_CLK_ENABLE();
else if(i2cHandle->Instance==I2C2)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_RCC_I2C2_CLK_ENABLE();
}
}
在屏幕调试中,由于1616的字看上去太小了,于是我调整为2424的大小,这个时候,网上的一些库就不够用了,需要看懂是如何完成一个汉字的显示。
利用PCtoLCD2002完成取字模,一般设置如下
数据如下:
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x08,0x10,0x70,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x80,0xC0,0xF8,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xC0,0x04,0x18,0xF0,0xE0,0x00,0x00},
{0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x1F,0x18,0x10,0x10,0x10,0x10,0x10,0x10,0x18,0x1F,0x08,0x00,0x00,0x00,0x00,0x00},/*"心",0*/
网上一般说取数据是按照一行一行来的,一个数据代表的是8位,不足8位的按照8位算。
但是我实际测试,是按列算的,例如第一个数据0x00,表示第一列中的前八个点全没有点亮,第二个0x00代表的是第二列数据的前8个也全部没有点亮,当前8行的所有列都完成后,计算中间8行,一列一列,之后计算下面8行(24*24的数据)
例如下图:
而我们采用的低位在前时:
当第一个格子点亮时,对应的就是0x01
在OLED汉字显示的函数中,需要将汉字分为三上中下,完成三次打印,组成一个完整的汉字
//显示24*24汉字
void OLED_ShowCHinese24(uint8_t x,uint8_t y,uint8_t no)
{
uint8_t t;
OLED_Set_Pos(x,y);
for(t=0;t<24;t++)
{
OLED_WR_Byte(CHI_24_24[3*no][t],OLED_DATA);
}
OLED_Set_Pos(x,y+1);
for(t=0;t<24;t++)
{
OLED_WR_Byte(CHI_24_24[3*no+1][t],OLED_DATA);
}
OLED_Set_Pos(x,y+2);
for(t=0;t<24;t++)
{
OLED_WR_Byte(CHI_24_24[3*no+2][t],OLED_DATA);
}
}
在移植操作系统的时候,创建了两个任务,一个完成WS2812和蜂鸣器的控制,一个完成MAX30102和OLED的控制。
其中出现了一些跨任务的信号量,尝试利用任务通知方式进行数据保护,但是效果不佳,最后还是利用了临界区保护,在信号改写的地方进行了保护(临界区时间太长,系统会崩溃)
心跳跟随心形灯
通过制作这个小项目,还是学习了HAL的IIC配置,PWM的DMA模式,WS2812的控制,OLED屏幕控制,FreeRTOS也理解更多了一点,当然还只是刚开始入门,要学的东西还是有很多,在这个小项目中,还有很多bug和不完善的地方,比如说MAX30102测量的心率不准等等,但是目前打算就先这样结束,先继续学习吧。