微機(jī)總線地址
地址總線:
- 百度百科解釋: 地址總線 (Address Bus;又稱:位址總線) 屬于一種電腦總線 (一部份),是由CPU 或有DMA 能力的單元,用來溝通這些單元想要存?。ㄗx取/寫入)電腦內(nèi)存元件/地方的實(shí)體位址。
- 地址總線 = cpu能夠訪問內(nèi)存的范圍:用一個現(xiàn)象來解釋地址總線:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可是系統(tǒng)只識別了3.8G,裝了64位,才能識別到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
,每一個數(shù)值都對應(yīng)內(nèi)存中的一個內(nèi)存單元,所以可以找到編號為00000000~ 11111111
號的內(nèi)存單元。如果傳輸?shù)臄?shù)據(jù)為00110011
,那么就會找到00110011
號內(nèi)存單元,如果傳輸?shù)臄?shù)據(jù)為10110111
,那么就會找到10110111
號內(nèi)存單元。編號不在[00000000,11111111]
范圍內(nèi)的CPU就尋找不到,例如100000000
號內(nèi)存單元,CPU就尋找不到。尋址能力就是計(jì)算CPU能尋找多少個內(nèi)存單元,00000000~11111111
號內(nèi)存單元,一共有256個,一個內(nèi)存單元的大小為1byte,這256個內(nèi)存單元的大小為256byte。 - CPU是通過地址總線來指定存儲單元的。
-
**==地址總線決定了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)存.
- 比如上面我們說的那個現(xiàn)象:裝了32位的win7系統(tǒng),明明內(nèi)存條8G,可系統(tǒng)只是別了3.8G,裝了64位才能識別到8G。裝了32位的操作系統(tǒng)CPU的訪問范圍是2^32bit,就是4194304kbit,就是4096Mbit,等于4G。樹莓派也是32位 ,一個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ù)線一次可傳送一個8位二進(jìn)制數(shù)據(jù)(即一個字節(jié))。
- 數(shù)據(jù)總線是數(shù)據(jù)線數(shù)量之和,數(shù)據(jù)總線的位數(shù)決定CPU單次通信能交換的信息數(shù)量。
數(shù)據(jù)總線的寬度對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)卡物理地址存儲器中存儲單元對應(yīng)實(shí)際地址稱物理地址,與邏輯地址相對應(yīng)。網(wǎng)卡的物理地址通常是由網(wǎng)卡生產(chǎn)廠家寫入網(wǎng)卡的EPROM(一種閃存芯片,通常可以通過程序擦寫),它存儲的是傳輸數(shù)據(jù)時真正賴以標(biāo)識發(fā)出數(shù)據(jù)的電腦和接收數(shù)據(jù)的主機(jī)的地址。
-
這里說的 物理地址是內(nèi)存中的內(nèi)存單元實(shí)際地址,不是外部總線連接的其他電子元件的地址!**==物理地址屬于比較好理解的,物理地址就是內(nèi)存中每個內(nèi)存單元的編號==**,這個編號是順序排好的,物理地址的大小決定了內(nèi)存中有多少個內(nèi)存單元,物理地址的大小由地址總線的位寬決定!物理地址是硬件實(shí)際地址或絕對地址
虛擬地址(VA)
-
虛擬地址是Windows程序時運(yùn)行在386保護(hù)模式下,這樣程序訪問存儲器所使用的==邏輯地址
(基于算法的地址[軟件層面的地址:假地址])
稱為虛擬地址==,與實(shí)地址模式下的分段地址類似,虛擬地址也可以寫為“段:偏移量”的形式,這里的段是指段選擇器。而linux沒有各種保護(hù)模式,本來用的就是虛擬地址。 - 虛擬地址是CPU保護(hù)模式下的一個概念,保護(hù)模式是80286系列和之后的x86兼容CPU操作模式,在CPU引導(dǎo)完操作系統(tǒng)內(nèi)核后,操作系統(tǒng)內(nèi)核會進(jìn)入一種CPU保護(hù)模式,也叫虛擬內(nèi)存管理,在這之后的程序在運(yùn)行時都處于虛擬內(nèi)存當(dāng)中,虛擬內(nèi)存里的所有地址都是不直接的,所以你有時候可以看到一個虛擬地址對應(yīng)不同的物理地址,比如A進(jìn)程里的call函數(shù)入口虛擬地址是0x001,而B也是,但是它倆對應(yīng)的物理地址卻是不同的,操作系統(tǒng)采用這種內(nèi)存管理方法。
- 是防止程序?qū)ξ锢淼刂穼憯?shù)據(jù)造成一些不可必要的問題,比如知道了A進(jìn)程的物理地址,那么向這個地址寫入數(shù)據(jù)就會造成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)存會比物理內(nèi)存要大許多。在進(jìn)入虛擬模式之前CPU以及Bootloader(BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行??梢猿跏蓟布O(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境),操作系統(tǒng)內(nèi)核均運(yùn)行在實(shí)模式下,直接對物理地址進(jìn)行操作!虛擬內(nèi)存中也有分頁管理,這種管理方法是為了確保內(nèi)存中不會出現(xiàn)內(nèi)存碎片,當(dāng)操作系統(tǒng)內(nèi)核初始化完畢內(nèi)存中的分頁表后CPU的分頁標(biāo)志位會被設(shè)置,這個分頁標(biāo)志位是給MMU看的!
-
MMU
是Memory Management Unit的縮寫,中文名是內(nèi)存管理單元,它是==中央處理器(CPU)中用來管理虛擬存儲器、物理存儲器的控制線路,同時也負(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)行時會被加載到內(nèi)存中的4G空間里,其實(shí)說是有4G其實(shí)并沒有真正在的4G空間,4G空間中有一小部分被映射到了物理內(nèi)存中,或者被映射到了硬盤的文件上(fopen),或者沒有被映射,還有一部分在內(nèi)存當(dāng)中就會被劃分棧,堆,其中有大片大片的內(nèi)存是沒有被映射的,同樣物理內(nèi)存也是被分頁了用來與虛擬內(nèi)存產(chǎn)生映射關(guān)系。將虛擬地址映射為物理地址有一個算法(頁表)決定了將虛擬地址映射到物理地址的哪個位置,頁表是通過MMU(分頁內(nèi)存管理單元)來管理的,就是設(shè)計(jì)完頁表后通過MMU來執(zhí)行將虛擬地址映射為物理地址。
- 其實(shí)真正情況下只有3G用戶空間,假如你的內(nèi)存是4G的那么其中有1G是給操作系統(tǒng)內(nèi)核使用的,所謂的4G空間只是操作系統(tǒng)基于虛擬內(nèi)存這種拆東墻補(bǔ)西墻的形式給你一種感覺每個進(jìn)程都有4G的可用空間一樣!這里來說一下拆東墻補(bǔ)西墻,當(dāng)我們程序被加載進(jìn)4G空間時其實(shí)根本用不了所謂的4G空間,其中有大片內(nèi)存被閑置,那么這個時候呢,其他程序被加載進(jìn)來時發(fā)現(xiàn)內(nèi)存不夠了,就把其他程序里的4G空間里閑置部分拿出來給這個進(jìn)程用,換之這個進(jìn)程內(nèi)存不夠時就會把其他進(jìn)程里閑置的空間拿過來給該進(jìn)程使用。銀行也是如此!
- 當(dāng)我們要對物理地址做操作時比如if語句要根據(jù)CPU的狀態(tài)標(biāo)志寄存器來做不同的跳轉(zhuǎn),那么這個時候就要對CPU額狀態(tài)寄存器做操作了就必須知道它的物理地址,內(nèi)存中有一個電子元件叫MMU負(fù)責(zé)從操作系統(tǒng)已經(jīng)初始化好的內(nèi)存映射表里查詢與虛擬地址對應(yīng)的物理地址并轉(zhuǎn)換,比如mov0x4h8這個是虛擬地址,當(dāng)我們要對這個虛擬地址里寫數(shù)據(jù)時那么MMU會先判斷CPU的分頁狀態(tài)寄存器里的標(biāo)志狀態(tài)是否被設(shè)定,如果被設(shè)定那么MMU就會捕獲這個虛擬地址物理并在操作系統(tǒng)內(nèi)核初始化好的內(nèi)存映射表里查詢與之對應(yīng)的物理地址,并將其轉(zhuǎn)換成真正的實(shí)際物理地址,然后在對這個實(shí)際的物理地址給CPU,在由CPU去執(zhí)行對應(yīng)的命令,相反CPU往內(nèi)存里讀數(shù)據(jù)時比如A進(jìn)程要讀取內(nèi)存中某個虛擬地址的數(shù)據(jù),A進(jìn)程里的指令給的是虛擬地址,MMU首先會檢查CPU的分頁狀態(tài)寄存器標(biāo)志位是否被設(shè)置,如果被設(shè)置MMU會捕獲這個虛擬地址并將其轉(zhuǎn)換成相應(yīng)的物理地址然后提交給CPU,在由CPU到內(nèi)存中去取數(shù)據(jù)!
更詳細(xì)的地址問題看這里
BCM2835芯片手冊
下面截取樹莓派芯片手冊的一張圖:BCM2835是樹莓派3B CPU的型號,是ARM-cotexA53架構(gòu),cpu Bus是地址總線,00000000~FFFFFFFF
是CPU尋址的范圍(4G)。DMA是高速拷貝單元,CPU可以發(fā)動DMA直接讓DMA進(jìn)行數(shù)據(jù)拷貝,直接內(nèi)存訪問單元。物理地址(PA)1G、虛擬地址(VA)4G若程序大于物理地址1G,是不是就跑不了了,不是的,它有個MMU的單元,把物理地址映射成虛擬地址,我們操作的代碼基本上都是在虛擬地址,它有一個映射頁表(上面提及到過)
-
通過芯片手冊了解樹莓派的GPIO:有54條通用I/O GPIO行,分為兩行,備用功能通常是外圍IO并且可以在每個銀行中出現(xiàn)一個外圍設(shè)備,以允許靈活地選擇IO電壓。
-
GPIO有41個寄存器,所有訪問都是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,下圖給出第九個引腳的功能選擇示例,對寄存器的29-27進(jìn)行配置,進(jìn)而設(shè)置相應(yīng)的功能。根據(jù)圖片下方的register 0表示0~9使用的是register 0這個寄存器。
-
輸出集寄存器用于設(shè)置GPIO管腳。SET{n}字段定義,分別對GPIO引腳進(jìn)行設(shè)置,將“0”寫入字段沒有作用。如果GPIO管腳為在輸入(默認(rèn)情況下)中使用,那么SET{n}字段中的值將被忽略。然而,如果引腳隨后被定義為輸出,那么位將被設(shè)置根據(jù)上次的設(shè)置/清除操作。分離集和明確功能取消對讀-修改-寫操作的需要。GPSETn寄存器為了使IO口設(shè)置為1,set4位設(shè)置第四個引腳,也就是寄存器的第四位。
-
輸出清除寄存器用于清除GPIO管腳。CLR{n}字段定義要清除各自的GPIO引腳,向字段寫入“0”沒有作用。如果的在輸入(默認(rèn)),然后在CLR{n}字段的值是忽略了。然而,如果引腳隨后被定義為輸出,那么位將被定義為輸出根據(jù)上次的設(shè)置/清除操作進(jìn)行設(shè)置。分隔集與清函數(shù)消除了讀-修改-寫操作的需要。GPCLRn是清零功能寄存器。
配置樹莓派的pin4引腳為輸出引腳:
功能選擇輸出/輸入(GPIOFunctionSelectRegisters)32位
14-12001=GPIOPin4isanoutput
只需要將GPFSL0這個寄存器的14~12位設(shè)置為001就可以了。只需要將0x6(對應(yīng)的2進(jìn)制是110)左移12位·然后取反再與上GPFSL0就可以將13、14這兩位配置為0,然后再將0x6(對應(yīng)2進(jìn)制110)左移12位,然后或上GPFSL0即可將12位置1。
- 可使用copy_from_user()這個函數(shù)在驅(qū)動代碼里面讀取用戶輸入的指令,使用copy_to_user()這個函數(shù)讓引腳反饋現(xiàn)在的狀態(tài),也就是讓用戶讀取到。
若想找樹莓派引腳點(diǎn)這里
樹莓派IO操控驅(qū)動代碼:
ioremap、iounmap:
一. 一般我們的外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的,通常包括控制寄存器、狀態(tài)寄存器、數(shù)據(jù)寄存器三大類。外設(shè)的寄存器通常被連續(xù)編址,并且根據(jù)CPU的體系架構(gòu)不同CPU對IO端口的編制方式有兩種:
- IO映射方式(IO-mapped):比較典型的有X86處理器為外設(shè)專門實(shí)現(xiàn)了一個單獨(dú)的地址空間,稱為“IO端口空間”或者“IO地址空間”,此時CPU可以通過專門的指令(比如X86的IN和OUT)來訪問這個“IO端口空間”。
- 內(nèi)存映射方式(memory-mapped):RISC指令系統(tǒng)的CPU一般只實(shí)現(xiàn)一個物理地址空間,外設(shè)IO端口成為內(nèi)存的一部分。此時CPU可以訪問外設(shè)的IO端口,就像訪問自己的內(nèi)存一樣方便,不必再設(shè)置專門的指令來訪問。在驅(qū)動開發(fā)過程中一般使用內(nèi)存映射方式。
二、 在驅(qū)動開發(fā)過程中,一般來說外設(shè)的IO內(nèi)存資源的物理地址是已知的,由硬件的設(shè)計(jì)決定。但是CPU不會為這些已知的外設(shè)IO內(nèi)存資源預(yù)先指定虛擬地址的值,所以驅(qū)動程序不可以直接就通過外設(shè)的物理地址訪問到IO內(nèi)存,而必須要將其映射到虛擬地址空間(通過頁表),然后才能根據(jù)內(nèi)核映射過后的虛擬地址來通過內(nèi)存指令訪問這些IO內(nèi)存,并對其進(jìn)行操作。
三、 在Linux內(nèi)核的io.h頭文件中聲明了ioremap()函數(shù),用來將IO內(nèi)存資源映射到核心虛擬地址空間(3Gb~4GB)中,當(dāng)然不用了可以將其取消映射iounmap()。這兩個函數(shù)在mm/ioremap.c文件中:
開始映射:void*ioremap(unsignedlongphys_addr,unsignedlongsize,unsignedlongflags)
//用map映射一個設(shè)備意味著使用戶空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或?qū)懭?,?shí)際上就是對設(shè)備的訪問。
第一個參數(shù)是映射的起始地址
第二個參數(shù)是映射的長度
第二個參數(shù)怎么定???
====================
這個由你的硬件特性決定。
比如,你只是映射一個32位寄存器,那么長度為4就足夠了。
(這里樹莓派IO口功能設(shè)置寄存器、IO口設(shè)置寄存器都是32位寄存器,所以分配四個字節(jié)就夠了)
比如:GPFSEL0=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);
這三行是設(shè)置寄存器的地址,volatile的作用是作為指令關(guān)鍵字
確保本條指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值
ioremap函數(shù)將物理地址轉(zhuǎn)換為虛擬地址,IO口寄存器映射成普通內(nèi)存單元進(jìn)行訪問。
解除映射:voidiounmap(void*addr)//取消ioremap所映射的IO地址
比如:
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);//卸載驅(qū)動時釋放地址映射
樹莓派IO口四的驅(qū)動代碼:
#include//file_operations聲明
#include//module_initmodule_exit聲明
#include//__init__exit宏定義聲明
#include//classdevise聲明
#include//copy_from_user的頭文件
#include//設(shè)備號dev_t類型聲明
#include//ioremapiounmap的頭文件
staticstructclass*pin4_class;
staticstructdevice*pin4_class_dev;
staticdev_tdevno;//設(shè)備號
staticintmajor=231;//主設(shè)備號
staticintminor=0;//次設(shè)備號
staticchar*module_name="pin4";//模塊名
volatileunsignedint*GPFSEL0=NULL;
volatileunsignedint*GPSET0=NULL;
volatileunsignedint*GPCLR0=NULL;
//這三行是設(shè)置寄存器的地址
//volatile的作用是作為指令關(guān)鍵字,確保本條指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值
//led_open函數(shù)
staticintpin4_open(structinode*inode,structfile*file)
{
printk("pin4_open
");//內(nèi)核的打印函數(shù)和printf類似
//配置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類似
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類似
//獲取上層write函數(shù)的值
copy_from_user(&usercmd,buf,count);//將應(yīng)用層用戶輸入的指令讀如usercmd里面
//根據(jù)值來操作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限定這個結(jié)構(gòu)體的作用,僅僅只在這個文件。
int__initpin4_drv_init(void)//真實(shí)的驅(qū)動入口
{
intret;
devno=MKDEV(major,minor);//創(chuàng)建設(shè)備號
ret=register_chrdev(major,module_name,&pin4_fops);//注冊驅(qū)動告訴內(nèi)核,把這個驅(qū)動加入到內(nèi)核驅(qū)動的鏈表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");//讓代碼在dev下自動>生成設(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ū)動時釋放地址映射
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major,module_name);//卸載驅(qū)動
}
module_init(pin4_drv_init);//入口,內(nèi)核加載驅(qū)動的時候,這個宏會被調(diào)用,去調(diào)用pin4_drv_init這個函數(shù)
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPLv2");
1. 設(shè)置寄存器的地址
設(shè)置寄存器的地址,但是這樣寫是有問題的,我們上面講到了在內(nèi)核里代碼和上層代碼訪問的是虛擬地址(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)鍵字,確保本條指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值
我們先把地址初始
volatileunsignedint*GPFSEL0=NULL;
volatileunsignedint*GPSET0=NULL;
volatileunsignedint*GPCLR0=NULL;
在初始化int __init pin4_drv_init(void) //真實(shí)的驅(qū)動入口
里賦值。
//整數(shù)11//0xb 11 00010001即便是16進(jìn)制也是整數(shù),左邊是
volatile unsigned int* GPFSEL0
右邊也強(qiáng)制轉(zhuǎn)換成(volatile unsigned int*)
volatile
的作用是作為指令關(guān)鍵字,確保本條 ==指令不會因編譯器的優(yōu)化而省略==,==且要求每次直接讀值==因?yàn)樗堑刂肺蚁M菬o符號的unsigned
我們在編寫驅(qū)動程序的時候,IO空間的起始地址是0x3f000000
,加上GPIO的偏移量0x2000000
,所以GPIO的物理地址應(yīng)該是從0x3f200000
開始的然后在這個基礎(chǔ)上進(jìn)行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。用到了一個函數(shù)ioremap
//物理地址轉(zhuǎn)換成虛擬地址,io口寄存器映射成普通內(nèi)存單元進(jìn)行訪問
GPFSEL0=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);//4是4個字節(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ù)值來操作io口,高電平或者低電平
用copy_form_user(char *buf , user_buf , count)
獲取上層write函數(shù)的值
intusercmd;
copy_from_user(&usercmd,buf,count);//將應(yīng)用層用戶輸入的指令讀如usercmd里面
//根據(jù)值來操作io口,高電平或者低電平
printk("getvalue
");
if(usercmd==1){
printk("set1
");//置1
*GPSET0|=0x01<4;//用|或操作目的是不影響其他位
//寫1是讓寄存器開啟置1讓bit4為高電平
}
elseif(usercmd==0){
printk("set0
");//清0
*GPCLR0|=0x01<4;//用|或操作目的是不影響其他位
//寫1是讓清0寄存器開啟置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);//先銷毀設(shè)備
class_destroy(pin4_class);//再銷毀類
unregister_chrdev(major,module_name);//卸載驅(qū)動
}
上層測試代碼:
#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類型是int所以寫4
}
驅(qū)動卸載
在裝完驅(qū)動后可以使用指令:sudo rmmod +驅(qū)動名
(不需要寫ko)將驅(qū)動卸載。
IO口驅(qū)動代碼編譯
-
首先在系統(tǒng)目錄
/SYSTEM/linux-rpi-4.14.y
下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
對驅(qū)動模塊進(jìn)行編譯生成.ko
文件. -
然后將編譯后的驅(qū)動發(fā)送到樹莓派:
scp ./drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi
,然后再將上層代碼進(jìn)行編譯:arm-linux-gnueabihf-gcc pin4test.c -o realtest
,然后再將測試代碼傳到樹莓派:scp realtest pi@192.168.43.136:/home/pi/
-
然后在樹莓派上面使用指令:
insmod pin4drive.ko
進(jìn)行加載驅(qū)動(然后lsmod
即可查看到該驅(qū)動), -
然后使用指令:
sudo chmod 666 /dev/pin4
給予pin4這個設(shè)備可訪問權(quán)限,還可以在虛擬機(jī)上面使用mk5sum查看驅(qū)動文件的值,并在樹莓派上面使用該指令進(jìn)行查看該驅(qū)動文件的值,看是否一致。 -
dmesg
查看內(nèi)核打印的信息,如下圖所示: -
然后運(yùn)行測試代碼,在新建一個窗口,使用指令
gpio readall
可以看到BCM
下面的4
號引腳模式是輸出模式,電平是低電平或高電平(根據(jù)輸入的上層代碼而定,輸入0就是低電平,輸入1就是高電平),這里我輸入的是0,如下圖所示:
有關(guān)驅(qū)動代碼里面GPIO口地址的問題:
有關(guān)驅(qū)動代碼里面GPIO口地址的問題:
- 7Ennnnn意思是7E00000到7EFFFFFF,F(xiàn)2000000是3F000000映射的虛擬地址,然后7E00000和F200000對應(yīng),芯片手冊里面使用的是和虛擬地址F200000有著對應(yīng)關(guān)系的地址——7E00000,芯片手冊上面地址偏移多少物理地址就偏移多少
- 根據(jù)上方圖片描述,外設(shè)的物理地址范圍是m 0x3F000000 to0x3FFFFFFF,所以你看到的7E200000對應(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ù)芯片手冊可知對應(yīng)寄存器的偏移量為,比如GPFSEL0寄存器的實(shí)際地址是0x3f200000=0x3F000000 + (7E200000-7E000000)
- 然后通過數(shù)據(jù)手冊可以看到,樹莓派相關(guān)寄存器的總線地址(和映射的虛擬地址有某種對應(yīng)關(guān)系的地址)進(jìn)而可得知偏移量,如下圖所示:
原文標(biāo)題:樹莓派高級開發(fā)——“IO口驅(qū)動代碼的編寫“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊知識
文章出處:【微信公眾號:一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
-
數(shù)據(jù)總線
+關(guān)注
關(guān)注
2文章
57瀏覽量
17567 -
樹莓派
+關(guān)注
關(guān)注
116文章
1706瀏覽量
105606 -
驅(qū)動代碼
+關(guān)注
關(guān)注
2文章
14瀏覽量
7583 -
地址總線
+關(guān)注
關(guān)注
1文章
10瀏覽量
3888
原文標(biāo)題:樹莓派高級開發(fā)——“IO口驅(qū)動代碼的編寫“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊知識
文章出處:【微信號:yikoulinux,微信公眾號:一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論