IOポートとADCの初期化関数を一つの関数にまとめると、IOポートの初期化を先にしても、ADCを先にしても、後ろに記述した関数の部分でエラーが発生し、Keilが認識できなくなります(図参照)。しかし、この二つを別々の関数に分けると正常に動作します。原因がわからず、どなたか教えていただけませんか?
すべてのコードをコピーして貼り付けてください。スクリーンショットでは、記号の問題(中英文の記号の違い)かどうかわかりません。
これは非常に古典的でイライラする C言語標準に関する問題 であり、特にKeil(特にC51や古いバージョンのコンパイラ)を使用する際に頻繁に遭遇します。
根本原因:C89 標準の制限
エラーの原因は次の点にあります:C89 標準(多くの組み込み系コンパイラがデフォルトで準拠している)では、すべての変数宣言は関数ブロックの先頭、つまり実行文の前に行わなければならないという制約です。
あなたのコード(下図参照)を見てみましょう:
- 最初に
GPIO_InitStructureを宣言しています(これは宣言なので問題なし)。 - その後、
GPIO_InitStructure.Mode = ...の代入やGPIO_Inilize(...)の呼び出しが行われています(これらは実行文です)。 - そして11行目で再び
ADC_InitTypeDef ADC_InitStructure;という宣言を行っています。
11行目の変数宣言が10行目の関数呼び出し後に現れているため、コンパイラはこれを認識できず、syntax error(構文エラー)およびその後の undefined identifier(未定義識別子)エラーが発生します。
解決方法
方法1:変数宣言を関数の先頭に移動する(推奨)
最も確実な修正法です。すべての InitTypeDef 構造体の宣言を関数の冒頭に集めます。
void ADC_Init()
{
// 1. まずすべての変数宣言を完了させる
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 2. その後でロジックと初期化コードを記述
GPIO_InitStructure.Mode = GPIO_PullUp;
// ... その他のGPIO設定
GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);
ADC_InitStructure.ADC_SMPduty = 15;
// ... その他のADC設定
ADC_Inilize(&ADC_InitStructure);
ADC_PowerControl(ENABLE);
}
方法2:C99 モードを有効にする
C++ や Java のように、使用する場所で変数を宣言したい場合は、Keil の設定で C99 モードを有効にできます。
- 魔法の杖アイコン(Options for Target)をクリック。
- C/C++ タブを選択。
- C99 Mode をチェック。
注意:一部の8051用コンパイラ(例:古いバージョンのC51)はC99をサポートしていない場合があります。チェック後にさらに多くのエラーが出る場合は、方法1に戻ってください。
なぜ関数を分けるとうまくいくのか?
関数を分けることで、それぞれの関数の最初の行が変数宣言になり、「実行文より前に宣言を行う」というルールに従うようになるため、コンパイラが正常に処理できるようになります。
ちょっとしたアドバイス:
コード中の GPIO_Inilize という綴り(“i” が一つ多い)は、STC ライブラリ特有の「伝統芸能」ですが、見た目は違和感があっても、ヘッダファイルと一致していることを確認してください。
やばい、私も以前まったく同じ罠にハマったことある!![]()
Keil C51というこの古いコンパイラは、デフォルトでC89標準なので、変数は関数の先頭にすべて宣言しなければならず、途中で新しい変数を定義すると、すぐに構文エラーを吐いてくる。私も当時、30分近く悩んでやっと気づいたよ。
あなたのコードを見ると、まずGPIOの構造体を定義して、次にGPIO_Initializeを呼び出し、その後でADCの構造体を定義しているけど、これだとコンパイラが混乱して、まるでゴミコードを書いているかのように判断してしまうんだ。
解決法は超簡単:ADC_InitTypeDefとGPIO_InitTypeDefの定義を、関数の先頭に持ってくる。すべての変数宣言を最初にまとめて行い、その後で実行コードを書けば、すぐに直るはずだ。
どうやら「C89」特有の問題に遭遇したようですね!最近のプログラミング言語や、C99・C11といった新しいC標準では変数宣言をどこでも行えますが、Keil C51コンパイラ(STCマイクロコントローラ用)はしばしば古いANSI C(C89) 標準に従うように設定されています。
問題の原因:「文の前の宣言」ルール
C89標準では、すべての変数宣言はコードブロック(関数など)の先頭にまとめて記述されなければならず、実行可能なコード(代入、関数呼び出しなど)より前に置く必要があります。
あなたのコードを見てみましょう:
GPIO_InitStructureを宣言(宣言 → OK)GPIO_InitStructure.Mode = ...と設定(実行文 → OK)- その後、
ADC_InitTypeDef ADC_InitStructure;を宣言(実行文の後に宣言 → エラー!)
このように、コンパイラは10行目以降にさらに処理が続くことを想定しているため、新たな変数宣言が現れると混乱してしまいます。
解決法:宣言を先頭に移動する
これを修正するには、ADC_Init() 関数の先頭にすべての変数宣言を集めるだけです。
void ADC_Init()
{
// 1. すべての宣言をここに書く
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 2. 初期化処理などの実際の処理を次に記述
GPIO_InitStructure.Mode = GPIO_PullUp;
GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1 | ...;
GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);
ADC_InitStructure.ADC_SMPduty = 15;
ADC_InitStructure.ADC_Speed = ADC_SPEED_2X16T;
// ... 以下同様
ADC_Inilize(&ADC_InitStructure);
ADC_PowerControl(ENABLE);
}
なぜ関数を分けるとうまくいくのか?
変数宣言を別々の関数に分割した場合、それぞれの変数はその関数ブロックの先頭に位置します。つまり、各関数内で実行文の前に宣言があるため、コンパイラは問題なく処理できるのです。
プロのヒント:Keilの設定変更
今後このような制限を避け、より「現代的」なCコードを書きたい場合は、KeilでC99モードを有効にできます。
- Target または Project フォルダを右クリック
- Options for Target を選択
- C51(または C/C++)タブへ移動
- C99 Mode というラベルのチェックボックスを探してオンにする
注意:C51コンパイラのすべてのバージョンでC99が完全にサポートされているわけではないため、「宣言を先頭に移動する」方法が、8051系チップ向けコードの移植性を保つ上で最も確実な手段です。
