STM32应用开发

您所在的位置:网站首页 ws2812引脚怎么看 STM32应用开发

STM32应用开发

2024-07-15 05:05| 来源: 网络整理| 查看: 265

STM32应用开发——使用PWM+DMA驱动WS2812

目录 STM32应用开发——使用PWM+DMA驱动WS2812前言1 硬件介绍1.1 WS2812介绍1.1.1 芯片简介1.1.2 引脚描述1.1.3 工作原理1.1.4 时序1.1.5 传输协议 1.2 电路设计 2 软件编程2.1 软件原理2.2 测试代码2.2.1 底层驱动2.2.2 灯效应用 2.3 运行测试2.3.1 时序测试2.3.2 实际效果 结束语

前言

串行灯带的应用十分广泛,其中以WS2812最为经典,这种灯带一般都是通过单总线的方式来驱动,也就是由一根数据线按照特定的时序输出,继而驱动灯带。这种方式在硬件和软件上都非常简单,但是如果软件用GPIO模拟时序的话比较占用主线程的资源,因此,如果能用硬件外设(比如PWM、SPI、串口)来模拟出这个时序,就能节省MCU的资源。 本文以PWM+DMA为例介绍如何驱动WS2812。

1 硬件介绍 1.1 WS2812介绍 1.1.1 芯片简介

WS2812是一款智能控制LED光源,其外观采用最新的MOLDING封装技术、控制电路和RGB芯片集成在2020组件的封装中。其内部包括智能数字端口数据锁存和信号整形放大驱动电路。还包括精密内部振荡器和电压可编程恒流控制部分,有效保证像素点光源的颜色。

1.1.2 引脚描述 引脚名称描述DO数据输出控制数据输出到下一个芯片GND地电源负极DI数据输入控制数据输入VDD电源电源正极 1.1.3 工作原理

通过级联法把每个灯的DI和DO引脚首尾相连,数据可以从第一个IC开始,不断的传输到后面每一个IC,从而实现整条灯带的控制。 在这里插入图片描述

1.1.4 时序

WS2812通过不同的时序来表示0码、1码和复位码,如下图所示: 在这里插入图片描述 其中各信号的电平如下图所示: 在这里插入图片描述 注:不同型号的芯片在时序上会有点差异,具体以芯片数据手册为准。

1.1.5 传输协议

传输过程如下图所示: 在这里插入图片描述

每一个灯珠的RGB数据排列如下: 在这里插入图片描述

1.2 电路设计

WS2812的控制方法很简单,每个灯珠首尾相接进行级联即可,如下图所示: 在这里插入图片描述 其中,第一个灯珠的DI引脚接入到MCU的一个GPIO上面。

我这里使用STM32F103来作为主控MCU,引脚接线如下:

MCU引脚灯带引脚描述PA0DI由MCU发送控制信号输入到灯带 2 软件编程 2.1 软件原理

通过DMA可以精确控制PWM输出的每一个方波,然后通过调整占空比,就可以输出0码、1码和复位码,从而实现灯珠的驱动。 举个例子:按照上面的手册的时序要求,每一个逻辑电平周期在1.25us左右,也就是800kHz,那么PWM输出的频率就可以设置为800kHz。此时改变PWM的占空比,就可以区分编码“0”和编码“1”,比如编码“0”的高电平脉宽和低电平脉宽分别为0.4us和0.85us,那么对应的PWM占空比就是32%和68%,然后通过DMNA连续传输RGB数据就可以实现灯带的颜色和亮度控制。

测试电平时序如下:

逻辑电平脉宽PWM占空比逻辑0高电平0.40±0.15us32%逻辑0低电平0.85±0.15us68%逻辑1高电平0.85±0.15us68%逻辑1低电平0.40±0.15us32%复位低电平1.25±0.60us0% 2.2 测试代码

根据上述原理,编写测试代码。

2.2.1 底层驱动

ws2812_driver.h :

#ifndef __WS2812_DRIVER_H #define __WS2812_DRIVER_H #include "stm32f10x.h" #include "stm32f10x_conf.h" #define TIM2_CCR1_Address 0x40000034 // stm32 tim2 base address offset 0x34 #define LED_NUM 8 // LED的数量 #define RGB_BIT 24 // 每个灯有24bit的RGB数据,依次按G R B排列 #define RESET_WORD 5 // 在传输RGB数据前保持一段低电平 #define DUMMY_WORD 5 // 在传输RGB数据后保持一段低电平 #define TIMING_0 29 // T0H(32%) = 1.25us * (29 / 90) = 0.40us, T0L(68%) = 1.25 - 0.40 = 0.85us #define TIMING_1 61 // T1H(68%) = 1.25us * (61 / 90) = 0.85us, T1L(32%) = 1.25 - 0.85 = 0.40us void led_display(uint8_t (*led_buf)[3], uint8_t led_num); void ws2812_init(void); #endif

ws2812_driver.c :

#include "ws2812_driver.h" #include "string.h" uint16_t pwm_dma_buf[RESET_WORD + RGB_BIT * LED_NUM + DUMMY_WORD]; void pwm_init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_ResetBits(GPIOA, GPIO_Pin_0); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 90 - 1; // 72MHz / 90 = 800kHz TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); /* PWM2 Mode configuration: Channel1 */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 50; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // TIM_ARRPreloadConfig(TIM2, ENABLE); /* TIM2 enable counter */ TIM_Cmd(TIM2, ENABLE); } void pwm_dma_init(void) { /* configure DMA */ DMA_InitTypeDef DMA_InitStructure;//定义DMA初始化结构体 /* DMA clock enable */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA时钟(用于SPI的数据传输) memset((uint8_t*)&pwm_dma_buf, 0, sizeof(pwm_dma_buf)); /* DMA1 Channel5 Config for PWM2 by TIM2_CH1*/ DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM2_CCR1_Address; // physical address of Timer 3 CCR1 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&pwm_dma_buf; // this is the buffer memory DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // data shifted from memory to peripheral DMA_InitStructure.DMA_BufferSize = sizeof(pwm_dma_buf)/2; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // automatically increase buffer index 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_Medium; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel5, &DMA_InitStructure); /* TIM2 DMA Request enable */ TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE); TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE); } void pwm_dma_send(void) { DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(pwm_dma_buf)/2); // load number of bytes to be transferred DMA_Cmd(DMA1_Channel5, ENABLE); // enable DMA channel 5 TIM_Cmd(TIM2, ENABLE); // enable Timer 2 while(!DMA_GetFlagStatus(DMA1_FLAG_TC5)) ; // wait until transfer complete DMA_Cmd(DMA1_Channel5, DISABLE); // disable DMA channel 5 DMA_ClearFlag(DMA1_FLAG_TC5); // clear DMA1 Channel 5 transfer complete flag TIM_Cmd(TIM2, DISABLE); // disable Timer 2 } void led_display(uint8_t (*led_buf)[3], uint8_t led_num) { uint8_t i, j; // led_buf -> pwm_dma_buf for(i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3