-1

I am entirely new to Arduino and have not coded in C/C++ for quite a few years. I'm looking through a NeoMatrix library which extends upon NeoPixel class. That class extends Adafruit_GFX class which extends Print class. The NeoMatrix class provides a lot of logic for different configurations of NeoPixel strands. Adafruit_GFX provides a drawChar method and that's the last bit that's clear to me.

From the examples one is supposed to call print method, which is inherited from the Arduino core's Print class (as I understand this). I've been looking through the source file and for the life of me I cannot understand how this works. In the Print class there are no calls to anything that would print characters, I cannot see any of the methods overriden anywhere higher up in the Class inheritance hierarchy. Can you help me put on the write track on figuring out what's the call stack here - if I call a print method on Adafruit_NeoMatrix instance how does it finally print the text? I imagine there must be code somewhere which does call the drawChar method, checks the character width, advances the cursor - but I cannot seem to locate it :)

Here are the libraries in question: https://github.com/adafruit/Adafruit_NeoMatrix https://github.com/adafruit/Adafruit_NeoPixel https://github.com/adafruit/Adafruit-GFX-Library

3
  • 1
    please edit your post and add a link to the library
    – jsotola
    Apr 7 at 4:45
  • To be precise, it's C++ not C.
    – Nick Gammon
    Apr 7 at 5:58
  • Linked the libs. It is C++ true.
    – theadam
    Apr 7 at 7:41

2 Answers 2

4

Nick Gammon's answer is correct. You left a comment on it indicating you needed some detail:

... The libraries I've mentioned do not seem to override any of the Print class's methods.

You will see that it does as linked and described in #3 below. And that is the analogous part to the virtual size_t write (const byte c) shown in Nick Gammon's answer.

I'm also not sure how just overwriting write as you've mentioned works.

The write(uint8_t) in Print is pure virtual. Full treatment of (pure) virtual functions would fill chapter (or maybe two) in a C++ book. But briefly: This is a function declaration in Print without an implementation. It's essentially a hole intentionally left in Print for a byte-writing-type-thing to be filled in by a derived class, which in this case is Adafruit_GFX. If you try to instantiate a variable of type Print (literally like Print myPrint;) you will see an error mentioning write(uint8_t) because it is still yet missing from the complete type. If you know what a callback is using a function pointer, a pure virtual function is a bit like that. When Adafruit_GFX overrides Print and supplies its own definition for write(uint8_t) (as you'll see below) it effectively registers that as a callback that is used anytime anything tries to use Print's write(uint8_t) on the Adafruit_GFX object (or in this case, that part of the larger Adafruit_NeoMatrix object). That includes the other parts of Print (the other overloads of print/println that understand how to numbers or whatever else) that use write(uint8_t) directly or indirectly. If you really want to know more about what that looks like in typical generated code look up how vtables work.

I call a print method, something should iterate through all the characters and call the write methods - I'm looking for that code, trying to understand the whole flow of the printing process.

You can dig through the code and find all the places that it does that. Here's one example of far too many. It's an internal helper function for printing an integer that you'll find used from some of the publicly accessible print() functions that deal with numbers. As you can see, it uses write(uint8_t). As you dig around in whatever core, you will find that everything funnels toward calls to that function.

But you can also just observe that the only pure virtual function in Print is the one for writing individual bytes. You'll notice that there is also nothing similar; e.g. there's no function to register a callback. So, in the general case of Print-capable things (LiquidCrystal, SD.h files, Serial, etc) the pure virtual single-byte handling overload of write() is the necessary and sufficient thing for making a class derived from Print. It can optionally have specific behavior around flushing. It can optionally have an override of write that handles multiple characters; that would reduce call overhead for multiple characters. If you spend a few hours and track down all of the calls in ArduinoCore-API's or ArduinoCore-AVR's Print internals you will find that they all boil down do calls to the pure virtual write(uint8_t) (in the absence of a subclass overriding the block-form of write). So this one (or nearly one) pure virtual function simply is the mechanism by which the generic stuff in Print causes the use-case-specific stuff in the derived class to happen.

Your specific case

The below shows a sequence of events including where the implementation of write(uint8_t) is specific to your case.

Diagram of the function call interactions which are described in the following list.

  1. There's only so much to say about this. You're calling some print function. So you're calling directly into the Print code, not anything in Adafruit's code yet. But, keep in mind you're doing this on an object for which Print has been completed with a definition of write(uint8_t) inside Adafruit_GFX, linked below.

  2. There's not note specific thing to understand this part except to say that there are only a handful of pure virtual functions in Print. The only one of note here is write(uint8_t). It's technically possible for derived from Print to implement the block-accepting form of write(). But Adafruit_GFX like most things does not do that and instead relies on the default implementation of the block-accepting overload for write() to defer to overload that handle individual bytes.

  3. Here Adafruit_GFX is pushing the work from its write(uint8_t) to drawChar() I've only highlight one place where it does that call.

  4. drawChar() offloading work to writePixel()

  5. writePixel() calls drawPixel(). There's a note above that apparently speaks to a potential distinction between write vs draw for things that override writePixel. NeoMatrix does not override writePixel(). So, no distinction.

  6. drawPixel() is pure virtual in Adafruit_GFX. Its implementation for NeoMatrix calls into the NeoPixel class.

  7. The sketch call to show() just goes right into the NeoPixel part o the object to send the wire payload per usual. It's not overridden by NeoMatrix to do anything additional.

1
  • Nicely explained, very good!
    – Nick Gammon
    Apr 7 at 20:54
1

If you derive a class from the Print class then all you have to do is override the write function to write to the device in whatever way you want. For example, using SPI, I2C, Neopixels, or whatever.

This lets you leverage off the things Print lets you do (print integers, characters, strings) and all you have to do is supply a function that outputs a single character.

Here's an example from my web page about doing Arduino debugging:

 class tSPIdebug : public Print
  {
  public:
    virtual size_t write (const byte c)  
      { 
      digitalWrite(SS, LOW); 
      SPI.transfer (c); 
      digitalWrite(SS, HIGH); 
      return 1;
      }  // end of tSPIdebug::write
  }; // end of tSPIdebug

Notice how I've made a class derived from Print and all it does is override how write handles output. All the rest then is handled by Print and all my class has to do is actually do the printing of one byte.

2
  • I understand how this is supposed to work. The libraries I've mentioned do not seem to override any of the Print class's methods. I'm also not sure how just overwriting write as you've mentioned works. I call a print method, something should iterate through all the characters and call the write methods - I'm looking for that code, trying to understand the whole flow of the printing process.
    – theadam
    Apr 7 at 7:44
  • Timemage has explained in a lot more detail. Adafruit_GFX.cpp implements size_t Adafruit_GFX::write(uint8_t c) which is the very function (write) that I mentioned. That class is derived from Print: class Adafruit_GFX : public Print.
    – Nick Gammon
    Apr 7 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.