IMX6ULL学习笔记(18)——GPIO中断

2023-03-14  本文已影响0人  Leung_ManWah

一、中断简介

相比 STM32 的 NVIC,IMX6ULL 的中断控制系统更复杂,它的中断管理器使用的是 GIC V2,GIC V2 的实现方式与我们熟知的 NVIC 差别较大。

1.1 GIC

GIC(Generic Interrupt Controller),直译为通用中断控制器,它是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,类似 Cortex-M 内核中的NVIC。目前共有 4 个版本 V1~V4,IMX6ULL 使用的是 GIC V2。GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、Cortex-A9、Cortex-A15 等,V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。

GIC 最多支持 8 个处理器(processor0~ processor7)。不同处理器的 GIC 功能是相同的,我们只看其中一个即可。GIC 架构主要分为 分发器(Distributor)CPU接口(CPU interface/Virtual CPU interface)


简化后:

1.1.1 分发器

分发器用于 管理CPU所有中断源确定每个中断的优先级管理中断的屏蔽和中断抢占。最终将优先级最高的中断转发到一个或者多个CPU接口

分发器主要工作如下:

CPU的中断源分为三类:

1.1.2 CPU接口

CPU 接口是和 CPU Core 相连接的,就是分发器和 CPU Core 之间的桥梁。与分发器类似它也提供了一些编程接口,我们可以通过CPU接口实现以下功能:

简单来说,CPU 接口可以开启或关闭发往 CPU 的中断请求,CPU 中断开启后只有优先级高于“中断优先级掩码”的中断请求才能被发送到 CPU。 在任何时候 CPU 都可以从其 GICC_Hppir(CPU接口寄存器) 读取当前活动的最高优先级。

1.2 CP15协处理器

CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有 16 个 32 位寄存器(编号为C0~C15)。关于 CP15 协处理器和其相关寄存器的详细内容请参考下面两份文档:《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第 1469 页“B3.17 Oranization of the CP15 registers in a VMSA implementation”。《Cortex-A7 Technical ReferenceManua.pdf》第 55 页“Capter 4 System Control”。

1.2.1 CP15协处理器寄存器的访问

在NXP的官方启动文件中有两处用到了CP15协处理寄存器,第一处是系统复位中断服务函数开始处,这里通过CP15修改系统控制寄存器,第二处是获取GIC控制器的基地址。

CP15 寄存器只能使用 MRC/MCR 寄存器进行读、写。

  1. MRC: 将 CP15 协处理器中的寄存器(c0~c15)数据读到 ARM 通用寄存器中(r0~r12)。
mrc {cond} p15, <opc1>, <Rd>, <CRn>, <CRm>, <opc2>
  1. MCR: 将 ARM 通用寄存器(r0~r12)的数据写入到 CP15 协处理器寄存器中(c0~c15)。
mcr {cond} p15, <opc1>, <Rd>, <CRn>, <CRm>, <opc2>

CP15寄存器读、写指令说明如下:

二、中断向量表

跟 STM32 一样,Cortex-A7 也有中断向量表,中断向量表也是在代码的最前面。Cortex-A7 内核有 8 个异常中断:

向量地址 中断类型 中断模式 描述
0X00 复位中断(Rest) 特权模式(SVC) 系统上电或者硬件复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、DDR 等等。
0X04 未定义指令中断(Undefined Instruction) 未定义指令中止模式(Undef) 如果CPU检测到无法识别的指令时会进入未定义指令异常中断。这种情况下系统已经无法继续运行,只能通过硬件复位或者看门狗复位系统。
0X08 软中断(Software Interrupt,SWI) 特权模式(SVC) 这种中断用于带 Linux 操作系统的情况,Linux 内核(即驱动程序)运行在 SVC(特权模式),而 Linux 应用程序运行在 usr 模式。应用程序中如果需要调用驱动程序,就需要首先通过系统调用中断切换到 SVC (特权模式),即我们常说的从”用户(应用)空间”切换到”内核空间”。
0X0C 指令预取中止中断(Prefetch Abort) 中止模式 在CPU执行当前指令时会”预取”下一个要执行的指令。如果”取指”失败就会进入该中断。CPU无法获取指令,所以这种情况下可以认为系统”挂了”。
0X10 数据访问中止中断(Data Abort) 中止模式 CPU读取数据终止,就是说系统读数据错误、读不到数据,所以这种中断后系统也是不正常的。
0X14 未使用(Not Used) 未使用
0X18 IRQ 中断(IRQ Interrupt) 外部中断模式(IRQ) 芯片内部的外设中断(串口中断、DMA中断、外部中断等等)都会引起此中断的发生。
0X1C FIQ 中断(FIQ Interrupt) 快速中断模式(FIQ) 如果需要快速处理中断的话就可以使用此中断。

异常向量表并不总是从 0 地址开始,IMX6ULL 可以设置 vector base 寄存器,指定向量表在其他位置,比如设置 vector base 为 0x80000000,指定为 DDR 的某个地址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是 0,中断是 0x18。

三、共享中断实现

无论是 SPI 中断、PPI 中断、还是 SGI 中断,它们都链接到了 CPU 接口,而 CPU 接口输出到 CPU 的只有两个 FIQ 和 IRQ(VFIQ 和 VIRQ 这里没有用到,暂时忽略)。 中断标号为 0~1019 的任意一个中断发生后 CPU 都会跳转到 FIQ 或 IRQ 中断服务函数去执行。在官方裸机代码中默认关闭了 FIQ,只使用了 IRQ。

3.1 IRQ共享中断实现

NXP 官方裸机共享中断实现代码如下所示, 我们将参照官方代码讲解并最终将官方启动文件移植到我们自己的工程中。

部分代码:

IRQ_Handler:

    push    {lr}         /* Save return address+4     */
    push    {r0-r3, r12} /* Push caller save registers           */

    MRS     r0, spsr    /* Save SPRS to allow interrupt reentry  */
    push    {r0}

    MRC     P15, 4, r1, C15, C0, 0   /* Get GIC base address  */
    ADD     r1, r1, #0x2000          /* r1: GICC base address  */
    LDR     r0, [r1, #0xC]           /* r0: IAR */

    push    {r0, r1}

   CPS  #0x13 /* Change to Supervisor mode to allow interrupt reentry */

    push    {lr}            /* Save Supervisor lr  */
    LDR     r2, =SystemIrqHandler
    BLX     r2              /* Call SystemIrqHandler with param GCC */
    POP     {lr}

    CPS     #0x12           /* Back to IRQ mode */

    POP     {r0, r1}

    STR     r0, [r1, #0x10] /* Now IRQ handler finished: write to EOIR */

    POP     {r0}
    MSR     spsr_cxsf, r0

    POP     {r0-r3, r12}
    POP     {lr}
    SUBS    pc, lr, #4
    .size IRQ_Handler, . - IRQ_Handler

    .align 2
    .arm
    .weak FIQ_Handler
    .type FIQ_Handler, %function

代码解析:

push    {lr}         /* Save return address+4     */
push    {r0-r3, r12} /* Push caller save registers           */
MRS     r0, spsr    /* Save SPRS to allow interrupt reentry  */
push    {r0}
MRC     P15, 4, r1, C15, C0, 0   /* Get GIC base address  */
ADD     r1, r1, #0x2000          /* r1: GICC base address  */
LDR     r0, [r1, #0xC]           /* r0: IAR */
push    {r0, r1}
CPS  #0x13 /* Change to Supervisor mode to allow interrupt reentry */
push    {lr}            /* Save Supervisor lr  */
LDR     r2, =SystemIrqHandler
BLX     r2              /* Call SystemIrqHandler with param GCC */
POP     {lr}

3.2 SystemIrqHandler共享中断处理函数

函数 SystemIrqHandler 保存在 SDK_2.2_MCIM6ULL\devices\MCIMX6Y2\system_MCIMX6Y2.c 文件内。


去掉不必要的条件编译后如下所示。部分代码:
__attribute__((weak)) void SystemIrqHandler(uint32_t giccIar)
{
  uint32_t intNum = giccIar & 0x3FFUL;

  /* Spurious interrupt ID or Wrong interrupt number */
  if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
  {
    return;
  }

  irqNesting++;

  __enable_irq();      /* Support nesting interrupt */

  /* Now call the real irq handler for intNum */
  irqTable[intNum].irqHandler(giccIar, irqTable[intNum].userParam);

  __disable_irq();
  irqNesting--;
}

代码解析:

__attribute__((weak)) void SystemIrqHandler(uint32_t giccIar)
{
  ···
}
uint32_t intNum = giccIar & 0x3FFUL;
if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
{
  return;
}
/* Now call the real irq handler for intNum */
irqTable[intNum].irqHandler(giccIar, irqTable[intNum].userParam);

本地中断向量表定义在system_MCIMX6Y2.h文件:

/*中断数量*/
#define NUMBER_OF_INT_VECTORS 160
/*本地中断向量表*/
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

本地中断向量表 的作用是当IRQ中断发生后找到对应的处理函数。从上方代码不难看出,本地中断向量表是一个sys_irq_handle_t结构体类型的数组。结构体如下所示:

typedef void (*system_irq_handler_t) (uint32_t giccIar, void *param);
/**
 * @brief IRQ handle for specific IRQ
 */
typedef struct _sys_irq_handle
{
   //执行完成后IRQ中断并没有结束,这部分代码用于标记中断处理完成并恢复中断前的状态。
   system_irq_handler_t irqHandler; /**< IRQ handler for specific IRQ */
   void *userParam;    /**< User param for handler callback */
} sys_irq_handle_t;

sys_irq_handle_t结构体中有一个函数指针和函数参数指针,函数指针用于指定中断的中断处理函数,函数参数指针用于指定中断处理程序的用户参数。初始化中断时我们会根据中断编号初始化对应的数组项(irqTable[])。同样,中断发生后在SystemIrqHandler函数中根据中断标号找到对应的中断处理函数。

四、引脚确定

我使用的是 野火_EBF6ULL S1 Pro 开发板


KEY按键连接至 SNVS_TAMPER1 引脚,用作普通的按键。

板上4个按键的信息及相应GPIO端口引脚号的总结具体如下:

按键 丝印编号 GPIO功能 按键按下时的电平 其它功能
RST复位按键 SW1 不支持 低电平 复位芯片
ON/OFF按键 SW3 不支持 低电平 从低功耗唤醒
MODE按键 SW4 支持 BOOT_MODE[0]与BOOT_MODE[1]相反 选择芯片启动方式
KEY按键 SW2 支持 高电平

五、编程流程

1. 创建工程文件夹
2. 移植官方SDK寄存器定义文件
3. 移植官方SDK引脚复用和引脚属性定义文件
4. 移植官方SDK中断相关文件
5. 移植野火PAD属性配置文件
6. 编写启动文件
7. 编写链接文件
8. 编写makefile文件
9. 编写C语言代码
(1) 添加中断服务函数到“中断向量表”
(2) 开启GPIO时钟
(3) 设置引脚的复用功能以及引脚PAD属性
(4) 设置引脚方向
(5) 设置引脚中断类型
(6) 使能引脚中断

六、创建工程文件夹

  1. 创建一个文件夹 interrupt_init
  2. 创建一个用于存放头文件的文件夹 include
  3. 创建一个用于存放驱动源码的文件 device
  4. 创建一个启动文件 start.S
  5. 创建一个源文件 main.c
  6. 创建一个链接脚本 base.lds

七、移植官方SDK寄存器定义文件

/interrupt_init/include 目录下添加官方SDK寄存器定义文件 MCIMX6Y2.h,位于 SDK_2.2_MCIM6ULL_EBF6ULL/devices/MCIMX6Y2 目录下。

在官方SDK的头文件 MCIMX6Y2.h 文件多达4万多行,包含了i.MX6U芯片几乎所有的寄存器定义以及中断编号的定义。

这里只列 GPIO1相关寄存器 的部分代码。其他寄存器定义与此类似。 添加这些定义之后我们就可以 直接使用 “GPIO1->DR” 语句操作GPIO1的DR寄存器。操作方法与STM32非常相似。

typedef struct {
   __IO uint32_t DR;     /**< GPIO data register, offset: 0x0 */
   __IO uint32_t GDIR;   /**< GPIO direction register, offset: 0x4 */
   __I  uint32_t PSR;    /**< GPIO pad status register, offset: 0x8 */
   __IO uint32_t ICR1;   /**< GPIO interrupt configuration register1,*/
   __IO uint32_t ICR2;   /**< GPIO interrupt configuration register2, */
   __IO uint32_t IMR;   /**< GPIO interrupt mask register, offset: 0x14 */
   __IO uint32_t ISR; /**< GPIO interrupt status register, offset: 0x18 */
   __IO uint32_t EDGE_SEL;/**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;

/*********************以下代码省略***************************8*/
/** Peripheral GPIO1 base address */
#define GPIO1_BASE                               (0x209C000u)
/** Peripheral GPIO1 base pointer */
#define GPIO1                                    ((GPIO_Type *)GPIO1_BASE)

八、移植官方SDK引脚复用和引脚属性定义文件

/interrupt_init/include 目录下添加官方SDK引脚复用和引脚属性定义文件 fsl_iomuxc.h,位于 SDK_2.2_MCIM6ULL_EBF6ULL/devices/MCIMX6Y2/drivers 目录下。

使用每一个引脚之前我们都要选择引脚的复用功能以及引脚的pad属性。在官方SDK的 fsl_iomuxc.h 中定义了所有可用引脚以及这些引脚的所有复用功能,我们需要哪种复用功能只需要选择即可,并且官方SDK中提供了初始化函数。

#define IOMUXC_GPIO1_IO00_I2C2_SCL \
                        0x020E005CU, 0x0U, 0x020E05ACU, 0x1U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_GPT1_CAPTURE1L \
                        0x020E005CU, 0x1U, 0x020E058CU, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ANATOP_OTG1_IDL   \
                        0x020E005CU, 0x2U, 0x020E04B8U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ENET1_REF_CLK1L  \
                        0x020E005CU, 0x3U, 0x020E0574U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_MQS_RIGHTL  \
                        0x020E005CU, 0x4U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_GPIO1_IO00L  \
                        0x020E005CU, 0x5U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_ENET1_1588_EVENT0_INL \
                        0x020E005CU, 0x6U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_SRC_SYSTEM_RESETL  \
                        0x020E005CU, 0x7U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO00_WDOG3_WDOG_BL   \
                        0x020E005CU, 0x8U, 0x00000000U, 0x0U, 0x020E02E8U
#define IOMUXC_GPIO1_IO01_I2C2_SDAL    \
                        0x020E0060U, 0x0U, 0x020E05B0U, 0x1U, 0x020E02ECU
#define IOMUXC_GPIO1_IO01_GPT1_COMPARE1L  \
                        0x020E0060U, 0x1U, 0x00000000U, 0x0U, 0x020E02ECU
#define IOMUXC_GPIO1_IO01_USB_OTG1_OCL    \
                        0x020E0060U, 0x2U, 0x020E0664U, 0x0U, 0x020E02ECU
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
                                    uint32_t muxMode,
                                    uint32_t inputRegister,
                                    uint32_t inputDaisy,
                                    uint32_t configRegister,
                                    uint32_t inputOnfield)
{
   *((volatile uint32_t *)muxRegister) =
                  IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) |\
                  IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);

   if (inputRegister)
   {
      *((volatile uint32_t *)inputRegister) = \
      IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
   }
}
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
                                       uint32_t muxMode,
                                       uint32_t inputRegister,
                                       uint32_t inputDaisy,
                                       uint32_t configRegister,
                                       uint32_t configValue)
{
   if (configRegister)
   {
      *((volatile uint32_t *)configRegister) = configValue;
   }
}

代码屏蔽 #include "fsl_common.h"

九、移植官方SDK中断相关文件

9.1 移植头文件

/interrupt_init/include 目录下共添加了5个中断相关的头文件。

9.2 移植源文件

/interrupt_init/device 目录下添加 系统初始化源文件

位于/SDK_2.2_MCIM6ULL/devices/MCIMX6Y2目录下,这里包含我们需要的中断初始化代码以及MMU、时钟等等初始化代码。

代码修改成如下:

/*!
 * @file MCIMX6Y2
 * @version 3.0
 * @date 2017-02-28
 * @brief Device specific configuration file for MCIMX6Y2 (implementation file)
 *
 * Provides a system configuration function and a global variable that contains
 * the system frequency. It configures the device and initializes the oscillator
 * (PLL) that is part of the microcontroller device.
 */

#include <stdint.h>
#include "led.h"
#include "system_MCIMX6Y2.h"

uint32_t __VECTOR_TABLE = 0x80002000;


/* Local irq table and nesting level value */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
static uint32_t irqNesting;



/* ----------------------------------------------------------------------------
   -- Core clock
   ---------------------------------------------------------------------------- */

uint32_t SystemCoreClock = DEFAULT_SYSTEM_CLOCK;

/* ----------------------------------------------------------------------------
   -- SystemInit()
   ---------------------------------------------------------------------------- */

void SystemInit(void)
{
  uint32_t sctlr;
  uint32_t actlr;
 #if ((__FPU_PRESENT == 1) && (__FPU_USED == 1))
  uint32_t cpacr;
  uint32_t fpexc;
 #endif

  L1C_InvalidateInstructionCacheAll();
  L1C_InvalidateDataCacheAll();

  actlr = __get_ACTLR();
  actlr = (actlr | ACTLR_SMP_Msk); /* Change to SMP mode before enable DCache */
  __set_ACTLR(actlr);

  sctlr = __get_SCTLR();
  sctlr = (sctlr & ~(SCTLR_V_Msk | /* Use low vector */
                     SCTLR_A_Msk | /* Disable alignment fault checking */
                     SCTLR_M_Msk)) /* Disable MMU */
          | (SCTLR_I_Msk |         /* Enable ICache */
             SCTLR_Z_Msk |         /* Enable Prediction */
             SCTLR_CP15BEN_Msk |   /* Enable CP15 barrier operations */
             SCTLR_C_Msk);         /* Enable DCache */
  __set_SCTLR(sctlr);

  /* Set vector base address */
  GIC_Init();

  __set_VBAR((uint32_t)__VECTOR_TABLE);
  // rgb_led_init();
  // blue_led_on;
 #if ((__FPU_PRESENT == 1) && (__FPU_USED == 1))
  cpacr = __get_CPACR();
  /* Enable NEON and FPU */
  cpacr = (cpacr & ~(CPACR_ASEDIS_Msk | CPACR_D32DIS_Msk)) | (3UL << CPACR_cp10_Pos) | (3UL << CPACR_cp11_Pos);
  __set_CPACR(cpacr);

  fpexc = __get_FPEXC();
  fpexc |= 0x40000000UL; /* Enable NEON and FPU */
  __set_FPEXC(fpexc);
 #endif /* ((__FPU_PRESENT == 1) && (__FPU_USED == 1)) */
}



// /* ----------------------------------------------------------------------------
//    -- SystemCoreClockUpdate()
//    ---------------------------------------------------------------------------- */

// void SystemCoreClockUpdate (void) {
//   /* i.MX6ULL systemCoreClockUpdate */
//   uint32_t PLL1SWClock;
//   uint32_t PLL2MainClock;
//   if (CCM->CCSR & CCM_CCSR_PLL1_SW_CLK_SEL_MASK)
//   {
//     if (CCM->CCSR & CCM_CCSR_STEP_SEL_MASK)
//     {
//         /* Get SYS PLL clock*/
//         if (CCM_ANALOG->PLL_SYS & CCM_ANALOG_PLL_SYS_DIV_SELECT_MASK)
//         {
//           PLL2MainClock = (24000000UL * 22UL + (uint64_t)(24000000UL) * (uint64_t)(CCM_ANALOG->PLL_SYS_NUM) / (uint64_t)(CCM_ANALOG->PLL_SYS_DENOM));
//         }
//         else
//         {
//           PLL2MainClock = (24000000UL * 20UL + (uint64_t)(24000000UL) * (uint64_t)(CCM_ANALOG->PLL_SYS_NUM) / (uint64_t)(CCM_ANALOG->PLL_SYS_DENOM));
//         }

//         if (CCM->CCSR & CCM_CCSR_SECONDARY_CLK_SEL_MASK)
//         {
//             /* PLL2 ---> Secondary_clk ---> Step Clock ---> CPU Clock */
//             PLL1SWClock = PLL2MainClock;
//         }
//         else
//         {
//             /* PLL2 PFD2 ---> Secondary_clk ---> Step Clock ---> CPU Clock */
//             PLL1SWClock = ((uint64_t)PLL2MainClock * 18) / ((CCM_ANALOG->PFD_528 & CCM_ANALOG_PFD_528_PFD2_FRAC_MASK) >> CCM_ANALOG_PFD_528_PFD2_FRAC_SHIFT);
//         }
//     }
//     else
//     {
//       /* Osc_clk (24M) ---> Step Clock ---> CPU Clock */
//       PLL1SWClock = 24000000UL;
//     }
//   }
//   else
//   {
//     /* ARM PLL ---> CPU Clock */
//     PLL1SWClock = 24000000UL;
//     PLL1SWClock = ( PLL1SWClock * (CCM_ANALOG->PLL_ARM & CCM_ANALOG_PLL_ARM_DIV_SELECT_MASK) >> CCM_ANALOG_PLL_ARM_DIV_SELECT_SHIFT) >> 1UL;
//    }

//   SystemCoreClock = PLL1SWClock / (((CCM->CACRR & CCM_CACRR_ARM_PODF_MASK) >> CCM_CACRR_ARM_PODF_SHIFT) + 1UL);
// }




/* ----------------------------------------------------------------------------
   -- SystemInstallIrqHandler()
   ---------------------------------------------------------------------------- */

void SystemInstallIrqHandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
  irqTable[irq].irqHandler = handler;
  irqTable[irq].userParam = userParam;
}

/* ----------------------------------------------------------------------------
   -- SystemIrqHandler()
   ---------------------------------------------------------------------------- */

__attribute__((weak)) void SystemIrqHandler(uint32_t giccIar)
{

  uint32_t intNum = giccIar & 0x3FFUL;

  // rgb_led_init();
  // blue_led_on;

  /* Spurious interrupt ID or Wrong interrupt number */
  if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
  {
    return;
  }

  irqNesting++;

  // __enable_irq();      /* Support nesting interrupt */

  /* Now call the real irq handler for intNum */
  irqTable[intNum].irqHandler(giccIar, irqTable[intNum].userParam);

  // __disable_irq();

  irqNesting--;
}





// uint32_t SystemGetIRQNestingLevel(void)
// {
//   return irqNesting;
// }

/* Leverage GPT1 to provide Systick */
// void SystemSetupSystick(uint32_t tickRateHz, void *tickHandler, uint32_t intPriority)
// {
//     uint32_t clockFreq;
//     uint32_t spllTmp;

//     /* Install IRQ handler for GPT1 */
//     SystemInstallIrqHandler(GPT1_IRQn, (system_irq_handler_t)(uint32_t)tickHandler, NULL);

//     /* Enable Systick all the time */
//     CCM->CCGR1 |= CCM_CCGR1_CG10_MASK | CCM_CCGR1_CG11_MASK;

//     GPT1->CR = GPT_CR_SWR_MASK;
//     /* Wait reset finished. */
//     while (GPT1->CR == GPT_CR_SWR_MASK)
//     {
//     }
//     /* Use peripheral clock source IPG */
//     GPT1->CR = GPT_CR_WAITEN_MASK | GPT_CR_STOPEN_MASK | GPT_CR_DOZEEN_MASK |
//                GPT_CR_DBGEN_MASK | GPT_CR_ENMOD_MASK | GPT_CR_CLKSRC(1UL);
//     /* Set clock divider to 1 */
//     GPT1->PR = 0;

//     /* Get IPG clock*/
//     /* Periph_clk2_clk ---> Periph_clk */
//     if (CCM->CBCDR & CCM_CBCDR_PERIPH_CLK_SEL_MASK)
//     {
//         switch (CCM->CBCMR & CCM_CBCMR_PERIPH_CLK2_SEL_MASK)
//         {
//             /* Pll3_sw_clk ---> Periph_clk2_clk ---> Periph_clk */
//             case CCM_CBCMR_PERIPH_CLK2_SEL(0U):
//                 clockFreq = (24000000UL * ((CCM_ANALOG->PLL_USB1 & CCM_ANALOG_PLL_USB1_DIV_SELECT_MASK) ? 22U : 20U));
//                 break;

//             /* Osc_clk ---> Periph_clk2_clk ---> Periph_clk */
//             case CCM_CBCMR_PERIPH_CLK2_SEL(1U):
//                 clockFreq = 24000000UL;
//                 break;

//             case CCM_CBCMR_PERIPH_CLK2_SEL(2U):
//             case CCM_CBCMR_PERIPH_CLK2_SEL(3U):
//             default:
//                 clockFreq = 0U;
//                 break;
//         }

//         clockFreq /= (((CCM->CBCDR & CCM_CBCDR_PERIPH_CLK2_PODF_MASK) >> CCM_CBCDR_PERIPH_CLK2_PODF_SHIFT) + 1U);
//     }
//     /* Pll2_main_clk ---> Periph_clk */
//     else
//     {
//         /* Get SYS PLL clock*/
//         if (CCM_ANALOG->PLL_SYS & CCM_ANALOG_PLL_SYS_DIV_SELECT_MASK)
//         {
//           spllTmp = (24000000UL * 22UL + (uint64_t)(24000000UL) * (uint64_t)(CCM_ANALOG->PLL_SYS_NUM) / (uint64_t)(CCM_ANALOG->PLL_SYS_DENOM));
//         }
//         else
//         {
//           spllTmp = (24000000UL * 20UL + (uint64_t)(24000000UL) * (uint64_t)(CCM_ANALOG->PLL_SYS_NUM) / (uint64_t)(CCM_ANALOG->PLL_SYS_DENOM));
//         }

//         switch (CCM->CBCMR & CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK)
//         {
//             /* PLL2 ---> Pll2_main_clk ---> Periph_clk */
//             case CCM_CBCMR_PRE_PERIPH_CLK_SEL(0U):
//                 clockFreq = spllTmp;
//                 break;

//             /* PLL2 PFD2 ---> Pll2_main_clk ---> Periph_clk */
//             case CCM_CBCMR_PRE_PERIPH_CLK_SEL(1U):
//                 clockFreq = ((uint64_t)spllTmp * 18) / ((CCM_ANALOG->PFD_528 & CCM_ANALOG_PFD_528_PFD2_FRAC_MASK) >> CCM_ANALOG_PFD_528_PFD2_FRAC_SHIFT);
//                 break;

//             /* PLL2 PFD0 ---> Pll2_main_clk ---> Periph_clk */
//             case CCM_CBCMR_PRE_PERIPH_CLK_SEL(2U):
//                 clockFreq = ((uint64_t)spllTmp * 18) / ((CCM_ANALOG->PFD_528 & CCM_ANALOG_PFD_528_PFD0_FRAC_MASK) >> CCM_ANALOG_PFD_528_PFD0_FRAC_SHIFT);
//                 break;

//             /* PLL2 PFD2 divided(/2) ---> Pll2_main_clk ---> Periph_clk */
//             case CCM_CBCMR_PRE_PERIPH_CLK_SEL(3U):
//                 clockFreq = ((((uint64_t)spllTmp * 18) / ((CCM_ANALOG->PFD_528 & CCM_ANALOG_PFD_528_PFD2_FRAC_MASK) >> CCM_ANALOG_PFD_528_PFD2_FRAC_SHIFT)) >> 1U);
//                 break;

//             default:
//                 clockFreq = 0U;
//                 break;
//         }
//     }
//     clockFreq /= (((CCM->CBCDR & CCM_CBCDR_AHB_PODF_MASK) >> CCM_CBCDR_AHB_PODF_SHIFT) + 1U);
//     clockFreq /= (((CCM->CBCDR & CCM_CBCDR_IPG_PODF_MASK) >> CCM_CBCDR_IPG_PODF_SHIFT) + 1U);

//     /* Set timeout value and enable interrupt */
//     GPT1->OCR[0] = clockFreq / tickRateHz - 1UL;
//     GPT1->IR = GPT_IR_OF1IE_MASK;

//     /* Set interrupt priority */
//     GIC_SetPriority(GPT1_IRQn, intPriority);
//     /* Enable IRQ */
//     GIC_EnableIRQ(GPT1_IRQn);

//     /* Start GPT counter */
//     GPT1->CR |= GPT_CR_EN_MASK;
// }

// void SystemClearSystickFlag(void)
// {
//     GPT1->SR = GPT_SR_OF1_MASK;
// }

9.3 GIC相关API操作函数

core_ca7.h 中的 10 个 API 函数如下:

函数 描述
GIC_Init 初始化 GIC。
GIC_EnableIRQ 使能指定的外设中断。
GIC_DisableIRQ 关闭指定的外设中断。
GIC_AcknowledgeIRQ 返回中断号。
GIC_DeactivateIRQ 无效化指定中断。
GIC_GetRunningPriority 获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping 设置抢占优先级位数。
GIC_GetPriorityGrouping 获取抢占优先级位数。
GIC_SetPriority 设置指定中断的优先级。
GIC_GetPriority 获取指定中断的优先级。

十、移植野火PAD属性配置文件

/interrupt_init/device 目录下添加 pad_config.h

通常情况下一个引脚要设置8种PAD属性,而这些属性只能通过数字指定。为简化PAD属性设置野火编写了一个PAD属性配置文件 pad_config.h (embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h)【源码下载:https://gitee.com/Embedfire/embed_linux_driver_tutorial_imx6_code.git】,这里使用宏定义了引脚可选的PAD属性值,并且通过宏定义的名字很容易知道宏代表的属性值:

/* SPEED 带宽配置 */
#define SPEED_0_LOW_50MHz       IOMUXC_SW_PAD_CTL_PAD_SPEED(0)
#define SPEED_1_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(1)
#define SPEED_2_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(2)
#define SPEED_3_MAX_200MHz      IOMUXC_SW_PAD_CTL_PAD_SPEED(3)


/* PUE 选择使用保持器还是上下拉 */
#define PUE_0_KEEPER_SELECTED       IOMUXC_SW_PAD_CTL_PAD_PUE(0)
#define PUE_1_PULL_SELECTED         IOMUXC_SW_PAD_CTL_PAD_PUE(1)


/* PUS 上下拉配置 */
#define PUS_0_100K_OHM_PULL_DOWN  IOMUXC_SW_PAD_CTL_PAD_PUS(0)
#define PUS_1_47K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(1)
#define PUS_2_100K_OHM_PULL_UP    IOMUXC_SW_PAD_CTL_PAD_PUS(2)
#define PUS_3_22K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(3)

完整的代码请阅读源文件,这里只列出了文件“pad_config.h”部分代码(embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h)【源码下载:https://gitee.com/Embedfire/embed_linux_driver_tutorial_imx6_code.git】。

十一、编写启动文件

启动文件start.S参照官方GCC版本启动文件startup_MCIMX6Y2.S修改,位于/SDK_2.2_MCIM6ULL/devices/MCIMX6Y2/gcc

11.1 完整代码

/*DDR的前8K字节保留, [0x80000000-0x80001FFF] 保留为ROM区域 */

/*定义内存起始地址和大小*/
#define m_DDR_start             0x80000000
#define m_DDR_size              0x20000000

/*定义主代码区域,m_text_start将会作为中断向量表的起始地址,链接脚本中
*将该地址用作起始链接地址。
*/
#define  m_text_start           0x80002000

/*定义Supervisor工作模式的栈起始地址和大小
*野火开发板标配512M字节的DDR, Supervisor工作模式的栈和IRQ工作模式的栈
*位于DDR的后2M地址,大小均为1M。
*/
#define   SUP_model_stack_start     0x9FE00000
#define   SUP_model_stack_size      0x00100000


/*定义IRQ工作模式的栈起始地址和大小,大小为1M*/
#define   IRQ_model_stack_start     0x9FF00000
#define   IRQ_model_stack_size      0x00100000



.globl light_led

.text
.align 2         //设置字节对齐
.global _start
_start:

    ldr     pc, =Reset_Handler           /* Reset                  */
    ldr     pc, =Undefined_Handler       /* Undefined instructions */
    ldr     pc, =SVC_Handler             /* Supervisor Call        */
    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */
    ldr     pc, =DataAbort_Handler       /* Data abort             */
    .word   0                            /* RESERVED               */
    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */
    ldr     pc, =FIQ_Handler             /* FIQ interrupt          */



Reset_Handler:
    cpsid   i                         /* 全局关闭中断 */

/* 关闭 I,DCache 和 MMU 
 * 采取读-改-写的方式。
 */
    mrc     p15, 0, r0, c1, c0, 0     /*读取CP15系统控制寄存器   */
    bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
    bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
    bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
    bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)关闭分支预测   */
    bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
    mcr     p15, 0, r0, c1, c0, 0     /*  将修改后的值写回CP15寄存器   */

/* 设置各个模式下的栈指针,
 * 注意:IMX6UL 的堆栈是向下增长的!
 * 堆栈指针地址一定要是 4 字节地址对齐的!!!
 * DDR 范围:0X80000000~0X9FFFFFFF 或者 0X8FFFFFFF
 */
    /* 定义IRQ工作模式的栈起始地址 */
    cps     #0x12                
    ldr     sp, =IRQ_model_stack_start    
    
    /*定义User工作模式的栈起始地址,与Supervisor相同*/
    cps     #0x1F               
    ldr     sp, =SUP_model_stack_start    

    /*定义Supervisor工作模式的栈起始地址,与User相同 */
    cps     #0x13                
    ldr     sp, =SUP_model_stack_start   

    /*跳转到系统初始化函数,初始化GIC、CACHE-L1、mmu等等*/
    ldr     r2, =SystemInit      
    blx     r2  
    
    /*开启全局中断*/
    cpsie   i                   
    
    /*跳转到到 main 函数执行,*/
    b main                
    b .        /*死循环*/





Undefined_Handler:
    b Undefined_Handler
    .size Undefined_Handler, . - Undefined_Handler

    .align 2
    .arm
    .weak SVC_Handler
    .type SVC_Handler, %function
SVC_Handler:
    ldr   r0,=SVC_Handler
    bx    r0
    .size SVC_Handler, . - SVC_Handler

    .align 2
    .arm
    .weak PrefAbort_Handler
    .type PrefAbort_Handler, %function
PrefAbort_Handler:
    ldr   r0,=PrefAbort_Handler
    bx    r0
    .size PrefAbort_Handler, . - PrefAbort_Handler

    .align 2
    .arm
    .weak DataAbort_Handler
    .type DataAbort_Handler, %function
DataAbort_Handler:
    ldr   r0,=DataAbort_Handler
    bx    r0
    .size DataAbort_Handler, . - DataAbort_Handler

    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function
IRQ_Handler:
    push    {lr}                         /* Save return address+4                                */
    push    {r0-r3, r12}                 /* Push caller save registers                           */

    MRS     r0, spsr                     /* Save SPRS to allow interrupt reentry                 */
    push    {r0}

    MRC     P15, 4, r1, C15, C0, 0       /* Get GIC base address  */
    ADD     r1, r1, #0x2000              /* r1: GICC base address  */
    LDR     r0, [r1, #0xC]               /* r0: IAR  */

    push    {r0, r1}

    CPS     #0x13                        /* Change to Supervisor mode to allow interrupt reentry */

    push    {lr}                         /* Save Supervisor lr  */
    ldr     r2, =SystemIrqHandler
    blx     r2
                           
    POP     {lr}

    CPS     #0x12                        /* Back to IRQ mode                                     */

    POP     {r0, r1}

    STR     r0, [r1, #0x10]              /* Now IRQ handler finished: write to EOIR              */

    POP     {r0}
    MSR     spsr_cxsf, r0

    POP     {r0-r3, r12}
    POP     {lr}
    SUBS    pc, lr, #4
    .size IRQ_Handler, . - IRQ_Handler

    .align 2
    .arm
    .weak FIQ_Handler
    .type FIQ_Handler, %function


FIQ_Handler:
    ldr   r0,=FIQ_Handler
    bx    r0
    .size FIQ_Handler, . - FIQ_Handler

    .end

11.2 分析代码

/*DDR的前8K字节保留, [0x80000000-0x80001FFF] 保留为ROM区域 */

/*定义内存起始地址和大小*/
#define m_DDR_start             0x80000000
#define m_DDR_size              0x20000000

/*定义主代码区域,m_text_start将会作为中断向量表的起始地址,链接脚本中
*将该地址用作起始链接地址。
*/
#define  m_text_start           0x80002000

/*定义Supervisor工作模式的栈起始地址和大小
*野火开发板标配512M字节的DDR, Supervisor工作模式的栈和IRQ工作模式的栈
*位于DDR的后2M地址,大小均为1M。
*/
#define   SUP_model_stack_start     0x9FE00000
#define   SUP_model_stack_size      0x00100000


/*定义IRQ工作模式的栈起始地址和大小,大小为1M*/
#define   IRQ_model_stack_start     0x9FF00000
#define   IRQ_model_stack_size      0x00100000
.text            //代码段
.align 2         //设置2字节对齐
.global _start   //定义一个全局标号
_start:          //程序的开始

    ldr     pc, =Reset_Handler           /* Reset                  */
    ldr     pc, =Undefined_Handler       /* Undefined instructions */
    ldr     pc, =SVC_Handler             /* Supervisor Call        */
    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */
    ldr     pc, =DataAbort_Handler       /* Data abort             */
    .word   0                            /* RESERVED               */
    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */
    ldr     pc, =FIQ_Handler             /* FIQ interrupt          */
Reset_Handler:
    cpsid   i                         /* 全局关闭中断 */

    mrc     p15, 0, r0, c1, c0, 0     /*读取CP15系统控制寄存器   */
    bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
    bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
    bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
    bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)分支预测   */
    bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
    mcr     p15, 0, r0, c1, c0, 0     /*  将修改后的值写回CP15寄存器   */
/* 定义IRQ工作模式的栈起始地址 */
cps     #0x12
ldr     sp, =IRQ_model_stack_start

/*定义User工作模式的栈起始地址,与Supervisor相同*/
cps     #0x1F
ldr     sp, =SUP_model_stack_start

/*定义Supervisor工作模式的栈起始地址,与User相同 */
cps     #0x13
ldr     sp, =SUP_model_stack_start
/*跳转到系统初始化函数,初始化GIC、CACHE-L1、mmu等等*/
ldr     r2, =SystemInit
blx     r2
/*开启全局中断*/
cpsie   i

/*跳转到到 main 函数执行,*/
b main

SystemInit()函数执行完成后,系统已经可以接受中断了。SystemInit()函数如下所示:

void SystemInit(void)
{
  uint32_t sctlr;
  uint32_t actlr;
/*FPU 相关代码省略*/

  L1C_InvalidateInstructionCacheAll();
  L1C_InvalidateDataCacheAll();

  actlr = __get_ACTLR();
  actlr = (actlr | ACTLR_SMP_Msk); /* Change to SMP mode before enable DCache */
  __set_ACTLR(actlr);

  sctlr = __get_SCTLR();
  sctlr = (sctlr & ~(SCTLR_V_Msk | /* Use low vector */
                     SCTLR_A_Msk | /* Disable alignment fault checking */
                     SCTLR_M_Msk)) /* Disable MMU */
          | (SCTLR_I_Msk |         /* Enable ICache */
             SCTLR_Z_Msk |         /* Enable Prediction */
             SCTLR_CP15BEN_Msk |   /* Enable CP15 barrier operations */
             SCTLR_C_Msk);         /* Enable DCache */
  __set_SCTLR(sctlr);

  /* Set vector base address */
  GIC_Init();
  __set_VBAR((uint32_t)__VECTOR_TABLE);

/*FPU 相关代码省略*/
}

第7、8行: 无效化Icache和Dcache。虽然我们前面已经关闭了cache这里再次无效化cache可能出于安全考虑,我们不深究,参考官方的写即可。
第10-11行: 修改辅助控制寄存器ACTLR,使能SMP模式。
第14-22行: 修改系统控制寄存器,开启我们需要的的功能,例如默认开启了Cahe,关闭了MMU。
第25行: 初始化GIC。在GIC_Init初始化函数可直接使用NXP官方函数,无需修改。
第27行: 这部分非常重要,它用于设置中断向量表起始地址。在程序中,中断向量表可以放到任意的位置,但是必须将中断向量表的起始地址写入VBAR寄存器。这样中断发生后CPU才能找到中断向量表。

十二、编写链接脚本

写好的代码(无论是汇编还是C语言)都要经过编译、汇编、链接等步骤生成二进制文件或者可供下载的文件。在编译阶编译器会对每个源文件进行语法检查并生成对应的汇编语言,汇编是将汇编文件转化为机器码。

使用 arm-none-eabi-gcc -g -c led.S -o led.o 命令完成源码的编译、汇编工作,生成了 .o文件。编译和汇编是针对单个源文件,也就编译完成后一个源文件(.c.S.s)对应一个 .o 文件。程序链接阶段就会将这些 .o 链接成一个文件。

链接脚本的作用就是告诉编译器怎么链接这些文件,比如那个文件放在最前面,程序的代码段、数据段、bss段分别放在什么位置等等。

/interrupt_init 下创建 base.lds

12.1 完整代码

ENTRY(_start)
SECTIONS {
    . = 0x80002000;
    
    . = ALIGN(4);
    .text :
    {
    start.o (.text)
    *(.text)
    }

    . = ALIGN(4);
    .data : 
    {
    *(.data)
    }
    
    . = ALIGN(4);
    .bss : 
    {
    *(.bss) 
    }
}

12.2 分析代码

 ENTRY(_start)
 SECTIONS {
···
···
}
. = 0x80002000;
   . = ALIGN(4);
   .text :
   {
   start.o (.text)
   *(.text)
   }
   . = ALIGN(4);
   .data :
   {
   *(.data)
   }
. = ALIGN(4);
   .bss :
   {
   *(.bss)
   }

十三、编写makefile文件

程序编写完成后需要依次输入编译、链接、格式转换命令才能最终生成二进制文件。这种编译方式效率低、容易出错。

使用makefile只需要在所在文件夹下执行make命令,makefile工具便会自动完成程序的编译、链接、格式转换等工作。正常情况下我们可以在当前目录看到生成的一些中间文件以及我们期待的.bin文件。

修改makefile主要包括两部分

13.1 编写子makefile

/interrupt_init/device 下创建 makefile

子makefile: 用于将“device”文件夹下的驱动源文件编译为一个“.o”文件

all : button.o  led.o system_MCIMX6Y2.o
    arm-none-eabi-ld -r $^  -o device.o
    
%.o : %.c
    arm-none-eabi-gcc ${header_file} -c $^
    
%.o : %.S
    arm-none-eabi-gcc ${header_file} -c $^

clean:
    -rm -f *.o *.bak
all : button.o  led.o system_MCIMX6Y2.o
    arm-none-eabi-ld -r $^  -o device.o
%.o : %.c
  arm-none-eabi-gcc ${header_file} -c $^
%.o : %.S
  arm-none-eabi-gcc ${header_file} -c $^
clean:
  -rm -f *.o *.bak

13.2 修改主makefile

主makefile的改动主要有两点:

  1. 在编译命令中指明头文件位置。
  2. 使用命令调用子makefile,生成依赖文件。
#定义变量,用于保存编译选项和头文件保存路径
header_file := -fno-builtin -I$(shell pwd)/include
export header_file


all : start.o main.o device/device.o 
    arm-none-eabi-ld -Tbase.lds $^ -o base.elf 
    arm-none-eabi-objcopy -O binary -S -g base.elf base.bin


%.o : %.S
    arm-none-eabi-gcc -g -c $^ 
%.o : %.c
    arm-none-eabi-gcc $(header_file) -c $^  

#调用其他文件的makefile
device/device.o :
    make -C device all


.PHONY: copy
copy:
    cp ./base.bin  /home/pan/download/embedfire

#定义清理伪目标
.PHONY: clean
clean:
    make -C device clean
    -rm -f *.o *.elf *.bin 
header_file := -fno-builtin -I$(shell pwd)/include
export header_file
all : start.o main.o device/device.o
arm-none-eabi-ld -Tbase.lds $^ -o base.elf
arm-none-eabi-objcopy -O binary -S -g base.elf base.bin
%.o : %.S
  arm-none-eabi-gcc -g -c $^
%.o : %.c
  arm-none-eabi-gcc $(header_file) -c $^
device/device.o :
  make -C device all
.PHONY: clean
clean:
  make -C device clean
  -rm -f *.o *.elf *.bin

十四、编写C语言代码

14.1 添加按键中断初始化和中断服务函数代码

14.1.1 button.h

/interrupt_init/include 下创建 button.h

#ifndef button_h
#define button_h

#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "pad_config.h"
#include "system_MCIMX6Y2.h"

/*按键2 GPIO端口、引脚号及IOMUXC复用宏定义*/
#define button2_GPIO               GPIO5
#define button2_GPIO_PIN           (1U)
#define button2_IOMUXC             IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01

/* 按键PAD配置 */
#define button_PAD_CONFIG_DATA            (SRE_0_SLOW_SLEW_RATE| \
                                        DSE_6_R0_6| \
                                        SPEED_2_MEDIUM_100MHz| \
                                        ODE_0_OPEN_DRAIN_DISABLED| \
                                        PKE_0_PULL_KEEPER_DISABLED| \
                                        PUE_0_KEEPER_SELECTED| \
                                        PUS_0_100K_OHM_PULL_DOWN| \
                                        HYS_1_HYSTERESIS_ENABLED)   
    /* 配置说明 : */
    /* 转换速率: 转换速率慢
      驱动强度: R0/6 
      带宽配置 : medium(100MHz)
      开漏配置: 关闭 
      拉/保持器配置: 关闭
      拉/保持器选择: 保持器(上面已关闭,配置无效)
      上拉/下拉选择: 100K欧姆下拉(上面已关闭,配置无效)
      滞回器配置: 开启 */ 

/*函数*/
void button2_init(void);
int get_button2_status(void);
void interrupt_button2_init(void);
void EXAMPLE_GPIO_IRQHandler(void);

#endif

14.1.2 button.c

/interrupt_init/device 下创建 button.c

为了简化程序,我们并没有使用GIC接口寄存器配置每个中断的中断优先级、中断分组等等。中断优先级和中断优先级分组保持函数SystemInit()设定的默认值。

/*说明:
 * 按键对应Pro 开发板 button2 ,button2输入引脚接有下拉电阻,默认低电平,按键按下后变为高电平。
 * 按键初始化函数有两个,函数button2_init 仅将引脚设置为输入,通过轮询检测按键状态。函数interrupt_button2_init
 * 初始化了GPIO中断,上升沿触发.
*/
#include "button.h"



/*简单延时函数*/
void delay_button(uint32_t count)
{
    volatile uint32_t i = 0;
    for (i = 0; i < count; ++i)
    {
        __asm("NOP"); /* 调用nop空指令 */
    }
}


/*按键初始化函数*/
void button2_init(void)
{
    /*按键初始化*/
    CCM_CCGR1_CG15(0x3);  //开启GPIO5的时钟

    /*设置 绿灯 引脚的复用功能以及PAD属性*/
    IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0);     
    IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, button_PAD_CONFIG_DATA); 

    GPIO5->GDIR &= ~(1<<1);  //设置GPIO5_01为输入模式
}


extern uint8_t button_status;  //按键状态标记 0,按键未按下。1,按键按下
/*按键初始化函数*/
void interrupt_button2_init(void)
{
    volatile uint32_t *icr;  //用于保存 GPIO-ICR寄存器的地址,与 icrShift 变量配合使用
    uint32_t icrShift;       //引脚号大于16时会用到,

    icrShift = button2_GPIO_PIN;  //保存button2引脚对应的 GPIO 号

    /*添加中断服务函数到  "中断向量表"*/
    SystemInstallIrqHandler(GPIO5_Combined_0_15_IRQn, (system_irq_handler_t)EXAMPLE_GPIO_IRQHandler, NULL);
    GIC_EnableIRQ(GPIO5_Combined_0_15_IRQn);                 //开启中断


    CCM_CCGR1_CG15(0x3);  //开启GPIO5的时钟

    /*设置 按键引脚的PAD属性*/
    IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0);     
    IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, button_PAD_CONFIG_DATA); 
    
    /*设置GPIO方向(输入或输出)*/
    GPIO5->IMR &= ~(1 << button2_GPIO_PIN);  //寄存器重置为默认值
    GPIO5->GDIR &= ~(1<<1);                  //设置GPIO5_01为输入模式

    /*设置GPIO引脚中断类型*/
    GPIO5->EDGE_SEL &= ~(1U << button2_GPIO_PIN);//寄存器重置为默认值

    if(button2_GPIO_PIN < 16)
    {
        icr = &(GPIO5->ICR1);
    }
    else
    {
        icr = &(GPIO5->ICR2);
        icrShift -= 16;
    }

    /*按键引脚默认低电平,设置为上升沿触发中断*/
     *icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));

     button2_GPIO->IMR |= (1 << button2_GPIO_PIN); //使能GPIO引脚中断
}


/*按键状态输出函数*/
int get_button2_status(void)
{
    if((GPIO5->DR)&(1<<1))
    {
        delay_button(0xFF);
         if((GPIO5->DR)&(1<<1))
         {
             return 1;
         }
    }
    return 0;
}


/*按键中断处理函数*/
void EXAMPLE_GPIO_IRQHandler(void)
{
    /*按键引脚中断服务函数*/
    button2_GPIO->ISR = 1U << button2_GPIO_PIN;  //清除GIIP中断标志位
    if(button_status > 0)
    {
        button_status = 0;
    }
    else
    {
        button_status = 1;
    }
}
/*添加中断服务函数到  "中断向量表"*/
SystemInstallIrqHandler(GPIO5_Combined_0_15_IRQn, \
  (system_irq_handler_t)EXAMPLE_GPIO_IRQHandler, NULL);
void SystemInstallIrqHandler(IRQn_Type irq, \
              system_irq_handler_t handler,\
                           void *userParam)
{
  irqTable[irq].irqHandler = handler;
  irqTable[irq].userParam = userParam;
}
GIC_EnableIRQ(GPIO5_Combined_0_15_IRQn);                 //开启中断
CCM->CCGR1 |= CCM_CCGR1_CG15(0x3);  //开启GPIO5的时钟
/*设置GPIO方向(输入或输出)*/
GPIO5->IMR &= ~(1 << button2_GPIO_PIN);  //寄存器重置为默认值
GPIO5->GDIR &= ~(1<<1);                  //设置GPIO5_01为输入模式
/*设置GPIO引脚中断类型*/
GPIO5->EDGE_SEL &= ~(1U << button2_GPIO_PIN);//寄存器重置为默认值

if(button2_GPIO_PIN < 16)
{
  icr = &(GPIO5->ICR1);
}
else
{
  icr = &(GPIO5->ICR2);
  icrShift -= 16;
}
button2_GPIO->IMR |= (1 << button2_GPIO_PIN); //使能GPIO引脚中断

14.2 main.c

/interrupt_init 下创建 main.c

GPIO引脚中断初始化完成后,验证代码就比较简单了。我们定义一个全局变量,在按键中断服务函数中循环切换0和1。 在main函数循环检测全局变量的值,大于0则亮红灯,否则亮绿灯。

#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "pad_config.h"
#include "system_MCIMX6Y2.h"

#include "button.h"
#include "led.h"



uint8_t button_status = 0;
/*简单延时函数*/
void delay(uint32_t count)
{
    volatile uint32_t i = 0;
    for (i = 0; i < count; ++i)
    {
        __asm("NOP"); /* 调用nop空指令 */
    }
}



int main()
{
    int i = 0;
    rgb_led_init();                          //初始化 RGB 灯,初始化后 默认所有灯都不亮。
    interrupt_button2_init();                //初始化引脚,和引脚的中断方式以及开启引脚中断。
    
    while (1)
    {
        if(button_status > 0)
        {
            /*绿灯亮*/
            red_led_off;
            green_led_on;

        }
        else
        {
            /*红灯亮*/
            green_led_off;
            red_led_on;
        }
        
    }

    return 0;
}

十五、编译下载验证

15.1 编译代码

make

执行make命令,生成base.bin文件。

15.2 代码烧写

编译成功后会在当前文件夹下生成.bin文件,这个.bin文件也不能直接放到开发板上运行, 这次是因为需要在.bin文件缺少启动相关信息。

为二进制文件添加头部信息并烧写到SD卡。查看 IMX6ULL学习笔记(12)——通过SD卡启动官方SDK程序

进入烧写工具目录,执行 ./mkimage.sh <烧写文件路径> 命令,例如要烧写的 base.bin 位于 home 目录下,则烧写命令为 ./mkimage.sh /home/button.bin

执行上一步后会列出linux下可烧写的磁盘,选择你插入的SD卡即可。这一步 非常危险!!!一定要确定选择的是你插入的SD卡!!,如果选错很可能破坏你电脑磁盘内容,造成数据损坏!!! 确定磁盘后SD卡以“sd”开头,选择“sd”后面的字符即可。例如要烧写的sd卡是“sdb”则输入“b”即可。

15.3 实验现象

烧写完成,首先将开发板启动方式设置为SD卡启动,将SD卡插入开发板卡槽。 接通电源后循环按下sw2(KEY)按键,正常情况下可以看到RGB灯交替亮红、绿色。


• 由 Leung 写于 2023 年 3 月 15 日

• 参考:10. 中断

上一篇 下一篇

猜你喜欢

热点阅读