ARM5.(beep,key,中断)

1.beep


S8550,是PNP型 三极管,基极电压 高于或接近发射极 电压,基极和发射 极之间没有正向偏置电压,三极管处于截止状态。

逻辑与LED类似
beep.h:

复制代码
#ifndef _BEEP_H_
#define _BEEP_H_

extern void init_beep(void);
extern void beep_on(void);
extern void beep_off(void);
extern void beep_nor(void);


#endif

beep.c:

复制代码
#include "beep.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"


void init_beep(void)
{
    IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0); /* 配置为 */
    IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0x10b0); /* 配置电气属性 */
    GPIO5->GDIR = (1<<1);              /* 设置 GPIO1_IO03 为输出 */
}

void beep_on(void)
{
    GPIO5->DR &= ~(1<<1); /* 清除第 3 位,输出低电平 */
}

void beep_off(void)
{
    GPIO5->DR  |= (1<<1); /* 清除第 3 位,输出高电平 */
}

void beep_nor(void)
{
    GPIO5->DR ^= (1<<1); /* 反转第三位电平*/
}

2.key

我们以同样的逻辑为其设置引脚复用:

这次我尝试用自己封装的gpio函数完成对引脚的初始化以及读写:

gpio.c:

复制代码
#include "gpio.h"

void init_gpio(GPIO_Type *base, int pin, struct GPIO_Type_t *gpio)  // GPIO初始化函数,传入基地址、引脚号和配置结构体
{
    if (gpio->direction == gpio_output)                             // 判断是否为输出模式
    {
        base->GDIR |= (1 << pin);                                   // 设置对应引脚为输出
        if(gpio->defalut_value)                                     // 判断默认电平是否为高
        {
            base->DR |= ( 1<< pin);                                 // 输出高电平
        }
        else                                                        // 默认电平为低
        {
            base->DR &= ~( 1<< pin);                                // 输出低电平
        }
    }
    else                                                            // 输入模式
    {
        base->GDIR &= ~(1 << pin);                                  // 设置对应引脚为输入
    }
}

void write_gpio(GPIO_Type *base, int pin,int value)
{
    if(value)
    {
        base->DR |=(1 << pin);
    }
    else
    {
        base->DR &= ~( 1<< pin); 
    }

}

int read_gpio(GPIO_Type *base, int pin)
{
    return (base->DR &(1 << pin));
}

key.c:

复制代码
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpio.h"

void init_key(void)
{
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 配置为 ALT5(GPIO1_IO03) */
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xf0b0); /* 配置电气属性 */
    struct GPIO_Type_t t = 
    {
        .direction = gpio_input,
        .defalut_value = 1
    };
   // GPIO1->GDIR = (1<<3);              /* 设置 GPIO1_IO03 为输出 */
   init_gpio(GPIO1,18, &t);
}

int key_pressed(void)
{
    if (read_gpio(GPIO1, 18) == 0)
    {
        delay(0xffff);
        if (read_gpio(GPIO1, 18) == 0)
            return 1;
    }
    return 0;
}

main.c:

复制代码
/*
#define CCM_CCGR0 (*((volatile unsigned int *)(0x020c4068)))
#define CCM_CCGR1 (*((volatile unsigned int *)(0x020c406c)))
#define CCM_CCGR2 (*((volatile unsigned int *)(0x020c4070)))
#define CCM_CCGR3 (*((volatile unsigned int *)(0x020c4074)))
#define CCM_CCGR4 (*((volatile unsigned int *)(0x020c4078)))
#define CCM_CCGR5 (*((volatile unsigned int *)(0x020c407c)))
#define CCM_CCGR6 (*((volatile unsigned int *)(0x020c4080)))

#define SW_MUX_CTL_PAD_GPIO1_IO03 (*((volatile unsigned int *)(0x020e0068)))
#define SW_PAD_CTL_PAD_GPI01_I003 (*((volatile unsigned int *)(0x020e02f4)))
#define GPIO1_DR (*((volatile unsigned int *)(0x0209c000)))
#define GPIO1_GDIR (*((volatile unsigned int *)(0x0209c004)))

struct GPIO_Type_t
{
    volatile unsigned int DR;
    volatile unsigned int GDIR;
    volatile unsigned int PSR;
    volatile unsigned int ICR1;
    volatile unsigned int ICR2;
    volatile unsigned int IMR;
    volatile unsigned int ISR;
    volatile unsigned int EDGE_SEL;
};

#define GPIO1 ((struct GPIO_Type_t *)(0x0209c000))
#define GPIO2 ((struct GPIO_Type_t *)(0x020Ac000))
*/
#include "led.h"
#include "beep.h"
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
/*
 * enable_clicks
 * 功能:打开所有外设时钟
 * 说明:i.MX6ULL 默认关闭外设时钟,必须先使能
 */
void enable_clicks(void)
{
    CCM->CCGR0 = 0xffffffff;  /* 打开 CCGR0 中所有外设时钟 */
    CCM->CCGR1 = 0xffffffff;  
    CCM->CCGR2 = 0xffffffff;  
    CCM->CCGR3 = 0xffffffff;  
    CCM->CCGR4 = 0xffffffff;  
    CCM->CCGR5 = 0xffffffff;  
    CCM->CCGR6 = 0xffffffff;  
}

/*
 * init_led
 * 功能:初始化 GPIO1_IO03 为 GPIO 输出
 */


void delay(unsigned int n)
{
    while(n--);
}


int main (void)
{
    enable_clicks(); 
    init_led();        
    init_beep(); 
    while (1)       
    {
       // led_nor();
        //beep_nor();
       // delay(0xfffff);
       if(key_pressed())
       {
            led_on();
            beep_on();
       }
       else
       {
            led_off();
            beep_off();
       }
    }

    return 0; 
}

运行结果:在按下按键后,LED闪烁,蜂鸣器启动。

3.中断

如果主函数中多出一段延时,在轮询过程中恰好在延迟进行中按下按键,则蜂鸣器和led灯不会响应,有没有什么好的解决办法呢?因此我们需要在按键按下的时候进行中断处理。

3.1外部中断

外部中断的一般过程:

  1. 外设触发中断(如 GPIO1_IO18)

  1. GIC 接收中断 → 根据优先级、使能状态 → 分发给 CPU

  1. CPU 收到中断 → 跳转到异常向量表(由 SCTLR.TE 决定位置)

  1. 执行中断服务函数(ISR)

  1. 写 GIC_EOIR 通知 GIC 中断处理完成

3.1.1中断寄存器

按键复用为gpio1_18,GPIOx_ICR2的第4-5位控制第18号引脚的中断方式。

同时也要将GPIO1_IMR中断屏蔽寄存器第18位置1,取消中断屏蔽。

这是中断标记寄存器GPIOx_ISR,写1清零,那个引脚发生中断,那个引脚就会置1。

双边沿触发寄存器,当给18号引脚置1时gpio1-18无论是上升下降都会触发中断,且在GPIOx_ICR2中设置的上升或下降沿触发会被无效化。

3.1.2GIC

GIC将中断分为三类:

  1. SPI(Shared Peripheral Interrupt),共享中断, (注意!不是 SPI 总线那个中断),这类中断泛指所有的外设中断;
  2. PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有 的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断;
  3. SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

imx6ull中断编号:

由SCTLR确定异常向量表位置:

SCTLR 的 Bit 13(V 位)用来选择异常向量表的位置。V=0 时,向量表基地址是 0x00000000,可以通过 VBAR 寄存器重映射;V=1 时,向量表固定在 0xFFFF0000,且不能被重映射。在裸机开发中,必须确保 V 位的设置与你存放中断向量表的地址一致,否则 CPU 找不到中断处理函数。

SCTLR 的 Bit 12(I 位)用来控制指令缓存(I-Cache)的开关。I=0 禁用指令缓存,CPU 每次取指都直接访问内存,速度较慢但行为最直观,适合早期初始化和调试;I=1 启用指令缓存,能显著提升程序运行速度,通常在系统初始化完成后开启。

复制代码
_enable_icahce:
    mrc p15, 0, r0, c1, c0, 0
    bic r0, r0, #(1 << 13)
    orr r0, r0, #(1 << 12)
    mcr p15, 0, r0, c1, c0, 0
//配置 ARM 协处理器 CP15 的 SCTLR(系统控制寄存器),
//具体操作为:先读取 SCTLR 到 r0,然后清除第 13 位(V 位,将异常向量表基地址设为 0x00000000),可修改
//接着设置第 12 位(I 位,启用指令缓存),最后将修改后的值写回 SCTLR。
//效果是让 CPU 从 0x00000000 开始寻找中断向量表,并开启指令缓存以提升性能。
    bx lr
复制代码
_set_vbar:

ldr r0, =0x87800000
mcr p15, 0, r0, c12, c0, 0
bx lr
//把异常向量表位置挪到了0x87800000

在key_passed函数中使能IRQ

复制代码
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
//告诉 GIC:允许 GPIO1 的第 16~31 号引脚产生的中断送到 CPU。

在汇编函数中调用c语言函数需要保护现场

复制代码
_irq_handler:                        // IRQ中断处理
    sub lr, lr, #4             //返回地址会被加4,在原有地址减4
    stmfd sp!, {r0 - r12, lr}                //跳转c函数保护现场
    bl system_interrupt_handler
    ldmfd sp!, {r0 - r12, lr}^       //回复现场以及工作模式

查异常状态返回地址偏移量可知IRQ中断地址返回时会加4,因此在保护现场前-4确保正确返回。

在主函数中循环延迟,测试是否会由按键触发中断导致LDE闪烁以及蜂鸣器启动:

中断正常进行

接下来我们需要用GIC的中断通知寄存器以及中断结束寄存器

_irq_handler及其调用的c函数:

复制代码
_irq_handler:                        // IRQ中断处理
    sub lr, lr, #4             //返回地址会被加4,在原有地址减4
    mrc p15, 4, r1, c15, c0, 0   //读取 GIC 的基地址放在r1,用这个基地址加偏移量访问寄存器
    add r1, r1, #0x2000
    ldr r0, [r1, #0x0c]  //读取中段编号存入r0
    stmfd sp!, {r0 - r12, lr}                //跳转c函数保护现场
    stmfd sp!, {r0, r1}  //保护两次r0,r1,避免c函数对其修改。
    bl system_interrupt_handler//中断编号r0传参
    ldmfd sp!, {r0, r1} 
    str r0,[r1 , #0x10] //将寄存器 r0 中的值,写入到以 (r1 + 0x10) 为地址的内存单元中,清除中断标记
    ldmfd sp!, {r0 - r12, pc}^       //回复现场以及工作模式


===============================================================
void system_interrupt_handler( int num)//中断处理,参数为r0的值
{
    if(num == GPIO1_Combined_16_31_IRQn)
    {
        if( GPIO1->ISR & (1 << 18) != 0)
        {
            led_nor();//r0为99(gic寄存器 GPIO1_Combined_16_31_IRQn= 99,)
            //且外设GPIO1_18的中断标记寄存器isr为1则LED状态反转
            GPIO1->ISR |= (1 << 18);
        }
    }
}

同样,不光要把gic的中断标记位清零,也要给外设中断标记寄存器GPIOx_ISR写1清零

我们再次测试,发现led灯在按下按键后亮起,再次按下熄灭,说明传递的中断编号正确:

3.2中断优先级

最后就剩下一个中断优先级的问题了, GIC 控制器最多可以支持 256 个优先级,数字越小,优 先级越高!Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存 器用来决定使用几级优先级, GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,而我们使用的Cortex-A7只有32个优先级

由于irq中断类型不能打断irq中断,因此我们首先需要在中断处理前切换为系统模式,在中断处理结束后切回irq模式:

中断优先级设置及获取函数

相关推荐
崇山峻岭之间13 小时前
单片机RNG实验
单片机·嵌入式硬件
JNX_SEMI13 小时前
EG1160:600V半桥驱动,2.5A强驱带保护
stm32·单片机·嵌入式硬件
芯岭技术13 小时前
PY32L020单片机,多种低功耗模式,电流低至 0.7μA,适合电池供电产品
单片机·嵌入式硬件
qq_4294995713 小时前
STM32串口中断接收
stm32·单片机·嵌入式硬件
嵌入式×边缘AI:打怪升级日志13 小时前
串口调试 — printf 重定向与 USART 通信
单片机·嵌入式硬件
M1582276905514 小时前
工业级 CAN 转以太网网关|SG-CANET-210/410,打通 CAN 与以太网,工业通信无边界
单片机·嵌入式硬件·php
爱搬砖的狮子14 小时前
编译appweb源代码
stm32·单片机·嵌入式硬件
hoiii18714 小时前
STM32 开发板上用 USART 实现 Modbus 协议控制设备的方案
stm32·单片机·嵌入式硬件
Lucky_ldy14 小时前
51单片机的学习上(结合中科协的个人自用笔记)
嵌入式硬件·学习·51单片机