linux驅動篇-Button-中斷法

本篇是linux下按鍵設備驅動,採用的中斷法,也是屬於字符設備類的驅動,一起來動手吧。下面的話,老朋友可以跳過了直接從《需求描述》章節看起,新朋友可以試着看看。

 

前言

在嵌入式行業,有很多從業者。我們工作的主旋律是拿開源代碼,拿廠家代碼,完成產品的功能,提升產品的性能,進而解決各種各樣的問題。或者是維護一個模塊或方向,一搞就是好幾年。

 

時間長了,中年潤髮現我們對從零開始編寫驅動、應用、算法、系統、協議、文件系統等缺乏經驗。沒有該有的廣度和深度。中年潤也是這樣,工作了很多年,都是針對某個問題點修修補補或者某個模塊的局部刪刪改改。很少有機會去獨自從零開始編寫一整套完整的代碼。

 

當然,這種現狀對於企業來說是比較正常的,可以降低風險。但是對於員工本身,如果缺乏必要的規劃,很容易工作多年卻還是停留在單點的層面,而喪失了提升到較高層面的機會。隨着時間的增長很容易喪失競爭力。

 

另外,根據中年潤的經驗,絕大多數公司對於0-5年經驗從業者的定位主要是積極的問題解決者。而對於5-10經驗從業者的定位主要是積極的系統規劃者和引領者。在這種行業規則下,中年潤認爲,每個從業者都應該問自己一句,“5年後,我是否具備系統化把控軟件的能力呢?”。

 

當前的這種行業現狀,如果我們不做出一點改變,是沒有辦法突破的。有些東西,僅僅知道是不夠的,還需要深思熟慮的思考和必要的訓練,簡單來說就是要知行合一。

 

也許有讀者會有疑惑?這不就是重複造輪子麼?我們確實是在重複造輪子,因爲別人會造輪子那是別人的能力,我們自己會造輪子是我們自己的能力。在行業中,有太多的定製化需求是因爲輪子本身有原生性缺陷,我們無法直接使用,或者需要對其進行改進,或者需要抽取開源代碼的主體思想和框架,根據公司的需要定製自己的各項功能。設想,如果我們具備這種能力,必然會促使我們在行業中脫穎而出,而不是工作很多年一直在底層搬磚。底層搬磚沒什麼不好,問題是當有更廉價更激情的勞動力湧進來的時候,我們這些老的搬磚民工也就失去了價值。我們不會天天重複造輪子,我們需要通過造幾個輪子使得自己具備造輪子的能力,從而更好的適應這個環境,適應這個世界。

 

針對當前行業現狀,中年潤經過深思熟慮,想爲大家做點實實在在的事情,希望能夠幫助大家在鞏固基礎的同時提升系統化把控軟件的能力。當然,中年潤的水平也有限,有些觀點也只是一家之談,希望大家獨立思考,謹慎採用,如果寫的有錯誤或者不對的地方還請讀者們批評斧正,我們一起共同進步。

 

在這裏簡單介紹下中年潤,中年潤現在就職於一家大型國際化公司,工作經驗6年,碩士畢業。曾經擔任過組內的項目主管,項目經理,也曾經組建過新團隊,帶領大家衝鋒陷陣。在工作中,有做的不錯的地方,也有失誤的地方,有激情的時刻,也有失落的時刻。現在偏安一隅,專心搞技術,目前個人規劃的技術方向是嵌入式和AI基礎設施建設,以及嵌入式和AI的融合發展。

 

最後,說了這麼多,中年潤希望,在未來的日子裏和未知的領域裏,你我同行,爲我們的美好生活而努力奮鬥。

 

總體目標

本篇文章的目標是介紹如何從自頂向下從零編寫linux下的按鍵字符設備驅動(採用中斷法)。着力從總體思路,需求端,分析端,實現端,詳盡描述一個完整需求的開發流程,是中年潤多年經驗的提煉,希望讀者能夠有所收穫。最後的實戰目標,請讀者儘量完成,這樣讀者才能形成自己的思路。

本示例採用arm920架構,天祥電子生產的tx2440a開發板,核心爲三星的s3c2440。Linux版本爲2.6.31,是已經移植好的版本。編譯器爲arm920t-eabi-4.1.2.tar。

 

總體思路

總體思路是嚴格遵循需求的開發流程來,不遺漏任何思考環節。讀者在閱讀時請先跟中年潤的思路走一遍,然後再拋棄中年潤的思路,按照自己的思路走一遍,如果遇到困難請先自己思考,實在不會再來參考中年潤的思路和實現。

 

中年潤在寫代碼的的總體思路如下:

需求描述—能夠詳細完整的描述一個需求。

需求分析—根據需求描述,提取可供實現的功能,需要有定量或者定性的指標。(從宏觀上確定需要什麼功能)。

需求分解—根據需求分析,考慮要實現需求所需要做的工作(根據宏觀確定的功能,拆分成小的可單獨實現的功能)。

編寫思路—根據需求分解從總體上描述應該如何編寫代碼,(解決怎麼在宏觀上實現)。

詳細步驟—根據編寫思路,落實具體步驟,(解決怎麼在微觀上實現)。

編寫框架—根據編寫思路,實現總體框架(實現編寫思路里主體框架,細節內容留在具體代碼裏編寫)。

具體代碼—根據編寫框架和詳細步驟,編寫每一個函數裏所需要實現的小功能,主要是實現驅動代碼,測試代碼。

Makefile—用來編譯驅動代碼。

目錄結構—用來說明當完成編碼後的結果。

測試步驟—說明如何對驅動進行測試,主要是加載驅動模塊,執行測試代碼。

執行結果—觀察執行結果是否符合預期。

結果總結—回顧本節的知識點,api,結構體。

實戰目標—說明如何根據本文檔訓練。

請大家儘量按照自頂向下的學習思路來學習和實戰,因爲我們所有工作的動力都是我們心中的需求。這些步驟僅僅是我們達到目標所要走過的路。目錄起到提綱挈領的重要作用,寫的時候要實時看下提綱,看有沒有偏離自己的方向。

 

需求描述

使用linux提供的字符設備api接口,編寫一個按鍵字符設備驅動和一個測試代碼,能夠在輸入./button命令後,按下按鍵時,在串口輸出按下了哪個按鍵。同時用戶不希望用戶線程佔用太多的cpu資源。

 

需求分析

對於按鍵字符設備驅動來說,測試代碼就是我們的用戶,因此我們可以通過分析測試代碼的邏輯來分析我們的需求。

 

首先梳理用戶的工作流程,用戶的工作流程如下:

1用戶調用open系統調用打開/dev/mybutton設備節點,獲取fd

2用戶拿到fd之後,調用read系統調用讀取fd中的值                                                                     

3如果沒有數據,希望用戶進程睡眠,不希望大量佔用cpu資源

4如果有數據,直接返回數據

5如果按下或鬆開按鍵,在中斷中保存當前的數據,並喚醒用戶線程,用戶線程直接拿數據。

 

根據用戶的工作流程,我們可以梳理出驅動所要做的工作,下圖說明了驅動所要做的主體,跟用戶層是一一對應的。

梳理完用戶工作流程後,我們再來看下用戶進程和中斷之間的交互,這樣對於爲什麼編寫某些函數會有直觀的感受。

需求分解

根據《需求分析》和用戶的操作,我們需要做以下幾件工作。

1用戶調用open系統調用打開/dev/mybutton設備節點,獲取fd

1.3   需要提供一個設備節點/dv/mybutton,

1.4   需要提供一個操作設備節點的open函數

2用戶拿到fd之後,調用read系統調用讀取fd中的值

2.1 需要提供操作設備節點的read函數,用來將讀取的數據返回     

3如果沒有數據,希望用戶進程睡眠,不希望大量佔用cpu資源

3.1 需要能判斷當前有無數據,需要在無數據時進入休眠狀態

4如果有數據,直接返回數據

4.1 需要能判斷當前有無數據,需要在有數據時能立即返回數據,並標記當前數據狀態爲無

4.2 需要一個有無數據的標誌,在產生數據時標記有,在讀取完畢後標記無

5如果按下或鬆開按鍵,在中斷中保存當前的數據,並喚醒用戶線程,用戶線程直接拿數據。

5.1 需要一箇中斷處理函數

5.2 需要能夠檢測當前按下的是哪個按鍵

5.3 需要在中斷中保存數據,並標記當前數據狀態爲有

5.4 需要喚醒用戶進程

5.5 在中斷函數中保存數據後,read函數能夠直接操作被保存的數據並返回給用戶

 

用一張圖來總結上述驅動所要做的工作,如下圖所示:

編寫思路

編寫思路主要用來搭建代碼框架,解決在宏觀上如何用代碼實現驅動的功能。

確定目標:實現需求分解中的所有函數

Init函數,exit函數,open函數,read函,close函數,中斷處理函數(在詳細步驟中編寫)

 

確定基本思路:

0搭建基礎框架

0.1編寫代碼框架,頭文件

0.2編寫空的出口函數

0.3編寫空的入口函數

0.4修飾出口函數,修飾入口函數,聲明LICENSE

 

在入口函數中所做的工作如下

1入口函數

1.1註冊一個字符設備,名字爲s3c_button

1.1.1定義file_operations結構體

1.1.2編寫空的打開函數,關閉函數,讀函數

1.2創建一個類,名字爲button-int,(決定了/sys/class下目錄的名字)

1.3創建一個設備節點,名字爲mybutton(決定了/dev下文件的名字)

1.4映射gpio的物理地址爲虛擬地址,一個是控制寄存器地址,一個是數據寄存器地址

 

在出口函數中所作的工作如下

2出口函數

2.1卸載字符設備

2.2刪除設備節點

2.3銷燬這個類

2.4取消地址映射

 

將上述步驟編寫完成就得到了《編寫框架》章節中的代碼

 

詳細步驟

詳細步驟主要用來在代碼框架裏填充必要的細節代碼,解決在微觀上如何用代碼實現驅動各個小功能。

 

0搭建基礎框架

0.1編寫代碼框架,頭文件

0.2編寫空的出口函數

0.3編寫空的入口函數

0.4修飾出口函數,修飾入口函數,聲明LICENSE

 

在入口函數中所做的工作如下

1入口函數

1.1註冊一個字符設備,名字爲s3c_button

1.1.1定義file_operations結構體

1.1.2編寫空的打開函數,關閉函數,讀函數

1.2創建一個類,名字爲button-int,(決定了/sys/class下目錄的名字)

1.3創建一個設備節點,名字爲mybutton(決定了/dev下文件的名字)

1.4映射gpio的物理地址爲虛擬地址,一個是控制寄存器地址,一個是數據寄存器地址

 

在出口函數中所作的工作如下

2出口函數

2.1卸載字符設備

2.2刪除設備節點

2.3銷燬這個類

2.4取消地址映射

 

3編寫file_operations裏的操作函數

3.1編寫打開函數,註冊中斷和中斷處理函數

3.2編寫關閉函數,釋放中斷

3.3編寫讀函數,讀取按下的鍵值

3.4編寫中斷處理函數,判斷當前是哪個按鍵按下,以及當前是按下還是鬆開

所有函數的基本流程圖如下圖示所示:

編寫框架

根據編寫思路,實現總體框架(實現編寫思路里主體框架,細節內容留在具體代碼裏編寫)。

/* 本文件名字爲button_drv_int_skel.c*/
/* 本文件是依照button-中斷法驅動<編寫思路>章節編寫,本文件
 * 的目的是編寫代碼框架,不做具體細節的編寫
 */

/* 0.1編寫代碼框架,頭文件 */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>

volatile unsigned long * gpfconf;
volatile unsigned long * gpfdat;
static struct class *my_button_cls;
static struct device * my_button_dev;
static int major;

/* 1.1.2編寫空的打開函數 */
static int button_open (struct inode * inode, struct file * filep)
{
	return 0;	
}

/* 1.1.2編寫空的關閉函數 */
static int button_release (struct inode * inode, struct file * filep)
{
	return 0;
}

/* 1.1.2編寫空的讀函數 */
static ssize_t button_read (struct file * filep, char __user * buff, size_t size, loff_t * pos)
{
	return 0;
}

/* 1.1.1定義file_operations結構體 */
static struct file_operations button_fops = {
	.owner  = THIS_MODULE,
	.open   = button_open,
	.read   = button_read,
	.release  = button_release,
	
};

/* 0.3編寫空的入口函數 */
static int my_button_init(void)
{
	int ret = 0;
	/* 1.1註冊一個字符設備,名字爲s3c_button */
	major = register_chrdev(0,"s3c_button",&button_fops);
	/* 1.2創建一個類,名字爲button-int,(決定了/sys/class下目錄的名字) */
	my_button_cls = class_create(THIS_MODULE,"button-int");
	/* 1.3創建一個設備節點,名字爲mybutton(決定了/dev下文件的名字) */
	my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton");
	/* 1.4映射gpio的物理地址爲虛擬地址,一個是控制寄存器地址,一個是數據寄存器地址 */
	gpfconf = (volatile unsigned long *)ioremap(0x56000050,16);	
	//獲得數據寄存器地址
	gpfdat  = gpfconf + 1;

	return ret;
}

/* 0.2編寫空的出口函數 */
static void my_button_exit(void)
{
	/* 2.1卸載字符設備 */
	unregister_chrdev(major," s3c_button");
	/* 2.2刪除設備節點 */
	device_unregister(my_button_dev);
	/* 2.3銷燬這個類 */
	class_destroy(my_button_cls);
	/* 2.4取消地址映射 */
	iounmap(gpfconf);
	return;
}

/* 0.4修飾出口函數,修飾入口函數,聲明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

驅動代碼

根據《編寫框架》《詳細步驟》章節,編寫每一個函數裏所需要實現的小功能。

/* 本文件名字爲button_drv_int.c*/
/* 本文件是依照button-中斷法驅動<詳細步驟>章節編寫,本文件
 * 的目的是編寫具體操作函數,不做框架介紹
 */

/* 0.1編寫代碼框架,頭文件 */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>

volatile unsigned long * gpfconf;
volatile unsigned long * gpfdat;
static struct class *my_button_cls;
static struct device * my_button_dev;
static int major;

DECLARE_WAIT_QUEUE_HEAD(button_waitq);

//引腳描述符
struct pin_desc{
	int pin;//代表是哪個引腳
	int key_val;//代表是按下還是鬆開
};

//引腳描述符,按引腳的不同定義不同的值,在中斷中進行處理
struct pin_desc pin_desc[4] = 
{
	{S3C2410_GPF4_EINT4,0x1},
	{S3C2410_GPF5_EINT5,0x2}, 
	{S3C2410_GPF6_EINT6,0x3},
	{S3C2410_GPF7_EINT7,0x4},
};

//標誌是否進入中斷
static int do_press_loos;

/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 */
/* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 */
/* 主要是爲了區分是按下還是鬆開 */
static unsigned char key_val;

irqreturn_t button_irq(int irq, void * devid)
{
	int pin_val;
	struct pin_desc * pin_readed;
	/*
	 *	1 獲取當前引腳的電平值
	 *	2 根據電平值判斷當前是按下還是鬆開
	 *		鬆開爲高電平,返回0x8x
	 *		按下爲低電平,返回0x0x
	 *	3 標記已有數據
	 *	4 喚醒處在內核態的用戶進程
	 */		
	pin_readed = (struct pin_desc *)devid;
	//獲取某個引腳是高還是低
	pin_val = s3c2410_gpio_getpin(pin_readed->pin);
		
	if (pin_val) //鬆開是高電平
	{
		key_val = 0x80 | pin_readed->key_val;
	}
	else		//按下爲低電平
	{
		//把當前按鍵的鍵值給一個全局靜態變量,在read函數裏給用戶
		key_val = pin_readed->key_val;
	}
	
	//標記中斷已經觸發
	do_press_loos = 1;

	//喚醒用戶的讀進程
	wake_up_interruptible(&button_waitq);

	return IRQ_RETVAL(IRQ_HANDLED);
}

/* 1.1.2編寫空的打開函數 */
static int button_open (struct inode * inode, struct file * filep)
{
	/* 3.1編寫打開函數,註冊中斷和中斷處理函數 */
	//request_irq裏已經幫忙將GPF4-GPF7設置成中斷引腳了
	
	int ret;
	/* 註冊外部中斷4,類型爲下降沿中斷,名字爲S1,
	   中斷處理函數爲button_irq
	   傳入中斷處理函數中的參數爲pin_desc[0] */
	ret = request_irq(IRQ_EINT4,button_irq,IRQ_TYPE_EDGE_FALLING,"S1",
						(void *)&pin_desc[0]);
	if (ret)
		goto errout;
	ret = request_irq(IRQ_EINT5,button_irq,IRQ_TYPE_EDGE_FALLING,"S2",
						(void *)&pin_desc[1]);
	if (ret)
		goto errout;
	ret = request_irq(IRQ_EINT6,button_irq,IRQ_TYPE_EDGE_FALLING,"S3",
						(void *)&pin_desc[2]);
	if (ret)
		goto errout;
	ret = request_irq(IRQ_EINT7,button_irq,IRQ_TYPE_EDGE_FALLING,"S4",
						(void *)&pin_desc[3]);
	if (ret)
		goto errout;

	return 0;
errout:
	return ret;	
}

/* 1.1.2編寫空的關閉函數 */
static int button_release (struct inode * inode, struct file * filep)
{
	/* 3.2編寫關閉函數,釋放中斷 */
	free_irq(IRQ_EINT4, &pin_desc[0]);
	free_irq(IRQ_EINT5, &pin_desc[1]);
	free_irq(IRQ_EINT6, &pin_desc[2]);
	free_irq(IRQ_EINT7, &pin_desc[3]);
	return 0;
}

/* 1.1.2編寫空的讀函數 */
static ssize_t button_read (struct file * filep, char __user * buff, size_t size, loff_t * pos)
{
	int ret = 0;
	/* 3.3編寫讀函數,讀取按下的鍵值,以及當前是按下還是鬆開 */
	if (size != 1)
		return -EINVAL;
	//根據do_press_loos判斷,如果沒有中斷,直接進入休眠
	wait_event_interruptible(button_waitq, do_press_loos);
	//如果被喚醒拷貝數據到用戶空間
	ret = copy_to_user(buff, &key_val, sizeof(key_val));
	if (ret) {
		return -EFAULT;
	}
	//讀取數據完畢後需要將標誌位清零,表示暫時無數據可讀
	do_press_loos = 0;
	return 1;	
}

/* 1.1.1定義file_operations結構體 */
static struct file_operations button_fops = {
	.owner  = THIS_MODULE,
	.open   = button_open,
	.read   = button_read,
	.release  = button_release,
	
};

/* 0.3編寫空的入口函數 */
static int my_button_init(void)
{
	int ret = 0;
	/* 1.1註冊一個字符設備,名字爲s3c_button */
	major = register_chrdev(0,"s3c_button",&button_fops);
	/* 1.2創建一個類,名字爲button-int,(決定了/sys/class下目錄的名字) */
	my_button_cls = class_create(THIS_MODULE,"button-int");
	/* 1.3創建一個設備節點,名字爲mybutton(決定了/dev下文件的名字) */
	my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton");
	/* 1.4映射gpio的物理地址爲虛擬地址,一個是控制寄存器地址,一個是數據寄存器地址 */
	gpfconf = (volatile unsigned long *)ioremap(0x56000050,16);	
	//獲得數據寄存器地址
	gpfdat  = gpfconf + 1;

	return ret;
}

/* 0.2編寫空的出口函數 */
static void my_button_exit(void)
{
	/* 2.1卸載字符設備 */
	unregister_chrdev(major," s3c_button");
	/* 2.2刪除設備節點 */
	device_unregister(my_button_dev);
	/* 2.3銷燬這個類 */
	class_destroy(my_button_cls);
	/* 2.4取消地址映射 */
	iounmap(gpfconf);
	return;
}

/* 0.4修飾出口函數,修飾入口函數,聲明LICENSE */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");

測試代碼

測試代碼編寫思路如下:

1打開/dev/mybutton文件

2讀取獲得的值

3打印獲得的值

 

測試代碼如下:

/* 本文件是button_test.c,是根據button-中斷法驅動的
  * <測試代碼>章節編寫,主要任務是用來測試
  * button 驅動
  */

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

/* button 驅動,中斷法實驗
  */
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	int cnt;
	
	fd = open("/dev/mybutton", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	while (1)
	{
		read(fd, &key_val, 1);
		printf("key_val = 0x%x\n", key_val);
	}
	
	return 0;
}

Makefile

KERN_DIR = /home/linux/tools/linux-2.6.31_TX2440A
all:
       make -C $(KERN_DIR) M=`pwd` modules
clean:
       make -C $(KERN_DIR) M=`pwd` modules clean
       rm -rf modules.order
obj-m += button_drv_int.o

目錄結構

代碼編寫完成的目錄結構如下所示。直接執行make即可生成.ko文件。

.

├── button_drv_int.c(驅動代碼)

├── button_drv_int_skel.c(驅動框架代碼)

├── button_test.c(驅動測試代碼)

└── Makefile(用來編譯驅動代碼)

 

測試步驟

0 在linux下的makefile +180行處配置好arch爲arm,cross_compile爲arm-linux-(或者arm-angstrom-linux-gnueabi-)

1 在menuconfig中配置好內核源碼的目標系統爲s3c2440

2 在pc上將驅動程序編譯生成.ko,命令:make

3 在pc上將測試程序編譯生成elf可執行文件,生成的button就是我們所要使用的命令。

編譯:arm-angstrom-linux-gnueabi-gcc button_test.c -o button_test

4 掛載nfs,這樣就可以在開發板上看到pc端的.ko文件和測試文件

mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root  /mnt/nfs

5 insmod button_drv.ko,加載按鍵的驅動模塊

6執行命令./button,依次按下四個按鍵,觀察串口的打印信息。

 

執行結果

執行完./button後,依次按下四個按鍵,串口的日誌如下,按下一個按鍵一次,會出現很多次打印。

[root@TX2440A 4-button-int]# insmod button_drv_int.ko

[root@TX2440A 4-button-int]# lsmod

button_drv_int 3356 0 - Live 0xbf000000

[root@TX2440A 4-button-int]# ./button_test

key_val = 0x4

key_val = 0x4

key_val = 0x3

key_val = 0x2

key_val = 0x2

key_val = 0x2

key_val = 0x2

key_val = 0x2

key_val = 0x2

key_val = 0x1

key_val = 0x1

key_val = 0x1

上述log表明,當前的驅動有按鍵抖動的現象,還需要繼續優化。

 

結果總結

在本篇文章中,中年潤跟讀者分享了按鍵字符設備驅動的編寫思路和方法,其中貫穿始終的有幾個函數和關鍵數據結構,它們分別是:

struct file_operations

struct class

struct class_device

 

register_chrdev

class_create

device_create

unregister_chrdev

device_destroy

class_destroy

ioremap

iounmap

DECLARE_WAIT_QUEUE_HEAD

request_irq

free_irq

請讀者盡力去了解這些函數的作用,入參,返回值。

 

問題彙總

暫無

 

實戰目標

1請讀者根據《需求描述》章節,獨立編寫需求分析和需求分解。

2請讀者根據需求分析和需求分解,獨立編寫編寫思路和詳細步驟。

3請讀者根據編寫思路,獨立寫出編寫框架。

4請讀者根據詳細步驟,獨立編寫驅動代碼和測試代碼。

5請讀者根據《Makefile》章節,獨立編寫Makefile。

6請讀者根據《測試步驟》章節,獨立進行測試。

7請讀者拋開上述練習,自頂向下從零開始再編寫一遍驅動代碼,測試代碼,makefile

8如果無法獨立寫出7,請重複練習1-6,直到能獨立寫出7。

 

參考資料

《linux設備驅動開發祥解》

《TX2440開發手冊及代碼》

《韋東山嵌入式教程》

《魚樹驅動筆記》

《s3c2440a》芯片手冊英文版和中文版

 

致謝

感謝在嵌入式領域深耕多年的前輩,感謝中年潤的家人,感謝讀者。沒有前輩們的開拓,我輩也不能站在巨人的肩膀上看世界;沒有家人的鼎力支持,我們也不能集中精力完成自己的工作;沒有讀者的關注和支持,我們也沒有充足的動力來編寫和完善文章。看完中年潤的文章,希望讀者學到的不僅僅是如何編寫代碼,更進一步能夠學到一種思路和一種方法。

 

爲了省去驅動開發者蒐集各種資料來寫驅動,中年潤後續有計劃按照本模板編寫linux下的常見驅動,敬請讀者關注。

 

聯繫方式

微信羣:自頂向下學嵌入式(可先加微信號:runzhiqingqing, 通過後會邀請入羣。)

微信訂閱號:自頂向下學嵌入式  公衆號:EmbeddedAIOT

CSDN博客:中年潤  網址:https://blog.csdn.net/chichi123137

郵箱:[email protected]

QQ羣:766756075

更多原創文章請關注微信公衆號。另外,中年潤還代理銷售韋東山老師的視頻教程,歡迎讀者點擊下面二維碼購買。在中年潤這裏購買了韋東山老師的視頻教程,除了能得到韋東山官方的技術支持外,還能獲得中年潤細緻入微的技術和非技術的支持和幫助。歡迎選購哦。

公衆號二維碼如下圖

入羣小助手二維碼如下圖

中年潤代理銷售的韋東山視頻購買地址如下圖

如果略有所獲,歡迎讚賞,您的支持對中年潤無比重要

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章