短短的20多天培训,使我加深了对嵌入式linux的了解,谢谢你们的关照。
整理了一下之前的笔记,分享给大家。
1.嵌入式操作系统简介
1.1作用
文件系统《-应用程序-》进程
内存管理《- -》设备
网络管理《-
1.2地位
操作系统-》封装硬件层给应用层提供接口,让其调用
linux分为稳定版、开发版、发行版(内核+GNU+桌面)
UNIX-》BSD-》Darwin-》IOS
1.3应用领域
个人桌面、服务器、嵌入式(以应用为中心、以计算机技术为基础、软硬件可以裁减、可满足功能、成本、体积、功耗等标准的严格要求)
2.文件系统的目录结构
数据文件、程序文件、设备文件、网络文件
管理员、普通用户能访问的存储的指令-》bin
管理员能访问的存储的指令-》sbin
放不可以改的程序文件-》usr(头文件)
放可以改的程序文件-》var(锁定文件)
临时文件-》tmp
挂载点-》mnt、media(发行版不同)
本机系统信息-》proc
3.1 shell
作用:用户语言-计算机语言
cat进行编写要在编写的下一退出
ln创建文件链接,相当于快捷文件
grep查找 查着内容 查找所在文件 -n(显示的是第几行)(命令)
3.2 文件权限
drwxr-xr-x marquis marquisd表示为目录,l表示为链接,rwx为marquis权限 r-x为当前用户marquis权限,r-x为其它用户权限
chown更改哪些用户类型可以执行 chmod更改文件权限
tar打包 解压到某文件夹 -》tar -参数 压缩包 -get=”_blank”>C 路径 (命令)
tar大全:http://www.jb51.net/LINUXjishu/43356.html
mount 设备节点 挂载点 (命令)
ssh root@ip:文件所在路径 存放路径 (ssh到远程服务器下载文件到本地)
4、vim
git clone 代码地址(在github下载代码)
编辑模式
插入模式
最后一行模式(输入命令)
vi常用技巧
撤销:u
恢复:ctrl+r
复制1行:yy
复制n行:nyy
复制1个单词:yw
复制n个单词:nyw
剪切1行:dd
剪切n行:ndd
剪切1个单词:dw
剪切n个单词:ndw
黏贴:p
定位到文件的末尾:G
定位到文件的开头:gg
定位到第几行::800(定位到800行)
设在行号:set nu (set nonu)
o (下一行插入)
0跳到行首$跳到行尾
/+要找的内容,n向下切换页,N向上切换页
:e filename 打开文件进行编辑
:n1,n2 co n3 将n1到n2之间的内容cp到n3下
:n1,n2 d将n1到n2之间的内容删除
:s/p1/p2/g 将当前行的p1全部换成p2
:g/p1/s//p2/g 将所有p1换成p2
:n1,n2 s/p1/p2/g 将n1到n2之间 p1换成p2
vimctags插件(可以找到函数定义)apt-get install ctags
5、gcc
预处理器(展开头文件及宏定义)-gcc编译器:编译c
g++编译器:编译c++
二进制工具:as(汇编器)、ld(链接器)
gcc编译过程
——————————————————————————–
| |
hello.c———hello.i——汇编代码——–目标代码——–可执行文件
a预处理 b编译 c汇编器 d链接器
a、gcc -E hello.c -o hello.i
b、gcc -S hello.i -o hello.s
c、gcc -C hello.s -o hello.o
d、gcc -o hello hello.o
编译常用参数:
-o 指定了生成文件的名称
-Wall 生成所有警告信息(最后加)
-w 不生成警告信息(最后加)
-static 不支持动态共享库,把函数库内容静态链接到可执行程序中
-shared 生成支持动态库的可执行程序
-I指定额外的头文件的搜索路径
-L 指定额外的库函数的搜索路径
动态库、静态库
静态链接
优点:依赖小、兼容性好。缺点:程序大、库更新程序需重新编译
动态链接
优点:程序小,进程可共享
缺点:依赖动态库,不能独立运行
制作静态链接库(在linux中后缀为.a,以lib开头,如libmylib.a)
先制作源文件(include/mylib.h、lib/mylib.c、test.c)
gcc -c mylib.c -o mylib.o 编译目标文件
ar rc libmylib.a mylib.o 制作静态库
使用静态库 1:
gcc -o test test.c -I include(头文件路径) -L lib(库文件路径) -lmylib
制作动态链接库
gcc -shared mylib.c -o libmylib.so
使用动态库1
gcc -o test test.c -I include(头文件路径) -L lib(库文件路径) -lmylib
编译通过,运行时出错,编译时找到了库函数,但链接时找不到库,执行以下操作,把当前目录加入搜索路径
export LD_LIBRARY_PATH=.so文件的路径LD_LIBRARY_PATH
6、交叉编译器
搭建即安装、配置交叉编译工具链。
宿主机在该环境下编译出嵌入式Linux系统所需的操作系统应用程序等,然后再上传到目标机上。
快速搭建交叉编译环境
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential
sudo apt-get install gcc
sudo apt-get install gcc-arm-linux-gnueabihf-
sudo apt-get install file
apt-get 基本命令
sudo apt-get update 更新源
sudo apt-get upgrade 更新已安装的包
sudo apt-cache search package 搜索包
sudo apt-cache show package 获取包的相关信息,如说明、大小、版本等
sudo apt-get install package 安装包
sudo apt-get install package – – reinstall 重新安装包
sudo apt-get remove package 删除包
sudo apt-get source package 下载该包的源代码
diff 命令常用来比较文件、目录,也可以用来制作补丁文件。
diff -urNwB 1.c 2.c 3.diff
patch命令被用来打补 ,就是根据补丁文件中指明了要修改的文件的路径
patch 3.diff
7、git
代码工具、分散式、版本控制系统
git config –global user.name “git用户名”
git config –global user.email (git登陆邮箱)
git init 初始化命令
git status 查看git仓库状态
git add 文件 将文件放进暂存区(文件修改后要重新add、因为执行的还是原来暂存的)
显示 modified(被修改)
git remote add origin https://github.com/code-marquis/git-test.git
git push -u origin master(将本地上的 origin 提交到github、master总分支)
git branch分支名创建分支
git checkout分支名切换分支
git merge分支名合并分支
git log 查看提交日志
git tag 标签
在子分支下添加、修改、提交保存创建的文件。在切换的主分支是看不到在子分支创建的文件
但分支合并有文件冲突后 能在文件标示冲突信息,需要修改冲突文件
在冲突还没有解决的话,不能切换到其它分支
在远程仓库创建分支
git push 到远程仓库 只是提交最后一次的操作
git push origin test-project0.1(分支) 将子分子放到远程仓库
详细方法:http://www.liaoxuefeng.com/wiki/ … 8c67b8067c8c017b000
8、Makefile
规则:“被修改”
(目标文件:依赖文件)
a.o : a.c
gcc -c -o a.o a.c (当a.o的时间比a.c的要老。判断a.c已经被修改)
简化
test : test.o a.o b.o
gcc -o $@ $^
%.o : %.c
gcc -c -o $@ $($@ $^ $ 自动变量)
gcc -o test test.c a.c
test.c 预处理
编译
汇编 -》1.0
a.c -》2.0
链接 test.ca.c
gcc -c -o test.o test.c
gcc -c -o a.o a.c(修改a.c 只需要执行这一句及链接)
gcc -o test test.o a.o
(1)初始
gcc -c -o 1.o 1.c
gcc -c -o 2.o 2.c
…
gcc -c -o 1000.o 1000.c
gcc -o test 1.o 2.o …1500.o
(2)修改1500.c
gcc -c -o 1500.o 1500.c
gcc -o test 1.o 2.o …1500.o
有的编译出.o文件需要加上头文件才能更新此文件
%.o : %.c %.h
gcc -c -o $@ $
简化(带.h文件)
test : test1.o a.o b.o c.o
gcc -o $@ $^
%.o : %.c
gcc -c -Wp,-MD,$@.d -o $@ $
//完整1
objs = test1.o a.o b.o c.o
test : $(objs)
gcc -o $@ $^
%.o : %.c
gcc -c -Wp,-MD,$@.d -o $@ $
deps := $(foreach v, $(objs), $(v).d)
(循环查找objs.d文件)
deps := $(wildcard $(deps))
(判断文件是否对应)
ifneq ($(deps),)
(判断存在)
include $(deps)
(将.o.d文件的内容包含进来)
endif
clean:
(清除文件)
rm $(objs) $(deps) *.d
对于子目录,最好是进入子目录在make
make -f makefile重命名
.phony 假象目标,目标后面的命令永远成立
9、系统启动与Bootloader
soc(ARM)-》bootloader-》kernel-》挂载rootfs-》应用程序
驱动-》DDR控制器、声卡控制器、网卡控制器
BROM 32k 一小段程序
BROM-》SDC-》NAND-》SDC2-》SPI ANAD
-》 -》 -》
-》bootloader(SPL-》uboot)-》kernel-》rootfs
SPL程序流程如下:
初始化ARM处理器
初始化串口控制台
配置时钟和最基础的分频
初始化SDRAM
配置引脚多路复用功能
启动设备初始化(即上面选择的启动设备)
加载完整的uboot程序并转交控制权
u-boot编译
make mrproper (先清除依赖)
make cubietruck_config 配置 -》makefileCubietruck_config ::
目标 @$(mkconfig)
MKCONFIG :=$(CURDIR)/mkconfig
当前目录(定义好)
mkconfig-ACubietruck
CONFIG_NAME=sun7i $#8
BOARD_NAME=Cubietruck $1Ative
arch=arm $2arm
cpu=armv7
board=sunxi soc=sunxi tmp=sunxi options=CUBIE…….
uboot烧写进TF卡
先去除挂载的tf卡 umount /dev/..
将 SD 卡插入读卡器,插进 PC
$umount /media/XXX
// 卸载分区挂载,可能不止一个分区,XXX 换成正确
的路径,也有可以 PC 不自动挂载 SD 卡
$sudo fdisk -l 看 SD 卡在哪个设备节点
$card=/dev/sdc
(TF卡路径)
dd if=要烧写的文件 of=/dev/sdc(TF卡路径) bs=1M count=1
$fdisk /dev/sdc(TF卡路径) 分区
格式化分区:
$sudo mkfs.vfat ${card}1
$sudo mkfs.ext4 ${card}2
#需要稍等片刻
然后写入 bootloader :
$cd u-boot-sunxi/
$sudo dd if=u-boot-sunxi-with-spl.bin of=$card bs=1M seek=8
bs:块大小 seek:指定跳过8kb
拔出读卡器,将卡插进 CT,插电启动
sync (在拔出u盘前,将内存数据写进TF卡)
10、内核
拷贝kernel_deconfig到linux源代码根目录下。(cp kernel_deconfig sun-linux/.config
或代码根目录:make sun7i_defconfig,获取到sun7i对应的.config)
配置参数 .config
make mrproper(删除所有的编译生成文件, 还有内核配置文件, 再加上各种备份文件)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 uImage modules(驱动模块)
编译出的内核文件:arch/arm/boot/uImage和准备好的 uEvn.txt boot.scr script.bin 复制到sd卡的第一分区
文件系统启动阶段
断电,拔出 SD,插回 PC,正常会自动挂载,将编译内核生成的 modules 安装第二分区(将XXX 改为正确路径)
$cd kernel-source
$sudo make INSTALL_MOD_PATH=/media/XXX ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install
$cd ..
$sudo tar -get=”_blank”>C /media/XXX –strip-components=1 -zxvf linaro-quantal-alip-20130422-342.tar.gz
$sync
uboot————》kernel-》
配置参数
11、驱动模块编译
make sunxi7_defconfig
在linux源码根目录生成.config配置文件
make menuconfig ARCH=arm
-M-修改成驱动模块,编译出来,保存
保存之后,让他准备一下:
make prepare
make scripts
4.现在我们进入要编译的驱动的源码目录,比如sun4i-gpio.c在 drivers/misc 目录下:
cd /home/lany/workspace/linux-sunxi/drivers/misc/
make -C /home/lany/workspace/linux-sunxi/ M=`pwd` modules
modinfo检查模块版本
驱动添加
在驱动所在目录 makefile 后面按相同格式添加,并把驱动.c、.h等源文件加进这个目录下
驱动目录下的Kconfig (bool Y N)会被menuconfig调用,在驱动添加的时候,也要在kconfig文件添加相关配置,格式相同
12、根文件系统
更换根文件系统,需要更换对应uImage
kernel———–》rootfs
/sbin/init(第一个用户态程序)
busybox(简单文件系统)
制作:
sudo wget http://busybox.net/downloads/busybox-1.21.1.tar.bz2
sudo tar jxvf busybox-1.21.1.tar.bz2
cd busybox-1.21.1
sudo make menuconfig ARCH=arm
set busybox settings → build option → Cross Compiler prefix to “arm-linux-gnueabihf-”
sudo make
sudo make install
sudo mount /dev/sdb2 /mnt
sudo cp -Rv _install/* /mnt
sudo cp -Rv examples/bootfloppy/etc /mnt
cd /mnt
sudo mkdir dev proc sys var home tmp mnt run boot boot2 dev/pts
sudo rm etc/fstab
sudo vi etc/fstab
加入:
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
devpts /dev/pts devpts gid=4,mode=620 0 0
tmpfs /tmp tmpfs defaults 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
/dev/mmcblk0p1 /boot2 vfat defaults 0 2
/dev/mmcblk0p2 / ext4 defaults,noatime 0 1
保存退出
sudo chmod 777 etc/fstab
在busybox添加源码,/archivals
13、添加内核模块
#includelinux/init.h
#includelinux/module.h (必要头文件)
驱动模块
insmod xx.ko (加载到内存)
rmmod xxx.ko(自动找到模块并加载)modprobexxx
查看 lsmod
(查看模块信息)modinfo xxx.ko
同内核版本,非同一套内核可能编译出来的.ko无法使用。
在加载模块报错:“no symbol version for module_layout”
解决办法:在linux源码根目录添加文件Module.symvers
14、内核剪裁
硬件-》iRom(有些ARM没有)-》uboot(调用thekernel引导内核)-》kernel-》rootfs-》应用
linux内核从存储外设(硬盘/SD/Nand)-》拷贝到内存运行
初始化系统时钟 初始化串口(UART,调试程序) 点亮lcd 初始化lcd 初始化外设
初始化内存 重定位kernel the kernel
看门狗:定时检测系统是否异常
uboot 启动流程(2440)(start.s)
(关闭看门狗、有些芯片已经默认关闭)
初始化系统时钟、初始化内存、初始化nand、重定位kernel、初始化UART、thekernel(0,362,0x30000100)
(2440)物理内存起始地址:0x300000000
362:机器ID, 0x30000100:启动参数首地址、指定kernel取参数的内存地址
commmandline_tag(“noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”)
设置rootfs地址 ,应用程序的第一个应用init ,console 控制台的打印方式
内核启动流程(head.s)
head.s
ENTRY(入口)、_lookup_processor_type(判断linux是否支持此cpu)
_lookup_machine_type(判断linux是否支持该单板)
_creat_page_tables(创建页表)
_enable mmu(使能mmu)
_switch_data (数据处理)
start_kernel(初始化工作)
set_arch(取出启动地址)
rest_init(启动应用程序init)
物理地址———————虚拟地址
映射到(MMU)
物理地址 虚拟地址
0x0~ox100000 0x100000~0x200000
通过.的物理地址和.的虚拟地址相减获得偏移值,计算_arch_info_begin(虚拟地址和.的偏移值相加获得它的实际地址)和_arch_info_end(虚拟地址和.的偏移值相加获得它的实际地址)的实际地址
_arch_info_begin
*(.arch.info.init) (结构体)
_arch_info_end
.c参数 1 2 3
.s r0r1r2
thekernel–vmlinux-zImage、 uImage -》自解压代码+vmlinux
判断linux是否支持此cpu 判断linux是否支持该单板创建交表使能mmu处理一些数据start_kernel(初始化工作) 挂载文件系统/启动应用
block :块设备 drivers:驱动目录 fs:文件系统目录 init:初始化相关的函数
crypto:加密文件Documentation:帮助文档 include:头文件ipc:通信
kernel:本身管理代码 lib:库文件 mm:存储管理 net:网络设备、协议代码
scripts:脚本文件 security:密码相关 sound:声卡相关程序
内核编译方法
Kconfig-》
make menuconfig (y编译进内核、m编译为模块、n不编译)
make config(命令行)
make xconfig(需要依赖qt库)
make sun7i_defconfig (默认.defconfig配置文件/arch/arm/config/)
-》.config -》Makefile
make 生成zImage
make clean清理
Kconfig:
menu(在menuconfig生成目录)
depends on(依赖关系,只有选中才会出现menu)
configXXX(在.config生成CONFIG_XXX)
bool “XXX” 只能配置成Y、N
tristate “XXX” 可以配置为Y、N、M
Makefile:
obj-$(CONFIG_hello_world) += hello.o
CONFIG_hello_world == confighello_world(Kconfig)
在上层driver/Kconfig 加上source “drivers/hello/Kconfig”driver/makefile加上
obj-y += hello/
14、开发板测试固件
测试固件:1、体积小
2、系统启动快
3、测试硬件功能
测试程序-脚本(查找设备相关文件是否存在) infow:绘制命令 logw:打印信息
开机启动—-ir
—-绘制硬件测试信息(硬件文件检测)
—-测试网卡、声卡
rcS加上ifconfig 文件(将打印信息存入文件里)
15、GPIO驱动
硬件—-》kernel—-》lib—》app
swi api
会在/dev/ 下创建设备节点(操作硬件-open()–打开该硬件设备节点)
MODULE_AUTHOR()驱动程序作者
MODULE_DESCRIPTION()驱动说明
dmesg(查看驱动日志)
lodcat(查看应用日志)
驱动框架
驱动入口、出口函数—-定义出、设置file_operations—–定义要用到的函数(open()、write())—–注册—–创建设备节点(mknod XX(设备节点名) c(c字符设备,b块设备)主设备号 0),
man 函数名(查看头文件)
register_chrdev(主设备号,驱动名,file_operations名)(注册)
unregister_chrdev(主设备号,驱动名)
open(设备节点路径,权限(O_WDWR))write(fd,a,1); fd:句柄 , a:要写的数据(传到驱动write的buf指针保存),1:长度
copy_from_user():获取从应用传来的值
//自动创建设备节点
hello_dev_class = class_create(THIS_MODULE,”hello”);
device_create(hello_dev_class,NULL,MKDEV(HELLO_MAJOR,0),NULL,”hello”);//dev/hello
自动创建设备节点
ioctl:应用程序控制硬件驱动
IS_ERR() (调试)
PULL:控制上拉电阻、下拉电阻
DIR:方向寄存器(配置输入还是输出)
数据寄存器
CONFIG寄存器
PH21—-LED1 PH20—–LED2
PH_CFG2
PH21_select001 PH20_select001
映射地址
PH_DATA :用到的led在芯片引用的管脚,如PH21 则在21为置1则置高。
16、设备驱动模型—平台总线
驱动程序由两部分组成:硬件相关内容和纯软件(对硬件进行操作)的通用部分
Linux内核分离思想
就是将硬件相关内容和纯软件(对硬件进行操作)的通用部分分离开,让驱动开发者关注硬件相关的部分,而通用的操作流程(open、read……)基本不需要维护和修改
平台总线,platform_bus,虚拟的,而非硬件真实存在的总线
平台总线维护着两个链表:dev,drv
dev链表里存放硬件相关的信息
drv链表里存放对硬件操作的流程(纯软件部分)
平台总线里面有一个match函数,它进行dev和drv链表的两两匹配工作,通过dev的name和drv的name匹配,如果匹配成功会调用drv里面的probe函数,probe函数的实现由驱动开发作者实现,而具体的硬件信息在匹配时候已经得到
匹配过程:
当向总线注册dev信息(添加节点到dev链表)同时会从drv链表里取出每一个drv节点,跟自己的dev的name进行匹配,如果匹配成功会调用drv的probe
当向总线注册drv信息(添加节点到drv链表)同时会从dev链表里取出每一个dev节点,跟自己的drv的name进行匹配,如果匹配成功会调用drv的probe
dev和drv链表对应的结构?
grep -rhnw platform_bus_type ./*
(搜索平台总线结构体)
dev对应结构:struct platform_device
里面至少有个name信息
里面有硬件相关信息:struct reource
drv对应结构:struct platform_driver
有probe函数remove(卸载函数)driver(里面有个name,跟platform_device的name匹配)
如何注册dev和drv?
platform_device_register 向内核注册一个platform_device
platform_driver_register 向内核注册一个platform_driver
Probe如何获得硬件资源?
获取的是硬件对应dev的结构体
probe函数的行参数pdev指针就是指向开始注册的platform_device结构
这个结构体本身在初始化时候就包含硬件相关信息(struct reource可用这个存放自己特定的硬件信息),后续驱动开发者根据需求实现probe的编写,例如:注册一个字符设备
错误: ‘led_probe’未声明(不在函数内)
解决办法:将 static int led_probe(struct platform_device *pdev)写在static struct platform_driver led_driver={}上面
struct reource硬件资源的信息
.start:起始位(寄存器物理地址)
.end:终止位
.flags:标志,资源类型(内存:IORESOURCE_MEN,中断:IORESOURCE_IRQ,DMA通道:IORESOURCE_DMA)
res=platfrom_get_resource(dev,IORESOURCE_MEN,0);
dev:指向注册好的 platfrom_device结构
资源信息 IORESOURCE_MEN
索引0(若很多资源信息,才判断索引是哪个资源信息)
17、I2C
i2c_sunxi_xfer主要7位地址 第一位地址为设备地址
SCL:时钟线,时钟是由CPU(主端)提供
SDA:数据线,可以是输入或输出
I2C总线可以连接很多设备,cpu如何进行区分?
设备地址:唯一性,一般由芯片厂家定义。由芯片手册和硬件原理图共同决定
START信号
STOP信号
ACK信号,反馈信号,应答信号为低电平时,规定为有效应答位
芯片手册看时序
读写操作要根据芯片手册来进行
数据操作流程一定要看芯片手册细节:
以AT24C04为例子:
随机写一个数据到某一个地址:
1. CPU发射起始地址
2.CPU发射设备地址+写位(0)
3.CPU接收到ACK
4.CPU发送要写的芯片上的地址
5.CPU接收到ACK
6.CPU发送数据
7.CPU接收到ACK
8.CPU发送STOP信号
随机读:
1. CPU发射起始地址
9.CPU发射设备地址+写位(0)
10.CPU接收到ACK
11.CPU发送要写的芯片上的地址
12.CPU接收到ACK
13.CPU发送起始信号
14.CPU发射设备地址+读写位(1)
15.NO ACK
16.CPU发送STOP信号
时钟线和数据线如何搭配?
数据线上的数据改变在SCL为低电平时进行
从数据线上取数在SCL为高电平时进行
linux I2C子系统架构
I2C驱动架构
很多CPU内部已经集成了I2C控制器,也表示I2C的时序可由控制器发起,总线驱动的人要操作控制器的寄存器,不需要操作相应的时序
linux内核驱动框架
linux总线驱动:
它管理的对象是I2C控制器,这个驱动程序只是负责数据的传输,而不关心数据的具体含义,一般总线驱动都由厂家来提供,如果cpu中集成了I2C控制器并且linux内核支持这个CPU,总线驱动方面内核已经做好了
linux设备驱动:
它管理的对象I2C从设备,这个驱动程序关注的具体数据含义,怎么传输丢给总线驱动去做,
常用的一些也都包含内核中。
驱动框架:
app:open read write …..
********************
I2C设备驱动
at2404_open,at2404_read ,at2404_write
********************
内核提供统一的访问I2C总线的接口
i2c_transfer //老接口
SMBUS //新的接口,内核鼓励使用,兼容老的接口
********************
i2c 总线驱动 具体关注如何传输数据
********************
sunxi i2c 控制器
I2C设备驱动的实现
make menuconfig 选上总线驱动
i2c设备驱动模型:
设备-总线-驱动
内核为其定义一个虚拟的总线i2c_bus_type
这个总线上有2个链表:dev链表和drv链表
dev链表:存放的是i2c_client结构,描述硬件相关信息(最主要是设备地址)
drv链表:存放的是i2c_driver结构,描述软件相关信息(对i2c设备的操作)
每当i2c_client或者i2c_client.name跟i2c_driver.id_table.name两两比较,如果比较成功,调用
i2c_driver里面probe函数,将i2c_client的地址传递给probe函数,peobe函数具体做什么,由需求来决定,比如:注册一个
字符设备,这些内容跟platform总线思路一样
i2c_driver如何去使用
1.分配初始化i2c_driver
.probe
.remove
.id_table//终点是其中的name字段,一定要和i2c_client要一样
最终才能会匹配成功,调用probe函数
2.注册i2c_driver
3. 调用i2c_add_driver进行注册
画图分析。
进内核看别人怎么用?(./drivers/media/video/sun4i_csi/device/ov7670.c,哪个name重要)
i2c_client如何去使用?
参看内核源码Documentationi2cinstantiating-devices,关注方法1,2
1 总线号声明i2c设备
static struct i2c_board_info __initdata h4_i2c_board_info[] = {
{
I2C_BOARD_INFO(“isp1301_omap”, 0x2d),
.irq = OMAP_GPIO_IRQ(125),
},
{ /* EEPROM on mainboard */
I2C_BOARD_INFO(“24c01”, 0x52),
.platform_data= m24c01,
},
{ /* EEPROM on cpu card */
I2C_BOARD_INFO(“24c01”, 0x57),
.platform_data= m24c01,
},
};
static void __init omap_h4_init(void)
{
(…)
i2c_register_board_info(1, h4_i2c_board_info,
ARRAY_SIZE(h4_i2c_board_info));
(…)
}
如何分配初始化注册i2c_client:
方法1:
1.分配初始化i2c_board_info结构
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //最终会赋值给i2c_client.name
unsigned short flags; //对设备的操作方式:读/写
unsigned short addr; //设备地址
void *platform_data;//传递硬件相关的私有数据信息
}
};
初始化:
关键是type,addr如果有其他硬件信息,给platform_data
2.注册分配初始化好的i2c_board_info
o(int busnum,struct i2c_board_info const *info, unsigned len)
busnum:这个实参从硬件原理图可知,当前设备挂接到哪个总线上
info:指向分配初始化好的i2c_board_info
len:有多少项
设备驱动 M注册添加硬件资源
总线驱动 *遍历寻找资源
3 提问:注册完这些I2C设备硬件信息以后,何时8何地使用__i2c_board_list链表??
答: 内核初始化时候,会初始化I2C总线驱动,总线驱动由
i2c_adapter结构体维护。并通过i2c_regiser_adapter来注册,注册时,会调用i2c_scan_static_board_info函数,其实就是来扫描有哪些i2c设备。如果扫描到以后,去创建i2c_client.
这种方法不适合采用insmod来动态加载。
一般来说,使用此法规则:
关于i2c_board_info的分配,初始化,注册一般都是在平台代码中完成,
切记:内核会帮你分配初始化注册i2c_client,以后I2C设备驱动
在向内核注册i2c_driver的时候,就会进行匹配。
记号,以上代码执行的顺序要早于I2C总线驱动初始化
i2c_client:
1.包含I2C设备的硬件信息
2.包含I2C总线驱动的信息(操作I2C控制器的实现)
方法2:看那个文档。
通过i2c_new_device直接去定义分配一个i2c_client
Example (from the sfe4001 network driver):
static struct i2c_board_info sfe4001_hwmon_info = {
I2C_BOARD_INFO(“max6647”, 0x4e),
};
int sfe4001_init(struct efx_nic *efx)
{
(…)
efx-board_info.hwmon_client =
i2c_new_device(efx-i2c_adap, sfe4001_hwmon_info);
(…)
}
1.strut i2c_client *client;
分配初始化一个i2c_board_info
2.client = i2c_new_device(i2c总线驱动adapter, i2c_board_info);
总结:
创建的i2c_client中相关的字段说明
.adapter = 指向总线驱动中注册i2c_adapter
.addr = 存储了设备的设备地址
.name = 设备名称,用于跟i2c_driver.id_table.name做比较
client创建完name开始匹配 ,probe。。。
案例:利用I2C设备驱动框架实现eeprom at24c04的驱动
开始写。看内核。vim ./drivers/video/backlight/tosa_lcd.c
vim ./drivers/input/touchscreen/ft5x_ts.c
i2c_master_send
问:一个实参总线驱动如何获取:
struct i2c_adapter *i2c_adap;
获取总线驱动信息
i2c_adap = i2c_get_adapter(总线编号);
释放总线驱动信息
i2c_put_adapter(i2c_adap);
数据传输流程:
1.应用程序请求一个I2C操作
2 设备驱动程序根据请求构造i2c传输数据包
3 设备驱动调用内核提供的统一接口i2c_transfer/smbus接口,将构造好的数据包发给总线驱动(i2c_adapter.algo.master_xfer)
4 最终总线驱动根据数据包的的内容(读还是写,还有数据信息),通过I2C控制器将数据在总线上进行传输。
接口内核文档:i2c_transfer (参考)
/smbus接口
i2c_smbus_write_byte_data()
i2c_smbus_read_word_data()
用在.write、.read
18、网络编程
TCP-》面向连接 UDP-》不面向连接
可靠 不可靠
(传文件、准确数据)
客户端(主动发请求) 服务器(被动反应)
TCP客户端流程:
iSocketClient = Socket(AF_INET,SOCK_STREAM,0);
创建socket句柄参数1 表示用ipv4
iRet = connect(iSocketClient, (const struct sockaddr *)tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf(“connect error!
“);
return -1;
}
与服务器连接
tSocketServerAddr结构体
可以开始传数据(发数据:write/send ,接受数据:read/recv)
fgets(ucSendBuf, 999, stdin) 获取用户输入的数据
send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);第二个参数:要发的数据
TCP服务器端流程:
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
创建socket句柄 判断它的返回值
iRet = bind(iSocketServer, (const struct sockaddr *)tSocketServerAddr, sizeof(struct sockaddr));
绑定端口号、ip地址
tSocketServerAddr结构体
tSocketServerAddr.sin_family = AF_INET;//TCP
tSocketServerAddr.sin_port = htons(SERVER_PORT);
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
开始监听
iRet = listen(iSocketServer, BACKLOG);
#define BACKLOG 10
表示可以有10个客户端跟服务器连接
iSocketClient = accept(iSocketServer, (struct sockaddr *)tSocketClientAddr, iAddrLen);
等待客户端来连接 (没有客户端连接,此处会阻塞)
tSocketClientAddr存放客户端信息
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
接收数据 ucRecvBuf 存放接收到的数据
UDP服务端流程:
创建socket句柄
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
绑定服务器的ip和端口:
iRet = bind(iSocketServer, (const struct sockaddr *)tSocketServerAddr, sizeof(struct sockaddr));
接收数据:
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)tSocketClientAddr, iAddrLen);
第二个参数是存放接收到的数据,第三个参数是接收的字节数
UDP客户端流程:
创建socket句柄
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
收发数据:
iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0, (const struct sockaddr *)tSocketServerAddr, iAddrLen);
参数五告诉客户端要发的服务器
总结的非常不错、、