Raspberry Pi 3B+ で「初めてのカーネルモジュール～７セグLEDドライバー」の代替えサンプルがサクッとできた話。

# はじめに

RT Corp. の Raspberry Pi Mouse V2 でROS遊びをしていますが、寄り道してデバイスドライバーについて知りたくなりました。「**Raspberry Pi で学ぶROSロボット入門**」（上田隆一著 2017 日経BP）に「初歩の初歩」のデバイスドライバーの作成方法についての説明がありますが、それでも仕組みやら何やらがよくわからなくて、「**Raspberry Piで学ぶARMデバイスドライバープログラミング**」（米田聡著 2014 ソシム）も入手したのですが、7セグLEDドライバー[^1]のサンプルコードが Raspberry Pi 3B+ で動作しなくて[^2]四苦八苦しているうちに、「**お手軽ARMコンピュータ ラズベリー・パイでI/O**」（2013 CQ出版）というInterface増刊の「第8章 自作LinuxドライバでI/O制御を簡単・確実に！」（桑野正彦）という記事に出会い、これらの記事のサンプルコードを使って簡単な一桁７セグLED用のドライバができましたので、忘備録を兼ねて情報共有します。

[^1]: 著者は、謙遜してなのか、「GPIOを操作するだけの単純なドライバーですが、それでも結構な量のコードを書く必要があり、そのわりには７セグLEDに文字を表示するだけです。これでは、あまりドライバーを作ったメリットが感じられず、ちょっとがっかりしてしまったかもしれません」（参考図書②p.105）と書かれていますが、私の場合はむしろ逆で、この回路を見たときにはラズパイでのデバイスドライバーの使い方に目が覚める思いがしました。

[^2]: これは、決してプログラム上の不備があるという訳ではなく、この本が出版された2014年ごろの環境を2020年の現代に再構築して、古参のRaspberry Pi B+でRaspbianの2014年ごろのソースを使えば今でもちゃんと動くことは実証ずみです。古いラズパイをお持ちの方でRaspbian での動作に興味がある方は、[こちらの記事](https://raspberrypi1.hatenablog.com/entry/2017/07/02/150126)も参考にして楽しんでみてください。ただし、私の場合、カーネルソースのローカルビルドツリーの作成には、今年の夏の猛暑の中でラズパイが熱暴走気味で12時間以上かかりましたが、それはそれで実際に（何度か・・・）やってみると技術の進歩を実感できて良い経験（？）になりました。さすがに、在宅ワークで通勤時間を節約できる労働環境の中でも、同書で主に使用が想定されているRaspberry pi B Rev.2を入手して実験することはかろうじて断念し、道楽人の酔狂に陥るのは避けられました。

##開発環境
- Raspberry Pi 3B/3B+[^5]
- Ubuntu 18.04 LTS
- Microsoft Visual Studio Code (Windows 10)

[^5]: 将来的には、Raspberry Pi 3A+でも試して、Pi3シリーズ(?)のコンプリートも目指します。

##参考図書（出版年度が古いもの順）と資料（ランダム順）

####ドライバーの作成に役立ったもの
① 「[お手軽ARMコンピュータ ラズベリー・パイでI/O](https://www.socym.co.jp/support/s-940 "米田聡著 2014 ソシム"))」（Interface増刊 2013 CQ出版）
　　第8章記事
② 「[Raspberry Piで学ぶ ARM デバイスドライバー プログラミング](https://www.socym.co.jp/support/s-940 "米田聡著 2014 ソシム")」（米田聡著 2014 ソシム）
　　第１章、第2章記事
③ 「[Raspberry Piで学ぶ ROSロボット入門](https://github.com/ryuichiueda/raspi_led_device_driver "上田隆一著 2017 日経BP")」（上田隆一著 2017 日経BP）
　　付録第A章記事

####資料集
④ 「[Linuxカーネルが基礎からわかる本](https://shop.nikkeibp.co.jp/front/commodity/0000/LI1230/ "日経Linux 2020年1月号特別付録")」（日経Linux 2020年1月号特別付録）
⑤ 「Raspberry Pi クックブック」初版第2刷（2014 オライリー・ジャパン）
⑥ C-Language [GPIO Code Sample](https://elinux.org/RPi_GPIO_Code_Samples#C) of ["RPi Low-level peripherals"](https://elinux.org/RPi_Low-level_peripherals) at [Embedded Linux Wiki](https://elinux.org/Main_Page)
⑦ [bcm2835.h source](https://www.airspayce.com/mikem/bcm2835/bcm2835_8h_source.html) at [C library for Broadcom BCM 2835 as used in Raspberry Pi](https://www.airspayce.com/mikem/bcm2835/index.html)
⑧ [Linux Device Drivers, Third Edition](https://lwn.net/Kernel/LDD3/)


## 7セグLEDドライバのテストボードの作成

参考図書②の第２章冒頭掲載の回路図を少し変更して、７セグLEDのDP端子の操作にGPIO11（23番ピン）を用います。カソードコモンタイプの赤色７セグLEDとロジックICにはTC74HC4511を使っており、私自身は実質的には何も工夫はしていません。[^4]プログラムしやすいようにGPIOの連続したピン番号を確保するため、昔からGPIO7からGPIO11までの間から連続した番号を選んでいるサンプルが多く、参考図書②ではGPIO7からGPIO10までが使用されています。ここでは、空いているGPIO11をDP端子用に使って、23番ピンとの間には510Ω[^3]のカーボン抵抗(1/4W)を入れて直接接続しています。以下の別記事にもこのプロジェクトで作成したデバイスの写真やページの回路図へのリンクを示しておきますので参考にしてみてください。

- [「ubuntu 18.04 にraspi-configをインストールしてGPIOの初期設定を変更した話」](https://qiita.com/ivvakanni/items/e29378dc666d8204fa58)

[^4]: 資料②では、この装置をブレッドボード上で作るように紹介されていますが、私は、せっかくなのでこの回路を秋月電子のRaspberry Pi用ユニバーサル基板の上に部品をはんだ付けしてHATのようにGPIOに直接差し込んで使っています。ブレッドボードはお手軽でいいのですが、デバイスドライバを作るなら、やっぱりハード的に基板に乗っていないと雰囲気が出ないですし、何より、Raspberry Pi の各世代のボードに乗せてRaspbianやらUbuntuやらでバージョンも変えてドライバーの制作実験する上ではブレッドボードの回路では心もとないし、持ち運びも面倒（？）です。ただし、raspberry pi用プロトタイプ基板は当然小さく、7セグLED、ロジックIC、抵抗器8個を乗せると、後はプッシュスイッチ2個をぎりぎり載せられるかどうか・・・という程度で、基板上もブレッドボード並みにジャンパー線だらけになりました。

[^3]: 初代の頃の「Raspberry PiのGPIOピンは、たった3mAの電流しか供給できない」（参考図書p.188）仕様だったらしいのですが、Raspberry Pi 3B+の「[ハードウェア・データシート](https://www.raspberrypi.org/documentation/hardware/computemodule/datasheets/rpi_DATA_CM3plus_1p0.pdf "Compute Module 3"))」では、「各GPIOピンは16mAまでの電流を供給でき、GPIOピン全体の合計で50mAを超えてはならない。」と、なんとも嬉しくも心強い仕様に機能強化されています。7セグLEDのデバイスでは、GPIOは全部で５本使用しますので、最大で各ピンからの出力が各10mAを超えない設計にすればよいのですが、私の回路図では手元に510Ωの抵抗があったとの理由でこれを使用しています。Lチカはどこにでもある回路なので計算するまでもないのですが、それでも確認したい方は、[こちらの記事](https://elinux.org/RPi_Tutorial_EGHS:LED_output)はいかがでしょうか。実際にDPを点灯させてみると数字部分に比べてかなり暗いです。かろうじて、ついてるのかなぁ・・・、とわかる程度です。

##それでは、実験をはじめましょう。

###準備
- Ubuntu 18.04 LTSのarmhfの最新版を入手して、microSDにインストールします。[^6]
[^6]: 仕組みは調べていませんが、最近のarmrf用のOSは、自分でFdiskを使わなくても初回起動時に自動でSDカードいっぱいに記憶領域を拡張してくれます。そのためか、未フォーマットのディスクにOSをインストールすると起動時の虹色画面でフリーズする現象があるようです。虹色画面からカーネルの読み込みが始まらない場合、ディスク全体を上書きフォーマットし直してからOSをインストールすると、この問題は解決できるようです。
- 次に、Linuxのヘッダファイルをインストールします。Ubuntu Documentation の[こちらの記事](https://help.ubuntu.com/community/Kernel/Compile)にあるとおり、今回のような作業環境、作業目的ではカーネルコンパイルは必要ありません。


###修正作業

- 上記図書③のソースコードを参考に、同時に複数のGPIOへの出力ができるように必要な修正を行い、あわせて、マクロを使ってコメントなしでもコードを読みやすいようにしてみました。ソースコード全体を以下に示します。カーネルを組み込んで実際に７セグLEDに数字を表示したり、DPの点灯、消灯をデバイスドライバで操作できるようになります。


###ソースコード

```C:odssledc.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>

MODULE_AUTHOR("Ivvakanni");
MODULE_DESCRIPTION("one digit seven segment LED control simple driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("0.0.1");

#define DEVICE_DRIVER_NAME      "odssledc"
#define DRIVER_MAJOR_NUM        0
#define DRIVER_MINOR_NUM        1
#define DEVICE_COUNT            1

/* for raspbery pi */
#define BCM2708_PERI_BASE　　　　0x20000000 
/* for raspberry pi 2 or 3 */
#define BCM2837_PERI_BASE　　　　0x3F000000
#define MYDEV_GPIO_BASE　　　　　(BCM2837_PERI_BASE + 0x200000)

#define MAP_SPACE　　　　1024
#define GPIO_BASE_NUM   7
#define GPIO_CLR        10
#define GPIO_OUT        7

static dev_t dev;
static struct cdev cdv;
static struct class *cls = NULL;
static volatile u32 *gpio_register = NULL;

static int gpio_put( int arg ){
        int v = 0;
        v = arg;
        return v;
}

static ssize_t pin_write(struct file* filp, const char* buf, size_t count, loff_t* pos)
{
        char c;
        if(raw_copy_from_user(&c,buf,sizeof(char)))
                return -EFAULT;
        if(c == '.')
                gpio_register[GPIO_CLR] = 0x10 << GPIO_BASE_NUM;
        if(c == ',')
                gpio_register[GPIO_OUT] = 0x10 << GPIO_BASE_NUM;
        if(c == '0')
                gpio_register[GPIO_CLR] = 0x0F << GPIO_BASE_NUM;
        else if(c > '0'){
                int val = gpio_put(c);
                gpio_register[GPIO_CLR] = 0x0F << GPIO_BASE_NUM;
                gpio_register[GPIO_OUT] = ( val & 0x0F ) << GPIO_BASE_NUM;
        }
        return 1;
}

static struct file_operations led_fops = {
        .owner = THIS_MODULE,
        .write = pin_write
};

static void gpio_function_setup(int n){
        const u32 gpio_number = n;
        const u32 port = gpio_number / 10;
        const u32 shift = (gpio_number % 10) * 3;
        const u32 zero_mask = ~(0x07 << shift);
        gpio_register[port] = (gpio_register[port] & zero_mask) | (0x01 << shift);
}

static int __init init_mod(void)
{
        gpio_register = ioremap_nocache(MYDEV_GPIO_BASE, MAP_SPACE);

        gpio_function_setup(GPIO_BASE_NUM + 0);
        gpio_function_setup(GPIO_BASE_NUM + 1);
        gpio_function_setup(GPIO_BASE_NUM + 2);
        gpio_function_setup(GPIO_BASE_NUM + 3);

        alloc_chrdev_region(&dev,DRIVER_MAJOR_NUM,DRIVER_MINOR_NUM,DEVICE_DRIVER_NAME);
        printk(KERN_INFO "%s load success at %d\n",__FILE__,MAJOR(dev));

        cdev_init(&cdv, &led_fops);
        cdv.owner = THIS_MODULE;
        cdev_add(&cdv, dev, DEVICE_COUNT);
        
        cls = class_create(THIS_MODULE, DEVICE_DRIVER_NAME);
	device_create(cls, NULL, dev, NULL, DEVICE_DRIVER_NAME);

        return 0;
}

static void __exit exit_mod(void)
{
	cdev_del(&cdv);
	device_destroy(cls, dev);
	class_destroy(cls);
        unregister_chrdev_region(dev, 1);
        printk(KERN_INFO "%s unloaded from %d\n",__FILE__,MAJOR(dev));
}

module_init(init_mod);
module_exit(exit_mod);
```
また、あわせて、Raspberry Pi上でのローカルコンパイル用のMakeファイルも掲載しておきます。

```C:Makefile
TARGET:= odssledc.ko

all: ${TARGET}

obj-m := odssledc.o

odssledc.ko: odssledc.c
	make -C /usr/src/linux-headers-`uname -r` M=`pwd` V=1 modules

clean:
	make -C /usr/src/linux-headers-`uname -r` M=`pwd` V=1 clean
```

makeしてから、insmod、chmodをします。

```
$ make
$ sudo insmod odssledc.ko
$ chmod 666 /dev/odssledc
```

数字の表示は、以下のように操作します。

```
$ echo 1 > /dev/odssledc   //数字の 1 を表示
$ echo 2 > /dev/odssledc   //数字の 2 を表示
　　・
　　・
　　・
$ echo . > /dev/odssledc   // DPの消灯
$ echo , > /dev/odssledc   // DPの点灯 
```

##おわりに

以上の通り、デバイスドライバとして一応は動く形にまとめたのですが、まだいくつか課題が残りました。

・「Raspberry Piで学ぶARMデバイスドライバープログラミング」のサンプルをubuntu 18.04でも使えるようにしたい。
・GPIOは7番から11番を使用していますが、回路の将来的な応用を考えると、別な連番を使う方がよい。
・このドライバには文字部分の消灯ができないので、数字の10又は11を入力した場合の機能として追加したい。
・ROSをこのドライバーと連携して、7セグLEDの数字の表示を遠隔で操作できるようにしたい。

まだまだボンヤリとした理解しかできていない部分もあります。とはいえ、やはり実際に自分で作ってみる方が本を読んでいるだけよりずっと理解が深まりました。