概述

重点在于梳理系统框架,形成系统性的学习才是重点!!

  • 学习功能框图:看用户手册,外设的功能框图和外设寄存器功能一起看,理解外设工作流程和知道如何配置寄存器来使用外设
  • 学习组合功能:看用户手册,多个外设之间的配合是怎么配置的,需要打开哪些开关和寄存器
  • 学习库函数:查看对应模块的固件库的.c和.h文件,通过头文件里的库函数声明查看外设有哪些功能可配置以及如何配置,通过源文件的功能说明可以查看库函数可以输入哪些类型的参数,看懂是在操作哪些寄存器就行,这样就相当于掌握了库函数,没必要背诵,记住常用的一些就行
  • 学习写代码:一定要自己设计个程序把外设融会贯通用起来,很多东西都会遗忘,但只要记住最核心的几步就行,形成系统化学习才是关键!比如,外设的一般通用功能都有:
    • (1)开时钟(RCC库函数)
    • (2)设置参数(比如ADC_Init()
    • (3)配置中断(比如ADC_ITConfig()
    • (4)读取中断标志(比如ADC_GetITStatus()
    • (5)清除中断标志(比如ADC_ClearITPendingBit()
    • (6)使能启动(比如ADC_Cmd()
    • (7)特有的功能,比如ADC规则组配置(ADC_RegularChannelConfig()这种函数)等等

  • EXIT的时钟默认是开启的,无需手动使用RCC开启

  • NVIC的时钟默认是开启的,无需手动使用RCC开启

  • AFIO主要有2个功能:重映射配置和打开引脚对应的外部中断信号使其能够进入到EXIT模块

  • AFIO是单独的硬件模块,具有单独的时钟,需要RCC手动开启时钟

  • 如果使用EXIT功能,那么这是一条信号路径,需要这条线路上的所有外设都启动:外部信号-GPIO - AFIO - EXIT - NVIC
    在这里插入图片描述

  • ADC调用使能函数即上电启动是必要的即函数ADC_Cmd(ADC1, ENABLE),这个和ADC启动转换是两回事

  • ADC参数里可设置启动触发源:【1】参数配置是必须的,【2】并且还需要调用个函数进行正式的触发启动,比如硬件触发要在初始化时调用一次ADC_ExternalTrigConvCmd(ADC1, ENABLE)即可;软件触发则是每次启动ADC转换时(若为单次模式,循环模式则只需初始化启动一次即可)就需要调用一次ADC_SoftwareStartConvCmd(ADC1,ENABLE)

  • 外设需要使用DMA辅助时,除了配置DMA模块外,每个外设都还要调用自己的DMA使能函数,而非是配置参数,使得外设产生DMA触发信号给到DMA,比如ADC是调用ADC_DMACmd(ADC1, ENABLE)UART是调用USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE)DAC是调用DAC_DMACmd(DAC1, ENABLE)等等,这一点尤其注意

  • 类似的,外设(包括DMA外设)需要产生中断响应时,除了配置NVIC模块外,每个外设还要调用自己的ITConfig配置函数选择打开某一种中断触发,(个别外设是配置参数而没有写成函数,这是标准库函数写的问题,比如EXIT需要配置参数为中断响应而非事件响应)。比如,ADC外设需要调用ADC_ITConfig( ADC1, ADC_IT_EOC, ENABLE)打开转换完成中断触发UART外设需要调用USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)打开接收非空的中断触发

  • DMA配置需调用使能函数DMA_Cmd(DMA1_Channel1, ENABLE)打开DMA功能具体DMA的启动触发源是谁,软件配置上DMA并不关心,而是由CPU内部的外设连接决定的,比如DMA1_Channel1的可选触发源有ADC1、TIM2_CH3、TIM4_CH1三个,三者经过或门到达DMA1的通道1,那么只要三者任意一个配置了自己的DMA功能,那么就会产生触发信号,从而让DMA按照配置好的参数启动搬运

    • 如果是ADC1请求,只有在规则通道的转换结束时才产生DMA请求,并将转换的数据从ADC_DR寄存器传输到用户指定的目的地址。因为设计目的就是:因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据。
    • 如果是TIM2和TIM4触发了DMA启动,可以通过定时器功能,定时触发DMA的启动,那么DMA会定时去搬运设置好的外设和存储器之间的数据,比如这里配置搬运ADC_DR的数据到存储器,但搬运时机和ADC转换时序对不上,搬运的不是最新数据,不太合适。

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

  • STM32中ADC触发DMA的时机可概括为:当ADC完成一次转换(产生EOC信号)时,就会触发一次DMA请求,将ADC_DR中的当前转换结果传输到内存

    • 单通道:每次转换后触发一次DMA;
    • 多通道:每个通道转换后触发一次DMA(需开启EOCS=0,若有);
    • 连续转换:循环触发DMA(配合DMA循环模式);
    • 单次转换:仅触发对应次数的DMA(如3通道单次扫描→3次DMA)。
  • TIM2_CH3作为DMA1_Channel1的触发源:用于通过TIM2_CH3的事件(定时/捕获)触发DMA传输,实现“事件驱动的自动数据搬运指定外设和存储器之间的数据”

    • 选择TIM2_CH3作为触发源,本质是控制DMA1_Channel1定时启动(通过TIM2的定时事件),从而定时搬运指定外设与存储器之间的数据(如ADC数据→内存、内存→串口)。实现定时数据采集、事件驱动的数据存储、定时发送数据等,核心价值是无需CPU干预,提升系统效率。
  • DMA的存储器到存储器搬运和循环模式不能同时开启

  • 补充介绍一点DMA的特点:自动重装载即循环模式和DMA触发方式。开启了自动重装载功能后,每次搬运计数器减到0后会将搬运计数器自动重新装载会初始值即用户设置的DMA一轮的搬运次数,但是,并不是说搬运计数器不为0,DMA就会不停地工作,而是说还要有DMA启动触发信号才可以!比如说,现在ADC设置为连续扫描规则组模式,规则组内有2个ADC通道,那么DMA一轮搬运就需要设置为搬运2次,DMA的启动触发源需要设置为ADC转换完成,那么,ADC启动转换后先转换规则组里的第一个ADC通道,此通道转换完成后产生一个转换完成脉冲给到DMA,DMA则立即去搬运此时ADC数据寄存器的值到目标地址比如SRAM里,然后DMA搬运计数器自动减1(由2变为1),此时DMA就停止工作了,需要等待下一次启动指令。由于ADC为连续模式,因此ADC在通道1转换完成后立即开始通道2的转换,通道2转换完成后同样产生一个转换完成脉冲给到DMA,DMA开始搬运ADC数据寄存器的值到目标地址SRAM里,然后搬运计数器自动减1(由1变为0),DMA停止工作,并且如果DMA目标地址是自增的,那么此时DMA目标地址会恢复为最开始的目标地址,以方便开始下一轮的搬运工作。如果DMA没有设置自动重装载功能,那么DMA此时的搬运计数器为0,那么即使再来了ADC转换完成脉冲触发DMA,DMA也是无法开始工作的。如果DMA设置了自动重装载功能,那么此时搬运计数器自动恢复为0,但是DMA还不能开始工作,要等到ADC转换完成脉冲来到,DMA才能开始新一轮的搬运工作,这一轮的搬运,DMA的各个参数配置都恢复到和前一轮一模一样,包括搬运源地址和目标地址

  • NVIC属于内核外设,需要在内核手册里查看功能。

  • NVIC模块默认是一直开启的

  • 外设输出中断信号到NVIC后,NVIC需要打开自身内部对应的中断信号开关,中断信号才能到达CPU

  • NVIC可以设置这个中断信号或者说中断通道的优先级,是在这里设置,而非在外设里设置

  • NVIC需要注册这个中断信号的回调函数是在这里设置参数,而非在外设里设置

  • 需要重写对应的中断回调函数

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU

NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型

//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}
  • 重映射功能
    • 把外设功能映射到其他引脚或其他外设
  • 一种是把功能映射到其他外设
    • 比如对于这个ADC1启动触发源的重映射,默认情况下ADC1的触发源之一是EXIT11而没有TIM8_TRGO,但是现在如果想要使用TIM8_TRGO作为启动转换的触发源,那么就需要设置AFIO寄存器中的ADC1_ETRGINJ_REMAP = 1,然后再去设置TIM8产生对应的事件响应即把脉冲给到ADC1ADC1那边则需要将外部触发信号选择为ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO,这样就可以通过TIM8自动触发ADC1的启动转换了
  • 一种是把功能映射到其他引脚
    • 比如对于UART2引脚的重映射,默认情况下UART2使用的是PA2作RX,使用PA4作TX,如果恰好PA2和PA4被其他外设占用,又想使用UART2,那么此时就需要开启UART2的重映射,即设置AFIO寄存器USART2_REMAP = 1,这样的话PD6就当做了UART2的RX,PD7当做UART2的TX,这一组组合是不可配置更改的,就只能是PD6和PD7,而不是任意的引脚组合

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


  • GPIO核心功能:采样输入的电平值;对外输出指定电平;配合其他外设设置工作模式;工作参数方向、速度等;
寄存器 偏移地址 复位值(默认状态)
端口配置低寄存器CRL 0x00 0x4444 4444(浮空输入)
端口配置高寄存器CRH 0x04 0x4444 4444(浮空输入)
端口输入数据寄存器IDR 0x08 0x0000 xxxx(初值为0)
端口输出数据寄存器ODR 0x0C 0x0000 0000(输出为0)
端口位设置/清除寄存器BSRR 0x10 0x0000 0000(不影响ODR)
端口位清除寄存器BRR 0x14 0x0000 0000(不影响ODR)
端口配置锁定寄存器LCKR 0x18 0x0000 0000(不锁定端口)
  • AFIO核心功能(1)设置重映射;(2)打开外部中断通道开关使外部信号能进入EXIT;
寄存器 偏移地址 复位值(默认状态)
事件控制寄存器(AFIO_EVCR) 0x00 当设置该位后,Cortex的EVENTOUT将连接到由PORT[2:0]和PIN[3:0]选定的I/O口
复用重映射和调试I/O配置寄存器(AFIO_MAPR) 0x04 每个bit可以配置各个外设的重映射
外部中断配置寄存器 1- 4(AFIO_EXTICR1-4) 0x08-0x14 打开对应引脚的外部中断通道,允许通向EXIT
  • EXIT核心功能(1)接收和判断信号边沿;(2)打开中断响应开关和产生中断响应;(3)打开事件响应开关和产生事件响应脉冲;
寄存器 偏移地址 复位值(默认状态)
中断屏蔽寄存器(EXTI_IMR) 0x00 屏蔽/接收来自线x上的中断请求
事件屏蔽寄存器(EXTI_EMR) 0x04 屏蔽/接收来自线x上的事件请求
上升沿触发选择寄存器(EXTI_RTSR) 0x08 禁止/允许输入线x上的上升沿触发(中断和事件)
下降沿触发选择寄存器(EXTI_FTSR) 0x0C 禁止/允许输入线x上的下降沿触发(中断和事件)
软件中断事件寄存器(EXTI_SWIER) 0x10 当该位为’0’时,写’1’将设置EXTI_PR中相应的挂起位。如果在EXTI_IMR和EXTI_EMR中允许产生该中断,则此时将产生一个中断
挂起寄存器(EXTI_PR) 0x14 当在外部中断线上发生了选择的边沿事件,该位被置’1’。在该位中写入’1’可以清除它,也可以通过改变边沿检测的极性清除
  • ADC核心功能:启动采样以及可配置启动触发源;采样参数比如右对齐、采样时间的设置;采样模式规则组还是注入组、连续/单次/扫描/非扫描的设置;打开中断功能和产生中断响应;需要设置各种使能比如上电、启动、硬件触发等
  • DMA核心功能:搬运参数比如方向、地址、单次宽度、地址自增的设置;搬运参数比如是否循环、一个循环的转运次数的设置;搬运优先级的设置;搬运是否产生中断
  • NVIC核心功能:中断分组的设置;中断开关的打开以允许接收来自其他外设的中断信号;中断优先级的设置;外设的中断回调函数由NVIC注册和设置(M3编程手册)
寄存器 偏移地址 复位值(默认状态)
Interrupt set-enable registers (NVIC_ISERx) 0x00- 0x0B Enable interrupt、Interrupt enabled
Interrupt clear-enable registers (NVIC_ICERx) 0x00- 0x0B Disable interrupt、Interrupt enabled
Interrupt set-pending registers (NVIC_ISPRx) 0x00- 0x0B Changes interrupt state to pending、Interrupt is pending
Interrupt clear-pending registers (NVIC_ICPRx) 0x00- 0x0B Removes the pending state of an interrupt、Interrupt is pending
Interrupt active bit registers (NVIC_IABRx) 0x00- 0x0B Interrupt active
Interrupt priority registers (NVIC_IPRx) 0x00- 0x0B Interrupt priority
Software trigger interrupt register (NVIC_STIR) 0xE00 generate a Software Generated Interrup

  • 多个模块硬件上联动工作每个模块都有自己的海关!对于外来信号的处理,需要打对应的输入开关才能让其进入;对于输出信号到外部其他外设硬件里去,也需要打开对应的输出开关。典型的就比如ADC1的使用:

    • (1)ADC启动采样的触发源可以选择软件触发(用户写寄存器启动)或者硬件触发(外部事件出现后自动触发ADC启动采样),硬件触发比如可以选择定时器事件触发,因此ADC需要打开硬件触发功能并选择定时器事件,例如ADC的参数ADC_ExternalTrigConv可以选择外部ADC_ExternalTrigConv_T1_CC1
    • (2)并且对应的定时器也需要配置产生相应的响应事件给到ADC,例如定时器的事件响应
    • (3)ADC如果需要DMA辅助,那么ADC需要打开自己通往DMA的通道即调用ADC_DMACmd(ADC1, ENABLE);
    • (4)并且DMA也要配合打开DMA接收外部信号触发的开关即调用DMA_Cmd(DMA1_Channel1, ENABLE),其实就只是使能DMA模块,只有这一个开关;
    • (5)ADC如果需要中断函数,那么ADC需要打开自己的中断功能即调用ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE),这样ADC产生的中断信号才能从ADC模块中输出出去,到达NVIC
    • (6)并且NVIC对于外来中断信号,也需要配置自己的“海关”决定是否允许对方进入到NVIC并到达CPU,也是一道开关,并且设置这个中断信号的优先级以及注册对应的中断回调函数,也就是统一管理中断信号。
    • 其他外设之间的联合工作,都可参考这个流程
  • CPU提供了很多中断功能/中断通道/中断函数,方便用于监视各种事件。分为内核级别的事件中断(10个)和外设级别的事件中断(60个);

  • 外部中断里的多个模块协同工作模式1

    • (1)外部信号 ——>
    • (2)GPIO设置为输入模式,采样信号)——>
    • (3)AFIO【1】打开外部中断通道开关,允许外部信号进入EXIT) ——>
    • (4)EXIT【1】打开中断响应功能,【2】自动根据信号特征是否产生中断信号,【可以使用对应的中断回调函数,但函数需要在NVIC里配置,中断回调函数都归NVIC管理】)——>
    • (5)NVIC属于内核外设,【1】采样管理打开中断信号通道开关,【2】并设置中断信号的优先级;【3】外设的中断回调函数由NVIC注册和设置) ——>
    • (6)CPU
  • 外部中断里的多个模块协同工作模式2

    • (1)外部信号 ——>
    • (2)GPIO设置为输入模式,采样信号)——>
    • (3)AFIO【1】打开对应引脚的中断通道开关,允许外部信号进入EXIT)——>
    • (4)EXIT【1】打开事件响应开关,【2】自动根据外部信号特征是否产生脉冲给到其他外设,【3】不再产生中断信号)——>
    • (5)ADC【1】硬件触发选择外部中断;【2】打开DMA开关,允许数据寄存器连接到DMA)——>
    • (6)DMA(【1】打开DMA开关即使能DMA,允许接收来自ADC的DMA请求(并不会特意指定是来自ADC,而是手册里哪个DMA触发源被使能了就会输入到DMA,DMA并不识别来自哪个具体外设);【2】打开中断开关,允许DMA搬运一个循环后产生中断信号并输出给到NVIC)——>
    • (7)NVIC属于内核外设,【1】打开中断通道开关,允许接收来自DMA的中断信号,【2】并设置DMA中断的优先级;【3】外设的中断回调函数由NVIC注册和设置)——>
    • (8)CPU

一、NVIC模块功能框图

需结合M3内核手册查看、编程手册

1.1 有多少内外部中断

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

在这里插入图片描述

  • 10个内核中断,60个外设中断,能够很好的监视外部的事件,节省CPU资源

1.2 NVIC负责帮CPU管理中断

一个外设可能产生n个中断信号到NVIC,但NVIC在某一个时刻只输出一个中断信号给CPU。NVIC说白了就是专门管理这么多中断信号,方便管理和提高CPU工作效率。在M3内核手册里查看NVIC功能介绍
在这里插入图片描述

  • 每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。这里说的每个中断通道指的是每个中断函数吗?也就是中断向量表里的那些。
  • 所谓的16个优先级,也就是16个等级管理,这里的16个优先级指的是一种分组下有16个优先级,但实际上可以有5种分组形式。比如,给中断函数A分配各最高优先级,那么就选择分组0并设置0抢占和0响应,另一个函数B优先级低一些,可以选择分组0并设置0抢占和1响应,都是户可以任意设置的。
  • 程序里只能选择其中1中分组方式,所以说程序中就只能设置一次分组。

1.3 NVIC设置优先级

  • 抢占优先级:可以抢占也就是可以打断当前正在执行的中断任务
  • 响应优先级:如果通道到达,则按照分配的优先级顺序排队执行,不能打断正在执行的中断任务
  • 如果优先级完全相同,那么就按照中断向量表里的中断号排序执行

在这里插入图片描述

  • NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
  • 抢占优先级高的可以中断嵌套和打断正在执行的中断
    多个中断同时到达,响应优先级高的可以优先排队,
    抢占优先级和响应优先级均相同的按中断号排队

1.3 NVIC注册回调函数

在启动文件里(.s文件)已经给各个中断回调函数起好名字了,但默认实现是空循环,因此如果需要使用某个中断回调函数,那么就需要在源文件里重新实现此回调函数,要求名字保持一致,没有输入参数,没有返回值,例如


DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
 
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型

//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}

1.4 NVIC寄存器

寄存器 偏移地址 复位值(默认状态)
Interrupt set-enable registers (NVIC_ISERx) 0x00- 0x0B Enable interrupt、Interrupt enabled
Interrupt clear-enable registers (NVIC_ICERx) 0x00- 0x0B Disable interrupt、Interrupt enabled
Interrupt set-pending registers (NVIC_ISPRx) 0x00- 0x0B Changes interrupt state to pending、Interrupt is pending
Interrupt clear-pending registers (NVIC_ICPRx) 0x00- 0x0B Removes the pending state of an interrupt、Interrupt is pending
Interrupt active bit registers (NVIC_IABRx) 0x00- 0x0B Interrupt active
Interrupt priority registers (NVIC_IPRx) 0x00- 0x0B Interrupt priority
Software trigger interrupt register (NVIC_STIR) 0xE00 generate a Software Generated Interrup

1.5 库函数

在这个文件:#include "misc.h"

1.5.1 库函数功能
  • 主要库函数
    • 设置中断分组
    • 初始化参数
    • 设置中断向量表的存放地址
//设置中断分组
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
//设置中断参数、优先级等等
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
//设置中断向量表
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
  • 主要参数
    • 进行中断分组
    • 打开指定通道
    • 注册回调函数
    • 设置中断优先级
	//NVIC:打开对应的中断通道、中断函数和中断优先级
	//中断函数或者说通道号IRQn_Type ,是枚举类型,表示的是一个中断号
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
1.5.2 参数取值

这个其实就对应到了有哪些内部和外部中断函数、中断通道、中断号

typedef enum IRQn
{
/******  Cortex-M3 Processor Exceptions Numbers ***************************************************/
  NonMaskableInt_IRQn         = -14,    /*!< 2 Non Maskable Interrupt                             */
  MemoryManagement_IRQn       = -12,    /*!< 4 Cortex-M3 Memory Management Interrupt              */
  BusFault_IRQn               = -11,    /*!< 5 Cortex-M3 Bus Fault Interrupt                      */
  UsageFault_IRQn             = -10,    /*!< 6 Cortex-M3 Usage Fault Interrupt                    */
  SVCall_IRQn                 = -5,     /*!< 11 Cortex-M3 SV Call Interrupt                       */
  DebugMonitor_IRQn           = -4,     /*!< 12 Cortex-M3 Debug Monitor Interrupt                 */
  PendSV_IRQn                 = -2,     /*!< 14 Cortex-M3 Pend SV Interrupt                       */
  SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt                   */

/******  STM32 specific Interrupt Numbers *********************************************************/
  WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt                            */
  PVD_IRQn                    = 1,      /*!< PVD through EXTI Line detection Interrupt            */
  TAMPER_IRQn                 = 2,      /*!< Tamper Interrupt                                     */
  RTC_IRQn                    = 3,      /*!< RTC global Interrupt                                 */
  FLASH_IRQn                  = 4,      /*!< FLASH global Interrupt                               */
  RCC_IRQn                    = 5,      /*!< RCC global Interrupt                                 */
  EXTI0_IRQn                  = 6,      /*!< EXTI Line0 Interrupt                                 */
  EXTI1_IRQn                  = 7,      /*!< EXTI Line1 Interrupt                                 */
  EXTI2_IRQn                  = 8,      /*!< EXTI Line2 Interrupt                                 */
  EXTI3_IRQn                  = 9,      /*!< EXTI Line3 Interrupt                                 */
  EXTI4_IRQn                  = 10,     /*!< EXTI Line4 Interrupt                                 */
  DMA1_Channel1_IRQn          = 11,     /*!< DMA1 Channel 1 global Interrupt                      */
  DMA1_Channel2_IRQn          = 12,     /*!< DMA1 Channel 2 global Interrupt                      */
  DMA1_Channel3_IRQn          = 13,     /*!< DMA1 Channel 3 global Interrupt                      */
  DMA1_Channel4_IRQn          = 14,     /*!< DMA1 Channel 4 global Interrupt                      */
  DMA1_Channel5_IRQn          = 15,     /*!< DMA1 Channel 5 global Interrupt                      */
  DMA1_Channel6_IRQn          = 16,     /*!< DMA1 Channel 6 global Interrupt                      */
  DMA1_Channel7_IRQn          = 17,     /*!< DMA1 Channel 7 global Interrupt   
#ifdef STM32F10X_MD
  ADC1_2_IRQn                 = 18,     /*!< ADC1 and ADC2 global Interrupt                       */
  USB_HP_CAN1_TX_IRQn         = 19,     /*!< USB Device High Priority or CAN1 TX Interrupts       */
  USB_LP_CAN1_RX0_IRQn        = 20,     /*!< USB Device Low Priority or CAN1 RX0 Interrupts       */
  CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt                                   */
  CAN1_SCE_IRQn               = 22,     /*!< CAN1 SCE Interrupt                                   */
  EXTI9_5_IRQn                = 23,     /*!< External Line[9:5] Interrupts                        */
  TIM1_BRK_IRQn               = 24,     /*!< TIM1 Break Interrupt                                 */
  TIM1_UP_IRQn                = 25,     /*!< TIM1 Update Interrupt                                */
  TIM1_TRG_COM_IRQn           = 26,     /*!< TIM1 Trigger and Commutation Interrupt               */
  TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                       */
  TIM2_IRQn                   = 28,     /*!< TIM2 global Interrupt                                */
  TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                */
  TIM4_IRQn                   = 30,     /*!< TIM4 global Interrupt                                */
  I2C1_EV_IRQn                = 31,     /*!< I2C1 Event Interrupt                                 */
  I2C1_ER_IRQn                = 32,     /*!< I2C1 Error Interrupt                                 */
  I2C2_EV_IRQn                = 33,     /*!< I2C2 Event Interrupt                                 */
  I2C2_ER_IRQn                = 34,     /*!< I2C2 Error Interrupt                                 */
  SPI1_IRQn                   = 35,     /*!< SPI1 global Interrupt                                */
  SPI2_IRQn                   = 36,     /*!< SPI2 global Interrupt                                */
  USART1_IRQn                 = 37,     /*!< USART1 global Interrupt                              */
  USART2_IRQn                 = 38,     /*!< USART2 global Interrupt                              */
  USART3_IRQn                 = 39,     /*!< USART3 global Interrupt                              */
  EXTI15_10_IRQn              = 40,     /*!< External Line[15:10] Interrupts                      */
  RTCAlarm_IRQn               = 41,     /*!< RTC Alarm through EXTI Line Interrupt                */
  USBWakeUp_IRQn              = 42      /*!< USB Device WakeUp from suspend through EXTI Line Interrupt */  
#endif /* STM32F10X_MD */
} IRQn_Type;  


二、EXIT模块功能框图

EXIT模块是和GPIO这种一样的属于硬件模块外设,只不过exit的时钟一直是默认开启的

2.1 EXIT功能框图

在这里插入图片描述

  • 外部信号通过GPIO输入到边沿检测电路
  • 边沿检测电路会根据上升沿寄存器、下降沿寄存器的配置来控制边沿检测电路输出0还是1
  • 紧接着来到个或门,一个输入是外部信号(进行触发),一个输入是软件中断事件寄存器即通过软件触发的中断事件,两个都可以触发
  • 从或门出来后兵分两路,一路到与门、脉冲发生器,这一路就是事件触发器,用于检测到外部中断信号后产生一个脉冲通往其他外设而非NVIC和CPU;另一路就是中断响应了,信号通往请求挂起寄存器即设置挂起寄存器的值,相当于是触发的中断标志位,然后这个挂起寄存器和屏蔽寄存器一起通过与门通道NVIC,这里的屏蔽寄存器相当于EXIT模块里的中断流动开关,控制能否流向NVIC。NVIC应该还会有一道开关,决定是否接收外部中断信号。多层开关控制流向

2.2 EXIT寄存器

寄存器 偏移地址 复位值(默认状态)
中断屏蔽寄存器(EXTI_IMR) 0x00 屏蔽/接收来自线x上的中断请求
事件屏蔽寄存器(EXTI_EMR) 0x04 屏蔽/接收来自线x上的事件请求
上升沿触发选择寄存器(EXTI_RTSR) 0x08 禁止/允许输入线x上的上升沿触发(中断和事件)
下降沿触发选择寄存器(EXTI_FTSR) 0x0C 禁止/允许输入线x上的下降沿触发(中断和事件)
软件中断事件寄存器(EXTI_SWIER) 0x10 当该位为’0’时,写’1’将设置EXTI_PR中相应的挂起位。如果在EXTI_IMR和EXTI_EMR中允许产生该中断,则此时将产生一个中断
挂起寄存器(EXTI_PR) 0x14 当在外部中断线上发生了选择的边沿事件,该位被置’1’。在该位中写入’1’可以清除它,也可以通过改变边沿检测的极性清除

2.3 库函数

2.3.1 库函数功能
//复位即清除EXIT的配置参数,恢复为复位后的默认值
void EXTI_DeInit(void);
//将用户配置的参数写入到对应的exit寄存器里
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//用于给用户定义的配置参数结构体赋值,用户自行修改参数
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
//调用一次这个函数就能产生一次软件触发中断
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
//获取标志位状态,可获取任意指定的标志位状态,包括中断标志位
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
//清除标志位状态,可清除任意指定的标志位状态,包括中断标志位
void EXTI_ClearFlag(uint32_t EXTI_Line);
//获取中断标志位状态,可获取任意指定的中断标志位状态,常在中断服务函数里读取一次中断标志位
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//清除中断标志位状态,可清除任意指定的中断标志位状态,常在中断服务函数里清除一次中断标志位
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
  • 复位即清除EXIT的配置参数,恢复为复位后的默认值
  • 将用户配置的参数写入到对应的exit寄存器
  • 用于给用户定义的配置参数结构体赋值,用户自行修改参数
  • 调用一次这个函数就能产生一次软件触发中断
  • 获取标志位状态,可获取任意指定的标志位状态,包括中断标志位
  • 清除标志位状态,可清除任意指定的标志位状态,包括中断标志位
  • 获取中断标志位状态,可获取任意指定的中断标志位状态,常在中断服务函数里读取一次中断标志位
  • 清除中断标志位状态,可清除任意指定的中断标志位状态,常在中断服务函数里清除一次中断标志位
2.3.2 参数取值
EXTI_Linex //外部中断通道号

typedef struct
{
  uint32_t EXTI_Line;               /*!< Specifies the EXTI lines to be enabled or disabled.
                                         This parameter can be any combination of @ref EXTI_Lines */
   
  EXTIMode_TypeDef EXTI_Mode;       /*!< Specifies the mode for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  FunctionalState EXTI_LineCmd;     /*!< Specifies the new state of the selected EXTI lines.
                                         This parameter can be set either to ENABLE or DISABLE */ 
}EXTI_InitTypeDef;

三、AFIO模块功能框图

3.1 重映射功能

把外设功能映射到其他引脚或其他外设

3.1.1 一种是映射到其他外设

比如对于这个ADC1启动触发源的重映射,默认情况下ADC1的触发源之一是EXIT11而没有TIM8_TRGO,但是现在如果想要使用TIM8_TRGO作为启动转换的触发源,那么就需要设置AFIO寄存器中的ADC1_ETRGINJ_REMAP = 1,然后再去设置TIM8产生对应的事件响应即把脉冲给到ADC1ADC1那边则需要将外部触发信号选择为ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO,这样就可以通过TIM8自动触发ADC1的启动转换了
在这里插入图片描述

3.1.2 另一种常见的是映射到其他引脚

比如对于UART2引脚的重映射,默认情况下UART2使用的是PA2作RX,使用PA4作TX,如果恰好PA2和PA4被其他外设占用,又想使用UART2,那么此时就需要开启UART2的重映射,即设置AFIO寄存器USART2_REMAP = 1,这样的话PD6就当做了UART2的RX,PD7当做UART2的TX,这一组组合是不可配置更改的,就只能是PD6和PD7,而不是任意的引脚组合

在这里插入图片描述

3.2 打开外部中断通道

例如,通过AFIO寄存器可以控制让PA0引脚的外部信号进入到EXIT的0号外部中断通道,通过AFIO寄存器可以控制让PB0引脚的外部信号进入到EXIT的0号外部中断通道。

在这里插入图片描述

四、EXIT模块产生中断信号

  • 要产生中断,必须先配置好并使能中断线。根据需要的边沿检测设置2个触发寄存器,同时在中断屏蔽寄存器的相应位写’1’允许中断请求。
  • 当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位也随之被置’1’。在挂起寄存器的对应位写’1’,将清除该中断请求。
  • 如果需要产生事件,必须先配置好并使能事件线。根据需要的边沿检测通过设置2个触发寄存器,同时在事件屏蔽寄存器的相应位写’1’允许事件请求。当事件线上发生了需要的边沿时,将产生一个事件请求脉冲,对应的挂起位不被置’1’。
  • 通过在软件中断/事件寄存器写’1’,也可以通过软件产生中断/事件请求。

4.1 硬件中断选择

  • 通过下面的过程来配置20个线路做为中断源:
    ● 配置20个中断线的屏蔽位(EXTI_IMR)
    ● 配置所选中断线的触发选择位(EXTI_RTSR和EXTI_FTSR);
    ● 配置对应到外部中断控制器(EXTI)的NVIC中断通道的使能和屏蔽位,使得20个中断线中的请求可以被正确地响应。

4.2 硬件事件选择

  • 通过下面的过程,可以配置20个线路为事件源
    ● 配置20个事件线的屏蔽位(EXTI_EMR)
    ● 配置事件线的触发选择位(EXTI_RTSR和EXTI_FTSR)
    软件中断/事件的选择20个线路可以被配置成软件中断/事件线。下面是产生软件中断的过程:
    ● 配置20个中断/事件线屏蔽位(EXTI_IMR, EXTI_EMR)
    ● 设置软件中断寄存器的请求位(EXTI_SWIER)

五、外部中断使用基本流程

5.1 使用外部中断的基本流程

  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发(可以通过其他外设的动作来手动改写寄存器从而触发外部中断)

  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(比如PA0 PB0不能同时用外部中断即pin号相同的)

  • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(也就是说根据前面的NVIC结构图,可以看到EXIT是一个小模块,能够接收这些外部通道信号源)

  • 触发响应方式:中断响应/事件响应

  • 中断响应是指的检测到GPIO的外部中断信号后,GPIO产生中断信号给到NVIC

  • 事件响应是指的检测到GPIO的外部中断信号后,GPIO不产生中断信号给到NVIC而是产生一个事件信号通向其他外设而不是通向CPU,用来触发其他外设,外设之间的联合工作

  • 通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。所以说需要AFIO配合才能使用GPIO的外部中断功能

  • 通道形式,并且外部通道的5到9用的同一个通道,10到15用的也是同一个通道,节省了通往NVIC的中断通道,需要再根据标志位区分到底是哪个GPIO上的外部中断信号

5.2 外部信号路径

在这里插入图片描述

这是一条线路,需要这条线路上的所有外设都启动:
外部信号-GPIO - AFIO - EXIT - NVIC

  • RCC
    • 打开GPIO模块的时钟
    • 打开AFIO模块的时钟
    • EXIT模块的时钟默认开始,无需手动打开
    • NVIC模块的时钟默认开启,无需手动打开
  • GPIO
    • GPIO需要启动基础功能,能采集到引脚电平信号,即GPIO配置成输入
  • AFIO
    • AFIO主要有2个功能:设置重映射和打开外部中断通道使得GPIO引脚信号进入到EXIT模块,这里就需要设置AFIO的寄存器,打开对应引脚的外部中断通道
  • EXIT
    • 设置检测外部中断信号的上升沿还是下降沿
    • 设置是否触发中断响应,即产生中断信号到NVIC
    • 设置是否触发事件响应,即不产生中断而是产生脉冲到其他外设
  • NVIC
    • 设置是否打开接收某个中断通道的开关
    • 设置这个中断的优先级
    • 配置这个中断对应的中断回调函数
    • 重写中断回调函数

5.3 如何使用内部中断-RCC

	//RCC:开启所需外设的时钟 GPIO AFIO
	//exit外设 nvic内核外设的时钟是一直打开着的,不需要再手动打开了
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

5.4 如何使用内部中断-GPIO

	//GPIO:配置GPIO的引脚、参数和模式(打开选择开关)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA10  
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA11设置成输入,默认上拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA11 

5.5 如何使用内部中断-AFIO

在STM32中,AFIO主要完成两个任务:

  • 复用功能引脚重映射
  • 中断引脚选择即打开引脚的外部中断开关

在这里插入图片描述

	//AFIO:重映射功能或者打开外部中断通道
	//这个外设没有专门的库函数文件而是放在了GPIO文件里,打开AFIO开关
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource10); //PA10
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource3); //PA3

5.6 如何使用内部中断-EXIT

	//EXIT:配置EXIT模块的通道和模式(打开选择开关)
	EXTI_InitStructure.EXTI_Line = EXTI_Line10 ;//所用线路 10号对应Px10引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	EXTI_InitStructure.EXTI_Line =  EXTI_Line11;//所用线路 11号对应Px11引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);

5.7 如何使用内部中断-NVIC

	//NVIC:打开对应的中断通道、中断函数和中断优先级
	//中断函数或者说通道号IRQn_Type ,是枚举类型,表示的是一个中断号
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}

//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI3_IRQHandler(void)
{
	
	if(SET == EXTI_GetITStatus(EXTI_Line3))
	{
		exit_cnt_key++;
	}
	EXTI_ClearITPendingBit(EXTI_Line3);
	
}

六、测试验证

中断和主程序里不要对同一个硬件操作,除非有保护

6.1 基础功能

识别外部中断信号并触发中断回调函数,一路打开对应外设的开关和设置参数、中断:测量串口RX引脚的下降沿个数和按键的上升沿个数

uint32_t exit_cnt_rx = 0, exit_cnt_key = 0;

//测量串口接收引脚的下降沿和识别按键上升沿
void Exit_Test()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	//// 各个模块管理各自的【配置参数】和【打开必要的开关】
	
	//RCC:开启所需外设的时钟 GPIO AFIO
	//exit外设 nvic内核外设的时钟是一直打开着的,不需要再手动打开了
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	//GPIO:配置GPIO的引脚、参数和模式(打开选择开关)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA10  
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA3设置成输入,默认上拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA3 
	
	//AFIO:重映射功能或者打开外部中断通道
	//这个外设没有专门的库函数文件而是放在了GPIO文件里,打开AFIO开关
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource10); //PA10
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource3); //PA3
	
	//EXIT:配置EXIT模块的通道和模式(打开选择开关)
	EXTI_InitStructure.EXTI_Line = EXTI_Line10 ;//所用线路 10号对应Px10引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	EXTI_InitStructure.EXTI_Line =  EXTI_Line3;//所用线路 3号对应Px3引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	//NVIC:打开对应的中断通道、中断函数和中断优先级
	//中断函数或者说通道号IRQn_Type ,是枚举类型,表示的是一个中断号
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
		
}
//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}

//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI3_IRQHandler(void)
{
	
	if(SET == EXTI_GetITStatus(EXTI_Line3))
	{
		exit_cnt_key++;
	}
	EXTI_ClearITPendingBit(EXTI_Line3);
	
}

几个要点:

  • 时刻熟悉信号流经哪些外设以及所需要的功能:RCC打开外设时钟 – GPIO输入采样 – AFIO打开外部中断通道 – EXIT配置边沿触发模式和产生中断信号 – NVIC打开中断通道并设置优先级、注册回调函数
  • EXIT的时钟默认是打开的
  • NVIC的时钟默认是打开的
  • 重新实现中断服务函数:函数名已经在启动文件里设定好

使用上位机一直下发数据给STM32,然后连接上Jlink,可以通过Jlink得到下位机程序中的全局变量的实时变化情况:exit_cnt_rx(串口接收每检测到一个下降沿就加1)和exit_cnt_key(按键每按下一次就加1)
在这里插入图片描述

在这里插入图片描述

6.2 组合功能

和其他外设组合工作,典型的就是当做触发源。exit还是检测串口接收引脚的下降沿,每检测到一次就产生一个脉冲去直接触发其他外设启动,比如触发ADC开始采集。ADC则需要把触发源选择为外部中断,不过对于规则组,只能选择EXIT11通道线作为触发了,这样就调整为按键触发吧。这里的程序不够完善,因为只是触发了ADC采集,没有配置ADC的读取和ADC中断的功能。

在这里插入图片描述

配置流程
如果是一个完整的工作流程,一路需要哪些外设:
外部信号 – GPIO – AFIO – EXIT – ADC – DMA – NVIC

//测量串口接收引脚的下降沿和识别按键上升沿
void Exit_Test()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	//// 各个模块管理各自的【配置参数】和【打开必要的开关】
	
	//RCC:开启所需外设的时钟 GPIO AFIO
	//exit外设 nvic内核外设的时钟是一直打开着的,不需要再手动打开了
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

	//GPIO:配置GPIO的引脚、参数和模式(打开选择开关)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA10  
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA11设置成输入,默认上拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA11 
	
	//AFIO:重映射功能或者打开外部中断通道
	//这个外设没有专门的库函数文件而是放在了GPIO文件里,打开AFIO开关
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource10); //PA10
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource3); //PA3
	
	//EXIT:配置EXIT模块的通道和模式(打开选择开关)
	EXTI_InitStructure.EXTI_Line = EXTI_Line10 ;//所用线路 10号对应Px10引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	EXTI_InitStructure.EXTI_Line =  EXTI_Line11;//所用线路 11号对应Px11引脚
	EXTI_InitStructure.EXTI_LineCmd = DISABLE; //【关闭中断功能,选择事件响应】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;//事件响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	//NVIC:打开对应的中断通道、中断函数和中断优先级
	//中断函数或者说通道号IRQn_Type ,是枚举类型,表示的是一个中断号
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
}


//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}

区别在于:PA11配置成时间响应模式,则EXIT需要关闭中断即DISABLE,并且不需要中断回调函数以及也不需要对应的NVIC配置了。ADC那边需要配合将触发源选择为EXIT11

//ADC线路:外部信号 -- GPIO -- ADC -- NVIC -- DMA
//GPIO:开时钟,配置参数和模式(多路开关选择),配置中断
//ADC :开时钟,配置参数和模式(多路开关选择),配置中断
//DMA :开时钟,配置参数和模式(多路开关选择),配置中断
//NVIC:开时钟,配置参数和模式(多路开关选择),配置中断
void ADC_Init_Test(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef  ADC_InitStructure;
	
	//开时钟:GPIO、ADC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	//配置IO参数和模式(多路开关选择)以及中断
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	
	//配置ADC参数和模式(多路开关选择)以及中断
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_Mode =  ADC_Mode_Independent;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO;
	ADC_InitStructure.ADC_NbrOfChannel = 2;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	
	//初始化写入参数到寄存器
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
	ADC_Init(ADC1, &ADC_InitStructure);
	
	//ADC独有的规则组配置
	ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_55Cycles5);
	
	//启动命令:一些外设有上电启动指令以及一些功能需要启动一下
	ADC_Cmd(ADC1, ENABLE); // ADC上电
	ADC_ExternalTrigConvCmd(ADC1, ENABLE); //启动硬件触发
	//ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	
}

按下一次按键,才会启动一次ADC规则组转换,DR寄存器才会刷新,平时则不会启动规则组转换,DR也就不刷新
在这里插入图片描述

6.3 配合DMA

添加DMA功能

uint32_t exit_cnt_rx = 0, exit_cnt_key = 0;

//测量串口接收引脚的下降沿和识别按键上升沿
void Exit_Test()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	//// 各个模块管理各自的【配置参数】和【打开必要的开关】
	
	//RCC:开启所需外设的时钟 GPIO AFIO
	//exit外设 nvic内核外设的时钟是一直打开着的,不需要再手动打开了
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	//GPIO:配置GPIO的引脚、参数和模式(打开选择开关)
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA10  
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //PA11设置成输入,默认上拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化PA11 
	
	//AFIO:重映射功能或者打开外部中断通道
	//这个外设没有专门的库函数文件而是放在了GPIO文件里,打开AFIO开关
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource10); //PA10
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA , GPIO_PinSource3); //PA3
	
	//EXIT:配置EXIT模块的通道和模式(打开选择开关)
	EXTI_InitStructure.EXTI_Line = EXTI_Line10 ;//所用线路 10号对应Px10引脚
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; //【开启中断功能】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	EXTI_InitStructure.EXTI_Line =  EXTI_Line11;//所用线路 11号对应Px11引脚
	EXTI_InitStructure.EXTI_LineCmd = DISABLE; //【关闭中断功能,选择事件响应】
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;//事件响应模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	//NVIC:打开对应的中断通道、中断函数和中断优先级
	//中断函数或者说通道号IRQn_Type ,是枚举类型,表示的是一个中断号
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//中断的通道号是IRQn_Type类型
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
//	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//中断的通道号是IRQn_Type类型
//	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//开启中断,打开通道开关允许进入CPU
//	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
//	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;//响应优先级
//	NVIC_Init(&NVIC_InitStructure);
		
}


//重新实现中断服务函数:函数名已经在启动文件里设定好
void EXTI15_10_IRQHandler(void)
{
	//可以读取指定外部通道线的中断标志
	if(SET == EXTI_GetITStatus(EXTI_Line10))
	{
		exit_cnt_rx++;
	}
	EXTI_ClearITPendingBit(EXTI_Line10);//清除中断标志
	
}

////重新实现中断服务函数:函数名已经在启动文件里设定好
//void EXTI3_IRQHandler(void)
//{
//	
//	if(SET == EXTI_GetITStatus(EXTI_Line3))
//	{
//		exit_cnt_key++;
//	}
//	EXTI_ClearITPendingBit(EXTI_Line3);
//	
//}
uint16_t AD_Value[4];
//ADC线路:外部信号 -- GPIO -- ADC -- NVIC -- DMA
//GPIO:开时钟,配置参数和模式(多路开关选择),配置中断
//ADC :开时钟,配置参数和模式(多路开关选择),配置中断
//DMA :开时钟,配置参数和模式(多路开关选择),配置中断
//NVIC:开时钟,配置参数和模式(多路开关选择),配置中断
void ADC_Init_Test(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef  ADC_InitStructure;
	DMA_InitTypeDef  DMA_InitStructure;
	
	//开时钟:GPIO、ADC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	//配置IO参数和模式(多路开关选择)以及中断
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	
	//配置ADC参数和模式(多路开关选择)以及中断
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_Mode =  ADC_Mode_Independent;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //DISABLE
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO; //  ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO
	ADC_InitStructure.ADC_NbrOfChannel = 2;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	
	//ADC可使用DMA1的通道1
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //外设到存储
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_BufferSize = 2;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//increase
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//increase
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	
	
	//初始化写入参数到寄存器
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
	ADC_Init(ADC1, &ADC_InitStructure);
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	//ADC独有的规则组配置
	ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_55Cycles5);
	
	//启动命令:一些外设有上电启动指令以及一些功能需要启动一下
	ADC_Cmd(ADC1, ENABLE); // ADC上电
	ADC_ExternalTrigConvCmd(ADC1, ENABLE); //启动硬件触发
	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
	DMA_Cmd(DMA1_Channel1, ENABLE);		
	//ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	

	
}

按下一次按键,才会启动一次ADC规则组转换,DR寄存器才会刷新,并且会搬运到AD_Value数组中,数据内容也刷新,平时则不会启动规则组转换,DR也就不刷新,AD_Value数组也不刷新

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐