Raspberry Pi(ラズペリーパイ)を使い、組み込みソフトウェアを理解する入門シリーズ第6回。
今回は、C言語を使って7セグLEDを点灯します。
組み込みソフトウェアを作るスキルを習得するのが目的であれば、ライブラリの使用は極力控え、下層レベルの処理を自ら書けるようになる目標を持つべきだと思います。
この記事はそのような方に向けた情報にしていますので、WiringPiを使わずに書きました。
WiringPiというライブラリと使えば、レジスタを触ることなく簡単にGPIOが制御できますが、最初からこのようなライブラリに頼ったソースコードを書く癖がつくと、どのように制御しているかと言う肝の部分が理解できず、応用が効かないエンジニアになります。
今回は、極力原始的なソースコードを書きました。
仕組みがわかったら、その上でライブラリを上手く利用して効率よく良くコードをプログラム作成をすれば良いと思います。
また、ソースコードの説明を書いていますが、C言語を習得している事を前提としているので、C言語そのものについての詳細説明は割愛しています。
C言語を学習するにあたり、私がバイブルと思っている学習本を最後に紹介していますので、そちらを御覧下さい。名著です。
ラズパイと7セグLEDの接続方法については、こちらを御覧下さい。
7セグLEDとGPIO ピン番号対応図
今回配線したGPIOピン番号を7セグLED図に赤字で付記しました。この配線を元に制御します。
ソースコード
/* 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) /* GPSET0 Address下位1byte */ #define GPCLR0 (0x28) /* GPCLR0 Address下位1byte */ #define GPLEV0 (0x34) /* GPLEV0 Address下位1byte */ int getSegData( int num ); /* GPIO 7セグ点灯データ作成処理 */ char seg_map[11][8] = { /*2 3 4 5 6 7 8 9 pin */ { 1, 1, 1, 1, 1, 0, 1, 0 }, /* 0 */ { 0, 1, 1, 0, 0, 0, 0, 0 }, /* 1 */ { 1, 1, 0, 1, 1, 1, 0, 0 }, /* 2 */ { 1, 1, 1, 0, 1, 1, 0, 0 }, /* 3 */ { 0, 1, 1, 0, 0, 1, 1, 0 }, /* 4 */ { 1, 0, 1, 0, 1, 1, 1, 0 }, /* 5 */ { 1, 0, 1, 1, 1, 1, 1, 0 }, /* 6 */ { 1, 1, 1, 0, 0, 0, 1, 0 }, /* 7 */ { 1, 1, 1, 1, 1, 1, 1, 0 }, /* 8 */ { 1, 1, 1, 0, 1, 1, 1, 0 }, /* 9 */ { 0, 0, 0, 0, 0, 0, 0, 1 } /* DP */ }; 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 初期設定 */ /*==========================================================*/ gpio = (unsigned int *)map; gpio[GPFSEL0/4] = 0x09249240; gpio[GPSET0/4] = 0x000003FC; gpio[GPCLR0/4] = 0x00000000; gpio[GPLEV0/4] = 0x0; usleep( 500000 ); /*==========================================================*/ /* 7seg点灯処理 */ /*==========================================================*/ for( i = 0; i < 11; i++ ){ gpio[GPCLR0/4] = getSegData( i ); usleep( 500000 ); gpio[GPSET0/4] = 0x000003FC; } gpio[GPFSEL0/4] = 0x00000000; munmap( map, BLOCK_SIZE ); return 0; } int getSegData( int num ) { int data = 0x00000000; data |= (seg_map[num][0] << 2); data |= (seg_map[num][1] << 3); data |= (seg_map[num][2] << 4); data |= (seg_map[num][3] << 5); data |= (seg_map[num][4] << 6); data |= (seg_map[num][5] << 7); data |= (seg_map[num][6] << 8); data |= (seg_map[num][7] << 9); return data; }
プログラムの実行方法
$ gcc main.c -I/opt/vc/include -L/opt/vc/lib -lbcm_host $ sudo ./a.out
ソースコードの説明
Lチカ制御のソースコードを流用していますので、ベースの説明はこちらを御覧下さい。
ライブラリを使わずにGPIOを制御する方法は、上記記事で説明しています。
制御レジスタの詳細や、データシートのリンク先も上記記事で紹介しています。
今回のプログラムは「0→1→2 ~ 9→10→.」と点灯する処理となっています。
70行目: gpio[GPFSEL0/4] = 0x09249240;
GPFSEL0レジスタのGPIO2~9番をOUT設定にしています。
GPFSEL0レジスタの詳細はデータシートを見ればわかるのですが、内容を簡単に知りたい方は上に貼った過去記事で解説しているので御覧下さい。
72行目: gpio[GPSET0/4] = 0x000003FC;
GPIO2~9番を制御して7セグ全てを消灯させています。初期化です。
81行目: gpio[GPCLR0/4] = getSegData( i );
getSegData()という関数を作り、ここで7セグ点灯データを作成しています。
今回使った7セグLEDはアノード・コモンなので、GPCLRレジスタを制御してGPIOをLOレベルに落とすとセグメントが点灯します。GPCLRの点灯させたいGPIO対応ビットに1をセットすると点灯する事になります。
関数への引数i は、数値の0~10を代入します。
0~9のいずれかの値を渡すと、その数字に対応したセグメント点灯データが戻り値として返ってきます。
10を渡すと、DPが点灯します。
83行目: gpio[GPSET0/4] = 0x000003FC;
次のデータ表示前にGPSETレジスタのGPIO2~9pin対応ビットに1をセットして、全セグメントを消灯させます。
92行目: getSegData() 関数
二次元配列seg_mapに点灯させたいデータを用意しています。
GPCLR0は下位2ビット目がGPIO 2pin、そこから上位に順番に3pin~9pinまで並んでいます。
なので、seg_mapのデータを2ビット左シフト~9ビット左シフトを順番に行い、全てのセグメント点灯情報を作成しています。
この関数はもっとスマートな書き方がありますが、可読性を重視した実装としました。
この関数で作成されるデータは下記となります。
0:0x0000017c
1:0x00000018
2:0x000000ec
3:0x000000dc
4:0x00000198
5:0x000001d4
6:0x000001f4
7:0x0000011c
8:0x000001fc
9:0x000001dc
.:0x00000200
プログラム実行結果
ラズパイは非常に安価でLinux PCとしても遊べるので、興味がある方は手に取ってみて下さい。
KeeYees電子工作キットにはブレッドボード、抵抗、LED、リード線(コの字ワイヤやジャンパー線)など、電子工作部品が一式揃っているので、電子工作の取っ掛かりにオススメのセットです。
C言語を習得するなら、オススメなのは独習Cです。
ご覧いただき、ありがとうございました。
ランキングに参加しています。ポチッと押して去って頂く方に感謝します。ありがとうございます。
コメント