Форматированный вывод в Serial

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015
Начинающий коллега увидел у меня в коде форматированный по типу printf отладочный вывод в Serial (когда вместо:
1Serial.print("U=");
2Serial.print(u);
3Serial.print("V f=");
4Serial.print(f);
5Serial.println("KHz");
пишется
1SerialPrintf("U=%dV f=%dKHz\n, v, f);
попросил дать ему код и посоветовал опубликовать здесь, т.к., по его мнению, многие с удовольствием воспользуются.
В последнем сомневаюсь, но, на всякий случай, публикую, может и правда кому сгодится. Вывод реализован одной функцией. Параметры у неё как у printf. Текст функции и пример использования:

 

 
Piskunov
Offline
Зарегистрирован: 13.02.2014

Спасибо.

Для меня это полезная и удобная вещь.
Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Piskunov пишет:

Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?

Я не знаю, я просто знаю стандартные библиотеки С/С++ и, когда мне нужно что-то оттуда, проверяю, есть ли это в библиотеках Arduino (просто вставляю в код и смотрю скомпилирует или скажет "нет такой"). Там ведь, на самом деле язык Ардуино - всего лишь препроцессор, после работы которого код передаётся стандартному С++ компилятору gcc.

ratman
Offline
Зарегистрирован: 11.10.2015

Ты бы хоть копирайт свой оставил, чтобы страна помнила своих героев.

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

1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.

 

2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.

Maverik
Offline
Зарегистрирован: 12.09.2012

спасибо, полезная штучка.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

andriano пишет:

1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.

Здесь используется vsnprintf, а она возвращает длину без терминального нуля ( just попробуйте :)))

andriano пишет:

2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.

Ну, да, кто-то использует таймер, кто-то пины, кто-то кучу, кто-то стек - всем в этом мире от бедняги разработчика чего-то нужно :)))))

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

Цитата:
Здесь используется vsnprintf, а она возвращает длину без терминального нуля ( just попробуйте :)))

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

Хотя, в принципе, если существующая реализация сделана не постандарту, то, согласен, лучше перестраховаться и добавить - на случай, когда поправят реализацию.

Цитата:
Ну, да, кто-то использует таймер, кто-то пины, кто-то кучу, кто-то стек - всем в этом мире от бедняги разработчика чего-то нужно :)))))

Так никто не говорит, что это плохо. Просто эту функцию, как и любое сильнодействующее средство, нужно применять осторожно.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

andriano пишет:

нужно применять осторожно.

В смысле форматы на мегабайты не писать? А и напишет кто - если ей памяти не хватит, она код ошибки вернёт. Ничего не сломается, просто не выведет ничего.

Ваше "осторожно" напомнило книгу "Физики продолжают шутить". Там был фразеологический словарь для перевода научных статей на нормальный язык. В частности были такие определения:

"Во время эксперимента прибор был слегка повреждён" - перевод: "Уронили на пол"

"С прибором обращались с исключительной осторожностью" - перевод: "Не роняли на пол" :)

_kp
Offline
Зарегистрирован: 07.10.2016

Случайно натолкнулся на этот  очередной "велосипед".
Всё время это делалось стандартным образом:

01#include <stdio.h>
02#include <avr/pgmspace.h>
03 
04int uart0_putchar(char ch, FILE *stream)
05{
06  Serial.print(ch);
07  return 0;
08}
09 
10FILE *uart_stream;
11 
12void setup(void)
13{
14  Serial.begin(9600);
15  uart_stream = fdevopen(uart0_putchar, /*uart0_getchar*/ NULL );
16  stderr = stdout = stdin = uart_stream;
17  printf("Init stdio - OK.\n");
18  printf_P(PSTR("\nString in FLASH.\n"));
19}

Преимущества:
- Расход ОЗУ меньше, вывод идёт сразу в UART.
- Можно использовать строковые константы из флеш.
- меньше лишнего кода
- По аналогии, можно связать printf с буферизированным UART(ами) и LCD.

PS: Впрочем, за рабочий "велосипед" - зачет. Ардуино для этого и предназначена.
 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

_kp пишет:

Преимущества:

- Расход ОЗУ меньше, вывод идёт сразу в UART.
- Можно использовать строковые константы из флеш.
- меньше лишнего кода
- По аналогии, можно связать printf с буферизированным UART(ами) и LCD.
 

Недостатки

Отжирает лишние 900 байтов памяти программы. Пруф. ниже

Но за хороший учебный материал, безсусловно, зачёт. Дополнение очень в струю. Жаль нельзя добавить в первоначальный топик.

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

Могу показать свой логгер, который использую везде в коде: TelnetServer

Там файлы: Logger.cpp и Logger.h

Используется так:

1extern CLogger Logger;
2 
3#define logdebug( fmt, ... ) Logger.Log( PSTR( "[DEBUG] " fmt ), ##__VA_ARGS__ )
4#define logerror( fmt, ... ) Logger.Log( PSTR( "[ERROR] " fmt ), ##__VA_ARGS__ )
1char s1[32], s2[32];
2 
3strncpy_P( s1, ( const char * ) CVersion::GetVersionString(), sizeof( s1 ) );
4strncpy_P( s2, ( const char * ) CVersion::GetBuildDateString(), sizeof( s2 ) );
5 
6s1[ sizeof( s1 ) - 1 ] = '\0';
7s1[ sizeof( s2 ) - 1 ] = '\0';
8 
9logdebug( "Telnet Server, version %s, %s", s1, s2 );

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

uni пишет:

Могу показать свой логгер, который использую везде в коде: TelnetServer

Спасибо!

Тема изначально учебная, поэтому здорово, что появляются посты типа Вашего и _kp. Больше примеров, хороших и разных!

Сам я для отладочной печати использую потоковую нотацию, очень компактно, но, разумеется без printf'овского форматирования. Но там есть засада. Параметр передаётс по значению, поэтому, например, если печатать String, то создаётся новый экземпляр со всеми вытекающими.

_kp
Offline
Зарегистрирован: 07.10.2016

Цитата:
Отжирает лишние 900 байтов памяти программы. Пруф. ниже

1.
Отлично! Будем считать, первый вариант оптимизированным, например чиcто для отладочного вывода. Когда от библиотеки stdio нужен только printf.

2.
С первым вариантом есть проблема с перерасходом ОЗУ.
При вызове например SerialPrintf("Fun begins!\n"); Строковая константа "Fun begins!\n" будет размещена и во флеш, и в ОЗУ(а для AVR это сверх зло).
Плюс выделяется дополнительный временный буфер.
Несколько десятков "отладочных printf", и для основной программы совсем нехватит ОЗУ.
На AVR обычно не пользуются стандартным printf/sprintf, а используют printf_P/sprintf_P

3.
На минималистичной программе, сравнение размеров кода не объективно, практическая разница будет всего 120..150 байт. Но зато единообразно работает ввод/вывод и UART, и с LCD, и с SdCard.

4,
При переделке SerialPrintf для экономии ОЗУ,  при замене  vsnprintf на vsnprintf_P,  размер  минималистичных программ заметно распухает, и не менее заметно сокращается разница в размера кода для обоих вариантов, приближаясь к рассчитанной выше.. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

О, Господи, не лезьте в драку, это учебный материал. Я показал как использовать sprintf, Вы показали как перенаправить поток - прекрасно, что показано и то, и другое.

тем более, что 

_kp пишет:

На AVR обычно не пользуются стандартным printf/sprintf, а используют printf_P/sprintf_P

Господь с Вами, в сколько-нибудь серьёзных программах никто не использует ни того, ни другого. 

_kp
Offline
Зарегистрирован: 07.10.2016

И снова привет.

 

На станице http://playground.arduino.cc/Main/Printf говорится, о том что можно добавить метод printf в базовый класс Print.
Фича в том, что метод xxx.printf появится не только у Serial, но и у всех классов, его использующих, то есть LCD и TFT дисплеи.

Там же  есть и пример, показывающий что именно надо подправить в фирменной библиотеке.
Но, как обычно, там вариант с временным текстовым буфером. Что для AVR крайне вредно, и буфера всё равно мало.

Вот сделал почти тоже самое, но с существенно меньшим расходом ОЗУ, и работающее быстрее.
То есть, это вариант добавления printf в базовый класс Print.
После чего можно писать примерно так:

1Serial.printf("var1=%4d var2=0x%06X\n",var1,var2);
2TFT1.printf(F("/e[%d;%dH V%02u = %08lu"), yst+n,x, d[n]);

Для установки нужно изменить оригинальные библиотеки Arduino, предварительно сохранив оригиналы.

файл: arduino-1.6.11\hardware\arduino\avr\cores\arduino\Print.h
// Добавляем в описание класса это:
 

файл: arduino-1.6.11\hardware\arduino\avr\cores\arduino\Print.cpp
// Добавляем в файл  это:
 

Результаты тестов.
1.  avr-g++ гененерирует компактнее код для статических классов, по сравнению с передачей кучи указателей в функции в стиле си.
То есть добавление printf в базовый класс Print выгоднее, по сравнению с fprintf(stream,format,...). Объём кода меньше.
При объёме кода за 20кБ этот вариант, printf в классе даёт самый компактный код.

2. При неиспользовании printf, лишнего кода во флеш нет.

3. Даже при неиспользовании printf, отъедается 12 байт ОЗУ, под структуру FILE,
которую можно перенести в методы print, но  нецелесообразно из за быстродействия.
При вызове Serial.print, думаю разницы в быстродействии не увидеть, а при выводе на TFT LCD быстродействие и так на грани возможного.

4. Расход ОЗУ минимален. 12 байт под структуру FILE, 11+1 временный буфер в libc.vprintf плюс стек под переменные.

5.  Ну и о недостатках. Библиотеки vprintf/printf/sprintf в Arduino без поддержки float.
Штатными методами не лечится.
- Можно подменить библиотеку, но так каждый проэкт будет больше, что нехорошо.
- Можно использовать arduino-makefile. Кроме того что новичкам надо разбираться, проблем нет.
- Можно для форматирования float использовать - dtostrf
- Ну и конечно, не "злоупотреблять" float'ами на AVR.    

ps: я так понял, что файл то тут приложить ни как?

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

_kp пишет:
 

ps: я так понял, что файл то тут приложить ни как?

файл - нет.

код - да.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ratman пишет:

Ты бы хоть копирайт свой оставил, чтобы страна помнила своих героев.

Коллега - копираст?

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Добрый день. 

Взял чей то вполне рабочий скетч, немного переделал под себя, пока все устравало. Но ТЗ расширилось и вместо вывода в порт чисел типа byte, понадобилось выволдить числа double.

Привожу первый рабочий вариант

Значения перед выводом хранятся в строковом буферe посредством snprintf . Вот ее описание:

int snprintf(char *restrict buf, size_t num,
              const char restrict format, ...);

Она идентична функции sprintf() за исключением того, что в массиве, адресуемом указателем buf, будет сохранено максимум num-1символов. По окончании работы функции этот массив будет завершаться символом конца строки (нуль-символом). Таким образом, функция snprintf() позволяет предотвратить переполнение буфера buf.

Нерабочий 

В первом варианте спецификатор d - для целого знакового десятичного числа, во втором  f - для число с плавающей точкой. Подскажите в чем не прав

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
1//переменные которые надо менять
2byte kp1 = 0;      byte kp2 = 0;
3byte ki1 =  0;      byte ki2 = 0;
4byte kd1 =  0;      byte kd2 = 0;
1double kp1 = 0;      double kp2 = 0;
2double ki1 =  0;      double ki2 = 0;
3double kd1 =  0;      double kd2 = 0;

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

1snprintf(ch1, 16, "%d        ", *mMenu[curMenu].k1);
2snprintf(ch2, 16, "%d        ", *nMenu[curMenu].k2);
1snprintf(ch1, 16, "%f         ", *mMenu[curMenu].k1);
2snprintf(ch2, 16, "%f         ", *nMenu[curMenu].k2);

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

В том, что sprintf с плавающей точкой ниработаить

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

А чем это заменить чтобы заработало?

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

dtostrf()

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
strtod видимо. Функция strtod преобразовывает строку string в double. Да уже понял
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Strtod это перевести строку в double

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

А взат, из числа в строку - dstrtof()

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BuonanotteMasha,

1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.

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

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Да спасибо, уже работает

001//Назначаем пины кнопок управления
002#define BUTTON_RIGHT 10
003#define BUTTON_UP 9
004#define BUTTON_DOWN 8
005 
006//состояние кнопок по умолчанию
007boolean State_Up = LOW;
008boolean State_Down = LOW;
009boolean State_Left = LOW;
010boolean State_Right = LOW;
011boolean State_Ok = LOW;
012 
013//переменные которые надо менять
014float kp1 = 0;      float kp2 = 0;
015float ki1 =  0;      float ki2 = 0;
016float kd1 =  0;      float kd2 = 0;
017 
018//переменные для кнопок
019long ms_button = 0;
020boolean  button_state = false;
021boolean  button_long_state = false;
022 
023byte curMenu = 0; //текущий пункт меню,
024bool b_ShowmMenu = 0; // флаг отображения меню
025const byte CountMenu = 3; //количество пунктов меню
026  
027//массив элементов меню
028struct MENU_1{ //
029char name1[13];
030  float *k1;
031};
032 
033struct MENU_2{ //
034char name2[13];
035  float *k2;
036};
037 
038//инициализация меню
039//строковое имя, адрес переменной которую надо менять
040MENU_1 mMenu[CountMenu] = {
041"   kP1 =", &kp1,
042"   kI1 =", &ki1,
043"   kD1 =", &kd1
044};
045 
046MENU_2 nMenu[CountMenu] = {
047"   kP2 =", &kp2,
048"   kI2 =", &ki2,
049"   kD2 =", &kd2
050};
051  
052//функция выполнения меню
053void menu_setup(){
054   
055  if (State_Right == LOW && millis() - ms_button > 200) {   //следующий пункт меню по кругу
056      ms_button =  millis();
057      if (curMenu == CountMenu - 1)
058      curMenu = 0;
059      else  curMenu++;
060  }
061    
062  if (State_Up == LOW && millis() - ms_button > 200) {
063        ms_button =  millis();
064        (*mMenu[curMenu].k1)+=0.5;
065        (*nMenu[curMenu].k2)+=0.5;
066  }
067 
068  if (State_Down == LOW && millis() - ms_button > 200) {
069      ms_button =  millis();
070      (*mMenu[curMenu].k1)-=0.5;
071      (*nMenu[curMenu].k2)-=0.5;
072  }
073 
074 
075  //вывод использую буфер
076  char charbuf1[16];
077  char charbuf2[16];
078   
079  dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
080  dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
081   
082  //snprintf(charbuf1, 16, "%f         ", *mMenu[curMenu].k1);
083  //snprintf(charbuf2, 16, "%f         ", *nMenu[curMenu].k2);
084   
085  if ( millis() - ms_button > 1000) {
086  ms_button =  millis();
087  Serial.println(mMenu[curMenu].name1);
088  Serial.println(charbuf1);
089  Serial.println(nMenu[curMenu].name2);
090  Serial.println(charbuf2);
091  }
092   
093}
094 
095 
096void setup() {
097  Serial.begin(9600);
098  pinMode (BUTTON_UP, INPUT);     digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор
099  pinMode (BUTTON_DOWN, INPUT);   digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор
100  pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор 
101}
102 
103 
104void loop() {
105   
106  //Считываем состояние кнопок управления
107  State_Up = digitalRead(BUTTON_UP);
108  State_Down = digitalRead(BUTTON_DOWN);
109  State_Right = digitalRead(BUTTON_RIGHT);
110  //-------------------------------------/
111   
112    menu_setup(); //вывод меню 
113}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Только не пойму, почему в порт только одно число выводится

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Тут ошибок не наблюдаю

01//вывод использую буфер
02char charbuf1[16];
03char charbuf2[16];
04 
05dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
06dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
07 
08if ( millis() - ms_button > 1000) {
09ms_button =  millis();
10Serial.println(mMenu[curMenu].name1);
11Serial.println(charbuf1);
12Serial.println(nMenu[curMenu].name2);
13Serial.println(charbuf2);
14}

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

ЕвгенийП пишет:

BuonanotteMasha,

1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.

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

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Танцы с бубном не помогли 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Внимательно пока не смотрел, но попробуйте поставить после строки 92 Serial.flush();

Виноват, поправил на 92 - в общем, после печати сразу. А то в старом скетче это было 90.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Нет не помогло, пробую с floatToString. Поменял как вы сказали на 92 тоже нет

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Покажите текущий код (в котором не помогло)

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
001//Назначаем пины кнопок управления
002#define BUTTON_RIGHT 10
003#define BUTTON_UP 9
004#define BUTTON_DOWN 8
005 
006//состояние кнопок по умолчанию
007boolean State_Up = LOW;
008boolean State_Down = LOW;
009boolean State_Left = LOW;
010boolean State_Right = LOW;
011boolean State_Ok = LOW;
012 
013//переменные которые надо менять
014float kp1 = 0;      float kp2 = 0;
015float ki1 =  0;      float ki2 = 0;
016float kd1 =  0;      float kd2 = 0;
017 
018//переменные для кнопок
019long ms_button = 0;
020boolean  button_state = false;
021boolean  button_long_state = false;
022 
023byte curMenu = 0; //текущий пункт меню,
024bool b_ShowmMenu = 0; // флаг отображения меню
025const byte CountMenu = 3; //количество пунктов меню
026  
027//массив элементов меню
028struct MENU_1{ //
029char name1[13];
030  float *k1;
031};
032 
033struct MENU_2{ //
034char name2[13];
035  float *k2;
036};
037 
038//инициализация меню
039//строковое имя, адрес переменной которую надо менять
040MENU_1 mMenu[CountMenu] = {
041"   kP1 =", &kp1,
042"   kI1 =", &ki1,
043"   kD1 =", &kd1
044};
045 
046MENU_2 nMenu[CountMenu] = {
047"   kP2 =", &kp2,
048"   kI2 =", &ki2,
049"   kD2 =", &kd2
050};
051  
052//функция выполнения меню
053void menu_setup(){
054   
055  if (State_Right == LOW && millis() - ms_button > 200) {   //следующий пункт меню по кругу
056      ms_button =  millis();
057      if (curMenu == CountMenu - 1)
058      curMenu = 0;
059      else  curMenu++;
060  }
061    
062  if (State_Up == LOW && millis() - ms_button > 200) {
063        ms_button =  millis();
064        (*mMenu[curMenu].k1)+=0.5;
065        (*nMenu[curMenu].k2)+=0.5;
066  }
067 
068  if (State_Down == LOW && millis() - ms_button > 200) {
069      ms_button =  millis();
070      (*mMenu[curMenu].k1)-=0.5;
071      (*nMenu[curMenu].k2)-=0.5;
072  }
073 
074 
075  //вывод использую буфер
076  static char charbuf1[16];
077  static char charbuf2[16];
078   
079  dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
080  dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
081   
082   
083  if ( millis() - ms_button > 1000) {
084  ms_button =  millis();
085  Serial.print(mMenu[curMenu].name1);
086  Serial.println(charbuf1);
087  Serial.print(nMenu[curMenu].name2);
088  Serial.println(charbuf2);
089  Serial.flush();
090  }
091   
092}
093 
094 
095void setup() {
096  Serial.begin(9600);
097  pinMode (BUTTON_UP, INPUT);     digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор
098  pinMode (BUTTON_DOWN, INPUT);   digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор
099  pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор 
100}
101 
102 
103void loop() {
104   
105  //Считываем состояние кнопок управления
106  State_Up = digitalRead(BUTTON_UP);
107  State_Down = digitalRead(BUTTON_DOWN);
108  State_Right = digitalRead(BUTTON_RIGHT);
109  //-------------------------------------/
110   
111    menu_setup(); //вывод меню 
112}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Евгений спасибо вам за помощь, но у меня самого получилось

001#include<stdlib.h> // included for floatToString function
002 
003String floatToString(float x, byte precision = 2) {
004  char tmp[50];
005  dtostrf(x, 0, precision, tmp);
006  return String(tmp);
007}
008 
009//Назначаем пины кнопок управления
010#define BUTTON_RIGHT 10
011#define BUTTON_UP 9
012#define BUTTON_DOWN 8
013 
014//состояние кнопок по умолчанию
015boolean State_Up = LOW;
016boolean State_Down = LOW;
017boolean State_Left = LOW;
018boolean State_Right = LOW;
019boolean State_Ok = LOW;
020 
021//переменные которые надо менять
022float kp1 = 0;      float kp2 = 0;
023float ki1 =  0;      float ki2 = 0;
024float kd1 =  0;      float kd2 = 0;
025 
026//переменные для кнопок
027long ms_button = 0;
028boolean  button_state = false;
029boolean  button_long_state = false;
030 
031byte curMenu = 0; //текущий пункт меню,
032bool b_ShowmMenu = 0; // флаг отображения меню
033const byte CountMenu = 3; //количество пунктов меню
034  
035//массив элементов меню
036struct MENU_1{ //
037char name1[13];
038  float *k1;
039};
040 
041struct MENU_2{ //
042char name2[13];
043  float *k2;
044};
045 
046//инициализация меню
047//строковое имя, адрес переменной которую надо менять
048MENU_1 mMenu[CountMenu] = {
049"   kP1 =", &kp1,
050"   kI1 =", &ki1,
051"   kD1 =", &kd1
052};
053 
054MENU_2 nMenu[CountMenu] = {
055"   kP2 =", &kp2,
056"   kI2 =", &ki2,
057"   kD2 =", &kd2
058};
059  
060//функция выполнения меню
061void menu_setup(){
062   
063  if (State_Right == LOW && millis() - ms_button > 200) {   //следующий пункт меню по кругу
064      ms_button =  millis();
065      if (curMenu == CountMenu - 1)
066      curMenu = 0;
067      else  curMenu++;
068  }
069    
070  if (State_Up == LOW && millis() - ms_button > 200) {
071        ms_button =  millis();
072        (*mMenu[curMenu].k1)+=0.5;
073        (*nMenu[curMenu].k2)+=0.5;
074  }
075 
076  if (State_Down == LOW && millis() - ms_button > 200) {
077      ms_button =  millis();
078      (*mMenu[curMenu].k1)-=0.5;
079      (*nMenu[curMenu].k2)-=0.5;
080  }
081 
082 
083  //вывод использую буфер
084  static char charbuf1[16];
085  static char charbuf2[16];
086   
087  //dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
088  //dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
089   
090   String str1 = floatToString(*mMenu[curMenu].k1); // conversion call
091   String str2 = floatToString(*nMenu[curMenu].k2); // conversion call
092  //floatToString(charbuf1, *mMenu[curMenu].k1, 2);
093  //floatToString(charbuf2, *nMenu[curMenu].k2, 2);
094   
095  //snprintf(charbuf1, 16, "%f         ", *mMenu[curMenu].k1);
096  //snprintf(charbuf2, 16, "%f         ", *nMenu[curMenu].k2);
097   
098  if ( millis() - ms_button > 1000) {
099  ms_button =  millis();
100  Serial.print(mMenu[curMenu].name1);
101  Serial.println(str1);
102  Serial.print(nMenu[curMenu].name2);
103  Serial.println(str2);
104  Serial.flush();
105  }
106   
107}
108 
109 
110void setup() {
111  Serial.begin(9600);
112  pinMode (BUTTON_UP, INPUT);     digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор
113  pinMode (BUTTON_DOWN, INPUT);   digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор
114  pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор 
115}
116 
117 
118void loop() {
119   
120  //Считываем состояние кнопок управления
121  State_Up = digitalRead(BUTTON_UP);
122  State_Down = digitalRead(BUTTON_DOWN);
123  State_Right = digitalRead(BUTTON_RIGHT);
124  //-------------------------------------/
125   
126    menu_setup(); //вывод меню 
127}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

проблема в указателях

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Окончательный рабочий код

001#include<stdlib.h> // included for floatToString function
002 
003String floatToString(float x, byte precision = 2) {
004  char tmp[50];
005  dtostrf(x, 0, precision, tmp);
006  return String(tmp);
007}
008 
009//Назначаем пины кнопок управления
010#define BUTTON_RIGHT 10
011#define BUTTON_UP 9
012#define BUTTON_DOWN 8
013 
014//состояние кнопок по умолчанию
015boolean State_Up = LOW;
016boolean State_Down = LOW;
017boolean State_Left = LOW;
018boolean State_Right = LOW;
019boolean State_Ok = LOW;
020 
021//переменные которые надо менять
022float kp1 = 0;      float kp2 = 0;
023float ki1 =  0;      float ki2 = 0;
024float kd1 =  0;      float kd2 = 0;
025 
026//переменные для кнопок
027long ms_button = 0;
028boolean  button_state = false;
029boolean  button_long_state = false;
030 
031byte curMenu = 0; //текущий пункт меню,
032bool b_ShowmMenu = 0; // флаг отображения меню
033const byte CountMenu = 3; //количество пунктов меню
034  
035//массив элементов меню
036struct MENU_1{ //
037char name1[13];
038  float *k1;
039};
040 
041struct MENU_2{ //
042char name2[13];
043  float *k2;
044};
045 
046//инициализация меню
047//строковое имя, адрес переменной которую надо менять
048MENU_1 mMenu[CountMenu] = {
049"   kP1 =", &kp1,
050"   kI1 =", &ki1,
051"   kD1 =", &kd1
052};
053 
054MENU_2 nMenu[CountMenu] = {
055"   kP2 =", &kp2,
056"   kI2 =", &ki2,
057"   kD2 =", &kd2
058};
059  
060//функция выполнения меню
061void menu_setup(){
062   
063  if (State_Right == LOW && millis() - ms_button > 200) {   //следующий пункт меню по кругу
064      ms_button =  millis();
065      if (curMenu == CountMenu - 1)
066      curMenu = 0;
067      else  curMenu++;
068  }
069    
070  if (State_Up == LOW && millis() - ms_button > 200) {
071        ms_button =  millis();
072        (*mMenu[curMenu].k1)+=0.5;
073        (*nMenu[curMenu].k2)+=0.5;
074  }
075 
076  if (State_Down == LOW && millis() - ms_button > 200) {
077      ms_button =  millis();
078      (*mMenu[curMenu].k1)-=0.5;
079      (*nMenu[curMenu].k2)-=0.5;
080  }
081   
082  String str1 = floatToString(*mMenu[curMenu].k1); // conversion call
083  String str2 = floatToString(*nMenu[curMenu].k2); // conversion call
084   
085  if ( millis() - ms_button > 1000) {
086  ms_button =  millis();
087     Serial.print(mMenu[curMenu].name1);
088     Serial.println(str1);
089     Serial.print(nMenu[curMenu].name2);
090     Serial.println(str2);
091     Serial.flush();
092  }
093   
094}
095 
096 
097void setup() {
098  Serial.begin(9600);
099  pinMode (BUTTON_UP, INPUT);     digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор
100  pinMode (BUTTON_DOWN, INPUT);   digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор
101  pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор 
102}
103 
104 
105void loop() {
106   
107  //Считываем состояние кнопок управления
108  State_Up = digitalRead(BUTTON_UP);
109  State_Down = digitalRead(BUTTON_DOWN);
110  State_Right = digitalRead(BUTTON_RIGHT);
111  //-------------------------------------/
112   
113    menu_setup(); //вывод меню 
114}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А, ну понятно, в строках 79-80 замените 16 на 15 - у Вас терминальный ноль не помещается.

А лучше заменять не на 15, а на sizeof(charbuf1)-1 и sizeof(charbuf2)-1

Я бы на Вашем месте остался бы на dtostrf. Возьмите код из поста #36 и поправьте, что я сказал, там делов-то.

moozon
Offline
Зарегистрирован: 04.04.2018

ЕвгенийП пишет:

Отжирает лишние 900 байтов памяти программы. Пруф. ниже

/*

Дак Вы вызывали printf, а не свою SerialPrintf функцию. При вызове SerialPrintf, скетч использует аж 3 700 байт;)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Сколько кто отжирает приведено в том посте, так что ля-ля не надо. А "свою", "не свою" - это Вы мимо. Они все мои. И использование scanf - тоже (я и его пользую).

Данный топик не для вбросов, а для решения задачи. Есть хорошее предложение? Выкладываейте, будет одним больше. Нет - не вбрасывайте, срача всё равно не будет. С моим участием, по крайней мере, точно не будет.

moozon
Offline
Зарегистрирован: 04.04.2018

Какой вброс? Вы о чем? Я лишь указал на ошибку в ваших тестах. Нужно уметь признавать свои ошибки.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Да, действительно, только сейчас заметил. Извините.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Здравствуйте, ТС и форумчане. Имею скетч, пока занимает 774 (37%) оперативы и 16 794 байт (52%) флэш. Но скетч постоянно дополняется и улучшается и предстоит еще немало доработать.  Поэтому если не составит труда подскажите как элементы структуры ниже с помощью PROGMЕM перенести во флэш чтобы потом не возникало желания перейти на другую плату помощнее, uno вполне устраивает

01//массив элементов меню
02struct PODMENU_1{ //
03char name1[6];
04  double *k1;
05};
06 
07struct PODMENU_2{ //
08char name2[6];
09  double *k2;
10};
11 
12//инициализация меню
13//строковое имя, адрес переменной которую надо менять
14PODMENU_1 mMenu[CountMenu] = {
15"KP1 =", &kp1,
16"KI1 =", &ki1,
17"KD1 =", &kd1,
18"PW1 =", &pwr_BOTTOM
19};
20 
21//инициализация меню
22//строковое имя, адрес переменной которую надо менять
23PODMENU_2 nMenu[CountMenu] = {
24"KP2 =", &kp2,
25"KI2 =", &ki2,
26"KD2 =", &kd2,
27"PW2 =", &pwr_TOP
28};
29 
30void setup() {}
31void loop() {}
32 
33//функция выполнения меню в loop
34void selectParam(void){
35 
36  // добавить сохранение текущих параметров в eeprom  void SaveOption()
37   
38  //вывод использую буфер
39  static char charbuf1[8];
40  static char charbuf2[8];
41   
42  dtostrf(*mMenu[curMenu].k1, sizeof(charbuf1)-1, 2, charbuf1);
43  dtostrf(*nMenu[curMenu].k2, sizeof(charbuf2)-1, 2, charbuf2);
44   
45  lcd.setCursor(5,0);  lcd.print(mMenu[curMenu].name1); lcd.print(charbuf1);   
46  lcd.setCursor(5,2);  lcd.print(nMenu[curMenu].name2); lcd.print(charbuf2);
47}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
1const char  str[] PROGMEM = "STOP";
2const char str1[] PROGMEM = "PEAK";
3const char str_[] PROGMEM = "    ";
4 
5if (number) lcd.print(i ? (const __FlashStringHelper*)str : (const __FlashStringHelper*)str_);
6     else lcd.print(i ? (const __FlashStringHelper*)str1 : (const __FlashStringHelper*)str_)

Делал со строками в коде таким образом, но здесь такое не прокатит

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BuonanotteMash,

если Вы не заметили, это тема о форматированном выводе в поток, и Ваш вопрос здесь - оффтопик и флуд. Потрудитесь, пожалуйста, найти подходящую тему или создайте свою.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

ЕвгенийП, извините, сразу не подумал. Последую вашему совету