電子書籍の厳選無料作品が豊富!

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

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

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

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

A 回答 (17件中11~17件)

>例えば、サーミスタで温度を測定し、ファンモータの速度を可変するとかです。


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

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

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

>カーニハン/リッチーだと、printfなど組み込みには


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

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

この回答への補足

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

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

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

補足日時:2013/01/19 10:00
    • good
    • 0

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



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


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

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

この回答への補足

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

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

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

補足日時:2013/01/19 10:16
    • good
    • 0

かつての自分を思い出すようです。


当時はファミコンのプログラミングをアセンブラでやっていましたので、ちょうど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さんはどのように、このような苦境を乗り越えられましたか。もう少しお話していただけませんか?
宜しくお願いします

補足日時:2013/01/19 17:03
    • good
    • 0

構造体なんか、もろインデックレジスタを使ったインデックス・アドレッシングだと思いますけどね。

配列もアドレス計算しているだけなのでアセンブラでも普通にやっている事だと思います。

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

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

教科書としては有名なカーニハン/リッチーのもので良いと思います。

後は、コンパイル時のオプションでアセンブラソースを出力する事が出来るコンパイラーがほとんどです。出力されたアセンブラソースを見てください。もちろんコンパイラーによってアセンブラへの落とし方は違います。

この回答への補足

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

補足日時:2013/01/18 21:11
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!