您当前的位置: 首页 >  linux

韦东山

暂无认证

  • 0浏览

    0关注

    506博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

第一课:linux设备树的引入与体验(基于linux4.19内核版本)

韦东山 发布时间:2018-11-22 11:30:14 ,浏览量:0

在线课堂:https://www.100ask.net/index(课程观看) 论  坛:http://bbs.100ask.net/(学术答疑) 开 发 板:https://100ask.taobao.com/ (淘宝)      https://weidongshan.tmall.com/(天猫) 交流群一:QQ群:869222007(鸿蒙开发/Linux/嵌入式/驱动/资料下载) 交流群二:QQ群:536785813(单片机-嵌入式) 公 众 号:百问科技

版本日期作者说明V12020韦东山技术文档

本套视频面向如下三类学员:

  1. 有Linux驱动开发基础的人, 可以挑感兴趣的章节观看;
  2. 没有Linux驱动开发基础但是愿意学习的人,请按顺序全部观看,我会以比较简单的LED驱动为例讲解;
  3. 完全没有Linux驱动知识,又不想深入学习的人, 比如应用开发人员,不得已要改改驱动, 等全部录完后,我会更新本文档,那时再列出您需要观看的章节。
第01节_字符设备的三种写法 怎么写驱动?

①看原理图:

a.确定引脚;

b.看芯片手册,确定如何操作引脚;

②写驱动程序;

起封装作用;

③写测试程序;

如下原理图,VCC经过一个限流电阻到达LED的一端,再通向芯片的引脚上。

当芯片引脚输出低电平时,电流从高电平流向低电平,LED灯点亮; 当芯片引脚输出高电平时,没有电势差,没有电流流过,LED灯不亮; 从原理图可以看出,控制了芯片引脚,就等于控制了灯。

在Linux里,操作硬件都是统一的接口,比如操作LED灯,需要先open,如果要读取LED状态就调用read,如果要操作LED就调用write函数,也可以通过ioctl去实现。 在驱动里,针对前面应用的每个调用函数,都写一个对应的函数,实现对硬件的操作。

可以看出驱动程序起封装作用,它让应用程序访问硬件变得简单,屏蔽了硬件更加复杂的操作。

如何写驱动程序?

①分配一个file_operations结构体; ②设置:  a. .open=led_open;把led引脚设置为输出引脚  b. .read=led_write;根据APP传入的值设置引脚状态

③注册(告诉内核),register_chrdev(主设备号,file_operations,name) ④入口函数 ⑤出口函数

在驱动中如何指定LED引脚?

有如下三种方法: ①传统方法:在代码led_drv.c中写死; ②总线设备驱动模型:  a. 在led_drv.c里分配、注册、入口、出口等  b. 在led_dev.c里指定引脚 ③使用设备树指定引脚  a. 在led_drv.c里分配、注册、入口、出口等  b. 在jz2440.dts里指定引脚

可以看到,’’‘无论何种方法,驱动写法的核心不变,差别在于如何指定硬件资源’’’。 对比下三种方法的优缺点。 假设这样一个情况,某公司用同一个芯片做了两款产品,其中一款是TV(电视盒子),使用Pin1作为LED的指示灯控制引脚,其中一款是Cam(监控摄像头),使用Pin2作为LED的指示灯控制引脚。

TV设备Cam设备优缺点1.传统方法led_drv.c①分配一个file_operations结构体;②设置: a .open=led_open;设置Pin1为输出引脚 b .read=led_read;根据APP传入的值设置引脚状态③注册(告诉内核)④入口函数⑤出口函数led_drv.c①分配一个file_operations结构体;②设置: a. .open=led_open;设置Pin2为输出引脚 b. .read=led_read;根据APP传入的值设置引脚状态③注册(告诉内核)④入口函数⑤出口函数优点:简单缺点:不易扩展,需要重新编译2.总线设备驱动模型led_drv.c①分配/设置/注册 platform_driver;② .probe: a 分配一个file_operations结构体; b .open=led_open;设置平台设备总指定的引脚为输出引脚  .read=led_read;根据APP传入的值设置引脚状态 c注册③ .driver{ .name }led_dev.c①分配/设置/注册 platform_device;② .resource:指定引脚;,name为Pin1led_dev.c①分配/设置/注册 platform_driver;② .resource:指定引脚;,name为Pin2优点:易扩展缺点:稍复杂,冗余代码太多,需要重新编译3.设备树led_drv.c①分配/设置/注册 platform_driver;② .probe: a 分配一个file_operations结构体; b .open=led_open;设置平台设备总指定的引脚为输出引脚  .read=led_read;根据APP传入的值设置引脚状态 c注册③ .driver{ .name }.dts指定资源内核根据dts生成的dtb文件分配/设置/注册platform_device.dts指定资源内核根据dts生成的dtb文件分配/设置/注册platform_device优点:易扩展缺点:稍复杂,冗余代码太多,需要重新编译 第02节_字符设备驱动的传统写法

在上一节视频里我们介绍了三种编写驱动的方法,也对比了它们的优缺点,后面我们将使用比较快速的方法写出驱动程序,因为写驱动程序不是我们这套视频的重点,所以尽快的把驱动程序写出来,给大家展示一下。

这节视频我们使用传统的方法编写字符驱动程序,以最简单的点灯驱动程序为示例。 先回顾下写字符设备驱动的五个步骤: 1.2.3.分配/设置/注册file_operations 4.入口 5.出口 所谓分配file_operations,我们可以定义一个file_operations结构体,就不需要分配了。

static struct file_operations myled_oprs = {
	.owner = THIS_MODULE, //表示这个模块本身
	.open  = led_open,
	.write = led_write,
	.release = led_release,
};

定义好了file_operations结构体,再去入口函数注册结构体。

static int myled_init(void)
{
	major = register_chrdev(0, "myled", &myled_oprs);

	return 0;
}

第一个参数:主设备号写0,让系统为我们分配; 第二个参数:设置名字,没有特殊要求; 第三个参数:file_operations结构体; 对应的出口操作进行相反向操作:

static void myled_exit(void)
{
	unregister_chrdev(major, "myled");
}

然后用宏module_init对入口、出口函数进行修饰,表示它们和普通函数不一样:

module_init(myled_init);
module_exit(myled_exit);

module_init(myled_init)实际就是int init_module(void) attribute((alias(“myled_init”))),表示myled_init的别名是init_module,以后就可以使用init_module来引用myled_init

此外,还要加上GPL协议:

MODULE_LICENSE("GPL");

写到这里,驱动程序的框架已经搭建起来了,接下来实现具体的硬件操作函数:led_open()和led_write()。 在led_open()里把对应的引脚配置为输出引脚,在led_write()根据应用程序传入的数据点灯,让其输出高电平或低电平。 为了让程序更具有扩展性,把GPIO的寄存器放在一个数组里:

static unsigned int gpio_base[] = {
	0x56000000, /* GPACON */
	0x56000010, /* GPBCON */
	0x56000020, /* GPCCON */
	0x56000030, /* GPDCON */
	0x56000040, /* GPECON */
	0x56000050, /* GPFCON */
	0x56000060, /* GPGCON */
	0x56000070, /* GPHCON */
	0,          /* GPICON */
	0x560000D0, /* GPJCON */
};

定义好了引脚的组,还得确定使用该组的哪个引脚,使用宏来确定哪个引脚:

#define S3C2440_GPA(n)  (0            
关注
打赏
1658827356
查看更多评论
0.0679s