1.查詢方式獲取按鍵
1.框架
頭文件
file_operations結構體
.open =
.read = second_drv_read,
read函數的參數
入口函數註冊結構體 second_drv_init
major = register_chrdev(0,”secon_drv”,&second_drv_fops);
出口函數second_drv_exit(void)
unregister_chrdev(major,”secon_drv”);
修飾
module_init(second_drv_init)
module_exit(second_drv_exit)
讓udev自動創建設備節點(mdev是udev的簡化版本)
定義類和設備
static struct class *second_drv_class;
創建class,創建class device
seconddrv_class = classs_create(THIS_MODULE, “second_drv”);
gbseconddrv_class_device = class_device_create(seconddrv_class,NULL,MKDEV(major,0),NULL,”buttons”);
卸載設備和class
class_device_unregister(firstdrv_class_dev);
class_destroy(seconddrv_class);
MODULE_LINCENS(“GPL”)
上傳驅動文件,修改Makefile,編譯,拷貝到根文件系統,加載驅動,檢查驅動是否正常加載
2.硬件操作
看原理圖,確定引腳
看2440手冊,確定引腳如何操作,相關寄存器
編寫程序,單片機編程直接用物理地址;Linux驅動中使用虛擬地址。
vA = ioremap(pA,len);
程序規劃:
在open函數中配置引腳
在read中返回IO值
在inint中進行寄存器映射
編程:
init中建立映射
gpfcon = (volatile unsigned log *)ioremap(0x56000050,16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned log *)ioremap(0x56000060,16);
gpgdat = gpgcon + 1;
exit中取消映射
iounmap(gpfcon);
iounmap(gpgcon);
open中配置GPF0,2,GPG3,11爲輸入,對應位清零
*gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
*gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
read中返回引腳電平,ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loft_t *ppos)
unsigned char key_vals[4];
int regval;
regval = *gpfdat;
key_vals[0] = regval & BIT0; 或者 key_vals[0] = (regval & (1<<0)) ? 1: 0
key_vals[1] = (regval & (1<<2)) ? 1: 0
數據返回給用戶空間
if (size != sizeof(key_vals))
return -EINVAL;
copy_to_user(buf,key_vals,sizeof(key_vals));
測試程序
open(“/dev/buttons”,O_RDWR);
read(fd,keyvals,sezeof(key_vals));
if (!keyval[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
{
printf(“keyval: %4d %4d %4d %4d”,key_vals[0],,key_vals[1],key_vals[2],key_vals[3])
}
2.Linux的中斷處理框架
Linux的中斷處理與單片機本質上是一致的,單片機中,中斷髮生後硬件根據中斷向量表跳轉到對應的地址去執行;在ARM架構Linux中,中斷產生後,CPU進入異常處理模式,即中斷異常,調用中斷總入口函數 asm_do_IRQ() ,該函數又會根據中斷號調用對應的中斷處理函數,這個處理函數就相當於我們在單片機編程中寫的中斷服務子程序。
而Linux的中斷編程就是填充相關的數據結構和編寫中斷服務函數。
關鍵的數據結構(這裏只列出了數據結構中比較關鍵的成員):
struct irq_desc {
irq_flow_handler_t handle_irq; /*highlevel irq-events handler [if NULL, __do_IRQ()]*/
struct irq_chip *chip; /*low level interrupt hardware access*/
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int irq_count; /* For detecting broken IRQs */
const char *name; /*flow handler name for /proc/interrupts output*/
} ____cacheline_internodealigned_in_smp;
其中irq_chip 結構體用於定義與硬件相關的底層操作,如啓動或關閉中斷、使能或禁止中斷、設置中斷屏蔽設置中毒觸發方式等。
irqaction 結構體用於存放用戶定義的中斷處理函數,對於共享中斷,可以有多箇中斷處理函數,故irqaction是一個結構鏈表。
另外,在單片機低功耗編程中,我設置好中斷後進入休眠狀態,當中斷髮生後喚醒系統進行中斷處理,中斷完成後再次進入休眠等待中斷髮生;在Linux按鍵中斷編程中,中斷處理函數喚醒read函數,read函數將數據發送給用戶應用後再次進入休眠,這樣就不會一直佔用CPU時間。則與單片機的低功耗編程很相似。
3.中斷方式的按鍵驅動框架
與查詢方式的按鍵驅動不一樣的地方在於:
1.在open函數中使用request_irq()函數註冊中斷
2.在close函數中使用free_irq()函數釋放中斷
3.文件操作結構體 gbthird_drv_fops 中添加 .release = gbthird_drv_close, 即關閉設備時需要調用驅動的close函數,該函數中釋放中斷。
4.編寫中斷服務函數 buttons_irq()
其實對於Linux的中斷編程,我們只需要做幾件簡單的事就可以,前面說到的數據結構填充都會有Linux自動完成。當然這是站在一個比較低的層次來看,我相信中斷編程還有很多需要深入的地方。
1. gbthird_drv_open 函數中註冊中斷:
request_irq(IRQ_EINT8, buttons_irq,IRQT_BOTHEDGE,”S1″,&pins_desc[0]);
參數依次爲:中斷號,中斷處理函數,觸發方式,名稱,設備號,這些參數與前面的結構體是對應的,request_irq() 函數會使用這些參數完成數據結構的填充。設備號可以是任意數,也可以是指針。
2. gbthird_drv_close 函數中釋放中斷
free_irq(IRQ_EINT8 , &pins_desc[0]);
參數依次爲:中斷號,設備號,與註冊中斷時一致。
3. 文件操作結構體 gbthird_drv_fops 中添加 .release = gbthird_drv_close
4. 編寫中斷服務子函數,當中斷產生時將會調用該函數
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc*)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
//ok6410中是使用gpio_get_value(pindesc->pin); 括號中參數可以爲 S3C64XX_GPN(0)等等
if (pinval)
{
key_val = 0x80 | pindesc->key_val; //relrese
}
else
{
key_val = pindesc->key_val;
}
ev_press = 1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);
}
5. 修改原有的Read函數,read函數等待中斷喚醒,然後拷貝按鍵值到用戶空間。
static ssize_t gbthird_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned char KeyVal[6];
int RegVal = 0;
if ( size != 1 )
return -EINVAL;
/* 如果沒有按鍵動作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按鍵動作, 返回鍵值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return size;
}
這樣就完成了中斷方式的按鍵驅動。
PS:新接觸的linux命令
cat /proc/interrupts 查看中斷註冊、使用情況
./button_test & 後臺方式運行程序
ps 顯示運行的進程
/////////////////////////////////////////////////////////////////////////////////////////
s3c2410_gpio_getpin()函數說明
unsigned int s3c2410_gpio_getpin(unsigned int pin)
{
void __iomem *base = S3C24XX_GPIO_BASE(pin);
unsigned long offs = S3C2410_GPIO_OFFSET(pin);
return __raw_readl(base + 0x04) & (1<< offs);
}
s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值與所要讀取的GPIO對應的bit mask相與以後的值,0表示該GPIO對應的bit爲0, 非0表示該bit爲1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9爲低電平則返回的是0,如果是高電平則返回的是GPxDAT中的GPG9對應位的值爲0x0100而不是0x0001,查處問題後修改也很簡單了。