Начинающий коллега увидел у меня в коде форматированный по типу printf отладочный вывод в Serial (когда вместо:
пишется
1 | SerialPrintf("U=%dV f=%dKHz\n, v, f); |
попросил дать ему код и посоветовал опубликовать здесь, т.к., по его мнению, многие с удовольствием воспользуются.
В последнем сомневаюсь, но, на всякий случай, публикую, может и правда кому сгодится. Вывод реализован одной функцией. Параметры у неё как у printf. Текст функции и пример использования:
11 | const size_t SerialPrintf ( const char *szFormat, ...) { |
13 | va_start(argptr, szFormat); |
15 | const size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1; |
16 | if (nBufferLength == 1) return 0; |
17 | szBuffer = ( char *) malloc(nBufferLength); |
18 | if (! szBuffer) return - nBufferLength; |
19 | vsnprintf(szBuffer, nBufferLength, szFormat, argptr); |
20 | Serial .print(szBuffer); |
22 | return nBufferLength - 1; |
31 | while (! Serial ) yield(); |
32 | SerialPrintf( "Fun begins!\n" ); |
33 | SerialPrintf( "%d%s = 0x%04X%s\n" , 321, "(decimal)" , 321, "(hexadecimal)" ); |
Спасибо.
Для меня это полезная и удобная вещь.
Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?
Не могли бы Вы в дополнение пояснить, как (где) узнать про другие функции, которые не описаны в Arduino Language Reference, но реально работают?
Ты бы хоть копирайт свой оставил, чтобы страна помнила своих героев.
1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.
2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.
спасибо, полезная штучка.
1. Хотельсь бы знать, зачем в строке 15 дополнительно прибавляется 1. vsprintf сам по себе возвращает длину с учетом терминирующего символа.
2. При использовании данной процедуры необходимо помнить, что она использует кучу, уменьшая при этом размер стека, причем на заранее неизвестную глубину. Что в условиях существенно ораниченного объема памяти Arduino может оказаться весьма существенным.
А Вы добавьте из предложенной Вами функции вывод в Serial значения, возвращаемого vsnprintf, и посмотрите, что она напечатает.
Хотя, в принципе, если существующая реализация сделана не постандарту, то, согласен, лучше перестраховаться и добавить - на случай, когда поправят реализацию.
нужно применять осторожно.
Ваше "осторожно" напомнило книгу "Физики продолжают шутить". Там был фразеологический словарь для перевода научных статей на нормальный язык. В частности были такие определения:
"Во время эксперимента прибор был слегка повреждён" - перевод: "Уронили на пол"
"С прибором обращались с исключительной осторожностью" - перевод: "Не роняли на пол" :)
Случайно натолкнулся на этот очередной "велосипед".
Всё время это делалось стандартным образом:
01
#include <stdio.h>
02
#include <avr/pgmspace.h>
03
04
int
uart0_putchar(
char
ch, FILE *stream)
05
{
06
Serial
.print(ch);
07
return
0;
08
}
09
10
FILE *uart_stream;
11
12
void
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: Впрочем, за рабочий "велосипед" - зачет. Ардуино для этого и предназначена.
Преимущества:
- Расход ОЗУ меньше, вывод идёт сразу в UART.
- Можно использовать строковые константы из флеш.
- меньше лишнего кода
- По аналогии, можно связать printf с буферизированным UART(ами) и LCD.
Недостатки
Отжирает лишние 900 байтов памяти программы. Пруф. ниже
01
/*
02
* SerialPrintf
03
* Реализует функциональность printf в Serial.print
04
* Применяется для отладочной печати
05
* Параметры как у printf
06
* Возвращает
07
* 0 - ошибка формата
08
* отрицательное чило - нехватка памяти, модуль числа равен запрашиваемой памяти
09
* положительное число - количество символов, выведенное в Serial
10
*/
11
const
size_t SerialPrintf (
const
char
*szFormat, ...) {
12
va_list argptr;
13
va_start(argptr, szFormat);
14
char
*szBuffer = 0;
15
const
size_t nBufferLength = vsnprintf(szBuffer, 0, szFormat, argptr) + 1;
16
if
(nBufferLength == 1)
return
0;
17
szBuffer = (
char
*) malloc(nBufferLength);
18
if
(! szBuffer)
return
- nBufferLength;
19
vsnprintf(szBuffer, nBufferLength, szFormat, argptr);
20
Serial
.print(szBuffer);
21
free(szBuffer);
22
return
nBufferLength - 1;
23
}
24
25
26
/*
27
* Пример использования SerialPrintf
28
*/
29
void
setup
() {
30
Serial
.begin(9600);
31
printf(
"Hello, world!\n"
);
32
}
33
34
void
loop
() {}
35
36
/////////////////////////////////////////////////////////
37
// Результат компиляции в IDE 1.6.12
38
// Скетч использует 1 508 байт (4%) памяти устройства.
39
/////////////////////////////////////////////////////////
01
#include <stdio.h>
02
#include <avr/pgmspace.h>
03
04
int
uart0_putchar(
char
ch, FILE *stream)
05
{
06
Serial
.print(ch);
07
return
0;
08
}
09
10
FILE *uart_stream;
11
12
void
setup
(
void
)
13
{
14
Serial
.begin(9600);
15
uart_stream = fdevopen(uart0_putchar,
/*uart0_getchar*/
NULL );
16
stderr = stdout = stdin = uart_stream;
17
printf(
"Hello, world!\n"
);
18
}
19
20
void
loop
() {}
21
22
/////////////////////////////////////////////////////////
23
// Результат компиляции в IDE 1.6.12
24
// Скетч использует 2 408 байт (7%) памяти устройства.
25
/////////////////////////////////////////////////////////
Но за хороший учебный материал, безсусловно, зачёт. Дополнение очень в струю. Жаль нельзя добавить в первоначальный топик.
Могу показать свой логгер, который использую везде в коде: TelnetServer
Там файлы: Logger.cpp и Logger.h
Используется так:
1
extern
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__ )
1
char
s1[32], s2[32];
2
3
strncpy_P( s1, (
const
char
* ) CVersion::GetVersionString(),
sizeof
( s1 ) );
4
strncpy_P( s2, (
const
char
* ) CVersion::GetBuildDateString(),
sizeof
( s2 ) );
5
6
s1[
sizeof
( s1 ) - 1 ] =
'\0'
;
7
s1[
sizeof
( s2 ) - 1 ] =
'\0'
;
8
9
logdebug(
"Telnet Server, version %s, %s"
, s1, s2 );
Если кто захочет использовать, придётся доработать под себя. У меня для каждой строчки выводится дата и время, т.к. есть поддержка времени в коде программы.
Могу показать свой логгер, который использую везде в коде: 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.
После чего можно писать примерно так:
1
Serial
.printf(
"var1=%4d var2=0x%06X\n"
,var1,var2);
2
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
// Добавляем в описание класса это:
1
FILE oStream;
//жрёт 12байт ОЗУ. Конечно, можно вынести в print, но быстродействие упадёт.
2
size_t printf (
const
char
*szFormat, ...);
3
size_t printf (
const
__FlashStringHelper *szFormat, ...);
файл: arduino-1.6.11\hardware\arduino\avr\cores\arduino\Print.cpp
// Добавляем в файл это:
01
#include <stdarg.h>
02
#include <stdio.h>
03
04
//non class member function for callback from stdio.FILE steam.
05
//in field FILE.udata passed class->this
06
static
int
dummy_putchar(
char
c, FILE *stream )
07
{
08
Print* pPrint = (Print*)(stream->udata);
09
pPrint->print(c);
10
return
1;
11
}
12
13
size_t Print::printf(
const
char
*fmt, ...)
14
{
15
oStream.put = dummy_putchar;
16
oStream.flags |= __SWR;
17
oStream.udata = (
void
*)
this
;
18
//----
19
oStream.flags &= ~__SPGM;
20
//
21
va_list ap;
22
va_start( (ap), (fmt) );
23
size_t i = vfprintf(&oStream, fmt, ap);
24
va_end(ap);
25
return
i;
26
}
27
28
size_t Print::printf(
const
__FlashStringHelper *fmt_P, ...)
29
{
30
PGM_P fmt = reinterpret_cast<PGM_P>(fmt_P);
31
32
oStream.put = dummy_putchar;
33
oStream.flags |= __SWR;
34
oStream.udata = (
void
*)
this
;
35
//----
36
oStream.flags |= __SPGM;
37
//
38
va_list ap;
39
va_start( (ap), (fmt) );
//Ignore warning, code worked: "second parameter of 'va_start' not last named argument"
40
size_t i = vfprintf(&oStream, fmt, ap);
41
42
va_end(ap);
43
return
i;
44
}
Результаты тестов.
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.
Привожу первый рабочий вариант
001
#include <Wire.h> // библиотека для управления устройствами по I2C
002
003
//Назначаем пины кнопок управления
004
#define BUTTON_RIGHT 10
005
#define BUTTON_UP 9
006
#define BUTTON_DOWN 8
007
008
//состояние кнопок по умолчанию
009
boolean State_Up = LOW;
010
boolean State_Down = LOW;
011
boolean State_Left = LOW;
012
boolean State_Right = LOW;
013
boolean State_Ok = LOW;
014
015
//переменные которые надо менять
016
byte
kp1 = 0;
byte
kp2 = 0;
017
byte
ki1 = 0;
byte
ki2 = 0;
018
byte
kd1 = 0;
byte
kd2 = 0;
019
020
//переменные для кнопок
021
long
ms_button = 0;
022
boolean button_state =
false
;
023
boolean button_long_state =
false
;
024
025
byte
curMenu = 0;
//текущий пункт меню,
026
bool
b_ShowmMenu = 0;
// флаг отображения меню
027
const
byte
CountMenu = 3;
//количество пунктов меню
028
029
//массив элементов меню
030
struct
MENU_1{
//
031
char
name1[13];
032
byte
*k1;
033
};
034
035
struct
MENU_2{
//
036
char
name2[13];
037
byte
*k2;
038
};
039
040
//инициализация меню
041
//строковое имя, адрес переменной которую надо менять
042
MENU_1 mMenu[CountMenu] = {
043
" kP1 ="
, &kp1,
044
" kI1 ="
, &ki1,
045
" kD1 ="
, &kd1
046
};
047
048
MENU_2 nMenu[CountMenu] = {
049
" kP2 ="
, &kp2,
050
" kI2 ="
, &ki2,
051
" kD2 ="
, &kd2
052
};
053
054
//функция выполнения меню
055
void
menu_setup(){
056
057
if
(State_Right == LOW && millis() - ms_button > 200) {
//следующий пункт меню по кругу
058
ms_button = millis();
059
if
(curMenu == CountMenu - 1)
060
curMenu = 0;
061
else
curMenu++;
062
}
063
064
if
(State_Up == LOW && millis() - ms_button > 200) {
065
ms_button = millis();
066
(*mMenu[curMenu].k1)++;
067
(*nMenu[curMenu].k2)++;
068
}
069
070
if
(State_Down == LOW && millis() - ms_button > 200) {
071
ms_button = millis();
072
(*mMenu[curMenu].k1)--;;
073
(*nMenu[curMenu].k2)--;
074
}
075
076
077
//вывод использую буфер
078
char
ch1[16];
079
char
ch2[16];
080
081
snprintf(ch1, 16,
"%d "
, *mMenu[curMenu].k1);
082
snprintf(ch2, 16,
"%d "
, *nMenu[curMenu].k2);
083
084
if
( millis() - ms_button > 1000) {
085
ms_button = millis();
086
Serial
.println(mMenu[curMenu].name1);
087
Serial
.println(ch1);
088
Serial
.println(nMenu[curMenu].name2);
089
Serial
.println(ch2);
090
}
091
092
}
093
094
095
void
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
103
void
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
}
Значения перед выводом хранятся в строковом буферe посредством snprintf . Вот ее описание:
Она идентична функции sprintf() за исключением того, что в массиве, адресуемом указателем buf, будет сохранено максимум num-1символов. По окончании работы функции этот массив будет завершаться символом конца строки (нуль-символом). Таким образом, функция snprintf() позволяет предотвратить переполнение буфера buf.
Нерабочий
001
#include <Wire.h> // библиотека для управления устройствами по I2C
002
003
//Назначаем пины кнопок управления
004
#define BUTTON_RIGHT 10
005
#define BUTTON_UP 9
006
#define BUTTON_DOWN 8
007
008
//состояние кнопок по умолчанию
009
boolean State_Up = LOW;
010
boolean State_Down = LOW;
011
boolean State_Left = LOW;
012
boolean State_Right = LOW;
013
boolean State_Ok = LOW;
014
015
//переменные которые надо менять
016
double
kp1 = 0;
double
kp2 = 0;
017
double
ki1 = 0;
double
ki2 = 0;
018
double
kd1 = 0;
double
kd2 = 0;
019
020
//переменные для кнопок
021
long
ms_button = 0;
022
boolean button_state =
false
;
023
boolean button_long_state =
false
;
024
025
byte
curMenu = 0;
//текущий пункт меню,
026
bool
b_ShowmMenu = 0;
// флаг отображения меню
027
const
byte
CountMenu = 3;
//количество пунктов меню
028
029
//массив элементов меню
030
struct
MENU_1{
//
031
char
name1[13];
032
double
*k1;
033
};
034
035
struct
MENU_2{
//
036
char
name2[13];
037
double
*k2;
038
};
039
040
//инициализация меню
041
//строковое имя, адрес переменной которую надо менять
042
MENU_1 mMenu[CountMenu] = {
043
" kP1 ="
, &kp1,
044
" kI1 ="
, &ki1,
045
" kD1 ="
, &kd1
046
};
047
048
MENU_2 nMenu[CountMenu] = {
049
" kP2 ="
, &kp2,
050
" kI2 ="
, &ki2,
051
" kD2 ="
, &kd2
052
};
053
054
//функция выполнения меню
055
void
menu_setup(){
056
057
if
(State_Right == LOW && millis() - ms_button > 200) {
//следующий пункт меню по кругу
058
ms_button = millis();
059
if
(curMenu == CountMenu - 1)
060
curMenu = 0;
061
else
curMenu++;
062
}
063
064
if
(State_Up == LOW && millis() - ms_button > 200) {
065
ms_button = millis();
066
(*mMenu[curMenu].k1)+=0.5;
067
(*nMenu[curMenu].k2)+=0.5;
068
}
069
070
if
(State_Down == LOW && millis() - ms_button > 200) {
071
ms_button = millis();
072
(*mMenu[curMenu].k1)-=0.5;
073
(*nMenu[curMenu].k2)-=0.5;
074
}
075
076
077
//вывод использую буфер
078
char
ch1[16];
079
char
ch2[16];
080
081
snprintf(ch1, 16,
"%f "
, *mMenu[curMenu].k1);
082
snprintf(ch2, 16,
"%f "
, *nMenu[curMenu].k2);
083
084
if
( millis() - ms_button > 1000) {
085
ms_button = millis();
086
Serial
.println(mMenu[curMenu].name1);
087
Serial
.println(ch1);
088
Serial
.println(nMenu[curMenu].name2);
089
Serial
.println(ch2);
090
}
091
092
}
093
094
095
void
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
103
void
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
}
В первом варианте спецификатор d - для целого знакового десятичного числа, во втором f - для число с плавающей точкой. Подскажите в чем не прав
1
//переменные которые надо менять
2
byte
kp1 = 0;
byte
kp2 = 0;
3
byte
ki1 = 0;
byte
ki2 = 0;
4
byte
kd1 = 0;
byte
kd2 = 0;
1
double
kp1 = 0;
double
kp2 = 0;
2
double
ki1 = 0;
double
ki2 = 0;
3
double
kd1 = 0;
double
kd2 = 0;
Чтобы легче понять, вот их основные различия. Во втором коде в порт выводятся не числа а знаки ?.
1
snprintf(ch1, 16,
"%d "
, *mMenu[curMenu].k1);
2
snprintf(ch2, 16,
"%d "
, *nMenu[curMenu].k2);
1
snprintf(ch1, 16,
"%f "
, *mMenu[curMenu].k1);
2
snprintf(ch2, 16,
"%f "
, *nMenu[curMenu].k2);
В том, что sprintf с плавающей точкой ниработаить
А чем это заменить чтобы заработало?
dtostrf()
strtod
преобразовывает строкуstring
вdouble
. Да уже понялStrtod это перевести строку в double
Да нашел, спасибо
http://geekmatic.in.ua/arduino_otobrazhenie_dannyih_float
А взат, из числа в строку - dstrtof()
BuonanotteMasha,
1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.
2.
Если есть лишние полкило памяти программ, то можно обойтись без печати в буфер, а связать штатный printf с потоком и выводить в любой поток просто штатным printf. ПРимеры я тут в разных темах много раз выкладывал.
Да спасибо, уже работает
001
//Назначаем пины кнопок управления
002
#define BUTTON_RIGHT 10
003
#define BUTTON_UP 9
004
#define BUTTON_DOWN 8
005
006
//состояние кнопок по умолчанию
007
boolean State_Up = LOW;
008
boolean State_Down = LOW;
009
boolean State_Left = LOW;
010
boolean State_Right = LOW;
011
boolean State_Ok = LOW;
012
013
//переменные которые надо менять
014
float
kp1 = 0;
float
kp2 = 0;
015
float
ki1 = 0;
float
ki2 = 0;
016
float
kd1 = 0;
float
kd2 = 0;
017
018
//переменные для кнопок
019
long
ms_button = 0;
020
boolean button_state =
false
;
021
boolean button_long_state =
false
;
022
023
byte
curMenu = 0;
//текущий пункт меню,
024
bool
b_ShowmMenu = 0;
// флаг отображения меню
025
const
byte
CountMenu = 3;
//количество пунктов меню
026
027
//массив элементов меню
028
struct
MENU_1{
//
029
char
name1[13];
030
float
*k1;
031
};
032
033
struct
MENU_2{
//
034
char
name2[13];
035
float
*k2;
036
};
037
038
//инициализация меню
039
//строковое имя, адрес переменной которую надо менять
040
MENU_1 mMenu[CountMenu] = {
041
" kP1 ="
, &kp1,
042
" kI1 ="
, &ki1,
043
" kD1 ="
, &kd1
044
};
045
046
MENU_2 nMenu[CountMenu] = {
047
" kP2 ="
, &kp2,
048
" kI2 ="
, &ki2,
049
" kD2 ="
, &kd2
050
};
051
052
//функция выполнения меню
053
void
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
096
void
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
104
void
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
}
Только не пойму, почему в порт только одно число выводится
Тут ошибок не наблюдаю
01
//вывод использую буфер
02
char
charbuf1[16];
03
char
charbuf2[16];
04
05
dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
06
dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
07
08
if
( millis() - ms_button > 1000) {
09
ms_button = millis();
10
Serial
.println(mMenu[curMenu].name1);
11
Serial
.println(charbuf1);
12
Serial
.println(nMenu[curMenu].name2);
13
Serial
.println(charbuf2);
14
}
BuonanotteMasha,
1.
кажется, где-то в опциях линкера можно было включить поддержку флоатов в штатной библиотеке, но точно не помню и не уверен.
я пыталса анажды найти, как в спринтф включать плавающую точку, прям из атмеловской документации пытался повторить, неделю нерничал, пил по чёрному, но так и не смог.
Танцы с бубном не помогли
001
#include <Wire.h> // библиотека для управления устройствами по I2C
002
003
//Назначаем пины кнопок управления
004
#define BUTTON_RIGHT 10
005
#define BUTTON_UP 9
006
#define BUTTON_DOWN 8
007
008
//состояние кнопок по умолчанию
009
boolean State_Up = LOW;
010
boolean State_Down = LOW;
011
boolean State_Left = LOW;
012
boolean State_Right = LOW;
013
boolean State_Ok = LOW;
014
015
//переменные которые надо менять
016
float
kp1 = 0;
float
kp2 = 0;
017
float
ki1 = 0;
float
ki2 = 0;
018
float
kd1 = 0;
float
kd2 = 0;
019
020
//переменные для кнопок
021
long
ms_button = 0;
022
boolean button_state =
false
;
023
boolean button_long_state =
false
;
024
025
byte
curMenu = 0;
//текущий пункт меню,
026
bool
b_ShowmMenu = 0;
// флаг отображения меню
027
const
byte
CountMenu = 3;
//количество пунктов меню
028
029
//массив элементов меню
030
struct
MENU_1{
//
031
char
name1[13];
032
float
*k1;
033
};
034
035
struct
MENU_2{
//
036
char
name2[13];
037
float
*k2;
038
};
039
040
//инициализация меню
041
//строковое имя, адрес переменной которую надо менять
042
MENU_1 mMenu[CountMenu] = {
043
" kP1 ="
, &kp1,
044
" kI1 ="
, &ki1,
045
" kD1 ="
, &kd1
046
};
047
048
MENU_2 nMenu[CountMenu] = {
049
" kP2 ="
, &kp2,
050
" kI2 ="
, &ki2,
051
" kD2 ="
, &kd2
052
};
053
054
//функция выполнения меню
055
void
menu_setup(){
056
057
if
(State_Right == LOW && millis() - ms_button > 200) {
//следующий пункт меню по кругу
058
ms_button = millis();
059
if
(curMenu == CountMenu - 1)
060
curMenu = 0;
061
else
curMenu++;
062
}
063
064
if
(State_Up == LOW && millis() - ms_button > 200) {
065
ms_button = millis();
066
(*mMenu[curMenu].k1)+=0.5;
067
(*nMenu[curMenu].k2)+=0.5;
068
}
069
070
if
(State_Down == LOW && millis() - ms_button > 200) {
071
ms_button = millis();
072
(*mMenu[curMenu].k1)-=0.5;
073
(*nMenu[curMenu].k2)-=0.5;
074
}
075
076
077
//вывод использую буфер
078
static
char
charbuf1[16];
079
static
char
charbuf2[16];
080
081
dtostrf(*mMenu[curMenu].k1, 16, 3, charbuf1);
082
dtostrf(*nMenu[curMenu].k2, 16, 3, charbuf2);
083
084
//snprintf(charbuf1, 16, "%f ", *mMenu[curMenu].k1);
085
//snprintf(charbuf2, 16, "%f ", *nMenu[curMenu].k2);
086
087
if
( millis() - ms_button > 1000) {
088
ms_button = millis();
089
Serial
.print(mMenu[curMenu].name1);
090
Serial
.println(charbuf1);
091
Serial
.print(nMenu[curMenu].name2);
092
Serial
.println(charbuf2);
093
}
094
095
}
096
097
098
void
setup
() {
099
Serial
.begin(9600);
100
pinMode (BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH);
//подключаем подтягивающий резистор
101
pinMode (BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH);
//подключаем подтягивающий резистор
102
pinMode (BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH);
//подключаем подтягивающий резистор
103
}
104
105
106
void
loop
() {
107
108
//Считываем состояние кнопок управления
109
State_Up = digitalRead(BUTTON_UP);
110
State_Down = digitalRead(BUTTON_DOWN);
111
State_Right = digitalRead(BUTTON_RIGHT);
112
//-------------------------------------/
113
114
menu_setup();
//вывод меню
115
}
Внимательно пока не смотрел, но попробуйте поставить после строки 92 Serial.flush();
Виноват, поправил на 92 - в общем, после печати сразу. А то в старом скетче это было 90.
Нет не помогло, пробую с floatToString. Поменял как вы сказали на 92 тоже нет
Покажите текущий код (в котором не помогло)
001
//Назначаем пины кнопок управления
002
#define BUTTON_RIGHT 10
003
#define BUTTON_UP 9
004
#define BUTTON_DOWN 8
005
006
//состояние кнопок по умолчанию
007
boolean State_Up = LOW;
008
boolean State_Down = LOW;
009
boolean State_Left = LOW;
010
boolean State_Right = LOW;
011
boolean State_Ok = LOW;
012
013
//переменные которые надо менять
014
float
kp1 = 0;
float
kp2 = 0;
015
float
ki1 = 0;
float
ki2 = 0;
016
float
kd1 = 0;
float
kd2 = 0;
017
018
//переменные для кнопок
019
long
ms_button = 0;
020
boolean button_state =
false
;
021
boolean button_long_state =
false
;
022
023
byte
curMenu = 0;
//текущий пункт меню,
024
bool
b_ShowmMenu = 0;
// флаг отображения меню
025
const
byte
CountMenu = 3;
//количество пунктов меню
026
027
//массив элементов меню
028
struct
MENU_1{
//
029
char
name1[13];
030
float
*k1;
031
};
032
033
struct
MENU_2{
//
034
char
name2[13];
035
float
*k2;
036
};
037
038
//инициализация меню
039
//строковое имя, адрес переменной которую надо менять
040
MENU_1 mMenu[CountMenu] = {
041
" kP1 ="
, &kp1,
042
" kI1 ="
, &ki1,
043
" kD1 ="
, &kd1
044
};
045
046
MENU_2 nMenu[CountMenu] = {
047
" kP2 ="
, &kp2,
048
" kI2 ="
, &ki2,
049
" kD2 ="
, &kd2
050
};
051
052
//функция выполнения меню
053
void
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
095
void
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
103
void
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
}
Евгений спасибо вам за помощь, но у меня самого получилось
001
#include<stdlib.h> // included for floatToString function
002
003
String 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
//состояние кнопок по умолчанию
015
boolean State_Up = LOW;
016
boolean State_Down = LOW;
017
boolean State_Left = LOW;
018
boolean State_Right = LOW;
019
boolean State_Ok = LOW;
020
021
//переменные которые надо менять
022
float
kp1 = 0;
float
kp2 = 0;
023
float
ki1 = 0;
float
ki2 = 0;
024
float
kd1 = 0;
float
kd2 = 0;
025
026
//переменные для кнопок
027
long
ms_button = 0;
028
boolean button_state =
false
;
029
boolean button_long_state =
false
;
030
031
byte
curMenu = 0;
//текущий пункт меню,
032
bool
b_ShowmMenu = 0;
// флаг отображения меню
033
const
byte
CountMenu = 3;
//количество пунктов меню
034
035
//массив элементов меню
036
struct
MENU_1{
//
037
char
name1[13];
038
float
*k1;
039
};
040
041
struct
MENU_2{
//
042
char
name2[13];
043
float
*k2;
044
};
045
046
//инициализация меню
047
//строковое имя, адрес переменной которую надо менять
048
MENU_1 mMenu[CountMenu] = {
049
" kP1 ="
, &kp1,
050
" kI1 ="
, &ki1,
051
" kD1 ="
, &kd1
052
};
053
054
MENU_2 nMenu[CountMenu] = {
055
" kP2 ="
, &kp2,
056
" kI2 ="
, &ki2,
057
" kD2 ="
, &kd2
058
};
059
060
//функция выполнения меню
061
void
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
110
void
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
118
void
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
}
проблема в указателях
Окончательный рабочий код
001
#include<stdlib.h> // included for floatToString function
002
003
String 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
//состояние кнопок по умолчанию
015
boolean State_Up = LOW;
016
boolean State_Down = LOW;
017
boolean State_Left = LOW;
018
boolean State_Right = LOW;
019
boolean State_Ok = LOW;
020
021
//переменные которые надо менять
022
float
kp1 = 0;
float
kp2 = 0;
023
float
ki1 = 0;
float
ki2 = 0;
024
float
kd1 = 0;
float
kd2 = 0;
025
026
//переменные для кнопок
027
long
ms_button = 0;
028
boolean button_state =
false
;
029
boolean button_long_state =
false
;
030
031
byte
curMenu = 0;
//текущий пункт меню,
032
bool
b_ShowmMenu = 0;
// флаг отображения меню
033
const
byte
CountMenu = 3;
//количество пунктов меню
034
035
//массив элементов меню
036
struct
MENU_1{
//
037
char
name1[13];
038
float
*k1;
039
};
040
041
struct
MENU_2{
//
042
char
name2[13];
043
float
*k2;
044
};
045
046
//инициализация меню
047
//строковое имя, адрес переменной которую надо менять
048
MENU_1 mMenu[CountMenu] = {
049
" kP1 ="
, &kp1,
050
" kI1 ="
, &ki1,
051
" kD1 ="
, &kd1
052
};
053
054
MENU_2 nMenu[CountMenu] = {
055
" kP2 ="
, &kp2,
056
" kI2 ="
, &ki2,
057
" kD2 ="
, &kd2
058
};
059
060
//функция выполнения меню
061
void
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
097
void
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
105
void
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
}
А, ну понятно, в строках 79-80 замените 16 на 15 - у Вас терминальный ноль не помещается.
А лучше заменять не на 15, а на sizeof(charbuf1)-1 и sizeof(charbuf2)-1
Я бы на Вашем месте остался бы на dtostrf. Возьмите код из поста #36 и поправьте, что я сказал, там делов-то.
Отжирает лишние 900 байтов памяти программы. Пруф. ниже
/*
01
* Пример использования SerialPrintf
02
*/
03
void
setup
() {
04
Serial
.begin(9600);
05
printf(
"Hello, world!\n"
);
06
}
07
08
void
loop
() {}
09
10
/////////////////////////////////////////////////////////
11
// Результат компиляции в IDE 1.6.12
12
// Скетч использует 1 508 байт (4%) памяти устройства.
13
/////////////////////////////////////////////////////////
Дак Вы вызывали printf, а не свою SerialPrintf функцию. При вызове SerialPrintf, скетч использует аж 3 700 байт;)
Сколько кто отжирает приведено в том посте, так что ля-ля не надо. А "свою", "не свою" - это Вы мимо. Они все мои. И использование scanf - тоже (я и его пользую).
Данный топик не для вбросов, а для решения задачи. Есть хорошее предложение? Выкладываейте, будет одним больше. Нет - не вбрасывайте, срача всё равно не будет. С моим участием, по крайней мере, точно не будет.
Какой вброс? Вы о чем? Я лишь указал на ошибку в ваших тестах. Нужно уметь признавать свои ошибки.
Да, действительно, только сейчас заметил. Извините.
Здравствуйте, ТС и форумчане. Имею скетч, пока занимает 774 (37%) оперативы и 16 794 байт (52%) флэш. Но скетч постоянно дополняется и улучшается и предстоит еще немало доработать. Поэтому если не составит труда подскажите как элементы структуры ниже с помощью PROGMЕM перенести во флэш чтобы потом не возникало желания перейти на другую плату помощнее, uno вполне устраивает
01
//массив элементов меню
02
struct
PODMENU_1{
//
03
char
name1[6];
04
double
*k1;
05
};
06
07
struct
PODMENU_2{
//
08
char
name2[6];
09
double
*k2;
10
};
11
12
//инициализация меню
13
//строковое имя, адрес переменной которую надо менять
14
PODMENU_1 mMenu[CountMenu] = {
15
"KP1 ="
, &kp1,
16
"KI1 ="
, &ki1,
17
"KD1 ="
, &kd1,
18
"PW1 ="
, &pwr_BOTTOM
19
};
20
21
//инициализация меню
22
//строковое имя, адрес переменной которую надо менять
23
PODMENU_2 nMenu[CountMenu] = {
24
"KP2 ="
, &kp2,
25
"KI2 ="
, &ki2,
26
"KD2 ="
, &kd2,
27
"PW2 ="
, &pwr_TOP
28
};
29
30
void
setup
() {}
31
void
loop
() {}
32
33
//функция выполнения меню в loop
34
void
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
}
1
const
char
str[] PROGMEM =
"STOP"
;
2
const
char
str1[] PROGMEM =
"PEAK"
;
3
const
char
str_[] PROGMEM =
" "
;
4
5
if
(number) lcd.print(i ? (
const
__FlashStringHelper*)str : (
const
__FlashStringHelper*)str_);
6
else
lcd.print(i ? (
const
__FlashStringHelper*)str1 : (
const
__FlashStringHelper*)str_)
Делал со строками в коде таким образом, но здесь такое не прокатит
BuonanotteMash,
если Вы не заметили, это тема о форматированном выводе в поток, и Ваш вопрос здесь - оффтопик и флуд. Потрудитесь, пожалуйста, найти подходящую тему или создайте свою.
ЕвгенийП, извините, сразу не подумал. Последую вашему совету