絶対にわかる組み込みソフトウェア 8: 4桁7セグLEDダイナミック点灯~ソースコード公開~

組み込みソフトウェア

Raspberry Pi(ラズペリーパイ)を使った、組み込みソフトウェアを理解する入門シリーズ第8回。
今回は、C言語を使って4桁の7セグLEDを点灯制御します。

本ブログでは、組み込みソフトウェアについて初歩から習得できるよう、カテゴリを作って簡単なプログラムから順に紹介しています。
includeしているライブラリやビルド方法、GPIO制御の為にマッピングしている処理については過去記事で説明しています。
プログラミングに興味がある方や、今回の記事で用語がわからない方は、まずは下記記事を初回から御覧下さい。


「絶対にわかる」と謳っていますが、C言語と16進数について最低限の知識がある方向けとなります。
言語習得はたくさん良質な書籍が出ていますので、そちらで独習する事をオススメします。
その為、完全初心者に対しての説明は割愛しています。
C言語については、最後に私のオススメ本を紹介していますので、興味のある方は参考にして下さい。

今回の記事の導入部にあたる記事はこちらになります。データシートや配線図について記載しています。

本ブログの主旨は「組み込みソフトウェアを自力で作るスキルを習得する」事にあります

python言語やWiringPi等のライブラリを使えば、「なぜ動作するのか?」という知識が無くとも機器を制御するプログラムを書く事ができますが、「もっと低層の制御方法を知りたい」、「自力でライブラリを書くスキルを身に着けたい」、「応用力を身につけたい」という方に向けた記事となります。
ソースコードはC言語初心者でも読みやすいよう、極力簡単に書きました。可読性を重視した為、マジックナンバーを多用しています。

The main purpose of this blog is to “learn the skills to create embedded software on your own”.

By using python language and libraries such as WiringPi, you can write programs to control devices without having knowledge of “why does it work? However, this article is intended for those who want to learn more about low-level control methods and acquire the skills to write libraries on their own.
The source code has been written as simply as possible so that it can be easily read by C beginners. For the sake of readability, I have used a lot of magic numbers.

7セグのデータシート

データシートは前回の記事に記載していますので、そちらを御覧下さい。

Please refer to the previous article for the datasheet.

絶対にわかる組み込みソフトウェア 7: 4桁7セグLED点灯制御~回路図紹介~ – 鬱リーマン、デイトレとパチスロと不毛な日々

ソースコード

#include <stdio.h>
#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	GPFSEL1		(0x04)			/* GPFSEL1 Address下位1byte	*/
#define	GPFSEL2		(0x08)			/* GPFSEL2 Address下位1byte	*/
#define	GPSET0		(0x1c)			/* GPSET0  Address下位1byte	*/
#define	GPCLR0		(0x28)			/* GPCLR0  Address下位1byte	*/
#define	GPLEV0		(0x34)			/* GPLEV0  Address下位1byte */
char seg_map[11][8] = {
    /*13 14 18 19 23 24 25 26 pin */
    /*DP  B  F  C  A  E  D  G segment */
    {  0, 1, 1, 1, 1, 1, 1, 0 },	/* 0 */
    {  0, 1, 0, 1, 0, 0, 0, 0 },	/* 1 */
    {  0, 1, 0, 0, 1, 1, 1, 1 },	/* 2 */
    {  0, 1, 0, 1, 1, 0, 1, 1 },	/* 3 */
    {  0, 1, 1, 1, 0, 0, 0, 1 },	/* 4 */
    {  0, 0, 1, 1, 1, 0, 1, 1 },	/* 5 */
    {  0, 0, 1, 1, 1, 1, 1, 1 },	/* 6 */
    {  0, 1, 0, 1, 1, 0, 0, 0 },	/* 7 */
    {  0, 1, 1, 1, 1, 1, 1, 1 },	/* 8 */
    {  0, 1, 1, 1, 1, 0, 0, 1 },	/* 9 */
    {  1, 0, 0, 0, 0, 0, 0, 0 }		/* DP */
};
int	getSegData( int num );

/****************************************************************/
/* main function						*/
/****************************************************************/
int main( void )
{
    volatile	unsigned int *gpio;
    void *map;
    int fd;
    int adr_gpio_base;
    int sleep_time;
    int i = 0;
    int j = 0;
    int k = 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]	= 0x00001040;	/* FSEL2,4 */
    gpio[GPFSEL1/4]	= 0x09009200;	/* FSEL13,14,15,18,19 */
    gpio[GPFSEL2/4]	= 0x00049208;	/* FSEL21,23,24,25,26 */
    gpio[GPSET0/4]	= 0x07ACE014;	/* GPIO Pin Output Set Registers */
    gpio[GPCLR0/4]	= 0x00000000;
    gpio[GPLEV0/4]	= 0x0;
    usleep( 100000 );
    /*==========================================================*/
    /* 7seg点灯処理						*/
    /*==========================================================*/
    sleep_time = 150000;
    for( k = 0; k < 200; k++ ){
        /*--------------------------------------*/
        /* digit select				*/
        /*--------------------------------------*/
        for( j = 0; j < 4; j++ ){
            gpio[GPCLR0/4]	= 0x00208014;	/* DIGIT selector ALL LO */
            switch( j ){
                case 0:	/* DIGIT.1 */
                    gpio[GPSET0/4] = 0x00008000;	/* GPIO 15 HI */
                    break;
                case 1:	/* DIGIT.2 */
                    gpio[GPSET0/4] = 0x00000010;	/* GPIO 4 HI */
                    break;
                case 2:	/* DIGIT.3 */
                    gpio[GPSET0/4] = 0x00000004;	/* GPIO 2 HI */
                    break;
                case 3:	/* DIGIT.4 */
                    gpio[GPSET0/4] = 0x00200000;	/* GPIO 21 HI */
                    break;
                default:
                    break;
            }
            /*----------------------------------*/
            /* LED lighting			*/
            /*----------------------------------*/
            switch( j ){
                case 0:	/* DIGIT.1 */
                    gpio[GPCLR0/4] = getSegData( 1 );
                    break;
                case 1:	/* DIGIT.2 */
                    gpio[GPCLR0/4] = getSegData( 2 );
                    break;
                case 2:	/* DIGIT.3 */
                    gpio[GPCLR0/4] = getSegData( 3 );
                    break;
                case 3:	/* DIGIT.4 */
                    gpio[GPCLR0/4] = getSegData( 4 );
                    break;
                default:
                    break;
            }
            usleep( sleep_time );
            gpio[GPSET0/4]	= 0x07ACE014;
            /*------------------------------------------*/
            /* Gradually speed up the lighting interval */
            /*------------------------------------------*/
            if( sleep_time > 3000 ){
                sleep_time -= 1000;
            }
        }
    }
    /* GPIO deinit */
    gpio[GPFSEL0/4]	= 0x00000000;
    gpio[GPFSEL1/4]	= 0x00000000;
    gpio[GPFSEL2/4]	= 0x00000000;
    munmap( map, BLOCK_SIZE );

    return 0;
}

/****************************************************************/
/* Segment lighting data creation				*/
/****************************************************************/
int	getSegData( int num )
{
    int	data = 0x00000000;
    /*13 14 18 19 23 24 25 26 pin */
    /*DP  B  F  C  A  E  D  G segment */
    data |= (seg_map[num][0] << 13);
    data |= (seg_map[num][1] << 14);
    data |= (seg_map[num][2] << 18);
    data |= (seg_map[num][3] << 19);
    data |= (seg_map[num][4] << 23);
    data |= (seg_map[num][5] << 24);
    data |= (seg_map[num][6] << 25);
    data |= (seg_map[num][7] << 26);

    return data;
}

プログラムの実行方法

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

プログラムの実行結果

ジャンパー線が足りなかったので、配線図とは異なり抵抗を使用していません。

コーディングからデバッグまで行い、特にデバッグに時間がかかる場合、バグが発生して特定のセグメントが点灯しっぱなしになる事が想定されます。
このような時に、抵抗を挟んでいないと電流の流れすぎによってLEDの損傷や劣化が進むので、初心者の方は必ず抵抗を挟むようにして下さい。

Since I didn’t have enough jumper wires, I didn’t use any resistors, unlike the wiring diagram.
The LED will not cut out if there is a short period of continuity, but if you want to see it in action, make sure to put a resistor in between.

プログラムの説明

今回もWiringPiというGPIO制御ライブラリは使わずに実装しました。

多数のGPIO端子を使用したので、GPFSELnレジスタは0~2の3レジスタを使用しています。
なぜこの設定値になるのか、設定値設計に使用した表を添付しますので参考にして下さい。

組み込みソフトウェア ラズパイ GPIO GPFSEL

GPFSEL0~GPFSEL2レジスタ設定値


GPSET0レジスタの設定値は07ACE014Hとなります。
アノード・コモンなので、GPSET0に07ACE014Hを書き込んで全てのピンをHIにする事で、LED消灯状態にしています。
これは、使用しているGPIOピン番号を見て頂ければ、該当ピンを全て1をセットしている事がわかると思います。
下記に、端子割付表のリンクを貼っておきます。

The setting value of the GPSET0 register is 07ACE014H.
Since this is an anode common, we write 07ACE014H to GPSET0 to set all pins to HI, thus making the LED off.
If you look at the GPIO pin numbers used, you will see that all the pins are set to 1.
The following is a link to the terminal assignment table.

絶対にわかる組み込みソフトウェア 7: 4桁7セグLED点灯制御~回路図紹介~ – 鬱リーマン、デイトレとパチスロと不毛な日々

 

DIGIT.1~DIGIT.4を順番にHIレベルに上げて、表示する桁を切り替えています。
1桁の表示時間は、最初は150msからスタートし、徐々に点灯時間を短くしています。
1桁を表示終了した時点で、点灯時間を1ms減算し、最終的には3ms間隔で点灯させています。

このように、1桁ずつ点灯させて全てのLEDを表示させる方式をダイナミック点灯方式と呼びます。
全てのセグメントを常時点灯させない理由は複数ありますが、1つのメリットは消費電流を抑える事です。

この方式は身の回りにある家電で一般的に使われています。
写真やビデオ撮影した時に、7セグなどの表示が欠けて見える事があります。
それは、このダイナミック点灯間隔が長く、かつ、消灯中のセグメントが撮影された場合に起こります。
5ms前後の間隔であれば、肉眼では常時点灯しているように見えます。これより間隔が長いとチラツキがわかります。

 

点灯させるセグメントデータは前回の1桁7セグ表示プログラムと同様に、getSegData()関数で作成しています。
(配線が変わったので、関数内の処理は異なっています。)
使い方は前回と同じで、引数の数値に対応したセグメント点灯データを戻り値で返却します。

この関数については、配線図とGPCLR0レジスタの各ビットの意味をデータシートで確認すれば、何をやっているのかわかりますので、ソースコードが読みやすいように、意図的にマジックナンバーを使って実装しました。

 

今回はここまでとなります。

ラズパイは非常に安価でLinux PCとしても遊べるので、興味がある方は手に取ってみて下さい。

KeeYees電子工作キットにはブレッドボード、抵抗、LED、リード線(コの字ワイヤやジャンパー線)など、電子工作部品が一式揃っているので、電子工作の取っ掛かりにオススメのセットです。

C言語を習得するなら、オススメなのは独習Cです。説明がわかりやすく、これ1冊でC言語マスターになれる名著です。

ご覧いただき、ありがとうございました。
ランキングに参加しています。ポチッと押して去って頂けく方に感謝です。ありがとうございます。
ブログランキング・にほんブログ村へにほんブログ村

人気ブログランキング

コメント

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