[原创] 基于OMAPL138的Linux设备驱动程序开发入门
526 查看
2 回复
 楼主 | 发布于 2020-08-27 | 只看楼主
分享到:

LED设备驱动程序

 LED设备驱动程序解析

开发板LED编号和GPIO对应关系如下:


表 1

开发板型号

GPIO0[0]

GPIO0[5]

GPIO0[1]

GPIO0[2]

TL138/1808-EVM

D7

D6

D9

D10

TL138/1808-EasyEVM

D7

D6

D9

D10

TL138/1808-EthEVM

D7

D6

D9

D10

TL138/1808F-EasyEVM

\

GD1

GD2

GD3

TL138/1808F-EVM

\

D1

D2

D3


开发板资料光盘中有LED设备驱动程序源码,其路径为:

led.c:demo\driver\linux-3.3\led\led.c

下面以TL138/1808-EVM开发板为例讲解此设备驱动程序。

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/gpio.h>

#include <linux/platform_device.h>


/* 因为使用了平台相关的头文件,所以编译时需要ARCH=arm */

#include <asm/mach-types.h>

#include <asm/mach/arch.h>

#include <mach/da8xx.h>

#include <mach/mux.h>


/*定义4个用户LED对应的GPIO,开发板LED对应编号分别是D7,D6,D9,D10 */

#define DA850_USER_LED0 GPIO_TO_PIN(0, 0)

#define DA850_USER_LED1 GPIO_TO_PIN(0, 5)

#define DA850_USER_LED2 GPIO_TO_PIN(0, 1)

#define DA850_USER_LED3 GPIO_TO_PIN(0, 2)


/* assign the tl som board LED-GPIOs*/

static const short da850_evm_tl_user_led_pins[] = {

/* These pins are definition at <mach/mux.h> file */

DA850_GPIO0_0, DA850_GPIO0_1, DA850_GPIO0_2, DA850_GPIO0_5,

-1

};


/*定义4个LED对应的GPIO号、有效电平(熄灯电平)、名称、触发模式等*/

/*使用Linux提供的标准gpio-led框架*/

static struct gpio_led da850_evm_tl_leds[] = {

{

.active_low = 0, /*有效电平(熄灯电平):低电平*/

.gpio = DA850_USER_LED0, /*GPIO号:LED对应gpio管脚*/

.name = "user_led0", /*名称:对应/sys/class/leds/下的名称*/

.default_trigger = "default-on", /*触发模式:默认点亮*/

},

{

.active_low = 0,

.gpio = DA850_USER_LED1,

.name = "user_led1",

.default_trigger = "default-on",

},

{

.active_low = 0,

.gpio = DA850_USER_LED2,

.name = "user_led2",

.default_trigger = "default-on",

},

{

.active_low = 0,

.gpio = DA850_USER_LED3,

.name = "user_led3",

.default_trigger = "default-on",

},

};


static struct gpio_led_platform_data da850_evm_tl_leds_pdata = {

.leds = da850_evm_tl_leds,

.num_leds = ARRAY_SIZE(da850_evm_tl_leds),

};


static void led_dev_release(struct device *dev)

{

};


/*使用Linux提供的标准platform_device 框架*/

static struct platform_device da850_evm_tl_leds_device = {

.name = "leds-gpio",

.id = 1, /*先确定id号是否被使用,此id是platform_device的id,跟LED个数无关*/

.dev = {

.platform_data = &da850_evm_tl_leds_pdata,

.release = led_dev_release,

}

};


static int __init led_platform_init(void)

{

int ret;


#if 0

/*使用davinci pinmux设置接口,把LED对应的管脚配置成gpio模式*/

ret = davinci_cfg_reg_list(da850_evm_tl_user_led_pins);

if (ret)

pr_warning("da850_evm_tl_leds_init : User LED mux failed :"

"%d\n", ret);

#endif


/*注册LED device设备,系统LED框架将会接收到这个注册,生成相应LED节点*/

ret = platform_device_register(&da850_evm_tl_leds_device);

if (ret)

pr_warning("Could not register som GPIO expander LEDS");

else

printk(KERN_INFO "LED register sucessful!\n");


return ret;

}


static void __exit led_platform_exit(void)

{

platform_device_unregister(&da850_evm_tl_leds_device);


printk(KERN_INFO "LED unregister!\n");

}


module_init(led_platform_init);

module_exit(led_platform_exit);


MODULE_DESCRIPTION("Led platform driver");

MODULE_AUTHOR("Tronlong");

MODULE_LICENSE("GPL");


以上是LED设备驱动程序解析,对于Linux对LED设备框架,这里稍微说明一下:

  1. Linux的LED设备类在内核"Documentation/leds/leds-class.txt"文件有详细说明。
  2. 注册一个LED设备成功后,会"/sys/class/leds/"生成相应的设备节点。
  3. 用户可以通过读写节点目录下的brightness文件控制LED亮灭。

对于GPIO口的操作,有以下几点步骤:

  1. 查看开发板的原理图,找到与LED连接的GPIO。TL138/1808-EVM开发板与LED连接的GPIO分别是GPIO0[5]、GPIO0[0]、GPIO0[1]、GPIO0[2]。
  2. 查看OMAP-L138的数据手册,查找对应PINMUX寄存器的地址,将对应的管脚的寄存器中相应位设置为GPIO的工作模式。本例中使用的是PINMUX1。
  3. 设置GPIO的方向寄存器。本例程中将GPIO口配置为输出。
  4. 配置GPIO的数据寄存器,写"1"表示输出高电平,写"0"表示输出低电平。

编译LED设备驱动程序

此处使用Makefile编译LED设备驱动程序。工程中源文件有时候很多,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

开发板资料光盘中有LED设备驱动程序Makefile文件,其路径为:

Makefile: demo\driver\linux-3.3\led\Makefile

以下为LED设备驱动程序Makefile文件的解析:

ifneq ($(KERNELRELEASE),)

obj-m := led.o /*定义了要编译的驱动文件为led.c,生成的模块名字为led.ko*/

else

/*以下定义运行编译命令时使用的内核源码、驱动源码路径、平台、使用的交叉编译工具链等参数*/

all:

make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-

/*定义运行"make clean"时清除的文件*/

clean:

rm -rf *.ko *.o *.mod.o *.mod.c *.symvers  modul* .button.* .tmp_versions


#help: make KDIR=<you kernel path>

endif


图 1


将光盘"demo\driver\linux-3.3\led"的led.c和Makefile文件复制到开发系统Ubuntu任意路径,并在led.c和Makefile目录运行以下命令编译LED设备驱动程序:

Host# make KDIR=/home/tl/omapl138/linux-3.3


图2


"KDIR=/home/tl/omapl138/linux-3.3"是内核源码路径,在运行前必须已正确编译过内核源码。运行以上命令后,系统会根据Makefile文件的规则去编译整个驱动源码,产生了驱动程序镜像文件led.ko和其他中间文件。


LED设备驱动测试脚本解析

开发板资料光盘有LED设备驱动测试脚本,运行此测试脚本LED会循环点亮。其路径为:

led_loop.sh demo\app\led\led_loop.sh

以下为测试脚本的解析:

#init all user led #关闭所有LED灯

echo 0 > /sys/class/leds/user_led0/brightness

echo 0 > /sys/class/leds/user_led1/brightness

echo 0 > /sys/class/leds/user_led2/brightness

echo 0 > /sys/class/leds/user_led3/brightness


DELAY_TIME=0.5 #定义流水灯延时时间


#led loop

while true; do

    echo 1 > /sys/class/leds/user_led0/brightness #点亮LED0 D7

    sleep $DELAY_TIME

    echo 0 > /sys/class/leds/user_led0/brightness #关闭LED0 D7

    echo 1 > /sys/class/leds/user_led1/brightness

    sleep $DELAY_TIME

    echo 0 > /sys/class/leds/user_led1/brightness

    echo 1 > /sys/class/leds/user_led2/brightness

    sleep $DELAY_TIME

    echo 0 > /sys/class/leds/user_led2/brightness

    echo 1 > /sys/class/leds/user_led3/brightness

    sleep $DELAY_TIME

    echo 0 > /sys/class/leds/user_led3/brightness

done


具体的LED测试步骤请查看用户手册快速体验相关小节。


按键设备驱动程序

按键设备驱动程序解析


开发板资料光盘中有按键设备驱动程序源码,对应的按键为SW5和SW6,以linux-3.3内核驱动为例,其路径为:

button.c: demo\driver\linux-3.3\button\button.c

以下为此驱动程序的解析:

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/gpio.h>

#include <linux/gpio_keys.h>

#include <linux/platform_device.h>

#include <linux/input.h>


#include <asm/mach-types.h>

#include <asm/mach/arch.h>

#include <mach/da8xx.h>

#include <mach/mux.h>


/* 定义两个用户按键对应的GPIO,在开发板上对应的是GPIO0_6 和 GPIO6_1 */

#define DA850_USER_KEY0 GPIO_TO_PIN(0, 6) //SW5

#define DA850_USER_KEY1 GPIO_TO_PIN(6, 1) //SW6


#define DA850_KEYS_DEBOUNCE_MS 10

/*

 * At 200ms polling interval it is possible to miss an

 * event by tapping very lightly on the push button but most

 * pushes do result in an event; longer intervals require the

 * user to hold the button whereas shorter intervals require

 * more CPU time for polling.

 */

#define DA850_GPIO_KEYS_POLL_MS 200


#if 0

/* assign the tl base board KEY-GPIOs*/

static const short tl138_user_key_pins[] = {

DA850_GPIO0_6, DA850_GPIO6_1,

-1

};

#endif


/*定义两个按键对应的GPIO号,有效电平(按下电平),名称,触发模式*/

/*使用linux提供的标准gpio-keys框架*/

static struct gpio_keys_button tl138_user_keys[] = {

[0] = {

.type = EV_KEY,

.active_low = 1,  /*有效电平(按下电平):高电平*/

.wakeup = 0,

.debounce_interval = DA850_KEYS_DEBOUNCE_MS,

.code = KEY_PROG1,

.desc = "user_key0", /*名称*/

.gpio = DA850_USER_KEY0, /*GPIO号:按键对应GPIO管脚*/

},

[1] = {

.type = EV_KEY,

.active_low = 1,

.wakeup = 0,

.debounce_interval = DA850_KEYS_DEBOUNCE_MS,

.code = KEY_PROG2,

.desc = "user_key1",

.gpio = DA850_USER_KEY1,

},

};


/*使用linux提供的标准platform_device 框架*/

static struct gpio_keys_platform_data tl138_user_keys_pdata = {

.buttons = tl138_user_keys,

.nbuttons = ARRAY_SIZE(tl138_user_keys),

//.poll_interval = DA850_GPIO_KEYS_POLL_MS,

};


static void  tl138_user_keys_release(struct device *dev)

{

};


static struct platform_device  tl138_user_keys_device = {

.name = "gpio-keys",

.id = 1,  /*可以先确定id号是否已经被使用,注意这个id是platform_device的id,跟按键个数无关*/

.dev = {

.platform_data = &tl138_user_keys_pdata,

.release = tl138_user_keys_release,

},

};


static int  __init  tl138_user_keys_init(void)

{

int ret;

#if 0     

ret = davinci_cfg_reg_list(tl138_user_key_pins);

if (ret)

pr_warning("tl138_user_keys_init : User KEYS mux failed :"

"%d\n", ret);

#endif

 /*注册KEY device设备,系统中的KEY框架将会接收到这个注册,生成相应在/dev/input下生成响应的设备节点*/

ret = platform_device_register(&tl138_user_keys_device);

if (ret)

pr_warning("Could not register baseboard GPIO tronlong keys");

        else

                printk(KERN_INFO "USER KEYS register sucessful!\n");

       return ret;

}


static void __exit tl138_user_keys_exit(void)

{

platform_device_unregister(&tl138_user_keys_device);


printk(KERN_INFO "KEYS unregister!\n");

}


module_init(tl138_user_keys_init);

module_exit(tl138_user_keys_exit);

MODULE_DESCRIPTION("USER KEYS platform driver");

MODULE_AUTHOR("Tronlong");

MODULE_LICENSE("GPL");


编译按键设备驱动程序

开发板资料光盘中有按键设备驱动程序Makefile文件,其路径为:

Makefile: demo\driver\linux-3.3\button\Makefile

以下为按键设备驱动程序Makefile文件的解析:

ifneq ($(KERNELRELEASE),)

obj-m := button.o/*定义了要编译的驱动文件为button.c,生成的模块名字为button.ko*/

else

/*以下定义运行编译命令时使用的内核源码、驱动源码路径、平台、使用的交叉编译工具链等参数*/

all:

make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE)

/*定义运行"make clean"时清除的文件*/

clean:

rm -rf *.ko *.o *.mod.o *.mod.c *.symvers  modul* .button.* .tmp_versions .*.*.cmd

help:

@echo "make KDIR=<you kernel path> CROSS_COMPILE=<your CROSS_COMPILE>"

endif


图 3


将光盘"demo\driver\linux-3.3\button"中的button.c和Makefile文件复制到Ubuntu任意路径,在button.c和Makefile文件所在目录运行如下命令编译按键设备驱动程序:

Host# make KDIR=/home/tl/omapl138/linux-3.3 CROSS_COMPILE=arm-none-linux-gnueabi-


图4


即可看到已生成驱动程序镜像文件button.ko。"KDIR=/home/tl/omapl138/linux-3.3"是内核源码路径,在运行前必须已正确编译过内核源码。

"CROSS_COMPILE=arm-none-linux-gnueabi-"是交叉编译工具链,从此项可以看出,Makefile文件中的一些编译参数可以以变量的形式,通过编译命令参数传递进去。


按键设备驱动测试程序解析

开发板资料光盘中有按键设备驱动测试程序源码,其路径为:

button_test.c  demo\app\button\button_test.c

以下为按键设备驱动测试程序解析:

/*头文件*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <time.h>

#include <fcntl.h>

#include <linux/input.h>


int main(int argc, char **argv)

{

int key_state;

int fd;

int ret;

int code;

struct input_event buf;


/*打开按键设备节点*/

fd = open("/dev/input/event1", O_RDONLY);


if (fd < 0) {

printf("Open Gpio_Keys failed!\n");

return -1;

}


/*打印成功打开按键设备节点提示信息*/

printf("Open Gpio_Keys successed!\n");


while(1) {

/*监听按键状态*/

ret = read(fd, &buf, sizeof(struct input_event));

if (ret <= 0) {

printf("read failed!\n");

return -1;

}

code = buf.code;

key_state = buf.value;


switch(code)

{

case KEY_PROG1:

code = '1';

break;

case KEY_PROG2:

code = '2';

break;

}


if(code != 0)

/*打印按键状态信息*/

printf("KEY_PROG_%c state= %d.\n", code, key_state);


}


printf("Key test finished.\n");

close(fd);

return 0;

}


编译设备驱动测试程序

将button_test.c文件复制到Ubuntu任意路径,在button_test.c文件所在目录运行如下命令编译按键设备驱动测试程序:

Host# arm-none-linux-gnueabi-gcc button_test.c -o button_test


图 5


可以看到在当前目录生成了测试程序镜像文件button_test。具体按键测试步骤请看用户手册快速体验相关小节。


设备驱动模块静态编译进内核

假如需要将设备驱动程序模块静态编译进内核,请按照如下步骤操作。

以LED设备驱动程序为例,将光盘"demo\driver\linux-3.3\led"目录下的设备驱动程序源代码led.c放到内核源码"drivers/char"目录下,修改内核源码"drivers/char"目录下Kconfig菜单配置文件,在"menu "Character devices""行下面添加如下内容:


图 6


config USER_LED:USER_LED是驱动程序的配置名称。

tristate "user led":在使用"make menuconfig"配置内核时菜单栏出现的驱动名字。

depends on ARM:注明是ARM平台下的驱动程序。

default y:默认是静态编译到内核镜像的。

---help---:驱动程序的补充信息,让用户进一步了解此驱动程序的作用。

修改内核源码"drivers/char"目录下的Makefile编译文件,在最后添加如下内容:

obj-$(CONFIG_USER_LED)    += led.o


图7


obj-$(CONFIG_USER_LED):"USER_LED"此内容必须和前面步骤Kconfig文件中添加的内容一致。

+= led.o:这个前缀必须是"led",编译驱动程序时,系统会去找"driver/char"目录下的led.c文件。

在内核源码顶层目录执行以下命令查看设备内核配置情况:

Host# make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-

可在"device drivers->character devices"下有"user led"的驱动配置选项,如下图:


图 8


前面的"*"符号代表将设备驱动模块静态编译进内核。保存退出,并重新编译内核,然后使用编译得到的内核镜像启动开发板,可发现在不用安装led.ko的情况下,可以直接运行led_loop.sh来实现LED的循环点亮。

若需要将设备驱动模块编译成内核模块的形式,按空格键将"*"变为"M",变为空表示不编译。

(0 ) (1 )
回复 举报

回复于 2020-08-27 沙发

感谢分享
(0 )
评论 (0) 举报

回复于 2020-08-27 2#

感谢分享
(0 )
评论 (0) 举报
  • 发表回复
    0/3000





    举报

    请选择举报类别

    • 广告垃圾
    • 违规内容
    • 恶意灌水
    • 重复发帖

    全部板块

    返回顶部