C言語 組み込みマイコンでポインタをよく使う3つの場面

  • このエントリーをはてなブックマークに追加
Pocket

C言語で一番難解なポインタを使用する場面は、いろいろありますが、
ここでは、組み込みマイコンでよく使用する場面を3つ紹介したいと思います。

・ハードウェアの設定や入出力を扱うとき
・ハードウェア依存部分とアプリケーション部分とのやり取り
・2つ以上の戻り値を持つ関数を作るとき

順に解説していきます。

ハードウェアの設定や入出力を扱うときにポインタを使う

組み込みマイコンでは、必ず、ハードウェアの初期設定を行ってから、プログラムを起動させます。

組み込みマイコンには、あらかじめ決めらたアドレス空間があり、ハードウェアの設定等にアクセスするアドレスが決められています。

その定義のされ方が、構造体や共用体を使って定義されていますので、ポインタでのアクセスが必要になってきます。

どのような定義をされているかは、使用するマイコンによって異なりますので、マイコンの仕様書を読む必要があります。

どのような設定をするかは、用途によって異なるでしょうが、大抵の場合、ポートの入出力モード、割り込み、通信、クロック、メモリの初期化といったところでしょうか。

これらを最初に設定しておかなければ、ハードウェア依存部分のプログラムを作成することはできません。

あなたが構造体、共用体とはなんぞや?と思った場合は、構造体、共用体の項目をお読みください。

組み込みマイコンでは構造体、共用体でハードウェア部分の定義がされているため、この部分はどうしても避けて通れないのですね。

どんな感じで定義されているかイメージを示します。

struct st_sample {
    union {
        short a;

        struct {
            unsigned char b15:1;
            unsigned char b14:1;
            unsigned char b13:1;
            unsigned char b12:1;
            unsigned char b11:1;
            unsigned char b10:1;
            unsigned char b9:1;
            unsigned char b8:1;

            unsigned char b7:1;
            unsigned char b6:1;
            unsigned char b5:1;
            unsigned char b4:1;
            unsigned char b3:1;
            unsigned char b2:1;
            unsigned char b1:1;
            unsigned char b0:1;
        } BIT_S;
     } DATA1;

     short c[4];
};

実際にはもっと複雑に宣言されています。これはあくまでイメージです。
構造体の中に共用体があり、その中にまた構造体があるって、なんじゃこりゃと
思われたかもしれません。

共用体は先頭アドレスがすべて同じです。したがって、例ではDATA1.aとDATA1.BIT_Sの先頭アドレスは同じなので、aのデータをビットで表したものが構造体BIT_Sとなります。

そういう2バイトのデータが1つとcという要素数4つのshort型の配列をメンバに持つsampleという構造体であるということになります。

ハードウェアへアクセスするためのこういう構造体がヘッダファイルで宣言されています。

この構造体へアクセスはこういう感じになります。

struct st_sample* s_sample;

s_sample->DATA1.a = 0;
s_sample->DATA1.BIT_S.b0 = 1;
s_sample->c[0] = 10;

ハードウェアへアクセスするための構造体は、アドレスが定義されています。
したがって、s_sampleにはそのアドレスを代入する必要があります。

しかし、ヘッダファイルには、次のように定義されていることが多いので、その定義を使う方がいいでしょう。

#define SAMPLE (*(struct sample *)(0x00ffd000))


アドレスは適当ですが、このような感じで定義されていますので、SAMPLEを用いて、

SAMPLE.DATA1.a = 0;
SAMPLE.DATA1.BIT_S.b0 = 1;
SAMPLE.c[0] = 10;

と記述することが出来ます。こうしておくと、ポインタでアクセスしているという感じはしなくなりませんか?

ハードウェア依存部分とアプリケーション部分とのやり取りにポインタを使う

組み込みマイコンでは、ハードウェア依存部とアプリケーション部に分かれています。

エレベータでいうと、ボタンが押されて、それをマイコンへ伝わって、入力処理するところまでがハードウェア依存、その入力で、エレベータを目的階へ走行させる部分がアプリケーション部です。

マイコンが変わったとしても、その入力で、エレベータを目的階へ走行させるということは変わらないですよね?入力される部分や処理が変わるだけですよね?

この入力はアプリケーション部から見たら、同じものです。しかし、ハードウェア依存部から見たら、マイコンが違うため、必ずしも同じではありません。

こういうデータのやり取りのためには、ハードウェア側の関数化が必要です。マイコンによって、その関数の中身が異なるというイメージです。アプリケーション側からしたら、同じ関数へアクセスすれば、必要なデータを取得したり、渡したりできるわけです。

このときに必要なのがポインタです。

こういうときのデータは1つではありませんので、配列や構造体でデータをやり取りを行います。

アプリケーション側で、データ格納用配列等を宣言し、それをポインタでハードウェア側へ関数を通じて渡し、そのポインタに入力されたデータを格納したり、必要なデータを取得して出力したりします。

なぜ、こうしておくかというと、マイコンが変わったときに、アプリケーション側までプログラムを変更しなくていいようにしておくためです。

ここも、簡単なイメージプログラムを記述します。
先ほどのst_sample構造体を使用します。

/* ハードウェア依存部 */
struct st_sample {
    union {
        short a;

        struct {
            unsigned char b15:1;
            unsigned char b14:1;
            unsigned char b13:1;
            unsigned char b12:1;
            unsigned char b11:1;
            unsigned char b10:1;
            unsigned char b9:1;
            unsigned char b8:1;

            unsigned char b7:1;
            unsigned char b6:1;
            unsigned char b5:1;
            unsigned char b4:1;
            unsigned char b3:1;
            unsigned char b2:1;
            unsigned char b1:1;
            unsigned char b0:1;
        } BIT_S;
     } DATA1;

     short c[4];
};

#define SAMPLE (*(struct sample *)(0x00ffd000))

void get_input(short* data1)
{
    if (data1 != NULL) {
        *data1 = SAMPLE.DATA1.a;
        for (i = 1; i <= 4; i++) {
            *(data1 + i) = SAMPLE.DATA1.c[i - 1];
        }
    }
}

/* アプリケーション部 */
void app(void)
{
    short input[5];

    /* ハードウェアからデータ取得 */
    get_input(input);

    /* 入力された状態で決められた動作を行う処理が以降続く */
      ・
    ・
    ・
}

なんとなく、イメージできたでしょうか?

マイコンが変わった場合、変更するのはget_input関数の処理だけです。
これがもし、app関数のところにあれば、アプリケーション部分を変更することになってしまいます。

例のようなものだと、app関数内で記述しても、たかがしれてますが、膨大なプログラムになると、あらゆるところからアクセスしたりするので、そこをいちいち修正していては、修正忘れやバグが入りやすくなってしまいます。

したがって、ハードウェアに依存する部分の処理はアプリケーション部分にいれないようにするのが組み込みマイコンでは基本です。

だから、関数化してポインタを使うのです。

2つ以上の戻り値を持つ関数を作るときポインタを使う

これは、組み込みマイコンでなくても、普通にあると思います。
関数はreturn文で返せる値は1つです。

しかし、2つ以上の返り値がほしい場合が実際にあります。こういう場合は、ポインタを渡し、ポインタを操作し、値を返します。

こんな感じです。

#include <stdio.h>

void calculation(short data1, short data2, long* wa, long* sa)
{
    if ((wa != NULL) && (sa != NULL)) {
        *wa = data1 + data2;
        *sa = data1 - data2;
    }
}

void main(void)
{
    short a, b;
    short r1, r2;

    printf("2つの数値を入力してください。\n");
    printf("a = ");
    scanf("%d", &a);
    printf("b = ");
    scanf("%d", &b);

    calculation(a, b, &r1, &r2);

    printf("a + b = %d/n", &r1);
    printf("a - b = %d/n", &r2);
}

単純に2つの数値を入力して、その和と差を求めて、結果を出力したものです。
わざわざ関数化するほどのものではありませんが、戻り値2つの関数を作りたいときのポインタ利用方法を解説するために作ってみました。

これはよくやる手法なので、覚えておいて損はないはずです。

まとめ

C言語の組み込みマイコンでポインタを使う場面を解説しました。
ハードウェア依存側を作らなければ、ポインタを使う場面が少ないかもしれません。

しかし、組み込みマイコンのプログラミングをしていくのであれば、ハードウェア依存側もプログラミングできなければ、プログラマーとして伸びていきません。

ハードウェア依存部分は一度作ってしまえば、マイコンが変わらない限り、触ることがないため、アプリケーション部分しか触れないプログラマーも実際にいます。

したがって、あなたは、ハードウェアの仕様書も読みながら、プログラミングをしていけるようになってください。

  • このエントリーをはてなブックマークに追加

コメントを残す

*