Представляю Вашему вниманию еще одну маленькую библиотечку. Для чего она нужна?
Пожалуй самый популярный способ “отладки” программ для Arduino – добавление в код скетча текстовых отладочных сообщений, а затем их отслеживание через подключение по COM-порту. Что-то вроде этого:
HTTPClient https; if (https.begin(wifiClient, httpRequest)) { int httpCode = https.GET(); if (httpCode > 0) { if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { result = true; Serial.printf("TLG :: Send \"%s\": OK (%d)\n", message.c_str(), httpCode); } else { Serial.printf("TLG :: Send \"%s\": FAILED (%d) %s\n", message.c_str(), httpCode, https.errorToString(httpCode).c_str()); }; } else { Serial.println(F("TLG :: Send FAILED: Unable to connect!")); }; https.end(); } else { Serial.println(F("TLG :: Send FAILED: Unable to create HTTPS client!")); };
Приведенный выше код содержит множество отладочных Serial.println(…), позволяющих через COM-порт определить, на каком этапе что пошло не так. Все это просто и хорошо, но ровно до того момента, когда устройство оказывается полностью отлажено и собрано, то есть готово “в продакшен”. В “боевой” обстановке устройство обычно работает вдали от компьютера и вывод в serial только мешает – занимает и процессорные ресурсы, и память. Хорошо бы иметь возможность отключать вывод отладочных сообщений, а при возможных доработках проекта – включать.
Именно такой принцип заложен в библиотеку, которая “встроена” в Framework Arduino для ESP32: “esp32-hal-log.h” (в фреймворке ESP-IDF есть более “навороченная” версия – “esp_log.h”). Причем отключать “отладку” можно “частями” – чем ниже заданный уровень – тем меньше сообщений Вы увидите. Например: ничего, только ошибки, ошибки + предупреждения, ошибки + предупреждения + сообщения и т.д. Большая часть построена на макросах, а это означает, что сообщения выше заданного уровня будут полностью исключены из кода. Очень удобно. И достаточно просто.
Так зачем же еще изобретать велосипед? Есть несколько “но”:
- Во-первых, включив уровень “DEBUG” в настройках проекта, Вы увидите не только “свои” сообщения, но и кучу отладочных сообщений из всех подключенных библиотек фреймворка. Хотелось бы как-то разделить все-таки. То есть подавить “системные” сообщения, оставив только нужные.
- Во-вторых, эти библиотеки не будут работать на ESP8266, и уж тем более на AVR. А из-за этого код какой-нибудь “своей” библиотеки перестает работать на другом контроллере только из-за отладки. Конечно, можно нагородить леса типа с помощью макросов #if … #endif, но это сильно загромождает код и усложняет написание программы. Хотелось бы какого-то однообразия.
- Ну и наконец – Вы не сможете изменить настройки sdkconfig.h, если используется только “framework-arduinoespressif32”, он поставляется уже сконфигурированным. То есть Вы не сможете самостоятельно изменить уровень отладки.
Исходя из этого мне ничего не оставалось, кроме как самым наглейшим образом передрать код “esp32-hal-log.h”, портировать его на ESP8266 и AVR, и сделать отдельные настройки для всего этого. Вот так и получилась эта библиотека. Использовать ее можно на ESP8266, ESP32, AVR и других контроллерах, в Arduino IDE и PlatformIO, в том числе в фреймворке ESP-IDF.
Библиотека бесплатная, Open source, скачать ее можно с GitHub с помощью кнопки ниже.
Как использовать
Есть несколько уровней отладочных сообщений:
- RLOG_LEVEL_NONE – Сообщения не выводятся
- RLOG_LEVEL_ERROR – Критические ошибки, программный модуль не может восстановиться самостоятельно
- RLOG_LEVEL_WARN – Состояния ошибки, из которых были приняты меры по устранению
- RLOG_LEVEL_INFO – Информационные сообщения, описывающие нормальный ход событий
- RLOG_LEVEL_DEBUG – Дополнительная информация, которая не требуется для нормального использования (значения, указатели, размеры и т.д.).
- RLOG_LEVEL_VERBOSE – Большие фрагменты отладочной информации или частые сообщения, которые потенциально могут переполнить вывод.
Им соответствуют несколько функций для вывода этих сообщений:
- rlog_e(tag, format, …) – Сообщения уровня RLOG_LEVEL_ERROR
- rlog_w(tag, format, …) – Сообщения уровня RLOG_LEVEL_WARN
- rlog_i(tag, format, …) – Сообщения уровня RLOG_LEVEL_INFO
- rlog_d(tag, format, …) – Сообщения уровня RLOG_LEVEL_DEBUG
- rlog_v(tag, format, …) – Сообщения уровня RLOG_LEVEL_VERBOSE
где: tag – идентификатор, с помощью которого удобно обозначать модуль, откуда пришло сообщение (например “mqtt” или “telegram”) format – собственно сообщение с символами форматирования (или без них) … – переменные, передаваемые в строку форматирования (например “Value = %d\n”, 10). Их может и не быть вовсе.
Соответственно, чем выше уровень отладки в Вашем проекте, тем больше сообщений Вы увидите.
Уровень отладки задается с помощью определения #define CONFIG_RLOG_PROJECT_LEVEL RLOG_LEVEL_XXXX, где RLOG_LEVEL_XXXX – этот самый уровень и есть. Кроме этого, есть возможность включить вывод системного времени и цветных ASCII-кодов в сообщения. Эти опции также включаются с помощью макросов.
Например при CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_INFO в код попадут только макросы-функции rlog_e, rlog_w, rlog_i, а остальные будут игнорированы.
Для CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_DEBUG будут отображаться ещё и rlog_d.
А при CONFIG_RLOG_PROJECT_LEVEL == RLOG_LEVEL_NONE все отладочные сообщения будет исключены.
Настройка уровня вывода
Осталось придумать, как определить этот самый CONFIG_RLOG_PROJECT_LEVEL
Если весь Ваш код содержится в одном файле – то с этим не будет никаких проблем. Просто добавьте воды #define CONFIG_RLOG_PROJECT_LEVEL RLOG_LEVEL_INFO в начале файла и проблема будет решена.
Сложнее, если Вы будете дробить код по разным файлам. В этом случае целесообразно вынести определения макросов в отдельный файл, например “project_config.h”. Если все файлы проекта находятся в одной папке, то проблем не возникнет.
Проблема будет, если Вы будете подключать какие-то библиотеки, которые находятся в каталогах, отличных от каталога проекта (общие библиотеки, которые используется в нескольких проектах одновременно). В этом случае компилятор при сборке не найдет Ваш “project_config.h”. Нужно ему подсказать, где искать. В PlatformIO это можно сделать с помощью опции -Iкаталог, например так:
[env] build_flags = -Isrc
Вуаля. При компиляции библиотек компилятор обязательно заглянет в каталог src, найдет Ваш файл “project_config.h” и подключит его к коду.
А вот в Arduino IDE я не нашел другого выхода, кроме как свалить все в одну кучу и хорошенько перемешать, то есть скопировать все библиотеки в каталог проекта. Впрочем, я не сильно и старался.
Примеры
Примеры для различных платформ и фреймворков Вы можете найти в архиве с исходниками (см. ссылку в конце статьи) в каталоге examples.
#include "Arduino.h" #include "project_config.h" #include "rLog.h" void setup() { // put your setup code here, to run once: Serial.begin(19200); Serial.println(); rlog_v("DEMO", "Test message, level: %s", "VERBOSE"); rlog_d("DEMO", "Test message, level: %s", "DEBUG"); rlog_i("DEMO", "Test message, level: %s", "INFORMATION"); rlog_w("DEMO", "Test message, level: %s", "WARNING"); rlog_e("DEMO", "Test message, level: %s", "ERROR"); } void loop() { // put your main code here, to run repeatedly: // nothing }
Результаты компиляции для различных платформ:
Arduino Nano + Framework Arduino
ESP8266 + Framework Arduino
ESP32 + Framework Arduino
ESP32 + Framework ESP-IDF
Скачать библиотеку:
Библиотека бесплатная, Open source, скачать ее можно с GitHub с помощью кнопки ниже. В архиве Вы также найдете примеры скетча для Arduino IDE и PlatformIO – загляните в папку examples.
✳️ Другие мои библиотеки для PlatformIO и ArduinoIDE
💠 Полный архив статей вы найдете здесь
Пожалуйста, оцените статью:
Спасибо! Очень помогает эта библиотека.
Отличная библиотека, взял на вооружение.
Но как напечатать ip адрес, маску, mac – адрес?
Добрый день. Зависит от того, откуда вы берете данные.
Например можно так:
esp_netif_ip_info_t local_ip = wifiLocalIP();
if (local_ip.ip.addr != 0) {
uint8_t * loc_ip = (uint8_t*)&(local_ip.ip);
rlog_d(logTAG, “%d.%d.%d.%d”, loc_ip[0], loc_ip[1], loc_ip[2], loc_ip[3]);
}
Столкнулся со странной проблемой. Никак не хочет библиотека показывать число типа float.
Переменная “a” несколько раз преобразуется по ходу вычислений. Вывожу лог:
rlog_d(“func()”, “1 a = %f”, a);
Serial.print(“1 a = “);Serial.println(a);
00:00:00 [D] func() :: 1 a = ?
1 a = 9108906.00
00:00:00 [D] func() :: 2 a = ?
2 a = -9.00
00:00:00 [D] func() :: 3 a = ?
3 a = -0.09
Arduino IDE 1.8.19, плата Arduino Nano (Atmega 328p)
Хм, много раз выводил float и double – никаких проблем не наблюдалось. Может у вас все таки не float, а что-то иное?
Ну так специально рядом обычный сериал принт выводит как надо.
Serial.print использует принципиально другой метод преобразования числа в строку. Они НЕ МОГУТ быть одинаковыми.
Данная библиотека использует универсальную форматную vsnprintf, которая, кстати, входит в стандарт Си – https://learn.microsoft.com/ru-ru/cpp/c-runtime-library/vprintf-functions?view=msvc-160
Я подозреваю, что для вашего МК она может быть реализована криво – вопросы не ко мне, а к производителю фреймворка для вашего МК. На ESP32 она работает отлично.
Я так же подозреваю, что вы использовали какой-то недопустимый тип данных под переменную. Serial.print пофиг на типы данных, она проглотит все. А вот vsnprintf – далеко не без разницы.
Я сотни раз использовал вывод чисел с плавающей запятой – и все всегда работало. Впрочем, у меня сейчас даже нет указанного вами контроллера, чтобы проверить ваши слова – я все раздал друзьям или использовал в проектах. Остались только ESP. Да и нет там разницы особой – можете сами заглянуть в исходники:
// And finally we can print a message
#if __RLOG_SERIAL__
Serial.print(temp);
#else
printf(“%s”, temp);
#endif
Я не понимаю, что такое “недопустимый тип данных”. Ну вот кусок кода, который с проблемой.
uint32_t sumX = 0;
uint32_t sumY = 0;
uint32_t sumX2 = 0;
uint32_t sumXY = 0;
for (int i = 0; i < 6; i++) { // для всех элементов массива
sumX += _time_array[i];//=15 сумма значений оси X
sumY += _pressure_array[i];//сумма значений оси Y
sumX2 += _time_array[i] * _time_array[i];//квадрат суммы значений оси X
sumXY += _time_array[i] * _pressure_array[i];
}
rlog_v("func()", "sumX = %d", sumX);
rlog_v("func()", "sumY = %lu", sumY);
rlog_v("func()", "sumX2 = %d", sumX2);
rlog_v("func()", "sumXY = %lu", sumXY);
float a = 0;
a = 6.0 * (float)sumXY; // расчёт коэффициента наклона прямой
rlog_d("func()", "1 a = %f", a);
Serial.print("1 a = ");Serial.println(a);
a = a – (float)sumX * (float)sumY;
rlog_d("func()", "2 a = %f", a);
Serial.print("2 a = ");Serial.println(a);
a = a / (6.0 * (float)sumX2 – (float)sumX * (float)sumX);
rlog_d("func()", "3 a = %f", a);
Serial.print("3 a = ");Serial.println(a);
int16_t delta = (int)floor(a * 6); // расчёт изменения давления
rlog_d("func()", "delta = %d", delta);
Вывод лога взял чуть заранее, чтоб было понято что за числа.
00:00:00 [D] func() :: _pressure_array[0] = 102075
00:00:00 [D] func() :: _pressure_array[1] = 102075
00:00:00 [D] func() :: _pressure_array[2] = 102075
00:00:00 [D] func() :: _pressure_array[3] = 102075
00:00:00 [D] func() :: _pressure_array[4] = 102075
00:00:00 [D] func() :: _pressure_array[5] = 102072
00:00:00 [V] func() :: sumX = 15
00:00:00 [V] func() :: sumY = 612447
00:00:00 [V] func() :: sumX2 = 55
00:00:00 [V] func() :: sumXY = 1531110
00:00:00 [D] func() :: 1 a = ?
1 a = 9186660.00
00:00:00 [D] func() :: 2 a = ?
2 a = -45.00
00:00:00 [D] func() :: 3 a = ?
3 a = -0.43
00:00:00 [D] func() :: delta = -3
Что я делаю не так?
Возможно. Вы попробуйте vrintf(). Она правильно обрабатывает такие переменные?