Openwrt开发指南 第19章 驱动开发之LED驱动程序

描述

开发环境:

主机:Ubuntu12.04

开发板:RT5350

Openwrt:Openwrt15.05

1 硬件原理

OpenWrt

图1

由于发光二级管单向导电特性,即只有在正向电压(二极管的正极接正,负极接负)下才能导通发光。如图所示,如果 GPIO 输出高电平,LED 就会被点亮,如果 GPIO 输出低电平,LED 就会熄灭。对于我们的驱动开发,无论是单片机、还是 ARM、或者是我们的 MIPS,核心思想,都是读写某个地址,即操作某个寄存器。

2 寄存器介绍

RT5350 一共有 28 个 GPIO 管脚,这 28 个 GPIO,除了 GPIO0,其他全部是与其他功能引脚复用的。

表1

OpenWrt

GPIO1、GPIO2 与 I2C 复用。

表2

OpenWrt

GPIO3~6 与 SPI 复用。

表3

OpenWrt

GPIO7~14,与 UARTF 即串口 2 复用。

这些复用关系,都可以通过查阅 RT5350 的芯片手册得到。

而这些复用功能, 我们可以通过 GPIOMODE 寄存器来进行选择, 通过查手册得知,GPIOMODE寄存器的地址为 0x10000060。

表4

OpenWrt

GPIOMODE 寄存器 bit0 位用于选择 GPIO1、GPIO2 对应的引脚是用于 IIC 总线,还是用于GPIO。

GPIOMODE 寄存器 bit1 位用于选择 GPIO3~6 对应的引脚用于 SPI 总线,还是用于 GPIO。

GPIOMODE 寄存器 bit2~4 位用于选择 UARTF 对应的引脚工作于哪个模块,具体定义如下。

表5

OpenWrt

从该表格可以看出,UARTF 对应的引脚,可以工作于 UARTF、PCM、I2S、GPIO 四种模式,将 GPIOMODE 寄存器 bit24 位设置为相应的值,就能让这些引脚工作于相应的模式,比如将GPIOMODE 寄存器 bit24 位的值设置为 7,则让相应的引脚工于 GPIO 模式。

当将相应的引脚设置为 GPIO 以后,我们接下来就需要操作该 GPIO 了,操作 GPIO 不外乎就是设置 GPIO 是输入还是输出、让其输出高电平还是低电平、读取其电平状态。不管是哪种操作,都有对应的寄存器。

表6

OpenWrt

GPIO21_00_DIR 寄存器,用于设置 GPIO0~21 的方向,当相应的位被设置为 1,则表示相应的 GPIO 管脚被设置为了输出,如果被设置为了 0,则相应的 GPIO 引脚就被设置为了输入。

表7

OpenWrt

当相应 GPIO 引脚被设置为输出时,设置 GPIO21_00_DATA 寄存器的相应位为 1,则让该GPIO 引脚输出了高电平,如果设置 GPIO21_00_DATA 寄存器的相应位为 0,则让该 GPIO 引脚输出了低电平。

当相应 GPIO 引脚被设置为输入时,则通过读取 GPIO21_00_DATA 寄存器时,就能读取相应的 GPIO 引脚的状态。

关于更多的 GPIO 操作的寄存器介绍,请自行查阅手册。

3 编写驱动程序

我们通过前面章节的学习,掌握了驱动程序的框架,接下来我们就来写一个驱动程序,实现操作 GPIO25、GPIO26 两个 GPIO 引脚。具体驱动实现如下。

#include < linux/mm.h >
#include < linux/miscdevice.h >
#include < linux/slab.h >
#include < linux/vmalloc.h >
#include < linux/mman.h >
#include < linux/random.h >
#include < linux/init.h >
#include < linux/raw.h >
#include < linux/tty.h >
#include < linux/capability.h >
#include < linux/ptrace.h >
#include < linux/device.h >
#include < linux/highmem.h >
#include < linux/crash_dump.h >
#include < linux/backing-dev.h >
#include < linux/bootmem.h >
#include < linux/splice.h >
#include < linux/pfn.h >
#include < linux/export.h >
#include < linux/io.h >
#include < linux/aio.h >
#include < linux/kernel.h >
#include < linux/module.h >
#include < asm/uaccess.h >
#define MYLEDS_LED1_ON 0
#define MYLEDS_LED1_OFF 1
#define MYLEDS_LED2_ON 2
#define MYLEDS_LED2_OFF 3

volatile unsigned long *GPIOMODE;
volatile unsigned long *GPIO27_22_DIR;
volatile unsigned long *GPIO27_22_DATA;
static struct class *myleds_class;

static int myleds_open(struct inode *inode, struct file *file)
{
/* 让 GPIO#25、GPIO#26 输出高电平,同时熄灭 LED1、LED2 */
*GPIO27_22_DATA &= ~((1< < 3)|(1< < 4));
return 0;
}
static long myleds_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case MYLEDS_LED1_ON:// 点亮 LED1
*GPIO27_22_DATA |= (1< < 3);
break;
case MYLEDS_LED1_OFF: // 熄灭 LED1
*GPIO27_22_DATA &= ~(1< < 3);
break;
case MYLEDS_LED2_ON:// 点亮 LED2
*GPIO27_22_DATA |= (1< < 4);
break;
case MYLEDS_LED2_OFF: // 熄灭 LED2
*GPIO27_22_DATA &= ~(1< < 4);
break;
default:
break;
}
return 0;
}
/* 1.分配、设置一个 file_operations 结构体 */
static struct file_operations myleds_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译
模块时自动创建的__this_module 变量 */
.open = myleds_open,
.unlocked_ioctl = myleds_unlocked_ioctl,
};
int major;
static int __init myleds_init(void)
{
/* 2.注册 */
major = register_chrdev(0, "myleds", &myleds_fops);
/* 3.自动创建设备节点 */
/* 创建类 */
myleds_class = class_create(THIS_MODULE, "myleds");
/* 类下面创建设备节点 */
device_create(myleds_class, NULL, MKDEV(major, 0), NULL, "myleds"); //
/dev/myleds
/* 4.硬件相关的操作 */
/* 映射寄存器的地址 */
GPIOMODE = (volatile unsigned long *)ioremap(0x10000060, 4);
GPIO27_22_DIR = (volatile unsigned long *)ioremap(0x10000674, 4);
GPIO27_22_DATA = (volatile unsigned long *)ioremap(0x10000670, 4);
/* 设置相应管脚用于 GPIO */
/*
** LED1 ---- GPIO#25
** LED2 ---- GPIO#26
*/
*GPIOMODE |= (0x1< < 14);
/* 将 GPIO#25、GPIO#26 设置为输出 */
*GPIO27_22_DIR = (1< < 3)|(1< < 4);
return 0;
}
static void __exit myleds_exit(void)
{
unregister_chrdev(major, "myleds");
device_destroy(myleds_class, MKDEV(major, 0));
class_destroy(myleds_class);
iounmap(GPIOMODE);
iounmap(GPIO27_22_DIR);
iounmap(GPIO27_22_DATA);
}
module_init(myleds_init);
module_exit(myleds_exit);
MODULE_LICENSE("GPL");

因为我们的开发板上跑的是 Linux 系统,因此操作某个寄存器的时候,需要将它的物理地址映射成虚拟地址,通过 ioremap()函数来进行映射,该函数的参数 1,就是对应的寄存器的物理地址,参数 2 是需要映射多大,可以理解为寄存器有多大,返回值就是该寄存器对应的虚拟地址了。

当寄存器地址映射为虚拟地址以后,然后基于前面写的驱动程序框架,就能很容易的编写出自己的 GPIO 驱动了,和操作单片机没有什么差别了。

4 编写 Makefile

驱动写好以后,自然是需要想办法来编译该驱动了,通过前面的章节的学习,我们需要给该驱动编写一个 Makefile 文件。

首先是新建一个文件夹,取名为 myleds,然后在 myleds 目录下再新建一个文件夹,取名为 src,然后将上面的驱动文件复制到 src 目录下,并且在 src 目录下新建一个 Makefile,

内容如下。

obj-m += myleds.o

然后回到 myleds 目录,再创建一个 Makefile 文件,内容如下。

# 
#Copyright (C) 2008-2019 OpenWrt.org
#
#This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=myleds
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define KernelPackage/myleds
SUBMENU:=Other modules
# DEPENDS:=@!LINUX_3_3
TITLE:=Motor driver
FILES:=$(PKG_BUILD_DIR)/myleds.ko
# AUTOLOAD:=$(call AutoLoad,30,myleds,1)
KCONFIG:=
endef
define KernelPackage/myleds/description
This is a myleds drivers
endef
MAKE_OPTS:= \\
ARCH="$(LINUX_KARCH)" \\
CROSS_COMPILE="$(TARGET_CROSS)" \\
SUBDIRS="$(PKG_BUILD_DIR)"
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \\
$(MAKE_OPTS) \\
modules
endef
$(eval $(call KernelPackage,myleds))

如果不清楚为什么这样编写 Makefile 文件,请查看前面章节的相关介绍。

5 编译驱动程序

接下来,我们就来配置编译驱动程序。首先将 myleds 文件夹传到 OpenWrt 源码的package/kernel 目录下。然后进入 OpenWrt 源码的顶层目录,执行 make menuconfig。

$ make menuconfig

在弹出的菜单界面里,首先进入 Kernel modules 选项。

Kernel modules  --- >
Other modules  --- >
< * > kmod-myleds

OpenWrt

图2

然后我们就能看到我们的驱动程序的选项了 kmod-myleds,将它配置成M,也可以便已进入内核。最后,退出保存。然后再执行编译命令。

make V=99

编译完成以后,就能在源码目录下的bin/ramips/packages/base目录下看到我们的内核模块驱动程序的软件包 kmod-myleds_3.18.109-1_ramips_24kec.ipk。

19.6 动态的加载和卸载内核驱动模块软件包

通过前面的努力,我们终于得到了我们自己的内核驱动模块软件包了。接下来就来使用它。首先将 kmod-myleds_3.18.109-1_ramips_24kec.ipk 软件包传到开发板上面。然后使用 opkg install 命令来安装软件包。

opkg install kmod-myleds_3.18.109-1_ramips_24kec.ipk

OpenWrt

使用 opkg list 命令来查看已经安装了哪些软件包。这里是否执行该命令都无所谓。接下来进入我们的/lib/modules/3.18.109目录,就能看到我们的驱动程序模块了。

cd lib/modules/3.18.109/

OpenWrt

接下来就通过 insmod 命令来装载驱动模块。

insmod myleds.ko

驱动安装成功以后,我们就能看到 GPIO 驱动对应的设备节点了。

OpenWrt
 
 审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分