概要
基于中微CMS32L051驱动WS2812B全彩灯,使用SPI+DMA模式。SPI时钟设置为6MHz,发送一个字节是8/6000000=1.333us。ws2812b设置一个灯珠的颜色需要3个字节(r, g, b),3*8=24位。使SPI的字节正好等于ws2812b的一个位。因此控制一个ws2812b需要SPI发送24byte,在头部加入两个字节0,在尾部加入一个字节0进行消抖,总共需要24+3=27byte
代码
bsp_ws2812b.c
#include "UserConfigure.h"
typedef union
{
#if (WS2812B_PWM_MODE)
struct {
uint16_t head1; // 0,DMA发送消抖
uint16_t head2; // 0,DMA发送消抖
PWM_t DMABuffer[WS2812B_BUFFER_SIZE]; // 实际ws2812b数据
uint16_t stop; // 0,停止PWM
};
uint16_t date[WS2812B_FRAME_SIZE]; // 2 head + 24 * WS2812B_BUFFER_SIZE + 1 stop
#elif (WS2812B_SPI_MODE)
struct {
uint8_t head1; // 0,DMA发送消抖
uint8_t head2; // 0,DMA发送消抖
PWM_t DMABuffer[WS2812B_BUFFER_SIZE]; // 实际ws2812b数据
uint8_t stop; // 0,停止PWM
};
uint8_t date[WS2812B_FRAME_SIZE]; // 2 head + 24 * WS2812B_BUFFER_SIZE + 1 stop
#endif
}pwm_frame_t;
static pwm_frame_t pwm_frame;
/***********************************************************************************************************************
* Function Name: Spi0_Init
* @brief simple spi init
* @param
* @return status
***********************************************************************************************************************/
void NSS_set()
{
// GPIO_SetBits(GPIO_PORT6, GPIO_Pin_2);
}
void NSS_clr()
{
// GPIO_ResetBits(GPIO_PORT6, GPIO_Pin_2);
}
RGB_t bsp_ws2812b_set_rgb(uint8_t r, uint8_t g, uint8_t b)
{
RGB_t tmp = { r, g, b };
return tmp;
}
void bsp_ws2812b_fill_solid_rgb(RGB_t color)
{
uint8_t r = color.r;
uint8_t g = color.g;
uint8_t b = color.b;
uint8_t mask = 0x80;
pwm_frame.head1 = 0;
pwm_frame.head2 = 0;
pwm_frame.stop = 0;
int i;
for (i = 0; i < 8; i++)
{
pwm_frame.DMABuffer->r[i] = r & mask ? WS2812B_PULSE_HIGH : WS2812B_PULSE_LOW;
pwm_frame.DMABuffer->g[i] = g & mask ? WS2812B_PULSE_HIGH : WS2812B_PULSE_LOW;
pwm_frame.DMABuffer->b[i] = b & mask ? WS2812B_PULSE_HIGH : WS2812B_PULSE_LOW;
mask >>= 1;
}
}
#if (WS2812B_PWM_MODE)
void bsp_dma_pwm_tx(DMA_VECTOR_t dma_vector, DMA_Mode_t mode, void *src_adr, void *dst_adr, uint16_t count)
{
unsigned char CTRL_DATA_PWM = 10;
DMA_InitTypeDef DMA_InitStructure = {};
DMA_InitStructure.DMA_Vector = dma_vector; //根据功能选择不同的dma向量区
DMA_InitStructure.DMA_CtrlId = CTRL_DATA_PWM; //选择控制数据区
DMA_InitStructure.DMA_SrcAddr = (uint32_t)src_adr; //配置dma源地址
DMA_InitStructure.DMA_DstAddr = (uint32_t)dst_adr; //配置dma目标地址
DMA_InitStructure.DMA_BufferSize = count;
DMA_InitStructure.DMA_SrcInc = DMA_SrcInc_Enable;//源地址增量模式
DMA_InitStructure.DMA_DstInc = DMA_DstInc_Disable;//目标地址固定
DMA_InitStructure.DMA_DataSize = DMA_DataSize_HalfWord;//传输数据长度选择
DMA_InitStructure.DMA_Mode = mode;//普通模式
DMA_Init(&DMA_InitStructure);
DMA_Start(DMA_InitStructure.DMA_Vector);
}
#elif (WS2812B_SPI_MODE)
/***********************************************************************************************************************
* Function Name: DMA_Sspi_Write
* @brief config DMA for spi write
* @param src_adr: dma source address
* @param dst_adr: dma destine address
* @param count: dma transform num
* @return None
***********************************************************************************************************************/
void DMA_Sspi_Write(DMA_VECTOR_t dma_vector, uint8_t ctrl_data, DMA_Mode_t mode, void *src_adr, void *dst_adr, uint16_t count)
{
DMA_InitTypeDef DMA_InitStructure = {};
DMA_InitStructure.DMA_Vector = dma_vector; //根据功能选择不同的dma向量区
DMA_InitStructure.DMA_CtrlId = ctrl_data; //选择控制数据区
DMA_InitStructure.DMA_SrcAddr = (uint32_t)src_adr; //配置dma源地址
DMA_InitStructure.DMA_DstAddr = (uint32_t)dst_adr; //配置dma目标地址
DMA_InitStructure.DMA_BufferSize = count;
DMA_InitStructure.DMA_SrcInc = DMA_SrcInc_Enable;//源地址增量模式
DMA_InitStructure.DMA_DstInc = DMA_DstInc_Disable;//目标地址固定
DMA_InitStructure.DMA_DataSize = DMA_DataSize_Byte;//传输数据长度选择
DMA_InitStructure.DMA_Mode = mode;//普通模式
DMA_Init(&DMA_InitStructure);
DMA_Start(DMA_InitStructure.DMA_Vector);
}
/***********************************************************************************************************************
* Function Name: Spi_Dma_Write
* @brief spi write data by dma
* @param None
* @return None
***********************************************************************************************************************/
void Spi20_Dma_Write(uint8_t *tx_buf, uint16_t tx_num)
{
unsigned int CTRL_DATA_SPI20 = 7;
SSPI_Set_TransmitMode(SSPI20, SSPI_TransmitMode_Send);
SSPI_Start(SSPI20);
INTC_EnableIRQ(SPI20_IRQn);
DMA_Sspi_Write(DMA_VECTOR_SPI20, CTRL_DATA_SPI20, DMA_Mode_Normal, tx_buf, (void *)&SSPI20_SDR, tx_num); //config dma transmission
pData.flag = INT_DMA; //set data direction
DMA_Trigger(DMA_VECTOR_SPI20);
}
#endif
/****************************************************************************
* Function Name: uart0_dma_send
* @brief UART0 Send interrupt service routine
* @param None
* @return None
*****************************************************************************/
void bsp_ws2812b_send(void)
{
#if (WS2812B_PWM_MODE)
bsp_dma_pwm_tx(DMA_VECTOR_TM41_CH1, DMA_Mode_Normal, pwm_frame.date, (void *)&TM41->TDR11, WS2812B_FRAME_SIZE); //config dma transmission
DMA_Trigger(DMA_VECTOR_TM41_CH1);
#elif (WS2812B_SPI_MODE)
Spi20_Dma_Write(pwm_frame.date, sizeof(pwm_frame.date));
#endif
}
void bsp_ws2812b_init(void)
{
#if (WS2812B_PWM_MODE)
TIM_InitTypeDef TIM_InitStructure = {};
GPIO_InitTypeDef GPIO_InitStruct = {};
GPIO_PinAFConfig(GPIO_PORT1, GPIO_Pin_4, GPIO_P14, GROUP_AF_TO11); // TO11 can be used to any disired pins
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Level = GPIO_Level_LOW;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Ctrl = GPIO_Control_DIG;
GPIO_Init(GPIO_PORT1, &GPIO_InitStruct);
//TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
// 1 / 32000000 * 40 = 1.25us = 0.85us + 0.4us
// WS2812B_FREQUENCY 32000000
TIM_InitStructure.TIM = TIM41;
TIM_InitStructure.TIM_Selection_Master = TTM_Channel_0; // when multi-tim combination,it can generate pwm wave
TIM_InitStructure.TIM_Channel = TTM_Channel_1; // |TTM_Channel_2|TTM_Channel_3
TIM_InitStructure.TIM_ClkDivision = TIM_CLK1_Div2; // specify the operation clk of tim
TIM_InitStructure.TIM_Period[0] = WS2812B_PERIOD; // specify the number of count clock
TIM_InitStructure.TIM_Period[1] = 0; // specify duty
TIM_InitStructure.TIM_Trigger = TIM_Trigger_Software; // specify the software trigger
TIM_InitStructure.TIM_Mode = TIM_Mode_PWM_Master; // PWM_Master mode
TIM_InitStructure.TIM_StartInt = TIM_StartInt_Enable; // the relationship between startCount and interrupt setting
TIM_Init(&TIM_InitStructure);
#elif (WS2812B_SPI_MODE)
GPIO_InitTypeDef GPIO_InitStruct = {0};
SPI_InitTypeDef SPI_InitStructure;
GPIO_PinAFConfig(GPIO_PORT1, GPIO_Pin_4, GPIO_P14, GROUP_AF_SDO20); // MOSI(SDO20) can be disired to any pins with PxxCFG register
/*SDO(MOSI) GPIO CONFIG*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Ctrl = GPIO_Control_DIG;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Level = GPIO_Level_HIGH;
GPIO_Init(GPIO_PORT1, &GPIO_InitStruct);
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI 从模式SLAVE
SPI_InitStructure.SPI_DataSize = SPI_Data_Bits_8; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_Phase_Mode = SPI_Phase_Mode0; //选择使用spi的工作模式
SPI_InitStructure.SPI_ClockSpeed = 6000000; //选择spi运行时钟
SPI_InitStructure.SPI_Bitorder = SPI_Bit_Msb; //选择bit高先传
SSPI_Init(SSPI20, &SPI_InitStructure);
ISR_Register(SPI20_IRQn, spi20_interrupt); //中断服务路径注册
NSS.Active = NSS_clr; //NSS片选初始化
NSS.Inactive = NSS_set;
NSS.Inactive(); //NSS初始化空闲状态 MODE0 情况下低电平为空闲,在clk第一个边沿采样
Spi20_Dma_Write(pwm_frame.date, sizeof(pwm_frame.date)); // 拉低MOSI,否则在高电平状态下发送rgb数据识别错误
#endif
}
/***********************************************************************************************************************
* Function Name: spi_callback_error
* @brief UART0 Receive interrupt service routine
* @param None
* @return None
***********************************************************************************************************************/
void spi_callback_error()
{
//user edit here when appear error
}
/***********************************************************************************************************************
* Function Name: spi11_interrupt
* @brief spi write/read interrupt service routine
* @param None
* @return None
***********************************************************************************************************************/
void spi20_interrupt(void *msg)
{
volatile uint8_t err_type;
volatile uint8_t sio_dummy;
ATE_FRAME_t *pFrame = (ATE_FRAME_t *)msg;
SCIAFSelect_TypeDef func = SSPI20;
err_type = SSPI_GetErrStaus(SSPI20, SSPI_FLAG_OVERRUN);
INTC_ClearPendingIRQ(SPI20_IRQn);
if (1U == err_type)
{
spi_callback_error();//error condition
}
else
{
if (pFrame->flag == INT_TX)
{
if (pFrame->len > 0)
{
if (0U != pFrame->data)
{
SSPI_SendByte(SSPI20, *pFrame->data);
pFrame->data++;
pFrame->len--;
}
}
else
{
if (0U != pFrame->data)
{
if ((SSPI_Get_MasterMode(SSPI20) != SPI_Mode_Master) && (SSPI_Get_TransmitMode(SSPI20) != SSPI_TransmitMode_Recv))//slave mode :sclk is inputed by master clk
{
;//slave mode;
}
else
{
while (SSPI_GetErrStaus(SSPI20, SCI_UNDER_EXECUTE)); //waitting conmunction end, if register ST turns 1,or SS turns 1
}
}
SSPI_Stop(func);
g_spi_tx_end = 1;
}
}
if (pFrame->flag == INT_RX)
{
if (pFrame->len > 1)
{
if (0U != pFrame->data)
{
*pFrame->data = SSPI_ReceiveByte(SSPI20);
pFrame->data++;
pFrame->len--;
}
else
{
sio_dummy = SSPI_ReceiveByte(SSPI20);
}
if (SSPI_Get_MasterMode(SSPI20) == SPI_Mode_Master)
{
SSPI_SendByte(SSPI20, 0xFFU); // dummy write in master reception mode
}
}
else if (pFrame->len == 1)
{
while (SSPI_GetErrStaus(SSPI20, SCI_VALID_STORED) == 0); // wait last data stored in register SDR
if (0U != pFrame->data)
{
*pFrame->data = SSPI_ReceiveByte(SSPI20);
pFrame->len--;
}
else
{
sio_dummy = SSPI_ReceiveByte(SSPI20);
}
SSPI_Stop(func);
g_spi_rx_end = 1;
}
}
if (pFrame->flag == INT_DMA)
{
// while (SSPI_GetErrStaus(SSPI20, SCI_VALID_STORED) == 0); // wait last data stored in register SDR
*pFrame->data = SSPI_ReceiveByte(SSPI20);
SSPI_Stop(func);
g_spi_tx_end = 1;
g_spi_rx_end = 1;
}
}
}
/***********************END OF FILE***********************/
bsp_ws2812b.h
#ifndef _BSP_WS2812B_H_
#define _BSP_WS2812B_H_
#include "UserConfigure.h"
#define WS2812B_SPI_MODE 1
#define WS2812B_PWM_MODE 0
#define WS2812B_FREQUENCY (32000000)
#define WS2812B_PERIOD (40) // 1 / 32000000 * 40 = 1.25us
#if (WS2812B_PWM_MODE)
#define WS2812B_PULSE_HIGH (27) // 1 / 32000000 * 27 = 0.85us
#define WS2812B_PULSE_LOW (13) // 1 / 32000000 * 13 = 0.40us
#elif (WS2812B_SPI_MODE)
#define WS2812B_PULSE_HIGH (0xfc) // 6*150=900us => 1111 1100b = 0xfc
#define WS2812B_PULSE_LOW (0xe0) // 3*150=450us => 1110 0000b = 0xe0
#endif
#define WS2812B_BUFFER_SIZE (1)
#define WS2812B_FRAME_SIZE (2+WS2812B_BUFFER_SIZE*24+1)
#define WS2812B_START_SIZE (0)
#define NUM_LEDS (1)
/*
* __attribute__((packed)):
* 用于告诉编译器取消结构体在编译过程中的优化对齐,
* 按照实际占用字节数进行对齐。这意味着结构体的成员变量将紧密排列,不会插入任何填充字节
*/
struct __attribute__((packed)) PWM
{
#if (WS2812B_PWM_MODE)
uint16_t g[8], r[8], b[8];
#elif (WS2812B_SPI_MODE)
uint8_t g[8], r[8], b[8];
#endif
};
struct RGB
{
uint8_t r, g, b; // r, g, b 取值1-255
};
typedef struct PWM PWM_t;
typedef struct RGB RGB_t;
void spi20_interrupt(void *msg);
void bsp_ws2812b_init(void);
void bsp_ws2812b_send(void);
void bsp_ws2812b_fill_solid_rgb(RGB_t color);
RGB_t bsp_ws2812b_set_rgb(uint8_t r, uint8_t g, uint8_t b);
#endif
小结
- 使用SPI+DMA的方式发送,字节与字节之间也存在一定延迟时间,需要实际测量波形,查看是否满足WS2812B高低位定义的时间。
- 程序里设计ws2812b的‘1’是发送一个spi数据0xfc,6x150=900us => 1111 1100b = 0xfc;ws2812b的‘0’是发送一个spi数据0xe0,3x150=450us => 1110 0000b = 0xe0。