#STM32+PWM+DMA驱动 WS2812灯带
#文章目录
- 1.理论:
- 2代码:
理论:
1.WS2812参考数据手册:https://wenku.baidu.com/view/0925958fba68a98271fe910ef12d2af90342a80e.html
2.使用STM32F103C8T6驱动WS2812灯带,每个灯由24位组成三种颜色RGB,每8位控制一种颜色,颜色值0x00-0xFF。想要点亮一个灯需要将24位颜色值写入WS2812寄存器,如若点亮num个灯则需要写入 num24 位。根据数据手册可知还要加复位信号0(正常的0),因此需要写入 num24+reset(reset=?自己调) 位。
3.怎样发送num24+reset位数据呢? 由于WS2812的 0码 和 1码 并不是我们正常的 0 和 1,他是高低电平占不同的占空比,因此需要用PWM来模拟 0码 和 1码。当需要发送 0码 或 1码 时,我们只需要改变定时器的CCRx寄存器,来改变占空比,PWM频率800KHZ。
4.DMA则是搬运,将事先弄好的 num24+reset位 对应的占空比值传送给 TIMx的CCRx寄存器。这样PWM输出的就是对应的颜色值了。
5.我之前使用的是TIM1_CH2对应的DMA1_Channel3通道行不通,因为这个找了好久原因。后面试了TIM1_UP对应的DMA1_Channel7通道就可以了。我也不知道为什么一定要用TIM1_UP事件,看网上也有用了非TIMx_UP事件的。
6.一般会出现的问题 1.PWM配置那里没配必须的那两句。2.DMA配置传输长度与定义的数组类型不符。3.DMA事件得要用TIMx_UP事件和其对应得通道。4.复位信号时间给的不够
代码:
/*----------------------------------------- timer1 输出pwm --------------------------------------------------------*/
u16 LED_Buffer[2400]; //存放CCR值得数组
u8 color_buff[3] = {0x00,0x00,0xFF};//定义颜色GRB
#define ONE 44 //我的对应CCR=44 为1码
#define ZERO 22 //我的对应CCR=22 为0码
void Timer1_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
//clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
//GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//TIME
TIM_TimeBaseStructure.TIM_Period = 80-1; // 800kHZ 我时钟是64MHZ 64/80=800KHZ
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
//PWM
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState=TIM_OutputNState_Disable;//高级定时器这句一定要配
TIM_OCInitStructure.TIM_Pulse = 0; //CCRX寄存器值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM1,ENABLE);
TIM_OC2PreloadConfig(TIM1,TIM_OCPreload_Enable);//使能预加载值,这句一定要配,不然时序会乱
//DMA
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(TIM1->CCR2);//设置CCR值
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)LED_Buffer; //存放CCR值的数组
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 240;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//这里传输的是多少位,根据你定义的存放CCR值得数据类型,我是u16
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//这里传输的是多少位,根据你定义的存放CCR值得数据类型,我是u16
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
//这里我之前用的是TIM_DMA_CC2 对应的DMA1_Channel3通道行不通 ,具体为何我也不知道,后面使用TIM_DMA_Update 对应的DMA1_Channel5
TIM_DMACmd(TIM1, TIM_DMA_Update, ENABLE);
TIM_Cmd(TIM1, DISABLE);
}
/*---------- 组包 将颜色位 转变成CCR值 发送颜色顺序GRB高位先发 我这里所有的灯都一样的颜色要不一样的自己改下面的-----------*/
void WS2812_led_send(uint8_t *color, uint16_t len)
{
uint8_t i;
uint16_t memaddr;
uint16_t buffersize;
memset(LED_Buffer,0,2400);
buffersize = (len*24)+400; //颜色位数+复位信号
memaddr = 0;
while(len)
{
for(i=0; i<8; i++) // GREEN
{
LED_Buffer[memaddr] = ((color[0]<<i) & 0x80) ? ONE:ZERO;
memaddr++;
}
for(i=0; i<8; i++) // RED
{
LED_Buffer[memaddr] = ((color[1]<<i) & 0x80) ? ONE:ZERO;
memaddr++;
}
for(i=0; i<8; i++) // BLUE
{
LED_Buffer[memaddr] = ((color[2]<<i) & 0x80) ? ONE:ZERO;
memaddr++;
}
len--;
}
DMA_SetCurrDataCounter(DMA1_Channel5, buffersize); //下面这几句的顺序,不同型号MCU可能也会不同
DMA_Cmd(DMA1_Channel5, ENABLE);
TIM_Cmd(TIM1, ENABLE);
while(!DMA_GetFlagStatus(DMA1_FLAG_TC5)) ;
TIM_Cmd(TIM1, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC5);
}
int main(void)
{
Timer1_init();
while(1)
{
WS2812_led_send(color_buff,10);
delay_ms(5);
}
}