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

本文介绍了如何使用HC32平台通过SPI接口精确控制WS2812 LED阵列,包括了WS2812的工作原理、SPI控制细节、代码实现以及实际应用,重点展示了如何调整SPI参数以发送0和1码实现颜色控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

最近公司打算引进新的平台(华大)来做产品开发,板子到手后,老大让我先点个流水灯看看效果。本来以为只是一个简单的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的功能已实现,具体效果图就不放上来了,后续有其他东西再分享。

### 使用 HC32F460 微控制器点亮 WS2812 LED 的方法 为了实现使用 HC32F460 控制器点亮 WS2812 LED,可以采用定时器和 GPIO 来模拟 WS2812 所需的信号协议。以下是具体的操作说明: #### 初始化硬件资源 首先需要配置 GPIO 和定时器来生成精确的时间脉冲序列。 ```c #include "hc32f460.h" #define WS2812_PIN P05 // 定义用于控制 WS2812 的引脚 #define TMR_CHANNEL TIMER_UNIT_1 // 定义使用的定时器通道 void Ws2812_Init(void){ stc_timer_init_t stcTimerInit; CRGCLK_PeripheralClockCmd(APBPeriph_GPIO, Enable); GPIO_SetFunc(GPIO_PORT_P0, WS2812_PIN, Func_Timer_TmrCh0); /* 配置定时器 */ MEM_ZERO_STRUCT(stcTimerInit); Timer_StructInit(&stcTimerInit); stcTimerInit.u16Mode = TimerMode_UpCounting; stcTimerInit.u16Prescaler = 83; // 设置预分频系数使得计数频率为 1MHz (假设系统时钟为 84 MHz) stcTimerInit.u16Arr = 1000; // 自动重装载值设置为 1ms Timer_Init(TMR_CHANNEL, &stcTimerInit); } ``` #### 发送数据给 WS2812 通过改变高电平持续时间和低电平时间长短表示不同的二进制位。 ```c static void SendBit(bool bitValue){ uint16_t highTime = bitValue ? 7 : 2; // '1' 对应较长的高电平周期;'0' 则较短 uint16_t lowTime = 24 - highTime; GPIO_ResetBits(WS2812_PIN); Timer_SetCompareValue(TMR_CHANNEL, highTime); Timer_EnableCounter(TMR_CHANNEL); while (!Timer_GetFlagStatus(TMR_CHANNEL, Status_Ch0)); Timer_ClearIntPendingBit(TMR_CHANNEL, Int_Ch0); GPIO_SetBits(WS2812_PIN); Timer_SetCompareValue(TMR_CHANNEL, lowTime); while (!Timer_GetFlagStatus(TMR_CHANNEL, Status_Ch0)); Timer_ClearIntPendingBit(TMR_CHANNEL, Int_Ch0); } // 发送完整的字节到 WS2812 void SendByte(uint8_t byteToSend){ for(int i=0;i<8;i++){ bool currentBit = ((byteToSend >> (7-i)) & 0x01)? true:false ; SendBit(currentBit); } } ``` #### 主函数逻辑结构 最后,在主程序里调用上述初始化以及发送命令完成整个过程。 ```c int main(){ SystemCoreClockUpdate(); Ws2812_Init(); uint8_t colorData[] = {0xFF, 0x00, 0x00}; // 红色光颜色编码(RGB) while(1){ for(size_t i=0 ;i<sizeof(colorData)/sizeof(*colorData);++i){ SendByte(colorData[i]); } DelayMs(1000); // 延迟一秒再重复显示 } } ``` 以上代码展示了如何利用 HC32F460 单片机驱动 WS2812[^2]。需要注意的是实际应用中可能还需要考虑更多细节比如电源管理、噪声抑制等问题。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Top0_0lll

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

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

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

打赏作者

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

抵扣说明:

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

余额充值