0.工具与参考源
(1) STM32CubeMX 13.0 和 STM32CubeIDE 1.17.0
可以去官网下载:https://www.st.com/,没有账号就注册一个,很方便
安装路径不要有中文!
(2) Java,选的最新的JDK23.0.2 windows
Java Downloads | Oracle下载地址:Java Downloads | Oracle
(3) Deepseek
本文是用deepseek问了以下问题后整理的,感兴趣的也可以去搜一下相同问题
“使用STM32G474RET6控制器,输出高分辨率pwm波的示例有吗?”
“HRTIM中四个比较值怎么配置”
“HRTIM的寄存器说明”
(4) b站视频资源 up主 cckong998的视频
STM32G474-HRTIM-四对互补移相_x264_哔哩哔哩_bilibili
(5) CSDN博客
【STM32H7教程】第63章 STM32H7的高分辨率定时器HRTIM基础知识和HAL库API-CSDN博客
因为H7也有HRTIM,所以就看了一下,讲的很详细
(6) 官方手册
STM32G474RET6的芯片手册
https://www.st.com.cn/resource/en/datasheet/stm32g474re.pdf
STM32G474RET6的应用手册
https://www.st.com/resource/en/reference_manual/dm00355726.pdf
1.建立工程
(1) 芯片选型
我之前没用过Cube,所以直接新建工程,从MCU开始,第一次会下载一些文件
(2) 时钟配置
配置RCC-使用高速时钟,选晶振 - 右侧能看到 PF0和PF1亮了,分别是RCC_OSC_IN 和 OUT
高速晶振为12MHz,使用锁相环 PLL来配置,PLLM /3*85/2 得到最大时钟频率170MHz
(直接在HCLK那里输入目标频率 ENTER 可以自动计算配置好各部分的分频系数)
(3) SYS模块配置
供烧写与在线调试
(4) HRTIM配置
PWM波来驱动全桥逆变器,需要两对互补输出信号,用TimerC 和 TimerD
Master Timer
设置32倍频,Master Timer 的 Period 设置为802,则周期频率为6783042Hz
Repetition Counter 设置为49
这里其实对于如何分频有一些小的注意事项
100MHz系统时钟--32倍频--472Period = 6.779661Hz;
160MHz系统时钟--32倍频--755Period = 6.781457Hz;
170MHz系统时钟--32倍频--802Period = 6.783042Hz;
本文选170MHz这个方案,一是频差可以接受,二是主频高计算也会快一点
但是如果频差为第一要素的话,第一种方案的频差是最小的
Preload 使能,Repetition update 使能,中断请求设置一个,为Repetition溢出使能
前面repetition为49,则Master Timer 每达到50次Period 则触发一次中断
中断函数中可以调节PWM波的脉宽,死区时间,移相角等等
然后配置 TimerC 和 TimerD
32倍频,但Period = 10000 因为我不需要 Timer C D来达到 他们的Period来触发事件或者中断
所以Repetition 也无所谓了,默认是0
Preload Enable,Repetition Update Disable(反正也达不到Period,默认是关)
插入死区
更新触发源 为 Master Timer 更新
置零触发源 为 Master Timer 达到Period 事件(这里的Period是Master 的Period 即 802)
使能TimerC的比较器 C_CMP1=100,C_CMP2=500
死区时间这里的分辨率略低一些,170MHz*8=1360MHz,也就是0.7353ns的分辨率
上升沿和下降沿选择14,得到约10.3ns的死区时间
PS.如果想要更高分辨率的死区的话,可以不配置插入死区,通过增加CMP3 4 来设置软件死区
配置在OUT1在计数器达到C_CMP1时置位,C_CMP2时清零
在配置完OUT1后,OUT2则自动生成,不用配置
TimerD的配置与C完全相同,不再赘述,只有D_CMP1 = 101, D_CMP2 = 501
打开中断 NVIC
(5) 比较器配置
使用COMP1比较器,PA1为输入引脚,为正输入端,负输入端选了1/2Vref
使用边沿触发中断,上升沿和下降沿都能触发中断
滞环电压为20mV
(6) NVIC配置
我需要通过COMP1触发比较器中断,在比较器中断函数中更改移相值
为了保证稳定的PWM波形,配置COMP1中断的优先级[1] 低于 HRTIM中断优先级[0]
(7) 生成代码
我还得登录一下,下载一些文件,不过也挺快的
(8) 注意事项
HRTIM中的CMP值是有最小值和最大值的
对于我们的32分频来说,最小值为0x0060,最大值为0xFFDF
实际配置过程中也能看到只能是96到65503,这也是为什么我们的C_CMP1要设置为100
假如C_CMP1设置为20时,即便counter达到了20,也不会触发事件,比较值无效
2.代码
生成代码后直接open project 或者 在IDE打开刚创建的代码(界面色调比Keil好看多了hhh)
(1) HRTIM部分
打开Core-Src-main.c
在USER CODE BEGIN 和 END 中间写代码
记得要把自己所有的代码写在USER CODE BEGIN 和END 之间
这样CubeMX生成的代码不会覆盖写的代码段
我定义了一个全局变量 angle
// This is in the main.c
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint32_t angle = 0;
/* USER CODE END PV */
记得在 main.h 中声明一下
// This is in the main.h
/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN Private defines */
extern uint32_t angle;
/* USER CODE END Private defines */
回到 main.c 中,在main()函数中写
打开Master Timer 和 Timer C 和 Timer D
再打开C 和 D 的两组互补输出通道
/* USER CODE BEGIN 2 */
// HRTIM
HAL_HRTIM_WaveformCounterStart_IT(&hhrtim1,HRTIM_TIMERID_MASTER);
HAL_HRTIM_WaveformCounterStart(&hhrtim1,HRTIM_TIMERID_TIMER_C|HRTIM_TIMERID_TIMER_D);
HAL_HRTIM_WaveformOutputStart(&hhrtim1,HRTIM_OUTPUT_TC1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1,HRTIM_OUTPUT_TC2);
HAL_HRTIM_WaveformOutputStart(&hhrtim1,HRTIM_OUTPUT_TD1);
HAL_HRTIM_WaveformOutputStart(&hhrtim1,HRTIM_OUTPUT_TD2);
/* USER CODE END 2 */
选中HAL_HRTIM_WaveformCounterStart_IT右键打开声明
可以看到这个函数在 stm32_hal_legacy.h 中
HAL_HRTIM_WaveformCounterStart_IT 打开计数器的同时,也打开中断
之前配置了在Master Timer的competition 寄存器溢出时会触发中断
HAL_HRTIM_WaveformCounterStart 只打开计数器 让Timer C 和 Timer D 开始计数
/** @defgroup HAL_HRTIM_Aliased_Functions HAL HRTIM Aliased Functions maintained for legacy purpose
* @{
*/
#if defined (STM32H7) || defined (STM32G4) || defined (STM32F3)
#define HAL_HRTIM_WaveformCounterStart_IT HAL_HRTIM_WaveformCountStart_IT
#define HAL_HRTIM_WaveformCounterStart_DMA HAL_HRTIM_WaveformCountStart_DMA
#define HAL_HRTIM_WaveformCounterStart HAL_HRTIM_WaveformCountStart
#define HAL_HRTIM_WaveformCounterStop_IT HAL_HRTIM_WaveformCountStop_IT
#define HAL_HRTIM_WaveformCounterStop_DMA HAL_HRTIM_WaveformCountStop_DMA
#define HAL_HRTIM_WaveformCounterStop HAL_HRTIM_WaveformCountStop
#endif
/**
* @}
*/
同样看到 函数HAL_HRTIM_WaveformOutputStart 在 stm32g4xx_hal_hrtim.c中
用来使能两组互补输出
/**
* @brief Enable the generation of the waveform signal on the designated output(s)
* Outputs can be combined (ORed) to allow for simultaneous output enabling.
* @param hhrtim pointer to HAL HRTIM handle
* @param OutputsToStart Timer output(s) to enable
* This parameter can be any combination of the following values:
* @arg HRTIM_OUTPUT_TA1: Timer A - Output 1
* @arg HRTIM_OUTPUT_TA2: Timer A - Output 2
* @arg HRTIM_OUTPUT_TB1: Timer B - Output 1
* @arg HRTIM_OUTPUT_TB2: Timer B - Output 2
* @arg HRTIM_OUTPUT_TC1: Timer C - Output 1
* @arg HRTIM_OUTPUT_TC2: Timer C - Output 2
* @arg HRTIM_OUTPUT_TD1: Timer D - Output 1
* @arg HRTIM_OUTPUT_TD2: Timer D - Output 2
* @arg HRTIM_OUTPUT_TE1: Timer E - Output 1
* @arg HRTIM_OUTPUT_TE2: Timer E - Output 2
* @arg HRTIM_OUTPUT_TF1: Timer F - Output 1
* @arg HRTIM_OUTPUT_TF2: Timer F - Output 2
* @retval HAL status
*/
HAL_StatusTypeDef HAL_HRTIM_WaveformOutputStart(HRTIM_HandleTypeDef *hhrtim,
uint32_t OutputsToStart)
{
/* Check the parameters */
assert_param(IS_HRTIM_OUTPUT(OutputsToStart));
/* Process Locked */
__HAL_LOCK(hhrtim);
hhrtim->State = HAL_HRTIM_STATE_BUSY;
/* Enable the HRTIM outputs */
hhrtim->Instance->sCommonRegs.OENR |= (OutputsToStart);
hhrtim->State = HAL_HRTIM_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(hhrtim);
return HAL_OK;
}
打开Core-Src- hrtim.c 写 HRTIM的回调函数 HAL_HRTIM_RepetitionEventCallback
因为程序中只有Master Timer 的Repetition 溢出会触发中断,所以不用判断是不是MASTER了
然后根据angle的值更改CMP1 和 CMP2
这里是直接操作寄存器的值
只改变TD的 D_CMP1 和 D_CMP2 的值
/* USER CODE BEGIN 1 */
void HAL_HRTIM_RepetitionEventCallback(HRTIM_HandleTypeDef * hhrtim , uint32_t TimerIdx)
{
/* only Master IT is enabled, so no need to judge;
* if (TimerIdx == HRTIM_TIMERID_MASTER)
{
}
*/
// my function to change the CMP
/* sTimerxRegs[0] - TimerA
* sTimerxRegs[1] - TimerB
* sTimerxRegs[2] - TimerC
* sTimerxRegs[3] - TimerD
* sTimerxRegs[4] - TimerE
* sTimerxRegs[5] - TimerF
* */
if(angle == 90){
HRTIM1->sTimerxRegs[3].CMP1xR = 200;
HRTIM1->sTimerxRegs[3].CMP2xR = 600;
}
else {
HRTIM1->sTimerxRegs[3].CMP1xR = 101;
HRTIM1->sTimerxRegs[3].CMP2xR = 501;
}
}
/* USER CODE END 1 */
sTimerxRegs 在 stm32g474xx.h 中
能看到sTimerxRegs结构体中寄存器的值,而TimerC 和 TimerD 是[2] 和 [3]
/* HRTIM Timer A to F registers definition */
typedef struct
{
__IO uint32_t TIMxCR; /*!< HRTIM Timerx control register, Address offset: 0x00 */
__IO uint32_t TIMxISR; /*!< HRTIM Timerx interrupt status register, Address offset: 0x04 */
__IO uint32_t TIMxICR; /*!< HRTIM Timerx interrupt clear register, Address offset: 0x08 */
__IO uint32_t TIMxDIER; /*!< HRTIM Timerx DMA/interrupt enable register, Address offset: 0x0C */
__IO uint32_t CNTxR; /*!< HRTIM Timerx counter register, Address offset: 0x10 */
__IO uint32_t PERxR; /*!< HRTIM Timerx period register, Address offset: 0x14 */
__IO uint32_t REPxR; /*!< HRTIM Timerx repetition register, Address offset: 0x18 */
__IO uint32_t CMP1xR; /*!< HRTIM Timerx compare 1 register, Address offset: 0x1C */
__IO uint32_t CMP1CxR; /*!< HRTIM Timerx compare 1 compound register, Address offset: 0x20 */
__IO uint32_t CMP2xR; /*!< HRTIM Timerx compare 2 register, Address offset: 0x24 */
__IO uint32_t CMP3xR; /*!< HRTIM Timerx compare 3 register, Address offset: 0x28 */
__IO uint32_t CMP4xR; /*!< HRTIM Timerx compare 4 register, Address offset: 0x2C */
__IO uint32_t CPT1xR; /*!< HRTIM Timerx capture 1 register, Address offset: 0x30 */
__IO uint32_t CPT2xR; /*!< HRTIM Timerx capture 2 register, Address offset: 0x34 */
__IO uint32_t DTxR; /*!< HRTIM Timerx dead time register, Address offset: 0x38 */
__IO uint32_t SETx1R; /*!< HRTIM Timerx output 1 set register, Address offset: 0x3C */
__IO uint32_t RSTx1R; /*!< HRTIM Timerx output 1 reset register, Address offset: 0x40 */
__IO uint32_t SETx2R; /*!< HRTIM Timerx output 2 set register, Address offset: 0x44 */
__IO uint32_t RSTx2R; /*!< HRTIM Timerx output 2 reset register, Address offset: 0x48 */
__IO uint32_t EEFxR1; /*!< HRTIM Timerx external event filtering 1 register, Address offset: 0x4C */
__IO uint32_t EEFxR2; /*!< HRTIM Timerx external event filtering 2 register, Address offset: 0x50 */
__IO uint32_t RSTxR; /*!< HRTIM Timerx Reset register, Address offset: 0x54 */
__IO uint32_t CHPxR; /*!< HRTIM Timerx Chopper register, Address offset: 0x58 */
__IO uint32_t CPT1xCR; /*!< HRTIM Timerx Capture 1 register, Address offset: 0x5C */
__IO uint32_t CPT2xCR; /*!< HRTIM Timerx Capture 2 register, Address offset: 0x60 */
__IO uint32_t OUTxR; /*!< HRTIM Timerx Output register, Address offset: 0x64 */
__IO uint32_t FLTxR; /*!< HRTIM Timerx Fault register, Address offset: 0x68 */
__IO uint32_t TIMxCR2; /*!< HRTIM Timerx Control register 2, Address offset: 0x6C */
__IO uint32_t EEFxR3; /*!< HRTIM Timerx external event filtering 3 register, Address offset: 0x70 */
uint32_t RESERVED0[3]; /*!< Reserved, 0x74..0x7C */
}HRTIM_Timerx_TypeDef;
/* HRTIM register definition */
typedef struct {
HRTIM_Master_TypeDef sMasterRegs;
HRTIM_Timerx_TypeDef sTimerxRegs[6];
HRTIM_Common_TypeDef sCommonRegs;
}HRTIM_TypeDef;
(2) COMP1部分
还是在main.c中写 而且记得写在USER CODE中
打开COMP
// COMP
HAL_COMP_Start(&hcomp1);
/* USER CODE END 2 */
在stm32g4xx_hal_comp.c中看到 HAL_COMP_Start()
我是没什么好看的,列在这里
/**
* @brief Start the comparator.
* @param hcomp COMP handle
* @retval HAL status
*/
HAL_StatusTypeDef HAL_COMP_Start(COMP_HandleTypeDef *hcomp)
{
__IO uint32_t wait_loop_index = 0UL;
HAL_StatusTypeDef status = HAL_OK;
/* Check the COMP handle allocation and lock status */
if (hcomp == NULL)
{
status = HAL_ERROR;
}
else if (__HAL_COMP_IS_LOCKED(hcomp))
{
status = HAL_ERROR;
}
else
{
/* Check the parameter */
assert_param(IS_COMP_ALL_INSTANCE(hcomp->Instance));
if (hcomp->State == HAL_COMP_STATE_READY)
{
/* Enable the selected comparator */
SET_BIT(hcomp->Instance->CSR, COMP_CSR_EN);
/* Set HAL COMP handle state */
hcomp->State = HAL_COMP_STATE_BUSY;
/* Delay for COMP startup time */
/* Wait loop initialization and execution */
/* Note: Variable divided by 2 to compensate partially */
/* CPU processing cycles. */
/* Note: In case of system low frequency (below 1Mhz), short delay */
/* of startup time (few us) is within CPU processing cycles */
/* of following instructions. */
wait_loop_index = (COMP_DELAY_STARTUP_US * (SystemCoreClock / (1000000UL * 2UL)));
while (wait_loop_index != 0UL)
{
wait_loop_index--;
}
}
else
{
status = HAL_ERROR;
}
}
return status;
}
之前配置了比较器在超过 1/2Vref 时,上升沿触发中断
在 comp.c 中 写 回调函数 HAL_COMP_TriggerCallback
angle是之前自定义的一个全局变量
先判断进入这个中断是COMP1 //这一步其实可以省略,我没删掉而已
然后用 HAL_COMP_GetOutputLevel() 这个函数提取输出电平
COMP_OUTPUT_LEVEL_HIGH 为高电平
如果比较器输出为高电平,angle为90;如果低电平, angle为1;
/* USER CODE BEGIN 1 */
void HAL_COMP_TriggerCallback(COMP_HandleTypeDef *hcomp)
{
if(hcomp->Instance == COMP1){
if(HAL_COMP_GetOutputLevel(&hcomp1) == COMP_OUTPUT_LEVEL_HIGH) angle = 90;
else angle = 1;
}
}
/* USER CODE END 1 */
HAL_COMP_GetOutputLevel() 在 stm32g4xx_hal_comp.c中
读取选择的寄存器的输出电平
这里也能看到输出值 COMP_OUTPUT_LEVEL_HIGH 和 COMP_OUTPUT_LEVEL_LOW
/**
* @brief Return the output level (high or low) of the selected comparator.
* On this STM32 series, comparator 'value' is taken before
* polarity and blanking are applied, thus:
* - Comparator output is low when the input plus is at a lower
* voltage than the input minus
* - Comparator output is high when the input plus is at a higher
* voltage than the input minus
* @param hcomp COMP handle
* @retval Returns the selected comparator output level:
* @arg COMP_OUTPUT_LEVEL_LOW
* @arg COMP_OUTPUT_LEVEL_HIGH
*
*/
uint32_t HAL_COMP_GetOutputLevel(const COMP_HandleTypeDef *hcomp)
{
/* Check the parameter */
assert_param(IS_COMP_ALL_INSTANCE(hcomp->Instance));
return (uint32_t)(READ_BIT(hcomp->Instance->CSR, COMP_CSR_VALUE)
>> COMP_OUTPUT_LEVEL_BITOFFSET_POS);
}
3. 检验
(1) TC1 和 TC2 的波形
输出PWM的频率 6.782MHz
死区时间 10.24ns
导通时间 63.38ns
(2) TC1 和 TD1 的波形
比较器输出高电平 -- angle为90
C_CMP1 为100;C_CMP2 为500
D_CMP1 为200;D_CMP2 为600
(3) TC1 和 TD1 的波形
比较器输出低电平 -- angle为1
C_CMP1 为100;C_CMP2 为500
D_CMP1 为101;D_CMP2 为501
4. 感谢参考的博主和学弟
希望大家都来做6.78MHz!