ticker

29 Nov 2010

I am writing a program that controls a device over the serial port.  I am writing a routine that periodically updates the state of the device.  Not a problem, I will use "ticker" to run the routine about 20 times a second.

The wrinkle is this, sending commands to the device requires a variety of data bytes that can not be interrupted or the device hangs.  I can't have a command sequence being interrupted by the update sequence.

Is there a way to turn off the ticker process temporarally?

29 Nov 2010

Hi Chris,

When you start a section of code that can't be interrupted set a flag, then on the ticker look for the flag - if it is set exit the routine if not continue.

There is also some low level code that disables interrupts which might work - I'm not sure where it is but someone will reply here with it!

29 Nov 2010

I wouldn't "mess" with trying to disable Timer3 interrupt (which is what the Mbed library uses). You could end up with a mess.

There's possible a few solitions.

  1. Remove the attachment at the start of the ticker callback and then at the end, re-attach it.
  2. Sounds like the ticker is going off while still trying to send characters out of a serial port. Try using MODSERIAL that will buffer the output thus meaning your x.printf(whatever) go into a serial TX buffer meaning your printf()s return much faster probably avoid being re-interrupted.
  3. Probably more solutions but I need to venture out into the snow to buy milk ;)
29 Nov 2010

Hi Chris,

Take a look at Working with Interrupts. It currently shows the basics of enabling and disabling interrupts, which should be exactly what you need.

Simon

30 Nov 2010

enable_irq()/disable_irq() was exactly what I was looking for. Thanks. It would be nice if there was better documentation of those kids of features.

It works for a while, but my program hangs up. I suspect that I need an error handler for when the serial receive gets out of sorts.

30 Nov 2010

disabling/enabling irqs can help out but before using them you have to think carefully on whats going to happen.

You have a Ticker caling back 20per/second, thats every 0.05seconds. Assuming your serial port is going at 9600baud, that's (approx) 960 characters per second.

But you only have 0.05seconds, so the most you can send is 48characters. And that time assumes a buffer with 48chars ready to go and no time for any logic.

If you Ticker callback function is longer than the time between ticks then you are storing up problems. Eventually something will give up and that's possibly why your system is hanging. The fact you are having to disable the irq on entry almost points to this being the case.

MODSERIAL (I mentioned eariler) will help a bit as it buffers your serial output so your serial printf functions will return faster. But eventually, you will fill it's output buffer and same thing will happen.

If you have a scope available try setting an output pin at the start and clearing it at the end of the ticker callback. That way you can measure how long you are spending inside your callback.

30 Nov 2010

My serial port is going at 57600 baud. I'm only sending at most 5 bytes and receiving 26. The most I figure my interrupt can take is 4 or 5 ms, so that can't be the problem. The disable/enable irq is to keep the receive routine from interrupting a send routine. Yet it IS hanging up.

I am also using a couple of "wait" calls to debug (toggling pins at various points). Would wait interfer with ticker?

30 Nov 2010

Can you publish your code so we can look at it? Not going to make much progress with just descriptions otherwise

30 Nov 2010

Btw...

Quote:

I'm only sending at most 5 bytes and receiving 26

At 57000baud you shouldn't have a problem. However, without scoping out what's happening there's still an issue. Basically, how much time is it between sending your 5 bytes and getting back the first of your 26 expected bytes? How long it is till you have back the last byte of the 26? The entire exchange has to occur within your Ticker time. But without seeing your code it's rather hard to determine what's going on.

01 Dec 2010

Here is the code. The basic idea is to have the Roomba sensors update the mbed in the background using the ticker interrupt.

#include "mbed.h"

/* ROI - Roomba Open Interface  */

#define _start       0x80
#define _baud_rate   0x81
#define _control     0x82
#define _safe        0x83
#define _full        0x84
#define _power       0x85
#define _spot        0x86
#define _clean       0x87
#define _max         0x88
#define _dock        0x8f
#define _drive       0x89
#define _motors      0x8a
#define _leds        0x8b
#define _songs       0x8c
#define _play        0x8d
#define _sensors     0x8e
#define _reset       0x07


/* physical sensors */
char        bumpers;
char        wall;
char        cliff_left;
char        cliff_front_left;
char        cliff_front_right;
char        cliff_right;
char        v_wall;
char        motor_ovr_cur;
char        dirt_detect_left;
char        dirt_detect_right;

/*  buttons     */
char        remote;
char        buttons;
int         distance;
int         angle;

/*  power sensors  */

char        charging_state;
unsigned int v_batt;
int         cur_batt;
char        t_batt;
unsigned int batt_chg;
unsigned int batt_cap;

DigitalOut myled(LED1);


/*  code   */

Serial roi(p9, p10);
DigitalOut dd(p8);

/* output commands */
void roi_init(void) {

    roi.baud(57600);
    dd=1;
}

void roi_startup(void) {
    __disable_irq();
    dd=0;
    wait(0.5);
    dd=1;
    wait(2);
    roi.putc(_start);
    roi.putc(_control);
    roi.putc(_full);
    __enable_irq();
}

void    max(void) {
    __disable_irq();
    roi.putc(_max);
    __enable_irq();
}

void    clean(void) {
    __disable_irq();
    roi.putc(_clean);
    __enable_irq();
}

void    spot(void) {
    __disable_irq();
    roi.putc(_spot);
    __enable_irq();
}

void    dock(void) {
    __disable_irq();
    roi.putc(_dock);
    __enable_irq();
}

void   reset(void) {
    __disable_irq();
    roi.putc(_reset);
    __enable_irq();
}

void    drive(int velocity, int radius) {
    __disable_irq();
    roi.putc(_drive);
    roi.putc(velocity>>8);
    roi.putc(velocity);
    roi.putc(radius>>8);
    roi.putc(radius);
    __enable_irq();
}

void    motors(char motor_bits) {
    __disable_irq();
    roi.putc(_motors);
    roi.putc(motor_bits);
    __enable_irq();
}

void    leds(char bits, char color, char intensity) {
    __disable_irq();
    roi.putc(_leds);
    roi.putc(bits);
    roi.putc(color);
    roi.putc(intensity);
    __enable_irq();
}

/* input commands */
DigitalOut led4(LED4);
void    get_sens(void) {
    __disable_irq();
    led4=!led4;
    roi.putc(_sensors);
    roi.putc(0);
    bumpers=roi.getc();
    wall=roi.getc();
    cliff_left=roi.getc();
    cliff_front_left=roi.getc();
    cliff_front_right=roi.getc();
    cliff_right=roi.getc();
    v_wall=roi.getc();
    motor_ovr_cur=roi.getc();
    dirt_detect_left=roi.getc();
    dirt_detect_right=roi.getc();
    remote=roi.getc();
    buttons=roi.getc();
    distance=roi.getc()<<8+roi.getc();
    angle=roi.getc()<<8+roi.getc();
    charging_state=roi.getc();
    v_batt=roi.getc()<<8+roi.getc();
    cur_batt=roi.getc()<<8+roi.getc();
    t_batt=roi.getc();
    batt_chg=roi.getc()<<8+roi.getc();
    batt_cap=roi.getc()<<8+roi.getc();
    __enable_irq();
}

char     bump_left(void) {
    return bumpers & 2;
}

char     bump_right(void) {
    return bumpers & 1;
}

char    wheel_drop_right(void) {
    return bumpers & 4;
}

char    wheel_drop_left(void) {
    return bumpers & 8;
}

char    wheel_drop_cster(void) {
    return bumpers & 0x10;
}

char    side_brush_ovc(void) {
    return motor_ovr_cur & 1;
}

char    vacuum_ovc(void) {
    return motor_ovr_cur & 2;
}

char    main_brush_ovc(void) {
    return motor_ovr_cur & 4;
}

char    drive_right_ovc(void) {
    return motor_ovr_cur & 8;
}

char    drive_left_ovc(void) {
    return motor_ovr_cur & 0x10;
}


char    button_max(void) {
    return buttons & 1;
}

char    button_clean(void) {
    return buttons & 2;
}

char    button_spot(void) {
    return buttons & 4;
}

char    button_power(void) {
    return buttons & 8;
}


Ticker tick;
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);


int main() {
    led1=1;
    roi_init();
    led1=0;
    roi_startup();
    tick.attach_us(&get_sens,100000);
    while (1) {
        leds(2,128,128);
        led3=!led3;
        wait(0.1);
        leds(1,1,255);
        wait(0.1);
        led1=bump_left();
        led2=bump_right();
    }
}
01 Dec 2010

All those enable/disable of interrupts really just shouldn't be there. If they are needed then there's something fundamentally wrong. Specifically, having many comms IO calls within a function that is attached to a callback like that will always end up being an app killer. The comms should be moved out of the callbacks.

Additionally, I previously mentioned trying MODSERIAL and I think you'll found it may well have helped to some degree. However, MODSERIAL adds extra functionality you can use to implement what you are trying to do. Here's some sample code I knocked up:-

#include "mbed.h"
#include "MODSERIAL.h"

#define _sensors     0x8e

/**
 * @file Sample program to demo getting the sensor data
 * @see http://www.roombadevtools.com/docs_roombasci.pdf
 *
 * Only a sample, doesn't actually get them all, see the code
 */
  
char bumpers;
char wall;

MODSERIAL   roi(p9, p10);
Ticker      tick;

bool getSensorsFlag = false;

void get_sensors_callback(void) {
    // Don't send another request if the last 26
    // have not yet been completely received.
    if (!getSensorsFlag) {
        getSensorsFlag = true;
    }
}

void request_sensors(void) {
    if (getSensorsFlag == true) {
        getSensorsFlag = false;
        if (roi.rxBufferGetCount() == 0) {
            roi.putc(_sensors);
            roi.putc(0);
        }        
    }
}

int get_sensors(void) {
    if (roi.rxBufferGetCount() == 26) {
        bumpers = roi.getc();
        wall = roi.getc();
        // ... continue getting your other parameters.
        // ... then ensure the rx buffer is clean.            
        roi.rxBufferFlush();
        getSensorsFlag = false;
        return 1;
    }
    return 0;
}

int main() {
    roi.baud(57600);
    tick.attach(&get_sensors_callback, 0.1);

    roi.rxBufferFlush();

    while(1) {
        // If the ticker callback has set the flag, send the request.
        if (getSensorsFlag == true) {
            request_sensors();  
        }
 
        // get_sensors() will return 1 when it's read teh 26 characters,
        // otherwise zero.
        if (get_sensors() != 0) {
            // horray, we should have all your data.
        }        
    }
}

Notice how the ticker callback just sets a flag. In the main loop if the flag is set and the rx buffer is clear (empty) we send the command. Then, in the main loop we check how many characters have arrived into the serial buffer. Once we have all 26bytes, it's at that point we read them into their variables. Notice also we don't send and request for more data until we know for sure we have all the data from the last request.

Also notice that get_sensors() doesn't start trying to read the data from the serial port until it has the 26 characters in it's buffer.

The above code does compile, however, it's not complete, just an example for you to look at.

01 Dec 2010

The problem is that it doesn't do what I want it to do. I want to collect the sensors whilst the mbed is doing other things. Your code only starts an update the sensors at the beginning of the main loop. I want to be able to forget about asking it to update

Also, I can't send anything out other than request sensors. The roomba can't send and receive at the same time.

The point of making the request sensors routine the interrupt and leaving the command routines in the main code is that you can't do both at the same time. I am either sending a command to the roomba or receiving data from it. The enable/disable code is to prevent the send command from colliding with a request data command. The reason there are a lot of them is that I made several simple routines rather than one more complicated routine.

The problem isn't buffering the incoming data, it's keeping the send data routine from being messed up by the get data routine.

I am obviously missing something here. I'll chalk it up to my inexperience with C++ (I'm fine with C, but C++ drives me nuts) and the maddening lack of documentation for Mbed.

01 Dec 2010

Quote:

The enable/disable code is to prevent the send command from colliding with a request data command

Have to be honest here, your use of the enable/disable is just down to poor design of the program. What you need to do is create a state machine that manages the flow of data to/from the Roomba. MODSERIAL implements callbacks when it gets a character. SO, for example, you have a variable called "int roi_state;" and you use that to manage the flow control. So, for example:-

#include "mbed.h"
#include "MODSERIAL.h"

#define ROI_STATE_IDLE        0
#define ROI_STATE_GET_SENSORS 1
#define ROI_STATE_COMMAND     2

int roi_state = ROI_STATE_IDLE;

#define _sensors     0x8e
#define _drive       0x89


/**
 * @file Sample program to demo getting the sensor data
 * @see http://www.roombadevtools.com/docs_roombasci.pdf
 *
 * Only a sample, doesn't actually get them all, see the code
 */
  
char bumpers;
char wall;

MODSERIAL   roi(p9, p10);
Ticker      tick;

void tick_callback(void) {
    if (roi_state == ROI_STATE_IDLE) {
        roi_state = ROI_STATE_GET_SENSORS;
        roi.rxBufferFlush();
        roi.txBufferFlush();
        roi.putc(_sensors);
        roi.putc(0);
    }    
}

void serial_callback(void) {
    if (roi_state == ROI_STATE_GET_SENSORS) {
        if (roi.rxBufferGetCount() == 26) {
            bumpers = roi.getc();
            wall = roi.getc();
            // ... continue getting your other parameters.
            // ... then ensure the rx buffer is clean.            
            roi.rxBufferFlush();
            // Reset the state machine back to idle.
            roi_state = ROI_STATE_IDLE;
        }
    }
}

// This just one example of how to send a command
// to the Roomba
void drive(int velocity, int radius) {
    // If the serial system is not idle we
    // must wait for it to become idle.
    // However, this causes your program to "block"
    // Wait. You could have this function return 
    // immediately with an error code instead of 
    // waiting around.
    while (roi_state != ROI_STATE_IDLE) ;
    
    // Now, claim control of the serial system
    // so we can safely send a command.
    // We loop to ensure we get control so 
    // that if an interrupt "nips in" and
    // changes state we don't go on in error.
    while (roi_state != ROI_STATE_COMMAND) {
        roi_state = ROI_STATE_COMMAND;
    }
    
    // We now safely have control.
    roi.putc(_drive);
    roi.putc(velocity>>8);
    roi.putc(velocity);
    roi.putc(radius>>8);
    roi.putc(radius);
    
    // Assuming that's it, reset the state machine 
    // back to idle and release serial control.
    roi_state = ROI_STATE_IDLE;
}

int main() {
    roi.baud(57600);
    
    // Ensure the buffers are clean before attaching
    // the interrupts to serial and ticker.
    roi.rxBufferFlush();
    roi.txBufferFlush();
    roi.attach(&serial_callback, MODSERIAL::RxIrq);
    tick.attach(&tick_callback, 0.1);

    while(1) {
        // Main loop of your program. Above is an 
        // example of sending commands so as not 
        // to conflict with putc/getc made via
        // interrupts.
    }
}

The above program will get the sensor data "in the background". However, the state machine is to ensure there's no conflict between how the interrupt callbacks use the serial port and how your main program uses it. When two or more devices communicate it's important you develope a "protocol" rather than just send/receive bytes in an adhoc fashion. The Internet would desolve in seconds without the Internet Protocol.

I doubt it's a C++ verse C issue. The Mbed librraies maybe C++ but your program is effectively C. I think the real issue is you may not be use to interrupts in the embedded enviroment. If you are not use to handling them they can really give you a bad day.

01 Dec 2010

btw, in teh simple program above I only created #define ROI_STATE_COMMAND 2

If command you send may make the Roomba send a reply you may have to define a state for each command. That way your serial_callback() function knows what to do with incoming characters. The state machine defines "whats happening" to all parts of your system.

Also, use MODSERIAL and use the roi.rxBufferGetCount() function to check for how many character are in the buffer verses what state you are in. Doing this means you never hang around inside an interrupt callback. So, if you have issued a command and you know you expect two bytes back from that command then in your serial callback you can check what command was issued verses how many characters are available to read. Only when the Roomba has sent all expected bytes back to you do you try to read them. That's what MODSERIAL is really developed for. It allows you to collect up a "packet" but only act on it once you have received the complete packet of bytes rather than trying to read them one by one or worst still do a getch() that stalls out your entire system waiting on just one byte.

01 Dec 2010

You mentioned C vs C++ earlier. I can understand that, I can remember trying C++ after many years of C/Assembler and think "why? just why?". But one day the light dawned.

Now, as I said, your program is actually C. Here's a C++ "object" that may make your application alot easier:-

Import programRoomba

Roomba robot class

So, what's this all about? Well, first up, I don't have a Roomba so this class is totally untested. However, it should be a good starting point. Why bother doing this? Well, look at main.cpp. Once you have created a Roomba object using it is simple. There's only two functions you need. command() and sensor(). All that nitty gritty serial stuff and interrupts is hidden out of sight in the class/object. As you can see, once the Roomba class works fine you can concentrate on the fun bit which is your application and making it do cool stuff.

This is where C++ really comes into it's own. Controlling your entire robot with just two simple functions.

02 Dec 2010

Damn! It was sort of the idea to write this up as a library. You beat me to it.

Oh well. Mine wasn't working anyway.

OK. How do we get different size packets? The roomba is able to send different sized packets for different sensor groups. For the simpler roomba model, 26 bytes is the max. The Create and the more advanced roombas can send up to 52 bytes. It might be good to change the number of bytes received. I'm thinking a flag that serial_callback reads to determine the number of bytes read and the how they re processed.

02 Dec 2010

Hi Chris,

Well, it seems to me you still have to write this up as a library, I just gave you "a leg up" :)

What I basically did was build that from the datasheet I found that showed it pretty much static. I didn't realize there were many models!

Is there a way in the protcol somewhere that allows you to query what model it's connected to? If so, get that first and then store it as a class property. From that info you can use it to modify how the state machine may work and how the serial interface works.

And here's a fun C++ exercise. Assuming the Roomba so far is teh "base model" you can use the class as a "base class". You then create new classes for each model that inherit the base class (so getting the base functionalty) and just add to your new class the new bits the next model up offers.

But that depends on how much the Roombas build up. If they differ markedly in serial protocol for example, you won't gain much.

Anyway, I'll leave you to play with what I've done so far. It was a "get you going" exercise and get rid you of those pesky IRQ problems. I think now you'll see probably the best way to go about building your project. And if you win the Mbed Challenge with it you owe me a beer :)