1 硬件和软件平台
硬件:自制STM32F103C8T6软件:STM32CubeMX 6.1.0硬件包:STM32Cube_FW_F1 V1.8.3
2 定时器介绍
STM32的定时器可以通过另外一个定时器的某一个条件被触发而启动,即同步的工作方式。发出触发信号的定时器工作于主模式(Master),接受触发信号而启动的定时器工作于从模式(Slave)。它们之间通过TIM内部触发连接(ITR)。使用不同的主从定时器,使用的ITR不同,根据参考手册(版本ENV20)可以知道对应的ITR。
图1 参考手册中的高级定时器内部触发连接
图2 参考手册中的通用定时器内部触发连接
3 CubeMX配置
不再介绍CubeMX创建工程的方法。

3.1 设置时钟

在RCC中配置高速时钟,选择外部晶振。
图3 高速时钟的配置
在Clock Configuration中选择HSE,并配置HCLK为72MHz。
图4 Clock Configuration 配置

3.2 设置仿真模式

在SYS设置仿真模式,此处我使用的是SW,根据情况自行选择。
图5 Debug配置

3.3 设置主定时器(Master)

此处我使用TIM2作为Master,通道2输出PWM。TIM1为Slave。在CubeMX中选择TIM2,配置主定时器。 Channel2选择PWM Generation CH2。
图6 Master TIM模式配置
预分频系数为71(设置值不能大于65535),计数器周期为9,这两个配置控制PWM输出的频率。由于我们时钟是72MHz,此处设置的PWM频率为100kHz。
图7 Master TIM参数配置
图8 PWM频率计算公式
设置Pulse为5,该值和计数器周期共同控制PWM的占空比,此处为50%。
图9 PWM占空比计算公式
使能主从模式,触发事件选择Update Event。禁用输出比较预加载。输出极性Low。

3.4 设置从定时器(Slave)

此处我选择TIM1为Slave。由图1可知,TIM2为Master,TIM1为Slave时,使用ITR1。在CubeMX中选择TIM1。设置Slave Mode为Gated Mode,触发源选择ITR1(根据自己使用的定时器选择),时钟源选择内部时钟。
图10 Slave TIM模式配置
参数设置基本是保持默认。
图11 Slave TIM参数配置
使能从定时器的中断。此处我使用的是高级定时器作为Slave,如果是通用定时器,只需使能定时器全局中断即可。
图12 Slave TIM中断配置
CubeMX配置完成,点击GENERATE CODE生成代码。
4 代码
CubeMX已经帮我们生成了初始化代码,无需修改。
主定时器(此处是TIM2)初始化代码:
/* TIM2 init function */ void MX_TIM2_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 71; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 5; sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } __HAL_TIM_DISABLE_OCxPRELOAD(&htim2, TIM_CHANNEL_2); HAL_TIM_MspPostInit(&htim2); }
从定时器(此处是TIM1)初始化代码:
/* TIM1 init function */ void MX_TIM1_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_SlaveConfigTypeDef sSlaveConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 65535; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim1) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sSlaveConfig.SlaveMode = TIM_SLAVEMODE_GATED; sSlaveConfig.InputTrigger = TIM_TS_ITR1; if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { Error_Handler(); } }
在main.c中定义标志位以指示PWM是否处于输出状态。
/* USER CODE BEGIN PV */ uint8_t PWM_OK = 0; /* USER CODE END PV */
在main函数初始化完成后添加以下代码。
/* USER CODE BEGIN 2 */ HAL_TIM_Base_Start_IT(&htim1); HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_2); __HAL_TIM_SET_AUTORELOAD(&htim1, 0); /* USER CODE END 2 */
在stm32f1xx_it.c中添加外部变量引用和中断回调函数,也可以添加到其他文件中,根据自己习惯来。由于我们使能了TIM1的中断,此处CubeMX会自动添加TIM1的引用,只需手动添加TIM2的引用即可。
/* USER CODE BEGIN PV */ extern uint8_t PWM_OK; /* USER CODE END PV */ /* USER CODE BEGIN EV */ extern TIM_HandleTypeDef htim2; /* USER CODE END EV */
在中断回调函数中清除中断标志位,并停止PWM。
/* USER CODE BEGIN 1 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == (&htim1)) { if(__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_CC1); HAL_TIM_PWM_Stop_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_Base_Stop_IT(&htim1); } PWM_OK = 1; } } /* USER CODE END 1 */
在main.c中封装PWM输出函数,并在main.h中声明。
/** * @brief The application outputs a specified number of pulses. * @param num is the number of pulses. */ void OutPwm(uint32_t num) { if(PWM_OK == 1) { PWM_OK = 0; __HAL_TIM_SET_AUTORELOAD(&htim1, num - 1); HAL_TIM_Base_Start_IT(&htim1); HAL_TIM_PWM_Start_IT(&htim2, TIM_CHANNEL_2); } }
在while(1)中输出10个脉冲并延迟40ms。
while (1) { HAL_Delay(40); OutPwm(10); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }
5 测试结果
因为我的硬件中使用了缓冲器将PWM的电平提高到了5V,因此测量结果都是5Vpp。
可以看到,每40ms有脉冲输出。
图13 PWM脉冲输出
放大细节可以看到得到100kHz的10个脉冲,波形还不错。
图14 PWM输出细节