使用PWM控制串行LED灯的精彩之旅

说明

  1. 本文描述的驱动原理是通用的,但是下文的初始化代码只供参考。

资料下载

RGB_LED灯带_5050慢闪_datasheet

STM32控制LED灯带

根据上面的说明书可知,通过修改800KHz的PWM波形的占空比可以控制LED的颜色。
假设现在有3颗串联起来的灯珠,如下图:
典型应用电路


如果U1/U2/U3需要显示红/绿/蓝色,根据说明书,需要从DIN发送0x00FF00_FF0000_0000FF(高位先发)。

假设现在U1/U2/U3需要的颜色为从DIN发送0x123456_789ABC_DEF0AA(高位先发),则我们需要从DIN输入如下脉冲:
从上面的PWM波形可以看出,PWM的频率一直都是800KHz(周期为1.25us),但是占空比一直在32%(T0)68%(T1)两个值中变化。
我们平时使用STM32PWM输出时,都是设置一个占空比,在有需要的时候再修改占空比,但是这种方式并不能保证每个PWM波形后都更新一次占空比,那怎么办呢?
这就需要使用到我们的HAL_StatusTypeDef HAL_TIMEx_PWMN_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);或者HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);函数了(具体使用哪个,根据占空比需要的高低电平状态决定)。这个函数通过DMA的方式,指定某个定时器的某一通道,发送LengthPWM波形,每个PWM波形高电平的计数值(相当于占空比)存储在pData数组中,当然使用前需要初始化定时器以及相应的DMA外设。

DMA初始化

void Tim8DMAInit(void)
{
	static DMA_HandleTypeDef hdma_tim_A;

    __HAL_RCC_DMA2_CLK_ENABLE();
	
	/* config led A DMA stream */
	hdma_tim_A.Init.Channel  = DMA_CHANNEL_7;
	hdma_tim_A.Init.Direction = DMA_MEMORY_TO_PERIPH;
	hdma_tim_A.Init.PeriphInc = DMA_PINC_DISABLE;
	hdma_tim_A.Init.MemInc = DMA_MINC_ENABLE;
	hdma_tim_A.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
	hdma_tim_A.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
	hdma_tim_A.Init.Mode = DMA_NORMAL;
	hdma_tim_A.Init.Priority = DMA_PRIORITY_HIGH;
	hdma_tim_A.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
	hdma_tim_A.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
	hdma_tim_A.Init.MemBurst = DMA_MBURST_SINGLE;
	hdma_tim_A.Init.PeriphBurst = DMA_PBURST_SINGLE; 

	/* Set hdma_tim_A instance */
	hdma_tim_A.Instance = DMA2_Stream4;

	/* Link hdma_tim_A to hdma[3] (channel3) */
	__HAL_LINKDMA(&Tim8PwmHandle.TimHandleInit, hdma[3], hdma_tim_A);  //@fixme

	/* Initialize TIMx DMA handle */
	HAL_DMA_Init(Tim8PwmHandle.TimHandleInit.hdma[3]);   //@fixme

	/*##-4- Configure the NVIC for DMA #########################################*/
	HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 15, 0);   
	HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
	
	BSP_IntVectSet(DMA2_Stream4_IRQn, DMA2_Stream4_IRQHandler);
}

TIM初始化

void Tim8PwmConfig(void)
{
	Tim8PwmHandle.TimHandleInit.Instance 				= TIM8;
	Tim8PwmHandle.TimHandleInit.Init.Prescaler	 		= 1;		// 系统时钟到TIM的分频系数,由于TIM8是挂在APB2总线上,而APB2总线最高时钟不能超过90MHz,系统时钟配置为168MHz,所以设置为1分频,即TIM8的时钟为84MHz。
	Tim8PwmHandle.TimHandleInit.Init.Period             = 105 - 1;	// PWM周期,105 * 800KHz = 84MHz
	Tim8PwmHandle.TimHandleInit.Init.RepetitionCounter 	= 0;    // 不需要重加载
	Tim8PwmHandle.TimHandleInit.Init.ClockDivision 		= 0;    // 定时器时钟不需要分频
	Tim8PwmHandle.TimHandleInit.Init.CounterMode  		= TIM_COUNTERMODE_UP;   // 向上计数
	Tim8PwmHandle.TimClkEnable 							= Tim8ClkEnable;        // 使能定时器时钟的函数指针
    Tim8PwmHandle.PwmChannel1.EnableFlag                = false;
	Tim8PwmHandle.PwmChannel2.EnableFlag 				= false;
	Tim8PwmHandle.PwmChannel4.EnableFlag 				= false;
	/** channel 3 使能 **/
	Tim8PwmHandle.PwmChannel3.EnableFlag 				= true;
	Tim8PwmHandle.PwmChannel3.GpioClkInit 				= Tim8Channel3ClkEnable;    // 通道1的GPIO时钟使能
	Tim8PwmHandle.PwmChannel3.GpioInit.Pin				= GPIO_PIN_15;
	Tim8PwmHandle.PwmChannel3.GpioInit.Mode				= GPIO_MODE_AF_PP;
	Tim8PwmHandle.PwmChannel3.GpioInit.Pull				= GPIO_PULLDOWN;
	Tim8PwmHandle.PwmChannel3.GpioInit.Speed			= GPIO_SPEED_FAST;
	Tim8PwmHandle.PwmChannel3.GpioInit.Alternate		= GPIO_AF3_TIM8;
	Tim8PwmHandle.PwmChannel3.GpioPort					= GPIOH;
	Tim8PwmHandle.PwmChannel3.OConfig.OCMode 			= TIM_OCMODE_PWM1;
	Tim8PwmHandle.PwmChannel3.OConfig.OCNPolarity		= TIM_OCNPOLARITY_HIGH;
	Tim8PwmHandle.PwmChannel3.OConfig.OCFastMode		= TIM_OCFAST_ENABLE;
	Tim8PwmHandle.PwmChannel3.OConfig.OCIdleState       = TIM_OCIDLESTATE_RESET;
	Tim8PwmHandle.PwmChannel3.OConfig.OCNIdleState      = TIM_OCNIDLESTATE_SET;
	Tim8PwmHandle.PwmChannel3.OConfig.Pulse				= 0;
	
	if(BspPwmHandleInit(&Tim8PwmHandle) != HAL_OK)
	{}  // todo

	Tim8DMAInit();
}

LED颜色控制逻辑

#define PWM_LOW     34          // 根据LED的datasheet,T0的占空比为'0.4/1.25=32%',所以需要'105*32%=34'个计数
#define PWM_HIGH    71          // 根据LED的datasheet,T1的占空比为'0.85/1.25=68%',所以需要'105*68%=71'个计数
bool ledAFinishFlag = true;     // LED颜色控制的PWM波形是否已经发送完成
typedef struct
{
	u32 LedMaxNum;			// LED串联的个数
	u32 *LedInfoBuff; 		// LED色素缓存数组,每个灯需要24位空间(实际分配位u32类型),所以这个指针指向"u32 LedInfoBuff[LedMaxNum]"类型
	u32 *PWMValueBuff; 		// 有LedMaxMum个灯,每个灯有24位表示颜色,每个位都对应了一个PWM,而这个PWM的占空比是一个u32类型的值,所以这个指针指向"u32 PWMValueBuff[LedMaxNum*24]"类型
} LEDHandleDef;

BspLEDStatusTypeDef PWMValueGet(LEDHandleDef *LEDHandle)
{
	u32 i, j;

	if(LEDHandle == NULL)
		return LED_HANDLE_ERR;

	for(i = 0; i < LEDHandle->LedMaxNum; i++)
	{
		for(j = 0; j < 24; j++)
		{
			if(LEDHandle->LedInfoBuff[i] & (0x00800000u >> j))  // 根据某一位的值设置相应的占空比计数
				LEDHandle->PWMValueBuff[i*24 + j] = PWM_HIGH;
			else
				LEDHandle->PWMValueBuff[i*24 + j] = PWM_LOW;
		}
	}
	return LED_GET_DATA_OK;
}

BspLEDStatusTypeDef ledDealA(LEDHandleDef *LEDHandle)
{
	u32 i;
	
	while(ledAFinishFlag != true)   // 等待上次LED颜色发送完成,这个值在void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)函数中赋值为true
	{}

    // 将所有灯的颜色都设置为 0x000000 颜色
	for(i = 0; i < LEDHandle->LedMaxNum * 24; i++)
	{
		LEDHandle->PWMValueBuff[i] = PWM_LOW;
	}
	// if(  HAL_TIM_PWM_Start_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3, (u32*)LEDHandle->PWMValueBuff, LEDHandle->LedMaxNum * 24) != HAL_OK)
    if(  HAL_TIMEx_PWMN_Start_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3, (u32*)LEDHandle->PWMValueBuff, LEDHandle->LedMaxNum * 24) != HAL_OK)
		return LED_HAL_ERR;
	else
		ledAFinishFlag = false;
	
	while(ledAFinishFlag != true)   // 等待LED颜色重置完成
	{}
	
    // 根据每个LED的颜色值计算其占空比的计数值
	PWMValueGet(&LEDHandleA);

    // 按需求设置LED颜色
    // if(  HAL_TIM_PWM_Start_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3, (u32*)LEDHandle->PWMValueBuff, LEDHandle->LedMaxNum * 24) != HAL_OK)
	if(  HAL_TIMEx_PWMN_Start_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3, (u32*)LEDHandle->PWMValueBuff, LEDHandle->LedMaxNum * 24) != HAL_OK)	
		return LED_HAL_ERR;
	else
		ledAFinishFlag = false;

	return LED_DEAL_OK;
}

void SetLedPartAColor(u8 ledRed, u8 ledGreen, u8 ledBlue)
{
	u8 j = 0;
	u32 uTmpColor = 0;

	uTmpColor = ((ledGreen<<16) | (ledRed<<8) | (ledBlue));     // 根据datasheet,给LED发送的颜色顺序是GRB

	for(j = 0; j < LED_NUM; j++)
	{
		gLedAInfoBuff[j] = uTmpColor;
	}
	LEDHandleA.LedMaxNum = LED_NUM;
	LEDHandleA.LedInfoBuff = gLedAInfoBuff;
	LEDHandleA.PWMValueBuff = gLedAPwmvalBuff;
	ledDealA(&LEDHandleA);
}

/*
* @brief:   Led灯带颜色设置
* @input:   color[31-24] - don't care
*           color[23-16] - R
*           color[15-8]  - G
*           color[7-0]   - B
* @return:  None
*/
static void LedStripePartAColorSet(const u32 color)
{
    u8  R = 0, G = 0, B = 0;

	R = (u8)((color>>16) & 0xFF);
    G = (u8)((color >> 8) & 0xFF);
    B = (u8)((color ) & 0xFF);
    SetLedPartAColor(R, G, B);
}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM8)
	{
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		{
//			HAL_TIM_PWM_Stop_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_1);
			HAL_TIMEx_PWMN_Stop_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_1);
			ledFinishFlag = true;
			Tim8PwmHandle.TimHandleInit.Instance->CCR1	= 0;
			Tim8PwmHandle.TimHandleInit.Instance->EGR = TIM_EGR_UG;
		}
        
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
		{
//			HAL_TIM_PWM_Stop_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3);
			HAL_TIMEx_PWMN_Stop_DMA(&Tim8PwmHandle.TimHandleInit, TIM_CHANNEL_3);
			ledAFinishFlag = true;
			Tim8PwmHandle.TimHandleInit.Instance->CCR3	= 0;
			Tim8PwmHandle.TimHandleInit.Instance->EGR = TIM_EGR_UG;
		}		
	}
}
物联沃分享整理
物联沃-IOTWORD物联网 » 使用PWM控制串行LED灯的精彩之旅

发表评论