Saturday, March 16, 2013

The Analog Pin Classes; An Introduction to Event Driven Programming

An Arduino analog pin take approx. 100 us to sample and convert a measurement from analog to digital value (max resolution 10-bits, and max recommended conversion frequency). The conversion is done by special hardware in the processor. In the Arduino/Wiring implementation, analogRead(), the processor will loop, busy-wait, for the conversion to be completed. Other things could be done while waiting or the processor could be put in idle mode to reduce power consumption.

This posting presents some of the ways to use the Cosa AnalogPin class. The first style of usage is, as Arduino, pure synchronous where the processor will busy-wait for the analog conversion.

AnalogPin sensor(Board::A0);
...
uint16_t luminance = sensor.sample();


As described in previous postings, Cosa is an object-oriented approach to programming the Arduino where the resources in the processor are C++ objects. Analog pins are objects with methods/functions. In the above example the object named sensor is an instance of AnalogPin and may perform the methods (member functions) available in the class. The method sample() corresponds directly to the Arduino function analogRead() with the difference that no parameter is required as the pin number this is already known by the AnalogPin instance.

Fig.1: AnalogPin Member Functions
 
The AnalogPin constructor requires the Board analog pin name (e.g. Board::A0) and has also a reference voltage parameter with the default value AVCC_REFERENCE. The full expanded statement for the above example is:

AnalogPin sensor(Board::A0, AnalogPin::AVCC_REFERENCE);

There are three reference voltage types defined as an enum in the AnalogPin class; APIN_REFERENCE, use the Arduino reference voltage pin, AVCC_REFERENCE, use the power supply voltage as reference, and A1V1_REFERENCE, 1.1 internal voltage reference. Depending on your application you should select the correct reference voltage and perform necessary scaling of the sampled values.

The reference voltage may be changed with the method, set_reference(). It should be noted that the reference voltage is per analog pin (per sample) and not a single setting for all analog pins. This is a major difference compared to the Arduino analogReference() function which is global and does not use strong data typing, i.e., provide compile checking of the parameter. The Cosa AnalogPin class also provides an operator>> variant of the sample() method.

sensor >> luminance;

The latest sample value is available with the access method get_value(). This is an important aspect of Cosa AnalogPin as the state of the analog pin is maintained by the object. This reduces application code and memory footprint.

There are two primary programming styles in Cosa to allow applications to execute code while waiting for a conversion to complete. The first method is to request a sample, execute some code and then wait the conversion. This partially reduces the busy-wait section and adds concurrency.

sensor.sample_request();
...

// Some code to execute before waiting for the conversion to complete
...
uint16_t luminance = sensor.await();


For instance, you could print a message before waiting for the analog conversion to complete. This is an interesting example as the UART is also a hardware unit and works concurrently with the processor. In this case, you could have three concurrent activities processing at the same time; 1) the analog to digital conversion (ADC), 2) the UART transmitting characters from the IO buffer, 3) the processor itself which could be doing some computation with a previous sample value.

The Arduino processor (ATmega328P) has a number of hardware units (Timers, UART, SPI, I2C, EEPROM, etc) all possible to run concurrently with the processor. It is important to have a programming paradigm that does not limit this ability. The IO processing should be conducted, synchronized, by the processor and not worked in a serial, sequential, fashion. An analog to digital conversion takes about 1800 instructions cycles (112 us) to complete, and the maximum number of conversions per second is less than 10,000. This should be compared to the 16,000,000 instructions per second that the processor can execute. Busy-waiting for the conversion is basically a waste of 1800 instruction cycles and "electricity".  

The default behavior for the AnalogPin interrupt handler (ISR) is to push  Event:: SAMPLE_COMPLETED_TYPE with the object and conversion value onto the event queue. The interrupt handler, on_interrupt(), is also a virtual method and may be replaced by the application if needed.  The interrupt handler is per instance which allows additional modification after application requirements.

The sketch must use an event dispatcher in the Arduino loop() to process the completion events.  The events in the queue should be viewed as delayed function calls. Instead of executing code in the interrupt handler the code, action, is delayed as an event.

void loop()
{
  Event event;
  Event::queue.await(&event);
  event.dispatch();
  ...

}

The event dispatcher will call the AnalogPin implementation of the virtual method Event::Handler::on_event(). The default behavior of this method is to handle two events, Event::TIMEOUT_TYPE, and the above Event::SAMPLE_COMPLETED_TYPE. On timeout the event handler will issue a sample_request() which allows periodic sampling of an analog pin by attaching the pin object to one of the Watchdog timer queues. There is one queue for each of the Watchdog timeout levels.

Watchdog::attach(&sensor, 64);

The above statement will attach the sensor to receive timeout events every 64 milliseconds and automatically request a new sample. The analog pin may be viewed as holding a continues snapshot of sampled values.

When receiving Event::SAMPLE_COMPLETE_TYPE the AnalogPin default event handler will check if the value has changed and call the on_change() virtual method. Applications should defined their own action by sub-classing AnalogPin and implementing the on_change() virtual method. Other methods of allowing extension is to provide a callback function. The advantage of using a virtual method is that the context of the callback is provided by the object.

The flow of control from interrupt to event handler and action is:

(1)  ISR(ADC_vect) 

     => AnalogPin::on_interrupt(uint16 value) 
     ===> Event::push(Event::SAMPLE_COMPLETED_TYPE, this, value);

(2)  Event::queue.await(&event);
(3)  event.dispatch() 

(4)  => AnalogPin::on_event() 
     ===> AnalogPin::on_change()
  1. Interrupt Service Routine (ISR) is called when the analog conversion is completed. It will enqueue an Event::SAMPLE_COMPLETED_TYPE, together with the AnalogPin object and the converted value.
  2. The dispatch loop function Event::queue.await(&event) will dequeue the new event. 
  3. The event.dispatch() corresponds to calling the on_event()  method for the receiving object; the AnalogPin object.
  4. The default on_event() behavior for the AnalogPin will call the on_change() if the value on the analog pin has changed compared to previous sample.
To summarize: The Cosa AnalogPin class allows the same sampling of analog pins as Arduino together with two asynchronous methods where applications may perform additional operations while waiting for the analog measurement to be completed. The Cosa AnalogPin interrupt handler is fully integrated with the Cosa Event handler and adapted to both timed sampling of analog pins and callback when analog pin values change.

Cosa also support the sampling of a batch of analog pins. Please see the documentation of Cosa AnalogPins class for more details.

3 comments:

  1. Hi,

    Nice walkthrough, but I couldn't get the event-based version to run on my board. I'm trying to use a movement detector to trigger the on_change handler. The polling version of the AnalogPin class works fine, also when I subclass it. But the compiler complains about the Watchdog::attach(&sensor, 64); line (no matching function found in Watchdog). It was satisfied if I also sub-classed the Link class, but the handler is never called. I must admit I'm new to Cosa, and maybe something has changed since you posted this? You wouldn't have a running example of this?

    /Andréas

    ReplyDelete
    Replies
    1. Hi! Yes the design has changed since this blog post. The best example of how to use the AnalogPin events is the Keypad handler. https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/Keypad.hh. See also the example code https://github.com/mikaelpatel/Cosa/blob/master/examples/LCD/CosaKeypad/CosaKeypad.ino for the necessary setup and loop. Cosa has a default event handler as the loop function. You can omit that if you want and use the default.

      Since the blog post was written in March a lot has happened. More or less double the number of classes, files and lines of code. A number of classes have been refactored. The AnalogPin class is one of them. Previously it also inherited from Link but to save memory this is not default.

      Cheers! Mikael

      Delete
  2. Great! I'll have a look at those examples. I've just ordered an LCD shield with buttons anyway, so this will be interesting.

    Thanks for the good work on cosa. For me it makes arduino a lot more fun and interesting. I can sharpen my C++ skills and play around with HW stuff at the same time :)

    ReplyDelete