Cosa: An Object-Oriented Platform for Arduino programming

Mikael,
This is really interesting what you are doing.
I have peeked into your Cosa OOP documentation over the past weeks and can see you have put a tremendous amount of work into this.
It looks looks to be a highly organised, well thought out and useable framework.

I look forward to learning more and hopefully trying it out.


Paul

Paul, thanks for your comments and encouragement.

Cosa as a project is great fun and I have lots on the agenda. It is very much a bottom-up process to build a framework and I want/need to get feedback early on to move in the right direction. Obviously more support for Wireless, Ethernet, USB, and many more sensor drivers are needed. Also the host part of Ciao/Cosa Fai is also needed before becoming really useful.

If you get the time to test Cosa I would very much appreciate feedback.

Cheers!

A new Cosa blog post is available. It presents the basic working procedure for getting Arduino Cosa sketches working on ATtiny. Special focus is on how to use the Cosa Soft UART and PCD8544 LCD driver for trace output from ATtiny.

Cheers!

Just stumbled onto this. Nice lib!

I am writing a modest lib myself that is only for advanced users and targets to minimize ram usage (at the expense of using more prog-mem) and allow total control. I got frustrated with the stupid Arduino lib implementations that seem to be the standard -they are great for demo's and simple prototypes, but will fight you when you want to do some real work. I also use mainly template classes and no virtual functions to accomplish my goals. I intentionally leave out/do not use certain OO/C++ features in order to keep it small.

What do you think of this (to me, this looks very logical):
ViewPort<TextLcd<LcdDriver<..pin io..>, ..physical size..>, ..virtual size..>

This demonstrates:

  • separation of concerns: the actual hardware driver can be swapped out for different types of LCDs, or any one of the template classes
  • extensablility: if you don't like the impl. of some part, replace with your own.
  • No ram: all parameters are compiled into the code and the template classes do not use virtuals. This assumes you will not add/change LCD displays dynamically.

To avoid using virtuals, I use this a lot:

template<BaseT>
class SomeService : BaseT
{
public:
  void SomeMethod()
  {
     BaseT::MethodOnBaseT();
  }
};

Currently I still depend on the Arduino lib, but I'm planning to replace that.

Anyway, all the best with your library.

The library looks nice, but I don't get how I can subscribe to an event (for example of an ExternalInterruptPin) when subclassing is not an option? The class pushes an event but I do not see how I it can be handled from outside.

Ferio:
...how I can subscribe to an event (for example of an ExternalInterruptPin) when subclassing is not an option? The class pushes an event but I do not see how I it can be handled from outside.

Hi Ferio. Thanks for your interest in this project. The short answer to your question is that you can always handle the event directly instead of calling dispatch(). Below is snippet from CosaFSMBlink which uses the dispatch-method.

void loop()
{
  // The basic event dispatcher
  Event event;
  Event::queue.await(&event);
  event.dispatch();
}

And below is a snippet from the CosaNEXAreceiver sketch which handles the event instead of calling the on_event() method.

void loop()
{
  // Wait for the next event
  Event event;
  Event::queue.await(&event);
  uint8_t type = event.get_type();
  Event::Handler* handler = event.get_target();

  // Check that the event is an read completed and from the correct source
  if ((type != Event::READ_COMPLETED_TYPE) || (handler != &receiver)) return;
  ...
}

The flow of control is:

  • The event.await() will perform a sleep while waiting for events
  • The ExternalInterruptPin::on_interrupt() method is called when an interrupt occurs. The default implementation will push a changed event onto the event queue
  • The event.await() returns with the event
  • If event.dispatch() is called the event target on_event() method is called, otherwise decode the event type and target

You can find more details about this on the blog, Cosa: The Analog Pin Classes; An Introduction to Event Driven Programming and Cosa: Object-Oriented Interrupt Handling

Cheers!

[quote author=obiwanjacobi link=topic=150299.msg1227898#msg1227898 date=1367654559]
Just stumbled onto this. Nice lib!

Thanks obiwanjacobi I had a quick look at your library. Many interesting ideas! While I started off not using templates, your approach is not using virtual.

I have introduced a few templates for some of the basic classes (such as Queue and IOBuffer) to allow additional compiler optimization and strangely higher performance. I use virtual mainly for callbacks and abstract interfaces with code reuse.

Using functions as callbacks often requires additional parameters and state. In these cases an instance/object is a nice way to handle this. It scales better than callback functions and global variables. Virtual also allows reuse of a lot of code and reduces the program memory footprint. A good example is the IOStream::Device class and the default implementation which is reused many time by both IOBuffer, UART, SUART, etc. The extra SRAM cost for the virtual table handling is only 2 bytes (per instance). For 20-40 instances this is only 40-80 byte in total. If the target is a ATtiny with less than 0.5Kbyte then this might seem a lot.

Have you seen Rick Kimball's library https://github.com/RickKimball/msp430_code. This is a full fledged template based library. And uses a lot of the template tricks. Very modern and nice coding style.

Cheers!

Thanks for the link, didn't know that lib. I will look into it.

Although my code is not as elaborate as your lib (yet! XD) I have not found any need to have virtuals. I basically put the class hierarchy upside down and that eliminates the need for virtuals - also eliminates polymorphism. My reasoning is that in small embedded systems you have more use for control than abstraction (so not for beginners). As in the LCD example I gave previously, I am also looking to separate concerns/logic. The logic of how to write to an LCD display is always the same (for the same type of LCD displays). But the way you hooked it up to your MCU can differ for each design (directly on the pins, shift regs, etc). This approach leaves the door open to change the driver code and still reuse the LCD code, something I have never seen in other libs.

I am currently testing a way for lightweight cooperative mutlitasking. Once I figured out how to do async time delays I can go to town on supporting other typical devices (I2C, LCD etc).

Thanks for the fast answer. Handing the event in the dispatch loop would become a bit messy on the long run.

Just to depict my problem: I'm planning to use a rotary encoder which uses two pins. Therefore subclassing the ExternalInterruptPin as in the IR::Receiver example is not possible. So probably I would have to subclass the pin in order to be able to delegate the event handling to some other class/function.

But I'm in the moment just looking into different lib to see which would fit my needs best. Yours looked quite interesting.

kowalski:
The short answer to your question is that you can always handle the event directly instead of calling dispatch(). Below is snippet from CosaFSMBlink which uses the dispatch-method.

Ferio:
Thanks for the fast answer. Handing the event in the dispatch loop would become a bit messy on the long run.

Just to depict my problem: I'm planning to use a rotary encoder which uses two pins. Therefore subclassing the ExternalInterruptPin as in the IR::Receiver example is not possible. So probably I would have to subclass the pin in order to be able to delegate the event handling to some other class/function.

You are welcome! This is an interesting design problem.

The solution you are sketching is similar to the pattern used in the NRF24L01P driver where a sub-class of ExternalInterruptPin is defined within the class itself and posts events back to the "parent", i.e. container.
Below is a snippet from the driver.

class NRF24L01P : private SPI::Driver {
private:
  ...
  class IRQPin : public ExternalInterruptPin {
    friend class NRF24L01P;
  private:
    NRF24L01P* m_nrf;
  public:
    IRQPin(Board::ExternalInterruptPin pin, Mode mode, NRF24L01P* nrf) :
      ExternalInterruptPin(pin, mode),
      m_nrf(nrf)
    {}
    virtual void on_interrupt(uint16_t arg = 0);
  };
  IRQPin m_irq;
  ...
};

void
NRF24L01P::IRQPin::on_interrupt(uint16_t arg)
{
   ...
   Event::push(Event::RECEIVE_COMPLETED_TYPE, m_nrf, status);
   ...
}

You could have several InterruptPins defined within a container class which post events to the same target object (i.e. the container).

Cheers!

obiwanjacobi:
Thanks for the link, didn't know that lib. I will look into it.
...
I am currently testing a way for lightweight cooperative mutlitasking. Once I figured out how to do async time delays I can go to town on supporting other typical devices (I2C, LCD etc).

You are welcome. Here are a few links to Proto-threads which is a style of implementing coroutines. These are actually from the comment block in Cosa/Thread.hh, http://dl.dropboxusercontent.com/u/993383/Cosa/doc/html/d0/d51/classThread.html and https://github.com/mikaelpatel/Cosa/blob/master/Cosa/Thread.hh.

[1] Adam Dunkels et al, Protothreads: Simplifying Event-Driven Programming of Memory-Constrained Embedded Systems, SenSys'06, November 1-3, 2006, Boulder, Colorado, USA.
[2] Larry Ruane, protothread: An extremely lightweight thread library for GCC, Google Code Archive - Long-term storage for Google Code Project Hosting.
[3] Protothread - Wikipedia

It is possible to avoid the "switch" problem by using gcc local labels and label address. The Cosa implementation is object-oriented and fully integrated with the Cosa periodic timer queue.

Cheers!

Nice docs!

That does look like a good implementation. Most threading impl's I've seen are over the top for the small MCU they're supposed to work on (IMHO).

I will stick with my macro's and switch statement (two bytes) for a while and see if I can get it to work. My aim is to allow the tasks to run with and without a scheduler. The scheduler only handles waiting tasks, at least that's the idea.

Thanx!

A new Cosa blog post is available. It presents the Cosa NEXA driver; RF433 transmitter and receiver for Home Automation with NEXA/HomeEasy remote and receivers. Driver and example sketches may be run on ATtiny.

Cheers!

Some news on the latest updates to Cosa.

  1. ATtiny X5 and X4 is now supported. In reality only 8K devices, ATtiny84 and ATtiny85, are practical to work with. The others have too small memory size (SRAM). Most drivers have been ported to the ATtiny's; DHT11/22, OneWire DS18B20, LCD (PCD8544/ST7565), Virtual Wire, Ultra-sonic range module and more. More details: Cosa: Programming ATtiny
  2. Enhanced Virtual Wire Interface (VWI), VWI::Transceiver, with message acknowledgement, auto-retransmission and support for message type encoding/decoding. Cosa: Enhanced Virtual Wire Interface
  3. Support for Home Automation NEXA/HomeEasy transmitter, receiver and "listener" objects. Cosa: More RF433 Wireless: Home Automation
  4. Soft UART with IOStream integration. Mainly for debug trace output from ATtiny as this only requires a single pin for the TX.
  5. Improved driver support for DHT11/22.
  6. Cosa RTC includes three levels of "clocks"; micro-, milli- and second base. The millisecond timer base (Timer0/B) interrupt handling may be extended with a periodic callback for more accurate event timing.
  7. BCD print support. May be used to avoid BCD-binary conversion when BCD data is not processed (such as when using the I2C Realtime Clock, DS1307, see DS1307::timekeeper_t::print(IOStream& stream, const char* format), https://github.com/mikaelpatel/Cosa/blob/master/Cosa/TWI/Driver/DS1307.cpp)
  8. LCD ST7565 support. As PCD8544 the LCD driver is integrated with IOStream. Graphics is performed using the OffScreen Canvas. This driver also supports text scroll without using a memory buffer. Please see the example sketch; https://github.com/mikaelpatel/Cosa/blob/master/examples/LCD/CosaST7565P/CosaST7565P.ino
  9. New methods in the IOStream::Device interface; room(), number of bytes before output buffer is full, and peekchar(), access the next character in the input buffer without removing it. Updates to the implementations of the interface; UART, IOBuffer, etc.
  10. Updated support for io vector buffers (iovec) and improved handling in communication drivers (TWI, SPI and VWI).
  11. Major refactoring of Cosa TWI (I2C driver). Totally rewritten I2C/Two-Wire driver with higher level of application support. Reduced to less than 300 commented LOC.
  12. Updated OffScreen Canvas to a template class. Improved robustness and performance.

Cheers!

Some news on the latest updates to Cosa.

  1. TWI (I2C) slave mode on Arduino and ATtiny. Allows easy design of TWI slave devices using a simple event callback design. The Cosa TWI ATtiny driver uses USI and implements variable write blocks with STOP detection. See CosaTWImaster/slave for an example of usage pattern; Cosa/examples/TWI at master · mikaelpatel/Cosa · GitHub and the example sketches CosaTWImaster/slave and CosaTWIremotePinMaster/Slave.

  2. New Cosa driver class for Rotary Encoders. Support for direction detection and dials with min, max and initial value. Uses the Cosa support for Interrupt Pins and the Event manager. See example sketch; https://github.com/mikaelpatel/Cosa/blob/master/examples/Sandbox/CosaRotaryEncoder/CosaRotaryEncoder.ino

  3. GCC weak attribute introduced on Cosa objects; UART, SPI, TWI, etc. Makes it easy for applications to override. See UART for an example; https://github.com/mikaelpatel/Cosa/blob/master/Cosa/IOStream/Driver/UART.cpp and redefinition in CosaTWIslave example sketch; Cosa/CosaTWIslave.ino at master · mikaelpatel/Cosa · GitHub

Cheers!

New Cosa driver class for Rotary Encoders.

I've got two quadrature output kinds of encoders (ie. see http://www.bourns.com/data/global/pdfs/ECW1J.pdf ):
a) with full cycle per detent
b) 1/4 cycle per detent
Would it be possible to configure it for that modi?

pito:

New Cosa driver class for Rotary Encoders.

I've got two quadrature output kinds of encoders (ie. see http://www.bourns.com/data/global/pdfs/ECW1J.pdf ):
a) with full cycle per detent
b) 1/4 cycle per detent
Would it be possible to configure it for that modi?

Hi Pito. The Cosa Rotary Encoder driver is a repackaging of Ben Buxton's excellent implementation into the object-oriented style of Cosa and integrated with Interrupt Pins and the Event Manager. Buxtronix: Rotary encoders, done properly

It is possible to configure in two modes; half and full step. The default is full step. Define HALF_STEP to change mode (in Rotary.cpp). I think this covers at least one of the cases.

Cheers!

Yea, it seems the half step is the "1/4 cycle" and the full step means "full cycle". I can test it when an example is available.. Thanks

pito:
Yea, it seems the half step is the "1/4 cycle" and the full step means "full cycle". I can test it when an example is available.. Thanks

I have updated the Rotary Encoder/Dial driver so that the stepping mode may be selected at run-time instead of build-time. It may also be updated. Below is a snippet from the updated version of the example sketch https://github.com/mikaelpatel/Cosa/blob/master/examples/Sandbox/CosaRotaryEncoder/CosaRotaryEncoder.ino

// Construct Dial connected to interrupt pins D6 and D7, start in full step mode,
// with initial value -100, min -100, and max 10.
Rotary::Dial dial(Board::PCI6, Board::PCI7, Rotary::Encoder::FULL_STEP, -100, -100, 10);

void loop()
{
  // Rotary Encoder interrupt pin handler will push an event when a change occurs
  Event event;
  Event::queue.await(&event);

  // Dispatch the event so that the dial value is updated
  event.dispatch();

  // Change step mode at min and max values
  static int old_value = -100;
  int new_value = dial.get_value();
  if (old_value == -100 && new_value == -99)
    dial.set_step(Rotary::Encoder::FULL_STEP);
  else if (old_value == 10 && new_value == 9)
    dial.set_step(Rotary::Encoder::HALF_STEP);
  old_value = new_value;

  // Print the new value
  trace << new_value << endl;
}

Cheers and thanks for the feedback!

Hmm, I am coping with the stuff, indeed :slight_smile:
I am using 1284p mighty, IDE 1.5.2, and an encoder with pullups on A6/A7, and in "Cosa/Board/mighty.hh" I did following changes (mighty uses a reversed numbering in Ax):

...
  /**
   * Return Pin Change Mask Register for given Arduino pin number.
   * @param[in] pin number.
   * @return pin change mask register pointer.
   */
  static volatile uint8_t* PCIMR(uint8_t pin) 
  { 
    return (pin < 8  ? &PCMSK0 : 
	    pin < 14 ? &PCMSK1 : 
	    pin < 24 ? &PCMSK2 :
	    &PCMSK3);
  }
...
 /**
   * Analog pin symbols; mapping from name to port<5>:bit<3>.
   */
  enum AnalogPin {
    A0 = 31,
    A1 = 30,
    A2 = 29,
    A3 = 28,
    A4 = 27,
    A5 = 26,
    A6 = 25,
    A7 = 24
  } __attribute__((packed));
...
  /**
   * Pin change interrupt. Number of port registers.
   */
  enum InterruptPin {
    /*PCI0 = A0,
    PCI1 = A1,
    PCI2 = A2,
    PCI3 = A3,
    PCI4 = A4,
    PCI5 = A5,
    PCI6 = A6,
    PCI7 = A7*/
    PCI0 = D0,
    PCI1 = D1,
    PCI2 = D2,
    PCI3 = D3,
    PCI4 = D4,
    PCI5 = D5,
    PCI6 = D6,
    PCI7 = D7,
    PCI8 = D8,
    PCI9 = D9,
    PCI10 = D10,
    PCI11 = D11,
    PCI12 = D12,
    PCI13 = D13,
    PCI14 = D14,
    PCI15 = D15,
    PCI16 = D16,
    PCI17 = D17,
    PCI18 = D18,
    PCI19 = D19,
    PCI20 = D20,
    PCI21 = D21,
    PCI22 = D22,
    PCI23 = D23,
    PCI24 = A0,
    PCI25 = A1,
    PCI26 = A2,
    PCI27 = A3,
    PCI28 = A4,
    PCI29 = A5,
    PCI30 = A6,
    PCI31 = A7
  } __attribute__((packed));
...
  /**
   * Auxiliary
   */
  enum {
    VBG = (_BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)),
    EXT_MAX = 3,
    PCINT_MAX = 4,
    PIN_MAX = A0
  } __attribute__((packed));
};

Not sure whether I do understand the Auxiliary well - it seems to me pcint_max = 4(??) and pin_max = A0 (??) (pin code 31).
So the pin change interrupt is set to A6, A7 with the code:

// MIGHTY ATMEL ATMEGA1284P
//
//                       +---\/---+
//           (D 0) PB0  1|        |40  PA0 (AI 0 / PCI24 / D24)
//           (D 1) PB1  2|        |39  PA1 (AI 1 / PCI25 / D25)
//      INT2 (D 2) PB2  3|        |38  PA2 (AI 2 / PCI26 / D26)
//       PWM (D 3) PB3  4|        |37  PA3 (AI 3 / PCI27 / D27)
//    PWM/SS (D 4) PB4  5|        |36  PA4 (AI 4 / PCI28 / D28)
//      MOSI (D 5) PB5  6|        |35  PA5 (AI 5 / PCI29 / D29)
//  PWM/MISO (D 6) PB6  7|        |34  PA6 (AI 6 / PCI30 / D30)
//   PWM/SCK (D 7) PB7  8|        |33  PA7 (AI 7 / PCI31 / D31)
//                 RST  9|        |32  AREF
//                 VCC 10|        |31  GND 
//                 GND 11|        |30  AVCC
//               XTAL2 12|        |29  PC7 (D 23)
//               XTAL1 13|        |28  PC6 (D 22)
//      RX0 (D 8)  PD0 14|        |27  PC5 (D 21) TDI
//      TX0 (D 9)  PD1 15|        |26  PC4 (D 20) TDO
// RX1/INT0 (D 10) PD2 16|        |25  PC3 (D 19) TMS
// TX1/INT1 (D 11) PD3 17|        |24  PC2 (D 18) TCK
//      PWM (D 12) PD4 18|        |23  PC1 (D 17) SDA
//      PWM (D 13) PD5 19|        |22  PC0 (D 16) SCL
//      PWM (D 14) PD6 20|        |21  PD7 (D 15) PWM
//                       +--------+
//

#include "Cosa/Rotary.hh"
#include "Cosa/Trace.hh"
#include "Cosa/IOStream/Driver/UART.hh"

void setup()
{
  // Use the UART as output stream
  uart.begin(115200);
  trace.begin(&uart, PSTR("CosaRotaryEncoder: started"));

  // Start the interrupt pin handler
  InterruptPin::begin();
  
}


// Construct Dial connected to interrupt pins (A6 and A7 on mighty), start in full step mode,
// with initial value -100, min -100, and max 10.
Rotary::Dial dial(Board::PCI30, Board::PCI31, Rotary::Encoder::FULL_STEP, -100, -100, 10);

void loop()
{
  // Rotary Encoder interrupt pin handler will push an event when a change occurs
  Event event;
  Event::queue.await(&event);

  // Dispatch the event so that the dial value is updated
  event.dispatch();

  // Change step mode at min and max values
  static int old_value = -100;
  trace << old_value << endl;
  int new_value = dial.get_value();
 //if (old_value == -100 && new_value == -99)
 //  dial.set_step(Rotary::Encoder::FULL_STEP);
 //else if (old_value == 10 && new_value == 9)
 //  dial.set_step(Rotary::Encoder::HALF_STEP);
 // old_value = new_value;

  // Print the new value
  trace << new_value << endl;
}

However it prints only:

CosaRotaryEncoder: started

with whatever I've done above.. It does not even print the old_value..

PS1: it waits/stops/crashes(?) on " Event::queue.await(&event);"
PS2: tried with other pins, no luck yet..

PS3: reading interruptpin.hh I've set PCINT_MAX = 4 in above mighty.hh

PS4: I think we have to have in l.50 interruptpin.hh (as the mighty has got all 4 PCMSKs):

#if !defined(__ARDUINO_MEGA__)
  friend void PCINT3_vect(void);

PS5: yea, the interrupt handler has to be fine-tuned for above mighty.hh.. Thanks :slight_smile: