【迅為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() 裡面的操作!