实验(五):TIM应用:基本定时器定时和脉宽测量输入捕获实验设计

一、实验目的与任务

实验目的:

1. 学习对TIM的使用;

2. 掌握KEIL5的仿真与调试。

任务:

1.   根据要求编写程序,并写出原理性注释;

2. 将检查程序运行的结果,分析一下是否正确;

3. 完成所建工程的验证调试。

二、实验要求

实验一:

本实验利用基本定时器 TIM6/7 定时1s, 1s时间到 LED 翻转一次。基本定时器是单片机内部的资源,没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。

实验二:

根据开发板引脚使用情况,选用通用定时器 TIM5 的 CH1, 就 PA0 这个 GPIO 来测量信号的脉宽。在开发板中 PA0 接的是一个按键,默认接 GND, 当按键按下的时候 IO口会被拉高,这个时候我们可以利用定时器的输入捕获功能来测量按键按下的这段高电平的时间。

三、实验内容及步骤

实验一:

1. 软件设计

①  实验新建文件步骤:

运行Keil 5开发环境。编写定时器驱动文件,Timer.c 和Timer.h,用来配置定时器中断优先级和和初始化定时器。

②  编程要点:

   开定时器时钟 TIMx_CLK, x[6,7];

   初始化时基初始化结构体;

   使能 TIMx, x[6,7] update 中断;

   打开定时器;

   编写中断服务程序。

通用定时器和高级定时器的定时编程要点跟基本定时器差不多,只是还要再选择下计数器的计数模式,是向上还是向下。因为基本定时器只能向上计数,且没有配置计数模式的寄存器,默认是向上。

2. 实验步骤

(1)运行Keil uVision5开发环境,建立一个项目工程。

(2)在工程中添加main.c文件,因需要用到LED灯,所以将之前实验写好的LED文件移植到该工程中,然后在main.c中调用,如图1所示。

图1 移植程序

(3)在工程中添加定时器相关文件,因其本实验用定时器计数,故再建立一个Timer.c和Timer.h的两个文件,开启定时器中断(由于开发板没有通用定时器TIM6/7,因此使用其他通用定时器TIM2),首先编写Timer.c的源代码,如图2所示。

图2 编写Timer.c代码

(4)编写Timer.h程序,方便以后工程文件以移植,使项目工程工具有移植性,如图3所示。

图3 Timer.h程序

(5)编写main.c程序,调用定时器2中断函数(计次+闪烁),在主程序中进行初始化设置,如图4所示。

图4 main.c程序

运行并调试成功并无错误和警告。

3. 调试验证及结果

(1)将开发板连接到电脑上,使用STLINK将程序烧录到STM32单片机中,如图5所示:

图5 烧录程序

(2)程序烧录后,观察到LED灯闪烁,如图6所示:

图6 LED灯闪烁

(3)程序烧录后,观察到OLED显示屏上记录中断调用次数如图7所示:

图7 OLED显示次数

实验二:

根据开发板引脚使用情况,选用通用定时器 TIM5 的 CH1, 就 PA0 这个 GPIO 来测量信号的脉宽。在开发板中 PA0 接的是一个按键,默认接 GND, 当按键按下的时候 IO口会被拉高,这个时候我们可以利用定时器的输入捕获功能来测量按键按下的这段高电平的时间,按键的具体原理图见图8。

图8 实验2原理图

1. 软件设计

①  实验新建文件步骤:

运行Keil 5开发环境。编写定时器驱动文件,Timer.c 和Timer.h,用来配置定时器中断优先级和和初始化定时器。

②  编程要点:

   定时器用到的 GPIO初始化;

   定时器时基结构体 TIM_TimeBaseInitTypeDef 初始化;

   定时器输入捕获结构体 TIM_ICInitTypeDef 初始化;

   编写中断服务函数,读取捕获值,计算出脉宽的时间。

2. 实验步骤

(1)运行Keil uVision5开发环境,建立一个项目工程。

(2)在工程中添加main.c文件,因需要用到OLED显示屏,所以将写好的OLED文件移植到该工程中,然后在main.c中调用,如图9所示。

图9 移植程序

(3)在工程中添加定时器相关文件,因其本实验用定时器计数,故再建立一个Timer.c和Timer.h的两个文件,开启定时器中断(由于开发板没有通用定时器TIM5,因此使用其他通用定时器TIM3、通道1、PA6引脚),首先编写主函数Timer.c的源代码,如图10所示。

图10 Timer.c程序

(4)编写Timer.h程序,方便以后工程文件以移植,使项目工程工具有移植性,如图11所示。

图11 Timer.h程序

(5)编写main.c程序,调用定时器3中断函数(输入捕获判断、计时),在主程序中进行初始化设置、脉冲时间处理、OLED显示,如图12所示。

图12 main.c程序

运行并调试成功并无错误和警告。

3. 调试验证及结果

(1)将开发板连接到电脑上,使用STLINK将程序烧录到STM32单片机中,如图13所示:

图13 烧录程序

(2)程序烧录后,观察到按下、松开按键,观察OLED显示屏数据,如图14所示:

图14 OLED显示数据

四、实验代码分析

实验一:

(1)TIM2初始化程序:

void Timer_Init(void){
	//开启时钟,TIM2是APB1的时钟外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    	
	//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
    //手动清除中断标志位,避免刚初始化完就进入中断
	TIM_ClearFlag(TIM2,TIM_IT_Update);
	
	//使能中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	//配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);
}

(2)TIM2功能函数程序:

void TIM2_IRQHandler(void){
	//判断中断
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){
		num++;
		if(num % 2 == 1)
			LED_on();
		if(num % 2 == 0)
			LED_off();
		//清除中断标志位
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

(3)main函数程序:

#include "stm32f10x.h"                  // Device header
#include "OLED.h" 
#include "Timer.h" 
#include "LED.h"
#include "Delay.h"

uint16_t num = 0;

int main(){
	LCD_Init();
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	while(1){ 
		OLED_ShowNum(2,2,num / 2,3);
	}
}

实验二:

(1)TIM3初始化程序:

void Timer_Init(void){
	//开启时钟,TIM3是APB1的时钟外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	//配置GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_ResetBits(GPIOA, GPIO_Pin_6);
	
	//选择时基单元的时钟,可以不选,默认上电后选择内部时钟
	TIM_InternalClockConfig(TIM3);
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;//ARR自动重装器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级定时器使用
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	//配置捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//选择通道
	TIM_ICInitStructure.TIM_ICFilter = 0x00;//选择输入捕获滤波器
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择分频器
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3,&TIM_ICInitStructure);
	
	
	//中断分组初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	
	TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断	
	
	//启动定时器
	TIM_Cmd(TIM3,ENABLE);
}

(2)TIM3功能函数程序:

//定时器3中断服务程序	 
void TIM3_IRQHandler(void)
{ 
	// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
	// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
	if (TIM_GetITStatus( TIM3, TIM_IT_Update) != RESET ) {
		Capture_Period ++;
		TIM_ClearITPendingBit(TIM3, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
	}

	if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
	{
		if(Caputer_Flag == 0)//第一次捕获上升沿
		{
			TIM_SetCounter(TIM3, 0);//计数器清零
			Capture_Period = 0;//自动重装载寄存器更新标志清 0
			Caputer_Value = 0;//捕获时间清理
			TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Falling);//设置为下降沿捕获
			Caputer_Flag = 1;//捕获标志位置一
		}
		else
		{
			Caputer_Value = TIM_GetCapture1(TIM3);//获取捕获数据
			TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Rising); //设置为上升沿捕获
			Caputer_Flag = 0;//捕获标志位清零
			Caputer_Read = 1;//读取时间标志位置一
		}
	}
	TIM_ClearITPendingBit(TIM3, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
 
}

(3)main函数程序:

#include "stm32f10x.h"                  // Device header
#include "OLED.h" 
#include "Timer.h" 
#include "Delay.h"

uint8_t  Caputer_Flag = 0;//捕获标准位
uint16_t Caputer_Value = 0;//捕获数值
uint8_t  Caputer_Read = 0;//计算读取脉冲时间标志位
uint16_t Capture_Period; // 自动重装载寄存器更新标志

int main(){
	uint32_t time = 0;
	OLED_Init();
	Timer_Init();
	
	OLED_ShowString(1,1,"Num:");
	while(1){ 
		Delay_ms(10);
		if(Caputer_Read == 1)
		{
			time = Capture_Period * 65536 + Caputer_Value + 1;
			Caputer_Read = 0;
		}
		OLED_ShowNum(2,2,time / 1000000,3);
		OLED_ShowString(2,5,".");
		OLED_ShowNum(2,6,time % 1000000,6);
		OLED_ShowString(2,12,"s");
	}
}

五、实验总结

本次实验中,我学习了如何使用STM32F1系列单片机的基本定时器TIM2和通用定时器TIM3,并完成了相应的实验。通过实验,我深刻认识到了定时器在嵌入式系统中的重要性,以及如何在KEIL5中进行仿真与调试。

在实验1中,我使用了基本定时器TIM2来实现LED灯每隔1秒翻转一次。具体实现是先开启TIM2的时钟,然后设置TIM2的预分频和计数值,使其计数到1s后触发更新事件。在更新事件中,我通过GPIO库控制LED的输出,使其翻转状态。在实验过程中,我遇到了一些问题,例如计数值的设置不准确、更新事件的触发问题等,但通过对资料的查阅和代码的调试,我最终成功实现了LED的翻转。

在实验2中,我学习了如何使用通用定时器TIM3的输入捕获功能来测量按键按下的时间。具体实现是先开启TIM3的时钟和输入捕获功能,然后将TIM3的捕获引脚连接到PA6上,并设置TIM3的捕获值。在按键按下时,TIM3会触发输入捕获事件,并记录下此时TIM3的计数值。当按键弹起时,又会触发一次输入捕获事件,并记录下此时TIM3的计数值。两次计数值之差即为按键按下的时间。在实验过程中,我也遇到了一些问题,例如捕获引脚的设置、计数值的转换问题等,但通过不断尝试和调试,我最终成功测量出了按键按下的时间。

总的来说,本次实验让我深入了解了定时器在嵌入式系统中的应用,并提高了我的代码调试能力。在实验中,我也发现了一些问题,例如资料的理解不够透彻、代码实现的细节不够精细等,这些问题让我更加意识到了自己的不足之处,也激励着我更加努力地学习嵌入式系统的知识。

同时,本次实验也让我认识到了实验操作中的细节问题对实验结果的影响。例如在实验1中,如果计数值设置不正确,会导致LED的翻转频率不符合要求;在实验2中,如果捕获引脚设置不正确,会导致无法正确测量按键按下的时间。因此,我在实验中更加注重细节问题的处理,以确保实验结果的正确。

源码:

实验5-1

实验5-2

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是基于STM32F103RCT6,用HAL库写出通用定时器输入捕获实验的代码: ```c #include "stm32f1xx_hal.h" TIM_HandleTypeDef htim2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); while (1) { } } void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); printf("Pulse width: %d\n", pulse_width); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_IC_InitTypeDef sConfigIC = {0}; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFF; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 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_IC_Init(&htim2) != HAL_OK) { Error_Handler(); } sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 这段代码使用了STM32F103RCT6的TIM2通用定时器来进行输入捕获实验。在主函数中,我们初始化了GPIO和TIM2,并启动了TIM2的输入捕获模式。在输入捕获回调函数中,我们读取了捕获到的并打印出来。 注意:这段代码仅供参考,具体实现可能需要根据具体的硬件和需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值