微機(jī)總線地址
地址總線:
- 百度百科解釋?zhuān)?/strong> 地址總線 (Address Bus;又稱(chēng):位址總線) 屬于一種電腦總線 (一部份),是由CPU 或有DMA 能力的單元,用來(lái)溝通這些單元想要存?。ㄗx取/寫(xiě)入)電腦內(nèi)存元件/地方的實(shí)體位址。
- 地址總線 = cpu能夠訪問(wèn)內(nèi)存的范圍:用一個(gè)現(xiàn)象來(lái)解釋地址總線:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可是系統(tǒng)只識(shí)別了3.8G,裝了64位,才能識(shí)別到8G。32位能表示/訪問(wèn) 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是通過(guò)地址總線來(lái)指定存儲(chǔ)單元的。
-
**==地址總線決定了cpu所能訪問(wèn)的最大內(nèi)存空間的大小==。eg: 10根地址線能訪問(wèn)的最大的內(nèi)存為
1024
**(2的10次方)位二進(jìn)制數(shù)據(jù)(1B) - 地址總線是地址線數(shù)量之和。若CPU的地址總線寬度是32位,那么CPU的尋址范圍是4G(2的32次方位),所以最多支持4G內(nèi)存.
- 比如上面我們說(shuō)的那個(gè)現(xiàn)象:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可系統(tǒng)只是別了3.8G,裝了64位才能識(shí)別到8G。裝了32位的操作系統(tǒng)CPU的訪問(wèn)范圍是2^32bit,就是4194304kbit,就是4096Mbit,等于4G。樹(shù)莓派也是32位 ,一個(gè)G的內(nèi)存,但它只能訪問(wèn)949M剩下的挪作他用。
數(shù)據(jù)總線:
- CPU通過(guò)地址總線尋址,然后通過(guò)數(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ù)之一(通常,我們說(shuō)“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根地址總線,能訪問(wèn)1M(2的20次方),32位機(jī)有32根數(shù)據(jù)總線,32根地址總線,能訪問(wèn)4G(2的32次方),64位機(jī)確實(shí)有64根數(shù)據(jù)總線。
物理地址(PA)
-
百度百科解釋?zhuān)?/strong>網(wǎng)卡物理地址存儲(chǔ)器中存儲(chǔ)單元對(duì)應(yīng)實(shí)際地址稱(chēng)物理地址,與邏輯地址相對(duì)應(yīng)。網(wǎng)卡的物理地址通常是由網(wǎng)卡生產(chǎn)廠家寫(xiě)入網(wǎng)卡的EPROM(一種閃存芯片,通??梢酝ㄟ^(guò)程序擦寫(xiě)),它存儲(chǔ)的是傳輸數(shù)據(jù)時(shí)真正賴(lài)以標(biāo)識(shí)發(fā)出數(shù)據(jù)的電腦和接收數(shù)據(jù)的主機(jī)的地址。
-
這里說(shuō)的 物理地址是內(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ù)模式下,這樣程序訪問(wèn)存儲(chǔ)器所使用的==邏輯地址
(基于算法的地址[軟件層面的地址:假地址])
稱(chēng)為虛擬地址==,與實(shí)地址模式下的分段地址類(lèi)似,虛擬地址也可以寫(xiě)為“段:偏移量”的形式,這里的段是指段選擇器。而linux沒(méi)有各種保護(hù)模式,本來(lái)用的就是虛擬地址。 - 虛擬地址是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(niǎo)也是,但是它倆對(duì)應(yīng)的物理地址卻是不同的,操作系統(tǒng)采用這種內(nèi)存管理方法。
- 是防止程序?qū)ξ锢淼刂穼?xiě)數(shù)據(jù)造成一些不可必要的問(wèn)題,比如知道了A進(jìn)程的物理地址,那么向這個(gè)地址寫(xiě)入數(shù)據(jù)就會(huì)造成A進(jìn)程出現(xiàn)問(wèn)題,在虛擬內(nèi)存中運(yùn)行程序永遠(yuǎn)不知道自己處于內(nèi)存中那一段的物理地址上!現(xiàn)在操作系統(tǒng)運(yùn)行在保護(hù)模式下即便知道其他進(jìn)程的物理地址也不允許向其寫(xiě)入!但是可以通過(guò)操作系統(tǒng)留下的后門(mén)函數(shù)獲取該進(jìn)程上的虛擬地址空間所有控制權(quán)限并寫(xiě)入指定數(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)存中也有分頁(yè)管理,這種管理方法是為了確保內(nèi)存中不會(huì)出現(xiàn)內(nèi)存碎片,當(dāng)操作系統(tǒng)內(nèi)核初始化完畢內(nèi)存中的分頁(yè)表后CPU的分頁(yè)標(biāo)志位會(huì)被設(shè)置,這個(gè)分頁(yè)標(biāo)志位是給MMU看的!
-
MMU
是Memory Management Unit的縮寫(xiě),中文名是內(nèi)存管理單元,它是==中央處理器(CPU)中用來(lái)管理虛擬存儲(chǔ)器、物理存儲(chǔ)器的控制線路,同時(shí)也負(fù)責(zé)虛擬地址映射為物理地址,以及提供硬件機(jī)制的內(nèi)存訪問(wèn)授權(quán),多用戶(hù)多進(jìn)程操作系統(tǒng)==。作用有兩點(diǎn),地址翻譯和內(nèi)存保護(hù)。==MMU將虛擬地址翻譯為物理地址。==
有關(guān)各種地址介紹的博文:
物理地址、虛擬地址、總線地址物理地址和總線地址區(qū)別
頁(yè)表(MMU的單元)
分頁(yè)管理:
- 內(nèi)存分頁(yè)其實(shí)就是我們所說(shuō)的4G空間,內(nèi)存的所有內(nèi)存被操作系統(tǒng)內(nèi)核以4G為每頁(yè)劃分開(kāi),當(dāng)我們程序運(yùn)行時(shí)會(huì)被加載到內(nèi)存中的4G空間里,其實(shí)說(shuō)是有4G其實(shí)并沒(méi)有真正在的4G空間,4G空間中有一小部分被映射到了物理內(nèi)存中,或者被映射到了硬盤(pán)的文件上(fopen),或者沒(méi)有被映射,還有一部分在內(nèi)存當(dāng)中就會(huì)被劃分棧,堆,其中有大片大片的內(nèi)存是沒(méi)有被映射的,同樣物理內(nèi)存也是被分頁(yè)了用來(lái)與虛擬內(nèi)存產(chǎn)生映射關(guān)系。將虛擬地址映射為物理地址有一個(gè)算法(頁(yè)表)決定了將虛擬地址映射到物理地址的哪個(gè)位置,頁(yè)表是通過(guò)MMU(分頁(yè)內(nèi)存管理單元)來(lái)管理的,就是設(shè)計(jì)完頁(yè)表后通過(guò)MMU來(lái)執(zhí)行將虛擬地址映射為物理地址。
- 其實(shí)真正情況下只有3G用戶(hù)空間,假如你的內(nèi)存是4G的那么其中有1G是給操作系統(tǒng)內(nèi)核使用的,所謂的4G空間只是操作系統(tǒng)基于虛擬內(nèi)存這種拆東墻補(bǔ)西墻的形式給你一種感覺(jué)每個(gè)進(jìn)程都有4G的可用空間一樣!這里來(lái)說(shuō)一下拆東墻補(bǔ)西墻,當(dāng)我們程序被加載進(jìn)4G空間時(shí)其實(shí)根本用不了所謂的4G空間,其中有大片內(nèi)存被閑置,那么這個(gè)時(shí)候呢,其他程序被加載進(jìn)來(lái)時(shí)發(fā)現(xiàn)內(nèi)存不夠了,就把其他程序里的4G空間里閑置部分拿出來(lái)給這個(gè)進(jìn)程用,換之這個(gè)進(jìn)程內(nèi)存不夠時(shí)就會(huì)把其他進(jìn)程里閑置的空間拿過(guò)來(lái)給該進(jìn)程使用。銀行也是如此!
- 當(dāng)我們要對(duì)物理地址做操作時(shí)比如if語(yǔ)句要根據(jù)CPU的狀態(tài)標(biāo)志寄存器來(lái)做不同的跳轉(zhuǎn),那么這個(gè)時(shí)候就要對(duì)CPU額狀態(tài)寄存器做操作了就必須知道它的物理地址,內(nèi)存中有一個(gè)電子元件叫MMU負(fù)責(zé)從操作系統(tǒng)已經(jīng)初始化好的內(nèi)存映射表里查詢(xún)與虛擬地址對(duì)應(yīng)的物理地址并轉(zhuǎn)換,比如mov0x4h8這個(gè)是虛擬地址,當(dāng)我們要對(duì)這個(gè)虛擬地址里寫(xiě)數(shù)據(jù)時(shí)那么MMU會(huì)先判斷CPU的分頁(yè)狀態(tài)寄存器里的標(biāo)志狀態(tài)是否被設(shè)定,如果被設(shè)定那么MMU就會(huì)捕獲這個(gè)虛擬地址物理并在操作系統(tǒng)內(nèi)核初始化好的內(nèi)存映射表里查詢(xún)與之對(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的分頁(yè)狀態(tài)寄存器標(biāo)志位是否被設(shè)置,如果被設(shè)置MMU會(huì)捕獲這個(gè)虛擬地址并將其轉(zhuǎn)換成相應(yīng)的物理地址然后提交給CPU,在由CPU到內(nèi)存中去取數(shù)據(jù)!
更詳細(xì)的地址問(wèn)題看這里
BCM2835芯片手冊(cè)
下面截取樹(shù)莓派芯片手冊(cè)的一張圖:BCM2835是樹(shù)莓派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)存訪問(wèn)單元。物理地址(PA)1G、虛擬地址(VA)4G若程序大于物理地址1G,是不是就跑不了了,不是的,它有個(gè)MMU的單元,把物理地址映射成虛擬地址,我們操作的代碼基本上都是在虛擬地址,它有一個(gè)映射頁(yè)表(上面提及到過(guò))
-
通過(guò)芯片手冊(cè)了解樹(shù)莓派的GPIO:有54條通用I/O GPIO行,分為兩行,備用功能通常是外圍IO并且可以在每個(gè)銀行中出現(xiàn)一個(gè)外圍設(shè)備,以允許靈活地選擇IO電壓。
-
GPIO有41個(gè)寄存器,所有訪問(wèn)都是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”寫(xiě)入字段沒(méi)有作用。如果GPIO管腳為在輸入(默認(rèn)情況下)中使用,那么SET{n}字段中的值將被忽略。然而,如果引腳隨后被定義為輸出,那么位將被設(shè)置根據(jù)上次的設(shè)置/清除操作。分離集和明確功能取消對(duì)讀-修改-寫(xiě)操作的需要。GPSETn寄存器為了使IO口設(shè)置為1,set4位設(shè)置第四個(gè)引腳,也就是寄存器的第四位。
-
輸出清除寄存器用于清除GPIO管腳。CLR{n}字段定義要清除各自的GPIO引腳,向字段寫(xiě)入“0”沒(méi)有作用。如果的在輸入(默認(rèn)),然后在CLR{n}字段的值是忽略了。然而,如果引腳隨后被定義為輸出,那么位將被定義為輸出根據(jù)上次的設(shè)置/清除操作進(jìn)行設(shè)置。分隔集與清函數(shù)消除了讀-修改-寫(xiě)操作的需要。GPCLRn是清零功能寄存器。
配置樹(shù)莓派的pin4引腳為輸出引腳:
功能選擇輸出/輸入(GPIOFunctionSelectRegisters)32位
14-12001=GPIOPin4isanoutput
只需要將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)代碼里面讀取用戶(hù)輸入的指令,使用copy_to_user()這個(gè)函數(shù)讓引腳反饋現(xiàn)在的狀態(tài),也就是讓用戶(hù)讀取到。
若想找樹(shù)莓派引腳點(diǎn)這里
樹(shù)莓派IO操控驅(qū)動(dòng)代碼:
ioremap、iounmap:
一. 一般我們的外設(shè)都是通過(guò)讀寫(xiě)設(shè)備上的寄存器來(lái)進(jìn)行的,通常包括控制寄存器、狀態(tài)寄存器、數(shù)據(jù)寄存器三大類(lèi)。外設(shè)的寄存器通常被連續(xù)編址,并且根據(jù)CPU的體系架構(gòu)不同CPU對(duì)IO端口的編制方式有兩種:
- IO映射方式(IO-mapped):比較典型的有X86處理器為外設(shè)專(zhuān)門(mén)實(shí)現(xiàn)了一個(gè)單獨(dú)的地址空間,稱(chēng)為“IO端口空間”或者“IO地址空間”,此時(shí)CPU可以通過(guò)專(zhuān)門(mén)的指令(比如X86的IN和OUT)來(lái)訪問(wèn)這個(gè)“IO端口空間”。
- 內(nèi)存映射方式(memory-mapped):RISC指令系統(tǒng)的CPU一般只實(shí)現(xiàn)一個(gè)物理地址空間,外設(shè)IO端口成為內(nèi)存的一部分。此時(shí)CPU可以訪問(wèn)外設(shè)的IO端口,就像訪問(wèn)自己的內(nèi)存一樣方便,不必再設(shè)置專(zhuān)門(mén)的指令來(lái)訪問(wèn)。在驅(qū)動(dòng)開(kāi)發(fā)過(guò)程中一般使用內(nèi)存映射方式。
二、 在驅(qū)動(dòng)開(kāi)發(fā)過(guò)程中,一般來(lái)說(shuō)外設(shè)的IO內(nèi)存資源的物理地址是已知的,由硬件的設(shè)計(jì)決定。但是CPU不會(huì)為這些已知的外設(shè)IO內(nèi)存資源預(yù)先指定虛擬地址的值,所以驅(qū)動(dòng)程序不可以直接就通過(guò)外設(shè)的物理地址訪問(wèn)到IO內(nèi)存,而必須要將其映射到虛擬地址空間(通過(guò)頁(yè)表),然后才能根據(jù)內(nèi)核映射過(guò)后的虛擬地址來(lái)通過(guò)內(nèi)存指令訪問(wèn)這些IO內(nèi)存,并對(duì)其進(jìn)行操作。
三、 在Linux內(nèi)核的io.h頭文件中聲明了ioremap()函數(shù),用來(lái)將IO內(nèi)存資源映射到核心虛擬地址空間(3Gb~4GB)中,當(dāng)然不用了可以將其取消映射iounmap()。這兩個(gè)函數(shù)在mm/ioremap.c文件中:
開(kāi)始映射:void*ioremap(unsignedlongphys_addr,unsignedlongsize,unsignedlongflags)
//用map映射一個(gè)設(shè)備意味著使用戶(hù)空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或?qū)懭耄瑢?shí)際上就是對(duì)設(shè)備的訪問(wèn)。
第一個(gè)參數(shù)是映射的起始地址
第二個(gè)參數(shù)是映射的長(zhǎng)度
第二個(gè)參數(shù)怎么定啊?
====================
這個(gè)由你的硬件特性決定。
比如,你只是映射一個(gè)32位寄存器,那么長(zhǎng)度為4就足夠了。
(這里樹(shù)莓派IO口功能設(shè)置寄存器、IO口設(shè)置寄存器都是32位寄存器,所以分配四個(gè)字節(jié)就夠了)
比如:GPFSEL0=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);
這三行是設(shè)置寄存器的地址,volatile的作用是作為指令關(guān)鍵字
確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值
ioremap函數(shù)將物理地址轉(zhuǎn)換為虛擬地址,IO口寄存器映射成普通內(nèi)存單元進(jìn)行訪問(wèn)。
解除映射:voidiounmap(void*addr)//取消ioremap所映射的IO地址
比如:
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);//卸載驅(qū)動(dòng)時(shí)釋放地址映射
樹(shù)莓派IO口四的驅(qū)動(dòng)代碼:
#include//file_operations聲明
#include//module_initmodule_exit聲明
#include//__init__exit宏定義聲明
#include//classdevise聲明
#include//copy_from_user的頭文件
#include//設(shè)備號(hào)dev_t類(lèi)型聲明
#include//ioremapiounmap的頭文件
staticstructclass*pin4_class;
staticstructdevice*pin4_class_dev;
staticdev_tdevno;//設(shè)備號(hào)
staticintmajor=231;//主設(shè)備號(hào)
staticintminor=0;//次設(shè)備號(hào)
staticchar*module_name="pin4";//模塊名
volatileunsignedint*GPFSEL0=NULL;
volatileunsignedint*GPSET0=NULL;
volatileunsignedint*GPCLR0=NULL;
//這三行是設(shè)置寄存器的地址
//volatile的作用是作為指令關(guān)鍵字,確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值
//led_open函數(shù)
staticintpin4_open(structinode*inode,structfile*file)
{
printk("pin4_open
");//內(nèi)核的打印函數(shù)和printf類(lèi)似
//配置pin4引腳為輸出引腳
*GPFSEL0&=~(0x6<<12);//把bit13、bit14置為0
//0x6是110<<12左移12位?~取反?&按位與
*GPFSEL0|=~(0x1<<12);//把12置為1|按位或
return0;
}
//read函數(shù)
staticintpin4_read(structfile*file,char__user*buf,size_tcount,loff_t*ppos)
{
printk("pin4_read
");//內(nèi)核的打印函數(shù)和printf類(lèi)似
return0;
}
//led_write函數(shù)
staticssize_tpin4_write(structfile*file,constchar__user*buf,size_tcount,loff_t*ppos)
{
intusercmd;
printk("pin4_write
");//內(nèi)核的打印函數(shù)和printf類(lèi)似
//獲取上層write函數(shù)的值
copy_from_user(&usercmd,buf,count);//將應(yīng)用層用戶(hù)輸入的指令讀如usercmd里面
//根據(jù)值來(lái)操作io口,高電平或者低電平
if(usercmd==1){
printk("set1
");
*GPSET0|=0x01<4;
}
elseif(usercmd==0){
printk("set0
");
*GPCLR0|=0x01<4;
}
else{
printk("undo
");
}
return0;
}
staticstructfile_operationspin4_fops={
.owner=THIS_MODULE,
.open=pin4_open,
.write=pin4_write,
.read=pin4_read,
};
//static限定這個(gè)結(jié)構(gòu)體的作用,僅僅只在這個(gè)文件。
int__initpin4_drv_init(void)//真實(shí)的驅(qū)動(dòng)入口
{
intret;
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=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);
printk("insmoddriverpin4success
");
return0;
}
void__exitpin4_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("GPLv2");
1. 設(shè)置寄存器的地址
設(shè)置寄存器的地址,但是這樣寫(xiě)是有問(wèn)題的,我們上面講到了在內(nèi)核里代碼和上層代碼訪問(wèn)的是虛擬地址(VA),而現(xiàn)在設(shè)置的是物理地址,**==必須把物理地址轉(zhuǎn)換成虛擬地址==**
//這三行是設(shè)置寄存器的地址
volatileunsignedint*GPFSEL0=volatile(unsignedint*)0x3f200000;
volatileunsignedint*GPSET0=volatile(unsignedint*)0x3f20001C;
volatileunsignedint*GPCLR0=volatile(unsignedint*)0x3f200028;
//volatile的作用是作為指令關(guān)鍵字,確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值
我們先把地址初始
volatileunsignedint*GPFSEL0=NULL;
volatileunsignedint*GPSET0=NULL;
volatileunsignedint*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(wú)符號(hào)的unsigned
我們?cè)诰帉?xiě)驅(qū)動(dòng)程序的時(shí)候,IO空間的起始地址是0x3f000000
,加上GPIO的偏移量0x2000000
,所以GPIO的物理地址應(yīng)該是從0x3f200000
開(kāi)始的然后在這個(gè)基礎(chǔ)上進(jìn)行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。用到了一個(gè)函數(shù)ioremap
//物理地址轉(zhuǎn)換成虛擬地址,io口寄存器映射成普通內(nèi)存單元進(jìn)行訪問(wèn)
GPFSEL0=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);//4是4個(gè)字節(jié)
2. 配置pin4引腳為輸出引腳
配置pin4引腳為輸出引腳 bit 12-14 配置成001
3130······1413121110987654321
00······00100000000000
//配置pin4引腳為輸出引腳bit12-14配置成001
*GPFSEL0&=~(0x6<<12);//把bit13、bit14置為0
//0x6是110<<12左移12位?~取反?&按位與
*GPFSEL0|=~(0x1<<12);//把12置為1|按位或
忘記按位與 按位或 點(diǎn)這里
3. 獲取上層write函數(shù)的值,根據(jù)值來(lái)操作io口,高電平或者低電平
用copy_form_user(char *buf , user_buf , count)
獲取上層write函數(shù)的值
intusercmd;
copy_from_user(&usercmd,buf,count);//將應(yīng)用層用戶(hù)輸入的指令讀如usercmd里面
//根據(jù)值來(lái)操作io口,高電平或者低電平
printk("getvalue
");
if(usercmd==1){
printk("set1
");//置1
*GPSET0|=0x01<4;//用|或操作目的是不影響其他位
//寫(xiě)1是讓寄存器開(kāi)啟置1讓bit4為高電平
}
elseif(usercmd==0){
printk("set0
");//清0
*GPCLR0|=0x01<4;//用|或操作目的是不影響其他位
//寫(xiě)1是讓清0寄存器開(kāi)啟置0讓bit4為低電平
}
else{
printk("undo
");//提示不支持該指令
}
4. 解除映射
解除映射:void iounmap(void* addr);
//取消ioremap所映射的IO地址
void__exitpin4_drv_exit(void)
{
iounmap(GPFSEL0);//解除映射GPFSEL0
iounmap(GPSET0);//解除映射GPSET0
iounmap(GPCLR0);//解除映射GPCLR0
device_destroy(pin4_class,devno);//先銷(xiāo)毀設(shè)備
class_destroy(pin4_class);//再銷(xiāo)毀類(lèi)
unregister_chrdev(major,module_name);//卸載驅(qū)動(dòng)
}
上層測(cè)試代碼:
#include
#include
#include
#include
#include
#include
intmain()
{
intfd;
intcmd;
intdata;
fd=open("/dev/pin4",O_RDWR);
if(fd<0){
printf("openfailed
");
}else{
printf("opensuccess
");
}
printf("inputcommnd:1/0
1:setpin4high
0:setpin4low
");
scanf("%d",&cmd);
printf("cmd=%d
",cmd);
fd=write(fd,&cmd,4);//cmd類(lèi)型是int所以寫(xiě)4
}
驅(qū)動(dòng)卸載
在裝完驅(qū)動(dòng)后可以使用指令:sudo rmmod +驅(qū)動(dòng)名
(不需要寫(xiě)ko)將驅(qū)動(dòng)卸載。
IO口驅(qū)動(dòng)代碼編譯
-
首先在系統(tǒng)目錄
/SYSTEM/linux-rpi-4.14.y
下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
對(duì)驅(qū)動(dòng)模塊進(jìn)行編譯生成.ko
文件. -
然后將編譯后的驅(qū)動(dòng)發(fā)送到樹(shù)莓派:
scp ./drivers/char/pin4driver.ko [email protected]:/home/pi
,然后再將上層代碼進(jìn)行編譯:arm-linux-gnueabihf-gcc pin4test.c -o realtest
,然后再將測(cè)試代碼傳到樹(shù)莓派:scp realtest [email protected]:/home/pi/
-
然后在樹(shù)莓派上面使用指令:
insmod pin4drive.ko
進(jìn)行加載驅(qū)動(dòng)(然后lsmod
即可查看到該驅(qū)動(dòng)), -
然后使用指令:
sudo chmod 666 /dev/pin4
給予pin4這個(gè)設(shè)備可訪問(wèn)權(quán)限,還可以在虛擬機(jī)上面使用mk5sum查看驅(qū)動(dòng)文件的值,并在樹(shù)莓派上面使用該指令進(jìn)行查看該驅(qū)動(dòng)文件的值,看是否一致。 -
dmesg
查看內(nèi)核打印的信息,如下圖所示: -
然后運(yùn)行測(cè)試代碼,在新建一個(gè)窗口,使用指令
gpio readall
可以看到BCM
下面的4
號(hào)引腳模式是輸出模式,電平是低電平或高電平(根據(jù)輸入的上層代碼而定,輸入0就是低電平,輸入1就是高電平),這里我輸入的是0,如下圖所示:
有關(guān)驅(qū)動(dòng)代碼里面GPIO口地址的問(wèn)題:
有關(guān)驅(qū)動(dòng)代碼里面GPIO口地址的問(wèn)題:
- 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 to0x3FFFFFFF,所以你看到的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ù)莓派的外設(shè)空間的起始地址是0x3f000000,根據(jù)芯片手冊(cè)可知對(duì)應(yīng)寄存器的偏移量為,比如GPFSEL0寄存器的實(shí)際地址是0x3f200000=0x3F000000 + (7E200000-7E000000)
- 然后通過(guò)數(shù)據(jù)手冊(cè)可以看到,樹(shù)莓派相關(guān)寄存器的總線地址(和映射的虛擬地址有某種對(duì)應(yīng)關(guān)系的地址)進(jìn)而可得知偏移量,如下圖所示:
原文標(biāo)題:樹(shù)莓派高級(jí)開(kāi)發(fā)——“IO口驅(qū)動(dòng)代碼的編寫(xiě)“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊(cè)知識(shí)
文章出處:【微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
數(shù)據(jù)總線
+關(guān)注
關(guān)注
2文章
59瀏覽量
17633 -
樹(shù)莓派
+關(guān)注
關(guān)注
117文章
1710瀏覽量
105899 -
驅(qū)動(dòng)代碼
+關(guān)注
關(guān)注
2文章
14瀏覽量
7655 -
地址總線
+關(guān)注
關(guān)注
1文章
10瀏覽量
3904
原文標(biāo)題:樹(shù)莓派高級(jí)開(kāi)發(fā)——“IO口驅(qū)動(dòng)代碼的編寫(xiě)“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊(cè)知識(shí)
文章出處:【微信號(hào):yikoulinux,微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論