一、实现蜂鸣器功能① 蜂鸣器功能实现步骤1查看原理图设计2查阅IMX6ULL参考手册按以下顺序配置开启外设时钟设置引脚复用功能配置引脚电气属性设置GPIO方向寄存器配置GPIO数据寄存器② 在LED/bsp文件夹基础上添加beep.c、beep.h//beep.h #ifndef __BEEP_H__ #define __BEEP_H__ extern void beep_init(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 beep_init(void) { // 1. 复用功能 IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0); // 2. 电气属性 IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0); // 3. GPIO方向输出模式 GPIO5-GDIR | (1 1); } void beep_on(void) { GPIO5-DR ~(1 1); } void beep_off(void) { GPIO5-DR | (1 1); } void beep_nor(void) { GPIO5-DR ^ (1 1); }③ main.c//main.c #include MCIMX6Y2.h #include fsl_iomuxc.h #include led.h #include beep.h void enable_clocks(void) { CCM-CCGR0 0xFFFFFFFF; CCM-CCGR1 0xFFFFFFFF; CCM-CCGR2 0xFFFFFFFF; CCM-CCGR3 0xFFFFFFFF; CCM-CCGR4 0xFFFFFFFF; CCM-CCGR5 0xFFFFFFFF; CCM-CCGR6 0xFFFFFFFF; } //延迟函数 void delay(unsigned int n) { while(n--); } int main(void) { //使能外设时钟 enable_clocks(); //led初始化 led_init(); //beep初始化 beep_init(); while(1) { led_nor(); beep_nor(); delay(0x5FFFF); } return 0; }二、按键2.1 IMX6ULL按键介绍① 按键数量与功能共 3 个按键两红一黄功能分工明确左侧(on/off)低功耗控制按钮中间(reset)系统复位按钮右侧(KEY0)用户独立控制的试验按键本次实验主要使用KEY0电平逻辑开关断开时引脚保持高电平开关按下时引脚变为低电平关键逻辑代码设计核心依据② 核心寄存器参考手册硬件模块参考文档核心章节说明引脚复用《IMX6ULL 参考手册.pdf》Chapter 32IOMUX Controller配置 GPIO 功能复用GPIO 控制《IMX6ULL 参考手册.pdf》Chapter 28General Purpose Input/OutputGPIO 方向、数据读取时钟控制《IMX6ULL 参考手册.pdf》Chapter 18Clock Controller Module使能 GPIO 时钟中断控制器《ARM Generic Interrupt Controller V2.0.pdf》第 23 页GIC 中断分发与 CPU 接口内核控制《Cortex-A7 Technical Reference Manual.pdf》Chapter 4System Control协处理器 CP15 配置2.2 轮询方式实现按键功能① 轮询方式原理循环检测GPIO引脚电平状态来判断按键动作适用于逻辑简单且对实时性要求不高的场景。但当主程序存在大量耗时任务时可能导致按键响应延迟因此在汽车制动、工业急停等高实时性场景中禁止使用。② 代码实现步骤步骤 1初始化配置4 大核心模块1GPIO 时钟使能CCM功能使能 GPIO1 组时钟默认时钟关闭不使能则 GPIO 无响应关键寄存器CCM_CCGR1GPIO1 组共用该时钟门控2引脚复用配置IOMUXC功能将UART1_CTS_B引脚复用为GPIO1_IO18关键寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B低 4 位控制复用模式3电气特性配置PAD_CTL功能配置引脚的上拉 / 下拉、速度、驱动能力等确保电平稳定关键寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_CTS_B4GPIO 方向配置GDIR功能设置 GPIO 为输入模式读取按键电平关键寄存器GPIO1-GDIRbit18 对应 GPIO1_IO180 输入1 输出步骤 2按键检测轮询核心1功能循环读取 GPIO 数据寄存器判断按键按下 / 松开状态关键寄存器GPIO1-PSRbit18 对应引脚电平1 高电平断开0 低电平按下③ 代码实现//key.c #include MCIMX6Y2.h #include fsl_iomuxc.h #include key.h void key_init(void) { //1.引脚复用 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0); //2.电气属性 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0); //3.GPIO方向输入模式 GPIO1-GDIR ~(1 18); } int key_press(void) { if((GPIO1-PSR (1 18)) 0) { return 1; } else { return 0; } }//main.c #include MCIMX6Y2.h #include fsl_iomuxc.h #include led.h #include beep.h #include key.h void enable_clocks(void) { CCM-CCGR0 0xFFFFFFFF; CCM-CCGR1 0xFFFFFFFF; CCM-CCGR2 0xFFFFFFFF; CCM-CCGR3 0xFFFFFFFF; CCM-CCGR4 0xFFFFFFFF; CCM-CCGR5 0xFFFFFFFF; CCM-CCGR6 0xFFFFFFFF; } //延迟函数 void delay(unsigned int n) { while(n--); } int main(void) { //使能外设时钟 enable_clocks(); //led初始化 led_init(); //蜂鸣器beep初始化 beep_init(); //按键初始化 key_init(); led_off(); beep_off(); while(1) { if(key_press()) { led_on(); beep_on(); } else { led_off(); beep_off(); } } return 0; }2.3 中断方式实现按键功能① 中断方式原理当按键按下时GPIO 触发外部中断EINTCPU 暂停当前任务优先执行中断服务程序ISR待处理完成后恢复原任务从而避免轮询方式导致的漏检并提升实时性。② 中断处理流程6 步核心中断源发出中断请求按键按下 -- GPIO 引脚电平变化 -- 触发外部中断请求CPU检查是否响应中断及该中断是否被屏蔽CPU 判断中断是否被屏蔽GIC 中是否允许该中断比较中断优先级GIC 分发器Distributor选择最高优先级中断保护现场保存当前 CPU 寄存器值如 R0~R15避免数据丢失执行中断服务函数处理按键逻辑恢复现场恢复保存的寄存器值回到原业务继续执行③ IMX6ULL ——GPIO外设中断触发模式上升沿下降沿高电平和低电平触发④ 中断实现方法⑤ GIC 中断控制器GICGeneric Interrupt Controller通用中断控制器是为ARM内核提供服务的中断控制器负责中断的分发与管理IMX6ULL 中 GIC版本为GICV2型号(GIC-400)GIC最多能管理 1020 个中断号ID0~ID1019IMX6ULL支持1600 - 159个中断分类如下1SGI(Software-generated Interrupt)软件中断由软件触发引起的中断通过向寄存器GICD_SGIR 写入数据来触发系统会使用 SGI 中断来完成多核之间的通信。2PPI(Private Peripheral Interrupt)私有中断我们说了 GIC 是支持多核的每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理因此这些中断就叫做私有中断3SPI(Shared Peripheral Interrupt),共享中断(注意不是 SPI 总线那个中断)这类中断泛指所有的外设中断。中断类型范围特点典型用途SGI软件中断0~15软件触发多核间通信多核CPU同步如核0通知核1PPI私有中断16~31每个CPU独有绑定核心核内定时器、看门狗等SPI共享中断32~1019多CPU共享外设中断GPIO、UART、SPI等外设中断GIC版本介绍GICv1初代基础版本是ARM最早标准化的通用中断控制器仅实现最基础的中断优先级管理、中断路由功能没有多核支持、安全扩展这类高级特性。CortexA5已弃用GICv232位多核标准版本最多支持8个CPU核心可管理1020个中断ID。该架构明确定义了SGI、PPI、SPI三类中断类型0~15为SGI软中断软件生成中断16~31为PPI私有外设中断核间通信内核与内核之间中断32~1019为SPI共享外设中断其中SPI可作为外部设备的中断源。Cortex-A7、Cortex-A9GICv364位架构普及版本重新设计了硬件架构新增Redistributor中断再分配器组件用层级亲和性标识CPU核心理论支持最多2^32个处理器单元新增LPI消息中断和ITS中断翻译服务最大支持超1600万个中断ID完全适配PCIe等高速外设的大量中断需求。GICv4虚拟化优化版本完全兼容GICv3的所有功能新增虚拟LPI中断直接注入能力不需要虚拟机管理器中转就能把虚拟中断直接送到对应vCPU大幅降低虚拟化场景下的中断开销提升云服务器的多核调度效率。GICv5下一代全新架构版本对整个中断处理链路做了全栈重新设计原生支持大规模异构核心集群、硬件级中断负载均衡、实时中断确定性调度专门适配大算力场景下数万级中断源的管理需求是未来超算、AI服务器的标准中断控制器方案。⑥ 协处理器1协处理器是一种协助主处理器完成特定功能的专用处理单元。在 ARM 架构中协处理器通过一组专用的指令MCR/MRC和寄存器与主 CPU 交互用于完成主 CPU 不擅长或效率不高的任务。对比项MRCMove to ARM Register from CoprocessorMCRMove from ARM Register to Coprocessor核心功能从协处理器寄存器读取数据到 ARM 核心通用寄存器从 ARM 核心通用寄存器写入数据到协处理器寄存器数据流向协处理器寄存器 → ARM 核心寄存器读操作ARM 核心寄存器 → 协处理器寄存器写操作指令格式MRC{cond} coproc, opcode1, Rd, Crn, Crm{, opcode2}MCR{cond} coproc, opcode1, Rd, Crn, Crm{, opcode2}关键字段含义coproc协处理器编号如 CP15 对应 15Rd目标 ARM 寄存器数据读入此寄存器Crn/Crm协处理器主/次寄存器opcode1/opcode2协处理器操作码coproc协处理器编号如 CP15 对应 15Rd源 ARM 寄存器数据从此寄存器写出Crn/Crm协处理器主/次寄存器opcode1/opcode2协处理器操作码应用场景CP15 示例1. 读取处理器 IDc0/c0 寄存器2. 读取系统控制状态如 MMU 使能状态c1/c0 寄存器3. 读取中断屏蔽状态c12/c0 寄存器4. 读取 Cache 配置信息c9/c0 寄存器1. 使能 MMU修改 c1/c0 寄存器的 M 位2. 设置中断向量表基址修改 c12/c0 寄存器3. 刷新 Cache操作 c7/c6 寄存器4. 配置内存权限修改 c2/c0 寄存器2ARM 协处理器编号ARM 架构最多支持16 个协处理器编号为cp0 ~ cp15。3协处理器 CP15 配置CP15 是 Cortex-A7 的系统控制协处理器负责中断向量表、缓存、MMU 等配置中断相关核心寄存器如下寄存器名称功能描述关键配置项代码示例MIDR (c0)主 ID 寄存器内核型号、版本信息通过mrc p15,0,r0,c0,c0,0读取内核型号及版本信息调试时读取确认内核型号SCTLR (c1)系统控制寄存器中断异常向量表基地址V 位第13位、控制指令缓存开关I 位第12位mrc p15,0,r0,c1,c0,0; bic r0,r0,#(113);mcr p15,0,r0,c1,c0,0;向量表基址0x00000000VBAR (c12)向量基址寄存器中断向量表物理地址重定向后的异常向量表基地址.__set_VBAR(0x87800000);设置向量表基址为0x87800000CBAR (c15)配置基址寄存器GIC 寄存器物理基址mrc p15,4,r0,c15,c0,0;读取 GIC 基址⑦ 中断方式代码实现//key.c #include MCIMX6Y2.h #include fsl_iomuxc.h #include key.h #include core_ca7.h #include interrupt.h #include led.h void key_interrupt_handler(void) { if((GPIO1-ISR (1 18)) ! 0) { led_nor(); GPIO1-ISR | (1 18); //清除中断标志 } } void key_init(void) { //1.引脚复用 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0); //2.电气属性 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0); //3.GPIO方向输入模式 GPIO1-GDIR ~(1 18); //4.配置中断触发方式下降沿 GPIO1-ICR2 | (3 4); //5.使能GPIO1_18引脚中断 GPIO1-IMR | (1 18); //6.注册按键中断 system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_interrupt_handler); //7.设置中断优先级 GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0); //8.GIC使能中断 GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); } int key_press(void) { if((GPIO1-PSR (1 18)) 0) { return 1; } else { return 0; } }//start.S .global _start _start: ldr pc, _reset_handler ldr pc, _undefine_handler ldr pc, _svc_handler ldr pc, _prefetch_aboart_handler ldr pc, _data_abort_handler ldr pc, _reserved_handler ldr pc, _irq_handler ldr pc, _fiq_handler _fiq_handler: ldr pc, _fiq_handler _irq_handler: subs lr, lr, #4 //GIC基地址 mrc p15, 4, r1, c15, c0, 0 add r1, r1, #0x2000 ldr r0, [r1, #0x0c] //中断号 stmfd sp!,{r0-r12, lr} stmfd sp!, {r0, r1} cps #0x1F //切换到system模式在执行c函数是可以被其他中断打断 stmfd sp!, {lr} bl system_interrupt_handler ldmfd sp!, {lr} cps #0x12 //切换到IRQ模式 ldmfd sp!, {r0, r1} str r0, [r1, #0x10] //清中断标志 ldmfd sp!, {r0-r12, pc}^ _reserved_handler: ldr pc, _reserved_handler _data_abort_handler: ldr pc, _data_abort_handler _prefetch_aboart_handler: ldr pc, _prefetch_aboart_handler _svc_handler: ldr pc, _svc_handler _undefine_handler: ldr pc, _undefine_handler _reset_handler: //切换工作模式IRQ //mrs r0, cpsr //bic r0, r0 ,#0x1F //orr r0, r0, #0x12 cps #0x12 //直接切换工作模式 cpsie i //使能IRQ中断 // bic r0, r0, #(1 7) //使能中断 //msr cpsr, r0 //设置IRQ模式下的栈 ldr sp, 0x86000000 //切换工作模式System //mrs r0, cpsr // bic r0, r0, #0x1F // orr r0, r0, #0x1F //msr cpsr, r0 cps #0x1F //设置system模式下的栈 ldr sp, 0x84000000 //清空bss段和common段 bl _bss_clear //打开icache bl _enable_icache //设置异常向量表重映射后的地址 bl _set_vbar //跳转到main函数执行 b main b finished _enable_icache: mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #(1 13) orr r0, r0, #(1 12) mcr p15, 0, r0, c1, c0, 0 bx lr _set_vbar: ldr r0, 0x87800000 mcr p15, 0, r0, c12, c0, 0 bx lr _bss_clear: ldr r0, __bss_start ldr r1, __bss_end loop: mov r2, #0 str r2, [r0] add r0, r0, #4 cmp r0, r1 blt loop bx lr finished: b finished//interrupt.c #include interrupt.h #include MCIMX6Y2.h #include core_ca7.h irq_handler_t interrupt_vector_table[160]; void system_interrupt_init(void) { GIC_Init(); } //注册中断函数 void system_interrupt_register(int num, irq_handler_t handler) { interrupt_vector_table[num] handler; } //irq中断服务函数 void system_interrupt_handler(int num) { if (interrupt_vector_table[num] ! NULL) { interrupt_vector_table[num](); } } //interrupt.h #ifndef __INTERRUPT_H__ #define __INTERRUPE_H__ typedef void (*irq_handler_t) (void); extern void system_interrupt_init(void); extern void system_interrupt_register(int num, irq_handler_t handler); #endif//main.c #include MCIMX6Y2.h #include fsl_iomuxc.h #include led.h #include beep.h #include key.h #include core_ca7.h #include interrupt.h //延迟函数 void delay(unsigned int n) { while(n--); } void enable_clocks(void) { CCM-CCGR0 0xFFFFFFFF; CCM-CCGR1 0xFFFFFFFF; CCM-CCGR2 0xFFFFFFFF; CCM-CCGR3 0xFFFFFFFF; CCM-CCGR4 0xFFFFFFFF; CCM-CCGR5 0xFFFFFFFF; CCM-CCGR6 0xFFFFFFFF; } int main(void) { //使能外设时钟 enable_clocks(); //使能中断 system_interrupt_init(); //led初始化 led_init(); //蜂鸣器beep初始化 beep_init(); //按键初始化 key_init(); led_off(); beep_off(); while(1) { delay(0xFFFFFFFF); } return 0; }