概述
测量两次按下按键之间经过的时间。第一次按下按键,点亮LED,开始计时。第二次按下按键,熄灭LED,停止计时。记录下两次按键的时间节点。计算出两次按键的时间间隔。最后将结果通过UART输出到电脑。本实验使用正点原子MiniSTM32板子,芯片为STM32F103RCT6
主要完成以下内容:
- 按键触发外部中断并进行消抖。
- 定时器启动和停止
- 串口和上位机通信,发送时间数据到电脑
- 使用STM32CubeMX配置GPIO引脚。将PD2引脚配置为GPIO_Output,并设定标签LED,其他选项默认即可。将PC5引脚配置为GPIO_EXTI,因为按键按下后接地,并且没有外置上拉电阻,所以配置为上拉模式,下降沿触发,标签设置为KEY0。
- 设置定时器,这里选用TIM6。将其预分频值Prescaler设置为72 – 1,即定时器的时钟频率为1MHz。计数模式为Up,计数周期Counter Period设置为最大值65535。使能定时器中断。在定时器溢出时,增加计数值。如果不使能中断,则最大只能计量到65536×10^(-6)s = 0.065536秒。
- 设置串口外设。使能USART1即可,其他配置为默认,即波特率115200。
- 设置NVIC优先级。因为需要在按键触发的外部终端中使用HAL_Delay函数来进行按键延时消抖,而该函数需要使用到Systick Timer中断。在CubeMX配置中,Systick Timer的中断优先级最低。导致在外部中断回调函数中无法正常运行HAL_Delay,使得单片机宕机。所以一改将SystickTimer的中断优先级设置的比外部中断的优先级高。
硬件原理简图
代码
本次实验使用TIM6,一个基本计数器,只有简单的向上计数功能。当计数值CNT达到ARR值时,自动归零,重新开始计数,如果设置了中断,则进入中断回调函数。
启用TIM中断。
配置按键
使用外部中断,来检测按键状态。配置KEY0为外部中断EXTI模式,下降沿触发,上拉Pull-up。并且将其中断优先级设置的比Time base中断低,如上图所示。
配置UART
直接启用即可,其他默认。
代码
首先配置好串口,重定向printf,方便使用。
/* *
* @brief 重定向printf函数,将printf的输出重定向的串口USART1
* @note 根据宏,自动选择fputc或者时__io_putchar
* */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE* f)
#endif /* __GNUC__ */
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}
#ifdef __cplusplus
}
#endif //__cplusplus
接下来,配置TIM时钟的中断回调。每当TIM计数计满时,进入TIM的中断回调。在TIM中断回调中只需要简单,对记录中断次数的变量加一即可
/*@var time_period_cnt
用于计数TIM6的中断次数,一次中断时间约为0.065536s*/
uint32_t time_period_cnt = 0;
/*@var time_cap_start_cnt
记录第一次按键按下时,定时器Counter寄存器的值*/
uint16_t time_cap_start_cnt = 0;
/*@var time_cap_stop_cnt
记录第二次按键按下时,计时结束,定时器Counter寄存器的值*/
uint16_t time_cap_stop_cnt = 0;
/* *
* 标记当前定时器状态
* 0: 默认状态Idle,什么事也没有,等待按键然后触发TIM
* 1: TIM开始计时
* 2: 计时完成
* */
uint8_t time_cap_state = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);//该语句可不加,仅仅用来指示程序正常运行
++time_period_cnt;
}
}
接下来时对于按键的处理
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY0_Pin)
{
HAL_Delay(15);
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) == GPIO_PIN_RESET)
{
/*第一次按键按下,开始计时*/
if(time_cap_state == 0)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
__HAL_TIM_SET_COUNTER(&htim6, 0); //清空当期计数器的,将CNT设为0
HAL_TIM_Base_Start_IT(&htim6); //启用定时器,开始计时
time_cap_start_cnt = __HAL_TIM_GET_COUNTER(&htim6);
time_period_cnt = 0; //将TIM中断次数清零
time_cap_state = 1;
}
/*第二次按键被按下,计时结束,设置time_cap_state为2,
* 表示定时结束,在主循环中打印数据到电脑*/
else if(time_cap_state == 1)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
HAL_TIM_Base_Stop_IT(&htim6);
time_cap_stop_cnt = __HAL_TIM_GET_COUNTER(&htim6);
time_cap_state = 2;
}
}
}
}
主循环中负责轮询当前状态,如果已经完成测量,就进行串口发送。
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
while (1)
{
if(time_cap_state == 2)
{
double time_elapsed = (1e-6) * (time_period_cnt * 65536
+ time_cap_start_cnt - time_cap_stop_cnt);// 计算经过的时间,1e-6表示tim计数一次的周期
printf("Time Elapsed:%lf s\r\n", time_elapsed);
time_cap_state = 0;
}
}
}
例程下载例题代码,使用STM32CubeIDE打开更方便