无意间搜到了韦东山老师的6ul网站,上面有一个6ul的qemu仿真器,下载下来用了用,非常好用,有UI,比原装的qemu-system-arm提供的6ul开发板多了很多功能。
下面贴出的就是韦东山老师的qemu网站:
百问网imx6ull-qemu
但是默认的跑了linux,没有裸机的例程。所以本文写了几个裸机的程序以供参考学习6ul soc上一些外设IP。目的是以最简单的代码来帮助对6ul感兴趣的朋友属性IP的使用。
本教程源码
目标实现以下模块的裸机驱动教程:
注:本文中的驱动只适用于QEMU仿真器上使用,不一定能在真实的芯片上跑起来。原因主要是跟clock,timing有关。因为qemu是纯软件的东西,qemu并没有对timing有严格的要求,所以在本文中所有驱动都没有对clock和timing进行处理。
首先看看6UL SOC的memory map
我们关心如下几段内存空间:
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上。其中:
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之外其他都是一个死循环。本文所有代码作了以下简化:
启动代码很简单,0地址就是一条B reset_handler的代码,然后reset handler设了一下特权模式下的栈指针,就跳转c函数的入口c_entry中。以下是start.s编译器编出来的代码。
00000000 :
0: ea000006 b 20
4: eafffffe b 4
8: eafffffe b 8
c: eafffffe b c
10: eafffffe b 10
14: eafffffe b 14
18: eafffffe b 18
1c: eafffffe b 1c
00000020 :
20: e3a00609 mov r0, #9437184 ; 0x900000
24: ee0c0f10 mcr 15, 0, r0, cr12, cr0, {0}
28: e59fd004 ldr sp, [pc, #4] ; 34
2c: eb000034 bl 104
30: eafffffe b 30
34: 00908008 addseq r8, r0, r8
使用cgdb debug启动代码如下:
打印的代码涉及到UART,这里先不解释了,后面解释UART的时候再说。代码实现imx_uart.c和qemu_print.c中。
从原理图上看到,我们要选择点亮的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
#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
GPIO 是通用输入输出端口的简称,简单来说就是6UL可控制的引脚,6UL芯
片的GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。
6UL芯片的GPIO 被分成很多组,每组有32 个引脚.
[注:以下内容节选自《i.MX RT 库开发实战指南—基于野火RT1052 开发板》]
下面我们按图 7-1 中的编号对GPIO 端口的结构部件进行说明。
GPIO.ICR1 & GPIO.ICR2
这两个寄存器决定了每一个引脚触发中断的方式,每一个引脚有两个bit表示。因此一个32位的寄存器只能表示16个pin。ICR1控制GPIOX_0 - GPIOX_15. ICR2控制GPIOX_16 - GPIOX_31。
每一个GPIO有4种中断触发方式:
#ifndef __IMX_GPIO_H__
#define __IMX_GPIO_H__
#include
#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();
}
}
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();
}