iMX6ULL上手体验

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/03/28/iMX6ULL上手体验/#more

第一次接触NXP/Freescale的SOC,记录拿到板子后快速上手的技巧和思维的方式。

iMX6ULL感觉还是很有优势的,除了之前接触的NanoPi(全志H3),就没见过几个运行Linux,只卖100多元的开发板。
Cortex-A7架构,主频528MHz,一些普通的嵌入式Linux应用领域足够了。
感觉未来几年,运行Linux的板子会越来越便宜,嵌入式Linux会越来越普及。

1.准备资料

对于一个陌生的SOC,首先就是要准备相关的资料,核心的就是参考手册电路图
资料的来源无非有三个:

  • 芯片官网
    官网是参考手册的来源;
    NXP的官网做得还是比较清晰,很容易就找到了i.MX6ULL提供的文档。

  • 开发板提供厂家
    开发板厂家一般都提供所有的资料,包括参考手册电路图使用手册工具系统等;
    我这使用的是米尔科技的板子,官网的资料路径不好找,直接问客服要资料链接更快。

  • 网络
    Google/Baidu用于搜索相关博文的一些细节资料,比如某一块别人的分析。
    随便提一下,科学上网是基本素养。

2.观察开发板

拿到一个板子,首先是观察板子上大致有什么资源。

比如看到SOC附近有两个芯片,一般一个是RAM,一个Flash;
有个TF卡接口和拨动开关,多半是TF卡启动和Flash的启动选择;
一个网口、USB接口、Micro USB接口、电源接口,USB接口可能用于下载或串口或供电;
三组排针,其中三针上的丝印有RX、TX、GND,肯定是串口接口;
两个按键和几个LED灯,背后还有一个FPC插座,多半是接显示屏;
以及我的是IOT版,还有一个WIFI天线。

再查看厂家提供的资料,验证一些猜想。

上面的猜想几乎八九不离十,现在知道了可以通过“Boot Select”来TF/Nand启动。

现在有三个方向,

  • 一是做应用,比如搞个微型服务器,此时插上电源、串口、网络,基本就可以直接使用了,无需再关心板子硬件细节;
  • 二是做驱动,比如试试写个iMX6ULL的LED驱动,这就需要部署开发环境,在Ubuntu里安装交叉编译工具链、编译提供内核、编写驱动等;
  • 三是做裸机,比如试试在裸机上点个LED灯,这就需要编写裸机程序,然后启动验证;

其实,这三个领域,都能玩,但都比较尴尬,

  • 做应用,感觉没有好项目,树莓派还在吃灰;
  • 做驱动,手里的Tiny4412还在研究,没必要另起炉灶,也没工作的需求;
  • 做裸机,应该没人拿跑Linux的板子做裸机吧,单片机可选的一大堆;

既然这样,就做无任何资料的裸机吧,开启hard模式。

3.系统更新

确定了方向,先是做裸机,
首先就需要知道如何将裸机代码放到存储介质(Nand或TF卡),然后启动裸机代码。

如何下手呢?
我也不知道,跟着厂家提供的资料,重新烧写一遍系统,这个过程中肯定包含Uboot,Uboot就是一个大裸机程序,只要炮制Uboot的烧写方式烧写裸机即可。

i.MX6ULL系统更新使用两种方法,MfgTool更新SD卡更新

  • MFGTools

MFGTools是NXP官方推荐的一个使用OTG来升级镜像的软件。可以用来升级Linux、升级Android;单独刷写某一系统分区,如 android的boot.img分区等;独立地刷写spi nor、emmc 等等;
操作方式按着厂商的操作即可。

另外,MfgTool的文件更新有两个部分:firmwarefiles
firmware是烧写系统的镜像文件(作为媒介用途的镜像),路径为"MYS-6ULXmfgtools/
Profiles/Linux/OS Firmware/firmware/"。
files目录下为烧写的目标镜像文件(真正烧录到emmc或者nand的镜像文件),路径为"MYS-6ULX-mfgtools/Profiles/Linux/OS
Firmware/files/"。
之所以存着这两种镜像,是因为MFGTools的烧写原理是先将媒介镜像下载到到ddr内存里面,然后启动linux,再通过这个启动的linux把目标镜像固化到emmc或者nand里
因此,当更新系统的分区大小或烧写方式时才需要更新firmware中的文件。

更新完,重新启动开开发板即是新系统。

  • SD卡更新
    和前面的原理类似,即先将一个媒介镜像烧写到SD卡上,SD卡启动后,再通过SD卡的系统烧写Nand,从而更新系统。
    因此需要先做一个“用于SD启动更新的镜像”,使用厂商提供的build-sdcard.sh脚本即可。
    同样的,“firmware”目录下是烧写系统的镜像文件(作为媒介用途的镜像),一般情况下不需要修改。
    ”mfgimages-mys-imx6ull-ddr256m-nand256m“目录下是烧写的目标镜像文件(真正烧录到emmc或者nand的镜像文件)。
    使用脚本后,会生成一个.sdcard后缀的文件,即是“用于SD启动更新的镜像”,下面需要将它烧到SD卡上,可以使用Linux下的dd命令。
sudo dd if=mys6ull-xx.rootfs.sdcard of=/dev/sdb conv=fsync

然后改为SD卡启动,就可以进入SD卡的系统,并在系统里自动的烧写Nand。
完成后,改为Nand启动,即可进入新系统。

综上两个方法,都可以实现烧写Uboot到Nand上,但却都是通过进入“媒介系统”完成的烧写,看来直接烧写裸机到Nand是比较麻烦的。
反观SD卡启动,是通过先使用脚本制作一个.sdcard后缀的文件,再通过dd命令,完整的复制到SD卡上。
因此只需要分析下脚本如何操作即可。

通过过脚本build-sdcard.sh进行分析:

dd if=${FIRMWARE_DIR}/u-boot-${MACHINE}.${UBOOT_SUFFIX_SDCARD} of=${SDCARD} conv=notrunc seek=2 bs=512

以及博客参考。
确定了Uboot是被放在了SD卡开始的512x2=1K处。
即,裸机代码必须放在SD卡的偏移地址1K位置处

4.裸机文件加头

这时候,按理说下一步是编写个LED裸机程序,使用dd命令放在偏移地址1K位置处。
但是,如厂商提供资料文档里说的:

由于i.MX6ULL/i.MX6UL烧写bootloaer时需要使用kobs-ng工具添加头部信
息,需要在操作系统上才可以烧写。

同时,Uboot文件名为*.imx后缀,因此这里的裸机文件还需要先加一个头。

那么问题来了,这个头怎么加?
肯定还是从Uboot切入,使用厂家提供Uboot,重新编译生成Uboot,在这个过程中,肯定会将u-boot.bin变为u-boot.imx

编译Uboot的过程参考厂家文档,先安装交叉编译工具链,再指定配置文件编译即可。
这里编译完后,是不会有什么提示信息的,这里就需要--just-print编译参数,将整个编译过程打印出来:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- --just-print  > 123.txt

在生成的123.txt里搜索u-boot.bin,很快就能定位到加头操作附近:

echo '  MKIMAGE u-boot.imx'; ./tools/mkimage -n board/myir/mys_imx6ull/imximage.cfg.cfgtmp -T imximage -e 0x87800000 -d u-boot.bin u-boot.imx  >/dev/null;

这里的./tools/mkimage -n board/myir/mys_imx6ull/imximage.cfg.cfgtmp -T imximage -e 0x87800000 -d u-boot.bin u-boot.imx命令就是加头操作。
需要mkimage工具和imximage.cfg.cfgtmp配置文件,而且这几个文件路径也可以从命令得知。

此时,将编译好的u-boot.bin,使用上面的命令完成加头操作,得到自己的u-boot.imx,尝试烧到SD卡上,看能否启动。

这里的烧写有一个坑,当使用dd命令进行烧写:

sudo dd if=u-boot.imx of=/dev/sdb bs=512 seek=2 conv=fsync 

还是先介绍下dd命令,dd是对块进行操作,cp是对文件操作,
比如复制一个数据从A->B,dd是放在指定的位置,cp是放在空闲的位置。

同时结合SD卡的分区简图:

可以看出,烧写到SD卡上,是无法直观的从SD上得知是否烧写成功的,烧写的偏移地址1K位置处,无法从SD卡的分区剩余大小上判断。

解决方法是,通过dd命令读取出数据,再将读取的数据和烧写的数据进行简单比较,因此烧写完成后,要使用以下命令进行检查,判断是否烧写成功:

sudo dd if=/dev/sdb of=read_uboot.bin  bs=512 skip=2 count=2

hexdump u-boot.imx | more
hexdump read_uboot.bin | more

比较两者前面部分数据相同即可。

然后把SD卡插上开发板,设置为SD卡启动,成功启动Uboot,且打印的Uboot生成日期是当前日期,证明裸机文件加头的方式是正确的。

5.运行LED

知道了怎么加头,怎么烧写到SD卡,就可以编写裸机程序了,第一个裸机当然是最简单的点灯。
在点灯之前,一般都需要关看门狗、初始化时钟、初始化SDRAM/DDR等。
上面的操作不一定都是必须的,比如看门狗可能默认时间很长,对于点灯来说,无所谓。
又比如SDRAM/DDR在点灯这个小程序上,没必要用到。

因此,最基本的肯定是设置GPIO引脚,控制LED灯。
点灯一般需要使能引脚时钟、设置引脚功能为GPIO功能、引脚设值等。
在设置了引脚方向寄存器和引脚数据寄存器后,抱着试一试的心态编译、加头后烧写了进去,居然成功亮灯。
确实很惊讶,这应该是遇到的步骤最少的亮灯代码。
看门狗、时钟什么的,猜测应该被初始化了。

而且,还有更大的惊喜。
在前面加头的操作,就很纳闷有个参数是-e 0x87800000,应该是个地址,这个地址处于:

Start address   End address     Size     Description
8000_0000      FFFF_FFFF      2048 MB   MMDC—x16 DDR Controller.

也就是DDR的位置,难道DDR也被初始化了?
写个测试函数,尝试读写DDR所在的0x80000000:
{% codeblock lang:c %}
#define DDR_ADDRESS (*(volatile unsigned long )0x80000000) //P175 ARM Platform Memory Map
#define DDR_ADDR(offset) (
(volatile unsigned long *)(0x80000000+offset))

#define TEST_SIZE (1024*1024)

void test_ddr(void)
{
int i;
unsigned int offset;
int equal_flag = 1;

//写寄存器
offset = 0;
for(i=0; i

}
{% endcodeblock %}
结果发现居然能正确读取出来,看来DDR也被初始化了。

不得不说,很强,很完美。(●’◡’●)

倒回来想,不应能初始化DDR,不同的板子,DDR型号不一定相同,不可能做到适配所有的DDR。
整个过程,就做了加头操作,答案应该在加头操作里面。

打开imximage.cfg.cfgtmp可以看到一堆寄存器:

…………
DATA 4 0x021B0000 0x84180000
DATA 4 0x021B0890 0x00400000
…………

这里的0x021B0000刚好是DDR的寄存器:MMDC Core Control Register (MMDC_MDCTL);
其上电复位值是0x00,尝试读取寄存器值是不是为0,就知道是否真的被设置了:
{% codeblock lang:c %}
void read_ddr_reg(void)
{
unsigned int reg_value = 0;

reg_value = MMDC_MDCTL;

if (reg_value & (0x01<<30)) //SDE_1
	led_mode(1);	


if (reg_value & (0x01<<31)) //SDE_0
	led_mode(2);//结果亮

while(1);

}
{% endcodeblock %}
结果其31位,还真是1,和imximage.cfg.cfgtmpDATA 4 0x021B0000 0x84180000里的

0x84180000 = ‭10000100000110000000000000000000‬

最高为1是吻合的。

6.移植串口

点灯很轻松的被解决了,其它常规的初始化也被完成了。
尝试加点难度,移植下串口,为什么是移植呢?
不想从头去看参考手册的详细说明,直接移植Uboot里的串口操作即可。

Uboot里面一堆start.S,哪一个才是本开发板的呢?
笨方法是根据芯片型号分类去慢慢找,聪明的方法是一个命令解决:

find -name start.o

得到:

./arch/arm/cpu/armv7/start.o

因为前面根据本开发板配置文件编译过Uboot,理论上现在生成的所有*.o文件都是本开发板所使用的,这样就可以直接找到用到的start.S

start.S进行分析,没发现里面有串口相关的调用操作。

茫茫代码,如何找到需要的“uart”相关代码呢。
既然所有*.o才是用到的,就先找出所有*.o,再在对应的C文件搜索uart即可。

find -name ".o"

将得到的结果里面所有的文件名改为.*,再作为参数传给grep:

grep -nr "uart" ./test/dm/cmd_dm.* \
./test/dm/built-in.* \
./test/built-in.* \
./common/image-fdt.* \
./common/env_attr.* \
…………

可以得到如下结果:

./common/console.c:10:#include 

./board/myir/mys_imx6ull/mys_imx6ull.c:330:static iomux_v3_cfg_t const uart1_pads[] = {
./board/myir/mys_imx6ull/mys_imx6ull.c:400:static void setup_iomux_uart(void)
./board/myir/mys_imx6ull/mys_imx6ull.c:402:     imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads));
./board/myir/mys_imx6ull/mys_imx6ull.c:850:     setup_iomux_uart();


./tools/kwbimage.c:34:  { 0x69, "uart" },

./arch/arm/cpu/armv7/mx6/soc.c:448:static void set_uart_from_osc(void)
./arch/arm/cpu/armv7/mx6/soc.c:452:     /* set uart clk to OSC */
./arch/arm/cpu/armv7/mx6/soc.c:578:             set_uart_from_osc();

./arch/arm/cpu/armv7/mx6/clock.c:132:void enable_uart_clk(unsigned char enable)
./arch/arm/cpu/armv7/mx6/clock.c:412:static u32 get_uart_clk(void)
./arch/arm/cpu/armv7/mx6/clock.c:414:   u32 reg, uart_podf;
./arch/arm/cpu/armv7/mx6/clock.c:426:   uart_podf = reg >> MXC_CCM_CSCDR1_UART_CLK_PODF_OFFSET;
./arch/arm/cpu/armv7/mx6/clock.c:428:   return freq / (uart_podf + 1);
./arch/arm/cpu/armv7/mx6/clock.c:1049:u32 imx_get_uartclk(void)
./arch/arm/cpu/armv7/mx6/clock.c:1051:  return get_uart_clk();
./arch/arm/cpu/armv7/mx6/clock.c:1269:          return get_uart_clk();

./arch/arm/cpu/armv7/mx6/clock.su:3:clock.c:412:12:get_uart_clk 16      static
./arch/arm/cpu/armv7/mx6/clock.su:10:clock.c:132:6:enable_uart_clk      8      static
./arch/arm/cpu/armv7/mx6/clock.su:20:clock.c:1049:5:imx_get_uartclk     0      static
./drivers/serial/serial_mxc.c:145:      u32 clk = imx_get_uartclk();
./drivers/serial/serial_mxc.c:241:struct mxc_uart {
./drivers/serial/serial_mxc.c:270:      struct mxc_uart *const uart = plat->reg;
./drivers/serial/serial_mxc.c:271:      u32 clk = imx_get_uartclk();
./drivers/serial/serial_mxc.c:273:      writel(4 << 7, &uart->fcr); /* divide input clock by 2 */
./drivers/serial/serial_mxc.c:274:      writel(0xf, &uart->bir);
./drivers/serial/serial_mxc.c:275:      writel(clk / (2 * baudrate), &uart->bmr);
./drivers/serial/serial_mxc.c:278:             &uart->cr2);
./drivers/serial/serial_mxc.c:279:      writel(UCR1_UARTEN, &uart->cr1);
./drivers/serial/serial_mxc.c:287:      struct mxc_uart *const uart = plat->reg;
./drivers/serial/serial_mxc.c:289:      writel(0, &uart->cr1);
./drivers/serial/serial_mxc.c:290:      writel(0, &uart->cr2);
./drivers/serial/serial_mxc.c:291:      while (!(readl(&uart->cr2) & UCR2_SRST));
./drivers/serial/serial_mxc.c:292:      writel(0x704 | UCR3_ADNIMP, &uart->cr3);
./drivers/serial/serial_mxc.c:293:      writel(0x8000, &uart->cr4);
./drivers/serial/serial_mxc.c:294:      writel(0x2b, &uart->esc);
./drivers/serial/serial_mxc.c:295:      writel(0, &uart->tim);
./drivers/serial/serial_mxc.c:296:      writel(0, &uart->ts);
./drivers/serial/serial_mxc.c:304:      struct mxc_uart *const uart = plat->reg;
./drivers/serial/serial_mxc.c:306:      if (readl(&uart->ts) & UTS_RXEMPTY)
./drivers/serial/serial_mxc.c:309:      return readl(&uart->rxd) & URXD_RX_DATA;
./drivers/serial/serial_mxc.c:315:      struct mxc_uart *const uart = plat->reg;
./drivers/serial/serial_mxc.c:317:      if (!(readl(&uart->ts) & UTS_TXEMPTY))
./drivers/serial/serial_mxc.c:320:      writel(ch, &uart->txd);
./drivers/serial/serial_mxc.c:328:      struct mxc_uart *const uart = plat->reg;
./drivers/serial/serial_mxc.c:329:      uint32_t sr2 = readl(&uart->sr2);

./drivers/serial/serial.c:143:serial_initfunc(mxs_auart_initialize);
./drivers/serial/serial.c:156:serial_initfunc(uartlite_serial_initialize);
./drivers/serial/serial.c:234:  mxs_auart_initialize();
./drivers/serial/serial.c:247:  uartlite_serial_initialize();
./drivers/serial/serial.c:525: * uart_post_test() - Test the currently selected serial port using POST
./drivers/serial/serial.c:535:/* Mark weak until post/cpu/.../uart.c migrate over */
./drivers/serial/serial.c:537:int uart_post_test(int flags)

可以看到分别是初始化uart引脚、时钟、设置相关寄存器等函数。
非常的清晰,很容易就移植过来:
{% codeblock lang:c %}
static void uart1_clock_enable(void)
{
//uart时钟
CCM_CSCDR1 |= (0x01<<6); //P676 Selector for the UART clock multiplexor:1 derive clock from osc_clk
CCM_CCGR5 |= (0x03<<24); //uart1 clock (uart1_clk_enable)
}

static void uart1_iomux(void)
{
//uart引脚复用
IOMUXC_UART1_TX |= (0x01<<16 | 0x02<<14 | 0x01<<13 | 0x01<<12 | 0x02<<6 | 0x06<<3 | 0x01<<0);
IOMUXC_UART1_RX |= (0x01<<16 | 0x02<<14 | 0x01<<13 | 0x01<<12 | 0x02<<6 | 0x06<<3 | 0x01<<0);

IOMUXC_UART1_TX &= ~(0x0F<<0); //P1578  0000 ALT0 — Select mux mode: ALT0 mux port: UART1_TX of instance: uart1
IOMUXC_UART1_RX &= ~(0x0F<<0); //P1579  0000 ALT0 — Select mux mode: ALT0 mux port: UART1_RX of instance: uart1

}

void raise (int sig_nr)
{
;
}

unsigned int get_uart_clk(void)
{
unsigned int reg, uart_podf;
unsigned int freq, div;

//div = CCM_ANALOG_PLL_USB1;
div = CCM_CACRR;
div &= 0x00000003;
freq = 26000000 * (20 + (div << 1));	

reg = CCM_CSCDR1;
if (reg & (1<<6))
		freq = 26000000;


reg &= 0x3F; 
uart_podf = reg >> 0;

return freq / (uart_podf + 1);

}

//uart配置
static void uart1_config(void)
{
unsigned int clk;

UART1_UCR1 = 0;
UART1_UCR2 = 0;
while(!(UART1_UCR2 & (1<<0)));
UART1_UCR3 = (0x704 | (1<<7));
UART1_UCR4 = (0x8000);
UART1_UESC = (0x2b);
UART1_UTIM = (0);
UART1_UTS  = (0);	

clk = get_uart_clk(); //实测是25952384
UART1_UFCR = (4<<7 | 2<<10 | 1<<0);
//UART1_UFCR = (4<<7);
UART1_UBIR = (0xf);
UART1_UBMR = (clk / (2 * 125000));//115200 - 9.42     125000 - 8.75

UART1_UCR2 = (1<<5 | 1<<14 | 1<<1 | 1<<2 | 1<<0);
UART1_UCR1 = (1<<0);

}

void uart_init()
{
uart1_clock_enable();

uart1_iomux();

uart1_config();

}

void uart_PutChar(char c)
{
UART1_UTXD = c;
while(!(UART1_UTS & (1<<6)));
}

void uart_PutString(char *ptr)
{

while(*ptr != '\0')
{          
	uart_PutChar(*ptr++);
}

}
{% endcodeblock %}
这里的移植后遇到两个问题:
1.程序里打印45,实际打印出tu,通过ASCLL表和逻辑分析仪发现数据有点错位,代码里的115200波特率对应的脉宽宽了,这里直接把程序里的波特率改为125000,再用逻辑分析仪看就很“正”了。

2.前面的get_uart_clk()函数涉及到了除法,交叉编译工具链是不支持硬件除法的。解决方法有两个:

  • 在Makefile添加含除法的GCC库
# Add GCC lib
PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
  • 打印出get_uart_clk()的返回值,直接赋值。

7.重定位

前面的uart程序,后面实测发现一些问题,很大概率打印的数据是错误或者无法打印,研究后发现,是没有重定位的原因。

原来,开发板上电后,会从Flash中复制代码到SRAM,在SRAM里面一句一句的执行指令(此时运行的地址是硬件决定的)。
实际上,我们更多的是希望他在SDRAM上运行,因为SDRAM的空间更大,于是在链接脚本中,指定它应该运行的地址。
于是代码开始时实际运行的地址和期望运行的地址一般是不一样的,就需要重定位代码到链接脚本指定的地址。
不然的话,假如一个数据,在链接脚本里指定放在了高地址某处,但实际代码运行在低地址附近。代码执行时,需要读取高地址位置的数据,但高地址的数据并没有任何东西,一但读取就很可能发生异常。

首先编写链接脚本:
{% codeblock lang:asm [imx6ul.lds] https://github.com/hceng/learn/blob/master/imx6ull/hardware/uart/imx6ul.lds %}
SECTIONS {
. = 0x80000000;
.text : { start.o(.text)
main.o(.text)
led.o(.text)
uart.o(.text)
printf.o(.text)
(.text)
}
.rodata ALIGN(4) : {
(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
{% endcodeblock %}
这是一个比较通用的链接脚本,指定了代码段、只读数据段、数据段、BSS段等的位置。
开始的0x80000000就是我们期望它运行的地址,一般都是SDRAM中的某个地址,如果这个地址和代码实际运行的地址相同,就没必要重定位了。

然后在start.S里重定位操作:
{% codeblock lang:asm [start.S] https://github.com/hceng/learn/blob/master/imx6ull/hardware/uart/start.S %}
.text
.global _start
_start:

@设置栈
ldr sp,=0x90000000 @设置栈

bl relocate      @重定位
@bl clean_bss     @清BSS段

@adr r0, _start @可用于获取当前代码的地址,作为参数传给main,main里面再打印出来"int main(int addr)"
@ldr  pc, =main @如果没重定位,这样直接跳到main代码的位置(链接脚本的期望地址),那个位置的数据未知,肯定出错
bl main          @bl相对跳转,不管有没有重定位,都能到main的位置

halt:
b halt

relocate:
adr r0, _start @r0:代码当前被放在的位置,由硬件特性决定
ldr r1, =_start @r1:代码期望被放在的位置,即链接脚本里的地址,用户想放在的位置,比如SDRAM
@当两者相同则不用重定位,否则需要重定位
cmp r0, r1 @比较r0和r1
moveq pc,lr @相等则pc=lr,即跳回到调用relocate的位置;不相等跳过执行下面的指令

ldr r2, =__bss_start @r2等于链接脚本里的__bss_start,即代码段、只读数据段、数据段的结束位置

cpy:
ldr r3, [r0], #4 @将r0地址的数据放到r3,r0往后再移动一个字节
str r3, [r1], #4 @将r3的数据放到r1,r1往后再移动一个字节
@这两句完成了代码从当前位置复制到期望的链接地址位置的操作
cmp r1, r2 @判断是不是复制完了
bne cpy @不相等继续复制

mov pc, lr		     @pc=lr,即跳回到调用relocate的位置;

clean_bss:
ldr r0, =__bss_start @r0=bss段开始位置
ldr r1, =__bss_end @r1=bss段结束位置
mov r2, #0 @r0=0,填充0用
clean_loop:
str r2, [r0], #4 @将0写到bss段开始位置,并r0向后移一个字节
cmp r0, r1 @比较bss段是不是完了
bne clean_loop @不相等则继续清0

mov pc, lr		     @pc=lr,即跳回到调用clean_bss的位置;	

{% endcodeblock %}

开始的栈地址,选择SDRAM的最高地址即可。其它没什么说的了,注释写的很清楚,目的就是把当前位置的代码(一般是SRAM)复制到期望运行的地址(一般是SDRAM)。

8.移植printf

移植printf就很简单了,搞定了uart打印字符的函数后,利用以下框架即可:

  • 复制提供的printf.cprintf.h
  • printf.h里定义的__out_putchar宏改为uart里打印字符的函数即可;

实测效果:

9.其它

以上就是拿到一个全新的板子,如何快速上手板子的过程。

将以上思路,应用于RK3288,发现完全适用,也是先编译Uboot,得知加头的方式,然后得知下载方式,点灯,重定位,仅仅半天就可以实现串口的打印。
对RK3288的操作就不详细写了,思路上是完全一摸一样的,相关代码在文章最后。

后续有时间的话,可能会尝试去移植Nand,这些后续再看情况。

对iMX6ULL的初步上手就差不多了,感觉这SOC做得还是很不错,上手很快,价格低廉。

相关代码Github地址:
IMX6ULL
RK3288

你可能感兴趣的:(嵌入式基础,ARM裸机)