对于STM32F103共存在6个外设定时器:高级定时器TIM1,通用定时器TIM2、TIM3、TIM4。
方式 | 函数 |
---|---|
轮询方式 | HAL_TIM_Base_Start(TIM_HandleTypeDef *htim );HAL_TIM_Base_Stop TIM_HandleTypeDef *htim ); |
中断方式 | HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim); |
DMA方式 | HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length); |
预分频模块工作原理: 定时器启动后,预分频计数器的初值为0,预分频时钟CK_PSC每来一个时钟,预分频计数器的值就加1。当计数值等于预分频寄存器所设定的预分频系数PSC时,预分频计数器的值将清零,开始下一轮计数。
预分频时序图:
假设预分频系数PSC=3,预分频计数器从0计数到PSC实际计数值为PSC+1,也就是进行了四分频。
计数模式 | 计数器溢出值 | 计数器重载值 |
---|---|---|
递增计数 | CNT=ARR | CNT=0 |
递减计数 | CNT=0 | CNT=ARR |
中心对齐计数 | CNT=ARR-1CNT=1 | CNT=ARRCNT=0 |
成员变量ClockDivision的取值范围:
TIM_CLOCKDIVISION_DIV1 | 对定时器时钟TIM_CLK进行1分频 |
---|---|
TIM_CLOCKDIVISION_DIV2 | 对定时器时钟TIM_CLK进行2分频 |
TIM_CLOCKDIVISION_DIV4 | 对定时器时钟TIM_CLK进行4分频 |
成员变量CounterMode的取值范围
TIM_COUNTERMODE_UP | 递增计数模式 |
---|---|
TIM_COUNTERMODE_DOWN | 递减计数模式 |
TIM_COUNTERMODE_CENTERALIGNED1 | 中心对齐计数模式1 |
TIM_COUNTERMODE_CENTERALIGNED2 | 中心对齐计数模式2 |
TIM_COUNTERMODE_CENTERALIGNED3 | 中心对齐计数模式3 |
成员变量AutoReloadPreload的取值范围
TIM_AUTORELOAD_PRELOAD_DISABLE | 预装载功能关闭 |
---|---|
TIM_AUTORELOAD_PRELOAD_ENABLE | 预装载功能开启 |
时基单元初始化函数:HAL_TIM_Base_Init
函数原型 | HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim) |
---|---|
功能描述 | 按照定时器句柄中指定的参数初始化定时器时基单元 |
入口参数 | htim:定时器句柄的地址 |
返回值 | HAL状态值 |
注意事项 | 1. 该函数将调用MCU底层初始化函数HAL_TIM_Base_MspInit完成引脚、时钟和中断的设置2. 该函数由CubeMX自动生成 |
轮询模式启动函数:HAL_TIM_Base_Start
函数原型 | HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim) |
---|---|
功能描述 | 在轮询方式下启动定时器运行 |
入口参数 | htim:定时器句柄的地址 |
返回值 | HAL状态值 |
注意事项 | 1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于轮询方式下启动定时器运行 |
中断模式启动函数:HAL_TIM_Base_Start_IT
函数原型 | HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim) |
---|---|
功能描述 | 使能定时器的更新中断,并启动定时器运行 |
入口参数 | htim:定时器句柄的地址 |
返回值 | HAL状态值 |
注意事项 | 1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于使能定时器的更新中断,并启动定时器运行3. 启动前需要调用宏函数 __HAL_TIM_CLEAR_IT 来清除更新中断标志 |
定时器中断通用处理函数HAL_TIM_IRQHandler
函数原型 | void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) |
---|---|
功能描述 | 作为所有定时器中断发生后的通用处理函数 |
入口参数 | htim:定时器句柄的地址 |
返回值 | 无 |
注意事项 | 1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成中断处理2. 该函数由CubeMX自动生成 |
定时器更新中断回调函数HAL_TIM_PeriodElapsedCallback
函数原型 | void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) |
---|---|
功能描述 | 回调函数,用于处理所有定时器的更新中断,用户在该函数内编写实际的任务处理程序 |
入口参数 | htim:定时器句柄的地址 |
返回值 | 无 |
注意事项 | 1.该函数由定时器中断通用处理函数HAL_TIM_IRQHandler调用,完成所有定时器的更新中断的任务处理2.函数内部需要根据定时器句柄的实例来判断是哪一个定时器产生的本次更新中断3.函数由用户根据具体的处理任务编写 |
计数值读取函数__HAL_TIM_GET_COUNTER
#define __HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)- >Instance- >CNT)
__HANDLE__
:定时器句柄的地址
该函数通过直接访问计数器寄存器TIMx_CNT来获取计数器的当前计数值。
定时器中断标志清除函数__HAL_TIM_CLEAR_IT
#define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)- >Instance- >SR = ~(__INTERRUPT__))
__HANDLE__
:定时器句柄的地址
__INTERRUPT__
:定时器中断标志
基于STM32F103C8T6,开发板原理图
利用开发板上的按键KEY2来触发外部脉冲,按键每按下一次,就利用PA1引脚发送一个周期2ms左右的脉冲,送到定时器2的外部触发引脚ETR(PA0)进行计数,并将计数结果通过串口发送到PC上显示。
注:本任务例程使用的开发板,KEY2与PA5相连接。KEY2原理图如下:
使用按键时,需要设置PA5为输入上拉模式,这样在KEY2没有按下时,PA5可以读取到高电平,KEY2按下时PA5可以读取到低电平。
配置PA1为GPIO_Output(User Label:PULSE),PA5为GPIO_Input(User Label:KEY2),上拉模式。
上述操作在stm32f1xx_hal_gpio.c
中生成GPIO引脚初始化函数MX_GPIO_Init
,并在main.c中调用
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
/*Configure GPIO pin : PA1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
配置定时器2时钟源为外部触发引脚ETR2,自动重载寄存器ARR配置为一个相对较大的计数,防止溢出。外部脉冲信号采用默认配置:不使用滤波,不进行脉冲信号反相,不进行脉冲信号分频。
上述操作在tim.c
生成引脚初始化函数:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_baseHandle- >Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**TIM2 GPIO Configuration
PA0-WKUP ------ > TIM2_ETR
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle- >Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspDeInit 0 */
/* USER CODE END TIM2_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_TIM2_CLK_DISABLE();
/**TIM2 GPIO Configuration
PA0-WKUP ------ > TIM2_ETR
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);
/* USER CODE BEGIN TIM2_MspDeInit 1 */
/* USER CODE END TIM2_MspDeInit 1 */
}
}
上述操作在tim.c
生成如下代码完成初始化定时器,并在main.c
中调用
/* TIM2 init function */
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};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 65535;
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_ETRMODE2;
sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
sClockSourceConfig.ClockFilter = 0;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
}
其中调用的HAL_TIM_Base_Init
函数将调用HAL_TIM_Base_MspInit
完成引脚初始化。
配置串口外设USART1,选择异步模式,无硬件流控。
上述操作在usart.c中生成串口配置相关函数,并在main.c中调用。usart的封装逻辑与tim相同,不再赘述。
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle- >Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------ > USART1_TX
PA10 ------ > USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle- >Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------ > USART1_TX
PA10 ------ > USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
程序编写
使用串口输出,需要在Keil的Options中勾选Use MicroLIB.
在main.c中重定义printf
和scanf
函数
/* USER CODE BEGIN Includes */
#include < stdio.h >
/* USER CODE END Includes */
/* USER CODE BEGIN 4 */
int fputc (int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 4 */
用户变量定义代码
/* USER CODE BEGIN PV */
uint8_t Result = 0;
/* USER CODE END PV */
用户变量初始化代码
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
printf("Timer count function test: n");
/* USER CODE END 2 */
用户应用代码
/* USER CODE BEGIN 3 */
if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET)
{
HAL_Delay(10);
if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_SET);
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_RESET);
HAL_Delay(1);
Result = __HAL_TIM_GET_COUNTER(&htim2);
printf("Count = %d", Result);
}
while (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET);
}
}
/* USER CODE END 3 */
实验现象
设计电子时钟,从00:00:00开始计时,并将计时信息通过串口UART1发送到PC进行显示。
配置定时器1产生1s的更新中断。计算PSC和ARR的值,将T=1s,TIM_CLK=8000000Hz带入公式
得可取值PSC=1999,ARR=3999.
在CubeMX中使能TIM1,采用内部时钟 (8MHz) ,设置PSC和ARR值,并在嵌套向量中断控制器NVIC设置中使能TIM1的更新中断。
上述操作在tim.c
中生成TIM1初始化函数,并在main.c
中调用
配置USART1串口输出,生成代码后应在工程中重定向printf
和scanf
函数,方法同任务实践1,这部分不再赘述。
编写代码
在main.c
中进行用户数据类型定义
/* USER CODE BEGIN PTD */
typedef struct
{
uint8_t hour;
uint8_t minutes;
uint8_t second;
}CLOCK_Typedef;
/* USER CODE END PTD */
用户变量定义
/* USER CODE BEGIN PV */
CLOCK_Typedef clock = {0};
/* USER CODE END PV */
用户初始化代码
/* USER CODE BEGIN 2 */
// 清除更新中断标志,避免定时器一启动就进入中断
__HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE);
HAL_TIM_Base_Start_IT(&htim1);
/* USER CODE END 2 */
用户应用代码
/* USER CODE BEGIN 3 */
printf("Time:%02d:%02d:%02d.rn", clock.hour, clock.minutes, clock.second);
HAL_Delay(1000);
}
/* USER CODE END 3 */
编写定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim- >Instance == TIM1) // 判断时钟源
{
clock.second++;
if (clock.second == 60)
{
clock.second = 0;
clock.minutes++;
if (clock.minutes == 60)
{
clock.minutes = 0;
clock.hour++;
if (clock.hour == 24)
{
clock.hour = 0;
}
}
}
}
}
全部0条评论
快来发表一下你的评论吧 !