Проговаривание float.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Делаю сигнализацию. Хочу в принципе избавится от СМС. Только дозвон или емайл оповещение.
Если со вторым все понятно, то с первым затык.
В чем проблема?
Вот к примеру прочитал я данные с DHT22, или вычлинил цифры из запроса баланса СИМ-карты, запомнил в переменные типа float. Дальше - звонит "начальника" и посылает DTMF 0 = получить инфу о состоянии системы.

И тут я затупил. Вот как мне разбить float по разряду, типа сотые, десятые, потом единицы, десятки, сотни, тысячи, и т.д, дабы проиграть, соответствующие разряду файлы, на микрофонный вход модема?

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

http://netcode.ru/cpp/?artID=247

Вот код из файла Print.cpp среды Ардуино.

size_t Print::printFloat(double number, uint8_t digits) 
{ 
  size_t n = 0;
  
  if (isnan(number)) return print("nan");
  if (isinf(number)) return print("inf");
  if (number > 4294967040.0) return print ("ovf");  // constant determined empirically
  if (number <-4294967040.0) return print ("ovf");  // constant determined empirically
  
  // Handle negative numbers
  if (number < 0.0)
  {
     n += print('-');
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
  
  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  n += print(int_part);

  // Print the decimal point, but only if there are digits beyond
  if (digits > 0) {
    n += print("."); 
  }

  // Extract digits from the remainder one at a time
  while (digits-- > 0)
  {
    remainder *= 10.0;
    unsigned int toPrint = (unsigned int)(remainder);
    n += print(toPrint);
    remainder -= toPrint; 
  } 
  
  return n;
}

 

kostyamat
Offline
Зарегистрирован: 16.11.2017

То, что надо! Спасибо большое.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Вот что-то такое получилось. (я там Serial.print() использую вместо проигрывания файлов, чисто для отладки)

void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:
  PlayFloat("humidy.wav", -9815.48, "celyh.wav", "sotyh.wav", "percents.wav");

}


void PlayFloat(String wav0, float number, String terminator, String wav1, String wav2)
{
  Serial.println(wav0);
  // Handle negative numbers
  if (number < 0.0)
  {
    Serial.println("minus.wav");
    number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  float rounding = 0.5;
  for (uint8_t i = 0; i < 2; ++i)rounding /= 10.0;
  number += rounding;



  // Extract the integer part of the number 
  unsigned long int_part = (unsigned long)number;
  float remainder = number - (float)int_part;

  int int_part_length = String(int_part).length();
  char str_int_part[int_part_length + 1];
  String(int_part).toCharArray(str_int_part, int_part_length + 1);

  if (int_part >= 19) {
    for (int i = 4; i > 0; i--) {
      if ((int_part_length > 3) && (i == 4)) {
        Serial.println("000" + String(str_int_part[0]) + F(".wav"));
        Serial.println(F("tysyach.wav"));
      }
      if ((int_part_length > 2) && (i  == 3)) {
        Serial.println("0" + String(str_int_part[int_part_length - i]) + F("00.wav"));
      }

      if ((int_part_length > 1) && (i == 2)) {
        if ((String(str_int_part[i]).toInt() > 1) && (i >= 2)) {
          Serial.println("00" + String(str_int_part[i]) + F("0.wav"));
          Serial.println("000" + String(str_int_part[i + 1]) + F(".wav"));
        }
        else {
          Serial.println("00" + String(str_int_part).substring(String(int_part).length() - 2, String(int_part).length()) + F(".wav"));
        }
      }

    }
  } else {
    if (int_part_length == 1) {
      Serial.println("000" + String(int_part) + F(".wav"));
    }
    if (int_part_length == 2) {
      Serial.println("00" + String(int_part) + F(".wav"));
    }
  }
  Serial.println(terminator);

  char _fname[3] = "00";
  if ((unsigned int)(remainder * 100) > 19) {
    for (int i = 0; i <= 1; i++) {

      remainder *= 10.0;
      unsigned int toPrint = (unsigned int)(remainder);
      _fname[i] = '0' + toPrint;
      Serial.println("00" + String(_fname) + F(".wav"));
      remainder -= toPrint;
      _fname[i] = '0';

    }
  } else {
    String((unsigned int)(remainder * 100)).toCharArray(_fname, 3);
    Serial.println("00" + String(_fname) + F(".wav"));
  }
  Serial.println(wav1);
  Serial.println(wav2);
}




void loop() {
  // put your main code here, to run repeatedly:

}

 

 

а вот это вывовд в монитор порта

humidy.wav
minus.wav
0009.wav
tysyach.wav
0800.wav
0015.wav
celyh.wav
0040.wav
0008.wav
sotyh.wav
percents.wav

Вроде все корректно.

Может кто умный глянет? Может как-то его оптимизировать, код этот, а то больно много памяти жрет - 12%. А мне ещ и все остальное писать, а тут только эта функция столько ОЗУ сожрала.

Вот чувствую - быдлокод это, а сам уже лучше не сделаю. Учусь только. ((

 

ПС. Забыл добавить, функция обрабатывает только четыре разряда + сотые, то-есть до +-9999.99. Я так думаю больше денег на балансе сим-карты вряд-ли кто держать будет.

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

А зачем ему знать прям до сотых копеек? Давно делали что-то подобное, так просто по команде запрос баланса проговаривалось 3и сообщения: "Баланс отрицательный", "Баланс меньше 100 рублей", "Баланс больше 100 рублей". Когда меньше 100 докладывали, а если больше ста так и ничего не надо делать. С влажностью тоже по началу прям точно выводили, а потом заказчик сказал "заколебался я слушать" и сделали тоже несколько сообщений типа "влажность ниже нормы", "влажность в норме", "влажность выше нормы". Так интуитивно понятнее чем слышать корявым голосом цифры и потом соображать ага 55% это сойдет.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Так я решил сразу на все случаи жизни сделать.
Эта функция, в принципе что хочешь проговорить сможет, те же часы к примеру. Замени "целые" на "часов", а "сотые" на "минут", и вот, - уже часы с кукушкой. ))

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

kostyamat. по моему вы сами усложняете себе и м.к. задачу. Нужно отказаться от float вообще. Поправить библу dht, что б отдавала результат измерения в двух байтах. И все дела.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

dimax пишет:

kostyamat. по моему вы сами усложняете себе и м.к. задачу. Нужно отказаться от float вообще. Поправить библу dht, что б отдавала результат измерения в двух байтах. И все дела.

совсем без библиотеки - можно сказать ничего в памяти не ест


// OneWire функции:

void OneWireReset()
{
  PORTB &= ~_BV(DS_BIT);
  DDRB |= _BV(DS_BIT);
  _delay_us(500);
  DDRB &= ~_BV(DS_BIT);
  _delay_us(500);
}

void OneWireOutByte(uint8_t d)
{
  uint8_t n;
  for (n = 8; n != 0; n--)
  {
    if ((d & 0x01) == 1)
    {
      PORTB &= ~_BV(DS_BIT);
      DDRB |= _BV(DS_BIT);
      _delay_us(5);
      DDRB &= ~_BV(DS_BIT);
      _delay_us(60);
    }
    else
    {
      PORTB &= ~_BV(DS_BIT);
      DDRB |= _BV(DS_BIT);
      _delay_us(60);
      DDRB &= ~_BV(DS_BIT);
    }
    d = d >> 1;
  }
}


uint8_t OneWireInByte()
{
  uint8_t d, n, b;
  for (n = 0; n < 8; n++)
  {
    PORTB &= ~_BV(DS_BIT);
    DDRB |= _BV(DS_BIT);
    _delay_us(5);
    DDRB &= ~_BV(DS_BIT);
    _delay_us(5);
    b = ((PINB & _BV(DS_BIT)) != 0);
    _delay_us(50);
    d = (d >> 1) | (b << 7);
  }
  return (d);
}


word GetTemp() {
  uint8_t DSdata[2];
  OneWireReset();
  OneWireOutByte(0xcc);
  OneWireOutByte(0x44);
  PORTB |= _BV(DS_BIT);
  DDRB |= _BV(DS_BIT);
  _delay_ms(1000); // если хотим ждать когда датчик посчитает температуру.
  DDRB &= ~_BV(DS_BIT);
  PORTB &= ~_BV(DS_BIT);
  OneWireReset();
  OneWireOutByte(0xcc);
  OneWireOutByte(0xbe);
  DSdata[0] = OneWireInByte();
  DSdata[1] = OneWireInByte();
  word TReading = (word)(DSdata[1] << 8) + DSdata[0];
  if ((word)(TReading & 0x8000) == (word)(0x8000)) {
    TReading = (~TReading) + (word)1;
    TReading = (((word)6 * TReading) + TReading / (word)4) / (word)10;
  } else {
    TReading = (((word)6 * TReading) + TReading / (word)4) / (word)10 + (word)2000;
  }
  return TReading;
}

 

kostyamat
Offline
Зарегистрирован: 16.11.2017

Спасибо, конечно. Но у меня влажность только как пример. И к сути не относится. Да и не заметил я, чтобы DHT библиотека так уж ОЗУ жрала. Да и суть вопроса не в DHT.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

kostyamat пишет:
Спасибо, конечно. Но у меня влажность только как пример. И к сути не относится. Да и не заметил я, чтобы DHT библиотека так уж ОЗУ жрала. Да и суть вопроса не в DHT.

 

а.... виноват, не к тому датчику бросил прогу это для DS18B20, извиняюсь.

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

У меня вот так в лоб влажность проигрывалась

char* percent[] = {"procentov.wav", "procent.wav", "procenta.wav", "procenta.wav", "procenta.wav", "procentov.wav", "procentov.wav", "procentov.wav", "procentov.wav", "procentov.wav"};
char* digits[] = {"d0.wav", "d1.wav", "d2.wav", "d3.wav", "d4.wav", "d5.wav", "d6.wav", "d7.wav", "d8.wav", "d9.wav"};
char* desyatki[] = {"", "", "d20.wav", "d30.wav", "d40.wav", "d50.wav", "d60.wav", "d70.wav", "d80.wav", "d90.wav"};
char* d1019[] = {"d10.wav", "d11.wav", "d12.wav", "d13.wav", "d14.wav", "d15.wav", "d16.wav", "d17.wav", "d18.wav", "d19.wav"};

void playHumidity(byte value)
{
  if(value > 99)
    value = 99;
  if((value >= 10) && (value <= 19))
  {
    Serial.println(d1019[value%10]);
    Serial.println(percent[0]);
  }
  else
    if(value < 10)
    {
      Serial.println(digits[value]);
      Serial.println(percent[value%10]);
    }
    else
      if(value >= 20)
      {
        Serial.println(desyatki[value/10]);
        byte two_digit = value%10;
        if(two_digit > 0)
        {
          Serial.println(digits[two_digit]);
          Serial.println(percent[two_digit]);
        }
        else
          Serial.println(percent[0]);
      }
}

Можно спокойно добавить и до 9999 и больше. Если надо именно float то разбиваете его на два целых числа как-нибудь вот так

float balans = 9815.48;
int celoe = balans;
byte drobnoe = (balans-celoe)*100;

ну это как один из вариантов и дальше проигрываете как два числа и между ними проигрываете "celyh.wav"

kostyamat
Offline
Зарегистрирован: 16.11.2017

Очень интересное, иэффективное решение. Но вот в чем странность, ваш пример потребляет 5% ОЗУ, но если сделать вот так  

  byte celoe = (int)balans;
  byte drobnoe = (balans - celoe) * 100;
  playValue(celoe);
  playValue(drobnoe);

или так

  int celoe = balans;
  byte drobnoe = (balans - celoe) * 100;
  playValue(celoe);
  playValue(drobnoe);

без разницы, то потребление ОЗУ резко растет до АЖ! 25%

Мой же пример, используя PROGMEM удалось урезонить до 9%.

Ваш пример

"Скетч использует 2040 байт (6%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 520 байт (25%) динамической памяти, оставляя 1528 байт для локальных переменных. Максимум: 2048 байт.

Мой пример

Скетч использует 5224 байт (17%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 198 байт (9%) динамической памяти, оставляя 1850 байт для локальных переменных. Максимум: 2048 байт.

Мой забирает под себя 17% флеш, против ваших 6%, но он и читает сразу весь флоат, и сразу универсальнее. К тому же подозреваю, что потребление флеш растет из-за использования класса String, котрый линкуется, при компиляции. То есть, исходя из того , что String я все равно использую в основной программе, функция, в реале , не должна увеличивать потребление флеш так уж критично.

 

Но вот что занятно. Почему повторное использование вашей функции резко подымает потребление ОЗУ? Понять не могу.

 

Конечный вариант моего примера




void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:
  PlayFloat(F("humidy.wav"), -1234.19, F("celyh.wav"), F("sotyh.wav"), F("percents.wav"));

}


void PlayFloat(String wav0, float number, String terminator, String wav1, String wav2)
{
  Serial.println(wav0);
  // Handle negative numbers
  if (number < 0.0)
  {
    Serial.println(F("minus.wav"));
    number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  float rounding = 0.5;
  for (uint8_t i = 0; i < 2; ++i)rounding /= 10.0;
  number += rounding;



  // Extract the integer part of the number
  unsigned long int_part = (unsigned long)number;
  float remainder = number - (float)int_part;

  int int_part_length = String(int_part).length();
  char str_int_part[int_part_length + 1];
  String(int_part).toCharArray(str_int_part, int_part_length + 1);

  if (int_part >= 19) {
    for (int i = 4; i > 0; i--) {
      if ((int_part_length > 3) && (i == 4)) {
        Serial.println(String(F("000")) + String(str_int_part[0]) + F(".wav"));
        Serial.println(F("tysyach.wav"));
      }
      if ((int_part_length > 2) && (i  == 3)) {
        Serial.println(String(F("0")) + String(str_int_part[int_part_length - i]) + F("00.wav"));
      }

      if ((int_part_length > 1) && (i == 2)) {
        if ((String(str_int_part[i]).toInt() > 1) && (i >= 2)) {
          Serial.println(String(F("00")) + String(str_int_part[i]) + F("0.wav"));
          Serial.println(String(F("000")) + String(str_int_part[i + 1]) + F(".wav"));
        }
        else {
          Serial.println(String(F("00")) + String(str_int_part).substring(String(int_part).length() - 2, String(int_part).length()) + F(".wav"));
        }
      }

    }
  } else {
    if (int_part_length == 1) {
      Serial.println(String(F("000")) + String(int_part) + F(".wav"));
    }
    if (int_part_length == 2) {
      Serial.println(String(F("00")) + String(int_part) + F(".wav"));
    }
  }
  Serial.println(terminator);

  char _fname[3] = "00";
  if ((unsigned int)(remainder * 100) > 19) {
    for (int i = 0; i <= 1; i++) {

      remainder *= 10.0;
      unsigned int toPrint = (unsigned int)(remainder);
      _fname[i] = '0' + toPrint;
      Serial.println(String(F("00")) + String(_fname) + F(".wav"));
      remainder -= toPrint;
      _fname[i] = '0';

    }
  } else {
    String((unsigned int)(remainder * 100)).toCharArray(_fname, 3);
    Serial.println(String(F("00")) + String(_fname) + F(".wav"));
  }
  Serial.println(wav1);
  Serial.println(wav2);
}




void loop() {
  // put your main code here, to run repeatedly:

}

 

Logik
Offline
Зарегистрирован: 05.08.2014

Если Вас волнует расход ресурсов, а судя по сообщениям, то так, то навсегда забудте String и почти навсегда float. Например для передачи указателя на строку в PROGMEM используйте PGM_P, обявляем  void SendAT_P( PGM_P at){  вызываем  SendAT_P(PSTR("abc")); Вместо float пользуйте формат с фиксированой точкой и т.д.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Извините, нубский вопрос: SendAT_P(PSTR("abc")); и SendAT_P(F("abc"));
разве не равнозначно?
И второй вопрос: используя PROGMEM мы выиграли в ОЗУ но потеряли флеш? Нет?. Хотя, логика подсказывает, флеш мы теряем в любом случае, судя по всему.

Logik
Offline
Зарегистрирован: 05.08.2014

1. Нет. F дает __FlashStringHelper* В принципе  тоже иногда пользую, он удобней для вывода в Serial, и менее удобен в остальных случаях. Но если только про расположение в PROGMEM разговор - то оба расположат строку там одинаково.

2. В ОЗУ выигрываем т.к. строка  не копируется в него, а в флеше она будет в обоих случаях. Если на мелочи не смотреть (нужен код для извлечения из флеша, извлекая из флеша надо кудато расположит, и т.д.) То выигрываем в ОЗУ и не теряем в флеше.

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

kostyamat пишет:

Мой же пример, используя PROGMEM удалось урезонить до 9%.

Ну если мягкое с теплым сравнивать, то конечно оно будет как попало. Если в тот мой кусочек кода добавить PROGMEM то оперативнки будет занимать 188 байт в независимости от того сколько раз вызывается. Тот кусок просто показывал, что можно без String'а обойтись, тут его ой как не любят ) Да и если есть возможность обойтись без него, лучше так и сделать.

kostyamat
Offline
Зарегистрирован: 16.11.2017

2. Я так и думал.
String мне, как начинающему, все равно использовать придется. В классе String очень много удобных, и главное понятных, инструментов для работы с текстовыми данными. Аналоги которых, в случае char, придется фактически писать самому. И я правильно понимаю? Если я уж использую String один раз где-то в программе, то класс String линкуеться, при сборке, один раз, и повторное его использование к увеличению объема компилированого кода не приводит. Тяжело мне пока с char.

Penni, упаси Б-г, если Вы подумали, что я критикую ваш код. Я просто многого пока не понимаю. Но стараюсь. И да, мне ваш пример показался очень эффектным, с точки зрения простоты кода. Думаю и работает он раз в сто быстрее моего. Что не мало важно. Вот только прикол с ОЗУ я не понял.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Поблема не в String как механизм, проблема у том что вовремя не освобождаются от лишних переменных когда они уже не нужны. Вот у вас есть объем пространства. Вам нужно что то там сделать. Вы занесли туда иструмент и сделали. Но инструмент надо вынести из этого пространства. И тогда объем сохранится. Но если постоянно заносить инструменты, то ваше простанство будет забито ими. Да кончно все под рукой. Но рабочий объем станет маленьким и вы не сможете ремонтировать более объемные вещи.  Это основная причина почему идет рост объема ОЗУ. Необходимо работать с большими вещами и держать еще в этом объеме много инструментов, которыми давно на деле уже не пользуются. Но облом выбрасывать.

Logik
Offline
Зарегистрирован: 05.08.2014

kostyamat пишет:
String мне, как начинающему, все равно использовать придется. В классе String очень много удобных, и главное понятных, инструментов для работы с текстовыми данным

Вас шантажируют или угрожают? ;) 

Проблема тут как с наркотиками, начнете пользовать - тяжело отказатся. А наличие его сильно ограничит возможности проекта. Получается так, человек подсядет на String, пишет простенькие проги, все вроде хороше, растет опыт, нарабатывает себе готовый код с стрингами. Постепенно проекты усложняются и начинает нехватать ресурса. И получается вилка: либо 1.переходить на более мощный процессор либо 2.переучиватся и переписывать код более эффективным. Ну еще и третий вариант - забить на все и пойти на пиво:) Как правило 1 таки приводит к 2. т.к. с совместимостю все гладко только поверхам, тот же PROGMEM как правило отсутствует впринципе. Вобщем выстраданій совет - лучше с плохим не начинать . Темболее альтернатива вот http://cppstudio.com/cat/309/325/ и эффективно и универсально (работает последние 30 лет на любом проце!) и не так уж сложно.

Logik
Offline
Зарегистрирован: 05.08.2014

qwone пишет:

Поблема не в String как механизм....

угу. плюс еще способствует фрагментации кучи, когда и место вроде есть, а программа падает. Причем падает давно работающая и вроде 100 раз проверенная. И раз в час (или день или месяц или год - как повезет). Просто так обстоятельства сложились, так события наложились, память выделялась и освобождалась так, что на миллионном проходе вдруг нет целого блока свободной памяти для очень нужного. И все, зависли. Хороше если хоть есть куда хоть шото вывести о ошибке (тогда мы видим на экране торгового автомата какието коды например). А иногда и нет даже такой возможности (при ошибке в модуле экрана на экран не выведеш ;). Это одна из специфик встроенных систем. Такую ошибку даже отделу профи пофиксить - задача (логер не допишеш, места нет,  небольшое вмешательство в код - ошибка то уйдет, то вернется, тестируй днями, есть она или нет).

Особо все плохо от того что такие проблемы - в конце проекта и при эксплуатации, когда позно уже все переписывать.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Logik. Разумеется так и есть. Программы чаще достраиваются а не пишутся сверху вниз. Разумеется так выгоднее и дешевле. Сначало постоили один сарай, потом постоили еще и еще. И в конце сараев много , а подьехать не получится, потому что подъезды застоены сараями. Теперь надо выносить все на хр**н. И строить многоэтажный производственный центр.  ПС: Считайте что String это стандартный каркасный сарай. И если вам хочется небоскреб, то надо рисовать новый проект элементов для строительства этого небоскреба. А char строки , по той-же анологии, это только кирпичк которому надо изучить методы кладки еще.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Мы все тут чуток в прострацию ударились. А у новичков, типа меня, с char огромнейшие проблемы. Вот к примеру, даже в примере вверху, я долго не мог заставить char принимать цифру как символ, а не код символа, час рылся в инете пока не нашел реализацию:
_fname[i] = '0' + toPrint;
В другой программе, которую сейчас доделываю, мне нужно разом очистить char str[60] типа как
String str = "";
решения я пока так и не нашел
Делать в коде двести раз
for (int i = 0; i <= 59, i++) {
str[i] = ' ';
}
считаю бредом сивой кобылы, да и повторение этой конструкции не улучшает положение с размером в ОЗУ и флеш. (Хотя подозреваю, что после компилятора эта конструкция занимает во флеш меньше чем String str = "";). Но все равно, пахнет быдлокодом. И писать это 100500 раз в программе - увольте, даже копи/пастом. Наверное есть же простое решение?

Проблема в том, что ардуино занимаются миллионы, а документировано, именно такое элементарное, очень плохо.
Новичкам нужны примеры простых вещей, и желательно без умных слов "живёт на стеке", "указатель на переменную" и т.п.
Вот я к примеру, "указатель на переменную" в лоб так и не понимаю, разве что где-то на интуитивном уровне, но так чтобы уверенно взять и пользоваться - нет. И не помещается с разгону все в 45-ти летнюю голову.
Ещё одна проблема, может кто подскажет: подстановка переменной как макрос (вот даже не знаю как это правильно назвать) в функцию (несколько раз упростило бы жизнь, обошел другими путями), типа
char parm = "\"AT\", \"OK\", 1000"

SendAT($parm);
В бытность мою (до 28 лет) программистом FoxPro, там такая штука была. Это есть даже в bash shell на линукс.
В Arduino Wirning, C# это реализуемо?

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

kostyamat пишет:
мне нужно разом очистить char str[60] типа как String str = ""; решения я пока так и не нашел Делать в коде двести раз for (int i = 0; i <= 59, i++) { str[i] = ' '; } считаю бредом сивой кобылы

Выше Вам давали ссылку на функции для работы со строками, вот там есть memset

memset(str, ' ', 60); полностью заменит весь цыкл.

kostyamat пишет:
Ещё одна проблема, может кто подскажет: подстановка переменной как макрос (вот даже не знаю как это правильно назвать) в функцию

Имеется ввиду передача парметра функции? Передаете указатель на строку и все.

kostyamat
Offline
Зарегистрирован: 16.11.2017

"Передаете указатель на строку и все"
Що! Апять? Я же писал что не понимаю сути "указатель на..."
Можно просто пример такой передачи? Может догоню, наконец. ((

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

Дык почитайте https://habrahabr.ru/post/256443/

Если что не поймете, конкретно спросите.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Вот у вас есть мобильник. У мобильника есть номер. С точки зрения мобильника номер мобильника это его имя. А вот вы являетесь переменой. Потому что мобильник может оказаться у другого.   Если кто-то позвонит вам и скажет передайте трубку вашей , к примеру, жене. Это идет присвоение.  Но если вы напишете СМС позвоните по этому номеру, то эта СМС-ка и есть указатель на ваш номер. 

typedef mobilnik;// создадим тип мобильник
mobilnik _1234567 ; // это мобильник с номером 1234567
_1234567 = ВАСЯ; // вася купил мобильник 1234567
mobilnik * NOMEP ; // указатель на номер мобильника
NOMEP = & _1234567;// теперь в указателе на номер лежит номер телефона _1234567, которым владеет ВАСЯ.
// функция позвони по указателю на номер
void POZVONI(mobilnik * AAA) {

}
// а вот ее выполнение 
POZVONI(NOMEP);

 

Logik
Offline
Зарегистрирован: 05.08.2014

kostyamat пишет:
Вот я к примеру, "указатель на переменную" в лоб так и не понимаю, разве что где-то на интуитивном уровне, но так чтобы уверенно взять и пользоваться - нет.

Указатель - просто адрес в памяти. Если пишем типа char s[]="abc", то s - указатель (адрес) где располагается первый символ строки, его можно вывести как число и посмотреть Serial.print((int)s); можна использовать как число и менять.  Если пишем *s или s[0] - получим первый символ строки, *(s+1) или s[1] или char *r=s+1; *r - второй символ и т.д. Можна менять значения в строке s[4]='D' и строка станет "abD", можна  а s[4] - байт 0 (не символ '0') признак конца строки. Если сделать s=s+1  то теперь строка s ,будет "bc", можна вернуть обратно s=s-1. Можна математику с символами строки делать s[0]=s[0]+3; и первая буква станет не 'а' а 'd' (по используемой таблице символов смотрим). Строка - просто массив символов завершающаяся нулем. Найдите функцию вывода в сириал дампа области памяти (не найдете я выложу, у меня дето валяется), выведете дамп строки до и после своих опытов - за 5 минут поймете что куда. Там все примитивно просто. К тому же прикольно сочитается с теми функциями что выше было например, memset(str, ' ', 60); заполнит пробелами первуе 60 символов, а memset(str+4, ' ', 10); оставит как есть 4 первых символа и очистит 10 следующих а str[5]=str[4] сделает шестой символ таким же как пятый. 

kostyamat пишет:
Ещё одна проблема, может кто подскажет: подстановка переменной как макрос (вот даже не знаю как это правильно назвать) в функцию (несколько раз упростило бы жизнь, обошел другими путями), типа char parm = "\"AT\", \"OK\", 1000" SendAT($parm); В бытность мою (до 28 лет) программистом FoxPro, там такая штука была. Это есть даже в bash shell на линукс. В Arduino Wirning, C# это реализуемо?
Понять бы что надо...  Может такое http://www.c-cpp.ru/content/sprintf в строку собираются строковые отображения переменных совершенно разных типов.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

kostyamat пишет:
Проблема в том, что ардуино занимаются миллионы, а документировано, именно такое элементарное, очень плохо.

Есть такой момент.

Только совершенно не там, где Вы его видите.

Вот представьте, у Вас спрашивают, как проехать туда-то. Вы начинаете отвечать, но Вас переспрашивают, сколько раз нужно нажать на педаль газа и сколько раз - на педаль торможа, и в какие моменты времени это нужно делать.

К чему это я? А к тому, что в нормальном случае тот, кто спрашивает дорогу, долже уже уметь водить машину, а тот, кто дорогу объясняет, вправе рассчитывать на такое умение у спрашивающего.

В Ардуино используется стандартный язык программирования. И искать сведения, касающиеся языка, в документации по Ардуино все равно, что требовать у того, кто показывает дорогу, научить Вас водить автомобиль.

А вот документация на язык программирования (в отличие от документации на Ардуино) как раз имеется в больших количествах и высоком качестве.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Вот, вариант совершенно без использования класса String


void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:
  PlayFloat("humidy.wav", -9876.19, "celyh.wav", "sotyh.wav", "percents.wav");
}



void PlayFloat(char* wav0, float number, char* terminator, char* wav1, char* wav2)
{

  // Handle negative numbers
  Serial.println(wav0);
  if (number < 0.0)
  {
    Serial.println(F("minus.wav"));
    number = -number;
  }
  if (number > 9999.99) {
    Serial.println(F("over10t.wav"));
    Serial.println(wav2);
    return;
  }
  // Round correctly so that print(1.999, 2) prints as "2.00"
  float rounding = 0.5;
  for (uint8_t i = 0; i < 2; ++i)rounding /= 10.0;
  number += rounding;



  // Extract the integer part of the number
  int int_part = (int)number;
  int8_t remainder = (number - int_part) * 100;

  char str_int_part[5];
  char str_remainder[3];


  sprintf(str_int_part, "%d", int_part);
  sprintf(str_remainder, "%d", remainder);
  int8_t int_part_length = strlen(str_int_part);
  char _fname[9] = "0000.wav";

  if (int_part >= 19) {
    for (int8_t i = 4 - int_part_length; i < 4; i++) {
      memset(_fname, '0', 4);
      _fname[i] = str_int_part[i - (4 - int_part_length)];
      Serial.println(_fname);
    }
  }
  else {
    memset(_fname, '0', 4);
    if (int_part_length == 1) {
      _fname[3] = str_int_part[0];
    }
    if (int_part_length == 2) {
      _fname[2] = str_int_part[0];
      _fname[3] = str_int_part[1];
    }
    Serial.println(_fname);
  }
  Serial.println(terminator);

  if (remainder < 20) {
    memset(_fname, '0', 4);
    if (remainder >= 10) {
      _fname[2] = str_remainder[0];
      _fname[3] = str_remainder[1];
    } else {
      _fname[3] = str_remainder[0];
    }
    if (remainder > 1) {
      Serial.println(_fname);
    }
  } else {
    for (int8_t i = 0; i < 2; i++) {
      memset(_fname, '0', 4);
      _fname[i + 2] = str_remainder[i];
      Serial.println(_fname);
    }
  }

  if (remainder > 0) {
    Serial.println(wav1);
  }
  Serial.println(wav2);
}




void loop() {
  // put your main code here, to run repeatedly:

}

Смею заметить, но писать такое совершенно не удобно, а вот это все http://cppstudio.com/cat/309/325/ ведет себя иногда совершенно неадекватно.

К примеру вот это

Serial.println(strcat("abc", _fname);

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

Странно то, что раз работает корректно, а иногда, между if ---- else,  начинается вот такое вот.

А вот это int8_t int_part_length = strlen(int_part) возвращает 45, хотя int_part = 1234, то-есть 4 знакоместа. Чтобы получить корректное значение нужно сначало int перевести в char, тогда получишь корректное значение. То-есть сначало получить длинну, а потом задать размер char под этот int в лоб не получится. Нужно использовать промежуточную переменную. В котрой мерять длинну, а потом переписывать ее уже в нужную, создав ее по длинне строки с интом. Фатал! ((

кстати,

"Скетч использует 3366 байт (10%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 244 байт (11%) динамической памяти, оставляя 1804 байт для локальных переменных. Максимум: 2048 байт."

Пример со String кушал ОЗУ меньше, правда флеша почти в два раза больше. Но опять же из-за линковки класса. А так, как линковать к программе мне его все равно придется, то метод String получается єкономичнее. Разве что вообще табу на String в программе сделать, тогда линковаться не будет. Но и выигрыш флеша смешной в ущерб удобству.

Penni
Penni аватар
Offline
Зарегистрирован: 18.01.2015

Все там нормально работает! Посмотрите что возвращает strcat. Указатель на "первый параметр", т.е. подразумевается что выделено достаточно памяти под объединение строк, а у Вас первым параметром передается "abc"... По той ссылке даже пример использования дан который будет прекрасно работать.

По поводу strlen так на то она и называется strlen, почитайте описание там написано, что она возвращает длину строки по нулевому символу (\0). Вы передали указатель на тип инт вот она и пошла по памяти пока не нашла \0 поэтому и вернула 45.

qwone объяснял почему String не используют.

По мне Ваша задача решается вообще без использования каких-либо строк, будть то String либо char* (ну кроме тех что в progmem будут лежать с именами проигрываемых файлов).

Ну и, например, если ввести 30.00 что должна выдать программа? Поидее 30 целых 0 сотых процента а что выведет? 30, 0, целых, процента.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Мда, забыл проверку по 0 сотых сделать. Кстати, а как названия файлов правильно в пPROGMEM положить. А то F(), не работает. Если передавать функции значения char* file через вот так PSTR("celych.wav"), то в выводе в сериал каша из символов, вместо названий файлов. При этом компилятор молчит шо партизан. Никаких ошибок. Why?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

kostyamat пишет:

К примеру вот это

Serial.println(strcat("abc", _fname);

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

Ничего удивительного после строки:

    memset(_fname, '0', 4);

В Си-строке обязан быть завершающий ноль, а у Вас вместо 0 - '0', что далеко не одно и то же.

Цитата:

Пример со String кушал ОЗУ меньше, правда флеша почти в два раза больше.

Это Вам так кажется.

String опасна тем, что кушает динамическую память, а Вы опираетесь на статистику по использованию статической.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Переделал немного


void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:
  Serial.println(F("humidy.wav"));
  PlayFloat(110.21, "_rubl.wav", "_kopeyk.wav");
  Serial.println(F("persent.wav"));
}



void PlayFloat(float number, char* terminator, char* wav1)
{

  // Handle negative numbers
  if (number < 0.0)
  {
    Serial.println(F("minus.wav"));
    number = -number;
  }
  if (number > 9999.99) {
    Serial.println(F("over10t.wav"));
    return;
  }
  // Round correctly so that print(1.999, 2) prints as "2.00"
  float rounding = 0.5;
  for (uint8_t i = 0; i < 2; ++i)rounding /= 10.0;
  number += rounding;



  // Extract the integer part of the number
  int int_part = (int)number;
  int8_t remainder = (number - int_part) * 100;

  char str_int_part[5];
  char str_remainder[3];


  sprintf(str_int_part, "%d", int_part);
  sprintf(str_remainder, "%d", remainder);
  int8_t int_part_length = strlen(str_int_part);
  char _fname[9] = "0000.wav";

  if (int_part >= 19) {
    for (int8_t i = 4 - int_part_length; i < 4; i++) {
      memset(_fname, '0', 4);
      if (str_int_part[i - (4 - int_part_length)] != '0') {
        _fname[i] = str_int_part[i - (4 - int_part_length)];
        Serial.println(_fname);
      }
    }
  }

  else {
    memset(_fname, '0', 4);
    if (int_part_length == 1) {
      _fname[3] = str_int_part[0];
    }
    if (int_part_length == 2) {
      _fname[2] = str_int_part[0];
      _fname[3] = str_int_part[1];
    }
    Serial.println(_fname);
  }


  if ((_fname[3] - '0') == 1) memset(terminator, '1', 1);
  if (((_fname[3] - '0') > 1 && (_fname[3] - '0') <= 4 )) memset(terminator, '2', 1);
  if ((_fname[3] - '0') >= 5 || ((_fname[3] - '0') == 0)) memset(terminator, '3', 1);
  Serial.println(terminator);



  if (remainder > 0) {
    if (remainder < 20) {
      memset(_fname, '0', 4);
      if (remainder >= 10) {
        _fname[2] = str_remainder[0];
        _fname[3] = str_remainder[1];
      } else {
        _fname[3] = str_remainder[0];
      }
      if (remainder > 1) {
        Serial.println(_fname);
      }
    } else {
      for (int8_t i = 0; i < 2; i++) {
        memset(_fname, '0', 4);
        _fname[i + 2] = str_remainder[i];
        Serial.println(_fname);
      }
    }
    if ((_fname[3] - '0') == 1) memset(wav1, '1', 1);
    if (((_fname[3] - '0') > 1 && (_fname[3] - '0') <= 4 )) memset(wav1, '2', 1);
    if ((_fname[3] - '0') >= 5 || ((_fname[3] - '0') == 0)) memset(wav1, '3', 1);
    Serial.println(wav1);
  }
}




void loop() {
  // put your main code here, to run repeatedly:

}

убрал ошибку вывода 0000.wav, плюс добавил обработку разно-звучание "одна гривня, две гривни, пять гривен". Мне под українську мову работает отлично, под русский рубль надо еще чуток доделывать. В украинском "одна гривня - одна ціла", в русском "один рубль НО одна целая" - этого функция не учитывает. Еще решил не передавать в нее заглавие и скажем "процентов". А вызывать перед ней и после, так универсальнее. Так можно и баланс СИМки читать, и температуру, и влажность, давление и т.п.

ПС. Значения больше 9999.99 не озвучиваются, проигрывается файл "оверДофига" и на выход. ))

 

Slacky
Slacky аватар
Offline
Зарегистрирован: 16.11.2017

В связи с обсуждением отказа от String вопрос - как Arduino IDE относится к malloc, realloc и т.п?

Slacky
Slacky аватар
Offline
Зарегистрирован: 16.11.2017

В принципе, прошелся поиском, нормально относится :))

Вопрос снят ...

clawham
Offline
Зарегистрирован: 26.03.2020

Привет! Код агонь! А можно листинг какие цифры надо озвучивать? как вы их генерировали? не в микрофон же бубнеть?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Если не умеете работать с микрофоном или стесняетесь своего голоса, можно найти программу с массой заготовок, например, автомобильного навигатора, и из нее надергать файлы с нужными числительными.

kostyamat
Offline
Зарегистрирован: 16.11.2017

Звуковые файли создавал вот этой программой https://play.google.com/store/apps/details?id=hesoft.T2S
Выбираете нужный язык генерации в настройках и она вам генерит введённый текст в waw формат. Особенно мне в ней украинский язык понравился. Очень голос приятный, и сочное такое произношение. :)
Файлы по формату 0000.waw.
1 = 0001.waw, 90 = соответственно 0090.waw.
Файлы нужны для таких рядов цифр 0-20, потом десятки 30 - 90 (0030.waw, 0040.waw и и.д), аналогично сотни 100-900.
minus.waw для минусовых чисел, over10t.waw - что то типа "число вне диапазона".

Вот не помню как там тысячи. Ну введите в функцию что-то типа 2057.67 и посмотрите что она выводит.