こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

組み込み系C言語の学習法

私は組み込みソフトを8年やってきました。
規模としては、白物家電をイメージしてもらうと良いです。
但し、電子レンジなど表示機能がない製品が近いかもしれません。
近いものでは、洗濯機や冷蔵庫といった感じでしょうか。
マイコンは、旧三菱の740ファミリのM38000系です。

ただソフト経験(アセンブラ)はあるものの、C言語の経験は皆無なのです。
ただし、if-else, switch-case, for, while, break, continue は充分理解できます。
何故かというと、旧三菱系のマイコンには構造化アセンブラと言って、
C言語もどきの記述ができるのです。上記文のみ特別に設けられていたのです。
それを使ってソフトを作っていたので上記分は理解できるのです。
しかし、ややこしいのがポインタや構造体や共有体や配列です。
同じことをアセンブラでもやっているはずなのです。
ポインタや構造体や共有体をアセンブラで書くとどうなるかが知りたいのです。
ここの部分が書いてある書籍がないのです。
例えば、ポインタや配列は、旧三菱系で言うとXレジスタやYレジスタを
使って行っていた作業と似てるのかなと推測できます。

同じ境遇の方で克服された方がいらっしゃいましたらアドバイスをお願いします。
また、アマゾンで下記の本を見つけました。
参考になるのでしょうか?
よろしくお願いします。

 ""組込み現場の「C」プログラミング基礎からわかる徹底入門""

投稿日時 - 2013-01-18 17:52:35

QNo.7898181

困ってます

質問者が選んだベストアンサー

質問者さんは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


ちなみにアセンブラでも個性はありますし、嫌な書き方もさんざん見て来ました。テクニックに走りすぎでレジスタを訳分からん使い方しているのも見ました。
逆に綺麗なコードもあります。そういうのはアリゴリズムの選択やらレジスタの使い方がエレガントなんです。
アセンブラで、ちゃんとモジュールの構造化しているとかでも差がでますからね。

投稿日時 - 2013-01-20 16:31:23

ANo.17

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(17)

ANo.16

zwi

Visarさん、wormholeさん、その等号条件の定数問題は平行線を辿るのでこの位で良いのではないでしょうか? 好き嫌いのレベルだと思います。

投稿日時 - 2013-01-19 20:59:34

補足

確かに、"好き嫌い"は、個人の性格ですよね。
私が言いたいのがこれなんですよ。

他人の書いたコードを見て、「スマートじゃない」とか
「私だったらこうするとあーするとか」
ゴチャゴチャ言うのです。
正直、付き合いきれないのです。

アセンブラの場合は、書き方に自由度が無いため、
個人の性格は反映されにくいので、職場は円満でした。
今の職場は数名いますが、性格が2つに分かれます。
凝性な人とそうでない人です。
私は後者です。
凝性な人は、職人さん気質なので妥協しないのです。

C言語の話が職場の話になってしまいましたね。すいません。

投稿日時 - 2013-01-20 15:52:15

ANo.15

>比較演算子で定数を左にもってくるコーディングは、理由があってしています。
(以下略)

その理由は知っています。
ですが左辺、右辺両方とも変数だったらどうするんですかね。
片方が定数の時はコンパイラがエラーにしてくれるかもしれませんが、共に変数だった場合には役に立ちません。
そんな事なら「常に比較演算子を使うようなときには気をつける」そういう癖をつける方がよっぽどいいと私は思います。

投稿日時 - 2013-01-19 20:27:30

ANo.14

こんばんは。

>比較演算子で定数を左にもってくるコーディングを推進してるのや
>NULLと0を混同してるようなのはうんざりしますけど読めないわけじゃないですし。

NULLと0の混同は問題外ですが、

比較演算子で定数を左にもってくるコーディングは、理由があってしています。

'80年代前半のCコンパイラはとっても賢くて、
'=='と'='を打ち間違えても、ワーニングとか出してくれませんでした。


if( a == 0 ) {
 なんたら
}


if( a = 0 ) {
 なんたら
}
とかした場合です。

しかし、定数を左辺に持っていくコーディングをすると、
'=='と'='を打ち間違えたら
if( 0 = a ) {
 なんたら
}
いくら超賢い'80年代のCコンパイラでも、さすがに
0にaは代入できないのでコンパイルエラーとなります。

化石プログラマーの転ばぬ先の杖的な発想です。

P.S.
もうひとつ、理由があって、当時のディスプレーは、
80桁×25桁の表示能力しか無くて、比較演算子の定数を右側に書くと、
ソースを縦スクロールして眺めるときに、ソースが横に長すぎると、
演算子の定数を見るのに横スクロールも必要になるので面倒で
見づらいからです。

過去の遺物かな。

失礼しました。

投稿日時 - 2013-01-19 19:16:39

ANo.13

>やたらとdefineで文字化してるし、型の取り方が自分と違うし正直まいってます。

「やたらdefine」というのは文字列化ではなく定数化かと。
数値に名前を付けることで可読性を上げるのと数値の変更に柔軟に対応できるようにしてるんだと思います。
数値を直接ソース上に書いた場合、その数値の意味はコメントに書かなければわかりませんし、その数値が多数の箇所で使われているような場合、その数値を探し出して変更していかなければなりません。また同値で意味が異なるものがあるような場合はさらに面倒なことになります。
アセンブラでもシンボルに値を設定するとかすることあると思いますけど使ったことありませんか?

投稿日時 - 2013-01-19 18:18:57

補足

使ったことはあります。
多数の箇所で使われているような場合は納得できるし、
後で数値が変更になる場所とかね。
人によって、一箇所の場所でも定数化(名前化)するのです。
それもあまり変更するような個所でも。
だからソフトって人の性格なんですよね。
上手い下手というより個性だと私は思うのです。
気が合う人とは友達や恋人になりますが、
そうでない人とはそうはなりません。
みなさんはどう思われますか?

投稿日時 - 2013-01-20 15:59:51

ANo.12

>この辺はみなさんどのように感じて仕事されてますか?

職場ではコーディングスタイルの統一などあってますが私自身はよほど変な書き方でも無い限り別段気になりません。
比較演算子で定数を左にもってくるコーディングを推進してるのやNULLと0を混同してるようなのはうんざりしますけど読めないわけじゃないですし。

投稿日時 - 2013-01-19 18:10:22

ANo.11

zwi

>私は質問にも書きましたが、アセンブラ(構造化アセンブラ)経験者です。
>要するに、凝りに凝ったC言語風の書き方は出来ないです。
>アセンブラ風のC言語みたいな感じです。

時代の流れとしてC言語が必要になったのであれば受け入れるしか無いでしょうね。
組み込み現場によってはC++やJavaが使われている所もありますので、いきなりオブジェクト指向言語に成らないだけでも高級アセンブラと呼ばれるC言語でよかったな思うべきでしょう。

書き方に関しては共同作業を前提として統一コーディング・スタイルを決めるている職場は結構あります。作業に支障がありますから。
ただ、個別作業の多い現場では各人が個性的に書いている所もあるってのが現状で、そういう職場なのだと思います。
慣れてくれば大抵は読めるようになりますが、それでも読みたくないコードはあります。今の状態が嫌ならコーディング・スタイルの統一を働きかけてみたら良いでしょう。

投稿日時 - 2013-01-19 17:47:11

ANo.10

こんにちは。

>例えば、サーミスタで温度を測定し、ファンモータの速度を可変するとかです。
>もっと具体的言うと、サーミスタからの入力電圧をAD変換して、
>その値を"AD値対温度の表"から温度に変換し、
>DAポートからアナログ出力してファンモータの速度指令電圧を可変する。こういう例題を望んでいるのです。

そのなので良いのなら、工業高校生が部活でやってますよ。
ルネサス主催のMCR、「マイコン・カー・ラリー」のサイトの
技術情報ダウンロードサイトが参考になるのではありませんか。
※MCR:何とか・コントロール・◎◎◎ウェアてな感じじゃないです。

彼らは、H8マイコン(H8/3048F-ONE、H8/3687F)やR8Cマイコンで
AD/DA、EEPROM、PWM、UART、SPIやエンコーダを使いこなしてますよ。

参考URL:http://www.mcr.gr.jp/tech/download/main01.html

投稿日時 - 2013-01-19 13:14:48

ANo.9

zwi

あっ書き忘れました。
残念ながらM38000(MELPS740)の日本語の書籍は私は一度も見た事はありませんので、他のもっとアマチュアにも知名度があるマイコンを学習用には選択すべきだと思います。

投稿日時 - 2013-01-19 13:07:43

ANo.8

zwi

完全に無視されている気もしますが私の書いた
「Amazon.co.jp: C言語による H8マイコン プログラミング入門: 横山 直隆: 本」
http://www.amazon.co.jp/dp/4774118036
ではダメなのでしょうか? ダメな理由をお聞かせください。
旧日立系という条件はクリアしていると思いますが、これがダメなら具体的にOKなマイコン名を羅列して頂かないと的確に答えられる人は少ないと思います。

投稿日時 - 2013-01-19 12:36:59

補足

返事遅くなってすいません。
ネット上では全部の中身は見れないですが、駄目ではないです。
また、ルネサスのセミナーのテキストもあるので、合わせてみてみるのもいいかもしれません。

話が反れるかもしれませんが、もうひとつ相談に乗ってもらえませんか?
私は質問にも書きましたが、アセンブラ(構造化アセンブラ)経験者です。
要するに、凝りに凝ったC言語風の書き方は出来ないです。
アセンブラ風のC言語みたいな感じです。
もちろん関数の引数や返し値は使います。
C言語って個人の性格が反映されがちで個性がでます。
性格が似た人同士ならソフトを見ても理解できますが、
性格が違う人のを見るとうんざりします。
やたらとdefineで文字化してるし、型の取り方が自分と違うし
正直まいってます。

この辺はみなさんどのように感じて仕事されてますか?
宜しくお願いします

投稿日時 - 2013-01-19 16:51:45

ANo.7

>例えば、サーミスタで温度を測定し、ファンモータの速度を可変するとかです。
>もっと具体的言うと、サーミスタからの入力電圧をAD変換して、
>その値を"AD値対温度の表"から温度に変換し、
>DAポートからアナログ出力してファンモータの速度指令電圧を可変する。こういう例題を望んでいるのです。

さすがにピンポイント過ぎますし限定されたハードウェアでの話になりそうなのでC入門書としてはないと思います。出版しても売れないでしょうし・・・
でもそういうのをアセンブラで組める方ならアセンブラでひとまず書いて人力で逆コンパイルしてCソースにするとかできそうな気がしますけど。

投稿日時 - 2013-01-19 11:47:22

ANo.6

私はアセンブラはあまり分かりませんが、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 {

投稿日時 - 2013-01-19 10:01:46

ANo.5

>カーニハン/リッチーだと、printfなど組み込みには
>馴染みのない記述で紹介されており、読むのに億劫になります。

C標準関数など一切使わずに書かれているC入門書がよい。ということですか?
おそらくそのようなC入門書は存在しないと思いますけど・・・
C入門書というわけではないですけど、PICの入門書とかはどうでしょうか(私は読んだことないので具体的な書籍名は出せませんけど)

投稿日時 - 2013-01-19 01:17:00

補足

>C標準関数など一切使わずに書かれているC入門書がよい。ということですか?

そうです。
例えば、サーミスタで温度を測定し、ファンモータの速度を可変するとかです。
もっと具体的言うと、サーミスタからの入力電圧をAD変換して、
その値を"AD値対温度の表"から温度に変換し、
DAポートからアナログ出力してファンモータの速度指令電圧を可変する。こういう例題を望んでいるのです。

それとか、マイコンとEEPROMとの通信データのやり取りとか。
通信データとかは複数BYTEあるので配列とか使ってやると思います。
この辺の例題があると喜びます。

投稿日時 - 2013-01-19 10:00:25

ANo.4

zwi

三菱系のマイコンに拘らないなら実践主義で、H8とかCコンパイラが無料で使えて書籍の多いマイコンとかで勉強すれば手っ取り早いのでは?

「Amazon.co.jp: C言語による H8マイコン プログラミング入門: 横山 直隆: 本」
http://www.amazon.co.jp/dp/4774118036


>printfなど組み込みには馴染みのない記述で紹介されており、読むのに億劫になります。

printfってマイコンでもシリアルで使う場合がありますよ。幅広いマイコンを使えるように成りたいなら、そんな事を行っている場合じゃないと思いますけどね。

投稿日時 - 2013-01-18 22:08:35

補足

>printfってマイコンでもシリアルで使う場合がありますよ。幅広いマイコンを使えるように成りたいなら…

私の場合、マイコンと通信する場合、パソコンのエクセルのVBAを使って通信します。(EASYCOMMのこと)
受信したデータをエクセルに表示して確認をしてます。
扱うデータはバイナリデータです。
今のところ、printfは全く使いません。

マイコンは旧三菱、旧日立系限定です。
幅広くマイコンは使いません。

投稿日時 - 2013-01-19 10:16:06

ANo.3

かつての自分を思い出すようです。
当時はファミコンのプログラミングをアセンブラでやっていましたので、ちょうど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)ですね。

構造体は、異なる型の要素を集めた配列と考えれば、同じように書くことができます。

投稿日時 - 2013-01-18 19:56:16

補足

iactaさんはどのように、このような苦境を乗り越えられましたか。もう少しお話していただけませんか?
宜しくお願いします

投稿日時 - 2013-01-19 17:03:51

ANo.2

zwi

構造体なんか、もろインデックレジスタを使ったインデックス・アドレッシングだと思いますけどね。配列もアドレス計算しているだけなのでアセンブラでも普通にやっている事だと思います。

私もアセンブラから入りましたが、自分で言語作成もアセンブラで行なっていたため特にC言語の基本部分では躓いた覚えがありません。
※ ポインタの加算は勘違いしましたが。

と言うことで、私もアセンブラコードを出力させるという案に賛成です。ただ、知っているアセンブラじゃないと意味が無いと思うので、旧三菱系(R8C/M16C?)のマイコンのC言語コンパイラ(HEWで可能なはず)があるなら、それでアセンブラコードを出力すると良いと思います。

投稿日時 - 2013-01-18 19:38:05

ANo.1

教科書としては有名なカーニハン/リッチーのもので良いと思います。後は、コンパイル時のオプションでアセンブラソースを出力する事が出来るコンパイラーがほとんどです。出力されたアセンブラソースを見てください。もちろんコンパイラーによってアセンブラへの落とし方は違います。

投稿日時 - 2013-01-18 18:32:06

補足

カーニハン/リッチーだと、printfなど組み込みには
馴染みのない記述で紹介されており、読むのに億劫になります。
あくまでマイコンを使った組み込みがモチーフで書かれているのが良いです。そういう本が見つからないんですよ・・・・。

投稿日時 - 2013-01-18 21:11:35

あなたにオススメの質問