SmallSerial. No Magic Numbers. Works on Uno 16Mhz and Attiny 1Mhz

I want to be able to read serial data received via an IR detector on an Attiny operating at 1Mhz and I got interested in this Thread SoftwareSerial magic numbers - Libraries - Arduino Forum about SoftwareSerial which "out of the box" only works at 8MHz and above.

SoftwareSerial uses various tables of "magic numbers" to define the different times needed for different baudrates and @robtillart had figured out a formula as an alternative to the tables.

It seemed to me that there must surely be a simple "formula" that could deal with any baudrate and any CPU frequency (always recognizing that high baudrates and low MHz are incompatible). For low baudrates it is possible to use micros() for timing but as the baudrates increase the 4 microseconds (usecs) granularity of micros() becomes a problem. That got me thinking that any code loop can be its own clock if you carefully measure how long it takes to run.

The attached file BlinkSmallSerial.zip contains SmallSerial.ino and BlinkSmallSerial.ino which together comprise a little demo sketch. BlinkSmallSerial will work on an Uno at 38400 baud and on the 1MHz Attiny at 2400 baud WITHOUT ANY CHANGES. (In case it's not obvious 38400 / 16 = 2400). The demo is designed to work with the IDE Serial Monitor and an FTDI cable (or equivalent). If you send the character '0' it will respond with some text. If you send one of the characters '1' to '9' it will flash an led on Pin 2 - 2 flashes for the character '2', 3 flashes for '3' etc. Note that Arduino Pin 2 is physical pin 7 on an Attiny.

The demo should be sufficient to illustrate how SmallSerial.ino can be included in your own project.

SmallSerial.ino is not intended to be a general purpose replacement for SoftwareSerial. Rather it is intended for use in a situation where small amounts of data need to be exchanged between an Arduino and a PC or another Arduino and where small size and simplicity have a high value. (I don't know whether the timing concept in SmallSerial could be adopted into SoftwareSerial to dispense with the magic numbers and make it more broadly applicable).

My goal was to keep the code in SmallSerial easy to understand and small. It doesn't use any interrupts. It should be easy to understand and adapt. It seems to operate comfortably up to 115200 baud on my 16MHz Uno. It can operate at any baudrate (specified in the #defines) and it can use different baudrates for send and receive. As written it expects Tx on pin8 and Rx on pin9 on the Uno or Pins 5 and 6 on the Attiny45. This is to minimize the amount of code and maximize the speed. If people need to change the speed-sensitive code (the routines recvBits() and sendBits()) they will need to recalibrate the speed and I have also included a sketch to do that.

The key to SmallSerial is the time it takes for the loops in recvBits() and sendBits() to run. Their speed is measured in the sketch CalibrateSmallSerial.ino and its associated file TestSpeed.ino that are included in CalibrateSmallSerial.zip. To use CalibrateSmallSerial you must add SmallSerial.ino to the project (it would not fit within the 4k upload limit if I included it in the zip file).

The calibration has already been done for this version of SmallSerial.ino and the loop speeds are included in the lines #define sendShortLoop1024 386 and #define sendLongLoop1024 2055 (and their equivalents in the recv routine). The numbers 386 and 2055 are the number of microseconds for 1024 iterations of the short and long loops. In other words 0.377usecs and 2.007usecs. These values are appropriate for a 16MHz CPU and pro-rata adjustments should work at different CPU speeds. For example 386 at 16Mhz should be 193 at 8Mhz or 24 at 1MHz. I suspect calibration needs to be done at 16Mhz to get sufficient resolution.

I have made no attempt to package SmallSerial.ino into a library as that seems to contradict the purpose of keeping it small and simple, and easy to uderstand and modify. It does mean there is a slight risk of conflicting global variable names, but the user should be able to deal with that as the code is very short.

Comments and observations are welcome.

...R

BlinkSmallSerial.zip (3.02 KB)

CalibrateSmallSerial.zip (2.35 KB)

This is a great peace of code. Almost exactly what I needed. Thank you! It's amazing I'm the first one to comment, though.

If I wanted to use the Rx part to read from 2 pins independently (I'm connecting a single master Arduino to read from 2 "slaves"), I need to change the port/pin for at least one of them. Can you shed some light on using a pin different than 9?

It's a long time since I wrote it or used it. I have had a quick look at the code and I don't think there is any requirement to use any specific pins - but you will have to figure out the appropriate Port and bit within the Port.

You may also be interested in the slightly more sophisticated Yet Another Software Serial which uses an interrupt pin (which restricts the pins it can use) and Hardware Timer2 which means it is not dependent on the number of clock cycles in a loop of code.

,,,R

Thanks for quick reply! Well, I liked your code because it does not abuse interrupts, which I already use for other purposes. So I'd rather stick with it, and not Yet Another Software Serial or Timer2.

Any clues to start with when figuring out which ports and bits to use?

Also, you wrote you chose those ports/pins "to minimize the amount of code and maximize the speed", so how changing the port or bits could slow things down?

All the info about Ports and pins is in a combination of the relevant Atmega datasheet and the pin-mapping for the Arduino board you are using.

I did not mean that the specific pins I have chosen are important - just the way I am accessing them using port manipulation rather than digitalRead() and digitalWrite(). If you decide to use a different pin and or different port it should not affect the performance as long as you don't add or subtract any code in the critical loops.

...R

Thank you. This is really helpful. Following your advice, I found the info on ports and pins in Arduino Reference - Arduino Reference.