• 方案介绍
  • 附件下载
  • 相关推荐
申请入驻 产业图谱

STM32使用SPI+DMA驱动WS2812

13小时前
274
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

stm32f103_ws2812_spi.7z

共1个文件

# STM32应用开发——使用SPI+DMA驱动WS2812
@[TOC](目录)
## 前言
串行灯带的应用十分广泛,其中以WS2812最为经典,这种灯带一般都是通过单总线的方式来驱动,也就是由一根数据线按照特定的时序输出,继而驱动灯带。这种方式在硬件和软件上都非常简单,但是如果软件用GPIO模拟时序的话比较占用主线程的资源,因此,如果能用硬件外设(比如PWM、SPI、串口)来模拟出这个时序,就能节省MCU的资源。
本文以SPI+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,从而实现整条灯带的控制。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e0a07a2cf9474cbe12e4006c0042121b.png =300x)
#### 1.1.4 时序
WS2812通过不同的时序来表示`0码`、`1码`和`复位码`,如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b9926fdef2ada1f7bdd77651a8d67bbe.png)
其中各信号的电平如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e14c3f5627d73f9241e237f4415d6a1d.png)
<font color=#ff9966>注:不同型号的芯片在时序上会有点差异,具体以芯片数据手册为准。</font>
#### 1.1.5 传输协议
传输过程如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0f013697de7c8a98004a2e4709cdbb16.png)
每一个灯珠的RGB数据排列如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/be316baf49659a892155066dd7ff5598.png)
### 1.2 电路设计
WS2812的控制方法很简单,每个灯珠首尾相接进行级联即可,如下图所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/92452f5c6165dfb2e7cc99158e6cc7b4.png)
其中,第一个灯珠的DI引脚接入到MCU的一个GPIO上面。
我这里使用STM32F103来作为主控MCU,引脚接线如下:
| MCU引脚 | 灯带引脚 | 描述 |
|:-------|:------|:------|
| PB15 | DI | 由MCU发送控制信号输入到灯带 |
| PB14 | 无 | MCU输出的SPI CLK,即使是用SPI的MOSI模拟时序,对于MCU来说依然是SPI接口,因此CLK还是会输出时钟信号,但是该信号对于LED灯来说是无用的,因此LED端不需要接 |
<font color=ff0000>注:使用SPI模拟LED时序时,SPI的CLK引脚不能作为普通IO使用,因为SPI和CLK和MOSI是硬件一起输出的,无法单独只输出MOSI,因此即使实际上只有MOSI一个引脚用于驱动LED灯,CLK引脚也不能作为其他功能引脚使用。软件设置SPI为单线SPI输出时,MISO可以单独作为普通GPIO使用。</font>
## 2 软件编程
### 2.1 软件原理
通过DMA可以控制SPI连续输出自定义数据,然后通过调节SPI速率以及数据组合凑出`0码`、`1码`和`复位码`等波形,从而实现灯珠的驱动。
举个例子:按照上面的手册的时序要求,每一个逻辑电平周期在1.25us左右,那么SPI输出的频率就可以设置为2.25M(72M/32)。然后用SPI数据的3个bit表示灯的1个bit,就可以区分编码“0”和编码“1”,因为编码“0”和编码“1”的高低电平脉宽比例约为1:2和2:1,而SPI的3个bit可以设置成100和110,对应LED灯的编码。编码“0”的高电平脉宽和低电平脉宽分别为0.4us和0.85us,那么对应到SPI的1个bit脉宽是0.44us(1/2.25M),2个bit脉宽是0.88,3个bit为一个周期1.33us,在LED灯的容错范围内,因此可以通过DMA+SPI的方式模拟时序连续传输RGB数据就可以实现灯带的颜色和亮度控制。
时序示意图:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e4c0377a704749f5973bbef2830bc890.png)
测试电平时序如下:
| 逻辑电平 | 脉宽 | SPI数据 |
|:------------|:------------|:------------|
| 逻辑0高电平 | 0.40±0.15us | 0.44us(1bit) |
| 逻辑0低电平 | 0.85±0.15us | 0.88us(2bit) |
| 逻辑1高电平 | 0.85±0.15us | 0.88us(2bit) |
| 逻辑1低电平 | 0.40±0.15us | 0.44us(1bit) |
| 复位低电平 | 1.25±0.60us | 1.33us(3bit) |
### 2.2 测试代码
根据上述原理,编写测试代码。
#### 2.2.1 底层驱动
<font color=#0033ff>ws2812_driver.h :</font>
```c
#ifndef __WS2812_DRIVER_H
#define __WS2812_DRIVER_H
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#define SPI2_BOUNDARY_ADDR 0x40003800  // spi2 base address
#define SPI2_DR_ADDR       SPI2_BOUNDARY_ADDR + 0x0C  // spi2 data address offset 0x0C
#define LED_NUM     8
#define RGB_BIT     24
void led_display(uint8_t (*led_buf)[3], uint8_t led_num);
void ws2812_init(void);
#endif
```
<font color=#0033ff>ws2812_driver.c :</font>
```c
#include "ws2812_driver.h"
#include "string.h"
uint8_t spi_buf[LED_NUM][9];
uint8_t dma_txbuf[LED_NUM*9];
void spi_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,  ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //设置SPI单向或者双向的数据模式:SPI设置为单向发送
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//串行同步时钟的第一个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//定义波特率预分频的值:波特率预分频值为32,速率:2.25MHz
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
}
void spi_dma_init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI2_DR_ADDR;      //设置发送外设(0x4000380C) 地址(源地址)
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dma_txbuf;             //设置 SRAM 存储地址(源地址)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                      //传输方向 内存-外设
    DMA_InitStructure.DMA_BufferSize = sizeof(dma_txbuf);                   //设置 SPI2 接收长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;        //外设地址增量(不变)
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                 //内存地址增量(变化)
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设传输宽度(字节)
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         //内存传输宽度(字节)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                           //传输方式,一次传输完停止,不重新加载
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;                 //中断方式-高(三级)
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                            //内存到内存方式禁止
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);                         //开启 DMA1_Channel5 传输完成中断
    // DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);                      //开启 DMA1_Channel5 传输错误中断
    /* Enable DMA TX request */
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);                        //发送缓冲区DMA使能
    DMA_Cmd(DMA1_Channel5, DISABLE);                                        //暂时不开启DMA 通道 DMA1_Channel5
}
void dma_nvic_init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable DMA1 channel5 IRQ Channel */
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
void DMA1_Channel5_IRQHandler(void)
{
    /* Test on DMA1 Channel5 Transfer Complete interrupt */
    if(DMA_GetITStatus(DMA1_IT_TC5))
    {
        DMA_Cmd(DMA1_Channel5, DISABLE);
        /* Clear DMA1 Channel5 Half Transfer, Transfer Complete and Global interrupt pending bits */
        DMA_ClearFlag(DMA1_FLAG_TC5);
        DMA_ClearITPendingBit(DMA1_IT_GL5);
    }
}
uint32_t ws2812_rgb_encoding(uint32_t *value)
{ // 把GRB的每一个bit扩展为3bits,0b110代表WS2812B的逻辑'1',0b100代表WS2812B的逻辑'0'
    uint32_t encoding=0;
    int index = 0;
    while (index < 8)
    {
        encoding = encoding << 3;
        if (*value & (1 << 23))
        {
            encoding |= 6;   // 0b110
        }
        else
        {
            encoding |= 4;   // 0b100
        }
        *value <<= 1;
        index++;
    }
return encoding;
}
void ws2812_data_pack(uint8_t led_idx, uint32_t value)
{
    uint32_t encoding;
// Process the GREEN byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][0] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][1] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][2] = (encoding & 0xFF);
    // Process the RED byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][3] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][4] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][5] = (encoding & 0xFF);
    // Process the BLUE byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][6] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][7] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][8] = (encoding & 0xFF);
}
void spi_dma_send(void)
{
    memcpy(dma_txbuf, spi_buf, sizeof(spi_buf));
    DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(dma_txbuf));
    DMA_Cmd(DMA1_Channel5, ENABLE);
}
void led_display(uint8_t (*led_buf)[3], uint8_t led_num)
{
uint8_t i;
    uint32_t color_value;
// led_buf -> spi_buf
for(i = 0; i < led_num; i++)
{// N led
        color_value = (led_buf[i][1] << 16) | (led_buf[i][0] << 8) | led_buf[i][2];
        ws2812_data_pack(i, color_value);
}
// spi start
spi_dma_send();
}
void ws2812_init(void)
{
    spi_init();
    dma_nvic_init();
    spi_dma_init();
}
```
#### 2.2.2 灯效应用
<font color=#0033ff>ws2812_app.h :</font>
```c
#ifndef __WS2812_APP_H
#define __WS2812_APP_H
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "ws2812_driver.h"
typedef enum
{
LED_MODE_OFF,
LED_MODE_ALL_ON,
LED_MODE_BREATHE,
LED_MODE_GRADIENT,
LED_MODE_FLOW,
}led_mode_t;
typedef struct _led_config_t
{
    led_mode_t mode;
    uint8_t g;
uint8_t r;
uint8_t b;
uint8_t brightness;
}led_config_t;
void led_init(void);
void led_handle(void);
#endif
```
<font color=#0033ff>ws2812_app.c :</font>
```c
#include "ws2812_driver.h"
#include "string.h"
uint8_t spi_buf[LED_NUM][9];
uint8_t dma_txbuf[LED_NUM*9];
void spi_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,  ENABLE );//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //设置SPI单向或者双向的数据模式:SPI设置为单向发送
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//串行同步时钟的第一个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;//定义波特率预分频的值:波特率预分频值为32,速率:2.25MHz
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
}
void spi_dma_init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI2_DR_ADDR;      //设置发送外设(0x4000380C) 地址(源地址)
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dma_txbuf;             //设置 SRAM 存储地址(源地址)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                      //传输方向 内存-外设
    DMA_InitStructure.DMA_BufferSize = sizeof(dma_txbuf);                   //设置 SPI2 接收长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;        //外设地址增量(不变)
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                 //内存地址增量(变化)
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设传输宽度(字节)
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         //内存传输宽度(字节)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                           //传输方式,一次传输完停止,不重新加载
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;                 //中断方式-高(三级)
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                            //内存到内存方式禁止
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);                         //开启 DMA1_Channel5 传输完成中断
    // DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);                      //开启 DMA1_Channel5 传输错误中断
    /* Enable DMA TX request */
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);                        //发送缓冲区DMA使能
    DMA_Cmd(DMA1_Channel5, DISABLE);                                        //暂时不开启DMA 通道 DMA1_Channel5
}
void dma_nvic_init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable DMA1 channel5 IRQ Channel */
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
void DMA1_Channel5_IRQHandler(void)
{
    /* Test on DMA1 Channel5 Transfer Complete interrupt */
    if(DMA_GetITStatus(DMA1_IT_TC5))
    {
        DMA_Cmd(DMA1_Channel5, DISABLE);
        /* Clear DMA1 Channel5 Half Transfer, Transfer Complete and Global interrupt pending bits */
        DMA_ClearFlag(DMA1_FLAG_TC5);
        DMA_ClearITPendingBit(DMA1_IT_GL5);
    }
}
uint32_t ws2812_rgb_encoding(uint32_t *value)
{ // 把GRB的每一个bit扩展为3bits,0b110代表WS2812B的逻辑'1',0b100代表WS2812B的逻辑'0'
    uint32_t encoding=0;
    int index = 0;
    while (index < 8)
    {
        encoding = encoding << 3;
        if (*value & (1 << 23))
        {
            encoding |= 6;   // 0b110
        }
        else
        {
            encoding |= 4;   // 0b100
        }
        *value <<= 1;
        index++;
    }
return encoding;
}
void ws2812_data_pack(uint8_t led_idx, uint32_t value)
{
    uint32_t encoding;
// Process the GREEN byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][0] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][1] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][2] = (encoding & 0xFF);
    // Process the RED byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][3] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][4] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][5] = (encoding & 0xFF);
    // Process the BLUE byte
encoding = ws2812_rgb_encoding(&value);
    spi_buf[led_idx][6] = ((encoding >> 16) & 0xFF);
    spi_buf[led_idx][7] = ((encoding >> 8) & 0xFF);
    spi_buf[led_idx][8] = (encoding & 0xFF);
}
void spi_dma_send(void)
{
    memcpy(dma_txbuf, spi_buf, sizeof(spi_buf));
    DMA_SetCurrDataCounter(DMA1_Channel5, sizeof(dma_txbuf));
    DMA_Cmd(DMA1_Channel5, ENABLE);
}
void led_display(uint8_t (*led_buf)[3], uint8_t led_num)
{
uint8_t i;
    uint32_t color_value;
// led_buf -> spi_buf
for(i = 0; i < led_num; i++)
{// N led
        color_value = (led_buf[i][1] << 16) | (led_buf[i][0] << 8) | led_buf[i][2];
        ws2812_data_pack(i, color_value);
}
// spi start
spi_dma_send();
}
void ws2812_init(void)
{
    spi_init();
    dma_nvic_init();
    spi_dma_init();
}
```
<font color=#0033ff>main.c :</font>
```c
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "ws2812_app.h"
uint32_t fac_us, fac_ms;
void delay_init(void)
{
    /* 配置时钟源 --> 72MHz / 8 = 9MHz,
*  滴答定时器每计数一次所需时间为 T = 1/(9MHz) s,即 1us = 10^(-6)s ,
*  T = (1/9) * 10^(-6) s = 1/9 us , 即每计数一次的时间为1/9微秒
*  换言之,系统时钟频率为72MHz时,1us需要计数9次
*/
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
/* fac_us 和 fac_ms 是定义的全局变量,是倍频因子*/
fac_us = SystemCoreClock / 8000000;
fac_ms = 1000 * fac_us;
}
void delay_us(unsigned int us)
{
unsigned int temp = 0;
/*1us需要计数9次,计数初值为9*/
/*设置重装载值*/
SysTick->LOAD = fac_us * us;
/*当前值寄存器清0,即清空计数器*/
SysTick->VAL = 0x00;
/*滴答定时器控制寄存器,使能滴答定时器*/
SysTick->CTRL |= 0x01;
do
{
/*获取控制寄存器的当前状态*/
temp = SysTick->CTRL;
}while((temp & 0x01) && !(temp & (0x01 << 16)));
SysTick->CTRL &= 0x00;
SysTick->VAL = 0x00;
}
void delay_ms(unsigned int ms)
{
unsigned int temp = 0;
/*设置重装载值*/
    if(fac_ms * ms <= ((0x01 << 24) - 1))
    {
        SysTick->LOAD = fac_ms * ms;
    }
    else
    {
        return ;
    }
/*设置重装载值*/
SysTick->VAL = 0x00;
/*滴答定时器控制寄存器,使能滴答定时器*/
SysTick->CTRL |= 0x01;
do
{
/*获取控制寄存器的当前状态*/
temp = SysTick->CTRL;
}while((temp & 0x01) && !(temp & (0x01 << 16)));
SysTick->CTRL &= 0x00;
SysTick->VAL = 0x00;
}
int main(void)
{
    SystemInit();
    delay_init();
    led_init();
    while(1)
    {
        led_handle();
        delay_ms(10);
    }
}
```
### 2.3 运行测试
#### 2.3.1 时序测试
使用逻辑分析仪抓取信号,得到的结果如下:
1. 8个LED连续写入RGB值:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/58abe6ca046d4958b2a1a4dd2e258737.png)
2. 编码1电平:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d259f0c4f0664cdf8fa83e803df17f80.png)
3. 编码0电平:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/134c1fac909f490aac008d5a431167b5.png)
<font color=ff033ff>结论:实际输出的波形和理论基本一致,周期和脉宽稍微有点出入属于正常误差范围。</font>
## 结束语
关于stm32如何使用SPI+DMA驱动WS2812的讲解就到这里,如果还有什么问题,欢迎在评论区留言。
[源码下载链接](https://download.csdn.net/download/ShenZhen_zixian/92247791)
如果这篇文章能够帮到你,就.....你懂的。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/880ac6ca4d69ca0ba91eeaae53dadcef.png =300x)
  • stm32f103_ws2812_spi.7z
    下载

相关推荐