マイコンのC言語プログラムの構造体の宣言とvolatileの必要性について

このQ&Aのポイント
  • マイコンのC言語プログラムで、構造体の宣言が不要な場合とは?
  • マイコンのC言語プログラムで、volatileが必要なのはなぜ?
  • マイコンのC言語プログラムにおける構造体の宣言とvolatileの必要性について解説します。
回答を見る
  • ベストアンサー

マイコンのプログラム(C言語)について

マイコンのサンプルプログラムの中に以下のような記述がありました。 ヘッダーファイル内 struct st_hudi { union { unsigned short WORD; struct { unsigned short TI:8; } BIT; }SDIR; }; #define HUDI (*(volatile struct st_hudi *)0xFFFE2000) そして、この構造体の使い方は HUDI.SDIR.WORD = 0; と、いった感じです。 通常は、構造体の宣言(struct st_hudi aなど)を行わないと使えないと思うのですが、以上のプログラムでエラーは出ません。 1.上記のような場合は、構造体の宣言はいらないのでしょうか? 現在は、#defineの部分でアドレスを指定してあるため、構造体の宣言はいらない、という認識を持っています。 2.volatileがなくても動きそうなのですが、これは必要なのでしょうか? アドレスを明記しているため、volatileがなくても正常に動きそうな気がします。 回答よろしくお願いします。

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

  • ベストアンサー
  • chie65535
  • ベストアンサー率43% (8528/19386)
回答No.5

>ためしに >struct st_hudi *p = HUDI; >と記述してみても、型が違うとエラーを吐かれてしまいました。 HUDIは「実体」なので、アドレスが欲しい場合は & 演算子を使う。 struct st_hudi *p = &HUDI; 因みに、構造体のポインタをインクリメントすると、アドレスが+1されるとは限らない。 アドレスが幾つ足されるかは「処理系に依存する」ので、やってはいけない。 もし「構造体や変数は、常に4バイト境界に配置する」と言う処理系の場合、p++すると、pは「構造体のサイズより大きい、4の倍数ごと」にアドレス加算される。 例えば、構造体サイズが1~4バイトなら4、構造体サイズが5~8バイトなら8がアドレス加算される事になる。 今回の場合も、p++した場合、0xFFFE2000が0xFFFE2002になるか0xFFFE2004になるか0xFFFE2008になるか、やってみないと判らない。 もし、やってみて「期待通りになった」としても、コンパイラを別の物に変えたら、期待通りにはならなくなる可能性がある(移植性が無く、処理系依存になってしまう)

ieee8140
質問者

補足

移植性については全く考えていませんでした。 構造体を配列にするのではなく、構造体のメンバを配列にすることで対応しようと思います。

その他の回答 (5)

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.6

処理系不明なので正確なことは何ともいえません。 > 1.上記のような場合は、構造体の宣言はいらないのでしょうか? > 現在は、#defineの部分でアドレスを指定してあるため、構造体の宣言はいらない、という認識を持っています。 確かにそうなのですが、整数値 0xFFFE2000 をポインタにキャストした場合の結果は処理系定義です。 ハードウェア的なアドレスと一致するかどうかもわかりません。 > 2.volatileがなくても動きそうなのですが、これは必要なのでしょうか? > アドレスを明記しているため、volatileがなくても正常に動きそうな気がします。 volatileがなければ、最適化によって、ソースコード上では記述されている読み書きが、実際には省略されてしまったり、順序が入れ替わったりすることがあります。 ただ、制御レジスタなどはワード単位でアクセスしなければならない場合が多いのですが、volatileを付けていたとしても、バイト単位でアクセスしてしまう可能性があり、正しく動作するかどうかわかりません。 実際には、特定のハードウェアや特定のコンパイラに特化したコードだと思いますので、そうした状況下では正しく動く可能性はあります。

  • chie65535
  • ベストアンサー率43% (8528/19386)
回答No.4

>1.(*(volatile struct st_hudi *)0xFFFE2000)この部分の考え方は >(volatile struct st_hudi *)が明示的なキャストを示しているのでしょうか? そう。 「(volatile struct st_hudi *)0xFFFE2000」は、0xFFFE2000を構造体のポインタにキャストしている。 >そして、*がついているので、0xFFFE2000の中身を表すということで合っていますか? ちがう。 HUDIを「 (*『何かのポインタ』)」と定義している。 「何かのポインタの中身」として定義しているから HUDI->SDIR.WORD = 0; ではなく HUDI.SDIR.WORD = 0; と書く事が出来る。 >2.volatileがなくても動きそうなのですが、これは必要なのでしょうか? 必要。 これの意味は「書き込む際、読み出す際に、前の値がどうなっているか忘れて、毎回、指定のメモリに読み書きしなさい」って意味。 じゃないと a=HUDI.SDIR.WORD; /*最初の状態を読み出す*/ (一秒待つ処理) b=HUDI.SDIR.WORD; /*1秒後の状態を読み出す*/ って言うコーディングをした時に、最適化されてしまって a=HUDI.SDIR.WORD; /*最初の状態を読み出す*/ (一秒待つ処理) b=a;          /*1秒後の状態を読み出す筈なのに読み出さない*/ というプログラムになってしまいます。 余計な最適化されちゃうと、もし「aに読み出した後、1秒後にbに読み出し、aとbが違う値になっている時に、何か行う」って言う書き方をしたら、正常に動作しません。 なので「読み書きする時は、面倒でも、毎回ちゃんと読み書きしてね」って言う指示が「volatile」なのです。

ieee8140
質問者

補足

1. 認識を正していただきありがとうございます。 言われるまで「->」か「.」かは気にしていませんでした。 2. >読み書きする時は、面倒でも、毎回ちゃんと読み書きしてね このように柔らかく表現していただけると、理解しやすいです。 ありがとうございます。

  • unacyo
  • ベストアンサー率51% (35/68)
回答No.3

1については、その通りですね。 私はポインタでしか使いませんでしたが、キャストの前に*を着けると、"."でのアクセスになります。 2については、無いと動作が変わります。 ステータスレジスタの類いは、書き込むのはハードウェアで、ソフトウェアは読むばかりなので、最適化が働くと読み込みが省略されて、まともに動作してくれません。 複数回連続で読み込む場合とか、読み込まなかったり、一回しか読まないコードになっていたりと、困った結果になります。 新人の頃これを知らず、デバッグで苦労した覚えがあります。

ieee8140
質問者

補足

回答ありがとうございます。 volatileについての知識が深まりました。 私は現在新人で、同様に苦労していました。

  • KEIS050162
  • ベストアンサー率47% (890/1879)
回答No.2

(ファーム系のプログラミングから離れて、相当年月が経っているので、かなりウロ覚えではありますが…) 1.の認識は概ねその通りです。 define分で、既に共用体の宣言がされている実アドレス(恐らく、メモリマップされたI/O空間なのでしょう)が定義されているので、参照するときは、共用体の中のどの型で参照しているか(構造体を利用するときは、そのメンバー)を明記するだけで参照できます。 例では、WORD (unsigned short)で参照されています。(ビット参照、ワード参照が可能な様に共用体宣言されているのでしょう) レジスタ制御する為の共用体宣言の例 http://monoist.atmarkit.co.jp/mn/articles/0701/23/news094.html 2.必要です。 コンパイラの最適化の処理によりますが、意図しない最適化を防ぐために、明記しておくのが無難でしょう。 具体的な例は、下記URLを参照してください。 http://proger.blog10.fc2.com/blog-entry-20.html 要は、先ほど述べた メモリマップされたI/Oなどの場合、初期設定以降は単に変化を待ち続ける場合多々ありますが、それを明示してあげないと、コンパイラが”恒久的に変化しない変数”という風に勘違いしてしまうことがあって、プログラマが意図しない方向に最適化してしまう場合がある、ということですね。 ご参考に。

ieee8140
質問者

補足

参考URLも記載していただきありがとうございます。 URL先を読ませていただきました。 その結果、以下のような新しい疑問が出てきました。 もしも、このような手法(任意のアドレスに宣言したい)で構造体の配列を用いたい時はどのようにすれば良いのでしょうか。 構造体のポインタを作成し、インクリメントしていけば大丈夫なのでしょうか? ためしに struct st_hudi *p = HUDI; と記述してみても、型が違うとエラーを吐かれてしまいました。

  • hashioogi
  • ベストアンサー率25% (102/404)
回答No.1

Q.通常は、構造体の宣言(struct st_hudi aなど)を行わないと使えないと思うのですが… A.プリプロセサが HUDI.SDIR.WORD = 0; を (*(volatile struct st_hudi *)0xFFFE2000).SDIR.WORD = 0; にしますので翻訳のエラーは出ません。 Q.アドレスを明記しているため、volatileがなくても正常に動きそうな気がします A.volatileは、マルチタスクで動作していたり、割り込みが動作するような場合は誰がいつアドレス0xFFFE2000の内容を書き換えるかわからないのでこのアドレスをアクセスする場合は最適化をしないで下さいという指定です。ですからvolatileを外したら正常に動かなくなる場合があります。

ieee8140
質問者

補足

1.(*(volatile struct st_hudi *)0xFFFE2000)この部分の考え方は (volatile struct st_hudi *)が明示的なキャストを示しているのでしょうか? そして、*がついているので、0xFFFE2000の中身を表すということで合っていますか? 2.納得できました。

関連するQ&A

  • [C言語]変数のアドレスを直接指定する構文

    C言語でのマイコン開発環境に付属しているヘッダーファイルにはレジスタを共用体と構造体でアクセスできるようにしてあります。 アドレスを割り付ける部分の記述は一行ですが、意味を考えた時に構文が複雑すぎて躓いてしまいました。 #define PD (*(volatile struct st_pd *)0xFFFF83A0) /* PD Address*/ 後ろの"*"はst_pdのポインタ型だとして前の"*"はどういう効果になっているのでしょうか? また、複数行に分解して同じ効果を書くこと(例えば変数unsigned long valにアドレス0xFFFF83A0を割り付け)は出来るのでしょうか? 理解を助けるためにお願いします。

  • H8マイコン C言語でのプログラミング *((volatile unsigned char *)ってなんですか??

    現在、ある参考書に載っている、マイコンを用いてマザーボード上のLEDを点滅させるCのプログラムを勉強のために見ています。 その中に、 #define P5DDR (*(volatile unsigned char *)0xfffc8) という一行があるんですが、この中の*(volatile unsigned char *)という表現を見たことがなくて困っております。これは簡単に言うと、 #define P5DDR *0xfffc8 と、『P5DDRを0xfffc8のポインタで置き換える』と言い換えていいのでしょうか??

  • プログラム(C)

    #include <stdio.h> #include <stdlib.h> #define FNAME "smp.bmp" #define WSIZE 256 #define HSIZE 256 #define BSIZE 1024 int main(void) { struct BMPFILEHEADER { ・   ・ }; struct BMPINFOHEADER { ・   ・ }; unsigned char img[HSIZE][WSIZE][3]; unsigned char buf[BSIZE]; struct BMPFILEHEADER lpHead; struct BMPINFOHEADER lpInfo; FILE *fp; int i; int j; int k; fp = fopen(FNAME,"rb"); if (fp==NULL) { printf("ファイルをオープンできません\n"); return 0; } fread(&lpHead.bfType, sizeof(unsigned short),1,fp); fread(&lpHead.bfSize, sizeof(unsigned int),1,fp); fread(&lpHead.bfReserved1, sizeof(unsigned short),1,fp); fread(&lpHead.bfReserved2, sizeof(unsigned short),1,fp); fread(&lpHead.bf0ffBits, sizeof(unsigned int),1,fp); fread(&lpInfo, sizeof(struct BMPINFOHEADER),1,fp); for(i=0;i<HSIZE;i++) { fread(buf,sizeof(unsigned char),WSIZE*3,fp); for(j=0;j<WSIZE;j++) { for(k=0;k<3;k++) { img[HSIZE-1-i][j][k]=buf[j*3+k]; } } } fclose; return 0; } このプログラムはBMP画像を読み込むプログラムなんですが このプログラムに画素値を出力するプログラムにしたいのですがうまくできません。 結果は(真っ白な画像の時)255255255・・・255255と出力したいのです。白黒画像なのでR=G=Bで1画素値は255だけでいいのですが。どうしても255255255や25500などとでてしまいます。 アドバイスお願いします。(800字までなので構造体の宣言は抜いてしましました) 。

  • マイコンH8-3664 走行カー C言語プログラム改良

    SW0がONで3秒間前進して停止 SW0がOFFの後、再度ONで3秒間前進して停止  するプログラムです! これを、 3秒間前進後、3秒右旋回(右モーターのみ回転)後、3秒間前進、3秒右旋回を繰り返し、元の位置に戻るというプログラムに改良せよ!というのですが? /**************************************************************/ #include <stdio.h> #include <sysio.h> /*タイマーA*/ #define TMA (*((volatile unsigned char *)0xFFA6) /*割り込み*/ #define IENR1 (*((volatile unsigned char *)0xFFF4)) #define IRR1 (*((volatile unsigned char *)0xFFF6)) #define PDR8 (*((volatile unsigned char *)0xFFDB)) #define PCR8 (*((volatile unsigned char *)0xFFEB)) long count = 0; long x = 0; void interrupt timer( ) { IRR1 &= ~0x40 ; if( count == 300 ) { x = 1 ; count = 0 ; } else { count++; } } void main( ) { unsigned char a; TMA = 0x03 ; IENR1 |= 0x40 ; PCR8 = 0x0f ; while(1){ a = PDR8 >> 4 ; if( x == 0 && a == 0x0e ) { PDR8 = 0x0a ; _ei( ) ; } else if( x == 1 ) [ PDR8 = 0x00; _di( ) : if (( PDR8 >> 4) == 0x0f) { x = 0 ; } } } } /**************************************************************/ 尚、SW はプルアップされている!

  • 共用体のアドレスを取得したい

    SH7047のプログラムです。 何をしたいのかと言うと、P_PORTD.PDDRL.BIT.PD4DR等を関数の中で変化させたいのです。 うまく行かない理由はおそらく「P_PORTD.PDDRL.BIT.PD4DRのアドレスは渡すことが出来ない」だとおもいます。 まぁそれも当然な話だと思うのですが、どうにかならないでしょうか? 個人的には「unsigned short* port」→「bit* port」の様に出来れば理想的なのですが。 何か良い方法を教えてください。よろしくお願いします。 #define P_PORTD (*(volatile struct st_portd *)0xFFFF83A2)/* PORTD Address */ struct st_portd { /* struct PORTD */ union { /* PDDRL */ unsigned short WORD; /* Word Access */ struct { /* Bit Access */ unsigned short :7; /* */ unsigned short PD8DR:1; /* PD8DR */ unsigned short PD7DR:1; /* PD7DR */ unsigned short PD6DR:1; /* PD6DR */ unsigned short PD5DR:1; /* PD5DR */ unsigned short PD4DR:1; /* PD4DR */ unsigned short PD3DR:1; /* PD3DR */ unsigned short PD2DR:1; /* PD2DR */ unsigned short PD1DR:1; /* PD1DR */ unsigned short PD0DR:1; /* PD0DR */ } BIT; /* */ } PDDRL; /* */ }; /* */ void main(void) { output(&P_PORTD.PDDRL.BIT.PD4DR,0xff); output(&P_PORTD.PDDRL.BIT.PD5DR,0xff); output(&P_PORTE.PEDRL.BIT.PE12DR,0xff); } void output(unsigned short* port,int data) { P_PORTE.PEIORL.WORD = 0x3fff; P_PORTE.PEDRL.WORD = data & 0xff; *port = 1; *port = 0; P_PORTE.PEIORL.WORD = 0x3f00; }

  • C言語 #defineについて

    C言語でどの様に考えれば良いか分からない事があるので、教えて下さい。 以下の様な行が出てきたのですが、どういう意味なのでしょうか? #define SYSTEM (*(volatile struct st_system *)0x80000) 現状分かっている事は 1.#define ABC 100   ならこれ以降で出てくるABCは100と読み替える。 2.volatileはコンパイラの最適化を抑制する様な意味。 とい事です。 以上、宜しくお願い致します。

  • C言語構造体についてです。

    C言語、π=PI 3.14159265で構造体を用いてmensekiを呼び出して 半径と面積を計算して出力するプログラムです。下の空白を埋めてもらえますか? /* 構造体のプログラム */ #include <stdio.h> #include <math.h> #define PI 3.14159265 struct Data { /* 半径rと面積areaをdoubleで宣言 */ }; void menseki(struct ... 構造体のポインタを引数とする ){ /* 面積を計算する際に構造体のアドレスから計算する */ circle->area=PI* .....; } main(){ struct Data example[2]; //構造体配列 int i; /* 半径rを構造体配列を用いて2例入力する */ /* 2例の各々について mensekiを呼び出して 半径と面積を計算して出力する */ } よろしくお願いします。

  • H8 外部バスのデータアクセス

    H8 外部バスのデータアクセス ルネサスのH8マイコン初心者です。(C言語も初心者です) 外部バスのCS1(0x200000)に512KBのSRAMが繋がっているのですが、 HEWのセクションを変更せず、直接SRAMに対して読み書きしたいと思っています。 バスコントローラは設定済みという前提で以下の(A)~(C)のように 宣言すればデータの読み書きは出来ると思うのですが、 (A)~(C)を配列や構造体として扱いたい場合、どのように宣言すれば 良いのでしょうか? (A) #define SRAM1 (*(volatile unsigned int*)(0x200000)) (B) #define SRAM2 (*(volatile unsigned int*)(0x200002)) (C) #define SRAM3 (*(volatile unsigned int*)(0x200004)) (D) SRAM1 = 1; (E) SRAM2 = 2; (F) SRAM2 = 3; よろしくお願いします。

  • C言語のプログラムで...

    下のプログラムは参考書にあったサンプルプログラムなのですが /* 関数の宣言 */ int print_struct(struct person dat); のところはなぜint型なのですか? #include <stdio.h> #include <string.h> /* 構造体の定義 */ struct person { char name[20]; /* 名前 */ double height; /* 身長 */ double weight; /* 体重 */ int bpl; /* 最低血圧 */ int bph; /* 最高血圧 */ }; /* 関数の宣言 */ int print_struct(struct person dat); /* main関数 */ int main(void) { /* 変数の宣言 */ struct person dat; /* 構造体のメンバーに値を代入 */ strcpy(dat.name,"山田太郎"); dat.height = 173.5; dat.weight = 63.0; dat.bpl = 98; dat.bph = 113; /* struct person関数を実行 */ print_struct(dat); return 0; } /* print_struct関数 */ int print_struct(struct person dat) { /* 画面に出力 */ printf("%s\n",dat.name); printf("%f\n",dat.height); printf("%f\n",dat.weight); printf("%d\n",dat.bpl); printf("%d\n",dat.bph); return 0; }

  • マイコンとフラッシュメモリの接続について

    マイコンとフラッシュメモリの接続について 2点質問があります。 質問1 マイコンH8S、フラッシュはスパンションの http://www.spansion.com/Support/Datasheets/S29AL008D_00_A11_e.pdf を使用しています。 ワードモードでマイコン側A0-A18、rom側A0-A18で接続したのですが、 どうもワードモードだとマイコン側A1-A19、rom側A0-A18とアドレスを1つ ずらして接続するのが普通のようなのですが、マイコン側A0-A18、rom側A0-A18では アクセスできないのでしょうか?必ず1つずらして接続しないとだめなのでしょうか? 実際デバイスIDのコマンドを実行してみたのですが、リードできませんでした。 (*(volatile short *)0x400555) = 0xAA; (*(volatile short *)0x4002aa) = 0x55; (*(volatile short *)0x400555) = 0x90; a = (*(volatile short *)0x400002); a=0xFFFFとなりました。 質問2 マイコン側A0-A18、rom側A0-A18ではデバイスIDがリードできなかったので、改造して マイコン側A1-A19、rom側A0-A18としたところ、 (*(volatile short *)0x400aaa) = 0xAA; (*(volatile short *)0x400554) = 0x55; (*(volatile short *)0x400aaa) = 0x90; a = (*(volatile short *)0x400002); でデバイスID0x22DAを読むことができました。 ここで質問ですが、フラッシュのマニュアル26PだとデバイスIDの4回目のコマンドは、 ワードモードだとX01となっています。 しかし (*(volatile short *)0x400aaa) = 0xAA; (*(volatile short *)0x400554) = 0x55; (*(volatile short *)0x400aaa) = 0x90; a = (*(volatile short *)0x400001); としたところ0x400000の製造メーカIDの0x0001がよめるだけでした。 そもそも0x400001だと奇数アドレスなのでH8Sはアクセスできないので 当然な気がするのですが、マニュアルのX01とはどういう意味なのでしょうか? コンパイラに起因する問題でしょうか?