4
\$\begingroup\$

I am currently building a robot (as some of you may know from my previous questions). The current task I a dealing with is that of communication. To keep things simple, assume I have 5 commands to deliver from my ground station to my robot:

  1. Drive forward
  2. Drive reverse
  3. Activate servo 1
  4. Activate servo 2
  5. Motor speed

Now, I am sending these commands from my computer to a USB to Serial adapter and then over an RF link. But this is not important, as far as I know, to my question.

So my question is, how does communication work in general? My idea is that I will have five different 8 bit data packets (one for each command) which I will continuously send from my computer. So I will keep an infinite loop going which will check, say, the position of an analog joystick. If it is pointing upward, command 1 will carry the drive forward message. If it is pointing downward, command 2 will carry the message, etc. Depending on how far the joystick is in the up/down position will dictate the contents of command 5. And the state of some keys on the keyboard will dictate whether commands 4 and 5 contain info to actuate servos.

So, again, I plan on having a continuous loop which will check each of the 5 states and send the appropriate commands over the USART from the computer's side.

The problem I am having, conceptually at least, is that what if there is a lag on the robot's end to process the data coming through the USART? As I understand, to ensure data isn't lost, the data is stored in a buffer on the MCU. Essentially, I want a "last in first out" system on my robot, so even if I miss the older commands for whatever reason, the robot is doing what I want it to do now, not what I wanted 2 seconds ago. But with this methodology, I'm also afraid that I will skip data packets of commands 1-4 because it will just keep reading the last data packet (i.e. command 5).

I came up with this communication "roadmap" on my own, so I'm sure there are much better ways to accomplish what I want. But I hope you understand what I mean. Also, I am using an ATmega328 on my robot, if it matters.

I would appreciate any advice pertaining to this situation.

\$\endgroup\$
0
\$\begingroup\$

What I usually do is I try to ensure that the controller doesn't send commands faster than the robot can process. Sending instructions consistently faster than can be processed by the robot will always eventually result in a loss of information with either a FIFO or a LIFO buffer. As a backup to intermittent lag on the robot I would use a small FIFO buffer. The exact size depends on what the trade-off between lost commands vs. responsiveness.

a LIFO buffer really doesn't make much sense in any case because you'll end up with an out of order operation in case the robot responds to commands slowly. consider the following:

  1. Send drive forward command
  2. Send Activate servo 1
  3. Send drive back command
  4. Send Activate servo 2

With a LIFO structure, say all the commands get sent before the robot gets time to process any commands. The commands are now going to be executed in reverse order! The robot will activate servo 2, drive back, activate servo 1, then drive forward. Most likely this is never what you want.

If you're only ever processing the latest instruction, then no additional buffer would be needed at all.

\$\endgroup\$
  • \$\begingroup\$ Unless such a "don't send too fast" plan has a hard enforcing limitation (such as knowing you can keep up with everything the chosen baud rate can deliver), or a defined requirement for status querries following longer commands, its at risk of being broken by a different programmer less attuned to the unique limitations - which in practice means anyone but your present self, as even you may overlook it if you come back to the project in six months. \$\endgroup\$ – Chris Stratton Mar 13 '13 at 14:18
  • \$\begingroup\$ true, but it's hard to ensure no buffer overflow or loss of messages otherwise. A FIFO circular buffer would be the backup so that oldest instructions get overwritten in case of overflow rather than the newest instructions, and LIFO would still not be recommended due to the reverse order execution. \$\endgroup\$ – helloworld922 Mar 13 '13 at 14:47
  • \$\begingroup\$ Actually, no, it's fairly easy to insure it - simply make sure that your program can parse and execute commands as fast as the baud rate can deliver them, and that your buffering is sufficient for the maximim latency that might be caused by some other blocking task. The kinds of commands being discussed here are fairly quick to execute. What has not been discussed is waiting for the robot to accomplish something in the physical world before countermanding a command - apparently that is either being decided by a human with visual feedback, or yet to be addressed. \$\endgroup\$ – Chris Stratton Mar 13 '13 at 15:04
  • \$\begingroup\$ Yeah, my claim is message transmission rate must be enforced/throttled somehow if no loss of messages is important. This may be easy to enforce using slower baud rates, but without any defined limit you're eventually going to accumulate more messages than can be stored. Call it conservation of messages :P average messages in rate <= average messages out/processed rate to ensure buffer doesn't overflow. \$\endgroup\$ – helloworld922 Mar 13 '13 at 15:30
3
\$\begingroup\$

What I have done in some projects is to use a circular buffer that is filled by an ISR triggered by a UART.

So when the UART receives a byte, an interrupt is triggered and the handler loads that byte to end of the buffer.

Then in the main loop you can check if there is any bytes in the buffer and execute each command that has been queued up.

Lots of info on circular buffers in teh internet.

Cheers.

\$\endgroup\$
0
\$\begingroup\$

This is fairly similar to a project of mine using an ATmega16 that communicates over a Lantronix XPort. Using putty set up with ACK-character answerback on ENQ Code looks like this (have not worked out the timer stuff yet):

#define F_CPU 8000000UL                         // 8MHz - prevents default 1MHz
#define USART_BAUDRATE 38400UL                  //300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
#define BUFFER_SIZE 255

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define pinstate(sfr,bit) ((sfr) & (1<<(bit)))

int coms = 0;                                   // Do we have communication, reset by timer every x milliseconds
int BufferI = 0;                                // Buffer position
char Buffer[BUFFER_SIZE];                       // Receive buffer
char cmd[BUFFER_SIZE];                          // Command variable
int sendheartbeat = 0;
char clearandhome[7] = {'\e', '[', '2', 'J', '\e', '[', 'H'};
#define PROX_SW_EXPANDED            PINB1       // PIN2 POSITION WHEN EXPANDED, READY FOR START
#define MTR_RADIAL_CLOCKWISE        PINC3

void USARTWriteChar(char data) {
    while(!(UCSRA & (1<<UDRE))) { }             //Do nothing until the transmitter is ready
    UDR=data;                                   //Now write the data to USART buffer
}

void USARTWriteLine ( const char *str ) {
    while (*str) {
        USARTWriteChar(*str);
        str++;
    }
    USARTWriteChar(0x0D);                       //Carriage return, 13dec, ^M
    USARTWriteChar(0x0A);                       //Line feed      , 10dec, ^J
}
ISR(USART_RXC_vect) {
    char ReceivedByte;
    ReceivedByte = UDR;                         // Fetch the received byte value into the variable "ReceivedByte"
    coms = 1;                                   // We just received something
    TCNT1 = 0;                                  // reset counter
    if (ReceivedByte == 0x0A) {                 // Use linefeed as command separator
        Buffer[BufferI] = '\0';                 // String null terminator
        memcpy(cmd, Buffer, BufferI + 1);       // Copy complete command to command variable    
        BufferI = 0;                            // Reset buffer position
    } else if (ReceivedByte > 0x1F)             // Ignore Control Characters
        Buffer[BufferI++] = ReceivedByte;       // Add received byte to buffer
    #ifdef DEBUG
    switch (ReceivedByte) {
        case 'q': USARTWriteLine(Buffer); break;
        case 'w': USARTWriteLine(cmd); break;
    }    
    #endif  
}
ISR(TIMER1_OVF_vect) {
    coms = 0;                                       // Lost communication
}

ISR(TIMER0_OVF_vect) {
    if (sendheartbeat) USARTWriteChar(0x05);        // 15 times / second, 66,67ms
}

int main(void) {
    TIMSK=(1<<TOIE0) | (1<<TOIE1);      
    TCNT0=0x00;         
    TCCR0 = (1<<CS02) | (1<<CS00);  
    TCCR1B |= (1 << CS10) | (1 << CS12);

    UBRRL = BAUD_PRESCALE;                      // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
    UBRRH = (BAUD_PRESCALE >> 8);               // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
    UCSRB = (1<<RXEN)|(1<<TXEN | 1<< RXCIE);    // Turn on the transmission and USART Receive Complete interrupt (USART_RXC)
    UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  // Set frame format: 8data, 1stop bit

    DDRB = 0x00;                                // Set all pins on PORTB for output
    DDRC = 0xff;                                // Set all pins on PORTC for output
    sei();                                      // Enable the Global Interrupt Enable flag so that interrupts can be processed

    while(1) {
        if (!coms) PORTC = 0x00;                // If not coms turn of everything   
        else if (cmd[0] != '\0') {              // We have coms, check for new command
            if (strcmp("help", cmd) == 0) USARTWriteLine("liberate tuteme ex inferis");
            else if (strcmp("sync", cmd) == 0) sendheartbeat = !sendheartbeat;
            else if (strcmp("clear", cmd) == 0) USARTWriteLine(clearandhome);
            else if (strcmp("expand", cmd) == 0) {
                if (!pinstate(PINB, PROX_SW_EXPANDED))
                    sbi(PORTC, MTR_RADIAL_CLOCKWISE);
            } else if (!(strcmp("", cmd) == 0)) USARTWriteLine("Unknown command");
            cmd[0] = '\0';                      // Reset command string with null terminator.
        }
    }
}

Hopes this is helpfull.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.