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