stm32入门学习4-内部中断

定时器有很多内容,这里学习的是定时中断和时钟源选择

(一)内部中断简介

首先了解一下内部中断的流程图

通过这张图,我们可以大概了解到了如果我们要初始化内部中断要怎么做了,我们要操作的主要有这些(1)打开对应时钟,(2)选择时钟模式,(3)配置时基单元,(4)配置中断输出控制,(5)配置NVIC

定时器的操作代码都在tim.h文件中,由于代码比较多,这里挑一些会用到的新的代码列出来

void TIM_DeInit(TIM_TypeDef* TIMx);
//把定时器的设置恢复为默认值

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//设置定时器的时基单元,操作三用来初始化时基单元里的变量

void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//给定时器时基单元初始化结构体赋初始值,这里直接配置结构体,没有用到

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
//使能指定的时钟,等会用这个函数使能时钟2

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
//配置和使能时钟的中断,用来初始化中断输出控制,完成第四步

void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
//配置内部时钟模式,在第一步选择内部时钟源时用这个函数配置时钟,完成第二步

void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
//设置时钟模式为外部时钟模式1,用于第一步选择外部时钟源时配置,用于初始化第二步

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,  uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
//和之前两个函数一样,这个是选择时钟模式2,这里没有用到

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
//这个是配置预分频的,由于在计基单元初始化的结构体中已经配置了预分频,不需要单独配置,但是在代码执行中如果想修改预分频可以用这个函数修改

void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
//这个函数是用来设置计时器的计数模式的,可以选择向上计数,向下计数和中央计数,计数模式在时基单元初始化的时候同样通过结构体配置了,这个是用在代码执行中单独配置的

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
//用于获取计数器的计数值

uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
//获取计数器的预分频

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
//获取计数器的标志位,后面的四个和外部中断的类似

void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
//标志位清零

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
//获取标志位,用于中断中

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
//标志位清零,用于中断中,每次进入中断标志位都要手动清零,否则会一直申请中断

了解这些函数后就可以进行初始化配置了,我们先对每一步进行初始化配置,在封装成一个整体供外部调用

(二)内部时钟模式-实现秒表

(1)初始化配置

(1)打开时钟

我们操作的通用定时器的时钟在APB1中,打开时钟要调用的是RCC_APB1PeriphClockCmd函数

void timer_clock_init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
(2)选择时钟模式

我们选择的是内部时钟源,因此我们选择内部时钟模式,并且时钟选择为通用时钟TIM2

void timer_clock_channel_init()
{
	TIM_InternalClockConfig(TIM2);
}
(3)时基单元配置

这里用到之前提到的TIM_TimeBaseInit函数,第一个参数是要打开的时钟,我们这里选择的是通用时钟TIM2,可以跳转定义看下初始化结构体

这里第一个参数是设置预分频的,第二个参数是设置计数方式的,第三个参数是设置自动重装值的,第四个参数是设置时钟刻度(设置采样频率用于消除干扰),最后一个是设置重复计数器值(通用定时器没有,高级定时器才有,可以忽略)

了解参数后,我们可以这样配置,首先第四个TIM_ClockDivision是设置采样频率的,有三种方式(TIM_CKD_DIV1:不分频,TIM_CKD_DIV2:二分频,TIM_CKD_DIV4:四分频),这里我们使用的是内部时钟,我们选择不分频,最后一个TIM_RepetitionCounter由于通用定时器没有这个功能,直接给0,第二个TIM_CounterMode有这几种计数模式

我们选择的是向上计数,所以参数给TIM_CounterMode_Up

最后决定计数器计数时间长短的就是预分频和自动重装值,这里我们有一个计算计时器中断的时间公式T=\frac{(TIM_Period+1)*(TIM_Prescaler)}{clock frequecy},这里我们的系统时钟频率是72MHZ,如果我们要计数器一秒产生中断我们可以让period=10000-1,让prescaler=7200-1,这样相除后T=1

这样我们就配置好了时基单元部分

void timer_time_base_init()
{
	TIM_TimeBaseInitTypeDef timebase_init;
	timebase_init.TIM_ClockDivision = TIM_CKD_DIV1;
	timebase_init.TIM_CounterMode = TIM_CounterMode_Up;
	timebase_init.TIM_Period = 10000-1;	//72Mhz / (period+1)(repetion+1)
	timebase_init.TIM_Prescaler = 7200-1;
	timebase_init.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &timebase_init);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}
(4)中断输出控制

这个很简单,只要调用一下中断控制函数TIM_ITConfig,配置好参数就行了

这里主要看下第二个参数,这个参数选择的是特定的中断,其中第一个TIM_IT_Update是更新中断,也就是溢出中断,后面几个是一些捕获比较中断,这里没有用到,我们选择第一个

void timer_interrupt_control_init()
{
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}
(5)NVIC

这个就很简单了,和之前配置外部中断一样,我们配置好优先级分组和初始化结构体就可以了,这里的通道我们可以和之前一样去到stm32f10x.h上看一下

选择TIM2_IRQn就可以了

void timer_nvic_init()
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef nvic_init;
	nvic_init.NVIC_IRQChannel = TIM2_IRQn;
	nvic_init.NVIC_IRQChannelCmd = ENABLE;
	nvic_init.NVIC_IRQChannelPreemptionPriority = 1;
	nvic_init.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&nvic_init);
}
(6)最后处理

最后我们还要使能一下TIM2时钟,否则时钟是不工作的

void timer_open()
{
	TIM_Cmd(TIM2, ENABLE);
}

最后合成为一个外部可调用的初始化函数,就完成了timer模块的编写

#include "stm32f10x.h"                  // Device header

void timer_clock_init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}

void timer_clock_channel_init()
{
	TIM_InternalClockConfig(TIM2);
}

void timer_time_base_init()
{
	TIM_TimeBaseInitTypeDef timebase_init;
	timebase_init.TIM_ClockDivision = TIM_CKD_DIV1;
	timebase_init.TIM_CounterMode = TIM_CounterMode_Up;
	timebase_init.TIM_Period = 10000-1;	//72Mhz / (period+1)(repetion+1)
	timebase_init.TIM_Prescaler = 7200-1;
	timebase_init.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &timebase_init);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);    //清除上一句代码产生的中断标志位
}

void timer_interrupt_control_init()
{
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}

void timer_nvic_init()
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef nvic_init;
	nvic_init.NVIC_IRQChannel = TIM2_IRQn;
	nvic_init.NVIC_IRQChannelCmd = ENABLE;
	nvic_init.NVIC_IRQChannelPreemptionPriority = 1;
	nvic_init.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&nvic_init);
}

void timer_open()
{
	TIM_Cmd(TIM2, ENABLE);
}

void timer_init()
{
	timer_clock_init();
	timer_clock_channel_init();
	timer_time_base_init();
	timer_interrupt_control_init();
	timer_nvic_init();
	timer_open();
}

头文件仅需要对timer_init()声明

#ifndef __TIMER_H__
#define __TIMER_H__

void timer_init(void);

#endif

(2)主函数和中断函数

打开startup_stm32f10x_md.s文件可以找到TIM2的中断函数

我们在里面例行判断一下其状态(虽然这个通道只有一个中断),然后在中断后一定要记住把中断标志位清零,再加上一些秒表的判断语句和OLED屏幕的显示语句,就可以实现在OLED上秒表计数了(当然为了简单没有开始和结束,只能按复位键重置)

#include "stm32f10x.h"                  // Device header
#include "timer.h"
#include "OLED.h"

unsigned char min = 0;
unsigned char sec = 0;
unsigned char hour = 0;

int main()
{
	timer_init();
	OLED_Init();
	OLED_ShowChar(1, 3, ':');
	OLED_ShowChar(1, 6, ':');
	while(1)
	{
		OLED_ShowNum(1, 1, hour, 2);
		OLED_ShowNum(1, 4, sec, 2);
		OLED_ShowNum(1, 7, min, 2);
	}
	return 0;
}


void TIM2_IRQHandler()
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		min++;
		if (min >= 60)
		{
			sec++;
			min = 0;
			if (sec >= 60)
			{
				hour++;
				sec = 0;
				if (hour >= 24)
				{
					hour = 0;
				}
			}
		}
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

(三)外部时钟模式-对射式红外传感器计次

我们把红外对射传感器收到遮挡时产生的高低电平跳变作为时钟,每计数十次进入中断,值得注意的是我们的ETR是和PA0复用一个端口,因此我们要把红外对射传感器插在PA0口而不是其他口

(1)初始化

初始化和使用内部时钟模式的代码不同的地方有n点

(1)除了打开TIM2时钟外还要打开GPIOA时钟;

(2)要对PA0初始化,设置为输入模式;

(3)时钟通道选择的是外部时钟模式2,因此调用的是TIM_ETRClockMode2Config函数;

(4)把预分频设置为0,自动重装值设置为10,这样我们的红外产生十次信号就会进入中断

外部时钟配置函数可能会有一些分频(第二个:TIM_ExtTRGPrescaler)、电平翻转(第三个:TIM_ExtTRGPolarity)和滤波(最后一个:ExtTRGFilter),这里选择不分频,不电源翻转,不滤波

外部时钟滤波

这样就可以写好定时器模块了

#include "stm32f10x.h"                  // Device header

void external_apb_init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}

void external_gpio_init()
{
	GPIO_InitTypeDef gpio_initdef;
	gpio_initdef.GPIO_Mode = GPIO_Mode_IPU;
	gpio_initdef.GPIO_Pin = GPIO_Pin_0;
	gpio_initdef.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA, &gpio_initdef);
}

void external_clock_channel_init()
{
	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
}

void external_time_base_init()
{
	TIM_TimeBaseInitTypeDef time_base_initdef;
	time_base_initdef.TIM_ClockDivision = TIM_CKD_DIV1;
	time_base_initdef.TIM_CounterMode = TIM_CounterMode_Up;
	time_base_initdef.TIM_Period = 10-1;
	time_base_initdef.TIM_Prescaler = 1-1;
	time_base_initdef.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &time_base_initdef);
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}

void external_interrupt_init()
{
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}

void external_nvic_init()
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef nvic_initdef;
	nvic_initdef.NVIC_IRQChannel = TIM2_IRQn;
	nvic_initdef.NVIC_IRQChannelCmd = ENABLE;
	nvic_initdef.NVIC_IRQChannelPreemptionPriority = 1;
	nvic_initdef.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&nvic_initdef);
}

void external_timer_init()
{
	TIM_Cmd(TIM2, ENABLE);
}

void external_clock_init()
{
	external_apb_init();
	external_gpio_init();
	external_clock_channel_init();
	external_time_base_init();
	external_interrupt_init();
	external_nvic_init();
	external_timer_init();
}

只要把external_clock_init()放在头文件声明即可,主函数只要调用这个函数即可初始化中断

(2)主函数

主函数可以显示一下中断次数和目前的计数值

#include "stm32f10x.h"                  // Device header

#include "OLED.h"
#include "internal_clock_timer.h"

unsigned int count = 0;

int main()
{
	OLED_Init();
	external_clock_init();
	while(1)
	{
		OLED_ShowNum(1, 1, count, 5);
		OLED_ShowNum(2, 1, TIM_GetCounter(TIM2), 3);
	}
	return 0;
}

void TIM2_IRQHandler()
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update))
	{
		count++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

(四)总结

通过实现秒表和红外计次的功能,我们初步认识了内部中断,了解了内部中断的基本配置和内外时钟源的选择,内部中断远远不止这些,还要我们继续探索

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值