AFSoftwareSerial w/ internal clock 8MHz

I'm working on a project with low-voltage devices to be put in nature:
http://absences.sofianaudry.com/

I would like to implement a network of objects able to share information with several input/output. So I need software serial. However, I find the SoftwareSerial library from Arduino not really interesting as it doesn't have any buffer/interrupt.

I discovered and tested the AFSoftwareSerial from ladyada and it suits my needs perfectly except for one thing: I run with low voltage, so I use ATMega168V10 chips with internal clock so it runs at 8 MHz (same as lilypad). The AFSoftwareSerial is hardcoded very specifically to run at 16MHz.

In one of her replies, ladyada suggests it's possible to implement it.

Looking at AFSoftwareSerial.cpp I found out the critical section:

#if (F_CPU == 16000000)
void whackDelay(uint16_t delay) {
  uint8_t tmp=0;

  asm volatile("sbiw    %0, 0x01 \n\t"
           "ldi %1, 0xFF \n\t"
           "cpi %A0, 0xFF \n\t"
           "cpc %B0, %1 \n\t"
           "brne .-10 \n\t"
           : "+r" (delay), "+a" (tmp)
           : "0" (delay)
           );
}
#endif

I decided to create this post to address this specific issue. I don't know much about ASM code so I don't know how to code it for F_CPU=8000000. Any suggestions?

There's actually another issue I just found out about regarding AFSoftwareSerial, which is that the receive buffer is static. Which means that if I got several AFSoftSerial objects they will all have the same buffer.

This is another issue that would need to get addressed IMHO since one of the main objectives behind SoftwareSerial is to be able to have several serial communication channels.

if someone has the time to implement all these changes, they will be wrapped into the project

I'm currently working on a patch for separate buffers for each receive pin. I'll send it here once done.

I would need help wrt whackDelay(uint16_t delay) however, to get it to work with 8MHz. I'm just not familiar with assembly so I don't know what you intended to do in that function (and why you needed to use assembly code, for that matter).

Thanks for the great code.

Here are the patches:

--- AFSoftSerial.h.orig.h      2008-04-05 14:37:46.000000000 -0400
+++ AFSoftSerial.h      2008-10-06 20:26:17.000000000 -0400
@@ -24,12 +24,21 @@
 
 uint16_t whackDelay2(uint16_t delay);
 
-static void recv(void);
-
 class AFSoftSerial
 {
+  public:
+    static char **_receive_buffers;
+    static uint8_t _receive_buffers_index[14];
+    static int _bitDelay;
+    static long _baudRate;
+    static void recv(int pin);
+
   private:
-    long _baudRate;
+    uint8_t _receivePin;
+    uint8_t _transmitPin;
+
+    char *_receive_buffer;
+//    char _receive_buffer[AFSS_MAX_RX_BUFF]; 
     void printNumber(unsigned long, uint8_t);
     
   public:
--- AFSoftSerial.cpp.orig.cpp      2008-04-05 15:25:50.000000000 -0400
+++ AFSoftSerial.cpp      2008-10-06 22:55:26.000000000 -0400
@@ -21,6 +21,8 @@
  * Includes
  ******************************************************************************/
 #include <avr/interrupt.h>
+#include <stdlib.h>
+#include <string.h>
 #include "WConstants.h"
 #include "AFSoftSerial.h"
 
@@ -33,13 +35,6 @@
 /******************************************************************************
  * Statics
  ******************************************************************************/
-static uint8_t _receivePin;
-static uint8_t _transmitPin;
-static int _bitDelay;
-
-static char _receive_buffer[AFSS_MAX_RX_BUFF]; 
-static uint8_t _receive_buffer_index;
-
 #if (F_CPU == 16000000)
 void whackDelay(uint16_t delay) { 
   uint8_t tmp=0;
@@ -55,43 +50,45 @@
 }
 #endif
 
-/******************************************************************************
- * Interrupts
- ******************************************************************************/
+char **AFSoftSerial::_receive_buffers = 0;
+uint8_t AFSoftSerial::_receive_buffers_index[14];
+int AFSoftSerial::_bitDelay = 0;
+long AFSoftSerial::_baudRate = 0;
 
-SIGNAL(SIG_PIN_CHANGE0) {
-  if ((_receivePin >=8) && (_receivePin <= 13)) {
-    recv();
-  }
-}
-SIGNAL(SIG_PIN_CHANGE2)
-{
-  if (_receivePin <8) {
-    recv();
-  }
-}
-
-
-void recv(void) { 
+void AFSoftSerial::recv(int pin) { 
   char i, d = 0; 
-  if (digitalRead(_receivePin)) 
+  if (digitalRead(pin)) 
     return;       // not ready! 
   whackDelay(_bitDelay - 8);
   for (i=0; i<8; i++) { 
     //PORTB |= _BV(5); 
     whackDelay(_bitDelay*2 - 6);  // digitalread takes some time
     //PORTB &= ~_BV(5); 
-    if (digitalRead(_receivePin)) 
+    if (digitalRead(pin)) 
       d |= (1 << i); 
    } 
   whackDelay(_bitDelay*2);
-  if (_receive_buffer_index >=  AFSS_MAX_RX_BUFF)
+  if (AFSoftSerial::_receive_buffers_index[pin] >= AFSS_MAX_RX_BUFF)
     return;
-  _receive_buffer[_receive_buffer_index] = d; // save data 
-  _receive_buffer_index++;  // got a byte 
+  AFSoftSerial::_receive_buffers[pin][AFSoftSerial::_receive_buffers_index[pin]] = d; // save data 
+  AFSoftSerial::_receive_buffers_index[pin]++;  // got a byte 
 } 
-  
 
+/******************************************************************************
+ * Interrupts
+ ******************************************************************************/
+
+SIGNAL(SIG_PIN_CHANGE0) {
+  for (int pin=8; pin<=13; pin++)
+    if (AFSoftSerial::_receive_buffers[pin])
+      AFSoftSerial::recv(pin);
+}
+SIGNAL(SIG_PIN_CHANGE2)
+{
+  for (int pin=0; pin<8; pin++)
+    if (AFSoftSerial::_receive_buffers[pin])
+      AFSoftSerial::recv(pin);
+}  
 
 /******************************************************************************
  * Constructors
@@ -99,16 +96,39 @@
 
 AFSoftSerial::AFSoftSerial(uint8_t receivePin, uint8_t transmitPin)
 {
-  _receivePin = receivePin;
-  _transmitPin = transmitPin;
-  _baudRate = 0;
+  setRX(receivePin);
+  setTX(transmitPin);
 }
 
 void AFSoftSerial::setTX(uint8_t tx) {
   _transmitPin = tx;
+  pinMode(_transmitPin, OUTPUT);
+  digitalWrite(_transmitPin, HIGH);
 }
+
 void AFSoftSerial::setRX(uint8_t rx) {
   _receivePin = rx;
+  if (!_receive_buffers) {
+    _receive_buffers = (char**)malloc(14 * sizeof(char*));
+    memset(_receive_buffers, 0, 14*sizeof(char*));
+  }
+  if (!_receive_buffers[_receivePin])
+    _receive_buffers[_receivePin] = (char*)malloc(AFSS_MAX_RX_BUFF * sizeof(char));
+  _receive_buffer = _receive_buffers[_receivePin];
+  _receive_buffers_index[_receivePin] = 0;
+
+  pinMode(_receivePin, INPUT); 
+  digitalWrite(_receivePin, HIGH);  // pullup!
+
+   if (_receivePin < 8) {
+     // a PIND pin, PCINT16-23
+     PCMSK2 |= _BV(_receivePin);
+     PCICR |= _BV(2);
+  } else if (_receivePin <= 13) {
+    // a PINB pin, PCINT0-5
+    PCICR |= _BV(0);    
+    PCMSK0 |= _BV(_receivePin-8);
+  } 
 }
 
 /******************************************************************************
@@ -117,12 +137,6 @@
 
 void AFSoftSerial::begin(long speed)
 {
-  pinMode(_transmitPin, OUTPUT);
-  digitalWrite(_transmitPin, HIGH);
-
-  pinMode(_receivePin, INPUT); 
-  digitalWrite(_receivePin, HIGH);  // pullup!
-
   _baudRate = speed;
   switch (_baudRate) {
   case 115200: // For xmit -only-!
@@ -143,17 +157,7 @@
     _bitDelay = 470; break;
   default:
     _bitDelay = 0;
-  }    
-
-   if (_receivePin < 8) {
-     // a PIND pin, PCINT16-23
-     PCMSK2 |= _BV(_receivePin);
-     PCICR |= _BV(2);
-  } else if (_receivePin <= 13) {
-    // a PINB pin, PCINT0-5
-    PCICR |= _BV(0);    
-    PCMSK0 |= _BV(_receivePin-8);
-  } 
+  }
 
   whackDelay(_bitDelay*2); // if we were low this establishes the end
 }
@@ -162,22 +166,22 @@
 {
   uint8_t d,i;
 
-  if (! _receive_buffer_index)
+  if (! _receive_buffers_index[_receivePin])
     return -1;
 
   d = _receive_buffer[0]; // grab first byte
   // if we were awesome we would do some nifty queue action
   // sadly, i dont care
-  for (i=0; i<_receive_buffer_index; i++) {
+  for (i=0; i<_receive_buffers_index[_receivePin]; i++) {
     _receive_buffer[i] = _receive_buffer[i+1];
   }
-  _receive_buffer_index--;
+  _receive_buffers_index[_receivePin]--;
   return d;
 }
 
 uint8_t AFSoftSerial::available(void)
 {
-  return _receive_buffer_index;
+  return _receive_buffers_index[_receivePin];
 }
 
 void AFSoftSerial::print(uint8_t b)
@@ -319,3 +323,4 @@
   for (; i > 0; i--)
     print((char) (buf[i - 1] < 10 ? '0' + buf[i - 1] : 'A' + buf[i - 1] - 10));
 }
+

Notice that these patches will only patch the "separate buffers" problem (which goes beyond the original scope of this topic).

The rest still needs to be addressed.

Here is a hack you can try, why not call softwareSerial.begin with double the baud rate, this will be pretty close to the correct speed at the slower baud rates.

If that doesn't work at the baud rate you need, or you want something a little less hacky, you could tweek the values for _bitDelay in the case statement in the begin() method of AFSoftwareSerial.cpp.

I like that :slight_smile: Very clever if it works.

This is also what is suggested here:
http://forums.ladyada.net/viewtopic.php?t=6793&sid=5c6c655ebd8e259c3407f25b58edc1b9

The post also contains a nice analysis of the assembly code.

I'm giving it a try right now, I'll keep you posted.

Unfortunately that link doesn't have a suggestion for changing the software so the hack isn't necessary.

If someone here wants to tinker, I suggest that the #if is removed from the asm code and put in case statement in the method that calculates _bitdelay. At the slower baud rates, _bitdelay will be half its current value on an 8mhz clock. As the baud rate increases it will be increasingly less than half (as the time delay of digitalWrite becomes more significant).

This could be a fun project for someone with an oscilloscope and an 8mhz board.

I just did exactly that (removed the #if) and added the following line:

  // Adjust the _bitDelay.
  _bitDelay = ((unsigned long)_bitDelay * F_CPU) / 16000000L;

in method begin.

It works fine with an internal clock @ 8 MHz! I am even able to communicate without any problem with Arduino Diecimila boards (that run at 16 MHz).

Good to hear you have it working, but I expect that it will fail at high baud rates because the delay will need to be reduced to compensate for the 4us or more it takes digitalWrite when running on an 8mhz clock. I think some of the higher baud case statements may need a #if statment to compensate for this, but you may need a scope to find the best values.

How high a baud rate have you tested it?

You seem to be right in the problem you mention, I believe I'm having problems of that kind now. I need to run some more tests. I'll keep you posted.

tatien: can you post a patched version of the library (with multiple buffer support)? The patches you posted didn't seem to apply cleanly to the the version of the library I downloaded.

I have put the full version online attached to this post:

http://absences.sofianaudry.com/en/node/18

Coming soon: I will put the project on a SVN.

Awesome, thanks. I'm going to take a look at replacing the current SoftwareSerial library with this one.

IF so, may I make the suggestion that it be modified slightly to inherit from Print before incorporation? That way we don't have to duplicate the formatting code that Print already provides, especially if/when floating point is added to Print. It would be nice if the HW and SW serial interfaces were identical.

Mikal

Good point. :slight_smile:

If you're feeling bored and want to do it, that would be fine too. Otherwise, I'll get to it at some point.

I'd be delighted! Thanks.

Mikal

@ladyada and @tatien: May I ask why AFSoftwareSerial seems to support only pins 0-13 (and not 14-19 -- the ones marked "analog in")?

   if (_receivePin < 8) {
     // a PIND pin, PCINT16-23
     PCMSK2 |= _BV(_receivePin);
     PCICR |= _BV(2);
  } else if (_receivePin <= 13) {
    // a PINB pin, PCINT0-5
    PCICR |= _BV(0);    
    PCMSK0 |= _BV(_receivePin-8);
  }

@tatien: Can you explain why all instances of your modified AFSoftwareSerial must share the same baud rate? Is there a physical reason for that, or is it just a matter of convenience?

Thank you,

Mikal