Friday, February 17, 2012

RC Lap Timer - Go Kart Lap Timer Part 2 - Arduino LCD Interface

Suggested Audience : Arduino users who are new to programming and want to move their projects beyond the basics.

Before we look at a simple strategy for managing the Arduino user interface lets look at the 'RC Car/Go Kart Lap Timer' as an example of a typical Arduino project with a user interface.

The RC Car/Go Kart Lap Timer Project


The Physical Interface -

We have an LCD which is used to display several different sets of information.
We have four buttons - Up, Down, Ok and Cancel which are used to navigate through the information and operate upon it.

The system operates in the following modes -

Idle - this is the start up mode and shows summary information about the number of sessions, the number of laps and the number of laps that can be recorded with the remaining space.


Record - In this mode the system creates a new session and begins recording laps to the session

Session Summary - In this mode the user can use the up/down buttons to review session information and the Ok button to enter Session Review mode or the Cancel button to return to idle


Session Review - In this mode the up/down buttons are used to cycle through the individual laps within a session.




Additional modes are provided for clearing all laps and sessions and for deleting individual sessions.


The challenge we face is that in each mode (or context if you prefer) the purpose of the buttons can change, for example to access the 'delete all sessions' option the user can press the cancel button on the start up screen. This will take the user to a new context where in order to confirm the delete operation the user must then press OK, in this new context the cancel button means, cancel this current operation and take me back to where I was.

From an end user perspective this is a reasonable interface, cancel always means 'Clear this' or 'Get me out of here', whereas OK always means 'Go to this' or 'Yes, I really do want to do this'.

Hold this thought for a moment - within our code we need to manage multiple situations where the same buttons must execute different code.

For a very limited user interface such as an on/off toggle it is enough to simply track a state, for example in the 'radio controlled car - child mode' project elsewhere on this blog, there is a mode variable which is either set to child mode or dad mode. In this simple case there are no other controls and so this approach is entirely sufficient.

Further project details here -

http://rcarduino.blogspot.com/2012/01/traction-control-part-13-we-have.html

       switch(nMode)
       {
         case DAD_MODE:
          // Do or show this
          break;
         case CHILD_MODE:
          // Do or show that
          break;
       }  

As a rule this approach is sufficient when there is no need to repurpose the controls to reflect the new mode and also when each mode is stateless. Take note of the term 'stateless' we will come back to it.

The child mode project, has two modes and one input to toggle between them = 2*1 = 2 possible interactions. If this is as far as your projects go, you can stop reading here, but if you ever want to add a data review mode, the option to clear part or all existing data or any other form of user interaction, read on.



It is tempting to try and extend this example to cope with the situation where there are multiple inputs and modes and your certainly welcome to try this -

       switch(nMode)
       {
         case IDLE:
          switch(nKeyPressed)
          {
            case OK:
             nMode = RECORD;
            break;
            case CANCEL:
             nMode = CONFIRM_DELETE_SESSIONS;
            break;
          }
          break;
         case RECORD:
          switch(nKeyPressed)
          {
            case OK:
             // do nothing, keep recording
            break;
            case CANCEL:
             // exit record mode back to idle
             nMode = IDLE;
            break;
          }

          break;
       } 

This is the approach I initially took when building the lap timer interface however it quickly gets out of hand.

The Lap Timer project, 5 Modes, 4 buttons = 5*4 = 20 Possible Interactions.



The typical approach is to nest switch statements inside switch statements. The outer statement manages the mode and then the inner statement manages the action for each key or button within the mode. The example code above covers only a small fraction of the modes and keys required for the lap timer and its already hard to read, it also does not actually do anything, at some point we still need to call the functions that will actually do the work. And thats where I got to thinking.


Functions

Functions are naturally hierarchical. In our Arduino code, loop is the head of the hierarchy, we can call other functions from loop but when they exit, they return to loop which is still exactly as it was when they left it.

Isn't that a bit like way most of us need our user interfaces to work ?

As an example in the lap timer I begin in an idle mode and can then enter 'session summary' and from there 'lap review'. If I exit lap review I want to fall back to 'session summary', it would also be neat if this mode was able to show me the session I was last looking at wouldnt it ?


Why introduce and have to manage a 'mode' variable when the hierarchical nature of functions naturally gives us the the flow we want ? I can't think of a single good reason to use a mode variable in place of this approach and it gets better.

Remember that term from earlier - 'stateless' ? well there is nothing better at describing the state of a function than the function itself. Why do we care ? well if we look at the preceding diagram, we want to give users the option to cancel out of the lap review screen and return to the session review screen, we also want to ensure that when they do they are presented with the session they last selected, thats the state.

Now go back and look at the example code that started to implement the Lap Timer interface using nested switch statements, where is the state ? There isn't any state, you need to code it alongside the 20 possible user interactions, I can't help but get the feeling that this is building mess on top of mess on top of mess.

So whats the alternative ? well very simply we can create each mode as a function and let the hierarchical and self describing nature of functions do the work for us.

In the case of the Lap Timer we have functions doIdle, doSessionReview, doLapReview, doRecord and doConfirmDelete. In each function we need a very simple version of the switch statement we tried to create earlier, but the beauty of this approach is the phrase 'a very simple version of the switch statement we tried to create' it really is so much easier to write, understand and maintain the entire user interface this way.

The advantages -

  • Each state or mode is defined as a standalone function
  • Each function describes its own state without additional variables or code to manage them
  • The hierarchical nature of functions supports the hierarchical nature of many user interfaces without additional code or effort.
  • Its clearer to read, understand, fix and maintain
  • There is no overhead to maintain a redundant 'mode' variable.

What does it look like ?

A main loop might look something like this -

void loop()
{
 // lets keep control of the loop
 while(true)
 {
  // wait for a key command to tell us what to do
  while((sKey = getKeys()) == KEY_NONE)
  {
    // do nothing
  }

  // I only need to consider what the keys do in this mode,
  // I am by definition not in any other mode and so I have
  // no concern what the keys might do if I were
  switch(sKey)
  {
   // start recording
   case KEY_OK:
          // don't try and manage a mode and its state, just call a function that implements the mode
          // when the function exits, we are not in the mode anymore so no need to keep track of the 
          // mode or its state. Keeping it simple, neat and efficient
    doRecord();
    break;
   // delete all sessions
   case KEY_CANCEL:
    doConfirmDeleteSessions();
    showTotals();
    break;
   // scroll through recorded session summaries
   case KEY_UP:
   case KEY_DOWN:
    doShowSessionSummaries();
    break;
  }
 
  // We will normally be here because cancel or ok was pressed to exit
  // one of the other function so wait for the key to be released
  waitForKeyRelease();
 }
}

This can be described with the following blue print -

1) Look for any events that might require us to change mode
2) Depending on the event, call a function that 'is the mode'
3) When the function we called exits, we are no longer in the mode, so we don't need to track it or its state here.
4) Lets get right back to the start and look for an event again

Each of the mode functions is implemented in exactly the same way -

//////////////////////////////////////////////////////////////////////////////////
//
// doShowSessionSummaries
//
//////////////////////////////////////////////////////////////////////////////////
void doShowSessionSummaries()
{
 Serial.println("Entering doShowSessionSummaries");
  
 short sCurrentSession = 0;

 showCurrentSessionSummary(sCurrentSession);

 short sKey = KEY_NONE;
  
 boolean bFinished = false;

 do
 {
  waitForKeyRelease();

  switch(sKey)
  {
   case KEY_UP:
    sCurrentSession = showCurrentSessionSummary(++sCurrentSession);
    break;
   case KEY_DOWN:
    sCurrentSession = showCurrentSessionSummary(--sCurrentSession);
    break;
   case KEY_NONE:
    break;
   case KEY_CANCEL:
    bFinished = true;
    break;
   case KEY_OK:
    doLapScroll();
    break;
  }
 
 }while(!bFinished);

 Serial.println("Leaving doShowSessionSummaries");
}

The blue print is the same again here -

1) Check for an event that could cause us to change modes
2) Call a function that 'Is The Mode'
3) If the function exits, we are not in the mode anymore so don't need to track anything here
4) back to 1

The final case is quite interesting. If the user presses the button representing OK (case KEY_OK: in the example), we call doLapScroll to enter the lap review mode for the current session. The user can scroll back and forth through all the laps recorded for this session, however if they press cancel to exit they fall right back into the current mode exactly where they left it, even looking at the same session.

With a little bit of effort this approach - 'Functions for Modes' just works, it gives you mode management and state management without you having to explicitly code any of it.

Let me know what you think, could the explanation be clearer ? do you disagree with the approach or have a better alternative ?

Duane B

Thursday, February 16, 2012

RC Lap Timer. Go Kart Lap Timer - Part 1

In between other projects I have been working on a personal RC Lap Timer. The system uses a simple mechanism to detect a lapping car and is as a result limited to one car, however there is a secondary goal.



The secondary goal is to design the system to work equally well whether it is timing a passing car or actually in the passing car, in particular I want to be able to strap the lap timer to the steering wheel of an 'arrive and drive' go-kart in order to get in car lap times.

The system uses infra red so is completely portable, it also works equally well whether the transmitter is passing the timer or the timer is itself in a car passing a track side transmitter. The system is not based on a beam breaker and will not register false laps for other cars or karts, instead the system uses a coded infrared signal and could in principle be extended to time more than one car.

Before starting work on the enclosure and user interface the system was tested extensively timing my Tamiya M03.

The Final Form


As the system stands the components are too bulky too fit the form factor I am aiming for -


In the near future when I have finalised the software I will replace the Arduino UNO with a stripboard Arduino which should allow me to fit the entire system in the palm sized project box pictured.

I am currently testing the user interface which allows the user to -

  • View initial screen with session count, recorded lap count and remaining lap count
  • Delete all sessions
  • View summary screen for each session with number or laps, average lap time, best lap time
  • Delete individual session
  • Cycle through individual laps within a session

And also to press record to begin recording a new session. 

I am really looking forward to being able to use this both with the RC Cars and to get back into Kart racing.

Stay tuned ...

Friday, February 3, 2012

Can I Control More Than X Servos With An Arduino ? Follow Up Results

The servo library can be uses a single Arduino Timer to control upto 12 Servos.

See here for a detailed overview and sample code - How To Control X Servos and MultiSweep Example

To achieve this the library attaches an interrupt service route to the timer which is called around 50 times per second for each connected servo. In the case of 12 Servos, this is 600 times per second.

Within the timer service routine, there are array access operations, function calls and also a few calculations are performed. It is reasonable to expect that repeating these operations 600 times per second may have a performance impact on the Arduino.

To measure the impact I have modified the multi sweep sketch to run a busy loop and a hardware timer. When the timer counts down to 0, the loop exits and prints the number of loops completed during the countdown. The countdown period is approximately 3.8 seconds.

With no servos attached, the busy loop is executed 219,744 times per countdown. We will consider this bench mark as equivalent to 100% available processing power.

With 12 Servos attached, the busy loop is executed 217,719 times per second, based on the no servo bench mark this is equivalent to 99% meaning that to drive 12 servos, only 1% of available processing power is required.

If my logic and calculations are correct, thats very Impressive.

Try it for yourself -

// Multi Servo Benchmark
// The loop tests the base load for the Arduino Servo
// library to maintain positions on upto 12 connected servos
// to change the number of servos change the CONNECTED_SERVOS value

#include <Servo.h>
// Sample sketch for driving 12 Servos from an Arduino UNO, servos are attached to digital pins 2,3,4,5,6,7,8,9,10,11,12,13

#define CONNECTED_SERVOS 12

// macro just adds two - the first servo is attached to digital pin 2, this gives us upto 12 servos - digital 2 to 13
#define SERVO_TO_PIN(x) (x+2)

Servo myServos[CONNECTED_SERVOS];

#define COUNT_DOWN -1
#define COUNT_UP +1
#define INCREMENT 10 // move in steps of 10 milliseconds

int nPulseWidth = DEFAULT_PULSE_WIDTH ; // 1500, defined in servo.h
int nDirection = COUNT_UP;

volatile unsigned long ulStart = 0;
volatile unsigned long ulStartToEnd = 0;


#define CLICK_START 255

volatile short sClicks = CLICK_START;
volatile unsigned long ulAnother = 0;

ISR(TIMER2_OVF_vect)
{
  if(sClicks)
  {
    sClicks--;
  }
};

void setup()
{
  TCCR2A = 0;
  TCCR2B = (1<<CS02|1<<CS01|1<<CS00);
  TIMSK2 = 1<<TOIE2;
 
  Serial.begin(9600);

  // attach the servos
  for(int nServo = 0;nServo < CONNECTED_SERVOS;nServo++)
  {
    myServos[nServo].attach(SERVO_TO_PIN(nServo));
  }
 
  pinMode(13,OUTPUT);
 
  digitalWrite(13,LOW);
}

void loop()
{
   unsigned int uiOuter = 0;
   unsigned int uiInner = 0;
  
   for(;sClicks && uiOuter < 0xFFFF;uiOuter++)
   {
    uiInner = 0;
    for(;sClicks && uiInner < 0xFFFF;uiInner++)
    {
     int nTemp = uiInner%10;
     ulAnother = uiOuter/nTemp;
    }
   }
  
   Serial.println(uiOuter);
   Serial.println(uiInner);
  
   sClicks = CLICK_START;
}



Duane B.

Wednesday, February 1, 2012

Newbie Advice and A New Take On An Everyday RC Experience

Speed isn't everything. 

The following quote from an RC forum thread about building cars for maximum speed sums this up nicely - 'I built my car up to go as fast as possible, I got it over 100km/h, but, it got boring very quickly.'

It's true, fast model cars are just not very interesting. It's easy to build or buy a car that will crack 100km/h, but really, what are you going to do with a car that goes from 0 to out of sight in 3 seconds ?

Most of us that have a long term involvement in RC started with something moderately fast, learned to control it, upgraded it, then bought something faster. Many of us actually end up selling the faster cars and building a growing collection of moderately fast cars.


Part of my collection of moderately fast Tamiya M-Chassis Cars



A New Take On An Everyday RC Experience

Its a universal experience shared by RC Enthusiasts all over the world, your driving your model, working on your line through a fast s-bend you have marked up in the street. Someone approaches you, they could be of any age, race, or background and the conversation will be exactly the same -



Passerby: How fast does it go ?
You: Answer whatever you like ...
PasserBy: I have one that goes 100km/h
You: Thats interesting, where is it ?
 

Choose one -
PasserBy:I don't use it much (we knew that, fast cars are boring aren't they)
PasserBy:Its broken I need to get get it fixed (it was too fast for me, I crashed it, I would get it fixed, but it wasn't that much fun anyway so i never got around to it)

The interesting thing is that these guys who bought the faster cars and since lost interest, they always make the effort to come over and talk to you. 


It often feels like they want to show off.

I am trying to see it a different way -

They see you skillfully threading your model through the corner,fine tuning your line at each pass, the car looks amazing, braking slightly on entry, turning in, holding the speed through the corner and then accelerating out. It's everything they wanted their own RC Experience to be, they just bought the wrong car.

Do I ever offer these guys a turn of my own car ? If I am honest, no! never.

But now that I understand them a bit better, next time I will slow my car down and offer them a chance to see how much fun a moderately fast car can be.

Can I Control More Than X Servos With An Arduino ? Follow Up

I can't help feeling that the original post is incomplete. While I have shown how to control 12 Servos using the Arduino Servo library, I have not provided any indication of the performance impact. If it takes 90% of the available processing time to control the 12 Servos, using a single Arduino is not a very practical solution.

Its not that big problem either, anything that requires 12 Servos could probably benefit from the additional processing power and sensor inputs that one or more additional Arduinos would bring. When an additional Arduino can be built for less than 10 dollars there is no good reason not to take this approach.

Anyway, in the interest of finishing the original post and learning a little more about the Atmega architecture, I plan to follow up a few ideas I have for using Timer0 and a set of loops to measure the impact of 0,1, 6 and 12 servos. At the moment I am several thousand miles from home but will get onto this in the next few days.

Duane B