1. 输出固定的 PWM
时钟树等基础功能配置,这里不做说明,请自行研究。
找到 HRTIM 定时器,配置使能 CH1A 输出:

设置 CH1A 输出频率 20KHz 占空比百分之 50% 的 PWM 方波,我们采用 Compare 1 的功能,来翻转 CH1A 引脚从而实现 PWM 的控制输出,其原理如下图所示:

配置 TimerA 定时器,如下:

- 选择 Advanced (using HAL_Waveform methods) 。
- 我的芯片使用的是 STM32G474 最大主频是 170MHz,这里选择 Prescalor 使用 170MHz 的基准时钟。
- 设置 PWM 输出的频率为 20KHz,即 P e r i o d = 170 M H z 20 K H z = 8500 Period = \frac{170MHz}{20KHz} = 8500 Period=20KHz170MHz=8500
- 设置 PWM 的占空比为百分之五十,则为 8500 ÷ 2 = 4250 8500 \div 2 = 4250 8500÷2=4250
我们需要在 CubeMx 里面设置当每次 Counter 的值与 Compare 1 相同时就触发 TA1 信号翻转:

接着在配置 TA1 输出端口:

- 配置输出信号来自 TA1 (Timer A);
- 输出的有效电平为高电平(Ouput is ative HIGH)
- 当 Compare1 事件触发时,输出为高电平;
- 当 Period 事件触发时(定时器计数器达到 Period 的值),触发输出为低电平;
设置完成后生成代码即可。
在程序的应用层,需要通过软件手动开启 PWM 的输出:
c++
#include "stm32g474xx.h"
#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_hrtim.h"
#include "FocService.hpp"
extern HRTIM_HandleTypeDef hhrtim1;
bool FocService::init()
{
HAL_HRTIM_SimpleOCStart(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A, HRTIM_OUTPUT_TA1);
return true;
}
void FocService::yield()
{
}
我通过 HAL_HRTIM_SimpleOCStart 开启了 PWM 的输出。

2. SPWM
2.1 SPWM 介绍
SPWM 的原理是基于载波和正选波的交点,当载波大于正选波时输出高电平,当载波小于正选波的时候输出低电平。
更详细的介绍可以参考 SPWM基本原理详解(图文并茂+公式推导+C程序实现)

我们定义载波(上图的三角形)为 V s V_s Vs,其电压其幅值为 V s m V_{sm} Vsm,频率为 f c f_c fc 的三角波;
再定义正弦波的电压表达式为:
V a m = V m ∗ s i n ( ω t ) V_{am} = V_m*sin(\omega t) Vam=Vm∗sin(ωt)
定义载波(三角波)和调制波(正弦波)的幅值之比为: m m = V m V s m m_m = \frac{V_m}{V_{sm}} mm=VsmVm,称为调制深度。
定义载波和生成出来的 SPWM 的频率之比为载波比,即: m f = f c f m_f = \frac{f_c}{f} mf=ffc, f f f 为 PWM 的频率;
2.2 Matlab 仿真 SPWM
模型设计如下:

使用频率为 50Hz 的调制波:

设置模型的步长为 0.0001s,查看结果到如下的 SPWM 波形:

2.3 HRTIM 输出 SPWM
通过 Matlab 直观得观察到 SPWM 的输出波形,那么如何在 STM32 的 HRTIM 中设计这个波形呢?
这里采用面积等效法来实现,设计思路如下:
详细可以参考 【嵌入式】STM32输出SPWM实现逆变
-
SPWM逆变的精髓就是【冲量等效原理】------将一周期正弦波等分为 N 段,每段的长度为 T,可以算出任意一段的积分面积 S (即正弦波在 T 时间段内与横轴围成的面积),如果在每一段 T 时间段内,SPWM 波与横轴围成的面积都等于 T 内正弦波的定积分 S,那么在后级对 SPWM 进行滤波后即可得到正弦波。
-
确定载波频率 f b f_b fb:载波即下图中红线标出的波形,占空比的调整都在每个载波周期内进行。对于单片机,我们只需要控制在某个载波周期内,高低电平所占的时间即可。一般来讲,载波频率越高,滤出的正弦波越细腻,能调出的正弦波频率上限也越高。

-
确定正弦波频率 f s f_s fs
-
得到正弦波一周波分段 N:
N = f b f s N=\frac{f_b}{f_s} N=fsfb ,由此可见,正弦波频率越低,N 越大,基波频率越低,N 越小。 -
分段计算正弦波积分 S,得出 PWM 占空比序列
这里的分段计算,就是在整个正弦波一周期内,依次计算。计算出每段S后,另 PWM 与横轴围成的面积等于 S,即可得出该段的 PWM 占空比。一周期计算完成后,即得出了 PWM 的占空比序列。
掌握以上原理后,需要先通过软件计算生成 spwm 序列,根据以上原理,编写 matlab 程序如下:
matlab
%-----硬件设置-------------------------------------
timerCLK = 170000000; %timer时钟,170MHz/1分频
Period = 8500; %载波频率设计为 20KHz
baseRate = timerCLK / (Period + 1); %载波频率 20KHz
%-----正弦波设置------------------------------------
F = 50;%频率
T = 1/F;%周期
ampScale = 0.85;%调幅
N = round(baseRate / F);%一周波分多少段
stepTime = 1/F/N; %积分步进值(每一段的长度)
%-----计算------------------------------------------
CCR = (1:1:N)';
for i=1:1:N
area = double(quad(@(x)sin(2*pi*F*x),(i-1)*stepTime,i*stepTime));
CCR(i) = round((ampScale*area*Period / stepTime + Period) / 2);
end
基于中断实现
CubeMX 配置输出:
- Time Base 设置
设置 PWM 的输出频率为 20KHz 也就是载波的频率,采用 up-counting 方式:

- 配置 Timing Unit
- Preload Enable:配置为 Enable,当我们更新 Compare1 的值不会直接写入 Compare1 的寄存器,而且写入 Compare1 的 Preload 寄存器,在 Timer 中有多个寄存器是存在 Preload 寄存器,下图中有两个框的寄存器都是带 Preload 功能的:

- Reset Update:在 Timer reset 或者触发 roll-over 的时候更新寄存器的值,Timer Reset 的来自 crossbars 上面的 Reset 事件可以触发,roll-over 当计数器的值重新变为 0 的时候会触发这个事件;因此,可以实现在计数器变为零的时候,将Compare1 的 Preload 更新到 Compare1。
- Interrupt Requests Sources Selection:设置 Timer update interrupt enable,在定时器值更新过后触发中断。我们就可以将新的 Compare1 的值填到 Preload 里面;
- Preload Enable:配置为 Enable,当我们更新 Compare1 的值不会直接写入 Compare1 的寄存器,而且写入 Compare1 的 Preload 寄存器,在 Timer 中有多个寄存器是存在 Preload 寄存器,下图中有两个框的寄存器都是带 Preload 功能的:
- Compare1 的配置

- 输出端口配置

- 开启 Timer A 的中断

第 3、4点的配置正常的 PWM 输出一致。
程序实现:
c
#include "spwm.h"
#include "stm32g474xx.h"
#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_hrtim.h"
const uint16_t g_tFb20KHz_Fs50Hz[400] = {
4278, 4335, 4392, 4449, 4505, 4562, 4618, 4675, 4731, 4787,
4843, 4899, 4955, 5010, 5066, 5121, 5176, 5231, 5285, 5339,
5393, 5447,5500, 5553, 5606, 5659, 5711,5762, 5814, 5865,
5915, 5965, 6015, 6064, 6113, 6162, 6210, 6257, 6304, 6350,
6396, 6442, 6486, 6531, 6574, 6618, 6660, 6702, 6744, 6784,
6824, 6864, 6903, 6941, 6978, 7015, 7051, 7087, 7122, 7156,
7189, 7222, 7254, 7285, 7315, 7345, 7374, 7402, 7429, 7456,
7482, 7506, 7531, 7554, 7577, 7598, 7619, 7639, 7658, 7677,
7694, 7711, 7727, 7742, 7756, 7769, 7782, 7793, 7804, 7813,
7822, 7830, 7837, 7844, 7849, 7853, 7857, 7860, 7861, 7862,
7862, 7861, 7860, 7857, 7853, 7849, 7844, 7837, 7830, 7822,
7813, 7804, 7793, 7782, 7769, 7756, 7742, 7727, 7711, 7694,
7677, 7658, 7639, 7619, 7598, 7577, 7554, 7531, 7506, 7482,
7456, 7429, 7402, 7374, 7345, 7315, 7285, 7254, 7222, 7189,
7156, 7122, 7087, 7051, 7015, 6978, 6941, 6903, 6864, 6824,
6784, 6744, 6702, 6660, 6618, 6574, 6531, 6486, 6442, 6396,
6350, 6304, 6257, 6210, 6162, 6113, 6064, 6015, 5965, 5915,
5865, 5814, 5762, 5711, 5659, 5606, 5553, 5500, 5447, 5393,
5339, 5285, 5231, 5176, 5121, 5066, 5010, 4955, 4899, 4843,
4787, 4731, 4675, 4618, 4562, 4505, 4449, 4392, 4335, 4278,
4222, 4165, 4108, 4051, 3995, 3938, 3882, 3825, 3769, 3713,
3657,3601,3545,3490,3434,3379,3324,3269,3215,3161,
3107, 3053, 3000, 2947, 2894, 2841, 2789, 2738, 2686, 2635,
2585, 2535, 2485, 2436, 2387, 2338, 2290, 2243, 2196, 2150,
2104, 2058, 2014, 1969, 1926, 1882, 1840, 1798, 1756, 1716,
1676, 1636, 1597, 1559, 1522, 1485, 1449, 1413, 1378, 1344,
1311, 1278, 1246, 1215, 1185, 1155, 1126, 1098, 1071, 1044,
1018, 994, 969, 946, 923, 902, 881, 861, 842, 823,
806, 789, 773, 758, 744, 731, 718, 707, 696, 687,
678, 670, 663, 656, 651, 647, 643, 640, 639, 638,
638, 639, 640, 643, 647, 651, 656, 663, 670, 678,
687, 696, 707, 718, 731, 744, 758, 773, 789, 806,
823, 842, 861, 881, 902, 923, 946, 969, 994, 1018,
1044, 1071, 1098, 1126, 1155, 1185, 1215, 1246, 1278, 1311,
1344, 1378, 1413, 1449, 1485, 1522, 1559, 1597, 1636, 1676,
1716, 1756, 1798, 1840, 1882, 1926, 1969, 2014, 2058, 2104,
2150, 2196, 2243, 2290, 2338, 2387, 2436, 2485, 2535, 2585,
2635, 2686, 2738, 2789, 2841, 2894, 2947, 3000, 3053, 3107,
3161, 3215, 3269, 3324, 3379, 3434, 3490, 3545, 3601, 3657,
3713, 3769, 3825, 3882, 3938, 3995, 4051, 4108, 4165, 4222
};
extern HRTIM_HandleTypeDef hhrtim1;
static uint16_t s_index = 0;
// 更新 Compare1 的值
static void spwm_compare1_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_tFb20KHz_Fs50Hz[s_index]);
s_index = (s_index + 1) % (sizeof(g_tFb20KHz_Fs50Hz)/sizeof(uint16_t));
}
bool spwm_init(void)
{
// 让 SPWM 初始的输出为 g_tFb20KHz_Fs50Hz 中的第一个
spwm_compare1_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A);
HAL_HRTIM_WaveformCountStart_IT(&hhrtim1, HRTIM_TIMERID_TIMER_A);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA1);
return true;
}
void spwm_yield(void)
{
}
// 我们在定时器更新事件回调里面更新 Compare1 的值
void HAL_HRTIM_RegistersUpdateCallback(HRTIM_HandleTypeDef *hhrtim, uint32_t TimerIdx)
{
if (TimerIdx == HRTIM_TIMERINDEX_TIMER_A) {
spwm_compare1_update(hhrtim, HRTIM_TIMERINDEX_TIMER_A);
}
}
最后的输出波形的展示:

基于 DMA 实现
上面基于中断的实现,在我们想实现更高频率的载波时,可能会存在一定的局限性,需要消耗 CPU 时间、以及触发中断等一系列操作。
那么一个优化的方向就是可以考虑使用 DMA 来实现:
- 开启 DMA,设置传输方向为内存到外设,设置 Half word 和 Circular 循环传输:

- 关闭 Timer A 的中断,不再需要:

- 配置 Timer A 的 Timing Unit

- 开启一个 DMA 传输的触发源,配置为 Timer Compare1,一旦 Compare1 信号触发就让 DMA 帮我们将新的 Compare1 的值传入到 Preload 寄存器里;
- DMA Src Address:指定为内存地址,也就是我们的
(uint32_t)(&(g_tFb20KHz_Fs50Hz[0])); - DMA Dst Address:指定为定时器的 Compare1,开启了 Preload Enable 后,实际上是传入到对应的 Preload 寄存器,即
(uint32_t)(&(hhrtim1.Instance->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_A].CMP1xR)); - DMA Size:注意把右边的 No check 打开,然后就可以通过 sizeof 动态指定要传输的大小,即
(uint32_t)(sizeof(g_tFb20KHz_Fs50Hz)/sizeof(uint16_t))。
然后对我们的中断程序稍加修改:
c
#include "spwm.h"
#include "stm32g474xx.h"
#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_hrtim.h"
const uint16_t g_tFb20KHz_Fs50Hz[400] = {
4278, 4335, 4392, 4449, 4505, 4562, 4618, 4675, 4731, 4787,
4843, 4899, 4955, 5010, 5066, 5121, 5176, 5231, 5285, 5339,
5393, 5447,5500, 5553, 5606, 5659, 5711,5762, 5814, 5865,
5915, 5965, 6015, 6064, 6113, 6162, 6210, 6257, 6304, 6350,
6396, 6442, 6486, 6531, 6574, 6618, 6660, 6702, 6744, 6784,
6824, 6864, 6903, 6941, 6978, 7015, 7051, 7087, 7122, 7156,
7189, 7222, 7254, 7285, 7315, 7345, 7374, 7402, 7429, 7456,
7482, 7506, 7531, 7554, 7577, 7598, 7619, 7639, 7658, 7677,
7694, 7711, 7727, 7742, 7756, 7769, 7782, 7793, 7804, 7813,
7822, 7830, 7837, 7844, 7849, 7853, 7857, 7860, 7861, 7862,
7862, 7861, 7860, 7857, 7853, 7849, 7844, 7837, 7830, 7822,
7813, 7804, 7793, 7782, 7769, 7756, 7742, 7727, 7711, 7694,
7677, 7658, 7639, 7619, 7598, 7577, 7554, 7531, 7506, 7482,
7456, 7429, 7402, 7374, 7345, 7315, 7285, 7254, 7222, 7189,
7156, 7122, 7087, 7051, 7015, 6978, 6941, 6903, 6864, 6824,
6784, 6744, 6702, 6660, 6618, 6574, 6531, 6486, 6442, 6396,
6350, 6304, 6257, 6210, 6162, 6113, 6064, 6015, 5965, 5915,
5865, 5814, 5762, 5711, 5659, 5606, 5553, 5500, 5447, 5393,
5339, 5285, 5231, 5176, 5121, 5066, 5010, 4955, 4899, 4843,
4787, 4731, 4675, 4618, 4562, 4505, 4449, 4392, 4335, 4278,
4222, 4165, 4108, 4051, 3995, 3938, 3882, 3825, 3769, 3713,
3657,3601,3545,3490,3434,3379,3324,3269,3215,3161,
3107, 3053, 3000, 2947, 2894, 2841, 2789, 2738, 2686, 2635,
2585, 2535, 2485, 2436, 2387, 2338, 2290, 2243, 2196, 2150,
2104, 2058, 2014, 1969, 1926, 1882, 1840, 1798, 1756, 1716,
1676, 1636, 1597, 1559, 1522, 1485, 1449, 1413, 1378, 1344,
1311, 1278, 1246, 1215, 1185, 1155, 1126, 1098, 1071, 1044,
1018, 994, 969, 946, 923, 902, 881, 861, 842, 823,
806, 789, 773, 758, 744, 731, 718, 707, 696, 687,
678, 670, 663, 656, 651, 647, 643, 640, 639, 638,
638, 639, 640, 643, 647, 651, 656, 663, 670, 678,
687, 696, 707, 718, 731, 744, 758, 773, 789, 806,
823, 842, 861, 881, 902, 923, 946, 969, 994, 1018,
1044, 1071, 1098, 1126, 1155, 1185, 1215, 1246, 1278, 1311,
1344, 1378, 1413, 1449, 1485, 1522, 1559, 1597, 1636, 1676,
1716, 1756, 1798, 1840, 1882, 1926, 1969, 2014, 2058, 2104,
2150, 2196, 2243, 2290, 2338, 2387, 2436, 2485, 2535, 2585,
2635, 2686, 2738, 2789, 2841, 2894, 2947, 3000, 3053, 3107,
3161, 3215, 3269, 3324, 3379, 3434, 3490, 3545, 3601, 3657,
3713, 3769, 3825, 3882, 3938, 3995, 4051, 4108, 4165, 4222
};
extern HRTIM_HandleTypeDef hhrtim1;
static uint16_t s_index = 0;
static void spwm_compare1_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_tFb20KHz_Fs50Hz[s_index]);
s_index = (s_index + 1) % (sizeof(g_tFb20KHz_Fs50Hz)/sizeof(uint16_t));
}
bool spwm_init(void)
{
spwm_compare1_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A);
HAL_HRTIM_WaveformCountStart_DMA(&hhrtim1, HRTIM_TIMERID_TIMER_A);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA1);
return true;
}
void spwm_yield(void)
{
}
烧录后,同样实现了我们想要的效果:

3. 对 SPWM 增加互补输出
SPWM 一个常见的应用就是控制电机、逆变器的 H 敲。
例如:G1 和 G4 导通时,输出电压为 +Udc,G2 和 G3 导通时, 输出电压为 -Udc

配合上 SPWM 就可以实现,如下图的波形:

再加上电容电感进行滤波,我们就可以还原出被调制的正弦波,这也就是我们前面通过 SPWM 调制正弦函数的意义!!!
因此,我们需要在 G1/G4 闭合时断开 G2/G3,反之依然。要实现这个功能,可以借助互补 HRTIM 的一些机制来帮助我们实现这个互补输出。
下面在 SPWM 的基础上为其添加一个带互补输出的通道 TA2:
- 将 Timer A 改为双通道输出

- 设置 TA2 的输出,其刚好与 TA1 相反:

最后修改应用层程序:
c++
#include "spwm.h"
#include "stm32g474xx.h"
#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_hrtim.h"
const uint16_t g_tFb20KHz_Fs50Hz[400] = {
4278, 4335, 4392, 4449, 4505, 4562, 4618, 4675, 4731, 4787,
4843, 4899, 4955, 5010, 5066, 5121, 5176, 5231, 5285, 5339,
5393, 5447,5500, 5553, 5606, 5659, 5711,5762, 5814, 5865,
5915, 5965, 6015, 6064, 6113, 6162, 6210, 6257, 6304, 6350,
6396, 6442, 6486, 6531, 6574, 6618, 6660, 6702, 6744, 6784,
6824, 6864, 6903, 6941, 6978, 7015, 7051, 7087, 7122, 7156,
7189, 7222, 7254, 7285, 7315, 7345, 7374, 7402, 7429, 7456,
7482, 7506, 7531, 7554, 7577, 7598, 7619, 7639, 7658, 7677,
7694, 7711, 7727, 7742, 7756, 7769, 7782, 7793, 7804, 7813,
7822, 7830, 7837, 7844, 7849, 7853, 7857, 7860, 7861, 7862,
7862, 7861, 7860, 7857, 7853, 7849, 7844, 7837, 7830, 7822,
7813, 7804, 7793, 7782, 7769, 7756, 7742, 7727, 7711, 7694,
7677, 7658, 7639, 7619, 7598, 7577, 7554, 7531, 7506, 7482,
7456, 7429, 7402, 7374, 7345, 7315, 7285, 7254, 7222, 7189,
7156, 7122, 7087, 7051, 7015, 6978, 6941, 6903, 6864, 6824,
6784, 6744, 6702, 6660, 6618, 6574, 6531, 6486, 6442, 6396,
6350, 6304, 6257, 6210, 6162, 6113, 6064, 6015, 5965, 5915,
5865, 5814, 5762, 5711, 5659, 5606, 5553, 5500, 5447, 5393,
5339, 5285, 5231, 5176, 5121, 5066, 5010, 4955, 4899, 4843,
4787, 4731, 4675, 4618, 4562, 4505, 4449, 4392, 4335, 4278,
4222, 4165, 4108, 4051, 3995, 3938, 3882, 3825, 3769, 3713,
3657,3601,3545,3490,3434,3379,3324,3269,3215,3161,
3107, 3053, 3000, 2947, 2894, 2841, 2789, 2738, 2686, 2635,
2585, 2535, 2485, 2436, 2387, 2338, 2290, 2243, 2196, 2150,
2104, 2058, 2014, 1969, 1926, 1882, 1840, 1798, 1756, 1716,
1676, 1636, 1597, 1559, 1522, 1485, 1449, 1413, 1378, 1344,
1311, 1278, 1246, 1215, 1185, 1155, 1126, 1098, 1071, 1044,
1018, 994, 969, 946, 923, 902, 881, 861, 842, 823,
806, 789, 773, 758, 744, 731, 718, 707, 696, 687,
678, 670, 663, 656, 651, 647, 643, 640, 639, 638,
638, 639, 640, 643, 647, 651, 656, 663, 670, 678,
687, 696, 707, 718, 731, 744, 758, 773, 789, 806,
823, 842, 861, 881, 902, 923, 946, 969, 994, 1018,
1044, 1071, 1098, 1126, 1155, 1185, 1215, 1246, 1278, 1311,
1344, 1378, 1413, 1449, 1485, 1522, 1559, 1597, 1636, 1676,
1716, 1756, 1798, 1840, 1882, 1926, 1969, 2014, 2058, 2104,
2150, 2196, 2243, 2290, 2338, 2387, 2436, 2485, 2535, 2585,
2635, 2686, 2738, 2789, 2841, 2894, 2947, 3000, 3053, 3107,
3161, 3215, 3269, 3324, 3379, 3434, 3490, 3545, 3601, 3657,
3713, 3769, 3825, 3882, 3938, 3995, 4051, 4108, 4165, 4222
};
extern HRTIM_HandleTypeDef hhrtim1;
static uint16_t s_index = 0;
static void spwm_compare1_update(HRTIM_HandleTypeDef *hhrtim, uint16_t TimerIdx)
{
__HAL_HRTIM_SETCOMPARE(hhrtim, TimerIdx, HRTIM_COMPAREUNIT_1, g_tFb20KHz_Fs50Hz[s_index]);
s_index = (s_index + 1) % (sizeof(g_tFb20KHz_Fs50Hz)/sizeof(uint16_t));
}
bool spwm_init(void)
{
spwm_compare1_update(&hhrtim1, HRTIM_TIMERINDEX_TIMER_A);
HAL_HRTIM_WaveformCountStart_DMA(&hhrtim1, HRTIM_TIMERID_TIMER_A);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA2);
return true;
}
void spwm_yield(void)
{
}
在原来的基础上打开 HAL_HRTIM_WaveformOutputStart(&hhrtim1, HRTIM_OUTPUT_TA2); 的输出。
输出波形如下:

4. 为互补 SPWM 增加死区
还是回到前面的 H 桥,MOS 管在导通时是需要时间的,即可能存在 G1 还没有完全关断的情况下 G2 被正处于打开中,这时就可能造成 G1 和 G2 同时被打开的意外情况,这时候会导致电源直接短路,从而烧穿我们的两个 MOS 管,为了避免这种情况,最后是在 G1 断开和 G2 打开直接插入一小段间隙,确保 G1 完全断开后再打开 G2,反之同理,这个小段间隙就可以称为死区。

HRTIM 提供了一个很简便的死区插入方式:

The Dead time insertion is enabled by setting DTEN bit in HRTIM_OUTxR register. The complementary signals are built based on the reference waveform defined for output 1, using HRTIM_SETx1R and HRTIM_RSTx1R registers: HRTIM_SETx2R and HRTIM_RSTx2R registers are not significant when DTEN bit is set.
我们只需要设置 HRTIM_OUTxR 寄存器里面的 DTEN 使能,互补输出信号就会参考 CH1A 的输出,这是只使用 HRTIM_SETx1R 和 HRTIM_RSTx1R 两个寄存器来控制两个通道的输出。CH2A 的 HRTIM_SETx2R 和 HRTIM_RSTx2R 寄存器直接被忽略掉。啥意思呢?就是说,HRTIM 会在 CHA1 要输出的高电平时,自动将 CHA2 拉低,同时在 CHA1 的电平切换的前后自动插入两个间隙时间,这两个间隙时间就作为死区。
因此,我们借助这个在 CHA1 前后插入一段间隙的特性(甚至插入一个负间隙),我们可以制造出如下这些波形:

死区的时长配置公式,如下:
t D T x = + / − D T x [ 8 : 0 ] × t D T G t_{DTx} = +/- DTx[8:0] \times t_{DTG} tDTx=+/−DTx[8:0]×tDTG
其中,
t D T G = ( 2 ( D T P R S C [ 2 : 0 ] ) ) × ( t H R T I M / 8 ) . t_{DTG} = (2^{(DTPRSC[2:0])}) \times (t_{HRTIM} / 8). tDTG=(2(DTPRSC[2:0]))×(tHRTIM/8).
啥意思呢?下面这张图展示了死区的时长配置:

- DTPRSC[2:0]:控制死区的预分频系数,也就是最小的时间刻度;
- DTx[8:0]:控制总的刻度数;
例如,我的假设目前通过 DTPRSC[2:0] 配合 f H R T I M f_{HRTIM} fHRTIM,组合后设置的最小时间刻度是 1ns,那么我设置 DTx[8:0] = 2,那么也就是说我设置我的死区为 2ns。
我目前的 PWM 频率是 20KHz,也就是 1 s 20 × 10 3 = 50 u s \frac{1s}{20 \times 10^3} = 50us 20×1031s=50us。如果我设置上下死区都为 1us,应该也能很好得观察到波形。我们的基准 f H R T I M f_{HRTIM} fHRTIM 是 170MHz,计算一下 1 u s 5.89 n s = 169.779.... ≈ 170 \frac{1us}{5.89ns} = 169.779.... \approx 170 5.89ns1us=169.779....≈170,因此我们设置 DTx[8:0] 为 170。
-
开启插入死区

-
配置死区

我这里都指定了 Positive deadtime,即插入一个正值的死区,此外设置了死区为 Lock 确保在中途中无法被写入。
应用程序不需要改动,生成代码即可,运行后效果如下,在两个波之间刚好插入了 1us 的死区:

5. 小结
本篇完成了 HRTIM 常用功能的探索,这些功能基本能够完成我们很多应用了。