###### tags: `RaspberryPi` Raspberry pi GPIO mmap === mmap在虛擬地址空間創建了一段新的mapping,[mmap man page](https://man7.org/linux/man-pages/man2/mmap.2.html)。白話一點,就是將檔案文件映射到虛擬空間。但由於Linux是一個高度物件導向設計的作業系統。因此大多的東西都是以檔案形式提供開發者操作。 本文章主要紀錄,將GPIO從實體記憶體(/dev/mem),經由mmap映射到虛擬記憶體,並操作的一個過程。 以下是mmap的原型: ``` void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_toffset); ``` 1. addr: 指向欲映射的核心起始位址,通常設為NULL,代表讓系統自動選定位址,核心會自己在進程位址空間中選擇合適的位址建立映射。映射成功後返回該位址。如果不是NULL,則給核心一個提示,應該從什麼位址開始映射,核心會選擇start之上的某個合適的位址開始映射。建立映射後,真正的映射位址通過返回值可以得到。 2. length: 映射的長度。通常會採用分頁長度(Raspberry pi為4K)。 3. prot: 描述想要記憶體的保護方式。 :::info PROT_EXEC Pages may be executed. PROT_READ Pages may be read. PROT_WRITE Pages may be written. PROT_NONE Pages may not be accessed. ::: 4. flags: 影響映射區域的各種特性。 :::info * MAP_SHARED: 分享這個mmaping。允許其他映射該文件的行程共享,對映射區域的寫入數據會複製回文件。 * MAP_PRIVATE 不允許其他映射該文件的行程共享,對映射區域的寫入操作會產生一個映射的複製(copy-on-write),對此區域所做的修改不會寫回原文件。 * MAP_ANONYMOUS 建立匿名映射。此時會忽略參數fd,不涉及文件,而且映射區域無法和其他進程共享。 * MAP_DENYWRITE 只允許對映射區域的寫入操作,其他對文件直接寫入的操作將會被拒絕。 * MAP_LOCKED 將映射區域鎖定住,這表示該區域不會被置換(swap)。 ::: 5. fd: mapping的來源文件。 6. off_toffset: 從文件映射開始處的偏移量,通常為0,代表從文件最前方開始映射。offset必須是分頁大小的整數倍(在32位體系統結構上通常是4K)。 [參考](https://welkinchen.pixnet.net/blog/post/41312211-%E8%A8%98%E6%86%B6%E9%AB%94%E6%98%A0%E5%B0%84%E5%87%BD%E6%95%B8-mmap-%E7%9A%84%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) --- * 本次實驗: 1. Raspberry pi 4 2. ubuntu 20.04 LTS * Raspberry pi 4 SOC: [BCM2711 ref](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf) * Raspberry pi pinout:  圖裡的GPIOX,表示是BCM SOC實際的GPIO腳位 * Register Map:  * GPLSEX(設定GPIO功能): :::info Raspberry pi 4 SOC P.84 ::: * GPLSEX(設定GPIO功能): :::info Raspberry pi 4 SOC P.84 ::: * GPSETX(將該位GPIO設置為1) :::info Raspberry pi 4 SOC P.88 ::: * GPCLRX(將該位GPIO設置為1) :::info Raspberry pi 4 SOC P.89 ::: * GPLEVX(讀取該位GPIO的值) :::info Raspberry pi 4 SOC P.89 ::: 第一步,我們將mmap宣告成可讀寫的空間,並offset到GPIO第一個位置。 ```Clike= // mmap int fd = open("/dev/mem", O_RDWR | O_SYNC); mmio_gpio = (volatile unsigned*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASE); close(fd); ``` 至於GPIO地址該如何得知,除了辛苦的翻找device tree。我們可以透過以下指令得知: ``` $ cat /proc/iomem | grep gpio 00000000-00000000 : fe200000.gpio gpio@7e200000 ```  第二步,定義各個暫存器的位址(offset為32bit,也就是GPFSEL0->GPFSEL1 為offset 1個32bit): ```Clike= #define PERI_BASE 0xFE000000 #define GPIO_BASE (PERI_BASE + 0x200000) // offset (32 bits) #define GPFSEL(x) (0x00 + (x) / 10) #define GPSET(x) (0x07 + ((x) << 1)) #define GPCLR(x) (0x0A + ((x) << 1)) #define GPLEV(x) (0x0D + ((x) / 32)) ``` 第三步,設定要控制的GPIO: * input: ```Clike= *(mmio_gpio + GPFSEL((gpio_num))) &= ~(7 << (gpio_num % 10 * 3)); ``` * output (先寫入,在輸出): ```Clike= *(mmio_gpio + GPFSEL((gpio_num))) &= ~(7 << (gpio_num % 10 * 3)); *(mmio_gpio + GPFSEL((gpio_num))) |= (1 << (gpio_num % 10 * 3)); ``` 第四步,寫入與讀取: 1. 寫入1: ```Clike= *(mmio_gpio + GPSET( (gpio_num / 32) )) = 1 << gpio_num; ``` 2. 寫入0: ```CLike= *(mmio_gpio + GPCLR( (gpio_num / 32) )) = 1 << gpio_num; ``` 3. 讀取: ```CLike= (*(mmio_gpio + GPLEV((gpio_num))) & (1 << gpio_num)) >> gpio_num; ``` [完整程式](https://github.com/Raspberry-Pi-4-MCU/gpio_test)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up