LinuxAlphaMini

Cortex-A7 MPCore架构

汇编基础

1
label: instruction @ comment 

label 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识符都会被识别为一个标号。
instruction 即指令,也就是汇编指令或伪指令。
@符号,表示后面的是注释
comment 就是注释内容。

例:

1
2
add:
MOVS R0, #0X12 @设置 R0=0X12

注意! ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。

1
.section .testsection @定义一个 testsetcion 段 
  • .text 表示代码段。
  • .data 初始化的数据段。
  • .bss 未初始化的数据段。
  • .rodata 只读数据段。

伪操作:

  • .byte 定义单字节数据,比如.byte 0x12。
  • .short 定义双字节数据,比如.short 0x1234。
  • .long 定义一个 4 字节数据,比如.long 0x12345678。
  • .equ 赋值语句,格式为: .equ 变量名,表达式,比如.equ num, 0x12,表示 num=0x12。
  • .align 数据字节对齐,比如: .align 4 表示 4 字节对齐。
  • .end 表示源文件结束。
  • .global 定义一个全局符号,格式为: .global symbol,比如: .global _start(汇编程序的默认入口标号是_start )。

函数:

1
2
3
函数名:
函数体
返回语句
1
2
3
4
/* SVC 中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0 @跳转

常用汇编指令

处理器内部数据传输指令 mov mrs msr

指令目的
MOVR0R1将 R1 里面的数据复制到 R0 中。
MRSR0CPSR将特殊寄存器 CPSR 里面的数据复制到 R0 中。
(特殊寄存器只能由这个指令 读 )
MSRCPSRR1将 R1 里面的数据复制到特殊寄存器 CPSR 里中。
(特殊寄存器只能由这个指令 写 )

存储器访问指令 ldr str

I.MX6UL中的RAM,寄存器都需要这个指令访问。

LDR和STR是按照字进行读取和写入的,LDRB和STRB是按字节,LDRH和STRH是按半字操作。

指令
LDR Rd, [Rn , #offset]从存储器 Rn+offset 的位置读取数据存放到 Rd 中。
STR Rd, [Rn, #offset]将 Rd 中的数据写入到存储器中的 Rn+offset 位置。

LDR

  • 读取寄存器值

  • 加载立即数到寄存器

1
2
3
4
@加载立即数
LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
@读取寄存器值
LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中

STR

1
2
3
LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
LDR R1, =0X12345678 @R1 保存要写入到寄存器的值,即 R1=0X12345678
STR R1, [R0] @将 R1 中的值写入到 R0 中的地址上

压栈出栈指令 push pop

指令
PUSH <reg list>将寄存器列表存入栈中。
POP <reg list>从栈中恢复寄存器列表。
1
2
3
4
5
6
@处理器的堆栈是向下增长的 见下图
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
PUSH {LR} @将 LR 进行压栈

POP {LR} @先恢复 LR
POP {R0~R3,R12} @在恢复 R0~R3,R12

image-20210707144653945

1
2
3
4
5
6
7
8
9
10
11
12
13
@这个和上面的是等同的
STMFD SP!,{R0~R3, R12} @R0~R3,R12 入栈
STMFD SP!,{LR} @LR 入栈

LDMFD SP!, {LR} @先恢复 LR
LDMFD SP!, {R0~R3, R12} @再恢复 R0~R3, R12


LDMFD = LDM + FD
STMFD = STM + FD
LDM(多个) -> LDR(单个)
STM(多个) -> STR(单个)
FD = Full Descending 即满递减的意思(根据 ATPCS 规则,ARM 使用的 FD 类型的堆栈, SP 指向最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长的堆栈,)

跳转指令 b bx bl blx

跳转操作的方法

  1. 直接使用跳转指令 B、 BL、 BX 等
  2. 直接向 PC 寄存器里面写入数据。
指令
B <label>跳转到 label,如果跳转范围超过了+/-2KB,
可以指定 B.W <label>使用 32 位版本的跳转指令, 这样可以得到较大范围的 跳转
BX <Rm>间接跳转,跳转到存放于 Rm 中的地址处,并且切换指令集
BL <label>跳转到标号地址,并将返回地址保存在 LR 中。
BLX <Rm>结合 BX 和 BL 的特点,跳转到 Rm 指定的地址,并将返回地 址保存在 LR 中,切换指令集。

B指令

这是最简单的跳转指令, B 指令会将 PC 寄存器的值设置为跳转目标地址, 一旦执行 B 指令, ARM 处理器就会立即跳转到指定的目标地址。

1
2
3
_start:
ldr sp,=0X80200000 @设置栈指针
b main @跳转到 main 函数

上述代码就是典型的在汇编中初始化 C 运行环境,然后跳转到 C 文件的 main 函数中运行 ,上述代码只是初始化了 SP 指针,有些处理器还需要做其他的初始化,比如初始化 DDR 等等。因为跳转到 C 文件以后再也不会回到汇编了,所以在第 4 行使用了 B 指令来完成跳转 。

BL指令

BL 指令相比 B 指令,在跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用一个基本但常用的手段。

1
2
3
4
5
6
7
8
push {r0, r1} 			@保存 r0,r1
cps #0x13 @进入 SVC 模式,允许其他中断再次进去

bl system_irqhandler @加载 C 语言中断处理函数到 r2 寄存器中

cps #0x12 @进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0X10] @中断执行完成,写 EOIR

上述代码中第 5 行就是执行 C 语言版的中断处理函数,当处理完成以后是需要返回来继续执行下面的程序,所以使用了 BL 指令。

算术运算指令

汇编中也可以进行算术运算, 比如加减乘除,常用的运算指令用法如表 7.2.5.1 所示:

指令计算公式
ADD Rd, Rn, RmRd = Rn + Rm加法运算,指令为 ADD
ADD Rd, Rn, #immedRd = Rn + #immed加法运算,指令为 ADD
ADC Rd, Rn, RmRd = Rn + Rm + 进位带进位的加法运算,指令为 ADC
ADC Rd, Rn, #immedRd = Rn + #immed +进位带进位的加法运算,指令为 ADC
SUB Rd, Rn, RmRd = Rn – Rm减法
SUB Rd, #immedRd = Rd - #immed减法
SUB Rd, Rn, #immedRd = Rn - #immed减法
SBC Rd, Rn, #immedRd = Rn - #immed – 借位带借位的减法
SBC Rd, Rn ,RmRd = Rn – Rm – 借位带借位的减法
MUL Rd, Rn, RmRd = Rn * Rm乘法(32 位)
UDIV Rd, Rn, RmRd = Rn / Rm无符号除法
SDIV Rd, Rn, RmRd = Rn / Rm有符号除法

逻辑运算指令

指令计算公式
AND Rd, RnRd = Rd &Rn按位与
AND Rd, Rn, #immedRd = Rn &#immed按位与
AND Rd, Rn, RmRd = Rn & Rm按位与
ORR Rd, RnRd = Rd | Rn按位或
ORR Rd, Rn, #immedRd = Rn | #immed按位或
ORR Rd, Rn, RmRd = Rn | Rm按位或
BIC Rd, RnRd = Rd & (~Rn)位清除
BIC Rd, Rn, #immedRd = Rn & (~#immed)位清除
BIC Rd, Rn , RmRd = Rn & (~Rm)位清除
ORN Rd, Rn, #immedRd = Rn | (#immed)按位或非
ORN Rd, Rn, RmRd = Rn | (Rm)按位或非
EOR Rd, RnRd = Rd ^ Rn按位异或
EOR Rd, Rn, #immedRd = Rn ^ #immed按位异或
EOR Rd, Rn, RmRd = Rn ^ Rm按位异或

启动方式

BOOT

OOT_MODE[1:0]的值是可以改变的,有两种方式,一种是改写 eFUSE(熔丝),一种是修改相应的 GPIO 高低电平。

  • BOOT_MODE1 和 BOOT_MODE0 在芯片内部是有 100KΩ下拉电阻的 ,所以默认是0。
BOOT_MODE[1:0]BOOT 类型
00从 FUSE 启动
01串行下载
10内部 BOOT 模式
11保留

串行下载

串行下载的意思就是可以通过 USB 或者 UART 将代码下载到板子上的外置存储设备中,我们可以使用 OTG1 这个 USB口向开发板上的 SD/EMMC、 NAND 等存储设备下载代码。

内部 BOOT 模式

芯片会执行内部的 boot ROM 代码,代码会进行硬件初始化(一部分外设),然后从 boot 设备(就是存放代码的设备、比如 SD/EMMC、 NAND)中将代码拷贝出来复制到指定的 RAM 中,一般是 DDR。

BOOT模式 启动设备

当 BOOT_MODE 设置为内部 BOOT 模式以后,可以从以下设备中启动:
①、接到 EIM 接口的 CS0 上的 16 位 NOR Flash。
②、接到 EIM 接口的 CS0 上的 OneNAND Flash。
③、接到 GPMI 接口上的 MLC/SLC NAND Flash, NAND Flash 页大小支持 2KByte、 4KByte和 8KByte, 8 位宽。
④、 Quad SPI Flash。
⑤、接到 USDHC 接口上的 SD/MMC/eSD/SDXC/eMMC 等设备。
⑥、 SPI 接口的 EEPROM。

启动设备是通过 BOOT_CFG1[7:0]、 BOOT_CFG2[7:0]和 BOOT_CFG4[7:0]这 24 个配置 IO配置,这 24 个配置 IO 刚好对应着 LCD 的 24 根数据线 LCD_DATA0~LCDDATA23,当启动完成以后这 24 个 IO 就可以作为 LCD 的数据线使用。

boot1boot0启动设备
01xxxxxx串行下载,可以通过 USB 烧写镜像文件。
10000010SD 卡启动。
10100110EMMC 启动。
10001001NAND FLASH 启动。

烧写镜像

imxdownload 会在 led.bin前面添加一些头信息,重新生成一个叫做 load.imx 的文件,头部信息包含以下

  • Image vector table,简称 IVT, IVT 里面包含了一系列的地址信息,这些地址信息在ROM 中按照固定的地址存放着。

  • Boot data,启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少等等。

  • Device configuration data,简称 DCD,设备配置信息,重点是 DDR3 的初始化配置。

  • 用户代码可执行文件,比如 led.bin。

最终烧写到 I.MX6U 中的程序其组成为: IVT+Boot data+DCD+.bin 。(3KByte 的 IVT+Boot Data+DCD)

.bin是从0x87800000这个地方开始,往前推load.imx就是从0x877FF400开始的。

点灯Makefile

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 定义了一个变量 objs, objs 包含着要生成 ledc.bin 所需的材料: start.o 和 main.o
# 这里要注意 start.o 一定要放到最前面!因为在后面链接的时候 start.o 要在最前面,因为 start.o 是最先要执行的文件!
objs := start.o main.o

# 使用依赖文件(objs:=start.o main.o),生成目标可执行文件ledc.bin
ledc.bin:$(objs)
# 使用编译器 arm-linux-gnueabihf-ld 进行连接,-Ttext指定起始地址是0X87800000
# $^ 是所有依赖文件的集合,也就是变量objs的值(start.o main.o)
# 展开等价 arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf start.o main.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
# 将ledc.elf转换成ledc.bin文件
# $@ 的意思是目标集合,在这里就是 ledc.bin
# 展开等价 arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
# 反汇编,生成 ledc.dis 文件
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis

# 下面规则展开等价
# start.o:start.s
# arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s
# %匹配任意字符
%.o:%.s
# $@ 的意思是目标集合,在这里就是 %.o
# $< 所有依赖文件的集合,在这就是 %.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<

clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 类似于上面的只是用到了变量
CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME ?= ledc

CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
OBJS := start.o main.o

$(NAME).bin:$(OBJS)
$(LD) -Timx6ul.lds -o $(NAME).elf $^
$(OBJCOPY) -O binary -S $(NAME).elf $@
$(OBJDUMP) -D -m arm $(NAME).elf > $(NAME).dis

%.o:%.s
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
$(CC) -Wall -nostdlib -c -O2 -o $@ $<

clean:
rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
CROSS_COMPILE	?= arm-linux-gnueabihf-
# 编译后 目标名
TARGET ?= bsp

# 编译器相关
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump

# include dir
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay

# sourc dir
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay

# 这里用到了 patsubst 函数 通过这个函数给 INCDIRS 前面加了一个 -I
# 展开 INCLUDE := -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
INCLUDE := $(patsubst %, -I %, $(INCDIRS))

# 这里用到了 foreach 和 wildcard 函数
# 展开 SFILES := project/start.S
# 展开 CFILES := project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

# 这里用到了 notdir 函数 除去路径函数
# 展开 SFILENDIR := start.S
# 展开 CFILENDIR := main.c bsp_clk.c bsp_led.c bsp_delay.c
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))

# 展开 SOBJS := obj/start.o
# 展开 COBJS := obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
# 展开 OBJS := obj/start.o obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)

VPATH := $(SRCDIRS)

.PHONY: clean

$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis

$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<

clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

Board Support

  • 正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡,FSL_SDHC(1)接的 EMMC。

开发环境搭建

Ubuntu 交叉编译工具链安装

交叉编译器有很多种,我们使用 Linaro 出品的交叉编译器,Linaro 是一间非营利性质的开放源代码软件工程公司,Linaro 开发了很多软件,最著名的就是 Linaro GCC 编译工具链(编译器)

Linaro 编译器7.5.0下载链接

  1. 有很多种 GCC 交叉编译工具链,因为我们所使用的 I.MX6U-ALPHA 开发板是一个 Cortex-A7 内核的开发板,因此选择 arm-linux-gnueabihf

    aarch64-elf
    aarch64-linux-gnu
    aarch64_be-elf
    aarch64_be-linux-gnu
    arm-eabi
    arm-linux-gnueabi
    arm-linux-gnueabihf
    armeb-eabi
    armeb-linux-gnueabihf
    armv8l-linux-gnueabihf

  2. gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz 这个文件就是7.5.0版本的GCC交叉编译器,用于X86——64位的linux上运行

    1. 需要注意的是,这个开发板用的是4.9.4版本的编译器,过高版本的编译器会有一些问题。
    2. Linaro 4.9.4 下载链接
  3. 下载好编译器之后需要做的就是将编译器添加到环境变量,使得可以在任何目录下调用编译器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    sudo mkdir /usr/local/arm

    sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f

    sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

    sudo vi /etc/profile

    export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
  4. 搞定环境变量之后需要安装一些其他的编译库

    1. sudo apt-get install lsb-core lib32stdc++6
  5. 验证编译器

    1. 任意目录输入arm-linux-gnueabihf-gcc -v 即可查看编译器版本号
    2. arm 表示这是编译 arm 架构代码的编译器。
      linux 表示运行在 linux 环境下。
      gnueabihf 表示嵌入式二进制接口。
      gcc 表示是 gcc 工具

烧写程序

烧写Uboot

烧写到SD卡,通过ta启动uboot

1
2
3
4
5
6
chmod 777 imxdownload
# 烧写到 SD 卡,注意区别设备号 不能烧写到/dev/sda 或 sda1 设备里面!
./imxdownload u-boot.bin /dev/sdd

# 烧写uboot之后就可以通过网络的方式加载 linux kernel 和 rootfs 参考boot章节
# 或者通过mfg工具将准备好的固件打包下载到特定存储介质上

烧写到EMMC/NAND,通过ta启动uboot

U-Boot

uboot 的全称是 Universal Boot Loader。

Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader 程序。这段 bootloader程序会先初始化 DDR等外设,然后将Linux 内核从flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了,bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核,bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。所以我们要先搞定bootloader,很庆幸,有很多现成的 bootloader 软件可以使用,比如 U-Boot、vivi、RedBoot 等等,其中以 U-Boot 使用最为广泛。

U-Boot分为以下几种

  • 第一种,uboot官方维护的uboot代码,更新最快,包含所有常用芯片。
  • 第二种,半导体厂商维护的uboot代码,例如NXP的uboot,针对性更强,对自己家芯片的支持性更好
  • 第三种,产品开发公司维护的uboot代码,使用芯片的公司自己添加了一些支持,boot移植就是讲的这个

NXP uboot-imx 链接

!注意!

  • 只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 的话会失败!
  • uboot 命令中的数字都是十六进制的!不是十进制的!
  • EMMC 核心板 uboot 环境变量的存储起始地址就是第1536(0x600)block。1536*512=786432
  • u-boot.imx是从第0个分区的第2个block开始的。
  • 千万不要写 SD 卡或者 EMMC 的前两个块(扇区),里面保存着分区表!

Uboot的编译

  1. 安装库 sudo apt-get install libncurses5-dev

  2. 解压对应的uboot代码 tar -vxjf uboot-imx-2016.03-2.1.0-g8b546e4.tar.bz2

  3. 使用脚本编译uboot代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bash
    # 使用了 make 命令,用于清理工程,也就是每次在编译 uboot 之前都清理一下工程。
    # 这里的 make 命令带有三个参数,第一个是 ARCH,也就是指定架构,这里肯定是 arm;
    # 第二个参数 CROSS_COMPILE 用于指定编译器,只需要指明编译器前缀就行了,比如 arm-linux-gnueabihf-gcc 编译器的前缀就是“arm-linux-gnueabihf-”
    # 最后一个参数 distclean 就是清除工程
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
    # 设定配置文件为 mx6ull_14x14_ddr512_emmc_defconfig
    # 这个配置文件在 uboot 源码的 configs 目录中
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig
    make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

     uboot 是 bootloader 的一种,可以用来引导Linux,但是 uboot 除了引导 Linux 以外还可以引导其它的系统,而且 uboot 还支持其它的架构和外设,比如 USB、网络、SD 卡等。这些都是可以配置的,需要什么功能就使能什么功能。所以在编译 uboot 之前,一定要根据自己的需求配置 uboot。而这个配置文件在上面的例子中就是mx6ull_alientek_emmc_defconfig

Uboot的启动

通过前面介绍过的烧写程序的方法将uboot烧写到设备,修改启动方式,启动对应设备内的uboot程序。

接上串口,不出意外就会出现下面的提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 指示了uboot版本 和 编译时间
U-Boot 2016.03-g0ae7e33 (Aug 14 2022 - 19:42:45 +0800)
# 指示cpu型号 和 运行频率
CPU: Freescale i.MX6ULL rev1.1 792 MHz (running at 396 MHz)
# CPU温度范围 和 当前温度
CPU: Industrial temperature grade (-40C to 105C) at 47C
# 复位原因 当前的复位原因是 POR。I.MX6ULL 芯片上有个 POR_B 引脚,将这个引脚拉低即可复位。
Reset cause: POR
# board name
Board: I.MX6U ALPHA|MINI
I2C: ready
DRAM: 512 MiB
# 当前有两个 MMC/SD 卡控制器:FSL_SDHC(0)和 FSL_SDHC(1)
# 正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡 FSL_SDHC(1)接的 EMMC。
MMC: FSL_SDHC: 0, FSL_SDHC: 1
# 标准输入、标准输出和标准错误所使用的终端,这里都使用串口(serial)作为终端。
In: serial
Out: serial
Err: serial
# 切换到 emmc 的第 0 个分区上,因为当前的 uboot 是 emmc 版本的,也就是从 emmc 启动的。
switch to partitions #0, OK
mmc1(part 0) is current device
# 网口信息,提示我们当前使用的 FEC1 这个网口,I.MX6ULL 支持两个网口。
Net: FEC1
# 提示正常启动,也就是说 uboot 要从 emmc 里面读取环境变量和参数信息启动 Linux内核了。
Normal Boot
# 倒计时结束之前按下任意键就会进入Linux命令行模式。如果在倒计时结束以后没有按下,那么Linux内核就会启动,Linux内核一旦启动,uboot就会寿终正寝。
Hit any key to stop autoboot: 2

如果在 3 秒倒计时结束之前按下任意键,那么就会进入 uboot 的命令行模式。

Uboot的命令及相关操作

1
2
3
4
# 输入 help 或者 ? 即可查看当前uboot支持的命令
==> help
# 输入 ? env 或者 env help 即可查看 env 命令的使用方法
==> env help

bdinfo

DRAM 的起始地址和大小、启动参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息。

△ env

  • env print 输出环境变量
  • env set 设置环境变量,设置为空就是删除环境变量
    • 示例 设置serverip env set serverip 192.168.192.100
    • 示例 带空格的环境变量 env set teest_args 'xxx xxx xxx'
  • env save 用set设定的环境变量只是临时的,使用save命令将环境变量固化到非易失存储器内。
环境变量名
bootdelayboot的延迟等待时间
bootcmd前面说过 uboot 倒计时结束以后就会启动 Linux 系统,其实就是执行的 bootcmd 中的启动命令。
ipaddr开发板 ip 地址,可以不设置,使用 dhcp 命令来从路由器获取 IP 地址。
ethaddr开发板的 MAC 地址,一定要设置。
gatewayip网关地址。
netmask子网掩码。
serverip服务器 IP 地址,也就是 Ubuntu 主机 IP 地址,用于调试代码。
1
2
3
4
5
6
setenv ipaddr 192.168.192.120
setenv ethaddr b8:ae:1d:01:00:00
setenv gatewayip 192.168.192.1
setenv netmask 255.255.255.0
setenv serverip 192.168.192.100
saveenv

md DRAM查看

md 命令用于显示内存值。

1
2
3
4
5
6
7
8
9
10
md[.b, .w, .l] address [# of objects]

# [.b .w .l]对应 byte、word 和 long
# address 就是要查看的内存起始地址
# [# of objects]表示要查看的数据长度 16进制 单位是前面的bwl


# 示例
# 从0x80000000 查看 0x10 个 long 的数据
md.l 80000000 10

nm DRAM修改

地址不自增

mm DRAM修改

mm 命令用于修改指定地址的内存值,地址自增。

1
2
3
4
5
6
7
8
mm[.b, .w, .l] address

# 示例
# 修改 0x80000000 的数据
# 输入 q 即可退出修改
=> mm.l 80000010
80000010: 00000000 ? _

mw 填充DRAM

命令 mw 用于使用一个指定的数据填充一段内存

1
2
3
4
mw[.b, .w, .l] address value [count]

# 示例
=> mw.l 80000010 faaffaaf 4

cp copy

cp 是数据拷贝命令,用于将 DRAM 中的数据从一段内存拷贝到另一段内存中,或者把 NorFlash 中的数据拷贝到 DRAM 中

1
2
3
4
5
cp[.b, .w, .l] source target count

# source 为源地址
# target 为目的地址
# count 为拷贝的数量 单位是前面的bwl

dhcp

dhcp 用于从路由器获取 IP 地址

△ nfs

nfs - boot image via network using NFS protocol

Usage:

1
2
3
4
5
6
7
8
nfs [loadAddress] [[hostIPaddr:]bootfilename]

# loadAddress 是要保存的 DRAM 地址
# [[hostIPaddr:]bootfilename]是要下载的文件地址

nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/u-boot.imx
nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage
nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb

△ tftp & tftpboot

tftpboot - boot image via network using TFTP protocol

Usage:

1
2
3
4
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]

# loadAddress 是要保存的 DRAM 地址
# [[hostIPaddr:]bootfilename]是要下载的文件地址

△ mmc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
mmc info
# △ 显示当前mmc的信息
# - display info of the current MMC device

mmc read addr blk# cnt
# addr 是数据读取到 DRAM 中的地址
# blk 是要读取的块起始地址(十六进制)
# cnt 是块计数
# 示例
# => mmc read 80800000 600 10
# 从mmc的第0x600个block开始,读取0x10个block,到DRAM的0x80800000处
# 注意从mmc info可以看到一个block的大小是512Byte

mmc write addr blk# cnt
mmc erase blk# cnt
mmc rescan

mmc part
# △ 显示分区
# - lists available partition on current mmc device

mmc dev [dev] [part]
# △ 显示或者切换当前的设备
# [dev] 设备号,mmc list可以查看
# [part] 分区号,mmc part可以查看
# - show or set current mmc device [partition]

mmc list
# - lists available devices

mmc hwpartition [args...]
# - does hardware partitioning
# arguments (sizes in 512-byte blocks):
# [user [enh start cnt] [wrrel {on|off}]] - sets user data area attributes
# [gp1|gp2|gp3|gp4 cnt [enh] [wrrel {on|off}]] - general purpose partition
# [check|set|complete] - mode, complete set partitioning completed
# WARNING: Partitioning is a write-once setting once it is set to complete.
# Power cycling is required to initialize partitions after set to complete.

mmc bootbus dev boot_bus_width reset_boot_bus_width boot_mode
# - Set the BOOT_BUS_WIDTH field of the specified device

mmc bootpart-resize <dev> <boot part size MB> <RPMB part size MB>
# - Change sizes of boot and RPMB partitions of specified device

mmc partconf dev boot_ack boot_partition partition_access
# - Change the bits of the PARTITION_CONFIG field of the specified device
# 说明:设置启动分区
# dev:mmc设备编号
# boot_ack:是否应答
# boot_partition:用户选择发送到主机的引导数据
# partition_access:用户选择要访问的分区
# 示例
# => mmc partconf 1 1 0 0
# 将mmc dev 1 设置为启动分区

mmc rst-function dev value
# - Change the RST_n_FUNCTION field of the specified device
# WARNING: This is a write-once field and 0 / 1 / 2 are the only valid values.

mmc setdsr <value>
# - set DSR register value

在Uboot中更新uboot程序

思路就是先通过网络把文件存在DRAM中(nfs、tftp),再将文件写入到特定位置(mmc write)。

如果 EMMC 里面烧写了 Linux 系统的话,EMMC 是有 3 个分区的

  • 第 0 个分区存放 uboot,
  • 第 1 个分区存放Linux 镜像文件和设备树,
  • 第 2 个分区存放根文件系统。
1
2
3
4
5
mmc dev 1 0              # 切换到 EMMC 分区 0
tftp 80800000 u-boot.imx # 下载 u-boot.imx 到 DRAM
# 或 nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/u-boot.imx
mmc write 80800000 2 32E # 烧写 u-boot.imx 到 EMMC 中 (注意这里是block的数量,xxx Byte/512Byte)
mmc partconf 1 1 0 0 # 分区配置,EMMC 需要这一步! SD卡不需要。

fatinfo

fatinfo 命令用于查询指定 MMC 设备分区的文件系统信息

1
2
3
4
5
6
7
8
9
10
11
12
fatinfo <interface> [<dev[:part]>]
# interface 表示接口,比如 mmc,
# dev 是查询的设备号,
# part 是要查询的分区。

# 查询 EMMC 分区 1 的文件系统信息,命令如下:
=> fatinfo mmc 1:1
Interface: MMC
Device 1: Vendor: Man 000015 Snr 3ee65248 Rev: 0.6 Prod: 8GTF4R
Type: Removable Hard Disk
Capacity: 7456.0 MB = 7.2 GB (15269888 x 512)
Filesystem: FAT32 "NO NAME "

fstype 查看文件系统格式

fstype 用于查看 MMC 设备某个分区的文件系统格式

1
2
3
4
5
6
7
8
9
10
11
12
13
fstype <interface> <dev>:<part>

# 正点原子 EMMC 核心板上的 EMMC 默认有 3 个分区,我们来查看一下这三个分区的文件系统格式
=> fstype mmc 1:0
Failed to mount ext2 filesystem...
** Unrecognized filesystem type **
=> fstype mmc 1:1
fat
=> fstype mmc 1:2
ext4
# 从上可以看出,分区 0 格式未知,因为分区 0 存放的 uboot,并且分区 0 没有格式化,所以文件系统格式未知。
# 分区 1 的格式为 fat,分区 1 用于存放 linux 镜像和设备树。
# 分区 2 的格式为 ext4,用于存放 Linux 的根文件系统(rootfs)。

fatls fat列出文件

fatls 命令用于查询 FAT 格式设备的目录和文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fatls <interface> [<dev[:part]>] [directory]
# interface 是要查询的接口,比如 mmc,
# dev 是要查询的设备号,
# part 是要查询的分区,
# directory是要查询的目录。

# 查询 EMMC 分区 1 中的所有的目录和文件,输入命令:
# 分区1中存放着dts文件,通过这个命令可以查看到
=> fatls mmc 1:1 /
6785480 zimage
39459 imx6ull-14x14-emmc-4.3-480x272-c.dtb
39459 imx6ull-14x14-emmc-4.3-800x480-c.dtb
39459 imx6ull-14x14-emmc-7-800x480-c.dtb
39459 imx6ull-14x14-emmc-7-1024x600-c.dtb
39459 imx6ull-14x14-emmc-10.1-1280x800-c.dtb
40295 imx6ull-14x14-emmc-hdmi.dtb
40203 imx6ull-14x14-emmc-vga.dtb

8 file(s), 0 dir(s)

fatload 设备文件load到DRAM

fatload 命令用于将指定的文件读取到 DRAM 中

1
2
3
4
5
6
7
8
9
10
11
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
# interface 为接口,比如 mmc,
# dev 是设备号,
# part 是分区,
# addr 是保存在 DRAM 中的起始地址,filename 是要读取的文件名字。
# bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省略的话表示读取整个文件。
# pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的话表示从文件首地址开始读取。


# 我们将 EMMC 分区 1 中的 zImage 文件读取到 DRAM 中的 0X80800000 地址处,命令如下:
fatload mmc 1:1 80800000 zImage

fatwrite 文件写入设备

fatwirte 命令用于将 DRAM 中的数据写入到 MMC 设备中

  • 我们可以通过 fatwrite 命令在 uboot 中更新 linux 镜像文件和设备树。
  • 注意!uboot 默认没有使能 fatwrite 命令,需要修改板子配置头文件。找到自己开发板对应的配置头文件然后添加如下一行宏定义来使能 fatwrite 命令:
    #define CONFIG_FAT_WRITE /* 使能 fatwrite 命令 */
1
2
3
4
5
6
7
8
9
10
11
fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>

# interface 为接口,比如 mmc,
# dev 是设备号,
# part 是分区,
# addr 是要写入的数据在 DRAM 中的起始地址,
# filename 是写入的数据文件名字,
# bytes 表示要写入多少字节的数据。

# 把位于 0x80800000 的 0x6788f8 字节的 zimage 文件 写入到 mmc 1:1 中
fatwrite mmc 1:1 80800000 zImage 6788f8

ext4ls ext4列出文件

ext4load

ext4write

reset 重启

go 执行指定地址的程序

△ run 运行环境变量中的脚本

1
2
3
4
5
6
run bootcmd


setenv boot2nfs 'nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage; nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb; bootz 80800000 - 83000000'
setenv boot2nfs 'nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage; nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb; bootz 80800000 - 83000000'
run boot2nfs

mtest DRAM test

1
2
3
mtest [start [end [pattern [iterations]]]]

=> mtest 80000000 80001000

Uboot的BOOT操作

uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常用的跟 boot 有关的命令有:bootz、bootm 和 boot。

我们也可以通过设置环境变量,通过run指令来执行环境变量的方式来启动预定义的linux系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
run bootcmd


setenv boot2nfs 'nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage; nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb; bootz 80800000 - 83000000'
run boot2nfs



setenv serverip "192.168.3.5’
setenv ipaddr '192.168.3.135'
setenv gatewayip '192.168.3.1'
setenv netmask '255.255.255.0'
setenv oikiou_zImage '/home/frank/linuxMini/nfs_dir/oikiou_zImage'
setenv oikiou_dtbp '/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb'
setenv oikiou_rootfs '/home/frank/linuxMini/nfs_dir/rootfs_oikiou_buildroot'
setenv oikiou_netargs 'setenv bootargs console=ttymxc0,115200 root=/dev/nfs nfsroot=${serverip}:${oikiou_rootfs},proto=tcp rw ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off'
setenv onetboot 'echo Booting from net ...; run oikiou_netargs; nfs ${loadaddr} ${serverip}:${oikiou_zImage}; nfs ${fdt_addr} ${serverip}:${oikiou_dtbp}; bootz ${loadaddr} - ${fdt_addr};'
run onetboot

bootz

要启动 Linux,需要先将 Linux 镜像文件拷贝到 DRAM 中,如果使用到设备树的话也需要将设备树拷贝到 DRAM 中。

可以从 EMMC 或者 NAND 等存储设备中将 Linux 镜像和设备树文件拷贝到 DRAM,也可以通过 nfs 或者 tftp 将 Linux 镜像文件和设备树文件下载到 DRAM 中。
不管用那种方法,只要能将 Linux 镜像和设备树文件存到 DRAM 中就行,然后使用 bootz 命令来启动linux

1
2
3
4
5
bootz [addr [initrd[:size]] [fdt]]

# addr 是 Linux 镜像文件在 DRAM 中的位置,
# initrd 是 initrd 文件在DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可,
# fdt 就是设备树文件在 DRAM 中的地址。

网络方式通过bootz启动

1
2
3
nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage
nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb
bootz 80800000 - 83000000

bootm

bootm 和 bootz 功能类似,但是 bootm 用于启动 uImage 镜像文件。

boot

boot 命令也是用来启动 Linux 系统的,只是 boot 是通过读取环境变量 bootcmd 来启动 Linux 系统。

原始的bootcmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
run findfdt;
mmc dev ${mmcdev};
mmc dev ${mmcdev};
if mmc rescan;
then if run loadbootscript;
then run bootscript;
else if run loadimage;
then run mmcboot;
else run netboot;
fi;
fi;
else run netboot;
fi

'run findfdt;mmc dev ${mmcdev};mmc dev ${mmcdev};if mmc rescan;then if run loadbootscript;then run bootscript;else if run loadimage;then run mmcboot;else run netboot;fi;fi;else run netboot;fi'

Linux Kernel 内核

Linux 由 Linux 基金会管理与发布,Linux 官网为 https://www.kernel.org

Linux内核和Uboot也是类似,有linux原生代码,和NXP维护测试ok的代码。

Kernel的编译

编译内核之前需要先在 ubuntu 上安装 lzop 库,否则内核编译会失败!

1
sudo apt-get install lzop

Kernel文件解析

arch

arch/arm/configs 中就包含有 I.MX6U-ALPHA 开发板的默认配置文件。

arch/arm/boot/dts 目录里面是对应开发平台的设备树文件(.dtb文件)。

arch/arm/boot 目录下会保存编译出来的 Im age 和 zImage 镜像文件,而 zImage 就是我们要用的 linux 镜像文件。
arch/arm/mach-xxx 目录分别为相应平台的驱动和初始化文件,比如 mach-imx 目录里面就是 I.MX 系列 CPU 的驱动和初始化文件。

drivers

驱动目录文件,此目录根据驱动类型的不同,分门别类进行整理,比如 drivers/i2c 就是 I2C相关驱动目录,drivers/gpio 就是 GPIO 相关的驱动目录,这是我们学习的重点。

Rootfs 根文件系统

Linux 中的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文
件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

根文件系统是内核启动时所mount的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

rootfs的构建 (busybox)

usyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、mv、ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一
个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。

BusyBox 官网:https://busybox.net/

  1. 我们自己下载对应版本的busybox,这里示例版本是busybox-1.35.0
    对于现在2023年7月25日来说busybox-1.36.1这个版本太新了容易有一些问题,暂时不用。

  2. 下载对应版本的源码后我们先编辑顶层Makefile,编辑CROSS_COMPILE ?= arm-linux-gnueabihf-,指定编译器。

  3. busybox的官方不支持中文,所以需要修改一些代码来完成对中文的支持。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    diff -Nura a/libbb/printable_string.c b/libbb/printable_string.c
    --- a/libbb/printable_string.c 2021-01-12 06:27:20.785120436 -0500
    +++ b/libbb/printable_string.c 2021-01-12 06:40:37.481954486 -0500
    @@ -28,8 +28,10 @@
    }
    if (c < ' ')
    break;
    - if (c >= 0x7f)
    + /*support chinese display*/
    + /*if (c >= 0x7f)
    break;
    + */
    s++;
    }

    @@ -42,7 +44,9 @@
    unsigned char c = *d;
    if (c == '\0')
    break;
    - if (c < ' ' || c >= 0x7f)
    + /*support chinese display*/
    + /*if (c < ' ' || c >= 0x7f)*/
    + if (c < ' ' )
    *d = '?';
    d++;
    }
    diff -Nura a/libbb/unicode.c b/libbb/unicode.c
    --- a/libbb/unicode.c 2021-01-12 06:28:37.601117822 -0500
    +++ b/libbb/unicode.c 2021-01-12 06:44:05.502420078 -0500
    @@ -1019,7 +1019,9 @@
    while ((int)--width >= 0);
    break;
    }
    - *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
    + /*support chinese display*/
    + /**d++ = (c >= ' ' && c < 0x7f) ? c : '?';*/
    + *d++ = (c >= ' ') ? c : '?';
    src++;
    }
    *d = '\0';
    @@ -1027,7 +1029,9 @@
    d = dst = xstrndup(src, width);
    while (*d) {
    unsigned char c = *d;
    - if (c < ' ' || c >= 0x7f)
    + /* support chinese display*/
    + /*if (c < ' ' || c >= 0x7f)*/
    + if (c < ' ')
    *d = '?';
    d++;
    }
  4. busybox的配置。

    1. 先对busybox进行默认选项配置,再在这个基础上进行微调。

    2. 运行make defconfig,进行默认选项配置。
      busybox有以下几种配置选项。

      1. defconfig,缺省配置,也就是默认配置选项。
      2. allyesconfig,全选配置,也就是选中 busybox 的所有功能。
      3. allnoconfig,最小配置。
    3. 打开图像界面进行微调,make menuconfig

      1. 选中 这些选项

        1
        2
        3
        Settings-> [*] vi-style line editing commands
        Settings-> [*] Support Unicode
        Settings-> [*] Check $LC_ALL, $LC_CTYPE and $LANG environment variables
    4. 配置结束

  5. busybox的编译安装

    1. 配置好 busybox 以后就可以编译了,我们要将编译结果存储到前面创建的 rootfs 目录中,之后将这个目录作为nfs的server文件夹,用于开发板的rootfs挂载。

      1
      2
      make
      make install CONFIG_PREFIX=/home/frank/linuxMini/nfs_dir/rootfs
    2. 我们查看rootfs的输出文件夹 /home/frank/linuxMini/nfs_dir/rootfs

      1
      2
      3
      4
      5
      6
      7
      8
      $ ls
      bin linuxrc sbin usr

      # rootfs 目录下有 bin、sbin 和 usr 这三个目录,以及 linuxrc 这个文件。
      # 前面说过 Linux 内核 init 进程最后会查找用户空间的 init 程序,
      # 找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。
      # 如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,
      # 所以用户态空间的 init 程序是 busybox 来生成的
  6. busybox完善

    1. 可以看出来这个rootfs缺失很多文件,lib,ev、proc、mnt、sys、tmp 和 root 等

    2. 创建缺失的文件夹

      1
      mkdir dev proc mnt sys tmp root lib usr/lib -p
    3. 先添加lib文件,为了方便直接将所有的库文件都拷贝进去,这块后续可以根据用到的库文件做优化。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      # 拷贝 /lib
      cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-
      gnueabihf/libc/lib

      # “-d”表示拷贝符号链接
      cp *so* *.a /home/frank/linuxMini/nfs_dir/rootfs/lib/ -d

      # ld-linux-armhf.so.3 会链接到 ld-2.19-2014.08-1-git.so 上我们将这个符号链接删掉,cp一份原来的文件代替
      cd /home/frank/linuxMini/nfs_dir/rootfs/lib/
      rm ld-linux-armhf.so.3
      cp ld-2.19-2014.08-1-git.so ld-linux-armhf.so.3

      cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib
      cp *so* *.a /home/frank/linuxMini/nfs_dir/rootfs/lib/ -d

      # 拷贝 /usr/lib
      cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
      cp *so* *.a /home/frank/linuxMini/nfs_dir/rootfs/usr/lib/ -d
    4. 给rootfs添加/etc/init.d/rcS文件

      1. 创建/etc/init.d/rcS文件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        #!/bin/sh

        PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
        LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
        export PATH LD_LIBRARY_PATH
        mount -a
        mkdir /dev/pts
        mount -t devpts devpts /dev/pts

        echo /sbin/mdev > /proc/sys/kernel/hotplug
        mdev -s

        rcS 是个 shell 脚本,Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。

        第 1 行,表示这是一个 shell 脚本。
        第 3 行,PATH 环境变量保存着可执行文件可能存在的目录,这样我们在执行一些命令或 者可执行文件的时候就不会提示找不到文件这样的错误。
        第 4 行,LD_LIBRARY_PATH 环境变量保存着库文件所在的目录。
        第 5 行,使用 export 来导出上面这些环境变量,相当于声明一些“全局变量”。
        第 7 行,使用 mount 命令来挂载所有的文件系统,这些文件系统由文件/etc/fstab 来指定, 所以我们一会还要创建/etc/fstab 文件。
        第 8 和 9 行,创建目录/dev/pts,然后将 devpts 挂载到/dev/pts 目录中。
        第 11 和 12 行,使用 mdev 来管理热插拔设备,通过这两行,Linux 内核就可以在/dev 目录下自动创建设备节点。

      2. 将上面文件存储到/etc/init.d/rcS文件中,并给它加上可执行权限。

    5. 给rootfs添加/etc/fstab文件

      • fstab 在 Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #<file system> <mount point> <type> <options> <dump> <pass>
      proc /proc proc defaults 0 0
      tmpfs /tmp tmpfs defaults 0 0
      sysfs /sys sysfs defaults 0 0

      # <file system> <mount point> <type> <options> <dump> <pass>
      #
      # <file system>:要挂载的特殊的设备,也可以是块设备,比如/dev/sda 等等。
      # <mount point>:挂载点。
      # <type>:文件系统类型,比如 ext2、ext3、proc、romfs、tmpfs 等等。
      # <options>:挂载选项,在 Ubuntu 中输入“man mount”命令可以查看具体的选项。一般使用 defaults,也就是默认选项,defaults 包含了 rw、suid、 dev、 exec、 auto、 nouser 和 async。
      # <dump>:为 1 的话表示允许备份,为 0 不备份,一般不备份,因此设置为 0。
      # <pass>:磁盘检查设置,为 0 表示不检查。根目录‘/’设置为 1,其他的都不能设置为 1,其他的分区从 2 开始。一般不在 fstab 中挂载根目录,因此这里一般设置为 0。
    6. 给rootfs添加/etc/inittab文件

      • init 程序会读取/etc/inittab这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      #etc/inittab
      ::sysinit:/etc/init.d/rcS
      console::askfirst:-/bin/sh
      ::restart:/sbin/init
      ::ctrlaltdel:/sbin/reboot
      ::shutdown:/bin/umount -a -r
      ::shutdown:/sbin/swapoff -a

      # <id>:<runlevels>:<action>:<process>
      # <id>:每个指令的标识符,不能重复。但是对于 busybox 的 init 来说,<id>有着特殊意义。对于 busybox 而言<id>用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控制 tty
      # <runlevels>:对 busybox 来说此项完全没用,所以空着。
      # <action>:动作,用于指定<process>可能用到的动作。
      sysinit
      在系统初始化的时候 process 才会执行一次。
      respawn
      当 process 终止以后马上启动一个新的。
      askfirst
      和 respawn 类似,在运行 process 之前在控制台上显示“Please press Enter to activate this console.”。只要用户按下“Enter”键以后才会执行 process。
      wait
      告诉 init,要等待相应的进程执行完以后才能继续执行。
      once
      仅执行一次,而且不会等待 process 执行完成。
      restart
      当 init 重启的时候才会执行 procee。
      ctrlaltdel
      当按下 ctrl+alt+del 组合键才会执行 process。
      shutdown
      关机的时候执行 process。
      # <process>:具体的动作,比如程序、脚本或命令等。
  7. busybox搭建完成。

buildroot 配置 rootfs

《第三篇 系统移植篇》我们最后讲解了如何使用 busybox 构建文件系统,busybox 仅仅只是帮我们构建好了一些常用的命令和文件,像 lib 库、/etc 目录下的一些文件都需要我们自己手动创建,而且 busybox 构建的根文件系统默认没有用户名和密码设置。在后续的实验中,我们还要自己去移植一些第三方软件和库,比如 alsa、iperf、mplayer 等等。那么有没有一种傻瓜式的方法或软件,它不仅包含了 busybox 的功能,而且里面还集成了各种软件,需要什么软件就选择什么软件,不需要我们去移植。答案肯定是有的,buildroot 就是这样一种工具,buildroot 比 busybox 更上一层楼,buildroot 不仅集成了 busybox,而且还集成了各种常见的第三方库和软件,需要什么就选择什么,就跟我们去吃自助餐一样,想吃什么就拿什么。buildroot 极大的方便了我们嵌入式 Linux 开发人员构建实用的根文件系统。

  1. 下载buildroot https://buildroot.org/

    1. 解压
  2. make menuconfig 执行配置

    1.

     
    1
    2
    3
    4
    5
    6
    7
    8
    # 这里主要是配置架构相关的内容
    Target options
    -> Target Architecture = ARM (little endian)
    -> Target Binary Format = ELF
    -> Target Architecture Variant = cortex-A7
    -> Target ABI = EABIhf
    -> Floating point strategy = NEON/VFPv4
    -> ARM instruction set = ARM

    2.

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Toolchain 这里主要是配置交叉编译链
    # 这里设置为我们自己所使用的交叉编译器即可。buildroot 其实是可以自动下载交叉编译器的,但是都是从国外服务器下载的,鉴于国内的网络环境,强烈推荐大家设置成自己所使用的交叉编译器。

    Toolchain
    -> Toolchain type = External toolchain
    -> Toolchain = Custom toolchain #用户自己的交叉编译器
    -> Toolchain origin = Pre-installed toolchain #预装的编译器
    -> Toolchain path =/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf # 绝对路径
    -> Toolchain prefix = $(ARCH)-linux-gnueabihf #置交叉编译器前缀,要根据自己实际所使用的交叉编译器来设置,比如我们使用的是 arm-linux-gnueabihf-gcc,因此前缀就是$(ARCH)-linux-gnueabihf,其中 ARCH我们前面已经设置为了 arm。
    -> External toolchain gcc version = 4.9.x
    -> External toolchain kernel headers series = 4.1.x
    -> External toolchain C library = glibc/eglibc
    -> [*] Toolchain has SSP support? (NEW) #选中
    -> [*] Toolchain has RPC support? (NEW) #选中
    -> [*] Toolchain has C++ support? #选中
    -> [*] Enable MMU support (NEW) #选中

    3.

     
    1
    2
    3
    4
    5
    6
    7
    8
    # 系统配置
    System configuration
    -> System hostname = alpha_imx6ull #平台名字,自行设置
    -> System banner = Welcome to alpha i.mx6ull #欢迎语
    -> Init system = BusyBox #使用 busybox
    -> /dev management = Dynamic using devtmpfs + mdev #使用 mdev
    -> [*] Enable root login with password (NEW) #使能登录密码
    -> Root password = 123456 #登录密码为 123456

    4.

     
    1
    2
    3
    4
    5
    # 此选项配置我们最终制作的根文件系统为什么格式的
    -> Filesystem images
    -> [*] ext2/3/4 root filesystem #如果是 EMMC 或 SD 卡的话就用 ext3/ext4
    -> ext2/3/4 variant = ext4 #选择 ext4 格式
    -> [*] ubi image containing an ubifs root filesystem #如果使用 NAND 的话就用 ubifs

    5.

     
    1
    2
    3
    4
    5
    # buildroot 不仅仅能构建根文件系统,也可以编译 linux 内核和 uboot。当配置 buildroot,使能 linux 内核和 uboot 以后 buildroot 就会自动下载最新的 linux 内核和 uboot 源码并编译。但是我们一般都不会使用 buildroot 下载的 linux 内核和 uboot,因为 buildroot 下载的 linux 和 uboot官方源码,里面会缺少很多驱动文件,而且最新的 linux 内核和 uboot 会对编译器版本号有要求,可能导致编译失败。因此我们需要配置 buildroot,关闭 linux 内核和 uboot 的编译,只使用 buildroot 来构建根文件系统
    -> Kernel
    -> [ ] Linux Kernel #不要选择编译 Linux Kernel 选项!
    -> Bootloaders
    -> [ ] U-Boot #不要选择编译 U-Boot 选项!
    1. 接下来就是 Target packages 用来配置第三方库和软件

    2. 1
      2
      3
      iperf3
      netools
      Target packages->Networking applications->openssh
  3. 编译 buildroot

    1. sudo make 注意,一定要加 sudo,而且不能通过-jx 来指定多核编译
      1. 编译过程可能会下载很多组件,可以考虑给wget添加代理
    2. 编译完成后会在output/images生成根文件系统
      1. 其中 rootfs.tar 就是打包好的根文件系统

Yocto 构建 rootfs

Yocto 和 buildroot 构建 rootfs 的差异

值得注意的是,Yocto 和 Buildroot 本身并不是 Linux 发行版,它们只是帮助开发人员构建基于 Linux 的嵌入式系统(选择 Yocto,您可以构建 Linux 发行版;选择 Buildroot,您可以开发用于构建发行版的根文件系统)。

为什么应该选择 Yocto

尽管 Yocto 操作更复杂,但它也有很明显的优势。或许其中最大的优势就是 Yocto 拥有广泛的用户群体和支持,开发社区非常活跃,为其创建了各种新的工具、层级和特性。此外,它由 Linux 基金会出资扶持,因而也更得人心。

Yocto 的另一大好处则是它可以实现层级,用于各种任务,从功能添加,到项目版本中不可用平台的定位等,都可以实现。此外,还可以添加特殊功能,例如自定义浏览器,以便实现 Yocto 本身进一步的功能定制化。

Yocto 获得了多家半导体和电路板生产商的支持,在同类项目中,可以兼容的设备最多。也就是说,除了充满活力的 Yocto 生态系统之外,自定义 Yocto 构建还可以添加大量的 SDK、工具和功能。

正是有了这些要素,Yocto 实现了高度可定制,同时还拥有强劲的支持,可供计划构建嵌入式系统的开发人员使用。

Yocto 的缺点

即便如此,Yocto 也有一些缺点。一方面,入门时的学习曲线稍微有点陡峭。对于时间有限的小型单个项目或小型团队来说,使用这个工具可能根本没有意义。另一方面,众所周知,Yocto 的构建时间很长。这就降低了迭代频率,如用在时间紧迫的项目中,效果可能会不太理想。

为什么应该选择 Buildroot

Buildroot 的构建很简单——从运行方式到输出,都非常简便快捷。它的核心构建系统采用 Make 语言编写而成,语言十分简短,开发人员不需要学习,就能轻松理解。正如上文所述,Buildroot 使用标准的 Makefiles 和 kconfig 进行配置。Makefiles 和 kconfig 是 Linux 内核社区制作的两个工具,它们获得了广泛应用和支持。

Buildroot 要求的“企业”参与度较低,因此它也是开源社区的“宠儿”。这样就会实现更高的可定制性以及更多的能力,从而尽可能开发具有针对性的系统,满足开发团队的需求。

最后,Buildroot 还有一项极其有用的功能——它禁用了可选的构建时间设置,选择以即开即用的方式,创建尽可能小的镜像。这样大幅缩短了构建时间,减少了所需的必要计算资源(不过无法实现更灵活的构建)。对于小型团队或资源有限的团队而言,这可能是一种理想的解决方案。

Buildroot 的缺点

首先必须要讨论的就是它最明显的缺点——缺乏公司支持。尽管对于许多开发人员来说,这可能是一个好处,但其实它也有一些明显的不足。即,社区较小,且生态系统不太活跃。也就是说,它没有那么多的支持。由于许多开发人员更愿意专注研究应用更广泛的工具,因此,如果您遇到问题,您可能必须自己处理或学习如何解决。

Buildroot 的另一个主要缺点是它不支持增量构建。每当您需要进行更新,即使是很小的更新,您都必须从头开始重新制作镜像。这样必定会使开发周期延长,增加不必要的时间。

此外,Buildroot 最大的亮点就是其注重简约,但这也意味着,相比 Yocto,Buildroot 定制和特殊系统搭建会困难得多。
Buildroot 和 yocto的对比

对比内容:
(1) 嵌入式构建系统
目标是构建一个完整的,客制化的嵌入式Linux系统
包括root filesystem, toolchain, kernel, bootloader
(2) 从源代码开始
(3) 使用交叉编译工具链
(4) 非常活跃的维护和开发工程
(5) 工业界广泛使用
(6) 有文档和培训课程
(7) 自由软件

buildroot的通用信条
(1) 专注于简单化
(2) 使用简单,理解简单,扩展简单
(3) 通过扩展脚本而不是buildroot本身来处理特殊情况
(4) 使用现存的技术/语言:kconfig, make. (值得投入时间去学习)
(5) 默认小
(6) 目的无关的(Purpose-agnostic)
(7) 开放社区,没有供应商、官僚/公司的管理

yocto的通用信条
(1) 支持主要的CPU架构
OpenEmbedded:仅qemu
Yocto Project:为一小部分机器增加支持
(2) 只提供核心方法,使用layers来支持更多的package和机器
(3) 客户的改动应该在一个单独的layer
(4) 多用途的构建系统:尽可能灵活的处理更多的使用情况
(5) 开放社区,但是该工程被公司赞助商发起的Yocto Project Advisory Board监管
(6) OpenEmbedded 是一个独立社区驱动的工程。

buildroot 输出
(1) 主要是根文件系统镜像
同时包含:工具链, 内核镜像, bootloader等
(2) 支持多种格式:ext2/3/4, ubifs, iso9600等
(3) 没有二进制包, 没有包管理系统
一些人称之为一个firmware generator
通过包不可能更新
更新需要一个完整的系统更新,像Andorid一样
认为部分更新是有害的

Yocto 输出
(1) 构建distribution,主要的输出是一个package feed
包管理系统是可选的
装载和更新系统的一部分是可能的
(2) 通过安装一些包,也可以产生根文件系统镜像。支持ext2/3/4, ubifs, iso9600等,也支持VM镜像:vmdk,vdi,qcow2
(3) 最终,镜像类或者工具,wic可用来构建磁盘镜像
(4) 生成image时也可以生成SDK,可以让应用开发者编译和测试他们的应用(不用集成到build中)。但是SDK必须要和image匹配。

Buildroot 配置
(1) 和Linux kernel一样使用kconfig
(2) 简单的{menu,x,n,g}配置接口
(3) 整个配置保存在一个文件 .config/defconfig
(4) 定义系统的各个方面:架构,内核版本/内核配置,bootloader,用户空间package等等。
(5) make menuconfig, make
(6) 为不同的机器构建通用的系统:单独处理
一个可以从fragment中构建出defconfig的工具
可行的,但是并非超级简单
每台机器完全独立的构建

Yocto 配置
(1) 配置分成几个部分:
Distribution 配置 (package配置,toolchain和libc选择…)
Machine Configuration (定义架构, CPU功能, BSP)
Image recipe (target安装什么package)
Local配置 (Distribution和默认machine选择, 编译时使用多少个线程, 是否删除build artifact)
(2) 有必要收集将要被使用的layers,并宣布它们。
(3) 允许为不同的机器构建相同的镜像,或者为同一个机器构建不同的distribution或镜像。

Buildroot layers
(1) 没有layer的概念
(2) 所有的包在官方repository中维护
(3) 添加BR2_EXTERNAL
允许存储包定义、配置和其他人工文件
一个BR2_EXTERNAL
通常用作专有的/客制化的包和配置
仅增加包,不覆盖buildroot中的包

yocto layers
(1) layer机制允许修改和增加新package或image
(2) core build system, BSP和custome modifications之间明确分离
(3) 第三方提供为它们layers提供BSP,或者一套处理专用应用程序的方法
(4) Layers需要兼容和使用相同的OE branch base
(5) 谨防layer quality, 检查不是系统性的
(6) OpenEmbedded Metadata Index 列出了可用的layers,recipes,machines:http://layers.openembedded.org/layerindex/
(7) 此外,有一个强大的override机制,可以基于machine或者distribution调整recipe variables

buildroot/yocto toolchain
相同的功能:
(1) 构建自己的toolchain,基于gcc、C库(glibc, uClibc, musl)
(2) 使用external toolchain, 对于buildroot更简单,因为内置有这个功能,对于yocto,只有在additional vendor layers正真完全支持。

buildroot new package
涉及三个文件 Config.in xxx.mk xxx.hash

yocto new package
涉及一个文件×××.bb

buildroot: complexity
(1) 设计成简单使用
(2) 对于core,每个建议的功能以有用性/复杂度比来分析
(3) core逻辑完全使用make编写,少于1000行的code包含了230行注释:确实容易理解what、why、how;几乎和一个shell脚本一个接一个地下载、提取、构建、安装软件那样简单。
(4) 文档很充分,有很多资源可用
(5) 一个小时的talk足以描述所有内部实现(ELCE 2014)
(6) IRC上典型的反馈:来自Yocto,非常惊喜,使用起来这么简单。这是让我为难的第一件事。

Yocto Project: complexity
(1) 有点陡峭的学习曲线
(2) 核心是bitbake, 一个用python编写的单独项目(60千行代码)
(3) 一套class定义common task
(4) recipe 使用 bitbake specific language, python 和 shell 混合编写
(5) 日志和调试可帮助理解每个task具体做了什么
(6) 详细的文档,但是有很多不同的配置变量
(7) 并不总是容易理解最佳实践(比如, Poky 不能用于 production, distro/image 修改不能在local.conf中做, 删除tmp/)
(8) 人们依然对一些术语感到疑惑(Yocto Project, Poky, OpenEmbedded, bitbake)

Buildroot packages
(1) 1800+ packages
(2) Graphics: X.org, Wayland, Qt4/Qt5, Gtk2/Gtk3, EFL
(3) Multimedia: Gstreamer 0.10/1.x, ffmpeg, Kodi, OpenGL
(4) Languages: Python2/3, PHP, Lua, Perl, Erlang, Mono, Ruby, Node.js
(5) Networking: Apache, Samba, Dovecot, Exim, CUPS, lots of servers/tools
(6) Init systems: Busybox(default), initsysv, systemd
(7) No support for a toolchain on the target

Yocto Project packages
(1) 几千个recipes: 对于oe-core, meta-openembedded, meta-qt5大约2200个。通过Metadata Index知道多余8400
(2) 大部分和buildroot一样
(3) 更多的语言: Java, Go, Rust, smalltalk
(4) 对于Qt3仍有一个起作用的layer
(5) meta-virtualization(Docker, KVM, LXC, Xen)和 meta-openstack layers

Buildroot 依赖方法
(1) 极简依赖, 如果一个功能可以关闭,那么默认关闭
(2) 很多自动依赖,比如,如果你开启OpenSSL,将自动从其他可提供SSL支持的enabled的包中获得SSL支持
(3) 默认毫不费力的的得到小的根文件系统

Yocto Project 依赖方法
(1) 在distribution级进行package 配置
开启OpenSSL将对所有package打开,但是可以对一些package关闭,相反,也可以对选定的pacakge开启一些功能。
(2) 可以在machine级进行修改,但是应该避免这样做
(3) 每个recipe可以定义自己的默认功能集,一个稳健的默认配置。

Buildroot 更新/安全
(1) 每三个月release,两个月开发,一个月稳定
(2) release包含package版本更新:security 更新和major 更新
(3) 核心架构也可能潜在性的发生改变
(4) 没有LTS版本,用于需要自己处理
(5) 正在提供一个脚本来评估给定buildroot配置中未解决的CVE (Common Vulnerabilities & Exposures)

Yocto Project 更新/安全
(1) 每6个月release,一次在4月,一次在10月
(2) 可通过wiki: https://wiki.yoctoproject.org/wiki/Yocto_Project_v2.1_Status了解planning和roadmap
(3) 在M1和最终release之间的三个月内包含4个milestone
(4) 至少先前和当前release的版本有指定维护者,他们获取安全和重要的解决方法,但是没有recipe更新
(5) 旧版本由社区维护

Buildroot 检测配置修改
(1) Buildroot不很智能
(2) 当修改配置是,它不尝试检测哪些需要rebuild
(3) 一旦build一个package,buildroot将不rebuild它,除非你强制
(4) 大的配置修改需要full rebuild
(5) 小的配置修改可以不需要full rebuild
(6) 一个配置,一个build,不能配置间不能分享

Yocto Project 检测配置修改
(1) bitbake 维护一个shared State Cache允许增加的builds
(2) 它通过创建inputs的checksum检测task的input修改
(3) 该cache可在所有的builds间共享, 对于类似的machines,build很快
(4) 可以跨主机分享该cache,比如一个夜间服务器和一个开发机,大大加快full build

Buildroot: architecture support
(1) 支持很多架构
(2) ARM(64), MIPS, PowerPC(64), x86/x86-64
(3) 也支持很多更专用的架构:Xtensa, Blackfin, ARC, m68k, SPARC, Microblaze, NIOSII; ARM noMMU, especially ARMv7-M
(4) 架构供应商提供援助: Imagination Technologies的MIPS, IBM的PowerPC64, Synopsys的ARC, Analog Devices的Blackfin

Yocto Project: architecture support
(1) core中, ARM, MIPS, PowerPC, X86,以及它们64bit 系列
(2) separate layers:Microblaze, NIOSII
(3) 通常芯片厂商维护他们自己的BSP layer:meta-intell, meta-altera (ARM & NIOSII), meta-atmel, meta-fsl, meta-ti, mtea-xilinx …
(4) 社区提供:meta-rockchip, meta-sunxi

Buildroot: minimal build
最小的build花费15分25秒,image size 2.2MB

yocto project: minimal build
最小build花费50分47秒, image size为4.9MB。如果有存在的sstate-cache,花费1分21秒

License
(1) 都可以创建一个使用许可证的列表
(2) 都能够检测到许可证更改
(3) Yocto项目可以剔除GPLv3

Buildroot & Yocto 选择
Buildroot
(1) 非常专用的CPU架构
(2) 非常小的rootfs < 8M
(3) 对工程师没有很大的要求
(4) 不支持各种mechines或者类似的系统
(5) 不需要包/部分系统的更新
(6) 小系统

yocto
(1) 不是非常特殊的CPU架构,不是非常小的rootfs,需要有经验的工程师。
(2) 不是非常特殊的CPU架构,不是非常小的rootfs,需要有经验的工程师。支持几种类似的系统
(3) 不是非常特殊的CPU架构,不是非常小的rootfs,需要有经验的工程师。需要更新包和部分系统
(4) 不是非常特殊的CPU架构,不是非常小的rootfs,需要有经验的工程师。非常大的系统

Ubuntu-Base rootfs构建

Ubuntu是 Linux 系统的一种,可以简单的将 Ubuntu 理解为一个根文件系统,和我们用 busybox、buildroot制作的根文件系统一样。

根文件系统下载地址 http://cdimage.ubuntu.com/ (ubuntu-base)

Ubuntu 针对不同的 CPU 架构提供相应的 ubuntu base 根文件系统,有 amd64(64 位 X86)、armhf、i386(32 位 X86)、powerpc、ppc64el 等系统的。I.MX6ULL 是 Cortex-A7 内核的 CPU,并且有硬件浮点运算单元,因此选择 armhf 版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 解压
tar -vxf ubuntu-base-16.04.6-base-armhf.tar.gz -C rootfs_ubuntu_1604

# 安装qemu虚拟机
sudo apt-get install qemu-user-static

# 将 qemu-user-static 拷贝到ubuntu base目录下
cd rootfs_ubuntu_1604
sudo cp /usr/bin/qemu-arm-static ./usr/bin/

# 配置开发板用的rootfs
cd rootfs_ubuntu_1604
# 从主机复制 DNS配置文件
sudo cp /etc/resolv.conf ./etc/resolv.conf
# 换源 修改开发板 rootfs 的源
./etc/apt/sources.list

# 在主机(虚拟机(当前执行指令的设备))中挂载根文件系统(刚刚下载的,给开发板用的根文件系统)
# 相当于把主机的根文件系统改成了开发板用的根文件系统
# 把下面的脚本保存下来并添加运行权限
# 挂载脚本 例如保存为 mount.sh
#!/bin/bash
sudo mount -t proc /proc /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/proc
sudo mount -t sysfs /sys /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/sys
sudo mount -o bind /dev /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/dev
sudo mount -o bind /dev/pts /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/dev/pts
sudo chroot /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604

# 卸载脚本 unmount.sh
#!/bin/bash
sudo umount /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/proc
sudo umount /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/sys
sudo umount /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/dev/pts
sudo umount /home/frank/linuxMini/nfs_dir/rootfs_ubuntu_1604/dev

# 挂载
# 执行脚本之后发现shell变了
./mount.sh

# 安装软件
apt update
apt install sudo
apt install vim
apt install net-tools
apt install ethtool
apt install net-tools
apt install openssh-server openssh-client

# 设置root密码
passwd root

# 设置 hostname
echo "ubuntu1604" > /etc/hostname
echo "127.0.0.1 localhost" >> /etc/hosts

# 配置串口终端
ln -s /lib/systemd/system/getty@.service /etc/systemd/system/getty.target.wants/getty@ttymxc0.service

# 配置ssh
# 注意一下这些配置项
PermitRootLogin yes
PubkeyAuthentication yes


# 退出
exit
./unmount.sh

# 至此 ubuntu 配置完成 在uboot内更改启动参数就可以对这个根文件系统进行测试了

使用NFS挂载rootfs

uboot 里面的 bootargs 环境变量会设置“root”的值,所以我们将 root 的值改为 NFS 挂载即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>

<server-ip>:服务器 IP 地址,也就是存放根文件系统主机的 IP 地址,那就是 Ubuntu 的 IP地址,比如我的 Ubuntu 主机 IP 地址为 192.168.1.250。
<root-dir>:根文件系统的存放路径,比如我的就是/home/zuozhongkai/linux/nfs/rootfs。
<nfs-options>:NFS 的其他可选选项,一般不设置。
<client-ip>:客户端 IP 地址,也就是我们开发板的 IP 地址,Linux 内核启动以后就会使用此 IP 地址来配置开发板。此地址一定要和 Ubuntu 主机在同一个网段内,并且没有被其他的设备使用,在 Ubuntu 中使用 ping 命令 ping 一下就知道要设置的 IP 地址有没有被使用,如果不能ping 通就说明没有被使用,那么就可以设置为开发板的 IP 地址,比如我就可以设置为192.168.1.251。
<server-ip>:服务器 IP 地址,前面已经说了。
<gw-ip>:网关地址,我的就是 192.168.1.1。
<netmask>:子网掩码,我的就是 255.255.255.0。
<hostname>:客户机的名字,一般不设置,此值可以空着。
<device>:设备名,也就是网卡名,一般是 eth0,eth1….,正点原子的 I.MX6U-ALPHA 开发板的 ENET2 为 eth0,ENET1 为 eth1。如果你的电脑只有一个网卡,那么基本只能是 eth0。这里我们使用 ENET2,所以网卡名就是 eth0。
<autoconf>:自动配置,一般不使用,所以设置为 off。
<dns0-ip>:DNS0 服务器 IP 地址,不使用。
<dns1-ip>:DNS1 服务器 IP 地址,不使用。

根据上面的格式 bootargs 环境变量的值如下:

“proto=tcp”表示使用 TCP 协议,“rw”表示 nfs 挂载的根文件系统为可读可写。

1
2
3
4
5
6
7
8
9
10
11
# 设定 bootargs 环境变量
# linux kernel是通过读取这个环境变量来启动rootfs的
=> env set bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.192.100:/home/frank/linuxMini/nfs_dir/rootfs,proto=tcp rw ip=192.168.192.80:192.168.192.100:192.168.192.100:255.255.255.0::eth0:off'


=> env print boot2nfs
boot2nfs=nfs 80800000 192.168.192.100:/home/frank/linuxMini/nfs_dir/zImage; nfs 83000000 192.168.192.100:/home/frank/linuxMini/nfs_dir/imx6ull-14x14-emmc-4.3-480x272-c.dtb; bootz 80800000 - 83000000


# 运行boot2nfs
=> run boot2nfs

Drive 驱动

Linux 中的驱动分为三大类:字符设备驱动、块设备驱动、网络设备驱动

一个设备有可能属于多种设备类型,比如 USB WIFI,其使用 USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。

字符设备驱动

是占用篇幅最大的一类驱动,因为字符设备最多,从最简单的点灯到 I2C、SPI、音频等都属于字符设备驱动的类型。

块设备驱动

所谓的块设备驱动就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备

网络设备驱动

网络设备驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴

字符设备驱动

驱动调用流程

字符设备的函数调用

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,驱动运行于内核空间。

当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。

open 函数调用流程:

open 函数调用流程

驱动API函数

每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};

简单介绍一下 file_operation 结构体中比较重要的、常用的函数:
owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。
llseek 函数用于修改文件当前的读写位置。
read 函数用于读取设备文件。
write 函数用于向设备文件写入(发送)数据。
poll 函数是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
open 函数用于打开设备文件。
release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。

这些函数并不是全部都需要实现的,可以只实现需要的部分函数。

驱动的加载卸载

驱动的加载和卸载

Linux 驱动有两种运行方式

  • 第一种是将驱动编译进 Linux 内核中

    • 当 Linux 内核启动的时候就会自动运行驱动程序
  • 第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko)

    • 在Linux 内核启动以后使“insmod”命令加载驱动模块。

    • 在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。

      而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。

将驱动编译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进 Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。

模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

1
2
module_init(xxx_init);   //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

module_init() 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用insmod命令加载驱动的时候,xxx_init 这个函数就会被调用。

module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用rmmod命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

驱动加载函数和卸载函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

驱动编译完成后,编译出来的文件名为.ko我们可以通过命令去将这个文件加载、卸载。

驱动加载命令

驱动的加载有两个命令:

  • insmod

    •      insmod drv.ko
       
      1
      2
      3
      4
      5
      6
      7

      - insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。

      - modprobe

      - ```shell
      modprobe drv.ko
    • modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中。

    • modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能

驱动的卸载

对应的驱动的卸载有两个命令:

  • rmmod

    •     rmmod drv.ko
      
      1
      2
      3
      4
      5
      6
      7

      - 对应的 rmmod 也是卸载单一的驱动。

      - modprobe -r

      - ```shell
      modprobe -r drv.ko
    • 使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块。

    • 需要注意的是这些被依赖的模块需要确保已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。

驱动(字符设备)的注册和注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。

驱动注册函数和注销函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 原型
// major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,这里是主设备号
// name:设备名字,指向一串字符串
// fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

// major:要注销的设备对应的主设备号
// name:要注销的设备对应的设备名
static inline void unregister_chrdev(unsigned int major, const char *name);

// 定义 file_operations 结构的变量。这个就是操作函数集合。定义在linux内核的include/linux/fs.h中
static struct file_operations test_fops;
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
int retvalue = 0;
/* 注册字符设备驱动 */
// 注册设备号为 200 名为 "chrtest"
retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0) {
/* 字符设备注册失败,自行处理 */
}
return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(200, "chrtest");
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

设备号的动态分配

1
2
3
4
5
6
7
8
9
10
11
// 申请设备号
// dev:保存申请到的设备号
// baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
// count:要申请的设备号数量(次设备号的差别)
// name:设备名字。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

// 释放掉设备
// from:要释放的设备号
// count:表示从 from 开始,要释放的设备号数量
void unregister_chrdev_region(dev_t from, unsigned count)

驱动文件的整体实现

参考代码仓库

驱动测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 输入如下命令加载 chrdev_demo.ko 驱动文件:
insmod chrdev_demo.ko

# 如果使用 modprobe 加载驱动
modprobe chrdev_demo.ko

# 查看当前系统中存在的模块
lsmod
# 查看系统中的设备 可以看到 chrdev_demo.ko 的主设备号和设备名
cat /proc/devices


# 创建设备节点文件
# 驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。
# 其中 mknod 是创建节点命令,/dev/chrdev_demo 是要创建的节点文件,c 表示这是个字符设备,237 是设备的主设备号,0 是设备的次设备号
mknod /dev/chrdev_demo c 237 0

# 卸载驱动模块
rmmod chrdev_demo.ko

Q&A

什么是设备号?

设备号是Linux系统用来管理设备的一种方式,它由**主设备号(Major Device Number)次设备号(Minor Device Number)**组成。

主设备号用来区分不同种类的设备,如字符设备块设备网络设备等,它用来表示一个特定的驱动程序。

次设备号用来区分同一类型的多个设备,如不同的硬盘、串口、网卡等,它用来表示使用该驱动程序的特定设备。

以磁盘为例:在同一个系统中,磁盘设备的主设备号是唯一的。比如:scsi设备。次设备号只是在提供给scsi驱动程序内部使用,系统内核直接把次设备号传递给应用程序,scsi设备由驱动程序管理,我们可能有多个scsi设备,每个scsi设备都会分配一个次设备号。

注:

主设备对应一个特定的驱动程序,所以一个系统里面所有的scsi硬盘使用的都是scsi驱动,所以他们的主设备号是一致的。

而不同的硬盘通过次设备号来做区分,保证它们的唯一性。

主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。

设备号可以用一个32位的无符号整数表示,其中高12位是主设备号,低20位是次设备号。

设备号可以通过/dev目录下的设备文件来访问,也可以通过/proc/devices文件来查看当前配置的设备号。/proc/devices类似一个类别,而/dev/下是某个类别下的项,由他们两个最终确定了一对多关系。

1
2
3
4
5
6
7
8
9
# 查看主设备号
cat /proc/devices

# 查看次设备号
ll /dev/
# c 代表 char 设备 89 主设备 0 次设备
crw------- 1 root root 89, 0 Nov 7 08:23 i2c-0
# b 代表 block 设备 179 主设备 1 次设备
brw-rw---- 1 root disk 179, 1 Nov 7 08:23 mmcblk1p1

设备号的分配和使用有一定的规则和约定,以保证设备的唯一性和通用性。

  • 静态设备号:有一些常用的设备号已经被 Linux 内核开发者给分配掉了,具体分配的内容可以查看文档 Documentation/devices.txt。
  • 动态设备号:Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。

常见设备名称:

fb::frame缓冲
fd:—–:软驱
hd:—–:IDE 硬盘/光驱
md:—–:RAID设备(Metadisk)
dm:—–:LVM设备(DeviceMapper)
xd:—–:虚拟机中的硬盘驱动器
tty:—–:终端设备
psaux:—–:PS/2 鼠标设备
lp:—–:打印机
par:—–:并口
pt:—–:伪终端

s:—–:SCSI设备
scd:—–:SCSI音频光驱
sd:—–:SCSI硬盘
sg:—–:SCSI通用设备
sr:—–:SCSI数据光驱
st:—–:SCSI磁带
cdrom:—–:光驱的符号链接
mouse:—–:鼠标设备的符号链接
gpmdata:—–:伪设备
null:—–:写入消失
zero:—–:一直产生零

参考:

linux设备管理之主设备号与次设备号 - jinzi - 博客园 (cnblogs.com)

外设

LCD

VSYNC 垂直同步脉冲(Vertical synchronization, Vsync)是加在两帧之间。跟水平同步脉冲类似,但它指示着前一帧的结束,和新一帧的开始。 垂直同步脉冲是一个持续时间比较长的脉冲。

HSYNC 水平同步脉冲(Horizontal synchronization pulse, Hsync)加在两个扫描行之间。它是一个短小的脉冲,在一行扫描完成之后,它就会出现,指示着这一行扫描完成,同时它也指示着下一行将要开始。

LCD屏幕的数据总线接口类型

MCU-LCD和RGB-LCD的主要差别

  1. RGB-LCD的显存(GRAM)由系统RAM充当。而MCU-LCD的显存由驱动IC决定。
  2. 显存的不一致直接导致了他们更新速率上的差异,MCU-LCD需要发送画点指令来修改驱动IC的显存,而RGB-LCD直接操作系统RAM,由DMA搬运到驱动IC。

串口屏 UART 接口

SPI 接口

MCU MPU接口

LCD液晶屏的MCU接口主要针对单片机的领域。

MCU接口的标准是因特尔提出的8080总线标准,因此在很多文档中用I80来指MCU接口屏。

MCU接口模式分为I(intel) 8080模式M(Motorola) 6800模式,主要是时序的区别。数据位传输有8位,16位,18位,24位。

连线分为:CS/RS/RD/WR/。

优点是控制简单方便,无需时钟和同步信号。

缺点是要耗费GRAM,所以难以做到大屏,一般都用在4寸以下。

i80

  • CS 片选信号
  • RS (D/I 数据/指令选择线, 置1为写数据, 置0为写命令)
  • /WR (为0表示写数据)
  • /RD (为0表示读数据)
  • RESET 复位LCD(用固定命令系列 0 1 0来复位)

m6800

  • CS 片选信号
  • RS (D/I 数据/指令选择线, 置1为写数据, 置0为写命令)
  • /WR (读写信号)
  • E (锁存信号)
  • RESET 复位LCD(用固定命令系列来复位)

m6800其实际设计思想是与 I80 的思想是一样的,主要区别就是该模式的总线控制读写信号组合在一个引脚上(/WR),而增加了一个锁存信号(E)

img

RGB接口

LCD液晶屏的RGB接口通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及三者组合来得到丰富多彩的颜色。

RGB-LCD的显存“GRAM”是由系统内存充当的,因此其大小只受限于系统内存的大小,这样RGB-LCD可以做出较大尺寸

所以LCD液晶屏的RGB接口就是分三原色输入的视频接口 。

通常一个颜色通道由8bit表示, 即每个颜色通道值得范围是0~255, 通常称RGB888/RGB24。三个颜色通道总共能组合出约1678(256×256×256)万种色彩, 简称为1600万色或千万色, 也称为24位色。

在实际的使用中, 除了RGB888/RGB24, 还有**RGB555、RGB565、RGB32、RGB666、RGB16、RGB24、RGB32、ARGB32(A就是alpha,透明度通道)**等等。

img

LVDS接口

LCD液晶屏的LVDS接口即Low Voltage Differential Signaling,是一种低压差分信号技术接口。

克服以TTL电平方式传输宽带高码率数据时功耗大、EMI电磁干扰大等缺点而研制的一种数字视频信号传输方式。

LVDS输出接口利用非常低的电压摆幅(约350mV)在两条PCB走线或一对平衡电缆上通过差分进行数据的传输,即低压差分信号传输。

采用LVDS输出接口,可以使得信号在差分PCB线或平衡电缆上以几百Mbit/s的速率传输,由于采用低压和低电流驱动方式,因此,实现了低噪声和低功耗。

MIPI接口

LCD液晶屏的MIPI接口是Mobile Industry Processor Interface的缩写。

MIPI(移动行业处理器接口)是MIPI联盟发起的为移动应用处理器制定的开放标准。

MIPI是一个比较新的标准,其规范也在不断修改和改进,目前比较成熟的接口应用有DSI(显示接口)和CSI(摄像头接口)。CSI/DSI分别是指其承载的是针对Camera或Display应用,都有复杂的协议结构。

RGB 驱动原理

主要信号线:

信号线描述
R[7:0]8 根红色数据线
G[7:0]
B[7:0]
DE数据使能线 Data Enable
VSYNC垂直同步信号线 Vertical synchronization, Vsync
HSYNC水平同步信号线 Horizontal synchronization pulse, Hsync
PCLK像素时钟信号线
RESET

同步信号示意图:

image-20230821122002797

Timing 示意图(摘录于GC9503V):

在很多情况下PCLK时钟是不停歇 持续产生的。

The Pixel clock (PCLK) is running all the time without stopping, it is used for entering VS, HS, DE and DB [23:0] states when there is a rising edge of the PCLK.The PCLK can not be used as the internal clock for other functions of the display module.

image-20230818180021537image-20230818180129919

HSYNCHorizontal synchronization pulse, Hsync水平同步信号线
HLW/HSPW/THPHorizontal Low Pulse widthHSYNC 信号宽度,也就是 HSYNC 信号持续时间。
HBP/THBHorizontal Back Porch行同步信号后肩
HACT/HOZVAL/THDHorizontal显示一行数据所需的时间
HFP/THFHorizontal Front Porch行同步信号前肩
VSYNCVertical synchronization, Vsync垂直同步信号线
VLW/VSPW/TVPVertical Low Pulse width
VBP/TVBVertical Back Porch帧同步信号后肩
VACT/LINEVertical显示一帧有效数据所需的时间
VFP/TVFVertical Front Porch帧同步信号前肩
DCLKData Clock像素时钟

显示一行所需要的时间就是:

HLW + HBP + HACT + HFP

显示一帧所需要的时间就是:

(HLW + HBP+ HACT + HFP)*(VLW + VBP + VACT + VFP)

DCLK时钟的计算:

根据上面的公式计算出一帧数据的时钟数假定未Vclock,帧数为Vfps。

DCLK = Vclock * Vfps

例如一帧需要853440个CLOCK,帧数设定为60,那么PCLK时钟就要设置成为853440*60=51206400=51.2064M

最小显存的计算:

1
2
3
4
# 853*480 分辨率   RGB888 模式
853*480*3 = 1,228,320 Byte = 1.1714 MByte
# 1024*600 分辨率 ARGB8888 模式
1024*600*4 = 2457600Byte = 2.4 MByte

LinuxAlphaMini
https://www.oikiou.top/2021/9b4105ad/
作者
Oikiou
发布于
2021年7月7日
许可协议