80-SPI-DMA发送-驱动WS2812彩灯,AI8051U实验箱演示程序
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.3125usL=0.9375us, 数据1(3/4占空比): H=0.9375usL=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数据 1000 1110
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 xdataled_RGB; //LED对应的RGB,led_buff-->绿,led_buff-->红,led_buff-->蓝.
u8 xdataled_SPI; //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; //亮度(颜色)首地址
for(i=0; i<(LED_NUM*3); i++, px++) *px = 0; //清除所有的颜色
i = k;
led_RGB = COLOR; //红色
if(++i >= LED_NUM) i = 0;//下一个灯
led_RGB = COLOR; //绿色
if(++i >= LED_NUM) i = 0;//下一个灯
led_RGB = 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延时函数 ==============
voiddelay_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; //首地址
for(i=0, j=0; i<(LED_NUM*3); i++)
{
dat = *px;
px++;
for(k=0; k<4; k++)
{
if(dat & 0x80)led_SPI= 0xE0; //数据1
else led_SPI= 0x80; //数据0
if(dat & 0x40)led_SPI |= 0x0E; //数据1
else led_SPI |= 0x08; //数据0
dat <<= 2;
j++;
}
}
}
//========================================================================
// 函数: void SPI_Config(u8 io, u8 speed)
// 描述: SPI初始化函数。
// 参数: io: 切换到的IO, SSMOSI 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; //清除中断标志
}
页:
[1]