Adafruit_NeoMatrix / Adafruit_GFX / Класс печати — как здесь работает печать текста
Я совсем новичок в Arduino и не писал код на C/C++ уже несколько лет. Я просматриваю библиотеку NeoMatrix, которая расширяет класс NeoPixel. Этот класс расширяет класс Adafruit_GFX
, который расширяет класс Print
. Класс NeoMatrix предоставляет много логики для различных конфигураций нитей NeoPixel. Adafruit_GFX предоставляет метод drawChar
, и это последнее, что мне понятно.
Из примеров следует, что нужно вызвать метод print, который унаследован от класса Print ядра Arduino (насколько я понимаю). Я просматривал исходный файл и, хоть убей, не могу понять, как это работает. В классе Print
нет вызовов чего-либо, что могло бы печатать символы, я не вижу ни одного метода, переопределенного где-либо выше в иерархии наследования классов. Можете ли вы помочь мне включить запись, чтобы выяснить, что здесь за стек вызовов — если я вызываю метод print
на экземпляре Adafruit_NeoMatrix
, как он в итоге печатает текст? Я думаю, где-то должен быть код, который вызывает метод drawChar
, проверяет ширину символа, перемещает курсор — но я не могу его найти :)
Вот о каких библиотеках идет речь: https://github.com/adafruit/Adafruit_NeoMatrix https://github.com/adafruit/Adafruit_NeoPixel https://github.com/adafruit/Adafruit-GFX-Library
@theadam, 👍-1
2 ответа
Лучший ответ:
Ответ Ника Гэммона правильный. Вы оставили комментарий к нему, указав, вам нужны были некоторые подробности:
... Библиотеки, которые я посетил, Упомянутые методы, похоже, не переопределяют ни один из методов класса Print.
Вы увидите, что это делает то, что связано и описано в #3 ниже. И это аналогичная часть virtual size_t write (const byte c)
, показанная в ответе Ника Гэммона.
Я также не уверен, как просто перезаписать запись, как вы упомянули. работает.
write(uint8_t)
в Print
является чисто виртуальным. Полное рассмотрение (чисто) виртуальных функций заполнило бы главу (или, может быть, две) в книге по C++. Но вкратце: это объявление функции в Print
без реализации. По сути, это дыра, намеренно оставленная в Print
для типа-байт-записи-что-либо, которая должна быть заполнена производным классом, который в данном случае является Adafruit_GFX
. Если вы попытаетесь создать экземпляр переменной типа Print
(дословно как Print myPrint;
), вы увидите ошибку, в которой упоминается write(uint8_t)
, поскольку он все еще отсутствует в полном типе. Если вы знаете, что такое обратный вызов с использованием указателя функции, то чистая виртуальная функция немного похожа на это. Когда Adafruit_GFX
переопределяет Print
и предоставляет свое собственное определение для write(uint8_t)
(как вы увидите ниже), он фактически регистрирует это как обратный вызов, который используется всякий раз, когда что-либо пытается использовать write(uint8_t)
Print
на объекте Adafruit_GFX
(или в этом случае на той части большего объекта Adafruit_NeoMatrix
). Это включает в себя другие части Print
(другие перегрузки print
/println
, которые понимают, как работать с числами или чем-то еще), которые используют write(uint8_t)
напрямую или косвенно. Если вы действительно хотите узнать больше о том, как это выглядит в типичном сгенерированном коде, посмотрите, как работают vtables.
Я вызываю метод печати, что-то должно перебрать все символы и вызовите методы записи - я ищу этот код, пытаясь понять весь процесс печати.
Вы можете покопаться в коде и найти все места, где это происходит. Вот один пример из f ar too many. Это внутренняя вспомогательная функция для печати целого числа, которую вы найдете в некоторых общедоступных функциях print()
, работающих с числами. Как вы можете видеть, она использует write(uint8_t)
.
Если вы покопаетесь в ядре, вы обнаружите, что все направлено на вызовы этой функции.
Но вы также можете просто заметить, что единственная чисто виртуальная функция в Print
— это функция для записи отдельных байтов.
Вы заметите, что нет ничего похожего; например, нет функции для регистрации обратного вызова. Таким образом, в общем случае вещей, поддерживающих Print (файлы LiquidCrystal
, SD.h
, Serial
и т. д.), чистая виртуальная перегрузка обработки одного байта write()
является необходимой и достаточной вещью для создания класса, производного от Print
.
Он может опционально иметь определенное поведение вокруг очистки. Он может опционально иметь переопределение записи, которое обрабатывает несколько символов; это уменьшит накладные расходы на вызовы для нескольких символов. Если вы потратите несколько часов и отследите все вызовы в ArduinoCore-API или внутренностях Print ArduinoCore-AVR, вы обнаружите, что все они сводятся к вызовам do чистого виртуального write(uint8_t)
(при отсутствии подкласса, переопределяющего блочную форму write
). Так что эта одна (или почти одна) чисто виртуальная функция просто является механизмом, с помощью которого общие вещи в Print
вызывают выполнение вещей, специфичных для конкретного варианта использования, в производном классе.
Ваш конкретный случай
Ниже показана последовательность событий, включая случаи, когда реализация write(uint8_t)
специфична для вашего случая.
Сказать об этом можно только так много. Вы вызываете некоторую функцию
print
. Таким образом, вы вызываете напрямую в кодPrint
, а не что-либо в коде Adafruit. Но имейте в виду, что вы делаете это с объектом, для которогоPrint
был завершен с определениемwrite(uint8_t)
внутриAdafruit_GFX
, ссылка на который приведена ниже.Нет ничего особенного в понимании этой части, за исключением того, что в
Print
есть только несколько чисто виртуальных функций. Единственная заслуживающая внимания функция здесь — этоwrite(uint8_t)
. Технически возможно реализовать формуwrite()
, принимающую блоки. НоAdafruit_GFX
, как и большинство вещей, этого не делает и вместо этого полагается на реализацию по умолчанию перегрузки, принимающей блоки, дляwrite()
, чтобы передать ее перегрузке, обрабатывающей отдельные байты.Здесь Adafruit_GFX переносит работу из своего
write(uint8_t)
вdrawChar()
. Я выделил только одно место, где он делает этот вызов.drawChar()
передача работы вwritePixel()
writePixel()
вызываетdrawPixel()
. Выше есть примечание, которое, по-видимому, говорит о потенциальном различии между записью и рисованием для вещей, которые переопределяютwritePixel
. NeoMatrix не переопределяетwritePixel()
. Так что никакого различия.drawPixel()
является чисто виртуальным в Adafruit_GFX. Его реализация дляNeoMatrix
вызывает классNeoPixel
.Вызов скетча
show()
просто идет прямо в часть NeoPixel объекта, чтобы отправить полезную нагрузку провода, как обычно. Он не переопределяется NeoMatrix для выполнения чего-либо дополнительного. р>
Если вы наследуете класс от класса Print
, то все, что вам нужно сделать, это переопределить функцию write
для записи на устройство любым удобным вам способом. Например, с помощью SPI, I2C, Neopixels или чего-то еще.
Это позволяет вам использовать возможности Print (печатать целые числа, символы, строки), и все, что вам нужно сделать, это предоставить функцию, которая выводит один символ.
Вот пример с моей веб-страницы об отладке Arduino:
class tSPIdebug : public Print
{
public:
virtual size_t write (const byte c)
{
digitalWrite(SS, LOW);
SPI.transfer (c);
digitalWrite(SS, HIGH);
return 1;
} // конец tSPIdebug::write
}; // конец tSPIdebug
Обратите внимание, как я создал класс, производный от Print
, и все, что он делает, это переопределяет, как write
обрабатывает вывод. Все остальное затем обрабатывается Print
, и все, что должен сделать мой класс, это фактически выполнить печать одного байта.
- Управлять несколькими полосками WS2812B с разным количеством светодиодов.
- Светодиодная лента WS2812B + First Pixel продолжает ломаться
- Использование кольца NeoPixel без Arduino
- Странное поведение со светодиодной лентой WS2812B RGB
- Какая хорошая альтернатива Arduino Nano, которую можно использовать с Neopixels?
- Как сгладить переход на другой цвет NEOPIXELS
- Как запрограммировать кнопку на цвет для полоски неопикселей?
- Реструктурировать код для многозадачности Neopixel + ИК-пульт + ардуино