硬件(九)寄存器、外设与中断机制
一、结构体访问寄存器的本质
通过指针 + 偏移量实现,结构体在此扮演关键角色:
- 偏移量载体:把寄存器相对基地址的偏移值(如
0
、4
、8
),映射为易记的成员名(如DR
、GDIR
、PSR
)。 - 起始地址锚点:将指针强制转为结构体类型后,编译器会以该指针为 “起始地址”,按结构体成员的偏移量去访问对应物理地址。
- 最终效果:无需手动计算并书写
0x209C004
这类物理地址,由编译器自动完成 “基地址 + 偏移量” 的地址计算与访问。
示例,能更直观理解对 GPIO 的初始化操作:
enum GPIO_Dirction
{gpio_output,gpio_input
};struct GPIO_Type_t
{enum GPIO_Dirction direction;int default_value;
};void init_gpio(GPIO_Type *base, int pin, struct GPIO_Type_t *gpio)
{if(gpio->direction == gpio_output){base->GDIR |= (1 << pin);if(gpio->default_value){base->DR |= (1 << pin);}else{base->DR &= ~(1 << pin);} }else{base->GDIR &= ~(1 << pin);}
}void init_beep(void)
{IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0);IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0);struct GPIO_Type_t t = {.direction = gpio_output,.default_value = 1};init_gpio(GPIO5, 1, &t);
}
二、i.MX6ULL 按键功能实现
(一)硬件原理与引脚对应
按键电路中,按键KEY0
一端经电阻R12
接DCDC_3V3
电源(上拉),另一端接地。当按键按下时,引脚电平拉低;松开时,由上拉电阻维持高电平。
从芯片引脚定义可知,按键对应引脚复用为UART1_CTS
功能(实际作为 GPIO 输入使用)。
(二)软件配置流程
- IO 复用与电气属性设置:
调用IOMUXC_SetPinMux
设置引脚复用模式,IOMUXC_SetPinConfig
配置电气属性(如上下拉、驱动能力等)。示例代码中,将IOMUXC_UART1_CTS_B_GPIO1_IO18
引脚配置为 GPIO 功能并设置合适电气参数:IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0); IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
- GPIO 方向与默认值设置:
因为按键是输入设备,所以通过init_gpio
函数将对应 GPIO(GPIO1
的18
号引脚)配置为输入模式,且默认值设为1
(高电平,对应按键未按下状态):struct GPIO_Type_t t = {.direction = gpio_input,.default_value = 1 }; init_gpio(GPIO1, 18, &t);
- 中断触发方式与使能:
- 配置中断触发方式为下降沿触发(通过
GPIO1->ICR2
寄存器)。 - 使能对应 GPIO 引脚的中断(通过
GPIO1->IMR
寄存器)。 - 使能 GIC 中对应中断(
GPIO1_Combined_16_31_IRQn
),让内核能响应该中断:
GPIO1->ICR2 |= (3 << 4); GPIO1->IMR |= (1 << 18); GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
- 配置中断触发方式为下降沿触发(通过
三、中断相关知识
(一)中断的概念
中断是一种硬件机制,当外设(如按键、定时器等)或内核内部发生特定事件时,会主动向 CPU 发送请求,使 CPU 暂停当前任务,转而去处理该事件(执行中断服务程序),处理完毕后再回到原任务继续执行。
(二)GIC 控制器
- 作用:GIC(Generic Interrupt Controller,通用中断控制器)是 ARM 公司设计的外设,负责管理各类中断,接收外部中断后进行处理,最终向 ARM 内核上报(如
IRQ
信号)。 - 多内核支持:GIC V2.0 设计可支持 8 个内核,但 i.MX6ULL 仅单内核,仍可通过相关函数(如
GIC_Init
)统一管理中断。 - 中断分类:
SGI
(Software Generated Interrupt,软件生成中断):中断 ID0 - 15
,由软件触发。PPI
(Private Peripheral Interrupt,私有外设中断):中断 ID16 - 31
,特定于单个 CPU。SPI
(Shared Peripheral Interrupt,共享外设中断):中断 ID32 - 1019
,所有 CPU 可共享的外设中断,按键等外部中断多属于此类。
- 关键寄存器:
C_IAR
(Interrupt Acknowledge Register):中断确认寄存器,用于获取当前待处理中断的 ID。C_EOIR
(End Of Interrupt Register):中断结束寄存器,用于告知 GIC 中断处理完毕。
(三)协处理器(Coproc)
ARM 内核有cp0 - cp15
等协处理器,cp15
常用,负责管理系统级功能(如内存管理、中断控制等)。GIC 的基地址等关键信息由cp15
管理,需通过专门指令(MRC
、MCR
)访问cp15
寄存器。
MRC
指令:从协处理器寄存器读取数据到 ARM 通用寄存器。格式为MRC p15, 0, <Rd>, <Crn>, <Crm>, {<opc>}
,例如读取cp15
的c1
寄存器到r0
:mrc p15, 0, r0, c1, c0, 0
。MCR
指令:将 ARM 通用寄存器的数据写入协处理器寄存器。格式为MCR p15, 0, <Rd>, <Crn>, <Crm>, {<opc>}
,例如将r0
数据写入cp15
的c12
寄存器:mcr p15, 0, r0, c12, c0, 0
。
(四)SCTLR 寄存器(系统控制寄存器)
- V 位(位 13):选择异常向量表基地址。
V = 0
:使用普通异常向量表,基地址为0x00000000
,且可通过VBAR
寄存器重新映射基地址(如改为我们代码中的0x87800000
),方便将异常处理代码放在指定内存区域。V = 1
:使用高异常向量表,基地址固定为0xFFFF0000
,不可重映射,不符合我们灵活配置异常向量表的需求。
- I 位(位 12):指令缓存使能位。
I = 1
使能指令缓存(ICache
),可加速指令读取;数据缓存(DCache
)在此时不使能,避免数据一致性等复杂问题(后续按需开启)。
(五)异常向量表与地址设置
异常向量表存储各类异常(如中断、复位等)的处理入口地址。为让内核在异常发生时能跳转到我们编写的处理代码,需设置异常向量表基地址:
- 通过
_set_vbar
函数,利用MCR
指令将0x87800000
写入cp15
的c12
寄存器(VBAR
寄存器),完成异常向量表基地址的重映射。
(六)GIC 基地址获取(CBAR 寄存器)
CBAR
(Core Base Address Register)寄存器存储 GIC 的基地址。通过MRC
指令mrc p15, 4, r0, c15, c0, 0
,可将 GIC 基地址读取到r0
寄存器,后续基于此地址访问 GIC 的各类寄存器。
(七)中断使能与处理
- 全局中断使能:通过修改
CPSR
寄存器(当前程序状态寄存器),开启 IRQ 中断:_reset_handler:mrs r0, cpsrbic r0, r0, #0x1Forr r0, r0, #0x12 // 使能IRQ中断bic r0, r0, #(1 << 7)msr cpsr, r0
- 中断服务程序框架:
_irq_handler:subs lr, lr, #4 // 修正返回地址(因ARM流水线,地址会超前4字节)stmfd sp!, {r0-r12, lr} // 保存现场bl system_interrupt_handler // 调用中断处理函数ldmfd sp!, {r0-r12, pc}^ // 恢复现场并返回,^表示同时恢复CPSR
(八)按键中断初始化完整代码
#include "key.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpio.h"
#include "core_ca7.h"void init_key(void)
{IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);struct GPIO_Type_t t = {.direction = gpio_input,.default_value = 1};init_gpio(GPIO1, 18, &t);GPIO1->ICR2 |= (3 << 4); // 配置下降沿触发GPIO1->IMR |= (1 << 18); // 使能GPIO1_18引脚中断GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); // 使能GIC中对应中断
}int key_pressed(void)
{return !read_gpio(GPIO1, 18); // 读取引脚电平,判断按键是否按下
}