树莓派高级开发之树莓派博通BCM2835芯片手册导读与及“相关IO口驱动代码的编写”
首先我們要知道,驅(qū)動(dòng)的兩大利器:電路圖(通過電路圖去尋找寄存器)和芯片手冊
一、寄存器的介紹
芯片手冊第六章的89頁,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)下圖的地址是:總線地址(并不是真正的物理地址)
GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此類推,GPFSEL5就是pin50~pin53的配置寄存器。
| GPFSEL0 | GPIO Function select 0,功能選擇輸出/輸入 | 以引腳9舉例:000 = GPIO Pin 9 is an input,001 = GPIO Pin 9 is an output |
| GPSET0 | GPIO Pin output Set 0,輸出0 | 0 = No effect ,1 = Set GPIO pin n |
| GPSET1 | GPIO Pin output set 1,輸出1 | 0 = No effect ,1 = Set GPIO pin n |
| GPCLR0 | GPIO Pin output clear 0,清0 | 0 = No effect ,1 = Clear GPIO pin n |
在上面的文檔里已經(jīng)說的很清楚了,000是引腳輸入,而001則是引腳輸出,在這里要注意每個(gè)寄存器都是32位的
- FSELn表示GPIOn,下圖給出第九個(gè)引腳的功能選擇示例,對寄存器的29-27進(jìn)行配置,進(jìn)而設(shè)置相應(yīng)的功能。根據(jù)圖片下方的register
0表示0~9使用的是register 0(即GPFSEL0)這個(gè)寄存器。
- 輸出集寄存器用于設(shè)置GPIO管腳。SET{n}字段定義,分別對GPIO引腳進(jìn)行設(shè)置,將“0”寫入字段沒有作用。如果GPIO管腳為在輸入(默認(rèn)情況下)中使用,那么SET{n}字段中的值將被忽略。然而,如果引腳隨后被定義為輸出,那么位將被設(shè)置根據(jù)上次的設(shè)置/清除操作。分離集和明確功能取消對讀-修改-寫操作的需要。GPSETn寄存器為了使IO口設(shè)置為1,set4位設(shè)置第四個(gè)引腳,也就是寄存器的第四位。
- 輸出清除寄存器用于清除GPIO管腳。CLR{n}字段定義要清除各自的GPIO引腳,向字段寫入“0”沒有作用。如果在輸入(默認(rèn)),然后在CLR{n}字段的值是忽略了。然而,如果引腳隨后被定義為輸出,那么位將被定義為輸出根據(jù)上次的設(shè)置/清除操作進(jìn)行設(shè)置。分隔集與清函數(shù)消除了讀-修改-寫操作的需要。GPCLRn是清零功能寄存器。
把pin4引腳配置為輸出引腳:
FSEL4 14-12 001 我們把4引腳的14-12配置成001 GPIO Pin 4 is an output
詳細(xì)操作:
只需要將GPFSL0這個(gè)寄存器的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。
特別提示:進(jìn)行取反后再進(jìn)行按位與操作是為了不影響其他引腳
配置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)這里
代碼實(shí)現(xiàn):
*GPFSEL0 &=~(0x6 <<12); // 把13 、14置為0*GPFSEL0 |= (0x1 <<12); //把12置為1- 注意:我們配置的底層引腳對應(yīng)得是BCM 寄存器第0組位FESL0–9, 這個(gè)就是在寄存器GPFSEL0里,寄存器已經(jīng)分好組了
寄存器第1組位FSEL10–19,這個(gè)在寄存器GPFSEL1里
更多的引腳對應(yīng)的寄存器可以去樹莓派官網(wǎng)進(jìn)行查看
樹莓派引腳查看官網(wǎng)
在上圖中我們可以點(diǎn)擊對應(yīng)的引腳編號(hào),就可以查看到對應(yīng)的引腳的相關(guān)的信息
二、寄存器的地址問題
我們在編寫驅(qū)動(dòng)程序的時(shí)候,IO口空間的起始地址是0x3f00 0000,加上GPIO的偏移量0x200 0000,所以GPIO的物理地址應(yīng)該是0x3f20 0000開始的,然后在這個(gè)基礎(chǔ)上進(jìn)行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上。
上圖的尾部偏移是對的,根據(jù)GPIO的物理地址0x3f20 0000可以知道:
- 在原來框架的基礎(chǔ)上,添加寄存器的定義
完成以上代碼需要搞清楚的幾點(diǎn)
弄清楚寄存器的分組
GPFSEL0是pin0 ~ pin9的配置寄存器,GPFSEL1是pin10 ~ pin19的配置寄存器,以此類推,GPFSEL5就是pin50~pin53的配置寄存器。這個(gè)由查閱芯片手冊可以得知
volatile關(guān)鍵字的使用(筆試可能會(huì)考)
-
在此處的作用:防止編譯器優(yōu)化(可能是省略,也可能是更改)這些寄存器地址變量,常見于在內(nèi)核中對IO口進(jìn)行操作
-
作用:確保指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值,在這里的意思就是確保地址不會(huì)被編譯器更換
首先是在1.的基礎(chǔ)上,在pin4_drv_init這個(gè)函數(shù)里面添加寄存器地址的配置
寫出以上的代碼,要搞清楚以下幾點(diǎn)
分別找到幾個(gè)IO寄存器的物理地址(非常易錯(cuò)),弄清楚GPIO的物理地址(真實(shí)地址)
記住并不是用下面這張圖的地址來對應(yīng)GPIO功能選擇寄存器0的地址,否則編譯后運(yùn)行會(huì)有段錯(cuò)誤。
IO口的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的實(shí)際物理地址應(yīng)該是從0x3f200000開始的,然后在這個(gè)基礎(chǔ)上進(jìn)行Linux系統(tǒng)的MMU內(nèi)存虛擬化管理,映射到虛擬地址上,編程都是操作虛擬地址。
然后我們可以根據(jù)這個(gè)偏移值來確定寄存器的物理地址(真實(shí)的地址)
可以看到寄存器GPSET0相對于GPIO物理地址的偏移值為1C。即0x3f20001C
同樣的方法,寄存器GPCLR0的偏移值為28,即0x3f200028
寄存器GPFSEL0的偏移值為0,即0x3f200000
代碼實(shí)現(xiàn):
引腳輸出高電平:
*GPSET0 |= (0x1 << 4);
左移4位, 這里無論什么寄存器都是寫1,寫1并不是為某個(gè)io口去寫1,而是1是驅(qū)動(dòng)(SET)設(shè)置寄存器工作將bit4的電平拉高即變?yōu)楦唠娖?#xff0c;為什么要進(jìn)行或操作,是因?yàn)闉榱瞬挥绊懫渌_的狀態(tài)
引腳輸出低電平:
*GPCLR0 |= (0x1 << 4);
同樣道理,左移4位,這里的1也并不是為了某個(gè)io口去寫1,而是1是驅(qū)動(dòng)(CLR)清零寄存器將電平拉低,即變?yōu)榈碗娖?#xff0c;進(jìn)行或操作也一樣是為了不影響其他引腳的電平狀態(tài)
copy_from_user和copy_to_user這兩個(gè)函數(shù)相信做內(nèi)核開發(fā)的人都非常熟悉,分別是將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間以及將內(nèi)核空間中的數(shù)據(jù)拷貝到用戶空間
詳細(xì)了解copy_from_user和copy_to_user
三、驅(qū)動(dòng)代碼與應(yīng)用測試代碼
3.1 相關(guān)代碼
底層驅(qū)動(dòng)代碼:
上層應(yīng)用測試代碼:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main() {int fd;int userCmd;fd = open("/dev/pin4",O_RDWR);if(fd < 0){printf("fail to open the pin4\n");perror("the reason:");}else{printf("success to open the pin4\n");}printf("please Input 1-HIGH,0-LOW \n");scanf("%d",&userCmd);write(fd,&userCmd,4); //這里userCmd是一個(gè)整型數(shù),所以寫的是4個(gè)字節(jié)return 0; }至于怎么在虛擬機(jī)中編譯驅(qū)動(dòng)文件以及如何將編譯好的文件發(fā)送至樹莓派,敬請關(guān)注以下博文
如何將編譯好的文件發(fā)送至樹莓派底下
3.2 在樹莓派底下進(jìn)行代碼的測試與驗(yàn)證
相關(guān)的驅(qū)動(dòng)的裝載與卸載也查看驅(qū)動(dòng)裝載與卸載
- 先來查看一下樹莓派4號(hào)引腳的初始狀態(tài)是什么
輸入1,將引腳電平變?yōu)楦唠娖?br />
輸入0,將引腳電平變?yōu)榈碗娖?br />
到目前為止,我們經(jīng)過那么多節(jié)的對驅(qū)動(dòng)的學(xué)習(xí)的博文,現(xiàn)在終于自己終于實(shí)現(xiàn)了類似于wiringPi這樣的一個(gè)驅(qū)動(dòng)文件,我們在這里做的是引腳4的驅(qū)動(dòng),那么我們就可以按著模樣來寫引腳5,引腳6,甚至其他引腳的驅(qū)動(dòng),在這里想說一句,驅(qū)動(dòng)代碼的編寫,都是基于linux內(nèi)核源碼來進(jìn)行編寫的,linux內(nèi)核源碼這個(gè)文件在前面的博文有,如有需要自行去下載。
學(xué)習(xí)筆記,僅供參考
樹莓派高級(jí)開發(fā)之IO口驅(qū)動(dòng)代碼編寫 優(yōu)秀博文參考一
樹莓派高級(jí)開發(fā)之IO口驅(qū)動(dòng)代碼編寫 優(yōu)秀博文參考二
總結(jié)
以上是生活随笔為你收集整理的树莓派高级开发之树莓派博通BCM2835芯片手册导读与及“相关IO口驱动代码的编写”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是业财一体化?
- 下一篇: c++语言生成一个随机数,C++ 快速随