ARM-linux驱动开发学习笔记整理(PART-1)
前言
前景提要:曾经做过一个迷你Linux小电脑,自己画了个四层板,有gpio、屏幕、USB等外设,并且在Ubuntu系统下编译固件,驱动屏幕等外设,最终也算是写了一个小型QT程序,读取图片并且联网通过访问API的方式识别图片中的内容。但是从始至终,一直游荡于论坛,没有潜下心来学习学习linux驱动开发与应用开发,今天开始通过这个笔记记录自己学习的过程,实际上也就是学习《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6》的笔记
1、Ubuntu系统安装
这部分对于我来说很简单,主要涉及到虚拟机的安装与linux的基本操作,切换中文,换源,设定共享文件夹等,还是比较简单的,不跟着书一步一步走了
2、Ubuntu系统入门
这部分主要是Ubuntu系统的简单使用或者说是日常操作
终端操作
主要包含常见的指令:ls、cd、pwd、uname、clear、sudo、adduser、deluser、su、cat、ifconfig、man、reboot、poweroff、install
APT下载工具
最常用的指令:apt-get update
、apt-get check
、apt-get install xxx
、apt-get upgrade xxx
、apr-get remove xxx
文本编辑器
图形化的文本编辑器:gedit(简单易用)
终端文本编辑器:vi/vim(复杂不适合新手)
现在还记得曾经在树莓派上使用vi编辑器时懵逼的场景,曾经也系统的学了学,也做了一些纸质笔记,因此多余的操作不多赘述
文件系统
曾经也写过一篇博客专门讲述linux的文件系统,具体地说应该是讲的linux的目录结构,http://www.shumei52.top/index.php/archives/26.html,不在此做过多的阐述
文件操作指令
touch、mkdir、rm、rmdir、cp、mv、zip、unzip、tar、find、grep
权限管理
- 通过ls -l可以查看到文件权限与用户和用户组的关系
- chmod可以修改或者修改文件夹的权限,例如:chmod 777 test
chown用来修改某个文件或者某个目录的归属者
linux磁盘管理
windows中遇到分区的概念,但是这个概念在Linux中成为挂载点,挂载点就是将硬盘的一部分看作是文件夹的形式,这个文件夹的名字就叫做挂载点,通常来说,/dev/sda1是装ubuntu的硬盘,通常来说,U盘插入是/dev/sdb,SD插入是/dev/sdc。如果U盘有两个分区,就是/dev/sdb1、/dev/sdb2.
- 磁盘分区命令:fdisk
- 格式化命令:mkfs
- 挂载分区命令:mount
- 卸载命令:umount
3、Linux C 编程入门
gcc使用方法
gcc以前学习过,通常只用来编译只有一个或者几个源文件的小工程
makefile基础
makefile以前学习过,通常用来编译中等工程,笔记主要记录了以下几个知识点:
- 赋值符号:"="、":="、"?="、"+="
- 模式规则:"%.c"表示所有,"a.%.c "就表示以 a.开头,以.c 结束的所有文件
- 自动化变量:自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中。
- 伪目标:比如正常下make clean用来清除编译的中间文件,但是如果项目中存在clean文件夹,可以用
.PHONY : clean
声明一下,确保正常运行 - 条件判断:在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支, Makefile也支持,使用ifeq指令
函数使用:
- 字符串替换:subst
- 模式字符串替换:patsubst
- 获取目录:dir
- 去除文件中目录部分:notdir
- 完成循环:foreach
- 通配符:wildcard
入门结束
裸机开发
开发环境搭建
windows与ubuntu文件互传
使用ftp或者SSH实现文件互传,玩树莓派时都接触过,在此省略
安装交叉编译工具
使用linaro出品的交叉编译工具,这个在v3s时接触过,选择时不一定非要选择最新的版本,使用最常用的版本就好,并且编译器版本号通常与自己的芯片内核相对应,下载完成后解压在本地,并且对路径添加全局变量,这样在任何一个地方都可以调用该交叉编译器。
安装其他环境
编码使用VS code,串口使用ch340驱动,终端使用secureCRT或者是xshell、putty
I.MX6U-ALPHA开发平台介绍
看手册就行,笔记记这个干啥,总结而言,外设丰富,设计合理!
Cortex-A7 MPcore架构
这文中提到了STM32的特权模式和非特权模式,没听说过这个名字,专门查了一下:
特权模式(Privileged Mode)是处理器的一种特殊运行状态,也称为系统状态。在这种状态下,处理器具有完全的访问权限,可以访问系统的所有资源和寄存器,包括CPU的所有指令和操作,而且还可以使用一些特权指令,如访问保护控制寄存器等。在特权模式下,单片机可以执行任何操作,包括对外设的配置、操作系统的调度、中断的处理等。
非特权模式(Unprivileged Mode)是指处理器的另一种运行状态,也称为用户态。在这种状态下,处理器的访问权限受到限制,不能访问所有的系统资源和寄存器,包括一些特权指令。在非特权模式下,单片机只能执行一些受限制的操作,不能进行对外设的底层操作或访问一些特权寄存器等。
通常,非特权模式被用于运行应用程序,而特权模式则被用于运行操作系统内核或设备驱动程序等需要操作系统级别权限的程序。
在STM32单片机中,特权模式和非特权模式可以通过设置控制寄存器来进行切换,以实现不同的功能。
- Cortex-A7相比于STM32这种普通的arm,具有九种运行模式。
- Cortex-A具有16个32位的寄存器(R0~R15),前15个是通用的数据存储,R15是程序计数器PC,用来保存将要执行的指令。
- ARM 还提供了一个当前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是CPSR寄存器的备份
前面提到的九种运行模式,每一种运行模式都有一组与之对应的寄存器组,总结一下:CortexA 内核寄存器组成如下:
- 34 个通用寄存器,包括 R15 程序计数器(PC),这些寄存器都是 32 位的
- 8 个状态寄存器,包括 CPSR 和 SPSR。
- Hyp 模式下独有一个 ELR_Hyp 寄存器。
ARM汇编基础
GNU汇编适用于所有的架构,每行一条语句,每条语句有三个可选部分:
label: instruction @ comment
- lable:即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的":",任何以":"结尾的标识符都会被识别为一个标号。
- instruction 即指令,也就是汇编指令或伪指令,@符号,表示后面的是注释
- comment 就是注释内容
数据传输指令:
- MOV R0 R1 将 R1 里面的数据复制到 R0 中。MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面
- MRS R0 CPSR 将特殊寄存器 CPSR 里面的数据复制到 R0 中。MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令
- MSR CPSR R1 将 R1 里面的数据复制到特殊寄存器 CPSR 里中。MSR 指令和 MRS 刚好相反, MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用 MSR
存储器访问指令:
- LDR Rd, [Rn , #offset] 从存储器 Rn+offset 的位置读取数据存放到 Rd 中。LDR 主要用于从存储加载数据到寄存器 Rx 中,LDR 也可以将一个立即数加载到寄存器 Rx中, LDR 加载立即数的时候要使用“=”,而不是“#”
- STR Rd, [Rn, #offset] 将 Rd 中的数据写入到存储器中的 Rn+offset 位置。LDR 是从存储器读取数据, STR 就是将数据写入到存储器中
压栈和出栈指令:
- PUSH "
"将寄存器列表存入栈中。 - POP "
"从栈中恢复寄存器列表。
跳转指令:
- 直接使用跳转指令 B、 BL、 BX 等。
- 直接向 PC 寄存器里面写入数据。
算数运算指令:
加法运算,指令为 ADD:
- ADD Rd, Rn, Rm Rd = Rn + Rm
- ADD Rd, Rn, #immed Rd = Rn + #immed
带进位的加法运算,指令为 ADC
- ADC Rd, Rn, Rm Rd = Rn + Rm + 进位
- ADC Rd, Rn, #immed Rd = Rn + #immed +进位
减法
- SUB Rd, Rn, Rm Rd = Rn – Rm
- SUB Rd, #immed Rd = Rd - #immed
- SUB Rd, Rn, #immed Rd = Rn - #immed
带借位的减法
- SBC Rd, Rn, #immed Rd = Rn - #immed – 借位
- SBC Rd, Rn ,Rm Rd = Rn – Rm – 借位
乘法(32 位)
- MUL Rd, Rn, Rm Rd = Rn * Rm
无符号除法
- UDIV Rd, Rn, Rm Rd = Rn / Rm
有符号除法
- SDIV Rd, Rn, Rm Rd = Rn / Rm
逻辑运算指令:
按位与
- AND Rd, Rn Rd = Rd &Rn
- AND Rd, Rn, #immed Rd = Rn immed
- AND Rd, Rn, Rm Rd = Rn & Rm
按位或
- ORR Rd, Rn Rd = Rd | Rn
- ORR Rd, Rn, #immed Rd = Rn | #immed
- ORR Rd, Rn, Rm Rd = Rn | Rm
位清除
- BIC Rd, Rn Rd = Rd & (~Rn)
- BIC Rd, Rn, #immed Rd = Rn & (~#immed)
- BIC Rd, Rn , Rm Rd = Rn & (~Rm)
按位或非
- ORN Rd, Rn, #immed Rd = Rn | (#immed)
- ORN Rd, Rn, Rm Rd = Rn | (Rm)
按位异或
- EOR Rd, Rn Rd = Rd ^ Rn
- EOR Rd, Rn, #immed Rd = Rn ^ #immed
- EOR Rd, Rn, Rm Rd = Rn ^ Rm
汇编LED灯实验
- 这张主要讲了些这款芯片的GPIO类型,配置过程,其实与STM32大差不差,点灯过程都是:使能时钟、设置IO功能、配置IO、控制输出电平
实际开发过程中使用汇编开发的概率少之又少,不做过多停留 - 但是值得注意的是,与STM32不同的是,STM32固件起始地址总是0X08000000,在链接的时候IDE都帮我们做好了,但是linux开发板运行地址可能位于SD卡、EMMC、或者是NAND。因此在用gcc链接时,需要选择固定的地址。在本实验中链接地址选择在0X87800000,这是因为Uboot的链接地址就位于0X87800000,后期同意使用0X87800000这个链接地址
编译完成生成的是elf地址,还需要将其转换成bin文件,然后使用imxdownload将bin文件烧录到SD卡中
I.MX6U启动方式详解
BOOT可以通过两个外部引脚控制:
- 00,从FUSE启动
- 01,串行下载,可以通过NXP专用工具去下载固件
- 10,内部BOOT模式,从外部设备启动
- 11,保留
- 一部分LCD数据引脚被用来作为启动引脚
镜像烧写:上一章中提到imxdownload将bin文件烧录到SD卡中,实际上imxdownload先将IVT+Boot data+DCD+*.bin转换成.imx文件,再将imx文件烧录进去,page329详细的解释了imx文件的组成,这里不多赘述
C语言版本LED实验
- STM32的汇编在上电时的作用,设置栈大小->初始化SP指针->设置堆大小->复位中断服务函数->调用SystemInit()完成其他初始化工作->调用__main(),继而调用main()
- I.MX6U启动过程与STM32类似,不考虑中断向量表,只考虑初始化C环境:定义_start函数->设置处理器进入SVC模式,就是前面说的九种运行模式->设置SP指针->跳转到mian函数
C语言开发,I.MX6U的点灯也类似于STM32的寄存器开发
linux裸机开发
个人认为,linux的裸机开发在实际项目中是几乎没有意义的,如果用裸机开发为什么不用ARM,因此后续的裸机开发部分使用很少的篇幅带过
- 正常情况下,不是所有的厂商都会给Cortex-A架构的芯片编写裸机SDK包,但是NXP给I.MX6ULL编写了裸机SDK包,在NXP看来,这款芯片就是个Cotex-A的单片机
STM32很少涉及到外部RAM,因此简单记录一下linux裸机开发中的DDR3实验:
- SRAM通常被用来扩展RAM,SRAM全称是Static Random-Access Memory,也就是静态随机存储器,只要SRAM上电,数据就会一直保存,直到SRAM掉电。SRAM最大的缺点就是成本高。但是好处在于无需刷新(SDRAM需要刷新)
- SRAM的价格高容量小,又出现了一种内存名为SDRAM,全称是 Synchronous Dynamic Random Access Memory,翻译过来就是同步动态随机存储器,“同步”的意思是 SDRAM 工作需要时钟线,“动态”的意思是 SDRAM 中的数据需要不断的刷新来保证数据不会丢失,“随机”的意思就是可以读写任意地址的数据。
- DDR属于是SDRAM的升级版本,DDR 全称是 Double Data Rate SDRAM,也就是双倍速率SDRAM,DDR根据进化又分为DDR->DDR2->DDR3->DDR4->DDR5,速度是越来越快,正点原子的I.MX6U-ALPHA开发板上接了一个256MB/512MB的DDR3L
系统移植篇
U-Boot使用实验
- U-Boot会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核
U-Boot大致上有三种种类:
- uboot官方的uboot代码:由 uboot官方维护开发的uboot 版本,版本更新快,基本包含所有常用的芯片
- 半导体厂商的uboot代码:半导体厂商维护的一个uboot,专门针对自家的芯片,在对自家芯片支持上要比uboot官方的好
- 开发板厂商的uboot代码:开发板厂商在半导体厂商提供的uboot基础上加入了对自家开发板的支持
- Uboot编译生成的bin本质上也是个裸机程序,因此需要将其转换为imx文件
- Uboot输出命令包含哪些内容:时间信息、CPU频率、复位信息、外设就绪状态、内存状态、显示屏状态、输入终端状态、启动路径、网口信息等
boot命令行:
?
/help
帮助指令,单独输入查看所有指令,查看某个模块的帮助方法:? bootz 或 help bootz
- 常用的和信息查询有关的命令有 3 个:bdinfo、printenv 和 version
- 修改环境变量:
setenv bootdelay 5
、saveenv
- 新建环境变量:
setenv author shumei
、saveenv
- 删除环境变量:给环境变量赋空值
setenv author
、saveenv
内存操作:
- md命令:
md[.b, .w, .l] address [# of objects]
命令中的[.b .w .l]对应 byte、word 和long,也就是分别以1个字节、2个字节、4个字节来显示内存值。address就是要查看的内存起始地址,[# of objects]表示要查看的数据长度,为hex - nm命令:nm 命令用于修改指定地址的内存值,格式
nm [.b, .w, .l] address
- mm指令:mm 命令也是修改指定地址内存值的,使用mm修改内存值的时候地址会自增,而使用命令nm的话地址不会自增
- mw命令:命令mw用于使用一个指定的数据填充一段内存,格式
mw [.b, .w, .l] address value [count]
- cp命令:cp 是数据拷贝命令,用于将DRAM中的数据从一段内存拷贝到另一段内存中,或者把Nor Flash中的数据拷贝到DRAM中,格式为
cp [.b, .w, .l] source target count
- cmp命令:cmp是比较命令,用于比较两段内存的数据是否相等,格式如
cmp [.b, .w, .l] addr1 addr2 count
- md命令:
网络操作命令
- 涉及到的环境变量:
环境变量 描述 ipaddr 开发板 ip 地址,可以不设置,使用 dhcp 命令来从路由器获取 IP 地址 ethaddr 开发板的 MAC 地址,一定要设置 gatewayip 网关地址 netmask 子网掩码 serverip 服务器 IP 地址,也就是 Ubuntu 主机 IP 地址,用于调试代码 - ping命令:不用多说吧
- dhcp命令:dhcp 用于从路由器获取 IP 地址,前提得开发板连接到路由器上的
- nfs命令:nfs(Network File System)网络文件系统,通过nfs可以在计算机之间通过网络来分享资源,
nfs [loadAddress] [[hostIPaddr:]bootfilename]
,loadAddress 是要保存的 DRAM 地址,[[hostIPaddr:]bootfilename]
是要下载的文件地址。这里我们将正点原子官方编译出来的 Linux 镜像文件 zImage 下载到开发板 DRAM 的 0x80800000这个地址处 - tftp命令:tftp命令的作用和nfs命令一样,都是用于通过网络下载东西到DRAM中,
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]
EMMC和SD卡操作命令:
- 输入
? mmc
查看与mmc有关的命令:
命令 描述 mmc info 输出MMC设备信息 mmc read 读取 MMC 中的数据 mmc wirte 向 MMC 设备写入数据 mmc rescan 扫描 MMC 设备 mmc part 列出 MMC 设备的分区 mmc dev 切换 MMC 设备 mmc list 列出当前有效的所有 MMC 设备 mmc hwpartition 设置 MMC 设备的分区 mmc bootbus…… 设置指定 MMC 设备的 BOOT_BUS_WIDTH 域的值 mmc bootpart…… 设置指定 MMC 设备的 boot 和 RPMB 分区的大小 mmc partconf…… 设置指定 MMC 设备的 PARTITION_CONFG 域的值 mmc rst 复位 MMC 设备 mmc setdsr 设置 DSR 寄存器的值 - 输入
FAT 格式文件系统操作命令
- fatinfo 命令用于查询指定MMC设备分区的文件系统信息,
fatinfo <interface> [<dev[:part]>]
- fatls 命令用于查询FAT格式设备的目录和文件信息,
fatls <interface> [<dev[:part]>] [directory]
- fstype 用于查看MMC设备某个分区的文件系统格式,
fstype <interface> <dev>:<part>
- fatload 命令用于将指定的文件读取到DRAM中,
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
- fatwirte 命令用于将 DRAM 中的数据写入到 MMC 设备中,
fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>
- fatinfo 命令用于查询指定MMC设备分区的文件系统信息,
EXT 格式文件系统操作命令
- ext2load、ext2ls、ext4load、ext4ls 和 ext4write,与fat文件系统类似
NAND操作指令
- nand info命令
- nand device 命令
- nand erase 命令
- nand write 命令
- nand read 命令
BOOT 操作命令
- 引导命令 bootz:
bootz [addr [initrd[:size]] [fdt]]
,命令bootz有三个参数,addr是Linux镜像文件在DRAM中的位置,initrd是initrd文件在DRAM中的地址,如果不使用initrd的话使用‘-’代替即可,fdt就是设备树文件在 DRAM 中的地址 - bootm 命令:bootm 和 bootz 功能类似,但是 bootm 用于启动 uImage 镜像文件
- boot 命令:boot 会读取环境变量 bootcmd 来启动 Linux 系统,bootcmd 是一个很重要的环境变量,其名字分为“boot”和“cmd”,也就是“引导”和“命令”,说明这个环境变量保存着引导命令,其实就是启动的命令集合,具体的引导命令内容是可以修改的
- 引导命令 bootz:
uboot其他指令:
- reset、go、run、mtest
U-Boot顶层makefile
U-Boot目录分析
目录分析
名字 描述 备注 api 与硬件无关的 API 函数。 自带 arch 与架构体系有关的代码。 自带 board 不同板子(开发板)的定制代码。 自带 cmd 命令相关代码。 自带 common 通用代码。 自带 configs 配置文件 自带 disk 磁盘分区相关代码 自带 doc 文档 自带 drivers 驱动代码 自带 dts 设备树 自带 examples 示例代码 自带 fs 文件系统 自带 include 头文件 自带 lib 库文件 自带 Licenses 许可证相关 自带 net 网络相关代码 自带 post 上电自检程序 自带 scripts 脚本文件 自带 test 测试代码 自带 tools 工具文件夹 自带 .config 配置文件,重要的文件。 编译生成 config.MK 某个makefile会调用此文件 自带 .u-boot.xxx.cmd 一系列文件,用于保存一些命令 编译生成的文件 Kbuild 用于生成一些和汇编相关的文件 自带 Kconfig 图形界面配置的描述 自带 MAKEALL 一个shell脚本,帮助uboot编译 自带 Makefile 主makefile文件,非常重要 自带 u-boot 编译出来的uboot文件 编译出来的文件 u-boot.xxx 生成的一些 u-boot 相关文件,包括u-boot.bin、u-boot.imx.等 编译出来的文件 需要重点关注的文件夹
- arch文件夹:存放着和架构有关的东西
- board文件夹:具体与板子相关的
- config文件夹:芯片厂家或者开发板厂家制作的配置文件
- .u-boot.xxx_cmd文件:编译生成的文件
- Makefile文件:不必多说
- u-boot.*文件:编译出来的镜像、符号脚本、连接脚本等等
- .config文件:配置文件
顶层Makefile文件分析
- 版本号
MAKEFLAGS 变量:
- 调用子目录中的makefile:$(MAKE) -C subdir
- 导出/不导出子变量到底层makefile:export/unexport VARIABLE (默认所有变量传递到底层makefile)
- MAKEFLAGS += -rR --include-dir=$(CURDIR) 代码使用“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录
- make O=out,表示输出结果到out文件夹内
- make M=dir,表示单独编译某个模块
- 设置目标架构和交叉编译工具:不必多说
make xxx_defconfig过程:"make xxx_defconfig"配置uboot的时候如下两行命令会执行脚本scripts/Makefile.build,主要用来生成.config:
@make -f ./scripts/Makefile.build obj=scripts/basic @make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
make过程,主要用来生成bin:
U-Boot启动流程
- 启动入口为链接脚本:u-boot.lds,入口为_start
- 到__image_copy_start 为 0X87800000,text 的起始地址也是0X87800000
完整流程:_start->reset->save_boot_params->save_boot_params_ret->cpu_init_cp15、cpu_init_crit(lowlevel_init\s_init)->_main(board_init_f初始化外设、初始化GD成员变量、relocate_code代码拷贝、relocate_vectors向重定位向量表、board_init_r 完成board_init_f后续工作)->run_main_loop
bootz启动流程
- images是linux内核的灵魂,是定义于cmd/bootm.c的全局变量
- bootz命令的执行函数为do_bootz,do_bootz最后调用的是do_bootm_states
- do_bootm_states中,通过bootm_os_get_boot_func查找系统启动函数
- do_bootm_linux是最终的启动Linux内核的函数
U-Boot移植
本章省略,已经完成了在全志H3平台上的uboot编译与测试
比较重要的两个环境变量是bootargs与bootcmd,bootcmd的内容是从分区一寻找内核zimage与设备树并将其加载到指定位置,然后开始启动,bootargs设定了控制台参数和根文件系统的位置
以前在v3s平台上uboot上高了很多的实际测试,在这里不进行过多的记录了
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。