找回密码
 立即注册
查看: 56|回复: 0

80-SPI-DMA发送-驱动WS2812彩灯,AI8051U实验箱演示程序

[复制链接]
  • 打卡等级:以坛为家I
  • 打卡总天数:274
  • 最近打卡:2026-07-21 08:44:27
已绑定手机

292

主题

1027

回帖

3313

积分

超级版主

积分
3313
QQ
发表于 2026-7-2 13:12:43 | 显示全部楼层 |阅读模式
80-SPI-DMA发送-驱动WS2812彩灯,AI8051U实验箱演示程序

主程序main.C代码如下(汇编版本在附件中):

/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/* --- BBS: www.STCAIMCU.com  -----------------------------------------*/
/* 如果要在程序中使用此代码,请在程序中注明使用了STC的资料及程序            */
/*---------------------------------------------------------------------*/

/*************  功能说明    **************
请先别修改程序, 直接下载"SPI.hex"测试, 下载时选择主频24MHz或输入25.6MHz. 实测20~30MHz都能正常驱动。

使用SPI-MOSI输出直接驱动WS2812三基色彩灯, DMA传输,不占用CPU时间。本例驱动32个灯,接成环状。
本例使用USART1-SPI模式的P1.5-MOSI输出驱动信号直接驱动WS2812。
由于使用SPI主机模式,则P1.6、P1.7被SPI占用,不能做他用。
DMA发送时间:@25.6MHz, DMA发送一个字节耗时67T(2.6172us),一个灯要发送12个字节耗时31.5us,32K字节xdata最多一次驱动2730个灯.

每个灯3个字节,分别对应绿、红、蓝则,MSB先发.
800KHz码率, 数据0(1/4占空比): H=0.3125us  L=0.9375us, 数据1(3/4占空比): H=0.9375us  L=0.3125us, RESET>=50us.
高电平时间要精确控制在要求的范围内, 低电平时间不需要精确控制, 大于要求的最小值并小于RES的50us即可.

WS2812S的标准时序如下:
TH+TL = 1.25us±150ns, RES>50us
T0H = 0.25us±150ns = 0.10us - 0.40us
T0L = 1.00us±150ns = 0.85us - 1.15us
T1H = 1.00us±150ns = 0.85us - 1.15us
T1L = 0.25us±150ns = 0.10us - 0.40us
两个位数据之间的间隔要小于RES的50us.

SPI方案:
本例使用SPI模式的P1.5-MOSI输出驱动信号直接驱动WS2812。
由于使用SPI主机模式,则P1.6、P1.7被SPI占用,不能做他用。

用SPI传输, 速度3.0~3.5MHz,以3.2MHz为最佳, MSB先发, 每个字节高4位和低4位分别对应一个位数据, 1000为数据0, 1110为数据1.
SPI数据位       D7 D6 D5 D4    D3 D2 D1 D0
SPI数据         1  0  0  0     1  1  1  0
               WS2812数据0    WS2812数据1
SPI数据高半字节对应的WS2812数据0-->0x80, 数据1-->0xe0,
SPI数据低半字节对应的WS2812数据0-->0x08, 数据1-->0x0e,
主频25.6MHz, SPI分频8 = 3.2MHz. 最佳.

******************************************/


#include <intrins.h>
typedef         unsigned char        u8;
typedef         unsigned int        u16;
typedef         unsigned long        u32;
#define        Pin0                0x01        //IO引脚 Px.0
#define        Pin1                0x02        //IO引脚 Px.1
#define        Pin2                0x04        //IO引脚 Px.2
#define        Pin3                0x08        //IO引脚 Px.3
#define        Pin4                0x10        //IO引脚 Px.4
#define        Pin5                0x20        //IO引脚 Px.5
#define        Pin6                0x40        //IO引脚 Px.6
#define        Pin7                0x80        //IO引脚 Px.7
#define        PinAll                0xFF        //IO所有引脚
#define P1n_standard(bitn)                        P1M1 &= ~(bitn),        P1M0 &= ~(bitn)
#define P1n_push_pull(bitn)                        P1M1 &= ~(bitn),        P1M0 |=  (bitn)
#define P1n_pure_input(bitn)                P1M1 |=  (bitn),        P1M0 &= ~(bitn)
#define P1n_open_drain(bitn)                P1M1 |=  (bitn),        P1M0 |=  (bitn)

#define P2n_standard(bitn)                        P2M1 &= ~(bitn),        P2M0 &= ~(bitn)
#define P2n_push_pull(bitn)                        P2M1 &= ~(bitn),        P2M0 |=  (bitn)
#define P2n_pure_input(bitn)                P2M1 |=  (bitn),        P2M0 &= ~(bitn)
#define P2n_open_drain(bitn)                P2M1 |=  (bitn),        P2M0 |=  (bitn)

#define P3n_standard(bitn)                        P3M1 &= ~(bitn),        P3M0 &= ~(bitn)
#define P3n_push_pull(bitn)                        P3M1 &= ~(bitn),        P3M0 |=  (bitn)
#define P3n_pure_input(bitn)                P3M1 |=  (bitn),        P3M0 &= ~(bitn)
#define P3n_open_drain(bitn)                P3M1 |=  (bitn),        P3M0 |=  (bitn)

#define P4n_standard(bitn)                        P4M1 &= ~(bitn),        P4M0 &= ~(bitn)
#define P4n_push_pull(bitn)                        P4M1 &= ~(bitn),        P4M0 |=  (bitn)
#define P4n_pure_input(bitn)                P4M1 |=  (bitn),        P4M0 &= ~(bitn)
#define P4n_open_drain(bitn)                P4M1 |=  (bitn),        P4M0 |=  (bitn)

//=============================================================================================================
#define        PullUpEnable(port, pu)                port |=  (pu)                /* 允许端口内部上拉电阻       PxPU, 要设置的端口对应位为1 */
#define        PullUpDisable(port, pu)                port &= ~(pu)                /* 禁止端口内部上拉电阻(默认) PxPU, 要设置的端口对应位为1 */
#define        PullDownEnable(port, pu)        port |=  (pu)                /* 允许端口内部下拉电阻       PxPD, 要设置的端口对应位为1 */
#define        PullDownDisable(port, pu)        port &= ~(pu)                /* 禁止端口内部下拉电阻(默认) PxPD, 要设置的端口对应位为1 */
#define        SchmitEnable(port, smt)                port &= ~(smt)                /* 允许端口施密特触发功能(默认) PxNCS, 要设置的端口对应位为1 */
#define        SchmitDisable(port, smt)        port |=  (smt)                /* 允许端口施密特触发功能       PxNCS, 要设置的端口对应位为1 */
#define        SlewRateNormal(port, sr)        port |=  (sr)                /* 端口输出设置为普通速度(默认) PxSR, 要设置的端口对应位为1 */
#define        SlewRateHigh(port, sr)                port &= ~(sr)                /* 端口输出设置为高速模式       PxSR, 要设置的端口对应位为1 */
#define        CurrentNormal(port, cu)                port |=  (cu)                /* 端口输出电流为普通模式(默认) PxDR, 要设置的端口对应位为1 */
#define        CurrentHigt(port, cu)                port &= ~(cu)                /* 端口输出电流为大电流模式     PxDR, 要设置的端口对应位为1 */
#define        InputEnable(port, ie)                port |=  (ie)                /* 允许端口数字输入(默认) PxIE, 要设置的端口对应位为1 */
#define        InputDisable(port, ie)                port &= ~(ie)                /* 禁止端口数字输入       PxIE, 要设置的端口对应位为1 */
#define        BypassEnable(port, bp)                port &= ~(bp)                /* 允许外设自动控制端口       PxBP, 要设置的端口对应位为1 */
#define        BypassDisable(port, bp)                port |=  (bp)                /* 禁止外设自动控制端口(默认) PxBP, 要设置的端口对应位为1 */
//=============================================================================================================

/*************    本地常量声明    **************/


/*************    本地变量声明    **************/


/*************    本地函数声明    **************/

#define COLOR    50                //亮度,最大255

#define LED_NUM    32              //LED灯个数
#define SPI_NUM    (LED_NUM*12)    //LED灯对应SPI字节数

u8 xdata  led_RGB[LED_NUM][3];    //LED对应的RGB,led_buff[0]-->绿,led_buff[1]-->红,led_buff[0]-->蓝.
u8 xdata  led_SPI[SPI_NUM];       //LED灯对应SPI字节数

/*************  外部函数和变量声明 *****************/
void SPI_Config(u8 SPI_io, u8 SPI_speed);    //SPI初始化函数, 参数:  SPI_io: 切换到的IO, 0: 切换到P5.4 P1.3 P1.4 P1.5,  1: 切换到P2.2 P2.3 P2.4 P2.5, 2: 切换到P5.4 P4.0 P4.1 P4.3,  3: 切换到P3.5 P3.4 P3.3 P3.2,
                                                //                        SPI_speed: SPI的速度, 0: fosc/4,  1: fosc/8,  2: fosc/16,  3: fosc/2
void LoadSPI(void);
void SPI_DMA_TxTRIG(u8 xdata *TxBuf, u16 num);
void delay_ms(u16 ms);

/*************** 主函数 *******************************/

void main(void)
{
    u16    i,k;
    u8    xdata *px;

    EAXSFR();    //允许访问扩展寄存器
    WTST  = 0;
    CKCON = 0;
//    MCLKO47_DIV(100);    //主频分频输出,用于测量主频
    P1M0=0; P1M1 = 0;

    SPI_Config(0, 1);    //(SPI_io, SPI_speed), 参数:     SPI_io: 切换IO(SS MOSI MISO SCLK), 0: 切换到P1.4 P1.5 P1.6 P1.7,  1: 切换到P2.4 P2.5 P2.6 P2.7, 2: 切换到P4.0 P4.1 P4.2 P4.3,  3: 切换到P3.5 P3.4 P3.3 P3.2,
                        //                                SPI_speed: SPI的速度, 0: fosc/4,  1: fosc/8,  2: fosc/16,  3: fosc/2
    P1n_pure_input(Pin6);            // MISO-P1.6设置为高阻,并允许下拉电阻,这样SPI发送完成后MOSI是低电平。
    PullUpDisable(P1PU, Pin6);       // 禁止端口内部上拉电阻   PxPU, 要设置的端口对应位为1
    PullDownEnable(P1PD, Pin6);      // 允许端口内部下拉电阻   PxPD, 要设置的端口对应位为1
    EA = 1;

    k = 0;

    while (1)
    {
        px = &led_RGB[0][0];    //亮度(颜色)首地址
        for(i=0; i<(LED_NUM*3); i++, px++)    *px = 0;    //清除所有的颜色

        i = k;
        led_RGB[1] = COLOR;        //红色
        if(++i >= LED_NUM)    i = 0;  //下一个灯
        led_RGB[0] = COLOR;        //绿色
        if(++i >= LED_NUM)    i = 0;  //下一个灯
        led_RGB[2] = COLOR;        //蓝色

        LoadSPI();    //将颜色装载到SPI数据
        SPI_DMA_TxTRIG(led_SPI, SPI_NUM);    //u8 xdata *TxBuf, u16 num), 启动SPI DMA, 720字节一共耗时2.08ms @25.6MHz

        if(++k >= LED_NUM)    k = 0;            //顺时针
    //    if(--k >= LED_NUM)    k = LED_NUM-1;    //逆时针
        delay_ms(50);
    }
}

//================ N ms延时函数 ==============
void  delay_ms(u16 ms)
{
    u16 i;
    do
    {
        i = MAIN_Fosc / 6000;
        while(--i)    ;
    }while(--ms);
}

//================ 将颜色装载到SPI数据 ==============
void LoadSPI(void)
{
    u8    xdata *px;
    u16   i,j;
    u8    k;
    u8    dat;

    for(i=0; i<SPI_NUM; i++)    led_SPI = 0;
    px = &led_RGB[0][0];    //首地址
    for(i=0, j=0; i<(LED_NUM*3); i++)
    {
        dat = *px;
        px++;
        for(k=0; k<4; k++)
        {
            if(dat & 0x80)  led_SPI[j]  = 0xE0;    //数据1
            else            led_SPI[j]  = 0x80;    //数据0
            if(dat & 0x40)  led_SPI[j] |= 0x0E;    //数据1
            else            led_SPI[j] |= 0x08;    //数据0
            dat <<= 2;
            j++;
        }
    }
}

//========================================================================
// 函数: void SPI_Config(u8 io, u8 speed)
// 描述: SPI初始化函数。
// 参数: io: 切换到的IO,            SS  MOSI MISO SCLK
//                       0: 切换到 P1.4 P1.5 P1.6 P1.7
//                       1: 切换到 P2.4 P2.5 P2.6 P2.7
//                       2: 切换到 P4.0 P4.1 P4.2 P4.3
//                       3: 切换到 P3.5 P3.4 P3.3 P3.2
//       SPI_speed: SPI的速度, 0: fosc/4,  1: fosc/8,  2: fosc/16,  3: fosc/2
// 返回: none.
// 版本: VER1.0
// 日期: 2018-6-13
// 备注:
//========================================================================

void SPI_Config(u8 SPI_io, u8 SPI_speed)
{
    SPCTL = SPI_speed & 3;    //配置SPI 速度, 这条指令放在第一位, Bit7~Bit2清0
    SPCTL |= SSIG;    //1: 忽略SS脚,由MSTR位决定主机还是从机  0: SS脚用于决定主机还是从机。
    SPCTL |= SPEN;    //1: 允许SPI,                          0:禁止SPI,所有SPI管脚均为普通IO
    SPCTL &= ~DORD;    //1:LSB先发,                          0:MSB先发
    SPCTL |= MSTR;    //1:设为主机                           0:设为从机
    SPCTL &= ~CPOL;    //1: 空闲时SCLK为高电平,               0:空闲时SCLK为低电平
    SPCTL &= ~CPHA;    //1: 数据在SCLK前沿驱动,后沿采样.        0: 数据在SCLK前沿采样,后沿驱动.
    P_SW1 = (P_SW1 & ~0x0c) | ((SPI_io<<2) & 0x0c);      //切换IO

    HSCLKDIV   = 1;        //HSCLKDIV主时钟分频
    SPI_CLKDIV = 1;        //SPI_CLKDIV主时钟分频
    if(SPI_io == 0)
    {
        P1n_standard(0xf0);             //切换到 P1.4(SS) P1.5(MOSI) P1.6(MISO) P1.7(SCLK), 设置为准双向口
        PullUpEnable(P1PU, 0xf0);       //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P1n_push_pull(Pin7+Pin5);       //MOSI SCLK设置为推挽输出
        SlewRateHigh(P1SR, Pin7+Pin5);  //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
    }
    else if(SPI_io == 1)
    {
        P2n_standard(0xf0);            //切换到P2.4(SS) P2.5(MOSI) P2.6(MISO) P2.7(SCLK), 设置为准双向口
        PullUpEnable(P2PU, 0xf0);       //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P2n_push_pull(Pin7+Pin5);       //MOSI SCLK设置为推挽输出
        SlewRateHigh(P2SR, Pin7+Pin5);  //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
    }
    else if(SPI_io == 2)
    {
        P4n_standard(0x0f);         //切换到P4.0(SS) P4.1(MOSI) P4.2(MISO) P4.3(SCLK), 设置为准双向口
        PullUpEnable(P4PU, 0x0f);   //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P4n_push_pull(Pin3+Pin1);   //MOSI SCLK设置为推挽输出
        SlewRateHigh(P4SR, Pin3+Pin1);  //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
    }
    else if(SPI_io == 3)
    {
        P3n_standard(0x3C);         //切换到P3.5(SS) P3.4(MOSI) P3.3(MISO) P3.2(SCLK), 设置为准双向口
        PullUpEnable(P3PU, 0x3c);   //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P3n_push_pull(Pin4+Pin2);   //MOSI SCLK设置为推挽输出
        SlewRateHigh(P3SR, Pin4+Pin2);  //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
    }
}


//DMA_SPI_CR     SPI_DMA控制寄存器
#define        DMA_ENSPI    (1<<7)    // SPI DMA功能使能控制位,    bit7, 0:禁止SPI DMA功能,  1:允许SPI DMA功能。
#define        SPI_TRIG_M   (1<<6)    // SPI DMA主机模式触发控制位,bit6, 0:写0无效,          1:写1开始SPI DMA主机模式操作。
#define        SPI_TRIG_S   (1<<5)    // SPI DMA从机模式触发控制位,bit5, 0:写0无效,          1:写1开始SPI DMA从机模式操作。
#define        SPI_CLRFIFO       1    // 清除SPI DMA接收FIFO控制位,bit0, 0:写0无效,          1:写1复位FIFO指针。


//DMA_SPI_CFG     SPI_DMA配置寄存器
#define        DMA_SPIIE    (1<<7)    // SPI DMA中断使能控制位,bit7, 0:禁止SPI DMA中断,     1:允许中断。
#define        SPI_ACT_TX   (1<<6)    // SPI DMA发送数据控制位,bit6, 0:禁止SPI DMA发送数据,主机只发时钟不发数据,从机也不发. 1:允许发送。
#define        SPI_ACT_RX   (1<<5)    // SPI DMA接收数据控制位,bit5, 0:禁止SPI DMA接收数据,主机只发时钟不收数据,从机也不收. 1:允许接收。
#define        DMA_SPIIP    (0<<2)    // SPI DMA中断优先级控制位,bit3~bit2, (最低)0~3(最高).
#define        DMA_SPIPTY        0    // SPI DMA数据总线访问优先级控制位,bit1~bit0, (最低)0~3(最高).

//DMA_SPI_CFG2     SPI_DMA配置寄存器2
#define        SPI_WRPSS    (0<<2)    // SPI DMA过程中使能SS脚控制位,bit2, 0: SPI DMA传输过程不自动控制SS脚。  1:自动拉低SS脚。
#define        SPI_SSS           0    // SPI DMA过程中自动控制SS脚选择位,bit1~bit0, 0: P1.4,  1:P2.4,  2: P4.0,  3:P3.5。

//DMA_SPI_STA     SPI_DMA状态寄存器
#define        SPI_TXOVW    (1<<2)    // SPI DMA数据覆盖标志位,bit2, 软件清0.
#define        SPI_RXLOSS   (1<<1)    // SPI DMA接收数据丢弃标志位,bit1, 软件清0.
#define        DMA_SPIIF         1    // SPI DMA中断请求标志位,bit0, 软件清0.

//HSSPI_CFG  高速SPI配置寄存器
#define        SS_HOLD      (0<<4)    //高速模式时SS控制信号的HOLD时间, 0~15, 默认3. 在DMA中会增加N个系统时钟,当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.
#define        SS_SETUP          3    //高速模式时SS控制信号的SETUP时间,0~15, 默认3. 在DMA中不影响时间,       当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.

//HSSPI_CFG2  高速SPI配置寄存器2
#define        SPI_IOSW     (1<<6)    //bit6:交换MOSI和MISO脚位,0:不交换,1:交换
#define        HSSPIEN      (1<<5)    //bit5:高速SPI使能位,0:关闭高速模式,1:使能高速模式
#define        FIFOEN       (1<<4)    //bit4:高速SPI的FIFO模式使能位,0:关闭FIFO模式,1:使能FIFO模式,使能FIFO模式在DMA中减少13个系统时间。
#define        SS_DACT           3    //bit3~0:高速模式时SS控制信号的DEACTIVE时间,0~15, 默认3. 当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.

/*****************************************************************************
* @name       :void SPI_DMA_TxTRIG(u8 xdata *TxBuf, u16 num)
* @date       :2024-1-5
* @function   :Config SPI DMA
* @parameters :None
* @retvalue   :None
******************************************************************************/
void SPI_DMA_TxTRIG(u8 xdata *TxBuf, u16 num)
{
    u16 j;
    HSSPI_CFG  = SS_HOLD | SS_SETUP;    //SS_HOLD会增加N个系统时钟, SS_SETUP没有增加时钟。
    HSSPI_CFG2 = HSSPIEN | FIFOEN | SS_DACT;    //FIFOEN允许FIFO会减小13个时钟, 67T @8分频.

    j = (u16)TxBuf;        //取首地址
    DMA_SPI_TXAH = (u8)(j >> 8);        //发送地址寄存器高字节
    DMA_SPI_TXAL = (u8)j;               //发送地址寄存器低字节
    DMA_SPI_AMTH = (u8)((num-1)/256);   //设置传输总字节数 = n+1
    DMA_SPI_AMT  = (u8)((num-1)%256);   //设置传输总字节数 = n+1
    DMA_SPI_ITVH = 0;                   //增加的间隔时间,N+1个系统时钟
    DMA_SPI_ITVL = 0;
    DMA_SPI_STA  = 0x00;
    DMA_SPI_CFG  = DMA_SPIIE | SPI_ACT_TX | DMA_SPIIP | DMA_SPIPTY;
    DMA_SPI_CFG2 = SPI_WRPSS | SPI_SSS;
    DMA_SPI_CR   = DMA_ENSPI | SPI_TRIG_M | SPI_CLRFIFO;
    P10 = 1;
}

//========================================================================
// 函数: void SPI_DMA_ISR (void) interrupt DMA_SPI_VECTOR
// 描述:  SPI_DMA中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2024-1-5
//========================================================================
void SPI_DMA_ISR (void) interrupt USER_VECTOR//DMA_SPI_VECTOR
{
    P10 = 0;
    DMA_SPI_STA = 0;        //清除中断标志
}

80-SPI-DMA发送-驱动WS2812彩灯.zip (207.66 KB, 下载次数: 5)

热线19952583534
www.STCAI.com
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 08:58 , Processed in 0.070579 second(s), 43 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表