stc8a8k64d4 라이브러리 함수 사용 시 원인 불명의 오류 발생

, ,

IO 포트와 ADC 초기화 함수를 함께 넣으면 IO 포트를 앞에 놓든 ADC를 앞에 놓든 뒤에 오는 함수에서 항상 오류가 발생합니다. KEIL에서 인식을 못 하네요(사진 참조). 그런데 이 두 기능을 서로 다른 두 개의 함수로 분리하면 정상 동작합니다. 도대체 무슨 문제인지 알려주세요.

전체 코드를 복사해서 올려주세요. 스크린샷으로는 기호 문제인지 확인할 수 없습니다(중문 및 영문 기호는 다릅니다).

이것은 C 언어 표준에서 매우 전형적이면서도 짜증나는 문제이며, 특히 Keil(특히 C51 또는 오래된 버전의 컴파일러)을 사용할 때 자주 발생합니다.

근본 원인: C89 표준의 제한

오류가 발생하는 이유는 다음과 같습니다. C89 표준(많은 임베디드 컴파일러가 기본적으로 따르는 표준)에서는 함수 내부의 모든 변수 선언은 함수 몸체의 가장 처음 위치, 즉 어떤 실행 문장보다도 앞서야 합니다.

다음과 같은 코드를 살펴보세요(아래 이미지 참조):

  1. 먼저 GPIO_InitStructure를 선언합니다(선언이므로 문제 없음).
  2. 그 직후에 GPIO_InitStructure.Mode = ...를 실행하고 GPIO_Inilize(...) 함수를 호출합니다(이들은 실행 문장입니다).
  3. 그리고 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 모드를 활성화할 수 있습니다.

  1. 마법봉 아이콘(Options for Target) 클릭.
  2. C/C++ 탭 선택.
  3. C99 Mode 체크박스를 선택.

참고: 일부 8051 컴파일러(예: 오래된 버전의 C51)는 C99를 지원하지 않을 수 있으므로, 체크 후 더 많은 오류가 발생하면 방법 1로 되돌리는 것이 좋습니다.


왜 함수를 분리하면 잘 작동할까?

함수를 두 개로 나누면 각 함수의 첫 번째 줄이 모두 변수 선언이 되므로 "실행 문장보다 먼저 선언한다"는 규칙을 만족하게 됩니다. 따라서 컴파일러가 문제없이 통과시킬 수 있는 것입니다.

작은 팁:
코드 내 GPIO_Inilize라는 철자(불필요하게 'i’가 하나 더 있음)는 STC 라이브러리 함수만의 "전통적인 특징"입니다. 보기엔 어색하지만, 헤더 파일과 일치하도록 유지해야 함에 주의하세요.

와, 나도 예전에 똑같은 함정에 빠져본 적 있어!:face_with_steam_from_nose:

Keil C51이라는 이 오래된 컴파일러는 기본적으로 C89 표준을 따르기 때문에 변수를 함수 맨 처음에 모두 선언해야 해. 중간에 새로운 변수를 정의하면 바로 문법 오류를 뱉어내지. 나도 그때 거의 30분 가까이 헤매다가 겨우 깨달았거든.

너의 코드를 보면, 먼저 GPIO 구조체를 정의하고, GPIO_Initialize를 호출한 다음에 ADC 구조체를 정의하려 하잖아. 이러면 컴파일러는 아예 혼란스러워해서 마치 난수를 쓴 것처럼 인식해. 해결 방법은 정말 간단해: ADC_InitTypeDefGPIO_InitTypeDef의 정의를 모두 함수 맨 위로 옮겨. 모든 변수를 먼저 다 정의하고 나서 실행 코드를 작성하면 바로 해결돼.

C89에서 흔히 발생하는 문제에 부딪힌 것 같습니다! 최신 프로그래밍 언어나 C99, C11 같은 새로운 C 표준에서는 변수를 원하는 위치 어디서든 선언할 수 있지만, Keil C51 컴파일러(STC 마이크로컨트롤러에서 사용)는 종종 오래된 ANSI C(C89) 표준을 따르도록 설정되어 있습니다.

문제의 원인: “문장 이전에 선언해야 함”

C89 표준에서는 모든 변수 선언이 코드 블록(예: 함수)의 맨 처음에 와야 하며, 실행 가능한 코드(대입문, 함수 호출 등)보다 앞서야 합니다.

여러분의 코드를 살펴보면:

  1. GPIO_InitStructure를 선언함 (선언 - 정상)
  2. GPIO_InitStructure.Mode = ... 값을 설정함 (실행 문장 - 정상)
  3. 이후 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 모드를 활성화해볼 수 있습니다.

  1. Target 또는 Project 폴더를 마우스 오른쪽 버튼으로 클릭
  2. Options for Target 선택
  3. C51(또는 C/C++) 탭으로 이동
  4. C99 Mode라는 체크박스를 찾아 체크

참고: C51 컴파일러의 일부 버전은 C99을 완벽히 지원하지 않을 수 있으므로, 변수 선언을 위로 옮기는 방법이 8051 기반 칩에서 코드의 이식성을 보장하는 가장 신뢰할 수 있는 방법입니다.