почему не работает typedef enum?

axill
Offline
Зарегистрирован: 05.09.2011

в Си активно использую такие конструкции:

typedef enum {
....
} my_enum;

void my_function(my_enum v) {
    switch(v) {
.....
    }
}

в этом случае компилятор на себя берет проверку содержания переменной типа my_enum. Это как бы на самом деле короткое или обычное целое, но компилятор не проверяет списочно. В Atmel Studio в Си проектах это работает, а вот в Ардуино он само описание typedef проглатывает, но при дальнейшей ссылке на тип говорит, что он не определен

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Это известный глюк, мы уже где то на эту тему с кем то разбирались. Решение такое (создаем проект enum_test и сохраняем его):

1. В папаке проекта создаем .h файл, в нем описываем typedef. Файл enum_test.h. Лежит в той же папке, что и скетч:

typedef enum _Enum
{
  hello = 0,
  world
} Enum;

2. Выгружаем АрудиноИДЕ и заходим снова, открываем проект. Видим две вкладки. enum_test.ino и enum_test.h.

2. Файл enum_test.ino:

#include "enum_test.h"

void setup()
{
  Serial.begin(57600);
}

void func(Enum en)
{
  switch(en)
  {
    case hello:
      Serial.println("Hello");
    case world:
      Serial.println("world");
  }
}

void loop()
{
  func(hello);
  delay(1000);
  func(world);
  delay(1000);
}

Компилируется. Но я не запускал, на работе не на чем.

PS Писал подробно, мало ли кому пригодится.

UPS: Кстати, что бы не было вопросов. Делаем как обычно, всё в одном файле:

typedef enum _Enum
{
  hello = 0,
  world
} Enum;

void setup()
{
  Serial.begin(57600);
}

void func(Enum en)
{
  switch(en)
  {
    case hello:
      Serial.println("Hello");
    case world:
      Serial.println("world");
  }
}

void loop()
{
  func(hello);
  delay(1000);
  func(world);
  delay(1000);
}

Компилируем. Выводятся ошибки. Смотрим файл, который компилирует АрдуиноИДЕ и видим "прикол":

#line 1 "enum_test2.ino"
#include "Arduino.h"
void setup();
void func(Enum en);
void loop();
#line 1
typedef enum _Enum
{
  hello = 0,
  world
} Enum;

void setup()
{
  Serial.begin(57600);
}

void func(Enum en)
{
  switch(en)
  {
    case hello:
      Serial.println("Hello");
    case world:
      Serial.println("world");
  }
}

void loop()
{
  func(hello);
  delay(1000);
  func(world);
  delay(1000);
}

Здесь четко видно, что описание функции "void func(Enum en);" появляется ДО объявления типа "typedef enum _Enum".

 

axill
Offline
Зарегистрирован: 05.09.2011

Да вот все не так просто

у меня собственно была рабочая программа на Си и мне понадобилось подключить библиотеку ардуиновскую. Чтобы каждый раз не переписывать библиотеку на Си решил один раз переписать свою программу. 

Определение enum у меня и так в другом файле, но это не спасает. Здесь похоже тупость сборщика ардуино в том, что он исходники собирает в один файл, но при этом вначале файлы сортирует по алфавиту. Сейчас вспомнил, что уже с этим сталкивался. Значит дело не только в вынесении в файл, но еще и в порядке следования имен файлов по алфавиту

Главное в скетче ошибка, а в библиотеке (файл .c) ошибки нет

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Можете выслать мне проект, например, на (здесь был адрес), я посмотрю завтра. Мне и самому будет полезно узнать :)
UPD: адрес удалил

axill
Offline
Зарегистрирован: 05.09.2011

это проект из статьи (там есть Си код) http://radiokot.ru/circuit/digital/home/194

я его только начал на ардуину переводить

Jatixo
Offline
Зарегистрирован: 13.01.2016

Более простой вариант: если в коде есть include каких-либо файлов, то если объявить enum до include, то ошибки не будет.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

kisoft пишет:

UPS: Кстати, что бы не было вопросов. Делаем как обычно, всё в одном файле:

typedef enum _Enum
{
  hello = 0,
  world
} Enum;

void setup()
{
  Serial.begin(57600);
}

void func(Enum en)
{
  switch(en)
  {
    case hello:
      Serial.println("Hello");
    case world:
      Serial.println("world");
  }
}

void loop()
{
  func(hello);
  delay(1000);
  func(world);
  delay(1000);
}

Компилируем. Выводятся ошибки. Смотрим файл, который компилирует АрдуиноИДЕ и видим "прикол":

К стати в 1.6.7 этот код компилится, работает или нет на работе не могу проверить, не на чем :-(

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Andrey12 пишет:

kisoft пишет:

UPS: Кстати, что бы не было вопросов. Делаем как обычно, всё в одном файле:

typedef enum _Enum
{
  hello = 0,
  world
} Enum;

void setup()
{
  Serial.begin(57600);
}

void func(Enum en)
{
  switch(en)
  {
    case hello:
      Serial.println("Hello");
    case world:
      Serial.println("world");
  }
}

void loop()
{
  func(hello);
  delay(1000);
  func(world);
  delay(1000);
}

Компилируем. Выводятся ошибки. Смотрим файл, который компилирует АрдуиноИДЕ и видим "прикол":

К стати в 1.6.7 этот код компилится, работает или нет на работе не могу проверить, не на чем :-(

Да, на 1.6.7. компилируется, поскольку там многое уже поменялось. А почему он не должен работать?

 

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

А почему так нельзя?

enum EnCommand { cmdHello, cmdWord };

void func( EnCommand cmd );

void setup()
{
  Serial.begin(57600);
}

void func( EnCommand cmd )
{
  switch( cmd )
  {
    case cmdHello:
      Serial.println("Hello");
    case cmdWord:
      Serial.println("world");
  }
}

void loop()
{
  func(cmdHello);
  delay(1000);
  func(cmdWord);
  delay(1000);
}

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Подскажите, а возможно в 

func(hello);

вместо hello изменяймое значение например String?

Есть задача - нужно парсить шину и при совпадении присваивать переменную.

например запрос такого вида:

&var4=1

разбиваем его на строки и получаем var4 и значение 1

затем нужно присвоить переменной с названием var4 значение 1 (что бы в последующем в программе удобно было её использовать)

Вот была мысля сделать через enum

enum {var4, var5, var6} vari;
byte values[Count];
 
values[var4] = 1;

Скажите, возможно сравнивать названия переменных из enum?

 

 

 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012
ledmax
Offline
Зарегистрирован: 01.03.2016

Спасибо,

Может есть более "простые" способы решить мою задачу?

Чего то я совсем в тупик пришёл(

ledmax
Offline
Зарегистрирован: 01.03.2016

Хотя какой то мутант у меня таки получился, как более менее причешу - выложу, если конечно расчёску не сломаю)) 

diger67
Offline
Зарегистрирован: 25.07.2015

Есть предположение, что это может быть связано с тем что Arduino написано на C++. И разработчики делают упор на классы и наследование. При этом не пытались реализовать возможности структур и объединений. Изначально была заложена концепция конструктора. Берешь среду разработки, собираешь из кубиков текст скетча и готово. Для гибкого програмирования есть серьезные среды разработки Студия, IAR, Keil. По сути должно работать, вы наверное что то делаете не правильно. Ведь в принципе typedef enum это просто перечисление констант или переменных. Что можно описать как обычное присвоение с указанием типа переменной.

typedef enum {
    W25_WRITE_DISABLE = 0x04,
    W25_WRITE_ENABLE = 0x06,
     
    W25_READ_STATUS_1 = 0x05,
    W25_READ_STATUS_2 = 0x35,
    W25_READ_STATUS_3 = 0x15,
     
    W25_WRITE_STATUS_1 = 0x01,
    W25_WRITE_STATUS_2 = 0x31,
    W25_WRITE_STATUS_3 = 0x11,
     
    W25_CHIP_ERASE = 0xc7, //0x60
    W25_SECTOR_ERASE = 0x20,
    W25_BLOCK32_ERASE = 0x52,
    W25_BLOCK64_ERASE = 0xD8,
     
    W25_GET_DEV_ID = 0x90,
    W25_GET_JEDEC_ID = 0x9f,
     
    W25_ENABLE_RESET = 0x66,
    W25_RESET = 0x99,
     
    W25_PAGE_PROGRAMM = 0x02,
    W25_READ = 0x03,
    W25_FAST_READ = 0x0B,
    W25_EN_VAL_REG = 0x50,

    W25_IDENT_ERROR = 0,
    W25_OK = 1,
    WRITETYPE_CONT = 0,
  
} W25_Command_t;

А можно это описать и так

#define    W25_WRITE_DISABLE    0x04
#define    W25_WRITE_ENABLE     0x06
................................................
и.т.д.

 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

ledmax пишет:

Спасибо,

Может есть более "простые" способы решить мою задачу?

Чего то я совсем в тупик пришёл(

Зачем в твоей задаче нужен enum? Этот вопрос задай себе и ответь себе честно. Лично я не вижу нужды в enumе, у тебя есть имя переменной и её значение, т.е., втупую, простейшая структура, условно: struct variable { name; value; }, создать объект, который содержит список этих variable. Добавить в него методы addVariable(name, value), getVariable(name) и т.п. Я в своё время делал такое, но на большом компе и оно сильно заточено на специальные задачи. Здесь бы хорошо подошел map, но на микроконтроллере я эти штуки не использовал, думаю жрут память немеренно.

Вообще задача слишком абстрактная, чтобы давать какие либо советы.

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Вот, что у меня получилось на основе примера. Это решение работает в точности как мне надо, но есть подозрение, что это заход не стой стороны)) К сожалению мои познания в с++ - больше результат практического использования, поэтому в более сложных задачах начинаются танцы с бубном) 

valuename.h

#include "enum_helper_pre.h" 
 
enumeration_begin(valuename) 
declare_member(LIGHT) delimiter 
declare_member(TEMP) delimiter 
declare_member(DOOR) 
enumeration_end; 
 
#include "enum_helper_post.h"

Сдесь задаём названия переменных (их будет много)

enum_helper_pre.h

#ifndef delimiter 
    #define delimiter , 
#endif 
 
 
#ifndef enumeration_begin 
    #define enumeration_begin(arg) enum arg { 
#endif 
 
#ifndef enumeration_end 
    #ifdef last_enumerator 
        #define enumeration_end delimiter last_enumerator } 
    #else 
        #define enumeration_end } 
    #endif 
#endif 
 
#ifndef declare_member 
    #define declare_member(arg) arg 
#endif 
 
#ifndef member_value 
    #define member_value(arg) = arg 
#endif 

 

enum_helper_post.h

#undef delimiter 
#undef enumeration_begin 
#undef enumeration_end 
#undef last_enumerator 
#undef declare_member 
#undef member_value

Сам код

#define MAX_MESSAGE_BUFFER_LEN (10)

#include <stdio.h>



#define enumeration_begin(arg) const char* const arg##_strs[]={
#define declare_member(arg) #arg
#include "valuename.h"

#include "valuename.h"

int values[10]; //глобальнай массив переменных

//Serial test
/* Буфер для получения строки из Serial. +1 для добавления '\0' в конец буфера. */
char message_buffer[MAX_MESSAGE_BUFFER_LEN + 1];
byte message_buffer_index = 0;
/* Признак прихода всей входящей посылки */
bool messageIsComplete = false;
//end serial


String Valuename; //полученное имя переменной
String value; //полученая переменная

byte N;

void setup()
{
  Serial.begin(9600);
}



void loop()

{

  if (messageIsComplete) {

    // Serial test;
    messageIsComplete = false;
    message_buffer_index = 0;
    //  end serial;

    //Парсим полученные данные
    strtok(message_buffer, "$"); //вид запроса $PARAM$LIGHT=1
    char* data = strtok(NULL, "$"); //второй параметр - наша пара
    char* Valuename = strtok(data, "=");
    value = strtok(NULL, "=");
    int Value = value.toInt();//переводим полученую переменную в int

    for (byte i = 0; i < 3; i++) {//сравниваем Valuename c нашими переменными

      if (!strcmp(Valuename, valuename_strs[i])) {



        values[i] = Value;

        Serial.print( valuename_strs[i]);
        Serial.print(": ");
        Serial.print(values[i]);
        Serial.print("\r");



        N++;
      }

      if (N == 3) {

        Serial.print(values[LIGHT]);
        Serial.print("\r");

        Serial.print(values[TEMP]);
        Serial.print("\r");

        Serial.print(values[DOOR]);
        Serial.print("\r");

      }
    }
  }
}
//Serial works
void serialEvent() {
  while (Serial.available()) {
    char l_InChar = Serial.read();
    /* Записываем в буфер все данные до получения символа '\n' */
   if ('1' != l_InChar) {
      /* Записываем данные в буфер только если в нем еще есть место */
      if (message_buffer_index < MAX_MESSAGE_BUFFER_LEN) {
        message_buffer[message_buffer_index] = l_InChar;
        message_buffer_index++;
      }
      else {
        /* Здесь можно сделать выход по переполнению буфера или установить флажок ошибки. */

        message_buffer[message_buffer_index] = '\0';
        messageIsComplete = true;
        break;

      }
    }
    else
    {
      message_buffer[message_buffer_index] = '\0';
      messageIsComplete = true;
      break;
    }
  }
}

 

 
Думаю, что вы поняли, саму идею - получаем по сетки (сериал для дебага) ГЕТ запрос в виде $PARAM$LIGHT=1 и потом уже в программе используем переменные с названием эдентичным запроса values[LIGHT]) values[DOOR]). 
Рабочий Скетч очень большой и стояла задача сделать удобным и понятным использование переменных по всей программе.
 
Прошу вас дать свои рецензии по вышенаписаному
kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Ок, чтобы не разбрасываться временем, насколько я понял, переменных всегда определенное количество? Это навеяно строкой 072.

Если это так, и даже если это не так, то количество переменных - ограничено, т.е. заранее известно (ну или известно, что их не более M, у тебя это число 10, поскольку переменных не может быть больше 10), тогда проще сделать их размещение в массиве со значениями (values[]) позиционно-зависимым. В этом случае мы делаем константный массив имен переменных (можно во флеше, PROGRMEM. Собственно он у тебя уже есть), индекс переменной в этом массиве - дает нам позицию, где хранится эта переменная. И наоборот, по индексу можно считать имя переменной и её значение. Всё, что надо, пишется в одном методе getTokenIndex, который по имени найдет индекс переменной в values. Ну если уж так нужны enumы, тогда getTokenEnum вернет enum значение, чтобы потом им можно было пользоваться в switch, например. К тому же, enum может быть и индексом, поскольку всегда можно написать так: enum { var6 = 0, var7, var4, var12, maxVars }; здесь var6 = 1, var7 = 1, var4 = 2, var12 = 3, maxVars = 4 (максимальное количество переменных, тогда можно объявлять int values[maxVars];).

Строки 091 и 092 не соответствуют друг другу. Строка 072 намертво блокирует отображение переменных. Просто закомментарить, пусть всегда выводит текущее значение переменных.

PS Возможно это не новость, но strtok содержит статик переменную, потому одновременно (вперемежку) нельзя две разных строки парсить этой функцией. Хотя здесь всё нормально, сначала одна, потом другая. И да, она портит исходную строку, которую надо парсить. Если знаешь, забей, может кому то будет полезно.

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Да, колличество (их может быть больше 100) и имена переменных известны зарание - мы ведь должны знать, что парсим... 

Массив переменных у меня есть, его я и использую values[]. По соответствию его индекса и индекса 

valuename_strs[]  я и назначаю ему переменную value.

Но как туда записать и считать имя переменной, я чего то совсем не понимаю. И как я смогу потом сравнивать имя с парсингом через switch?

Не могли бы сделать маленький пример из всего того, что написали выше.

Спасибо

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

ledmax пишет:

Да, колличество (их может быть больше 100) и имена переменных известны зарание - мы ведь должны знать, что парсим... 

Массив переменных у меня есть, его я и использую values[]. По соответствию его индекса и индекса 

valuename_strs[]  я и назначаю ему переменную value.

Но как туда записать и считать имя переменной, я чего то совсем не понимаю. И как я смогу потом сравнивать имя с парсингом через switch?

Зачем имя записывать в массив? Мы всегда значем, что переменная с именем var6 хранится в values[0], с именем var7 хранится в values[1], с именем var4 в values[2], var12 в values[3].

Если нужно имя переменной, берите её из массива с именами переменных по индексу, например, имя переменной var6 берите из valuename_strs[0] и так далее.

Итак:

значение var7 храним в values[var7]

значение var6 храним в values[var6]

имена переменных храним в:
 
имя var7 в valuename_strs[var7]
имя var6 в valuename_strs[var6]

и т.д.

Здесь я считаю, что var6, var7... объявлены согласно моему примеру enum VARNAME { var6 = 0, var7, var4, var12, maxVars }; т.е. они имеют последовательные номера от 0 и до максимального.

По-поводу switch с enum:

VARNAME varName = var7;
switch(varName)
{
 case var7: call_foo7(); break;
 case var6: call_foo6(); break;
 ...
}

На счет имени переменной. Она нужна только на момент определения индекса/enum значения. В дальнейшем имя нам не нужно ни с чем сравнивать, поскольку у нас есть индекс/enum значение для этой переменной. По нему мы можем достать значение Serial.println(values[varName]); или её имя Serial.println(valuename_strs[varName]);

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Чего то я совсем запутался.. В последнем посте изложено одно решение или несколько вариантов?

kisoft пишет:

имена переменных храним в:

 
имя var7 в valuename_strs[var7]
имя var6 в valuename_strs[var6]

Как мне поместить имена в массив, без предварительного декларирования ?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Без предварительного декларирования - никак.

ledmax
Offline
Зарегистрирован: 01.03.2016

Я понял, ваш вариант я рассматривал в самом начале, просто была задача, что бы названия переменных указывались в одном месте. - скетч универсальный и часть большого проекта. Названия перменных каждый будет менять под свои нужды и будет очеь не удобно, если это надо будет в разных местах + если их будет например 100 - то верный путь к ошибкам..

Скажите, а чем плох мой первоначальный пример? Какие у него минусы? он по идеи должен использовать пинимум памяти мк.

И возможно ли декларацию переменных вынести из файла valuename.h в тело скетча?

 

enumeration_begin(valuename) 
declare_member(LIGHT) delimiter 
declare_member(TEMP) delimiter 
declare_member(DOOR) 
enumeration_end; 

 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

ledmax пишет:

Скажите, а чем плох мой первоначальный пример? Какие у него минусы? он по идеи должен использовать пинимум памяти мк.

Насколько я понимаю, это оно и есть, позиционно-зависимое расположение данных. На счет небольших минусов, но это опять же моё, я не люблю эти String, к тому же там String Valuename глобальная переменная и char *Valuename локальная переменная. Аккуратней с этим. Глобальные переменные можно использовать, если они нужны в разных частях кода программы, а если они используются только в одной функции, то нет смысла выносить их в глобальную часть. Если не перевру, то есть такая рекомендация "объявлять переменные нужно как можно ближе к тому месту, где они используются", разумеется я не собираюсь здесь обсуждать в куче эти переменные будут или в стеке или еще где, вопрос не в этом. А остальное вроде ничего особо в глаза не бросается, а в подробностях ковыряться как то некогда.

ledmax пишет:
 И возможно ли декларацию переменных вынести из файла valuename.h в тело скетча?

Я такие макросы не люблю, понятно, это моя проблема, не тема для обсуждения. А вцелом, объявление и нужно выносить в тело если не скетча, то в cpp файл, а не оставлять в заголовочном. Заголовочные файлы нужны (в данном контексте) для описания внешних переменных (extern int Value), но не для объявления самих переменных. Есть проблемы?

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Переделал вот так 

//Парсим полученные данные
    strtok(message_buffer, "$"); //вид запроса $PARAM$LIGHT=1
    char* data = strtok(NULL, "$"); //второй параметр - наша пара
    char* Valuename = strtok(data, "=");
    String value = strtok(NULL, "=");
    int Value = value.toInt();//переводим полученую переменную в int
 

kisoft пишет:

 

Я такие макросы не люблю, понятно, это моя проблема, не тема для обсуждения. А вцелом, объявление и нужно выносить в тело если не скетча, то в cpp файл, а не оставлять в заголовочном. Заголовочные файлы нужны (в данном контексте) для описания внешних переменных (extern int Value), но не для объявления самих переменных. Есть проблемы?

 

 

Проблема в том, что среда ардуина выносит файлы .Н в вкладки, а их  с кодом программы 25 штук. - не хотелось бы ещё больше запутывать людей. 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

5-6 строки создают String, который можно и обойти, для формирования int есть старая добрая функция strtod (либо, чуть проще atoi, но без контроля) http://www.cplusplus.com/reference/cstdlib/strtod/ Типа:

// Здесь нет контроля, если строка пустая или содержит "кривые" символы, то вернется 0, что не всегда хорошо
const char *lp_ptr = strtok(NULL, "=");
int value = atoi(lp_ptr);

На счет того, что среда выводит все файлы проекта во вкладки - это уж как есть и это совсем не значит, что нужно из-за этого всё класть в один файл. Либо "терпеть", либо использовать другую среду, но это, думаю, и так понятно. Другой вопрос, правильно ли то, что в проекте 25 файлов, возможно это не оптимально, а может и нет, это тебе видней. Еще, как вариант, части реализовать в виде библиотек, но это совсем другая история.

 

ledmax
Offline
Зарегистрирован: 01.03.2016

Большое спасибо за помошь!