- ベストアンサー
組み込み系C言語の学習法
- 組み込み系C言語の学習法について、経験のない方にアドバイスをお願いします。
- 組み込みソフトを長年経験してきたが、C言語の知識が足りない部分があります。
- 特にポインタや構造体や配列についてアセンブラとの関係について知りたいです。
- みんなの回答 (17)
- 専門家の回答
質問者が選んだベストアンサー
質問者さんはCに関しては初心者なので、本当は経験に基づいたありがたい知識なのかもしれませんよ。 早急に結論を出さずに、理解に努めてみたらどうでしょうか? こちらに書いてあるようなことを「確かにね」と思えるようになるまでがんばってみてください。 「プログラミングの禁じ手Web版 C言語編(InternetArchiveよりサルベージ) - Akio’s Log」 http://d.hatena.ne.jp/elwoodblues/20090206/1233878763 「Cプログラミング診断室」 http://www.pro.or.jp/~fuji/mybooks/cdiag/index.html#mokuji ちなみにアセンブラでも個性はありますし、嫌な書き方もさんざん見て来ました。テクニックに走りすぎでレジスタを訳分からん使い方しているのも見ました。 逆に綺麗なコードもあります。そういうのはアリゴリズムの選択やらレジスタの使い方がエレガントなんです。 アセンブラで、ちゃんとモジュールの構造化しているとかでも差がでますからね。
その他の回答 (16)
- OrangeCup150
- ベストアンサー率62% (109/174)
私はアセンブラはあまり分かりませんが、C言語で変数がどのように番地に割り当てられるかを説明します。ご参考いただければ幸いです。(また、正確かどうか確認していないので間違いがありましたらご容赦ください) 開発環境のウィザードが作成したヘッダファイルで番地と変数名を以下のように対応付けています。 #pragma ADDRESS グローバル変数名 番地 これは変数名と番地を対応付けているだけです。 これにあわせて、通常の変数宣言を行います。 これで、変数名、番地、型情報が結びつけられアクセスできます。 構造体も最終的にはメンバ変数の型でアクセスされます。 #pragma ADDRESS xxx 番地 unsigned char xxx; /* 上の pragma ADDRESS で指定した番地を読み書きする変数 */ #pragma ADDRESS yyy 番地 struct { unsigned char a; unsigned char b; unsigned char c; unsigned char d; } yyy; /* 同様に yyy.a が番地に対応し 番地+1 が yyy.b に対応するメンバ変数になります。 */ yyy は yyy.a とおなじ番地ですが yyy は構造体型のサイズ単位(上記の例では4バイト 構造体型のサイズはコンパイラが自動的に計算を行います)、 yyy.a は unsigned char で1バイト単位に読み書きを行います。 IOをメモリの番地で行うタイプであれば、こんな感じでレジスタの操作ができます。 #pragma ADDRESS はこういう目的のためにグローバル変数のアドレスをコンパイラやリンカに指示するために使われます。通常は、コンパイラとリンカは規則にもとづいて最適なアドレスを割り当てます。(たとえば各変数をぎゅうぎゅう詰めにしてメモリ使用量を最小にするか、アクセス速度の良いアライメントに配置して実行速度を稼ぐなど(メモリ使用量のロスが発生する)) アライメントは、たとえばパソコンなどでは下のように空白をおいて、メンバ変数のアドレスが4の倍数で始まるようにすることです。メモリのバスの都合に合わせて高速化しています。組み込みではあまり関係ないかもしれませんが、相互運用性上、アライメントに使用する倍数は環境依存で問題になることがあります。 struct a{ unsigned char a; /* 0 */ /* unsinged char [3] 分の空白がコンパイラによって置かれる */ unsinged int b; /* 4 */ unsinged int c; /* 8 */ }; ただし、 char 型は例外で詰め物はされません。(実用的な仕様だけど変則的) struct a { unsigned char a; /* 0 */ unsigned char b; /* 1 */ unsigned char c; /* 2 */ unsigned char d; /* 3 */ }; 構造体のビットフィールドを使用してビット単位にアクセスできます。 #pragma ADDRESS zzz 番地 union { struct { unsigned char b7:1; /* メンバ変数名:メンバに割り当てるビット数。1ビットずつ割り振ることでビット番号のビットを個別に操作できる */ unsigned char b6:1; /*略*/ unsigned char b1:1; unsigned char b0:1; } bit; unsigned char byte; } zzz; zzz.bit.b7 = 1; zzz.byte = 0x80; 共用体の zzz は pragma ADDRESS で指定した番地を指し、 共用体中の bit 構造体変数と byte 変数は同じ番地(zzz)に対して異なる方法での読み書きを提供します。 つまり、共用体は同じ番地(zzz)に対して異なる型(bit や byte)でアクセスします。 union COLOR_tag { long color; struct { unsigned char r, g, b, x; } RGB; }; union COLOR_tag dot; dot.color = 0xFF00FF00; dot.RGB.r = 0xFF; dot.RGB.g = 0x00; dot.RGB.b = 0xFF; dot.RGB.x = 0x00; 一般的にはRGBカラー値の操作で上記のような共用体を使うことなどで知られています。(共用体なんて使う人はごくわずかですから共用体を使う人にとっては上記は一般的ですが、実際はそれほど知られていないとおもいます。) 補足 型宣言の構文 union タグ名 { メンバ変数; } 変数名 ; struct タグ名 { メンバ変数; } 変数名 ; 型宣言の後に変数名を書いて変数宣言をいっしょにするとタグ名を省略することができます。 しかし、別のところで変数宣言するときにはタグ名を使用する必要があるので、省略しない方が良いです。 配列は、配列の先頭の要素のアドレス+型のサイズ×インデックスのようにアドレスを自動的に解決していると考えてください。 unsigned char a[10]; int i[10]; a[1] では、 a のアドレス + 1 に対する1バイトの操作ですが、 i[1] は i のアドレス + 2 に対する2バイトの操作です。このように、インデックスに型のサイズ(unsinged char は×1 int は×2)を掛けたアドレスを操作します。 (int 型のサイズは環境によってことなり 2byte か 4byte です) 2次元配列の場合、たとえば int ary [10][3] ; の場合、 ary[0][0] 1000 ary[0][1] 1002 ary[0][2] 1004 ary[1][0] 1006 ary[1][1] 1008 ary[1][2] 100A ary[2][0] 100C ary[2][1] 100E 以下略 のように後ろ側の要素数の配列(内側の配列)からアドレス が割り当てられます。 ary[1][2] は、 ary + (sizeof(int[3]) * 1) + (sizeof(int) * 2) のアドレスになります。( sizeof(型名) または sizeof(変数名) はコンパイラが計算したデータ型のサイズに置換されます ) 関数の宣言で void func_x(int a[][3]) や void func_y(int a[][10][3]) のように一番外側の要素数の指定を省略した宣言ができるのも上記の配列の割り振り規則の効用です。(しかし、10個が3セットと間違って解釈している人も多いです。正しくは、3個が10セットのように右側(内側)から解釈します) ポインタについては簡単に述べると変数の番地を持つ変数です。 int x; int *p; /* ポインタの変数宣言 */ p = &x; /* ポインタの書き込み */ *p = 10; /* ポインタの示す先の書き込み */ ポインタを利用するとポインタの示す番地をポインタの型で読み書きできます。 上記の例では p に x の番地を代入し、 int 型で書き込み(10を代入)を行っています。 構造体の場合、矢印演算子を使ってメンバ変数にアクセスする必要があります。 struct x_tag x, *p; p = &x; p->member = 10; *p.member では通常、正しくアクセスできません。 *(p.member) と解釈されるため。すなわち、 ポインタ変数の番地(&p)+メンバ変数オフセット(構造体の先頭からの位置)の番地に対する操作と解釈される。 (*p).member または p[0].member とすると正しくアクセスできます。ポインタの示す番地+メンバ変数のオフセットと解釈される。 どうみても、言語仕様の欠陥*1です。 (*pointer).member や pointer[0].member 表記が好まれなかったために pointer->member 表記が作られたのでしょう。 p[0] や p[1] は、ポインタの示すアドレス+ポインタのデータ型のサイズ×インデックス、でアクセスするアドレスを解決しています。 すこし、話はそれますが・・・。 int* p, x; int *p, x; 上と下は同じです。 p は int 型のポインタ、 x は int 型の変数です。 つまり int 型は p と x 両方に掛かっているのに、ポインタ宣言は各々の変数名にのみ掛かるという規則です。 両方ポインタにするときには各々の変数名に修飾します。 int *p1, *p2; C言語は変数宣言するときの型の装飾規則が非常にややこしいです。普通に使う分にはさほど難しくないですが、 実際に近い使用例は下記のようになります。 #pragma ADDRESS xxx0 0xXXXXXXXX #pragma ADDRESS xxx1 0xXXXXXXXX #pragma ADDRESS xxx2 0xXXXXXXXX #pragma ADDRESS xxx3 0xXXXXXXXX union xxx { struct {
- wormhole
- ベストアンサー率28% (1626/5665)
>カーニハン/リッチーだと、printfなど組み込みには >馴染みのない記述で紹介されており、読むのに億劫になります。 C標準関数など一切使わずに書かれているC入門書がよい。ということですか? おそらくそのようなC入門書は存在しないと思いますけど・・・ C入門書というわけではないですけど、PICの入門書とかはどうでしょうか(私は読んだことないので具体的な書籍名は出せませんけど)
- zwi
- ベストアンサー率56% (730/1282)
三菱系のマイコンに拘らないなら実践主義で、H8とかCコンパイラが無料で使えて書籍の多いマイコンとかで勉強すれば手っ取り早いのでは? 「Amazon.co.jp: C言語による H8マイコン プログラミング入門: 横山 直隆: 本」 http://www.amazon.co.jp/dp/4774118036 >printfなど組み込みには馴染みのない記述で紹介されており、読むのに億劫になります。 printfってマイコンでもシリアルで使う場合がありますよ。幅広いマイコンを使えるように成りたいなら、そんな事を行っている場合じゃないと思いますけどね。
補足
>printfってマイコンでもシリアルで使う場合がありますよ。幅広いマイコンを使えるように成りたいなら… 私の場合、マイコンと通信する場合、パソコンのエクセルのVBAを使って通信します。(EASYCOMMのこと) 受信したデータをエクセルに表示して確認をしてます。 扱うデータはバイナリデータです。 今のところ、printfは全く使いません。 マイコンは旧三菱、旧日立系限定です。 幅広くマイコンは使いません。
- jacta
- ベストアンサー率26% (845/3158)
かつての自分を思い出すようです。 当時はファミコンのプログラミングをアセンブラでやっていましたので、ちょうど740ファミリとほとんど同じアーキテクチャでした。 その経験からいうと、まずは「型」についてしっかり理解してください。 740ファミリのような6502のアーキテクチャでは、8ビットのレジスタしかありませんので、データ幅の異なる値を直接扱う機会もなく、この部分で最初につまづきます。 ポインタは、6502をやっているのであればすぐに理解できるはずです。 アセンブラの命令でいえば、インダイレクト Y アドレッシングモードに相当すると考えればよいでしょう。 実際には、Yレジスタに0をロードして、メモリ上の2バイトの値だけでアドレスを表現することと等価になります。 ただし、ポインタにも型がありますので、上記の方法では先頭バイトしか表せません。したがって、ポインタptrが指すアドレスから16ビットの値をvalueに格納するには、 LDY #0 LDA (ptr),Y STA value INY LDA (ptr),Y STA value+1 のようになるはずです。 配列であれば、アブソリュートX(またはY)ですね。 構造体は、異なる型の要素を集めた配列と考えれば、同じように書くことができます。
補足
iactaさんはどのように、このような苦境を乗り越えられましたか。もう少しお話していただけませんか? 宜しくお願いします
- zwi
- ベストアンサー率56% (730/1282)
構造体なんか、もろインデックレジスタを使ったインデックス・アドレッシングだと思いますけどね。配列もアドレス計算しているだけなのでアセンブラでも普通にやっている事だと思います。 私もアセンブラから入りましたが、自分で言語作成もアセンブラで行なっていたため特にC言語の基本部分では躓いた覚えがありません。 ※ ポインタの加算は勘違いしましたが。 と言うことで、私もアセンブラコードを出力させるという案に賛成です。ただ、知っているアセンブラじゃないと意味が無いと思うので、旧三菱系(R8C/M16C?)のマイコンのC言語コンパイラ(HEWで可能なはず)があるなら、それでアセンブラコードを出力すると良いと思います。
- kngj1740
- ベストアンサー率18% (197/1052)
教科書としては有名なカーニハン/リッチーのもので良いと思います。後は、コンパイル時のオプションでアセンブラソースを出力する事が出来るコンパイラーがほとんどです。出力されたアセンブラソースを見てください。もちろんコンパイラーによってアセンブラへの落とし方は違います。
補足
カーニハン/リッチーだと、printfなど組み込みには 馴染みのない記述で紹介されており、読むのに億劫になります。 あくまでマイコンを使った組み込みがモチーフで書かれているのが良いです。そういう本が見つからないんですよ・・・・。
- 1
- 2
補足
>C標準関数など一切使わずに書かれているC入門書がよい。ということですか? そうです。 例えば、サーミスタで温度を測定し、ファンモータの速度を可変するとかです。 もっと具体的言うと、サーミスタからの入力電圧をAD変換して、 その値を"AD値対温度の表"から温度に変換し、 DAポートからアナログ出力してファンモータの速度指令電圧を可変する。こういう例題を望んでいるのです。 それとか、マイコンとEEPROMとの通信データのやり取りとか。 通信データとかは複数BYTEあるので配列とか使ってやると思います。 この辺の例題があると喜びます。