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外部中断
外部中断的一般过程:

- 外设触发中断(如 GPIO1_IO18)
↓
- GIC 接收中断 → 根据优先级、使能状态 → 分发给 CPU
↓
- CPU 收到中断 → 跳转到异常向量表(由 SCTLR.TE 决定位置)
↓
- 执行中断服务函数(ISR)
↓
- 写 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将中断分为三类:
- SPI(Shared Peripheral Interrupt),共享中断, (注意!不是 SPI 总线那个中断),这类中断泛指所有的外设中断;
- PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有 的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断;
- 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模式:

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