【树莓派学习笔记】九、C语言寄存器操作控制GPIO
目錄
- CPU型號確定
- 寄存器的地址問題
- GPIO寄存器
- GPFESLn
- GPSETn
- GPCLRn
- 重要函數
- mmap函數
- munmap函數
- 點燈程序
平臺:樹莓派3B
版本: 2021-05-07-raspios-buster-armhf
CPU型號確定
由
pinout命令可知,所用的板子Soc型號為BCM2837
寄存器的地址問題
本節內容修改自虛擬地址/物理地址——virtual address(memory)/physical address: 樹莓派 mmap example —— 風竹夜
由于官方只公開了BCM2835的芯片手冊(Raspberry Pi Documentation),而我們用的板子Soc為BCM2837,因此由手冊得到的地址是不準確的。但兩個芯片外設上區別不大,手冊仍有一定的參考價值。
手冊第5頁描繪了BCM2835的地址映射情況
其中要區分文檔中的三個地址,Bus Address、Physical Address、Virtual Address
在樹莓派中,借助ARM內部的MMU,CPU外設物理地址映射成了虛擬地址。
這里的 Virtual Address 和 Physical Address 是通過 ARM MMU 來實現映射的(先不管cache等其他因素)。主板上外設的實際地址是 Physical Address,所以要訪問 GPIO 寄存器,也就是訪問 Physical Address 中從 0x20000000 開始的某處的地址, 那么就需要在代碼中訪問 Virtual Address 中從 0xF2000000 開始的某處的地址,由于該虛擬地址在高地址內存,因此只能在內核的代碼中才能夠訪問到。
而在樹莓派3B中我們可以通過
命令獲取物理地址分配情況
由圖可知,樹莓派3B GPIO 的物理起始地址為0x3f200000
GPIO寄存器
從手冊第89頁開始詳細地描述了GPIO外設。
第90-91頁的表格標明了和GPIO相關的寄存器的地址。
其中GPFESLn(選擇引腳功能)、GPSETn(設置引腳輸出高電平)和GPCLRn(設置引腳輸出低電平)是控制引腳輸出電平需要用到的寄存器。
雖然樹莓派3B的Soc換成了BCM2837,GPIO外設的基地址有變,但手冊上的偏移地址還是有參考價值的。
其中GPFESL0的偏移地址為0x00;GPSET0的偏移地址為0x1C;GPCLRn的偏移地址為0x28
GPFESLn
由手冊知
GPFESL0控制GPIO Pin 0~9;
GPFESL1控制GPIO Pin 10~19;
GPFESL2控制GPIO Pin 20~29;
GPFESL3控制GPIO Pin 30~39;
GPFESL4控制GPIO Pin 40~49;
GPFESL5控制GPIO Pin 50~59;
3位為間隔,000 為輸入,001為輸出
GPSETn
由手冊知
GPSET0控制GPIO pin 0~31
GPSET1控制GPIO pin 32~53
GPCLRn
由手冊知
GPCLR0控制GPIO pin 0~31
GPCLR1控制GPIO pin 32~53
重要函數
mmap函數
mmap將一個文件或者其它對象映射進內存。mmap操作提供了一種機制,讓用戶程序直接訪問設備內存,這種機制,相比較在用戶空間和內核空間互相拷貝數據,效率更高。
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射區的開始地址,設置為0時表示由系統決定映射區的起始地址。
length:映射區的長度。//長度單位是 以字節為單位,不足一內存頁按一內存頁處理。
prot:期望的內存保護標志,不能與文件的打開模式沖突。
PROT_EXEC //頁內容可以被執行;
PROT_READ //頁內容可以被讀取;
PROT_WRITE //頁可以被寫入;
PROT_NONE //頁不可訪問
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。
fd:有效的文件描述詞。一般是由open()函數返回。
offset:被映射對象內容的起點。
返回值:成功:被映射區的指針。失敗:MAP_FAILED[其值為(void *)-1]。
munmap函數
munmap()用來取消參數start所指的映射內存起始地址
int munmap(void *start,size_t length);
start:映射區的開始地址
length:欲取消的內存大小
返回值:成功:0;失敗:-1。
點燈程序
在合適的地方編寫main.c文件
nano main.c #include <stdio.h> #include <sys/mman.h> //mmap、munmap函數的定義 #include <fcntl.h> //open函數的定義 #include <unistd.h> //close函數的定義 #include <stdint.h> //uint8_t、uint32_t等類型的定義 #include <unistd.h> //sleep函數的定義#define GPIO_BASE_Physical_Address 0x3f200000#define GPFSEL0_Offs 0x00 #define GPSET0_Offs 0x1C #define GPCLR0_Offs 0x28 int main(int argc, char *argv[]) {int fd;uint8_t Pin = 3;fd = open("/dev/gpiomem", O_RDWR);if (fd == -1) //只有root用戶才能讀取/dev/mem, 故本實驗選擇讀取/dev/gpiomem,以實現普通用戶控制GPIO{printf("open Error!\n");return -1;}void *GPIO_BASE = mmap(0, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE, MAP_SHARED , fd, GPIO_BASE_Physical_Address);close(fd);if(GPIO_BASE == MAP_FAILED){printf("mmap Error!\n");return -1;}volatile uint32_t * GPFSEL0 = (uint32_t *)(GPIO_BASE + GPFSEL0_Offs);volatile uint32_t * GPSET0 = (uint32_t *)(GPIO_BASE + GPSET0_Offs);volatile uint32_t * GPCLR0 = (uint32_t *)(GPIO_BASE + GPCLR0_Offs);*GPFSEL0 = (*GPFSEL0 & ~((uint32_t)7 << (3 * Pin))) | ((uint32_t)1) << (3 * Pin); //設置Pin 3為輸出模式for(uint8_t i = 0; i < 10; ++i) //反轉Pin 3 i次{*GPCLR0 = ((uint32_t)1) << Pin; sleep(1);*GPSET0 = ((uint32_t)1) << Pin; sleep(1);}*GPFSEL0 = *GPFSEL0 & ~((uint32_t)7 << (3 * Pin)); //設置Pin 3為輸入模式if(munmap(GPIO_BASE, sysconf(_SC_PAGESIZE)) == -1){printf("munmap Error!\n");return -1;}GPIO_BASE = MAP_FAILED;GPFSEL0 = MAP_FAILED;GPSET0 = MAP_FAILED;GPCLR0 = MAP_FAILED;return 0; }其中sysconf(_SC_PAGESIZE)為頁的長度,相關知識見yaaangmin大佬的視頻:深入理解計算機系統20:內存 - 多級頁表、虛擬地址、物理地址
編寫Makefile文件
nano Makefile注意Makefile里的縮進為Tab而不是空格
main: main.ogcc -o main main.o main.o: main.cgcc -c main.c clean:rm *.orm main clear:rm *.orm main
編譯并測試
可見Pin 3 腳所連LED閃爍(Pin 3腳已事先接好LED和限流電阻下拉至GND)
此編號對應
gpio readall命令下的BCM編號
總結
以上是生活随笔為你收集整理的【树莓派学习笔记】九、C语言寄存器操作控制GPIO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jqurey操作radio总结
- 下一篇: picsart旧版本_picsart 2