系列文章目录
文章目录
前言
计划先了解一下Linux到底是什么,同时跟着做一个项目吧,我想学习的到底是什么方面,该找哪个方向的工作呢?
内核理解
应用程序是通过系统调用来请求内核执行事情
裸机编程:没有操作系统支持的编程环境称为裸机编程环境,譬如单片机上的编程开发,编写直接在硬件上运行的程序,没有操作系统支持;
Linux 驱动编程:的是基于内核驱动框架开发驱动程序,驱动开发工程师通过调用 Linux 内核提供的接口完成设备驱动的注册。
Linux 应用编程(系统编程):在应用程序中通过调用系统调用 API 完成应用程序的功能和逻辑,应用程序运行于操作系统之上。
应用编程:开发 Linux 应用程序,通过调用内核提供的系统调用或使用 C 库函数来开发具有相应功能的应用程序。
文件I/O即:输入外部数据进入系统,输出系统数据发送到外部,交互的意思。
LED应用层实验
应用层通过对设备文件的 I/O 操作来操控硬件设备,设备文件通常在/dev/目录下,叫做设备节点
sysfs 是一个基于内存的文件系统,称为虚拟文件系统,产生一个包含所有系统硬件层次的视图。
sysfs 文件系统挂载在/sys 目录下,其实就是一个按照不同分类方法存放的目录
包括 block、bus、class、dev、devices、firmware、fs、kernel、modules、power 等
devices:所有设备存放的目录
bus:所有设备按照总线类型分类放置的目录
class:所有设备按照其功能分类放置的目录
dev:按照设备号的方式放置的目录
firmware:内核中的固件。
fs:系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点。
kernel :内核中所有可调参数的位置。
module :这里有系统中所有模块的信息。
power:系统中电源选项,有一些属性可以用于控制整个系统的电源状态。
应用层操作两种方法:
/dev/目录下的设备文件
/sys/目录下设备的属性文件
LED、GPIO 等简单设备会使用 sysfs 方式操控
LCD 、触摸屏、摄像头等较复杂的设备通常会使用设备节点
驱动开发的时候会按照Linux规范来设计,即设备驱动框架,这样就有了一套标准统一的接口,当然也有自己定制的驱动,叫杂散设备,这类驱动工程师要给调用API的说明。
led的操作方法:
brightness:亮度,该文件可读可写; 0 表示 LED 灭,正整数表示 LED 亮,其值越大、LED 越亮;对于 PWM 控制的 LED 来说,这通常是适用的;但对于 GPIO控制的 LED 来说,只有 LED 亮和 LED 灭两种状态。
max_brightness:该属性文件只能被读取,不能写,用于获取 LED 设备的最大亮度等级。cat命令查看
trigger:触发模式,该属性文件可读可写。不同的触发模式其触发条件不同,LED 设备会根据不同的触发条件自动控制其亮、灭
状态,== cat 命令查看该属性文件,可获取 LED 支持的所有触发模式以及 LED 当前被设置的触发模式。==
方括号([heartbeat])括起来的表示当前 LED 对应的触发模式
none 表示无触发、mmc0(当对 mmc0 设备发起读写操作的时候 LED 会闪烁)、timer(LED 会有规律的一亮一灭,被定时器控制住)、heartbeat(心跳呼吸模式,LED 模仿人的心跳呼吸那样亮灭变化)。
echo命令,将指定的文本或变量内容输出到标准输出,试了一下>就是赋值的意思,中间有没有空格都行
同理,直接把目录给出来操作
刚进系统是~#,代表了是 root 超级用户,默认登录后的目录
/#代表的是根目录,文件系统起点,系统级操作(如挂载分区、修改系统配置)
自己编写的C语言文件需要U盘拷贝进去,或者SCP方法
先说U盘
u盘 操作
之后需要创建文件,将U盘挂载在上面
cd /mnt
mkdir usb
sudo mount -t vfat /dev/sda1 /mnt/usb
sudo CP ledapp /mnt //拷贝文件
退出该目录后解挂
sudo umount /mnt/usb
后面可以执行文件了
./testApp on # 点亮 LED
./testApp off # 熄灭 LED
./testApp trigger heartbeat # 将 LED 触发模式设置为 heartbeat,但这个代码里面好像没有写具体对应的功能
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define LED_TRIGGER "/sys/class/leds/sys-led/trigger"
#define LED_BRIGHTNESS "/sys/class/leds/sys-led/brightness"
#define USAGE() fprintf(stderr, "usage:\n" \
" %s <on|off>\n" \
" %s <trigger> <type>\n", argv[0], argv[0])
int main(int argc, char *argv[])
{
int fd1, fd2;
/* 校验传参 */
if (2 > argc) {
USAGE();
exit(-1);
}
/* 打开文件 */
fd1 = open(LED_TRIGGER, O_RDWR);
if (0 > fd1) {
perror("open error");
exit(-1);
}
fd2 = open(LED_BRIGHTNESS, O_RDWR);
if (0 > fd2) {
perror("open error");
exit(-1);
}
/* 根据传参控制 LED */
if (!strcmp(argv[1], "on")) {
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "1", 1); //点亮 LED
}
else if (!strcmp(argv[1], "off")) {
write(fd1, "none", 4); //先将触发模式设置为 none
write(fd2, "0", 1); //LED 灭
}
else if (!strcmp(argv[1], "trigger")) {
if (3 != argc) {
USAGE();
exit(-1);
}
if (0 > write(fd1, argv[2], strlen(argv[2])))
perror("write error");
}
else
USAGE();
exit(0);
}
错误输入报错:调用的USAGE宏定义,写入到错误文件中。
系统移植和根文件系统构建
烧写系统
USB连接到电脑,使用USB_OTG接口,板子上是EMMMC,所以下载的时候选,直接双击,会使用mfgtool上位机固化
拨码开关要拨到对应的状态
uboot
核心作用是为操作系统(如Linux、Android等)提供硬件初始化、环境配置和加载内核的功能,初始化cpu,mmu、DDR、网口、设备树。之后引导加载内核,把权限交给Linux
uboot用于启动Linux或者其他系统,工作主要是初始化DDR,因为Linux工作在DDR里面,还有把镜像文件等从外置flash拷贝到DDR。不只uboot可以做到,还有其他的。
"Linux镜像"指的是编译生成的Linux内核二进制文件,通常包含操作系统核心功能、驱动程序以及启动加载程序等信息 。这个镜像文件会被烧录到嵌入式设备的存储介质中,如SD卡、eMMC或Flash等,用于引导启动嵌入式设备
存储介质存放的是:Linux镜像zImage(uImage)+设备树
uboot就是bootloader,支持多种系统,可以去官网获取,难度巨大
SOC厂商会下载某个版本的uboot,然后加入相应的SOC和驱动,这个就是厂商定制的uboot(对的他们的开发板)
自己做开发板就要参考SOC厂商的开发板,不要重复造轮子而且容易有坑。
uboot启动了流程,这里最后有个三秒倒计时,如果没有点任何键,就会启动uboot操作,启动Linux
这里可以看到有读取zImage、.dtb的设备树文件
uboot编译及下载
编译
交叉编译是指在一种平台上编译生成在另一种平台上运行的可执行代码的过程,装好ARM和poky交叉编译工具链
先试着编译正点原子的,首先要用交叉编译器,分别是清理工程、配置uboot,配置文件mx6ull_14x14_ddr512_emmc_defconfig,最后make-j12就是用12核来编译uboot
使用了 make 命令,用于清理工程,也就是每次在编译 uboot 之前都清理一下工程。这里的 make 命令带有三个参数,第一个是 ARCH,也就是指定架构,这里肯定是 arm;第二个参数 CROSS_COMPILE 用于指定编译器,只需要指明编译器前缀就行了,比如 arm-linux-gnueabihf-gcc 编译器的前缀就是“arm-linux-gnueabihf-”;最后一个参数 distclean 就是清除工程。
使用 make 命令,用于配置 uboot。同样有三个参数,不同的是,最后一个参数是 mx6ull_14x14_ddr512_emmc_defconfig。前面说了 uboot 是 bootloader 的一种,可以用来引导Linux,但是 uboot 除了引导 Linux 以外还可以引导其它的系统,而且 uboot 还支持其它的架构和外设,比如 USB、网络、SD 卡等。这些都是可以配置的,需要什么功能就使能什么功能。所以在编译 uboot 之前,一定要根据自己的需求配置 uboot。mx6ull_14x14_ddr512_emmc_defconfig就是正点原子针对 I.MX6U-ALPHA 的 EMMC 核心板编写的配置文件,这个配置文件在 uboot源码的 configs 目录中。在 uboot 中,通过“make xxx_defconfig”来配置 uboot,xxx_defconfig就是不同板子的配置文件,这些配置文件都在 uboot/configs 目录中。
用于编译 uboot,配置好 uboot 以后就可以直接“make”编译 uboot 了。其中 V=1 用于设置编译过程的信息输出级别;-j 用于设置主机使用多少线程编译uboot,最好设置成我们虚拟机所设置的核心数,如果在 VMware 里面给虚拟就分配了 4 个核,那么使用-j4 是最合适的,这样 4 个核都会一起编译。
使用的是 512MB+8GB 的 EMMC 核心板
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12
第二天代码执行后,命令ls -a,可以看到一个.config的命令
全部执行完后会有u-boot.bin文件,I.MX6ULL必须添加一个头部信息,最后会通过/tool/mkimage软件添加头部信息,生成u-boot.imx,这个工具是uboot源码里的,编译时会生成对应的工具,看最后命令自己会执行生成一个.imx文件
脚本编译
也可以新建一个shell脚本,里面写上这些命令
vi xxxxxx.sh //创建打开shell脚本
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12
第 1 行是 shell 脚本要求的,必须是“#!/bin/bash”或者“#!/bin/sh”。
使用 chmod 命令给予 mx6ull_alientek_emmc.sh 文件可执行权限,然后就可以使用这个 shell脚本文件来重新编译 uboot
chmod 777 mx6ull_alientek_emmc.sh //先给一次权限
./mx6ull_alientek_emmc.sh
如果用图形化界面配置了uboot,shell脚本会清除配置项,如果是修改uboot源码就不用担心
下载到开发板
把他拿出来直接,用OTG方式烧写快一点,mfgtool,直接在firmware里面替换掉emmc版本的imx文件就行
把下图两个文件都替换掉,名字也要换成一样的,用的他们的软件
这就是我们自己编译的u-boot,看时间就知道了,这里的命令输入“help“或者“?”就能看到了,具体命令先不看了
第 1 行是 uboot 版本号和编译时间,可以看出,当前的 uboot 版本号是 2016.03,编译时间是 2025 年
第 3 和第 4 行是 CPU 信息,可以看出当前使用的 CPU 是飞思卡尔的 I.MX6ULL(I.MX 以前属于飞思卡尔,然而飞思卡尔被 NXP 收购了),频率为 792MHz,但是此时运行在 396MHz。这颗芯片是工业级的,结温为-40°C~105°C。
第 5 行是复位原因,当前的复位原因是 POR。I.MX6ULL 芯片上有个 POR_B 引脚,将这个引脚拉低即可复位 I.MX6ULL。
第 6 行是板子名字,当前的板子名字为“I.MX6U ALPHA|MINI”。
第 7 行提示 I2C 准备就绪。
第 8 行提示当前板子的 DRAM(内存)为 512MB,如果是 NAND 版本的话内存为 256MB。
第 9 行提示当前有两个 MMC/SD 卡控制器:FSL_SDHC(0)和 FSL_SDHC(1)。I.MX6ULL支持两个 MMC/SD,正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡,FSL_SDHC(1)接的 EMMC。
第 12~14 是标准输入、标准输出和标准错误所使用的终端,这里都使用串口(serial)作为终端。
switch to partitions #0, OK,切换到 emmc 的第 0 个分区上,因为当前的 uboot 是 emmc 版本的,也就是从 emmc 启动的。
网口信息,提示我们当前使用的 FEC1 这个网口,I.MX6ULL 支持两个网口。
提示 FEC1 网卡地址没有设置,后面我们会讲解如何在 uboot 里面设置网卡地址。
提示正常启动,也就是说 uboot 要从 emmc 里面读取环境变量和参数信息启动 Linux内核了。
倒计时提示,默认倒计时 3 秒,倒计时结束之前按下回车键就会进入 Linux 命令行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动,Linux 内核一旦启动,uboot 就会寿终正寝。
uboot命令
printenv:查看当前板子的环境变量
暂时不看命令
网络电脑暂时没有网口,先不看
uboot源码分析
用正点原子的uboot进行分析,这是所有的文件
之前我们编译uboot时会生成一些文件,其中重点关注arch文件夹,architecture架构的简称。
arch文件夹/cpu文件夹
有很多架构,比如 arm、avr32、m68k 等,我们现在用的是 ARM 芯片,所以只需要关心 arm 文件夹
还有一部分 mach-xxx 的文件夹。mach 开头的文件夹是跟具体的设备有关的,比如“mach-exynos”就是跟三星的 exyons 系列 CPU 有关的文件。我们使用的是 I.MX6ULL,所以要关注“imx-common”这个文件夹。另外“cpu”这个文件夹也是和 cpu 架构有关的。dts是设备树(device tree source)
打开cpu文件夹,有多种 ARM 架构相关的文件夹,I.MX6ULL 使用的 Cortex-A7 内核,Cortex-A7 属于 armv7,所以我们要关心“armv7”这个文件夹。cpu 文件夹里面有个名为==“u-boot.lds”==的链接脚本文件,这个就是 ARM 芯片所使用的 u-boot 链接脚本文件!armv7 这个文件夹里面的文件都是跟 ARMV7 架构有关的,是我们分析 uboot 启动源码的时候需要重点关注的。(linker description script)
board文件夹
board 文件夹就是和具体的板子有关的,里面全是不同的板子,有非常多的板子,正点原子的开发板也在里面,有个名为“freescale”的文件夹关注
I.MX 系列以前属于 freescale,只是freescale 后来被 NXP 收购了。打开此 freescale 文件夹,在里面找到和 mx6u(I.MX6UL/ULL)有关的文件夹,有五种板子,以“mx6ul”开头的表示使用I.MX6UL 芯片的板子,以 mx6ull 开头的表示使用 I.MX6ULL 芯片的板子。mx6ullevk 是 NXP官方的I.MX6ULL 开发板,正点原子的ALPHA开发板就是在这个基础上开发的,因此 mx6ullevk也是正点原子的开发板。
config文件夹
uboot 配置文件,uboot 是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”,xxx 表示开发板名字。对应的是板子。
只关心 mx6ull_14x14_ddr512_emmc_defconfig 和 mx6ull_14x14_ddr256_nand_defconfig这两个文件,一个是EMMC的,一个是NAND的,使用make命令来配置,编译uboot前,一定要使用 defconfig 来配置 uboot
make mx6ull_14x14_ddr512_emmc_defconfig
前面我们的命令其实就是包含了调用用 mx6ull_14x14_ddr512_emmc_defconfig 来配置 uboot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
执行make命令后,就会在uboot目录下生成一个.config文件,里面有详细的uboot配置信息
u-boot.xxx_cmd文件
一系列的文件,这些文件都是编译生成的,都是一些命令文件
README
顶层目录下的README文件
内核移植
用官方的LINUX,添加开发板,添加设备树文件,图形化配置修改一些主频
根文件系统构建,根文件系统一般也叫做 rootfs
根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
2 BusyBox 构建根文件系统