基础概念弄懂了之后,对于实际开发中断初始化流程和寄存器查阅是很重要的。
1.EXINT介绍
2.功能描述和配置流程
中断初始化流程☆
1>选择中断源
2>选择触发方式
3>使能中断或事件
4>产生软件触发
中断清除流程
其他注意点3.EXINT寄存器描述
4.例程源码分析(初始化源码)
文末附上按键中断配置(基于AT32)、定时器中断配置(AT32、STM32)、ADC接收中断 代码及分析。
但是注意:AT32定时器中断是特定外设(比如定时器模块)自带的一种中断类型,通常不是挂在外部中断线上。
1. EXINT介绍
EXINT 共计有 25 条中断线 EXINT_LINE[28:0] (其中位 19、20、24、27 为保留位),每条中断线均支持通过边沿检测触发和s来产生中断或事件。
EXINT 可以根据软件配置,独立的使能或禁止中断或事件,并采取不同的边沿检测方式(检测上升沿或检测下降沿或同时检测上升沿和下降沿)以及触发方式(边沿检测触发或软件触发或边沿检测和软件同时触发)响应触发源独立的产生中断或事件。
EXINT 控制器的主要特性:
中断线0~15所映射的IO可以独立的配置
每个中断线都有独立的触发方式选择
每个中断都有独立的使能位
每个事件都有独立的使能位
共25个可独立产生和清除的软件触发
每个中断都有独立的状态位
每个中断都可以被独立的清除
2. 功能描述和配置流程
EXINT 共计有 25 条中断线 EXINT_LINE[28:0] (其中位 19、位 20、位 24、位 27 为保留位),可以通过边沿检测的方式分别检测来自 GPIO 的外部中断源以及包括 PVM 输出,ERTC 闹钟事件,ERTC 入侵事件和时间戳事件,ERTC 唤醒事件,OTGFS 唤醒事件,USART1/USART2/USART3 唤醒事件以及 I2C1 唤醒事件共九种芯片内部的中断源;
其中来自 GPIO 的中断源可以通过软件编程配置 SCFG 中的复用外部中断配置寄存器 x(SCFG_EXINTCx)灵活的选择,需要注意的是这些输入源是互斥的,例如EXINT_LINE0 只能选择 PA0/PB0/PC0/PD0…中的某一个,而不能同时选择 PA0 和 PB0 作为输入源。
EXINT支持多种边沿检测方式,每条中断线可以通过软件编程配置极性配置寄存器1(EXINT_POLCFG1)和极性配置寄存器 2(EXINT_POLCFG2)独立的选择上升沿检测或下降沿检测或同时进行上升沿和下降沿检测,中断线上检测到的有效边沿触发可以用于产生事件或中断。
EXINT 支持独立的软件触发产生中断或事件,即除了来自中断线上的有效边沿外,用户可以通过软件编程配置软件触发寄存器(EXINT_SWTRG)对应位来产生对应的中断或事件。
EXINT 具备独立的中断和事件使能位,用户可以通过软件编程配置中断使能寄存器(EXINT_INTEN)和事件使能寄存器(EXINT_EVTEN)来使能或关闭对应的中断或事件,这意味着无论是通过边沿检测还是软件触发产生中断或事件,都需要提前使能对应的中断或事件。
EXINT 具备独立的中断状态位,用户可以通过中断状态寄存器(EXINT_INTSTS)读取对应的中断状态并通过对该寄存器相应位写 1 来清除已置位的状态标志。
中断初始化流程☆
1> 选择中断源
即配置复用外部中断配置寄存器 x(SCFG_EXINTCx)(如果需要使用 GPIO 作为中断源需要该步骤)。
2> 选择触发方式
即配置极性配置寄存器 1(EXINT_POLCFG1)和极性配置寄存器 2(EXINT_POLCFG2)。
3> 使能中断或事件
即配置中断使能寄存器(EXINT_INTEN)和事件使能寄存器(EXINT_EVTEN)。
4> 产生软件触发
即配置软件触发寄存器(EXINT_SWTRG)产生软件触发(此步骤仅适用于需软件触发产
生中断的应用)。
中断清除流程
清除标志,即对中断状态寄存器(EXINT_INTSTS)对应位写 1 来清除已产生的中断,同时该操作会同步清除软件触发寄存器(EXINT_SWTRG)中的对应位。
其他注意点
若需要更改中断源配置,应先关闭中断使能寄存器和事件使能寄存器后,再重新开始中断初始化流程的配置。
3. EXINT寄存器描述
下表列出了 EXINT 寄存器的映像和复位值。
必须以字(32 位)的方式操作这些外设寄存器。
4. 例程源码分析(初始化源码)
按键中断
/**
* @brief configure button exint
* @param none
* @retval none
*/
void button_exint_init(void)
{
exint_init_type exint_init_struct;
crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE);
scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOA, SCFG_PINS_SOURCE0);
exint_default_para_init(&exint_init_struct);
exint_init_struct.line_enable = TRUE;
exint_init_struct.line_mode = EXINT_LINE_INTERRUPT;
exint_init_struct.line_select = EXINT_LINE_0;
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
exint_init(&exint_init_struct);
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(EXINT0_IRQn, 0, 0);
}
软件触发方式触发 ADC
/**
* @brief exint configuration.
* @param none
* @retval none
*/
static void exint_config(void)
{
gpio_init_type gpio_initstructure; //配置GPIO初始化结构体变量
exint_init_type exint_init_struct; //配置外部中断初始化结构体变量
crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE); //启用外部中断所需的时钟
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); //启用相关GPIO端口的时钟
crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE); //启用相关GPIO端口的时钟
gpio_default_para_init(&gpio_initstructure); //默认初始化GPIO配置参数结构体
/* config exint line 11 and line 15 */
gpio_initstructure.gpio_pull = GPIO_PULL_DOWN; //下拉电阻
gpio_initstructure.gpio_mode = GPIO_MODE_INPUT; //输入模式
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;//强驱动
gpio_initstructure.gpio_pins = GPIO_PINS_11;
gpio_init(GPIOC, &gpio_initstructure);
gpio_initstructure.gpio_pull = GPIO_PULL_DOWN;
gpio_initstructure.gpio_mode = GPIO_MODE_INPUT;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pins = GPIO_PINS_15;
gpio_init(GPIOA, &gpio_initstructure);
/* 配置外部中断线,指定要使用的GPIO端口和引脚 */
scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOC, SCFG_PINS_SOURCE11);
scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOA, SCFG_PINS_SOURCE15);
exint_default_para_init(&exint_init_struct); //初始化外部中断配置参数结构体
exint_init_struct.line_enable = TRUE; //使能外部中断线
exint_init_struct.line_mode = EXINT_LINE_EVENT; //设置触发模式为事件触发
exint_init_struct.line_select = EXINT_LINE_11; //选择外部中断线11作为配置的外部中断线
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE; //上升沿触发
exint_init(&exint_init_struct); //按照结构体配置初始化
/*对外部中断线15进行配置时,会继承之前的触发设置,包括上升沿触发*/
exint_init_struct.line_select = EXINT_LINE_15; //选择外部中断线15作为配置的外部中断线
exint_init(&exint_init_struct); //按照结构体配置初始化
}
//NVIC中断优先级在后续ADC和DMA初始化里面自行配置
定时器中断
这里讲一下:
定时器中断一般指定时器的计数器溢出中断;
其他常见的定时器中断类型包括:
1. 比较匹配中断:当定时器计数器的值匹配预设的比较值时,会产生比较匹配中断。
这种中断可以用于实现定时器的 PWM(脉冲宽度调制)功能或者其他需要在特定计数值触发的操作。
2. 输入捕获中断:定时器可以用于捕获外部信号的时间信息,当输入信号发生边沿变化时,可以产生输入捕获中断。
3. 输出比较中断:定时器可以用于生成输出信号,当定时器计数器的值匹配预设的比较值时,可以产生输出比较中断。
4. PWM 中断: 在使用定时器实现 PWM 输出时,可能会配置 PWM 输出比较中断,用于控制 PWM 的输出状态。
/* stm32定时器初始化 + 中断使能 */
void Tim3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器时钟
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = arr;
TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//初始化定时器
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//开启定时器中断
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStruct);//配置NVIC
TIM_Cmd(TIM3,ENABLE);//使能定时器
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == 1)//检查是否更新中断
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除更新中断标志
LED1 = !LED1;
}
}
/* AT32定时器初始化 + 生成外部中断事件 */
void exint_line4_config(void); //配置外部中断线4
static void tmr1_config(void); //定时器1初始化
//uint8_t i,num,num1 = 0;
/**
* @brief exint line4 config. configure pa0 in interrupt mode
* @param None
* @retval None
*/
void exint_line4_config(void)
{
exint_init_type exint_init_struct;
crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE); //启用外设时钟
exint_default_para_init(&exint_init_struct); //初始化外部中断配置结构
exint_init_struct.line_enable = TRUE; //启用外部中断线
exint_init_struct.line_mode = EXINT_LINE_INTERRUPT; //置外部中断模式为中断模式
exint_init_struct.line_select = EXINT_LINE_4; //选择外部中断线4
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE; //设置触发极性为上升沿触发
exint_init(&exint_init_struct); //初始化外部中断
exint_flag_clear(EXINT_LINE_4); //清除外部中断标志
nvic_irq_enable(EXINT4_IRQn, 0, 1); //使能外部中断4的 NVIC 中断
}
/**
* @brief tmr1 configuration.
* @param none
* @retval none
* 配置定时器1,使其以1Hz的频率触发溢出中断,并在溢出时执行相应的中断处理程序
*/
static void tmr1_config(void)
{
crm_clocks_freq_type crm_clocks_freq_struct = {0}; //定义并初始化系统时钟频率结构体
/* get system clock */
crm_clocks_freq_get(&crm_clocks_freq_struct); //获取系统时钟频率
crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE); //启用定时器1的外设时钟
/* (systemclock / (system_core_clock/10000)) / 10000 = 1Hz(1s) */
/* 设置定时器1的基本参数,包括计数器的重载值和预分频器的值,以实现1Hz(1秒)的定时功能 */
tmr_base_init(TMR1, 10000-1, system_core_clock/10000-1); //初始化定时器基本参数:能推出 TMR1 的时钟源为系统时钟
tmr_cnt_dir_set(TMR1, TMR_COUNT_UP); //设置定时器计数方向为向上计数
tmr_clock_source_div_set(TMR1, TMR_CLOCK_DIV1); //设置定时器时钟源分频
tmr_interrupt_enable(TMR1, TMR_OVF_INT, TRUE); //使能定时器1的溢出中断
nvic_irq_enable(TMR1_OVF_TMR10_IRQn, 0, 0); //使能定时器1的 NVIC 中断
}
/**
* @brief tmr1 interrupt handler
* @param none
* @retval none
当定时器溢出时切换LED灯状态并生成exint软件中断事件,外部中断线4触发时切换LED灯状态
*/
void TMR1_OVF_TMR10_IRQHandler(void)
{
//检查定时器溢出中断标志位是否被设置:定时中断
if(tmr_interrupt_flag_get(TMR1,TMR_OVF_FLAG) != RESET)
{
//生成exint软件中断事件(触发1s定时器中断):EXINT_LINE_4
at32_led_toggle(LED2);
// printf("1s counter: %d\r\n", num1++);
exint_software_interrupt_event_generate(EXINT_LINE_4);
tmr_flag_clear(TMR1,TMR_OVF_FLAG);
}
}
/**
* @brief exint4 interrupt handler
* @param none
* @retval none
*/
void EXINT4_IRQHandler(void)
{
//检查外部中断标志位是否被设置
if(exint_interrupt_flag_get(EXINT_LINE_4) != RESET)
{
at32_led_toggle(LED3);
at32_led_toggle(LED4);
// if(i++ == 3) //三秒一次中断
// {
// delay_us(5);
// printf("3s counter: %d\r\n", num++);
// i = 0;
// }
exint_flag_clear(EXINT_LINE_4);
}
}