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를 선언함 (선언 - 정상)GPIO_InitStructure.Mode = ...값을 설정함 (실행 문장 - 정상)- 이후
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 기반 칩에서 코드의 이식성을 보장하는 가장 신뢰할 수 있는 방법입니다.
