STM8S(105K4)使用笔记——通过TIM1输出PWM做呼吸灯

引言

本篇博客将讲解PWM做呼吸灯的一个方法。
本篇博客将不会从TIM1设置做引入,而是从呼吸灯实际功能实现做引入。


STM8S105K4相关

已知的可以作为TIM1 PWM的输出通道为PC1、PC2、PC3、PC4。
已知可选的TIM1时钟为fmaster。

若使用的STM8S的芯片不为105K4,请查阅芯片相对应的文档,确认TIM1的PWM的输出通道,同时设置相应的选项字节。

呼吸灯功能需求设置

呼吸灯有这么两个最常见的功能需求:

  • LED灯一次灭到亮的耗时T(s),即周期/2
  • LED灯的刷新率P(Hz)

由这两个参数可得:

  • LED灯一次灭到亮需要刷新的次数N,N = T × P

例如:

  • LED灯一次灭到亮需求为1.5s
  • LED灯的刷新率为60Hz(约每0.017s刷新一次)
  • LED灯一次灭到亮需要刷新的次数为90次

至此,LED功能需求约定完成。


TIM1的PWM设置

由ST所提供的官方库中,有这么一个函数。

/**
  * @brief  Initializes the TIM1 Time Base Unit according to the specified parameters.
  * @param  TIM1_Prescaler specifies the Prescaler value.
  * @param  TIM1_CounterMode specifies the counter mode  from @ref TIM1_CounterMode_TypeDef .
  * @param  TIM1_Period specifies the Period value.
  * @param  TIM1_RepetitionCounter specifies the Repetition counter value
  * @retval None
  */
void TIM1_TimeBaseInit(uint16_t TIM1_Prescaler,
                       TIM1_CounterMode_TypeDef TIM1_CounterMode,
                       uint16_t TIM1_Period,
                       uint8_t TIM1_RepetitionCounter)
{
  /* Check parameters */
  assert_param(IS_TIM1_COUNTER_MODE_OK(TIM1_CounterMode));
  
  /* Set the Autoreload value */
  TIM1->ARRH = (uint8_t)(TIM1_Period >> 8);
  TIM1->ARRL = (uint8_t)(TIM1_Period);
  
  /* Set the Prescaler value */
  TIM1->PSCRH = (uint8_t)(TIM1_Prescaler >> 8);
  TIM1->PSCRL = (uint8_t)(TIM1_Prescaler);
  
  /* Select the Counter Mode */
  TIM1->CR1 = (uint8_t)((uint8_t)(TIM1->CR1 & (uint8_t)(~(TIM1_CR1_CMS | TIM1_CR1_DIR)))
                        | (uint8_t)(TIM1_CounterMode));
  
  /* Set the Repetition Counter value */
  TIM1->RCR = TIM1_RepetitionCounter;
}

相信很多人与我一样,无法理解使用PWM,需要如何配置TIM1_Prescaler,TIM1_Period这两个值。
下面将对这两个值的配置做出解释:

  • TIM1_Prescaler:该参数的设置会使TIM1的工作频率为f = fmaster / (TIM1_Prescaler + 1)Hz,即每1/f s做一次计数。
  • TIM1_Period:该参数的设置会使TIM1计数TIM1_Period次进行一次更新。即每(1 / f)× TIM1_Period s进行一次更新。
    同时,在PWM模式下,TIM1_Period也作为PWM占空比的宽度。当你在占空比设置时,若你设置的占空比值为TIM1_Period / 2,那么你的灯泡将会以50%的亮度被点亮。

例如:

  • HSI的频率fmaster = 16MHz
  • LED灯一次灭到亮需求为1.5s
  • LED灯的刷新率为60Hz(约每0.017s刷新一次)
  • LED灯一次灭到亮需要刷新的次数为90次
  • TIM1_Period设置为90
    由于LED灯的刷新率为60Hz,则需TIM1的更新频率(16000000/ (TIM1_Prescaler + 1) × 90) > 60Hz。
    即需TIM1_Period + 1 < 2962.962 ≈ 2963。

此时,只需要每0.017s使通道PWM输出的占空比值步进1,就能让LED灯以60Hz的刷新率在1.5s内由灭到亮(亮到灭)。

/* 关键代码片段 */
  	TIM1_TimeBaseInit(2963, TIM1_COUNTERMODE_UP, 90, 0);

/* 关键代码片段 */
void main(void)
{
  	static uint32_t startTick = 0;	

  	while (1)
  	{
    	static uint16_t pwm = 90;
    	static FlagStatus pwmtozero = RESET;
    	if (SYS_GetTick() - startTick > 17)
    	{
			if (pwmtozero == RESET)
      		{
        		pwm -= 1;
      		}
      		else
      		{
        		pwm += 1;
      		}
      		TIM1_SetCompare3(pwm);
      		if (pwm == 0)
      		{
        		pwmtozero = SET;
      		}
      		else if ((pwm == 90))
      		{
        		pwmtozero = RESET;
      		}
      		startTick = SYS_GetTick();
    	}
    }
}

注:SYS_GetTick()将返回一个uwTick变量,该变量每1ms步进一次,实现方式可以参考这篇博客:STM8S(105K4)使用笔记——TIM4的基础配置

至此,TIM1通过PWM输出实现LED呼吸灯基本实现。


Gamma

由上面的参数与代码逻辑可知,PWM的占空比变化是线性,因此,LED灯的亮度变化也是线性的。但由于人眼对亮度认知的问题,上面代码跑出来的呼吸灯,在视觉认知上的亮度变化并不是线性。如下方图片所示:
STM8S(105K4)使用笔记——通过TIM1输出PWM做呼吸灯_第1张图片
为此,需引入Gamma系数对占空比进行变换,让LED灯亮度完成视觉认知上线性变换。
根据不同颜色的灯,有如下Gamma系数:
STM8S(105K4)使用笔记——通过TIM1输出PWM做呼吸灯_第2张图片
由于stm8105k4的性能限制,开根对性能要求太大,pwm值变换引入的Gamma值一律为2。

本篇博客不对Gamma值进行探讨,仅引入解决方案。


引入Gamma系数

回到原本的例子:

  • HSI的频率fmaster = 16MHz
  • LED灯一次灭到亮需求为1.5s
  • LED灯的刷新率为60Hz(约每0.017s刷新一次)
  • LED灯一次灭到亮需要刷新的次数为90次
  • TIM1_Period设置为90^2 = 8100
    这里TIM1_Period的设置为刷新次数N^Gamma。若你所选用的芯片有较高的计算性能,可以选用合适的Gamma值,由于stm8s105k4的计算性能并不高,因此简单地使用2作为Gamma值。
  • TIM1_Prescaler至少需小于等于32
/* TIM1初始化配置 */
/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void _TIM1_Init(void)
{
	/* De-Init TIM1 peripheral*/
  	TIM1_DeInit();

  	/* Time Base configuration */
  	/*
  	TIM1_Period = 32
  	TIM1_Prescaler = 8100
  	TIM1_CounterMode = TIM1_COUNTERMODE_UP
  	TIM1_RepetitionCounter = 0
  	*/

  	TIM1_TimeBaseInit(32, TIM1_COUNTERMODE_UP, 8100, 0);
  
  	/*TIM1_Pulse = CCR3_Val*/
  	TIM1_OC3Init(TIM1_OCMODE_PWM1, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_DISABLE,
               	0, TIM1_OCPOLARITY_LOW, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET,
               	TIM1_OCNIDLESTATE_SET);
               
  	/* TIM1 counter enable */
  	TIM1_Cmd(ENABLE);

  	/* TIM1 Main Output Enable */
  	TIM1_CtrlPWMOutputs(ENABLE);
}

/* main中算法与代码 */
void main(void)
{
  	static uint32_t startTick = 0;

  	while (1)
  	{
    	static uint8_t number = 90;
    	static uint16_t pwm = 0;
    	static FlagStatus pwmtozero = RESET;
    
    	if (SYS_GetTick() - startTick > 17)
    	{
      		if (pwmtozero == RESET)
      		{
        		number -= 1;
      		}
      		else
      		{
        		number += 1;
      		}
      		pwm = number * number;

      		TIM1_SetCompare3(pwm);
      		if (number == 0)
      		{
        		pwmtozero = SET;
      		}
      		else if ((number == 90))
      		{
        		pwmtozero = RESET;
      		}

      		startTick = SYS_GetTick();
    	}
  	}
}

至此,符合人眼视觉认知的亮度线性变化完成。

注:官方库TIM1_OCxInitx()中的变量TIM1_Pulse可理解为初始占空比值,即LED灯初始亮度。


写在最后

我非常感谢我的同事Mak对我的帮助。最初在实验PWM功能时,查阅过很多博客,但大多都是在写如何初始化TIM1,并没有说如何实现一个LED呼吸灯功能,所以在做了诸多实际试验后,决定把我的理解和成果写在了这篇文章中,希望对大家有帮助。
还有一点!如果在此前对TIM1的时钟门控进行过关闭,请记得打开!!!不然你会调代码调到怀疑人生233~

你可能感兴趣的:(嵌入式相关)