Wednesday, July 17, 2013

Object-Oriented 1-Wire: An Introduction

One of the many interfaces and protocols that Cosa support is Maxim Integrated 1-Wire. The support classes, OWI, OWI::Driver and OWI::Search, allows easy implementation of 1-Wire device drivers. The support classes implement the full 1-Wire specification with support for parasite power devices and alarm search.

In a previous post the DS18B20 driver was used to implement a Virtual Wire Digital Thermometer. In this post more of the design and implementation of the 1-Wire device driver will be presented. A simple DS1990A iButton reader sketch is used to present the basic functionality. The iButton device is often used as an access control key or for equipment identification. It is the simplest of all 1-Wire devices as only the device identity ROM command is used and there is at most a single device connected on the 1-Wire bus.

The Cosa support for 1-Wire is called OWI (One Wire Interface). Below are the OWI class member functions. The class represents the 1-Wire bus and handles data transfer to and from devices on the bus. It also supports basic alarm search and dispatch function according to the 1-Wire specification.

Fig.1: OWI class member functions
1-Wire devices drivers use the OWI class to communicate with the connected device. All bus interaction is delegated to the OWI class instance.

Fig.2: OWI::Driver class member functions
The OWI::Driver class member functions are the 1-Wire ROM commands. The class holds the 1-Wire identity for a specific connected device. This can be given as an EEMEM 8 byte uint8_t vector or through the connect() member function.

Devices driver member functions initiate a device function command with a ROM command (match_rom/skip_rom) followed by the function command and read or write of data.

bool
DS18B20::read_scratchpad()
{
  if (!match_rom()) return (false);
  m_pin->write(READ_SCRATCHPAD);
  return (m_pin->read(&m_scratchpad, sizeof(m_scratchpad)));
}


Above is an example of the structure in the form of an implementation of the DS18B20 Read Scratchpad (BEh) command. Note that m_pin in the example code above is the OWI reference.

Fig.3: Arduino Pro Mini reading iButton
Back to the iButton (DS1990A) device. In the below example sketch iButton devices are read when connected and their identity (key) is check against a simple table with valid keys. The access is logged (i.e. printout to serial output).

Being the simplest of all 1-Wire devices only a single OWI::Driver member function is used; OWI::Driver::read_rom(). No specific device driver is necessary for this version of iButton. The OWI::Driver class already has the required functionality.

Let us start with the loop() function. When an iButton is connected the 1-Wire identity ROM is read and checked. If the key is valid a green LED is switched on otherwise a red LED will blink (for 5 seconds). The valid keys are stored in a table in program memory (PROGMEM). In an application they would be stored in eeprom (EEMEM) and there would also be an update procedure without recompiling the sketch. The procedure could be, for instance, present a master key and then the key to add/remove from the table. 

void loop()
{
  // Take a nap
  SLEEP(1);

  // Check if a key was connected
  OWI::Driver dev(&owi);
  if (!dev.read_rom()) return;

  // Check if it is an authorized key. Turn on led for 5 seconds
  uint8_t* rom = dev.get_rom();
  for (uint8_t i = 0; i < sizeof(KEY); i += OWI::ROM_MAX) {
    if (!memcmp_P(rom, &KEY[i], OWI::ROM_MAX)) {
      trace << dev << PSTR(":AUTHORIZED KEY") << endl;
      greenLed.on();
      SLEEP(5);
      greenLed.off();
      return;
    }
  }

  // Otherwise it is an illegal key. Flash led for 5 seconds
  trace << dev << PSTR(":ILLEGAL KEY") << endl;
  for (uint8_t i = 0; i < 10; i++) {
    redLed.on();
    MSLEEP(250);
    redLed.off();
    MSLEEP(250);
  }
}

The blue marks the 1-Wire code. The orange marks the log printouts. The Cosa 1-Wire support may be used on ATtinyX4/X5. Below the example sketch is run on an ATtiny85 with Soft::UART connected to D0, 1-Wire bus to D1, red LED to pin D2 and green LED to pin D4. A standard cell battery holder is used as the iBattery connector.

Fig.4: ATtiny85 iButton reader setup
The Cosa Soft::UART is used for the log printouts from the sketch. Below is a screenshot.

Fig.5: Screenshot of iButton reader sketch printouts
Last the sketch setup(). Below is the key table in program memory, the OWI bus object and the LED pins. The setup initiates the UART and binds the trace output stream. Cosa uses the Watchdog for low power sleep mode.

// Table with valid keys (64 bit 1-Wire identity, 8 bytes per entry)
uint8_t KEY[] PROGMEM = {
  0x01, 0x23, 0x81, 0xa3, 0x09, 0x00, 0x00, 0x7b,
  0x01, 0x29, 0x01, 0x27, 0x09, 0x00, 0x00, 0xa8,
  0x01, 0x26, 0xd9, 0x3e, 0x09, 0x00, 0x00, 0x47
};

// One-wire pin (D1 on ATtiny, D7 on others)
#if defined(__ARDUINO_TINY__)
OWI owi(Board::D1);
OutputPin redLed(Board::D2);
OutputPin greenLed(Board::D4);
#else
OWI owi(Board::D7);
OutputPin ledPin(Board::LED);
#define redLed ledPin
#define greenLed ledPin
#endif

void setup()
{
  // Initiate UART as trace output
  uart.begin(9600);
  trace.begin(&uart, PSTR("CosaDS1990A: started"));

  // Initiate the watchdog for low-power sleep mode
  Watchdog::begin();
}


Finally two pictures with the iButton reader in action.

Fig.6: Illegal key
Fig.7: Authorized key




Saturday, July 6, 2013

Event-driven LCD Keypad handler

The LCD Keypad Shield is equipped with a 16X2 character HD44780 LCD controller and six buttons. To reduce the number of pins required to handle the buttons they are connected in a resistor net to an analog pin. The different buttons, when pressed, give different analog readings.

Fig.1: LCD Keypad Shield with demo sketch CosaKeypad
The programming challenge is to reduce the complexity of handling the buttons so that application programmers only needs to deal with the logic of the buttons. And at the same time avoiding busy-wait or continuously reading the analog pin in the loop.

The Cosa approach is to use an object-oriented, event-driven, method where all the logic of reading the analog pin, mapping to key and checking for change is hidden. The resulting application support is a simple definition of two virtual method, on_key_down() and on_key_up(), which are called when a key is pushed and released. Below is a snippet from the Cosa example sketch, CosaKeypad.ino.

#include "Cosa/Keypad.hh"
#include "Cosa/IOstream.hh"
...
class KeypadTrace : public LCDKeypad {
private:
  IOStream m_out;
public:
  KeypadTrace(IOStream::Device* dev) : LCDKeypad(), m_out(dev) {}

  void trace(const char* msg, uint8_t nr);
  virtual void on_key_down(uint8_t nr) { trace(PSTR("down"), nr); }

  virtual void on_key_up(uint8_t nr) { trace(PSTR("up"), nr); }
};

The KeypadTrace implements the on_key_down/up() methods and will be called when a change is detected. The setup() and loop() needs to execute the event handler. Below is a snippet from these functions.

void setup()
{
   Watchdog::begin(16, SLEEP_MODE_IDLE, Watchdog::push_timeout_events);
   ...
}

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


The Watchdog is setup to wakeup every 16 ms from sleep mode and check if there are any timeouts. The Cosa Keypad class (inherited by LCDKeypad) acts as a periodic function that issues analog sample requests. The processor will go back to sleep while waiting for the Analog-Digital Converter (ADC) to complete (See previous blog post).

When a conversion is completed the ADC interrupt handler will push an event and the next step is performed, again dispatched by the event loop(). The value from the analog pin is mapped to a key index and if a change is detected the on_key() method is called. The processor will sleep while waiting for the Watchdog or Analog-Digital Conversion Competed interrupt.

The Cosa Keypad class may be used to implement other keypads based on resistor nets. The configuration is simply the analog thresholds for the resistor net. Below is the definition of the LCDKeypad. It defines the mapping (m_map) and the key codes.

class LCDKeypad : public Keypad {
private:
  // Analog reading to key index map
  static const uint16_t m_map[] PROGMEM;

public:
  // Key index
  enum {
    NO_KEY = 0,
    SELECT_KEY,
    LEFT_KEY,
    DOWN_KEY,
    UP_KEY,
    RIGHT_KEY
  } __attribute__((packed));
  LCDKeypad() : Keypad(Board::A0, m_map) {}
};

...
const uint16_t LCDKeypad::m_map[] PROGMEM = {
  1000, 700, 400, 300, 100, 0
};
 

More details on the example sketch, CosaKeypad. The KeypadTrace class will print the key index and name to the LCD. As the LCD is an IOStream::Device it may be used for IOStream output. Note that the parameter to the KeypadTrace constructor could be any other IOStream::Device such as the UART. Below is a snippet that binds and initiates the LCD.
 
#include "Cosa/IOStream.hh"
#include "Cosa/LCD/Driver/HD44780.hh"
...
HD44780::Port port;
HD44780 lcd(&port);
KeypadTrace keypad(&lcd);

...
void setup()
{
   ...
   lcd.begin();
   lcd.puts_P(PSTR("CosaKeypad: started"));
}


Last, the implementation of trace method where the key name, action and index are written to the IOStream/LCD. The key codes are defined in the class LCDKeypad.

void
KeypadTrace::trace(const char* msg, uint8_t nr)
{
  m_out << clear;
  switch (nr) {
  case NO_KEY:
    m_out << PSTR("NO_KEY");
    break;
  case SELECT_KEY:
    m_out << PSTR("SELECT_KEY");
    break;
  case LEFT_KEY:
    m_out << PSTR("LEFT_KEY");
    break;
  case DOWN_KEY:
    m_out << PSTR("DOWN_KEY");
    break;
  case UP_KEY:
    m_out << PSTR("UP_KEY");
    break;
  case RIGHT_KEY:
    m_out << PSTR("RIGHT_KEY");
    break;
  }
  
  m_out << ' ' << msg << endl;
  m_out << PSTR("key = ") << nr;

}


The keypad instance will connect to the Watchdog timeout queue and a callback will occur every 64 milli-seconds, which will give an effective debouncing of the button.

Fig.2: Pushing a button and executing the on_key callback.
For more details see the on-line documentation. Below is the full example sketch:

#include "Cosa/Types.h"
#include "Cosa/Keypad.hh"
#include "Cosa/IOStream.hh"
#include "Cosa/LCD/Driver/HD44780.hh"

class KeypadTrace : public LCDKeypad {
private:
  IOStream m_out;
public:
  KeypadTrace(IOStream::Device* dev) : LCDKeypad(), m_out(dev) {}
  virtual void on_key_down(uint8_t nr) { trace(PSTR("down"), nr); }
  virtual void on_key_up(uint8_t nr) { trace(PSTR("up"), nr); }
  void trace(const char* msg, uint8_t nr);
};

void
KeypadTrace::trace(const char* msg, uint8_t nr)
{
  m_out << clear;
  switch (nr) {
  case NO_KEY:
    m_out << PSTR("NO_KEY");
    break;
  case SELECT_KEY:
    m_out << PSTR("SELECT_KEY");
    break;
  case LEFT_KEY:
    m_out << PSTR("LEFT_KEY");
    break;
  case DOWN_KEY:
    m_out << PSTR("DOWN_KEY");
    break;
  case UP_KEY:
    m_out << PSTR("UP_KEY");
    break;
  case RIGHT_KEY:
    m_out << PSTR("RIGHT_KEY");
    break;
  }
  m_out << ' ' << msg << endl;
  m_out << PSTR("key = ") << nr;
}

HD44780::Port port;
HD44780 lcd(&port);
KeypadTrace keypad(&lcd);

void setup()
{
   Watchdog::begin(16, SLEEP_MODE_IDLE, Watchdog::push_timeout_events);
   lcd.begin();
   lcd.puts_P(PSTR("CosaKeypad: started"));
}

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

Tuesday, July 2, 2013

Object-Oriented LCD management

One for the latest developments in Cosa is the object-oriented interface for LCD devices. The goal has been to create a common interface for the Cosa LCD device drivers (PCD8544, ST7565 and HD44780) and allow LCD devices to act as an IOStream::Device and finally adapt to different forms of ports (e.g. parallel and serial communication). With a common interface applications may change output device more easily. Below is an UML Inheritance Diagram for IOStream::Device with all Cosa classes that currently implement the interface and are inter-changeable.

Fig.1: IOStream class hierarchy
The Cosa LCD class is basically a name space for the LCD::Device abstract class which holds the common interface for LCDs. This interface is an extension of the IOStream::Device interface with some LCD specific methods. By providing an implementation of IOStream::putchar() an LCD::Device can directly be used for text output with number conversion, string and formated output both function (e.g. print) and operator<< notation.

Fig.2: IOStream::Device interface
Above are the member functions in the IOStream::Device interface. The "abstract" implementation acts as a null device which accepts any input and always returns end-of-file (EOF(-1)). By overriding putchar() an output stream device reuses all other output methods, i.e. puts(), puts_P() and write(). The default implementation of these use putchar().

Number conversion (binary to textual representation) is performed by the IOStream class which delegates the actual output to the attached IOStream::Device. The IOStream and the null device code is reused by all devices.

The LCD::Device class extends the IOStream::Device class with additional methods typical for LCD. Most of these are pure abstract and must be defined by the device driver.

Fig.3: LCD::Device Interface
The abstract LCD::Device captures some display properties, cursor, tabulator and text mode handling. If an application only uses the abstract LCD interface the code may be more easily adapted for different LCD devices. Also as the LCD device acts as an IOStream::Device the text output may be redirected to any other device that implements the interface. See figure 1 for the current classes that implement the interface.

There are basically two major types of LCD devices; pixel and character based. Some pixel based devices restrict drawing of pixels to a vertical byte, 8 pixels at a time, but allow horizontal cursor handling on pixel level. Examples of this type of LCD devices are PCD8544 and ST7565. HD44780 aka 1602/1604/2004 are typical character based devices where the characters codes are written to the device and not pixels.

Fig. 4: HD44780 Device Driver collaboration diagram
Another issue when dealing with LCD devices is the method of communication. Most devices use serial communication such as I2C or SPI to reduce the number of pins. The alternative is parallel communication. This is the default communication method for the HD44780 LCD device. It allows 8-bit or 4-bit command/data parallel communication. An I2C IO expander may be used to adapt HD44780 to serial communication over the I2C bus. To handle adaption of the device driver an abstract IO port is defined.

Fig. 5: HD44780::IO class hierarchy
The HD44780 device driver can now be adapted to any of these communication modules by simply changing the IO port device. The Cosa LCD class hierarchy is constructed for Standard Arduino, Mega, Mighty and possible to be scaled down to ATtiny8X. The footprint both SRAM and PROGMEM of the LCD device driver becomes especially important for micro controllers with limited program memory.

The HD44780 device driver requires 2.5 Kbyte program memory for parallel port, 3.5 Kbyte with I2C expander. The PCD8544 device driver with font (System7X5) requires 3.5 Kbyte. And last, ST7565, 4 Kbyte including font. All devices drivers include IOStream::Device, LCD::Device and default virtual methods.

Fig. 6: Benchmarking the LCD device drivers
The above benchmark configurations are (left to right, top to bottom):
  1. Arduino Mega, 20x4 character, HD44780 with MJKDZ I2C IO expander.
  2. Arduino Nano, 128x64 pixel, ST7565, software bit serial, OutputPin::write().
  3. Arduino Nano, 84x48 pixel, PCD8544, software bit serial.
  4. Arduino Uno, 16x2 character, HD44780, 4-bit direct port access, D4..D7.
The example sketch CosaLCDspeed performs basic benchmarking of the different device drivers. The benchmarks are:
  1. Clear the display, lcd.display_clear().
  2. Fill the display with characters, lcd.putchar(), WIDTH * HEIGHT number of characters per iteration and lcd.set_cursor() per line.
  3. Write a string, lcd.puts(), 2x16 characters per iteration, and lcd.set_cursor() per iteration.
  4. Write a string from program memory, lcd.puts_P(), as previous.
  5. Write an integer in decimal format, lcd.set_cursor() and number print per iteration (trace << num).
  6. Write an integer in binary format, as previous (trace << bin << num).
The below measurements are in operations per second:

Tab. 1: Benchmark operations per second
Please note that the benchmark putchar() is adjusted with the number of characters written (80, 160, 84, resp 32). The New LiquidCrystal Library reports 299 fps and Cosa LCD HD44780 driver 564 fps (putchar() benchmark 4-bit) which is 1.9X faster and 6.5X faster than the original LiquidCrystal Library.

The Cosa I2C IO expander handler is further optimized to achieve higher performance by reducing the transmission overhead. Writing a command or data byte to the I2C IO expander is transmitted as a sequence of five bytes instead of four X two byte transmissions (address and port data, total eight bytes). This reduces the transmission time with nearly 40%. This method can be applied further to reduce string output (see puts() and compare with puts_P() in tab. 1). The I2C@100khz improvement is over 1.7X faster compared to that reported by New LiquidCrystal Library benchmarks (31 fps). With I2C@400khz the improvement is over 4.6X faster.

Fig.7: ATtiny84 acting as an I2C LCD slave device. Running 4-bit parallel LCD device driver
Running the benchmark on an ATtiny85/84 with the internal 8 Mhz clock shows also great improvements; 4.9X for 4-bit parallel mode and 1.26X for I2C mode. Using an ATtiny as a Virtual LCD slave device on I2C gives 2.3X compared using an I2C IO expander. The ATtiny acts as an I2C slave device and uses the 4-bit parallel mode.

With the new USI based TWI master device driver support even an ATtiny85 can be used with an LCD. 

Fig.8: ATtiny85 connected to LCD with I2C IO expander
For the pixel based displays (PCD8544 and ST7565) the actual number of bytes transmitted is 6X per character.

For further details see the Cosa on-line documentation and the LCD example sketches

Monday, May 6, 2013

More RF433 Wireless: Home Automation

The RF433 transmitter and receiver modules can be used with Home Automation equipment. This blog post presents the Cosa support for sending and receiving wireless commands for NEXA/HomeEasy equipment. With sensor modules and supporting drivers it is possible to build advanced Home Automation applications that both receive and send commands to monitor and control lighting, alarms, door-bells, heaters, fans, etc. The Cosa NEXA/HomeEasy may be used with ATtiny.

Fig.1: CosaNEXAreceiver on ATtiny85 with RF433 Receiver and NEXA Remote

The Cosa NEXA::Receiver allows Arduino sketches to listen for wireless commands. These may be transmitted by a standard remote as in the figure(1) above or from another Arduino running the NEXA::Transmitter as in the figure(2) below. The receiver supports message decoding and address matching hiding much of the protocol complexity. Below is an example sketch (CosaNEXAreceiver.ino). The command data type (NEXA::code_t) simplifies the access of the command members. It also supports address matching.  

OutputPin led(Board::LED);
NEXA::Receiver receiver(Board::EXT0);
NEXA::code_t device = 0;
...
void setup()
{
  ...
  // Use polling version to receive the remote button for device
  receiver.recv(device);
  trace << PSTR("learning: ") << device << endl;
  ...
  // Enable the interrupt driven version
  receiver.enable();
}
...
void loop()
{
  // Wait for the next event
  Event event;
  Event::queue.await(&event);
  ...
  // Get the received command code and check if it is for this device
  NEXA::code_t code = receiver.get_code();
  if (code == device) {
    trace << PSTR("matched: ") << code << endl;
    led << code.onoff;
  }
}


The NEXA::Receiver uses an external interrupt pin, Board::EXT0. Both a polling (busy-wait) and interrupt driven version are provided. In the sketch above the polled version, receiver.revc(), is used to receive the first command code. This code is used as the device address for matching command codes received from the interrupt driven version in the loop() function.

Fig.2: Arduino Nano with RF433 Transmitter and NEXA Receiver

An event is pushed by the interrupt handler when a command code has been received and decoded. The loop() simply waits for the next event, retrieves the received command code, get_code(), and matches it with the operator==. If the unit address matches the received code is printed to the trace stream and the LED is turned on according to the onoff member of the received command code. The Cosa NEXA class also contains a command code transmitter, NEXA::Transmitter. The current version of send() is delay-based. The below example sketch (CosaNEXAsender.ino) is the wireless version of the Blink sketch.

OutputPin led(Board::LED);
NEXA::Transmitter transmitter(Board::D9, 0xc05a01L);
...
void setup()
{
  ...
  // First code will be used by receiver as address (learning mode)
  transmitter.send(0, 1);
}
...
void loop()
{
  // Blink the transmitter and receiver leds
  led.on();
  transmitter.send(0, 1);
  SLEEP(1);
  transmitter.send(0, 0);
  led.off();
  SLEEP(5);
}


The NEXA::Transmitter constructor requires an output pin and the house address (26-bit). The unit number is given as a parameter to the send() method. There is also a broadcast() method to send a group-message.

  union code_t {
    int32_t as_long;
    struct {
      uint8_t device:4;       /** device number, group:unit<2,2> */
      uint8_t onoff:1;        /** device mode, off(0), on(1) */
      uint8_t group:1;        /** group command */
      uint32_t house:26;      /** house code number */
    };
    code_t(int32_t value = 0L);
    code_t(int32_t h, uint8_t g, uint8_t d, uint8_t f);
    bool operator==(const NEXA::code_t &other) const;
    friend IOStream& operator<<(IOStream& outs, code_t code);
  };


The last example sketch in this post integrates the Virtual Wire Interface (VWI) and NEXA Transmitter to form a wireless Thermostat. The sketch receives humidity and temperature readings from the wireless DHT11 sketch (CosaTinyDHT11.ino) running on a ATtiny85. When temperature drops too low a relay/heater is turned on and when the humidity is too high a relay/fan is turned on and a command is send to a NEXA receiver (vent). When the readings drop below a lower threshold the heater/fan/vent are turned off again.

Fig.3: CosaTinyDHT11 running on an ATtiny85 and receiver

Two support classes, Relay/RemoteRelay, are defined to make the setup() and loop() easier to read. Below is the setup() of CosaVWIthermostat.ino. The setup() starts the Virtual Wire Interface (VWI) and receiver, and sends a NEXA command to the vent.

Relay heater(Board::D2);
Relay fan(Board::D3);
RemoteRelay vent(Board::D4, 0xc05a01L, 0);
...
void setup()
{
  ...
  // Start virtual wire interface and receiver
  VWI::begin(SPEED);
  rx.begin();
  ...
  // Close the remote vent
  vent.close();
}

...
// Message type to receive
struct msg_t {
  uint16_t nr;
  int16_t humidity;
  int16_t temperature;
};
...
void loop()
{
  // Wait for a message
  msg_t msg;
  int8_t len = rx.recv(&msg, sizeof(msg));
  if (len != sizeof(msg)) return;
  int16_t humidity = msg.humidity;
  int16_t temperature = msg.temperature;
  ...
  // Check if heater should be turned on @ 22 C and off @ 26 C
  static const uint8_t TEMP_MIN = 22;
  static const uint8_t TEMP_MAX = 26;
  if (heater.is_on()) {
    if (temperature > TEMP_MAX) heater.off();
  }
  else if (temperature < TEMP_MIN) heater.on();
  ...
  // Check if fan/vent should be turned on @ 70 %RH and off @ 50 %RH.
  static const uint8_t RH_MIN = 50;
  static const uint8_t RH_MAX = 70;
  if (fan.is_on()) {
    if (humidity < RH_MIN) {
      fan.off();
      vent.close();
    }
  }
  else if (humidity > RH_MAX) {
    vent.open();
    fan.on();
  }
}


Please see the Cosa examples folder for the full listing of the example sketches.

[Update 2013-06-02]

Since the first publishing of this post the NEXA device driver has been improved to make it even easier to handle Remote Control device actions. A NEXA::Receiver::Device class has been added. Below is an example of usage:

class LED : public NEXA::Receiver::Device {
private:
  OutputPin m_pin;
public:
  LED(Board::DigitalPin pin) : NEXA::Receiver::Device(0L), m_pin(pin) {}
  virtual void on_event(uint8_t type, uint16_t value) { m_pin << value; }
};
...
NEXA::Receiver receiver(Board::EXT0);
LED device(Board::LED);
...
void setup()
{
  ...
  // Use polling version to receive the remote button to attach
  NEXA::code_t cmd;
  receiver.recv(cmd);
  device.set_key(cmd);
  receiver.attach(&device);
  ...
  // Enable the interrupt driven version of the receiver
  receiver.enable();
}


The NEXA::Receiver::Device class is used to create a LED output pin that is controlled by the NEXA Remote. The incoming command is filtered by the receiver and the matching device will receive event and the value (on/off/dim level). In the example above the event value is written to the output pin (m_pin). To bind the device to the receiver we need to 1) set the key (address) of the device, 2) attach the device to the receiver. That is all. Once the interrupt handler for the receiver is enabled all incoming commands are compared to the attached devices and events passed on a match. The loop() function looks like this:

void loop()
{
  // Wait for the next event and dispatch
  Event event;
  Event::queue.await(&event);
  event.dispatch();
}


This is the standard event loop for Cosa. Typically in an application there would be several devices and a special "learning" mode where the devices would be bound to buttons on the Remote. The keys would be stored in EEPROM and read on startup. The LED (NEXA::Receiver::Device) class can actually be used for any control that would be the toggling of an output pin and not just LEDs.

The NEXA classes may be used with both ATtinyX4 and ATtinyX5.

Sunday, April 28, 2013

Programming ATtiny

One of the unique things with the Cosa platform is that the full range of integrated classes can be used for all Arduino boards and ATtiny/ATmega processors. Cosa allows code to be developed on a standard Arduino board and more or less reconfigured and compiled for an ATtiny. No extra cores are necessary. 

With todays technology it is both easy and cheap to program micro controllers. The Cosa framework together with the Arduino IDE makes it possible to achieve a very fast prototype turn-around development loop. From code change to test on the ATtinyX5 (ATtiny25/45/85) typically within a minute. 

Fig.1: Arduino DIY Programmer Shield
This blog post describes the basic steps to compile, run and debug Cosa example sketches for the ATtiny. The Cosa Soft UART (Soft::UART) transmitter makes it possible to reuse all the Cosa IOStream support directly for the ATtiny and run trace output over the Soft UART without modification of sketch code. The implementation only requires a single output pin.  

Fig.2: The ATtiny breadboard version of CosaButton example sketch
Before starting you will need some equipment. Other than the ATtiny you will need a Programmer. If you have available an Arduino and a breadboard you can use them for a Programmer. More frequently programming of ATtiny devices may require either investing in an ISP or building a DIY Programmer Shield from a prototype shield and a universal ZIF socket (component cost approx. $5-6 on eBay). The available LEDs on the prototype shield may be used for the Programmers state. The soldering required is very basic. See below.

Fig.3: DIY Programmer Shield wiring
A ZIF socket allows you to connect and disconnect the chip without having to power down the Arduino. Just insert the ATtiny into the ZIF socket and fasten it by pulling down the lever, then upload the sketch as you would for an Arduino, unfasten the chip and move it to your breadboard test circuit or PCB prototyping board.

The Cosa ISP sketch is part of the Cosa example tools code and may be found in the Cosa example menu (File>Sketchbook>hardware>Cosa>Tools>CosaISP). The Cosa core contains support for ATtiny (ATtinyX4, ATtinyX5 and ATtinyX61). 

When using a new ATtiny for the first time it is important to burn the "bootloader", i.e. set the fuse bits. If you forget this the sketches will not run correctly. Especially the Cosa Watchdog/RTC timer will not work correctly.

Fig.4: Burn ATtiny45/85 Bootloader (Fuse bits)
Insert the chip into the ZIF socket, select the "Serial Port" for the Cosa ISP in the Tools menu, select the ATtiny45/861/85 in the board menu, and then select "Burn Bootloader". You only need to do this once per chip. Programming is now simply inserting the chip into the ZIF socket, uploading through the Arduino Programmer to the ATtiny and then removing the chip and inserting it into the breadboard or socket on a PCB.

I would recommend that you always get the sketch running first on a standard Arduino and then port the sketch to the ATtiny. When porting it is important to fully understand the footprint of the sketch and reduce it to the available memory on the ATtiny. This might require trimming away to get within the limited memory size both program and data memory. Especially the amount of data and stack memory (SRAM) used. Remember that the ISRs must be able to execute "on top" of the deepest function call in the sketch. The Arduino IDE reports the program memory size and will not build if the sketch is too large.

Debugging on the ATtiny is even more difficult than on the Arduino. Most applications will need all pins and hardware units. Cosa provides serial output from the ATtiny using a single pin and a simple software UART transmitter. It is implemented with the serial output support, OutputPin::write() method, and mimics an UART output port.

namespace Soft {
  class UART : public IOStream {
public:
  ... 
  virtual int putchar(char c)
  {
    // Add start and stop bits and write to output pin with given pulse width

    uint16_t d = ((0xff00 | c) << 1);
    m_pin.write(d, m_bits + m_stops + 1, m_us);
    return (c);
  }

  ...
};
};
 
A synchronized delay loop is used instead of a timer and interrupt service routine to keep the memory footprint low and not allocate other hardware resources. Special care must be taken to avoid affecting the sketch timing as interrupt handling is turned off during the transmission of a character. At baudrate of 9600 that is in the range of 1 milli-second.

On the ATtiny you should/must alway place literal strings (constants) in program memory. The Trace support macros in Cosa does this for you but when using the IOStream always use PSTR() together with IOStream::print_P() and IOStream::printf_P().

Fig. 5: USB to UART module
A USB to UART module can be used to establish a connection to the Arduino IDE serial monitor on your host machine. These module are very cheap (approx. $2 on eBay) and work nicely as they contain internal buffers for the input and output UART data stream and can power your breadboard circuit with both 5V and 3.3V (check current limits). You could also connect to one of the extra UART ports on an Arduino Mega. Please remember that only serial output is currently available. No code changes are necessary to achieve trace output from the ATtiny. And only a single output pin is required. 

It is also possible to use an LCD for sketch and trace output. The Cosa PCD8544 LCD driver is available for the ATtiny. There is a reduced version of the system font (5x7) to keep program memory footprint down. Replacing Soft UART output with the LCD is made easy as both are Cosa IOStream::Device's. In many cases only a single line of code needs changing.

In the Cosa example sketch folder there are a few demonstration sketches specifically for the ATtiny85. You can find them in the folder named Tiny. The CosaTinyReceiver is especially interesting as it uses the LCD driver together with the Virtual Wire Interface Receiver. The sketch will wait for messages from an Arduino and/or ATtiny running the CosaVWIsender sketch and display the measurements together with some statistics, battery status and tick count. 

Fig.6: CosaTinyDHT11 and CosaTinyReceiver with PCD8544 LCD
Let us take a look at a full example sketch. The below sketch acts as a wireless DHT11 sensor and reports humidity and temperature readings back to the CosaTinyReceiver or CosaVWIreceiver sketches. With the support in Cosa the code is very straight forward. 

// Allocate instances and bind to pins
DHT11 sensor(Board::D1);
VirtualWireCodec codec;
VWI::Transmitter tx(Board::D2, &codec);
const uint16_t SPEED = 4000;
...
// Message with DHT11 reading
struct msg_t {
  uint16_t nr;
  int16_t humidity;
  int16_t temperature;
};
...
void setup()
{
  // Start watchdog and virtual wire interface
  Watchdog::begin();
  VWI::begin(SPEED);
  tx.begin();
}
...
void loop()
{
  // Read sensor and send message every two seconds
  static uint16_t nr = 0;
  SLEEP(2);
  msg_t msg;
  if (!sensor.read(msg.humidity, msg.temperature)) return;
  msg.nr = nr++;
  tx.send(&msg, sizeof(msg));
  tx.await();
}

Both 1-Wire and the Virtual Wire Interface (VWI/RF433) are available together with drivers for InterruptPin, ExternalInterruptPin, EEPROM, DHT11/22 (Temperature/Humidity), TSOP4838 (IR Receiver), NEXA Remote Receiver (RF433), HCSR04 (Ultrasonic Range Module), Soft UART Transmitter, PCD8544 LCD and the Cosa Object-Oriented Pin handling. So there is plenty to get started with.

More details on Arduino as an ISP may be found here.  

[Update: 2013-06-05]

The Cosa now supports ATtinyX4, ATtinyX5 and ATtinyX61. A SPI master driver is now implemented (USI), and the SPI drivers (ADXL345 and NRF24L01P) are available for ATtiny. 

A TWI slave device is also available. This allows ATtiny's to be used as slave units on the TWI bus. This could be for a standard Arduino or even a Raspberry Pi and may be used to bridge other interfaces such as OneWire and VirtualWire. 

All LCD drivers work with ATtiny.

[Update: 2013-07-16]

The USI based I2C slave and master device driver for ATtiny has now been completed and device drivers are being ported.

Fig.7: ATtiny85 running LCD device driver benchmark (CosaLCDspeed).
The TWI master device driver allows ATtiny85 to connect to an LCD (HD44780) using an I2C IO expander. 

Fig.8: ATtiny84 acting as a Virtual LCD device (I2C slave device)
Also a Virtual LCD device driver (VLCD) has been introduced. It allows higher performance I2C LCD connection and may be used with for instance an ATtiny84 running the 4-bit parallel implementation of the LCD device driver. In fig.8 above the Arduino Nano connects to a Virtual LCD device on the I2C bus. The device is implemented with an ATtiny84 running LCD device driver in 4-bit parallel access mode. 

[Update 2014-01-05]
The Cosa Tools directory (in examples) contains an implementation of the STK500v1 protocol and may be used as an ISP alternative. There is also a TWI scanner to help debug TWI drivers. 

[Update 2014-03-01]
The Cosa Nucleo support for multi-tasking may be used with ATtiny. Please see the Nucleo example sketches. They include a variant of the blink sketch that will run three threads; one for the LED, one for LED controller that changes the blink frequency and the main thread in the background. The main thread will also power down while waiting for the next watchdog tick. 

Saturday, April 27, 2013

Enhanced Virtual Wire Interface

This post presents the final design of the Cosa Virtual Wire Interface. It now includes many of the features discussed in the previous posts. The main enhancement is reliable wireless communication on cheap RF433 transmitter and receiver modules with node addressing, message filtering, auto-retransmission and support for message dispatch. As before this Cosa sub-system may be used with ATtinyX5 to create ultra tiny and low power sensor devices.

The first section of the post describes the modifications to the Cosa VWI Receiver and Transmitter classes, adding addressing and message tags. The second section describes the new VWI::Transceiver class which provides message acknowledgement with auto-retransmission and reduces much of the complexity of designing and implementing wireless message protocols.

To achieve this the original Virtual Wire Interface (VWI) has been extended. A new overloaded send() method is introduced to allow gathering of multiple buffers (iovec_t). This makes implementation of communication protocols much easier and reduces message memory buffer copying.

To improve performance of retransmission a new resend() method is also introduced. This method will simple retransmit the message already encoded in the transmission buffer. There are also access methods for the next message sequence counter as this will be needed later for retransmission. Below is the updated VWI::Transmitter API.

 Fig.1: Enhanced VWI Transmitter Member Functions

The enhanced mode is enabled by providing a 32-bit node address when calling VWI::begin(). Below is the setup() from the example sketch CosaVWItempsensor.ino to give an idea how this works. 

// Connect RF433 transmitter to Arduino/D9
VirtualWireCodec codec;
VWI::Transmitter tx(Board::D9, &codec);

const uint16_t SPEED = 4000;
const uint32_t ADDR = 0xC05a0002;

...
void setup()
{
  ...
  // Start the Virtual Wire Interface/Transmitter
  VWI::begin(ADDR, SPEED);
  tx.begin();
  ...
}


The blue section is the only change needed to enable addressing from the sensor node, i.e. the new address parameter. The loop() of the temperature sensor will read two 1-Wire Thermometers and send the values together with a reading of the power supply voltage. The message is defined as a struct (sample_t) and given a message type tag, SAMPLE_CMD. This is used as an extra parameter to the send() method and provides the receiver with information about what type of message the payload contains. See the blue sections below.

// Message from the device; temperature and voltage reading
const uint8_t SAMPLE_CMD = 42;
struct sample_t {
  int16_t temperature[2];
  uint16_t voltage;
};

...
void loop()
{

  ...  // Initiate the message with measurements
  sample_t msg;  
  msg.temperature[0] = indoors.get_temperature();
  msg.temperature[1] = outdoors.get_temperature();
  msg.voltage = AnalogPin::bandgap(1100);
  // Enable wireless transmitter and send. Wait completion and disable
  VWI::enable();
  tx.send(&msg, sizeof(msg), SAMPLE_CMD);
  tx.await();
  VWI::disable();
  ...
}


The VWI::Receiver class is also enhanced with filtering of incoming messages. The API is not changed, i.e. the recv() method has the same prototype. Incoming messages are matched against the receiver node address using a simple sub-net mask. The mask is provided as an extra parameter to the begin() method for the receiver. The logic of the address filtering is:

(incoming-message-address & receive-node-sub-net-mask) == receive-node-address

This implies that receiving nodes only listens for messages from transmitters with the same sub-net address as the receiver. Below is the setup() snippet from the CosaVWItempmonitor.ino example sketch:

// Virtual Wire Interface Receiver connected to pin D8
VirtualWireCodec codec;
VWI::Receiver rx(Board::D8, &codec);

const uint16_t SPEED = 4000;
const uint32_t ADDR = 0xc05a0000UL;
const uint32_t MASK = 0xffffff00UL;

...
void setup()
{
  ...
  // Start virtual wire interface and receiver. Use eight bit sub-net mask
  // Transmitters must have the same 24 MSB address bits as the receiver.
  VWI::begin(ADDR, SPEED);
  rx.begin(MASK);
}


In the above snippet the sub-net MASK will allow for 255 transmitting sensor reporting to the monitoring node. The first sub-net address (0) is the receiver itself. With the <24:8> bit addressing scheme in the example sketch there could be 2**24 monitor nodes if in a flat structure. Additional sub-nets are possible, if for instance <16:8:8>, the monitoring node would also report to a sub-net with yet another layer of nodes. And parts of the address could be used to sensor type tagging.

The VWI::Receiver::recv() method will return the full message with the enhanced mode header and the application payload to the caller. Further filtering and dispatch of message will require access to the enhanced VWI header. Defining a struct (msg_t) with the header and a union of the application message data types, as in the snippet below, is a convenient way to describe the incoming messages. Also it is practical to collect all messages in a separate header file to keep the code base consistent. Below is the loop() of the example monitor with an example of message filtering based on the message type (command/cmd). 

// Message received from VWI in extended mode
struct msg_t {
  VWI::header_t header;
  union {
    sample_t sample;

    // Add additional message types here
  };
};

...
void loop()
{
  // Receive a message. Sanity check the message size
  msg_t msg;
  int8_t len = rx.recv(&msg, sizeof(msg));
  if (len <= 0) return;
  // Print message header; transmitter address and sequence number
  trace << hex << msg.header.addr << ':' << msg.header.nr << ':';
  // Check message type and print contents
  if (msg.header.cmd == SAMPLE_CMD) {
  ...
  }
  else {
    trace << msg.header.cmd << PSTR(":unknown message type") << endl;
  }
}


VWI::header_t contains the transmitter node address, message type tag and sequence number. The node address is provided as the parameter to VWI::begin(), the message type tag (cmd) as the additional parameter to VWI::Transmitter::send() and the sequence number is maintained internally to the Transmitter. Below is the implementation:

class VWI {
public:
  ...
  /** Message header for enhanced Virtual Wire Interface mode */
  struct header_t {
    uint32_t addr;         /**< Transmitter node address */
    uint8_t cmd;           /**< Command or message type */
    uint8_t nr;            /**< Message sequence number */
  };
  ...

};

So far the modifications are to the original Virtual Wire Interface and have provided addressing and message types. In the following section the new VWI::Transceiver class is presented. This class adds reliable communication with message acknowledgement and auto-retransmission.


Fig.2: VWI::Transceiver Public Member Functions and Attributes

The VWI::Transceiver interface is now reduced to basically send() and recv(). The Transceiver contains both a Transmitter and Receiver instance (rx and tx attributes above). The send() method will transmit an enhanced mode message and wait for an acknowledgement. The acknowledgement is transmitted by the receiving node. If the message or acknowledgement is lost an automatic retransmission will occur. The sender node will wait a TIMEOUT period before retransmission and perform RETRANS_MAX retransmissions. The send() method will return the number of transmissions or a negative error code(-1) if the maximum number of retransmissions was exceeded.

Note that the VWI::Transmitter::send() method will return directly after placing the message into the transmission buffer. This is not the case with VWI::Transceiver::send(). This method will wait until it receives an acknowledgement with possible retransmission.

Below is a snippet of the CosaVWIclient.ino example sketch. This sketch demonstrates how to use the VWI::Transceiver and multiple message types. The first message type (sample_t/SAMPLE_CMD) contains two analog readings and is sent every second from the wireless sensor device. The second message type (stat_t/STAT_CMD) contains power supply reading and transceiver statistics. This message is sent every 15th second.

// Network configuration
const uint32_t ADDR = 0xc05a0001UL;
const uint16_t SPEED = 4000;
...
// Virtual Wire Interface Transceiver
VWI::Transceiver trx(Board::D8, Board::D9, &codec);
...
// Analog pins to sample for values to send
AnalogPin luminance(Board::A2);
AnalogPin temperature(Board::A3);
...
// Message type to send (should be in an include file for client and server)
const uint8_t SAMPLE_CMD = 1;
struct sample_t {
  uint16_t luminance;
  uint16_t temperature;
  sample_t(uint16_t lum, uint16_t temp)
  {
    luminance = lum;
    temperature = temp;
  }
};
...
const uint8_t STAT_CMD = 2;
struct stat_t {
  uint16_t voltage;
  uint16_t sent;
  uint16_t resent;
  uint16_t received;
  uint16_t failed;
  void update(int8_t nr)
  {
    sent += 1;
    if (nr <= 0)
      failed += 1;
    else if (nr > 1)
      resent += (nr - 1);
  }
};
...
// Statistics
stat_t stat;
... 
void setup()
{
  // Start watchdog for delay
  Watchdog::begin();
  RTC::begin();
  ...
  // Start virtual wire interface in extended mode; transceiver
  VWI::begin(ADDR, SPEED);
  trx.begin();
}
...
void loop()
{
  // Send message with luminance and temperature
  sample_t sample(luminance.sample(), temperature.sample());
  int8_t nr = trx.send(&sample, sizeof(sample), SAMPLE_CMD);
  stat.update(nr);
  // Send message with battery voltage and statistics every 15 messages
  if (stat.sent % 15 == 0) {
    stat.voltage = AnalogPin::bandgap(1100);
    nr = trx.send(&stat, sizeof(stat), STAT_CMD);
    stat.update(nr);
  }

  // Take a nap
  SLEEP(1);
}


A server will receive the messages from the client (above), filter, acknowledge and print the messages. Note that the receiving node must filter received messages so that multiple messages with the same sequence number are only handled once (if necessary). Below is a snippet from the CosaVWIserver.ino example sketch:

// Virtual Wire Interface Transceiver
VWI::Transceiver trx(Board::D8, Board::D9, &codec);
...
// Network configuration
const uint32_t ADDR = 0xc05a0000UL;
const uint32_t MASK = 0xffffff00UL;
const uint16_t SPEED = 4000;
..
void setup()
{
  ...
  // Start virtual wire interface transceiver
  VWI::begin(ADDR, SPEED);
  trx.begin(MASK);
}
...
// Message types
const uint8_t SAMPLE_CMD = 1;
struct sample_t {
  uint16_t luminance;
  uint16_t temperature;
};
const uint8_t STAT_CMD = 2;
struct stat_t {
  uint16_t voltage;
  uint16_t sent;
  uint16_t resent;
  uint16_t received;
  uint16_t failed;
};
...
// Extended mode message with header
struct msg_t {
  VWI::header_t header;
  union {
    sample_t sample;
    stat_t stat;
  };
};

...
void loop()
{
  // Processed sequence number (should be one per client)
  static uint8_t nr = 0xff;
  // Wait for a message. Sanity check the length
  msg_t msg;
  int8_t len = trx.recv(&msg, sizeof(msg));
  if (len <= 0) return; 
  // Check that this is not a retransmission

  // Should be one counter (nr) per connection
  if (nr == msg.header.nr) return;
  nr = msg.header.nr;
  // Print header, type message type and print contents
  trace << hex << msg.header.addr << ':' << msg.header.nr << ':';
  switch (msg.header.cmd) {
  case SAMPLE_CMD:
    ...
    break;
  case STAT_CMD:
    ...
    break;
  }


The next development of Cosa VWI is to increase performance and adapt to the event driven state-machine framework. One goal is to abstracting the interface toward wireless connection over Ethernet, WiFi, RF433, NFR24L01P, etc, so that applications are written without concern to the wireless device. RF433 is an interesting challenge as the protocol stack must be built from the ground up. 

[Update 2013-11-17]
This extension of the Virtual Wire interface in Cosa has been deprecated and replaced by an abstract Wireless interface. This interface is implemented by RF433 modules (VWI), CC1101 and NRF24L01P. The above extended VWI interface is still possible to implement on top of the new Wireless interface. This will be added in later releases of Cosa. Please see the Cosa documentation for further details.