ARM03-蜂鸣器和中断

一、实现蜂鸣器功能

① 蜂鸣器功能实现步骤:

(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 32(IOMUX Controller) 配置 GPIO 功能复用
GPIO 控制 《IMX6ULL 参考手册.pdf》 Chapter 28(General Purpose Input/Output) GPIO 方向、数据读取
时钟控制 《IMX6ULL 参考手册.pdf》 Chapter 18(Clock Controller Module) 使能 GPIO 时钟
中断控制器 《ARM Generic Interrupt Controller V2.0.pdf》 第 23 页 GIC 中断分发与 CPU 接口
内核控制 《Cortex-A7 Technical Reference Manual.pdf》 Chapter 4(System Control) 协处理器 CP15 配置

2.2 轮询方式实现按键功能

① 轮询方式原理:循环检测GPIO引脚电平状态来判断按键动作,适用于逻辑简单且对实时性要求不高的场景。但当主程序存在大量耗时任务时,可能导致按键响应延迟(因此在汽车制动、工业急停等高实时性场景中禁止使用)。

② 代码实现步骤:

步骤 1:初始化配置(4 大核心模块)

(1)GPIO 时钟使能(CCM)
功能:使能 GPIO1 组时钟(默认时钟关闭,不使能则 GPIO 无响应)
关键寄存器:CCM_CCGR1(GPIO1 组共用该时钟门控)
(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_B

(4)GPIO 方向配置(GDIR)
功能:设置 GPIO 为输入模式(读取按键电平)
关键寄存器:GPIO1->GDIR(bit18 对应 GPIO1_IO18,0 = 输入,1 = 输出)

步骤 2:按键检测(轮询核心)

(1)功能:循环读取 GPIO 数据寄存器,判断按键按下 / 松开状态

关键寄存器:GPIO1->PSR(bit18 对应引脚电平,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 触发外部中断(EINT),CPU 暂停当前任务,优先执行中断服务程序(ISR),待处理完成后恢复原任务,从而避免轮询方式导致的漏检并提升实时性。

② 中断处理流程(6 步核心)

  • 中断源发出中断请求:按键按下 --> GPIO 引脚电平变化 --> 触发外部中断请求
  • CPU检查是否响应中断及该中断是否被屏蔽:CPU 判断中断是否被屏蔽(GIC 中是否允许该中断)
  • 比较中断优先级:GIC 分发器(Distributor)选择最高优先级中断
  • 保护现场:保存当前 CPU 寄存器值(如 R0~R15),避免数据丢失
  • 执行中断服务函数:处理按键逻辑
  • 恢复现场:恢复保存的寄存器值,回到原业务继续执行

③ IMX6ULL ------ GPIO外设中断触发模式: 上升沿,下降沿,高电平和低电平触发

④ 中断实现方法

⑤ GIC 中断控制器

GIC:Generic Interrupt Controller(通用中断控制器)是为ARM内核提供服务的中断控制器,负责中断的分发与管理;IMX6ULL 中 GIC版本为 GICV2 ,型号(GIC-400),GIC最多能管理 1020 个中断号(ID0~ID1019),IMX6ULL支持160(0 - 159)个中断,分类如下:

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

中断类型 范围 特点 典型用途
SGI(软件中断) 0~15 软件触发,多核间通信 多核CPU同步(如核0通知核1)
PPI(私有中断) 16~31 每个CPU独有,绑定核心 核内定时器、看门狗等
SPI(共享中断) 32~1019 多CPU共享,外设中断 GPIO、UART、SPI等外设中断

GIC版本介绍:

GICv1:初代基础版本,是ARM最早标准化的通用中断控制器,仅实现最基础的中断优先级管理、中断路由功能,没有多核支持、安全扩展这类高级特性。(CortexA5已弃用)

GICv2:32位多核标准版本,最多支持8个CPU核心,可管理1020个中断ID。该架构明确定义了SGI、PPI、SPI三类中断类型:0~15为SGI(软中断,软件生成中断),16~31为PPI(私有外设中断,核间通信,内核与内核之间中断),32~1019为SPI(共享外设中断),其中SPI可作为外部设备的中断源。(Cortex-A7、Cortex-A9)

GICv3:64位架构普及版本,重新设计了硬件架构,新增Redistributor(中断再分配器)组件,用层级亲和性标识CPU核心,理论支持最多2^32个处理器单元,新增LPI消息中断和ITS中断翻译服务,最大支持超1600万个中断ID,完全适配PCIe等高速外设的大量中断需求。

GICv4:虚拟化优化版本,完全兼容GICv3的所有功能,新增虚拟LPI中断直接注入能力,不需要虚拟机管理器中转就能把虚拟中断直接送到对应vCPU,大幅降低虚拟化场景下的中断开销,提升云服务器的多核调度效率。

GICv5:下一代全新架构版本,对整个中断处理链路做了全栈重新设计,原生支持大规模异构核心集群、硬件级中断负载均衡、实时中断确定性调度,专门适配大算力场景下数万级中断源的管理需求,是未来超算、AI服务器的标准中断控制器方案。

⑥ 协处理器

(1) 协处理器 是一种协助主处理器完成特定功能 的专用处理单元。在 ARM 架构中,协处理器通过一组专用的指令(MCR/MRC)和寄存器与主 CPU 交互,用于完成主 CPU 不擅长或效率不高的任务。

| 对比项 | MRC(Move to ARM Register from Coprocessor) | MCR(Move 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 对应 15) <Rd>:目标 ARM 寄存器(数据读入此寄存器) <Crn>/<Crm>:协处理器主/次寄存器 <opcode1>/<opcode2>:协处理器操作码 | <coproc>:协处理器编号(如 CP15 对应 15) <Rd>:源 ARM 寄存器(数据从此寄存器写出) <Crn>/<Crm>:协处理器主/次寄存器 <opcode1>/<opcode2>:协处理器操作码 |

应用场景(CP15 示例) 1. 读取处理器 ID(c0/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 寄存器)

(2)ARM 协处理器编号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,#(1<<13); mcr p15,0,r0,c1,c0,0;(向量表基址 0x00000000
VBAR (c12) 向量基址寄存器 中断向量表物理地址(重定向后的异常向量表基地址.) __set_VBAR(0x87800000);(设置向量表基址为 0x87800000
CBAR (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;
}
相关推荐
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-7 12-24小时制(C语言实现)
c语言·开发语言·数据结构·算法
KaifuZeng2 小时前
电源面试问题汇总一
单片机·嵌入式硬件·面试·电路
runafterhit2 小时前
python基础语法命令(C程序员刷leetcode)
c语言·python·leetcode
笨笨饿2 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
驱动探索者3 小时前
RISC-V PLIC 深度解析:从 Spec 到 Zephyr 驱动实现
单片机·嵌入式硬件·中断控制器
LCG元4 小时前
STM32 FreeRTOS 实战:任务调度 + 互斥锁 + 消息队列 + 串口命令解析器
stm32·单片机·嵌入式硬件
Hello_Damon_Nikola4 小时前
姿态式饮水量估算从原理到实战
嵌入式硬件
LCG元4 小时前
STM32 SPI Flash 读写实战:W25Q64 驱动 + FatFS 文件系统移植 + 日志存储系统
stm32·单片机·嵌入式硬件
不想学习!!5 小时前
【STM32 基础复习 01】初识 STM32F103ZET6:型号、封装、引脚编号与特殊引脚
stm32·单片机·嵌入式硬件