Skip to main content

Generic OpenMoco C++ serial library

Posted in

I believe we have a strong need for a library that handles all the serial communication for the OpenMoco project. The engine now holds all the serial code in the file "OM_Serial_Com_Client.pde". Most of the code from that file is needed on the TouchShield Slide and on the Arduino of the TouchShield too. A library that implements the functionality once that can be used on multiple devices greatly enhances the maintenance of our code. The library needed for all three of these devices should (in my opinion) do the following:

  • The serial code should be generic and should work on all serial devices. This can be hardware devices or software serial like NewSoftSerial. The current code of the TLE cannot do this because this is all built around the Serial object. The TouchShield features 4 hardware serial devices; two of these will be used for the final implementation for example.
  • Provide functionality that makes it very easy to implement the OpenMoco serial protocol on all devices in your setup (when AVR based).

I have made a start of a C++ class that just does this. It's a wrapper around HardwareSerial and NewSoftwareSerial. It implements all the generic/re-usable code from the current engine serial code. There is still device specific serial code, like reacting to the serial commands. I will explain that later. The code of the C++ class that I have so far is:

Header file

#include <WProgram.h>
#include <HardwareSerial.h>

// We only need softserial on devices with only one UART
#if not defined (_TOUCH_SLIDE_)
#define _INCLUDE_SOFTSERIAL__
#endif

#ifdef _INCLUDE_SOFTSERIAL__
#include <NewSoftSerial.h>
#endif

#ifndef OPENMOCO_SERIAL_H
#define OPENMOCO_SERIAL_H

// maximum command data length
#define SER_COM_BUFFER_LEN 9

// how many ms to wait for a serial command to complete?
#define SERIAL_TIMEOUT 15000

class OMSerial{
private:
  boolean use_hardware_serial;
       
  HardwareSerial *_HardwareSerial;
#ifdef _INCLUDE_SOFTSERIAL__
  NewSoftSerial *_NewSoftSerial;
#endif
       
  void begin(long speed);
  int available(void);
  int read(void);
  void print(char n, int base);
public:
  byte command_val;
  byte com_byte_count;
  byte input_serial_buffer[SER_COM_BUFFER_LEN];
  unsigned long serial_time;

  byte serial_got_command();
  byte get_response();

  void serial_write_command( char command_byte, char data_length_byte);
  void serial_write( byte val );
  void serial_write( int16_t val );
  void serial_write( unsigned int val );
  void serial_write( unsigned long val );
  void serial_write( long val );
  void run(void);
  OMSerial(HardwareSerial *HardwareSerialObj, long speed);
#ifdef _INCLUDE_SOFTSERIAL__
  OMSerial(NewSoftSerial *NewSoftSerialObj, long speed);
#endif
  virtual void serial_execute_command(byte command) {};

};

#endif

Implementation of the class:

#include "OpenMocoSerial.h"

OMSerial::OMSerial(HardwareSerial *HardwareSerialObj, long speed){
  serial_time = 0;
  _HardwareSerial = HardwareSerialObj;
  use_hardware_serial = true;
  _HardwareSerial->begin(speed);
}

#ifdef _INCLUDE_SOFTSERIAL__
OMSerial::OMSerial(NewSoftSerial *NewSoftSerialObj, long speed){
  serial_time = 0;
  _NewSoftSerial = NewSoftSerialObj;
  use_hardware_serial = false;
  _NewSoftSerial->begin(speed);
}
#endif

void OMSerial::begin(long speed){
  if (use_hardware_serial)
    _HardwareSerial->begin(speed);
#ifdef _INCLUDE_SOFTSERIAL__
  else
    _NewSoftSerial->begin(speed);
#endif
}

int OMSerial::available(void){
  if (use_hardware_serial)
        return _HardwareSerial->available();
#ifdef _INCLUDE_SOFTSERIAL__
  else
        return _NewSoftSerial->available();
#endif
}

int OMSerial::read(void){
  if (use_hardware_serial)
        return _HardwareSerial->read();
#ifdef _INCLUDE_SOFTSERIAL__
  else
        return _NewSoftSerial->read();
#endif
}

void OMSerial::print(char n, int base){
  if (use_hardware_serial)
        _HardwareSerial->print(n,base);
#ifdef _INCLUDE_SOFTSERIAL__
  else
    _NewSoftSerial->print(n, base);
       
#endif
}

void OMSerial::run(void){

  byte ser_com = serial_got_command();

  if( ser_com ) {
    // we have a command, execute it.
    serial_execute_command( ser_com );
  }
}

byte OMSerial::serial_got_command() {

  command_val    = 0;
  com_byte_count = 0;

  if( available() ) {
    // hardware serial (host pc?) has data ready for us
    command_val = (byte) read();

    if( command_val > 0 ) {
      // wait until we have data
      while( ! available() ) {
        ;
      }
      // get data length    
      com_byte_count = (byte) read();
    }

  }
  else {
    // serial does not have data in the buffer
    return( 0 );
  }

  // check for overflow
  // note: we will still be in error condition, as
  // there will be values left in the serial buffer
  // on the next read, from this command

  if(com_byte_count > SER_COM_BUFFER_LEN)
    com_byte_count = SER_COM_BUFFER_LEN;

  // clear out any previous command data
  memset(input_serial_buffer, 0, sizeof(char) * SER_COM_BUFFER_LEN);

  serial_time = millis();

  // populate command data buffer
  for( int i = 1; i <= com_byte_count; i++ ) {
    // get com_byte_count character values from the serial
    // buffer

      // wait until we have data ready
    while( ! available() ) {
      if( millis() - serial_time > SERIAL_TIMEOUT ) {
        // timed out waiting for complete input
        // send 'fail' response
        serial_write((byte)0);
        serial_write((byte)0);
        return( 0 );  
      }
    }

    input_serial_buffer[i - 1] = (byte) read();    
  }

  return( command_val );
}

byte OMSerial::get_response() {
       
        // clear out any old data received
        memset(input_serial_buffer, 0, SER_COM_BUFFER_LEN);
       
        // wait for response from engine
        while( ! available() ) {
                ;
        }
       
        byte response = (byte) read();
       
        // wait until we have a response length
        while( ! available() ) {
                ;
        }
        // get data length    
       
        byte byte_count = (byte) read();
       
        // if no data, return the response code
        if( byte_count == 0 )
                return(response);
       
        for( byte i = 0; i < byte_count; i++) {
                while( ! available() ) {
                        ;
                }  
                input_serial_buffer[i] = read();
        }
       
        return(response);
}

void OMSerial::serial_write( byte val ) {

  print(val, BYTE);

}

void OMSerial::serial_write_command( char command_byte, char data_length_byte){
        print(command_byte, BYTE);
        print(data_length_byte, BYTE);
}

// Signed version
void OMSerial::serial_write( int16_t val ) {
  print( val >> 8, BYTE );
  print(val, BYTE);
       
}

void OMSerial::serial_write( unsigned int val ) {

  print( val >> 8, BYTE );
  print(val, BYTE);

}

void OMSerial::serial_write( unsigned long val ) {

  print( val >> 24, BYTE );
  print( val >> 16, BYTE );
  print( val >> 8, BYTE );
  print(val, BYTE);

}

void OMSerial::serial_write( long val ) {

  print( val >> 24, BYTE );
  print( val >> 16, BYTE );
  print( val >> 8, BYTE );
  print(val, BYTE);

}

You see that the code above holds everything from the original engine serial code except for: serial_program_data() and serial_execute_command(). serial_execute_command is now a virtual that should be implemented by a device specific class that inherits the stuff from above. This way the class is generic/re-usable. The constructor of the class gets a pointer to the serial object that you want to use (hardware or software serial) and the baudrate.

The following code is from the TouchShield UI code; this is using hardware serial:

class TTSSerial: public OMSerial{
  private:
  public:
    void serial_execute_command(byte command);
    TTSSerial();
};

void TTSSerial::serial_execute_command(byte command){
  boolean fail = false;
  char buf[100];
  int16_t x,y;

  switch(command){
    case 1:
      // Just a demo; clear the previous coordinates from the screen
      // The code that handles the serial command will be here
      break;

    default:
      // We couldn't find the command that was sent
      fail = true;
      break;
  }

  // send serial response
  if( fail ) {
    serial_write((byte)0);
    serial_write((byte)0);
  }
  else {
    serial_write((byte)1);
    serial_write((byte)0);
  }
}

// Get the serial connection running on Serial and baudrate 9600
TTSSerial::TTSSerial(): OMSerial( &Serial, 9600 ){
}

OMSerial *arduino = new TTSSerial();

The following code is from the Arduino that sends data to the TouchShield through NewSoftwareSerial

class TTSSerial:
public OMSerial{
private:
public:
  void serial_execute_command(byte command);
  TTSSerial();
};

void TTSSerial::serial_execute_command(byte command){
  boolean fail = false;
  char buf[100];
  int16_t x,y;

  switch(command){
  default:
    // We couldn't find the command that was sent
    fail = true;
    break;
  }

  // send serial response
  if( fail ) {
    serial_write((byte)0);
    serial_write((byte)0);
  }
  else {
    serial_write((byte)1);
    serial_write((byte)0);
  }
}

TTSSerial::TTSSerial():
OMSerial( new NewSoftSerial(SLIDE_RX_PIN, SLIDE_TX_PIN), 9600 ){
}

OMSerial *tts = new TTSSerial();

The code is working on the TouchShield now. It needs a bit of attentions around the _INCLUDE_SOFTSERIAL__ define. This steers if NewSoftwareSerial should be compiled in the code or not. The code above does that now on the Duemilanove. This should be done better, because the TLE doesn't need this while the Duemilanove on the TouchShield does.

Chris: what do you think. I strongly hope that you are willing to include this idea in the engine itself. That way we have one implementation of the serial protocol stuff that can be used by all devices and all serial connections. The library that we discussed earlier that created the actual serial commands should be a separate one so that it is only included where needed.

Great job !!!!

Matt's picture

Great job !!!!

I like the idea, and we can

I like the idea, and we can find a way to work it in.

I'd extern that serial buffer, and use the class just for handling the communication parts. In this way, you can get closer to a "generic" library. The parts that act on commands, or create commands to be sent to the engine would be sub-classes such that one includes only what they need.

I'd have to provide another option, as softwareserial isn't the only option for the engine its self. (only needed when working with a touchshield, otherwise hardware serial is fine.)

There'll need to be some shared headers along the way, to handle modifying values. Should be no big deal.

Thanks for putting the work into this, I'll start connecting the dots very soon =)

!c

The buffer is available

The buffer is available outside the class since its in the public section.

Lets explain my setup. I have one Arduino with a TouchShield Slide. I call that my controller. This controller uses a wireless serial connection to a second Arduino that is running the engine.

I don't understand your comment about the acting of commands or creating commands. That is exactly what I am doing, right?. The specific creation and handling of commands is in the subclasses that I am using. I have three subclasses working now. One hardware serial on the Arduino connected to the TLE (Arduino <-> Arduino). One software serial on the Arduino that is connected with the Slide (Controller Arduino <-> Slide) and one hardware serial on the slide that is communicating with its Arduino. All three subclasses are working great now!

Hardware serial is already in the code above. I am using it between the two Arduino's.

Let me know when you want to start working on this because I am still tweaking this a little bit (I have a newer version). I can also provide a version of the TLE with this code included. Can't take more than an hour to do that.

OK, I understand better now

OK, I understand better now what you've done. I had read the code, and it seemed like your option was either to be running on a slide, or using newsoftserial. It appears it's just the define that's done that way (maybe a different name for it?).

I also see more clearly what you are doing with the sub-classes. Perhaps I was still asleep yesterday =)

Do you have a sourceforge account, so I can give you access to the svn repo? This way you can create a branch with the changes, rather than having to email the code or anything! I'm working on some overhauling of the motor driving code right now, so that would be nice to kill two birds w/ one stone. I haven't merged back to trunk yet, so release-0.82 branch should just be copied for the time being.

!c

.

.

Hello May I download

Hello

May I download /dev-cronix/Openmoco_Timelapse_Engine and test it ? or I must download and install any other file to go ?

and as a suggestion, it´s posible to read the entire command at the beginnig and before execute it do any kind of checksum

I think that the serial part of the program must only check that the command is a valid one and verify that the data is not corrupted, then, fill the input buffer. Other part inside the program must interpete that input buffer and execute the commands.

Best regards.
Inco.

Excuse me if writing I seem not very polite, English is not my mother language and is difficult to me to write what I really want to say.

Sure you can download it.

Sure you can download it. It's on sourceforge right :-)? You also need the serial library from sourceforge to compile the engine. I didn't change anything in the serial code itself. I just pulled it out the TLE source and made a library of it; functionality is not changed.

I use the library also for the Slide user interface that I am working on.

I´m not able to compile

I´m not able to compile /dev-cronix/Openmoco_Timelapse_Engine/OpenMoco_Timelapse_Engine.pde
I have installed
cppfix
NewSoftSerial
and OpenMocoSerial in the libraries folder

I get the next error :

o: In function `__vector_10':
C:\DOCUME~1\ADMINI~1\CONFIG~1\Temp\build1307807431194808623.tmp/OpenMoco_Timelapse_Engine.cpp:710: multiple definition of `__vector_10'

C:\DOCUME~1\ADMINI~1\CONFIG~1\Temp\build1307807431194808623.tmp\OpenMocoSerial\NewSoftSerial.cpp.o:C:\arduino-0018\libraries\OpenMocoSerial/NewSoftSerial.cpp:323: first defined here

any ideas ?

Best regards
Inco

Hi Inco, We had this before.

Hi Inco,

We had this before. Have a look at: http://openmoco.org/node/117