1. 程式人生 > >python環境下實現OrangePi Zero寄存器訪問及GPIO控制

python環境下實現OrangePi Zero寄存器訪問及GPIO控制

adc map函數 操作 pack 模式 更改 truct 不可 共享

  最近入手OrangePi Zero一塊,程序上需要使用板子上自帶的LED燈,在網上一查,不得不說OPi的支持跟樹莓派無法相比。自己摸索了一下,實現簡單的GPIO控制方法,作者的Zero安裝的是Armbian系統,使用python寫了一個讀寫寄存器的簡單模塊,通過這個模塊,即可實現對GPIO的控制。

  作者以前使用過STM32的MCU,這類MCU,如果要實現對GPIO的控制,只需要根據datasheet查找相應GPIO寄存器並進行配置,即可實現IO控制,例如,要將內存地址為0x12345678的寄存器全部置為0xFFFFFFFF,只需要一條C語句:

1 (uint32 *)(0x12345678
) = 0xFFFFFFFF;

  但是,這個方法在Linux中行不通,編譯運行的時候,會提示"segmentation fault",這個段錯誤應該就是訪問了不可訪問的內存,這個內存區要麽是不存在的,要麽是受到系統保護的。所以只能使用其他方法。

  首先總結一下實現對OrangePi GPIO控制的兩種方法,第一種是通過Linux內存映射的方式,將實際CPU硬件的內存地址映射到用戶程序的內存空間,再進行操作;第二種是通過sysfs方式控制GPIO,在程序中,操作/sys/class/gpio目錄實現io口的控制。

  這兩種方式都有現成已完成的案例,例如在python環境使用的pyH3庫、C環境的WiringPi庫使用的就是第一種方式;從樹莓派移植的OPi.GPIO庫使用的是第二種方式。其中第二種方式個人感覺更加簡單,因為只需要在用戶的程序裏面讀寫系統目錄的文件,即可實現對GPIO的控制,非常方便,但作者發現這種方式有個嚴重的問題,就是它無法使用那些板子上沒引出來的IO口,因為板載的兩個LED(紅燈和綠燈)分別是使用了PA17和PL10引腳,如果使用第二種方式控制這兩個引腳,會提示“Device or resource busy”的錯誤,通過以下命令:

cat /sys/kernel/debug/gpio

可以看出系統已經占用的IO口如下:

技術分享圖片

第一個GPIO17即為PA17,系統默認已經把該引腳配置為輸出模式,並置為低電平。這裏需要說明一下系統裏對GPIO口的編號方法,系統是按照PA~PL共12組、每組32個引腳的方式對IO口進行統一編號的,如上圖,GPIO-0~GPIO-31為PA口的IO,GPIO-32~GPIO-63為PB口的IO,以此類推。所以上圖最後一行GPIO-362實際就是PL10引腳,即電源的綠燈引腳。

  回歸本文,因為第二種方法無法操作PA17,所以只能使用第一種方法。

  第一種方法已經有現成的實現,通過深入研究庫源碼,內部實際都是通過C的mmap函數來實現CPU的物理地址映射到用戶程序的內存空間。作者習慣使用Pyhon在Linux環境下進行程序開發,pyH3庫使用感覺比較繁瑣。所以特意研究了一下能否在python環境下實現物理地址的映射。實際果然不出所料,C裏有mmap函數,python裏同樣有內置的mmap模塊。說明文檔在這裏:https://docs.python.org/2/library/mmap.html

  其中最重要的就是mmap類的構造函數:

class mmap.mmap(fileno, length[, flags[, prot[, access[, offset]]]])

fileno: 文件描述符,可以是file對象的fileno()方法,或者來自os.open(),在調用mmap()之前打開文件,不再需要文件時要關閉。

length:要映射文件部分的大小(以字節為單位),這個值為0,則映射整個文件,如果大小大於文件當前大小,則擴展這個文件。

flags:MAP_PRIVATE:這段內存映射只有本進程可用;mmap.MAP_SHARED:將內存映射和其他進程共享,所有映射了同一文件的進程,都能夠看到其中一個所做的更改;
prot:mmap.PROT_READ, mmap.PROT_WRITE 和 mmap.PROT_WRITE | mmap.PROT_READ。最後一者的含義是同時可讀可寫。

access:在mmap中有可選參數access的值有

ACCESS_READ:讀訪問。

ACCESS_WRITE:寫訪問,默認。

ACCESS_COPY:拷貝訪問,不會把更改寫入到文件,使用flush把更改寫到

  fileno參數需要指定為系統“/dev/mem”的文件描述符,可以通過open()函數和fileno()方法得到,flags、prot、access參數指定為讀寫訪問即可。

  length和offset參數比較重要,首先是offset參數,這個參數指示從哪個內存地址開始映射,註意,這個數值必須是頁大小的整數倍,在OrangePi Zero中,頁大小為4096字節。根據datasheet,GPIO的內存地址是從0x01C200800開始,但這個值並不是頁大小的整數倍,所以只能往前截取,最近一個頁大小整數倍的地址是0x01C200000,offset就是要設置為這個值。

技術分享圖片

  那還有0x0800的偏移量怎麽辦呢,這個就通過length參數來設定了,length參數指定了從這個offset開始,映射多少字節的物理內存到用戶程序的內存空間,顯然,這個length必須足夠長把整個gpio模塊的寄存器地址全部映射了,才能在用戶程序裏正常訪問GPIO寄存器,這裏設置為兩個頁大小,即8192字節(0x01C20000 ~ 0x01C21FFF),從datasheet看,這個地址空間包含了CCU、PIO、TIMER、OWA、PWM、KEYADC模塊的所有寄存器。

技術分享圖片

  映射之後,可以得到一個mmap類的對象,使用這個對象,我們可以像操作文件一樣對寄存器進行讀寫操作。在以下模塊的代碼中,實現了兩個方法:讀寄存器和寫寄存器。註意在操作寄存器的過程中,有一點必須註意,每次讀寫寄存器必須四字節對齊,即一次讀取或寫入4個字節(所有寄存器都是32位),讀寫的寄存器地址也必須是4的倍數,否則會操作失敗,板子會死機。

import mmap
import struct

class GPIO:
    
#-------------------------------------------------------------------------------------#
#定義GPIO相對0x01C20000的偏移地址
    PIO_ADDR_OFFSET = 0x0800    

#定義GPIOA的寄存器相對0x01C20000的偏移地址
#作者只寫了GPIOA的寄存器定義,如果需要使用其他IO,請參考datasheet在下面增加定義
    PIO_PA_CFG0_REG = PIO_ADDR_OFFSET + 0x00
    PIO_PA_CFG1_REG = PIO_ADDR_OFFSET + 0x04
    PIO_PA_CFG2_REG = PIO_ADDR_OFFSET + 0x08
    PIO_PA_CFG3_REG = PIO_ADDR_OFFSET + 0x0C
    PIO_PA_DATA_REG = PIO_ADDR_OFFSET + 0x10

    PIO_PA_DRV0_REG = PIO_ADDR_OFFSET + 0x14
    PIO_PA_DRV1_REG = PIO_ADDR_OFFSET + 0x18
    PIO_PA_PUL0_REG = PIO_ADDR_OFFSET + 0x1C
    PIO_PA_PUL1_REG = PIO_ADDR_OFFSET + 0x20

#-------------------------------------------------------------------------------------#
    #以下是構造函數和析構函數
    def __init__(self):
        self.m_mmap = None
        self.fd = None

    def __del__(self):
        if(self.m_mmap != None):
            self.m_mmap.close()
        if(self.fd != None):
            self.fd.close()
    
#-------------------------------------------------------------------------------------#
    #以下是成員函數
    def Init(self):
        """
        GPIO初始化函數
        函數會打開/dev/mem文件,並映射從0x01C20000地址開始,共8192字節長度(2頁)的內存空間到用戶的虛擬地址
        返回值:無
        """
        START_ADDR = 0x01C20000
        self.fd = open("/dev/mem", "rb+")
        self.m_mmap = mmap.mmap(self.fd.fileno(), 4096 * 2, mmap.MAP_SHARED, mmap.PROT_WRITE | mmap.PROT_READ, mmap.ACCESS_WRITE, START_ADDR)
        assert self.m_mmap != None,"Init Fails"


    def ReadReg(self,reg_addr):
        """
        讀取一個寄存器的值
        reg_addr:要讀取的寄存器地址(必須為4的倍數),且範圍在2個pagesize內,即小於8192
        返回值:寄存器的值(4字節)
        """
        assert self.m_mmap != None,"Init Fails"
        assert reg_addr%4 == 0,"reg_addr must be mutiple of 4"
        assert 0<=reg_addr<=8192,"reg_addr must be less than 8192,which is 2 pagesize"
        
        self.m_mmap.seek(reg_addr)
        ReadBytes = self.m_mmap.read(4)
        return struct.unpack(L,ReadBytes)[0]


    def WriteReg(self,reg_addr,value):
        """
        寫一個寄存器的值
        reg_addr:要寫入的寄存器地址(必須為4的倍數),且範圍在2個pagesize內,即小於8192
        value:要寫入的值,整形,一次寫入四個字節長度的整數,即0xffffffff
        返回值:無
        """     
        assert self.m_mmap != None,"Init Fails"
        assert reg_addr%4 == 0,"reg_addr must be mutiple of 4"
        assert 0<=reg_addr<=8192,"reg_addr must be less than 8192,which is 2 pagesize"
        assert 0<=value<=0xFFFFFFFF,"value must be less than 0xFFFFFFFF,which is 4 bytes"
        
        self.m_mmap.seek(reg_addr)
        BytesToWrite = struct.pack(L,value)
        self.m_mmap.write(BytesToWrite)
        return

  要使用這個模塊,只需要把這個模塊的py文件放在用戶程序同一個目錄下,直接導入即可,以下是令PA17(紅色LED)閃爍的範例。

  說明:

  1、OPiZero_GPIO是上面定義的模塊的文件名,直接導入使用即可。

  2、PIO_PA_CFG2_REG寄存器的第4~第7位為Pin17的模式配置,配置為001即輸出模式

  3、PIO_PA_DATA_REG寄存器的第17位為Pin17的高底電平輸出控制,這裏采用了一個巧妙的方法,讀取PIO_PA_DATA_REG的值與0x00020000按位異或即可實現第17位的取反。

  4、切勿直接往寄存器裏寫入數據,因為PA口有很多IO用作板子內部使用,直接寫入的話很容易導致其他IO口邏輯輸出錯誤,導致板子死機,作者已親身體驗n次,務必使用讀-修改-寫的模式修改寄存器的值。

import OPiZero_GPIO
import time

#以下為主程序
GPIO = OPiZero_GPIO.GPIO()
GPIO.Init();

#PA17配置為輸出模式
GPIO.WriteReg(GPIO.PIO_PA_CFG2_REG,GPIO.ReadReg(GPIO.PIO_PA_CFG2_REG) | 0x00000010)
while(1):
    GPIO.WriteReg(GPIO.PIO_PA_DATA_REG,GPIO.ReadReg(GPIO.PIO_PA_DATA_REG) ^ 0x00020000)
    time.sleep(0.3)

實際效果如下:   

技術分享圖片技術分享圖片

  最後把源碼附上:

  https://files.cnblogs.com/files/qzrzq1/OPiZero_GPIO.zip

  https://pan.baidu.com/s/1yiely1q_4LPZ4Bs8gyKDGg

python環境下實現OrangePi Zero寄存器訪問及GPIO控制