Возникла необъяснимая ошибка при использовании библиотечной функции stc8a8k64d4

, ,

Я поместил инициализацию IO-порта и ADC в одну функцию. Независимо от того, что стоит первым — порт или 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, можно включить режим C99 в настройках Keil:

  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, в котором все переменные должны быть объявлены строго в начале функции. Если вы попытаетесь объявить переменную посреди кода, компилятор тут же выдаст ошибку синтаксиса. Я сам застрял на этом почти на полчаса, прежде чем сообразил в чём дело.

Посмотри на свой код: ты сначала определил структуру GPIO, затем вызвал GPIO_Inilize, а после этого попытался определить структуру ADC. Компилятор просто теряется и решает, что ты пишешь какую-то бессмыслицу. Решение очень простое: перемести объявления ADC_InitTypeDef и GPIO_InitTypeDef в самое начало функции. Сначала объявляй все переменные, а потом уже пиши исполняемый код — и всё сразу заработает.

Похоже, вы столкнулись с классической проблемой стандарта «C89»! Хотя современные языки программирования (а также более новые стандарты C, такие как C99 или C11) позволяют объявлять переменные в любом месте, компилятор Keil C51 (используемый для микроконтроллеров STC) зачастую настроен на соблюдение старого стандарта ANSI C (C89).

Причина проблемы: «Объявления до операторов»

В стандарте C89 все объявления переменных обязаны находиться в самом начале блока кода (например, функции), до любого исполняемого кода (присваиваний, вызовов функций и т.д.).

Рассмотрим ваш код:

  1. Вы объявили GPIO_InitStructure (объявление — OK).
  2. Вы выполнили присвоение GPIO_InitStructure.Mode = ... (оператор — OK).
  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-код, попробуйте включить режим C99 в Keil:

  1. Щёлкните правой кнопкой мыши по папке Target или Project.
  2. Выберите Options for Target.
  3. Перейдите на вкладку C51 (или C/C++).
  4. Найдите флажок C99 Mode и установите его.

Примечание: Не все версии компилятора C51 полностью поддерживают C99, поэтому метод «переноса наверх» остаётся самым надёжным способом обеспечить переносимость кода для чипов на базе 8051.