Этюды для начинающих: потоковый вывод в Serial
- Войдите на сайт для отправки комментариев
Коллеги, не так я давно я публиковал здесь функцию SerialPrintf, для вывода в Serial в стиле функции, printf, когда вместо длинного кода:
Serial.print("Voltage="); Serial.print(u); Serial.print("V; Current="); Serial.print(a); Serial.println("ma");
можно писать коротко, как привыкли все программитсы на С:
SerialPrintf("Voltage=%dV; Current=%dma\n, u,a);
И меня тут ехидно спросили: "Ну, о привыкших к С ты позаботился, а как быть с любителями С++?". Действительно, в С++ принято писать в "потоковой нотации", примерно так:
cout << "Voltage: " << u << "V; Current:" << a << "mA\n";
Вопрос, можно ли как-то заставить горячо любимый Serial понимать такую нотацию? Чтобы можно было писать что-то вроде:
Serial << "Voltage: " << u << "V; Current:" << a << "mA\n";
и чтобы это спокойно выводилось?
Ответ: а) можно; б) очень просто - намного проще, чем SerialPrintf.
Дело, собственно, в одной строчке в начале скетча. Смотрите:
template <typename T> inline HardwareSerial & operator << (HardwareSerial &s, T n) { s.print(n); return s; } void setup() { Serial.begin(115200); int u = 12; float a = 0.5; Serial << "Voltage: " << u << "V; Current:" << a << "mA\n"; } void loop() {}
Можете запустить и убедиться. что всё работает. Результат должен быть:
Voltage: 12V; Current:0.50mA
Итак, что же написано в первой строке? Буквально там написано, что если в программе встретится оператор вида:
выражение типа HardwareStream (s) << выражение любого типа (v)
то его следует заменить на s.print(v)
Вот и весь фокус! Выражение Serial << 321; просто будет заменено на Serial.print(321); И так с любым типом, который поддерживается в Serial.print
Есть два замечания.
1. Если Вы подсунете справа от << что-то, чего Serial.print не понимает, то получите ошибку компиляции. Впрочем её же Вы получите и если подсунете это оригинальному Serial.print
2. У Serial.print есть второй параметр (система счисления там указывается, например). При таком потоковом подходе этой возможностью воспользоваться нельзя. Данный подход моделирует Serial.print с одним параметром.
Не могу проверить сейчас, если у кого всё под рукой, проверьте, пожалуйста.
Если чуть-чуть исправить шаблон, то тоже самое можно делать и с SoftwareSerial. Пример под катом:
Скетч компилируется без ошибок, но проверить не на чем. Это возможно потому, что оба класса (HardwareSerial и SoftwareSerial) имеют один и тот же базовый класс Stream. Есть подозрение, что этот же трюк прокатит и с классами Client, UDP, Wire и т.п.
ЕвгенийП, объяснения не Вам.
kisoft,
думаю, что можно даже дальше пойти, и написать там не Stream. а его базовый - Print (т.к. используется только метод print, а это метод класса Print и класс Stream его не переопределяет). Завтра попробую на практике.
Это да, согласен, Print еще ближе к методам print().
Вообще, лично мне сложно решать, просто Stream - оно мне кажется более интуитивно понятней, нежели Print для переопределения "<<".
Проверил. Работает нормально Serial << "Val="<<Get()<<"\n"; Причем сократился размер проги по сравнению с Serial.println(Get()); на 44 байта.
С Print ошибка компиляции.
Упс. А разве avr-gcc понимает template? Ещё в мае читал что ТАК писать низзя ибо не скомпиляет.. я только поэтому не стал свои автоматы писать на плюсах. Раз работает - пасибки. Буду переносить всю автоматную логику на плюсы. Оно куда как кузявее.
С Print ошибка компиляции.
Наверное, Вы где-то ошиблись при внесении изменений, у меня нормально компилируется. попробуйте скетч ниже.
Хотя, я тут полазил по классам, достаточно поправки от kisoft - она действительно полезная, Print же практически ничего нового не даёт.
Теперь, ещё пример.
Я тут в прошлом этюде обещал, что наша "печатабельная" структура будет работать и с потоковым выводом, а когда писал этот этюд пример привести забыл.
Вот пример. В нём структура из прошлого этюда и первая строчка из этого.
Структура нормально печатается. Результат:
С Print ошибка компиляции.
Наверное, Вы где-то ошиблись при внесении изменений, у меня нормально компилируется. попробуйте скетч ниже.
Хотя, я тут полазил по классам, достаточно поправки от kisoft - она действительно полезная, Print же практически ничего нового не даёт.
Да, ошибся. Заменил на Print не везде в своем скетче. Работает с ним.
А в чем разница, с практической стороны, через какой из классов шаблон? Судя по размеру кода различий нет. Есть какието тонкости?
Простой пример, специально утрирую. 5 классов наследуются от класса Stream, который в свою очередь наследуется от Print. 6 классов наследуются от класса Print. Если доработать только класс Stream, то этот функционал получат 5 классов, а если Print, то 11 классов.
Числа 5 и 6 условные.
Шо такое иерархия класов я представляю. Я ж просил - с практической стороны. Что для разработчика, здесь и сейчас, дает использование одного класа, а что другово.
Ну, вроде ж kisoft объяснил.
Если привязаться к Stream, то это будет работать только с теми классами, что происходят от Stream. А если привязаться к Print, то добавятся ещё и классы, произведённые от Print.
В стандартной поставке IDE от Print унаследованы следующие классы:
Еще один шикарный этюд -))
Для себя почерпнул кое что новое. Утянул в свою записную книжку.
Автору СПАСИБО.
Добрый день, знатоки.
Прошу помощи в в поиске ошибки в программе.
Задача - повесить на НАНО датчик DHT11 и GY68. И Экран 16х2 в будущем.
Сейчас процесс встал на том, что слепил из 2х скетчей програмку. Она вроде работает, только с датчика DHT11 значения влажности 8% , температура 22 град. С датчика давления показания 768 мм рт ст. и температура 26 град.
прога:
Serg1,
вообще-то здесь тема немного о другом. Да и код Вы неправильно вставили - читать трудно. Походите по форуму, почитайте как вставлять код и запостите в правильное место.
Возможно ли сделать тоже самое но не для serial.print а для
File dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.print(val);
Возможно ли сделать тоже самое но не для serial.print а для
File dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.print(val);
Попробуйте разобраться сами. Вводные такие:
1. Если File является производным от Stream (или даже от Print), это уже работает и делать ничего не нужно, просто надовключить строку
2. Если не является, то нужно определить класс с именем, например, PrintFile, которые необходимо пронаследовать и от File, и от Print и тогда объявлять не
а
и всё будет работать
В библиотеке SD.h пишут что
class File : public Stream
значит достаточно написать?
File dataFile = SD.open("datalog.txt", FILE_WRITE);
SSensorValue sv;
sv.timeStamp = millis();
sv.temperture = 36.6;
sv.humidity = 93.2;
sv.pressure = 755;
datafile << sv;
Если, там" class File : public Stream"
то для простых типов (int, float и т.п.) можно писать
по идее, должно работать, но сам я не самотрел как там File описан.
Что касается классов, как в Вашем примере, то чтобы это работало SSensorValue обязана иметь метод size_t printTo(Print& p), как это описано в другом этюде. Если такой метод есть, то всё должно работать и dataFile.print(sv); и dataFile << sv; Последнее требует всё той же строки template ...
У меня этим методом не может открыть файл
Дык, ... http://arduino.ru/forum/obshchii/vstavka-programmnogo-koda-v-temukommentarii
Строка 077 и ниже закоментирован код который работает
все пишется в файл, дальше код который пробую(компилируется без ошибок) но открыть тхт файл не может
возможно что то я сделал не так но при попытке писать в файл он его не находит или не может открыть
вот сюда код скинул и сразу нашел ошибку))
У меня этим методом не может открыть файл
Ну. я не знаю, с каким методом? Покажите. что Вы там сделали, посмотрим, только не обещаю сегодня - тяжёлый день, которокие сообщения пишу иногда, а если разьираться и внимкать, то может завтра или в пятницу.
вот сюда код скинул и сразу нашел ошибку))
Так всё решилось? Все теперь нормально?
строка 073
открыл файл
строка 089 снова его открываю
из за этого видимо не мог записать в файл
89 строку удалил все заработало
теперь нужно этим же методом научить на LCD писать))
теперь нужно этим же методом научить на LCD писать))
Хорошо бы :)))
Есть одна беда. Поток (Stream) предполагает, что мы пихаем данные байт за байтом и ни о чём не паримся. А LCD по своей идее не поток, т.к. там надо не просто пихать данные, а ещё и координаты указывать. Можно придумать как это сделать, но не очень понятно зачем, т.к. так легко и просто всё равно не получится.
Появился вопрос по теме.
Как используя потоковый вывод вывести число в шеснадцатитеричном формате?
Евгений, нашел ваш же пост с таким кодом
То есть для вывода в шеснадцатитеричном формате мне обязательно нужно использовать промежуточную переменную и функцию sprintf? Например так? Это будет правильное решение?
Ну, можно и так. А можено 1-ую строку слега модифицировать, чтобы она понимала модификаторы и если требуется шестнадцатириный вывод, вызывла print(n, HEX);
Ну, можно и так. А можено 1-ую строку слега модифицировать, чтобы она понимала модификаторы и если требуется шестнадцатириный вывод, вызывла print(n, HEX);
Ок, почитаю сегодня про шаблоны, попробую переделать. Что получится выложу. Просто на работе нет возможности поэксперементировать.
В итоге получился бред. Сначала сделал потом понял что строку передаю, а у меня же не строка а int который надо показать в шеснадцатитеричном виде. А преобразовывать в строку каким либо sprintf вобщем то глупо, ничем не отличается если преобразовать int сразу в строку в шеснадцатитеричный формат и вывести, как делал выше. Видимо модификаторы это нечто другое, пока мне не понятное.
Пока получилось вот что:
Ну, там не так делается. При таком подходе Вы не сможете эту строку просто напечатать, если потребуется.
Создаётся дурацкий тип данных, например
здесь в скобках перечислены все возможные модификаторы. Если нужен только один - значит один.
Теперь нужно к общему методу (шаблону) добавить специальный метод именно для данного типа. Тогда, если переданн данный тип, то будет работать специальный метод (вновь добавленный), а если любой другой, то будет работать общий метод.
Специальный метод ничего не делает, а только меняет глобальную (в классе) переменную-флаг, которая отвечает за формат вывода. А основной метод должен её проверять и выводить правильно.
Если остались непонятки, то я могу написать, но не сейчас, либо сегодня позже, либо завтра. Надо?
Ну, там не так делается. При таком подходе Вы не сможете эту строку просто напечатать, если потребуется.
Создаётся дурацкий тип данных, например
здесь в скобках перечислены все возможные модификаторы. Если нужен только один - значит один.
Если остались непонятки, то я могу написать, но не сейчас, либо сегодня позже, либо завтра. Надо?
Буду очень благодарен если напишите. Потому как сам посидел почитал про шаблоны и понял что быстро не разберусь. Ну либо ставить костыль из sprintf вне шаблона.
Может еще кому нибудь будет полезно кроме меня. Просто у меня параметры передаются в HEX и при отладке тоже удобнее их видеть в HEX, чтобы не переводить туда сюда в ручную.
Помогите пожалуйста, у меня проблемы с монитором последовательного порта, не знаю с какой стороны подступить к решению
http://arduino.ru/forum/programmirovanie/problema-s-printf-ili-serial
Ну, вообще-то по уму тут бы модифицировать системный класс Print. Но это не всегда желательно, т.к. надо помнить об этом при установке новых версий IDE, поэтому мы сделаем это по колхозному.
Значит, заводим специальный тип для модификаторов
Имеем модификаторы hex, oct, dec и bin. Не нравятся эти имена – поменяйте.
Теперь заводим глобальную переменную, где будет храниться текущий модификатор
Теперь заводим отдельную функцию, которая принимает «на печать» переменную типа TStreamModificator и не печатает её, а запоминает в той самой переменной.
Затем выписываем функции печати для всех целых типов:
И, наконец оставляем в покое нашу старую – добрую функцию – шаблон.
Теперь, если тип целый, то будет вызываться специальная функция для него, а елси любой другой, то будет вызываться шаблон как раньше.
Ну, вот собственно и всё на первый раз, вот полный пример – запускайте.
---------------------------
Можно навести некоторую красивость. Но для этого удобнее внутренности всех функций печати целых свести в одну подстановку, чтобы красивость в одном месте наводить, а не в куче. А в этой подстановке мы добавим печать "0x" перед 16-ричными числами, "0b" - перед двоичными и "0" - перед восьмиричными.
Вот, что получилось - можете запустить.
Ну, и наконец, поскольку тут уже не одна строка, а куча, возможно, удобнее всё это скинуть в отдельный .h файл.
P.S. Да, кстати, здесь, единожды встретив модификатор, она будет выводить все числа в этом формате пока не встретит новый. Если надо, чтобы модификатор срабатывал только на одно число, а потом "самоотменялся", можно в строке перед "return s;" добавить _iMod = dec;
у меня проблемы с монитором последовательного порта,
Видел я ту Вашу тему, но Вы же не дали никакой информации. Какая версия была. какую поставили. Кроме того, неплохо бы приевсти минимальный скетч на котором проявляется проблема, чтобы человек мог попытаться Вашу проблему повторить. А так, откуда мне знать что там у Вас за версия. Все что-ли перебирать в попытке повторить проблему?
Спасибо огромное за труды. С меня пиво! На работе нет возможности проверить на макете, но проверил в симуляторе, все отличнео работает! Еще раз спасибо, вечером буду допиливать приемник. А то вчера весь вечер разбирался с шаблонами, но решения так и не нашел.
Да, не за что. Кстати, если Вам нравится printf, то вовсе не обязательно каждый раз заморачиваться с буфером и sprintf'ом. Можно вставить в начало файла три строки и одну - в начало функции setup и потом спокойно пользоваться printf. На практике там просто файл stdout связывается с Serial'овским потоком.
Вот смотрите
Тоже классно. Сохраню в копилку. Сейчас попробую что удобнее будет использовать :-)
Кстати, туда (в последний пример) и scanf присобачить несложно. Ещё одна строка добавится и эти чуток изменятся. Если вдруг надо - не вопрос.
Если не сложно то я за. Буду ждать код. В принципе с примерами когда работаешь уже отлаженными легче понимать как все работает чем самому начинать с нуля.
Я понимаю что теорию нужно знать, это позволяет не тупо операторы и куски хода вставлять и както заставлять работать железо как тебе нужно, а писать именно правильно. Но это хорошо когда программирование основной род деятельности. А у меня в день не всегда есть пару часов чтобы разобраться с кодом.
Вот тут показано как подключить scanf и приведены два примера её использования - простой и посложнее. Если Вы с нею (со scanf) не на ты, то придётся привыкать к её форматам - она капризная, но привыкнете.
Вот тут показано как подключить scanf и приведены два примера её использования - простой и посложнее. Если Вы с нею (со scanf) не на ты, то придётся привыкать к её форматам - она капризная, но привыкнете.
Со scanf не на ты и даже не на вы...
Ну да привыкну, в принципе если есть желание разобраться можно. Спасибо за пример!
Два экрана с разными адресами на одной шине.
Зачем в этой программе используются сразу 2 адреса I2C 0x27 и 0x26 ? http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-po...
Потому как в устройстве которое обслуживает программа 2 дисплея lcd и lcd1
Вы сами моженте назначить микросхеме I2C адрес подавая на A0-A2 микросхемы высокий или низкий уровень.
В принцпе в даташите все написано - A [0..2] Address inputs 0 through 2. Connect directly to VCC or ground. Pullup resistors are not needed.