Custom Controller?

Please note that we are teaching a few weeks of College Class time worth of Serial Character handling in a few days… Yes, it might appear confusing…

Checking your past posts… There are a lot of issues that will result in problems, such as the following from Post #42:


      while(1){
            while(GetSerialPort2ByteCount()){
                  potentiometer=ReadSerialPortTwo();
            }
            switchD1 = GetDigitalInput(1); // Get the switch
            WriteSerialPortTwo(switchD1);
            SetPWM(1, potentiometer); // Set motor 1 to the potentiometer value
            Wait ( 16 ) ; 
      }


Starting with:


            while(GetSerialPort2ByteCount()){
                  potentiometer=ReadSerialPortTwo();
            }


I am not sure of all of these functions, but [FONT=Courier New]GetSerialPort2ByteCount()[/FONT] should return a number between 0 and maybe 255, being the number of Bytes waiting to be read.

0 means None, No Bytes so the while will be False and drop on through to the next lines on code, but if it Returns NOT 0, but 1, then a Single Byte will be read from the Serial Port 2 Incoming Que and be loaded into the Variable [FONT=Courier New]potentiometer[/FONT]. But what if there is 5 Bytes in the Que, then the While will loop many times, ( 5 times, unless more characters are received into the Serial Port 2 Incoming Que ) until the Last Byte in the Incoming Que is loaded into the Variable [FONT=Courier New]potentiometer[/FONT]. If there is only one Value, always, it won’t matter, but when multiple Variables are needed, you need to know which one is which…

This is not likely a desirable behavior…

You will need to Save the Return Value of ReadSerialPortTwo(), every time through the While Loop, until there is no more to Read, then examine the Data, looking for the New Line, then you can send it on to the Parsing Routine.

A few asides here, following up from jpearman and Quazar… There are a lot of different ways to achieve the same results, i.e. ASCII, Binary, Field Markers, Record Markers, which parsing methodology, etc… Some ways are Faster, some are more prone to failure ( maybe even spectacularly ), some might work well for one way of doing something, but become a problem if changes are needed later… I will show you some ways of doing what you are attempting, there is bound to be better ways, or faster ways or more reliable ways ( and Quazar and jpearman might “beat me up in private”, afterwords ), but your going to learn a bunch, and be empowered in a whole new way…

Sounds very interesting. If I ever get a controller working, I will give you guys credit for helping me :smiley:

Sweet!! I did not know about that. That will help alot :smiley:

It just occurred to me that there might be more than a confusing term or two here…

Talking just about the data between the Arduino and the PIC…

Positional Numbering Systems… Decimal, Hexadecimal, ( and Octal, for historic reasons ) read about them and be prepared to discuss… :wink:
For most Programming Discussions, Decimal numbers will look like ‘29’, Hexadecimal numbers will look like ‘0x1D’ or '\x1D, and Octal numbers will look like ‘035’ or ‘\35’. This is why the Joke, “Why do mathematicians always confuse Halloween and Christmas?” ( Answer ) is funny ( and at first, looks confusing )

I mention the Terms, Field and Record.
These are Data Base Terms, and are relevant because a Record is made up of One or More Fields. Fields can be made of different kinds of Data, thus making them different kinds of Field Types and each Field only holds one kind of Data:
e.g.
A String “Hello, this is one Field!”,
An Integer 1249,
A Boolean T, and so on.

Multiple Fields can be Placed into a Record, to be treated as Related Data Items. Each Record should look the same as the last, and the next. Not the Data Values, but the Data Types. Each Field needs a Field Divider and each Record needs a Record Divider. That Divider can either be a Length, Fixed or Variable, or a Special Character, Which is One that will NEVER occur in your Data.

Your Data example lends itself to the Fields and Records paradigm. Every time through the Main Loop of each of your Microcontrollers, you are going to read certain Peripherals, e.g. Joystick, and buttons, and translate each piece of Data, e.g. X position, Y position, Button S1, S2 and S3 into Five Fields, Two with Integers, and Three with Booleans, which are combined to make One Record. Every time through the Main Loop, gets 5 more Values, which become 5 More Fields, which become 1 More Record.

Starting with your Fields and Records in ASCII, there are logical choices…

Look at an ASCII chart, there are Certain Characters that are Reserved for Control, and are not typically Visible, e.g. Decimal 0 to 31 ( Hexadecimal 0x00 - 0x1F ). If you chose a Field Divider and Record Divider for your Data, you can have Variable Length Fields and Variable Length Records, but in the above case, there is not much reason to do so, because all the Data is quite Uniform. So we chose A Fixed Record Length, But… what if we have Noise that ADDS extra Characters to the Data Stream, or if some Characters, get Dropped/Lost… We suddenly start reading the Right Values, and the Wrong Times…

So a simple compromise is to mix it up… Fixed Field Lengths, giving a Fixed Record Size, but have a unique Record Divider at the end of every Record.

Your Data Record has changed a few times, but:
<0000 0000 X X X X\n>

This represents Two Analog Values and Four Boolean Values, with Space ASCII 32 ( 0x20 ) Field Dividers and the last Field has Only the Record Divider of a New Line ASCII 10 ( 0x0A ) ( Also note that New Line is also called a Line Feed ) The New Line ( Line Feed ) ( abbreviated NL or LF ) is a Synchronization Point in our Data Stream. If we know the length of the Record, ( 18 Bytes, including the LF, but NOT the ‘<’ and ‘>’ ) and the Record Divider, ( the LF ) character, we can search for a Line Feed ( New Line ), count the Bytes to the Next LF, and if they are 18 Bytes apart, we know there is ( most likely ) no Garbage Added, or Characters Lost, so we have a Good Record. This Good Record is Passed to the Parser that Breaks Out the individual Fields, and places them into the Working Variable of your Program

( We are leaving out the Scenario where Noise adds extra Characters, and delays loose other Character, resulting in a BAD Record, with the correct length.)

When using a Terminal Program, (I like to use both Tera Term and Real Term on Windows), the New Line character is one of those Reserved Control Characters, which means that if you chose it for the Record Divider, each Record will appear on its Own Line when displayed in the Terminal Program. Also note that different Operating Systems have different ways to End the Line:
UNIX ( LINUX ) uses the New Line ( 0x0A )
Macintosh ( before OS X ) uses the Carriage Return ( 0X0D )
and MS-DOS/Windows, uses BOTH, in the Order of Carriage Return/New Line ( 0x0D 0x0A )

You owe more than that!! We’re talking College Tuition here… This is Good Stuff!!!

I didn’t think there was this much to put into making a controller :(… Oh, and I have been having ideas for how to make the controller. 4 joysticks, 6 dual buttons(12 normal), acelarometer, maybe a LCD screen, maybe some other things like a wireless camera. I think this will have a lot to send over the xbee. For my little test with the pot and the switches, I made it only send the character when the switch or pot changed, so it isn’t constantly sending stuff. I would like to try and do that on the new system as well, so it won’t lag, or will lag less.

I was thinking of keeping the same-ish protocol. for the end, would I need \n, or would the > work?

Oh, FYI, I won’t be online until late Sunday, maybe every once in a while if I get wifi. Thanks all for your help so far.

I might be online tonight and tomorrow morning.

I am researching this Thread from the Beginning… There is now almost 100 Posts…

I will see if I can post some relevant Code, for both the VEX PIC and the Arduino…

Which Arduino environment are you using?? I have 0022 installed for a couple of Sparkfun Arduino Pro 328 5V/16MHz. It appears that arudino.cc is up to V 1.0.1.

I think I am using v1.0. I am using a Arduino UNO R3. If you need more info before I leave, post it soon.

While Mark is working on ASCII protocol code for you, I’m going to throw a wrench in here and suggest you may be better off using what we have described as a binary protocol. In addition to this I’m going to suggest that you change your current model to a master/slave senario. Currently either the Arduino or PIC can choose to start transmission at any time, this means that they may both choose to transmit together which can make life hard. Instead I would suggest that you designate one system as a master (probably the arduino controller) and the other as a slave. The master would initiate all communications and would inquire what the status of the slave was when it needed it. The simplest case of this is similar to you have now but with one important difference, the master would send the control message (joysticks etc.) and only then would the slave reply with the status of switches etc. This generally makes the logic simpler and will certainly help stop the arduino dropping characters that were being received when not expected (a problem with the softwareSerial library).

If I have time I will try and throw something together for the arduino tomorrow or Friday, I don’t have a PIC available (only a cortex) but the arduino code should port over to a PIC without much change. This type of code can generally be quite processor independent if done correctly by wrapping the low level calls like Serial.read() or ReadSerialPortTwo() in another level of function call, for example.

arduino

int
ReceiveChar()
{
    return( Serial.read() );
}

PIC

int
ReceiveChar()
{
    return( ReadSerialPortTwo() );
}

Now you can write more portable code and use ReceiveChar() (and other generic functions) in place of the processor specific calls.

Again, as an example,

// Read all characters from the serial port into a buffer of length maxLen
//
ReadBuffer(unsigned char *buffer, int maxLen )
{
    int  i;
    unsigned char *p = buffer;

    for( i=0; (ReceiveBytesAvailable() > 0) && (i < maxLen); i++ )
        {
        *p++ = ReceiveChar();
        }

    return(i);

}

I would like to say some things. The Arduino is a remote control ( or will be ) and it will have all the switches and pots and joysticks on that. It will send the button states and the pots. Maybe once I get that figured out and working, then we can focus on adding the Pic send back stuff. For right now, I think it is more important to figure out how the Arduino will send, and how the Pic will receive. That is what I think, but maybe it should all be done at once.

Have you ever looked at the code in this thread

https://vexforum.com/t/interfacing-a-ps3-controller-to-the-joystick-partner-port/21829/1

It is different and more complicated, but in essence is doing the same thing. It reads data from a PS3 controller into an arduino and then sends it over a serial link to another device (in this case a VEX joystick).

Here is the function that does the actual serial output. (the code from post #1 not the later posts)

/*-----------------------------------------------------------------------------*/
/*                                                                             */
/*  send the VEX data to the serial port                                       */
/*                                                                             */
/*-----------------------------------------------------------------------------*/

void
VexDataTransmit()
{
    int  i;
    
    for(i=0;i<14;i++)
      Serial.write( MyVexData.buffer* );
}

The data it is sending is contained in this structure (well union).

// Storage for the vex communications data
typedef union _vexdata { 
    struct {
      unsigned char  header_aa;
      unsigned char  header_55;
      unsigned char  reply_type;
      unsigned char  datalen;
      unsigned char  js_1;
      unsigned char  js_2;
      unsigned char  js_3;
      unsigned char  js_4;
      unsigned char  button_56;
      unsigned char  button_78;
      unsigned char  accel_y;
      unsigned char  accel_x;
      unsigned char  accel_z;
      unsigned char  checksum;
     } data;
     
   unsigned char    buffer[16];  
  } vexdata;

vexdata  MyVexData;

here is a excerpt of code where I am filling in the data structure, in this case some joystick values.

      MyVexData.data.js_1 = PS3.getAnalogHat(RightHatX); 
      MyVexData.data.js_2 = PS3.getAnalogHat(RightHatY); 

This code is transmit only, there is no path for receive data in this case. Anyway, it’s worth looking at.*

Interesting. The code seems a little advanced, as I can’t follow some of the code used. How does the Arduino send the variables from the PS3 controller? vexdata MyVexData; is this some sort of function that saves the variable?

No, I defined a new storage type, vexdata, which is a union. A union is a way of allowing you to access the same area of memory in different ways, in this case both as an array of unsigned char as well as a structure with named variables.

vexdata MyVexData;

is declaring a global variable MyVexData which has the type vexdata, it’s no different than declaring say a global integer

int MyVariable;

I will Install v1.0.0. Right now I am testing your Code from Post #24 with 0022…

Do you have this Arduino UNO R3??

It seems to have the same specs that my Pro does. I bet it will be an easy port from your posted Code to me, and back again…


Please review all posts in this thread… I am seeing what Quazar and jpearman have mentioned previously, and the earliest parts of the thread will make a little more sense to you now that you have tried a few more experiments and have had a chance to see what works and what doesn’t,


Remember the SoftSerial routines are all done in Software ( called BitBanging ), so there is a Speed Limitation. This might be of the issues you see in the Code of Post 42.


jpearman in Post 92 is showing an example of keeping the Data in a Binary Format and using a Structure and a Union to work with the data.

Binary Data is harder to view in the Data Stream, but much more efficient to work with, because extra Processor Time is NOT Spent converting from Binary to ASCII and Sending, ( which takes longer because the Binary is more compact ) and then converting from ASCII back to Binary.

Using ASCII is a good starting place, because it is quite easy to see how all the parts work, but just like the Bubble Sort, is it a Good Starting Place, but you want to Move On to the Better Stuff, ( like the Quicksort ) as soon as possible…


Also, a further note on my Post #78, The Errors from the Compiler are a Simple Double Click on the .err file located on the Project Tab, under “Log Files”. Then you can Select and Copy.

I have put something together but I’m in two minds about posting the whole thing as I’m afraid it will probably just confuse things even more. However, I will post an excerpt of the receive code and try and explain how it works.

Working with serial ports on an Auduino is a bit of a chore. The standard board only has one serial port so a choice has to be made to either use it for communications or debugging purposes as a console. The softwareSerial library is ok for simple tasks but suffers from a couple of issues.

  1. When transmitting, the receiver is disabled. In fact all interrupts are disabled
  2. As it uses “bit banging”, using high baud rates only marginally works, I would avoid 115200 even though it is supported.

To be honest, I would keep the baud rate at 38400. This will allow enough bandwidth for the type of communications you want to do but be low enough that any errors in the bit banging timing become insignificant. 38400 is a very common communication rate for the type of equipment I often work with and is pretty much a standard in the broadcast industry for equipment control.

The problem of not being able to transmit and receive at the same time can be avoided by the use of the master/slave protocol I suggested earlier.

Transmitting data is easy, receiving data is harder. When receiving data you have to account for corrupted messages, partial messages, cables being connected and disconnected and possible parity and framing errors. I prefer binary messages but the following technique will work just as well with ASCII messages.

So first of all we need somewhere to store the received information. Although a simple array will work, this example uses a union of both an array and a structure. A structure is a way of grouping several variables into one object. A union is a way of accessing the same region of memory in two (or more) different ways. This is very convenient when, for example, you want to be receiving a stream of bytes and placing them in a continuous array but afterwards access them by name. Here is a simple union that I will use for receiving data.

// Storage for the vex communications data
#define  VEXDATAOFFSET          4
#define  VEX_DATA_BUFFER_SIZE  16

typedef union _vexdata { 
    struct {
      unsigned char  header_aa;
      unsigned char  header_55;
      unsigned char  message_type;
      unsigned char  datalen;
      unsigned char  analog_0h;
      unsigned char  analog_0l;
      unsigned char  analog_1h;
      unsigned char  analog_1l;
      unsigned char  digital_0;
      unsigned char  digital_1;
      unsigned char  checksum;
     } data;
     
   unsigned char    buffer[VEX_DATA_BUFFER_SIZE];  
  } vexdata;

The union has two parts. The second part is an unsigned char array.

unsigned char    buffer[VEX_DATA_BUFFER_SIZE];

VEX_DATA_BUFFER_SIZE is defined to be 16, slightly larger than the structure that is the first part of the union. The first part of the union is a structure.

struct {
      unsigned char  header_aa;
      unsigned char  header_55;
      unsigned char  message_type;
      unsigned char  datalen;
      unsigned char  analog_0h;
      unsigned char  analog_0l;
      unsigned char  analog_1h;
      unsigned char  analog_1l;
      unsigned char  digital_0;
      unsigned char  digital_1;
      unsigned char  checksum;
     } data;

By using “typedef” in front of the union description I have defined a new data type, this can be used in the same way as “int” or “double”, to declare storage you do the following.


vexdata  MyVexDataRx;

This creates a new variable called “MyVexDataRx” which has the type “vexdata”. As an example, this is how you would access the first member in the array.


MyVexDataRx.buffer[0]

or


MyVexDataRx.data.header_aa;

The contents of “vexdata” will change depending on your chosen communications protocol, the structure above has variables that work with the protocol for my demo code, yours may be different. In this case the structure members have the following meanings.

header_aa
fixed value of 0xAA used to detect the start of a message.

header_55
fixed value of 0x55 used to detect the start of a message

message_type
One byte that tells me what information is in the message, not really used in the demo code.

datalen
The number of bytes in the remainder of the message.

analog_0h, analog_0l, analog_1h, analog_1l, digital_0, digital_1
The actual data for my message, the demo sent two 16 bit analog values and two 8 bit digital values. Each 16 bit analog value was split into two parts for transmission, the top 8 bits (high byte) and the lower 8 bits (lower byte).

checksum
A byte that allows the packet to be checked for simple communication errors.

As I said, transmission is easy, fill in the data structure and then send each byte using a loop that accesses each byte in the array.

Receiving is harder, I normally use a state machine to do this. A state machine allows us to keep track of where we are in receiving the message. To start off, you need to detect the header, when that has been done read the message type, next the data length etc. At each stage of receiving the message error checking can be done if, for example, the second byte of the header is wrong then the state machine is reset and you start over by waiting for the first byte of the header. Anyway, here is the receive state machine for the demo code.

/*-----------------------------------------------------------------------------*/
/*                                                                             */
/*  Message receive task                                                       */
/*                                                                             */
/*-----------------------------------------------------------------------------*/

void
serialRxTask()
{
    static  int  serialRxState      = 0;
    static  int  serialDataReceived = 0;
    static  long timeout            = 0;

    int    c;
    
    // check for a received character
    if( (c = serialRxChar()) < 0 )
      {
      // no received char 
      
      // Check for inter char timeout
      if( CheckTimeout(&timeout) == 1 )
          serialRxState = 0;

      // done
      return;
      }

    // A new character has been received so process it.
    
    // Start inter char timeout
    StartTimeout( &timeout );  

    // Where are we in the message parsing process ?
    switch( serialRxState )
      {
      case  0:
          // look for first header byte
          if( c == MyVexDataRx.data.header_aa )
            serialRxState++;
          else
            StopTimeout(&timeout);
          break;

      case  1:
          // look for second header byte
          if( c == MyVexDataRx.data.header_55 )
            serialRxState++;
          else
            {
            // Bad message
            StopTimeout(&timeout);
            serialRxState = 0;
            }
          break;

      case  2:
          // We have a good header so next is message type
          MyVexDataRx.data.message_type = c;
          serialRxState++;
          break;

      case  3:
          // next is data length byte
          MyVexDataRx.data.datalen = c;
          serialDataReceived = 0;
          serialRxState++;
          break;
          
      case  4:
          // receive the data packet
          MyVexDataRx.buffer VEXDATAOFFSET + serialDataReceived ] = c;
          if( ++serialDataReceived == MyVexDataRx.data.datalen )
            serialRxState++;
          // check for buffer overflow
          if( serialDataReceived >= (VEX_DATA_BUFFER_SIZE-VEXDATAOFFSET) )
            {
            // Error
            StopTimeout(&timeout);
            serialRxState = 0;
            }            
          break;
          
      case  5:
          // Received checksum byte
          
          // stop timeout
          StopTimeout( &timeout );
          
          // calculate received checksum
          VexDataChecksum( &MyVexDataRx );
          
          // compare checksums
          if( MyVexDataRx.data.checksum == c )
            {
            // Good data
#ifdef  DEBUG
            VexDataPrint (&MyVexDataRx);
#endif
           // Decode the message
            serialRxDecode();
            }
          else
            Serial.write("bad data\n");
          // done
          serialRxState = 0;
          break;

      default:
          break;
      }  
  
}

Yes, it looks long and complicated see the next post for a brief explanation of what is happening more or less.

(hit the 10000 char limit :frowning: )

part 2…

Brief explanation of the receiver state machine.

SerialRxTask() is called often from the main loop of the program.

Every time a character is received the current “state” is checked (the variable serialRxState ).

The header of the message is found.
The message type and data length is stored.
All the remaining bytes of the message are stored up to the checksum byte.
The checksum is calculated and compared to the received checksum.
If everything passes error checking then the message is decoded and the contents used.

Each received character starts a timer (somewhere around 5-10mS duration). If for some reason no more characters are received then the timer finished and the receive state machine reset back to looking for the header. When a good message is received the timer is stopped. There are calls in this code to generic functions, for example, I call serialRxChar to read the next received character if one is available. The actual contents of this function will vary depending on the platform the code is running, in the case of the arduino this was tested on it is as follows.

/*-----------------------------------------------------------------------------*/
/*                                                                             */
/*  Receive one character, return (-1) is nothing available                    */
/*                                                                             */
/*-----------------------------------------------------------------------------*/

int
serialRxChar()
{
    int  c;
     
    if( mySerial.available() )
      {
      c = mySerial.read();
      }
    else
      c = -1;
      
    return(c);  
}

Somewhat verbose, the mySerial.available() call is not strictly necessary as mySerial.read() will return (-1) is nothing is available anyway.

So I’m not sure if any of that is useful, it is hard to simplify this and also create robust code. It should at least be a starting place for more research.

I want to thank you James, this has been a very helpful thread. I have been working this summer on an approach to use X-Bee with the Serial port of the Cortex to allow an independent channel (from the main joystick) to monitor and control the cortex. These serial port discussions have helped me frame the next step. I obtained an Arduino (Mega 2560 with 4 H/W Serial ports) for testing as the other ‘robot’ and 2 X Bee Radios, which when properly configured pass the serial port data over the air very effectively, so these examples work well. I struggled up until recently to get the X-Bee Radio to work with the Cortex Serial port. I had a good run setting the baud rates to 9600 (worked fine on the arduino end but Cortex kept getting garbled serial data no joy week after week.) Finally I tried the baud at 115200 (based upon the Robot-C ‘Fire’ project) Fire Wiki and have gotten reasonable results since. I plan to take the next step and create the messaging framework as you have done for this project. I’ll post the results and any lessons learned as it becomes useful. My primary objective was to provide a low cost simple to add (although not competition legal) method of communicating with a Vex Robot.

Cheers Kb

I think it is awesome that someone else is working on a similar project :slight_smile:

Kevin, you are welcome, I’ve been trying to tone it down a bit but got carried away in yesterday’s post. Here is the arduino project I was playing with on Friday, I had two arduino pro’s connected, uncomment this line

//#define TRANSMITTER 1

to compile as transmitter, leave commented for receiver.

On the Mega 2560 you will not need to use softwareSerial but thats an easy change.
serialDemo.zip (2.73 KB)