Этюды для начинающих: потоковый вывод в Serial

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

Коллеги, не так я давно я публиковал здесь функцию 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 с одним параметром.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Не могу проверить сейчас, если у кого всё под рукой, проверьте, пожалуйста.

Если чуть-чуть исправить шаблон, то тоже самое можно делать и с SoftwareSerial. Пример под катом:

#include "SoftwareSerial.h"

#define SOFTWARESERIAL_INPUT_PIN  (2)
#define SOFTWARESERIAL_OUTPUT_PIN (3)

template <typename T> inline Stream & operator << (Stream &s, T n) {
  s.print(n);
  return s;
}

SoftwareSerial mySerial(SOFTWARESERIAL_INPUT_PIN, SOFTWARESERIAL_OUTPUT_PIN);

void setup() {
  mySerial.begin(9600);

  Serial.begin(115200);
  int u = 12;
  float a = 0.5;
  Serial << "Voltage: " << u << "V; Current:" << a << "mA\n";

  mySerial << "Voltage: " << u << "V; Current:" << a << "mA\n";
}

void loop()
{
}

Скетч компилируется без ошибок, но проверить не на чем. Это возможно потому, что оба класса (HardwareSerial и SoftwareSerial) имеют один и тот же базовый класс Stream. Есть подозрение, что этот же трюк прокатит и с классами Client, UDP, Wire и т.п.

ЕвгенийП, объяснения не Вам.

 

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

kisoft, 

думаю, что можно даже дальше пойти, и написать там не Stream. а его базовый - Print (т.к. используется только метод print, а это метод класса Print и класс Stream его не переопределяет). Завтра попробую на практике.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Это да, согласен, Print еще ближе к методам print().
Вообще, лично мне сложно решать, просто Stream - оно мне кажется более интуитивно понятней, нежели Print для переопределения "<<".

 

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

Проверил. Работает нормально Serial << "Val="<<Get()<<"\n";  Причем сократился размер проги по сравнению с  Serial.println(Get()); на 44 байта.

С Print ошибка компиляции.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Упс. А разве avr-gcc понимает template? Ещё в мае читал что ТАК писать низзя ибо не скомпиляет.. я только поэтому не стал свои автоматы писать на плюсах. Раз работает - пасибки. Буду переносить всю автоматную логику на плюсы. Оно куда как кузявее.

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

Logik пишет:

С Print ошибка компиляции.

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

Хотя, я тут полазил по классам, достаточно поправки от kisoft - она действительно полезная, Print же практически ничего нового не даёт.

Теперь, ещё пример. 

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

Вот пример. В нём структура из прошлого этюда и первая строчка из этого. 

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

struct SSensorValue : public Printable {
	unsigned long timeStamp;  //	Время съёма показаний (мс от запуска МК)
	float temperture;         //	температура в градусах С
	float humidity;           //	влажность в %
	float pressure;           //	давление в мм.рт.ст.

	size_t printTo(Print& p) const {
		size_t res = p.print("SENSOR VALUE: Time:");
		res += p.print(timeStamp);
		res += p.print("ms; T:");
		res += p.print(temperture);
		res += p.print("C; H:");
		res += p.print(humidity);
		res += p.print("%; P:");
		res += p.print(pressure);
		return res + p.println("mm");
	}
};

void setup() {
	Serial.begin(115200);
	//
	//	Объявляем структуру и заполняем какими-то значениями
	//
	SSensorValue sv;
	sv.timeStamp = millis();
	sv.temperture = 36.6;
	sv.humidity = 93.2;
	sv.pressure = 755;
	//
	//	Печатаем структуру
	//
	Serial << sv;
}

void loop() {}

Структура нормально печатается. Результат:

SENSOR VALUE: Time:0ms; T:36.60C; H:93.20%; P:755.00mm
 
Logik
Offline
Зарегистрирован: 05.08.2014

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

Logik пишет:

С Print ошибка компиляции.

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

Хотя, я тут полазил по классам, достаточно поправки от kisoft - она действительно полезная, Print же практически ничего нового не даёт.

Да, ошибся. Заменил на Print не везде в своем скетче. Работает с ним. 

А в чем разница, с практической стороны, через какой из классов шаблон? Судя по размеру кода различий нет. Есть какието тонкости?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Простой пример, специально утрирую. 5 классов наследуются от класса Stream, который в свою очередь наследуется от Print. 6 классов наследуются от класса Print. Если доработать только класс Stream, то этот функционал получат 5 классов, а если Print, то 11 классов.
Числа 5 и 6 условные.

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

Шо такое иерархия класов я представляю. Я ж просил -  с практической стороны. Что для разработчика, здесь и сейчас, дает использование одного класа, а что другово. 

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

Ну, вроде ж kisoft объяснил.

Если привязаться к Stream, то это будет работать только с теми классами, что происходят от Stream. А если привязаться к Print, то добавятся ещё и классы, произведённые от Print.

В стандартной поставке IDE от Print унаследованы следующие классы:

Stream (Stream.h)
Keyboard_ (USBAPI.h)
Server (Server.h)
SdFile (SdFat.h)
LiquidCrystal (LiquidCrystal.h)
GSM3ShieldV1ModemCore (GSM3ShieldV1ModemCore.h)
Fat16 (Fat16.h)
Adafruit_GFX (Adafruit_GFX.h)
 
И если Вы работаете с Print, то Ваш оператор будет работать с ними всеми и с теми, кто от них произведён. А если Вы привязываетсь к Stream - только с ним и с его производными.
pav2000
Offline
Зарегистрирован: 15.12.2014

Еще один шикарный  этюд -))

Для себя почерпнул кое что новое. Утянул в свою записную книжку.

Автору СПАСИБО.

Serg1
Offline
Зарегистрирован: 04.12.2015

Добрый день, знатоки.

Прошу помощи в в поиске ошибки в программе.

Задача - повесить на НАНО датчик DHT11 и GY68. И Экран 16х2 в будущем.

Сейчас процесс встал на том, что слепил из 2х скетчей програмку. Она вроде работает, только с датчика DHT11 значения влажности 8% , температура 22 град. С датчика давления показания 768 мм рт ст. и температура 26 град. 

прога:

#include <stDHT.h>
#include <Wire.h> 
#include <BMP085.h>
 
BMP085 dps = BMP085();    
 
long Temperature = 0, Pressure = 0;
 
DHT sens(DHT11);// не использовать одновременно DHT11 и DHT22
void setup() 
{
  Serial.begin(9600);
  pinMode(6, INPUT);
  digitalWrite(6, HIGH);
  pinMode(3, INPUT);
  digitalWrite(3, HIGH);
  Wire.begin(); 
  delay(3000);
  dps.init();  
}
 
void loop() 
{
  int t = sens.readTemperature(6); // чтение датчика на пине 6
  int h = sens.readHumidity(6);    // чтение датчика на пине 6
  delay(3000);
  
   dps.getPressure(&Pressure); 
  dps.getTemperature(&Temperature);
 
  Serial.print("Pressure(mm Hg):"); 
  Serial.print(Pressure/133.3);      //Выводим давление в мм.рт ст
  Serial.print("Temp:"); 
  Serial.println(Temperature*0.1);   // Температуру в градусах Цельсия
  delay(2000); 
  Serial.print("Hum: ");
  Serial.print(h);
  Serial.print(" %");
  Serial.print("Temp: ");
  Serial.print(t);
  Serial.println(" C "); 
}

 

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

Serg1,

вообще-то здесь тема немного о другом. Да и код Вы неправильно вставили - читать трудно. Походите по форуму, почитайте как вставлять код и запостите в правильное место.

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

Возможно ли сделать тоже самое но не для serial.print а для

File dataFile = SD.open("datalog.txt", FILE_WRITE);

dataFile.print(val);

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

Sikorskiy пишет:

Возможно ли сделать тоже самое но не для serial.print а для

File dataFile = SD.open("datalog.txt", FILE_WRITE);

dataFile.print(val);

Попробуйте разобраться сами. Вводные такие:

1. Если File является производным от Stream (или даже от Print), это уже работает и делать ничего не нужно, просто надовключить строку

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

2. Если не является, то нужно определить класс с именем, например, PrintFile, которые необходимо пронаследовать и от File, и от Print и тогда объявлять не

File dataFile = SD.open("datalog.txt", FILE_WRITE);

dataFile.print(val);

а

PrintFile dataFile = SD.open("datalog.txt", FILE_WRITE);

dataFile.print(val);

и всё будет работать

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

В библиотеке 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;


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

Если, там" class File : public Stream"

то для простых типов (int, float и т.п.) можно писать 

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }
...
File dataFile = SD.open("datalog.txt", FILE_WRITE);
...
int a = 321;
float f = 3.14;
...

dataFile << "a=" << a << "; f=" << f << "\n";

по идее, должно работать, но сам я не самотрел как там File описан.

Что касается классов, как в Вашем примере, то чтобы это работало SSensorValue обязана иметь метод size_t printTo(Print& p), как это описано в другом этюде. Если такой метод есть, то всё должно работать и dataFile.print(sv); и dataFile << sv; Последнее требует всё той же строки template ...

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

У меня этим методом не может открыть файл

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015
Sikorskiy
Offline
Зарегистрирован: 25.04.2016
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <SD.h>

LiquidCrystal_I2C lcd(0x27,16,2);
LiquidCrystal_I2C lcd1(0x26,16,2);

#define sens A0
#define sensmq A1
int chipSelect = 10;
int t;
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }
struct SSensorValue : public Printable {
  unsigned long timeStamp;  //  Время съёма показаний (мс от запуска МК)
  float temperture;         //  температура в градусах С
  float humidity;           //  влажность в %
  float pressure;           //  давление в мм.рт.ст.

  size_t printTo(Print& p) const { // в качестве p нам передадут, например, Serial
    size_t res = p.print("SENSOR VALUE: Time:");
    res += p.print(timeStamp);
    res += p.print("ms; T:");
    res += p.print(temperture);
    res += p.print("C; H:");
    res += p.print(humidity);
    res += p.print("%; P:");
    res += p.print(pressure);
    return res + p.println("mm");
  }
};

void setup() {
    t=0;
    Serial.begin(9600);
    while (!Serial);                
    lcd1.init();
    lcd.init();
    lcd.backlight();
    lcd1.backlight();
    lcd1.setCursor(0,0);
    lcd1.print("Initializing ");
    lcd1.setCursor(0,1);
    lcd1.print("SD card...");
    delay(1000);
    
            if (!SD.begin(chipSelect)) 
                { 
                  lcd1.setCursor(11,1);
                  lcd1.print("ERROR");
                  delay(1000);
                  lcd1.clear();
                  lcd1.setCursor(0,0);
                  lcd1.print("Card failed,");
                  lcd1.setCursor(0,1);
                  lcd1.print("or not present.");
                  delay(1000);
                  return;
                }
                
    lcd1.setCursor(11,1);
    lcd1.print("DONE");
    delay(1000);
    pinMode(sens, INPUT);
    pinMode(sensmq, INPUT);
   
  }

void loop() {
  int data = analogRead(sens);
  int data1 = analogRead(sensmq);
  
  File dataFile = SD.open("datalog.txt", FILE_WRITE);

            if (dataFile) 
                {
                  /*
                  dataFile.print(t++);
                  dataFile.print(" ");
                  dataFile.print(data);
                  dataFile.print(" ");
                  dataFile.print(data1);
                  dataFile.print(" ");
                  dataFile.print(data);
                  dataFile.print(" ");
                  dataFile.println(data1);
                  dataFile.close();*/
                  
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  
  //
  //  Объявляем структуру и заполняем какими-то значениями
  //
  SSensorValue sv;
  sv.timeStamp = millis();
  sv.temperture = analogRead(sens);
  sv.humidity = analogRead(sensmq);;
  sv.pressure = 755;
  //
  //  Печатаем структуру
  //
  dataFile.print(sv);
  dataFile.close();

                }
  
            else {
                  lcd1.clear();
                  lcd1.setCursor(0,0);
                  lcd1.print("Error opening ");
                  lcd1.setCursor(0,1);
                  lcd1.print("datalog.txt");
                  delay(1000);
                }
              
    lcd.setCursor(0,0);
    lcd.print("CO       ");
    lcd.setCursor(data < 100 ? 6 : 5, 0);
    lcd.print(data);
    lcd.setCursor(0,1);
    lcd.print("CH4      ");
    lcd.setCursor(data1 < 100 ? 6 : 5, 1);
    lcd.print(data1);
    lcd.setCursor(9,0);
    lcd.print("H2     ");
    lcd.setCursor(data < 100 ? 14 : 13, 0);
    lcd.print(data);
    lcd.setCursor(9,1);
    lcd.print("LPG     ");
    lcd.setCursor(data1 < 100 ? 14 : 13, 1);
    lcd.print(data1);
    delay(1000);
}
  

 

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

Строка 077 и ниже закоментирован код который работает

все пишется в файл, дальше код который пробую(компилируется без ошибок) но открыть тхт файл не может

возможно что то я сделал не так но при попытке писать в файл он его не находит или не может открыть

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

вот сюда код скинул и сразу нашел ошибку))

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

Sikorskiy пишет:

У меня этим методом не может открыть файл

Ну. я не знаю, с каким методом? Покажите. что Вы там сделали, посмотрим, только не обещаю сегодня - тяжёлый день, которокие сообщения пишу иногда, а если разьираться и внимкать, то может завтра или в пятницу.

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

Sikorskiy пишет:

вот сюда код скинул и сразу нашел ошибку))

Так всё решилось? Все теперь нормально?

Sikorskiy
Offline
Зарегистрирован: 25.04.2016

строка 073
открыл файл

строка 089 снова его открываю

из за этого видимо не мог записать в файл

89 строку удалил все заработало

теперь нужно этим же методом научить на LCD писать))

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

Sikorskiy пишет:

теперь нужно этим же методом научить на LCD писать))

Хорошо бы :)))

Есть одна беда. Поток (Stream) предполагает, что мы пихаем данные байт за байтом и ни о чём не паримся. А LCD по своей идее не поток, т.к. там надо не просто пихать данные, а ещё и координаты указывать. Можно придумать как это сделать, но не очень понятно зачем, т.к. так легко и просто всё равно не получится.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

Появился вопрос по теме.

Как используя потоковый вывод вывести число в шеснадцатитеричном формате?

Евгений, нашел ваш же пост с таким кодом

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

void setup() {
	Serial.begin(115200);
	char * s = "0xfc";
	int n;
	sscanf(s, "0x%x", &n);
	Serial << "n = " << n << "\n";
}

void loop() {
}

То есть для вывода в шеснадцатитеричном формате мне обязательно нужно использовать промежуточную переменную и функцию sprintf? Например так? Это будет правильное решение?

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

void setup() {
    Serial.begin(115200);
    int i = 255;
    char str[8];
    sprintf (str, "%X", i);
    Serial << "Init code int - " << i << " HEX - " << str <<"\n";
}

void loop() {
}
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, можно и так. А можено 1-ую строку слега модифицировать, чтобы она понимала модификаторы и если требуется шестнадцатириный вывод, вызывла print(n, HEX);

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

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

Ну, можно и так. А можено 1-ую строку слега модифицировать, чтобы она понимала модификаторы и если требуется шестнадцатириный вывод, вызывла print(n, HEX);

Ок, почитаю сегодня про шаблоны, попробую переделать. Что получится выложу. Просто на работе нет возможности поэксперементировать.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

В итоге получился бред. Сначала сделал потом понял что строку передаю, а у меня же не строка а int который надо показать в шеснадцатитеричном виде. А преобразовывать в строку каким либо sprintf вобщем то глупо, ничем не отличается если преобразовать int сразу в строку в шеснадцатитеричный формат и вывести, как делал выше. Видимо модификаторы это нечто другое, пока мне не понятное.

Пока получилось вот что:

template <typename T> inline Print & operator << (Print &s, T n)
{
  char buf[14] = "";
  sprintf(buf, "%12s", n);

  if (!strncmp(buf, "B!", 2))
  {
    s.print("0b");
    s.print(atol(strstr(buf, "!") + 1), BIN);
  }
  else if (!strncmp(buf, "X!", 2))
  {
    s.print("0x");
    s.print(atol(strstr(buf, "!") + 1), HEX);
  }
  else
  {
    s.print(n);
  }

  return s;
}


void setup() {
  Serial.begin(115200);
  Serial << "BIN - " << "B!4026531840"  << " HEX - " << "X!4026531840"  << " DEC - " << 0b11110000 << " QWERTYUIOPASDFGHJKL" << "\n";

}

void loop() {
}

 

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

Ну, там не так делается. При таком подходе Вы не сможете эту строку просто напечатать, если потребуется.

Создаётся дурацкий тип данных, например 

typedef enum { hex, octal } TStreamModificator;

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

Теперь нужно к общему методу (шаблону) добавить специальный метод именно для данного типа. Тогда, если переданн данный тип, то будет работать специальный метод (вновь добавленный), а если любой другой, то будет работать общий метод.

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

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

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

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

Ну, там не так делается. При таком подходе Вы не сможете эту строку просто напечатать, если потребуется.

Создаётся дурацкий тип данных, например 

typedef enum { hex, octal } TStreamModificator;

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

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

Буду очень благодарен если напишите. Потому как сам посидел почитал про шаблоны и понял что быстро не разберусь. Ну либо ставить костыль из sprintf вне шаблона.

Может еще кому нибудь будет полезно кроме меня. Просто у меня параметры передаются в HEX и при отладке тоже удобнее их видеть в HEX, чтобы не переводить туда сюда в ручную.

coockie
Offline
Зарегистрирован: 24.04.2017

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

Помогите пожалуйста, у меня проблемы с монитором последовательного порта, не знаю с какой стороны подступить к решению

http://arduino.ru/forum/programmirovanie/problema-s-printf-ili-serial

 

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

Ну, вообще-то по уму тут бы модифицировать системный класс Print. Но это не всегда желательно, т.к. надо помнить об этом при установке новых версий IDE, поэтому мы сделаем это по колхозному.

Значит, заводим специальный тип для модификаторов

typedef enum { hex=HEX, oct=OCT, dec=DEC, bin=BIN } TStreamModificator;

Имеем модификаторы hex, oct, dec и bin. Не нравятся эти имена – поменяйте.

Теперь заводим глобальную переменную, где будет храниться текущий модификатор

static TStreamModificator _iMod = dec; // по умолчанию dec

Теперь заводим отдельную функцию, которая принимает «на печать» переменную типа TStreamModificator  и не печатает её, а запоминает в той самой переменной.

inline Print & operator << (Print &s, const TStreamModificator m) { _iMod = m; return s; }

Затем выписываем функции печати для всех целых типов:

inline Print & operator << (Print &s, unsigned char n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, int n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, unsigned int n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, long n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, unsigned long n) { s.print(n, _iMod); return s; }

И, наконец оставляем в покое нашу старую – добрую функцию – шаблон. 

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

Теперь, если тип целый, то будет вызываться специальная функция для него, а елси любой другой, то будет вызываться шаблон как раньше.

Ну, вот собственно и всё на первый раз, вот полный пример – запускайте.

typedef enum { hex=HEX, oct=OCT, dec=DEC, bin=BIN } TStreamModificator;

static TStreamModificator _iMod = dec;

inline Print & operator << (Print &s, const TStreamModificator m) { _iMod = m; return s; }

inline Print & operator << (Print &s, unsigned char n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, int n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, unsigned int n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, long n) { s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, unsigned long n) { s.print(n, _iMod); return s; }

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

void setup() {
	Serial.begin(115200);
	Serial << "Test: " << 31 << ',' << hex << 31 << ',' << bin << 31 << ',' << oct << 31 << '\n';
}

void loop() {}

---------------------------

Можно навести некоторую красивость. Но для этого удобнее внутренности всех функций печати целых свести в одну подстановку, чтобы красивость в одном месте наводить, а не в куче. А в этой подстановке мы добавим печать "0x" перед 16-ричными числами, "0b" - перед двоичными и "0" - перед восьмиричными.

Вот, что получилось - можете запустить.

typedef enum { hex=HEX, oct=OCT, dec=DEC, bin=BIN } TStreamModificator;
static TStreamModificator _iMod = dec;
inline Print & operator << (Print &s, const TStreamModificator m) { _iMod = m; return s; }
#define _PRINT_INT_ {  s.print(_iMod==DEC ? "" : (_iMod==HEX ? "0x" : (_iMod==BIN ? "0b" : "0"))); s.print(n, _iMod); return s; }
inline Print & operator << (Print &s, unsigned char n) _PRINT_INT_
inline Print & operator << (Print &s, int n) _PRINT_INT_
inline Print & operator << (Print &s, unsigned int n) _PRINT_INT_
inline Print & operator << (Print &s, long n) _PRINT_INT_
inline Print & operator << (Print &s, unsigned long n) _PRINT_INT_
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

void setup() {
	Serial.begin(115200);
	Serial << "Test: " << 31 << ',' << hex << 31 << ',' << bin << 31 << ',' << oct << 31 << '\n';
}

void loop() {}

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

P.S. Да, кстати, здесь, единожды встретив модификатор, она будет выводить все числа в этом формате пока не встретит новый. Если надо, чтобы модификатор срабатывал только на одно число, а потом "самоотменялся", можно в строке  перед "return s;" добавить _iMod = dec;

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

coockie пишет:

у меня проблемы с монитором последовательного порта, 

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

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

Спасибо огромное за труды. С меня пиво! На работе нет возможности проверить на макете, но проверил в симуляторе, все отличнео работает! Еще раз спасибо, вечером буду допиливать приемник. А то вчера весь вечер разбирался с шаблонами, но решения так и не нашел.

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

Да, не за что. Кстати, если Вам нравится printf, то вовсе не обязательно каждый раз заморачиваться с буфером и sprintf'ом. Можно вставить в начало файла три строки и одну - в начало функции setup и потом спокойно пользоваться printf. На практике там просто файл stdout связывается с Serial'овским потоком.

Вот смотрите

//	Эти три строки пишем в начале файла
#include <stdio.h>
static int serial_fputchar(const char ch, FILE *stream) { Serial.write(ch); return ch; }
static FILE *serial_stream = fdevopen(serial_fputchar, NULL);

void setup(void) { 
	stdout = serial_stream; // эта строка первая в setup
	//
	// А ТЕПЕРЬ ТУПО ПОЛЬЗУЕМСЯ printf
	//
	Serial.begin(115200);
	int a = 28;
	// Печатаем a в разных видах
	printf("a=%d, a=%03d, a=%04d, a=%05d, a=%06d\n", a, a, a, a, a);
	// Тоже самое в hex
	printf("a=%d, a=%03x, a=%04x, a=%05x, a=%06x\n", a, a, a, a, a);
	// Тоже самое в HEX
	printf("a=%d, a=%03X, a=%04X, a=%05X, a=%06X\n", a, a, a, a, a);
}

void loop(void){}

 

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

Тоже классно. Сохраню в копилку. Сейчас попробую что удобнее будет использовать :-)

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

Кстати, туда (в последний пример) и scanf присобачить несложно. Ещё одна строка добавится и эти чуток изменятся. Если вдруг надо - не вопрос.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

Если не сложно то я за. Буду ждать код. В принципе с примерами когда работаешь уже отлаженными легче понимать как все работает чем самому начинать с нуля.

Я понимаю что теорию нужно знать, это позволяет не тупо операторы и куски хода вставлять и както заставлять работать железо как тебе нужно, а писать именно правильно. Но это хорошо когда программирование основной род деятельности. А у меня в день не всегда есть пару часов чтобы разобраться с кодом. 

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

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

//	Эти четыре строки пишем в начале файла
#include <stdio.h>
static int serial_fputchar(const char ch, FILE *stream) { Serial.write(ch); return ch; }
static int serial_fgetchar(FILE *stream) { int c = 0; if (!Serial.available()) delay(10); return Serial.read(); }
static FILE *serial_stream = fdevopen(serial_fputchar, serial_fgetchar);

void setup(void) { 
	stdin = stdout = serial_stream; // эта строка первая в setup
	//
	// А ТЕПЕРЬ ТУПО ПОЛЬЗУЕМСЯ printf и scanf
	//
	Serial.begin(115200);
	printf("Enter an integer:\n");
}

void loop(void){
	static bool simpleTest = true;
	
	if (Serial.available()) {
		if (!(simpleTest = !simpleTest)) {
			//
			// Простой пример ввод целого
			//
			int n;
			scanf("%d\n", &n);
			printf (
				"Single integer n=%d\n\nEnter URL parameter string with three single letter parameters\n"
				"For example: a=12&b=321&p=1\n",n);
		} else {
			//
			// Пример посложнее - вводе сроки их трёх параметров
			// с однобуквенными именами и числовыми значениями
			//
			char pNames[3];
			int pValues[3];
			scanf("%c=%d&%c=%d&%c=%d\n", pNames, pValues, pNames+1, pValues+1, pNames+2, pValues+2);
			printf("name=%c value=%d\nname=%c value=%d\nname=%c value=%d\n\nEnter an integer:\n", 
					pNames[0], pValues[0], pNames[1], pValues[1], pNames[2], pValues[2]);
		}
	}
}

 

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

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

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

Со scanf не на ты и даже не на вы...

Ну да привыкну, в принципе если есть желание разобраться можно. Спасибо за пример!

 

Дятел
Offline
Зарегистрирован: 27.05.2017

Sikorskiy пишет:

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <SD.h>

LiquidCrystal_I2C lcd(0x27,16,2);
LiquidCrystal_I2C lcd1(0x26,16,2);

#define sens A0
#define sensmq A1
int chipSelect = 10;
int t;
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }
struct SSensorValue : public Printable {
  unsigned long timeStamp;  //  Время съёма показаний (мс от запуска МК)
  float temperture;         //  температура в градусах С
  float humidity;           //  влажность в %
  float pressure;           //  давление в мм.рт.ст.

  size_t printTo(Print& p) const { // в качестве p нам передадут, например, Serial
    size_t res = p.print("SENSOR VALUE: Time:");
    res += p.print(timeStamp);
    res += p.print("ms; T:");
    res += p.print(temperture);
    res += p.print("C; H:");
    res += p.print(humidity);
    res += p.print("%; P:");
    res += p.print(pressure);
    return res + p.println("mm");
  }
};

void setup() {
    t=0;
    Serial.begin(9600);
    while (!Serial);                
    lcd1.init();
    lcd.init();
    lcd.backlight();
    lcd1.backlight();
    lcd1.setCursor(0,0);
    lcd1.print("Initializing ");
    lcd1.setCursor(0,1);
    lcd1.print("SD card...");
    delay(1000);
    
            if (!SD.begin(chipSelect)) 
                { 
                  lcd1.setCursor(11,1);
                  lcd1.print("ERROR");
                  delay(1000);
                  lcd1.clear();
                  lcd1.setCursor(0,0);
                  lcd1.print("Card failed,");
                  lcd1.setCursor(0,1);
                  lcd1.print("or not present.");
                  delay(1000);
                  return;
                }
                
    lcd1.setCursor(11,1);
    lcd1.print("DONE");
    delay(1000);
    pinMode(sens, INPUT);
    pinMode(sensmq, INPUT);
   
  }

void loop() {
  int data = analogRead(sens);
  int data1 = analogRead(sensmq);
  
  File dataFile = SD.open("datalog.txt", FILE_WRITE);

            if (dataFile) 
                {
                  /*
                  dataFile.print(t++);
                  dataFile.print(" ");
                  dataFile.print(data);
                  dataFile.print(" ");
                  dataFile.print(data1);
                  dataFile.print(" ");
                  dataFile.print(data);
                  dataFile.print(" ");
                  dataFile.println(data1);
                  dataFile.close();*/
                  
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  
  //
  //  Объявляем структуру и заполняем какими-то значениями
  //
  SSensorValue sv;
  sv.timeStamp = millis();
  sv.temperture = analogRead(sens);
  sv.humidity = analogRead(sensmq);;
  sv.pressure = 755;
  //
  //  Печатаем структуру
  //
  dataFile.print(sv);
  dataFile.close();

                }
  
            else {
                  lcd1.clear();
                  lcd1.setCursor(0,0);
                  lcd1.print("Error opening ");
                  lcd1.setCursor(0,1);
                  lcd1.print("datalog.txt");
                  delay(1000);
                }
              
    lcd.setCursor(0,0);
    lcd.print("CO       ");
    lcd.setCursor(data < 100 ? 6 : 5, 0);
    lcd.print(data);
    lcd.setCursor(0,1);
    lcd.print("CH4      ");
    lcd.setCursor(data1 < 100 ? 6 : 5, 1);
    lcd.print(data1);
    lcd.setCursor(9,0);
    lcd.print("H2     ");
    lcd.setCursor(data < 100 ? 14 : 13, 0);
    lcd.print(data);
    lcd.setCursor(9,1);
    lcd.print("LPG     ");
    lcd.setCursor(data1 < 100 ? 14 : 13, 1);
    lcd.print(data1);
    delay(1000);
}

Зачем в этой программе используются сразу 2 адреса I2C 0x27 и 0x26 ? http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-po...

 

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

Два экрана с разными адресами на одной шине.

Andrey12
Andrey12 аватар
Offline
Зарегистрирован: 26.12.2014

Дятел пишет:

 Зачем в этой программе используются сразу 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.