絶対にわかる組み込みソフトウェア 2: C言語でLED点灯(Lチカ)制御

組み込みソフトウェア

Raspberry Pi(ラズペリーパイ)を使い、組み込みソフトウェアを理解するシリーズ第3回。

Raspberry Pi 2 Model B ラズパイ
プログラミングの楽しさがわかるよ

ラズパイにLEDを繋ぎ、C言語で書いたプログラムでLEDを点滅させる、通称「Lチカ」を実現してみます。

 

今回はC言語で書いたソースコード公開までですが、次回以降に必ず内容が理解できるよう説明していきますのでお付き合いください。
また、タイトルで「誰でもわかる」と謳っていますが、第1回から順に読んで頂く事を前提としています。
www.initial-jj.com
www.initial-jj.com

C言語について

C言語はコンパイラ型言語です。
コンパイラ型の詳細は、第2回python編の記事を御覧下さい。

プログラミング言語について

Linuxには標準でgccというコンパイラが導入されています。
元々はGNU C Complierの略でgccという名称でしたが、現在はC言語以外のコンパイルにも対応しています。
また、コンパイラと呼ばれますが、gccコマンドに対してオプションと付けずに実行すると、アセンブラ、リンカーを順次呼び出して実行形式ファイル(デフォルトではa.outというファイル名)を作成します。
例えば、-Sオプションを付けてgccを実行するとアセンブラ・コード(拡張子 *.s)を生成します。

プログラム説明

ソースコード

/* file name : main.c */
#include <bcm_host.h>		/* required to use bcm_host_get_peripheral_address() */
#include <fcntl.h>		/* required to use open(), close(), usleep() */
#include <sys/mman.h>		/* required to use mmap(), munmap() */
#define BLOCK_SIZE	(4096)			/* マッピングするメモリサイズ(4KByte) */
#define	GPIO_OFFSET	(0x00200000)		/* see p.91 of dataseet		*/
#define	GPFSEL0		(0x00)			/* GPFSEL0 Address下位1byte	*/
#define	GPSET0		(0x1c)			/* GPFSET0 Address下位1byte	*/
#define	GPCLR0		(0x28)			/* GPFCLR0 Address下位1byte	*/
int main( void )
{
volatile	unsigned int *gpio;
void *map;
int fd;
int adr_gpio_base;
int i = 0;
/*==========================================================*/
/* GPIO制御レジスタ・アドレスを取得し、map変数に代入する	*/
/*==========================================================*/
fd = open( "/dev/mem", (O_RDWR | O_SYNC) );
/* open()エラー処理割愛 */
;
/*------------------------------------------------------------------------------------------*/
/* refer to									*/
/* https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md*/
/*											*/
/* ペリフェラル物理アドレスがラズパイ世代で異なる為、互換性を持たせる為に			*/
/* bcm_host_get_peripheral_address()でプログラム実行マシンの上の物理アドレスを取得する	*/
/*------------------------------------------------------------------------------------------*/
adr_gpio_base = bcm_host_get_peripheral_address();
/* mapにペリフェラル物理アドレスをマッピング */
map = mmap( NULL,
BLOCK_SIZE,
PROT_WRITE,
MAP_SHARED,
fd,
(adr_gpio_base + GPIO_OFFSET) );
/* mmap()エラー処理割愛 */
;
close( fd );
/*==========================================================*/
/* GPIO 2pin 初期設定					*/
/*==========================================================*/
gpio = (unsigned int *)map;
gpio[GPFSEL0/4]	|= 0x00000040;
gpio[GPCLR0/4]	|= 0x00000002;
usleep( 500000 );
/*==========================================================*/
/* LED点滅処理						*/
/*==========================================================*/
while (i < 5 ){
gpio[GPSET0/4]	|= 0x00000002;
usleep( 250000 );
gpio[GPCLR0/4]	|= 0x00000002;
usleep( 250000 );
i++;
}
gpio[GPFSEL0/4]	&= 0xfffffffb; /* INPUTに戻す(無くても良い処理) */
munmap( map, BLOCK_SIZE );
return 0;
}

最低限の処理だけ書いた為、エラー発生時にどうするか等の処理は割愛しています。

プログラムの説明

前回のpythonで実行させた時と同じく、GPIO 2番ピンを使っています。

プログラムの内容は、

  1. GPIO制御レジスタのアドレスを取得 (bcm_host_get_peripheral_address())
  2. map変数を介して、GPIO制御レジスタにアクセスできるようにする
  3. GPIO 2pinを出力設定にする (gpio[GPFSEL0/4] |= 0x00000040)
  4. GPIO 2pin の出力をLO出力してLEDを消灯 (gpio[GPCLR0/4] |= 0x00000002)
  5. 500ミリ秒ウェイト(usleep)
  6. GPIO 2pin の出力HI出力してLEDを点灯 (gpio[GPSET0/4] |= 0x00000002)
  7. 250ミリ秒ウェイト(usleep)
  8. GPIO 2pin の出力をLO出力してLEDを消灯 (gpio[GPCLR0/4] |= 0x00000002)
  9. 250ミリ秒ウェイト(usleep)
  10. 上記を5回繰り返す (while (i < 5 ))

となっています。

前回のpythonのソースコードと比較すると、何をしているのかサッパリわからないと思います。
また、今回のソースコードは必要最小限のライブラリしか使用していない為、C言語を理解している人でも、デバイスの制御仕様を理解しないと意味がわかりません。

GPIOの制御仕様(データシート)はラズパイ公式サイトから入手できます。
https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf
(Raspberry Pi Documentation より)
p.89の「6 General Purpose I/O (GPIO)」の仕様を読みソースコードを書きました。
この項を読むとソースコードが何をやっているのかわかるので、C言語を知っている方は次回の説明までにソース解析に是非挑戦してみて下さい。

プログラムの実行方法

$ gcc main.c -I/opt/vc/include -L/opt/vc/lib -lbcm_host
$ sudo ./a.out

gccコンパイラでmain.cをコンパイルします。
上述の通り、コンパイル→アセンブル→リンクまで行います。

今回のプログラムはbcm_hostライブラリを使用しているので、この機能をプログラムに組み込む(=リンクさせる)為に、-lオプションでbcm_hostライブラリの場所を指定しています。
このオプションを付けずにgccを走らせると、リンカーがbcm_hostライブラリの所在がわからず、実行ファイルにライブラリを結合する事ができないエラーが発生します。

プログラム実行は”./a.out”と入力してENTERを押せば良いのですが、メモリ・アクセスには特権レベル(スーパー・ユーザー)が必要な為、sudoコマンドでa.outを特権レベルで実行させます。特権レベルが無いと、OSがメモリ保護を行っており、与えられたメモリ空間外へのアクセスを禁止している為です。
sudo をつけなかった場合、メモリ・アクセスができずにエラーでプログラムは動作しません。

プログラムの実行結果

 

 

ご覧いただき、ありがとうございました。
ランキングに参加しています。ポチッと押して去って頂けると、次回に続く

ブログランキング・にほんブログ村へにほんブログ村


人気ブログランキング

コメント

タイトルとURLをコピーしました