4桁7セグLED駆動用Cプログラム～「Cの絵本」でわかる範囲の備忘録

###はじめに

「ARMデバイスドライバープログラミング」米田聡著（ソシム2014）の第3章で紹介されているユーザープログラムを学習する上で、必要最低限の機能部分だけを抜き出して、「Cの絵本」で学習したことをベースに書き直してみました。

こちらで紹介されている電気回路[^1]は、以下のICを使用しています。
- 74HC4511
- 74HC138
- 74HCT04

[^1]: このほかに、「PICと楽しむRaspberry Pi活用ガイドブック」後閑哲也著（技術評論社2017）の「おしゃべり時計の制作」の電気回路もあります。こちらは、ラズパイからPICへのデータ送信はシリアル通信を使用してラズパイとはシリアル通信する仕組みですが、残念ながらラズパイ側のプルグラムにPythonをつかっていますが、PICとラズパイの組み合わせは大変興味がありますので、今後の課題です。

###開発環境[^2]

- HW：Raspberry Pi B Rev.2
- OS：Raspbian

OSは、`NOOBS_v1_3_11`を使用してインストールしました。このOS内容は、次のようになっています。

[^2]: 2020年に古いデバイスやOSを使って学習することには賛否両論あるかもしれませんが、学習効率やコストを考えると、やはり古いデバイスを使う方が効率が高いと判断しました。

```json:os.json
{
  "name": "Raspbian",
  "version": "wheezy",
  "release_date": "2014-12-24",
  "kernel": "3.12",
  "description": "A community-created port of Debian wheezy, optimised for the Raspberry Pi",
  "url": "http://www.raspbian.org/",
  "username": "pi",
  "password": "raspberry",
  "feature_level": 386044
}
```

###ソースコード

まず、ソースコードの全体を示します。後に少しだけ、修正方針の説明を入れます。

```c:7segled_4digit_simple.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <inttypes.h>
#include <stdint-gcc.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

/* レジスタアドレス */
#define RPI_REG_BASE	0x20000000
#define GPIO_BASE		(RPI_REG_BASE + 0x200000)

#define PAGE_SIZE 		(4 * 1024)
#define BLOCK_SIZE 		(4 * 1024)

#define DIGITS          4	// 桁数
#define BLANK           10

/* GPIOO Mask */
#define RPI_GPIO_P1MASK	(uint32_t) (\
(0x01<<2) | (0x01<<3) | (0x01<<4) | \
(0x01<<7) | (0x01<<8) | (0x01<<9) | \
(0x01<<10)| (0x01<<11)| (0x01<<14)| \
(0x01<<15)| (0x01<<17)| (0x01<<18)| \
(0x01<<22)| (0x01<<23)| (0x01<<24)| \
(0x01<<25)| (0x01<<27))

int setup_io(void);
int rpi_gpio_deinit(void);
int rpi_gpio_function_set(int , uint32_t);
void rpi_gpio_set32(uint32_t, uint32_t);
void rpi_gpio_clear32(uint32_t, uint32_t);
void testmain();

static volatile uint32_t *gpio_base = NULL;

int mem_fd;
char *gpio_mem, *gpio_mmap;

int setup_io(void)
{
	/* open /dev/mem */
    if((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0){
		fprintf(stderr, "Can not open /dev/mem\n");
		return 0;
	}

    /* mmap GPIO */
    // Allocate MAP block
	if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL){
        printf("allocation error \n");
        return 0;
    } 

    // Make sure pointer is on 4K boundary
    if ((unsigned long)gpio_mem % PAGE_SIZE)
        gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

    // Now map it
    gpio_mmap = (char *)mmap(
        (caddr_t)gpio_mem,
	    BLOCK_SIZE,
		PROT_READ | PROT_WRITE, 
		MAP_SHARED,
		mem_fd, 
		GPIO_BASE
    );
	if(gpio_mmap == MAP_FAILED) {
		fprintf(stderr, "Can not mmap GPIO\n");
		return 0;
	}

	close(mem_fd);
	gpio_base = (volatile uint32_t *)gpio_mmap;

	return 1;
}

int rpi_gpio_deinit(void)
{
	munmap((void *)gpio_base, BLOCK_SIZE);
	gpio_base = NULL;

	return 1;
}

int rpi_gpio_function_set(int pin, uint32_t func)
{
	uint32_t index = pin / 10;
	uint32_t shift = (pin % 10) * 3;
	uint32_t mask = ~(0x7 << shift);
	gpio_base[index] = (gpio_base[index] & mask) | ((func & 0x7) << shift);

	return 1;
}

void rpi_gpio_set32(uint32_t mask, uint32_t val)
{
	gpio_base[7] = val & mask;
}

void rpi_gpio_clear32(uint32_t mask, uint32_t val)
{
	gpio_base[10] = val & mask;
}

// 管理データ
struct seg_data {
	int exit_flag;
	int display_value[4];
	int hz;
} data;
struct seg_data data = {0, {0,0,0,0}, 65};

// GPIO初期化

void led_init(void)
{
	// GPIOファンクションセット
	rpi_gpio_function_set(7, 0x01);
	rpi_gpio_function_set(8, 0x01);
	rpi_gpio_function_set(9, 0x01);
	rpi_gpio_function_set(10, 0x01);
	rpi_gpio_function_set(14, 0x01);
	rpi_gpio_function_set(15, 0x01);
}

void led_put(unsigned int v)
{
	rpi_gpio_clear32(RPI_GPIO_P1MASK, 0x0F << 7);
	rpi_gpio_set32(RPI_GPIO_P1MASK, (v & 0x0F) << 7);
}

// 4桁表示スレッド
void *exec_4digits(void *param)
{
	static int dig;
	struct seg_data *d = (struct seg_data *)param;
	int usec = (1000000/DIGITS) / d->hz;	// 1桁あたりの待ち時間

	// GPIOポート初期化
	led_init();

	while(!d->exit_flag) {
		if(++dig >= DIGITS) dig = 0;
		// Blankを入れる
		led_put(BLANK);
		// 点灯桁をシフトする
		rpi_gpio_clear32(RPI_GPIO_P1MASK, 0x03 << 14);
		rpi_gpio_set32(RPI_GPIO_P1MASK, (dig) << 14);
		// 数字を出力
		led_put(d->display_value[dig]);
		usleep(usec);
	}
	return NULL;
}

void testmain()
{
	int counter = 0;

	while(data.exit_flag == 0) {
		int c;
		if(counter > 9999) counter = 0;
		data.display_value[0] =  counter / 1000;
		if(data.display_value[0] == 0) data.display_value[0] = BLANK;
		c = counter % 1000;
		data.display_value[1] =  c / 100;
		if((data.display_value[1] == 0) && (counter < 100)) data.display_value[1] = BLANK;
		c = c % 100;
		data.display_value[2] =  c / 10;
		if((data.display_value[2] == 0) && (counter < 10)) data.display_value[2] = BLANK;
		data.display_value[3] =  c % 10;
		counter++;
		usleep(100*1000);
	}
}

int main(void)
{
	pthread_t thread;
	// GPIO初期化
	setup_io();
	pthread_create(&thread, NULL, exec_4digits, &data);
	testmain();
	// スレッド終了待ち
	pthread_join(thread, NULL);
	rpi_gpio_deinit();
	return 0;
} // main
```
#####修正方針の説明

米田聡氏の著書は、デバイスドライバの開発を目的として、電気回路のテストのため（だけ）にユーザーランドのプログラムを紹介していますが、デバイスドライバのプログラムを前提としている[^3]ためか、単純なことをやらせる単純なプログラムとしてソースコードを一つにまとめてしまうと、全体の見通しが少し悪くなってしまいました。そのため、以下のような修正を行っています。

[^3]: 前提として、この本は、デバイスドライバーの学習初心者向けというよりも、実際にデバイスドライバーの開発業務にかかわる人に多種デバイス向けに移植性を高める方法を示す目的も強かったのではないかと感じます。

- マクロはなるべく使わない
- 使用されていない関数を外す。
- 「ラズベリー・パイでI/O」（CQ出版社2013）で学習したメモリ割り当て関数（malloc）を強いて使う。
- GPIO出力セット関数（rpi_gpio_function_set）は、「ROSロボット入門」（日経BP2017）の表記に倣う。

###おわりに

いまだに「4桁表示スレッド」がポインタで宣言される理由が納得できていません。たぶん、pthread を使っているため、第3引数を関数としてポインタで参照したいからなのだと思いますが、この関数はさすがに「絵本」には載っていませんでした。

C言語（だけなじゃいけど）の初学者の非プログラマなので、「Cの絵本」アンク（株）著（翔泳社2002）を参考にしながらデバイスドライバーの仕組みを学習しています。機能上必要のない記述はぎりぎりまで削って、マクロもなるべく使わないように書いてある方が、プログラムの記述がコンパクトになって学習者にはわかりやすいと思うのは私だけでしょうか。