[HAL]STM32 SPI+DMA驱动WS2812

该程序是纯手敲,非Cube生成!所有代码均注释。

源码在文章后面获取

WS2818 简介

Keyword: 单线通讯、归零码、Reset、RGB顺序

RGB一共有24bit位 ->相当于驱动一个灯要24bit位 ->驱动若干个灯要24* n bit位,通过Reset码决定数据终止(保持)

24bit位应该如何发送?

可见:

表示低电平需要 T0H和T0L的配合,其关键在于高电平的时间,图中所示T0H时间为 0.85us ± 150ns

Q:怎么控制高低电平的时间

数据发送速度可达 800Kbps,就是 1.25us 发送一位数据,因为协议有一定的兼容性,所以实际上一个位的周期在1.25us±300ns之间都能识别到,因为是 us 级延时,所以时间要控制精准很难,因此我们借助 SPI 来控制 WS2812,我们用 SPI 的 MOSI 接口的一个 Byte(8位)模拟 WS2812 的一个位,比如下面的 SPI 我 设置的 4.5Mhz 速率,一个字节约为 1.76us,所以可以通过发送了一个字节的数据控制电平时间,然后模拟 0 和 1 到 WS2812,比如下面发送的0xF8

编程思路

① 若选择STM32F103C8T6最小系统的话,SPI1的时钟为72Mhz -> 需要16分频 -> 4.5Mhz -> 一个指令周期0.22us -> 0.22us * 8 = 1.76us与一个数据传输周期差不多(在F1已是较为接近)

② SPI需要发送什么数据来表示低电平 -> 0码时,高电平时间为0.4us ->一个指令周期为0.22us,所以需要两次高电平就够时间 -> 可表示为1100 0000 -> 0xC0 ;

③ 表示高电平 ->0码时,高电平时间0.85us -> 一个指令周期为0.22us,所以需要四次高电平时间就够了 -> 可表示为 1111 0000 –> 0 x F0 ;

④ Reset码可通过延迟函数来实现

程序基于正点原子的模板

实现目标: 灯的单个点亮(或每个灯显示不同颜色)

文件目录

main函数

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "led.h"
#include "spi_dma.h"
#include "rgb.h"
extern DMA_HandleTypeDef hdma1;
extern SPI_HandleTypeDef hspi1;

uint8_t textbuf[3] = {0xF0,0xF0,0xC0};
int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    led_init();                         /* LED初始化 */
    Spi_Dma_Init();
    
    while(1)
    { 
      RGB_RED(50);      // 50个红色灯珠亮
      //HAL_SPI_Transmit_DMA(&hspi1,textbuf,8);
      //HAL_SPI_Transmit(&hspi1,textbuf,24,1000);
      //delay_us(60);
      //RGB_RST();
      delay_us(100);
    }
}

SPI和DMA初始化 - 头文件

#ifndef __SPI_DMA_H
#define __SPI_DMA_H


#include  "stm32f1xx.h"

extern SPI_HandleTypeDef hspi1;  // SPI 1
extern DMA_HandleTypeDef hdma1;  // DMA 1

void SPI_DMA_Init(void);         // SPI 和 DMA初始化

#endif

SPI和DMA初始化 - 源文件

#include "spi_dma.h"


SPI_HandleTypeDef hspi1;  // SPI 1
DMA_HandleTypeDef hdma1;
// SPI 和 DMA初始化
void SPI_DMA_Init(void)
{
  hspi1.Instance               =  SPI1 ; // SPI1 基地址
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16 ; //波特率设置
  hspi1.Init.CLKPhase          = SPI_PHASE_2EDGE;           //时钟相位
  hspi1.Init.CLKPolarity       = SPI_POLARITY_HIGH;         //是时钟极性
  hspi1.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;//硬件CEC校验
  hspi1.Init.CRCPolynomial     = 10;                        //校验多项式
  hspi1.Init.DataSize          = SPI_DATASIZE_8BIT;         //帧格式(8bit)
  hspi1.Init.Direction         = SPI_DIRECTION_2LINES;      //全双工
  hspi1.Init.FirstBit          = SPI_FIRSTBIT_MSB;          //数据传输顺序
  hspi1.Init.Mode              = SPI_MODE_MASTER ;          //SPI模式
  hspi1.Init.NSS               = SPI_NSS_SOFT;              // 软件控制SS
  hspi1.Init.TIMode            = SPI_TIMODE_DISABLE;        // 帧格式(Motorola)
  HAL_SPI_Init(&hspi1); //SPI1 初始化
  
  HAL_NVIC_SetPriority(DMA1_Channel3_IRQn,0,0); //设置中断分组
  HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);// 使能中断
}

// SPI 初始化回调函数
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
  GPIO_InitTypeDef GPIO_InitTypeSture ={0};
  
  __HAL_RCC_SPI1_CLK_ENABLE(); // 使能SPI1时钟
  __HAL_RCC_GPIOA_CLK_ENABLE();// 使能GPIOA时钟
  __DMA1_CLK_ENABLE();         // 使能DMA1时钟
  
  if(hspi ->Instance ==SPI1) //判断基地址是否为SPI1
  {
    GPIO_InitTypeSture.Mode = GPIO_MODE_AF_PP;     //一定要复用推挽输出
    GPIO_InitTypeSture.Pin  = GPIO_PIN_5 | GPIO_PIN_7;
    GPIO_InitTypeSture.Pull = GPIO_PULLUP;
    GPIO_InitTypeSture.Speed = GPIO_SPEED_FREQ_HIGH; 
    HAL_GPIO_Init(GPIOA,&GPIO_InitTypeSture); //GPIO 初始化
    
    hdma1.Instance                 = DMA1_Channel3;         //基地址为DMA1通道3
    hdma1.Init.Direction           = DMA_MEMORY_TO_PERIPH;  // 内存到外设
    hdma1.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE ;   //一个字
    hdma1.Init.MemInc              = DMA_MINC_ENABLE;       //内存自增
    hdma1.Init.Mode                = DMA_NORMAL;            //不循环
    hdma1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE ;   //一个字
    hdma1.Init.PeriphInc           = DMA_PINC_DISABLE ;     //外设不自增
    hdma1.Init.Priority            = DMA_PRIORITY_MEDIUM;   // 优先级
    HAL_DMA_Init(&hdma1); //DMA 初始化
    
    __HAL_LINKDMA(hspi,hdmatx,hdma1);  // SPI与DMA进行连接
  }
}

// DMA1通道3中断服务函数
void DMA1_Channel3_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma1);  //处理DMA中断
}

WS2812 - 头文件

#ifndef __RGB_H
#define __RGB_H


#include "stm32f1xx.h" 

#include "spi_dma.h"

typedef struct
{
  uint8_t R;
  uint8_t G;
  uint8_t B;
}RGB_InitStruce;

// RGB数据发送程序
void RGB_Send(uint8_t* data);
// RGB 保持
void RGB_Rst(void);

// 存入缓存数组
void RGB_Set_Buf(uint8_t num,RGB_InitStruce color);
// RGB发送处理函数
void RGB_Send_Handler(uint8_t num);
//  RGB 红色测试函数
void RGB_Red(uint8_t num);
//  RGB 蓝色测试函数
void RGB_Blue(uint8_t num);


#endif

WS2812 - 源文件

#include "rgb.h"

#define RGB_NUM         60       // 灯珠数量
#define RGB_LowLevel    0xC0     // 0码
#define RGB_HighLevel   0xF0     // 1码

extern SPI_HandleTypeDef hspi1;  // SPI 1
extern DMA_HandleTypeDef hdma1;  // DMA 1

// 颜色定义
RGB_InitStruce RED    = {30,0,0};  // 红色
RGB_InitStruce GREEN  = {0,30,0};  // 绿色
RGB_InitStruce BLUE   = {0,0,30};  // 蓝色



RGB_InitStruce RGB_TempBuf[RGB_NUM]; // 颜色缓存数组

// RGB数据发送程序
void RGB_Send(uint8_t* data)
{
  while(HAL_DMA_GetState(&hdma1) != HAL_DMA_STATE_READY);  // 判断DMA是否空闲
  HAL_SPI_Transmit_DMA(&hspi1,data,24); // DMA发送
}

// RGB 保持
void RGB_Rst(void)
{
  uint8_t RGB_RstBuf[100] = {0};
  while(HAL_DMA_GetState(&hdma1) != HAL_DMA_STATE_READY);  // 判断DMA是否空闲
  HAL_SPI_Transmit_DMA(&hspi1,RGB_RstBuf,100); // DMA发送
}

// 存入缓存数组
// 颜色设置函数,传入ID和颜色,进而设置缓存区
void RGB_Set_Buf(uint8_t num,RGB_InitStruce Color)
{
  if( num <= RGB_NUM )
  {
    RGB_TempBuf[num].R = Color.R;
    RGB_TempBuf[num].G = Color.G;
    RGB_TempBuf[num].B = Color.B;
  }
}


// RGB发送处理函数
// 刷新函数,将颜色缓存区刷新到WS2812,输入参数是指定的刷新长度
void RGB_Send_Handler(uint8_t num)
{
  static uint8_t RGB_Buf[24] = {0};
  uint8_t i,j = 0;
  uint8_t Temp_R,Temp_G,Temp_B;
  //将数组颜色转化为24个要发送的字节数据
  if((num > 0 ) && ( num <= RGB_NUM ))
  {
    for (i = 0 ; i < num ;i++)
    {
      Temp_R = RGB_TempBuf[i].R;
      Temp_G = RGB_TempBuf[i].G;
      Temp_B = RGB_TempBuf[i].B;
      for ( j = 0 ; j < 8 ; j++ )
      {
        RGB_Buf[7  - j] =  (Temp_G& 0x01)? RGB_HighLevel : RGB_LowLevel ;
        RGB_Buf[15 - j] =  (Temp_R& 0x01)? RGB_HighLevel : RGB_LowLevel ;
        RGB_Buf[23 - j] =  (Temp_B& 0x01)? RGB_HighLevel : RGB_LowLevel ;
        Temp_R >>= 1;
        Temp_G >>= 1;
        Temp_B >>= 1;
      }
      RGB_Send(RGB_Buf);
    }
  }
}

//  RGB 红色测试函数
void RGB_Red(uint8_t num)
{
  uint8_t i = 0;
  for ( i = 0 ; i < num ; i++ )
  {
    RGB_Set_Buf(i,RED);
  }
  RGB_Send_Handler(num);
}

void RGB_Blue(uint8_t num)
{
  uint8_t i = 0;
  for ( i = 0 ; i < num ; i++ )
  {
    RGB_Set_Buf(i,BLUE);
  }
  RGB_Send_Handler(num);
}

效果图

下期预告

基于本文章的扩展,WS2812灯带随着音乐而变化(STM32F103C8T6)

📺 [HAL]STM32 SPI+DMA驱动WS2812灯带随音乐变化

获取源码

点击这里,文末获取下载!

### 使用STM32F103 HAL库通过SPIDMA驱动WS2812B LED灯带 为了实现STM32F103使用HAL库通过SPIDMA驱动WS2812B LED灯带的功能,下面提供了一个完整的代码示例。此方案利用了硬件SPI接口以及DMA传输机制来提高数据传输效率。 #### 初始化配置 首先,在`main.c`文件中的初始化部分设置SPI外设参数并开启DMA功能: ```c /* Includes */ #include "stm32f1xx_hal.h" // 定义全局变量用于存储颜色缓冲区 #define BUFFER_SIZE (NUM_LEDS * 24 / 8) uint8_t buffer[BUFFER_SIZE]; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_SPI1_Init(void); int main(void){ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); // 创建初始的颜色缓存 WS2812_MakeBuffer(); while (1){ // 发送数据到LED条形图上 HAL_SPI_Transmit_DMA(&hspi1, buffer, sizeof(buffer)); // 等待DMA完成后再继续操作 __WFI(); } } ``` 接着定义具体的SPI初始化函数如下所示: ```c static void MX_SPI1_Init(void){ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 7; if(HAL_SPI_Init(&hspi1)!= HAL_OK){ Error_Handler(); } // 配置DMA通道以自动发送数据给SPI模块 __HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx); } extern DMA_HandleTypeDef hdma_spi1_tx; ``` 最后编写辅助函数用来构建RGB位模式的数据包,并将其复制至最终的发送缓冲区内: ```c /** * @brief 将单个像素点转换成适合SPI输出的形式 * @param r,g,b 分别代表红色、绿色、蓝色分量值(范围为0~255) * @retval None */ void WS2812_CreatData(uint8_t r,uint8_t g,uint8_t b){ static uint8_t rgb_bit_buffer[24]; int index=0; for(int i=7;i>=0;--i){rgb_bit_buffer[index++]=(r&(1<<i))?0xE0:0x80;} for(int i=7;i>=0;--i){rgb_bit_buffer[index++]=(g&(1<<i))?0xE0:0x80;} for(int i=7;i>=0;--i){rgb_bit_buffer[index++]=(b&(1<<i))?0xE0:0x80;} memcpy(RGB_BIT_Buffer , rgb_bit_buffer , RGB_BIT ); } /** * @brief 构建整个显示缓冲区的内容 * @note 此处假设已经存在一个名为LED[] 的结构体数组保存着各个LED的状态信息 * @retval None */ void WS2812_MakeBuffer(){ for(uint16_t i=0 ; i<LED_NUMS ; ++i){ WS2812_CreatData(LED[i].R , LED[i].G , LED[i].B); memcpy(buffer+i*RGB_BIT , RGB_BIT_Buffer , RGB_BIT); } } ``` 上述代码片段展示了如何使用STM32 HAL库配合SPIDMA技术高效地控制WS2812B类型的LED灯带[^1]。需要注意的是实际应用时还需要考虑其他因素如电源管理等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值