Sipeed Lichee ZeroでLチカとか(GPIO操作)

#概要
Sipeed Lichee ZeroでLチカとスイッチの読み取りをやってみました。
恐らくLichee Nanoでも同じような感じで出来るんじゃないかな?

#方法
GPIOにアクセスする方法としては大まかにデバイスファイルを使う方法とメモリアクセスして操作する方法がある。デバイスファイルを使う方法は手軽だが速度が遅い。メモリアクセスして操作する方法は早いが結構面倒。一長一短やなぁ。

###方法１　デバイスファイルを使う
基板上のフルカラーLEDはPG0～PG2に繋がっている。この番号に対応する192～194の番号でデバイスファイルにアクセスすればフルカラーLEDが点灯する。
ただこのLEDアノードコモンなので0で点灯1で消灯になる。そもそも初期化でポートは0になるので初期化したら点灯する。

```console
# echo 192 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio192/direction 
# echo 1 > /sys/class/gpio/gpio192/value
# echo 0 > /sys/class/gpio/gpio192/value
```
PG3に接続したスイッチの値を読みたい場合は下記の様にすれば0か1が返ってくる。
(スイッチにはプルアップ抵抗が必要です。)

```console
# echo 195 > /sys/class/gpio/export
# echo in > /sys/class/gpio/gpio195/direction 
# cat /sys/class/gpio/gpio195/value
```

ポート番号とデバイスファイルの番号の対応は公式ドキュメントに書かれているのでそれで確認できる。
http://zero.lichee.pro/%E9%A9%B1%E5%8A%A8/GPIO_file.html


###方法2　メモリアクセスを使う
公式のドキュメントを読む限りこの方法でGPIOを操作出来るライブラリがあるような事が書かれているが何処にも見つけられなかった。コミュニティの方でも同じような質問が数年前にあったが無いという結論に達していたので恐らく無いのだろう。
なので欲しくなって作ったが初めての事だったので結構大変だった。あまり情報無いし…
機能としてはポートの初期化・設定と入出力のみ。割り込みとかは未実装。
量も少ないので全部ヘッダファイルの中に書いてしまった。

```C:test_io.h
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

/* ペリフェラルレジスタの物理アドレス(BCM2835の仕様書より) */
#define REG_ADDR_BASE        (0x01C20800)       /* bcm_host_get_peripheral_address()の方がbetter */
#define REG_ADDR_GPIO_BASE 0x1C20000
#define REG_ADDR_GPIO_LENGTH 0x1000
#define PORT_OFFSET 0x800
#define REG_ADDR_GPIO_GPFSEL_0 0x0000 + PORT_OFFSET
#define REG_ADDR_GPIO_OUTPUT_DATA_0 0x10 + PORT_OFFSET

#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printf("DUMP = %08X\n", REG(addr));

#define IN 0
#define OUT 1
#define DISABLE 2

#ifndef LICHEEIO_H
#define LICHEEIO_H

int io_init(void);
int io_release(void);
int port_no_check(int port, int pin);
int setup_gpio(char *pin_no, int io_set);
int output_gpio(char *pin_no, int hl_set);
int input_gpio(char *pin_no);

#endif /* LICHEEIO_H */


int address;    /* GPIOレジスタへの仮想アドレス(ユーザ空間) */
int fd;


int io_init(void){
    /* メモリアクセス用のデバイスファイルを開く */
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        perror("open");
        return -1;
    }

    // long sz = sysconf(_SC_PAGESIZE);
    // printf("%08X", sz);

    /* ARM(CPU)から見た物理アドレス → 仮想アドレスへのマッピング */
    address = (int)mmap(0, REG_ADDR_GPIO_LENGTH, 
                        PROT_READ | PROT_WRITE, MAP_SHARED, 
                        fd, REG_ADDR_GPIO_BASE);
    if (address == (int)MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    return 0;
}

int io_release(void){
    /* 使い終わったリソースを解放する */
    munmap((void*)address, REG_ADDR_GPIO_LENGTH);
    close(fd);
    return(0);
}


int port_no_check(int port, int pin){
    int err_F = 0;
    switch (port){
    case 1:
        if(pin < 0 || pin > 9)
            err_F = 1; 
        break;
    case 2:
        if(pin < 0 || pin > 3)
            err_F = 1; 
        break;
    case 4:
        if(pin < 0 || pin > 24)
            err_F = 1; 
        break;
    case 5:
        if(pin < 0 || pin > 6)
            err_F = 1; 
        break;
    case 6:
        if(pin < 0 || pin > 5)
            err_F = 1; 
        break;
    default:
        err_F = 1; 
        break;
    }
    return(err_F);
}


int setup_gpio(char *pin_no, int io_set){
    int err_F = 0;
    int port ,pin, reg_no;
    port = pin_no[0] - 'A';
    
    pin = atoi(pin_no + 1);
    if(port_no_check(port, pin) == 1){
        printf("errno");
        return(-1);
    }
    reg_no = pin / 8;
    if(io_set == IN){
        REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24 + reg_no) &= ~(0x07 << pin * 4);
    }
    else if (io_set == OUT){
        REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24 + reg_no) |= (0x1 << pin * 4);
        REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24 + reg_no) &= ~(0x6 << pin * 4);
        //REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) = 0x77777777;
    }
    else if (io_set == DISABLE){
        REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24 + reg_no) |= (0x07 << pin * 4);
    }
    //DUMP_REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24);
    return(0);
}


int output_gpio(char *pin_no, int hl_set){
    int port, pin;
    port = pin_no[0] - 'A';
    pin = atoi(pin_no + 1);
    if(port_no_check(port, pin) == 1){
        printf("errno");
        return(-1);
    }
    if(hl_set == 0){ //in
        REG(address + REG_ADDR_GPIO_OUTPUT_DATA_0 + port * 0x24) &= ~(0x1 << pin);
    }
    else if (hl_set == 1){ //out
        REG(address + REG_ADDR_GPIO_OUTPUT_DATA_0 + port * 0x24) |= (0x1 << pin);
    }
    //DUMP_REG(address + REG_ADDR_GPIO_OUTPUT_DATA_0 + port * 0x24);
    return(0);
}


int input_gpio(char *pin_no){
    int port, pin, data;
    port = pin_no[0] - 'A';
    pin = atoi(pin_no + 1);
    if(port_no_check(port, pin) == 1){
        printf("errno");
        return(-1);
    }
    
    data = REG(address + REG_ADDR_GPIO_OUTPUT_DATA_0 + port * 0x24) & (0x1 << pin);
    //DUMP_REG(address + REG_ADDR_GPIO_OUTPUT_DATA_0 + port * 0x24);
    if(data != 0){
        data = 1;
    }
    //printf("data = %08X\n", data);
    return(data);
}
```

青と緑が交互に点滅するサンプルプログラム。

```C:Ltika.c
#include "test_io.h"
#include <unistd.h>

int main(){
    int sta, data;
    printf("%d\n", io_init());

    sta += setup_gpio("G0", OUT);
    sta += setup_gpio("G1", OUT);
    printf("sta=%d\n", sta);

    while (1){
        output_gpio("G0", 0);
        output_gpio("G1", 1);
        usleep(1e5);
        output_gpio("G1", 0);
        output_gpio("G0", 1);
        usleep(1e5);
    }
    io_release();
}

```
コンパイル・実行

```console
gcc Ltika.c -o Ltika -lm -std=gnu99
sudo ./Ltika
```

PG3に接続したスイッチを押すと点灯するサンプルプログラム
(スイッチにはプルアップ抵抗が必要です。)

```C:sw.c
#include "test_io.h"
#include <unistd.h>

int main(){
    int sta, data;
    printf("%d\n", io_init());

    sta = setup_gpio("G0", OUT);
    sta = setup_gpio("G1", DISABLE);
    //sta = setup_gpio("G2", OUT);
    sta = setup_gpio("G3", IN);
    printf("%d\n", sta);
    //output_gpio("G1", 1);
    //output_gpio("G2", 1);
    
    while(1){
        data = input_gpio("G3");
        output_gpio("G0", data);
        usleep(1e3);
    }
    io_release();
}
```
コンパイル・実行

```console
gcc sw.c -o sw -lm -std=gnu99
sudo ./sw
```
※std=gnu99オプションはusleep(1e3)を使う為に付けています。

あとこのプログラムPG以外では実際に部品を繋いでのテストはまだしていないのでもしかしたら上手く動かない事があるかもしれません。
一応レジスタの状態が変化しているかはある程度確認してはいますが…

#おまけ
このプログラムはポート関係のレジスタの状態一覧表示プログラムです。

```C:pin_status.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

/* ペリフェラルレジスタの物理アドレス(BCM2835の仕様書より) */
#define REG_ADDR_BASE        (0x01C20800)       /* bcm_host_get_peripheral_address()の方がbetter */
#define REG_ADDR_GPIO_BASE 0x1C20000
#define REG_ADDR_GPIO_LENGTH 0x1000
#define PORT_OFFSET 0x800
#define REG_ADDR_GPIO_GPFSEL_0 0x0000 + PORT_OFFSET

#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printf("DUMP = %08X\n", REG(addr));



int address;    /* GPIOレジスタへの仮想アドレス(ユーザ空間) */
int fd;


int io_init(void){
    /* メモリアクセス用のデバイスファイルを開く */
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        perror("open");
        return -1;
    }

    // long sz = sysconf(_SC_PAGESIZE);
    // printf("%08X", sz);

    /* ARM(CPU)から見た物理アドレス → 仮想アドレスへのマッピング */
    address = (int)mmap(0, REG_ADDR_GPIO_LENGTH, 
                        PROT_READ | PROT_WRITE, MAP_SHARED, 
                        fd, REG_ADDR_GPIO_BASE);
    if (address == (int)MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    return 0;
}

int io_release(void){
    /* 使い終わったリソースを解放する */
    munmap((void*)address, REG_ADDR_GPIO_LENGTH);
    close(fd);
    return(0);
}



// int setup_gpio(char *pin_no, int io_set){
//     int err_F = 0;
//     int port ,pin;
//     port = pin_no[0] - 'A';
    
//     pin = atoi(pin_no + 1);
//     if(port_no_check(port, pin) == 1){
//         printf("errno");
//         return(-1);
//     }
//     if(io_set == IN){
//         REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) &= ~(0x07 << pin * 4);
//     }
//     else if (io_set == OUT){
//         REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) |= (0x1 << pin * 4);
//         REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) &= ~(0x6 << pin * 4);
//         //REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) = 0x77777777;
//     }
//     else if (io_set == DISABLE){
//         REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24) |= (0x07 << pin * 4);
//     }
//     //DUMP_REG(address + REG_ADDR_GPIO_GPFSEL_0 + port * 0x24);
//     return(0);
// }


int bit2int(int *bit_data, int *int_data){
    for(int i = 0; i < 4; i++){
        int mask = 0x0007, x;
        for(int j = 0; j < 8; j++){
            x = bit_data[i] & mask;
            //printf("%08x  ", x);
            //printf("%08x  ", x >> j * 4);
            int_data[i * 8 + j] = x >> j * 4;
            //printf("mask = %08X\n", mask);
            mask = mask << 4;
        }
    }
    //printf("\n");
}

int main(){
    io_init();
    int read_data[4];
    int port_status[5][32]; 
    for(int i = 0; i < 5; i++){
        if(i == 0){
            read_data[0] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 1 * 0x24);
            read_data[1] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 1 * 0x24 + 1);
            bit2int(read_data, port_status[0]);
        }
        if(i == 1){
            read_data[0] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 2 * 0x24);
            bit2int(read_data, port_status[1]);
        }
        if(i == 2){
            read_data[0] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 4 * 0x24);
            read_data[1] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 4 * 0x24 + 1);
            read_data[2] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 4 * 0x24 + 2);
            read_data[3] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 4 * 0x24 + 3);
            bit2int(read_data, port_status[2]);
        }
        if(i == 3){
            read_data[0] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 5 * 0x24);
            bit2int(read_data, port_status[3]);
        }
        if(i == 4){
            read_data[0] = REG(address + REG_ADDR_GPIO_GPFSEL_0 + 6 * 0x24);
            bit2int(read_data, port_status[4]);
        }


    }
    for(int i = 0; i < 5; i++){
        int indention = 7;
        if(i == 0){
            printf("PB\n");
        }
        else if(i == 1){
            printf("PC\n");
        }
        else if(i == 2){
            printf("PE\n");
        }
        else if(i == 3){
            printf("PF\n");
        }
        else if(i == 4){
            printf("PG\n");
        }
        for(int j = 0; j < 32; j++){            
            if(port_status[i][j] == 7){
                printf("%02d:無効　　", j);
            }
            else if(port_status[i][j] == 0){
                printf("%02d:入力　　", j);
            }
            else if(port_status[i][j] == 1){
                printf("%02d:出力　　", j);
            }
            else{
                printf("%02d:他%02d　　", j, port_status[i][j]);
            }
            if((i == 0 && j == 9) || (i == 1 && j == 3) || 
               (i == 2 && j == 24) || (i == 3 && j == 6) || 
               (i == 4 && j == 5)){
                   break;
               }
            if(j == indention){
                printf("\n");
                indention += 8;
            }
            
        }
        printf("\n\n");
    }


    io_release();
}
    
```


#参考資料
[公式ドキュメント：GPIO](http://zero.lichee.pro/%E9%A9%B1%E5%8A%A8/GPIO_index.html)
[メインチップデータシート]
(https://linux-sunxi.org/images/2/23/Allwinner_V3s_Datasheet_V1.0.pdf)
[Sipeed Lichee Zero回路図]
(https://dl.sipeed.com/LICHEE/Zero/HDK/lichee_zero.pdf)
[組み込みLinuxデバイスドライバの作り方 (5)](https://qiita.com/iwatake2222/items/cd1d7734c8911830386d)