imx6ull-qemu 裸机教程1:GPIO,IOMUX,I2C

9 篇文章 3 订阅
5 篇文章 9 订阅

无意间搜到了韦东山老师的6ul网站,上面有一个6ul的qemu仿真器,下载下来用了用,非常好用,有UI,比原装的qemu-system-arm提供的6ul开发板多了很多功能。
下面贴出的就是韦东山老师的qemu网站:
百问网imx6ull-qemu
但是默认的跑了linux,没有裸机的例程。所以本文写了几个裸机的程序以供参考学习6ul soc上一些外设IP。目的是以最简单的代码来帮助对6ul感兴趣的朋友属性IP的使用。
本教程源码
目标实现以下模块的裸机驱动教程:

  • GPIO LED
  • GPIO BUTTON
  • I2C AT24CXX EEPROM
  • GPT定时器
  • USDHC SD卡

注:本文中的驱动只适用于QEMU仿真器上使用,不一定能在真实的芯片上跑起来。原因主要是跟clock,timing有关。因为qemu是纯软件的东西,qemu并没有对timing有严格的要求,所以在本文中所有驱动都没有对clock和timing进行处理。

1. 6UL SOC 启动代码编写

1.1 System memory map

首先看看6UL SOC的memory map
在这里插入图片描述在这里插入图片描述
我们关心如下几段内存空间:

  • 0x0000_0000 - 0x0001_7FFF BootROM。 6ul真实芯片启动的第一条代码是从BootROM 0地址启动,6ul qemu第一条指令也是从0地址开始启动。但是不同的是,6ul qemu并没有BootROM的启动代码,所以我们编写的裸机程序的代码从0地址开始链接,这段ROM空间我们可以用作text段。在真实的芯片上,BootROM会从不同的启动设备上读出Boot Image,典型的如Uboot,然后把Boot Image扔到DDR或者OCRAM上去跑。
  • 0x0090_0000 - 0x0097_FFFF OCRAM。这段OCRAM我们可以用来存放data段,bss段和stack段。
  • 0x8000_0000 - 0xFFFF_FFFF DDR。这段内存可以用作通用内存,暂时在本文的代码中没用到。

1.2 链接文件

6ul_bare_metal/6ul_bare_metal.ld

ENTRY(reset)
SECTIONS
{
	. = 0x00000000;
 	.startup . : { start.o(.text) }
 	.text : { *(.text) }
    . = 0x00900000;
 	.data : { *(.data) }
 	.bss : { *(.bss COMMON) }
 	. = ALIGN(8);
 	. = . + 0x8000; /* 32kB of stack memory */
 	svc_stack_top = .;
}

正如上一节所述,test位于BootROM上,data,bss和stack段位于OCRAM上。其中:

  • start.o是启动文件及异常向量表, 所以将其链接到最顶端。
  • stack占用32KB大小内存。

1.3 启动汇编代码

6ul_bare_metal/start.S

.align 4
.global reset
.global c_entry
.section .isr_vector
.text
reset:
	B reset_handler
	B .
	B .             //SVC
	B .
	B .
	B .
	B .             //IRQ
	B .             //FIQ


reset_handler:
    ldr r0, =0x00900000
    mcr p15,0,r0,c12,c0,0
    ldr sp, =svc_stack_top
    bl c_entry
    b .

reset处存放了arm向量表,除了reset之外其他都是一个死循环。本文所有代码作了以下简化:

  • 不使用任何中断,所有驱动都查询中断标志位的方式。
  • 所有代码运行在特权模式下。
    每一个demo都实现了一种外设,在运行模式下切换没有什么意义,增加了demo的复杂性,所以对所有裸机demo做了以上简化。

启动代码很简单,0地址就是一条B reset_handler的代码,然后reset handler设了一下特权模式下的栈指针,就跳转c函数的入口c_entry中。以下是start.s编译器编出来的代码。

00000000 <reset>:
   0:	ea000006 	b	20 <reset_handler>
   4:	eafffffe 	b	4 <reset+0x4>
   8:	eafffffe 	b	8 <reset+0x8>
   c:	eafffffe 	b	c <reset+0xc>
  10:	eafffffe 	b	10 <reset+0x10>
  14:	eafffffe 	b	14 <reset+0x14>
  18:	eafffffe 	b	18 <reset+0x18>
  1c:	eafffffe 	b	1c <reset+0x1c>

00000020 <reset_handler>:
  20:	e3a00609 	mov	r0, #9437184	; 0x900000
  24:	ee0c0f10 	mcr	15, 0, r0, cr12, cr0, {0}
  28:	e59fd004 	ldr	sp, [pc, #4]	; 34 <reset_handler+0x14>
  2c:	eb000034 	bl	104 <c_entry>
  30:	eafffffe 	b	30 <reset_handler+0x10>
  34:	00908008 	addseq	r8, r0, r8

使用cgdb debug启动代码如下:
在这里插入图片描述
打印的代码涉及到UART,这里先不解释了,后面解释UART的时候再说。代码实现imx_uart.c和qemu_print.c中。

2 GPIO LED

2.1 IOMUXC

在这里插入图片描述
从原理图上看到,我们要选择点亮的LED使用了GPIO1_3这个pin。
在6UL上,每一个引脚可以有8个mux选项(也被称作ALT模式)。比如我们要使用的GPIO1_3,这个pin有8个mux选项可供不同的IP使用。因此,我们再使用该pin前,要先选择好特定的mux选项。
在这里插入图片描述
所以点亮LED前,我们先要写好IOMUXC的代码。很简单就是封装了一下IOMUXC寄存器的MUX_MODE和SION的操作。
imx_iomuxc.h

#ifndef __IMX6UL_IOMUXC_H__
#define __IMX6UL_IOMUXC_H__

#include <stdint.h>

#define SW_MUX_CTRL_PAD_MUX_MODE_MASK   0x00000007UL
#define SW_MUX_CTRL_PAD_MUX_MODE_SHIFT  0UL

#define SW_MUX_CTRL_PAD_SION_SHIFT      4UL
#define SW_MUX_CTRL_PAD_SION_MASK       (1UL << SW_MUX_CTRL_PAD_SION_SHIFT)
.......
static inline void iomuxc_set_daisy_in(uint32_t iomux_addr, uint8_t mode)
{
    *((volatile uint32_t *)iomux_addr) = 0;
    *((volatile uint32_t *)iomux_addr) = mode; 
}

static inline void iomuxc_enable_sion(uint32_t iomux_addr)
{
    *((volatile uint32_t *)iomux_addr) |= SW_MUX_CTRL_PAD_SION_MASK;
}

static inline void iomuxc_disable_sion(uint32_t iomux_addr)
{
    *((volatile uint32_t *)iomux_addr) &= ~SW_MUX_CTRL_PAD_SION_MASK;
}

static inline void iomuxc_set_mux(uint32_t iomux_addr, uint8_t mux_mode)
{
    *((volatile uint32_t *)iomux_addr) &= ~SW_MUX_CTRL_PAD_MUX_MODE_MASK;
    *((volatile uint32_t *)iomux_addr) |= SW_MUX_CTRL_PAD_MUX_MODE_MASK & mux_mode;
}

#endif

2.2 GPIO

GPIO 是通用输入输出端口的简称,简单来说就是6UL可控制的引脚,6UL芯
片的GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
6UL芯片的GPIO 被分成很多组,每组有32 个引脚.

[注:以下内容节选自《i.MX RT 库开发实战指南—基于野火RT1052 开发板》]
在这里插入图片描述
下面我们按图 7-1 中的编号对GPIO 端口的结构部件进行说明。

  1. PAD
    PAD 代表了一个RT1052 的GPIO 引脚。在它的左侧是一系列信号通道及控制线,如 input_on 控制输入开关,Dir 控制引脚的输入输出方向,Data_out 控制引脚输出高低电平,Data_in 作为信号输入,这些信号都经过一个IOMUX 的器件连接到左侧的寄存器。另外,对于每个引脚都有很多关于属性的配置,这些配置是由图 7-2 中的框架结构实现的。
  2. IOMUX 复用选择器
  3. Block 外设功能控制块
    Block 是外设功能控制块,例如具有ENET 的数据接收功能的引脚,它就需要网络外设ENET 的支持,具有PWM输出功能的引脚,它需要PWM外设的支持,这些外设在芯片内部会有独立的功能逻辑控制块,这些控制块通过IOMUX 的复用信号与IO 引脚相连。使用时通过IOMUX 选择具体哪个外设连接到IO。
  4. GPIO 外设
    GPIO 模块是每个IO 都具有的外设,它具有IO 控制最基本的功能,如输出高低电平、检测电平输入等。它也占用IOMUX 分配的复用信号,也就是说使用GPIO 模块功能时同样需要使用IOMUX 选中GPIO 外设。图中的GPIO.DR、GPIO.GDIR、GPIO.PSR 等是指GPIO 外设相关的控制寄存器,它们分别是数据寄存器、方向寄存器以及引脚状态寄存器,
    功能介绍如下:
    GPIO.GDIR 方向寄存器
    控制一个GPIO 引脚时,要先用GDIR 方向寄存器配置该引脚用于输出电平信号还是用作输入检测。典型的例子是使用输出模式可以控制LED 灯的亮灭,输入模式时可以用来检测按键是否按下。
    GDIR 寄存器的每一个数据位代表一个引脚的方向,对应的位被设置为0 时该引脚为输入模式,被设置为1 时该引脚为输出模式
    在这里插入图片描述GPIO.DR 数据寄存器
    DR 数据寄存器直接代表了引脚的电平状态,它也使用1 个数据位表示1 个引脚的电平,每位用1 表示高电平,用0 表示低电平
    在这里插入图片描述
    GPIO.PSR 引脚状态寄存器
    PSR 引脚状态寄存器相当于DR 寄存器的简化版,它仅在GDIR 方向寄存器设置为输入模式时有效,它的每个位表示一个引脚当前的输入电平状态。PSR 寄存器的权限是只读的,对它进行写操作是无效的。

GPIO.ICR1 & GPIO.ICR2
这两个寄存器决定了每一个引脚触发中断的方式,每一个引脚有两个bit表示。因此一个32位的寄存器只能表示16个pin。ICR1控制GPIOX_0 - GPIOX_15. ICR2控制GPIOX_16 - GPIOX_31。
每一个GPIO有4种中断触发方式:

  • 低电平触发
  • 高电平触发
  • 上升沿触发
  • 下降沿触发
    在这里插入图片描述
    GPIO.IMR中断屏蔽寄存器
    IMR中每一位控制一个pin的中断屏蔽,当置位1后,该GPIO就不会触发中断。
    在这里插入图片描述
    GPIO.ISR中断状态寄存器
    ISR中每一个bit表示每一个pin是否有中断触发,当置为1时,表示有中断触发,为0则没有中断发生。软件往该位写1则会清掉该中断标志位
    在这里插入图片描述
    GPIO的头文件imx_gpio.h
    代码很简单,仅仅是定义了gpio的结构体和声明了API。
#ifndef __IMX_GPIO_H__
#define __IMX_GPIO_H__

#include <stdint.h>

#define LOW_LEVEL_SENSITIVE 0
#define HIGH_LEVEL_SENSITIVE 1
#define RISING_EDGE 2
#define FALLING_EDGE 3

typedef struct imx_gpio_tag
{

    volatile uint32_t dr;
    volatile uint32_t gdir;
    volatile uint32_t psr;
    volatile uint32_t icr1;
    volatile uint32_t icr2;
    volatile uint32_t imr;
    volatile uint32_t isr;
    volatile uint32_t edge_sel;

} imx_gpio_t;

extern void gpio_set_dr(imx_gpio_t *, uint8_t);
extern void gpio_clr_dr(imx_gpio_t *, uint8_t);
extern void gpio_set_output(imx_gpio_t *, uint8_t);
extern void gpio_set_input(imx_gpio_t *, uint8_t);
extern uint8_t gpio_get_psr(imx_gpio_t *, uint8_t);
extern void gpio_set_int_cfg(imx_gpio_t *, uint8_t , uint8_t);
extern void gpio_mask_int(imx_gpio_t *, uint8_t);
extern void gpio_unmask_int(imx_gpio_t *, uint8_t);
extern uint32_t gpio_get_int_stat(imx_gpio_t *, uint8_t);
extern void gpio_clr_int_stat(imx_gpio_t *, uint8_t);
extern void gpio_set_edge_sel(imx_gpio_t *, uint8_t);
extern void gpio_clr_edge_sel(imx_gpio_t *, uint8_t);
extern void dump_gpio(imx_gpio_t *);

#endif

API实现也很简单,就是对GPIO硬件进行了软件封装

#include "imx_gpio.h"
#include "imx_uart.h"

void gpio_set_dr(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->dr |= (1 << idx);
}

void gpio_clr_dr(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->dr &= ~(1 << idx);
}

void gpio_set_output(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->gdir |= (1 << idx);
}

void gpio_set_input(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->gdir &= ~(1 << idx);
}

uint8_t gpio_get_psr(imx_gpio_t *gpio, uint8_t idx)
{
    return (gpio->psr & (1 << idx));
}

void gpio_set_int_cfg(imx_gpio_t *gpio, uint8_t idx, uint8_t cfg)
{
    uint32_t shift = 0;
    uint32_t mask = 0;

    if (idx < 16)
    {
        shift = idx * 2;
        mask = 3 << (idx * 2);
        gpio->icr1 &= ~mask;
        gpio->icr1 |= (cfg << shift);
    }
    else
    {
        shift = (idx - 16) * 2;
        mask = 3 << ((idx - 16) * 2);
        gpio->icr2 &= ~mask;
        gpio->icr2 |= (cfg << shift);
    }
}

void gpio_mask_int(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->imr &= ~(1 << idx);
}

void gpio_unmask_int(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->imr |= (1 << idx);
}

uint32_t gpio_get_int_stat(imx_gpio_t *gpio, uint8_t idx)
{
    return gpio->isr & (1 << idx);
}

void gpio_clr_int_stat(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->isr |= (1 << idx);
}

void gpio_set_edge_sel(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->edge_sel |= (1 << idx);
}

void gpio_clr_edge_sel(imx_gpio_t *gpio, uint8_t idx)
{
    gpio->edge_sel &= ~(1 << idx);
}

void dump_gpio(imx_gpio_t *gpio)
{
    printf("%s isr:%x\n", __func__, gpio->isr);
    printf("%s icr1:%x\n", __func__, gpio->icr1);
    printf("%s icr2:%x\n", __func__, gpio->icr2);
    printf("%s dr:%x\n", __func__, gpio->dr);
    printf("%s gdir:%x\n", __func__, gpio->gdir);
    printf("%s imr:%x\n\n", __func__, gpio->imr);
}

测试代码

测试代码先配置GPIO1_3的IOMUX为ALT5,然后就按时往GPIO1_3的DR的寄存器写1和0翻转LED即可。
entry.c

static void test_led()
{
    uint8_t i = 0;
    imx_gpio_t *gpio1 = (imx_gpio_t *)0x0209c000UL;

    iomuxc_set_mux(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03, MUX_MODE_ALT5);

    gpio_set_output(gpio1, 3);

    while(1) {
        if ((i % 2) == 0)
            gpio_set_dr(gpio1, 3);
        else
            gpio_clr_dr(gpio1, 3);
        printf("%s:%d\n", __func__, i++);
        delay();
    }
}

运行命令: make run,运行结果如下图
在这里插入图片描述

3 GPIO Button

在这里插入图片描述
Demo中使用GPIO1_18作为KEY2。使用轮询中断标志位的方式读取按键事件,当按键被按下的时候,将LED状态翻转。

首先配置LED的GPIO1_3为输出

static void test_button()
{
    uint32_t button_int_stat = 0;
    uint8_t led_status = 0;
    imx_gpio_t *gpio1 = (imx_gpio_t *)0x0209c000UL;

    iomuxc_set_mux(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03, MUX_MODE_ALT5);
    gpio_set_output(gpio1, 3);

配置KEY2的GPIO1_18为输入状态,使能中断但关闭中断,上升沿触发中断。即使屏蔽中断,当有上升沿事件触发后中断标志位还是会被置上

    iomuxc_set_mux(IOMUXC_SW_MUX_CTL_PAD_UART1_CTS, MUX_MODE_ALT5);
    gpio_set_input(gpio1, 18);
    gpio_set_int_cfg(gpio1, 18, RISING_EDGE);
    gpio_clr_int_stat(gpio1, 18);
    gpio_unmask_int(gpio1, 18);

定时查询KEY2的状态,一旦KEY2被按下弹起一次,LED就会翻转一次。

    while (1) {

        while((button_int_stat = gpio_get_int_stat(gpio1, 18)) == 0) {
            delay();
        }

        printf("button changed\n");
        led_status = ~led_status;
        gpio_clr_int_stat(gpio1, 18);
        if (led_status == 0) {
            gpio_set_dr(gpio1, 3);
        } else {
            gpio_clr_dr(gpio1, 3);
        }
        delay();
    }

代码很简单,直接make run
在这里插入图片描述

  • 8
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
imx6ull是一款基于Arm架构的处理器,它支持I2C总线。I2C驱动分为两部分:I2C控制器驱动和I2C设备驱动。I2C总线在寻址时使用从位,并且如果启用了中断,Arm平台会中断并检查从读/写位的状态。在复位后,默认情况下,I2C处于从接收操作状态,除非作为主设备操作或响应从设备发送地址。需要注意的是,imx6ullI2C设计与PhilipsTM I2C总线协议兼容,并且仅支持标准和快速模式。有关更多配置、协议和限制的信息,可以参考飞利浦半导体公司的I2C总线规范2.1版。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [i.MX 6ULL 驱动开发 二十一:I2C(I2C子系统+MISC子系统)](https://blog.csdn.net/OnlyLove_/article/details/127739570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [IMX6ULL平台的I2C](https://blog.csdn.net/weixin_52849254/article/details/130844599)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值