Cosa: An Object-Oriented Platform for Arduino programming

Interesting about the new for-loop syntax. I was an early adopter of C++ all those years ago but have hardly touched it since. I should get back into it.


Rob

The latest update focuses on reducing build time. Cosa currently supports 24 board variants. There are over 160 example sketches. Recompiling and testing all these variants takes some time.

In total the Cosa core contains 292 source code files and these are in total over 63 KLOC (lines of code). The Arduino (AVR) core contains 42 files and approx. 8 KLOC. The Cosa core is somewhat like having all the Arduino official libraries available at all time and seamless from ATtiny to ATmega.

The new build support reduces the sketch build time to approx. 1.5 second. This is achieved by caching the Cosa core library and object files. The initial build of the library for a board will take 5-7 seconds (depends on Arduino/AVR gcc tool chain). The core library is then kept between sketch builds. Only the sketch needs compiling and linking. Even the sketch object and hex files are kept between builds. Any change to the core or sketch will trigger rebuild of the necessary files.

Total rebuild of the Cosa core library for all 24 boards will now only take approx. 2 minutes for 1.0.X and 2.45 minutes for 1.5.7. Rebuilding all 164 example sketches for a specific board takes 4.15 minutes. The build times are for HP ProBook 4540s, Intel® Core™ i5-3230M CPU @ 2.60GHz × 4, 64G SSD, Ubuntu 14.4 LTS. The Cosa build uses the available processor cores (parallel make). More details in the build log, https://github.com/mikaelpatel/Cosa/blob/master/build/arduino-1.5.7.log and Cosa/arduino-1.0.6.log at master · mikaelpatel/Cosa · GitHub.

The Cosa build support is implemented as a set of scripts and makefiles. The Arduino-Makefile project is used as a basis for the implementation together with miniterm.py (serial monitor).

Geany (http://www.geany.org/) together with the Cosa command line build script this is a great alternative IDE for Arduino users on Linux.

Cheers!

Ref.

  1. Cosa.mk, makefile rules, Cosa/Cosa.mk at master · mikaelpatel/Cosa · GitHub
  2. cosa, makefile-less build script, Cosa/cosa at master · mikaelpatel/Cosa · GitHub
  3. tutto, total rebuild script, Cosa/tutto at master · mikaelpatel/Cosa · GitHub
  4. Install and setup, Cosa: An Object-Oriented Platform for Arduino programming - #382 by kowalski - Libraries - Arduino Forum

I have found a minor annoyance in W5100.cpp (line 618):

// Check for default gateware. Assume router is first address on network
uint8_t ROUTER[4];
if (gateway == NULL) {
memcpy(ROUTER, ip, sizeof(ROUTER) - 1);
ROUTER[3] = 1;
memcpy(m_dns, ROUTER, sizeof(ROUTER));
gateway = ROUTER;
}

This is a rather nasty assumption because it means unless you use DHCP, your board will not find the right default gateway. It also assumes that the DNS server is the same. I'm working on a patch, but my time is very limited. It probably needs an additional "begin" that allows DNS and the gateway to be specified.

@greypanda

Hi! Sorry for the delay.

I think you are referring to W5100::bind() which is used to connect a socket to a specific setting (and part for the setup W5100::begin() and begin_P()). https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/Socket/Driver/W5100.hh#L639

In W5100::begin_P(hostname, timeout) the IP address is given by the DHCP server which also provides the DNS server address.
https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/Socket/Driver/W5100.cpp#L575

For W5100::bind(ip, subnet, gateway) where gateway is NULL the assumption is that the gateway is the first address on the subnet and also provides DNS.

I will add a set_dns_addr() and consider adding an extra argument to bind().

Cheers and thanks for the catch!

Just discovered this library, looks like a great work! 8)
Let's dive into it now.

edit: awesome !

Hello,

I'm using Cosa to my home project. It's great.

I have a little question about dht22 class.

I see that in example data pin are connected to external interrupt.

May I use other pin ? My project will involve +-10 dht sensors so not enough external interrupt pin.

I suppose I'll lose the event part of the class but I will be able to make a simple sample ?

How much time take a sample ? I'm a little confused there. Like 2 second or 200ms ?

Thanks for your help.

Sorry for my bad English I speak French.

Happy to have just discovered Cosa - very nice stuff!

Perhaps I just don't understand it enough yet but is there a way to be notified of the arrival of data on the hardware and software UARTs?

I would like to implement a GPS abstract driver and NMEA implementation - and contribute it eventually. With a typical burst of nearly 256 bytes every second I would prefer to not have to buffer each burst. I'd like to consume the data as it arrives via a callback or event. I haven't found a way to do that with the current implementation.

What would be the suggested approach given the current Cosa implementation?

Would it make sense to enhance the hardware and software UARTs to do this? I suspect that having a callback in the receive interrupt handler, especially the software one, would not be desirable.

TribesTom:
I see that in example data pin are connected to external interrupt.
May I use other pin ? My project will involve +-10 dht sensors so not enough external interrupt pin.
I suppose I'll lose the event part of the class but I will be able to make a simple sample ?
How much time take a sample ? I'm a little confused there. Like 2 second or 200ms ?

@TribesTom
Thanks for your interest in Cosa. Sorry about the delay on answering your questions about the DHT device driver.

This driver requires an external interrupt pin. I guess it would be possible to multiplex several DHT sensors with an analog multiplexor (for instance 8:1) and reduce the number of external interrupt pins requires. It is also possible to implement a device driver with busy wait and use any pin instead of the Cosa event driven variant.

Please check the product description for the sample and transfer time. Most DHT devices require a 2 second period between reading measurements. The actual transfer rate is much faster.

Cheers!

@unixjedi

Thanks for your interest in Cosa. I will try to answer your questions. A lot has to do with the IO handling design in Cosa.

unixjedi:
Perhaps I just don't understand it enough yet but is there a way to be notified of the arrival of data on the hardware and software UARTs?

The quick answer is YES. The hardware and software UART implementation will call the input buffer (IOStream::Device) member function putchar() when data is received. It is possible to implement this interface/abstract class instead of the IOBuffer that is normally used. The requirement is that the member function must be able to be called from an interrupt service routine (ISR).

unixjedi:
What would be the suggested approach given the current Cosa implementation?

To keep things simple I would actually implement this with a simple check of available(). To reduce power I would also use Power::sleep() to wait for events.

unixjedi:
Would it make sense to enhance the hardware and software UARTs to do this?

Do not have to as this is already possible! Actually it is possible to connect the UART to the Cosa event dispatcher instead using the IOStream::Device and Event interface.

Cheers!

Thanks you for your response.

I look for a way to adapt it for a wait approch.

Real time isn't necessary in this case.

I'm not finding support for I2C LCD - the classic 20x4 "Blue" with I2C board. I've seen references to I2C LCD but I'm confused about where/how. Thanks

jediunix:
I'm not finding support for I2C LCD - the classic 20x4 "Blue" with I2C board. I've seen references to I2C LCD but I'm confused about where/how. Thanks

Please see the LCD examples. The CosaLCDclock.ino might be the best to start with. The Cosa LCD support handles multiple devices and multiple adapters. Below is the class hierarchy for LCD devices.

The adapter handle the wire level connection to the LCD. They range from parallel to I2C and SPI but all connect to the LCD device.
classHD44780.html#nested-classes
classLCD.html

Another important aspect is that the LCD devices are IOStream devices and can be used together with IOStream and Trace.

The I2C adapter classes are named after the I2C port expander products; DFRobot, MJKDZ, GYIICLCD, etc.

Below is the typical structure:

// Select the access port for the LCD; Parallel 4-bit, Shift-Register, SPI, I2C
// HD44780::Port4b port;
// HD44780::SR3W port;
// HD44780::SR3WSPI port;
// HD44780::SR4W port;
HD44780::MJKDZ port;
// HD44780::GYIICLCD port;
// HD44780::DFRobot port;
// HD44780::ERM1602_5 port;

// Bind the port to the lcd
HD44780 lcd(&port, 20, 4);

// Bind the lcd to an io-stream
IOStream cout(&lcd);

Cheers!

kowalski:
Please see the LCD examples.

Thanks for the info. I was able to get it working.

For others looking, boards with "LCM1602 IIC V1" on the back work with HD44780::DFRobot.

jediunix:
For others looking, boards with "LCM1602 IIC V1" on the back work with HD44780::DFRobot.

@jedjunix
I will add an alias for that I2C adapter. Thanks for the info.

Cheers!

kowalski:
The hardware and software UART implementation will call the input buffer (IOStream::Device) member function putchar() when data is received. It is possible to implement this interface/abstract class instead of the IOBuffer that is normally used. The requirement is that the member function must be able to be called from an interrupt service routine (ISR).

I must not understanding something obvious here.

I have my GPS implementation working well with available()/getchar() and now I want to drive it directly from input interrupt. I have subclassed from IOStream::Device and changed my consumer to be driven by putchar().

My implementation also needs to send commands to the GPS device. I don't understand how to implement this correctly. The UART must be constructed referring to my class as the input stream. My class needs to be constructed with an output stream. It is currently working with the application code containing:

Soft::UAT gps_output_uart(Board::D11);
GPS_NMEA_MT3339 gps(&gps_output_uart);
Soft::UART gps_uart(Board::D11, Board::PCI6, &gps);

This doesn't feel right for obvious reasons. This approach wouldn't work for the hardware UART.

It would be desirable to have an example in Cosa. In the meantime, could you provide some hints on how you intend this type of thing to work?

Thanks

Hello Kowalski,

I have seen an intersting chip : MCP23017, It's a I2C 16 port i/o expander.

Could you implement it in Cosa ? I'm not enough skillfull to this as clean and efficent like you.

Here is all info on this chip :

Adafruit info with datasheet :

Official library :

This chip is similar to PCF8574.

If you need some help to test or anything else that I can do, let me know.

Thanks and have a nice day.

Ps: Sorry for my bad english I'm french
Ps2: If you preffer I could make a request on GitHub. But I must find my password first.

@jediunx,

> I have my GPS implementation working well with available()/getchar() and
> now I want to drive it directly from input interrupt...

Did you receive a Personal Message from me? If not, here's what I sent:

I saw your comment about GPS, and I was about to point you to a thing that tht and I have worked on.

He started the process by creating an IOStream::Device that gets hooked to the UART. As characters come in, the device gets called from the InterruptHandler to process one character, much like your consume. He started out with a ublox-specific class, which I separated out into two classes: generic NMEA, and specific ublox. The NMEA class is mistakenly called NeoGPS. I really should fix that... :-[

Many GPS devices support multiple protocols, so I tried to be careful and allow for future derived classes. If you're interested, you can find what we have so far over here.

If you've seen TinyGPS, you'll recognize some of those techniques. The goal was a small RAM footprint, and the option to run without float support. For example, if you're just logging information, there's no reason to use float because you never compute anything like a distance between two lat/longs.

I'm proud to say that a generic instance takes only 41 bytes. No buffers are used; each byte is consumed as it is received. That's about 1/3 the size of a TinyGPS instance! And that's why you see all the structs (with methods?!?), unions and bitfields. They're really packed in there! The ublox instance is only slightly larger at 54 bytes.

This Cosa class is different from TinyGPS (and most Arduino code) in that it is event-oriented instead of polled, like with available and getchar. (After thinking about it, there is a way to use it in a polled way.1)

> I have subclassed from IOStream::Device and
> changed my consumer to be driven by putchar().

Sounds good! Because you want to handle the chars in the interrupt context of putchar, you can't do very much work. (Which is a little confusing because we're actually getting a char from the device... o_O) That's why putchar calls Event::push when a complete NMEA sentence is received and the CRC is correct.

Essentially, it puts an Event structure in a queue of things to handle a little bit later, after we're out of the interrupt routine. You can look at that queue in the main loop, where you can take all the time you want. Well, by "look at the event", I mean "dispatch" the events to the objects that care about them:

Event event;
while (Event::queue.dequeue( &event ))
    event.dispatch();

These events will find their way to the on_event method of the object that's supposed to get "kicked": the object then "handles" that event.

Because NeoGPS doesn't know what your specific needs are, you must derive your own class from NeoGPS and implement your own on_event. In general, that's how you handle events in Cosa.

I haven't checked in an example app yet, but here's the basic idea. First, derive from the NMEA class:

class MyGPS : public NeoGPS {
public:

    virtual void on_event ( uint8_t type, uint16_t value )
    {
          if ((NMEA_GGA == (nmea_msg_t)value) ||
              (NMEA_RMC == (nmea_msg_t)value)) {

              // Do something with the fix() data member, quickly, or the interrupt routine
              //    will start replacing data as more characters are received...
              // Copy it to a local_fix if you need more time.
          }
    }
};

Then make an instance of your class and hook it to the receiving UART:

extern UART uart1; // forward declaration of uart1
MyGPS gps( &uart1 ); // need back pointer to the UART so we can also send commands

static IOBuffer<UART::BUFFER_MAX> obuf;
UART uart1(1, &gps, &obuf); // normal forward pointer to the GPS device

There is a little syntactic monkey business going on here... The first extern "promises" that there is a real uart1 somewhere in all the code. Usually that means it's in a different file, but in this example, it's only two lines later. This is because gps and uart1 point at each other. Neither declaration could be first without needing to use the other. So, we throw in the forward declaration for one (uart1), then really declare the second object (gps), and finally really declare the first (uart1). The second uart1 declaration is how uart1 knows to call gps.putchar from the InterruptHandler. The first gps declaration is how gps knows to call uart1.putchar with transmitted data.

Normally, the IOStream::Device has no way to find the IOStream (UART) that called it. That's why there's a TX member in NeoGPS. When gps.send is called, it must use the TX member for the UART to send the data. Does that answer your question:

> My implementation also needs to send commands to the GPS device.
> I don't understand how to implement this correctly.

BTW, if you didn't need to send anything to the GPS device, then NeoGPS wouldn't need a back pointer to the receiving UART.

> The UART must be constructed referring to my class as the input stream. My
> class needs to be constructed with an output stream. It is currently working with
> the application code containing:
>
> Soft::UAT gps_output_uart(Board::D11);
> GPS_NMEA_MT3339 gps(&gps_output_uart);
> Soft::UART gps_uart(Board::D11, Board::PCI6, &gps);[/tt]
>
> This doesn't feel right for obvious reasons. This approach wouldn't work for the hardware UART.

Hmmm, I think you just need to make the gps_output_uart a forward declaration of gps_uart:

extern Soft::UART gps_uart;
GPS_NMEA_MT3339 gps(&gps_uart);
Soft::UART gps_uart(Board::D11, Board::PCI6, &gps);

I'll try to get that test program up there Real Soon Now.

One other note: there are also struct methods related to fusing multiple received sentences into one coherent fix. As you know, NMEA must report several sentences before you have all of time, position (lat/lon/alt) and velocity (course/heading) data. The gps_fix_t::operator |= helps with that fusing process. This makes it easier to have a merged fix that accumulates the different pieces you get from different NMEA sentences.

I hope this helps!

Cheers,
/dev


1 You could just use the regular (or Soft) uart instance:

static IOBuffer<UART::BUFFER_MAX> ibuf;
static IOBuffer<UART::BUFFER_MAX> obuf;
UART uart(0, &ibuf, &obuf);

MyGPS gps( &uart );

Then your main loop would do something like:

while (uart.available())
  gps.putchar( uart.getchar() );

This will still generate events, even though the chars are not processed in the InterruptRoutine. They're held in obuf until you get them. The back pointer to uart allows you to use gps.send methods.

@jediunix

This seems to be a question about C++ and forward declaration? You need to forward declare the gps or the soft uart class instances/variables. If you look at the definition of uart in the header file you will find an example of how to do this. https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/IOStream/Driver/UART.hh#L279

Cheers!

BW: Seems to be a nice project. Please publish more code.

UPDATE: Seems like /dev was both faster and gave a in-depth answer.

TribesTom:
I have seen an intersting chip : MCP23017, It's a I2C 16 port i/o expander.
Could you implement it in Cosa ? I'm not enough skillfull to this as clean and efficent like you.

@TribesTom

Thanks for your interest in Cosa. I have a very simple rule; I do not implement any device drivers that I cannot test and at this time I do not have any board with the MCP23017. Also I try to create a software interface that makes the device easy to use and it should also have some type of challenge. In this case the MCP23017 can generate interrupts on pin change. It also has a weak pull-up.

Cheers!

@/dev and @kowalski,

Doh! It was so simple I couldn't see it! I guess I'm rustier than I thought.

My simplistic GPS stuff is http://github.com/jeditekunum/CosaGPS. I discovered NeoGPS after I had most of mine working. I'm using MT3339 for GPS - on the AdaFruit breakout board.

At some point it would be nice if Cosa contained GPS support.

My current project is to modify a large (32"/91cm) wall clock to GPS with automatic savings time. Ours hangs very high on the wall. Battery powered.

I'm happy to see that Timer is pretty accurate for time keeping (Periodic not so much). I have it expiring every 500ms because the clock coil needs push/pull every second.