这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界» 论坛首页» DIY与开源设计» 电子DIY» 【星空灯日志】蓝牙控制星空灯

共16条 1/2 1 2 跳转至

【星空灯日志】蓝牙控制星空灯

助工
2021-05-05 13:49:56 打赏

白柴の推荐语:

楼主的每个步骤都介绍得很细,尤其是PWM+DMA模式点灯的部分,思路清晰,注意到了晶振频率的小细节,强推

PS:小猪佩奇,充满童趣\(^o^)/~


大家好,过完五一的“人从众”,经历了西湖的断桥(人桥,人太多了,感谢强国的疫苗)。来参加活动了,刚好手里有之前培训的蓝牙模块HC-06,可以实现星空灯的具体要求,图片如下。接下来就开始了,灯珠WS2812还没到,先写个开山贴预热一下。

这个模块很好用,只用四根线,分别是VCC、GND、TXD、RXD。我们在CubeMx工程中只用使能一下USART1或者USART2就行了,或者修改一下波特率



QQ图片6.png




关键词: STM32 单片机 星空灯 电子DIY

工程师
2021-05-05 23:55:59 打赏
2楼

祝你成功


管理员
2021-05-07 09:49:31 打赏
3楼

围观,期待成品

1620352154188902.gif


助工
2021-05-07 23:23:38 打赏
4楼

第二弹:点灯WS2812


大家好,灯珠到了,利用周末先点亮试试。WS2812模块有四个引脚,我们在点亮时只用三个引脚就够了,分别是GND/VCC/DI,DOUT是输出引脚,本项目用不到。因此我们只用配置DI一个引脚即可,引脚模式为输入。此外,查资料知道,驱动WS2812需要实现纳秒级别的电平翻转像一般主频较低的MCU很难实现这种级别的电平翻转。我用的板卡,恰好可以通过延时翻转高低电平模拟WS2812的通信时序进而实现对WS2812灯珠的驱动。文末附图一张,红红火火

QQ图片.png

具体步骤:

一是初始化IO端口,下面是软件生成的,也可以懒省事,自己宏定义一个

void ws2812_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); GPIO_InitStruct.Pin = GPIO_PIN_2; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); }

二是IO口模拟WS2812的时序

void ws2812_sendHigh(void) //发送1码 { PAout(0) = 1; ws2812_delay(2); PAout(0) = 0; ws2812_delay(1); } void ws2812_sendLow(void) //发送0码 { PAout(0) = 1; ws2812_delay(1); PAout(0) = 0; ws2812_delay(2); }

三是根据时序,实现点亮ws2812的函数,然后在主函数中调用函数ws2812_sendOne(),就可以点亮其中一个颜色。如果需要控制多个WS2812灯珠可以定义一个数组,首先将颜色数据依次填入数组,再通过发送函数将颜色数据依次发出即可。下面附图几张

void ws2812_sendOne(uint32_t dat) { uint8_t i; unsigned char byte; for(i = 24; i > 0; i--) { byte = ((dat>>i) & 0x01); //位操作,读取dat数据的第i位 if(byte == 1) { ws2812_sendHigh(); } else { ws2812_sendLow(); } } }

QQ图片1.jpg


院士
2021-05-08 08:38:18 打赏
5楼

支持一下


助工
2021-05-08 09:05:49 打赏
6楼

o


菜鸟
2021-05-08 10:41:58 打赏
7楼

期待过程学习


助工
2021-05-08 17:51:58 打赏
8楼

第三弹 蓝牙模块与蓝牙调试器的使用


1、大家好,利用周末开始,蓝牙模块就是用大Z老师推荐的HC-06,这个模块很好用,但一定要注意设置串口的波特率是9600bit/s,我第一次用波特率没有改,发送到蓝牙调试器上的Hello World全是问号,大家不要犯这个错误

2、就介绍一下蓝牙接受手机端发送的指令那个接收函数,这个函数固件库里面就有,但是我们需要看懂它里面的参数,方便我们在程序中的使用。第一个参数是&huart1,是串口的地址,第二个参数是&flag,这个参数最重要,它是我们需要发送字符的地址,我们可以令地址flag==xx字符,eg,令flag==1。这个flag相当于个标志位,我的嵌入式老师在上课时经常叮嘱要一定会用,在这里分享给大家。第三个参数是字长,在这里可以用求字长的函数sizeof,也可以自己手动数,在这里flag就是一位。也可以用下面贴的函数,我也是现学现卖,O(∩_∩)O哈哈~。


/* USER CODE BEGIN 2 */

HAL_UART_Receive(&huart1, &flag, sizeof(flag)/sizeof(uint8_t)-1, 0xffff);

/* USER CODE END 2 */


3、演示一个用手机上的蓝牙调试器控制WS2812的程序,顺便介绍一下这个参数

我的颜色顺序是这个,绿红蓝,和英文的首字母是一样的


#define GRB 24

这个控制着灯珠的颜色以及亮灭,这是个人理解,如果介绍的有问题希望大家不吝指教

ws2812_color(0xff, 0x00, 0x00)代表绿色
ws2812_color(0x00, 0xff, 0x00)代表红色
ws2812_color(0x00, 0x00, 0xff)代表蓝色

/* USER CODE BEGIN 3 */ HAL_UART_Receive(&huart1, &flag, 1, 0xffff); if(flag == 1)//点亮WS2812 { ws2812_colorWipe(ws2812_color(0x00, 0x00, 0xff), 250); } if(flag == 2)//熄灭 { ws2812_colorWipe(ws2812_color(0x00, 0x00, 0x00), 250); } } /* USER CODE END 3 */


4、蓝牙调试器的使用,在手机应用商店里面搜“蓝牙调试器”,下载完成后

第一步:进入按钮控制界面,

第二步:点击编辑模式

第三步:点击按钮,会出现编辑框,如下图

第四步:编辑框里面数据字节码可以根据自己的需要修改,然后把字节码放到主函数里面, 就可以实现蓝牙控制WS2812,

图一

QQ图片1.png

图二

QQ图片4.png

图三

QQ图片5.png



助工
2021-05-08 17:54:00 打赏
9楼

第四弹 PWM+DMA模式点灯知识分享


1、PWM输出就是对外输出脉宽(即占空比)可调的方波信号信号频率由自动重装载寄存器ARR的值决定,占空比由比较寄存器CCR的值决定,

2、读WS2812的数据手册我们知道:

一定时间的高电平+一定时间的低电平==WS2812承认的一位信号(高电平或低电平),

QQ图片8.png


3、数据传输时间(TH+TL=1.25us),1/800 000=0.000000125=1.25us,细节问题,不是所有的单片机的晶振频率都可以,灯珠的通信速度必须达到800k,才能完成小灯的数据传输,因此对单片机型号要求很高。在配置STM32定时器时可以设置,预分频因子为0,自动重装载初值为90-1,则频率为72M/90=800K,然后设置占空比值 为50为“1码”,设占空比为25可满足“0码”的时间要求。

QQ图片9.png


4、DMA的模式是储存器到外设,这种点灯方式就是储存器的数据直接发送到外设的模式,通过数据手册知道DMA1_Channel2对应的定时器2的通道,因此配置通道

QQ图片7.png


5、储存器是一个数组,数组中的每一个数据都是一个地址,因此储存器是自增模式

QQ图片10.png具体代码

点h文件:宏定义配置小灯数目

#ifndef __WS2812B_H #define __WS2812B_H #include "stm32f10x.h" #include "delay.h" //#define WS2812_IN_PIN PA0 void Timer2_init(void); void WS2812_send(uint8_t (*color)[3], uint16_t len,u8 flag); #endif /* __LED_H */

点c文件

#include "WS2812B.h" #define TIM2_CCR1_Address 0x40000034 #define TIMING_ONE 50 //根据高低电平算的 #define TIMING_ZERO 25 uint16_t LED_BYTE_Buffer[300]; //储存器的地址,是个全局变量 //---------------------------------------------------------------// void Timer2_init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /* GPIOA Configuration: TIM2 Channel 1 as alternate function push-pull *///PA0 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //初始化定时器的IO口 GPIO_Init(GPIOA, &GPIO_InitStructure); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能TIM2的通道一 /* Compute the prescaler value */ //PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1; /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = 90-1; // 灯珠的频率800kHz,通道2是72MHZ,7200 0000/800 000=90,重装值设为90 TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* PWM1 Mode configuration: Channel1 *///开启PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//第一个有效电平一定要是高电平 TIM_OC1Init(TIM2, &TIM_OCInitStructure); /* configure DMA */ /* DMA clock enable */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* DMA1 通道配置 Config */ DMA_DeInit(DMA1_Channel2); //DMA1对应定时器2的通道 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM2_CCR1_Address; // Timer CCR1的物理地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LED_BYTE_Buffer; // 写储存器的地址,buffer memory DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //方向,DMA数据转换模式从存储器到外设from memory to peripheral DMA_InitStructure.DMA_BufferSize = 0; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//不自增模式,因为地址不可以变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 储存器自增模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设的接收字节 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // stop DMA feed after buffer size is reached DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel2, &DMA_InitStructure); /* TIM3 CC1 DMA Request enable */ TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);//使能DMA的中断 } void WS2812_send(uint8_t (*color)[3], uint16_t len,u8 flag) { static unsigned char iie = 0; uint8_t i; uint16_t memaddr; uint16_t buffersize; buffersize = (len*24);//+43; // number of bytes needed is #LEDs * 24 bytes + 42 trailing bytes memaddr = 0; // reset buffer memory index while (len) { for(i=0; i<8; i++) // GREEN data { LED_BYTE_Buffer[memaddr] = ((color[iie][1]<=12) iie = 0; iie++; } len--; } //===================================================================// //bug:最后一个周期波形不知道为什么全是高电平,故增加一个波形 LED_BYTE_Buffer[memaddr] = ((color[0][2]<<8) & 0x0080) ? TIMING_ONE:TIMING_ZERO; //===================================================================// memaddr++; while(memaddr < buffersize) { LED_BYTE_Buffer[memaddr] = 0; memaddr++; } DMA_SetCurrDataCounter(DMA1_Channel2, buffersize); // load number of bytes to be transferred DMA_Cmd(DMA1_Channel2, ENABLE); // enable DMA channel 6 TIM_Cmd(TIM2, ENABLE); // enable Timer 3 while(!DMA_GetFlagStatus(DMA1_FLAG_TC2)) ; // wait until transfer complete TIM_Cmd(TIM2, DISABLE); // disable Timer 3 DMA_Cmd(DMA1_Channel2, DISABLE); // disable DMA channel 6 DMA_ClearFlag(DMA1_FLAG_TC2); // clear DMA1 Channel 6 transfer complete flag }




助工
2021-05-08 17:54:21 打赏
10楼

第五弹 组装外壳+显示图形

1、外壳粘好是这个样子,由于没带热熔胶枪,只能临时找些白乳胶代替

视频连接:https://www.bilibili.com/video/BV18V411j7wS/


1621172471951722.jpg

2、贴黑色墙纸的外壳

1621172635761655.jpg

3、点亮后的样子,图片又躺那了

1621172833273598.jpg

第二张

1621172871998508.jpg

第三张

1621172899411336.jpg


共16条 1/2 1 2 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册]