瑞萨e2studio(28)----SPI 驱动WS2812灯珠

描述

概述

本文介绍了如何使用瑞萨RA微控制器,结合E2STUDIO配置工具和SPI通讯接口,来驱动和控制WS2812 LED灯带。这是一个集硬件连接、软件配置和编程开发于一体的综合性项目,目标是实现对LED灯带颜色和亮度的精确控制。

视频教学

[https://www.bilibili.com/video/BV14u4y1h7u6/]

样品申请

[https://www.wjx.top/vm/wBbmSFp.aspx#]

源码下载

[https://download.csdn.net/download/qq_24312945/88489756]

芯片级联方法

瑞萨
芯片在上电复位以后,接收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_ */

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分