这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 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]<<i) & 0x0080) ? TIMING_ONE:TIMING_ZERO;
			memaddr++;
		}
		for(i=0; i<8; i++) // RED
		{
				LED_BYTE_Buffer[memaddr] = ((color[iie][0]<<i) & 0x0080) ? TIMING_ONE:TIMING_ZERO;
				memaddr++;
		}
		for(i=0; i<8; i++) // BLUE
		{
				LED_BYTE_Buffer[memaddr] = ((color[iie][2]<<i) & 0x0080) ? TIMING_ONE:TIMING_ZERO;
				memaddr++;
		}
		if(flag==1)
		{
		if(iie>=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 跳转至

回复

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