本次實驗基於匯流排裝置驅動模型實現按鍵驅動程式的編寫,給上層應用程式提供檢測按鍵是否按下的操作介面,上層應用根據按鍵是否按下控制led的亮滅。所以上層應用程式會同時使用led和按鍵的驅動介面,但是對於下層驅動而言,這二者是分離的,因此只需要專注於編寫按鍵驅動程式就可以了。
在正點原子imx6ull開發板上按鍵原理圖如下:
按鍵KEY0接到的引腳名字是uart1_cts,查詢晶片參考手冊第28章(GPIO章節),找到下面這個表
從表中找到GPIO1_IO18這一項,寫著UART1_CTS_B,跟原理圖上引腳名一致,說明這是按鍵Key0對應的引腳。我們要讀取按鍵是否按下,因此我們需要把UART1_CTS_B這個引腳也即GPIO1_IO18設定為GPIO功能(GPIO功能代指普通的輸入輸出功能)。與led驅動程式的步驟類似,我們需要檢視GPIO章節中有關引腳的原理圖,其涉及到IOMUXC控制器、CMM時鐘控制器,以及相關的GPIO控制器。
(1)CCM時鐘控制器
在CCM控制器章節找到CCGR1暫存器的CG13位域控制GPIO1模組的時鐘使能,接著在CCM章節的暫存器對映小節找到暫存器地址(20C_406Ch)及各位域的配置方式
(2)IOMUC複用控制器
在IOMUXC控制器章節找到複用暫存器IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B(20E_008Ch)
(2)GPIO控制器
需要將GPIO設定為輸入功能,因此涉及GDIR和PSR暫存器,其中GDIR將GPIO配置為輸入,PSR暫存器用於讀取引腳輸入狀態
在GPIO章節找到上表可以檢視到GPIO1相關的暫存器
這裡插入話題GPIO是什麼,為什麼叫GPIO:
GPIO全稱是General Purpose Input / Output 通用目的輸入輸出。既然是通用目的(General Purpose),那說明它可以用於不同的功能,比如有些引腳輸出能力強,可以作為輸出,這時候它就被用作最一般的輸出功能,但是並不意味著它只能做普通輸入輸出引腳,它也可以複用為其它功能(具體檢視晶片手冊)。又比如本次實驗中按鍵接到的引腳名字叫UART1_CTS,說明它的主要功能是用於UART串列埠,但是不代表不能用於輸入,本實驗就用它作為按鍵輸入。晶片就是透過輸入/輸出資料來提供功能的,所有引腳都是輸入/輸出資料,也因此才叫IO,但是同一個引腳可能可以被複用為多種功能,比如UART,PWM,SPI,因此它是通用的,也即General Purpose,所以合起來才叫GPIO。更細緻地分類解釋的話,所有的引腳都具有輸入/輸出功能,它們都屬於GPIO,但是如果某一個引腳只是單純的用於輸入/輸出,比如LED的引腳GPIO1_IO03,這個引腳具有較強的輸出能力,一般就用於輸出,並且這個引腳屬於GPIO1組的第3個IO引腳,那麼這個引腳就命名為GPIO1_IO03;再比如按鍵Key0的引腳一般情況下輸出能力較弱,會作為UART串列埠功能,那麼這個引腳就命名UART1_CTS,但是它實際上是GPIO1組的第18個IO引腳,因此它是GPIO1_IO18,但是命名為UART1_CTS。
分析完硬體和暫存器部分後,開始寫驅動程式碼,分為三部分:裝置樹、下層驅動程式碼,上層驅動框架
1、裝置樹
在根節點下新增如下節點
點選檢視程式碼
mykey0 {
compatible = "key0_driver";
pin = <GROUP_PIN(1, 18)>;
pin_reg = <
0x20C406C /*CCM_CCGR1*/
0x20E008C /*IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B*/
0x209C004 /*GPIO1_GDIR*/
0x209C008 /*GPIO1_PSR*/
>;
};
2、包含platform_driver結構體的程式碼
這部分就是下層涉及具體硬體的操作函式,除了填充platform_driver結構體的內容外,還要向上層提供具體裝置的操作函式,比如硬體的初始化、控制,同時在probe函式中呼叫上層介面為每一個匹配的裝置註冊檔案檢視。具體的程式碼如下:
標頭檔案button_rsc.h
點選檢視程式碼
#ifndef __BUTTON__RSC__H
#define __BUTTON__RSC__H
#define GROUP_PIN(g, p) ((g << 16) | (p)) //轉換成引腳編號
#define PIN(x) (x & 0xffff) //獲取引腳屬於哪一個
#define GROUP(x) ((x >> 16) & 0xffff) //獲取引腳屬於哪一組
struct button_resource{
int group_pin; //[31:16]存放組,[15:0]存放
//定義變數存放涉及到的暫存器的地址
unsigned int CCM_CCGR1;
unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
unsigned int GDIR;
unsigned int DR;
unsigned int PSR;
};
struct button_registers_after_map{
unsigned int* CCM_CCGR1;
unsigned int* IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
unsigned int* GDIR;
unsigned int* DR;
unsigned int* PSR;
};
#endif
標頭檔案button_opr.h
點選檢視程式碼
#ifndef __BUTTON_OPR__H
#define __BUTTON_OPR__H
struct button_operations{
int (*init) (unsigned int which); //初始化引腳,which表示引腳編號
int (*read) (unsigned int which); //讀取引腳電平狀態,which表示引腳編號
};
#endif
標頭檔案button_drv.h
點選檢視程式碼
#ifndef __BUTTON_DRV_H__
#define __BUTTON_DRV_H__
#include "button_opr.h" //如果在board_button.c中button_drv標頭檔案的包含在button_opr前面的話,這裡就需要先包含button_opr.h,否則會有警告,因為struct button_operations定義在button_opr.h
void button_device_register(int minor);
void button_device_unregister(int minor);
void register_button_operations(struct button_operations *button_opr);
#endif
board_button.c檔案
點選檢視程式碼
/*
下層按鍵驅動程式,實現具體的硬體操作
(1)實現按鍵GPIO引腳的操作函式,包括初始化函式、讀取按鍵狀態函式,透過結構體指標形式向上層註冊
(2)實現platform_driver結構體,從裝置樹生成的platform_device獲取暫存器資源,並提供匹配和解除安裝時的probe和remove函式,還有其他屬性
(3)實現驅動入口/出口函式
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "asm/delay.h"
#include "linux/ioport.h"
#include "linux/mod_devicetable.h"
#include "linux/of.h"
#include "linux/platform_device.h"
#include "asm/io.h"
#include <linux/delay.h>
#include "button_rsc.h"
#include "button_drv.h"
#include "button_opr.h"
int cur_counts = 0;
struct button_resource g_buttons[10];
struct button_registers_after_map g_buttons_map_addr[10];
/*
構造操作函式結構體,為上層驅動框架提供實體操作函式
*/
int button_init(unsigned int which) //初始化引腳,which表示引腳編號
{
/*根據which初始化GPIO,其中which是次裝置號,也是g_buttons中儲存資源結構體的下標*/
unsigned int val;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//地址對映
if(!g_buttons_map_addr[which].CCM_CCGR1)
{
g_buttons_map_addr[which].CCM_CCGR1 = ioremap(g_buttons[which].CCM_CCGR1, 4);
g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B = ioremap(g_buttons[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B, 4);
g_buttons_map_addr[which].GDIR = ioremap(g_buttons[which].GDIR, 4);
g_buttons_map_addr[which].PSR = ioremap(g_buttons[which].PSR, 4);
}
//使能時鐘
val = *g_buttons_map_addr[which].CCM_CCGR1;
val |= (0x3 << 26);
*(g_buttons_map_addr[which].CCM_CCGR1) = val;
/*
配置GPIO1_IO18為GPIO模式
*/
val = *g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
val &= (~0xf); //低四位先清零
val |= (0x5); //設定為GPIO模式
*(g_buttons_map_addr[which].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B) = val;
/*
設定GPIO1_IO18為輸出
*/
*(g_buttons_map_addr[which].GDIR) &= ~(0x1 << 18);
return 0;
}
int button_read(unsigned int which) //讀取引腳電平狀態,which表示引腳編號
{
// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18)))
{
//udelay(10000); //延時10ms 引數太大,udelay不允許,改為mdelay
mdelay(10);
if(!(*(g_buttons_map_addr[which].PSR) & (1 << 18)))
return 0;
}
return 1;
}
struct button_operations button_oprs = {
.init = button_init,
.read = button_read,
};
int board_button_probe(struct platform_device *pdev)
{
struct device_node *np;
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//獲取device_node結構體
np = pdev->dev.of_node;
if(!np)
{
printk("device_node error\n");
return -1;
}
//獲取暫存器資訊
err = of_property_read_u32(np, "pin", &g_buttons[cur_counts].group_pin);
err = of_property_read_u32_index(np, "pin_reg", 0, &g_buttons[cur_counts].CCM_CCGR1);
err = of_property_read_u32_index(np, "pin_reg", 1, &g_buttons[cur_counts].IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B);
err = of_property_read_u32_index(np, "pin_reg", 2, &g_buttons[cur_counts].GDIR);
err = of_property_read_u32_index(np, "pin_reg", 3, &g_buttons[cur_counts].PSR);
//建立裝置節點,使用上層驅動提供的函式
button_device_register(cur_counts);
//節點數加1
cur_counts++;
return 0;
}
int board_button_remove(struct platform_device *pdev)
{
int i;
int err;
struct device_node* np;
int button_pin;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//獲取device_node結構體
np = pdev->dev.of_node;
err = of_property_read_u32(np, "pin", &button_pin);
for(i = 0; i < cur_counts; i++)
{
if(g_buttons[i].group_pin == button_pin)
{
button_device_unregister(i);
g_buttons[i].group_pin = -1;
break;
}
}
for(i = 0; i < cur_counts; i++)
{
if(g_buttons[i].group_pin != -1)
{
break;
}
}
if(i == cur_counts)
{
cur_counts = 0;
}
return 0;
}
static struct of_device_id mykeys[] = {
{
.compatible = "key0_driver"
},
{},
};
struct platform_driver button_drv = {
.probe = board_button_probe,
.remove = board_button_remove,
.driver = {
.name = "key",
.of_match_table = mykeys,
},
};
static int __init button_driver_init(void)
{
int err;
err = platform_driver_register(&button_drv); //註冊平臺驅動
//呼叫上層介面註冊、向上層驅動提供操作函式
register_button_operations(&button_oprs);
return 0;
}
static void __exit button_driver_exit(void)
{
platform_driver_unregister(&button_drv);
}
module_init(button_driver_init);
module_exit(button_driver_exit);
MODULE_LICENSE("GPL");
3、上層驅動框架
點選檢視程式碼
/*
按鍵驅動程式上層框架,主要實現:
(1)file_operations結構體內容實現
(2)新增驅動入口/出口函式,在入口函式註冊驅動,在出口函式撤銷驅動
(3)提供下層驅動向上層註冊的介面:建立裝置節點、刪除裝置節點、下層向上層註冊按鍵相關的操作函式
*/
#include "asm/memory.h"
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/export.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/printk.h>
#include <linux/init.h>
#include <asm/io.h>
#include "button_opr.h"
int major; //主裝置號
struct class* button_class; //裝置類
struct button_operations* p_button_opr;
void button_device_register(int minor)
{
device_create(button_class, NULL, MKDEV(major, minor), NULL, "key_%d", minor);
}
void button_device_unregister(int minor)
{
device_destroy(button_class, MKDEV(major, minor));
}
void register_button_operations(struct button_operations* button_opr)
{
p_button_opr = button_opr;
}
EXPORT_SYMBOL(button_device_register);
EXPORT_SYMBOL(button_device_unregister);
EXPORT_SYMBOL(register_button_operations);
int button_open(struct inode * node, struct file * file)
{
int which;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
which = iminor(node);
p_button_opr->init(which);
return 0;
};
ssize_t button_read (struct file *file, char __user *user, size_t count, loff_t *start)
{
struct inode *node;
unsigned int which;
int ret;
int err;
// printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
node = file_inode(file);
which = iminor(node);
ret = p_button_opr->read(which);
err = copy_to_user(user, &ret, 1);
return ret;
};
int button_close (struct inode *node, struct file *file)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
struct file_operations button_opr = {
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
.release = button_close
};
static int __init button_init(void)
{
/*
驅動入口函式
(1)註冊驅動;(2)建立類
*/
int err;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(major, "button_driver", &button_opr);
button_class = class_create(THIS_MODULE, "button_class");
err = PTR_ERR(button_class);
if (IS_ERR(button_class))
{
unregister_chrdev(major, "button_driver");
return -1;
}
return 0;
}
static void __exit button_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
class_destroy(button_class);
unregister_chrdev(major, "button_driver");
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
4、應用程式,按鍵按下燈亮,再按下燈滅,如此反覆
點選檢視程式碼
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
int fd_button, fd_led;
char val;
char state;
/*判斷引數*/
if(argc != 3)
{
printf("Usage: %s <buton_dev> <led_dev>\n", argv[0]);
}
fd_button = open(argv[1], O_RDWR);
fd_led = open(argv[2], O_RDWR);
if(fd_button == -1 || fd_led == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/*讀檔案, 控制led燈*/
state = 0;
write(fd_led, &state, 1);
while(1)
{
read(fd_button, &val, 1);
if(val == 0)
{
usleep(10000);
read(fd_button, &val, 1);
if(val == 0)
{
state = ~state;
}
write(fd_led, &state, 1);
// printf("led state: %d", state);
sleep(1);
}
}
close(fd_button);
close(fd_led);
return 0;
}
至此,按鍵驅動程式完成。本次驅動的編寫與led驅動程式類似,可以看作是一次鞏固。
使用按鍵控制led亮滅看似很簡單的功能,但是以此為基礎探究Linux的知識卻可以很深入。比如使用中斷方式判斷按鍵是否按下、燈和按鍵是兩個事件,可以引申出程序執行緒,進而基於此實現程序執行緒的通訊、同步,訊息機制、互斥等等。而且任何嵌入式裝置功能的開發,本質都是操作IO操作暫存器,和點燈、按鍵沒有本質的區別,只不過框架稍微複雜一些,關鍵在linux的那些知識。
接下來要深入學習pinctrl和gpio子系統,這部分在嵌入式linux開發中使用率很高,屬於提供工作效率的工具,應該要掌握,預計花費1-2個月的時間學習掌握。