Tuesday 10 September 2013

e-Paper!!! You can't get much more retro than paper.



Now we are talking retro!

I have been playing with some e-ink panels from embedded artists. I will make two versions of the retro runner readout (one with LED 7-segment displays and one with e-paper).

I bought two of embedded artists 2.7" e-paper panels.

These are great but there was a minor issue with the software for them. Dynamic content required a buffer of 5808 bytes but my Arduino Pro Mini (like the Arduino Uno) only has 1 or 2 kB of SRAM.

The issue was that a pair of entire panel size buffers is allocated in the EPD_GFX object -- a more memory-efficient way is to reduce to a single buffer and allow for sub-parts of the buffer to be updated in serial.

In fact, if we get carried away we can make it so that we only use a single line buffer and iterate across every line. I got carried away:




That code takes the pair of 5808 byte buffers and reduces it to a single 33 byte buffer.

What it does is execute the "drawing" code on each segment (each line in the case above) and only applies the pixels that are in that segment. This is obviously very wasteful on processing cycles (as we are calculating every pixel many times) but MCUs are about trade-offs aren't they? We can even rewrite over a few segments (a few of the boxes and the text "Across" do that).

Here is a sample with 16 line segments (update of the "thermo" demo from repaper):





These changes and looping code were made against the repaper repository code on github at https://github.com/brodykenrick/gratis.

Wednesday 21 August 2013

Software - The main program setup and loop

The main program talks to the ANT+ device, decides on a message and drives our display.

The code is found at RetroRunnerReadout.ino.

The program follows the standard Arduino/MCU setup then loop paradigm.

It sets up the hardware (display and ANT+) and following that displays a few setup messages on the LED 7-segment display.

setup() 

Setup enables the hardware for the ANT+ and MAX7219 chips and prints a few messages to the display.
{
  Serial.begin(CONSOLE_BAUD_RATE);

  SERIAL_INFO_PRINTLN("CanToo Runner!");
  mydisplay.shutdown(0, false); // turns on display
  mydisplay.setIntensity(0, DISPLAY_INTENSITY); // 0..15 = brightest
  mydisplay.clearDisplay(0);
  mydisplay.shutdown(0, true); // turns off display


  //Print a few startup messages
  for(unsigned int counter_setup = 0; counter_setup < STARTUP_TEXTS_COUNT; counter_setup++)
  {
    const char * const* str_in_pm = &startup_texts[ counter_setup ];
    char text[MAX_CHARS_TO_DISPLAY_STR];
    CLEAR_STR(text, MAX_CHARS_TO_DISPLAY_STR);
    //Read from PROG_MEM
    strncpy_P(text, (char*)pgm_read_word( str_in_pm ), MAX_CHARS_TO_DISPLAY_STR);
    print_and_delay( text );
  }

  SERIAL_DEBUG_PRINTLN_F("ANT+ Config...");

  //We setup an interrupt to detect when the RTS is received from the ANT chip.
  //This is a 50 usec HIGH signal at the end of each valid ANT message received from the host at the chip
  attachInterrupt(0/*0==pin2==RTS_PIN*/, isr_ant, RISING);

  ant_serial.begin( ANTPLUS_BAUD_RATE );
  antplus.begin( ant_serial );

  //This should not be strictly necessary - the device should always come up by itself....
  //But let's make sure we didn't miss the first RTS in a power-up race
  antplus.hardwareReset();
  SERIAL_DEBUG_PRINTLN_F("ANT+ Config Finished");
}
 
Following that it drops into the processing loop. The loop alternates between servicing sending/receiving ANT+ messages and displaying messages to the screen.

 loop()

The loop function repeats executing ANT+ code and displaying (). The ANT+ code is given priority at the start as it establishes an ANT+ channel.
{
  loop_antplus();

  //Get the ANT+ channels up quickly
  // then send some display messages

  if(ret_val_ce == ANT_CHANNEL_ESTABLISH_COMPLETE)
  {
    //This has blocking sleeps in it and so causes things to stop
    loop_display();

  }
}

loop_display()

The display messages are hard coded loops. The loops however have special keywords that are replaced dynamically updating distance travelled, heart rate, a random name or motivation quote etc.
 
{
  static int counter = 0;
  char text[MAX_CHARS_TO_DISPLAY_STR];

  //Pull a string from the loop texts
  //These are one per loop and some of them get replaced if they are "magic" texts
  const char * const* str_in_pm = &loop_texts[ counter % LOOP_TEXTS_COUNT ];

  
  CLEAR_STR(text, MAX_CHARS_TO_DISPLAY_STR);
  strncpy_P(text, (char*)pgm_read_word( str_in_pm ), MAX_CHARS_TO_DISPLAY_STR);

  //Replace, print and then delay.
  print_and_delay( text );

  counter++;
}

loop_antplus()

The ANT+ loop does two things. Firstly it sets up  an ANT+ channel for receiving SDM/Footpod broadcasts and then it processes any messages coming in on that channel (updating variables that are read in the string replace function).
{
  byte packet_buffer[MAXPACKETLEN];
  ANT_Packet * packet = (ANT_Packet *) packet_buffer;
  MESSAGE_READ ret_val = MESSAGE_READ_NONE;
  
//We received an interrupt on the nRF24AP2 RTS pin
  if(state == 1)
  {
    antplus.rTSHighAssertion();
    //Clear the ISR flag
    state = 0;
  }
  ret_val = antplus.readPacket(packet, MAXPACKETLEN, 0 );
  if((ret_val == MESSAGE_READ_EXPECTED) || (ret_val == MESSAGE_READ_OTHER))
  {
//Some broadcast packets will update globals (like HRM and Footpod)
    process_packet(packet);
  }
  //Only one channel gets setup in this version
  if(ret_val_ce != ANT_CHANNEL_ESTABLISH_COMPLETE)
  {
//This function will progress a state machine in
//the ANT+ library to establish a channel
    ret_val_ce = antplus.progress_setup_channel( &sdm_channel );
    if(ret_val_ce == ANT_CHANNEL_ESTABLISH_COMPLETE)
    {
      SERIAL_DEBUG_PRINTLN_F( "Channel established!" );
    }
  }
}

If you want to dig deeper, see the interrupt, string replacement code look at the code on github. RetroRunnerReadout.ino.The libraries created for the project are at MAX72xx_SPI_Arduino and ANTPlus_Arduino.

The display

Here is a video from an early test of the (small) display with simulated distance countdown:




Assembling

Connections
The electrical connections are pretty simple so I did not do a schematic (power, an SPI connection through a level converter to the display unit and [software]serial/UART to nRF24AP2 module). I can make that up if people are interested......

To connect the device parts to the hat I decided "velcro" was the way to go. Allowing removal when needed. I waterproofed each of the elements in small "ziploc" bags with electrical type and put some velcro on the back of them. I then stitched the other half of the velcro into the hat (with some very poor sewing technique).

This is what it looks like:


and here is a quick video too:

and you can see in the video the bane of the 7-segment display -- the letter 'k'



Related work

Goodluckbuy nrf24AP2 ANT+ Module

This module is the one I am using for ANT+: http://www.goodluckbuy.com/nrf24ap2-wireless-module-zigbee-module.html

This module, marked with "NRF24AP2 YJ-ANT", lacks any documentation.....

I did trace analysis and then some testing (https://github.com/brodykenrick/nRF24AP2_SUART_Arduino) and can confirm:

The connector on the module is (looking from the front/top, pin 1 is marked []) has
[]GND(=VSS) | VDD(=3.3 volts)
UART_TX | UART_RX
!(SUSP) | SLEEP
RTS | !(RESET)
Baud rate of the module is 9600 bps.


Also appears to be:
http://www.goodluckbuy.com/nrf24ap2-wireless-module-zigbee-module.htm
http://www.aliexpress.com/store/product/Freeshipping-NRF24AP2-Wireless-Networking-Module-Zigbee-Module/809689_559703297.html

Power issues: 5V MAX7219 vs. 3V nRF24AP2

Now, a problem with the system is that the display controller (MAX7219 must run on ~5V) and the ANT+ module (nRF24AP2) is at ~3V.

To over come the differences a level converter was employed (http://www.freetronics.com/products/logic-level-converter-module).

Worked like a charm.




Now, we still have a problem in that our LiPo batteries aren't going to give 5V (even 4.2V at full charge wasn't enough to get the display updating properly).

Prototype one (without ANT+) used a boost converter to get to 5V from a single battery however the battery ran out after about 3.5 hours on the road.

As we need more power capacity and increased voltage I just added another identical battery in series and a 5V regulator (https://www.sparkfun.com/products/107).


This gives me a 5V source for the display subsystem and the Arduino Pro Mini 3v3 has its own 3V regulator.

The level converter is then put between the 3V Arduino SPI signals and the 5V display.