使用STM32的PWM和DMA功能
1.学习一下STM32的PWM+DMA方式输出脉冲信号,最典型的应该就是驱动WS2812B灯带了,用这个方式很方便,也很容易理解。本次实验用的主控MCU是STM32C8T6,模块使用WS2812B灯带,5V-30颗灯珠。
2
.STM32C8T6基本的资源介绍:
STM32F103x8和STM32F103xB增强型系列使用高性能的ARM® Cortex™-M3 32位的RISC内核,工
作频率为72MHz,内置高速存储器(高达128K字节的闪存和20K字节的SRAM),丰富的增强I/O端口
和联接到两条APB总线的外设。所有型号的器件都包含2个12位的ADC、3个通用16位定时器和1个
PWM定时器,还包含标准和先进的通信接口:多达2个I
2
C接口和SPI接口、3个USART接口、一个
USB接口和一个CAN接口。
3.
WS2812B:就是一个RGB灯带,只给信号,就量相应的颜色,理论上像素255*255*255种颜色,主要实验3原色显示。
4.下面是STM32F103配置PWM+DMA的方式,使用的标准库。我使用的是PA1,定时器2通道2.再看一下DMA的配置,TIM2_CH2对应的DMA1通道是7.在确认主控硬件资源之后,先别急着敲代码,先了解一下WS2812的驱动方式。
第1个WS2812B灯珠接收到了第1个24bit的数据,做出响应(发光);第N个WS2812B灯珠接收到了第N个24bit的数据,
再收到第二个24bit的数据后,直接转发给第二个WS2812B灯珠,由第二个WS2812B灯珠做出响应;依次类推
要想RGB灯亮,必须发送24位的800khz方波才行。
void Ws2812b_Pwm_Dma_GpioConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
DMA_InitTypeDef DMA_InitStruct;
/* 开启GPIO TIM DMA 外设时钟 */
RCC_APB2PeriphClockCmd(WS2812B_TIMx_GPIO_CLK, ENABLE);//GPIO时钟
RCC_APB1PeriphClockCmd(WS2812B_TIMx_CLK, ENABLE);//TIM2_CH2
RCC_AHBPeriphClockCmd(WS2812B_DMAx_CLK, ENABLE);//DMA1
/* GPIO端口配置 */
GPIO_InitStruct.GPIO_Pin = WS2812B_TIMx_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(WS2812B_TIMx_PORT, &GPIO_InitStruct);
/* 定时器时基配置 */
TIM_TimeBaseInitStruct.TIM_Prescaler = WS2812B_TIMx_Prescaler-1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = WS2812B_TIMx_Period-1; //72M / 90 = 800K
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(WS2812B_TIMx, &TIM_TimeBaseInitStruct);
/* 定时器输出比较配置 */
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OC2Init(WS2812B_TIMx, &TIM_OCInitStruct);//通道2初始化化
TIM_CtrlPWMOutputs(WS2812B_TIMx, ENABLE); //PWM输出使能
TIM_Cmd(WS2812B_TIMx, DISABLE);
TIM_DMACmd(WS2812B_TIMx, WS2812B_DMAx_Source, ENABLE);
/* DMA配置 */
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(TIM2->CCR2) ;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Pixel_Buff;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStruct.DMA_BufferSize = PIXEL_NUM * RGB;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
DMA_Init(WS2812B_DMAx_Channelx, &DMA_InitStruct);
DMA_Cmd(WS2812B_DMAx_Channelx, DISABLE);
}
定时器2在APB1中线上,系统时钟是72M,TIM_Prescaler=0,.TIM_Period=89,一个周期就是1.25us,符合WS2812的时序,一个时钟时间是1/72=14ns,25*14ns=350ns符合0码,45*14=630ns符合1码时序。只需要发送pwm(25)代表0码,pwm(45)代表1码。
//缓存数据
u16 Pixel_Buff[PIXEL_NUM][RGB]={0};
void Ws281b_Show(void)
{
DMA_SetCurrDataCounter(WS2812B_DMAx_Channelx, (u16)(PIXEL_NUM * RGB)); //设定DMA要传输数据的大小
DMA_Cmd(WS2812B_DMAx_Channelx, ENABLE);
TIM_OC2PreloadConfig(WS2812B_TIMx, TIM_OCPreload_Enable); //CH1预装载器使能
TIM_Cmd(WS2812B_TIMx, ENABLE);
while(DMA_GetFlagStatus(WS2812B_DMA_FLAG) != SET); //等待传输完成
DMA_Cmd(WS2812B_DMAx_Channelx, DISABLE);
TIM_OC2PreloadConfig(WS2812B_TIMx, TIM_OCPreload_Disable); //关闭CH1预装载器
TIM_Cmd(WS2812B_TIMx, DISABLE);
DMA_ClearFlag(WS2812B_DMA_FLAG);
WS2812B_PIN_L;
Delay_us(350);
}
void Ws2812b_CloseAll(void)//熄灭
{
u16 i;
u8 j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < 24; ++j)
{
Pixel_Buff[i][j] = T0H;
}
}
Ws281b_Show(); //传输数据
}
往数组填充数据就能使灯带亮起来了,WS2812灯珠自带有芯片的,所以只需要发送一个次数据就能亮起来