Linux驱动开发

MX6U嵌入式Linux驱动开发
Linux文件系统类型:ext2、ext3、ext4等

查看磁盘类型:df -T -h
分区命令:fdisk name
格式化命令:mkfs -t 格式类型 name
挂载分区: mount -t 格式类型 设备名 目的文件夹
ARCH ?= $(SUBARCH) 选择的架构
CROSS_COMPILE ?= ( ) 对 应 的 编 译 器 m a k e f i l e 语 法 : 赋 值 = 取 决 于 他 所 引 用 变 量 的 最 后 一 次 有 效 值 赋 值 : = 只 能 使 用 前 面 已 经 定 义 好 的 值 ? = 前 面 如 果 没 有 被 赋 值 , 那 变 量 就 为 赋 值 的 数 据 , 反 之 则 使 用 前 面 的 数 值 + = 字 符 串 的 相 加 减 . P H O N Y : c l e a n 清 除 文 件 条 件 判 断 : i f e q 、 i f n e q 、 i f d e f 、 i f n d e f 函 数 调 用 : () 对应的编译器 makefile语法: 赋值= 取决于他所引用变量的最后一次有效值 赋值 := 只能使用前面已经定义好的值 ?= 前面如果没有被赋值,那变量就为赋值的数据,反之则使用前面的数值 += 字符串的相加减 % 表示长度任意的非空字符串 .PHONY:clean 清除文件 条件判断:ifeq、ifneq、ifdef、ifndef 函数调用: ()makefile=:=使?=使+=.PHONY:cleanifeqifneqifdefifndef(函数名 参数集合)
subst函数 字符串替换
dir函数 获取目录
notdir 提取文件名
foreach函数 循环
uboot 命令;bdinfo 查看板子信息
setenv和saveenv 设置环境变量的值和保存环境变量
内存操作命令:mm、mw、cp、cmp
mm:显示内存值
nm:修改指定地址的内存值
mw:使用一个指定的数据来填充一段内存
CP: 将一段内存中的数据拷贝到另外一段内存中
cmp: 判断连段内存的数据是否相等
rs232:常用的串行通信接口标准之一
cortex-A :主要模式:user(普通工作模式)、svc(特权模式)、IRQ(一般中断模式)。
r13(sp):堆栈指针
r14(lr): 链接寄存器(保存寄存器)
R15(PC): 程序计数器(返回寄存器)
CPSR: 当前程序状态寄存器
SPSR: 程序备份寄存器
u-boot
u-boot是一个裸机程序,是bootloader,作用是启动linux或其他操作系统
uboot主要工作就是初始化DDR,为系统的启动做准备
uboot编译最后会通过/tools/mkimage添加头部信息生成uboot.imx
**** uboot命令:
nm:修改一个地址
mm: 修改一段内存地址

mmc命令:
mmc dev 1 切换到sd卡或者emmc
mmc part 查看分区
mmc read 800800000 600 10 从第0x600个块开始读,读0x10个块到800800000地址,每个块大小是512
mmc write addr 600 10 将addr上的数据从第0x600个块开始写,写10个块,每个块大小是512
mmc erase 600 10 从第0x600块开始擦除,擦除0x10块
一般SD/EMMC 分为三个分区
第一个分区:存放UBOOT
第二个分区:存放系统镜像,.dtb文件,FAT格式
第三个分区:系统的根文件系统,EXT4格式

fatinfo mmc 0:1 查看sd卡下的第二个分区
fatls mmc 1:1 查看分区下的文件
fstype mmc 1:1 查看分区格式
⭐⭐⭐⭐⭐fatload mmc 1:1 80800000 zImage 将emmc第二分区的zImage读到内存的80800000地址加载,将指定的文件督导DRAM中

ext4命令

nand命令和boot命令
flash必须要擦除

启动方式:可以从tftp上下载到内存,也可以通过emmc中的fatload mmc 读取到内存中去

BOOTZ启动zImage bootm启动uImage boot运行bootcmd的环境变量

go addr跳到内存的addr地址去执行,执行裸机代码

run 运行自定义的命令

u-boot源码目录分析:

在这里插入图片描述

arch/arm/cpu/u-boot.lds整个Uboot的连接脚本

board\freescale\mx6ullevk

configs目录是Uboot的默认配置文件,此目录下都是以defconfig结尾的,这些配置对应不同的板子

kconfig 图形界面的配置文件

顶层readme文件很重要

system.map 内存映射表

uboot的启动流程分析

1、顶层Makefile分析

makefile可以通过export或unexport导出变量给子makefile使用

makefile支持递归调用 $(MAKE) -C 子目录

SHELL和MAKEFIAGS的值会始终自动传递给子makefile

在这里插入图片描述

make 默认为短输出

make V=1为详细输出 make -s为静默输出

make O=/XX 输出到指定目录

PHONY:makefile里面的默认目标

链接:uboot链接首地址为0x87800000

※※※※※※Uboot启动流程

通过链接文件 u-boot.lds找到入口地址,用u-boot.map映射文件查看对应的地址

入口地址:_start

代码段

数据段

image_copy_start ----> 0x87800000 整个代码段映射的起始地址

.vector —> 0x87800000 -->中断向量表

->中断向量表 ---->start.o------>

image_copy_end ----> 0x878xxxxxx 整个代码段映射的的结束地址

uboot镜像大小为image_copy_end-imaged_copy_start

rel段

启动流程:

root的启动入口程序位start.s文件

1、reset函数:

①、设置svc模式,关闭FIQ和IRQ(设置cpsr寄存器,将I和F位置1)

②、设置CP15寄存器组的C1寄存器SCTLR(系统控制寄存器)寄存器,禁止MMU和I/D cache、重定位异常向量表,在使用C12寄存器设置异常向量表的偏移地址

2、lowlevel_init函数:

①、设置sp堆栈指针和其对齐方式。

在这里插入图片描述

3、s_init函数

空函数

4、_main函数

①、清零BSS段,初始化结构体gd,来初始化 DDR,定时器,完成代码拷贝

4、board_init_f

①、初始化一系列外设,比如串口、定时器或者打印一些消息,

②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就 是将自己拷贝到 DRAM 最后面的内存区域中。防止 Linux kernel 覆盖掉 uboot。

开启DEBUG

我们开启Uboot界面上的信息就是这里面打印的

内存分配

reserve_mmu 预留mmu的TLB缓存表的大小

reserver_uboot 预留出重定位uboot镜像的大小,进行4K对齐

5、relocate_code 函数(代码重定位)

①、拷贝Uboot镜像到重定向位置 通过label

6、relocate_vector(重定位中断向量表)

设置VBAR寄存器为重定位后的中断向量表的起始地址uboot的起始地址

7、board_init_r(初始化一些功能函数序列)

因为board_init_f并没有初始化所有的外设

①、emmc、网卡、flash等一些外设的初始化,uboot界面的网络显示就是在这里面打印的

8、run_main_loop函数

获取bootcmd环境变量的值,

autoboot_command判断是否有键盘输入,没有就会运行bootcmd的命令

接收到键盘输入后会运行cli_loop()函数解析终端输入的命令,并通过cmd_process()函数处理

9、cmd_process()函数

获取终端输入,去u-boot_list段中查找命令,找到以后返回一个cmd_tbl_t类型的值,并为结构体的cmd赋值直接运行

二、bootz启动linux内核过程和uboot的生命周期

(获取zimage和设备树的首地址,获取Linux系统的启动函数do_bootm_linux()将bootargs环境参数传到Linux内核中,输出start kernel在启动Linux(kenel_entry())

1、image全局变量(boot_head_t)

保存Linux内核的一些信息,比如镜像的入口地址,长度,设备树的入口地址和长度

2、bootz

执行函数为do_bootz函数()

bootz_setup判断zImage是否正确

boot_get_fdt 找到设备树,将设备树的起始地址和长度写入到全局变量images的成员变量中

kernel_entry()启动后linux内核,结束后生命周期就结束了

Uboot配置文件

宏 PHYS_SDRAM_SIZE 就是板子上 DRAM 的大小,设置DRAM大小

CONFIG_DISPLAY_CPUINFO,uboot 启动的时候可以输出 CPU 信息

CONFIG_DISPLAY_BOARDINFO,uboot 启动的时候可以输出板子信息

CONFIG_SYS_MALLOC_LEN 为 malloc 内存池大小,这里设置为 16MB。

CONFIG_BOARD_EARLY_INIT_F,这样 board_init_f 函数就会调用 board_early_init_f 函数。

CONFIG_BOARD_LATE_INIT,这样 board_init_r 函数就会调用 board_late_init 函数。

宏CONFIG_SYS_FSL_ESDHC_ADDR 为 EMMC 所使用接口的寄存器基地址,也就是 USDHC2 的基地址

跟 NAND 相关的宏,因为 NAND 和 USDHC2 的引脚冲突,因此如果使用 NAND 的只能使用一个 USDHC 设备(SD 卡)。如果没有使用 NAND,那么就有两个 USDHC 设 备(EMMC 和 SD 卡),宏 CONFIG_SYS_FSL_USDHC_NUM 表示 USDHC 数量。EMMC 版本的 核心版没有用到 NAND,所以 CONFIG_SYS_FSL_USDHC_NUM=2。

步骤分析:

1、修改defconfig文件为自己的板级文件

2、添加include下的开发板对应的头文件宏定义(参考官方的头文件定义)里面定义了一些板子设备的一些配置

3、添加开发板对应的板级文件夹,(参考官方的板级文件夹,修改一系列的文件)

4、添加自己的支持图形界面配置(参考官方)修改Kconfig

如果缺少驱动程序,一般都在xxx.h和xxx.C中修改对应的驱动代码

开发板网络一般是内部 MAC+外部 PHY 芯片组成结构

Linux顶层Makefile分析

将所有的.o文件和.a库文件准备好后,链接生成vmliux

vmlinux是未经过压缩的最原始的内核文件;Image是Linux的镜像文件,仅包含可执行的二进制数据;zImage是经过gzip压缩的Image;

Linux启动流程分析

链接脚本vmlinux.lds,linux内核的入口为stext函数

Linux 内核启动之前要求如下:

设置svc模式和关闭中断

①、关闭 MMU。 ②、关闭 D-cache。 ③、I-Cache 无所谓。 ④、r0=0。 ⑤、r1=machine nr(也就是机器 ID)。 ⑥、r2=atags 或者设备树(dtb)首地址。

最终调用 start_kernel 来启动 Linux 内核

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXlwyGM8-1618219801526)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210323155826695.png)]

start_kernel函数和rest_init 函数

start_kernel初始化函数

通过函数kernel_thread来创建kernel_init进程,这个进程就是Linux的init进程,init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根 文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程 序,init 进程就会实现从内核态到用户态的转变。init 进程的 PID 为 1。

调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2。kthreadd 进程负责所有内核进程的调度和管理

最后调用函数 cpu_startup_entry 来进入 idle 进程,cpu_startup_entry 会调用 cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle 进程叫做空闲进程

performance高性能

Linux内核移植

①、添加自己板子对应的deconfig文件

②、添加自己板子对应的设备树文件

③、

Linux调度机制

多任务系统可以划分为两类:非抢占式多任务和抢占式多任务。像所有Unix的变体和许多其他现代操作系统一样,Linux提供了抢占式的多任务模式。在此模式下,由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行的机会。这个强制的挂起动作就叫做抢占。进程在被抢占之前能够运行的时间是预先设置好的,而且还有一个专门的名字,叫进程的时间片(timeslice)。时间片实际上就是分配给每个可运行进程的处理器时间段。有效管理时间片能使调度程序从系统全局的角度做出调度决定,这样做还可以避免个别进程独占系统的资源。当今众多现代操作系统对程序运行都采用了动态时间片的计算的方式,并且引入了可配置的计算策略。

调度器可以切换进程的状态(process state),一个Linux进程从被创建到死亡,可能会经过很多种状态,比如执行、暂停、可中断睡眠、不可中断睡眠、退出等。我们可以把Linux下繁多的进程状态,归纳为三种基本状态

调度算法中最基本的一类就是基于优先级的调度

Linux采用了两种不同的优先级范围。第一种用nice值,它的范围是从-20到+19,默认值为0;越大的nice值意味着更低的优先级,nice似乎意味着你对系统中其他进程更"优待"。相比高nice值的进程,低nice值的进程可以获得更多的处理器时间。
  第二种范围是实时优先级,其值是可以配置的,默认情况下它的变化从0到99,与nice值意义相反,越高的实时优先级数值意味着进程优先级越高。任何实时进程的优先级都高于普通的进程完全公平调度器成为了Linux

的默认调度器。Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。

根文件系统

根文件系统是Linux内核启动以后挂在的第一个文件系统,然后去根文件系统中读取初始化脚本,比如 rcS,inittab

busybox构建根文件系统

集成了 Linux 的许多工具和命令

①、修改添加对应的编译器和架构在Makefile下

busybox有三种配置选项,1、默认的缺省配置 ;2、全选配置,选中所有红能;3、最小配置

②、使用字符界面配置对功能的选配,比如,使用动态编译

③、直接进行编译,将编译结果放到指定目录下

④、添加对应的库

⑤、选择挂载分区

/bin目录:存放可执行文件如ls等

/dev目录:设备文件

/usr软件资源目录

du 查看文件的大小

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.11.20: /home/zhaolin/nfs/rootfs,proto=tcp rw ip=192.168.11.66:192.168.11.20:192.168.1.1: 255.255.255.0::eth0:off

创建/etc/init.d/目录,设置rcS启动脚本

设置文件系统目录,/etc/fstab

烧写系统

使用Mfgtool烧写

分为两部分:

1、先向开发板的DDR下载一个Linux系统

2、通过下载的Linux最终完成

后台运行 xxx & ,关闭后台使用ps -9

vscode插件安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iJa3yjz-1618219801528)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210304155331807.png)]

汇编

@符号:注释符号

.text 表示代码段

.data 初始化的数据段

.bss 未初始化的数据段

.rodata 只读数据段

汇编程序默认入口是_start,也可以在链接脚本.lds文件中使用ENTRY指明其他的入口点

.global 定一个一个全局符号 .end 表示源文件结束

MSR MOV 等属于

裸机驱动

cortex-A汇编:

①汇编初始化一些SOC外设

②使用汇编初始化DDR

③设置sp指针,一般指向DDR,设置好C语言环境

STM32 IO 初始化流程

①使能GPIO时钟

②设置IO复用

③配置GPIO的电气属性

④输出高低电平

STM32:管脚名字:PA0~15,PB…PC…PD

IMX6ULLio表示:管脚名:PAD_BOOT_MODE0 管脚复用:IOMUXC_SNVS_SW_PAD

IMX6ULL 初始化流程

①使能时钟,CCGR0~CCGR6寄存器控制所有的外设时钟的使能

编写驱动

①、使用arm-linux-gnueabihf-gcc -g -c xxx.s -o xxx.o生成.o文件

②、将所有的.o文件链接为elf格式的二进制文件 arm-linux-gnueabihf-ld -Ttext 0x00000000 xx.o xx.elf

③、将elf文件转为bin文件 arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

④、将elf文件转为汇编

链接:就是将所有的.o文件链接在一起,并连接到指定的地址,链接起始地址就是代码运行的地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1btxzgP-1618219801529)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210305155603562.png)]

启动方式:

boot rom:设置内核时钟为396Mh。使能MMU和cache,使能L1cache L2cache MMU 目的是为了加速启动

MRS将CPSR寄存器数据读出到通用寄存器里面,而MSR指令将通用寄存器的值写入到CPSR寄存器里面去

BSP工程管理

为了模块化整理代码

主频和时钟配置

七路PLL

PPL1:ARMPLL供给ARM内核

PLL2: system PLL 528MHZ

PLL3:usb1 PLL

PLL4:Audio PLL主供给音频

PLL5:VIdeo PLL 主供给给视频使用

PLL6:NET PLL 网络独属

PLL7: usb PLL

通过设置系统时钟频率来增加性能

中断:

stm32中断:

中断向量偏移:手动设置中断向量表的偏移地址

NVIC中断管理机构,使能和关闭指定的中断,设置中断优先级

Cortex_A7中断:

设置中断向量偏移

GIC中断控制器:

①、SPI:共享中断

②PPI私有中断、SGI软件中断

中断号:中断ID。id0ID15,是SGI,ID16-ID31,是PPI,剩下的ID32ID1019是SPI。

IMX6Ul有128个中断ID

分发器和CPU接口端

分发器:是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制

每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端

①、全局中断使能控制。

②、控制每一个中断的使能或者关闭。

③、设置每个中断的优先级。

④、设置每个中断的目标处理器列表。

⑤、设置每个外部中断的触发模式:电平触发或边沿触发。

⑥、设置每个中断属于组 0 还是组 1。

CPUinterface:

①、使能或者关闭发送到 CPU Core 的中断请求信号

②、应答中断。

③、通知中断处理完成。

④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。(过滤)

⑤、定义抢占策略。

⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。

CP15协处理器组:

通过mcr和mrc访问cpu内部寄存器

CP15 协处理器一共有16 个 32 位寄存器

**C0:**获取处理器内核信息

C1:SCTLR系统控制寄存器 使能或者禁止 MMU、I/D Cache 等

**C12寄存器:VBAR 寄存器,也就是向量表基地址寄存器。**设置中断向量表的偏移地址

**C15寄存器:**GIC 的基地址就保存在 CBAR中

优先级:数字越小,优先级越高

中断服务函数

所有外设中断都会进入Irq处理函数

①、IRQ中断服务函数 ②、IRQ下读取对应中断号的中断服务函数并运行的具体的外设中断服务函数

中断流程:

①、设置中断向量表,初始化中断服务函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jYx7rsTv-1618219801530)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210324155820406.png)]

1、关闭I,Dcache和MMU,使用CP15协处理器下对应的寄存器,MRC,读,MCR 写,通过对STCL系统控制寄存器的位操作来打开或者关闭MMU,I,Dcache,关闭全局中断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJVZRjFr-1618219801530)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210310165959623.png)]

2、复位中断服务函数:设置处理器工作模式下的对应的SP指针

3、清除BSS段

4、设置中断向量偏移:将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5TxK8pc-1618219801531)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210310165733676.png)]

5、设置各个模式的SP指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Od7SmgM-1618219801531)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210310170954451.png)]

IRQ中断服务函数:

MRC P15,4,R1,C15,C0,0,读取CP15的CBAR寄存器,CBAR寄存器保存了GIC寄存器组的物理地址。GIC寄存器组偏移0x10000x1fff为GIC的分发器,0X20000X3FFF为CPU接口端。

读取GICC_IAR读取出中断ID和CPUID,得到对应的中断处理函数(C语言函数 )

GPIO中断设置

①、设置中断触发方式

②、使能GPIO对应的中断,设置GPIO_IMR寄存器

③、处理完中断后,需要清除中断标志位,清除GPIO_ISR寄存器相应的位,写1清零。

GIC配置

定时器;(EPIT)

是一个32位的向下计数器

高精度延时实验(GPT定时器)

32位的向上计数器

串口通信:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38cd31JS-1618219801532)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210311172257247.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCefYtAa-1618219801533)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210311173550755.png)]

URXD:保存串口接收到的数据

UTXD: 发送数据寄存器,将数据写入到寄存器中就可以发送数据

UCR1~UCR4:控制寄存器,

UFCR:设置分频 uart时钟源=PLL3/6=480/6

USR2:状态寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMpVugZ6-1618219801534)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210311175601366.png)]

DDR3实验

CSx:片选引脚

SRAM:价格高,容量小,一般做芯片内部RAM,做缓存

SDRAM:价格低:容量大,需要时钟线

DDR3:比SDRAM多8倍的预取,地址分行地址和列地址,分块,计算方式,2^行地址个数 X2^列地址个数x块x带宽x预取

传输速率:MT/S

MMDC控制器,接入DDR3

DDR3初始化和测试流程

ddr_stress_test

I2C实验

SCL是高电平,SDA线从高电平到低电平就是起始信号

SCL是高电平,SDA线由低变高就是停止信号,都有主机产生

写操作:发送设备地址--------发送寄存器地址---------发送数据

读操作:发送设备地址---------发送寄存器地址-----------发送设备地址--------发送数据

IFDR寄存器设置I2C频率,bit5:0设置分频

I2CR寄存器,bit7为I2C使能,BIT5为主从模式选择位,bit4发送接收设置位

I2SR寄存器,状态寄存器,判断当前状态

I2DR寄存器:数据寄存器

I2C写时序流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVKa7uGh-1618219801534)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210315105448647.png)]

1)、开始信号。

2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决

定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为

1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。

3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。

4)、从机发送的 ACK 应答信号。

5)、重新发送开始信号。

6)、发送要写写入数据的寄存器地址。

7)、从机发送的 ACK 应答信号。

8)、发送要写入寄存器的数据。

9)、从机发送的 ACK 应答信号。

10)、停止信号。

读时序流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XldDzHjZ-1618219801535)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210315105611002.png)]

I2C 单字节读时序比写时序要复杂一点,读时序分为 4 大步,第一步是发送设备地址,第

二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要

读取的寄存器值,我们具体来看一下这步。

1)、主机发送起始信号。

2)、主机发送要读取的 I2C 从设备地址。

3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号。

4)、从机发送的 ACK 应答信号。

5)、重新发送 START 信号。

6)、主机发送要读取的寄存器地址。

7)、从机发送的 ACK 应答信号。

8)、重新发送 START 信号。

9)、重新发送要读取的 I2C 从设备地址。

10)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。

11)、从机发送的 ACK 应答信号。

12)、从 I2C 器件里面读取到的数据。

13)、主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了。

14)、主机发出 STOP 信号,停止 I2C 通信。

SPI协议

4线协议:,一个接口可以连接多个外设,全双工。

①、CS/SS,Slave Select/Chip Select,这个是片选信号线,用于选择需要进行通信的从设备。

I2C 主机是通过发送从机设备地址来选择需要进行通信的从机设备的,SPI 主机不需要发送从机

设备,直接将相应的从机设备片选信号拉低即可。

②、SCK,Serial Clock,串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟。

③、MOSI/SDO,Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线

只能用于主机向从机发送数据,也就是主机输出,从机输入。

④、MISO/SDI,Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只

能用户从机向主机发送数据,也就是主机输入,从机输出。

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:

①、CPOL=0,串行时钟空闲状态为低电平。

②、CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具

体的传输协议。

③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。

④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。

字符设备驱动

modeprobe会分析模块间的依赖关系,比insmod智能一些

MMU:内存管理

ioremap : 将物理地址映射到虚拟地址 val = ioremap(0x111131313,10)

**iounmap:**释放映射

io操作函数:

读操作函数readl(读32位的) val = readl(xxxxx)

写操作函数:writel(val,xxxxxx);

**动态申请设备号:**alloc_chrdev_region(dev_t dev,unsigned baseminor,unsigned count,const char *name); count 多少个,baseminor,从多少号开始申请

**静态注册设备号:**register——chrdev_region(dev_t *dev,unsigned count,const char *name):

一般通过判断major来判断注册方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prtOnQ3u-1618219801535)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210316181435787.png)]

**释放设备号:**unregister_chrdev_region(dev_t from,unsigned count)

注册字符设备: cdev结构体,cdev就是字符设备

**初始化结构体:**cdev_init(*cdev,*ops)

**添加字符设备到Linux内核:**cdev_add(*cdev,dev_t,count)

**删除字符设备:**cdev_del(*cdev)(必须在删除设备号之前调用)

**申请一个字符设备:**cdev_alloc()

自动创建设备节点:

mdev机制:热插拔管理机制busybox提供了udev的简化版本

struct class *class,struct device *dev;

**创建类:**class_create(owner,name);返回值指针

**摧毁类:*class_destory(struct class)

创建设备:device_create(class,null,devid,null,ledname)返回值为指针

IS_ERR(*)判断指针是否正确,PTR_ERR()输出错误 //指针类型判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5z369Vc-1618219801536)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210317111723915.png)]

删除设备:device_destory(*class,devid); ( 先删除设备在删除类)

使用结构体描述一个设备对象

在read和write里面访问设备时直接读取私有数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdQqhtSk-1618219801536)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210317135517308.png)]

linux设备树开发

DTS:设备树文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0CBMXlB7-1618219801537)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210317143422357.png)]

.dtsi文件一般描述芯片内部外设

使用include来引用.dtsi文件

/dts-v1/

include引用dtsi头文件 /根节点

命名:node-name@unit-address

intc:interrupt-controller@0x11111111 :前面是标签,可以直接通过&标签访问添加节点或信息

在/proc/device_tree/目录下查看节点信息

SPI共享外设中断:触发方式:上升沿和高电平

特殊节点:aliases 定义别名

chosen:主要将Uboot的bootargs环境变量传递给Linux内核作为命令参数,在UBOOT的fdt_chosen.c[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0cRLNUp-1618219801539)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210317161853677.png)] 标准属性:

compatible属性:兼容性属性,status :状态(disable或者okey)

/节点下的:内核检查是否支持,类似于无设备树的mashid

reg内的参数个数由address-cells和size-cells根据父节点的address-cells和size-cells来决定

ranges:地址映射

device_type:一般在cpu和mem中使用

Linux内核的OF操作函数(一系列函数)在内核文件的include/linux/of.h中定义of函数

在of.h中配置config_of才能使用of系列函数,Linux内核系统一般默认使能

在驱动中可以通过of函数获取设备的属性信息

device_node结构体用来描述设备信息

struct property{}结构体来表示属性

查找节点:of_find_node_by_name(null,name)具体参考手册的of函数

of_find_node_by_path (path)根据路径去查找

of_find_propertry(*node,*name,NULL);查找节点的属性

of_property_read_string(node,“name”,&str)读取name属性的值到str中(字符串)

of_property_read_u32(node,“name”,&value)读取name属性的值到value中(单个数字)

int count = of_property_count_elems_of_size(node,“name”,sizeof(u32))获取数组元素的数量

设备树的数字都是u32

动态内存申请 :kmalloc(size,flags)(flags:GFP_KERNEL为正常分配)

of_iomap(device, index); io映射

pinctrl子系统和GPIO子系统实验:pinctrl子系统设置pin的复用和电气属性;gpio子系统设置gpio

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ueTPiPwi-1618219801540)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210318134342591.png)]

GPIO子系统驱动步骤:

①、编写设备树

of_find_node_by_path("/beep");

②、of_get_name_gpio(nd,name,index)获取需要使用的gpio的编号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzzJ5VTd-1618219801541)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210318160708322.png)]

③、gpio-request(gpio编号,name) 申请io,和gpio_free(gpio编号)一起使用 申请io失败一般是被其他设备占用

④、gpio_direction_input(gpio编号) gpio_direction_output()

⑤、gpio_set_value(gpio编号)设置gpio的高低电平

gpio_get_value()获取输入值

并发与竞争

原子操作:atomic变量操作,原子操作只能对整形变量或者位进行保护,,使用原子变量来代替整形变量(加减,自增)(设置地址清零)

自旋锁:spinlock_t lock;自旋锁的临界区不能调用任何睡眠或者阻塞函数 (忙循环-旋转-等待)

建议使用 spin_lock_irqsave/spin_unlock_irqrestore,因为这一组函数会保存中断状态,在释放锁的时候会恢复中断状态。适用于短时期的轻量级加锁

读写自旋锁:读可以并发,写只能一个使用

顺序锁:seqlock_t;实现同时读写,顺序锁保护的资源不能是指针,因为如果在写操作的时候可能会导致指针无效

信号量:semaphore:因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场

合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6Ebju2N-1618219801541)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210318170421909.png)]

②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。

③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换

线程引起的开销要远大于信号量带来的那点优势。

互斥体: 中断中只能使用自旋锁。

mutex:lock和unlock,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2lFIB0x-1618219801542)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210318170654311.png)]

linux内核定时器

软件定时器:依靠系统定时器

内核定时器:

HZ:节拍数

struct timer_list{}

①、init_timer()

②、devtime.function = function() 设置定时器执行函数

③、devtime.exires = jiffes + msec_to_jiffies(1000); 设置时间

④、add_timer() 添加定时器

mod_timer(timer,时间)重复开启定时器

del_timer_sync(timer) 删除定时器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NikqZ44S-1618219801542)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319105307765.png)]

ioctl()函数

常用命令构建函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsCunLBY-1618219801543)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319110424961.png)]

Linux中断机制

上半部和下半部:上半部就是中断处理函数,处理过程比较快,不会占用很长时间的处理

下半部:处理过程比较耗时,会将耗时代码提取出来,交给中断下半部去处理

cpu接受终端->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qQduFXlS-1618219801543)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319134221228.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-locYxePl-1618219801544)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319134155577.png)]

下半部机制:

①、软中断:softirq_action(一般不用)

Linux有10个软中断

②、tasklet:不允许睡眠

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BFwlEgKu-1618219801544)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210323152404829.png)]

使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化:DECLARE_TASKLET(name, func, data)

其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量,func就是 tasklet 的处理函数,data 是传递给 func 函数的参数。在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:*void tasklet_schedule(struct tasklet_struct t)

③、**work_struct:**工作队列允许睡眠或重新调度。

#define INIT_WORK(_work, _func) _work 表示要初始化的工作,_func 是工作对应的处理函数。

也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f) n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

bool schedule_work(struct work_struct *work) 调度工作运行

中断API函数:上半部的中断处理函数函数

设备树中断描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d4sjlPgr-1618219801545)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319140553124.png)]

获取中断号:

gpio中断:gpio_to_irq(gpio);

正常:irq_of_parse_and_map(nd,index) 从interrupts属性获取中断号

request_irq()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0Ga9xEy-1618219801546)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319150231388.png)]

中断处理函数的第二个参数,驱动程序也可以用它来指向自己的私有数据区(来标识哪个设备产生中断)

local_irq_save(flags)

local_irq_restore(flags)

这两个函数是一对,local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。

local_irq_restore 用于恢复中断,将中断到 flags 状态。

Linux阻塞和非阻塞

阻塞方式:

等待队列:设备文件不可操作就会进入休眠,让出cpu资源

等待队列头:wait_queue_head_t

等待队列项:wait_queue_t

添加等待队列项到等待队列头:add_wait_queue;

移除等待队列项:remove_wait_queue();

唤醒:wake_up()

非阻塞:

select、poll和e’poll

poll轮询

异步通知

软件层次对中断的一种模拟

**驱动里面:**fasync_struct {} 定义到设备结构体

kill_fasync函数,向用户发送信号函数

关闭驱动要删除信号

platform平台设备驱动

主机控制器驱动和设备驱动分离。主机驱动一般是半导体厂商写,我们需要在Linux驱动框架下写具体的设备i驱动

总线:bus_type{} 完成设备和驱动的匹配

注册总线:bus_register(*bus)/bus_unregister(*bus)

驱动:device_driver{}

注册驱动:driver_register(*driver)

设备:device{}

platform平台驱动模型

platform总线:

platform驱动:

struct platform_driver {}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nlchvSGu-1618219801546)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319164424832.png)]

使用platform_driver_register(driver)向内核注册驱动

platform设备

struct platform_device{}

无设备树:platform_device_register(device)

有设备树:of_device_id获取

匹配过程:

通过总线的match函数进行匹配

无设备树:设备编写:platform_device_register() /platform_device_unregister

初始化platform_device

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aohxbc7g-1618219801547)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319173655211.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HgczVn03-1618219801547)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210319173739141.png)]

驱动程序:platform_driver

靠of_match_table来找到对应的设备和驱动

有设备树:

靠driver.name进行匹配

**module_platform_driver(led_driver)😗*宏函数代替module_init()、module_exit()和注册删除平台设备驱动

设备树文档:源码->documention->devtree

MISC设备

主设备号都为10,会自动创建cdev

MISC设备结构体:miscdevice {}

注册设备:misc_register()

删除设备:misc_deregister(),如果minor为255,会自动分配此设备号

INPUT子系统

驱动层:输入设备的具体驱动程序,向内核层报告输入内容

核心层:承上启下,为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理

事件层:主要和用户空间进行交互

主设备号为13

申请input设备:struct input_dev *input_allocate_device(void)申请一个input_dev结构体变量

注销input设备:void input_free_device(struct input_dev *dev) 注销一个input设备

内核注册input驱动:int input_register_device(struct input_dev *dev)

注销input驱动:void input_unregister_device(struct input_dev *dev) 注销一个input驱动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tSX5WdT-1618219801548)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210322150736829.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UUdiXpzm-1618219801548)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210322150928229.png)]

上报按键所使用input_report_key 函数

上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件*void input_sync(struct input_dev dev)

RTC设备驱动

I2C驱动框架

I2C设备 I2C_client {}

I2C_driver初始化与注 册

i2c_add_driver(* i2cdriver) 初始化i2c

I2C_del_driver(* i2cdriver)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xVYhzqL-1618219801549)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210322165506184.png)]

类似于平台设备驱动

读取和传输数据函数,i2c_transfer()

i2c_msg{}数据传输格式结构体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PO97pqyZ-1618219801549)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210322174411757.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EIokPk5w-1618219801550)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210322174753258.png)]

串口

串口分TTL和RS232

0~3G 用户空间 代码段、数据段、堆段、栈段

3g~4G内核空间

Linux内核结构:

用户->系统调用——>内核

内核级:文件子系统:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IinmnQea-1618219801550)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210323101234265.png)]

页:内核内存管理的基本单位

操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上

MMU:一般存在于协处理器,将虚拟地址映射为物理地址

大多数使用虚拟存储器的系统都使用一种称为分页(paging)。虚拟地址空间划分成称为页(page)的单位,而相应的物理地址空间也被进行划分,单位是页框(frame).页和页框的大小必须相同

内存和外围存储器之间的传输总是以页为单位的

定出一个最小的内存单元,内存分配是这最小单元的组合,单元内的地址是连续的,但各个单元不一定连续。这样的内存小单元有页和段。

slab 就是专门为某一模块预先一次性申请一定数量的内存备用,当这个模块想要使用内存的时候,就不再需要从系统中分配内存了(因为从系统中申请内存的时间开销相对来说比较大),而是直接从预申请的内存中拿出一部分来使用,这样就提高了这个模块的内存申请速度。当某一子系统需要频繁地申请和释放内存时,使用 slab 才会合理一些

物理地址空间大于1G的内存区域称之为高端内存

虚拟地址,即逻辑地址,是指由程序产生的与段相关的偏移地址部分。物理地址: 放在寻址总线上的地址。

TTL电平从芯片出来,一般为3.3或者5v

RS232电平 ±15v

存储器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4g0XKWcq-1618219801551)(C:\Users\59966\AppData\Roaming\Typora\typora-user-images\image-20210323163756048.png)]

input子系统

input 子系统的所有设备主设备号都为 13

RGBLCD

像素格式:RGB 需要888共24位 HDR10 RGB101010

LCD屏幕接口

MI PI 、LVDS、MCU、RGB

水平同步信号:HSYNC

垂直同步信号:VSYNC

CAN总线

①、多主控制,多个单元发送时根据ID确定优先级

②、系统的柔软性 与总线相连的单元没有“地址”

③、通信速度块,距离远

④、具有错误检测、错误通知和错误恢复 所有的单元都可以检测错误

⑤、故障封闭功能 可以判断出错误产生的地方,并隔离

⑥、连接节点多 CAN可以连接多个单元的总线


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值