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

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

3天內不再提示

樹莓派IO操控驅動代碼的編寫方式

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

微機總線地址

地址總線:

  • 百度百科解釋: 地址總線 (Address Bus;又稱:位址總線) 屬于一種電腦總線 (一部份),是由CPU 或有DMA 能力的單元,用來溝通這些單元想要存?。ㄗx取/寫入)電腦內存元件/地方的實體位址。
  • 地址總線 = cpu能夠訪問內存的范圍:用一個現(xiàn)象來解釋地址總線:裝了32位的win7系統(tǒng),明明內存條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尋找外部的內存單元靠的是地址總線傳輸?shù)臄?shù)據(jù)。如果CPU有8根地址總線,每根線上傳輸0或1,那么傳輸?shù)臄?shù)據(jù)范圍為00000000~ 11111111,每一個數(shù)值都對應內存中的一個內存單元,所以可以找到編號為00000000~ 11111111號的內存單元。如果傳輸?shù)臄?shù)據(jù)為00110011,那么就會找到00110011號內存單元,如果傳輸?shù)臄?shù)據(jù)為10110111,那么就會找到10110111號內存單元。編號不在[00000000,11111111]范圍內的CPU就尋找不到,例如100000000號內存單元,CPU就尋找不到。尋址能力就是計算CPU能尋找多少個內存單元,00000000~11111111號內存單元,一共有256個,一個內存單元的大小為1byte,這256個內存單元的大小為256byte
  • CPU通過地址總線來指定存儲單元的。
  • **==地址總線決定了cpu所能訪問的最大內存空間的大小==。eg: 10根地址線訪問的最大的內存為1024**(2的10次方)位二進制數(shù)據(jù)(1B)
  • 地址總線是地址線數(shù)量之和。若CPU的地址總線寬度是32位,那么CPU的尋址范圍是4G(2的32次方位),所以最多支持4G內存.
  • 比如上面我們說的那個現(xiàn)象:裝了32位的win7系統(tǒng),明明內存條8G,可系統(tǒng)只是別了3.8G,裝了64位才能識別到8G。裝了32位的操作系統(tǒng)CPU的訪問范圍是2^32bit,就是4194304kbit,就是4096Mbit,等于4G。樹莓派也是32位 ,一個G的內存,但它只能訪問949M剩下的挪作他用。

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

  • CPU通過地址總線尋址,然后通過數(shù)據(jù)總線與外部設備互換信息。
  • 是CPU與內存或其他器件之間的數(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的性能指標之一)決定CPU和外設互換信息的速度。
  • 其次,數(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)卡物理地址存儲器中存儲單元對應實際地址稱物理地址,與邏輯地址相對應。網(wǎng)卡的物理地址通常是由網(wǎng)卡生產廠家寫入網(wǎng)卡的EPROM(一種閃存芯片,通??梢酝ㄟ^程序擦寫),它存儲的是傳輸數(shù)據(jù)時真正賴以標識發(fā)出數(shù)據(jù)的電腦和接收數(shù)據(jù)的主機的地址

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

虛擬地址(VA)

  • 虛擬地址是Windows程序時運行在386保護模式下,這樣程序訪問存儲器所使用的==邏輯地址(基于算法的地址[軟件層面的地址:假地址])稱為虛擬地址==,與實地址模式下的分段地址類似,虛擬地址也可以寫為“段:偏移量”的形式,這里的段是指段選擇器。而linux沒有各種保護模式,本來用的就是虛擬地址。
  • 虛擬地址是CPU保護模式下的一個概念,保護模式是80286系列和之后的x86兼容CPU操作模式,在CPU引導完操作系統(tǒng)內核后,操作系統(tǒng)內核會進入一種CPU保護模式,也叫虛擬內存管理,在這之后的程序在運行時都處于虛擬內存當中,虛擬內存里的所有地址都是不直接的,所以你有時候可以看到一個虛擬地址對應不同的物理地址,比如A進程里的call函數(shù)入口虛擬地址是0x001,而B也是,但是它倆對應的物理地址卻是不同的,操作系統(tǒng)采用這種內存管理方法。
  • 是防止程序對物理地址寫數(shù)據(jù)造成一些不可必要的問題,比如知道了A進程的物理地址,那么向這個地址寫入數(shù)據(jù)就會造成A進程出現(xiàn)問題,在虛擬內存中運行程序永遠不知道自己處于內存中那一段的物理地址上!現(xiàn)在操作系統(tǒng)運行在保護模式下即便知道其他進程的物理地址也不允許向其寫入!但是可以通過操作系統(tǒng)留下的后門函數(shù)獲取該進程上的虛擬地址空間所有控制權限并寫入指定數(shù)據(jù)。
  • 虛擬內存管理采用一種拆東墻補西墻的形式,所以虛擬內存的內存會比物理內存要大許多。在進入虛擬模式之前CPU以及Bootloader(BootLoader是在操作系統(tǒng)內核運行之前運行。可以初始化硬件設備、建立內存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個合適狀態(tài),以便為最終調用操作系統(tǒng)內核準備好正確的環(huán)境),操作系統(tǒng)內核均運行在實模式下,直接對物理地址進行操作!虛擬內存中也有分頁管理,這種管理方法是為了確保內存中不會出現(xiàn)內存碎片,當操作系統(tǒng)內核初始化完畢內存中的分頁表后CPU的分頁標志位會被設置,這個分頁標志位是給MMU看的!
  • MMU是Memory Management Unit的縮寫,中文名是內存管理單元,它是==中央處理器(CPU)中用來管理虛擬存儲器、物理存儲器的控制線路,同時也負責虛擬地址映射為物理地址,以及提供硬件機制的內存訪問授權,多用戶多進程操作系統(tǒng)==。作用有兩點,地址翻譯和內存保護。==MMU將虛擬地址翻譯為物理地址。==

有關各種地址介紹的博文:

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

頁表(MMU的單元)

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

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

更詳細的地址問題看這里

BCM2835芯片手冊

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

  • 通過芯片手冊了解樹莓派的GPIO:有54條通用I/O GPIO行,分為兩行,備用功能通常是外圍IO并且可以在每個銀行中出現(xiàn)一個外圍設備,以允許靈活地選擇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進行配置,進而設置相應的功能。根據(jù)圖片下方的register 0表示0~9使用的是register 0這個寄存器。d2c795f2-e63a-11ec-ba43-dac502259ad0.png

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

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

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

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

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

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

若想找樹莓派引腳點這里

樹莓派IO操控驅動代碼:

ioremap、iounmap:

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

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

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

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

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

比如:GPFSEL0=(volatileunsignedint*)ioremap(0x3f200000,4);
GPSET0=(volatileunsignedint*)ioremap(0x3f20001C,4);
GPCLR0=(volatileunsignedint*)ioremap(0x3f200028,4);
這三行是設置寄存器的地址,volatile的作用是作為指令關鍵字
確保本條指令不會因編譯器的優(yōu)化而省略,且要求每次直接讀值
ioremap函數(shù)將物理地址轉換為虛擬地址,IO口寄存器映射成普通內存單元進行訪問。

解除映射:voidiounmap(void*addr)//取消ioremap所映射的IO地址
比如:
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);//卸載驅動時釋放地址映射

樹莓派IO口四的驅動代碼:

#include//file_operations聲明
#include//module_initmodule_exit聲明
#include//__init__exit宏定義聲明
#include//classdevise聲明
#include//copy_from_user的頭文件
#include//設備號dev_t類型聲明
#include//ioremapiounmap的頭文件


staticstructclass*pin4_class;
staticstructdevice*pin4_class_dev;

staticdev_tdevno;//設備號
staticintmajor=231;//主設備號
staticintminor=0;//次設備號
staticchar*module_name="pin4";//模塊名

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

//led_open函數(shù)
staticintpin4_open(structinode*inode,structfile*file)
{
printk("pin4_open
");//內核的打印函數(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
");//內核的打印函數(shù)和printf類似

return0;
}

//led_write函數(shù)
staticssize_tpin4_write(structfile*file,constchar__user*buf,size_tcount,loff_t*ppos)
{
intusercmd;
printk("pin4_write
");//內核的打印函數(shù)和printf類似

//獲取上層write函數(shù)的值
copy_from_user(&usercmd,buf,count);//將應用層用戶輸入的指令讀如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限定這個結構體的作用,僅僅只在這個文件。
int__initpin4_drv_init(void)//真實的驅動入口
{
intret;
devno=MKDEV(major,minor);//創(chuàng)建設備號
ret=register_chrdev(major,module_name,&pin4_fops);//注冊驅動告訴內核,把這個驅動加入到內核驅動的鏈表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");//讓代碼在dev下自動>生成設備
pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//創(chuàng)建設備文件

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);//卸載驅動時釋放地址映射

device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major,module_name);//卸載驅動
}
module_init(pin4_drv_init);//入口,內核加載驅動的時候,這個宏會被調用,去調用pin4_drv_init這個函數(shù)
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPLv2");

1. 設置寄存器的地址

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

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

我們先把地址初始

volatileunsignedint*GPFSEL0=NULL;
volatileunsignedint*GPSET0=NULL;
volatileunsignedint*GPCLR0=NULL;

在初始化int __init pin4_drv_init(void) //真實的驅動入口里賦值。

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

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

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

//物理地址轉換成虛擬地址,io口寄存器映射成普通內存單元進行訪問
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);//將應用層用戶輸入的指令讀如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);//先銷毀設備
class_destroy(pin4_class);//再銷毀類
unregister_chrdev(major,module_name);//卸載驅動

}

上層測試代碼:

#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
}

驅動卸載

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

IO口驅動代碼編譯

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

有關驅動代碼里面GPIO口地址的問題:

有關驅動代碼里面GPIO口地址的問題:d469f3d2-e63a-11ec-ba43-dac502259ad0.pngd4a9258e-e63a-11ec-ba43-dac502259ad0.png

  • 7Ennnnn意思是7E00000到7EFFFFFF,F(xiàn)2000000是3F000000映射的虛擬地址,然后7E00000和F200000對應,芯片手冊里面使用的是和虛擬地址F200000有著對應關系的地址——7E00000,芯片手冊上面地址偏移多少物理地址就偏移多少
  • 根據(jù)上方圖片描述,外設的物理地址范圍是m 0x3F000000 to0x3FFFFFFF,所以你看到的7E200000對應的實際物理地址應該是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是物理地址,樹莓派的外設空間的起始地址是0x3f000000,根據(jù)芯片手冊可知對應寄存器的偏移量為,比如GPFSEL0寄存器的實際地址是0x3f200000=0x3F000000 + (7E200000-7E000000)
  • 然后通過數(shù)據(jù)手冊可以看到,樹莓派相關寄存器的總線地址(和映射的虛擬地址有某種對應關系的地址)進而可得知偏移量,如下圖所示:d51af970-e63a-11ec-ba43-dac502259ad0.png

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

文章出處:【微信公眾號:一口Linux】歡迎添加關注!文章轉載請注明出處。

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

    關注

    2

    文章

    55

    瀏覽量

    17491
  • 樹莓派
    +關注

    關注

    116

    文章

    1679

    瀏覽量

    105256
  • 驅動代碼
    +關注

    關注

    2

    文章

    14

    瀏覽量

    7563
  • 地址總線
    +關注

    關注

    1

    文章

    9

    瀏覽量

    3870

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

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

收藏 人收藏

    評論

    相關推薦

    樹莓教程 詳解樹莓驅動OLED模塊

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

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

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

    基于樹莓驅動小車

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

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

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

    樹莓驅動舵機介紹_樹莓驅動舵機代碼

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

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

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

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

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

    樹莓驅動步進電機

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

    樹莓驅動28byj步進電機

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

    樹莓小車入門之驅動電機

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

    樹莓驅動步進電機

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

    樹莓控制步進電機

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

    樹莓控制繼電器

    樹莓控制繼電器命令行輸入gpio readall查看樹莓io口屬性可以看到被分為左右兩側,左側為樹莓
    發(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>接繼電器的使用

    樹莓之繼電器的使用

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