1. 程式人生 > >【迅為iTop4412學習筆記】14.編寫一個LED驅動

【迅為iTop4412學習筆記】14.編寫一個LED驅動

宣告

以下都是我剛開始看驅動視訊的個人強行解讀,如果有誤請指出,共同進步。

本節目標

編寫一個LED驅動


本節我們就真正的來驅動一個板子上的LED(微控制器第一節就是點燈,沒想到Linux要學這麼久…)

我們首先理清思路。

註冊裝置 -> 註冊驅動 ->呼叫probe()

這是我們之前學習的過程

我們本節關注的重點就是在probe()裡以雜項裝置的方式生成裝置節點,以及申請GPIO資源

我們先上一個底稿程式碼,我們能以模組的方式插入核心並呼叫probe()

#include <linux/init.h>
#include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("MrYang"); int mryang_probe(struct platform_device *pdv) { printk(KERN_EMERG "probe!\n"); return 0; } int mryang_remove(struct platform_device *pdv) { printk(KERN_EMERG "remove!\n"); misc_deregister(&mryang_dev); return
0; } struct platform_driver mryang_driver = { .probe = mryang_probe, .remove = mryang_remove, .driver = { .name = "mryang_ctl", // 像這種變數最好定義一個巨集定義在開頭,別問我為啥 .owner = THIS_MODULE, } }; static int mryang_init(void) { printk(KERN_EMERG "HELLO MrYang\n"); platform_driver_register(&mryang_driver)
; return 0; } static void mryang_exit(void) { printk(KERN_EMERG "Bye MrYang\n"); platform_driver_unregister(&mryang_driver); } module_init(mryang_init); module_exit(mryang_exit);

我們接下來要做的,就是在probe()裡生成裝置節點和申請GPIO資源。

生成裝置節點

我們前面註冊雜項裝置,生成裝置節點章節其實就講過這方面。我們生成裝置節點的目的,主要就是為了對接上層的開啟(open()函式),關閉(close()函式),控制(ioctl()函式)。
所以我們包含註冊雜項裝置,生成裝置節點的標頭檔案和程式碼。我們要記住的是,我們的裝置節點名字叫做 mryang_led_ctl 。

#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

/******************** 生成裝置節點 ********************/
static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}

static int mryang_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_release\n");
	return 0;
}

static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{	
	printk("cmd is %u\n",cmd);
	printk("cmd is %lu\n",arg);
	
	gpio_set_value(EXYNOS4_GPL2(0),cmd);
	
	return 0;
}

static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	.open = mryang_open,
	.release = mryang_release,
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
	.minor = MISC_DYNAMIC_MINOR,
	// 次裝置的名字
	.name = "mryang_led_ctl",
	
	.fops = &mryang_ops,
};

把他們綜合起來,其實就是筆記9的內容。

我們本節的重點講解來了

申請GPIO資源

如果說學過微控制器,其實很好理解GPIO的初始化過程。
之前也提到過,對於linux來說,硬體不直接開放給上層,所以我們需要使用硬體的時候需要向 linux 申請,申請的函式就是

int gpio_request(unsigned gpio, const char *label)
gpio_request(EXYNOS4_GPL2(0),"LEDS");

而申請的標頭檔案

#include <linux/gpio.h>		// 這個屬於linux通用標頭檔案,下面標頭檔案已包含他(只介紹)

引數簡單命令,引數一表示你申請的GPIO引腳是哪一個,引數二隻是一個標籤,實際意義就是告訴人家你這個GPIO是幹啥用的,內容隨便怎麼寫,我們就寫個LEDS表面是個LED就行。

那麼問題來了,我們想控制的LED是GPL2_0腳。那麼填入的引數一應該是什麼呢?我們先上標頭檔案。

#include <mach/gpio.h>			// 三星平臺,配置GPIO的引數(如控制高低電平的巨集定義)
#include <mach/gpio-exynos4.h>// 三星平臺,定義實際晶片GPIO的巨集定義(比如GPL2_0)

這倆是三星4412平臺提供的標頭檔案,裡面定義了GPIO的引腳,以及配置GPIO的引數。
直接上例項,例項講解

	// 申請GPIO,他是GPL2(0)腳,這個引腳的標籤是LEDS(讓看程式碼的人知道這個引腳是用於LED)
	gpio_request(EXYNOS4_GPL2(0),"LEDS");
	// 配置GPL2(0)引腳為輸出(若學過微控制器不難理解)
	s3c_gpio_cfgpin(EXYNOS4_GPL2(0), S3C_GPIO_OUTPUT);
	// 設定預設輸出為低電平(0),因為LED原理圖看到三極體高電平開,低電平關。
	gpio_set_value(EXYNOS4_GPL2(0),0);

當然,我們實際程式碼不能只這麼寫,申請GPIO是有返回值的,我們要根據返回值來檢視資訊。沒申請到GPIO返回負值,那麼我們就沒必要繼續執行下去了。

配置好了GPIO,我們怎麼控制開關呢?自然是對上的ioctl來傳遞引數,所以用ioctl傳來的引數來控制LED。ioctl()裡面寫啥?上面預設電平的程式碼放進 mryang_unlocked_ioctl() 不就完事了,引數我們不寫0,就把cmd傳來的值當點評吧。

下面上整個驅動檔案的程式碼

#include <linux/init.h>
#include <linux/module.h>

#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
#include <mach/gpio-exynos4.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

/******************** 生成裝置節點 ********************/
static int mryang_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_open!\n");
	return 0;
}

static int mryang_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "mryang_release\n");
	return 0;
}

static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{	
	printk("cmd is %u\n",cmd);
	printk("cmd is %lu\n",arg);
	gpio_set_value(EXYNOS4_GPL2(0),cmd);
	return 0;
}

static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	.open = mryang_open,
	.release = mryang_release,
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次裝置號,可以指定次裝置號是多少,若是想讓linux自己去分配,則定義為巨集定義
	.minor = MISC_DYNAMIC_MINOR,
	// 次裝置的名字
	.name = "mryang_led_ctl",
	
	.fops = &mryang_ops,
};

/******************** mryang_ctl裝置的驅動 ********************/

int mryang_probe(struct platform_device *pdv)
{
	int flag = -1;
	
	printk(KERN_EMERG "probe!\n");
	
	/* 如果驅動最開始章節你的LED沒有取消編譯進核心,你會發現申請GPIO失敗,返回-16。
	因為迅為的核心裡有一個驅動正在驅動GPIO了,不釋放你無法申請此GPIO。
	所以在此用了一個告訴linux釋放GPIO的函式
	*/
	gpio_free(EXYNOS4_GPL2(0));
	
	// 申請GPIO
	flag = gpio_request(EXYNOS4_GPL2(0),"LEDS");
	
	if(flag < 0){
		printk(KERN_EMERG "gpio_request EXYNOS4_GPL2(0) failed!\n");
		return flag;
	}
	// 配置GPIO為輸出
	s3c_gpio_cfgpin(EXYNOS4_GPL2(0), S3C_GPIO_OUTPUT);
	// 設定預設輸出為0
	gpio_set_value(EXYNOS4_GPL2(0),0);
	
	misc_register(&mryang_dev);
	
	return 0;
}

int mryang_remove(struct platform_device *pdv)
{
	printk(KERN_EMERG "remove!\n");
	misc_deregister(&mryang_dev);
	return 0;
}

struct platform_driver mryang_driver = {
	.probe = mryang_probe,
	.remove = mryang_remove,
	.driver = {
		.name = "mryang_ctl",	// 像這種變數最好定義一個巨集定義在開頭,別問我為啥
		.owner = THIS_MODULE,
	}
};

static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	platform_driver_register(&mryang_driver);
	return 0;
}

static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	platform_driver_unregister(&mryang_driver);
}

module_init(mryang_init);
module_exit(mryang_exit);
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
	char *mryang_node = "/dev/mryang_led_ctl";
	
	int fd = open(mryang_node, O_RDWR|O_NDELAY);
	
	int cmd;
	
	if(fd < 0)
	{
		printf("open failed!\n");
	}
	else
	{
		cmd = atoi(argv[1]);
		printf("open success! fd=%d, cmd=%d\n", fd, cmd);
		ioctl(fd, cmd, 2);
	}
	
	close(fd);

	return 0;
}

驅動寫完了,我們想開關燈,那自然是要編寫應用了。根據上一節的知識。我們直接上一個簡單的模板

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
	char *mryang_node = "/dev/mryang_led_ctl";
	
	int fd = open(mryang_node, O_RDWR|O_NDELAY);
	
	if(fd < 0)
	{
		printf("open failed!\n");
	}
	else
	{
		printf("open success! fd=%d", fd);
		ioctl(fd, 1, 2);
	}
	
	close(fd);

	return 0;
}

但是這個驅動太傻了呀,第二個引數是cmd,我們寫入的是1,那麼燈就亮。但是寫的固定死了,不能隨心而動可不行。

這裡我們可以利用main()函式的argc和argv傳參

因為我們都是成年人了,簡單的展示,我就不檢查傳入的引數個數了,我們啟動應用的時候,後面跟一個0或者1,總之東西比較簡單,懶得解釋了…
直接上完整控制程式碼

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
	char *mryang_node = "/dev/mryang_led_ctl";
	
	int fd = open(mryang_node, O_RDWR|O_NDELAY);
	
	int cmd;
	
	if(fd < 0)
	{
		printf("open failed!\n");
	}
	else
	{
		cmd = atoi(argv[1]);
		printf("open success! fd=%d, cmd=%d\n", fd, cmd);
		ioctl(fd, cmd, 2);
	}
	
	close(fd);

	return 0;
}

這樣我們編譯之後
./app 1 則燈亮
./app 0 則燈滅

大功告成

收尾

這一節相比之前其實就多了一個申請GPIO,配置GPIO,設定GPIO電平這麼幾個東西。

我們要注意的是。我們的裝置是之前編譯進核心的mryang_ctl

這東西真的對應實際裝置嗎?其實沒有,就像你註冊了公司,但不代表你真開了公司,你只是你有佔了這個名份…

那麼他不是LED為什麼我們控制了LED?

我們本節的裝置和驅動,其實都是明修棧道,都是虛的。

在linux匹配裝置和驅動呼叫的probe()裡面我們呼叫的函式,這些操作才是暗度陳倉。

probe()裡面我們生成了裝置節點,以及申請了GPIO。這兩個才是上層應用呼叫時驅動的東西。至於我們本節註冊的裝置和驅動,只不過是一個手段罷了。

核心還是本節 probe() 裡面的操作!