GPIOの使い方

ZybqBerryのGPIOピン

ZynqBerryには、Rasberry Piと同様、図にあるように40ピンのGPIOが搭載されており、FPGA(PL部)とARMコア(PS部)の両方からアクセスできるようになっています。

また40本あるGPIOのピン配置は次のようになっています。Zynqberryの初期デザインでは、GPIO14とGPIO15ピンは使えない設定になっています。

 

Xilinxによると、PSからGPIOなどの外部ピンへはMIO(Multiplexed I/O)とEMIO(Extend MIO)を経由してアクセスすることができます。EMIOではMIOの外部ピンに加えて、PLを経由しPLのI/Oピンにアクセスすることができます。今回はEMIOを経由しPLにつながったGPIOをPS部分から制御しする方法を紹介します。

 

GPIOをつかってLチカ

GPIOのピンにLEDをつなぎ、Lチカをしてみましょう。GPIO 2ピンにLEDの+端子をGNDへ-端子を接続します。

GPIOをPSから制御するための仮想ファイルが/sys/class/gpioに用意されています。基本的にはRasberry PiでGPIOを制御する方法と同様に行うことができます。しかし Zynqで使えるGPIOは全部で118ピン分あり、それぞれ/sys/class/gpio/gpio906~/sys/class/gpio/gpio1023までに割り振られています。その中でも906~959まではMIOを経由したGPIO、960~1023はEMIOを経由したGPIOに割り振られているようです。今回はEMIO経由でGPIOの2番ピンへアクセスします。

 

GPIOのピン番号と制御用仮想ファイルの番号の対応は、

  • ピン番号が  2~13の場合:"ピン番号+958"番の仮想ファイル
  • ピン番号が16~26の場合:"ピン番号+956"番の仮想ファイル

となります(繰り返しになりますが、GPIO 14と15のピンへのアクセスはZynqberryの初期デザインではできません)

 

端末からの制御

/sys/class/gpioディレクトリの中身を確認してみます。

root@zynqberry ~$ ls /sys/class/gpio    

export  gpiochip906  unexport    

このexportへ使いたいGPIOの番号を書き出すと、そのGPIOを使うための仮想ファイルが用意されます。今回はGPIO2へのアクセスなので960番を書き出します。

root@zynqberry ~$ echo 960 > /sys/class/gpio/export

root@zynqberry ~$ ls /sys/class/gpio/

export  gpio960  gpiochip906  unexport

新しくできた/sys/class/gpio/gpio960の中身は

root@zynqberry ~$ ls /sys/class/gpio/gpio960    

active_low  device  direction  edge  power  subsystem  uevent  value    

となっています。directionはGPIOの入出力の制御、valueはGPIOの値(1ならHigh、0ならLow)の制御に使います。まずGPIO 2を出力用にするため

root@zynqberry ~$ echo "out" >  /sys/class/gpio/gpio960/direction    

root@zynqberry ~$ cat /sys/class/gpio/gpio906/direction    

out

となればOKです。この状態でvalueに1や0を書き出すと、GPIOに接続したLEDを点滅させることができます。

root@zynqberry ~$ echo 1 >  /sys/class/gpio/gpio960/value 

root@zynqberry ~$ echo 0 >  /sys/class/gpio/gpio906/value

プログラムからの制御

C言語からGPIOを制御する方法を紹介します。この方法は2種類あり、1つは/sys/class/gpioの仮想ファイルをC言語からアクセスする方法、もう1つは/dev/memからGPIOの物理アドレスへアクセスする方法です。

/sys/class/gpioを使う方法

Linuxのシステムコールを使い、/sys/class/gpioにある仮想ファイルを操作します。以下にC言語のソースコードを示します。

#include
#include
#include
#include
#include
#include
 
int main(){
  int fd;
  fd = open("/sys/class/gpio/export", O_WRONLY);
  if(fd < 0){
    printf("export\n");
    perror("open");
    return -1;
  }
 
  write(fd, "962",3);
  close(fd);
 
  fd = open("/sys/class/gpio/gpio962/direction", O_RDWR);
  if(fd < 0){
    printf("dir\n");
    perror("open");
    return -1;
  }
  write(fd, "out", 3);
  close(fd);
 
  fd = open("/sys/class/gpio/gpio962/value", O_RDWR);
  if(fd < 0){
    printf("value\n");
    perror("open");
    return -1;
  }
 
  int i;
  for(i=0;i<10;i++){
    write(fd, "1", 1);
    usleep(5000*100);
    write(fd,"0",1);
    usleep(5000*100);
  }
 
  close(fd);
 
  fd = open("/sys/class/gpio/unexport", O_RDWR);
  write(fd,"962",3);
  close(fd);
  return 0;
}

 

 

/dev/memを使う方法

/dev/memを使い、GPIOの物理アドレスに直接データを書き込む方法を紹介します。GPIOマップされているアドレスは

root@zynqberry ~$ cat /proc/iomem | grep gpio

e000a000-e000afff : /amba/gpio@e000a000

で確認できます。XilinxのドキュメントによるとGPIOは0XE000A000にマップされていますが、上で得た結果と同じです。このアドレスをベースとしてGPIO制御に必要なレジスタがいくつか用意されています。詳しくは参考文献2をご覧ください。このうち今回必要なものは以下の通りです。

 

レジスタ名

機能

オフセット値
DIRM_2 GPIO入出力。0が入力、1が出力 0x00000284
OEN_2 出力イネーブル。 0x00000288
DATA_2 GPIOピンへの出力値。 0x00000048

 

これらのレジスタは32ビットとなっており、各ビットごとにGPIOピンが割り当てられています。今回のようにGPIO 2ピンを操作する場合、上記レジスタの(下から)1ビット目を操作します。この他にもDIRM_0やOEN_3といったレジスタが用意されています。*_0と*_1のレジスタはGPIOをMIOから操作する際に、*_2と*_3のレジスタはEMIOからGPIOを操作する際に使います。次のソースコードはGPIO 2に接続したLEDを10回点滅させるプログラムです。注意点として、/dev/memからGPIOを制御する際は、/sys/class/gpioで制御したいGPIOをexportしておかなければなりません。つまり、下のコードを使ってLEDを点滅させるには、/sys/class/gpio/exportに960を書き込んでからプログラムの実行が必要になります。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
 
#define GPIO_BASE 0xE000A000 // GPIOのベースアドレス
#define BLOCK_SIZE 4096
 
/**************************************************************
ベースアドレスに対するオフセット値の1/4にアクセスする必要があるので注意。
ex)  DIRM_2は0x00000284がオフセット値だが、プログラム上では
       0x00000284/4 = 0x000000A1
       にアクセスする。
**************************************************************
 
/**************************************************************
 GPIOの出力方向を設定するためのアドレス
 0: input
 1: output
**************************************************************/
#define DIRM_0              0x00000081 
#define DIRM_1              0x00000091
#define DIRM_2              0x000000A1
#define DIRM_3              0x000000B1
 
/**************************************************************
 GPIO出力 イネーブルのアドレス
 0: Disable
 1: Enable
**************************************************************/
#define OEN_0               0x00000082
#define OEN_1               0x00000092
#define OEN_2               0x000000A2
#define OEN_3               0x000000B2
 
/**************************************************************
 GPIOへ出力するデータ
**************************************************************/
#define DATA_0              0x00000010
#define DATA_1              0x00000011
#define DATA_2              0x00000012
#define DATA_3              0x00000013
 
#define REG(address) *(volatile unsigned *)(address)
 
volatile unsigned *gpio; 
 
int main(){
  int fd;
  char *gpio_map;
 
  fd = open("/dev/mem", O_RDWR | O_SYNC);
  if(fd < 0){
    perror("open");
    return -1;
  }
 
  gpio_map = mmap(NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_BASE);
  close(fd);
  if(gpio_map == MAP_FAILED){
    perror("mmap");
    return -1;
  } 
  gpio = (volatile unsigned *) gpio_map;
 
  // EMIO からGPIO 2を操作する。
  // 出力方向へ設定
  REG(gpio + DIRM_2) = 0x1;
 
  // 出力をEnableに
  REG(gpio + OEN_2) = 0x1;
 
  // 0.2msごとにLEDを10回点滅させる
  int i = 0;
  for(i=0;i<10;i++){
    REG(gpio + DATA_2) = 0x1;  // 点灯
    usleep(200*1000);
    REG(gpio + DATA_2) = 0x0;  // 消灯
    usleep(2000*100);
  }
 
  munmap(gpio_map, BLOCK_SIZE);
  return 0;
}

 

参考文献

https://japan.xilinx.com/video/soc/mio-emio-configuration-zynq-7000.html

https://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf