摘要:前面已經(jīng)介紹了使用裸機(jī)點(diǎn)燈,今天使用驅(qū)動(dòng)開(kāi)發(fā)的方式點(diǎn)亮一個(gè)LED燈??纯磧烧哂猩秴^(qū)別不?
一、先看原理圖
首先查看原理圖,看看我們的板子上的LED等接在哪一個(gè)IO口上面。
好了,看原理圖我們知道LED燈接在芯片的GPIO1的第三個(gè)引腳上面,也就是GPIO1_IO03。
二、IMX6UL的GPIO操作方法
先掌握三個(gè)名詞
CCM: Clock Controller Module (時(shí)鐘控制模塊)
IOMUXC : IOMUX Controller,IO復(fù)用控制器
GPIO: General-purpose input/output,通用的輸入輸出口
2.1 GPIO模塊結(jié)構(gòu)
參考芯片手冊(cè)《Chapter 26: General Purpose Input/Output (GPIO)》我們知道了IMX6UL一共有有5組GPIO(GPIO1~GPIO5),每組引腳最多有32個(gè),但是可能實(shí)際上并沒(méi)有那么多。
?
GPIO1有32個(gè)引腳:GPIO1_IO0~GPIO1_IO31; GPIO2有22個(gè)引腳:GPIO2_IO0~GPIO2_IO21; GPIO3有29個(gè)引腳:GPIO3_IO0~GPIO3_IO28; GPIO4有29個(gè)引腳:GPIO4_IO0~GPIO4_IO28; GPIO5有12個(gè)引腳:GPIO5_IO0~GPIO5_IO11;
?
我們知道IM6ULL有很多的引腳IO,但是并不是每一個(gè)引腳都能當(dāng)做GPIO使用,它可以復(fù)用為其他模式的,比如作為I2C的時(shí)鐘線I2C2_SCL等其他的用處。所以要向把某一IO當(dāng)做GPIO使用需要將其復(fù)用,在linux中負(fù)責(zé)復(fù)用功能的寄存器IOMUXC_SW_MUX。還有要打開(kāi)這個(gè)GPIO的時(shí)鐘,在linux中叫做CCM,跟STM32一樣還要設(shè)置它的IO口速度、上下拉電阻啊、驅(qū)動(dòng)能力啊、壓擺率(就是 IO 電平跳變所需要的時(shí)間,比如從0到1需要多少時(shí)間,時(shí)間越小波形就越陡,說(shuō)明壓擺率越高)啊等這些,在linux中是用IOMUXC_SW_PAD。
因此如果想要使用某一組GPIO,比如GPIO1_IO03。首先要打開(kāi)GPIO1的時(shí)鐘,然后將GPIO1_IO03設(shè)置為GPIO模式,而不是IIC模式。然后在設(shè)置一下GPIO1_IO03這個(gè)引腳的模式,速度、上下拉電阻、壓擺率等。然后再設(shè)置GPIO1_IO03為輸出模式。最后我們就可以向GPIO1_IO03的DR寄存器也就是數(shù)據(jù)寄存器寫入0或者1,就可以輸出高低電平來(lái)控制LED等的亮滅了。
2.2 打開(kāi)的時(shí)鐘
根據(jù)芯片手冊(cè)我們可以看到,要想打開(kāi)GPIO1_IO03的時(shí)鐘就需要要去配置CCGR1這個(gè)寄存器的CG13這個(gè)位
而且我還知道了這個(gè)寄存器的地址是20C406CH,因此我們可以寫一個(gè)宏定義。
?
#define?CCM_CCGR1_BASE????(0X020C406C)//這個(gè)寄存器用來(lái)打開(kāi)GPIO1的時(shí)鐘的
/*?1、使能GPIO1時(shí)鐘?*/ val?=?readl(IMX6U_CCM_CCGR1); val?&=?~(3?<26);?/*?清楚以前的設(shè)置?*/ val?|=?(3?<26);?/*?設(shè)置新值?*/ writel(val,?IMX6U_CCM_CCGR1);
?
2.3 IOMUXC引腳復(fù)用和模式配置
參考資料:芯片手冊(cè)《Chapter 32: IOMUX Controller (IOMUXC)》。對(duì)于某個(gè)/某組引腳,IOMUXC中有2個(gè)寄存器用來(lái)設(shè)置它。
IOMUXC_SW_MUX_CTL_PAD_pad-name
?
IOMUXC_SW_MUX_CTL_PAD_?:Mux pad xxx,選擇某個(gè)pad的功能 IOMUXC_SW_MUX_CTL_GRP_ :Mux grp xxx,選擇某組引腳的功能
?
某個(gè)引腳,或是某組預(yù)設(shè)的引腳,都有8個(gè)可選的模式(alternate (ALT) MUX_MODE),
比如我們要把這個(gè)GPIO1_IO03設(shè)置為GPIO模式,就要將這個(gè)寄存器的bit[0..3]設(shè)置為0101,也就是5.
然后也看到這個(gè)寄存器的地址位Address: 20E_0000h base + 68h offset = 20E_0068h
?
#define?SW_MUX_GPIO1_IO03_BASE??(0X020E0068)//這個(gè)寄存器是將GPIO1_IO03復(fù)用為GPIO的
/*?2、設(shè)置GPIO1_IO03的復(fù)用功能,將其復(fù)用為 ?*??? GPIO1_IO03,最后設(shè)置IO屬性。 ?*/ writel(5,?SW_MUX_GPIO1_IO03);
?
IOMUXC_SW_MUX_CTL_GRP_group-name
?
IOMUXC_SW_PAD_CTL_PAD_:pad pad xxx,設(shè)置某個(gè)pad的參數(shù) IOMUXC_SW_PAD_CTL_GRP_ :pad grp xxx,設(shè)置某組引腳的參數(shù)

?
比如:
2.4 GPIO模塊內(nèi)部
框圖如下:
我們暫時(shí)只需要關(guān)心3個(gè)寄存器:
① GPIOx_GDIR:設(shè)置引腳方向,每位對(duì)應(yīng)一個(gè)引腳,1-output,0-input
② GPIOx_DR:設(shè)置輸出引腳的電平,每位對(duì)應(yīng)一個(gè)引腳,1-高電平,0-低電平
③ GPIOx_PSR:讀取引腳的電平,每位對(duì)應(yīng)一個(gè)引腳,1-高電平,0-低電平
三、怎么編程
3.1 讀GPIO
① 設(shè)置CCM_CCGRx寄存器中某位使能對(duì)應(yīng)的GPIO模塊,默認(rèn)是使能的。② 設(shè)置IOMUX來(lái)選擇引腳用于GPIO。③ 設(shè)置GPIOx_GDIR中某位為0,把該引腳設(shè)置為輸入功能。④ 讀GPIOx_DR或GPIOx_PSR得到某位的值(讀GPIOx_DR返回的是GPIOx_PSR的值)
3.2 寫GPIO
① 設(shè)置CCM_CCGRx寄存器中某位使能對(duì)應(yīng)的GPIO模塊,默認(rèn)是使能的。② 設(shè)置IOMUX來(lái)選擇引腳用于GPIO。③ 設(shè)置GPIOx_GDIR中某位為1,把該引腳設(shè)置為輸出功能。④ 寫GPIOx_DR某位的值。
需要注意的是,你可以設(shè)置該引腳的loopback功能,這樣就可以從GPIOx_PSR中讀到引腳的有實(shí)電平;你從GPIOx_DR中讀回的只是上次設(shè)置的值,它并不能反應(yīng)引腳的真實(shí)電平,比如可能因?yàn)?a href="http://www.brongaenegriffin.com/v/tag/1751/" target="_blank">硬件故障導(dǎo)致該引腳跟地短路了,你通過(guò)設(shè)置GPIOx_DR讓它輸出高電平并不會(huì)起效果。
有了上面的知識(shí),我們點(diǎn)亮led燈的流程基本就了解了。
四、GPIO寄存器操作方法
原則:不能影響到其他位。
4.1 直接讀寫
讀出、修改對(duì)應(yīng)位、寫入
要設(shè)置bit n
?
val?=?data_reg;//讀出 val?=?val?|?(1<?
要清除bit n
?
val?=?data_reg;//讀出 val?=?val?&?~(1<?
4.2 set-and-clear protocol
set_reg,clr_reg,data_reg 三個(gè)寄存器對(duì)應(yīng)的是同一個(gè)物理寄存器
要設(shè)置 bit n:set_reg = (1<
要清除 bit n:clr_reg = (1<
五、編寫驅(qū)動(dòng)程序的套路
1、確定主設(shè)備號(hào),也可以讓內(nèi)核分配。
2、定義自己的file_operations結(jié)構(gòu)體。
3、實(shí)現(xiàn)對(duì)應(yīng)的drv_open/drv_read/drv_write等函數(shù),填入file_operations結(jié)構(gòu)體。
4、把file_operations結(jié)構(gòu)體告訴內(nèi)核:register_chrdev。
5、誰(shuí)來(lái)注冊(cè)驅(qū)動(dòng)程序???得有一個(gè)入口函數(shù):安裝驅(qū)動(dòng)程序時(shí),就會(huì)去調(diào)用這個(gè)入口函數(shù)。
6、有入口函數(shù)就應(yīng)該有出口函數(shù):卸載驅(qū)動(dòng)程序時(shí),出口函數(shù)調(diào)用unregister_chrdev。
7、其他完善:提供設(shè)備信息,自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn):class_create,device_create。
驅(qū)動(dòng)怎么操作硬件?
通過(guò)ioremap映射寄存器的物理地址得到虛擬地址,讀寫虛擬地址。
驅(qū)動(dòng)怎么和APP傳輸數(shù)據(jù)?
通過(guò)copy_to_user、copy_from_user這 2 個(gè)函數(shù)。
六、地址映射
在編寫驅(qū)動(dòng)之前,我們需要先簡(jiǎn)單了解一下 MMU 這個(gè)神器,MMU全稱叫做 Memory Manage Unit,也就是內(nèi)存管理單元。在老版本的Linux中要求處理器必須有MMU,但是現(xiàn)在Linux內(nèi)核已經(jīng)支持無(wú)MMU的處理器了。MMU主要完成的功能如下:
①、完成虛擬空間到物理空間的映射。
②、內(nèi)存保護(hù),設(shè)置存儲(chǔ)器的訪問(wèn)權(quán)限,設(shè)置虛擬存儲(chǔ)空間的緩沖特性。
我們重點(diǎn)來(lái)看一下第①點(diǎn),也就是虛擬空間到物理空間的映射,也叫做地址映射。首先了解兩個(gè)地址概念:虛擬地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。對(duì)于 32 位的處理器來(lái)說(shuō),虛擬地址范圍是2^32=4GB,我們的開(kāi)發(fā)板上有512MB的DDR3,這512MB的內(nèi)存就是物理內(nèi)存,經(jīng)過(guò)MMU可以將其映射到整個(gè)4GB的虛擬空間
內(nèi)存映射
物理內(nèi)存只有512MB,虛擬內(nèi)存有4GB,那么肯定存在多個(gè)虛擬地址映射到同一個(gè)物理地址上去,虛擬地址范圍比物理地址范圍大的問(wèn)題處理器自會(huì)處理。
Linux內(nèi)核啟動(dòng)的時(shí)候會(huì)初始化MMU,設(shè)置好內(nèi)存映射,設(shè)置好以后CPU 訪問(wèn)的都是虛擬地址。比如 I.MX6ULL的GPIO1_IO03引腳的復(fù)用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的地址為0X020E0068。如果沒(méi)有開(kāi)啟MMU的話直接向0X020E0068這個(gè)寄存器地址寫入數(shù)據(jù)就可以配 GPIO1_IO03的復(fù)用功能?,F(xiàn)在開(kāi)啟了MMU,并且設(shè)置了內(nèi)存映射,因此就不能直接向0X020E0068這個(gè)地址寫入數(shù)據(jù)了。我們必須得到 0X020E0068這個(gè)物理地址在Linux系統(tǒng)里面對(duì)應(yīng)的虛擬地址,這里就涉及到了物理內(nèi)存和虛擬內(nèi)存之間的轉(zhuǎn)換,需要用到兩個(gè)函數(shù):ioremap 和 iounmap。
6.1 ioremap函數(shù)
ioremap函 數(shù)用于獲取指定物理地址空間對(duì)應(yīng)的虛擬地址空間,定義在arch/arm/include/asm/io.h文件中,定義如下:
?
#include#define?ioremap(cookie,size)?__arm_ioremap((cookie),?(size),MT_DEVICE) void?__iomem?*?__arm_ioremap(phys_addr_t?phys_addr,?size_t?size,unsigned?int?mtype) { ?return?arch_ioremap_caller(phys_addr,?size,?mtype,__builtin_return_address(0)); } ?
ioremap 是個(gè)宏,有兩個(gè)參數(shù):cookie 和 size,真正起作用的是函數(shù)__arm_ioremap,此函數(shù)有三個(gè)參數(shù)和一個(gè)返回值,這些參數(shù)和返回值的含義如下:
phys_addr:要映射給的物理起始地址。
size:要映射的內(nèi)存空間大小。
mtype:ioremap 的類型,可以選擇 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函數(shù)選擇 MT_DEVICE。
返回值:__iomem 類型的指針,指向映射后的虛擬空間首地址。
假如我們要獲取I.MX6ULL的IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器對(duì)應(yīng)的虛擬地址,使用如下代碼即可:
?
#define?SW_MUX_GPIO1_IO03_BASE??(0X020E0068) static?void?__iomem*?SW_MUX_GPIO1_IO03; SW_MUX_GPIO1_IO03?=?ioremap(SW_MUX_GPIO1_IO03_BASE,?4);?
宏SW_MUX_GPIO1_IO03_BASE是寄存器物理地址,SW_MUX_GPIO1_IO03是映射后的虛擬地址。對(duì)于I.MX6ULL 來(lái)說(shuō)一個(gè)寄存器是4 字節(jié)(32 位)的,因此映射的內(nèi)存長(zhǎng)度為 4。映射完成以后直接對(duì)SW_MUX_GPIO1_IO03進(jìn)行讀寫操作即可。實(shí)際上,它是按頁(yè)(4096 字節(jié))進(jìn)行映射的,是整頁(yè)整頁(yè)地映射的。所以說(shuō)雖然映射的是4字節(jié),實(shí)際上映射的是4096字節(jié)。
6.2 iounmap函數(shù)
卸載驅(qū)動(dòng)的時(shí)候需要使用iounmap函數(shù)釋放掉ioremap函數(shù)所做的映射,iounmap函數(shù)原型如下:
?
void?iounmap?(volatile?void?__iomem?*addr)?
iounmap只有一個(gè)參數(shù)addr,此參數(shù)就是要取消映射的虛擬地址空間首地址。假如我們現(xiàn)在要取消掉IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的地址映射,使用如下代碼即可:
?
iounmap(SW_MUX_GPIO1_IO03);?
6.3 volatile的使用
① 編譯器很聰明,會(huì)幫我們做些優(yōu)化,比如:
?
int?a; a?=?0;?//?這句話可以優(yōu)化掉,不影響?a?的結(jié)果 a?=?1;?
② 有時(shí)候編譯器會(huì)自作聰明,比如:
?
int?*p?=?ioremap(xxxx,?4);?//?GPIO?寄存器的地址 *p?=?0;?//?點(diǎn)燈,但是這句話被優(yōu)化掉了 *p?=?1;?//?滅燈?
③ 對(duì)于上面的情況,為了避免編譯器自動(dòng)優(yōu)化,需要加上 volatile,告訴它這是容易出錯(cuò)的,別亂優(yōu)化:
?
volatile?int?*p?=?ioremap(xxxx,?4);?//?GPIO?寄存器的地址 *p?=?0;?//?點(diǎn)燈,這句話不會(huì)被優(yōu)化掉 *p?=?1;?//?滅燈?
七、I/O內(nèi)存訪問(wèn)函數(shù)
這里說(shuō)的I/O是輸入/輸出的意思,并不是我們學(xué)習(xí)單片機(jī)的時(shí)候講的GPIO引腳。這里涉及到兩個(gè)概念:I/O端口和I/O內(nèi)存。
當(dāng)外部寄存器或內(nèi)存映射到IO空間時(shí),稱為I/O端口。當(dāng)外部寄存器或內(nèi)存映射到內(nèi)存空間時(shí),稱為I/O內(nèi)存。
但是對(duì)于ARM來(lái)說(shuō)沒(méi)有 I/O 空間這個(gè)概念,因此ARM體系下只有I/O內(nèi)存(可以直接理解為內(nèi)存)。使用ioremap函數(shù)將寄存器的物理地址映射到虛擬地址以后,我們就可以直接通過(guò)指針訪問(wèn)這些地址,但是Linux內(nèi)核不建議這么做,而是推薦使用一組操作函數(shù)來(lái)對(duì)映射后的內(nèi)存進(jìn)行讀寫操作。
上面的話是啥意思呢?
我說(shuō)通俗一點(diǎn)就是:我現(xiàn)在知道了GPIO1_IO03它的時(shí)鐘寄存器地址是0X020C406C,但是你不能直接操作它
?
#define?CCM_CCGR1_BASE????(0X020C406C)?
0X020C406C是它實(shí)際存在的也就是物理地址,但是呢在Linux內(nèi)核啟動(dòng)的時(shí)候會(huì)初始化MMU,設(shè)置好內(nèi)存映射,設(shè)置好以后CPU訪問(wèn)的都是虛擬地址,我們就不能操作實(shí)際的物理地址了。怎么辦呢?不用怕,Linux提供了ioremap內(nèi)存映射函數(shù),我知道了實(shí)際的物理地址,只要通過(guò)這個(gè)函數(shù)我們就自動(dòng)的獲取到了這個(gè)物理地址對(duì)應(yīng)的虛擬地址了
?
IMX6U_CCM_CCGR1?=?ioremap(CCM_CCGR1_BASE,?4);?
現(xiàn)在我們就得到了0X020C406C對(duì)應(yīng)的虛擬地址IMX6U_CCM_CCGR1 ,但是呢,現(xiàn)在我們還不能直接操作這個(gè)虛擬地址。這又為啥呢?因?yàn)槭褂胕oremap函數(shù)將寄存器的物理地址映射到虛擬地址以后,按說(shuō)我們就可以直接通過(guò)指針訪問(wèn)這些地址,但是Linux內(nèi)核不建議這么做,而是推薦使用一組操作函數(shù)來(lái)對(duì)映射后的內(nèi)存進(jìn)行讀寫操作。好家伙,Linux內(nèi)核它不建議這樣做,它又提供了讀寫函數(shù)對(duì)這個(gè)虛擬地址進(jìn)行操作。那么我們用戶只能按照它建議的這樣做了。比如我想操作這個(gè)地址后4個(gè)字節(jié)的某幾個(gè)位,就需要下面這樣做,先把這個(gè)地址對(duì)應(yīng)的內(nèi)存空間讀出來(lái),然后修改,最后再把修改好的數(shù)據(jù)寫入就可以了。
?
val?=?readl(IMX6U_CCM_CCGR1); val?&=?~(3?<26);?/*?清楚以前的設(shè)置?*/ val?|=?(3?<26);?/*?設(shè)置新值?*/ writel(val,?IMX6U_CCM_CCGR1);?
具體的讀操作和寫操作函數(shù)如下:
1 、讀操作函數(shù)
讀操作函數(shù)有如下幾個(gè):
?
u8?readb(const?volatile?void?__iomem?*addr)//讀8bit u16?readw(const?volatile?void?__iomem?*addr)//讀16bit u32?readl(const?volatile?void?__iomem?*addr)//讀32bit?
readb、readw 和readl這三個(gè)函數(shù)分別對(duì)應(yīng) 8bit、16bit 和 32bit讀操作,參數(shù)addr就是要讀取寫內(nèi)存地址,返回值就是讀取到的數(shù)據(jù)。
2 、寫操作函數(shù)
寫操作函數(shù)有如下幾個(gè):
?
void?writeb(u8?value,?volatile?void?__iomem?*addr)//寫8bit void?writew(u16?value,?volatile?void?__iomem?*addr)//寫16bit void?writel(u32?value,?volatile?void?__iomem?*addr)//寫32bit?
writeb、writew 和 writel 這三個(gè)函數(shù)分別對(duì)應(yīng) 8bit、16bit 和 32bit 寫操作,參數(shù)value是要寫入的數(shù)值,addr是要寫入的地址。
八、程序編寫
8.1 編寫驅(qū)動(dòng)程序
?
#include?#include? #include? #include? #include? #include? #include? #include? #include? #include? #include? /* 我們要配置某一個(gè)GPIO的引腳 1、先打開(kāi)這個(gè)GPIO的時(shí)鐘 2、在講這個(gè)GPIO復(fù)用為GPIO功能 3、設(shè)置這個(gè)GPIO的參數(shù)等 4、設(shè)置這個(gè)GPIO是輸入還是輸出 5、向這個(gè)GPIO的數(shù)據(jù)寄存器寫數(shù)據(jù)就可以了 */ #define?LED_MAJOR??200??/*?主設(shè)備號(hào)?*/ #define?LED_NAME??"led"??/*?設(shè)備名字?*/ #define?LEDOFF??0????/*?關(guān)燈?*/ #define?LEDON??1????/*?開(kāi)燈?*/ ? /*?寄存器物理地址?*/ #define?CCM_CCGR1_BASE????(0X020C406C)//這個(gè)寄存器用來(lái)打開(kāi)GPIO1的時(shí)鐘的 #define?SW_MUX_GPIO1_IO03_BASE??(0X020E0068)//這個(gè)寄存器是將GPIO1_IO03復(fù)用為GPIO的 #define?SW_PAD_GPIO1_IO03_BASE??(0X020E02F4)//這個(gè)寄存器是配置GPIO1_IO03的速度、驅(qū)動(dòng)能力、壓擺率等 #define?GPIO1_DR_BASE????(0X0209C000)//這個(gè)寄存器是GPIO1_IO03的數(shù)據(jù)寄存器 #define?GPIO1_GDIR_BASE????(0X0209C004)//這個(gè)寄存器是設(shè)置GPIO1_IO03的方向,輸入還是輸出 /*?映射后的寄存器虛擬地址指針?*/ static?void?__iomem?*IMX6U_CCM_CCGR1; static?void?__iomem?*SW_MUX_GPIO1_IO03; static?void?__iomem?*SW_PAD_GPIO1_IO03; static?void?__iomem?*GPIO1_DR; static?void?__iomem?*GPIO1_GDIR; /* ?*?@description??:?LED打開(kāi)/關(guān)閉 ?*?@param?-?sta??:?LEDON(0)?打開(kāi)LED,LEDOFF(1)?關(guān)閉LED ?*?@return????:?無(wú) ?*/ void?led_switch(u8?sta) { ?u32?val?=?0; ?if(sta?==?LEDON)?{ ??val?=?readl(GPIO1_DR); ??val?&=?~(1?<3);? ??writel(val,?GPIO1_DR); ?}else?if(sta?==?LEDOFF)?{ ??val?=?readl(GPIO1_DR); ??val|=?(1?<3);? ??writel(val,?GPIO1_DR); ?}? } /* ?*?@description??:?打開(kāi)設(shè)備 ?*?@param?-?inode??:?傳遞給驅(qū)動(dòng)的inode ?*?@param?-?filp??:?設(shè)備文件,file結(jié)構(gòu)體有個(gè)叫做private_data的成員變量 ?*????????一般在open的時(shí)候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。 ?*?@return????:?0?成功;其他?失敗 ?*/ static?int?led_open(struct?inode?*inode,?struct?file?*filp) { ?return?0; } /* ?*?@description??:?從設(shè)備讀取數(shù)據(jù)? ?*?@param?-?filp??:?要打開(kāi)的設(shè)備文件(文件描述符) ?*?@param?-?buf??:?返回給用戶空間的數(shù)據(jù)緩沖區(qū) ?*?@param?-?cnt??:?要讀取的數(shù)據(jù)長(zhǎng)度 ?*?@param?-?offt??:?相對(duì)于文件首地址的偏移 ?*?@return????:?讀取的字節(jié)數(shù),如果為負(fù)值,表示讀取失敗 ?*/ static?ssize_t?led_read(struct?file?*filp,?char?__user?*buf,?size_t?cnt,?loff_t?*offt) { ?return?0; } /* ?*?@description??:?向設(shè)備寫數(shù)據(jù)? ?*?@param?-?filp??:?設(shè)備文件,表示打開(kāi)的文件描述符 ?*?@param?-?buf??:?要寫給設(shè)備寫入的數(shù)據(jù) ?*?@param?-?cnt??:?要寫入的數(shù)據(jù)長(zhǎng)度 ?*?@param?-?offt??:?相對(duì)于文件首地址的偏移 ?*?@return????:?寫入的字節(jié)數(shù),如果為負(fù)值,表示寫入失敗 ?*/ static?ssize_t?led_write(struct?file?*filp,?const?char?__user?*buf,?size_t?cnt,?loff_t?*offt) { ?int?retvalue; ?unsigned?char?databuf[1]; ?unsigned?char?ledstat; ?retvalue?=?copy_from_user(databuf,?buf,?cnt); ?if(retvalue?0)?{ ??printk("kernel?write?failed! "); ??return?-EFAULT; ?} ?ledstat?=?databuf[0];??/*?獲取狀態(tài)值?*/ ?if(ledstat?==?LEDON)?{? ??led_switch(LEDON);??/*?打開(kāi)LED燈?*/ ?}?else?if(ledstat?==?LEDOFF)?{ ??led_switch(LEDOFF);?/*?關(guān)閉LED燈?*/ ?} ?return?0; } /* ?*?@description??:?關(guān)閉/釋放設(shè)備 ?*?@param?-?filp??:?要關(guān)閉的設(shè)備文件(文件描述符) ?*?@return????:?0?成功;其他?失敗 ?*/ static?int?led_release(struct?inode?*inode,?struct?file?*filp) { ?return?0; } /*?設(shè)備操作函數(shù)?*/ static?struct?file_operations?led_fops?=?{ ?.owner???=?THIS_MODULE, ?.open???=?led_open, ?.read???=?led_read, ?.write???=?led_write, ?.release?=?led_release, }; /* ?*?@description?:?驅(qū)動(dòng)出口函數(shù) ?*?@param???:?無(wú) ?*?@return???:?無(wú) ?*/ static?int?__init?led_init(void) { ?int?retvalue?=?0; ?u32?val?=?0; ?/*?初始化LED?*/ ?/*?1、寄存器地址映射?*/ ?IMX6U_CCM_CCGR1?=?ioremap(CCM_CCGR1_BASE,?4); ?SW_MUX_GPIO1_IO03?=?ioremap(SW_MUX_GPIO1_IO03_BASE,?4); ?SW_PAD_GPIO1_IO03?=?ioremap(SW_PAD_GPIO1_IO03_BASE,?4); ?GPIO1_DR?=?ioremap(GPIO1_DR_BASE,?4); ?GPIO1_GDIR?=?ioremap(GPIO1_GDIR_BASE,?4); ?/*?2、使能GPIO1時(shí)鐘?*/ ?val?=?readl(IMX6U_CCM_CCGR1); ?val?&=?~(3?<26);?/*?清楚以前的設(shè)置?*/ ?val?|=?(3?<26);?/*?設(shè)置新值?*/ ?writel(val,?IMX6U_CCM_CCGR1); ?/*?3、設(shè)置GPIO1_IO03的復(fù)用功能,將其復(fù)用為 ??*??? GPIO1_IO03,最后設(shè)置IO屬性。 ??*/ ?writel(5,?SW_MUX_GPIO1_IO03); ? ?/*寄存器SW_PAD_GPIO1_IO03設(shè)置IO屬性 ??*bit?16:0?HYS關(guān)閉 ??*bit?[15:14]:?00?默認(rèn)下拉 ?????*bit?[13]:?0?kepper功能 ?????*bit?[12]:?1?pull/keeper使能 ?????*bit?[11]:?0?關(guān)閉開(kāi)路輸出 ?????*bit?[7:6]:?10?速度100Mhz ?????*bit?[5:3]:?110?R0/6驅(qū)動(dòng)能力 ?????*bit?[0]:?0?低轉(zhuǎn)換率 ??*/ ?writel(0x10B0,?SW_PAD_GPIO1_IO03); ?/*?4、設(shè)置GPIO1_IO03為輸出功能?*/ ?val?=?readl(GPIO1_GDIR); ?val?&=?~(1?<3);?/*?清除以前的設(shè)置?*/ ?val?|=?(1?<3);?/*?設(shè)置為輸出?*/ ?writel(val,?GPIO1_GDIR); ?/*?5、默認(rèn)關(guān)閉LED?*/ ?val?=?readl(GPIO1_DR); ?val?|=?(1?<3);? ?writel(val,?GPIO1_DR); ?/*?6、注冊(cè)字符設(shè)備驅(qū)動(dòng)?*/ ?retvalue?=?register_chrdev(LED_MAJOR,?LED_NAME,?&led_fops); ?if(retvalue?0){ ??printk("register?chrdev?failed! "); ??return?-EIO; ?} ?return?0; } /* ?*?@description?:?驅(qū)動(dòng)出口函數(shù) ?*?@param???:?無(wú) ?*?@return???:?無(wú) ?*/ static?void?__exit?led_exit(void) { ?/*?取消映射?*/ ?iounmap(IMX6U_CCM_CCGR1); ?iounmap(SW_MUX_GPIO1_IO03); ?iounmap(SW_PAD_GPIO1_IO03); ?iounmap(GPIO1_DR); ?iounmap(GPIO1_GDIR); ?/*?注銷字符設(shè)備驅(qū)動(dòng)?*/ ?unregister_chrdev(LED_MAJOR,?LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zhiguoxin"); ?
有了上面的講解,代碼很簡(jiǎn)單就不用多說(shuō)了,就是按照那7步來(lái)操作的。
8.2 編寫測(cè)試程序
?
#include?"stdio.h" #include?"unistd.h" #include?"sys/types.h" #include?"sys/stat.h" #include?"fcntl.h" #include?"stdlib.h" #include?"string.h" /*************************************************************** 使用方法??: ./ledtest?/dev/led??0???關(guān)閉LED ./ledtest?/dev/led??1???打開(kāi)LED?? ***************************************************************/ #define?LEDOFF??0 #define?LEDON??1 /* ?*?@description??:?main主程序 ?*?@param?-?argc??:?argv數(shù)組元素個(gè)數(shù) ?*?@param?-?argv??:?具體參數(shù) ?*?@return????:?0?成功;其他?失敗 ?*/ int?main(int?argc,?char?*argv[]) { ?int?fd,?retvalue; ?char?*filename; ?unsigned?char?databuf[1]; ? ?if(argc?!=?3){ ??printf("Error?Usage! "); ??return?-1; ?} ?filename?=?argv[1]; ?/*?打開(kāi)led驅(qū)動(dòng)?*/ ?fd?=?open(filename,?O_RDWR); ?if(fd?0){ ??printf("file?%s?open?failed! ",?argv[1]); ??return?-1; ?} ?databuf[0]?=?atoi(argv[2]);?/*?要執(zhí)行的操作:打開(kāi)或關(guān)閉?*/ ?/*?向/dev/led文件寫入數(shù)據(jù)?*/ ?retvalue?=?write(fd,?databuf,?sizeof(databuf)); ?if(retvalue?0){ ??printf("LED?Control?Failed! "); ??close(fd); ??return?-1; ?} ?retvalue?=?close(fd);?/*?關(guān)閉文件?*/ ?if(retvalue?0){ ??printf("file?%s?close?failed! ",?argv[1]); ??return?-1; ?} ?return?0; }?
測(cè)試程序就很簡(jiǎn)單了,不用多說(shuō)。
3 8.編寫Makefile
?
KERNELDIR?:=?/home/zhiguoxin/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek CURRENT_PATH?:=?$(shell?pwd) obj-m?:=?led.o build:?kernel_modules kernel_modules: ?$(MAKE)?-C?$(KERNELDIR)?M=$(CURRENT_PATH)?modules ?$(CROSS_COMPILE)arm-linux-gnueabihf-gcc?-o?ledApp?ledApp.c? clean: ?$(MAKE)?-C?$(KERNELDIR)?M=$(CURRENT_PATH)?clean?
第1行,KERNELDIR表示開(kāi)發(fā)板所使用的Linux內(nèi)核源碼目錄,使用絕對(duì)路徑,大家根據(jù)自己的實(shí)際情況填寫。
第2行,CURRENT_PATH表示當(dāng)前路徑,直接通過(guò)運(yùn)行pwd命令來(lái)獲取當(dāng)前所處路徑。
第3行,obj-m表示將led.c這個(gè)文件編譯為led.ko模塊。
第8行,具體的編譯命令,后面的modules表示編譯模塊,-C表示將當(dāng)前的工作目錄切換到指定目錄中,也就是KERNERLDIR目錄。M表示模塊源碼目錄,make modules命令中加入M=dir以后程序會(huì)自動(dòng)到指定的 dir 目錄中讀取模塊的源碼并將其編譯為.ko 文件。
第9行,使用交叉編譯工具鏈將ledApp.c編譯成可以在arm板子上運(yùn)行的ledApp可執(zhí)行文件。
Makefile 編寫好以后輸入make命令編譯驅(qū)動(dòng)模塊,編譯過(guò)程如圖所示
九、運(yùn)行測(cè)試
9.1 上傳程序到開(kāi)發(fā)板執(zhí)行
開(kāi)發(fā)板啟動(dòng)后通過(guò)NFS掛載Ubuntu目錄的方式,將相應(yīng)的文件拷貝到開(kāi)發(fā)板上。簡(jiǎn)單來(lái)說(shuō),就是通過(guò)NFS在開(kāi)發(fā)板上通過(guò)網(wǎng)絡(luò)直接訪問(wèn)ubuntu虛擬機(jī)上的文件,并且就相當(dāng)于自己本地的文件一樣。
因?yàn)槲业拇a都放在/home/zhiguoxin/myproject/alientek_drv_development_source這個(gè)目錄下,所以我們將這個(gè)目錄作為NFS共享文件夾。
Ubuntu IP為192.168.10.100,一般都是掛載在開(kāi)發(fā)板的mnt目錄下,這個(gè)目錄是專門用來(lái)給我們作為臨時(shí)掛載的目錄。
文件系統(tǒng)目錄簡(jiǎn)介
然后使用MobaXterm軟件通過(guò)SSH訪問(wèn)開(kāi)發(fā)板。
?
ubuntu?ip:192.168.10.100 windows?ip:192.168.10.200 開(kāi)發(fā)板ip:192.168.10.50?
在開(kāi)發(fā)板上執(zhí)行以下命令就可以實(shí)現(xiàn)掛載了:
?
mount?-t?nfs?-o?nolock,vers=3?192.168.10.100:/home/zhiguoxin/myproject/alientek_drv_development_source?/mnt?
就將開(kāi)飯的mnt目錄掛載在ubuntu的/home/zhiguoxin/myproject/alientek_drv_development_source目錄下了。這樣我們就可以在Ubuntu下修改文件,然后可以直接在開(kāi)發(fā)板上執(zhí)行可執(zhí)行文件了。當(dāng)然我這里的/home/zhiguoxin/myproject/和windows之間是一個(gè)共享目錄,我也可以直接在windows上面修改文件,然后ubuntu和開(kāi)發(fā)板直接進(jìn)行文件同步了。
9.2 加載驅(qū)動(dòng)模塊
驅(qū)動(dòng)模塊led.ko和ledApp可執(zhí)行文件都已經(jīng)準(zhǔn)備好了,接下來(lái)就是運(yùn)行測(cè)試。這里我是用掛載的方式將服務(wù)端的項(xiàng)目文件夾掛載到arm板的mnt目錄,進(jìn)入到/mnt/02_led目錄輸入如下命令加載led.ko驅(qū)動(dòng)文件:
?
insmod?led.ko?
9.3 創(chuàng)建設(shè)備節(jié)點(diǎn)文件
驅(qū)動(dòng)加載成功需要在/dev目錄下創(chuàng)建一個(gè)與之對(duì)應(yīng)的設(shè)備節(jié)點(diǎn)文件,應(yīng)用程序就是通過(guò)操作這個(gè)設(shè)備節(jié)點(diǎn)文件來(lái)完成對(duì)具體設(shè)備的操作。輸入如下命令創(chuàng)建/dev/led這個(gè)設(shè)備節(jié)點(diǎn)文件:
?
mknod?/dev/led?c?200?0?
其中mknod是創(chuàng)建節(jié)點(diǎn)命令,/dev/hello_drv 是要?jiǎng)?chuàng)建的節(jié)點(diǎn)文件,c表示這是個(gè)字符設(shè)備,200是設(shè)備的主設(shè)備號(hào),0是設(shè)備的次設(shè)備號(hào)。創(chuàng)建完成以后就會(huì)存在/dev/led這個(gè)文件,可以使用ls /dev/led-l命令查看。
9.3 led設(shè)備操作測(cè)試
一切準(zhǔn)備就緒。使用ledtest 軟件操作led這個(gè)設(shè)備,看看是否可以正常打開(kāi)或關(guān)閉led。
?
./ledApp?/dev/led??0???關(guān)閉LED ./ledApp?/dev/led??1???打開(kāi)LED??
9.4 ?卸載驅(qū)動(dòng)模塊
如果不再使用某個(gè)設(shè)備的話可以將其驅(qū)動(dòng)卸載掉,比如輸入如下命令卸載掉hello_drv這個(gè)設(shè)備:
?
rmmod?led.ko?
卸載以后使用lsmod命令查看led這個(gè)模塊還存不存在:
可以看出,此時(shí)系統(tǒng)已經(jīng)沒(méi)有任何模塊了,led這個(gè)模塊也不存在了,說(shuō)明模塊卸載成功。而且系統(tǒng)中也沒(méi)有了led這個(gè)設(shè)備。
至此,led這個(gè)設(shè)備的整個(gè)驅(qū)動(dòng)就驗(yàn)證完成了,驅(qū)動(dòng)工作正常。以后的字符設(shè)備驅(qū)動(dòng)實(shí)驗(yàn)基本都可以此為模板進(jìn)行編寫。
?
審核編輯:湯梓紅
評(píng)論