RGB彩灯总结:
整个RGB彩灯的调试,应该花了不少于两个星期,中间遇到了很多让我很难受甚至很想放弃的problems,但是自己最终还是弄了下来,不能说是坚持了先来,但是至少能实现RGB彩灯的基本功能了,现在也在完善中。既然花了自己这么长时间,那,一定得好好记录一下。也希望大家通过我的调试过程,能够学到你们需要的东西
RGB的调试主要分了两种方法,一种是通过驱动芯片:SM16703、WS2811、TM1829三种芯片进行调试;另外一种是直接通过三个引脚,利用PWM进行调试。
两种方法其实各有优缺点:
---------------------------------------------------------------------------------
第一种方法的优点是:它只需要通过一个IO(DIN),通过给驱动芯片写入数据,就可以控制RGB彩灯的色变,但是它最大的缺点(我认为)是不管是哪个芯片,它的时序要求都是十分十分严格的,如果时序调不对,一般都是上电之后显示的是白光,当初也是因为在网上copy的代码,也没怎么详细的对代码进行分析,上来就开始调试,但是始终没有成功,又开始疯狂的在网上搜集资料,最后结论基本都是一样:调时序,时序、时序、时序是最重要的。
第二种方法,直接用PWM对R、G、B三个灯进行颜色控制,通过改变三个通道的占空比(0~255),三个灯进行颜色的组合,就可以实现不同的颜色。用这种方法虽然占用了三个IO口,但是在操作上确实是比较简单。
第一种方法,利用驱动芯片进行调试,下面以WS2811芯片为例:
WS2811芯片时序的图片。(我当时用的PE6为数据的输入引脚)
根据图表我们可以看得出:
TX0(发送0码时):
PEout(6)=1;延时为500ns;
pEout(6)=0;延时为2us;
TX1(发送1码时):
PEout(6)=1;延时为2us;
pEout(6)=0;延时为500ns;
RES设置为50us以上,我当时延时为80us;
既然延时那么重要,那我就简单的说一下调延时的思路,虽然我当时到最后也没有准确的调出来吧,但是还是想分享一下这个过程,望读者能有一些收获。
一、调ns延时函数思路:
要了解几个知识点:单片机的晶振、单片机系统时钟、时钟周期__nop()延时;
单片机的时钟:
单片机的系统时钟有几个来源(具体可以参考原子哥:第19讲 STM32时钟系统精讲),其中包括单片机内部晶振和外部晶振,STM32单片机内部和外部晶振均为8MHz,然后经过倍频、分频等一些操作成为系统时钟72MHz(可能写的不太好,也就是自己的简单了解);
上面说到了单片机的晶振,那晶振对于单片机有什么作用?
简单地说,没有晶振,就没有时钟周期,就无法执行程序代码,单片机就无法工作。STM32单片机有内部晶振和外部晶振,共同点是两个晶振都是8MHz;
不同点是:外部晶振稳定 内部晶振的误差比较大,但如果对频率要求不高的话(比如不涉及串口通信和精确定时等的话),用内部晶振就行 。但是内部时钟,频率受温度等其它影响。
时钟周期:
单片机工作时,是一条一条地从RoM中取指令,然后一步一步地执行。单片机访问一次存储器的时间,称之为一个机器周期,这是一个时间基准。—个机器周期包括12个时钟周期。如果一个单片机选择了12MHz晶振,它的时钟周期是1/12us,它的一个机器周期是12×(1/12)us,也就是1us
以STM32C8T6单片机而言:
外部晶振是8MHz,经过7倍频,为72MHz;所以它的一个时钟周期为1/72M=13.89ns
一个__nop();空语句按理来说应该占用的就是一个时钟周期,(这个可以通过逻辑分析仪进行测试)所以一个__nop();语句就是13.89ns;
这样就好说了,既然知道了一个__nop();语句占用的是13.89ns,那么当延时为500ns的时候,就可以500/13.89=36个,
// An highlighted block
void WS0_delay05us()//500ns延时
{
__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();
}
//而延时2us就可以通过调用4次WS0_delay05us()函数,
void WS0_delay2us(u8 i)//当i=4时,即2us
{
while(i--)
{
WS0_delay05us();
}
}
TX0函数:
void TX0() // 发送0
{
GPIO_SetBits(GPIOE, GPIO_Pin_6)
WS0_delay05us();//高电平延时500ns
GPIO_ResetBits(GPIOE, GPIO_Pin_6)
WS0_delay2us();//低电平延时2us
}
//同理TX1也是如此,只不过高低电平的持续时间正好相反。
**注意一点的是:**如何去测试TX0的时序是否正确,方法:
把上面的的TX0放到主函数中,主函数中只执行这一条语句,然后用逻辑分析仪去抓PE6引脚,看看它的高低电平持续为多长时间。
// An highlighted block
var foo = 'bar';
以我个人的调试经验,第一次测试的时候,高低电平持续的时间肯定和标准的有很大
的差距,那么怎么办那?
一步一步地调啊,(改变__nop();的数量,或者自己也可以用系统的delay__us();函数)千万千万不要心急,当你调试不出来的时候就想想我,我当时调这个时序的时
候都快要崩溃了,2个星期,所以,不要心急,耐心。
还有一点是,当一个芯片时序实在调试不出来的时候,自己也可以去尝试其他的芯
片,因为这三种芯片在延时函数的精度要求上还是有所区别的。
给大家推荐一个我当时参考的一个视频,我觉得讲的挺不错的,也希望大家能学到一些东西。
延时函数调试链接: link.
---------------------------------------------------------------------------------
接下来给大家说一下如何用PWM直接对RGB进行调试:
原理其实很简单,改变电平的占空比使RGB分别显示出不同的色值,然后通过组合显示出想要的颜色。目前实现的功能,通过PWM调试RGB彩灯能够实现一些特定的颜色,同时也可以同串口通信、485通信发送十六进制的数据来改变RGB的颜色(其中实现过程都采用的串口调试助手进行数据的发送)。
通过网上查阅资料,我搜集到了RGB彩灯的颜色表
RGB颜色链接: link.
我用的是TIM4的三个通道PB6/PB7/PB8
代码的主要配置如下:
timer.h
// An highlighted block
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM4_PWM_Init();
#endif
timer.c
(也就是PWM的初始化及配置),设置的重装载值为255(即0~255)正好对应RGB的颜色表。
// An highlighted block
#include "timer.h"
#include "usart.h"
#include "485.h"
//TIM4 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM4_PWM_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM4 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM4
TIM_TimeBaseStructure.TIM_Period =255; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =719; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM4 Channel/2/3/4 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
//TIM_OCInitStructure.TIM_Pulse=10;
TIM_OC1Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC1
TIM_OC2Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2
TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC3
TIM_OC4Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC4
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR1上的预装载寄存器
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR2上的预装载寄存器
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR3上的预装载寄存器
TIM_ARRPreloadConfig(TIM4,ENABLE);
TIM_Cmd(TIM4, ENABLE); //使能TIM3
TIM_CtrlPWMOutputs(TIM4,ENABLE);
}
//void RGB_SetDate(int rgb_data[])
//{
// TIM4_PWM_Init();
// TIM_SetCompare1(TIM4,rgb_data[0]);
// TIM_SetCompare2(TIM4,rgb_data[1]);
// TIM_SetCompare3(TIM4,rgb_data[2]);
//}
//*******************************************
//下面采用的是485通信进行的测试
//
//******************************************
//void RGB_SetDate()
//{
// //TIM4_PWM_Init();
// TIM_SetCompare1(TIM4,RS485_RX_BUF[0]);
// TIM_SetCompare2(TIM4,RS485_RX_BUF[1]);
// TIM_SetCompare3(TIM4,RS485_RX_BUF[2]);
//}
//下面的是利用串口通信
void RGB_SetDate()
{
//TIM4_PWM_Init();
TIM_SetCompare1(TIM4,USART_RX_BUF[0]);
TIM_SetCompare2(TIM4,USART_RX_BUF[1]);
TIM_SetCompare3(TIM4,USART_RX_BUF[2]);
}
main.c函数
// An highlighted block
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "timer.h"
#include "usart.h"
#include "485.h"
int red[3]={255,0,0};
int yellow[3]={255,255,0};
int green[3]={0,255,0};
int blue[3]={0,0,25500};
int qin[3]={0,255,255};
//int zi[3]={160,32,240};
int ziluolan[3]={138,43,226};
int hei[3]={0,0,0};
int main(void)
{
u8 key;
u16 t;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(9600);
delay_init(); //延时 函数初始化
TIM4_PWM_Init();
RS485_Init(9600); //初始化RS485 初始化串口的两个引脚PA2 PA3和PD7(485使能引脚)
while(1)
{
//**********下面采用的是485进行调试
// RS485_Send_Data(RS485_RX_BUF,5);
// RS485_Receive_Data(RS485_RX_BUF,&key);
// if(key)//接收到有数据
// {
// if(key>5)key=5;//最大是5个数据.
// }
//**********采用的是485进行调试
//----------------------------------------------------------
//----------------------------------------------------------
//**********下面采用的是串口进行调试
if(USART_RX_STA&0x8000)
{
for(t=0;t<3;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
USART_RX_STA=0;//清零语句是X必须有的,就像哪个输入捕获实验,如果不清零就不会再执行中断函数
//RGB_SetDate(USART_RX_BUF);
}
//***************采用的是串口进行调试
RGB_SetDate(USART_RX_BUF);
//RGB_SetDate(RS485_RX_BUF);
// delay_ms(500);
// RGB_SetDate(yellow);
// delay_ms(500);
// RGB_SetDate(green);
// delay_ms(500);
// RGB_SetDate(blue);
// delay_ms(500);
// RGB_SetDate(qin);
// delay_ms(500);
// RGB_SetDate(hei);
// delay_ms(500);
// RGB_SetDate(ziluolan);
// delay_ms(500);
}
}
嗯,最后放一下两个星期的调试成功吧:
RGB彩灯调试
代码链接:
链接:https://pan.baidu.com/s/1d4H_htjQOsrc5Wan0QwTuw
提取码:4yau
哈哈哈哈,不管怎么样,还是调试出来了,高兴,不过还需要继续完善。
//------------------------------------------------------------------------------------------
目前的问题:RGB电压的供电问题(目前是7.98V)控制1米的灯带,以及利用蓝牙通信(串口通信)时,只能收到一次数据的问题