STM32基础--中断应用

本文章里面假设中断就是异常,不做区分。

异常类型

F103 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中系统异常有 8 个(如果把 Reset 和 HardFault 也算上的话就是 10 个),外部中断有 60 个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。有关具体的系统异常和外部中断可在标准库文件 stm32f10x.h 这个头文件查询到,在 IRQn_Type 这个结构体里面包含了 F103 系列全部的异常声明。
F103 系统异常清单

编号优先级优先级类型名称说明地址
---保留(实际存的是MSP地址)0X0000 0000
-3固定Reset复位0X0000 0004
-2固定NMI不可屏蔽中断,RCC时钟安全系统(CSS)连接到NMI向量0X0000 0008
-1固定HardFault所有类型的错误0X0000 000C
0可编程MenManage存储器管理0X0000 0010
1可编程BusFault预取指失败,存储器访问失败0X0000 0014
2可编程UsageFault未定义的指令或非法状态0X0000 0018
---保留0x0000 001C - 0X0000 002B
3可编程SVCall通过SWI指令调用的系统服务0X0000 002C
4可编程Debug Monitor调试监控器0X0000 0030
---保留0x0000 0034
5可编程PendSV可挂起的系统任务0X0000 0038
6可编程SysTick系统滴答定时器0X0000 003C

F103 外部中断清单

编号优先级优先级类型名称说明地址
07可编程WWDG窗口看门狗中断0X0000 0040
…………其他的参与《STM32中文参考手册》第九章………………
5966可编程DMS2通道4_5DMS2通道4和通道5中断0X0000 012C

NVIC 简介

在讲如何配置中断优先级之前,我们需要先了解下 NVIC。NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC 是 Cortex-M 3 的 NVIC 的一个子集。

NVIC 寄存器简介

在固件库中,NVIC 的结构体定义可谓是颇有远虑,给每个寄存器都预留了很多位,恐怕为的是日后扩展功能。不过 STM32F103 可用不了这么多,只是用了部分而已,具体使用了多少可参考《Cortex-M3 内核编程手册》。

typedef struct {
__IO uint32_t ISER[8];// 中断使能寄存器
uint32_t RESERVED0[24];
__IO uint32_t ICER[8];// 中断清除寄存器
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8];// 中断使能悬起寄存器
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8];// 中断清除悬起寄存器
uint32_t RESERVED3[24];
__IO uint32_t IABR[8];// 中断有效位寄存器
uint32_t RESERVED4[56];
__IO uint8_tIP[240];// 中断优先级寄存器 (8Bit wide)
uint32_t RESERVED5[644];
__Ouint32_t STIR;// 软件触发中断寄存器
}NVIC_Type;

在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER 用来失能中断,IP 用来设置中断优先级。

NVIC 中断配置固件库

固件库文件 core_cm3.h 的最后,还提供了 NVIC 的一些函数,这些函数遵循 CMSIS 规则,只要是 Cortex-M3 的处理器都可以使用,具体如下:

NVIC库函数描述
void NVIC_EnableIRQ(IRQn_Type IRQn)使能中断
void NVIC_DisableIRQ(IRQn_Type IRQn)失能中断
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)设置中断悬起位
void NVIC_SetPendingIRQ(IRQn_Type IRQn)清除中断悬起位
void NVIC_ClearPendingIRQ(IRQn_Type IRQn)获取悬起中断编号
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)设置中断优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn)获取中断优先级
void NVIC_SystemReset(void)系统复位

这些库函数我们在编程的时候用的都比较少,甚至基本都不用。在配置中断的时候我们还有更简洁的方法。

优先级的定义

优先级定义

在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级,IPR宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数减少,在 F103 中,只使用了高 4bit,如下所示:

在这里插入图片描述

用于表达优先级的这 4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。

优先级分组

优先级的分组由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的 PRIGROUP[10:8] 位决定,F103 分为了 5 组,具体如下:主优先级 = 抢占优先级
在这里插入图片描述

设置优先级分组可调用库函数 NVIC_PriorityGroupConfig() 实现,有关 NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中。

/**
* 配置中断优先级分组:抢占优先级和子优先级
* 形参如下:
* @arg NVIC_PriorityGroup_0: 0bit for 抢占优先级
*4 bits for 子优先级
* @arg NVIC_PriorityGroup_1: 1 bit for 抢占优先级
*3 bits for 子优先级
* @arg NVIC_PriorityGroup_2: 2 bit for 抢占优先级
*2 bits for 子优先级
* @arg NVIC_PriorityGroup_3: 3 bit for 抢占优先级
*1 bits for 子优先
* @arg NVIC_PriorityGroup_4: 4 bit for 抢占优先级
*0 bits for 子优先级
* @ 注意 如果优先级分组为 0,则抢占优先级就不存在,优先级就全部由子优先级控制
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
// 设置优先级分组
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
优先级分组主优先级子优先级描述
NVIC_PriorityGroup_000-15主-0bit,子-4bit
NVIC_PriorityGroup_10-10-7主-1bit,子-3bit
NVIC_PriorityGroup_20-30-3主-2bit,子-2bit
NVIC_PriorityGroup_30-70-1主-3bit,子-1bit
NVIC_PriorityGroup_40-150主-4bit,子-0bit

中断编程

在配置每个中断的时候一般有 3 个编程要点:

  1. 使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。

  2. 初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。

typedef struct {
uint8_t NVIC_IRQChannel;// 中断源
uint8_t NVIC_IRQChannelPreemptionPriority;// 抢占优先级
uint8_t NVIC_IRQChannelSubPriority;// 子优先级
FunctionalState NVIC_IRQChannelCmd;// 中断使能或者失能
} NVIC_InitTypeDef;

有关 NVIC 初始化结构体的成员我们一一解释下:
a. NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序也不会报错,只会导致不响应中断。具体的成员配置可参考 stm32f10x.h 头文件里面的 IRQn_Type结构体定义,这个结构体包含了所有的中断源。

typedef enum IRQn
{
//Cortex-M3 处理器异常编号
/******  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                      */
  
  // 限于篇幅,中间部分代码省略,具体的可查看库文件 stm32f10x.h
  
    DMA2_Channel1_IRQn          = 56,     /*!< DMA2 Channel 1 global Interrupt                      */
  DMA2_Channel2_IRQn          = 57,     /*!< DMA2 Channel 2 global Interrupt                      */
  DMA2_Channel3_IRQn          = 58,     /*!< DMA2 Channel 3 global Interrupt                      */
  DMA2_Channel4_5_IRQn        = 59      /*!< DMA2 Channel 4 and Channel 5 global Interrupt        */
} IRQn_Type;

b. NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 优先级分组真值表。
c. NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 优先级分组真值表。
d. NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是 NVIC_ISER和 NVIC_ICER 这两个寄存器。

  1. 编写中断服务函数
    在启动文件 startup_stm32f10x_hd.s 中我们预先为每个中断都写了一个中断服务函数,只是这些中断函数都是为空,为的只是初始化中断向量表。实际的中断服务函数都需要我们重新编写,为了方便管理我们把中断服务函数统一写在 stm32f10x_it.c 这个库文件中。关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,如果写错,系统就在中断向量表中找不到中断服务函数的入口,直接跳转到启动文件里面预先写好的空函数,并且在里面无限循环,实现不了中断。

补充

后 续 其 他 例 程 的 某 个 外 设 配 置 过 程 的 自 定 义 函 数 如 NVIC_Configuration() 分 别 执 行 了NVIC_PriorityGroupConfig() 配置中断优先级分组,NVIC_Init(&NVIC_InitStructure) 配置结构体赋值的将要设置的中断向量和其优先级,注意这里的 NVIC_PriorityGroupConfig 是整个程序中只需要设置一次。

当设置好了中断优先级分组,其他各种外设对应的中断向量的中断优先级既是基于目前设置分组来解读,假设配置为 NVIC_PriorityGroup_0 或者 NVIC_PriorityGroup_4,那么给多个外设分别填充 NVIC_InitStructure 的子优先级或主优先级是无效的,所以说如果工程里面有用到许多的外设中断,那么在确定了想用哪一种优先级分组后再给每个外设对应的中断向量配置优先级。在其他例程中很多将 NVIC_PriorityGroupConfig 写在了每个外设自己的中断配置函数里面,有些可能多个外设配置函数下重复了 NVIC_PriorityGroupConfig() 这句,这里提醒用户后续编写自己程序时只需要调用一次即可,若重复调用相当于对中断相关寄存器重复赋值多次取最后一次赋值,并且从代码布局逻辑来说,NVIC_PriorityGroupConfig 适合放在 main() 函数中。

EXTI—外部中断/事件控制器

EXTI 简介

EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。

EXTI 功能框图

EXTI 的功能框图包含了 EXTI 最核心内容,掌握了功能框图,对 EXTI 就有一个整体的把握,在编程时思路就非常清晰。EXTI 功能框图见下图。
在图中可以看到很多在信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他 19 个线路原理也就知道了。
在这里插入图片描述

EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。

红色
首先我们来看图中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内。

编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输入线一般是存在电平变化的信号。

编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器 (EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而EXTI_RTSR 和EXTI_FTSR 两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。

编号 3 电路实际就是一个或门电路,它的一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器 (EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号1 就可以输出 1 给编号 4 和编号 6 电路。

编号 4 电路是一个与门电路,它的一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果 EXTI_IMR设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR) 内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。

编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。

绿色
接下来我们来看看绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。

产生事件线路是在编号 3 电路之后与中断线路有所不同,之前电路都是共用的。编号 6 电路是一个与门,它的一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器 (EXTI_EMR)。如果EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。

编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。

编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。

产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。

中断/事件线

EXTI 有 20 个中断/事件线,每个 GPIO 都可以被设置为输入线,占用 EXTI0 至 EXTI15,还有另外七根用于特定的外设事件,见表。
4 根特定外设中断/事件线由外设触发,具体用法参考《STM32F10X-中文参考手册》中对外设的具体说明。

中断/事件线输入源
EXTI0PX0(X可以为ABCDEFGHT)
EXTI1PX1(X可以为ABCDEFGHT)
EXTI2PX2(X可以为ABCDEFGHT)
EXTI3PX3(X可以为ABCDEFGHT)
EXTI4PX4(X可以为ABCDEFGHT)
EXTI5PX5(X可以为ABCDEFGHT)
EXTI6PX6(X可以为ABCDEFGHT)
EXTI7PX7(X可以为ABCDEFGHT)
EXTI8PX8(X可以为ABCDEFGHT)
EXTI9PX9(X可以为ABCDEFGHT)
EXTI10PX10(X可以为ABCDEFGHT)
EXTI11PX11(X可以为ABCDEFGHT)
EXTI12PX12(X可以为ABCDEFGHT)
EXTI13PX13(X可以为ABCDEFGHT)
EXTI14PX14(X可以为ABCDEFGHT)
EXTI15PX15(X可以为ABCDEFGHT)
EXTI16PVD输出
EXTI17RTC闹钟事件
EXTI18USB唤醒事件
EXTI19以太网唤醒事件(只适用互联型)

EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。由表可知,EXTI0 可以通过 AFIO 的外部中断配置寄存器 1(AFIO_EXTICR1) 的EXTI0[3:0] 位选择配置为 PA0、PB0、PC0、PD0、PE0、PF0、PG0、PH0 或者 PI0,见图 。其他 EXTI 线 (EXTI 中断/事件线) 使用配置都是类似的。

在这里插入图片描述

EXTI 初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体,比如 EXTI_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init() 调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。

初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f10x_exti.h 文件中,初始化库函数定义在 stm32f10x_exti.c 文件中,编程时我们可以结合这两个文件内注释使用。

typedef struct
{
 uint32_t EXTI_Line;        // 中断/事件线   
 EXTIMode_TypeDef EXTI_Mode;      // EXTI 模式
 EXTITrigger_TypeDef EXTI_Trigger;    // 触发类型
 FunctionalState EXTI_LineCmd;    	// EXTI 使能
}EXTI_InitTypeDef;
  1. EXTI_Line:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,可参考上面的表 EXTI 中断 _ 事件线选择。
  2. EXTI_Mode:EXTI 模式选择,可选为产生中断 (EXTI_Mode_Interrupt) 或者产生事件(EXTI_Mode_Event)。
  3. EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 (EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 (EXTI_Trigger_Rising_Falling)。
  4. EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。

注:中断和事件的区别,我认为就在于中断大多数是由外部产生的,而事件则是芯片内部产生,达到一定条件则完成一件事。

外部中断控制实验

中断在嵌入式应用中占有非常重要的地位,几乎每个控制器都有中断功能。中断对保证紧急事件得到第一时间处理是非常重要的。

我们设计使用外接的按键来作为触发源,使得控制器产生中断,并在中断服务函数中实现控制RGB 彩灯的任务。

硬件设计

轻触按键在按下时会使得引脚接通,通过电路设计可以使得按下时产生电平变化,见下图。
在这里插入图片描述

软件设计

我们创建了两个文件:bsp_exti.c 和 bsp_exti.h 文件用来存放 EXTI 驱动程序及相关宏定义,中断服务函数放在 stm32f10x_it.h 文件中。(别忘了添加.h文件的路径)

编程要点
  1. 初始化用来产生中断的 GPIO;
  2. 初始化 EXTI;
  3. 配置 NVIC;
  4. 编写中断服务函数;
软件分析

按键和 EXTI 宏定义(bsp_exti.h中)

//引脚定义
#define KEY1_INT_GPIO_PORT         GPIOA
#define KEY1_INT_GPIO_CLK          (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN          GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE    GPIO_PinSource0
#define KEY1_INT_EXTI_LINE         EXTI_Line0
#define KEY1_INT_EXTI_IRQ          EXTI0_IRQn

#define KEY1_IRQHandler            EXTI0_IRQHandler


#define KEY2_INT_GPIO_PORT         GPIOC
#define KEY2_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN          GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE    GPIO_PinSource13
#define KEY2_INT_EXTI_LINE         EXTI_Line13
#define KEY2_INT_EXTI_IRQ          EXTI15_10_IRQn

#define KEY2_IRQHandler            EXTI15_10_IRQHandler

(注意了EXIT0-4的中断源和5-9不一样,10-15又和其他的不一样具体查找stm32f10x.h)

使用宏定义方法指定与硬件电路设计相关配置,这对于程序移植或升级非常有用的。
在上面的宏定义中,我们除了开 GPIO 的端口时钟外,我们还打开了 AFIO 的时钟,这是因为等下配置 EXTI 信号源的时候需要用到 AFIO 的外部中断控制寄存器 AFIO_EXTICRx,具体见《STM32F10X-中文参考手册》8.4 章节 AFIO 寄存器描述。

EXTI 中断配置(bsp_exti.c中)

void EXTI_Key_Config(void)
{
		GPIO_InitTypeDef	GPIO_InitStructure;
		EXTI_InitTypeDef 	EXTI_InitStructure;
	
	
		/*开启按键GPIO口的时钟*/
		RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
		RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK,ENABLE);	
	
		/* 配置 NVIC 中断*/
		NVIC_Configuration();
/*--------------------------KEY1配置-----------------------------*/
	/* 选择按键用到的GPIO */	
  GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
  /* 配置为浮空输入 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
  GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 上升沿中断 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  /* 使能中断 */	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
  /*--------------------------KEY2配置-----------------------------*/
	/* 选择按键用到的GPIO */	
  GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
  /* 配置为浮空输入 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
  GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 下降沿中断 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  /* 使能中断 */	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
		
	
}

首先,使用 GPIO_InitTypeDef 和 EXTI_InitTypeDef 结构体定义两个用于 GPIO 和 EXTI 初始化配置的变量,关于这两个结构体前面都已经做了详细的讲解。

使用 GPIO 之前必须开启 GPIO 端口的时钟;用到 EXTI 必须开启 AFIO 时钟。调用 NVIC_Configuration 函数完成对按键 1、按键 2 优先级配置并使能中断通道。作为中断/事件输入线时需把 GPIO 配置为输入模式,具体为浮空输入,由外部电路完全决定引脚
的状态。

GPIO_EXTILineConfig 函数用来指定中断/事件线的输入源,它实际是设定外部中断配置寄存器的AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定 GPIO 端口源,第二个参数为选择对应 GPIO 引脚源编号。

我们的目的是产生中断,执行中断服务函数,EXTI 选择中断模式,按键 1 使用上升沿触发方式,并使能 EXTI 线。

按键 2 基本上采用与按键 1 相关参数配置,只是改为下降沿触发方式。

两个按键的电路是一样的,可代码中我们设置按键 1 是上升沿中断,按键 2 是下降沿中断,有人就会问这是不是设置错了?实际上可以这么理解,按键 1 检测的是按键按下的状态,按键 2 检测的是按键弹开的状态,那这样就解释的通了。

嵌套向量中断控制器 NVIC 配置(bsp_exti.c中)(放在中断配置之前,或者记得声明)

/**
 * @brief  配置嵌套向量中断控制器NVIC,NVIC_PriorityGroup_1,主优先级0-1,副优先级0-7
 * @param  无
 * @retval 无
 */
static void NVIC_Configuration(void)
{
 NVIC_InitTypeDef NVIC_InitStructure;
 
 /* 配置NVIC为优先级组1 */
   /* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
 
 /* 配置中断源:按键1 */
 NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
 /* 配置抢占优先级 */
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 /* 配置子优先级 */
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 /* 使能中断通道 */
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);
 
 /* 配置中断源:按键2,其他使用上面相关配置 */  
 NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
 NVIC_Init(&NVIC_InitStructure);
}

这里我们配置两个的中断软件优先级一样,如果出现了两个按键同时按下的情况,那怎么办,到底该执行哪一个中断?当两个中断的软件优先级一样的时候,中断来临时,具体先执行哪个中断服务函数由硬件的中断编号决定,编号越小,优先级越高。有关外设的硬件编号可查询《STM32F10X-中文参考手册》的中断和事件章节中的向量表,表中的位置编号即是每个外设的硬件中断优先级。当然,我们也可以把抢占优先级设置成一样,子优先级设置成不一样,这样就可以区别两个按键同时按下的情况,而不用硬件去对比硬件编号。

EXTI 中断服务函数(stm32f10x_it.c中名字一定要对,不行就查stm32f10x.h有名字)

/**
  * @brief  按键1中断处理函数
  * @param  None
  * @retval None
  */
void KEY1_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 中断
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) == SET){//上升沿
		//再次检查信号是否来自KEY1
		if(GPIO_ReadInputDataBit(KEY1_INT_GPIO_PORT,KEY1_INT_GPIO_PIN)){
			// LED1 取反
			LED1_TOGGLE;
			//清除中断标志
			EXTI_ClearFlag(KEY1_INT_EXTI_LINE);
		}
	}
}
/**
  * @brief  按键2中断处理函数
  * @param  None
  * @retval None
  */
void KEY2_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 中断
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) == SET){//下降沿
		//再次检查信号是否来自KEY2
		if(GPIO_ReadInputDataBit(KEY2_INT_GPIO_PORT,KEY2_INT_GPIO_PIN) == RESET){
			// LED2 取反
			LED2_TOGGLE;
			//清除中断标志位
			EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
		}

	}
}

当中断发生时,对应的中断服务函数就会被执行,我们可以在中断服务函数实现一些控制。

一般为确保中断确实发生,我们会在中断服务函数中调用中断标志位状态读取函数读取外设中断标志位并判断标志位状态。

EXTI_GetITStatus 函数用来获取 EXTI 的中断标志位状态,如果 EXTI 线有中断发生函数返回“SET”否则返回“RESET”。实际上,EXTI_GetITStatus 函数是通过读取 EXTI_PR 寄存器值来判断 EXTI 线状态的。按键 1 的中断服务函数我们让 LED1 翻转其状态,按键 2 的中断服务函数我们让 LED2 翻转其状态。

执行任务后需要调用EXTI_ClearITPendingBit或者EXTI_ClearFlag 函数清除 EXTI 线的中断标志位。

测试函数(bsp_exti.c中)
/**
  * @brief  外部中断测试函数,每次按下,小灯变换颜色
  * @param  无
  * @retval 无
  */
	
void EXTI_Key_Test(void)
{
	/* LED 端口初始化 */
	LED_GPIO_Config();
	  	
	/* 初始化EXTI中断,按下按键会触发中断,
  *  触发中断会进入stm32f4xx_it.c文件中的函数
	*  KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。
	*/
	EXTI_Key_Config(); 
	
	/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
	while(1);                            
}

测试函数非常简单,只有两个任务函数。LED_GPIO_Config 函数定义在 bsp_led.c 文件内,完成 RGB彩灯的 GPIO 初始化配置。EXTI_Key_Config 函数完成两个按键的 GPIO 和 EXTI 配置。

验证

保证开发板相关硬件连接正确,把编译好的程序下载到开发板。此时 RGB 彩色灯是暗的,如果我们按下开发板上的按键 1,RGB 彩灯变亮,再按下按键 1,RGB 彩灯又变暗;如果我们按下开发板上的按键 2 并弹开,RGB 彩灯变亮,再按下开发板上的 KEY2 并弹开,RGB 彩灯又变暗。按键按下表示上升沿,按键弹开表示下降沿,这跟我们软件设置是一样的。

补充说明

关于使用外部按键中断的理解补充,按教程与参考手册的中断/事件线与输入源的表格看,可以使用 EXTI0 至 15 线路对应到使用 16 个 GPIO 做外部中断,需要注意的是这 16 个线路并非都有单独的中断源,从 stm32f10x.h 查看对应芯片型号有 EXTI0_IRQn、EXTI1_IRQn、EXTI2_IRQn、EXTI3_IRQn、EXTI4_IRQn、EXTI9_5_IRQn、EXTI15_10_IRQn,再查看启动文件那边定义的中断函数同样也是EXTI0_IRQHandler、EXTI1_IRQHandler、EXTI2_IRQHandler、EXTI3_IRQHandler、EXTI4_IRQHandler、EXTI9_5_IRQHandler、EXTI15_10_IRQHandler。

除了前面 4 个线路有单独的中断函数,后面 5 至 9 和 10 至 15 线路使用 复用的思想,思考区分出什么是可以唯一标识的,什么是复用的,EXTI_Lines 在寄存器中都是一一对应状态标位, 中断函数复用,因此在 EXTI9_5_IRQHandler 和 EXTI15_10_IRQHandler 的中断函数里面使用多次EXTI_GetITStatus 函数来判断出线路。(也可以和我代码中一样,再判断一次是那个I/O口)。

这里再次强调一下关于 STM32 中断应用概览章节的补充说明内容,NVIC_PriorityGroupConfig是整个程序中只需要设置一次,当设置好了中断优先级分组,其他各种外设对应的中断向量的中断优先级既是基于目前设置分组来解读。

在其他例程中很多将 NVIC_PriorityGroupConfig 写在了每个外设自己的中断配置函数里面,有些可能多个外设配置函数下重复了 NVIC_PriorityGroupConfig() 这句,这里提醒用户后续编写自己程序时只需要调用一次即可,并且从代码布局逻辑来说适合放在 main() 函数中。

代码

bsp_exti.c

/**
 * *****************************************************************************
 * @file        bsp_exti.c
 * @brief     	外部中断点亮或者熄灭led灯
 * @author       (六千里)
 * @date        2024-03-12
 * @copyright   无
 * *****************************************************************************
 */
#include "bsp_exti.h"


 /**
  * @brief  配置嵌套向量中断控制器NVIC,NVIC_PriorityGroup_1,主优先级0-1,副优先级0-7
  * @param  无
  * @retval 无
  */
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 配置NVIC为优先级组1 */
	/* 提示 NVIC_PriorityGroupConfig() 在整个工程只需要调用一次来配置优先级分组*/
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中断源:按键1 */
  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
  /* 配置抢占优先级 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  /* 配置子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断通道 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  /* 配置中断源:按键2,其他使用上面相关配置 */  
  NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
  NVIC_Init(&NVIC_InitStructure);
}


 /**
  * @brief  初始化PA0和PC13 配置 IO为EXTI中断口,并设置中断优先级
  * @note  None
  * @param  None
  * @retval None
  */

void EXTI_Key_Config(void)
{
		GPIO_InitTypeDef	GPIO_InitStructure;
		EXTI_InitTypeDef 	EXTI_InitStructure;
	
	
		/*开启按键GPIO口的时钟*/
		RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
		RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK,ENABLE);	
	
		/* 配置 NVIC 中断*/
		NVIC_Configuration();
/*--------------------------KEY1配置-----------------------------*/
	/* 选择按键用到的GPIO */	
  GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
  /* 配置为浮空输入 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
  GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 上升沿中断 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  /* 使能中断 */	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
	
  /*--------------------------KEY2配置-----------------------------*/
	/* 选择按键用到的GPIO */	
  GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
  /* 配置为浮空输入 */	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);

	/* 选择EXTI的信号源 */
  GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE); 
  EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
	
	/* EXTI为中断模式 */
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	/* 下降沿中断 */
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  /* 使能中断 */	
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
		
	
}


/**
  * @brief  外部中断测试函数,每次按下,小灯变换颜色
  * @param  无
  * @retval 无
  */
	
void EXTI_Key_Test(void)
{
	/* LED 端口初始化 */
	LED_GPIO_Config();
	  	
	/* 初始化EXTI中断,按下按键会触发中断,
  *  触发中断会进入stm32f4xx_it.c文件中的函数
	*  KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。
	*/
	EXTI_Key_Config(); 
	
	/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
	while(1);                            
}

bsp_exti.h

#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H

/**
 * *****************************************************************************
 * 包含的头文件
 * *****************************************************************************
 */
 #include "stm32f10x.h"
 
  /**
 * *****************************************************************************
 * 宏定义
 * *****************************************************************************
 */
 
 //引脚定义
#define KEY1_INT_GPIO_PORT         GPIOA
#define KEY1_INT_GPIO_CLK          (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN          GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE    GPIO_PinSource0
#define KEY1_INT_EXTI_LINE         EXTI_Line0
#define KEY1_INT_EXTI_IRQ          EXTI0_IRQn

#define KEY1_IRQHandler            EXTI0_IRQHandler


#define KEY2_INT_GPIO_PORT         GPIOC
#define KEY2_INT_GPIO_CLK          (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN          GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE   GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE    GPIO_PinSource13
#define KEY2_INT_EXTI_LINE         EXTI_Line13
#define KEY2_INT_EXTI_IRQ          EXTI15_10_IRQn

#define KEY2_IRQHandler            EXTI15_10_IRQHandler
 
 
 
  /**
 * *****************************************************************************
 * .c文件中包含的函数
 * *****************************************************************************
 */
 static void NVIC_Configuration(void);
 void EXTI_Key_Config(void);
 void EXTI_Key_Test(void);
 
 
 
 
 
 
 
#endif /*__BSP_EXTI_H*/

stm32f10x_it.c

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_it.c 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Main Interrupt Service Routines.
  *          This file provides template for all exceptions handler and 
  *          peripherals interrupt service routine.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_it.h"

/** @addtogroup STM32F10x_StdPeriph_Template
  * @{
  */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
/*            Cortex-M3 Processor Exceptions Handlers                         */
/******************************************************************************/

/**
  * @brief  This function handles NMI exception.
  * @param  None
  * @retval None
  */
void NMI_Handler(void)
{
}

/**
  * @brief  This function handles Hard Fault exception.
  * @param  None
  * @retval None
  */
void HardFault_Handler(void)
{
  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Memory Manage exception.
  * @param  None
  * @retval None
  */
void MemManage_Handler(void)
{
  /* Go to infinite loop when Memory Manage exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Bus Fault exception.
  * @param  None
  * @retval None
  */
void BusFault_Handler(void)
{
  /* Go to infinite loop when Bus Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles Usage Fault exception.
  * @param  None
  * @retval None
  */
void UsageFault_Handler(void)
{
  /* Go to infinite loop when Usage Fault exception occurs */
  while (1)
  {
  }
}

/**
  * @brief  This function handles SVCall exception.
  * @param  None
  * @retval None
  */
void SVC_Handler(void)
{
}

/**
  * @brief  This function handles Debug Monitor exception.
  * @param  None
  * @retval None
  */
void DebugMon_Handler(void)
{
}

/**
  * @brief  This function handles PendSVC exception.
  * @param  None
  * @retval None
  */
void PendSV_Handler(void)
{
}

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
}



/**
  * @brief  按键1中断处理函数
  * @param  None
  * @retval None
  */
void KEY1_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 中断
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) == SET){
		//再次检查信号是否来自KEY1,上升沿
		if(GPIO_ReadInputDataBit(KEY1_INT_GPIO_PORT,KEY1_INT_GPIO_PIN)){
			// LED1 取反
			LED1_TOGGLE;
			//清除中断标志
			EXTI_ClearFlag(KEY1_INT_EXTI_LINE);
		}
	}
}
/**
  * @brief  按键2中断处理函数
  * @param  None
  * @retval None
  */
void KEY2_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 中断
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) == SET){
		//再次检查信号是否来自KEY2,下降沿
		if(GPIO_ReadInputDataBit(KEY2_INT_GPIO_PORT,KEY2_INT_GPIO_PIN) == RESET){
			// LED2 取反
			LED2_TOGGLE;
			//清除中断标志位
			EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
		}

	}
}


/******************************************************************************/
/*                 STM32F10x Peripherals Interrupt Handlers                   */
/*  Add here the Interrupt Handler for the used peripheral(s) (PPP), for the  */
/*  available peripheral interrupt handler's name please refer to the startup */
/*  file (startup_stm32f10x_xx.s).                                            */
/******************************************************************************/

/**
  * @brief  This function handles PPP interrupt request.
  * @param  None
  * @retval None
  */
/*void PPP_IRQHandler(void)
{
}*/

/**
  * @}
  */ 


/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

stm32f10x_conf.

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h 
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Library configuration file.
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H

/* Includes ------------------------------------------------------------------*/
/* Uncomment/Comment the line below to enable/disable peripheral header file inclusion */
#include "stm32f10x_adc.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_can.h"
#include "stm32f10x_cec.h"
#include "stm32f10x_crc.h"
#include "stm32f10x_dac.h"
#include "stm32f10x_dbgmcu.h"
#include "stm32f10x_dma.h"
#include "stm32f10x_exti.h"
#include "stm32f10x_flash.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_iwdg.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_rtc.h"
#include "stm32f10x_sdio.h"
#include "stm32f10x_spi.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_wwdg.h"
#include "misc.h" /* High level functions for NVIC and SysTick (add-on to CMSIS functions) */
/**
 *自己书写文件的头文件
 **/
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_led_bitbang.h"
#include "bsp_clkconfig.h"
#include "bsp_mcooutput.h"
#include "bsp_exti.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

/* Exported macro ------------------------------------------------------------*/
#ifdef  USE_FULL_ASSERT

/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr: If expr is false, it calls assert_failed function which reports 
  *         the name of the source file and the source line number of the call 
  *         that failed. If expr is true, it returns no value.
  * @retval None
  */
  #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
  void assert_failed(uint8_t* file, uint32_t line);
#else
  #define assert_param(expr) ((void)0)
#endif /* USE_FULL_ASSERT */

#endif /* __STM32F10x_CONF_H */

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

main.c

/**
 * *****************************************************************************
 * @file        main.c
 * @brief       主函数
 * @author       (六千里)
 * @date        2024-03-10
 * @copyright   无
 * *****************************************************************************
 */
#include "stm32f10x.h"  

int main(void)	
{
	// 来到这里的时候,系统的时钟已经被配置成72M。
	
		EXTI_Key_Test();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/458438.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

百度paddleocr GPU版部署

显卡&#xff1a;NVIDIA GeForce RTX 4070&#xff0c;Nvidia驱动程序版本&#xff1a;537.13 Nvidia驱动程序能支持的最高cuda版本&#xff1a;12.2.138 Python&#xff1a;python3.10.11。试过python3.12&#xff0c;安装paddleocr失败&#xff0c;找不到相关模块。 飞桨版本…

Java优先级队列(堆)

&#x1f435;本篇文章将对优先级队列&#xff08;堆&#xff09;的相关知识进行讲解 一、优先级队列 队列是一种“先入先出”的数据结构&#xff0c;但有时操作的数据带有优先级&#xff0c;需要优先处理&#xff0c;这时普通的队列就不能满足需求。比如&#xff1a;在排队取…

《JAVA与模式》之抽象工厂模式

系列文章目录 文章目录 系列文章目录前言一、使用简单工厂模式的解决方案二、引进抽象工厂模式三、抽象工厂模式结构四、抽象工厂模式的优缺点前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

Github上哪些好用的安全工具1

专注于web漏洞挖掘、内网渗透、免杀和代码审计&#xff0c;感谢各位师傅的关注&#xff01;网安之路漫长&#xff0c;与君共勉&#xff01; URLFinder 一款快速提取网页信息的工具。该项目可以快速爬取网页上的 URL 地址、JS 文件里的 API 接口等信息&#xff0c;支持批量抓取…

推荐算法中经典排序算法GBDT+LR

文章目录 逻辑回归模型逻辑回归对于特征处理的优势逻辑回归处理特征的步骤 GBDT算法GBDTLR算法GBDT LR简单代码实现 逻辑回归模型 逻辑回归&#xff08;LR,Logistic Regression&#xff09;是一种传统机器学习分类模型&#xff0c;也是一种比较重要的非线性回归模型&#xff0…

LeetCode199题:二叉树的右视图(python3)

代码思路&#xff1a;深度优先搜索&#xff0c;每次总访问右子树&#xff0c;value_depth用dict存放&#xff0c;深度为索引&#xff0c;存放节点的值&#xff0c;stack从根节点[(root, 0)]开始&#xff0c;添加node和depth class Solution:def rightSideView(self, root: Opt…

深入理解 CSS——CSS进阶与实践(5w字高频面试题整理)

本文总结了CSS高频面试题&#xff0c;并搭配了演示动画进行CSS样式演示。介绍了关于如何理解盒模型&#xff0c;如何实现块级元素水平居中&#xff0c;如何实现两侧固定中间自适应的三栏布局、如何实现两栏布局&#xff0c;如何进行响应式设计&#xff0c;对BFC的理解&#xff…

04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

SpringAMQP 概述 使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发 SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息 AMQP(Advanced Message Queuing Portocol)是用于在应用程序…

【危化品泄漏源定位】基于改进哈里斯鹰优化算法的危化品泄漏源定位算法 溯源定位算法【Matlab代码#63】

文章目录 【获取资源请见文章第7节&#xff1a;资源获取】1. 算法概述2. 原始哈里斯鹰算法&#xff08;HHO&#xff09;3. 改进哈里斯鹰算法&#xff08;IHHO&#xff09;3.1 动态自适应逃逸能量3.2 动态扰动策略 4. 构建源强和位置反算模型5. 部分代码展示6. 仿真结果展示7. 资…

牛-迈面试题----答案/类似题/知识点

来源在这里 来源在这里 1.Redis的优势 (1) 速度快&#xff0c;因为数据存在内存中&#xff0c;类似于HashMap&#xff0c;HashMap的优势就是查找和操作的时间复杂度都很低 (2)支持丰富数据类型&#xff0c;支持string&#xff0c;list&#xff0c;set&#xff0c;sorted set&…

Linux之线程互斥

目录 一、问题引入 二、线程互斥 1、相关概念 2、加锁保护 1、静态分配 2、动态分配 3、锁的原理 4、死锁 三、可重入与线程安全 1、概念 2、常见的线程不安全的情况 3、常见的线程安全的情况 4、常见不可重入的情况 5、常见可重入的情况 6、可重入与线程安全联系…

idea 导入项目

idea 导入项目并运行 导入设置设置 jdk查看maven 设置 导入 在项目首页 或者 file 选择 open, 然后选择项目根路径 设置 设置 jdk 查看maven 设置

Linux基础命令[18]-whoami

文章目录 1. whoami 命令说明2. whoami 命令语法3. whoami 命令示例4. 总结 1. whoami 命令说明 whoami&#xff1a;用于显示当前用户名&#xff0c;功能与 id -un 相同。基本信息如下&#xff1a; Usage: whoami [OPTION]... Print the user name associated with the curre…

数码管动态扫描显示

摸鱼记录 Day_16 (&#xff9f;O&#xff9f;) review 前边已经学习了&#xff1a; 串口接收&#xff1a;Vivado 串口接收优化-CSDN博客 1. 今日摸鱼任务 串口接收数据 并用数码管显示 (&#xff9f;O&#xff9f;) 小梅哥视频&#xff1a; 17A 数码管段码显示与动态扫…

ES6(一):let和const、模板字符串、函数默认值、剩余参数、扩展运算符、箭头函数

一、let和const声明变量 1.let没有变量提升&#xff0c;把let放下面打印不出来&#xff0c;放上面可以 <script>console.log(a);let a1;</script> 2.let是一个块级作用域,花括号里面声明的变量外面找不到 <script>console.log(b);if(true){let b1;}//und…

matlab 基操~

MATLAB基本操作 1. 对象定义 使用sym定义单个对象、使用syms定义多个对象 2. 使用limit求极限 $$ \lim_{v \rightarrow a} f(x) $$ limit(f,v,a) % 使用limit(f,v,a,left)可求左极限 3. 导数 使用diff(f,v,n)对$ f(v)v^{t-1} $求 $ n $ 阶导 $ \frac{d^nf}{d^nv} $&#xf…

【蓝桥杯-单片机】基础模块:数码管

文章目录 【蓝桥杯-单片机】基础模块&#xff1a;数码管01 数码管原理图什么是位选和段选共阳极数码管和共阴极数码管的区分&#xff08;1&#xff09;共阳极数码管&#xff08;Common Anode&#xff09;&#xff1a;&#xff08;2&#xff09;共阴极数码管&#xff08;Common …

C语言中大小写字母如何转化

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的…

MS08-067 漏洞利用与安全加固

文章目录 环境说明1 MS08_067 简介2 MS08_067 复现过程3 MS08_067 安全加固 环境说明 渗透机操作系统&#xff1a;2024.1漏洞复现操作系统: Windows XP Professional with Service Pack 2- VL (English)安全加固复现操作系统&#xff1a;Windows XP Professional with Service …