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

, 👍-1


2 ответа


Лучший ответ:

4

Ответ Ника Гэммона правильный. Вы оставили комментарий к нему, указав, вам нужны были некоторые подробности:

... Библиотеки, которые я посетил, Упомянутые методы, похоже, не переопределяют ни один из методов класса 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) специфична для вашего случая.

Схема взаимодействий вызовов функций, описанных в следующем списке.

  1. Сказать об этом можно только так много. Вы вызываете некоторую функцию print. Таким образом, вы вызываете напрямую в код Print, а не что-либо в коде Adafruit. Но имейте в виду, что вы делаете это с объектом, для которого Print был завершен с определением write(uint8_t) внутри Adafruit_GFX, ссылка на который приведена ниже.

  2. Нет ничего особенного в понимании этой части, за исключением того, что в Print есть только несколько чисто виртуальных функций. Единственная заслуживающая внимания функция здесь — это write(uint8_t). Технически возможно реализовать форму write(), принимающую блоки. Но Adafruit_GFX, как и большинство вещей, этого не делает и вместо этого полагается на реализацию по умолчанию перегрузки, принимающей блоки, для write(), чтобы передать ее перегрузке, обрабатывающей отдельные байты.

  3. Здесь Adafruit_GFX переносит работу из своего write(uint8_t) в drawChar(). Я выделил только одно место, где он делает этот вызов.

  4. drawChar() передача работы в writePixel()

  5. writePixel() вызывает drawPixel(). Выше есть примечание, которое, по-видимому, говорит о потенциальном различии между записью и рисованием для вещей, которые переопределяют writePixel. NeoMatrix не переопределяет writePixel(). Так что никакого различия.

  6. drawPixel() является чисто виртуальным в Adafruit_GFX. Его реализация для NeoMatrix вызывает класс NeoPixel.

  7. Вызов скетча show() просто идет прямо в часть NeoPixel объекта, чтобы отправить полезную нагрузку провода, как обычно. Он не переопределяется NeoMatrix для выполнения чего-либо дополнительного.

  8. р>
,

1

Если вы наследуете класс от класса 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, и все, что должен сделать мой класс, это фактически выполнить печать одного байта.

,