Форматированный вывод в Serial
- Войдите на сайт для отправки комментариев
Пнд, 31/08/2015 - 19:05
Начинающий коллега увидел у меня в коде форматированный по типу printf отладочный вывод в Serial (когда вместо:
Serial.print("U=");
Serial.print(u);
Serial.print("V f=");
Serial.print(f);
Serial.println("KHz");
пишется
SerialPrintf("U=%dV f=%dKHz\n, v, f);
попросил дать ему код и посоветовал опубликовать здесь, т.к., по его мнению, многие с удовольствием воспользуются.
В последнем сомневаюсь, но, на всякий случай, публикую, может и правда кому сгодится. Вывод реализован одной функцией. Параметры у неё как у printf. Текст функции и пример использования:
/*
* SerialPrintf
* Реализует функциональность printf в Serial.print
* Применяется для отладочной печати
* Параметры как у printf
* Возвращает
* 0 - ошибка формата
* отрицательное чило - нехватка памяти, модуль числа равен запрашиваемой памяти
* положительное число - количество символов, выведенное в Serial
*/
const size_t SerialPrintf (const char *szFormat, ...) {
va_list argptr;
va_start(argptr, szFormat);
char *szBuffer = 0;
const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1;
if (nBufferLength == 1) return 0;
szBuffer = (char *) malloc(nBufferLength);
if (! szBuffer) return - nBufferLength;
vsnprintf(szBuffer, nBufferLength, szFormat, argptr);
Serial.print(szBuffer);
free(szBuffer);
return nBufferLength - 1;
}
/*
* Пример использования SerialPrintf
*/
void setup() {
Serial.begin(19200);
while(!Serial) yield();
SerialPrintf("Fun begins!\n");
SerialPrintf("%d%s = 0x%04X%s\n", 321, "(decimal)", 321, "(hexadecimal)");
}
void loop() {
}
Спасибо.
Для меня это полезная и удобная вещь.
Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?
Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?
Ты бы хоть копирайт свой оставил, чтобы страна помнила своих героев.
1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.
2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.
спасибо, полезная штучка.
1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.
2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.
А Вы добавьте из предложенной Вами функции вывод в Serial значения, возвращаемого vsnprintf, и посмотрите, что она напечатает.
Хотя, в принципе, если существующая реализация сделана не постандарту, то, согласен, лучше перестраховаться и добавить - на случай, когда поправят реализацию.
нужно применять осторожно.
Ваше "осторожно" напомнило книгу "Физики продолжают шутить". Там был фразеологический словарь для перевода научных статей на нормальный язык. В частности были такие определения:
"Во время эксперимента прибор был слегка повреждён" - перевод: "Уронили на пол"
"С прибором обращались с исключительной осторожностью" - перевод: "Не роняли на пол" :)
Случайно натолкнулся на этот очередной "велосипед".
Всё время это делалось стандартным образом:
#include <stdio.h> #include <avr/pgmspace.h> int uart0_putchar(char ch, FILE *stream) { Serial.print(ch); return 0; } FILE *uart_stream; void setup(void) { Serial.begin(9600); uart_stream = fdevopen(uart0_putchar, /*uart0_getchar*/ NULL ); stderr = stdout = stdin = uart_stream; printf("Init stdio - OK.\n"); printf_P(PSTR("\nString in FLASH.\n")); }Преимущества:
- Расход ОЗУ меньше, вывод идёт сразу в UART.
- Можно использовать строковые константы из флеш.
- меньше лишнего кода
- По аналогии, можно связать printf с буферизированным UART(ами) и LCD.
PS: Впрочем, за рабочий "велосипед" - зачет. Ардуино для этого и предназначена.
Преимущества:
- Расход ОЗУ меньше, вывод идёт сразу в UART.
- Можно использовать строковые константы из флеш.
- меньше лишнего кода
- По аналогии, можно связать printf с буферизированным UART(ами) и LCD.
Недостатки
Отжирает лишние 900 байтов памяти программы. Пруф. ниже
/* * SerialPrintf * Реализует функциональность printf в Serial.print * Применяется для отладочной печати * Параметры как у printf * Возвращает * 0 - ошибка формата * отрицательное чило - нехватка памяти, модуль числа равен запрашиваемой памяти * положительное число - количество символов, выведенное в Serial */ const size_t SerialPrintf (const char *szFormat, ...) { va_list argptr; va_start(argptr, szFormat); char *szBuffer = 0; const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1; if (nBufferLength == 1) return 0; szBuffer = (char *) malloc(nBufferLength); if (! szBuffer) return - nBufferLength; vsnprintf(szBuffer, nBufferLength, szFormat, argptr); Serial.print(szBuffer); free(szBuffer); return nBufferLength - 1; } /* * Пример использования SerialPrintf */ void setup() { Serial.begin(9600); printf("Hello, world!\n"); } void loop() {} ///////////////////////////////////////////////////////// // Результат компиляции в IDE 1.6.12 // Скетч использует 1 508 байт (4%) памяти устройства. /////////////////////////////////////////////////////////#include <stdio.h> #include <avr/pgmspace.h> int uart0_putchar(char ch, FILE *stream) { Serial.print(ch); return 0; } FILE *uart_stream; void setup(void) { Serial.begin(9600); uart_stream = fdevopen(uart0_putchar, /*uart0_getchar*/ NULL ); stderr = stdout = stdin = uart_stream; printf("Hello, world!\n"); } void loop() {} ///////////////////////////////////////////////////////// // Результат компиляции в IDE 1.6.12 // Скетч использует 2 408 байт (7%) памяти устройства. /////////////////////////////////////////////////////////Но за хороший учебный материал, безсусловно, зачёт. Дополнение очень в струю. Жаль нельзя добавить в первоначальный топик.
Могу показать свой логгер, который использую везде в коде: TelnetServer
Там файлы: Logger.cpp и Logger.h
Используется так:
Если кто захочет использовать, придётся доработать под себя. У меня для каждой строчки выводится дата и время, т.к. есть поддержка времени в коде программы.
Могу показать свой логгер, который использую везде в коде: TelnetServer
Спасибо!
Тема изначально учебная, поэтому здорово, что появляются посты типа Вашего и _kp. Больше примеров, хороших и разных!
Сам я для отладочной печати использую потоковую нотацию, очень компактно, но, разумеется без printf'овского форматирования. Но там есть засада. Параметр передаётс по значению, поэтому, например, если печатать String, то создаётся новый экземпляр со всеми вытекающими.
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, размер минималистичных программ заметно распухает, и не менее заметно сокращается разница в размера кода для обоих вариантов, приближаясь к рассчитанной выше..
О, Господи, не лезьте в драку, это учебный материал. Я показал как использовать sprintf, Вы показали как перенаправить поток - прекрасно, что показано и то, и другое.
тем более, что
На AVR обычно не пользуются стандартным printf/sprintf, а используют printf_P/sprintf_P
Господь с Вами, в сколько-нибудь серьёзных программах никто не использует ни того, ни другого.
И снова привет.
На станице http://playground.arduino.cc/Main/Printf говорится, о том что можно добавить метод printf в базовый класс Print.
Фича в том, что метод xxx.printf появится не только у Serial, но и у всех классов, его использующих, то есть LCD и TFT дисплеи.
Там же есть и пример, показывающий что именно надо подправить в фирменной библиотеке.
Но, как обычно, там вариант с временным текстовым буфером. Что для AVR крайне вредно, и буфера всё равно мало.
Вот сделал почти тоже самое, но с существенно меньшим расходом ОЗУ, и работающее быстрее.
То есть, это вариант добавления printf в базовый класс Print.
После чего можно писать примерно так:
Serial.printf("var1=%4d var2=0x%06X\n",var1,var2); TFT1.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
// Добавляем в описание класса это:
FILE oStream; //жрёт 12байт ОЗУ. Конечно, можно вынести в print, но быстродействие упадёт. size_t printf (const char *szFormat, ...); size_t printf (const __FlashStringHelper *szFormat, ...);файл: arduino-1.6.11\hardware\arduino\avr\cores\arduino\Print.cpp
// Добавляем в файл это:
#include <stdarg.h> #include <stdio.h> //non class member function for callback from stdio.FILE steam. //in field FILE.udata passed class->this static int dummy_putchar(char c, FILE *stream ) { Print* pPrint = (Print*)(stream->udata); pPrint->print(c); return 1; } size_t Print::printf(const char *fmt, ...) { oStream.put = dummy_putchar; oStream.flags |= __SWR; oStream.udata = (void*) this; //---- oStream.flags &= ~__SPGM; // va_list ap; va_start( (ap), (fmt) ); size_t i = vfprintf(&oStream, fmt, ap); va_end(ap); return i; } size_t Print::printf(const __FlashStringHelper *fmt_P, ...) { PGM_P fmt = reinterpret_cast<PGM_P>(fmt_P); oStream.put = dummy_putchar; oStream.flags |= __SWR; oStream.udata = (void*)this; //---- oStream.flags |= __SPGM; // va_list ap; va_start( (ap), (fmt) ); //Ignore warning, code worked: "second parameter of 'va_start' not last named argument" size_t i = vfprintf(&oStream, fmt, ap); va_end(ap); return i; }Результаты тестов.
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: я так понял, что файл то тут приложить ни как?
ps: я так понял, что файл то тут приложить ни как?
файл - нет.
код - да.
Ты бы хоть копирайт свой оставил, чтобы страна помнила своих героев.
Коллега - копираст?
Добрый день.
Взял чей то вполне рабочий скетч, немного переделал под себя, пока все устравало. Но ТЗ расширилось и вместо вывода в порт чисел типа byte, понадобилось выволдить числа double.
Привожу первый рабочий вариант
#include <Wire.h> // библиотека для управления устройствами по I2C //Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять byte kp1 = 0; byte kp2 = 0; byte ki1 = 0; byte ki2 = 0; byte kd1 = 0; byte kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; byte *k1; }; struct MENU_2{ // char name2[13]; byte *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)++; (*nMenu[curMenu].k2)++; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)--;; (*nMenu[curMenu].k2)--; } //вывод использую буфер char ch1[16]; char ch2[16]; snprintf(ch1, 16, "%d ", *mMenu[curMenu].k1); snprintf(ch2, 16, "%d ", *nMenu[curMenu].k2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.println(mMenu[curMenu].name1); Serial.println(ch1); Serial.println(nMenu[curMenu].name2); Serial.println(ch2); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }Значения перед выводом хранятся в строковом буферe посредством snprintf . Вот ее описание:
Она идентична функции sprintf() за исключением того, что в массиве, адресуемом указателем buf, будет сохранено максимум num-1символов. По окончании работы функции этот массив будет завершаться символом конца строки (нуль-символом). Таким образом, функция snprintf() позволяет предотвратить переполнение буфера buf.
Нерабочий
#include <Wire.h> // библиотека для управления устройствами по I2C //Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять double kp1 = 0; double kp2 = 0; double ki1 = 0; double ki2 = 0; double kd1 = 0; double kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; double *k1; }; struct MENU_2{ // char name2[13]; double *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } //вывод использую буфер char ch1[16]; char ch2[16]; snprintf(ch1, 16, "%f ", *mMenu[curMenu].k1); snprintf(ch2, 16, "%f ", *nMenu[curMenu].k2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.println(mMenu[curMenu].name1); Serial.println(ch1); Serial.println(nMenu[curMenu].name2); Serial.println(ch2); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }В первом варианте спецификатор d - для целого знакового десятичного числа, во втором f - для число с плавающей точкой. Подскажите в чем не прав
Чтобы легче понять, вот их основные различия. Во втором коде в порт выводятся не числа а знаки ?.
В том, что sprintf с плавающей точкой ниработаить
А чем это заменить чтобы заработало?
dtostrf()
strtodпреобразовывает строкуstringвdouble. Да уже понялStrtod это перевести строку в double
Да нашел, спасибо
http://geekmatic.in.ua/arduino_otobrazhenie_dannyih_float
А взат, из числа в строку - dstrtof()
BuonanotteMasha,
1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.
2.
Если есть лишние полкило памяти программ, то можно обойтись без печати в буфер, а связать штатный printf с потоком и выводить в любой поток просто штатным printf. ПРимеры я тут в разных темах много раз выкладывал.
Да спасибо, уже работает
//Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять float kp1 = 0; float kp2 = 0; float ki1 = 0; float ki2 = 0; float kd1 = 0; float kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; float *k1; }; struct MENU_2{ // char name2[13]; float *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } //вывод использую буфер char charbuf1[16]; char charbuf2[16]; dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1); dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2); //snprintf(charbuf1, 16, "%f ", *mMenu[curMenu].k1); //snprintf(charbuf2, 16, "%f ", *nMenu[curMenu].k2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.println(mMenu[curMenu].name1); Serial.println(charbuf1); Serial.println(nMenu[curMenu].name2); Serial.println(charbuf2); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }Только не пойму, почему в порт только одно число выводится
Тут ошибок не наблюдаю
//вывод использую буфер char charbuf1[16]; char charbuf2[16]; dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1); dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.println(mMenu[curMenu].name1); Serial.println(charbuf1); Serial.println(nMenu[curMenu].name2); Serial.println(charbuf2); }BuonanotteMasha,
1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.
я пыталса анажды найти, как в спринтф включать плавающую точку, прям из атмеловской документации пытался повторить, неделю нерничал, пил по чёрному, но так и не смог.
Танцы с бубном не помогли
#include <Wire.h> // библиотека для управления устройствами по I2C //Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять float kp1 = 0; float kp2 = 0; float ki1 = 0; float ki2 = 0; float kd1 = 0; float kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; float *k1; }; struct MENU_2{ // char name2[13]; float *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } //вывод использую буфер static char charbuf1[16]; static char charbuf2[16]; dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1); dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2); //snprintf(charbuf1, 16, "%f ", *mMenu[curMenu].k1); //snprintf(charbuf2, 16, "%f ", *nMenu[curMenu].k2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.print(mMenu[curMenu].name1); Serial.println(charbuf1); Serial.print(nMenu[curMenu].name2); Serial.println(charbuf2); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }Внимательно пока не смотрел, но попробуйте поставить после строки 92 Serial.flush();
Виноват, поправил на 92 - в общем, после печати сразу. А то в старом скетче это было 90.
Нет не помогло, пробую с floatToString. Поменял как вы сказали на 92 тоже нет
Покажите текущий код (в котором не помогло)
//Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять float kp1 = 0; float kp2 = 0; float ki1 = 0; float ki2 = 0; float kd1 = 0; float kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; float *k1; }; struct MENU_2{ // char name2[13]; float *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } //вывод использую буфер static char charbuf1[16]; static char charbuf2[16]; dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1); dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.print(mMenu[curMenu].name1); Serial.println(charbuf1); Serial.print(nMenu[curMenu].name2); Serial.println(charbuf2); Serial.flush(); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }Евгений спасибо вам за помощь, но у меня самого получилось
#include<stdlib.h> // included for floatToString function String floatToString(float x, byte precision = 2) { char tmp[50]; dtostrf(x, 0, precision, tmp); return String(tmp); } //Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять float kp1 = 0; float kp2 = 0; float ki1 = 0; float ki2 = 0; float kd1 = 0; float kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; float *k1; }; struct MENU_2{ // char name2[13]; float *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } //вывод использую буфер static char charbuf1[16]; static char charbuf2[16]; //dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1); //dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2); String str1 = floatToString(*mMenu[curMenu].k1); // conversion call String str2 = floatToString(*nMenu[curMenu].k2); // conversion call //floatToString(charbuf1, *mMenu[curMenu].k1, 2); //floatToString(charbuf2, *nMenu[curMenu].k2, 2); //snprintf(charbuf1, 16, "%f ", *mMenu[curMenu].k1); //snprintf(charbuf2, 16, "%f ", *nMenu[curMenu].k2); if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.print(mMenu[curMenu].name1); Serial.println(str1); Serial.print(nMenu[curMenu].name2); Serial.println(str2); Serial.flush(); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }проблема в указателях
Окончательный рабочий код
#include<stdlib.h> // included for floatToString function String floatToString(float x, byte precision = 2) { char tmp[50]; dtostrf(x, 0, precision, tmp); return String(tmp); } //Назначаем пины кнопок управления #define BUTTON_RIGHT 10 #define BUTTON_UP 9 #define BUTTON_DOWN 8 //состояние кнопок по умолчанию boolean State_Up = LOW; boolean State_Down = LOW; boolean State_Left = LOW; boolean State_Right = LOW; boolean State_Ok = LOW; //переменные которые надо менять float kp1 = 0; float kp2 = 0; float ki1 = 0; float ki2 = 0; float kd1 = 0; float kd2 = 0; //переменные для кнопок long ms_button = 0; boolean button_state = false; boolean button_long_state = false; byte curMenu = 0; //текущий пункт меню, bool b_ShowmMenu = 0; // флаг отображения меню const byte CountMenu = 3; //количество пунктов меню //массив элементов меню struct MENU_1{ // char name1[13]; float *k1; }; struct MENU_2{ // char name2[13]; float *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять MENU_1 mMenu[CountMenu] = { " kP1 =", &kp1, " kI1 =", &ki1, " kD1 =", &kd1 }; MENU_2 nMenu[CountMenu] = { " kP2 =", &kp2, " kI2 =", &ki2, " kD2 =", &kd2 }; //функция выполнения меню void menu_setup(){ if (State_Right == LOW && millis() - ms_button > 200) { //следующий пункт меню по кругу ms_button = millis(); if (curMenu == CountMenu - 1) curMenu = 0; else curMenu++; } if (State_Up == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)+=0.5; (*nMenu[curMenu].k2)+=0.5; } if (State_Down == LOW && millis() - ms_button > 200) { ms_button = millis(); (*mMenu[curMenu].k1)-=0.5; (*nMenu[curMenu].k2)-=0.5; } String str1 = floatToString(*mMenu[curMenu].k1); // conversion call String str2 = floatToString(*nMenu[curMenu].k2); // conversion call if ( millis() - ms_button > 1000) { ms_button = millis(); Serial.print(mMenu[curMenu].name1); Serial.println(str1); Serial.print(nMenu[curMenu].name2); Serial.println(str2); Serial.flush(); } } void setup() { Serial.begin(9600); pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); //подключаем подтягивающий резистор pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); //подключаем подтягивающий резистор } void loop() { //Считываем состояние кнопок управления State_Up = digitalRead(BUTTON_UP); State_Down = digitalRead(BUTTON_DOWN); State_Right = digitalRead(BUTTON_RIGHT); //-------------------------------------/ menu_setup(); //вывод меню }А, ну понятно, в строках 79-80 замените 16 на 15 - у Вас терминальный ноль не помещается.
А лучше заменять не на 15, а на sizeof(charbuf1)-1 и sizeof(charbuf2)-1
Я бы на Вашем месте остался бы на dtostrf. Возьмите код из поста #36 и поправьте, что я сказал, там делов-то.
Отжирает лишние 900 байтов памяти программы. Пруф. ниже
/*
* Пример использования SerialPrintf */ void setup() { Serial.begin(9600); printf("Hello, world!\n"); } void loop() {} ///////////////////////////////////////////////////////// // Результат компиляции в IDE 1.6.12 // Скетч использует 1 508 байт (4%) памяти устройства. /////////////////////////////////////////////////////////Дак Вы вызывали printf, а не свою SerialPrintf функцию. При вызове SerialPrintf, скетч использует аж 3 700 байт;)
Сколько кто отжирает приведено в том посте, так что ля-ля не надо. А "свою", "не свою" - это Вы мимо. Они все мои. И использование scanf - тоже (я и его пользую).
Данный топик не для вбросов, а для решения задачи. Есть хорошее предложение? Выкладываейте, будет одним больше. Нет - не вбрасывайте, срача всё равно не будет. С моим участием, по крайней мере, точно не будет.
Какой вброс? Вы о чем? Я лишь указал на ошибку в ваших тестах. Нужно уметь признавать свои ошибки.
Да, действительно, только сейчас заметил. Извините.
Здравствуйте, ТС и форумчане. Имею скетч, пока занимает 774 (37%) оперативы и 16 794 байт (52%) флэш. Но скетч постоянно дополняется и улучшается и предстоит еще немало доработать. Поэтому если не составит труда подскажите как элементы структуры ниже с помощью PROGMЕM перенести во флэш чтобы потом не возникало желания перейти на другую плату помощнее, uno вполне устраивает
//массив элементов меню struct PODMENU_1{ // char name1[6]; double *k1; }; struct PODMENU_2{ // char name2[6]; double *k2; }; //инициализация меню //строковое имя, адрес переменной которую надо менять PODMENU_1 mMenu[CountMenu] = { "KP1 =", &kp1, "KI1 =", &ki1, "KD1 =", &kd1, "PW1 =", &pwr_BOTTOM }; //инициализация меню //строковое имя, адрес переменной которую надо менять PODMENU_2 nMenu[CountMenu] = { "KP2 =", &kp2, "KI2 =", &ki2, "KD2 =", &kd2, "PW2 =", &pwr_TOP }; void setup() {} void loop() {} //функция выполнения меню в loop void selectParam(void){ // добавить сохранение текущих параметров в eeprom void SaveOption() //вывод использую буфер static char charbuf1[8]; static char charbuf2[8]; dtostrf(*mMenu[curMenu].k1, sizeof(charbuf1)-1, 2, charbuf1); dtostrf(*nMenu[curMenu].k2, sizeof(charbuf2)-1, 2, charbuf2); lcd.setCursor(5,0); lcd.print(mMenu[curMenu].name1); lcd.print(charbuf1); lcd.setCursor(5,2); lcd.print(nMenu[curMenu].name2); lcd.print(charbuf2); }const char str[] PROGMEM = "STOP"; const char str1[] PROGMEM = "PEAK"; const char str_[] PROGMEM = " "; if (number) lcd.print(i ? (const __FlashStringHelper*)str : (const __FlashStringHelper*)str_); else lcd.print(i ? (const __FlashStringHelper*)str1 : (const __FlashStringHelper*)str_)Делал со строками в коде таким образом, но здесь такое не прокатит
BuonanotteMash,
если Вы не заметили, это тема о форматированном выводе в поток, и Ваш вопрос здесь - оффтопик и флуд. Потрудитесь, пожалуйста, найти подходящую тему или создайте свою.
ЕвгенийП, извините, сразу не подумал. Последую вашему совету