I'm running the Arduino stack on the hardware, more specific the arduino 1.0 release.
Now the atmega1280 (or atmega2560) has 4 hardware serial devices (called Serial, Serial1, Serial2, Serial3 in arduino) so using multiple serial devices is easy eh?
Not really.
For one use case I have a device connected on Serial1 at 57600 baud.
I want to be able to route serial traffic from that device to the Serial device connected to the PC.
Sounds easy enough, the follow arduino sketch does the job:
void setup(){
Serial.begin(57600);
Serial1.begin(57600);}
void loop() {
// from external device to serial console
if (Serial1.available() > 0) {
byte incomingByte = Serial1.read();
Serial.write(incomingByte);
}
// from serial console to external device
if (Serial.available() > 0) {
byte incomingByte = Serial.read();
Serial1.write(incomingByte);
}
}
Thing is, for short messages this works fine. However for longer messages only one direction is stable, the direction from the external device (Serial1) to the serial port (Serial).
I could run the serial port (Serial) at a slower speed then the device (Serial1). However then the other way around will start to drop bytes!
Let's have a look at HardwareSerial.cpp as provided in the arduino distribution.
It seems to work with an RX and TX buffer for each hardware serial device.
#define SERIAL_BUFFER_SIZE 64
Maybe that buffer is not large enough. The AtMega1280 I'm using has 8K SRAM and thats quite some more then I need. Let's double the buffer.
#define SERIAL_BUFFER_SIZE 128
Surely the behavior is somewhat better, but it is still dropping bytes in one direction.
Let's have a look at the write function.
size_t HardwareSerial::write(uint8_t c){
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
// ???: return 0 here instead?
while (i == _tx_buffer->tail)
;
_tx_buffer->buffer[_tx_buffer->head] = c;
_tx_buffer->head = i;
sbi(*_ucsrb, _udrie);
return 1;}
Look and behold:
while (i == _tx_buffer->tail) ;
it's a busy loop! If Serial1's transfer buffer is full the entire micro-controller will block and this keeps happening all the time.
The solution I introduced is only move data from one serial device to the other when both are ready. One should be readable (available() in arduino jargon) and the other should be writeable.
A writeable function is not available in Arduino so I added the following method to the HardwareSerial code:
bool HardwareSerial::writeable(){
int i = (_tx_buffer->head + 1) % SERIAL_BUFFER_SIZE;
return (i != _tx_buffer->tail);}
The loop from the sketch now looks like this:
void loop() {
if (Serial1.available() > 0 && Serial.writeable()) {
byte incomingByte = Serial1.read();
Serial.write(incomingByte);
}
if (Serial.available() > 0 && Serial1.writeable()) {
byte incomingByte = Serial.read();
Serial1.write(incomingByte);
}
}
This helps in the sense that it smooths the flow between the serial devices.
No more dropped bytes!