0

I'm writing a new serial library (eRCaGuy_Peer2Peer) to allow any 2 pins on an Arduino to be used for peer-to-peer type communication between Arduinos, withOUT interrupts or timers/counters.

Currently SPI, Serial, and I2C are pin-specific, and SoftwareSerial requires pin change interrupts. My implementation will solve these problems.

However, I first am trying to understand some things. As I study SoftwareSerial, I see it inherits from the Stream class, which inherits from the Print class.

I don't understand how Print's write is implemented. This is from Print.h:

virtual size_t write(uint8_t) = 0;
size_t write(const char *str) {
  if (str == NULL) return 0;
  return write((const uint8_t *)str, strlen(str));
}
virtual size_t write(const uint8_t *buffer, size_t size);
size_t write(const char *buffer, size_t size) {
  return write((const uint8_t *)buffer, size);
}

This is from Print.cpp:

/* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size)
{
  size_t n = 0;
  while (size--) {
    if (write(*buffer++)) n++; //<--where is this write defined?
    else break;
  }
  return n;
}

My Questions:

  1. What does this mean? I've never seen anything like this before: virtual size_t write(uint8_t) = 0; Why the = 0?
  2. What does it mean/how does it work defining functions in .h files? I've never done this before: ex:
    size_t write(const char *str) { if (str == NULL) return 0; return write((const uint8_t *)str, strlen(str)); }

  3. Where is the write command I've marked above defined? Is Print::write() calling itself recursively?

  4. Also, why does SoftwareSerial.h call using Print::write; (shown below) when it already re-implements the virtual write function just above that?

virtual size_t write(uint8_t byte); virtual int read(); virtual int available(); virtual void flush(); operator bool() { return true; }

using Print::write;
4
  • I found the answer to my first question: the ` = 0` means it is a "pure virtual function", which means derived classes MUST implement it: stackoverflow.com/questions/2652198/… Commented May 30, 2017 at 20:44
  • Will it work ? Do you have some explanation online to tell how you are going to make it ? When you are going to use micros or millis for synchronous data transfer, it might be limitied to 1 byter per 10 seconds. For example an Arduino with Ethernet shield that is retrieving a file from a SD card takes a while. When no timing will be used, you are going to need more pins for handshake signals.
    – Jot
    Commented May 31, 2017 at 0:44
  • Jot, follow my work live at my link above. I'm working on it as we speak. I have a clever plan. I believe I can make it happen. I don't expect it to be slow either. I'm expecting better than 115200 baud, and turning off interrupts for only a handful of microseconds at a time. Commented May 31, 2017 at 1:50
  • That might work. I like the idea and I like that the TX is always output and RX is always input. However, like the other software serial libraries, it has its own problems. The main problem is that the transmitter disables the interrupts for an unknown time, since the receiver might be very busy or it might execute interrupts while data is being transferred. I suppose it can be used as if it is full-duplex at a higher level using buffers just like the other serial libraries that inherit the Stream class. At a low level it is only half-duplex since RX is the acknowledge.
    – Jot
    Commented May 31, 2017 at 14:36

2 Answers 2

2

write is implemented in the class that inherits the Print class - such as HardwareSerial.

There are a number of write functions, and they are overloaded, which means that they are all distinct functions identified using their parameters as well as their name (write(uint8_t) and write(uint8_t *, size_t) are different functions). The child class overrides the write(uint8_t) function that is pure virtual1 in the main Print class, but the other write functions with other parameter lists aren't overridden and are just "used".

The using Print::write tells it "the write functions to which I am referring are from the Print class."

Using-declaration introduces a member of a base class into the derived class definition, such as to expose a protected member of base as public member of derived. In this case, nested-name-specifier must name a base class of the one being defined. If the name is the name of an overloaded member function of the base class, all base class member functions with that name are introduced. If the derived class already has a member with the same name, parameter list, and qualifications, the derived class member hides or overrides (doesn't conflict with) the member that is introduced from the base class.

I'm not entirely sure why they feel they need to use it, since the write functon(s) in Print are public and thus exposed already.

What does it mean/how does it work defining functions in .h files? I've never done this before

Exactly the same as if you had defined the function in a .cpp file, but with a few minor differences, the most important one of which is that the compiler finds it easier to inline the function (or if you're really lucky eradicate the function altogether). It's great for simple functions such as setters and getters that just set or return the value of a variable. In such cases inlining is desirable, and the compiler may even be able to replace the entire function with just direct access to the variable (if it's clever enough).

Another important thing to note with functions in .h files is that you can only really do it with class member functions or static functions. Otherwise you end up with compilation errors complaining about multiple definitions if you use the header file in more than one place.


1: A Pure Virtual function (such as virtual size_t write(uint8_t) = 0;) is one which has to be implemented in the child class. Failure to implement it results in a compilation error. The pure virtual function defines part of the interface but doesn't implement the software for it - that is down to the child class to implement.

3
  • I think there's more to it than that. SoftwareSerial inherits Stream which inherits Print, so what other write could it possibly be referring to? ie: I think there's more to the using keyword here than what you stated. Commented May 30, 2017 at 20:02
  • This seems to shed some light: forum.arduino.cc/index.php?topic=302730.0 Commented May 30, 2017 at 20:13
  • There is more to it, yes, but I was on my phone, so couldn't go into detail. Two terms you need to learn all about: overloading and polymorphism.
    – Majenko
    Commented May 30, 2017 at 21:02
2

In the Print class the virtual size_t write (uint8_t)=0 DEFINES and names a function that accepts a byte and returns a size. ANY "virtual" function in a base class CAN be over-ridden in a derived class; however, the =0 declares that there is NO actual function in the base class -- the compiler sticks a null function pointer there. This is why pure virtual functions MUST be over-ridden in the derived (child) class.

The using Print::write; in the .h header file of your derived class tells the compiler that YOUR size_t DerCl::write (uint8_t val) {} should fill in that null pointer in the base class...this is why your write function must be declared exactly like the one in the base class. If it were not, then when the base class calls it, it would push too many values onto the stack, it would pop too many values off the stack, or it would mix up the parameters sent to the function, etc.

This "polymorphism" allows the base class (Print) to define an interface (for printing in this case) and simultaneously allows the derived class to determine where the resulting byte stream will be sent. Serial sends it to the USART's serial port. SoftSerial sends it to any two port pins (one Tx and one Rx). But you also can create classes to use SPI, TWI(i.e. CAN), a HDMC file, an LCD display, etc. And you can leverage the uniformity and the functionality of all of the Write(), print(), and println() functions that you're already familiar with by writing a single, simple size_t write(uint8_t) function for your derived class.

0

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.