系统移植:
把操作系统(Linux)能够在芯片(板子)上运行
目标:在开发板上运行操作系统
嵌入式系统:(linux)
以应用为中心,把软硬件进行裁剪,适用于应用的专用计算机系统
1、交叉编译环境搭建
开发主机:
编译工具,针对开发板的编译工具(arm-gcc)
arm-none-linux-gnueabi-gcc
传输环境(网络):
tftp:上传下载服务器
sudo apt install tftpd-hpa 下载安装tftp
主机提供哪个目录用于上传下载
sudo vim /etc/default/tftpd-hpa(配置文件)
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpdir" #配置上传下载的目录
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
在对应的目录(根目录)下创建tftpdir
sudo mkdir /tftpdir
sudo chmod 0777 /tftpdir
nfs:文件共享服务器
sudo apt install nfs-kernel-server 下载安装nfs
主机提供哪个目录用于共享文件
sudo vim /etc/exports
/nfsdir/rootfs *(rw,sync,no_subtree_check,no_root_squash)
表示共享/nfsdir/rootfs
2、系统移植----linux
把别人写好的系统代码进行修改,放在自己的板子上可以运行
Linux系统的启动流程:
电源键----->引导程序(执行硬件自检,加载操作系统)------>加载系统(启动系统)------>加载文件系统(执行程序)
系统移植:
1、引导程序
2、系统(内核):linux
3、文件系统
------------------------
1、bootloader移植
bootloader:是硬件启动的引导程序,是加载操作系统的前提
在操作系统内核运行前的一小段代码,对软硬件进行相应的初始化和设置,为执行操作系统做准备,然后加载操作系统
bootloader不属于操作系统;需要依赖cpu的体系结构与板载硬件
bootloader的操作模式:
a、自启动模式:直接进行初始化化,加载内核(操作系统)到内存中运行
b、交互模式:留在bootloader执行,通过串口进行通信,然后完成控制
bootloader基本功能:
初始化硬件
bootloader自搬移
执行用户的命令
加载内核
bootloader的启动:
当芯片上电、复位后,所有cpu会自动从某个地址进行执行,只用把bootloader放在对应位置
常见的bootloader:
U-boot
LILO:linux磁盘引导程序
GRUB:GNU的LILO
LinuxBIOS
RedBoot
U-boot:通用引导程序,使用广,兼容性强
U-boot特点:
1、代码结构清晰、易于移植
2、支持多种处理器体系结构
3、支持众多开发板
4、命令丰富
5、支持网络协议、USB、SD等设备协议
6、支持文件系统
7、用户多,出现问题可以及时搜索解决
u-boot命令:
命令类型:环境变量、数据传输、存储器访问、加载运行
printenv:pri----打印所有环境变量
setenv:set------设置环境变量值
saveenv:save---保存环境变量值
bootdelay:进入自启动模式的倒数计时
gatewayip:网关地址
ipaddr:设备网络地址----板子
netmask:子网掩码
serverip:服务端地址---linux虚拟机
bootcmd:进入自启动模式后,uboot要执行的命令
bootargs:当加载操作系统时传递给操作系统的参数
loadb 地址
tftp:把网络中tftp服务端(serverip)文件下载某个地址
go xxxx:跳转到地址执行 pc = xxx
movi:
movi init----初始化emmc(flash),并显示信息
movi read
movi write
bootm:系统引导指令
sudo apt install u-boot-tools
sudo apt install libncurses5-dev
sudo dpkg --add-architecture i386
sudo wget -nc -O /usr/share/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/focal/winehq-focal.sources
sudo apt update
sudo apt install --install-recommends winehq-stable
系统移植:
能够在开发板(硬件)运行Linux系统,把Linux系统移植到特定硬件进行运行
系统移植:
1、引导程序
2、系统内核
3、文件系统
----------------------------------------
引导程序:bootloader
bootloader:是硬件启动的引导程序,是操作系统执行的前提,是系统内核运行前的一段代码,完成特定硬件初始化和设定
移植bootloader:适配于当前硬件的引导程序,能够在当前硬件上引导启动操作系统
bootloader:u-boot-----通用引导程序
u-boot命令:
pri:查看环境变量
set:设置环境变量
loadb
tftp
ping
go
bootm
u-boot移植:移植一个适配当前硬件的引导程序
在u-boot目录中包含了引导程序的所有代码,只要能够编译出对应的可执行程序----引导程序
u-boot支持众多cpu,所以在代码中包含所有cpu的引导功能,选择硬件对应的cpu进行编译
移植:在u-boot源码中指定我们需要的功能,需要的外设硬件,需要的cpu编译为适配于当前硬件的引导程序
平台相关:
arch:体系结构相关的代码--cpu
board:芯片厂商的板载开发文件,生产初始的硬件设备对应的引导流程代码(厂商板子配置的引导代码)
include:头文件
平台无关:
common:命令
drivers:驱动,硬件控制
net:网络
文档:
doc
工具:
tools
移植步骤:
0、选择对应的编译工具
修改Makefile,指定编译工具
1、查看当前芯片,u-boot找到最相近的硬件设备
指定产品cpu,我们所使用的是三星exynos4412(armv7)
查看arch/arm/cpu目录,进行搜索确定当前的u-boot源码支持我们的cpu
u-boot支持当前cpu
查找board最相近硬件设备,根据相近的板子配置来完成我们的配置
指定产品 BOARD,三星已经对exynos4412芯片设置了初始的硬件设备,完成了初始化的程序
相近:board/samsung/origen
参考对应board的配置
复制一份origen的配置,改为自己的配置(引导程序要完成的操作,代码)
boards.cfg:所有板子的描述说明
描述所支持的开发板和相应的信息,后续的编译过程(编译为引导程序)会根据配置名来检索到对应的信息
make fs4412_config
make
u-boot.bin---引导程序
2、添加三星的加密方式(三星才添加)
三星芯片:加密,把核心代码拿出
需要单独添加
exynos需要三星的初始引导加密,我们的u-boot才能执行
需要使用到三星提供的加密工具,目的就是根据三星的启动要求对u-boot.bin进行处理
sdfuse_q、CodeSign4SecureBoot、build.sh---->u-boot2013
修改Makefile:
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(BOARD_SIZE_CHECK)
添加:
@#./mkuboot
@split -b 14336 u-boot.bin bl2
@+make -C sdfuse_q/
@#cp u-boot.bin u-boot-4212.bin
@#cp u-boot.bin u-boot-4412.bin
@#./sdfuse_q/add_sign
@./sdfuse_q/chksum
@./sdfuse_q/add_padding
@rm bl2a*
@echo
要把三星核心文件添加到u-boot.bin中
uboot移植:
启动代码:引导内核(操作系统)的代码
uboot启动流程:
阶段一:
cpu初始化
svc
关闭mmu
基本硬件初始化
关闭中断、关闭看门狗;初始化时钟、串口、内存
自搬移到内存执行
设置堆栈
阶段二:
board_init_r:
board_init_f//大部分硬件初始化
主循环
for(;;)
{
main_loop();//进入交互模式(readline,run_command) 或 自启动模式 (bootdelay 读取bootcmd执行自启动)
}
移植:
1、下载解压uboot源码,进行移植
tar xvf u-boot-2013.01.tar.bz2
2、查看板子芯片cpu,找最相近的板子作为参考
cpu:exynos
2013uboot支持当前cpu
查找相近板子作为参考进行移植(board目录就是厂商提供的参考板的配置代码)
samsung/origen
a、用参考配置,生成当前硬件的配置fs4412目录
cp board/samsung/origen/ board/samsung/fs4412 -rf
b、修改fs4412目录变成自己硬件配置(配置代码)
文件名等修改
完成自己的配置(实现配置代码):引导程序需要有什么功能
3、编译生成
生成uboot程序,有引导程序
a、完成make的配置,让makefile知道怎么去编译
boards.cfg:编译配置说明
参考已有的配置说明,添加新的说明
三星(额外):
sdfuse_d目录、CodeSign4SecureBoot目录、build.sh
./build.sh
b、make编译
生成 u-boot.bin
u-boot-fs4412.bin:uboot程序,引导程序
4、烧写
烧写到sd卡
查看文档
烧写到板子上emmc----固化到芯片上
a、先运行一个有emmc功能的uboot
movi init----有emmc功能
FS4412[general] # movi init
Device: S5P_MSHC4
Manufacturer ID: 15
OEM: 100
Name: 4FPD3
Tran Speed: 0
Rd Block Len: 512
MMC version 4.0
High Capacity: Yes
Capacity: 7.3 MiB
Bus Width: 2-bit
b、运行下载,把要固化的uboot下载到板子的内存中
保证电脑与板子能够ping通
板子设置serverip(电脑ip)、gatewayip、ipaddr
ping 电脑ip
tftp 内存地址 文件名
板子上的内存 电脑上tftp服务端文件夹中的文件
tftp 41000000 u-boot-fs4412-general-2013.bin
错误:
Loading: *
TFTP error: 'Permission denied' (0)
Starting again
把要下载的文件的权限提升
c、把板子内存上的uboot烧录到emmc中的u-boot分区
movi write u-boot(emmc中uboot分区) 41000000
uboot烧写到emmc(板子上):
以sd卡启动uboot
使用tftp下载:
tftp 文件下载的内存地址 服务端的文件
tftp 41000000 u-boot-fs4412.bin
烧写到emmc中:
movi write 写入的位置 写入的内容的当前存放的地址
movi write u-boot(emmc中的u-boot分区中) 41000000
5、启动uboot
把拨码开关 1000----sd卡启动
1234
把拨码开关 0110----emmc启动
1234
6、实现当前硬件配置(配置代码)
移植内容:对当前硬件裁剪内容(添加、删除功能代码),启动的代码中进行添加
a、led
b、uart
c、net
d、emmc
通过网络烧写设置:
1、把虚拟机改为桥接模式
192.168.3.xxxx
2、关闭虚拟机网络托管服务
sudo /etc/i
nit.d/network-manager stop
3、修改板子的ip地址
serverip = 虚拟机的ip地址
ipaddr = 设置板子的ip地址
gatewayip = 网关 192.168.x.1
------------------------------------------------------------
系统移植:
Linux系统与Linux内核
内核:提供了硬件的处理(硬件抽象层)、磁盘和文件系统的控制、任务处理(进程管理、内存管理等)系统的基础运行部分
系统:包含了Linux内核、工具集、各种库、图形界面、桌面管理器、各种应用程序为一体的系统
系统移植:Linux内核,能够运行Linux核心组件
Linux内核组件:
进程管理
内存管理
文件系统
网络系统
设备管理
移植Linux内核:
根据Linux内核源码,通过添加、删除变为能够适配于当前硬件Linux内核
1、下载解压Linux内核源码
在3.0版本Linux内核前,没有设备树
在3.0版本Linux内核后,有设备树
arch:cpu相关的代码(体系结构相关代码)
Makefile:编译控制
把内核代码编译为内核程序
.config:编译配置的存放脚本
2、查看芯片,查找对应厂商与当前硬件最相近的板子的Linux内核配置(Linux内核移植配置)
先查看芯片:
exynos
查找对应三星厂商exynos芯片初始板的配置
所有厂商芯片的初始板的配置:
exynos芯片(三星厂商的默认配置):
arch/arm/config/exynos_defconfig-----芯片配置(内核要有什么功能)--生成内核镜像
外设硬件信息说明(三星厂商的默认硬件配置)板子:
arch/arm/boot/dts/exynos4412-origen.dts
选择配置:要编译内核的配置(哪些内容要加入到内核)
make exynos_defconfig
3、编译内核
生成内核镜像
make uImage
uImage专门给uboot进行启动引导的内核
uImage缺少当前硬件的硬件信息
生成设备树:设备树就是描述硬件信息文件
make dtbs
exynos4412-origen.dtb
系统移植:内核移植
Linux内核组件:
进程管理
内存管理
文件系统
网络管理
设备管理
移植内核:-----操作系统
多任务
多用户
1、下载解压linux内核
tar xvf linxu-3.14.tar.xz
Linux内核以不同的文件夹把不同的功能操作分开
每个目录就是对应的功能(代码)
最终当前硬件需要什么功能就移植什么功能
2、查看cpu,查找相近板子进行移植
内核
设备树
内核:查找芯片对应的芯片,使用对应的芯片的内核配置作为参考
linux-3.14/arch/arm/configs/exynos_defconfig
//linux-3.14/arch/arm/configs/exynos_fs4412_defconfig
内核编译配置选择------内核功能选择
make exynos_defconfig:使用参考内核配置作为当前内核编译的内核配置,生成.config文件
.config:内核编译配置文件
make uImage
生成内核镜像文件,内核文件
Image arch/arm/boot/uImage is ready
设备树:查找厂商的默认初始硬件配置文件(设备树文件),使用厂商的设备树配置作为参考
linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts
拷贝出新的设备树文件:作为设备描述
cp linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
fs4412.dts就是当前硬件使用参考配置
编译时需要添加这个设备树要进行编译
vim linux-3.14/arch/arm/boot/dts/Makefile
在对应位置添加一个编译:表示要编译新的设备树文件
dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \
exynos4412-fs4412.dtb \
编译设备树
make dtbs
arch/arm/boot/dts/exynos4412-fs4412.dtb
make exynos_defconfig
make uImage---内核镜像
make dtbs------设备树文件
3、uboot引导内核
网络下载引导:从主机获取对应uImage和exynos4412-fs4412.dtb,uboot加载内核
交互模式:
1、在uboot中通过网络下载的方式,把内核和设备树下载到硬件的内存中
uImage和exynos4412-fs4412.dtb需要在电脑上tftp下载目录中
tftp 41000000 uImage
tftp 42000000 exynos4412-fs4412.dtb
2、uboot从内存中引导内核执行
bootm 内核内存地址 文件系统内存地址 设备树内存地址
bootm 41000000 - 42000000
自启动模式
倒数计时结束后进入自启动模式
自启动模式执行环境变量为 : bootcmd 内容
执行bootmcd环境变量
设置环境变量:bootcmd---进行下载启动内核
set bootcmd xxxxxxx;
save
uboot引导内核,启动内核时,需要传递参数给内核,内核根据参数进行启动
bootargs:存储传递给内核的参数,内核根据bootargs作为启动执行参数
set bootargs root=/dev/nfs nfsroot=192.168.3.31:/nfsdir/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.3.20
save
root=/dev/nfs :文件系统
nfsroot=192.168.3.31:/nfsdir/rootfs :nfs网络文件系统位置
rw :读写
console=ttySAC2,115200 :内核启动信息显示位置
init=/linuxrc :指定内核启动后执行的第一个进程(文件系统中)
ip=192.168.3.198:内核系统的ip地址
4、修改内核,适配与当前硬件
默认配置和当前硬件---网卡
修改内核配置添加网卡
在内核中移植网络设备:
内核
1、为了硬件能够工作:
修改文件driver/clk/clk.c
修改
static bool clk_ignore_unused;
为
static bool clk_ignore_unused = true;
2、修改内核具备对应功能----把对应网卡功能编译进内核中
配置内核:--菜单配置,让内核添加删除功能
make menuconfig
读取.config文件,修改.config
network
网络协议:支持什么网络
driver
网卡配置:dm9000网卡功能
由于现在不会把文件系统固化到板子上,把电脑上的一个目录(文件夹)作为板子的文件系统
板子运行内核后,内核要运行文件系统执行程序
板子内核要把电脑上的目录作为文件系统
即:
板子运行内核从网络上挂载文件系统(通过网络的方式拿给内核运行)
nfs:共享文件夹
电脑通过nfs共享一个目录,板子就把电脑通过nfs共享的目录作为文件系统
板子文件系统在网络上通过nfs提供
file system
文件系统设置:支持网络文件系统
make uImage
设备树
在设备树中添加网卡设备的描述(添加网卡信息)
vim arch/arm/boot/dts/exynos4412-fs4412.dts
网卡描述
srom-cs1@5000000 { //bank1 物理设备 描述
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x5000000 0x1000000>;
ranges;
ethernet@5000000 {
compatible = "davicom,dm9000";
reg = <0x5000000 0x2 0x5000004 0x2>;
interrupt-parent = <&gpx0>;
interrupts = <6 4>;
davicom,no-eeprom;
mac-address = [00 0a 2d a6 55 a2];
};
};
make dtbs
5、make uImage过程
根据arch/arm/kernel/vmlinux.lds----->源码路径下生成vmlinux内核镜像(无压缩内核)------->Image------->二次压缩------>zImage------->mkimage(工具)---->uImage(uboot引导专用的内核镜像)
6、kernel启动流程
1、第一阶段:汇编阶段
arch/arm/boot/compressed/head.S
完成自解压
arch/arm/kernel/head.S
cpu自检
检查自己的合法性
cpu类型、机器类型
bl __vet_atags 检查bootargs,验证uboot传入的参数以及获取设备树
bl __create_page_tables 开启内存mmu 页表
为后面开启mmu(内存管理单元)做准备
b __enable_mmu 开启mmu
arch/arm/kernel/head-common.S
把数据段 搬移到虚拟内存
b start_kernel
2、第二阶段:C语言阶段
init/main.c
start_kernel(void)
进行各种设备 功能的初始化
rest_init();
开启内核多线程
kernel_thread();
kernel_init:内核线程,开启第一个用户进程
根文件系统挂载、运行用户进程
电脑要支持nfs V2
sudo cat /proc/fs/nfsd/versions
在/etc/default/nfs-kernel-server 最后位置新建一行添加
sudo vim /etc/default/nfs-kernel-server
RPCNFSDOPTS="--nfs-version 2,3,4 --debug --syslog"
重启nfs
sudo service nfs-kernel-server restart
移植文件系统:
1、使用busybox-1.22.1.tar.bz2工具来创建目录,以及命令
下载解压busybox工具
make menuconfig配置
make
生成目录与命令:
make install
在./_install目录,中存放生成的目录与命令
根据文件系统中应该有哪些目录进行创建新目录
bin dev etc linuxrc mnt proc root sbin sys tmp usr var
需要在文件系统中执行程序,文件系统中需要包含运行库
添加运行库
根据手册实现
把工具做好的文件系统_install 拷贝到nfs中为rootfs
sudo cp -rf _install/ /nfsdir/rootfs
驱动开发:
在操作系统的基础上实现驱动程序
1、驱动开发的环境
系统在网络中
1、ubuntu中配置环境
内核实现、文件系统实现、驱动实现---ubuntu中实现
gcc-4.6.4----->arm-none-linux-gnueabi-gcc
a、通过tftp提供uImage和.dtb----提供内核
b、通过nfs提供文件系统rootfs
2、板子运行环境配置
1、板子上运行uboot引导程序
2、配置uboot环境变量
ipaddr
serverip
gatewayip
支持网络--uboot
bootcmd----uboot自启动模式自动执行
bootcmd tftp 41000000 uImage \; tftp 42000000 xxx.dtb\; bootm 41000000 - 42000000
bootargs----uboot提供给操作系统内核,内核知道怎么执行----执行bootm时
bootargs /dev/nfs rootfs=xxxxx:/xxxxx/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=xxxxxx
3、uboot通过tftp下载内核和设备树
tftp 内存地址 uImage
tftp 内存地址 .dtb
4、uboot运行启动内核
bootm 内核地址 - 设备树地址
需要有内核和文件系统:
a、内核编译
步骤:
1、设置交叉编译环境
vim Makefile
ARCH ?= arm
CROSS_COMPILE ?= /home/ubuntu/tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-
2、选择soc,找到当前硬件对应的soc芯片,必须挑选适配于当前硬件
make exynos_defconfig
生成.config------编译配置(哪些要编译,哪些不编译,内核有哪些功能)
3、针对默认的soc芯片进行裁剪适配于当前硬件
make menuconfig
4、编译内核
make uImage
5、编译设备树:描述设备信息
找最相近的外设设备树,进行修改变成当前硬件设备的设备信息描述
以默认的dts作为参考,变为我们自己的dts
cp linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
vim linux-3.14/arch/arm/boot/dts/Makefile---添加编译
6、编译设备树
make dtbs
2、驱动开发:
编写驱动代码
1、用什么工具写代码
vim(任何编辑器)
查看代码工具(我们要知道Linux内核的函数原型(定义),才知道如何调用)
source insight---查看代码工具
安装source insight工具
根据文档提示安装软件
打开软件提示错误
https://blog.csdn.net/zanda_/article/details/82916744
要使用source insight工具用作查看内核代码:
在windows的文件夹下解压linux内核代码
1、设置新工程用作查看
project--->new project
在第一个对话框中,第一个文本框(行编辑器),输入工程的名字
在第二个对话框中,第一个本文框中选择刚解压的Linux内核源码目录(顶层linux-3.14),点击ok
在第三个对话框中,在对话框中选择要查看的目录/文件
需要选择的目录文件:
include
init
kernel
arch/arm/kernel
arch/arm/include/asm
driver/base
driver/char
driver/i2c
driver/spi
fs/char_dev.c
点击close关闭
重新选择project---->open project
选择刚才创建的工程名
ok
如果提示同步,则选择确认进行同步
2、写驱动
vim
记事本
使用source insight编写驱动代码
只要是驱动程序,就必须满足驱动框架,不然是没办法编译为驱动程序
驱动代码必须有4部分:
//1、头文件
#include <linux/init.h>
#include <linux/module.h>
//2、驱动入口函数的声明,在内核加载驱动时,执行哪个函数;在内核卸载驱动时,执行哪个函数
//声明:加载时的入口声明
module_init(hello_init);
//声明:卸载时的入口声明
module_exit(hello_exit);
//3、加载函数、卸载函数的实现
//加载函数的实现:当内核加载驱动(内核执行这个驱动时,就会调用的函数)
static int __init hello_init(void)
{
return 0;
}
//卸载函数的实现:当内核卸载驱动(内核删除这个驱动时,就会调用的函数)
static void __exit hello_exit(void)
{
}
//4、协议选择GPL
MODULE_LICENSE("GPL");
Linux内核如何使用模块驱动:
加载:
insmod 驱动程序路径.ko
查看:
lsmod:查看当前已经加载的驱动模块
卸载:
rmmod 驱动程序名
[root@farsight ]# rmmod 1st_drv
rmmod: can't change directory to '/lib/modules': No such file or directory
[root@farsight ]# mkdir /lib/modules--创建目录
[root@farsight ]# rmmod 1st_drv
rmmod: can't change directory to '3.14.0': No such file or directory
[root@farsight ]# mkdir /lib/modules/3.14.0--创建目录
[root@farsight ]# rmmod 1st_drv
[ 118.015000] hello exit func
驱动框架:
四部分组成
1、头文件部分
#include <linux/init.h>
#include <linux/module.h>
2、驱动的入口声明:
加载时的入口声明:当内核加载(安装)驱动时,执行的内容(函数)的声明
module_init(函数名);
卸载时的入口声明:当内核卸载(删除)驱动时,执行的函数
module_exit(函数名);
3、入口函数的实现
//加载函数
static int __init hello_init(void)
{
//加载驱动时执行
return 0;
}
//卸载函数
static void __exit hello_exit(void)
{
//卸载驱动时执行
}
//4、GPL协议
MODULE_LICENSE("GPL");
实现驱动后:通过makefile来进行编译
-----------------------------------------
字符设备驱动模型:字符设备驱动怎么写
驱动:
对于一个驱动而言需要有对应的驱动功能
作为字符设备驱动的要素:
1、必须要有一个设备号,用于在内核中的众多设备驱动进行区分
2、必须要有一个设备文件,用户必须知道设备驱动对应的设备节点(设备文件)
3、驱动对设备的操作,与应用程序中的系统调用关联,其实就是文件操作
应用空间调用open、read、write、ioctl函数,实际在驱动中调用对应的xxx_open、xxx_read等函数
实现字符设备框架:
a、作为一个字符设备驱动,必须有一个设备号----向内核申请设备号
int register_chrdev( unsigned int major,
const char *name,
const struct file_operations *fops)----注册设备号
参数1:
unsigned int major:主设备号,次设备号自动分配
无符号整数
设备号:32bit = 主设备号(12bit) + 次设备号(20bit)
主设备号:表示同一类设备
次设备号:表示同一类设备中的不同设备
参数2:
const char *name:描述一个设备驱动信息,自定义
字符串首地址
参数3:
const struct file_operations *fops:文件操作对象,函数关联(使用结构体来存储驱动与应用程序的关联)
结构体地址(指针)
返回值:
正确返回0,失败返回负数
b、创建一个设备节点(设备文件),能够提供给应用操作设备驱动
1、手动创建
mknod 设备节点名 设备类型 主设备号 次设备号
mknod /dev/led2 c 250 0
2、自动创建(通过udev/mdev机制)
struct class * class_create(owner,name);
参数1:
owner:拥有者,一般THIS_MODULE
参数2:
name:字符串,描述信息
返回值:
struct class *
信息结构体
//创建设备文件
struct device *device_create( struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)----创建设备节点
参数1:
struct class *class:class结构体,创建的设备文件的信息内容
通过 class_create()函数创建
参数2:
struct device *parent:表示父类对象,一般直接写NULL
结构体地址
参数3:
dev_t devt:设备号
dev_t(unsigned long)
参数4:
void *drvdata:私有数据,一般填NULL
参数5:
const char *fmt, ...:设备文件名
字符串首地址
返回值:
struct device *---------设备节点对象(设备文件描述)
成功返回地址,失败返回NULL
c、在驱动中实现文件io接口功能(与应用关联),应用程序调用文件io时,驱动程序也调用对应的文件io接口函数
在结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
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 (*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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};//函数指针的集合,
每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数
const struct file_operations fops = {
.open = led_open,//当应用程序调用open时,驱动则执行结构体成员open对应的赋值函数
.release = led_close,
};
d、应用程序操作驱动,应用程序调用文件io函数去控制驱动
e、应用和驱动传递数据
read、write
copy_to_user
copy_from_user
f、驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制
内核驱动是通过虚拟地址操作
初始化硬件
地址映射:
void * ioremap(cookie,size);
参数1:
cookie:物理地址
参数2:
size:映射内容大小,字节
返回值:
返回映射成功后的虚拟内存地址
操作虚拟内存地址中的内容就是操作对应的物理地址空间内容
1、编写字符设备驱动:能够控制硬件设备的驱动程序
步骤:
1、实现加载模块和卸载模块的函数实现
module_init(led_init);
module_exit(led_exit);
2、在模块加载函数中:当内核加载驱动时,申请是一个字符设备驱动
a、申请设备号,绑定文件io接口
设备号:内核中区分和管理不同的字符设备
register_chrdev(major,"led drv",&fops);
b、创建设备文件
//创建结构体
struct class * cls = class_create(THIS_MODULE,"led class");
//创建设备节点
struct device * dev = device_create(cls,NULL,250<<20|0,NULL,"led");
c、实现file_operations结构体,实现文件io接口----驱动程序能够和应用程序进行交互
提供功能给应用程序使用(调用文件IO函数)
const struct file_operations fops = {
.release = led_close,
.open = led_open,
.read = led_read,
.write = led_write,
};
d、硬件初始化-------驱动程序能够和硬件进行交互
1、地址映射
2、申请中断
3、实现硬件的寄存器初始化
e、实现硬件控制功能
应用程序在调用文件IO函数时,会把参数传递给驱动对应的函数参数
规范:
1、驱动卸载rmmod清空申请
在卸载入口中实现,清除
与初始化逆序过程进行卸载
//1、映射释放(中断释放)
iounmap(映射的虚拟内存地址);----释放映射地址
//2、释放设备文件
void device_destroy(struct class * class,dev_t devt);
//3、释放设备文件结构体
void class_destroy(struct class * cls)
/4、释放设备号
void unregister_chrdev(unsigned int major,const char * name)
2、面向对象的编程思想
用一个结构体来表示一个对象(驱动对象)
设计一个类型(结构体类型),描述设备驱动的信息
//设计结构体类型来表示整个驱动对象(描述设备驱动信息)
struct led_drv
{
unsigned int major;//主设备号
dev_t devno;//设备号
struct class * cls;//设备文件信息结构体
struct device* dev;//设备文件信息
unsigned int * gpx1con;//设备驱动映射的地址
unsigned int * gpx1dat;
};
//定义全局设备对象(存储信息)
struct led_drv led;
3、出错处理
在执行驱动操作时,有可能会出现错误不正确情况,需要对错误问题进行处理
ret = register_chrdev(led.major,"led drv",&fops);
if(ret < 0)//出错处理
{
printk("register devno error\n");
goto err_1;
//return -1;
}
led.cls = class_create(THIS_MODULE,"led class");
if(IS_ERR(led.cls))//判断出错
{
printk("class create error\n");
goto err_2;
//return -1;
}
........
err_5:
iounmap(led.gpx1con);
err_4:
device_destroy(led.cls,led.devno);
err_3:
class_destroy(led.cls);
err_2:
unregister_chrdev(led.major,"led drv");
err_1:
return -1;
------
实现蜂鸣器驱动
中断驱动---检测外部中断
获取外设的数据内容,通过中断信号进行获取
在驱动中设置外设为中断模式:当外设产生设定的特定信号(就是中断)
在驱动中实现中断处理操作(函数)
1、中断号:就是一个号码,中断控制器管理所有中断的编号,外设连接的引脚就对应了引脚的中断控制器的中断号
有硬件设备----设备的中断号
驱动如何获取中断号:
1、宏定义
IRQ_EINT(中断号)
2、设备树中描述,然后再驱动中获取
arch/arm/boot/dts/exynos4412-fs4412.dts
在设备树中添加硬件信息(包括中断)
硬件:
key3 ------GPX1_2--------EINT10
在设备树中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
gpx1: gpx1 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
#interrupt-cells = <2>;
};
在设备树中添加自己的硬件设备信息---添加key3节点-----描述当前设备的的信息内容(中断号)
arch/arm/boot/dts/exynos4412-fs4412.dts:实现硬件描述(中断号)
key3_node {
compatible = "key3";
interrupt-parent = <&gpx1>;
interrupts = <2 4>;//26
};
重新编译设备树
make dtbs
cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpdir/
2、实现驱动工作----中断驱动应该做什么
(外设产生中断,驱动要能检测到中断---申请中断(根据中断号))
(处理外设产生的中断)
在驱动中申请中断,实现中断处理
a、获取到中断号
获取设备树节点,返回值就是从设备树中找到的节点
struct device_node *of_find_node_by_path(const char *path);
从节点中获取到中断号,返回值就是中断号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index);
b、申请中断
typedef irqreturn_t (*irq_handler_t)(int, void *);----类型替换,irq_handler_t代表函数指针
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数1:
unsigned int irq:申请中断的中断号
参数2:
irqreturn_t (*)(int, void *) ---- irq_handler_t
irq_handler_t handler:函数指针,进行注册中断,当产生中断时调用对应的函数进行处理
参数3:
unsigned long flags:中断处理的触发方式
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
参数4:
const char *name:字符串首地址,中断的描述信息
/proc/inruppter
参数5:
void *dev:传递给参数2的函数进行自动调用的(作为参数2这个函数的参数)
返回值:
成功返回0,失败返回非0
释放中断:
void free_irq(unsigned int irq,void * dev_id)
参数1:
unsigned int irq
中断号
参数2:
void * dev_id:与申请中断第五个参数保持一致
4、驱动中要实现把硬件产生的数据传递给用户(应用程序)
a、驱动中获取到硬件的数据
通过中断获取到数据(中断处理函数中)
具体硬件对应着一个数据值
key2------'a'
key3------'q'
从硬件的寄存器中获取数据值
key3--------按下和抬起-------1/0
读取key3的gpio寄存器---数据寄存器
b、驱动传递给用户(应用)
驱动在文件io接口实现中,完成传递给应用
驱动在xxxx_read函数中将数据传递给用户
ret = copy_to_user(buf,&data,1);
c、用户如何拿到----编写应用程序
5、文件io模型
阻塞io模型------休眠等待
阻塞:当进程读取外部资源(数据),但是外部资源没有准备好,进程就进行休眠等待资源可用
在应用中:
read
wirte
accept
scanf
默认都是阻塞的
驱动中实现阻塞:
要创建等待队列头:
wait_queue_head_t head;
init_waitqueue_head(&head);
1、在需要等待的位置(没有数据),就阻塞等待
wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待
参数1:
wq:等待队列头,把当前进程加入到哪个等待队列中
参数2:
condition:是否执行阻塞等待的条件
condition:真---不进行阻塞
condition:假---进行阻塞
2、合适位置进行阻塞唤醒
wake_up_interruptible(&head);
文件io模型:
1、阻塞:阻塞io,当有数据时读取数据,没有数据时阻塞等待
2、非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回
应用程序:设置为非阻塞打开
int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);
驱动添加:
if((file->f_flags & O_NONBLOCK != 0) && (condition == 0))
return -1;
3、io多路复用
4、异步通知(异步信号):当有数据的时候,驱动就会发送信号(SIGIO)给应用,应用就可以异步接收数据,而不用主动接收(可以去完成其他工作)
a、应用程序----处理信号
void catch_signal(int signo)
{
char num;
if( read(fd,&num,1) < 0)
{
printf("no data;");
return ;
}
printf("data is %c\n",num);
}
//1、设置信号处理方式
signal(SIGIO,catch_signal);
//2、设置当前进程为SIGIO信号的属主进程
fcntl(fd,F_SETOWN,getpid());
//3、将io模式设置为异步模式
int flags = fcntl(fd,F_GETFL);
flags |= FASYNC;//添加异步属性
fcntl(fd,F_SETFL,flags);
b、驱动程序---发送信号
1、需要和应用进程进行关联-------信号要发送给谁
实现fasync接口,在接口中进行关联
int key_fasync (int fd , struct file * file, int no)
{
//记录信号发送给哪个应用
return fasync_helper(fd,file,no,&(key.fasync));
}
2、在合适位置发送信号
kill_fasync(&(key.fasync),SIGIO,POLLIN);
中断的下半部分:
1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能
2、tasklet:实际上就是内部调用了softirq
3、workqueue:工作队列
tasklet:
a、初始化
初始化任务队列
void tasklet_init(struct tasklet_struct * t,void(* func)(unsigned long),unsigned long data)
参数1:
struct tasklet_struct * t :任务队列头节点
参数2:
void(* func)(unsigned long):下半部分的实现逻辑
参数3:
unsigned long data:参数2函数的参数
b、在中断上半部分中,启动下半部分(放入内核线程中)
tasklet_schedule(struct tasklet_struct * t)
如:
tasklet_schedule(&(key.t));
workqueue:
a、初始化
初始化工作队列
INIT_WORK(struct work_struct *work,void (*work_func_t)(struct work_struct *work));
参数1:
struct work_struct *work:初始化的工作队列对象(头节点)
参数2:
void (*work_func_t)(struct work_struct *work):工作队列下半部分实现的逻辑
b、在中断上半部分中,启动下半部分(放入内核线程中)--启动
schedule_work(struct work_struct *work);
如:
schedule_work(&(key.mywork));
总线模型:
驱动框架:
0、声明实现入口函数(module_init、module_exit)
1、申请设备号(register_chrdev)
2、创建设备节点(class_create、device_create)
3、硬件初始化
ioremap地址映射
中断申请
4、实现文件IO接口
总线模型:
总线bus
驱动driver
设备device
总线bus:
struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配
struct bus_type
{
const char *name;:总线名字
int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否
};
注册总线:
int bus_register(struct bus_type * bus);
参数:
struct bus_type * bus:总线对象
注销总线:
void bus_unregister(struct bus_type * bus);
驱动driver:
struct device_driver :驱动对象,描述一个驱动,对驱动进行说明
struct device_driver {
const char *name;:驱动的名字
struct bus_type *bus;总线对象,表示要把驱动注册到哪条总线
int (*probe) (struct device *dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。)
int (*remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用
}
注册驱动到总线:
int driver_register(struct device_driver * drv);
从总线上注销:
void driver_unregister(struct device_driver * drv);
设备device:
struct device {
struct kobject kobj;//所有对象的父类
const char *init_name;设备名
struct bus_type *bus;总线对象,表示要把设备注册到哪条总线
void *platform_data;自定义数据,指向任意类型,可以存储设备信息
void (*release)(struct device *dev);设备对象从总线移除时会调用
};
注册设备到总线:
int device_register(struct device * dev);
从总线上注销:
void device_unregister(struct device * dev);
文件io模型:
1、阻塞:阻塞io,当有数据时读取数据,没有数据时阻塞等待
2、非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回
应用程序:设置为非阻塞打开
int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);
驱动添加:
if((file->f_flags & O_NONBLOCK != 0) && (condition == 0))
return -1;
3、io多路复用
4、异步通知(异步信号):当有数据的时候,驱动就会发送信号(SIGIO)给应用,应用就可以异步接收数据,而不用主动接收(可以去完成其他工作)
a、应用程序----处理信号
void catch_signal(int signo)
{
char num;
if( read(fd,&num,1) < 0)
{
printf("no data;");
return ;
}
printf("data is %c\n",num);
}
//1、设置信号处理方式
signal(SIGIO,catch_signal);
//2、设置当前进程为SIGIO信号的属主进程
fcntl(fd,F_SETOWN,getpid());
//3、将io模式设置为异步模式
int flags = fcntl(fd,F_GETFL);
flags |= FASYNC;//添加异步属性
fcntl(fd,F_SETFL,flags);
b、驱动程序---发送信号
1、需要和应用进程进行关联-------信号要发送给谁
实现fasync接口,在接口中进行关联
int key_fasync (int fd , struct file * file, int no)
{
//记录信号发送给哪个应用
return fasync_helper(fd,file,no,&(key.fasync));
}
2、在合适位置发送信号
kill_fasync(&(key.fasync),SIGIO,POLLIN);
中断的下半部分:
1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能
2、tasklet:实际上就是内部调用了softirq
3、workqueue:工作队列
tasklet:
a、初始化
初始化任务队列
void tasklet_init(struct tasklet_struct * t,void(* func)(unsigned long),unsigned long data)
参数1:
struct tasklet_struct * t :任务队列头节点
参数2:
void(* func)(unsigned long):下半部分的实现逻辑
参数3:
unsigned long data:参数2函数的参数
b、在中断上半部分中,启动下半部分(放入内核线程中)
tasklet_schedule(struct tasklet_struct * t)
如:
tasklet_schedule(&(key.t));
workqueue:
a、初始化
初始化工作队列
INIT_WORK(struct work_struct *work,void (*work_func_t)(struct work_struct *work));
参数1:
struct work_struct *work:初始化的工作队列对象(头节点)
参数2:
void (*work_func_t)(struct work_struct *work):工作队列下半部分实现的逻辑
b、在中断上半部分中,启动下半部分(放入内核线程中)--启动
schedule_work(struct work_struct *work);
如:
schedule_work(&(key.mywork));
总线模型:
驱动框架:
0、声明实现入口函数(module_init、module_exit)
1、申请设备号(register_chrdev)
2、创建设备节点(class_create、device_create)
3、硬件初始化
ioremap地址映射
中断申请
4、实现文件IO接口
总线模型:
总线bus
驱动driver
设备device
总线bus:
struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配
struct bus_type
{
const char *name;:总线名字
int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否
};
注册总线:
int bus_register(struct bus_type * bus);
参数:
struct bus_type * bus:总线对象
注销总线:
void bus_unregister(struct bus_type * bus);
驱动driver:
struct device_driver :驱动对象,描述一个驱动,对驱动进行说明
struct device_driver {
const char *name;:驱动的名字
struct bus_type *bus;总线对象,表示要把驱动注册到哪条总线
int (*probe) (struct device *dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。)
int (*remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用
}
注册驱动到总线:
int driver_register(struct device_driver * drv);
从总线上注销:
void driver_unregister(struct device_driver * drv);
设备device:
struct device {
struct kobject kobj;//所有对象的父类
const char *init_name;设备名
struct bus_type *bus;总线对象,表示要把设备注册到哪条总线
void *platform_data;自定义数据,指向任意类型,可以存储设备信息
void (*release)(struct device *dev);设备对象从总线移除时会调用
};
注册设备到总线:
int device_register(struct device * dev);
从总线上注销:
void device_unregister(struct device * dev);
总线模型:
led驱动
led驱动代码:
只有一份
led设备信息:
每个led都有一个设备信息
总线:
一条总线,挂载多个驱动和设备
编写针对多个led的驱动程序
--------
平台总线
用于升级
三星:2410、2440、6410、s5pc100、s5pv210、exynos4412
在平台进行升级时(硬件),部分的模块的控制方式,基本上是类似的,但是模块的地址有区别
gpio逻辑控制
1、配置gpio输入输出功能 gpxxcon
2、给gpio数据寄存器设置高低电平 gpxxdat
uart逻辑控制
1、设置功能:115200、8n1
基本操作都是类似
但是地址不同
问题:
当升级平台时,对于相似的设备驱动,需要编写多次驱动代码(没有总线),会造成大量重复的代码
解决:
引入平台总线
device(设备:设备信息--地址、中断号)和driver(驱动:操作逻辑----驱动功能)进行分离
在升级的时候只用修改device中的设备信息即可
实现一个driver代码就能够驱动多个平台相似的模块,修改很少的内容
总线bus:
不需要自己创建,在内核中已经使用
//平台总线对象
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
平台总线的匹配方法:platform_match
1、进行匹配platformdriver中的id_table,在驱动对象中的id_table就是列出支持哪些平台与平台对象名进行匹配
2、匹配驱动对象的名字和设备对象的名字
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
设备device:
平台对象:
struct platform_device {
const char *name;设备对象名,用于做匹配
int id;//一般都是直接给-1
struct device dev;//父类对象device
u32 num_resources;//资源个数
struct resource *resource;//资源(地址、中断):设备信息
}
/资源/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
注册设备对象到平台总线:
int platform_device_register(struct platform_device * pdev);
//注销平台设备
void platform_device_unregister(struct platform_device * pdev);
驱动driver:
平台驱动对象:
struct platform_driver {
int (*probe)(struct platform_device *);匹配成功后会调用
int (*remove)(struct platform_device *);//在匹配成功后,移除device设备,会调用
struct device_driver driver;
| const char *name;//驱动对象名字,可以用于匹配
const struct platform_device_id *id_table;//如果driver支持多个平台,在id_table中写出支持哪些平台,平台总线会用这个对象值用于与设备对象名字进行匹配
}
driver-----平台信息的描述(支持什么平台)
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];//平台设备名,用于进行匹配
kernel_ulong_t driver_data;
};
注册驱动对象到平台总线:
int platform_driver_register(struct platform_driver * drv);
注销:
void platform_driver_unregister(struct platform_driver * drv)
iic总线
mpu6050加速度陀螺仪
mpu6050-----I2C_SDA5和I2C_SCL5与芯片连接
通过芯片控制对应的i2c就可以与mpu6050传输数据
芯片i2c:
引脚:
GPB_2
GPB_3
i2c控制器:
i2c5
I2C5---------0x138B_0000
I2CCON------0x0000
I2CCON控制寄存器用于控制是否发出ACK信号、设置发送器的时钟、iic中断
I2CSTAT------0x0004
I2CSTAT状态寄存器用于选择iic的工作模式,发出S信号,发送P信号,使能发送/接收功能,并标识各种状态
I2CADD------0x0008
I2CDS--------0x000c
发送/接收移位寄存器
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
iic子系统框架
应用
----------------------------------------------------------
i2c driver:从设备驱动层
需要和应用进行交互
封装数据,不知道如何发送给硬件
----------------------------------------------------------
i2c core:i2c核心层 维护i2c bus 包括 i2c driver 、 i2c device链表
----------------------------------------------------------
i2c adapter:i2c 控制层,初始化i2c控制器
完成将数据写入或读出----从设备
不知道数据是什么,但是直到如何操作硬件
硬件信息-----device设备----添加到设备树
模板:
在内核 arch/arm/boot/dts/exynos4.dtsi
i2c_5: i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138B0000 0x100>;
interrupts = <0 63 0>;
clocks = <&clock 322>;
clock-names = "i2c";
status = "disabled";
};
i2c@13860000 {
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c0_bus>;
pinctrl-names = "default";
status = "okay";
s5m8767_pmic@66 {
compatible = "samsung,s5m8767-pmic";
reg = <0x66>;
}
}
根据参考,添加新的i2c从设备:添加i2c5控制器和它包含的从设备
i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};
实现 i2c driver 驱动 编写:
i2c驱动对象:
struct i2c_driver
{
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;//父类
|
const char *name;//驱动名字
const struct of_device_id *of_match_table;//与设备树设备进行匹配
/*struct of_device_id
{
char name[32];
char type[32];
char compatible[128];//设备树描述
const void *data;
}*/
}
注册驱动对象到i2c总线:
int i2c_add_driver(struct i2c_driver *driver);
注销驱动对象:
void i2c_del_driver(struct i2c_driver *driver);