• 正文
    • 微機(jī)總線地址
    • 數(shù)據(jù)總線:
    • 數(shù)據(jù)總線的寬度對(duì)CPU的性能的影響:
    • 物理地址(PA)
    • 虛擬地址(VA)
    • 頁表(MMU的單元)
    • IO口驅(qū)動(dòng)代碼編譯
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

樹莓派高級(jí)開發(fā)——“IO口驅(qū)動(dòng)代碼的編寫“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊(cè)知識(shí)

02/03 11:45
441
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

微機(jī)總線地址

地址總線

    百度百科解釋: 地址總線 (Address Bus;又稱:位址總線) 屬于一種電腦總線 (一部份),是由CPU 或有DMA 能力的單元,用來溝通這些單元想要存?。ㄗx取/寫入)電腦內(nèi)存元件/地方的實(shí)體位址。地址總線 = cpu能夠訪問內(nèi)存的范圍:用一個(gè)現(xiàn)象來解釋地址總線:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可是系統(tǒng)只識(shí)別了3.8G,裝了64位,才能識(shí)別到8G。32位能表示/訪問 4,294,967,296 bit
kbit——mbit——gbit 差1024
bit 4,294,967,296
kbit 4,194,304 K
mbit 4,096 M
gbit 4 G

地址總線 = CPU尋找外部的內(nèi)存單元靠的是地址總線傳輸?shù)臄?shù)據(jù)。如果CPU有8根地址總線,每根線上傳輸0或1,那么傳輸?shù)臄?shù)據(jù)范圍為00000000~ 11111111,每一個(gè)數(shù)值都對(duì)應(yīng)內(nèi)存中的一個(gè)內(nèi)存單元,所以可以找到編號(hào)為00000000~ 11111111號(hào)的內(nèi)存單元。

如果傳輸?shù)臄?shù)據(jù)為00110011,那么就會(huì)找到00110011號(hào)內(nèi)存單元,如果傳輸?shù)臄?shù)據(jù)為10110111,那么就會(huì)找到10110111號(hào)內(nèi)存單元。編號(hào)不在[00000000,11111111]范圍內(nèi)的CPU就尋找不到,例如100000000號(hào)內(nèi)存單元,CPU就尋找不到。尋址能力就是計(jì)算CPU能尋找多少個(gè)內(nèi)存單元,00000000~11111111號(hào)內(nèi)存單元,一共有256個(gè),一個(gè)內(nèi)存單元的大小為1byte,這256個(gè)內(nèi)存單元的大小為256byte。CPU是通過地址總線來指定存儲(chǔ)單元的。

**==地址總線決定了cpu所能訪問的最大內(nèi)存空間的大小==。eg: 10根地址線能訪問的最大的內(nèi)存為1024**(2的10次方)位二進(jìn)制數(shù)據(jù)(1B)地址總線是地址線數(shù)量之和。若CPU的地址總線寬度是32位,那么CPU的尋址范圍是4G(2的32次方位),所以最多支持4G內(nèi)存.比如上面我們說的那個(gè)現(xiàn)象:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可系統(tǒng)只是別了3.8G,裝了64位才能識(shí)別到8G。裝了32位的操作系統(tǒng)CPU的訪問范圍是2^32bit,就是4194304kbit,就是4096Mbit,等于4G。樹莓派也是32位 ,一個(gè)G的內(nèi)存,但它只能訪問949M剩下的挪作他用。

數(shù)據(jù)總線:

    CPU通過地址總線尋址,然后通過數(shù)據(jù)總線與外部設(shè)備互換信息。是CPU與內(nèi)存或其他器件之間的數(shù)據(jù)傳送的通道。數(shù)據(jù)總線的寬度決定了CPU和外界的數(shù)據(jù)傳送速度。每條傳輸線一次只能傳輸1位二進(jìn)制數(shù)據(jù)。eg: 8根數(shù)據(jù)線一次可傳送一個(gè)8位二進(jìn)制數(shù)據(jù)(即一個(gè)字節(jié))。數(shù)據(jù)總線是數(shù)據(jù)線數(shù)量之和,數(shù)據(jù)總線的位數(shù)決定CPU單次通信能交換的信息數(shù)量。

數(shù)據(jù)總線的寬度對(duì)CPU的性能的影響:

    首先,總線的速度(即:CPU的主頻,CPU的性能指標(biāo)之一)決定CPU和外設(shè)互換信息的速度。其次,數(shù)據(jù)總線的寬度也是表示CPU性能的參數(shù)之一(通常,我們說“64位的CPU”是指CPU的數(shù)據(jù)總線的寬度是64位)。如:64位數(shù)據(jù)總線的CPU一次就能取出64bit的數(shù)據(jù),8位數(shù)據(jù)總線的CPU一次只能取出8bit的數(shù)據(jù),在相同頻率的情況下,8位數(shù)據(jù)總線的CPU就得連續(xù)取8次數(shù)據(jù),數(shù)據(jù)量才能和64位數(shù)據(jù)總線一次取出的數(shù)據(jù)量相同,單就比較取數(shù)據(jù)的性能就相差8倍。況且,通常CPU中的寄存器的位數(shù)與數(shù)據(jù)總線的寬度一樣,所以在數(shù)據(jù)處理方面,64位的CPU又比8位的CPU快很多。CPU的地址總線位數(shù)和數(shù)據(jù)總線可以不同(典型代表就是51單片機(jī)),但是一般都相同。16位機(jī)有16根數(shù)據(jù)總線,20根地址總線,能訪問1M(2的20次方),32位機(jī)有32根數(shù)據(jù)總線,32根地址總線,能訪問4G(2的32次方),64位機(jī)確實(shí)有64根數(shù)據(jù)總線。

物理地址(PA)

百度百科解釋:網(wǎng)卡物理地址存儲(chǔ)器中存儲(chǔ)單元對(duì)應(yīng)實(shí)際地址稱物理地址,與邏輯地址相對(duì)應(yīng)。網(wǎng)卡的物理地址通常是由網(wǎng)卡生產(chǎn)廠家寫入網(wǎng)卡的EPROM(一種閃存芯片,通??梢酝ㄟ^程序擦寫),它存儲(chǔ)的是傳輸數(shù)據(jù)時(shí)真正賴以標(biāo)識(shí)發(fā)出數(shù)據(jù)的電腦和接收數(shù)據(jù)的主機(jī)的地址。

這里說的 物理地址是內(nèi)存中的內(nèi)存單元實(shí)際地址,不是外部總線連接的其他電子元件的地址!**==物理地址屬于比較好理解的,物理地址就是內(nèi)存中每個(gè)內(nèi)存單元的編號(hào)==**,這個(gè)編號(hào)是順序排好的,物理地址的大小決定了內(nèi)存中有多少個(gè)內(nèi)存單元,物理地址的大小由地址總線的位寬決定!物理地址是硬件實(shí)際地址或絕對(duì)地址

虛擬地址(VA)

    • 虛擬地址是Windows程序時(shí)運(yùn)行在386保護(hù)模式下,這樣程序訪問存儲(chǔ)器所使用的==邏輯地址

(基于算法的地址[軟件層面的地址:假地址])

    • 稱為虛擬地址==,與實(shí)地址模式下的分段地址類似,虛擬地址也可以寫為“段:偏移量”的形式,這里的段是指段選擇器。而linux沒有各種保護(hù)模式,本來用的就是虛擬地址。虛擬地址是CPU保護(hù)模式下的一個(gè)概念,保護(hù)模式是80286系列和之后的x86兼容CPU操作模式,在CPU引導(dǎo)完操作系統(tǒng)內(nèi)核后,操作系統(tǒng)內(nèi)核會(huì)進(jìn)入一種CPU保護(hù)模式,也叫虛擬內(nèi)存管理,在這之后的程序在運(yùn)行時(shí)都處于虛擬內(nèi)存當(dāng)中,虛擬內(nèi)存里的所有地址都是不直接的,所以你有時(shí)候可以看到一個(gè)虛擬地址對(duì)應(yīng)不同的物理地址,比如A進(jìn)程里的call函數(shù)入口虛擬地址是0x001,而B也是,但是它倆對(duì)應(yīng)的物理地址卻是不同的,操作系統(tǒng)采用這種內(nèi)存管理方法。是防止程序?qū)ξ锢淼刂穼憯?shù)據(jù)造成一些不可必要的問題,比如知道了A進(jìn)程的物理地址,那么向這個(gè)地址寫入數(shù)據(jù)就會(huì)造成A進(jìn)程出現(xiàn)問題,在虛擬內(nèi)存中運(yùn)行程序永遠(yuǎn)不知道自己處于內(nèi)存中那一段的物理地址上!現(xiàn)在操作系統(tǒng)運(yùn)行在保護(hù)模式下即便知道其他進(jìn)程的物理地址也不允許向其寫入!但是可以通過操作系統(tǒng)留下的后門函數(shù)獲取該進(jìn)程上的虛擬地址空間所有控制權(quán)限并寫入指定數(shù)據(jù)。虛擬內(nèi)存管理采用一種拆東墻補(bǔ)西墻的形式,所以虛擬內(nèi)存的內(nèi)存會(huì)比物理內(nèi)存要大許多。在進(jìn)入虛擬模式之前CPU以及Bootloader(BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行??梢猿跏蓟布O(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個(gè)合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境),操作系統(tǒng)內(nèi)核均運(yùn)行在實(shí)模式下,直接對(duì)物理地址進(jìn)行操作!虛擬內(nèi)存中也有分頁管理,這種管理方法是為了確保內(nèi)存中不會(huì)出現(xiàn)內(nèi)存碎片,當(dāng)操作系統(tǒng)內(nèi)核初始化完畢內(nèi)存中的分頁表后CPU的分頁標(biāo)志位會(huì)被設(shè)置,這個(gè)分頁標(biāo)志位是給MMU看的!

MMU

    是Memory Management Unit的縮寫,中文名是內(nèi)存管理單元,它是==中央處理器(CPU)中用來管理虛擬存儲(chǔ)器、物理存儲(chǔ)器的控制線路,同時(shí)也負(fù)責(zé)虛擬地址映射為物理地址,以及提供硬件機(jī)制的內(nèi)存訪問授權(quán),多用戶多進(jìn)程操作系統(tǒng)==。作用有兩點(diǎn),地址翻譯和內(nèi)存保護(hù)。==MMU將虛擬地址翻譯為物理地址。==

有關(guān)各種地址介紹的博文:

物理地址、虛擬地址、總線地址物理地址和總線地址區(qū)別

頁表(MMU的單元)

分頁管理:

    • 內(nèi)存分頁其實(shí)就是我們所說的4G空間,內(nèi)存的所有內(nèi)存被操作系統(tǒng)內(nèi)核以4G為每頁劃分開,當(dāng)我們程序運(yùn)行時(shí)會(huì)被加載到內(nèi)存中的4G空間里,其實(shí)說是有4G其實(shí)并沒有真正在的4G空間,4G空間中有一小部分被映射到了物理內(nèi)存中,或者被映射到了硬盤的文件上(fopen),或者沒有被映射,還有一部分在內(nèi)存當(dāng)中就會(huì)被劃分棧,堆,其中有大片大片的內(nèi)存是沒有被映射的,同樣物理內(nèi)存也是被分頁了用來與虛擬內(nèi)存產(chǎn)生映射關(guān)系。將虛擬地址映射為物理地址有一個(gè)算法(頁表)決定了將虛擬地址映射到物理地址的哪個(gè)位置,頁表是通過MMU(分頁內(nèi)存管理單元)來管理的,就是設(shè)計(jì)完頁表后通過MMU來執(zhí)行將虛擬地址映射為物理地址。其實(shí)真正情況下只有3G用戶空間,假如你的內(nèi)存是4G的那么其中有1G是給操作系統(tǒng)內(nèi)核使用的,所謂的4G空間只是操作系統(tǒng)基于虛擬內(nèi)存這種拆東墻補(bǔ)西墻的形式給你一種感覺每個(gè)進(jìn)程都有4G的可用空間一樣!這里來說一下拆東墻補(bǔ)西墻,當(dāng)我們程序被加載進(jìn)4G空間時(shí)其實(shí)根本用不了所謂的4G空間,其中有大片內(nèi)存被閑置,那么這個(gè)時(shí)候呢,其他程序被加載進(jìn)來時(shí)發(fā)現(xiàn)內(nèi)存不夠了,就把其他程序里的4G空間里閑置部分拿出來給這個(gè)進(jìn)程用,換之這個(gè)進(jìn)程內(nèi)存不夠時(shí)就會(huì)把其他進(jìn)程里閑置的空間拿過來給該進(jìn)程使用。銀行也是如此!當(dāng)我們要對(duì)物理地址做操作時(shí)比如if語句要根據(jù)CPU的狀態(tài)標(biāo)志寄存器來做不同的跳轉(zhuǎn),那么這個(gè)時(shí)候就要對(duì)CPU額狀態(tài)寄存器做操作了就必須知道它的物理地址,內(nèi)存中有一個(gè)電子元件叫MMU負(fù)責(zé)從操作系統(tǒng)已經(jīng)初始化好的內(nèi)存映射表里查詢與虛擬地址對(duì)應(yīng)的物理地址并轉(zhuǎn)換,比如mov

 

    0x4h8這個(gè)是虛擬地址,當(dāng)我們要對(duì)這個(gè)虛擬地址里寫數(shù)據(jù)時(shí)那么MMU會(huì)先判斷CPU的分頁狀態(tài)寄存器里的標(biāo)志狀態(tài)是否被設(shè)定,如果被設(shè)定那么MMU就會(huì)捕獲這個(gè)虛擬地址物理并在操作系統(tǒng)內(nèi)核初始化好的內(nèi)存映射表里查詢與之對(duì)應(yīng)的物理地址,并將其轉(zhuǎn)換成真正的實(shí)際物理地址,然后在對(duì)這個(gè)實(shí)際的物理地址給CPU,在由CPU去執(zhí)行對(duì)應(yīng)的命令,相反CPU往內(nèi)存里讀數(shù)據(jù)時(shí)比如A進(jìn)程要讀取內(nèi)存中某個(gè)虛擬地址的數(shù)據(jù),A進(jìn)程里的指令給的是虛擬地址,MMU首先會(huì)檢查CPU的分頁狀態(tài)寄存器標(biāo)志位是否被設(shè)置,如果被設(shè)置MMU會(huì)捕獲這個(gè)虛擬地址并將其轉(zhuǎn)換成相應(yīng)的物理地址然后提交給CPU,在由CPU到內(nèi)存中去取數(shù)據(jù)!

更詳細(xì)的地址問題看這里

BCM2835芯片手冊(cè)

下面截取樹莓派芯片手冊(cè)的一張圖:BCM2835是樹莓派3B CPU的型號(hào),是ARM-cotexA53架構(gòu),cpu Bus是地址總線,00000000~FFFFFFFF是CPU尋址的范圍(4G)。DMA是高速拷貝單元,CPU可以發(fā)動(dòng)DMA直接讓DMA進(jìn)行數(shù)據(jù)拷貝,直接內(nèi)存訪問單元。物理地址(PA)1G、虛擬地址(VA)4G若程序大于物理地址1G,是不是就跑不了了,不是的,它有個(gè)MMU的單元,把物理地址映射成虛擬地址,我們操作的代碼基本上都是在虛擬地址,它有一個(gè)映射頁表(上面提及到過)

通過芯片手冊(cè)了解樹莓派的GPIO:有54條通用I/O GPIO行,分為兩行,備用功能通常是外圍IO并且可以在每個(gè)銀行中出現(xiàn)一個(gè)外圍設(shè)備,以允許靈活地選擇IO電壓。

GPIO有41個(gè)寄存器,所有訪問都是32位的。

Description是寄存器的功能描述。GPFSEL0(寄存器名)GPIO Function Select 0(功能選擇:輸入或輸出);GPSET0 (寄存器名)GPIO Pin Output Set 0(將IO口置0);GPSET1(寄存器名)GPIO Pin Output Set 1(將IO口置1);GPCLR0(寄存器名)GPIO Pin Output Clear 0 (清0)下圖的地址是:總線地址(并不是真正的物理地址)

FSELn表示GPIOn,

下圖給出第九個(gè)引腳的功能選擇示例,對(duì)寄存器的29-27進(jìn)行配置,進(jìn)而設(shè)置相應(yīng)的功能。根據(jù)圖片下方的register 0表示0~9使用的是register 0這個(gè)寄存器。

輸出集寄存器用于設(shè)置GPIO管腳。SET{n}字段定義,分別對(duì)GPIO引腳進(jìn)行設(shè)置,將“0”寫入字段沒有作用。如果GPIO管腳為在輸入(默認(rèn)情況下)中使用,那么SET{n}字段中的值將被忽略。然而,如果引腳隨后被定義為輸出,那么位將被設(shè)置根據(jù)上次的設(shè)置/清除操作。分離集和明確功能取消對(duì)讀-修改-寫操作的需要。GPSETn寄存器為了使IO口設(shè)置為1,set4位設(shè)置第四個(gè)引腳,也就是寄存器的第四位。

輸出清除寄存器用于清除GPIO管腳。CLR{n}字段定義要清除各自的GPIO引腳,向字段寫入“0”沒有作用。如果的在輸入(默認(rèn)),然后在CLR{n}字段的值是忽略了。然而,如果引腳隨后被定義為輸出,那么位將被定義為輸出根據(jù)上次的設(shè)置/清除操作進(jìn)行設(shè)置。分隔集與清函數(shù)消除了讀-修改-寫操作的需要。GPCLRn是清零功能寄存器。

配置樹莓派的pin4引腳為輸出引腳:

功能選擇?輸出/輸入(GPIO?Function?Select?Registers)32位
14-12????001????=???GPIO?Pin4?is?an?output

只需要將GPFSL0這個(gè)寄存器的14~12位設(shè)置為001就可以了。只需要將0x6(對(duì)應(yīng)的2進(jìn)制是110)左移12位·然后取反再與上GPFSL0就可以將13、14這兩位配置為0,然后再將0x6(對(duì)應(yīng)2進(jìn)制110)左移12位,然后或上GPFSL0即可將12位置1。

    • 可使用copy_from_user()這個(gè)函數(shù)在驅(qū)動(dòng)代碼里面讀取用戶輸入的指令,使用copy_to_user()這個(gè)函數(shù)讓引腳反饋現(xiàn)在的狀態(tài),也就是讓用戶讀取到。

 

若想找樹莓派引腳點(diǎn)這里

樹莓派IO操控驅(qū)動(dòng)代碼:

ioremap、iounmap:

一. 一般我們的外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的,通常包括控制寄存器、狀態(tài)寄存器、數(shù)據(jù)寄存器三大類。外設(shè)的寄存器通常被連續(xù)編址,并且根據(jù)CPU的體系架構(gòu)不同CPU對(duì)IO端口的編制方式有兩種:

    IO映射方式(IO-mapped):比較典型的有X86處理器為外設(shè)專門實(shí)現(xiàn)了一個(gè)單獨(dú)的地址空間,稱為“IO端口空間”或者“IO地址空間”,此時(shí)CPU可以通過專門的指令(比如X86的IN和OUT)來訪問這個(gè)“IO端口空間”。內(nèi)存映射方式(memory-mapped):RISC指令系統(tǒng)的CPU一般只實(shí)現(xiàn)一個(gè)物理地址空間,外設(shè)IO端口成為內(nèi)存的一部分。此時(shí)CPU可以訪問外設(shè)的IO端口,就像訪問自己的內(nèi)存一樣方便,不必再設(shè)置專門的指令來訪問。在驅(qū)動(dòng)開發(fā)過程中一般使用內(nèi)存映射方式。

二、 在驅(qū)動(dòng)開發(fā)過程中,一般來說外設(shè)的IO內(nèi)存資源的物理地址是已知的,由硬件的設(shè)計(jì)決定。但是CPU不會(huì)為這些已知的外設(shè)IO內(nèi)存資源預(yù)先指定虛擬地址的值,所以驅(qū)動(dòng)程序不可以直接就通過外設(shè)的物理地址訪問到IO內(nèi)存,而必須要將其映射到虛擬地址空間(通過頁表),然后才能根據(jù)內(nèi)核映射過后的虛擬地址來通過內(nèi)存指令訪問這些IO內(nèi)存,并對(duì)其進(jìn)行操作。

三、 在Linux內(nèi)核的io.h頭文件中聲明了ioremap()函數(shù),用來將IO內(nèi)存資源映射到核心虛擬地址空間(3Gb~4GB)中,當(dāng)然不用了可以將其取消映射iounmap()。這兩個(gè)函數(shù)在mm/ioremap.c文件中:

開始映射:void*?ioremap(unsigned?long?phys_addr?,?unsigned?long?size?,?unsigned?long?flags)
//用map映射一個(gè)設(shè)備意味著使用戶空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或?qū)懭?,?shí)際上就是對(duì)設(shè)備的訪問。
第一個(gè)參數(shù)是映射的起始地址
第二個(gè)參數(shù)是映射的長(zhǎng)度
第二個(gè)參數(shù)怎么定???
====================
這個(gè)由你的硬件特性決定。
比如,你只是映射一個(gè)32位寄存器,那么長(zhǎng)度為4就足夠了。
(這里樹莓派IO口功能設(shè)置寄存器、IO口設(shè)置寄存器都是32位寄存器,所以分配四個(gè)字節(jié)就夠了)

比如:GPFSEL0=(volatile?unsigned?int?*)ioremap(0x3f200000,4);
???GPSET0?=(volatile?unsigned?int?*)ioremap(0x3f20001C,4);
??????GPCLR0?=(volatile?unsigned?int?*)ioremap(0x3f200028,4);
這三行是設(shè)置寄存器的地址,volatile的作用是作為指令關(guān)鍵字
確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值
ioremap函數(shù)將物理地址轉(zhuǎn)換為虛擬地址,IO口寄存器映射成普通內(nèi)存單元進(jìn)行訪問。
?
解除映射:void?iounmap(void*?addr)//取消ioremap所映射的IO地址
比如:
?????iounmap(GPFSEL0);
????????iounmap(GPSET0);
????????iounmap(GPCLR0);?//卸載驅(qū)動(dòng)時(shí)釋放地址映射

樹莓派IO口四的驅(qū)動(dòng)代碼:

#include?<linux/fs.h>????????????//file_operations聲明
#include?<linux/module.h>????//module_init??module_exit聲明
#include?<linux/init.h>??????//__init??__exit?宏定義聲明
#include?<linux/device.h>????????//class??devise聲明
#include?<linux/uaccess.h>???//copy_from_user?的頭文件
#include?<linux/types.h>?????//設(shè)備號(hào)??dev_t?類型聲明
#include?<asm/io.h>??????????//ioremap?iounmap的頭文件


static?struct?class?*pin4_class;
static?struct?device?*pin4_class_dev;

static?dev_t?devno;????????????????//設(shè)備號(hào)
static?int?major?=231;?????????????//主設(shè)備號(hào)
static?int?minor?=0;???????????????//次設(shè)備號(hào)
static?char?*module_name="pin4";???//模塊名

volatile?unsigned?int*?GPFSEL0?=?NULL;
volatile?unsigned?int*?GPSET0???=?NULL;
volatile?unsigned?int*?GPCLR0???=?NULL;
//這三行是設(shè)置寄存器的地址
//volatile的作用是作為指令關(guān)鍵字,確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值

//led_open函數(shù)
static?int?pin4_open(struct?inode?*inode,struct?file?*file)
{
????????printk("pin4_openn");??//內(nèi)核的打印函數(shù)和printf類似
???????
????????//配置pin4引腳為輸出引腳????????
????????*GPFSEL0?&=~(0x6?<<12);?//?把bit13?、bit14置為0??
????????//0x6是110??<<12左移12位?~取反?&按位與
????????*GPFSEL0?|=~(0x1?<<12);?//把12置為1???|按位或
????????
????????return?0;

}
//read函數(shù)
static?int?pin4_read(struct?file?*file,char?__user?*buf,size_t?count,loff_t?*ppos)
{
????????printk("pin4_readn");??//內(nèi)核的打印函數(shù)和printf類似

????????return?0;
}

//led_write函數(shù)
static?ssize_t?pin4_write(struct?file?*file,const?char?__user?*buf,size_t?count,?loff_t?*ppos)
{
????????int?usercmd;
????????printk("pin4_writen");??//內(nèi)核的打印函數(shù)和printf類似
????????
????????//獲取上層write函數(shù)的值????????????????
????????copy_from_user(&usercmd,buf,count);?//將應(yīng)用層用戶輸入的指令讀如usercmd里面
????????//根據(jù)值來操作io口,高電平或者低電平
????????if(usercmd?==?1){
????????????????printk("set?1n");
????????????????*GPSET0?|=?0x01?<<?4;
????????}
????????else?if(usercmd?==?0){
????????????????printk("set?0n");
????????????????*GPCLR0?|=?0x01?<<?4;
????????}
????????else{
????????????????printk("undon");
????????}
????????return?0;
}

static?struct?file_operations?pin4_fops?=?{

????????.owner?=?THIS_MODULE,
????????.open??=?pin4_open,
????????.write?=?pin4_write,
????????.read??=?pin4_read,
};

//static限定這個(gè)結(jié)構(gòu)體的作用,僅僅只在這個(gè)文件。
int?__init?pin4_drv_init(void)???//真實(shí)的驅(qū)動(dòng)入口
{
????????int?ret;
????????devno?=?MKDEV(major,minor);??//創(chuàng)建設(shè)備號(hào)
????????ret???=?register_chrdev(major,?module_name,&pin4_fops);??//注冊(cè)驅(qū)動(dòng)??告訴內(nèi)核,把這個(gè)驅(qū)動(dòng)加入到內(nèi)核驅(qū)動(dòng)的鏈表中
????????pin4_class=class_create(THIS_MODULE,"myfirstdemo");//讓代碼在dev下自動(dòng)>生成設(shè)備
????????pin4_class_dev?=device_create(pin4_class,NULL,devno,NULL,module_name);??//創(chuàng)建設(shè)備文件
????????
????????GPFSEL0=(volatile?unsigned?int?*)ioremap(0x3f200000,4);
????????GPSET0?=(volatile?unsigned?int?*)ioremap(0x3f20001C,4);
????????GPCLR0?=(volatile?unsigned?int?*)ioremap(0x3f200028,4);

????????printk("insmod?driver?pin4?successn");
????????return?0;
}

void?__exit?pin4_drv_exit(void)
{

????????iounmap(GPFSEL0);
????????iounmap(GPSET0);
????????iounmap(GPCLR0);?//卸載驅(qū)動(dòng)時(shí)釋放地址映射

????????device_destroy(pin4_class,devno);
????????class_destroy(pin4_class);
????????unregister_chrdev(major,?module_name);??//卸載驅(qū)動(dòng)
}
module_init(pin4_drv_init);??//入口,內(nèi)核加載驅(qū)動(dòng)的時(shí)候,這個(gè)宏會(huì)被調(diào)用,去調(diào)用pin4_drv_init這個(gè)函數(shù)
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL?v2");

1. 設(shè)置寄存器的地址

設(shè)置寄存器的地址,但是這樣寫是有問題的,我們上面講到了在內(nèi)核里代碼和上層代碼訪問的是虛擬地址(VA),而現(xiàn)在設(shè)置的是物理地址,**==必須把物理地址轉(zhuǎn)換成虛擬地址==**

//這三行是設(shè)置寄存器的地址
volatile?unsigned?int*?GPFSEL0?=?volatile?(unsigned?int?*)0x3f200000;
volatile?unsigned?int*?GPSET0??=?volatile?(unsigned?int?*)0x3f20001C;
volatile?unsigned?int*?GPCLR0??=?volatile?(unsigned?int?*)0x3f200028;
//volatile的作用是作為指令關(guān)鍵字,確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值

我們先把地址初始

volatile?unsigned?int*?GPFSEL0?=?NULL;
volatile?unsigned?int*?GPSET0???=?NULL;
volatile?unsigned?int*?GPCLR0???=?NULL;

在初始化int __init pin4_drv_init(void) //真實(shí)的驅(qū)動(dòng)入口里賦值。

//整數(shù)11
//0xb 11 00010001
即便是16進(jìn)制也是整數(shù),左邊是volatile unsigned int* GPFSEL0 右邊也強(qiáng)制轉(zhuǎn)換成(volatile unsigned int*)

volatile的作用是作為指令關(guān)鍵字,確保本條 ==指令不會(huì)因編譯器的優(yōu)化而省略==,==且要求每次直接讀值==因?yàn)樗堑刂肺蚁M菬o符號(hào)的unsigned

我們?cè)诰帉戲?qū)動(dòng)程序的時(shí)候,IO空間的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址應(yīng)該是從0x3f200000開始的然后在這個(gè)基礎(chǔ)上進(jìn)行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。用到了一個(gè)函數(shù)ioremap

//物理地址轉(zhuǎn)換成虛擬地址,io口寄存器映射成普通內(nèi)存單元進(jìn)行訪問
?GPFSEL0=(volatile?unsigned?int?*)ioremap(0x3f200000,4);
?GPSET0?=(volatile?unsigned?int?*)ioremap(0x3f20001C,4);
?GPCLR0?=(volatile?unsigned?int?*)ioremap(0x3f200028,4);???//4是4個(gè)字節(jié)

2. 配置pin4引腳為輸出引腳

配置pin4引腳為輸出引腳 ? ? ?bit 12-14 ?配置成001

31?30?······14?13?12?11?10?9?8?7?6?5?4?3?2?1?
0??0??······0??0??1??0??0??0?0?0?0?0?0?0?0?0?
?//配置pin4引腳為輸出引腳??????bit?12-14??配置成001??
??*GPFSEL0?&=~(0x6?<<12);?//?把bit13?、bit14置為0??
?//0x6是110??<<12左移12位?~取反?&按位與
??*GPFSEL0?|=~(0x1?<<12);?//把12置為1???|按位或

忘記按位與 按位或 點(diǎn)這里

3. 獲取上層write函數(shù)的值,根據(jù)值來操作io口,高電平或者低電平

copy_form_user(char *buf , user_buf , count)獲取上層write函數(shù)的值

int?usercmd;
copy_from_user(&usercmd,buf,count);?//將應(yīng)用層用戶輸入的指令讀如usercmd里面
???????
?//根據(jù)值來操作io口,高電平或者低電平
?printk("get?valuen");
????????if(usercmd?==?1){
????????????????printk("set?1n");????????//置1
????????????????*GPSET0?|=?0x01?<<?4;??????//用?|?或操作??目的是不影響其他位
????????????????//寫1?是讓寄存器????開啟置1??讓bit4為高電平
????????}
????????else?if(usercmd?==?0){???????????
????????????????printk("set?0n");????????//清0
????????????????*GPCLR0?|=?0x01?<<?4;??????//用?|?或操作??目的是不影響其他位
????????????????//寫1?是讓清0寄存器?開啟置0?讓bit4為低電平
????????}
????????else{
????????????????printk("undon");??//提示不支持該指令
????????}

4. 解除映射

解除映射:void iounmap(void* addr);//取消ioremap所映射的IO地址

void?__exit?pin4_drv_exit(void)
{
????????iounmap(GPFSEL0);???//解除映射?GPFSEL0
????????iounmap(GPSET0);????//解除映射?GPSET0
????????iounmap(GPCLR0);???//解除映射?GPCLR0

????????device_destroy(pin4_class,devno);//先銷毀設(shè)備
????????class_destroy(pin4_class);//再銷毀類
????????unregister_chrdev(major,?module_name);??//卸載驅(qū)動(dòng)

}

上層測(cè)試代碼:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include?<unistd.h>
#include<stdlib.h>
#include<stdio.h>

int?main()
{
???int?fd;
???int?cmd;
???int?data;

???fd?=?open("/dev/pin4",O_RDWR);
???if(fd<0){
???????????printf("open?failedn");
???}else{
????printf("open?successn");
???}
???
???printf("input?commnd:1/0?n?1:set?pin4?high?n?0?:set?pin4?lown");
???scanf("%d",&cmd);

???printf("cmd?=?%dn",cmd);
???fd?=?write(fd,?&cmd,4);?//cmd類型是int??所以?寫4
}

驅(qū)動(dòng)卸載

在裝完驅(qū)動(dòng)后可以使用指令:sudo rmmod +驅(qū)動(dòng)名(不需要寫ko)將驅(qū)動(dòng)卸載。

IO口驅(qū)動(dòng)代碼編譯

    1. 首先在系統(tǒng)目錄

/SYSTEM/linux-rpi-4.14.y

    1. 下使用指令:

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

    1. 對(duì)驅(qū)動(dòng)模塊進(jìn)行編譯生成

.ko

    1. 文件.然后將編譯后的驅(qū)動(dòng)發(fā)送到樹莓派:

scp ./drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi

    1. ,然后再將上層代碼進(jìn)行編譯:

arm-linux-gnueabihf-gcc pin4test.c -o realtest

    1. ,然后再將測(cè)試代碼傳到樹莓派:

scp realtest pi@192.168.43.136:/home/pi/

    1. 然后在樹莓派上面使用指令:

insmod pin4drive.ko

    1. 進(jìn)行加載驅(qū)動(dòng)(然后

lsmod

    1. 即可查看到該驅(qū)動(dòng)),然后使用指令:

sudo chmod 666 /dev/pin4

    1. 給予pin4這個(gè)設(shè)備可訪問權(quán)限,還可以在虛擬機(jī)上面使用mk5sum查看驅(qū)動(dòng)文件的值,并在樹莓派上面使用該指令進(jìn)行查看該驅(qū)動(dòng)文件的值,看是否一致。

dmesg

    1. 查看內(nèi)核打印的信息,如下圖所示:

然后運(yùn)行測(cè)試代碼,在新建一個(gè)窗口,使用指令gpio readall可以看到BCM下面的4號(hào)引腳模式是輸出模式,電平是低電平或高電平(根據(jù)輸入的上層代碼而定,輸入0就是低電平,輸入1就是高電平),這里我輸入的是0,如下圖所示:

有關(guān)驅(qū)動(dòng)代碼里面GPIO口地址的問題:

有關(guān)驅(qū)動(dòng)代碼里面GPIO口地址的問題:

    • 7Ennnnn意思是7E00000到7EFFFFFF,F(xiàn)2000000是3F000000映射的虛擬地址,然后7E00000和F200000對(duì)應(yīng),芯片手冊(cè)里面使用的是和虛擬地址F200000有著對(duì)應(yīng)關(guān)系的地址——7E00000,芯片手冊(cè)上面地址偏移多少物理地址就偏移多少根據(jù)上方圖片描述,外設(shè)的物理地址范圍是m 0x3F000000 to

 

    • 0x3FFFFFFF,所以你看到的7E200000對(duì)應(yīng)的實(shí)際物理地址應(yīng)該是0x3F000000 + (7E200000-7E000000)GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); GPSET0 ? =(volatile unsigned int *)ioremap(0x3f20001C,4); GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);0x3f200000,0x3f20001C,0x3f200028是物理地址,樹莓派的外設(shè)空間的起始地址是0x3f000000,根據(jù)芯片手冊(cè)可知對(duì)應(yīng)寄存器的偏移量為,比如GPFSEL0寄存器的實(shí)際地址是0x3f200000=0x3F000000 ?+ (7E200000-7E000000)然后通過數(shù)據(jù)手冊(cè)可以看到,樹莓派相關(guān)寄存器的總線地址(和映射的虛擬地址有某種對(duì)應(yīng)關(guān)系的地址)進(jìn)而可得知偏移量,如下圖所示:

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

公眾號(hào)『一口Linux』號(hào)主彭老師,擁有15年嵌入式開發(fā)經(jīng)驗(yàn)和培訓(xùn)經(jīng)驗(yàn)。曾任職ZTE,某研究所,華清遠(yuǎn)見教學(xué)總監(jiān)。擁有多篇網(wǎng)絡(luò)協(xié)議相關(guān)專利和軟件著作。精通計(jì)算機(jī)網(wǎng)絡(luò)、Linux系統(tǒng)編程、ARM、Linux驅(qū)動(dòng)、龍芯、物聯(lián)網(wǎng)。原創(chuàng)內(nèi)容基本從實(shí)際項(xiàng)目出發(fā),保持原理+實(shí)踐風(fēng)格,適合Linux驅(qū)動(dòng)新手入門和技術(shù)進(jìn)階。