0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

樹莓派IO操控驅(qū)動代碼的編寫方式

xCb1_yikoulinux ? 來源:一口Linux ? 作者:土豆居士 ? 2022-06-08 16:41 ? 次閱讀

微機總線地址

地址總線:

  • 百度百科解釋: 地址總線 (Address Bus;又稱:位址總線) 屬于一種電腦總線 (一部份),是由CPU 或有DMA 能力的單元,用來溝通這些單元想要存?。ㄗx取/寫入)電腦內(nèi)存元件/地方的實體位址。
  • 地址總線 = 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就尋找不到。尋址能力就是計算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次方)位二進制數(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位二進制數(shù)據(jù)。eg: 8根數(shù)據(jù)線一次可傳送一個8位二進制數(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單片機),但是一般都相同。16位機有16根數(shù)據(jù)總線,20根地址總線,能訪問1M(2的20次方),32位機有32根數(shù)據(jù)總線,32根地址總線,能訪問4G(2的32次方),64位機確實有64根數(shù)據(jù)總線。

物理地址(PA)

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

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

虛擬地址(VA)

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

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

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

頁表(MMU的單元)

分頁管理:d22b2e24-e63a-11ec-ba43-dac502259ad0.png

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

更詳細的地址問題看這里

BCM2835芯片手冊

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

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

  • 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)下圖的地址是:總線地址(并不是真正的物理地址)d291f442-e63a-11ec-ba43-dac502259ad0.png

  • FSELn表示GPIOn,d2b5c908-e63a-11ec-ba43-dac502259ad0.png下圖給出第九個引腳的功能選擇示例,對寄存器的29-27進行配置,進而設(shè)置相應(yīng)的功能。根據(jù)圖片下方的register 0表示0~9使用的是register 0這個寄存器。d2c795f2-e63a-11ec-ba43-dac502259ad0.png

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

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

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

功能選擇輸出/輸入(GPIOFunctionSelectRegisters3214-12001=GPIOPin4isanoutput

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

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

若想找樹莓派引腳點這里

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

ioremap、iounmap:

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

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

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

三、 在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)進行讀取或?qū)懭?,實際上就是對設(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)存單元進行訪問。

解除映射: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)//真實的驅(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) //真實的驅(qū)動入口里賦值。

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

volatile的作用是作為指令關(guān)鍵字,確保本條 ==指令不會因編譯器的優(yōu)化而省略==,==且要求每次直接讀值==因為它是地址我希望它是無符號的unsigned

我們在編寫驅(qū)動程序的時候,IO空間的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址應(yīng)該是從0x3f200000開始的d39eb294-e63a-11ec-ba43-dac502259ad0.png然后在這個基礎(chǔ)上進行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。用到了一個函數(shù)ioremap

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

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

d3c89406-e63a-11ec-ba43-dac502259ad0.png配置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|按位或

忘記按位與 按位或 點這里

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ū)動代碼編譯

  1. 首先在系統(tǒng)目錄/SYSTEM/linux-rpi-4.14.y下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules驅(qū)動模塊進行編譯生成.ko文件.
  2. 然后將編譯后的驅(qū)動發(fā)送到樹莓派:scp ./drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi,然后再將上層代碼進行編譯arm-linux-gnueabihf-gcc pin4test.c -o realtest,然后再將測試代碼傳到樹莓派:scp realtest pi@192.168.43.136:/home/pi/
  3. 然后在樹莓派上面使用指令:insmod pin4drive.ko進行加載驅(qū)動(然后lsmod即可查看到該驅(qū)動),
  4. 然后使用指令:sudo chmod 666 /dev/pin4給予pin4這個設(shè)備可訪問權(quán)限,還可以在虛擬機上面使用mk5sum查看驅(qū)動文件的值,并在樹莓派上面使用該指令進行查看該驅(qū)動文件的值,看是否一致。
  5. dmesg查看內(nèi)核打印的信息,如下圖所示:d422c098-e63a-11ec-ba43-dac502259ad0.png
  6. 然后運行測試代碼,在新建一個窗口,使用指令gpio readall可以看到BCM下面的4號引腳模式是輸出模式,電平是低電平或高電平(根據(jù)輸入的上層代碼而定,輸入0就是低電平,輸入1就是高電平),這里我輸入的是0,如下圖所示:d4443c1e-e63a-11ec-ba43-dac502259ad0.png

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

有關(guān)驅(qū)動代碼里面GPIO口地址的問題:d469f3d2-e63a-11ec-ba43-dac502259ad0.pngd4a9258e-e63a-11ec-ba43-dac502259ad0.png

  • 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)的實際物理地址應(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寄存器的實際地址是0x3f200000=0x3F000000 + (7E200000-7E000000)
  • 然后通過數(shù)據(jù)手冊可以看到,樹莓派相關(guān)寄存器的總線地址(和映射的虛擬地址有某種對應(yīng)關(guān)系的地址)進而可得知偏移量,如下圖所示:d51af970-e63a-11ec-ba43-dac502259ad0.png

原文標(biāo)題:樹莓派高級開發(fā)——“IO口驅(qū)動代碼的編寫“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊知識

文章出處:【微信公眾號:一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據(jù)總線
    +關(guān)注

    關(guān)注

    2

    文章

    57

    瀏覽量

    17571
  • 樹莓派
    +關(guān)注

    關(guān)注

    116

    文章

    1707

    瀏覽量

    105653
  • 驅(qū)動代碼
    +關(guān)注

    關(guān)注

    2

    文章

    14

    瀏覽量

    7602
  • 地址總線
    +關(guān)注

    關(guān)注

    1

    文章

    10

    瀏覽量

    3890

原文標(biāo)題:樹莓派高級開發(fā)——“IO口驅(qū)動代碼的編寫“ 包含總線地址、物理_虛擬地址、BCM2835芯片手冊知識

文章出處:【微信號:yikoulinux,微信公眾號:一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    樹莓教程 詳解樹莓驅(qū)動OLED模塊

    提供樹莓、STM32、arduino例程;連接樹莓的時候,選擇用7PIN排線連接,請參考下方的引腳對應(yīng)表格。
    的頭像 發(fā)表于 05-06 09:32 ?5552次閱讀
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>教程 詳解<b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>OLED模塊

    想為樹莓3移植內(nèi)核并編寫一些驅(qū)動如何實現(xiàn)?

    想為樹莓3移植內(nèi)核并編寫一些驅(qū)動,怎么做。
    發(fā)表于 08-05 23:50

    基于樹莓驅(qū)動小車

    讓我們一起共建樹莓派生態(tài)圈!樹莓環(huán)境搭建好后,就可以開始動手驅(qū)動小車了,本教程采用的是python語言編寫,編譯工具為emacs。物理連接
    發(fā)表于 09-06 07:55

    樹莓B的IO連接器電路原理圖

    Raspberry Pi B IO Connectors Schematic,樹莓B的IO連接器電路原理圖
    發(fā)表于 01-06 11:13 ?34次下載

    樹莓驅(qū)動舵機介紹_樹莓驅(qū)動舵機代碼

    本文主要詳細介紹了樹莓驅(qū)動舵機以及它的驅(qū)動代碼,具體的跟隨小編一起來了解一下。
    的頭像 發(fā)表于 05-30 14:47 ?1.4w次閱讀
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>舵機介紹_<b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>舵機<b class='flag-5'>代碼</b>

    dfrobot樹莓4B/3B+ IO擴展板簡介

    IO Expansion HAT是一款專為Raspberry Pi 開發(fā)的IO擴展板,擴展板將樹莓IO口均引出,包含數(shù)字端口、模擬端口、
    的頭像 發(fā)表于 01-07 10:40 ?6578次閱讀
    dfrobot<b class='flag-5'>樹莓</b><b class='flag-5'>派</b>4B/3B+ <b class='flag-5'>IO</b>擴展板簡介

    樹莓IO擴展板的技術(shù)規(guī)格 樹莓擴展板該怎么用

    樹莓IO擴展板是著名開源硬件商DFRobot專為樹莓開發(fā)的IO擴展板,
    的頭像 發(fā)表于 05-17 15:18 ?3477次閱讀
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>IO</b>擴展板的技術(shù)規(guī)格 <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>擴展板該怎么用

    樹莓驅(qū)動步進電機

    步進電機一般由電機、驅(qū)動器構(gòu)成,樹莓連接驅(qū)動器控制電機。 一、接線 DIR+/- 用于控制方向,PUL+/- 用于控制轉(zhuǎn)動; 正極?DIR+ 和 PUL+ 接到GPIO口,兩個負極接
    發(fā)表于 03-20 14:40 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>步進電機

    樹莓驅(qū)動28byj步進電機

    1.材料:樹莓4B、28byj步進電機、步進電機驅(qū)動(ULN2003)杜邦線若干(雙母頭)2.接線,給驅(qū)動板供電5—12vIN1IN2IN3IN4分別接
    發(fā)表于 03-20 11:09 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>28byj步進電機

    樹莓小車入門之驅(qū)動電機

    樹莓環(huán)境搭建好后,就可以開始動手驅(qū)動小車了,本教程采用的是python語言編寫,編譯工具為emacs 物理連接部分: 首先,在連接線路之前,先來了解下需要準(zhǔn)備的東西以及接口的解析
    發(fā)表于 03-20 14:05 ?1次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>小車入門之<b class='flag-5'>驅(qū)動</b>電機

    樹莓驅(qū)動步進電機

    ?閑了無聊,想用樹莓驅(qū)動步進電機。 測試了兩天終于OK了 下圖是DM422C接步進電機和樹莓GPIO 步進電機紅綠 一組, 黃藍 一組
    發(fā)表于 03-21 14:26 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b><b class='flag-5'>驅(qū)動</b>步進電機

    樹莓控制步進電機

    樹莓控制步進電機 前言 設(shè)備 連接 源碼 前言 測試步進電機 設(shè)備 名稱 型號 樹莓 3B+ 步進電機 28BYJ-48-5V 步進電機驅(qū)動
    發(fā)表于 03-21 11:39 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>控制步進電機

    樹莓控制繼電器

    樹莓控制繼電器命令行輸入gpio readall查看樹莓io口屬性可以看到被分為左右兩側(cè),左側(cè)為樹莓
    發(fā)表于 04-21 11:50 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>控制繼電器

    樹莓接繼電器的使用

    1.查看樹莓的針腳 gpio readall ?2.連線 ? ?我們需要把繼電器的VCC 連到樹莓3.3V的針腳、GND連到0V的針腳、IN連到GPIO的針腳? ???? ????
    發(fā)表于 04-26 11:40 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>接繼電器的使用

    樹莓之繼電器的使用

    準(zhǔn)備工作 1.樹莓 2.繼電器 1.接線 接上之后電源燈亮起 如果沒亮請檢查接線 2.編寫代碼控制IO口# include # inclu
    發(fā)表于 04-27 09:33 ?0次下載
    <b class='flag-5'>樹莓</b><b class='flag-5'>派</b>之繼電器的使用