使用PWM控制串行LED灯的精彩之旅
说明
- 本文描述的驱动原理是通用的,但是下文的初始化代码只供参考。
资料下载
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)
两个值中变化。
我们平时使用STM32
的PWM
输出时,都是设置一个占空比,在有需要的时候再修改占空比,但是这种方式并不能保证每个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
的方式,指定某个定时器的某一通道,发送Length
个PWM
波形,每个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;
}
}
}