1.1 开发前准备

1.1.1 Linux 驱动(面向对象)

1).Linux 驱动框架

思想:写驱动的时候,只提供操作硬件设备的函数接口

文件存放磁盘: open ,read ,write ,close

ARM exynos4412 origen(母板)

裸机驱动:(硬件操作 + 功能需求)

AD/I2C/UART/PWM(Timer + OUT)/LED/KEY (阅读原理图,芯片手册,编码控制硬件)

2).Linux 硬件子系统

3).动手编写驱动(学习)

实际工作的时候,更多调试

4).学习方法

​ [1]内核提供的函数接口(熟悉),参照别人的写法,来编写自己

​ [2]对linux系统的思想认识(大牛灌输)

​ [3]动手(一点一点加)

​ [4]思考

1.1.2 Linux 底层课程

1).系统移植(bootloader , Linux kernel , device tree , fs)

2).驱动编写

1.1.3 Source Insight 环境搭建

Linux 内核文件添加,“add Tree” 是添加包含该文件夹路径下的全部文件。

img

同步文件,类似之前linux ubuntu系统下的tags功能,方便跟踪函数定义

img

img

同步过程可能需要几分钟。。。。。。

img

Options ->document options 设置字体大小

img

img

代码显示风格设置

img

1.2 Linux 驱动模块编程框架

编写linux驱动模块的基本步骤:

<1>头文件

include <linux/init.h>

#include <linux/module.h>

<2>许可权限GPL

MODULE_LICENSE("GPL");

<3>模块入口函数

int xxx_init(void)

{

​ ....

​ return 0;

}

<4>模块出口函数

void xxx_exit(void)

{

​ ....

}

<5>模块入口

module_init(xxx_init);

<6>模块出口

module_exit(xxx_exit);

1.3 LED驱动——模块化编程

1.3.1 模块化编程基本思路

1).阅读原理图

2).查看datasheet

3).编写模块

4).编译模块

思想:

​ Linux 内核源码的编译系统可以编译我们编写的模块代码。

第一种(产品发布):

​ 将自己编写的代码,拷贝到Linux内核源码树下,然后配置编译,编译进内核。

第二种(驱动调试):

​ 自己编写Makefile,然后使用Linux内核的编译系统,编译自己的模块代码。

问1:Linux内核的编译系统在哪里?

​ 答:

<1>Linux内核源码下的Makefile

​ 注意:你的Linux内核源码必须已经根据自己所开发的平台进行了配置

[1]修改了Makefile,指定了开发工具链(cross compile )

img

[2]已经使用Linux内核默认配置文件进行了配置

<2>ubuntu系统自带的Linux内核编译系统(pc机,x86)

/lib/modules/3.13.0-32-generic/build/Makefile

img

问2:如何在自己编写的Makefile中使用Linux内核的编译系统?

​ 答:make -C linux内核编译系统的路径 M=需要编译的模块代码路径 modules

​ ifeq ($(KERNELRELEASE),)

​ KERNEL_BUILD = /lib/modules/$(shell uname -r)/build

​ MODULE_PATH := $(shell pwd)

​ module:

​ $(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) modules

​ clean:

​ $(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) clean

​ else

​ obj-m = led-driver.o

​ endif

img

img

install:

#拷贝led-driver.ko 到共享目录

cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs

#用ARM gcc 编译器test.c

arm-none-linux-gnueabi-gcc test.c -o test

#拷贝可执行文件test到共享目录

cp test /home/ubuntu/workdir/fs4412/fs/rootfs

#make install 自动执行以上三个步骤

1.3.2 Linux 常用驱动模块操作shell命令

查看设备中已经注册的设备号

img

sudo insmod led-driver.ko 安装模块

cat /proc/devices 查看设备中已经注册的设备号

sudo rmmod led-driver.ko 卸载模块

dmesg 查看内核空间printk打印的信息

img

modinfo led-driver.ko 查看模块包含的信息

img

lsmod 查看系统中的模块

img

make

make clean 清除之前编译的可执行文件及配置文件。

img

dmesg | tail

img

cd /sys/class 查看已经安装的驱动路径

cd /sys/class/led/led cat uevent 查看驱动配置信息

img

ubuntu@farsight:/sys/class/led/led$ cat uevent

MAJOR=250

MINOR=0

DEVNAME=led

1.3.3 问题汇总——复习课

1)应用层的open( ),close( )如何调用到底层对应的open(),release( )?

img

函数指针把自定义的led_open,led_release函数首地址赋值给应用层的open,release函数

img

1.4 LED驱动——字符设备驱动注册

1.4.1 应用层的进程如何访问底层的驱动程序

字符设备或块设备,我们可以通过设备文件来找到底层驱动程序

-----------------------------------------------------------------

驱动的标识:设备号

12bit(主设备号) + 20bit(次设备号) = 32bit

主设备号:标识一类设备

次设备号:为了区分同类型设备的不同设备(个体)

------------------------------------------------------------------

#define MINORBITS 20

#define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

//主要通过MKDEV(MAJOR, MINOR) 宏定义来计算设备号

问1:Linux内核有那么多驱动程序,如何才能确定自己需要访问的驱动程序?

答:通过设备文件中包含的设备号信息

问2:Linux内核中,如何描述文件?

答:<1>struct inode 描述文件属性信息(文件类型,权限,大小,修改时间,设备号[设备文件])

​ <2>struct file 描述一个打开的文件(打开的方式,文件偏移量,...)

​ [注意:只要打开一次文件,就会分配一次]

问3:应用层访问底层字符设备驱动的过程?

答:open---->设备文件

​ struct inode:设备号

​ --------->struct cdev:记录操作硬件设备的函数接口

寻找成功之后:

​ struct inode 结构体记录struct cdev这个结构体首地址

​ struct file 结构体记录struct file_operations这个结构体首地址

1.4.2 编写字符驱动基本步骤

问4:写字符驱动,需要做什么?

答:<1>struct cdev:Linux 针对字符设备的通用描述

​ struct led_device

​ {

​ struct cdev cdev;//通用的字符设备描述

​ ....

​ };

需要给自己设计的结构体分配空间(内核申请空间使用kmalloc函数)

kmalloc(sizeof(pdev), GFP_KERNEL);

​ <2>提供硬件设备的操作函数接口

​ struct file_operations 结构体做填充

​ 需要将这个结构体的首地址记录在struct cdev结构体中

​ <3>申请一个空闲的设备号

​ <4>使用设备号,将struct cdev这个结构体添加到系统中去

-------------------------------------------------------------------

mknod 设备文件名 设备文件类型 主设备号 次设备号

mknod /dev/led c 250 0

-------------------------------------------------------------------

mknod命令用于创建Linux中的字符设备文件和块设备文件

1.4.3 alloc_chrdev_region()动态获取设备号

img

1.4.4 class_create() , device_create()自动创建设备文件结点

img

1.4.5 内核空间与用户空间的数据拷贝函数

copy_from_user() 从用户空间拷贝数据

img

copy_to_user() 拷贝数据到用户空间

img

img

1.4.6 led字符设备驱动在ubuntu下加载bug解决

1.4.6.1 卸载了模块,但是查看设备号信息依然存在,重新加载报错误信息(文件已存在),需要重启虚拟机才消失?

img

img

这里当之前已经使用

s****udo insmod led-driver.ko 安装模块

rmmod卸载后再次insmod安装的时候,会提示1 个文件已经存在;调试代码发现如下指针类型匹配的问题:

img

img

img

img

1.4.6.2 多次使用insmod和rmmod后,cat /proc/devices发现有多个led的主设备号

img

这里当多次使用insmod和rmmod的时候,使用cat /proc/devices 查看设备号发现这里有多个led的主设备号,也即设备号删除失败。

注:为了方便调试,建议可以在每进行一个删除操作后执行一个printk()打印错误代码的操作(确认删除函数的返回值类型)。便于定位各项操作是否正确执行。

void led_exit(void) 里面有函数没有被正确执行

img

1.4.7 Led字符设备底层接口实现

Led 端口数据写值;

img

配置led端口输出模式

img

1.4.8 如何测试自己的led字符设备驱动是否添加成功

1.4.8.1 编写应用层Led 灯闪烁程序

img

1.4.8.2 驱动程序在开发板上加载

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14$ pwd

/home/ubuntu/workdir/fs4412/kernel/linux-3.14

img

img

make

然后把模块文件拷贝到共享路径下

ubuntu@farsight:~/workdir/fs4412/fs/rootfs$ pwd

/home/ubuntu/workdir/fs4412/fs/rootfs

cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs

img

img

ubuntu@farsight:/mnt/hgfs/share/learn_driver/led/led_version6$ arm-none-linux-gnueabi-gcc test.c -o test

ubuntu@farsight:/mnt/hgfs/share/learn_driver/led/led_version6$ cp test /home/ubuntu/workdir/fs4412/fs/rootfs

偷懒的做法,先make ;然后make install

img

install:

#拷贝led-driver.ko 到共享目录

cp led-driver.ko /home/ubuntu/workdir/fs4412/fs/rootfs

#用ARM gcc 编译器test.c

arm-none-linux-gnueabi-gcc test.c -o test

#拷贝可执行文件test到共享目录

cp test /home/ubuntu/workdir/fs4412/fs/rootfs

#make install 自动执行以上三个步骤

注:照抄的时候上面的文件名要根据自己的文件名修改过来。

img

1.4.8.3 led字符驱动在fs4412开发板加载过程中bug解决

img

img

img

SecureCRT 有打印led on 和led off信息,但是开发板上的led没有闪烁,这时候有一个基本的判断,之前的version在电脑上的ubuntu测试添加设备驱动成功,****所以设备驱动函数应该是没有问题的,led灯没有闪烁,问题出在针对底层硬件的操作,也即LED 的IO端口的输出模式寄存器配置以及输出电平的寄存器配置。

1.4.9 问题汇总——复习课

img

1.5 led字符设备驱动函数接口的改进ioctl()

img

I****octl()函数

img

1.6 基于platform子系统的字符驱动

img

M****akefile 里面文件名不匹配

img

调用函数写错,platform_device_register();

要注意区分device和driver

img

led-device.ko 驱动模块编写的有问题导致加载错误。

img

移除驱动的时候报错,这时需要新建目录

mkdir /lib/modules/3.14.0

这样就可以成功移除驱动了。

img

img

因为level可以是1,也可以是0,当为0时,如果前面一步没有做清零操作,则这一步|操作就没有起到作用(0与之前的状态相与还是之前的状态,无改变)。

img

这里注意~0 ——0取反不是1,

!0 ——0取非才是1.

1.7 如何添加多个led硬件设备驱动?

ls /sys/bus/platform/devices/

img

img

led_probe()函数多次执行,class_create()重复创建类fs4412-led.

img

L****ed2设备名字重名,

dev_name(&pdev->dev)****;

img

register_led_chrdev()传参数的时候需要传struct platform_device pdev****;

img

img

img

img

img

img

img

platform ——>led_version3 存在的卸载bug

img

测试老师的代码,使用rmmod led-driver 可以正常卸载

img

1.8 设备树文件

1.8.1 设备树文件的基本格式

img

img

img

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/arch/arm/boot/dts$

vi exynos4412-fs4412.dts ( X )

上面修改****exynos4412-fs4412.dts 后make dtbs 没有任何输出信息。

vi exynos4412-origen.dts ( √ )

注:这里要修改的是exynos4412-origen.dts,因为之前linux系统移植的时候Makefile 里面修改的是exynos4412-origen.dtb。

img

img

在linux-3.14路径下make dtbs 编译生成.dtb设备树文件

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14$ make dtbs

DTC arch/arm/boot/dts/exynos4412-origen.dtb

img

img

拷贝生成的.dtb设备树文件到tftpboot 目录:

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/arch/arm/boot/dts$

cp exynos4412-origen.dtb /home/ubuntu/workdir/fs4412/tftpboot/

ubuntu@farsight:~/workdir/fs4412/tftpboot$ chmod 777 exynos4412-origen.dtb

img

img

img

img

img

img

img

img

修改class_create()函数参数

img

img

img

img

[root@farsight ]# ls -lh /dev/led2-gpio.2

crw-rw---- 1 0 0 253, 0 Jan 1 00:00 /dev/led2-gpio.2

[root@farsight ]# ./test /dev/led2-gpio.2

img

img

为了使pled->trigger_level 为1,则这里要对flags做取反操作。

img

img

img

img

1.9 按键中断

1.9.1 linux系统中断注册

1)、注册中断函数接口devm_request_irq( )

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/Documentation/devicetree/bindings/interrupt-controller$ vi interrupts.txt

img

img

1.9.2 Linux系统中断子系统框架

img

1.9.3 中断共享 IRQF_SHARED

img

多个驱动注册同一个中断号

1)、每一个devm_request_irq注册函数中需要在中断标志上加上 IRQF_SHARED****;

2)、要保证每个注册函数的dev_id 要唯一。(因为linux系统在注销一个中断的时候,是通过中断号(irq)和dev_id这两个参数来决定需要注销的中断的)。

img

cat /proc/interrupts 查看中断信息

img

img

img

这里之前interrupt-names 名字没有匹配(少了‘s’),不是linux系统通用属性就需要驱动工程师自己去解析。

img

1.10 中断上下文与进程上下文

1.10.1 Linux内核实现中断下半部的三种方式:软中断,tasklet,workqueue

img

1.10.2 tasklet实现基本步骤

1)初始化一个tasklet 结构体

img

img

2)编写tasklet处理函数(中断下半部处理函数)

img

3)在中断上半部处理函数结束处调度tasklet——tasklet_schedule()

//tasklet_schedule(struct tasklet_struct * t)

tasklet_schedule(&pkey->tasklet);

img

1.10.3 workqueue(工作队列)实现基本步骤

1)初始化一个work 结构体

struct work_device{

​ int irq;

​ struct work_struct work ;

};

struct work_device *pkey;

INIT_WORK(&pkey->work, key_work_handler);

2)编写work处理函数(中断下半部处理函数)

static void key_work_handler(struct work_struct *work)

{

/*******************************************

container_of - cast a member of a structure out to the containing structure

@ptr: the pointer to the member.

@type: the type of the container struct this is embedded in.

@member: the name of the member within the struct.

*******************************************/

struct work_device pkey = container_of****(work, struct work_device, work);

​ printk("key work handler pkey->irq : %d\n",pkey->irq);

​ return;

}

3)在中断上半部处理函数结束处调度work—— schedule****_work ()

schedule_work(&pkey->work);//调度工作队列

1.10.4 下半部机制的使用总结

img

1.11 ADC驱动移植

<1>阻塞

等待队列

<2>时钟信号的使用

img

1.12 I2C驱动移植

1.12.1 linux驱动中I2C驱动架构

img

img

img

img

1.12.2 I2C的读写时序

img

img

注意:主机接收器在接收到最后一个数据后,

1.12.3 I2C驱动的分层思想

img

1.12.4 I2C从设备(MPU6050)驱动的实现方法一

I2C控制器驱动,由Samsung提供的i2c-s3c2410.c;

i2c-dev.c实现了从设备通用驱动,则工程师只需要编写I2C从设备的的驱动即可(也即MPU6050的I2C驱动)。

img

img

img

img

img

img

img

img

img

cd sys/bus/i2c/drivers/

img

在设备树中添加I2C从设备信息后(参考i2c-s3c2410.txt 的写法)

ubuntu@farsight:~/workdir/fs4412/kernel/linux-3.14/Documentation/devicetree/bindings/i2c$

vi i2c-s3c2410.txt

img

img

img

img