?
使用驅(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ù)寄存器寫(xiě)入0或者1,就可以輸出高低電平來(lái)控制LED等的亮滅了。
2.2 打開(kāi)的時(shí)鐘
根據(jù)芯片手冊(cè)我們可以看到,要想打開(kāi)GPIO1_IO03的時(shí)鐘就需要要去配置CCGR1這個(gè)寄存器的CG13這個(gè)位
而且我還知道了這個(gè)寄存器的地址是20C406CH,因此我們可以寫(xiě)一個(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_<PADNAME>?:Mux pad xxx,選擇某個(gè)pad的功能
IOMUXC_SW_MUX_CTL_GRP_<GROUP NAME>: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_NAME>:pad pad xxx,設(shè)置某個(gè)pad的參數(shù)
IOMUXC_SW_PAD_CTL_GRP_<GROUP NAME>: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 寫(xiě)GPIO
① 設(shè)置CCM_CCGRx寄存器中某位使能對(duì)應(yīng)的GPIO模塊,默認(rèn)是使能的。② 設(shè)置IOMUX來(lái)選擇引腳用于GPIO。③ 設(shè)置GPIOx_GDIR中某位為1,把該引腳設(shè)置為輸出功能。④ 寫(xiě)GPIOx_DR某位的值。
需要注意的是,你可以設(shè)置該引腳的loopback功能,這樣就可以從GPIOx_PSR中讀到引腳的有實(shí)電平;你從GPIOx_DR中讀回的只是上次設(shè)置的值,它并不能反應(yīng)引腳的真實(shí)電平,比如可能因?yàn)橛布收蠈?dǎo)致該引腳跟地短路了,你通過(guò)設(shè)置GPIOx_DR讓它輸出高電平并不會(huì)起效果。
有了上面的知識(shí),我們點(diǎn)亮led燈的流程基本就了解了。
四、GPIO寄存器操作方法
原則:不能影響到其他位。
4.1 直接讀寫(xiě)
讀出、修改對(duì)應(yīng)位、寫(xiě)入
-
要設(shè)置bit n
val?=?data_reg;//讀出
val?=?val?|?(1<<n);//修改
data_reg?=?val;//寫(xiě)入
-
要清除bit n
val?=?data_reg;//讀出
val?=?val?&?~(1<<n);//修改
data_reg?=?val;//寫(xiě)入
4.2 set-and-clear protocol
set_reg,clr_reg,data_reg 三個(gè)寄存器對(duì)應(yīng)的是同一個(gè)物理寄存器
-
要設(shè)置 bit n:set_reg = (1<<n);
-
要清除 bit n:clr_reg = (1<<n);
五、編寫(xiě)驅(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映射寄存器的物理地址得到虛擬地址,讀寫(xiě)虛擬地址。
驅(qū)動(dòng)怎么和APP傳輸數(shù)據(jù)?
-
通過(guò)
copy_to_user
、copy_from_user
這 2 個(gè)函數(shù)。
六、地址映射
在編寫(xiě)驅(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è)寄存器地址寫(xiě)入數(shù)據(jù)就可以配 GPIO1_IO03的復(fù)用功能?,F(xiàn)在開(kāi)啟了MMU,并且設(shè)置了內(nèi)存映射,因此就不能直接向0X020E0068這個(gè)地址寫(xiě)入數(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<asm/io.h>
#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)行讀寫(xiě)操作即可。實(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)行讀寫(xiě)操作。
上面的話是啥意思呢?
我說(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)行讀寫(xiě)操作。好家伙,Linux內(nèi)核它不建議這樣做,它又提供了讀寫(xiě)函數(shù)對(duì)這個(gè)虛擬地址進(jìn)行操作。那么我們用戶只能按照它建議的這樣做了。比如我想操作這個(gè)地址后4個(gè)字節(jié)的某幾個(gè)位,就需要下面這樣做,先把這個(gè)地址對(duì)應(yīng)的內(nèi)存空間讀出來(lái),然后修改,最后再把修改好的數(shù)據(jù)寫(xiě)入就可以了。
val?=?readl(IMX6U_CCM_CCGR1);
val?&=?~(3?<<?26);?/*?清楚以前的設(shè)置?*/
val?|=?(3?<<?26);?/*?設(shè)置新值?*/
writel(val,?IMX6U_CCM_CCGR1);
具體的讀操作和寫(xiě)操作函數(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就是要讀取寫(xiě)內(nèi)存地址,返回值就是讀取到的數(shù)據(jù)。
2 、寫(xiě)操作函數(shù)
寫(xiě)操作函數(shù)有如下幾個(gè):
void?writeb(u8?value,?volatile?void?__iomem?*addr)//寫(xiě)8bit
void?writew(u16?value,?volatile?void?__iomem?*addr)//寫(xiě)16bit
void?writel(u32?value,?volatile?void?__iomem?*addr)//寫(xiě)32bit
writeb、writew 和 writel 這三個(gè)函數(shù)分別對(duì)應(yīng) 8bit、16bit 和 32bit 寫(xiě)操作,參數(shù)value是要寫(xiě)入的數(shù)值,addr是要寫(xiě)入的地址。
八、程序編寫(xiě)
8.1 編寫(xiě)驅(qū)動(dòng)程序
#include?<linux/types.h>
#include?<linux/kernel.h>
#include?<linux/delay.h>
#include?<linux/ide.h>
#include?<linux/init.h>
#include?<linux/module.h>
#include?<linux/errno.h>
#include?<linux/gpio.h>
#include?<asm/mach/map.h>
#include?<asm/uaccess.h>
#include?<asm/io.h>
/*
我們要配置某一個(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ù)寄存器寫(xiě)數(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è)備寫(xiě)數(shù)據(jù)?
?*?@param?-?filp??:?設(shè)備文件,表示打開(kāi)的文件描述符
?*?@param?-?buf??:?要寫(xiě)給設(shè)備寫(xiě)入的數(shù)據(jù)
?*?@param?-?cnt??:?要寫(xiě)入的數(shù)據(jù)長(zhǎng)度
?*?@param?-?offt??:?相對(duì)于文件首地址的偏移
?*?@return????:?寫(xiě)入的字節(jié)數(shù),如果為負(fù)值,表示寫(xiě)入失敗
?*/
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!\r\n");
??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!\r\n");
??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 編寫(xiě)測(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!\r\n");
??return?-1;
?}
?filename?=?argv[1];
?/*?打開(kāi)led驅(qū)動(dòng)?*/
?fd?=?open(filename,?O_RDWR);
?if(fd?<?0){
??printf("file?%s?open?failed!\r\n",?argv[1]);
??return?-1;
?}
?databuf[0]?=?atoi(argv[2]);?/*?要執(zhí)行的操作:打開(kāi)或關(guān)閉?*/
?/*?向/dev/led文件寫(xiě)入數(shù)據(jù)?*/
?retvalue?=?write(fd,?databuf,?sizeof(databuf));
?if(retvalue?<?0){
??printf("LED?Control?Failed!\r\n");
??close(fd);
??return?-1;
?}
?retvalue?=?close(fd);?/*?關(guān)閉文件?*/
?if(retvalue?<?0){
??printf("file?%s?close?failed!\r\n",?argv[1]);
??return?-1;
?}
?return?0;
}
測(cè)試程序就很簡(jiǎn)單了,不用多說(shuō)。
3 8.編寫(xiě)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í)際情況填寫(xiě)。
-
第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 編寫(xiě)好以后輸入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è)備。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-428465.html
至此,led
這個(gè)設(shè)備的整個(gè)驅(qū)動(dòng)就驗(yàn)證完成了,驅(qū)動(dòng)工作正常。以后的字符設(shè)備驅(qū)動(dòng)實(shí)驗(yàn)基本都可以此為模板進(jìn)行編寫(xiě)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-428465.html
到了這里,關(guān)于嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)之點(diǎn)燈的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!