博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【嵌入式Linux驱动开发】五、LED驱动完善 - 面向对象·上下分层·左右分离
阅读量:2029 次
发布时间:2019-04-28

本文共 6758 字,大约阅读时间需要 22 分钟。

  除了知情权以外,人也应该拥有不知情权,后者的价值要大得多。它意味着高尚的灵魂不必被那些废话和空谈充斥。过度的信息对一个过着充实生活的人来说,是一种不必要的负担。

文章目录


一、面向对象·上下分层·左右分离思想

  • 面向对象
    • 字符设备驱动程序抽象出一个 file_operations 结构体;
    • 我们写的程序针对硬件部分抽象出 led_operations 结构体。
  • 上下分层,
    • 比如我们前面写的 LED 驱动程序就分为 2 层:
    • ① 上层实现硬件无关的操作,比如注册字符设备驱动: leddrv.c
    • ② 下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作
      在这里插入图片描述

  这两种思想在之前的程序中悄无声息的使用着,但是这样就完美了?在之前的程序基础上,考虑这样一种情况:如果硬件上更换一个引脚来控制 LED 怎么办?那就得去修改led_operations结构体初始化中的 init、 ctrl 函数实现。

  实际情况是,每一款芯片它的 GPIO 操作都是类似的。比如: GPIO1_3、 GPIO5_4 这 2个引脚接到 LED:

  • ① GPIO1_3 属于第 1 组,即 GPIO1。
    有方向寄存器 DIR、数据寄存器 DR 等,基础地址是 addr_base_addr_gpio1。
    设置为 output 引脚:修改 GPIO1 的 DIR 寄存器的 bit3。
    设置输出电平:修改 GPIO1 的 DR 寄存器的 bit3。
  • ② GPIO5_4 属于第 5 组,即 GPIO5。
    有方向寄存器 DIR、数据寄存器 DR 等,基础地址是 addr_base_addr_gpio5。
    设置为 output 引脚:修改 GPIO5 的 DIR 寄存器的 bit4。
    设置输出电平:修改 GPIO5 的 DR 寄存器的 bit4。

  既然引脚操作那么有规律, 并且这是跟主芯片相关的,那可以针对该芯片写出比较通用的硬件操作代码。

  比如 board_A.c 使用芯片 chipY,那就可以写出: chipY_gpio.c,它实现芯片 Y 的 GPIO操作,适用于芯片 Y 的所有 GPIO 引脚。
  使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。

  程序结构如下图所示:

在这里插入图片描述

  以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定义“资源”──要用哪一个引脚。在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,支持所有 GPIO。

  这样程序仍分为上下结构:

  • 上层 leddrv.c 向内核注册 file_operations 结构体;
  • 下层chipY_gpio.c 提供 led_operations 结构体来操作硬件。
    • 下层的代码分为 2 个:
    • chipY_gpio.c 实现通用的 GPIO 操作
    • board_A_led.c 指定使用哪个 GPIO,即“资源”( led_resource 结构体定义在led_resource.h 文件中)

二、编写程序

程序框架如下:

在这里插入图片描述

LED_resource.h

#ifndef _LED_RESOURCE_H#define _LED_RESOURCE_H/* GPIO3_0 *//* bit[31:16] = group *//* bit[15:0]  = which pin */#define GROUP(x) (x>>16)#define PIN(x)   (x&0xFFFF)#define GROUP_PIN(g,p) ((g<<16) | (p))struct led_resource {
int pin;};struct led_resource *get_led_resouce(void);#endif

资源结构体头文件需要说明的:

  • 定义了一个成员变量32位的pin成员,来指定是哪一组的那一个引脚
    • pin成员低16位用作哪一个引脚
    • pin成员高16位用作哪一组引脚

board_A_led.c

#include "led_resource.h"static struct led_resource board_A_led = {
.pin = GROUP_PIN(3,1),};struct led_resource *get_led_resouce(void){
return &board_A_led;}

单板A的GPIO需要说明的是

  • 实现了 led_resouce 结构体,具体定义了“资源”是GPIO3_1
  • 该程序只是一个框架,并没有初始化GPIO3_1的具体寄存器配置。

chipY_gpio.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"#include "led_resource.h"static struct led_resource *led_rsc;static int board_qemu_led_init (int which) /* 初始化LED, which-哪个LED */ { //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which); if (!led_rsc) { led_rsc = get_led_resouce(); } printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin)); switch(GROUP(led_rsc->pin)) { case 0: { printk("init pin of group 0 ...\n"); break; } case 1: { printk("init pin of group 1 ...\n"); break; } case 2: { printk("init pin of group 2 ...\n"); break; } case 3: { printk("init pin of group 3 ...\n"); break; } } return 0;}static int board_qemu_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */{ //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off"); printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin)); switch(GROUP(led_rsc->pin)) { case 0: { printk("set pin of group 0 ...\n"); break; } case 1: { printk("set pin of group 1 ...\n"); break; } case 2: { printk("set pin of group 2 ...\n"); break; } case 3: { printk("set pin of group 3 ...\n"); break; } } return 0;}static struct led_operations board_qemu_led_opr = { .num = 4, .init = board_qemu_led_init, .ctl = board_qemu_led_ctl,};struct led_operations *get_board_led_opr(void){ return &board_qemu_led_opr;}

chipY的所有GPIO操作程序需要说明的:

  • 该程序只是一个框架,并没有具体的GPIO操作!
  • GROUP(led_rsc->pin), PIN(led_rsc->pin),这两个宏在led_resource.h中定义,作用就是取出对应的组和引脚。
  • 其中的ioremap和iounmap自然放到了具体的单板GPIO初始化文件board_A_led.c中去了!

leddrv.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"/* 1. 确定主设备号 */static int major = 0;static struct class *led_class;struct led_operations *p_led_opr;/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */static int led_drv_open (struct inode *node, struct file *file){ int minor = iminor(node); /* 根据次设备号初始化LED */ p_led_opr->init(minor); return 0;}static int led_drv_close (struct inode *node, struct file *file){ return 0;}static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){ return 0;}/* write(fd, &val, 1); */static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){ int err; char status; struct inode *node = file_inode(file); int minor = iminor(node); err = copy_from_user(&status, buf, 1); /* 根据次设备号和status控制LED */ p_led_opr->ctl(minor, status); return 1;}/* 2. 定义自己的file_operations结构体 */static struct file_operations led_drv = { .owner = THIS_MODULE, .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close,};/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */static int __init led_init(void){ int err; int i; printk("LED init \r\n"); /* 4. 把file_operations结构体告诉内核:注册驱动程序 */ major = register_chrdev(0, "led", &led_drv); /* /dev/led */ /* 7. 其他完善:提供设备信息,自动创建设备节点 */ led_class = class_create(THIS_MODULE, "led_class"); err = PTR_ERR(led_class); if (IS_ERR(led_class)) { unregister_chrdev(major, "led"); return -1; } /* 注意要在创建设备之前获得led_operaions结构体(需要用到其中的num) */ p_led_opr = get_board_led_opr(); for (i = 0; i < p_led_opr->num; i++) device_create(led_class, NULL, MKDEV(major, i), NULL, "led%d", i); /* /dev/led0,1,... */ return 0;}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */static void __exit led_exit(void){ int i; printk("LED exit \r\n"); for (i = 0; i < p_led_opr->num; i++) device_destroy(led_class, MKDEV(major, i)); /* /dev/led0,1,... */ device_destroy(led_class, MKDEV(major, 0)); class_destroy(led_class); unregister_chrdev(major, "led");}module_init(led_init);module_exit(led_exit);MODULE_LICENSE("GPL");

LED驱动程序需要说明的

  • 跟之前的大差不差

Makefile

KERN_DIR = /home/clay/linux/qemu/kernel/100ask_imx6ull-qemu/linux-4.9.88all:	make -C $(KERN_DIR) M=`pwd` modules 	$(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:	make -C $(KERN_DIR) M=`pwd` modules clean	rm -rf modules.order	rm -f ledtest# 参考内核源码drivers/char/ipmi/Makefile# 要想把a.c, b.c编译成ab.ko, 可以这样指定:# ab-y := a.o b.o# obj-m += ab.o# leddrv.c chipY_gpio.c board_A_led.c 编译成 led.koled-y := leddrv.o chipY_gpio.o board_A_led.oobj-m	+= led.o

Makefile需要说明的:

  • 最后两行,对应的源文件多了,别忘了加上!

三、运行程序

编译程序没有问题后,运行qemu虚拟开发板,并做好准备工作!

  • 拷贝led.ko和ledtest到NFS中
cp *.ko ledtest ~/linux/qemu/NFS/
  • 在qemu终端,加载led.ko文件
insmod led.ko
  • 在qemu终端,运行应用程序打开LED0
./ledtest /dev/led0 on

在这里插入图片描述

  • 在qemu终端,运行应用程序关闭LED0
./ledtest /dev/led0 off

在这里插入图片描述

这一节也只是搭了框架哈,主要还是理解思想,下一节继续深入!

转载地址:http://hfnaf.baihongyu.com/

你可能感兴趣的文章
WebCollector爬虫爬取一个或多个网站
查看>>
WebCollector爬虫的数据持久化
查看>>
插入排序
查看>>
谷歌面试题-100层楼两个棋子的问题
查看>>
系统架构师设计培训心得之二——架构设计
查看>>
Kafka技术知识总结之二——Kafka事务
查看>>
Kafka技术知识总结之五——Kafka的高可用性
查看>>
Redis技术知识总结之三——Redis数据淘汰机制
查看>>
Spring技术知识点总结之三——Spring Bean 的注入过程
查看>>
Spring技术知识点总结之五——Servlet 生命周期
查看>>
Tomcat技术知识点总结
查看>>
数据库技术知识点总结之三——索引相关内容
查看>>
数据库技术知识点总结之四——乐观锁与悲观锁
查看>>
数据结构技术知识总结之一——二叉树
查看>>
Netty面试技术知识点总结
查看>>
设计模式总结之一——代理模式
查看>>
ZooKeeper 技术知识总结之一——Leader选举算法
查看>>
ZooKeeper 技术知识总结之二——ZK 惊群与脑裂
查看>>
Java并发技术总结之一——Java线程池
查看>>
Java并发技术总结之二——ThreadLocal
查看>>