[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灯带随音乐变化

获取源码

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

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ws2812b是一种智能彩灯,在STM32上使用HAL库进行编程时,可以利用PWM和DMA控制来控制彩灯的颜色和亮度。 PWM(脉宽调制)是一种常用的控制电子设备亮度的方法,通过改变PWM信号的占空比即高电平时间和低电平时间的比例,可以控制灯光的亮度。对于ws2812b彩灯,它需要接收到一串特定的脉冲信号来控制灯光的颜色和亮度,因此我们可以利用PWM信号来模拟这个特定的脉冲信号。 在使用HAL库进行编程时,可以利用定时器和PWM功能来生成脉冲信号。首先,我们需要初始化定时器和PWM通道,然后设置定时器的计数周期和预分频值,以确定脉冲信号的频率。接下来,我们可以通过改变PWM通道的CCR寄存器的值来改变脉冲信号的占空比,从而控制灯光的亮度。通过反复改变CCR寄存器的值,即可实现灯光的渐变效果。 为了实现更高效的控制,可以结合使用DMA(直接存储器访问)功能。DMA可以在处理器和外设之间直接传输数据,减轻CPU的负担。对于控制彩灯来说,我们可以把存储颜色和亮度信息的数组存放在内存中,然后通过DMA传输到PWM寄存器中,从而控制彩灯。通过配置DMA通道和中断,可以实现定时更新彩灯的效果。 总之,通过利用STM32HAL库,结合PWM和DMA控制,我们可以方便地对ws2812b彩灯进行编程,实现灯光的颜色和亮度控制,使其呈现出丰富多彩的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值