0x00前言
写下这篇文章的主要目的时总结一下最近发现的一个较方便的波形调制方式。可以一定程度脱离手工计算,支持发出任意频率、任意占空比PWM控制信号的方式,使用了较为方便的寄存器的方式进行驱动。本文分为两部分,分别讲述了一个调制信号的思路以及实现的方式、一个计算死区的方式。
阅读本文,您可能需要掌握的知识:
技能 | 熟练度 |
---|---|
英语 | 熟练 |
知识检索 | 熟练 |
数学计算 | 基础 |
逻辑思维 | 熟练 |
0x10 调制信号的方式
项目的主要要求在于,可以随意的调制出任意占空比的1~10KHz的PWM方波。因为这个需求的难度,所以使得软件模拟变成了很难的一件事情。遂我使用STM32系列的高级定时器对波形进行模拟。再查阅了相关资料后我得出的结论,计算波形主要的参数在于占空比以及分频系数上。
0x11 占空比
计算占空比的公式主要是:
P W M 占 空 比 = 占 空 比 阈 值 定 时 器 累 加 值 PWM占空比= \frac {占空比阈值} {定时器累加值} PWM占空比=定时器累加值占空比阈值
项目除了较为宽泛的频率选择,还有宽泛的占空比选择。所以为了方便选择占空比,本人主要的思想在于:确认一个固定的定时器累加值。从而在给定了PWM占空比的情况下,就可以得到当前占空比的阈值。而且只需要调高定时器累加值(分母),就可以得到更高的占空比精度。一般的占空比范围于定时器累加值范围表述约为:
占空比范围 | 定时器累加值 |
---|---|
0~100 | 100 |
0~100.0 | 1000 |
0~100.00 | 10000 |
一般来说,占空比精度做到大约万分之一即可。
0x12 分频系数
仔细翻找了官方的一些文档,以及第三方标准库(StdLib)。得到了一个较为完备的公式:
1 计 时 时 间 = ( 定 时 器 累 加 值 − 1 ) ∗ ( 定 时 器 分 频 值 − 1 ) 当 前 系 统 时 间 \frac {1}{计时时间}=\frac{(定时器累加值 - 1) * (定时器分频值-1)}{当前系统时间} 计时时间1=当前系统时间(定时器累加值−1)∗(定时器分频值−1)
而频率对于计时时间的转换公式为:
频
率
=
1
计
时
时
间
频率=\frac{1}{计时时间}
频率=计时时间1
则公式可以简化成:
频
率
=
(
定
时
器
累
加
值
−
1
)
∗
(
定
时
器
分
频
值
−
1
)
当
前
系
统
时
间
频率=\frac{(定时器累加值 - 1) * (定时器分频值-1)}{当前系统时间}
频率=当前系统时间(定时器累加值−1)∗(定时器分频值−1)
根据此公式,在已知频率、累加器、系统时钟的情况下,可以轻松得出当前定时器分频系数为:
定 时 器 分 频 系 数 = 频 率 ∗ 当 前 系 统 时 钟 定 时 器 累 加 器 − 1 + 1 定时器分频系数=\frac{频率*当前系统时钟}{定时器累加器-1}+1 定时器分频系数=定时器累加器−1频率∗当前系统时钟+1
直接带入即可计算出可以使用的分频系数,而且基本上不会溢出。也可以通过分频系数推断出当前最高输出的频率。
0x20 死区时间
根据官方给的记录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omvJ2pJM-1611468191137)(https://www.abcde.engineer/wp-content/uploads/2019/04/7bd8743e14ab72157360d33fddfb9774.png)]
而官方的寄存器记录中并没有提到关于DTS相关的信息,但是在前面的地方就会有解释,这个DTS可以认为是定时器总线时钟片的原子单位。本人使用了120MHz的时钟,也就是说t_DTS=8μs。
单 个 分 频 比 = 死 区 时 间 t D T S 单个分频比=\frac{死区时间}{t_{DTS}} 单个分频比=tDTS死区时间
可以先得到官方例子中的125ns得到对应的分频比:
死区时间 | 单个分频比 | 十六进制 |
---|---|---|
0~15875 | 0~127 | 0~0x7F |
16000~31750 | 128~254 | 0x80~0xFE |
32000~63000 | 256~504 | 0x100~0x1F8 |
64000~126000 | 512~1008 | 0x1FF~0x3F0 |
对此,就可以根据这个分频比就可以分出几个层级,并套用上方给出的几个公式。这里需要注意的是,这个DT计算出DTC的值,需要将其拼接在一起。
根据:
1
秒
120
M
H
z
=
8
n
s
\frac{1秒}{120MHz} = 8ns
120MHz1秒=8ns
8ns的分片可以得到:
单个分频比 | 死区区间 |
---|---|
0~127 | 0~1000ns |
128~254 | 1024~2032ns |
256~504 | 2048~4032ns |
512~1008 | 4096~8064ns |
而计算方式反推示例:
D
T
G
[
区
间
]
=
死
区
时
间
(
比
例
系
数
∗
单
个
分
频
比
)
−
固
定
比
例
DTG[区间]=\frac{死区时间}{(比例系数*单个分频比)}-固定比例
DTG[区间]=(比例系数∗单个分频比)死区时间−固定比例
其中,区间为上述文档中的数据区间,比例系数则为当前的区间比例(1,2,8,16),固定比例为64或者32。
0x30 伪代码
最后自然贴上一段伪代码,虽然加入了较为友好的注释,但是如果不仔细阅读前文估计还是会看的一头雾水…… 而且代码仅仅是伪代码,也就是表明了需要的变量以及一部分必要的流程,但是运行还需要一定的改进。仅供参考!!!!
void TIMER_Init(u16 Period,u16 dead_time,float PWM_Val)
u16 PerTime,PerPsc,DeadTimeValue; /* 核心变量,分别是定时器累加值和分频系数以及死区时间 */
float PreTemp; /* 关于分频系数计算,因为精度问题,所以需要浮点运算 */
float DeadTimeGen = 1000000000 / SystemClock;//时钟频率值 /* 晶振周期,一般为固定值 */
float death_time; /* 核心变量,单个分频比 */
death_time = dead_time / DeadTimeGen; /* 单个分频比基数 */
if (death_time > 0 && death_time <= 127) /* 1倍DTG */ /* 第一个区间 */
{
DeadTimeValue = (u16)death_time; /* 按照第一个区间定义而得 */
}
else if (death_time >128 && death_time <= 254)/* 2倍DTG */ /* 第二个区间 */
{ /* */
DeadTimeValue = 0x80 | (u16)(dead_time /(DeadTimeGen * 2) -64); /* 按照第二个区间定义公式而得 */
} /* */
else if (death_time > 256 && death_time <=504)/* 8倍DTG */ /* 第三个区间 */
{ /* */
DeadTimeValue = 0xC0 | (u16)(dead_time / (DeadTimeGen * 8) - 32); /* 按照第三个区间定义公式而得 */
} /* */
else if (death_time > 512 && death_time <= 1008)/* 16倍DTG */ /* 第四个区间 */
{ /* */
DeadTimeValue = 0xE0 | (u16)(dead_time / (DeadTimeGen * 16) - 32); /* 按照第四个区间定义公式而得 */
}
else
{
printf("OverFlow DeadTime!!,Please check your param,Error Value:%d",dead_time); /* 因为这样一定会出现没有包括的地区,所以需要提醒别人,查询配置表 */
}
PreTemp = 1000000 / Period; /* 计算出当前的周期,精度0.0 */
/* 分频系数 */
PreTemp = (SystemCoreClock / 1000000 * PreTemp / 999) - 1; /* 根据上面计算分频系数的方式计算出当前的分频系数 */
PerPsc = (u16)PreTemp; /* 人工去除浮点小数 */
/* 定时器累加值 */ /* */
PerTime = 999; /* 固定的定时器累加值 */
/* 占空比--输入例如21.5这样的数字 */ /* */
PWM_Val = (PWM_Val * 10); /* 占空比取千分之一0.1% */
0x40 更多
本文首发自 记:关于输出任意占空比与频率的一种方式——基于STM32单片机,更多文章可进入我的博客详查。