HC32F460驱动ws2812及流水灯灯效实现方法


前言

最近公司打算引进新的平台(华大)来做产品开发,板子到手后,老大让我先点个流水灯看看效果。本来以为只是一个简单的IO操作加上一些应用逻辑就可以实现的功能,但了解到了ws2812的操作时序要求后,也是花了点功夫才实现相应的功能。


一、WS2812是什么?

WS2812是一种集成了控制电路与发光电路于一体的LED光源。对于WS2812的具体介绍网上有很多资料,这里就不再赘述,下面我们从数据手册中罗列出我们编程所需要的关键内容。

主要特点:

● IC控制电路与LED点光源共用一个电源。
● 每个通道工作电流5mA.
● 控制电路与RGB芯片集成在一个2020封装的元器件中,构成一个完整的外控像素点。
● 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加。
● 内置上电复位和掉电复位电路。
● 每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。
● 端口扫描频率2KHz/s。
● 串行级联接口,能通过一根信号线完成数据的接收与解码。
● 任意两点传输距离在不超过5米时无需增加任何电路。
● 当刷新速率30帧/秒时,级联数不小于1024点。
● 数据发送速度可达800Kbps。
● 光的颜色高度一致,性价比高。

引脚及功能如下:
在这里插入图片描述
数据传输方法:
在这里插入图片描述
数据结构:
在这里插入图片描述
数据传输时间
在这里插入图片描述
时序波形图
在这里插入图片描述
从以上这些我们可以了解到,WS2812是可以多个器件同时级联的,通过串行数据传输可以同时对多个器件进行控制。控制一个WS2812所需要的字节数是三个字节(GRB),而从时序波形图中,我们可以了解到码0和码1所对应的波形图,相应的波形需要的时序达到ns级别的,因此,按照原来的想法,直接控制IO翻转操作来发送是达不到这样的时序要求的,因此需要通过其他方法来发送0码和1码。

二、通过SPI控制WS2812

1.原理

上网查找了一些资料,对于WS2812的控制,大部分人都是采用SPI的方式来控制的,我们也采用此方法。
从前面的码型图我们可以知道,0码和1码都是由不同占比的高低电平时间来表示的,相应的高低电平时间也有一定的冗余量,因此,我们可以通过调整SPI发送一帧数据的时钟频率以及发送的数据,来实现对应的0码和1码。简单的举个例子,我们可以通过控制分频系数来使得SPI的MOSI脚在1.25us内输出一帧数据(8byte),假设我们输出的数据是0xF0,这个时候,这帧数据的高低电平占比刚好是1:1,也就是高低电平持续的时间都是600ns左右,这刚好对应ws2812的1码,假设我们输出的数据是0xC0,同理,对应于0码。通过这种方式输出0码和1码,我们就可以很简单的实现操作ws2812。

2.功能实现

代码如下:
首先是SPI的初始化,我们需要先配置好SPI的时钟频率,这里根据板子不同配置也不一样。

/*宏定义*/
/* SPI_MOSI Port/Pin definition */
#define SPI_MOSI_PORT                   (PortB)
#define SPI_MOSI_PIN                    (Pin09)
#define SPI_MOSI_FUNC                   (Func_Spi3_Mosi)

/* SPI unit and clock definition */
#define SPI_UNIT                        (M4_SPI3)
#define SPI_UNIT_CLOCK                  (PWC_FCG1_PERIPH_SPI3)

/* SPI DMA unit and channel definition */
#define SPI_DMA_UNIT                    (M4_DMA1)
#define SPI_DMA_CLOCK_UNIT              (PWC_FCG0_PERIPH_DMA1)
#define SPI_DMA_TX_CHANNEL              (DmaCh1)
#define SPI_DMA_TX_TRIG_SOURCE          (EVT_SPI3_SPTI)

void WS2812C_Init(void)
{
	stc_spi_init_t stcSpiInit;

	/* configuration structure initialization */
	MEM_ZERO_STRUCT(stcSpiInit);

	/* Configuration peripheral clock */
	PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);

	/* Configuration SPI pin */
	PORT_SetFunc(SPI_MOSI_PORT, SPI_MOSI_PIN, SPI_MOSI_FUNC, Disable);

	/* Configuration SPI structure */
	stcSpiInit.enClkDiv = SpiClkDiv4;
	stcSpiInit.enFrameNumber = SpiFrameNumber1;
	stcSpiInit.enDataLength = SpiDataLengthBit16;	//一帧数据16个字节
	stcSpiInit.enFirstBitPosition = SpiFirstBitPositionMSB;
	stcSpiInit.enSckPolarity = SpiSckIdleLevelLow;
	stcSpiInit.enSckPhase = SpiSckOddChangeEvenSample;
	stcSpiInit.enReadBufferObject = SpiReadReceiverBuffer;
	stcSpiInit.enWorkMode = SpiWorkMode3Line;
	stcSpiInit.enTransMode = SpiTransFullDuplex;
	stcSpiInit.enCommAutoSuspendEn = Disable;
	stcSpiInit.enModeFaultErrorDetectEn = Disable;
	stcSpiInit.enParitySelfDetectEn = Disable;
	stcSpiInit.enParityEn = Disable;
	stcSpiInit.enParity = SpiParityEven;

	stcSpiInit.enMasterSlaveMode = SpiModeMaster;
	stcSpiInit.stcDelayConfig.enSsSetupDelayOption = SpiSsSetupDelayCustomValue;
	stcSpiInit.stcDelayConfig.enSsSetupDelayTime = SpiSsSetupDelaySck1;
	stcSpiInit.stcDelayConfig.enSsHoldDelayOption = SpiSsHoldDelayCustomValue;
	stcSpiInit.stcDelayConfig.enSsHoldDelayTime = SpiSsHoldDelaySck1;
	stcSpiInit.stcDelayConfig.enSsIntervalTimeOption = SpiSsIntervalCustomValue;
	stcSpiInit.stcDelayConfig.enSsIntervalTime = SpiSsIntervalSck6PlusPck2;
	
	SPI_Init(SPI_UNIT, &stcSpiInit);

	Spi_DmaConfig();
}

配置DMA功能

static uint8_t u8TxBuffer[48] = {0};
static uint16_t u16BufferLen = 48;
static uint16_t u16BufferCnt = 0;

static void Spi_DmaConfig(void)
{
    stc_dma_config_t stcDmaCfg;

    /* configuration structure initialization */
    MEM_ZERO_STRUCT(stcDmaCfg);

    /* Configuration peripheral clock */
    PWC_Fcg0PeriphClockCmd(SPI_DMA_CLOCK_UNIT, Enable);
    PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS, Enable);

    /* Configure TX DMA */
    stcDmaCfg.u16BlockSize = 1u;
    stcDmaCfg.u16TransferCnt = u16BufferLen;
    stcDmaCfg.u32SrcAddr = (uint32_t)(&u8TxBuffer[0]);
    stcDmaCfg.u32DesAddr = (uint32_t)(&SPI_UNIT->DR);
    stcDmaCfg.stcDmaChCfg.enSrcInc = AddressIncrease;
    stcDmaCfg.stcDmaChCfg.enDesInc = AddressFix;
    stcDmaCfg.stcDmaChCfg.enTrnWidth = Dma8Bit;
    stcDmaCfg.stcDmaChCfg.enIntEn = Disable;
    DMA_InitChannel(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, &stcDmaCfg);


    DMA_SetTriggerSrc(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, SPI_DMA_TX_TRIG_SOURCE);

    /* Enable DMA. */
    DMA_Cmd(SPI_DMA_UNIT, Enable);
}

输出码0和码1

#define WS2812_LOW						(0xF000)
#define WS2812_HIGH						(0xFF00)

static void RGB_Set_Up(void)
{
	u8TxBuffer[u16BufferCnt] = (WS2812_HIGH >> 8 & 0xFF);
	u8TxBuffer[u16BufferCnt+1] = WS2812_HIGH & 0xFF;
	u16BufferCnt += 2;
}

static void RGB_Set_Down(void)
{
	u8TxBuffer[u16BufferCnt] = (WS2812_LOW >> 8 & 0xFF);
	u8TxBuffer[u16BufferCnt+1] = WS2812_LOW & 0xFF;
	
	u16BufferCnt += 2;
}

ws2812控制接口

void WS2812C_SetRGB(uint32_t RGB888)
{
	int8_t i = 0;
	uint8_t byte = 0;

	u16BufferCnt = 0;
	
	for(i = 23; i >= 0; i--)
	{
		byte = ((RGB888>>i)&0x01);
		if(byte == 1)
		{
			RGB_Set_Up();
		}
		else
		{
			RGB_Set_Down();
		}
	}
	DMA_SetSrcAddress(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, (uint32_t)(&u8TxBuffer[0]));
	DMA_SetTransferCnt(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, u16BufferLen);
	/* Enable DMA channel */
	DMA_ChannelCmd(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, Enable);
	/* Enable SPI to start DMA */
	SPI_Cmd(SPI_UNIT, Enable);
	while (Reset == DMA_GetIrqFlag(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, TrnCpltIrq))
	{
	}
	DMA_ClearIrqFlag(SPI_DMA_UNIT, SPI_DMA_TX_CHANNEL, TrnCpltIrq);
	/* Disable SPI */
	SPI_Cmd(SPI_UNIT, Disable);
}

最后是一个流水灯的灯效

//先定义好一个数组并做好初始化
static uint32_t color_group[WS2812C_LED_NUM] = {0};

/* init color_group */
for(i = 0; i < WS2812C_LED_NUM; i++)
{
	if(i < 6)
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_BLUE & (~(0x01<<j));
	}
	else if(i < 12)
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_RED & (~(0x01<<j));
	}
	else if(i < 18)
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_GREEN & (~(0x01<<j));
	}
	else if(i < 24)
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_YELLOW & (~(0x01<<j));
	}
	else if(i < 30)
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_CYAN & (~(0x01<<j));
	}
	else
	{
		for(j = 0; j < 6; j++)
			color_group[i] = LED_COLOR_PURPLE & (~(0x01<<j));
	}
}

//流水灯
void Led_Cycle_Run(void)
{
	uint8_t i = 0;
	static uint8_t index = 0;
	for(i = 0; i < WS2812C_LED_NUM; i++)
	{
		WS2812C_SetRGB(color_group[(index + i)%WS2812C_LED_NUM]);
	}
	index ++;
	index %= WS2812C_LED_NUM;
	Ddl_Delay1ms(50);
}


总结

至此,通过HC32控制ws2812的功能已实现,具体效果图就不放上来了,后续有其他东西再分享。

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Top0_0lll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值