AT32(三):EXINT——GPIO引脚触发外部中断

前言

本文建立在上一篇笔记 的基础之上。记录了AT32F421实现GPIO触发外部中断的过程。代码由 AT官方的BSP(此处是AT32F421固件库BSP&Pack

BSP\project\at_start_f421\examples\exint\exint_config

项目文件修改而来。主要测试AT32F4单片机的外部中断功能。

环境

  • VScode ( EIDE + Cortex Debug )
  • Open On-Chip Debugger 0.11.0+dev-snapshot
  • GCC toolchain
  • AT32F421C8T7系统板 & ATLink
  • USB < - > TTL 模块

功能描述

使用外部中断的方式,检测PF7引脚外部输入的下降沿。

中断服务程序:串口打印PF7电平值、消抖后的按键状态,LED(PC13)闪烁一次。

注意事项

关于输入引脚的设置和处理

  • gpio_default_para_init()函数将传入的GPIOx port所有引脚默认初始化为输入模式、外部推挽、无上下拉、强驱动电流。这与实际情况只有out type一点不符——我板上的USRKEY_PIN 外部只有浮空(IDLE)和下拉(PRESS_DOWN)两种状态。out type可认为是开漏(GPIO_OUTPUT_OPEN_DRAIN),因此需要内部上拉。

  • 使用GPIO作为中断源,需要配置复用外部中断配置寄存器 xIOMUX_EXINTCx)或SCFG 外部中断配置寄存器xSCFG_EXINTCx) 。但是AT32F421没有IOMUX_EXINTCx寄存器,这里调用scfg_exint_line_config()函数配置后者,需要指定GPIO中断源的端口和线号。

关于中断的初始化、设置、使能和处理

  • 配置中断源——SCFG:系统配置寄存器SCFG是挂载在APB2上的外设,一定不要忘了使能其时钟!

    crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE);
    scfg_exint_line_config(SCFG_PORT_SOURCE_USRKEY, SCFG_PINS_SOURCE_USRKEY);
  • 中断(INT)/事件(EVT)初始化——EXINT:EXINT的初始化类似GPIO,在标准库中都是传入一个结构体给初始化函数。然后使用
    exint_init(&exint_init_struct);
    初始化EXINT寄存器。具体需要指定:
    • line mode,选择是INT还是EVT。它的值关系到两个使能寄存器;
    • line select,这个指的就是中断线的号码了。来自GPIO的中断,号码就是PIN号,取值0~15之间。PF7就占用中断线line7,要注意的是,line x只能被ABCDEF端口中的一个PIN x所占用,因此这里PA7和PB7就无法再使用中断线了。它的值关系到置位EXINT寄存器的第几位。
    • line polarity,指定外部中断的有效边沿类型。
  • 设置中断优先级——SCB:用nvic_priority_group_config()设置。奇怪的是在用这个函数设置优先级时,只传入了参数NVIC_PRIORITY_GROUP_x (x=0,1,2,3,4),MCU怎么知道这是在给谁设置优先级呢?事实上确实如此,所以此函数需要配合其他函数一同使用。具体而言,nvic_priority_group_config()的底层为一个定义在core_cm4.h中的函数:
    void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
    它把NVIC_PRIORITY_GROUP_x的信息处理后存放到SCB的AIRCR(应用中断和复位控制)寄存器中。而之后在中断使能的函数nvic_irq_enable() ,它会先调用
    uint32_t __NVIC_GetPriorityGrouping(void)
    读取SCB->AIRCR的值,重新获得优先级分组的信息,再结合该使能函数传入的抢占优先级子优先级的信息合成一个32位的值,用于在底层的
    void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
    中配置中断向量IQRn的优先级。所以,真正实现优先级设置的,是nvic_irq_enable()调用的__NVIC_SetPriority()
  • 使能中断——NVIC:用nvic_irq_enable()实现对中断向量IQRn的使能。但就如上文所说,该函数首先实现的是对IQRn优先级的设置。

这么多外设和寄存器,它们的关系和作用?

GPIO是挂载在AHB上的外设,SCFG/CMP和EXINT都是APB2上的外设,而NVIC是Cotex-M内核的控制器。设置中断优先级分组由内核的SCB(System Control Block)中的AIRCR寄存器指示。

最终决定外部中断优先级的是NVIC中的IP寄存器(可被__NVIC_SetPriority()设置)。

__NVIC_SetPriority()的内容还可以看出,决定内部中断优先级的是SCB中的SHP寄存器。

使用一个GPIO外部中断需要多少“使能”?

需要2个与时钟有关的使能、2个与中断有关的使能:

使能GPIO时钟;

使能SFCG时钟;

在EXINT的INTEN寄存器中对应line x的位使能;

最后在__NVIC_EnableIRQ()中使能中断。

EXINT可以做到单独使能每一条中断线,但NVIC通常都是分组管理中断线(多个中断线共用一个中断向量,少数可以单独被NVIC控制)。

值得注意的是,EXINT作为要用到的APB2外设却不需要时钟使能。只能reset其时钟。

代码实现

几个核心文件和功能:

  • main.c
    /**
     **************************************************************************
     * @file     main.c
     * @version  v2.0.5
     * @date     2022-04-02
     * @brief    main program
     */
    
    #include "at32f421_clock.h"
    #include "systick.h"
    #include "usart.h"
    #include "usrkey_exint.h"
    
    /**
     * @brief  main function.
     * @param  none
     * @retval none
     */
    
    int main(void)
    {
        system_clock_config();
        delay_init();
    
    	// user key init
        usrkey_init();
    
        // LED init
        gpio_init_type gpio_init_struct;
        crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
        gpio_default_para_init(&gpio_init_struct);
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init_struct.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
        gpio_init_struct.gpio_mode           = GPIO_MODE_OUTPUT;
        gpio_init_struct.gpio_pins           = GPIO_PINS_13;
        gpio_init_struct.gpio_pull           = GPIO_PULL_NONE;
        gpio_init(GPIOC, &gpio_init_struct);
    
    
        // exint init
        usrkey_exint_init();
    
    	//USART init
        uart_print_init(115200);
    
        /* output a message on hyperterminal using printf function */
        printf("waiting for user key pressing down...\r\n");
    
        // LED blink
        GPIOC->odt ^= GPIO_PINS_13;
        delay_sec(1);
        GPIOC->odt ^= GPIO_PINS_13;
        delay_ms(1);
    
        printf("current User Key pin value: %d\r\n", usrkey_state());
    
        while (1)
        {
            // 轮询状态,等待中断发生
            printf("Line 7 state bit: %d \t",exint_flag_get(EXINT_LINE_USRKEY_PIN));
            printf("current User Key pin value: %d\t", usrkey_state());
            printf("is User Key IDLE: %d\r\n", is_usrkey_pressed());
            delay_ms(500);
        }
    }
  • at32f421_int.c:由于在启动文件startup_at32f421.s中,对EXINT的_IRQHandler进行了弱定义[WEAK]。在配置完void nvic_irq_enable();函数后,在工程的任意位置编写同名的中断处理函数即可(建议统一置于at32f421_int.c方便代码管理和移植)
    /**
     * @brief  exint0 interrupt handler
     * @param  none
     * @retval none
     */
    void EXINT15_4_IRQHandler(void)
    {
        usrkey_isr();
    }
  • usrkey_exint.c:定义了初始化按键、查询状态、消抖的函数。定义了中断配置函数和中端服务程序。
    #include "usrkey_exint.h"
    #include "systick.h"
    #include <stdio.h>
    
    // ===========================> 配置按键中断 <===========================
    /**
     * @brief  configure user key exint
     * @param  none
     * @retval none
     */
    void usrkey_exint_init(void)
    {
        exint_init_type exint_init_struct;
    
        crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE);
    	// 程配置外部中断配置寄存器 SCFG_EXINTCx 以指定来自 GPIO 的中断源
        scfg_exint_line_config(SCFG_PORT_SOURCE_USRKEY, SCFG_PINS_SOURCE_USRKEY);
    
        exint_default_para_init(&exint_init_struct);
        exint_init_struct.line_enable   = TRUE;
    	// 选择功能 线中断 或者 线事件
        exint_init_struct.line_mode     = EXINT_LINE_INTERRUPUT;
    	// 使能 line0 上的中断请求和事件请求
        exint_init_struct.line_select   = EXINT_LINE_USRKEY_PIN;
    	// 选择触发方式
        exint_init_struct.line_polarity = EXINT_TRIGGER_FALLING_EDGE;
    
        exint_init(&exint_init_struct);
    
        nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
        nvic_irq_enable(EXINT15_4_IRQn, 1, 0);
    }
    
    
    /**
     * @brief  user key handler function
     * @param  none
     * @retval none
     */
    void usrkey_isr(void)
    {
        /* check exint line x flag again */
        if(exint_flag_get(EXINT_LINE_USRKEY_PIN) != RESET)
        {
    		printf("An external user key interruption is detected.\r\n");
            printf("current User Key pin value: %d\t", usrkey_state());
            printf("is User Key IDLE: %d\r\n", is_usrkey_pressed());
            // LED blink
            GPIOC->odt ^= GPIO_PINS_13;
            delay_sec(1);
            GPIOC->odt ^= GPIO_PINS_13;
            delay_ms(1);
            
            /* clear interrupt pending bit */
            exint_flag_clear(EXINT_LINE_USRKEY_PIN);
        }
    }
    
    
    // ===========================> 配置用户按键 <===========================
    /**
     * @brief  configure user key gpio
     * @param  none
     * @retval none
     */
    void usrkey_init(void)
    {
        gpio_init_type gpio_init_struct;
    
        /* enable the user key gpio clock */
        crm_periph_clock_enable(USRKEY_CRM_CLK, TRUE);
    
        /* set default parameter */
        gpio_default_para_init(&gpio_init_struct);
    
        /* configure user key pin as input with pull-up/pull-down */
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init_struct.gpio_mode           = GPIO_MODE_INPUT;
        gpio_init_struct.gpio_pins           = USRKEY_PIN;
    	// 在我的板上 USRKEY_PIN 外部只有浮空(IDLE)和下拉(PRESS_DOWN)两种状态。out type可认为是开漏,因此需要内部上拉
    	gpio_init_struct.gpio_out_type       = GPIO_OUTPUT_OPEN_DRAIN;
        gpio_init_struct.gpio_pull           = GPIO_PULL_NONE;
        gpio_init(USRKEY_PORT, &gpio_init_struct);
    }
    
    /**
     * @brief  returns USRKEY_PIN value
     * @param  none
     * @retval the USRKEY_PIN value
     */
    uint8_t usrkey_state(void)
    {
        return gpio_input_data_bit_read(USRKEY_PORT, USRKEY_PIN);
    }
    
    /**
     * @brief  returns whether the user key be pressed down or not 
     * @param  none
     * @retval the uer key state
     */
    usrkey_state_type is_usrkey_pressed()
    {
        static uint8_t pressed = 0;
        /* get user key state */
        if ((pressed == 0) && (usrkey_state() == RESET))
        {
            /* debounce */
            pressed = 1;
            delay_ms(5);
            if (usrkey_state() != SET)
                return PRESS_DOWN;
        }
        else if (usrkey_state() == SET)
        {
            pressed = 0;
        }
        return IDLE;
    }
    

效果 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值