一、工具
1、硬件:STM32F103VET6单片机;
2、编译工具:TrueSTUDIO;
3、辅助工具:STM32CubeMX。
二、单片机系统时钟配置
三、单片机定时器配置
这里参数设置说明下:
根据时钟树可以知道定时器2在APB1总线上(如下图所示),APB1的总线时钟由SYSCLK时钟经过AHB Prescaler分频器1分频和APB1 Prescaler分频器2分频得到最终36MHz,然而在给定时器做时钟源时会因为APB1 Prescaler进行了2分频再次x2进行倍频使得定时器2时钟源为72MHz,那么定时器2的周期计算公式t=(psc+1)x(period+1)/72MHz;根据公式可推导出:(7199+1)x(9999+1)/72000000=1s。
1、Counter Setting:
- Prescaler 预分频器,可以将计数器时钟频率除以1到65536之间的任何因子,是一个16bit的值。设置的值写入单片机定时器的TIMx_PSC寄存器中,设置值 =(预期值-1);
- Counter Mode 计数模式,设置向上计数(计数器从小到大递增计数 0-1-0)、向下计数(计数器从大到小递减计数 1-0-1)和居中对齐1~3(计数器先递增再递减 0-1-1-0或者先递减在递增1-0-0-1等 );
- Count Period 计数周期,决定定时器周期,是一个16bit的值。设置的值写入单片机定时器的TIMx_ARR寄存器中,设置值=(预期值-1);
- Internal Clock Division 内部时钟分割,表示由数字滤波器(TIx)使用的时钟(CK_INT)频率和采样时钟之间的分割比率(什么乱七八糟的,不管);
- auto-reload preload 自动重载预加载,使能就启动预加载,不使能就不启动预加载。设置的值写入单片机定时器的TIMx_CR1寄存器的ARPE位,具体作用如下图所示:
在定时器工作中,当不启动预加载,改变ARR的值,会立即生效,如下图所示:
在定时器工作中,当启动预加载,改变ARR的值,会等待下一次计数生效,而在当前计数周期会由影子自动重加载寄存器的值决定,如下图所示:
2、Trigger Output Parameters:
- 这里用不到,暂时不介绍,默认即可。
3、Output Compare Channel n:
- Mode 通道模式设置,设置定时器计数器与比较值相等时输出引脚的状态;
- Pulse 脉冲,设置比较寄存器的值(这里建议设置为0,在中断中改变比较寄存器的值);
- Output compare preload 输出比较预加载,作用和auto-reload preload 类似;
- CH Polarity 通道起始电平。
打开定时器中断,如果没要求优先级默认即可。
四、生成代码
1、关于生成的定时器配置的代码如下所示:
static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 7199; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 9999; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_OC_Init(&htim2) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_TOGGLE; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) { Error_Handler(); } if (HAL_TIM_OC_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ HAL_TIM_MspPostInit(&htim2); }
2、定时器的中断代码如下所示:
/** * @brief This function handles TIM2 global interrupt. */ void TIM2_IRQHandler(void) { /* USER CODE BEGIN TIM2_IRQn 0 */ /* USER CODE END TIM2_IRQn 0 */ HAL_TIM_IRQHandler(&htim2); /* USER CODE BEGIN TIM2_IRQn 1 */ /* USER CODE END TIM2_IRQn 1 */ }
3、引脚和与之相关的配置代码如下所示:
/** * @brief TIM_Base MSP Initialization * This function configures the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) { if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* TIM2 interrupt Init */ HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } } void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(htim->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspPostInit 0 */ /* USER CODE END TIM2_MspPostInit 0 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /**TIM2 GPIO Configuration PA2 ------> TIM2_CH3 PA3 ------> TIM2_CH4 */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USER CODE BEGIN TIM2_MspPostInit 1 */ /* USER CODE END TIM2_MspPostInit 1 */ } }
4、启动定时器代码:
这里我写了两种开始定时器的方式,主要区别是同时启动和先后启动,实际测试中这两种方式的差别基本看不出来。
void bsp_tim2_start(void) { #if 1 __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC3); __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC4); TIM_CCxChannelCmd(htim2.Instance, TIM_CHANNEL_3, TIM_CCx_ENABLE); TIM_CCxChannelCmd(htim2.Instance, TIM_CHANNEL_4, TIM_CCx_ENABLE); __HAL_TIM_ENABLE(&htim2); #else HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_3); HAL_TIM_OC_Start_IT(&htim2, TIM_CHANNEL_4); #endif }
五、完善中断服务函数:
定时器输出比较模式产生中断事件后,会调用函数HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim),需要定义一个该函数,添加中断需要处理的内容,具体操作如下所示:
函数的内容主要是获取定时器比较寄存TIMx_CCRn的值(即比较值),然后改变比较器下一次希望比较的值,每次比较器的值和计数器的值相等时就会触发中断;注意观察设置比较值的特点(我这里定时器寄存器TIMx_ARR的值设置的是10000),每个通道的最后一次设置的比较值非常重要,它决定了定时器的该通道是否能够重复输出期望的方波(如果你只在初始化定时器的时候设置了一次比较寄存器CCRn的值或者只在中断中改变一次比较寄存器CCRn的值,单片机对应的引脚只会在第一次启动程序的第一个周期输出你期望的方波,之后单片机会以定时器的周期为基准进行电平的反转,这是我在调试过程中发现的现象,如果你有不同的看法希望能指出)。
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3) { uhCapture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_3); /* Set the Capture Compare Register value */ if(uhCapture == 0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 1000); } else if(uhCapture == 1000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 2000); } else if(uhCapture == 2000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 10000); } else if(uhCapture == 10000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, 1000); } } if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4) { uhCapture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4); /* Set the Capture Compare Register value */ if(uhCapture == 0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 3000); } else if(uhCapture == 3000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 6000); } else if(uhCapture == 6000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 10000); } else if(uhCapture == 10000) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 3000); } } }
六、波形图
从波形图可看出,在一个周期内电平多次发生了反转。
#endif