電子產(chǎn)業(yè)一站式賦能平臺(tái)

PCB聯(lián)盟網(wǎng)

搜索
查看: 1350|回復(fù): 0
收起左側(cè)

基于OMAPL138的Linux設(shè)備驅(qū)動(dòng)程序開發(fā)入門

[復(fù)制鏈接]

678

主題

902

帖子

8293

積分

高級(jí)會(huì)員

Rank: 5Rank: 5

積分
8293
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2020-8-27 10:33:08 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
LED設(shè)備驅(qū)動(dòng)程序 LED設(shè)備驅(qū)動(dòng)程序解析開發(fā)板LED編號(hào)和GPIO對應(yīng)關(guān)系如下:

表 1
開發(fā)板型號(hào)
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

開發(fā)板資料光盤中有LED設(shè)備驅(qū)動(dòng)程序源碼,其路徑為:
led.c:demo\driver\linux-3.3\led\led.c
下面以TL138/1808-EVM開發(fā)板為例講解此設(shè)備驅(qū)動(dòng)程序。
#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>

/* 因?yàn)槭褂昧似脚_(tái)相關(guān)的頭文件,所以編譯時(shí)需要ARCH=arm */
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/da8xx.h>
#include <mach/mux.h>

/*定義4個(gè)用戶LED對應(yīng)的GPIO,開發(fā)板LED對應(yīng)編號(hào)分別是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個(gè)LED對應(yīng)的GPIO號(hào)、有效電平(熄燈電平)、名稱、觸發(fā)模式等*/
/*使用Linux提供的標(biāo)準(zhǔn)gpio-led框架*/
static struct gpio_led da850_evm_tl_leds[] = {
{
.active_low = 0, /*有效電平(熄燈電平):低電平*/
.gpio = DA850_USER_LED0, /*GPIO號(hào):LED對應(yīng)gpio管腳*/
.name = "user_led0", /*名稱:對應(yīng)/sys/class/leds/下的名稱*/
.default_trigger = "default-on", /*觸發(fā)模式:默認(rèn)點(diǎn)亮*/
},
{
.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提供的標(biāo)準(zhǔn)platform_device 框架*/
static struct platform_device da850_evm_tl_leds_device = {
.name = "leds-gpio",
.id = 1, /*先確定id號(hào)是否被使用,此id是platform_device的id,跟LED個(gè)數(shù)無關(guān)*/
.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設(shè)置接口,把LED對應(yīng)的管腳配置成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設(shè)備,系統(tǒng)LED框架將會(huì)接收到這個(gè)注冊,生成相應(yīng)LED節(jié)點(diǎn)*/
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設(shè)備驅(qū)動(dòng)程序解析,對于Linux對LED設(shè)備框架,這里稍微說明一下:
  • Linux的LED設(shè)備類在內(nèi)核"Documentation/leds/leds-class.txt"文件有詳細(xì)說明。
  • 注冊一個(gè)LED設(shè)備成功后,會(huì)"/sys/class/leds/"生成相應(yīng)的設(shè)備節(jié)點(diǎn)。
  • 用戶可以通過讀寫節(jié)點(diǎn)目錄下的brightness文件控制LED亮滅。
對于GPIO口的操作,有以下幾點(diǎn)步驟:
  • 查看開發(fā)板的原理圖,找到與LED連接的GPIO。TL138/1808-EVM開發(fā)板與LED連接的GPIO分別是GPIO0[5]、GPIO0[0]、GPIO0[1]、GPIO0[2]。
  • 查看OMAP-L138的數(shù)據(jù)手冊,查找對應(yīng)PINMUX寄存器的地址,將對應(yīng)的管腳的寄存器中相應(yīng)位設(shè)置為GPIO的工作模式。本例中使用的是PINMUX1。
  • 設(shè)置GPIO的方向寄存器。本例程中將GPIO口配置為輸出。
  • 配置GPIO的數(shù)據(jù)寄存器,寫"1"表示輸出高電平,寫"0"表示輸出低電平。
編譯LED設(shè)備驅(qū)動(dòng)程序此處使用Makefile編譯LED設(shè)備驅(qū)動(dòng)程序。工程中源文件有時(shí)候很多,其按類型、功能、模塊分別放在若干個(gè)目錄中,Makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進(jìn)行更復(fù)雜的功能操作,因?yàn)镸akefile就像一個(gè)Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。
開發(fā)板資料光盤中有LED設(shè)備驅(qū)動(dòng)程序Makefile文件,其路徑為:
Makefile: demo\driver\linux-3.3\led\Makefile
以下為LED設(shè)備驅(qū)動(dòng)程序Makefile文件的解析:
ifneq ($(KERNELRELEASE),)
obj-m := led.o /*定義了要編譯的驅(qū)動(dòng)文件為led.c,生成的模塊名字為led.ko*/
else
/*以下定義運(yùn)行編譯命令時(shí)使用的內(nèi)核源碼、驅(qū)動(dòng)源碼路徑、平臺(tái)、使用的交叉編譯工具鏈等參數(shù)*/
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
/*定義運(yùn)行"make clean"時(shí)清除的文件*/
clean:
rm -rf *.ko *.o *.mod.o *.mod.c *.symvers  modul* .button.* .tmp_versions

#help: make KDIR=<you kernel path>
endif
&#8203;

圖 1

將光盤"demo\driver\linux-3.3\led"的led.c和Makefile文件復(fù)制到開發(fā)系統(tǒng)Ubuntu任意路徑,并在led.c和Makefile目錄運(yùn)行以下命令編譯LED設(shè)備驅(qū)動(dòng)程序:
Host#make KDIR=/home/tl/omapl138/linux-3.3
&#8203;

圖2

"KDIR=/home/tl/omapl138/linux-3.3"是內(nèi)核源碼路徑,在運(yùn)行前必須已正確編譯過內(nèi)核源碼。運(yùn)行以上命令后,系統(tǒng)會(huì)根據(jù)Makefile文件的規(guī)則去編譯整個(gè)驅(qū)動(dòng)源碼,產(chǎn)生了驅(qū)動(dòng)程序鏡像文件led.ko和其他中間文件。

LED設(shè)備驅(qū)動(dòng)測試腳本解析開發(fā)板資料光盤有LED設(shè)備驅(qū)動(dòng)測試腳本,運(yùn)行此測試腳本LED會(huì)循環(huán)點(diǎn)亮。其路徑為:
led_loop.sh demo\app\led\led_loop.sh
以下為測試腳本的解析:
#init all user led #關(guān)閉所有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 #定義流水燈延時(shí)時(shí)間

#led loop
while true; do
    echo 1 > /sys/class/leds/user_led0/brightness #點(diǎn)亮LED0 D7
    sleep $DELAY_TIME
    echo 0 > /sys/class/leds/user_led0/brightness #關(guān)閉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測試步驟請查看用戶手冊快速體驗(yàn)相關(guān)小節(jié)。

&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設(shè)備驅(qū)動(dòng)程序&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設(shè)備驅(qū)動(dòng)程序解析
開發(fā)板資料光盤中有按鍵設(shè)備驅(qū)動(dòng)程序源碼,對應(yīng)的按鍵為SW5和SW6,以linux-3.3內(nèi)核驅(qū)動(dòng)為例,其路徑為:
button.c: demo\driver\linux-3.3\button\button.c
以下為此驅(qū)動(dòng)程序的解析:
#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>

/* 定義兩個(gè)用戶按鍵對應(yīng)的GPIO,在開發(fā)板上對應(yīng)的是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

/*定義兩個(gè)按鍵對應(yīng)的GPIO號(hào),有效電平(按下電平),名稱,觸發(fā)模式*/
/*使用linux提供的標(biāo)準(zhǔn)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號(hào):按鍵對應(yīng)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提供的標(biāo)準(zhǔn)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號(hào)是否已經(jīng)被使用,注意這個(gè)id是platform_device的id,跟按鍵個(gè)數(shù)無關(guān)*/
.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設(shè)備,系統(tǒng)中的KEY框架將會(huì)接收到這個(gè)注冊,生成相應(yīng)在/dev/input下生成響應(yīng)的設(shè)備節(jié)點(diǎn)*/
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");

編譯按鍵設(shè)備驅(qū)動(dòng)程序開發(fā)板資料光盤中有按鍵設(shè)備驅(qū)動(dòng)程序Makefile文件,其路徑為:
Makefile: demo\driver\linux-3.3\button\Makefile
以下為按鍵設(shè)備驅(qū)動(dòng)程序Makefile文件的解析:
ifneq ($(KERNELRELEASE),)
obj-m := button.o/*定義了要編譯的驅(qū)動(dòng)文件為button.c,生成的模塊名字為button.ko*/
else
/*以下定義運(yùn)行編譯命令時(shí)使用的內(nèi)核源碼、驅(qū)動(dòng)源碼路徑、平臺(tái)、使用的交叉編譯工具鏈等參數(shù)*/
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE)
/*定義運(yùn)行"make clean"時(shí)清除的文件*/
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
&#8203;

圖 3

將光盤"demo\driver\linux-3.3\button"中的button.c和Makefile文件復(fù)制到Ubuntu任意路徑,在button.c和Makefile文件所在目錄運(yùn)行如下命令編譯按鍵設(shè)備驅(qū)動(dòng)程序:
Host#make KDIR=/home/tl/omapl138/linux-3.3 CROSS_COMPILE=arm-none-linux-gnueabi-
&#8203;

圖4

即可看到已生成驅(qū)動(dòng)程序鏡像文件button.ko。"KDIR=/home/tl/omapl138/linux-3.3"是內(nèi)核源碼路徑,在運(yùn)行前必須已正確編譯過內(nèi)核源碼。
"CROSS_COMPILE=arm-none-linux-gnueabi-"是交叉編譯工具鏈,從此項(xiàng)可以看出,Makefile文件中的一些編譯參數(shù)可以以變量的形式,通過編譯命令參數(shù)傳遞進(jìn)去。

&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設(shè)備驅(qū)動(dòng)測試程序解析開發(fā)板資料光盤中有按鍵設(shè)備驅(qū)動(dòng)測試程序源碼,其路徑為:
button_test.c demo\app\button\button_test.c
以下為按鍵設(shè)備驅(qū)動(dòng)測試程序解析:
/*頭文件*/
#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;

/*打開按鍵設(shè)備節(jié)點(diǎn)*/
fd = open("/dev/input/event1", O_RDONLY);

if (fd < 0) {
printf("Open Gpio_Keys failed!\n");
return -1;
}

/*打印成功打開按鍵設(shè)備節(jié)點(diǎn)提示信息*/
printf("Open Gpio_Keys successed!\n");

while(1) {
/*監(jiān)聽按鍵狀態(tài)*/
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)
/*打印按鍵狀態(tài)信息*/
printf("KEY_PROG_%c state= %d.\n", code, key_state);

}

printf("Key test finished.\n");
close(fd);
return 0;
}

編譯設(shè)備驅(qū)動(dòng)測試程序將button_test.c文件復(fù)制到Ubuntu任意路徑,在button_test.c文件所在目錄運(yùn)行如下命令編譯按鍵設(shè)備驅(qū)動(dòng)測試程序:
Host#arm-none-linux-gnueabi-gcc button_test.c -o button_test
&#8203;

圖 5

可以看到在當(dāng)前目錄生成了測試程序鏡像文件button_test。具體按鍵測試步驟請看用戶手冊快速體驗(yàn)相關(guān)小節(jié)。

設(shè)備驅(qū)動(dòng)模塊靜態(tài)編譯進(jìn)內(nèi)核假如需要將設(shè)備驅(qū)動(dòng)程序模塊靜態(tài)編譯進(jìn)內(nèi)核,請按照如下步驟操作。
以LED設(shè)備驅(qū)動(dòng)程序?yàn)槔,將光盤"demo\driver\linux-3.3\led"目錄下的設(shè)備驅(qū)動(dòng)程序源代碼led.c放到內(nèi)核源碼"drivers/char"目錄下,修改內(nèi)核源碼"drivers/char"目錄下Kconfig菜單配置文件,在"menu "Character devices""行下面添加如下內(nèi)容:
&#8203;

圖 6

config USER_LED:USER_LED是驅(qū)動(dòng)程序的配置名稱。
tristate "user led":在使用"make menuconfig"配置內(nèi)核時(shí)菜單欄出現(xiàn)的驅(qū)動(dòng)名字。
depends on ARM:注明是ARM平臺(tái)下的驅(qū)動(dòng)程序。
default y:默認(rèn)是靜態(tài)編譯到內(nèi)核鏡像的。
---help---:驅(qū)動(dòng)程序的補(bǔ)充信息,讓用戶進(jìn)一步了解此驅(qū)動(dòng)程序的作用。
修改內(nèi)核源碼"drivers/char"目錄下的Makefile編譯文件,在最后添加如下內(nèi)容:
obj-$(CONFIG_USER_LED)    += led.o
&#8203;

圖7

obj-$(CONFIG_USER_LED):"USER_LED"此內(nèi)容必須和前面步驟Kconfig文件中添加的內(nèi)容一致。
+= led.o:這個(gè)前綴必須是"led",編譯驅(qū)動(dòng)程序時(shí),系統(tǒng)會(huì)去找"driver/char"目錄下的led.c文件。
在內(nèi)核源碼頂層目錄執(zhí)行以下命令查看設(shè)備內(nèi)核配置情況:
Host#make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
可在"device drivers->character devices"下有"user led"的驅(qū)動(dòng)配置選項(xiàng),如下圖:

&#8203;

圖 8

前面的"*"符號(hào)代表將設(shè)備驅(qū)動(dòng)模塊靜態(tài)編譯進(jìn)內(nèi)核。保存退出,并重新編譯內(nèi)核,然后使用編譯得到的內(nèi)核鏡像啟動(dòng)開發(fā)板,可發(fā)現(xiàn)在不用安裝led.ko的情況下,可以直接運(yùn)行l(wèi)ed_loop.sh來實(shí)現(xiàn)LED的循環(huán)點(diǎn)亮。
若需要將設(shè)備驅(qū)動(dòng)模塊編譯成內(nèi)核模塊的形式,按空格鍵將"*"變?yōu)?quot;M",變?yōu)榭毡硎静痪幾g。

嵌入式DSP、ARM、FPGA多核技術(shù)開發(fā),學(xué)習(xí)資料下載:http://site.tronlong.com/pfdownload

發(fā)表回復(fù)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

關(guān)閉

站長推薦上一條 /1 下一條


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表