历史上的今天
今天是:2024年09月29日(星期日)
2021年09月29日 | STM8S(105K4)使用笔记——通过TIM1输出PWM做呼吸灯
2021-09-29 来源:eefocus
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灯的亮度变化也是线性的。但由于人眼对亮度认知的问题,上面代码跑出来的呼吸灯,在视觉认知上的亮度变化并不是线性。如下方图片所示:

为此,需引入Gamma系数对占空比进行变换,让LED灯亮度完成视觉认知上线性变换。
根据不同颜色的灯,有如下Gamma系数:

由于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的时钟门控进行过关闭,请记得打开!!!不然你会调代码调到怀疑人生
史海拾趣
|
大家好,现在学习实践I2C总线方面的知识。这个过程中遇到的问题还希望能够得到大家的指教,谢谢! 下面这段话摘自“I2C总线规范”, “在SCL线是高电平时SDA线从高电平向低电平切换,这个情况表示起始条件。 在SCL线是高电平时SDA线由低电平向 ...… 查看全部问答> |
|
现有两个WINCE下驱动项目外包: 1、S3C2416下驱动16C554多串口芯片的驱动程序 2、S3C2416的声音驱动(芯片的驱动代码有2442平台下的可以做参考) 有意者请加QQ嵌入式外包群:48348107 谢谢各位!… 查看全部问答> |
|
VMware+RH9+skyeye1.2 仿真lcd出现segmentation fault VMware+RH9+skyeye1.2 仿真lcd出现segmentation fault 配置文件内容: cpu: arm720t mach: ep7312 mem_bank: map=M, type=R, addr=0x0, size=0xC0000 mem_bank: map=M, type=R, addr=0x000C0000, size=0x00340000, fil ...… 查看全部问答> |
|
我使用sim3000的gprs模块,昨天已经可以用ip地址访问我在花生壳上注册的地址了.然后我尝试用域名的形式访问网络.先 at+cipshut,关闭连接 然后 配置dns服务器 at+cdnscfg=\"202.96.128.56\" 然后设置为域名访问方式at+cdnsorip=1 最后使用域名访 ...… 查看全部问答> |
|
现在我用的是网口调试,经常出现下了一半就下不下去了 重起一次机器就好了 这样用就太不稳定了 想用USB的方法进行在线调试 但不知道怎么下手 麻烦各位大侠指点指点了 小女子先谢谢了哈… 查看全部问答> |
|
我用51单片机与pc机进行串口通讯,上位机软件用的是出口调试助手3.0,下位机是我自己编的,主要目的是pc向单片机发个数据,单片机接收后向p2口输出,之后再把接收到的数据发给pc。但是我发现pc上发出与收到的数据总是不一致。例如,我用pc输出字符 ...… 查看全部问答> |
|
看到许多朋友抱怨LPC17xx的资料难找,发一套全套示例代码 这是一套基于LPC17XX各个模块的示例代码,开发环境是基于KEIL MDK的,我现在用LPC1756做开发,也是初次接触。 这套资料还比较齐全,给了我不少帮助,希望对你们有用。… 查看全部问答> |




