在这里插入图片描述
从stm32的架构图可以看到,挂载在APB2上的外设有GPIO、EXTI、AFIO
GPIO的所有引脚默认是与输入输出寄存器相连映射的
AFIO算是一个不是外设的外设,因为它主要是用来将引脚重映射到其它外设寄存器的,但是对它操作也需要开启相应的时钟,所以也就放在了这里

从引脚到->EXTI中断控制器->NVIC控制器的整个硬件架构

详细可以参考以下链接:
STM32(五)- NVIC与EXTI

通用I/O端口以下图的方式连接到16个外部中断/事件线上,配置AFIO_EXTICRx寄存器
该部分主要是将引脚重映射到终端线上,也就是下下个图里的输入线
但是再这个之前必须先要将该寄存器外设的时钟打开(如下寄存器0位),才能对该寄存器操作
在这里插入图片描述

在这里插入图片描述

在中断控制器

在这里插入图片描述
在这里插入图片描述

以下为相应的寄存器
在这里插入图片描述

NVIC控制器

在这里插入图片描述
相应寄存器

在这里插入图片描述

注意点

除了NVIC的Pending位以外,EXTI有相应的Pending位在EXTI寄存器中,TIMER等也是,所以在中断处理函数中要调用相应的清除函数EXTI_ClearITPendingBit,TIM_ClearITPendingBit等,而NVIC的Pending位在进入中断处理函数时会被硬件清除
在这里插入图片描述
上面左边的是NVIC,下面有个pending位,右边是EXTI里也有一个pend位
NVIC寄存器组中的pending与外部中断挂起寄存器EXTI_PR pending是什么关系?
下图详细参考:NVIC寄存器组中的中断挂起SETPEND与消除CLRPEND,与外部中断挂起寄存器EXTI_PR有何关系?
在这里插入图片描述
在这里插入图片描述

根据如上顺序,就可以写出库函数的中断函数配置初始化步骤如下:

#include "stm32f10x_conf.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_exti.h"
#include "stdio.h"
#include "key.h"
#include "common.h"

/*首先将引脚配置为输入模式并初始化*/
void key_init(MODE mode) //首先需要确定某个引脚这里为GPIOC14为中断输入的引脚,然后将该引脚设置为输入模式,再初始化
{
	GPIO_InitTypeDef key;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	key.GPIO_Pin = GPIO_Pin_14;
	key.GPIO_Mode = GPIO_Mode_IPD;
	key.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&key);

    if(interrupt == mode)
    {
        key_exit_init();
        key_NVIC_init();

    }
    else
    {
        
    }
    
}

/*配置中断控制器,并初始化*/
/*在定义中断前首先要定义中断源的输入,也就是相应引脚的输入方式,根据该方式来确定是采用上升沿还是下降沿触发中断*/
void key_exit_init()
{
	EXTI_InitTypeDef key_exit; //定义中断参数结构体
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO寄存器的外设时钟
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource14); //选择exti信号源,将PC13引脚映射到中断线上
	
	
	
	
	key_exit.EXTI_Line = EXTI_Line14; //选择外部中断线14
	
	
	key_exit.EXTI_Mode = EXTI_Mode_Interrupt; //定义为中断模式
	key_exit.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //中断采用上升下降沿触发方式
	key_exit.EXTI_LineCmd = ENABLE;     //中断使能 
	EXTI_Init(&key_exit);             //将结构体参数代入,中断使能

}


/*配置NVIC控制器,并初始化*/
void key_NVIC_init()
{
	NVIC_InitTypeDef key_nvic;
	key_nvic.NVIC_IRQChannel = EXTI15_10_IRQn;   //中断线10-15都是连接同一中断处理函数
	key_nvic.NVIC_IRQChannelPreemptionPriority = 0;
	key_nvic.NVIC_IRQChannelSubPriority = 1;
	key_nvic.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&key_nvic);
}


//在中断函数和多线程中可以使用全局变量
u8 key_int_flag = 0;
void EXTI15_10_IRQHandler()
{
	//不要写循环,不要有延时,不要有阻塞
	if (SET== EXTI_GetITStatus(EXTI_Line14))
	{
		if(key_int_flag==1)
        {
            key_int_flag=0;
			printf("led on !\r\n");
        }
        else
        {
            key_int_flag++;
			printf("led off !\r\n");
        }
        EXTI_ClearITPendingBit(EXTI_Line14);
	}
	
	
}

定时器中断模型

整个定时器中断模型和EXTI中断模型是差不多的,就是把EXTI那部分换成TIMER,里面的结构体参数,初始化函数换一下,区别在于,EXTI它就是一个中断外设,在使能时就开了中断,而TIMER外设,它只是一个定时器外设,初始化使能时只是使能了它的定时功能,要想再开启它的中断功能,必须调用中断使能函数如下:

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//溢出中断时钟

还有一个区别是定时器(其实叫计数器更准确)需要选择时钟源,就是要有一个能有上升和下降沿的发生器才能出发计数器计数(这里其实又跟EXTI初始化时选择外部中断触发引脚类似了),但在写单纯的定时器计时功能时需要选用内部的时钟源,为什么没有相应的代码改变相应的寄存器呢?,如下:
在这里插入图片描述
在这里插入图片描述

相关的寄存器如下:
在这里插入图片描述
这里我们对比一下EXTI外设的相关寄存器:

在这里插入图片描述

其中TIMx_DIER寄存器实现的功能与EXTI_IMR、EXTI_RTSR、EXTI_FTSR三个寄存器实现的功能一样
TIMx_SR与EXTI_PR实现的功能一样,都是查看中断是不是有效实现了,在中断函数中软件复位的对象

代码如下:

/*
 * @Author: your name
 * @Date: 2021-01-07 20:26:04
 * @LastEditTime: 2021-01-07 22:17:08
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \projc:\Users\zj\Desktop\first_proj\main.c
 */
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_tim.h"
#include "misc.h"
#include "stdlib.h"

extern void delay_us(u16 time);
extern void delay_ms(u16 time);

	
void delay_us(u16 time)
{
	u16 i=0;
	while(time--)
	{
		i=10;
		while(i--);
	}
}

void delay_ms(u16 time)
{
	u16 i=0;
	while(time--)
	{
		i=12000;
		while(i--);
	}
}
/*
1us : (1 + 1)*(35 + 1) / 72
1s:		(1999 + 1)*(3499 + 1) / 72
*/
void timer2_init(void)
{
	TIM_TimeBaseInitTypeDef timer2;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//打开定时器时钟
	
	//timer2 参数配置
	timer2.TIM_Period = 19999;//自动重装载值 ARR 0-65535
	timer2.TIM_Prescaler = 3499;//定时器分频 PSC 0-65535
	timer2.TIM_ClockDivision =  TIM_CKD_DIV1;//定时器时钟分频 72M Hz
	timer2.TIM_CounterMode = TIM_CounterMode_Up;//计数模式
	
	
	TIM_TimeBaseInit(TIM2,&timer2);//定时器参数初始化
	TIM_Cmd(TIM2,ENABLE);//定时器使能
}


void timer2_nvic(void)
{
	NVIC_InitTypeDef timer2;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//0-3   0-3,每个中断的优先级由8位表示,实际起作用的只有4位(包含抢占和子优先级),这里主要是将优先级寄存器的4位分给抢占和子优先级,配置它们分别占多少位
	
	timer2.NVIC_IRQChannel = TIM2_IRQn;
	timer2.NVIC_IRQChannelSubPriority = 0;
	timer2.NVIC_IRQChannelPreemptionPriority = 1;
	timer2.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&timer2);

}



int main(void)
{
	uart1_init();
	delay_ms(100);
	timer2_init();
	delay_ms(100);
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//溢出中断
	delay_ms(100);
	timer2_nvic();
	
	uart1_send("init ok\r\n");
	while(1)
	{

	}

}

void TIM2_IRQHandler(void)
{

	if(SET == TIM_GetITStatus(TIM2,TIM_IT_Update))
	{
		uart1_send("timer-irq-2\r\n");
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//手动清除中断标志
	}
	
}

输入捕获计数器

STM32】通用定时器的输入捕获(实例:输入捕获)
STM32】通用定时器的输入捕获(实例:输入捕获)

输入捕获计数器相比较于单纯的定时器,多了一个输入捕获部分,输出部分,整个的架构如下所示:

在这里插入图片描述

时钟选择输入部分

外部输入/时钟模式1
在这里插入图片描述
外部触发输入/时钟模式2
在这里插入图片描述

时钟模式2的ETP寄存器和时钟模式1的CC2P都是控制边沿检测是上升还是下降沿有效
时钟模式2的滤波器一般不用,这样相比时钟模式1就是多了一个分频器

输入捕获

在这里插入图片描述
在这里插入图片描述

从以上时钟输入模式1和捕获输入架构来看,两个是共用同一个输入通道(也即输入时用的是同一个引脚),同一个滤波器和边沿检测器
输入捕获通道上控制滤波器的寄存器ICF,定义功能如下:
在这里插入图片描述

TIMx_CCMR1一个寄存器复用输入输出控制寄存器

在这里插入图片描述

该寄存器下的CC1xS位可以配置是选择定时器的输入通道还是输出通道(输入和输出通道统称为CCx通道,x为1、2、3、4)
控制上升沿触发还是下降沿触发的TIMx_CCER寄存器中的CCxP位是输入输出共用的

引脚的复用功能和重映射功能

我们知道单片机的引脚一般都是GPIO的作用,也就是说引脚一般都是与GPIO的输入输出寄存器相连的,但是有些引脚它不仅与GPIO的输入输出寄存器还与其它外设寄存器相连(比如USART串口寄存器,如下图红框中的),PA9与PA10这两引脚同时与三个外设相连,要使用这三个功能中的某一个时只需要开启该外设的时钟就可以,
但是如果是重映射功能,如PD12引脚,该引脚就不是直接与该外设相连,而需要通过AFIO寄存器将该引脚配置到重映射功能外设上,再开启该功能的时钟,才能将该引脚上的信号传递到相应外设或是将外设上的信号传输到该引脚上,完成相应功能
在这里插入图片描述
如下图为复用架构
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐