打印
[AT32L021]

【AT-START-L021测评】+ 驱动WS2812B

[复制链接]
95|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 suncat0504 于 2024-12-3 20:42 编辑

WS2812是一种基于数字信号传输的RGB LED灯珠,包含了三种颜色的LED和一个控制电路。电路构成中分为两个部分:数据输入和数据输出。

1、 数据输入:将控制器输出的数据信号通过串行通讯方式输入到WS2812内部的控制电路中。数据格式为24位二进制数,其中每8位表示一种颜色(红、绿、蓝),可以调节每种颜色的亮度和颜色值。
2、数据输出:根据接收到的数据信号,WS2812内部的控制电路会自动解码并控制三种颜色的LED发光,从而实现不同颜色和亮度的光效显示。具体来说,控制电路会将接收到的信号转换为PWM(脉冲宽度调制)信号,根据不同的PWM占空比来控制LED的亮度和颜色变化。

其构成的三色LED参数:

驱动时,每个LED占用3个字节,数据形式为:

每个 bit 都是通过(一个高电平 + 一个低电平)表示。

根据手册
1、一个短的(0.35 µs)高电平加一个长的(0.90 µs)低电平表示为“0”
2、一个长的(0.90 µs)高电平加一个短的(0.35 µs)低电平表示为“1”
3、单个 bit 信号周期, 高低电平时长合计为 1.25 µs
4、串接场合,发送超过 24 bit 信号后, 之前输入的信号会依次传递给串行的下一个 WS2812 LED
5、控制器发送数据前需要保持低电平超过 50 µs(又称为 RESET), 用于通知 WS2812 开始接收数据.
这些时间不要求严格遵守,允许有一定的偏差。传输信号时, 高低电平时间间隔如果不符合手册要求, 差距较大时LED会不工作(不亮), 在间隔接近但是不完全满足时, LED会出现显示错乱, 色彩乱跳等。
WS2812的工作电压范围比较宽,3.5V ~ 5.3V。WS2812的数据传输速率较慢,在使用单片机驱动WS2812时要注意传输速率。
在实际应用中经常使用级联方式,将多个WS2812串接起来,制作成灯条、灯带等方式。


在级联方式下的数据传输:

如果以SPI方式驱动WS2812,根据计算,每个Bit位按照1.2微秒,把每个位量化为4个脉冲对应的数据,每个基本单位数据占用0.3微秒。

按照每个位占用4个基本单位方式处理,一个LED需要用3个字节,24位,也就是说一个LED要是用24 x 4 = 96个基本单位。这96个基本单位是8和16的整倍数,因此可以换算为SPI传输的字节数据。也就是说,一个LED用的控制数据3字节(24位),可以转变为SPI传输用的码数据12字节(96位)。

比如发绿色光时,按照亮度为16,则一个LED的数据:

码数据就是通过SPI实际发送的数据。这样控制好SPI发送的速度,保证码数据的时序与WS2812的时序一致,就可以保证LED按照我们的要求显示颜色。

在AL32L021单片机中,拥有2个SPI接口,在主或从模式下的通信速率可达36兆位/秒。预分频器可产生多种主模式频率,可配置成每帧8位或16位。硬件的CRC产生/校验支持基本的SD卡、MMC模式、和SDHC模式。所有的SPI接口都可以使用DMA操作。

为了以SPI方式适配WS2812,需要严格控制SPI的通讯速度,根据上面WS2812的资料,需要1/0.3MHz≈3.3MHz的通讯速度。经过计算,可以设置开发板的主频为48MHz,经过16分频后给SPI外设。在时钟设置上,可以使用雅特力的WorkBench的设置生成系统时钟的代码。


SPI的时钟设置:
/* 配置SPI外设 */

static void spi_config(void) {

dma_init_type dma_init_struct; // DMA外设

spi_init_type spi_init_struct; // SPI外设

crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE); // DMA外设时钟

dma_reset(DMA1_CHANNEL2); // DMA通道

dma_reset(DMA1_CHANNEL3);

dma_reset(DMA1_CHANNEL4);

dma_reset(DMA1_CHANNEL5);

dma_flexible_config(DMA1, FLEX_CHANNEL3, DMA_FLEXIBLE_SPI1_TX); // 通道对应的SPI2外设端口:主机输出

dma_default_para_init(&dma_init_struct);

dma_init_struct.buffer_size = BUFFER_SIZE; // DMA缓冲区数据长度:96

dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE; // DMA数据单位长度:字节

dma_init_struct.memory_inc_enable = TRUE; // DMA自动调整地址:+1

dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE; // DMA数据单位长度:字节

dma_init_struct.peripheral_inc_enable = FALSE; // DMA自动调整地址:否

dma_init_struct.priority = DMA_PRIORITY_VERY_HIGH; // DMA处理优先级:中

dma_init_struct.loop_mode_enable = FALSE; // DMA循环模式:否

dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL; // DMA传输方向:内存 到 外设

dma_init_struct.memory_base_addr = (uint32_t)spi1_tx_buffer; // 内存地址:SPI1对应的发送数据缓冲区

dma_init_struct.peripheral_base_addr = (uint32_t)&(SPI1->dt); // 外设地址:SPI1的数据寄存器

dma_init(DMA1_CHANNEL3, &dma_init_struct); // 按照上诉设置初始化DMA通道3

// 初始化SPI外设

crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, TRUE);

spi_default_para_init(&spi_init_struct);

spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX; // 单向发送

spi_init_struct.master_slave_mode = SPI_MODE_MASTER; // 主机模式

spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_16; // 时钟频率:16分频,3.25MHz

spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB; // 高位数据先发

spi_init_struct.frame_bit_num = SPI_FRAME_8BIT; // 每帧一个字节

spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW; // 时钟极性

spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE; // 第2个脉冲沿有效

spi_init_struct.cs_mode_selection = SPI_CS_HARDWARE_MODE; // CS硬件操作

spi_init(SPI1, &spi_init_struct);

spi_hardware_cs_output_enable(SPI1, TRUE); // 允许使用硬件CS

// SPI1不接收,只发送

spi_i2s_dma_receiver_enable(SPI1, TRUE);

spi_i2s_dma_transmitter_enable(SPI1, TRUE);

spi_enable(SPI1, TRUE);

}

程序在ti_fullduplex_dma例程的基础上修改的,主程序代码如下:
/**
  **************************************************************************
  * [url=home.php?mod=space&uid=288409]@file[/url]     main.c
  * [url=home.php?mod=space&uid=247401]@brief[/url]    main program
  **************************************************************************
  *                       Copyright notice & Disclaimer
  *
  * The software Board Support Package (BSP) that is made available to
  * download from Artery official website is the copyrighted work of Artery.
  * Artery authorizes customers to use, copy, and distribute the BSP
  * software and its related documentation for the purpose of design and
  * development in conjunction with Artery microcontrollers. Use of the
  * software is governed by this copyright notice and the following disclaimer.
  *
  * THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
  * GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
  * TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
  * STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
  * INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
  *
  **************************************************************************
  */

#include "at32l021_board.h"
#include "at32l021_clock.h"

/** @addtogroup AT32L021_periph_examples
  * @{
  */

/** @addtogroup 021_SPI_ti_fullduplex_dma SPI_ti_fullduplex_dma
  * @{
  */
#define        LED_LD        5                                    //亮度,最大255
#define        WS2812_NUM        6                                //LED灯个数
#define        SPI_NUM        (WS2812_NUM*12)            //LED灯对应SPI字节数
uint8_t        led_RGB[WS2812_NUM][3];            //LED对应的RGB,led_buff[i][0]-->绿,led_buff[i][1]-->红,led_buff[i][0]-->蓝.

#define BUFFER_SIZE                      72

// SPI1 主机 发送
uint8_t spi1_tx_buffer[96] = {
    0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
    0x88, 0x88, 0x88, 0x88, 0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88,
    0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8E, 0xE8, 0x8E, 0x88,
    0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
    0x88, 0x88, 0x88, 0x88, 0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88,
    0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8E, 0xE8, 0x8E, 0x88,
    0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
    0x88, 0x88, 0x88, 0x88, 0x8E, 0xE8, 0x8E, 0x88, 0x88, 0x88, 0x88, 0x88
};


uint8_t spi1_rx_buffer[BUFFER_SIZE];

__IO uint8_t tx_index = 0;

volatile error_status transfer_status = ERROR;

static void gpio_config(void);
static void spi_config(void);
volatile error_status transfer_status1 = ERROR, transfer_status2 = ERROR;
volatile error_status transfer_status3 = ERROR, transfer_status4 = ERROR;

/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  buffer compare function.
  * @param  pbuffer1, pbuffer2: buffers to be compared.
  * @param  buffer_length: buffer's length
  * @retval the result of compare
  */
error_status buffer_compare(uint8_t* pbuffer1, uint8_t* pbuffer2, uint16_t buffer_length) {
    while(buffer_length--) {
        if(*pbuffer1 != *pbuffer2) {
            return ERROR;
        }

        pbuffer1++;
        pbuffer2++;
    }
    return SUCCESS;
}

/* 配置SPI外设 */
static void spi_config(void) {
    dma_init_type dma_init_struct;      // DMA外设
    spi_init_type spi_init_struct;      // SPI外设

    crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);   // DMA外设时钟
    dma_reset(DMA1_CHANNEL2);                               // DMA通道
    dma_reset(DMA1_CHANNEL3);
    dma_reset(DMA1_CHANNEL4);
    dma_reset(DMA1_CHANNEL5);

    dma_flexible_config(DMA1, FLEX_CHANNEL3, DMA_FLEXIBLE_SPI1_TX);     // 通道对应的SPI2外设端口:主机输出

    dma_default_para_init(&dma_init_struct);
    dma_init_struct.buffer_size = BUFFER_SIZE;                                  // DMA缓冲区数据长度:96
    dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;             //          DMA数据单位长度:字节
    dma_init_struct.memory_inc_enable = TRUE;                                   //          DMA自动调整地址:+1
    dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;     //          DMA数据单位长度:字节
    dma_init_struct.peripheral_inc_enable = FALSE;                              //          DMA自动调整地址:否
    dma_init_struct.priority = DMA_PRIORITY_VERY_HIGH;                             // DMA处理优先级:中
    dma_init_struct.loop_mode_enable = FALSE;                                   // DMA循环模式:否

    dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;                   // DMA传输方向:内存 到 外设
    dma_init_struct.memory_base_addr = (uint32_t)spi1_tx_buffer;                // 内存地址:SPI1对应的发送数据缓冲区
    dma_init_struct.peripheral_base_addr = (uint32_t)&(SPI1->dt);               // 外设地址:SPI1的数据寄存器
    dma_init(DMA1_CHANNEL3, &dma_init_struct);                                  // 按照上诉设置初始化DMA通道3

   
    // 初始化SPI外设
    crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, TRUE);
    spi_default_para_init(&spi_init_struct);
    spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;               // 单向发送
    spi_init_struct.master_slave_mode = SPI_MODE_MASTER;                        // 主机模式
    spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_16;                       // 时钟频率:16分频,3.25MHz
    spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;                 // 高位数据先发
    spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;                             // 每帧一个字节
    spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;                    // 时钟极性
    spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;                        // 第2个脉冲沿有效
    spi_init_struct.cs_mode_selection = SPI_CS_HARDWARE_MODE;                   // CS硬件操作
    spi_init(SPI1, &spi_init_struct);

    spi_hardware_cs_output_enable(SPI1, TRUE);                                  // 允许使用硬件CS
   
    // SPI1不接收,只发送
    spi_i2s_dma_receiver_enable(SPI1, TRUE);
    spi_i2s_dma_transmitter_enable(SPI1, TRUE);

    spi_enable(SPI1, TRUE);
}

/**
  * @brief  gpio configuration.
  * @param  none
  * @retval none
  */
static void gpio_config(void) {
    gpio_init_type gpio_initstructure;
    // SPI主机
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);

    // SPI1主机
    gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE4, GPIO_MUX_0);
    gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE5, GPIO_MUX_0);
    gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE6, GPIO_MUX_0);
    gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE7, GPIO_MUX_0);


    gpio_default_para_init(&gpio_initstructure);

    /* 主机SCK引脚:PA5 */
    gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
    gpio_initstructure.gpio_pull           = GPIO_PULL_DOWN;
    gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
    gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_initstructure.gpio_pins           = GPIO_PINS_5;
    gpio_init(GPIOA, &gpio_initstructure);

    /* 主机miso引脚:PA6 */
    gpio_initstructure.gpio_pull           = GPIO_PULL_DOWN;
    gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
    gpio_initstructure.gpio_pins           = GPIO_PINS_6;
    gpio_init(GPIOA, &gpio_initstructure);

    /* 主机mosi引脚:PA7 */
    gpio_initstructure.gpio_pull           = GPIO_PULL_DOWN;
    gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
    gpio_initstructure.gpio_pins           = GPIO_PINS_7;
    gpio_init(GPIOA, &gpio_initstructure);

    /* 主机cs引脚:PA4 */
    gpio_initstructure.gpio_pull           = GPIO_PULL_UP;
    gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
    gpio_initstructure.gpio_pins           = GPIO_PINS_4;
    gpio_init(GPIOA, &gpio_initstructure);
}

// 将颜色装载到SPI
void setWS2812Data(void) {
        uint8_t          *px;
        uint16_t  i,j;
        uint16_t  k;
        uint8_t          dat;
   
    // 测试用数据:红、绿、蓝、蓝、绿、红
    // 第1个LED:红色
    led_RGB[0][0] = 0;
    led_RGB[0][1] = LED_LD;        //红色的显示亮度
    led_RGB[0][2] = 0;
   
    // 第2个LED:绿色
    led_RGB[1][0] = LED_LD;        //绿色的显示亮度
    led_RGB[1][1] = 0;
    led_RGB[1][2] = 0;
   
    // 第3个LED:蓝色
    led_RGB[2][0] = 0;
    led_RGB[2][1] = 0;
    led_RGB[2][2] = LED_LD;        //蓝色的显示亮度

    // 第4个LED:蓝色
    led_RGB[3][0] = 0;
    led_RGB[3][1] = 0;
    led_RGB[3][2] = LED_LD;        //蓝色的显示亮度
   
    // 第5个LED:绿色
    led_RGB[4][0] = LED_LD;        //绿色的显示亮度
    led_RGB[4][1] = 0;
    led_RGB[4][2] = 0;
   
    // 第6个LED:红色
    led_RGB[5][0] = 0;
    led_RGB[5][1] = LED_LD;        //红色的显示亮度
    led_RGB[5][2] = 0;   

    // 初始化要发送的数据
        for(i=0; i<SPI_NUM; i++)        spi1_tx_buffer[i] = 0;
   
    // 设置中间变量使用的起始地址
        px = &led_RGB[0][0];        //首地址
   
    // 循环WS2812数量对应的红绿蓝三个字节数据
        for(i=0, j=0; i<(WS2812_NUM*3); i++) {
                dat = *px;
                px++;
        // 处理对应WS2812的字节数据,每个WS2812的红绿蓝3个字节,对应为24位。
        // 每次处理2位
        // 转换每个数据位对应的0码/1码为4位基本码位数据
                for(k=0; k<4; k++) {
                        if(dat & 0x80)        
                spi1_tx_buffer[j]  = 0xE0;        // 字码1 : 1110xxxx,
                        else                        
                spi1_tx_buffer[j]  = 0x80;        // 字码0 : 1000xxxx
            
                        if(dat & 0x40)        
                spi1_tx_buffer[j] |= 0x0E;        // 字码1 : 1110
                        else                        
                spi1_tx_buffer[j] |= 0x08;        // 字码0 : 1000
            
            // 每次处理2位,2位对应WS2912的一个字节
                        dat <<= 2;
            
            // 指向下一个字节(红、绿、蓝顺序)
                        j++;
                }
        }
   
    //
    printf("\r\n");
    for(i=0; i < BUFFER_SIZE; i++) {
        printf("0x%2x  ", spi1_tx_buffer[i]);
        if ((i%12) == 11) {
            printf("\r\n");
        }
    }
   
}


/**
  * @brief  main function.
  * @param  none
  * @retval none
  */
int main(void) {
    system_clock_config();        // 配置系统时钟
    at32_board_init();            // 板级初始化
    uart_print_init(115200);
   
    delay_us(100);                // 延迟
   
    setWS2812Data();
   
    gpio_config();                // 初始化用到的GPIO口
    at32_led_on(LED2);
    at32_led_off(LED2);

    spi_config();                 // 配置SPI外设
    dma_channel_enable(DMA1_CHANNEL3, TRUE);
    while(!dma_flag_get(DMA1_FDT3_FLAG));         // 等待DMA通道3完成数据传输   
   
    while(1) {
        at32_led_toggle(LED3);
        delay_ms(500);
    }
}

/**
  * @}
  */

/**
  * @}
  */

使用逻辑分析仪实测出来的SCLK和MOSI信号:

运行效果:

在调试程序的过程中,遇到过以下问题:
1、最开始WS2812模块使用的电源,选择了Arduino接口中的+5V,万用表测量为4.75V,看电压值,似乎是可用的,然后实际测量时,所有LED都不亮。

于是把WS2812模块使用的电源换成了拍真就口中的VDD,测试现象变为只有红色的显示正常,蓝色和绿色的一点不亮。

2、在分析1的实验结果时,观察SPI数据输出正常,猜测是因为蓝色和绿色的LED因为工作电压高,使用VDD的话,不够,所以不亮。

然后又选择开发板AT-LINK部分的E5V,结果依旧是所有颜色的LED又都不亮了。

3、再换成开发板AT-LINK部分的+3.3V电压后,才工作正常。

说明排针中的VDD(3.3V)带负载能力不够,3.3V可以驱动WS2812,虽然说明书中说工作电压在3.7V~5.3V范围。但实际应用还是高于3.7V好,并电压不够,蓝色LED的表现会不正常,进而影响其它混色的表现。


另外在完成测试后,改变SPI时钟主频,从10分频改到20分频,依然可以正常驱动WS2812,由此怀疑是不是构成0码和1码的信号,只要脉宽比正常,即使时间不匹配,也能正常。其它分频没有测试。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:大连伊飞特信息技术有限公司软件工程师
简介:本人于1993年毕业于大连理工大学。毕业后从事单片机开发工作5年,之后转入软件开发工作至今。

126

主题

3800

帖子

5

粉丝