瑞萨e2studio.28--SPI 驱动WS2812灯珠
- 概述
- 视频教学
- 样品申请
- 芯片级联方法
- 数据传输
- 时序
- 新建工程
- 软件准备
- 保存工程路径
- 芯片配置
- 开始SPI配置
- SPI属性配置
- 时钟配置
- SPI配置
- CPHA配置
- 代码
- hal_entry.c
- ws2812.c
- ws2812.h
概述
本文介绍了如何使用瑞萨RA微控制器,结合E2STUDIO配置工具和SPI通讯接口,来驱动和控制WS2812 LED灯带。这是一个集硬件连接、软件配置和编程开发于一体的综合性项目,目标是实现对LED灯带颜色和亮度的精确控制。
最近在弄ST和瑞萨RA的课程,需要样片的可以加群申请:615061293 。
视频教学
样品申请
https://www.wjx.top/vm/wBbmSFp.aspx#
芯片级联方法
芯片在上电复位以后,接收DIN端打来的数据,接收够24bit后,DO端口开始转发数据,供下一个芯片提供输入数据。在转发之前,DO口一直拉低。此时灯珠将不接收新的数据,内置RGB芯片根据接收到的24bit数据后产生的不同占空比信号,展现不同亮度。如果DIN端输入信号为RESET信号,芯片将接收到的数据送显示,芯片将在该信号结束后重新接收新的数据,在接收完开始的24bit数据后,通过DO口转发数据,灯珠在没有接收到RESET码前,RGB亮度保持不变,当接收到80us以上低电平RESET码后,灯珠内部RGB芯片将根据刚才接收到的24bit数据后产生的不同占空比信号,展现不同亮度。
数据传输
24bit数据结构:
时序
时序波形图如下所示。
新建工程
软件准备
保存工程路径
芯片配置
本文中使用R7FA4M2AD3CFP来进行演示。
开始SPI配置
点击Stacks->New Stack->Connectivity->SPI (r_sci_spi)。
SPI属性配置
将SPI的配置速度设为7.5M,数据宽度设为8位。
CPHA配置为第二边沿采样;在第一个跳变沿时,MOSI在空闲状态保持高电平,而在第二个跳变沿,它会保持上一次传输的最终电平。由于发送数据的最后一位总是低电平,这样配置可以避免WS2812误判。CPOL设置为高,确保SCK在空闲时保持高电平状态。
时钟配置
SPI (r_sci_spi)的时钟来自PLCKA。
查询用户手册,配置7.5M频率需要PLCK主频为60M。
开发板上的外部高速晶振为12M,需要修改XTAL为12M,配置PCLKA为60MHz。
SPI配置
系统采用单总线协议,通过总线上高低电平的时长来区分逻辑0和1。WS2811工作在800kHz频率下,将SPI设置为6.4MHz—即其工作频率的8倍—可以确保每个字节(8位)正好对应一个逻辑位。在这种设置下,‘11111000’(0xF8)代表逻辑1,‘11000000’(0xC0)代表逻辑0。
由于瑞萨RA在SPI发送时候会拉高电平。
所以在发送的时候需要先发送8位低电平进行复位,复位时间最短位80us。
7.5MHz频率换算位时间位133.3ns,80us/133.3ns≈600位,为了确保复位成功,需发送700位的低电平数据,即发送700/8≈88字节数据。
所以定义显存数组为88+实际灯珠数量。
//灯条显存SPI数据缓存
uint8_t gWs2812bDat_SPI[WS2812B_AMOUNT * 24+88] = {0};
逻辑分析仪显示如下所示。
逻辑0下发送的数据为11000000’(0xC0)。
逻辑0下高电平位264ns。
逻辑0下低电平位800ns。
逻辑1下发送的数据为‘11111000’(0xF8)。
逻辑1下高电平位666ns。
逻辑1下低电平位400ns。
这种精确的时序配置和电平控制对于确保WS2812灯带的正确驱动至关重要,可以通过上述配置来优化SPI接口的性能,确保与WS2812的高效通信。
CPHA配置
CPHA配置为第二边沿采样下数据传输结束如下所示。
CPHA配置为第二边沿采样下数据传输结束如下所示。
由于RESET Code为低电平,且要大于80us,所以数据传输完毕必须为低电平。
代码
在main.c中添加头文件。
#include "ws2812.h"
在main.c中添加函数申明和移位操作以及回调函数处理。
fsp_err_t err = FSP_SUCCESS;
volatile bool g_transfer_complete = false;
void sci_spi_callback (spi_callback_args_t * p_args)
{
if (SPI_EVENT_TRANSFER_COMPLETE == p_args->event)
{
g_transfer_complete = true;
}
}
extern tWs2812bCache_TypeDef gWs2812bDat[WS2812B_AMOUNT];
void move_Front()
{
uint8_t i;
uint8_t temp[3];
temp[0] = gWs2812bDat[0].R;
temp[1] = gWs2812bDat[0].G;
temp[2] = gWs2812bDat[0].B;
for (i = 0; i < WS2812B_AMOUNT-1; i++)
{
gWs2812bDat[i].R = gWs2812bDat[i+1].R;
gWs2812bDat[i].G = gWs2812bDat[i+1].G;
gWs2812bDat[i].B = gWs2812bDat[i+1].B;
}
gWs2812bDat[7].R = temp[0];
gWs2812bDat[7].G = temp[1];
gWs2812bDat[7].B = temp[2];
}
添加初始化显示。
err = R_SCI_SPI_Open(&g_spi0_ctrl, &g_spi0_cfg);
assert(FSP_SUCCESS == err);
sci_spi_extended_cfg_t sci_spi_extended_cfg_t1;
WS2812B_Task();
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
添加流水灯。
while (1)
{
WS2812B_Task();
move_Front();
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
}
hal_entry.c
#include "hal_data.h"
#include "ws2812.h"
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
fsp_err_t err = FSP_SUCCESS;
volatile bool g_transfer_complete = false;
void sci_spi_callback (spi_callback_args_t * p_args)
{
if (SPI_EVENT_TRANSFER_COMPLETE == p_args->event)
{
g_transfer_complete = true;
}
}
extern tWs2812bCache_TypeDef gWs2812bDat[WS2812B_AMOUNT];
void move_Front()
{
uint8_t i;
uint8_t temp[3];
temp[0] = gWs2812bDat[0].R;
temp[1] = gWs2812bDat[0].G;
temp[2] = gWs2812bDat[0].B;
for (i = 0; i < WS2812B_AMOUNT-1; i++)
{
gWs2812bDat[i].R = gWs2812bDat[i+1].R;
gWs2812bDat[i].G = gWs2812bDat[i+1].G;
gWs2812bDat[i].B = gWs2812bDat[i+1].B;
}
gWs2812bDat[7].R = temp[0];
gWs2812bDat[7].G = temp[1];
gWs2812bDat[7].B = temp[2];
}
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
err = R_SCI_SPI_Open(&g_spi0_ctrl, &g_spi0_cfg);
assert(FSP_SUCCESS == err);
sci_spi_extended_cfg_t sci_spi_extended_cfg_t1;
WS2812B_Task();
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
while (1)
{
WS2812B_Task();
move_Front();
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/*******************************************************************************************************************//**
* This function is called at various points during the startup process. This implementation uses the event that is
* called right before main() to set up the pins.
*
* @param[in] event Where at in the start up process the code is currently at
**********************************************************************************************************************/
void R_BSP_WarmStart(bsp_warm_start_event_t event)
{
if (BSP_WARM_START_RESET == event)
{
#if BSP_FEATURE_FLASH_LP_VERSION != 0
/* Enable reading from data flash. */
R_FACI_LP->DFLCTL = 1U;
/* Would normally have to wait tDSTOP(6us) for data flash recovery. Placing the enable here, before clock and
* C runtime initialization, should negate the need for a delay since the initialization will typically take more than 6us. */
#endif
}
if (BSP_WARM_START_POST_C == event)
{
/* C runtime environment and system clocks are setup. */
/* Configure pins. */
R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
}
}
#if BSP_TZ_SECURE_BUILD
FSP_CPP_HEADER
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ();
/* Trustzone Secure Projects require at least one nonsecure callable function in order to build (Remove this if it is not required to build). */
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ()
{
}
FSP_CPP_FOOTER
#endif
ws2812.c
/*
* ws2812.c
*
* Created on: 2023年10月31日
* Author: Administrator
*/
#include "ws2812.h"
#include "hal_data.h"
extern fsp_err_t err ;
extern volatile bool g_transfer_complete ;
//灯条显存SPI数据缓存
uint8_t gWs2812bDat_SPI[WS2812B_AMOUNT * 24+88] = {0};
//灯条显存
tWs2812bCache_TypeDef gWs2812bDat[WS2812B_AMOUNT] = {
//R G B
0XFF, 0X00, 0X00, //0
0X00, 0XFF, 0X00, //1
0X00, 0X00, 0XFF, //2
0X00, 0XFF, 0XFF, //3
0XFF, 0X00, 0XFF, //4
0XFF, 0XFF, 0X00, //5
0XFF, 0XFF, 0XFF, //6
0X00, 0X00, 0X00, //7
};
void WS2812b_Set(uint16_t Ws2b812b_NUM, uint8_t r,uint8_t g,uint8_t b)
{
uint8_t *pR = &gWs2812bDat_SPI[88+(Ws2b812b_NUM) * 24 + 8];
uint8_t *pG = &gWs2812bDat_SPI[88+(Ws2b812b_NUM) * 24];
uint8_t *pB = &gWs2812bDat_SPI[88+(Ws2b812b_NUM) * 24 + 16];
for(uint8_t i = 0; i < 8; i++) {
if(g & 0x80) {
*pG = CODE_1;
}
else {
*pG = CODE_0;
}
if(r & 0x80) {
*pR = CODE_1;
}
else {
*pR = CODE_0;
}
if(b & 0x80) {
*pB = CODE_1;
}
else {
*pB = CODE_0;
}
r <<= 1;
g <<= 1;
b <<= 1;
pR++;
pG++;
pB++;
}
}
void WS2812B_Task(void)
{
uint8_t dat = 0;
for(int i=0;i<88;i++)
{
gWs2812bDat_SPI[i]=0;
}
//将gWs2812bDat数据解析成SPI数据
for(uint8_t iLED = 0; iLED < WS2812B_AMOUNT; iLED++)
{
WS2812b_Set(iLED, gWs2812bDat[iLED].R, gWs2812bDat[iLED].G, gWs2812bDat[iLED].B);
}
//总线输出数据
/* Send the reset command */
g_transfer_complete = false;
err = R_SCI_SPI_Write(&g_spi0_ctrl, gWs2812bDat_SPI, sizeof(gWs2812bDat_SPI), SPI_BIT_WIDTH_8_BITS);
assert(FSP_SUCCESS == err);
/* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */
while ( g_transfer_complete==false)
{
;
}
// //使总线输出低电平
// g_transfer_complete = false;
// err = R_SCI_SPI_Write(&g_spi0_ctrl, dat, 1, SPI_BIT_WIDTH_8_BITS);
// assert(FSP_SUCCESS == err);
// /* Wait for SPI_EVENT_TRANSFER_COMPLETE callback event. */
// while ( g_transfer_complete==false)
// {
// ;
// }
//帧信号:一个大于50us的低电平
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
}
ws2812.h
/*
* ws2812.h
*
* Created on: 2023年10月31日
* Author: Administrator
*/
#ifndef WS2812_H_
#define WS2812_H_
#include <stdint.h>
// 编码 0 : 11000000
#define CODE_0 0xc0
// 编码 1 : 11111000
#define CODE_1 0xF8
/*ws2812b灯珠数量*/
#define WS2812B_AMOUNT 8
typedef struct
{
uint8_t R;
uint8_t G;
uint8_t B;
} tWs2812bCache_TypeDef;
extern tWs2812bCache_TypeDef gWs2812bDat[WS2812B_AMOUNT];
void WS2812b_Set(uint16_t Ws2b812b_NUM, uint8_t r,uint8_t g,uint8_t b);
void WS2812B_Task(void);
#endif /* WS2812_H_ */