linux驅動篇-touchscreen-精簡版

Touchscreen

本篇文章爲觸摸屏精簡版本,爲的是省去大家的時間,快速知道觸摸屏驅動的基本編寫步驟。如要看完整版,傳送門在下面。

https://blog.csdn.net/chichi123137/article/details/89256978

前言

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

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

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

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

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

 

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

 

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

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

 

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

 

總體目標

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

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

 

總體思路

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

 

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

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

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

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

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

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

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

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

Makefile—用來編譯驅動代碼。

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

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

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

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

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

請大家儘量按照自頂向下的學習思路來學習和實戰,因爲我們所有工作的動力都是我們心中的需求。這些步驟僅僅是我們達到目標所要走過的路。

 

需求描述

編寫觸摸屏驅動,要求如下:

1能夠在按下時在串口上連續輸出按下的座標值;

2能夠在鬆開時在串口上輸出已經鬆開。

 

需求分析

要分析出需要實現哪些功能,經過分析,可以得出需要實現以下功能。

1能夠控制ADC數模轉換器將模擬信號轉換成數字信號;

2能夠通過按下時的電阻值來確定當前按下的座標值;

3需要利用linux提供的input輸入子系統模塊相關api。

 

需求分解

根據需求分析的結果,分解出需要實現的功能:

1需要向內核註冊和卸載input設備

2配置adc和觸摸屏接口寄存器

3需要能夠檢測觸摸屏中斷,以及檢測觸摸屏按下還是鬆開

4需要啓動ADC轉換,將電阻值轉換爲數字值

 

編寫思路

編寫思路主要用來搭建代碼框架,解決在宏觀上如何用代碼實現驅動的功能。Touchscreen驅動的核心是註冊input設備,並註冊adc和觸摸中斷處理函數。

0編寫代碼框架,頭文件,出入口函數,聲明LICENSE

 

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

1需要向內核註冊input設備

1.1需要註冊input device

1.1.1需要申請struct input_dev結構體

1.1.2需要設置能產生哪類事件,產生哪些事件

1.1.3需要註冊struct input_dev結構體

1.2需要卸載input device

2配置adc和觸摸屏接口寄存器

2.1獲取並使能adc時鐘

2.2構造adc和觸摸屏相關寄存器結構體

2.3映射寄存器

2.4配置AD轉換器的預分頻使能和預分頻值

 

3需要能夠檢測觸摸屏中斷,以及檢測觸摸屏按下還是鬆開

3.1需要註冊觸摸屏中斷處理函數

3.1.1編寫觸摸屏中斷處理函數

3.2進入等待觸摸筆按下模式

 

4需要啓動ADC轉換,將電阻值轉換爲數字值

4.1需要註冊adc中斷處理函數

4.1.1需要編寫adc中斷處理函數

 

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

5在出口函數中釋放資源

 

詳細步驟

詳細步驟主要用來在代碼框架裏填充必要的細節代碼,解決在微觀上如何用代碼實現驅動各個小功能。Touchscreen驅動的總體核心是註冊input設備,並註冊adc和觸摸中斷處理函數。具體小功能則爲實現中斷處理函數和消除觸摸抖動。

0編寫代碼框架,頭文件,出入口函數,聲明LICENSE

1需要向內核註冊和卸載input設備

1.1需要註冊input device

1.1.1需要申請struct input_dev結構體

1.1.2需要設置能產生哪類事件,產生哪些事件

1.1.3需要註冊struct input_dev結構體

1.2需要卸載input device

2配置adc和觸摸屏接口寄存器

2.1獲取並使能adc時鐘

2.2構造adc和觸摸屏相關寄存器結構體

2.3映射寄存器

2.4配置AD轉換器的預分頻使能和預分頻值

 

3需要能夠檢測觸摸屏中斷,以及檢測觸摸屏按下還是鬆開

3.1需要註冊觸摸屏中斷處理函數

3.1.1編寫觸摸屏中斷處理函數

3.1.2通過檢測筆尖的落下和擡起狀態來進行操作

3.1.3進入自動測量模式,啓動adc,將電壓值轉換爲座標值

3.2進入等待觸摸筆按下模式

 

4需要啓動ADC轉換,將電阻值轉換爲數字值

4.1需要註冊adc中斷處理函數

4.1.1需要編寫adc中斷處理函數

4.1.2在按下時獲取座標值,並進入等待鬆開狀態

 

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

5在出口函數中釋放資源

5.1釋放申請的中斷資源

5.2取消寄存器的地址映射

5.3釋放申請的時鐘資源

5.4卸載input設備

5.5釋放input設備資源

 

編寫框架

/* Touchscreen_skeleton.c */
/* 本文件是依照touchscreen 驅動<編寫思路>章節編寫,本文件
  * 的目的是編寫代碼框架,不做具體細節的編寫
  */
/* 本頭文件是linux2.6.31內核所提供的,其他版本按需調整 */
 
/* 1.0編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
#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>
#include <linux/input.h>
#include <linux/clk.h>
 
/* 2.2構造adc和觸摸屏相關寄存器結構體 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
 
static void enter_wait_pen_down_mode(void)
{
 
}
 
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
       return 0;
}
 
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       return 0;
}
 
static int s3c_touchscreen_init(void)
{
       int ret;
       struct clk * adc_clk;
 
       /* 1.1需要註冊input device */
       /* 1.1.1需要申請struct input_dev結構體 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要設置能產生哪類事件,產生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要註冊struct input_dev結構體 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和觸摸屏接口寄存器 */
       /* 2.1獲取並使能adc時鐘 */
       adc_clk = clk_get(NULL,"adc");
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD轉換器的預分頻使能和預分頻值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先設爲0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能夠檢測觸摸屏中斷,以及檢測觸摸屏按下還是鬆開 */
       /* 2.1需要註冊觸摸屏中斷處理函數 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request_irq failed ts_pen\n");
       }
       /* 2.2進入等待觸摸筆按下模式 */
       enter_wait_pen_down_mode();
 
       /* 3需要啓動ADC轉換,將電阻值轉換爲數字值 */
       /* 3.1需要註冊adc中斷處理函數 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request_irq failed adc\n");
       }
       return 0;
}
 
static void s3c_touchscreen_exit(void)
{
       iounmap(s3c_ts_regs);
       /* 1.2需要卸載input device */
       input_unregister_device(s3c_ts_inputdev);
}
 
/* 1.0編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

 

驅動代碼

《驅動代碼未優化》對應touchscreen.c主要用來做觸摸屏中斷實驗,adc中斷實驗。

《驅動代碼優化措施1》提出了一種通過配置延時寄存器來優化驅動代碼的方法。

《驅動代碼優化措施2》提出了一種應對當adc轉換完成時已經鬆開觸摸屏的方法。

《驅動代碼優化措施3》提出了一種多次測量求平均座標值的方法。

《驅動代碼優化措施4》提出了一種通過軟件過濾所獲得的座標值的方法。

《驅動代碼優化措施5》提出了一種通過增加定時器和定時器處理函數來處理長按觸摸屏和滑動觸摸屏的處理方法。

《驅動代碼最終版》加入了上報事件的操作,是將5種優化措施結合後的代碼。

上述都是在完整版裏面,有需要的讀者請查看完整版本。

 

驅動代碼代碼未優化

/* Touchscreen.c可以用來做觸摸屏中斷實驗,adc中斷實驗,驅動代碼優化措施1實驗 */
 
/* 本文件是依照touchscreen 驅動<詳細步驟>章節編寫,本文件
  * 的目的是編寫代碼框架,不做具體細節的編寫
  */
/* 本頭文件是linux2.6.31內核所提供的,其他版本按需調整 */
 
/* 0編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
#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>
#include <linux/input.h>
#include <linux/clk.h>
 
#define TOUCH_SCREEN_INTERRUPT_TEST             1
#define ADC_INTERRUPT_TEST                        1
 
#define OPTIMIZED_METHOD1                       1
#define OPTIMIZED_METHOD2                       1    
#define OPTIMIZED_METHOD3                       1
#define OPTIMIZED_METHOD4                       1
#define OPTIMIZED_METHOD5                       1
 
/* 2.2構造adc和觸摸屏相關寄存器結構體 */
struct s3c_touchscreen_regs{
       unsigned long adccon;
       unsigned long adctsc;
       unsigned long adcdly;
       unsigned long adcdat0;
       unsigned long adcdat1;
       unsigned long adcupdn;
};
 
struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_touchscreen_regs *s3c_ts_regs;
 
/* 進入等待觸摸筆按下模式 */
static void enter_wait_pen_down_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]0:     檢測筆尖落下中斷信號
         * bit[7]1:     YM輸出驅動器使能
         * bit[6]1:     YP輸出驅動器禁止
         * bit[5]0:     XM輸出驅動器禁止
         * bit[4]1:     XP輸出驅動器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc轉換
         * bit[0-1]11:       進入等待中斷模式
         */
       s3c_ts_regs->adctsc = 0xd3;
}
 
/* 進入等待觸摸筆鬆開模式 */
static void enter_wait_pen_up_mode(void)
{
       /*
         * 0xd3 = 011010011
         * bit[8]1:     檢測筆尖擡起中斷信號
         * bit[7]1:     YM輸出驅動器使能
         * bit[6]1:     YP輸出驅動器禁止
         * bit[5]0:     XM輸出驅動器禁止
         * bit[4]1:     XP輸出驅動器禁止
         * bit[3]0:     XP上拉禁止
         * bit[2]0:     正常adc轉換
         * bit[0-1]11:       進入等待中斷模式
         */
       s3c_ts_regs->adctsc = 0x1d3;
}
 
/* 進入自動測量xy座標模式 */
static void enter_measure_xy_mode(void)
{
       /* 6 = (1<<3)|(1<<2)
         * bit[8]0:     檢測筆尖擡起落下信號
         * bit[7]0:     YM輸出驅動器禁止
         * bit[6]0:     YP輸出驅動器使能
         * bit[5]0:     XM輸出驅動器禁止
         * bit[4]0:     XP輸出驅動器使能
         * bit[3]1:     XP上拉禁止
         * bit[2]1:     自動順序X方向和Y方向測量
         * bit[0-1]00:       無操作模式
         */
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
 
/* 啓動adc */
static void start_adc(void)
{
       /*
         * bit[0]:A/D轉換啓動且此位在啓動後被清零
         */
       s3c_ts_regs->adccon |= (1<<0);
}
 
/* 3.1.1編寫觸摸屏中斷處理函數 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
#if TOUCH_SCREEN_INTERRUPT_TEST
       /* 3.1.2通過檢測筆尖的落下和擡起狀態來進行操作 */
       /* bit[15]:       筆尖擡起態 */
       if (s3c_ts_regs->adcdat0 & (1 << 15))
       {
              printk("pen up\n");
              enter_wait_pen_down_mode();
       }
       else
       {
              /* 筆尖落下態 */
              printk("pen down\n");
              enter_wait_pen_up_mode();
#if ADC_INTERRUPT_TEST
              /* 3.1.3進入自動測量模式,啓動adc,將電壓值轉換爲座標值 */
              enter_measure_xy_mode();
              start_adc();
#endif
       }
#endif
       return IRQ_HANDLED;
}
 
/* 4.1.1需要編寫adc中斷處理函數 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
       static int cnt = 0;
       unsigned long adcdat0 = s3c_ts_regs->adcdat0;
       unsigned long adcdat1 = s3c_ts_regs->adcdat1;
      
#if ADC_INTERRUPT_TEST
       /* 4.1.2在按下時獲取座標值,並進入等待鬆開狀態 */
       printk("adc_irq cnt = %d, x = %ld, y = %ld\n",
              ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
       enter_wait_pen_up_mode();
#endif
       return IRQ_HANDLED;
}
 
static struct clk * adc_clk;
/*入口函數*/
static int s3c_touchscreen_init(void)
{
       int ret;
      
 
       /* 1.1需要註冊input device */
       /* 1.1.1需要申請struct input_dev結構體 */
       s3c_ts_inputdev = input_allocate_device();
 
       /* 1.1.2需要設置能產生哪類事件,產生哪些事件 */
       set_bit(EV_KEY,s3c_ts_inputdev->evbit);
       set_bit(EV_ABS,s3c_ts_inputdev->evbit);
 
       set_bit(BTN_TOUCH,s3c_ts_inputdev->keybit);
 
       input_set_abs_params(s3c_ts_inputdev,ABS_X,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_Y,0,0X3FF,0,0);
       input_set_abs_params(s3c_ts_inputdev,ABS_PRESSURE,0,1,0,0);
 
       /* 1.1.3需要註冊struct input_dev結構體 */
       ret = input_register_device(s3c_ts_inputdev);
       if (ret != 0)
       {
              printk("input_register_device failed\n");
       }
 
       /* 2配置adc和觸摸屏接口寄存器 */
       /* 2.1獲取並使能adc時鐘 */
       adc_clk = clk_get(NULL,"adc");
       if (!adc_clk) {
              printk("failed to get adc clock source\n");
              return -ENOENT;
       }
       clk_enable(adc_clk);
      
       /* 2.3映射寄存器 */
       s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_touchscreen_regs));
 
       /* 2.4配置AD轉換器的預分頻使能和預分頻值 */
       /* bit[14]  : 1-A/D converter prescaler enable
         * bit[13:6]: A/D converter prescaler value,
         *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
         * bit[0]: A/D conversion starts by enable. 先設爲0
         */
       s3c_ts_regs->adccon = 1 << 14 | 49 << 6;
 
       /* 3需要能夠檢測觸摸屏中斷,以及檢測觸摸屏按下還是鬆開 */
       /* 3.1需要註冊觸摸屏中斷處理函數 */
       ret = request_irq(IRQ_TC, pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
       if (ret != 0)
       {
              printk("request touchscreen irq failed error code %d\n",ret);
       }
      
       /* 3.2進入等待觸摸筆按下模式 */
       enter_wait_pen_down_mode();
      
       /* 4需要啓動ADC轉換,將電阻值轉換爲數字值 */
       /* 4.1需要註冊adc中斷處理函數 */
       ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
       if (ret != 0)
       {
              printk("request adc irq failed error code %d\n",ret);
       }
#if OPTIMIZED_METHOD1
       /* 優化錯施1:
        * 設置ADCDLY爲最大值, 這使得電壓穩定後再發出IRQ_TC中斷
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif
       return 0;
}
/*出口函數*/
static void s3c_touchscreen_exit(void)
{
       /*
       * 5.1釋放申請的中斷資源
       * 5.2取消寄存器的地址映射
       * 5.3是否申請的時鐘資源
       * 5.4卸載input設備
       * 5.5是否input設備資源
       */
       free_irq(IRQ_ADC,NULL);
       free_irq(IRQ_TC,NULL);
       iounmap(s3c_ts_regs);
       if (adc_clk) {
              clk_disable(adc_clk);
              clk_put(adc_clk);
              adc_clk = NULL;
       }
 
       /* 1.2需要卸載input device */
       input_unregister_device(s3c_ts_inputdev);
       input_free_device(s3c_ts_inputdev);
}
 
/* 0編寫代碼框架,頭文件,出入口函數,聲明LICENSE */
module_init(s3c_touchscreen_init);
module_exit(s3c_touchscreen_exit);
MODULE_LICENSE("GPL");

 

驅動代碼優化措施1

基於《驅動代碼未優化》開啓OPTIMIZED_METHOD1後可以用來做優化措施1實驗。實驗結果見《執行結果-優化措施1》章節。

#if OPTIMIZED_METHOD1
       /* 優化錯施1:
        * 設置ADCDLY爲最大值, 這使得電壓穩定後再發出IRQ_TC中斷
       */
       s3c_ts_regs->adcdly = 0xffff;
#endif

 

測試代碼

本示例無需測試代碼,可以通過按下或者鬆開觸摸屏測試。

 

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 += touchscreen.o

如果名字不同,請更換obj-m後面.o的名字。比如中年潤這裏使用touchscreen_optimized.o,touchscreen_final.o等。

 

目錄結構

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

.
├── Makefile
├── touchscreen.c
├── touchscreen_final.c
├── touchscreen_optmized.c
└── touchscreen_skeleton.c

 

測試步驟

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

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

2 移植好的linux默認是有s3c2440 adc模塊的,如果我們的驅動也加載進去,會調用原有的驅動。因此需要在menuconfig中去掉原先的adc驅動。

make menuconfig

Location:                                                                                                                                                    

Device driver->Character devices->ADC driver for S3C2440 development boards

Device driver->Character devices-> TX2440 ADC Driver

3 make uImage 生成uImage,並使用新的內核啓動。

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

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

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

6 加載ko。

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input2

7按下或者鬆開觸摸屏,觀察現象

以上是基本的測試流程,如有要測試優化措施,則需要開啓對應的優化措施開關。開啓方法就是把相應的宏置1。具體優化措施請參考《驅動代碼-驅動代碼優化措施n》等章節。

 

執行結果

以下執行結果是按照驅動代碼的試驗和優化步驟完成,是一一對應的。

 

觸摸屏中斷觸發測試結果

基於《驅動代碼未優化》只打開TOUCH_SCREEN_INTERRUPT_TEST宏,其他宏置0。測試結果如下:

 

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input0

request_irq failed adc

[root@TX2440A 11touchscreen]# pen down

pen up

pen down

pen down

pen up

pen down

pen up

pen down

pen up

pen down

pen down

pen up

 

觸摸屏中斷和adc中斷觸發測試結果

基於《驅動代碼未優化》同時打開TOUCH_SCREEN_INTERRUPT_TEST宏和ADC_INTERRUPT_TEST宏,測試結果如下:

[root@TX2440A 11touchscreen]# pen down

adc_irq cnt = 1, x = 160, y = 735

pen up

pen down

adc_irq cnt = 2, x = 89, y = 748

pen up

pen down

adc_irq cnt = 3, x = 139, y = 733

pen up

pen down

adc_irq cnt = 4, x = 70, y = 825

pen up

pen up

pen down

 

存在的問題是,有很大概率出現按下時產生兩次中斷,鬆開時產生兩次中斷。爲了解決這個問題提出了幾種優化措施。

 

驅動代碼優化措施1結果

/* 優化錯施1: 設置ADCDLY爲最大值, 這使得電壓穩定後再發出IRQ_TC中斷 */

開啓相應開關後,測試結果如下,要比剛開始要好一點,少了很多重複中斷。

#define TOUCH_SCREEN_INTERRUPT_TEST             1

#define ADC_INTERRUPT_TEST                        1

#define OPTIMIZED_METHOD1                       1

 

pen down

adc_irq cnt = 77, x = 152, y = 498

pen up

pen down

adc_irq cnt = 78, x = 246, y = 837

pen up

pen down

adc_irq cnt = 79, x = 213, y = 710

pen down

adc_irq cnt = 80, x = 215, y = 709

依然存在的問題是,還是有重複觸發中斷的問題。

 

結果總結

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

 

分配一個input_dev結構體

struct input_dev *input_allocate_device(void)

釋放input設備

void input_free_device(struct input_dev *dev)

能產生哪類事件

static inline void set_bit(int nr, unsigned long *addr)

能產生這類事件的哪些事件

static inline void set_bit(int nr, unsigned long *addr)

設置abs事件的參數

void input_set_abs_params(struct input_dev *dev, unsigned int axis,

                      int min, int max, int fuzz, int flat)

註冊input設備

int input_register_device(struct input_dev *dev)

卸載input設備

void input_unregister_device(struct input_dev *dev)

每個普通事件的上報離不開同步事件

上報普通事件

void input_event(struct input_dev *dev,

             unsigned int type, unsigned int code, int value)

上報同步事件

static inline void input_sync(struct input_dev *dev)

上報abs事件

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)

上報key事件

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)

 

申請一箇中斷

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

          const char *name, void *dev)

釋放一箇中斷

void free_irq(unsigned int, void *)

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

 

問題彙總

安裝觸摸屏驅動報錯-22

代碼如下所示

ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED | IRQF_SAMPLE_RANDOM, "adc", NULL);

 

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input4

request adc irq failed error code -22

 

原因分析如下

manage.c 957行request_threaded_irq函數中

if ((irqflags & IRQF_SHARED) && !dev_id)

       return -EINVAL;

當所註冊的irq中斷中有IRQF_SHARED標誌,並且dev_id爲空則返回-EINVAL。

-EINVAL        /* Invalid argument */         無效的參數

 

安裝觸摸屏驅動報錯-16

代碼如下所示

ret = request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);

 

報錯log如下

[root@TX2440A 11touchscreen]# insmod touchscreen.ko

input: Unspecified device as /class/input/input5

request adc irq failed error code -16

 

原因分析如下

manage.c 732行__setup_irq函數中有ret = -EBUSY;           

-EBUSY  /* Device or resource busy */

 

調用關係如下

request_irq

request_threaded_irq

__setup_irq

              ret = -EBUSY;

這個問題是因爲中年潤所用的linux裏已經選擇了adc的驅動,註冊了adc的中斷(代碼在s3c2440_adc dev_init函數中)(menuconfig開關在Device driver->Character devices->ADC driver for S3C2440 development boards)

 

實戰目標

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

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

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

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

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

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

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

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

 

參考資料

《linux設備驅動開發祥解》

《TX2440開發手冊及代碼》

《韋東山嵌入式教程》

《魚樹驅動筆記》

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

 

致謝

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

 

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

 

聯繫方式

微信羣:見文章最底部,因微信羣有效期只有7天,感興趣的同學可以加下。微信羣裏主要是爲初學者答疑解惑,也可以進行技術和非技術的交流。如果微信羣失效了,大家可以加qq羣,我會在qq羣裏分享微信羣的二維碼。同時也歡迎和中年潤志同道合的中年人尤其是中年碼農的加入。

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

公衆號微信:EmbeddedAIOT

CSDN博客:chichi123137

CSDN博客網址:https://blog.csdn.net/chichi123137?utm_source=blog_pc_recommand

QQ郵箱:[email protected]

QQ羣:766756075

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

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