【玩轉開源】BananaPi R2——移植RPi.GPIO 到 R2
1. 首先給大家介紹一下什麽是RPi.GPIO.
簡單去講,RPi.GPIO就是一個運行在樹莓派開發板上可以通過Python去控制GPIO的一個中間件。
現在我這邊做了一個基礎功能的移植,接下來大家可以跟著我去學習一下RPi.GPIO是如何通過Python去實現控制開發板上的GPIO的。
2. 看一下效果圖:
2.1 硬件實物運行效果
2.2 執行Python腳本打印的log
3. 那麽RPi.GPIO在R2上是如何使用的呢?
3.1 首先在R2上面運行一個Ubuntu鏡像,然後下載代碼:git clone https://github.com/BPI-SINOVOIP/RPi.GPIO
這裏如果不清楚如何安裝一個Ubuntu鏡像到R2,歡迎留言。
3.2 安裝Python環境
1 sudo apt-get update 2 sudo apt-get install python-dev python3-dev
3.3 安裝中間件
sudo python setup.py install
或者是 sudo python3 setup.py install 來安裝
(前者是使用Python2.X,後者是Python3.X)
安裝參考:http://wiki.banana-pi.org/Getting_Started_with_R2#Install_RPi.GPIO(偷偷告訴你們這個wiki我也在維護哦,歡迎大家留言提建議)
3.4 安裝完成後進入到R2 Ubuntu下的路徑:cd /usr/local/bin,你會發現有個g40.py的python文件,我們可以打開簡單看一下(這裏代碼的註釋,是我加的,目的是為了讓大家更好理解這段Python程序):
#!/usr/bin/python import RPi.GPIO as GPIO #這裏導入RPi.GPIO模塊,重命名為GPIO import time #為了方便理解import xxx, 大家可以當作是C語言的#include<xxx.h>
#定義兩組要控制的Led GPIO pin數組 phy_led2 = [8, 10, 12, 16, 18, 22, 24, 26, 28, 32, 36, 38, 40,37, 35, 33, 31, 29, 27, 23, 21, 19, 15, 13, 11, 7, 5, 3]; phy_led = [8, 10, 12, 16, 18, 22, 24, 26, 32, 36, 38, 40, 37, 35, 33, 31, 29, 23, 21, 19, 15, 13, 11, 7, 5, 3]; print ‘Pi Board Information‘ print ‘---------------------‘
#這裏是一個for循環, 從GPIO.RPI_INFO.items裏面讀取數據,並打印出來
for key,val in GPIO.RPI_INFO.items(): print ‘%s => %s‘%(key,val)
#讀取鍵盤輸入 response = raw_input(‘\nIs this board info correct (y/n) ? ‘).upper()
#調用GPIO.setmode方法,並傳入參數GPIO.BOARD
#(這裏大家肯定看得一楞一楞的,這些GPIO.XXX方法,參數究竟是在哪裏定義的,不要急,後面就會介紹) GPIO.setmode(GPIO.BOARD)
#讀取phy_led數組,並調用GPIO.setup方法,傳入參數pin和GPIO.OUT for pin in phy_led: print pin, "GPIO.setup GPIO.OUT" GPIO.setup(pin, GPIO.OUT)
#這裏是一個死循環,意思就是設置GPIO循環輸出高低電平不斷打開,關閉LED while True: for pin in phy_led: GPIO.output(pin, True) print ‘on ‘, pin time.sleep(.1) for pin in phy_led: GPIO.output(pin, False) print ‘off ‘,pin time.sleep(.1) #如果大家對Python不太熟悉,建議可以先去學習一些基礎的語法,這樣有助於理解。
#不過不熟悉也沒有關系,這裏我也會盡量講明白這個Python文件是在做什麽。
3.5 最上層的接口看完了,接下來我們該看中間件是如何實現上面這些接口的調用的
下載代碼:git clone https://github.com/BPI-SINOVOIP/RPi.GPIO
代碼下載完成後,我們打開先簡單看一看,我們從g40.py裏面調用的第一個接口看起:GPIO.RPI_INFO.items()
我的開發環境是Ubuntu,使用的IDE是SourceInsight
我們先搜索一下接口RPI_INFO:
發現這個接口三在Py_gpio.c裏面有出現,我們點進去看一下:
原來這個接口在這裏,那這裏的PyModule_AddObject是幹什麽的呢?先看一下官方解釋:
這個接口實際上是Python調用外部代碼的一種方式,簡單理解就是可以通過 “Module.name” 的方式來調用 "value".
然後我們再來看一下g40.py裏面的這段代碼:
for key,val in GPIO.RPI_INFO.items(): print ‘%s => %s‘%(key,val)
Module的名稱為GPIO,name是"RPI_INFO",這裏使用GPIO.RPI_INFO實際上就調用到了value,根據上面代碼的定義,這個value是board_info,那麽這段代碼實際上就是調用board_info,接下來我們再看看board_info是怎麽定義的.
board_info = Py_BuildValue("{sissssssssss}", "P1_REVISION",rpiinfo.p1_revision, "REVISION",&rpiinfo.revision, "TYPE",rpiinfo.type, "MANUFACTURER",rpiinfo.manufacturer, "PROCESSOR",rpiinfo.processor, "RAM",rpiinfo.ram);
這裏可以看到board_info是從Py_BuildValue這裏獲取的值,那麽Py_BuildValue又是做什麽的呢?先看一下官方的解釋:
意思就是獲取C語言格式的字符串,看起來這裏Python和C開始有聯系咯,我們看看最開始的打印:
看到這裏的打印,和上面Py_BuildValue()裏面的參數對比一下,是不是開始有感覺了;沒錯這裏已經成功從Python調用到C了.
我們接下來再看看g40.py,代碼馬上執行到:GPIO.setmode(GPIO.BOARD),對應的我們去看源碼是怎麽樣的:
首先我們找一下這個BOARD參數,可以看到這個參數是一個int類型的變量;
接下來我們再看看setmode方法:
這裏可以看到setmode方法實際上是調用的py_setmode,我們再看看這個py_setmode是怎麽定義的:
看到這裏實際上我們會發現,其實就是C的寫法了,簡單去看就是返回一個PyObject* 類型的C函數;到這裏我們已經大概學習到Python如何調用C的參數和函數了.
4. 那麽 R2 上的GPIO又是如何實現被控制的呢?
在這裏大家有沒有註意到,當我們運行g40.py的時候,實際上裏面也是運行的C代碼,再挖一層實際上當執行這個Python腳本就相當於就是執行的C代碼,換句話說,就是我們只要實現了如何通過C去控制GPIO,那麽就能實現Python去控制GPIO;那麽我們要如何去實現C代碼控制GPIO呢?
這裏插入兩個知識點:用戶態,內核態
Linux系統運行的時候實際上是分內核態和用戶態的,內核態和用戶態之間相互分隔,但是可以通過一些特殊的接口讓他們之間相互訪問。
比如我們進到Ubuntu系統,GUI,桌面軟件,命令行等等這些實際上都是用戶態的操作。
一般涉及到較深層次的硬件驅動接口時,才會去操作內核態,比如我們開發嵌入式拿到的BSP代碼,去寫的各個硬件驅動代碼一般就是屬於內核部分。
如果你使用過單片機,你可能還記得操作GPIO,直接去給對應的寄存器賦值就好了,沒錯思路是對的;那麽我們如何通過Ubuntu系統去寫芯片GPIO的寄存器呢?
這裏我直接使用mmap的辦法,即內存映射,暫時就不發散用戶態如何調用到內核態的方法了,後續寫關於嵌入式的知識時再介紹.
那麽我們看看mmap是做什麽用處的:
簡單去講,就是虛擬地址映射到實際物理地址的方法;我們知道在單片機上面操作的寄存器地址,實際上就是它的物理地址;但是嵌入式系統不一樣,嵌入式系統有一個內存管理的機制,我們叫MMU,具體MMU的原理暫時也不發散了,後續有機會再介紹(後面會專門再寫一篇關於MMU的文章).
通過查詢芯片的Datasheet,我們可以找到GPIO的實際寄存器地址,然後讀取出來後,就可以像單片機操作GPIO那樣去寫程序了,這裏我貼R2的例子:
我們要操作的GPIO口是這些:
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define MMAP_PATH "/dev/mem" static uint8_t* gpio_mmap_reg = NULL; static int gpio_mmap_fd = 0;
//這裏定義GPIO 原理圖裏面的 PIN int pins[] = {75, 76, 206, 80, 79, 205, 56, 55, 54, 57, 126, 74, 73, 49, 202, 82, 81, 24, 25, 21, 18, 53, 20, 58, 72, 19, 22, 200};
//芯片GPIO寄存器 #define GPIO_DOUT_BASE_OFFSET 0x500 #define GPIO_MODE_BASE_OFFSET 0x760 #define GPIO_REG_BASE 0x10005000 static int gpio_mmap(void) { if ((gpio_mmap_fd = open(MMAP_PATH, O_RDWR|O_SYNC)) < 0) { fprintf(stderr, "unable to open mmap file"); return -1; } gpio_mmap_reg = (uint8_t*) mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED , gpio_mmap_fd, GPIO_REG_BASE); if (gpio_mmap_reg == MAP_FAILED) { perror("foo"); fprintf(stderr, "failed to mmap"); gpio_mmap_reg = NULL; close(gpio_mmap_fd); return -1; } return 0; } int main() { int ret = -1; int pin = 75; if (gpio_mmap()) return -1; printf("set dir\n"); uint32_t tmp; int position = 0; //SET MODE AS GPIO,GPIO模式的值為0 printf("BASEADDR=%X\nSET MODE AS GPIO\n", gpio_mmap_reg); for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ pin = pins[i]; position = gpio_mmap_reg + GPIO_MODE_BASE_OFFSET + (pin / 5) * 16; printf("pin=%d, positon = %X\n", pin, position); tmp = *(volatile uint32_t*)(position); tmp &= ~(1u << ((pin % 5) * 3)); //賦值為0 *(volatile uint32_t*)(position) = tmp; } printf("\nSET DIR AS OUT\n"); for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ pin = pins[i]; if(pin < 199){ position = gpio_mmap_reg + (pin / 16) * 16; }else{ position = gpio_mmap_reg + (pin / 16) * 16 + 0x10; } printf("pin=%d, positon = %X\n", pin, position); tmp = *(volatile uint32_t*)(position); tmp |= (1u << (pin % 16)); *(volatile uint32_t*)(position) = tmp; usleep(100000); } //SET VAULE printf("\nSET VALUE AS HIGH LEVEL\n"); for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ pin = pins[i]; position = gpio_mmap_reg + GPIO_DOUT_BASE_OFFSET + (pin / 16) * 16; printf("pin=%d, positon = %X\n", pin, position); tmp = *(volatile uint32_t*)(position); tmp |= (1u << (pin % 16)); *(volatile uint32_t*)(position) = tmp; usleep(100000); } printf("\nSET VALUE AS LOW LEVEL\n"); for(int i = 0; i < sizeof(pins)/sizeof(int); i++){ pin = pins[i]; position = gpio_mmap_reg + GPIO_DOUT_BASE_OFFSET + (pin / 16) * 16; printf("pin=%d, positon = %X\n", pin, position); tmp = *(volatile uint32_t*)(position); printf("tmp = %X\n", tmp); tmp &= ~(1u << (pin % 16)); printf("tmp = %X\n", tmp); *(volatile uint32_t*)(position) = tmp; usleep(100000); } close(gpio_mmap_fd); }
上面這個例子就實現了用戶態操作GPIO的方法,具體地址,和運算方法跟你的芯片GPIO寄存器定義有關,看到這裏如果有很多東西還是不太明白也沒有關系,因為這裏涉及的知識面稍微多一些,不過不要灰心,自己多實戰,再回過頭來看其實就變簡單了,關於嵌入式的學習也歡迎大家一起留言交流.
更多的實現請大家看github上的commit記錄:https://github.com/BPI-SINOVOIP/RPi.GPIO/commits/master
【玩轉開源】BananaPi R2——移植RPi.GPIO 到 R2