1. 程式人生 > >【玩轉開源】BananaPi R2——移植RPi.GPIO 到 R2

【玩轉開源】BananaPi R2——移植RPi.GPIO 到 R2

機會 tin 循環輸出 nal 腳本 evel 3.1 我們 api

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