HEWが自動生成したiodefine.hの読み方(for C言語)

H8マイコンでI/O操作を行うためには、H8のハードウェアマニュアルで指定された番地に対して、メモリの読み書きをすれば操作出来ます。アセンブリだと特定番地に対しするアクセスは容易ですが、C言語の場合、通常は変数が使用するメモリ番地はコンパイラ/リンカが自動で割り振ってくれるので、通常プログラマが具体的な”メモリ番地”を指定する事が有りません。

ですが、I/O操作に限っては具体的な番地を指定したアクセスが必要となります。この為にHEWではiodefine.hというヘッダを自動生成してくれます。

本記事では、H8マイコンが持っているシリアル通信機能(SCI機能)を使用するときに、iodefine.hの定義をどうやって使うかと、動作の仕組みについて説明します。




H8/3694FでシリアルポートをアクセスするにはSCIという機能を使用し、SCIを操作するためのメモリアドレス(レジスタ)は,iodefine.hの以下の定義がされています。
これは、iodefine.hの最後のほうにあります。

#define SCI3    (*(volatile struct st_sci3  *)0xFFA8)   /* SCI3  Address*/



このdefineですが、ちょっと複雑なので分解して考えてみます。

まずは、キャスト演算子です。
キャスト演算子は、指定した値を別の型とみなしてほしい場合に使用します。
たとえば以下のコードはchar型の変数aを,intとみなして代入しています。

char a = 10;
int b = (int)a;




0xFFA8というのは16進数で表記した単なる数字なのですが、これをメモリ番地(アドレス)とみなしてほしい為にキャストを使用しています。以下のコードで、int *型のポインタ変数cがFF8A番地を指すことができます。

int *c = (int *)0xFFA8;



今回は、int型を指すわけではなく構造体を指したいので、以下のコードになります。(st_sci3構造体については後述します。)

(struct st_sci3  *)0xFFA8;



さらに、コンパイラによる最適化を抑制したいのでvolatileを付けます。

(volatile struct st_sci3  *)0xFFA8;


これで、C言語の文法の仕様を使って、メモリの特定番地を指す事ができました。


構造体のポインタ変数だとメンバを指すのにアロー演算子が必要なので、ソース中のコードが見づらくなります。これを解消するためにアドレス演算子「*」を使います。

(*(volatile struct st_sci3  *)0xFFA8)




上記の表記は長いので、defineで別名定義してしまいます。

#define SCI3    (*(volatile struct st_sci3  *)0xFFA8)   /* SCI3  Address*/




というわけで、先ほどのdefine文が出来上がりました。



次に、先ほど説明を省略したst_sci3構造体です。
これは、ちょっと長いですが以下の定義になっています。

struct st_sci3 {                                        /* struct SCI3  */
               union {                                  /* SMR          */
                     unsigned char BYTE;                /*  Byte Access */
                     struct {                           /*  Bit  Access */
                            unsigned char COM :1;       /*    COM       */
                            unsigned char CHR :1;       /*    CHR       */
                            unsigned char PE  :1;       /*    PE        */
                            unsigned char PM  :1;       /*    PM        */
                            unsigned char STOP:1;       /*    STOP      */
                            unsigned char MP  :1;       /*    MP        */
                            unsigned char CKS :2;       /*    CKS       */
                            }      BIT;                 /*              */
                     }          SMR;                    /*              */
               unsigned char    BRR;                    /* BRR          */
               union {                                  /* SCR3         */
                     unsigned char BYTE;                /*  Byte Access */
                     struct {                           /*  Bit  Access */
                            unsigned char TIE :1;       /*    TIE       */
                            unsigned char RIE :1;       /*    RIE       */
                            unsigned char TE  :1;       /*    TE        */
                            unsigned char RE  :1;       /*    RE        */
                            unsigned char MPIE:1;       /*    MPIE      */
                            unsigned char TEIE:1;       /*    TEIE      */
                            unsigned char CKE :2;       /*    CKE       */
                            }      BIT;                 /*              */
                     }          SCR3;                   /*              */
               unsigned char    TDR;                    /* TDR          */
               union {                                  /* SSR          */
                     unsigned char BYTE;                /*  Byte Access */
                     struct {                           /*  Bit  Access */
                            unsigned char TDRE:1;       /*    TDRE      */
                            unsigned char RDRF:1;       /*    RDRF      */
                            unsigned char OER :1;       /*    OER       */
                            unsigned char FER :1;       /*    FER       */
                            unsigned char PER :1;       /*    PER       */
                            unsigned char TEND:1;       /*    TEND      */
                            unsigned char MPBR:1;       /*    MPBR      */
                            unsigned char MPBT:1;       /*    MPBT      */
                            }      BIT;                 /*              */
                     }          SSR;                    /*              */
               unsigned char    RDR;                    /* RDR          */
};                                                      /*              */



これも、いきなり全体を見ると難しいので、順を追って説明します。

まずは大枠を見ると、これは構造体定義になっています。
構造体内に有る”union…”という定義まで一度に確認すると難しくなるので、これを一旦unsigned charに置き換えてみました。

struct st_sci3 {                                        /* struct SCI3  */
               unsigned char    XXX1;
               unsigned char    BRR;
               unsigned char    XXX2;
               unsigned char    TDR;
               unsigned char    XXX3;
               unsigned char    RDR;
};



置き換えると非常にシンプルになりました。これは単なる6byte文の構造体定義ですね。
先ほどのdefineで、無理やり0xFFA8を指していたので、この構造体によって0xFFA8~0xFFADまでのエリアが、メンバを使うことでアクセスできます。
たとえば以下のコードで0xFFA9番地に0xffを書き込むことができます。

SCI3.BRR = 0xff;



次に、省略したunionのところを見ていきます。
全部同じ形になっているので、最初のメンバを見ると、以下の形になっています。

union {                                  /* SMR          */
     unsigned char BYTE;                /*  Byte Access */
     struct {                           /*  Bit  Access */
            unsigned char COM :1;       /*    COM       */
            unsigned char CHR :1;       /*    CHR       */
            unsigned char PE  :1;       /*    PE        */
            unsigned char PM  :1;       /*    PM        */
            unsigned char STOP:1;       /*    STOP      */
            unsigned char MP  :1;       /*    MP        */
            unsigned char CKS :2;       /*    CKS       */
            }      BIT;                 /*              */
     }          SMR;                    /*              */



unionの中はBYTE, BITと、2つのメンバがあります。
これによって、同じ1byteの領域をBYTEとBITのどちらの名前を使ってもアクセスできるようになります。

BITは、データ型が構造体になっており、メンバをビットフィールドで定義されています.
ですので、たとえば以下の2つのコードはともに0xFFA8番地の最上位ビットに1を書き込んでいます。
(メンバSMRは,st_sci3構造体の最初のメンバなので0xFFA8を指すことになります)

SCI.SMR.BIT.COM  = 1;
SCI.SMR.BYTE    |= 0x80;


両者は同じ処理ですが、BIT指定のほうが特定ビットを変更していることが分かり易いです。

また、SMRレジスタの場合下位2ビットのCKSは、2bitで1つの意味を持つデータです。
なので”CKS :2;”と、最後の数字が2になります。


以上で、iodefine.hで定義されている#define文と構造体定義の見方の説明は終わりです。
他にも沢山の#define定義がありますが、同じように見ていけば理解できます。




補足:ビットフィールドの型指定について
ビットフィールドの場合、変数に指定されたデータ型は意味がないように見えますが、実際のところ意味はなく文法上の都合で書いてあるだけです。
ただし、符号には意味があります。bit単位でのアクセスの場合通常unsignedで指定することが多いですが、一応signedも定義は可能です。例えばsigned char d:3とした場合、符号付きの3bitなので-4~3がセットできます。負の数が指定された時にどのようなビット列がセットされるかは処理系に依存します(通常は2の補数表現です)。


※この記事は、前回書いた内容に対する補足です。
12ステップで作る組込みOS自作入門: 1stステップの作成結果

関連記事

コメントを残す

メールアドレスが公開されることはありません。