[Lチカ] Raspberry PiでC言語からSoCのレジスタを操作してGPIOを制御する

  • 31
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

:pineapple: はじめに

Raspberry Piを極めるため(?)、WiringPiを使わずLEDをチカチカさせたのでメモします。なお、この記事はPi / Pi 2両方に対応していますが、手元にPi 2しかないので、Piの検証は行っていません。動作しない OR 間違いを発見した場合はご指摘ください。また、OSはRASPBIAN JESSIEを使用しています。

% uname -a ; gcc --version | head -n 1
Linux raspberrypi 4.1.6-v7+ #810 SMP PREEMPT Tue Aug 18 15:32:12 BST 2015 armv7l GNU/Linux
gcc (Raspbian 4.8.4-1) 4.8.4

GPIOの制御は単純?

この記事を書き始めるまで、GPIOの制御は複雑に違いない、WiringPiの中身なんて知りたくもないと思っていたのですが、実際にはGPIOの制御は単純でした。レジスタに値を書き込む、それだけです。

レジスタに値を書き込むと、その値に応じてGPIOの電圧を変化させたり、GPIOから電圧を読み取ったりできます。

レジスタ is 何

レジスタ (コンピュータ) - Wikipediaによると:

ペリフェラルレジスタは、ペリフェラル(プログラマブル・カウンタや割り込み制御、シリアル通信ポートなどのハードウェア)の動作を設定したり、動作状況を読み出したりするためのレジスタである。これらのレジスタはプロセッサ内のレジスタとは異なり、プロセッサからアクセスできるアドレス空間の一部に配置される。

SSDやHDDなどのストレージ、そして、この記事の読者は確実に16GBは積んでいるであろうメモリと並ぶ記憶領域がレジスタです。レジスタはSoCに内蔵されており、ハードウェアを制御するためのレジスタをPeripheral Register(ペリフェラルレジスタ)と呼びます。

そして、このレジスタをプログラムが制御できるようメモリ上のどこかにアドレスをマッピングします。そのアドレスに値を書き込むことで、レジスタに値が書き込まれたとみなされ、GPIOを含むハードウェアを制御できるようになります。

GPIOを制御する

さて、GPIOの制御方法はわかりましたが、メモリ上のどこに、とのような値を書き込めばいいのか分かりません。ということで、Raspberry Piが搭載しているSoC、BCM2835のドキュメントを参照します。メモリについてはChapter 1にて説明されています。

1.2.3 ARM physical addresses

Physical addresses range from 0x 2000 0000 to 0x 20FF FFFF for peripherals. The busaddresses for peripherals are set up to map onto the peripheral bus address range starting at 0x 7E00 0000. Thus a peripheral advertised here at bus address 0x 7Enn nnnn is available atphysical address 0x 20nn nnnn.

GPIOを含む周辺機器、USBやSPI、I2Cなどの制御用に、物理アドレス0x 2000 0000から0x 20FF FFFF(実際には0x 7E00 0000から0x 7EFF FFFF)の範囲でアドレスがマッピングされるそうです。

GPIOに対応するレジスタのアドレスを調べる

次に、アドレス0x 7E00 0000から0x 7EFF FFFFの内、GPIOに対応するアドレスを調べます。GPIOについてはChapter 6にて説明されています。

6 General Purpose I/O (GPIO)

There are 54 general-purpose I/O (GPIO) lines split into two banks. All GPIO pins have atleast two alternative functions within BCM. The alternate functions are usually peripheral IOand a single peripheral may appear in each bank to allow flexibility on the choice of IOvoltage.

6.1 Register View

The GPIO has 41 registers.

Address Field Name Description R/W
0x 7E20 0000 GPFSEL0 GPIO Function Select 0 R/W
0x 7E20 0004 GPFSEL1 GPIO Function Select 1 R/W
0x 7E20 0008 GPFSEL2 GPIO Function Select 2 R/W
0x 7E20 000C GPFSEL3 GPIO Function Select 3 R/W
0x 7E20 0010 GPFSEL4 GPIO Function Select 4 R/W
0x 7E20 0014 GPFSEL5 GPIO Function Select 5 R/W
0x 7E20 0018 - Reserved -
0x 7E20 001C GPSET0 GPIO Pin Output Set 0 W
0x 7E20 0020 GPSET1 GPIO Pin Output Set 1 W
0x 7E20 0024 - Reserved -
0x 7E20 0028 GPCLR0 GPIO Pin Output Clear 0 W
0x 7E20 002C GPCLR1 GPIO Pin Output Clear 1 W
... ... ... ...
0x 7E20 00B0 - Test R/W

ということで、GPIOに対応するレジスタは41個あり、アドレス0x 7E20 0000から0x 7E20 00B0の範囲にマッピングされていることが分かりました。このアドレスに値を書き込むことでGPIOを制御できます。

GPIOに対応するレジスタに書き込む値を調べる

次は、どのような値を書き込んでGPIOを制御するのか調べます。引き続きChapter 6を参照します。

GPIO Function Select 0

The function select registers are used to define the operation of the general-purpose I/O pins. Each of the 54 GPIO pins has at least two alternative functions as defined in section16.2. The FSEL{n} field determines the functionality of the nth GPIO pin. All unusedalternative function lines are tied to ground and will output a “0” if selected. All pins resetto normal GPIO input operation. FSCL0 to FSCL9

Range (bit) Description R/W R
31 - 30 Reserved - -
29 - 27 FSEL8 FSEL9 - Function Select 9 R/W 0
26 - 24 FSEL8 FSEL8 - Function Select 8 R/W 0
23 - 21 FSEL7 FSEL7 - Function Select 7 R/W 0
20 - 18 FSEL6 FSEL6 - Function Select 6 R/W 0
17 - 15 FSEL5 FSEL5 - Function Select 5 R/W 0
14 - 12 FSEL4 FSEL4 - Function Select 4 R/W 0
11 - 9 FSEL3 FSEL3 - Function Select 3 R/W 0
8 - 6 FSEL2 FSEL2 - Function Select 2 R/W 0
5 - 1 FSEL1 FSEL1 - Function Select 1 R/W 0
2 - 0 FSEL0 FSEL0 - Function Select 0 R/W 0
Bit Description
000 GPIO Pin is an input
001 GPIO Pin is an output
010 GPIO Pin takes alternate function 5
011 GPIO Pin takes alternate function 4
100 GPIO Pin takes alternate function 0
101 GPIO Pin takes alternate function 1
110 GPIO Pin takes alternate function 2
111 GPIO Pin takes alternate function 3

GPIO 1本につき3 bitの値が対応し、その3 bitでGPIOの機能を選択できることが分かりました。

GPIO制御のまとめ

以上をまとめると

  1. GPIOは54本ある
    • GPIO 00 から GPIO 09 は、アドレス0x 7E20 0000に対応。
    • GPIO 10 から GPIO 19 は、アドレス0x 7E20 0004に対応。
    • ...
  2. GPIOに対応したアドレスに値を書き込んで制御する。
    • 値は 32 bit
    • 3 bitのフラグでGPIOの機能を選択できる。
      • 0b 000 GPIOを入力に設定。
      • 0b 001 GPIOを出力に設定。
      • ...

詳細を知りたい場合は、PDFを参照してください。

LEDをチカチカさせる

GPIOの制御方法が分かりました。では、例として、GPIO 23に接続されたLEDを1秒間点灯させてみます。

  1. アドレス 0x 7E20 0008 に、値 0x 0000 0200 を書き込む。
    • 32 bit の値の内、GPIO 23 は 11 - 9 の 3 bit に対応。
    • 0b 001は出力に対応。
    • 値を求める。
      • 0b 00 000 000 000 000 000 000 001 000 000 000
      • => 0b 0000 0000 0000 0000 0000 0010 0000 0000
      • => `0x 0000 0200
    • GPIO 23が出力として設定される。
  2. アドレス 0x 7E20 001C に、値 0x 0800 0000 を書き込む。
    • 値を求める
      • ビットの位置がGPIOの番号に対応する。
      • 0b 0000 0000 1000 0000 0000 0000 0000 0000
      • => 0x 0080 0000
    • GPIO 23の電圧がHighになる
  3. 1秒 待つ
  4. アドレス 0x 7E20 0028 に、値 0x 0080 0000 を書き込む。
    • 値の求め方は2. と同様
    • GPIO 23の電圧がLowになる
  5. アドレス 0x 7E20 0008 に、値 0x 0000 0000 を書き込む。
    • 0b 000がデフォルトの値。
    • 0x 0000 0000を書き込んで、GPIO 23を元に戻す。

以上の手順で、GPIO 23に接続されたLEDが1秒間点灯します。

Lチカプログラム

GPIOの制御方法が分かったので、これをプログラムにします。blink.hblink.cmain.cを作成してください。

blink.h

#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

#define BLOCK_SIZE      4 * 1024
#define PERIPHERAL_BASE 0x3F000000
#define GPIO_BASE       PERIPHERAL_BASE + 0x00200000

typedef struct {
  unsigned long         gpio_base;
  int                   memory_fd;
  void                  *map;
  volatile unsigned int *addr;
} rpi_gpio;

int MapGPIO(rpi_gpio *gpio);
void UnmapGPIO(rpi_gpio *gpio);
void BlinkLED(rpi_gpio *gpio);

補足:

  • GPIO_BASE = 0x3F200000ではダメ?
    • 問題ない。
    • ただし、PiとPi 2でアドレスが異なるので注意。
      • Pi: GPIO_BASE = 0x20200000
      • Pi 2: GPIO_BASE = 0x3F200000
    • ということでGPIO_BASE = PERIPHERAL_BASE = 0x00200000とした。

blink.c

#include "blink.h"

int MapGPIO(rpi_gpio *gpio) {
  gpio->memory_fd = open("/dev/mem", O_RDWR|O_SYNC);

  if(gpio->memory_fd < 0) {
    perror("Failed to open /dev/mem, try change permission.");
    return 1;
  }

  gpio->map = mmap(
    NULL,
    BLOCK_SIZE,
    PROT_READ|PROT_WRITE,
    MAP_SHARED,
    gpio->memory_fd,
    gpio->gpio_base
  );

  if(gpio->map == MAP_FAILED) {
    perror("mmap");
    return 1;
  }

  gpio->addr = (volatile unsigned int *)gpio->map;
  return 0;
}

void UnmapGPIO(rpi_gpio *gpio) {
  munmap(gpio->map, BLOCK_SIZE);
  close(gpio->memory_fd);
}

void BlinkLED(rpi_gpio *gpio) {
  int n = 5;

  *(gpio->addr + 2)  = 0x00000200;

  while(n -= 1) {
    *(gpio->addr + 7)  = 0x00800000;
    usleep(500 * 1000);
    *(gpio->addr + 10) = 0x00800000;
    usleep(500 * 1000);
  }

  *(gpio->addr + 2)  = 0x00000000;
}

main.c

#include "blink.h"

int main() {
  rpi_gpio gpio = {GPIO_BASE};
  int map_status;

  map_status = MapGPIO(&gpio);
  if(map_status) {
    printf("Failed to blink LED.\n");
    return map_status;
  }

  BlinkLED(&gpio);
  UnmapGPIO(&gpio);
  return 0;
}

コンパイル

作成したプログラムをコンパイルします。

% gcc -c blink.c
% gcc main.c blink.o -o ./blinkLED

コンパイルが成功するとblinkLEDが作成されます。

% file blinkLED
blinkLED: ELF 32-bit LSB executable, ARM, version 1 (SYSV) ...(省略)

テストを始める前に

テストを始める前に、GPIOのピンアサインとLEDの配線を確認します

GPIOピンの番号について

Lチカをテストする前にGPIOピン番号を確認しておきます。次の図はGPIOのピンアサインを示しています。基板上のピン番号とGPIOピンの番号には関係がないので注意してください。

        +3V3 1  2   +5V
GPIO2   SDA1 3  4   +5V
GPIO3   SCL1 5  6   GND
GPIO4   GCLK 7  8   TXD0  GPIO14
         GND 9  10  RXD0  GPIO15
GPIO17  GEN0 11 12  GEN1  GPIO18
GPIO27  GEN2 13 14  GND
GPIO22  GEN3 15 16  GEN4  GPIO23
        +3V3 17 18  GEN5  GPIO24
GPIO10  MOSI 19 20  GND
GPIO9   MISO 21 22  GEN6  GPIO25
GPIO11  SCLK 23 24  CE0_N GPIO8
         GND 25 26  CE1_N GPIO7
EEPROM ID_SD 27 28  ID_SC EEPROM
GPIO5        29 30  GND
GPIO6        31 32        GPIO12
GPIO13       33 34  GND
GPIO19       35 36        GPIO16
GPIO26       37 38        GPIO20
         GND 39 40        GPIO21

LEDの配線

次のように配線してください。

GPIO 23 ---[抵抗]---[LED]
                      |
GND ------------------*

抵抗は10 Ohm程度でOKです。LEDの足の短いほうをGNDに接続してください。

テスト

感動の瞬間です。blinkLEDを実行しましょう。

% ./blinkLED
Failed to open /dev/mem, try change permission.: Permission denied
Failed to blink LED.

おっと、sudoを忘れていました。

% sudo ./blinkLED

LEDが4回点滅すれば成功です。

点滅しなかった場合は、LEDが正しく接続されているか確認し、blink.hPERIPHERAL_BASEが正しいか確認してください。Raspberry Piは0x20000000、Raspberry Pi 2は0x3F000000です。

Enjoy

Enjoy チカチカ

参考

  1. Low Level Programming of the Raspberry Pi in C
  2. BROADCOM BCM2835.pdf
  3. RPi GPIO Code Samples - eLinux.org
  4. RPi Low-level peripherals - eLinux.org
  5. raspberrypi.org BCM2836
  6. raspberrypi.org BCM2835
  7. Raspberry Pi2でGPIO制御(メモリマップトレジスタ)