STM32+PWM+DMA驱动WS2812

✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进

❤欢迎关注我的知乎:对error视而不见

代码获取、问题探讨及文章转载可私信。

☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。

🍎获取更多嵌入式资料可点击链接进群领取,谢谢支持!👇

点击领取更多详细资料

一、引言

WS2812是一种集控制电路与发光电路于一体的智能外控LED光源,内置信号整形电路,能保证波形畸变不会累加。STM32是意法半导体推出的一系列32位微控制器,具备强大的处理能力和丰富的外设资源。利用STM32的PWM(脉冲宽度调制)和DMA(直接内存访问)功能可以高效地驱动WS2812,实现多彩灯光效果。本文将详细介绍如何使用STM32的PWM和DMA功能来驱动WS2812,并给出相应的代码示例。

二、WS2812工作原理

2.1 信号传输

WS2812采用单总线通信协议,通过一根数据线接收数据。数据以串行方式传输,每个LED灯珠都能接收并转发数据,实现级联控制。

2.2 数据编码

WS2812的数据编码采用脉冲宽度调制,一个比特的数据由不同宽度的脉冲表示。逻辑“0”和逻辑“1”的脉冲宽度不同,通过精确控制脉冲宽度来传输数据。例如,逻辑“0”通常用一个较窄的脉冲表示,逻辑“1”用一个较宽的脉冲表示。

2.3 数据帧格式

每个WS2812灯珠需要24位数据来控制其RGB颜色,依次为绿(G)、红(R)、蓝(B)各8位。多个灯珠级联时,数据依次传输。

三、STM32的PWM和DMA功能

3.1 PWM功能

PWM是一种对模拟信号电平进行数字编码的方法,通过调节脉冲的占空比来控制输出信号的平均电压。STM32的定时器可以产生PWM信号,通过配置定时器的周期和占空比,可以精确控制PWM信号的频率和脉冲宽度。

3.2 DMA功能

DMA允许数据在不经过CPU干预的情况下,直接在内存和外设之间传输。使用DMA可以提高数据传输效率,减轻CPU的负担。在驱动WS2812时,可以使用DMA将存储在内存中的数据直接传输到定时器的比较寄存器,从而实现PWM信号的自动更新。

四、硬件连接

将WS2812的数据线连接到STM32的一个PWM输出引脚,例如PA0(对应定时器TIM2的通道1)。同时,确保WS2812的电源和地连接正确。

五、代码实现

5.1 定时器和DMA初始化

#include "stm32f10x.h"

#define LED_NUM 10  // WS2812灯珠数量
#define LED_DATA_LEN (LED_NUM * 24)  // 每个灯珠24位数据

uint16_t led_data[LED_DATA_LEN];  // 存储LED数据

// 定时器和DMA初始化
void TIM2_PWM_DMA_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    DMA_InitTypeDef DMA_InitStructure;

    // 使能GPIOA、TIM2和DMA1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    // 配置PA0为复用推挽输出
    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);

    // 配置定时器TIM2
    TIM_TimeBaseStructure.TIM_Period = 89;  // 定时器周期,根据WS2812的时钟要求设置
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    // 配置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);

    // 配置DMA
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR1;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)led_data;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = LED_DATA_LEN;
    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;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);

    // 使能DMA请求
    TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);

    // 使能定时器和DMA
    TIM_Cmd(TIM2, ENABLE);
    DMA_Cmd(DMA1_Channel2, ENABLE);
}

5.2 数据编码函数

// 编码逻辑0和逻辑1的脉冲宽度
#define T0H 26  // 逻辑0的高电平时间
#define T1H 56  // 逻辑1的高电平时间

// 编码一个字节的数据
void encode_byte(uint8_t byte, uint16_t *data) {
    for (int i = 7; i >= 0; i--) {
        if ((byte >> i) & 0x01) {
            *data++ = T1H;
        } else {
            *data++ = T0H;
        }
    }
}

// 编码一个灯珠的RGB数据
void encode_led(uint8_t r, uint8_t g, uint8_t b, uint16_t *data) {
    encode_byte(g, data);
    data += 8;
    encode_byte(r, data);
    data += 8;
    encode_byte(b, data);
}

// 设置所有灯珠的颜色
void set_all_leds(uint8_t r, uint8_t g, uint8_t b) {
    for (int i = 0; i < LED_NUM; i++) {
        encode_led(r, g, b, &led_data[i * 24]);
    }
}

5.3 主函数

int main(void) {
    TIM2_PWM_DMA_Init();

    while (1) {
        // 设置所有灯珠为红色
        set_all_leds(255, 0, 0);

        // 启动DMA传输
        DMA_Cmd(DMA1_Channel2, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Channel2, LED_DATA_LEN);
        DMA_Cmd(DMA1_Channel2, ENABLE);

        // 延时一段时间
        for (volatile int i = 0; i < 1000000; i++);

        // 设置所有灯珠为绿色
        set_all_leds(0, 255, 0);

        // 启动DMA传输
        DMA_Cmd(DMA1_Channel2, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Channel2, LED_DATA_LEN);
        DMA_Cmd(DMA1_Channel2, ENABLE);

        // 延时一段时间
        for (volatile int i = 0; i < 1000000; i++);

        // 设置所有灯珠为蓝色
        set_all_leds(0, 0, 255);

        // 启动DMA传输
        DMA_Cmd(DMA1_Channel2, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Channel2, LED_DATA_LEN);
        DMA_Cmd(DMA1_Channel2, ENABLE);

        // 延时一段时间
        for (volatile int i = 0; i < 1000000; i++);
    }
}

六、代码解释

6.1 定时器和DMA初始化

TIM2_PWM_DMA_Init函数中,首先使能了GPIOA、TIM2和DMA1的时钟。然后配置PA0为复用推挽输出,用于输出PWM信号。接着配置定时器TIM2的周期和PWM模式,以及DMA的相关参数,包括外设地址、内存地址、数据传输方向等。最后使能定时器和DMA。

6.2 数据编码函数

encode_byte函数用于编码一个字节的数据,将每个比特转换为逻辑0或逻辑1的脉冲宽度。encode_led函数用于编码一个灯珠的RGB数据,依次编码绿、红、蓝三个字节。set_all_leds函数用于设置所有灯珠的颜色。

6.3 主函数

在主函数中,首先调用TIM2_PWM_DMA_Init函数进行初始化。然后进入一个无限循环,依次设置所有灯珠为红、绿、蓝三种颜色,并通过DMA将数据传输到定时器的比较寄存器,实现PWM信号的更新。每次设置颜色后,延时一段时间,以便观察灯光效果。

七、总结

通过使用STM32的PWM和DMA功能,可以高效地驱动WS2812灯珠,实现多彩灯光效果。本文提供的代码示例可以作为一个基础,开发者可以根据实际需求进行扩展,例如实现渐变效果、流水效果等。同时,需要注意定时器的周期和PWM的脉冲宽度设置,以确保与WS2812的通信协议兼容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值