第二期培训分享一些学习笔记
短短的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 -参数 压缩包 -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/0013739516305929606dd18361248578c67b8067c8c017b000
本帖最后由 Marquis 于 2014-9-24 19:48 编辑
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 -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文件添加相关配置,格式相同
本帖最后由 Marquis 于 2014-9-24 20:01 编辑
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、添加内核模块
#include<linux/init.h>
#include<linux/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(若很多资源信息,才判断索引是哪个资源信息)
本帖最后由 Marquis 于 2014-9-24 20:12 编辑
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如何去使用?
参看内核源码Documentation\i2c\instantiating-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_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!\n");
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);
参数五告诉客户端要发的服务器 {:soso_e179:}{:soso_e179:} 很不错、、 {:soso_e189:} Marquis 发表于 2014-9-24 20:12 static/image/common/back.gif
18、网络编程
TCP-》面向连接 UDP-》不面向连接
可靠 ...
{:soso_e179:}{:soso_e179:}{:soso_e179:}
20天能学这么多{:soso_e179:}
页:
[1]