From 25851949cce42c297b8a5c6b4daf684196c78a01 Mon Sep 17 00:00:00 2001
From: Stanley Huang <stanleyhuangyc@gmail.com>
Date: Fri, 15 Aug 2014 21:36:03 +1000
Subject: Add NanoTimer

---
 nanotimer/MicroLCD.cpp  | 657 +++++++++++++++++++++++++++++++++++++
 nanotimer/MicroLCD.h    | 119 +++++++
 nanotimer/PCD8544.cpp   | 316 ++++++++++++++++++
 nanotimer/PCD8544.h     | 117 +++++++
 nanotimer/SH1106.cpp    | 429 +++++++++++++++++++++++++
 nanotimer/SSD1306.cpp   | 277 ++++++++++++++++
 nanotimer/SSD1306.h     | 112 +++++++
 nanotimer/config.h      |  32 ++
 nanotimer/datalogger.h  | 377 ++++++++++++++++++++++
 nanotimer/nanotimer.cbp | 598 ++++++++++++++++++++++++++++++++++
 nanotimer/nanotimer.ino | 395 +++++++++++++++++++++++
 pbox/README.txt         |  11 -
 pbox/config.h           |  50 ---
 pbox/datalogger.h       | 377 ----------------------
 pbox/images.h           |   5 -
 pbox/unologger.cbp      | 600 ----------------------------------
 pbox/unologger.ino      | 839 ------------------------------------------------
 17 files changed, 3429 insertions(+), 1882 deletions(-)
 create mode 100644 nanotimer/MicroLCD.cpp
 create mode 100644 nanotimer/MicroLCD.h
 create mode 100644 nanotimer/PCD8544.cpp
 create mode 100644 nanotimer/PCD8544.h
 create mode 100644 nanotimer/SH1106.cpp
 create mode 100644 nanotimer/SSD1306.cpp
 create mode 100644 nanotimer/SSD1306.h
 create mode 100644 nanotimer/config.h
 create mode 100644 nanotimer/datalogger.h
 create mode 100644 nanotimer/nanotimer.cbp
 create mode 100644 nanotimer/nanotimer.ino
 delete mode 100644 pbox/README.txt
 delete mode 100644 pbox/config.h
 delete mode 100644 pbox/datalogger.h
 delete mode 100644 pbox/images.h
 delete mode 100644 pbox/unologger.cbp
 delete mode 100644 pbox/unologger.ino

diff --git a/nanotimer/MicroLCD.cpp b/nanotimer/MicroLCD.cpp
new file mode 100644
index 0000000..a602b18
--- /dev/null
+++ b/nanotimer/MicroLCD.cpp
@@ -0,0 +1,657 @@
+/*************************************************************************
+* Arduino Text Display Library for Multiple LCDs
+* Distributed under GPL v2.0
+* Copyright (c) 2013 Stanley Huang <stanleyhuangyc@live.com>
+* All rights reserved.
+*************************************************************************/
+
+#include <Arduino.h>
+#include <Wire.h>
+#include "MicroLCD.h"
+
+// fonts data
+const PROGMEM unsigned char digits16x24[][48] = {
+{0x00,0x00,0x00,0xF0,0xFF,0x0F,0xFC,0xFF,0x3F,0xFE,0xFF,0x7F,0xFE,0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x07,0x00,0xE0,0x07,0x00,0xE0,0x07,0x00,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x7F,0xFE,0xFF,0x7F,0xFC,0xFF,0x3F,0xF0,0xFF,0x0F},/*"0",0*/
+{0x00,0x00,0x00,0x70,0x00,0x00,0x70,0x00,0x00,0x70,0x00,0x00,0x78,0x00,0x00,0xF8,0x00,0x00,0xFC,0xFF,0xFF,0xFE,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"1",0*/
+{0x00,0x00,0x00,0xF8,0x00,0xE0,0xFC,0x00,0xF8,0xFE,0x00,0xFE,0xFE,0x80,0xFF,0xFF,0xC0,0xFF,0x07,0xF0,0xFF,0x07,0xFC,0xFF,0x07,0xFF,0xEF,0xFF,0xFF,0xE3,0xFF,0xFF,0xE1,0xFE,0x7F,0xE0,0xFE,0x3F,0xE0,0xFC,0x0F,0xE0,0xF0,0x03,0x00,0x00,0x00,0x00},/*"2",2*/
+{0x00,0x00,0x00,0xF8,0x80,0x1F,0xFE,0x80,0x3F,0xFE,0x80,0x7F,0xFF,0x80,0x7F,0xFF,0x80,0xFF,0xFF,0x9C,0xFF,0xFF,0x9C,0xFF,0x07,0x1C,0xE0,0x07,0x3E,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x7F,0xFE,0xF7,0x7F,0xFC,0xF7,0x3F,0xF0,0xE3,0x1F},/*"3",3*/
+{0x00,0xF0,0x0F,0x00,0xFE,0x0F,0x80,0xFF,0x0F,0xE0,0xFF,0x0F,0xFC,0xBF,0x0F,0xFF,0x87,0x0F,0xFF,0x81,0x0F,0x3F,0x80,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x80,0x0F,0x00,0x80,0x0F},/*"4",4*/
+{0x00,0x00,0x00,0xFF,0xC7,0x0F,0xFF,0xC7,0x3F,0xFF,0xC7,0x7F,0xFF,0xC7,0x7F,0xFF,0xC7,0xFF,0xFF,0xC7,0xFF,0x87,0x01,0xE0,0xC7,0x01,0xE0,0xC7,0x01,0xE0,0xC7,0xFF,0xFF,0xC7,0xFF,0xFF,0xC7,0xFF,0x7F,0x87,0xFF,0x7F,0x87,0xFF,0x3F,0x07,0xFE,0x1F},/*"5",5*/
+{0x00,0x00,0x00,0xF0,0xFF,0x0F,0xFC,0xFF,0x3F,0xFE,0xFF,0x7F,0xFE,0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x07,0x06,0xE0,0x07,0x07,0xE0,0x07,0x07,0xE0,0x3F,0xFF,0xFF,0x3F,0xFF,0xFF,0x3E,0xFF,0x7F,0x3E,0xFE,0x7F,0x3C,0xFE,0x3F,0x38,0xF8,0x1F},/*"6",6*/
+{0x00,0x00,0x00,0x07,0x00,0x00,0x07,0x00,0x00,0x07,0x00,0xC0,0x07,0x00,0xF8,0x07,0x00,0xFF,0x07,0xE0,0xFF,0x07,0xFE,0xFF,0xC7,0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0x07,0xFF,0xFF,0x00,0xFF,0x0F,0x00,0xFF,0x01,0x00,0x1F,0x00,0x00,0x00,0x00,0x00},/*"7",1*/
+{0x00,0x00,0x00,0xF0,0xE3,0x1F,0xFC,0xF7,0x3F,0xFE,0xFF,0x7F,0xFE,0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x07,0x1C,0xE0,0x07,0x1C,0xE0,0x07,0x1C,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x7F,0xFE,0xF7,0x7F,0xFC,0xF7,0x3F,0xF0,0xE3,0x1F},/*"8",8*/
+{0x00,0x00,0x00,0xF8,0x1F,0x1C,0xFC,0x7F,0x3C,0xFE,0x7F,0x7C,0xFE,0xFF,0x7C,0xFF,0xFF,0xFC,0xFF,0xFF,0xFC,0x07,0xE0,0xE0,0x07,0xE0,0xE0,0x07,0x60,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0x7F,0xFE,0xFF,0x7F,0xFC,0xFF,0x3F,0xF0,0xFF,0x0F},/*"9",9*/
+};
+
+const PROGMEM unsigned char digits8x8[][8] = {
+{0x3C,0x7E,0x83,0x81,0x81,0x7E,0x3C,0x00},/*0*/
+{0x84,0x84,0x82,0xFF,0xFF,0x80,0x80,0x00},/*1*/
+{0x84,0xC6,0xE1,0xA1,0xB1,0x9F,0x8E,0x00},/*2*/
+{0x42,0xC3,0x81,0x89,0x89,0xFF,0x76,0x00},/*3*/
+{0x20,0x38,0x24,0x22,0xFF,0xFF,0x20,0x00},/*4*/
+{0x5F,0xDF,0x99,0x89,0x89,0xF9,0x70,0x00},/*5*/
+{0x3C,0x7E,0x89,0x89,0x89,0xFB,0x72,0x00},/*6*/
+{0x01,0x01,0xE1,0xF9,0x1D,0x07,0x01,0x00},/*7*/
+{0x6E,0xFF,0x89,0x89,0x99,0xFF,0x76,0x00},/*8*/
+{0x4E,0xDF,0x91,0x91,0x91,0x7F,0x3E,0x00},/*9*/
+};
+
+// The 7-bit ASCII character set...
+const PROGMEM unsigned char font5x8[][5] = {
+  { 0x00, 0x00, 0x5f, 0x00, 0x00 },  // 21 !
+  { 0x00, 0x07, 0x00, 0x07, 0x00 },  // 22 "
+  { 0x14, 0x7f, 0x14, 0x7f, 0x14 },  // 23 #
+  { 0x24, 0x2a, 0x7f, 0x2a, 0x12 },  // 24 $
+  { 0x23, 0x13, 0x08, 0x64, 0x62 },  // 25 %
+  { 0x36, 0x49, 0x55, 0x22, 0x50 },  // 26 &
+  { 0x00, 0x05, 0x03, 0x00, 0x00 },  // 27 '
+  { 0x00, 0x1c, 0x22, 0x41, 0x00 },  // 28 (
+  { 0x00, 0x41, 0x22, 0x1c, 0x00 },  // 29 )
+  { 0x14, 0x08, 0x3e, 0x08, 0x14 },  // 2a *
+  { 0x08, 0x08, 0x3e, 0x08, 0x08 },  // 2b +
+  { 0x00, 0x50, 0x30, 0x00, 0x00 },  // 2c ,
+  { 0x08, 0x08, 0x08, 0x08, 0x08 },  // 2d -
+  { 0x00, 0x60, 0x60, 0x00, 0x00 },  // 2e .
+  { 0x20, 0x10, 0x08, 0x04, 0x02 },  // 2f /
+  { 0x3e, 0x51, 0x49, 0x45, 0x3e },  // 30 0
+  { 0x00, 0x42, 0x7f, 0x40, 0x00 },  // 31 1
+  { 0x42, 0x61, 0x51, 0x49, 0x46 },  // 32 2
+  { 0x21, 0x41, 0x45, 0x4b, 0x31 },  // 33 3
+  { 0x18, 0x14, 0x12, 0x7f, 0x10 },  // 34 4
+  { 0x27, 0x45, 0x45, 0x45, 0x39 },  // 35 5
+  { 0x3c, 0x4a, 0x49, 0x49, 0x30 },  // 36 6
+  { 0x01, 0x71, 0x09, 0x05, 0x03 },  // 37 7
+  { 0x36, 0x49, 0x49, 0x49, 0x36 },  // 38 8
+  { 0x06, 0x49, 0x49, 0x29, 0x1e },  // 39 9
+  { 0x00, 0x36, 0x36, 0x00, 0x00 },  // 3a :
+  { 0x00, 0x56, 0x36, 0x00, 0x00 },  // 3b ;
+  { 0x08, 0x14, 0x22, 0x41, 0x00 },  // 3c <
+  { 0x14, 0x14, 0x14, 0x14, 0x14 },  // 3d =
+  { 0x00, 0x41, 0x22, 0x14, 0x08 },  // 3e >
+  { 0x02, 0x01, 0x51, 0x09, 0x06 },  // 3f ?
+  { 0x32, 0x49, 0x79, 0x41, 0x3e },  // 40 @
+  { 0x7e, 0x11, 0x11, 0x11, 0x7e },  // 41 A
+  { 0x7f, 0x49, 0x49, 0x49, 0x36 },  // 42 B
+  { 0x3e, 0x41, 0x41, 0x41, 0x22 },  // 43 C
+  { 0x7f, 0x41, 0x41, 0x22, 0x1c },  // 44 D
+  { 0x7f, 0x49, 0x49, 0x49, 0x41 },  // 45 E
+  { 0x7f, 0x09, 0x09, 0x09, 0x01 },  // 46 F
+  { 0x3e, 0x41, 0x49, 0x49, 0x7a },  // 47 G
+  { 0x7f, 0x08, 0x08, 0x08, 0x7f },  // 48 H
+  { 0x00, 0x41, 0x7f, 0x41, 0x00 },  // 49 I
+  { 0x20, 0x40, 0x41, 0x3f, 0x01 },  // 4a J
+  { 0x7f, 0x08, 0x14, 0x22, 0x41 },  // 4b K
+  { 0x7f, 0x40, 0x40, 0x40, 0x40 },  // 4c L
+  { 0x7f, 0x02, 0x0c, 0x02, 0x7f },  // 4d M
+  { 0x7f, 0x04, 0x08, 0x10, 0x7f },  // 4e N
+  { 0x3e, 0x41, 0x41, 0x41, 0x3e },  // 4f O
+  { 0x7f, 0x09, 0x09, 0x09, 0x06 },  // 50 P
+  { 0x3e, 0x41, 0x51, 0x21, 0x5e },  // 51 Q
+  { 0x7f, 0x09, 0x19, 0x29, 0x46 },  // 52 R
+  { 0x46, 0x49, 0x49, 0x49, 0x31 },  // 53 S
+  { 0x01, 0x01, 0x7f, 0x01, 0x01 },  // 54 T
+  { 0x3f, 0x40, 0x40, 0x40, 0x3f },  // 55 U
+  { 0x1f, 0x20, 0x40, 0x20, 0x1f },  // 56 V
+  { 0x3f, 0x40, 0x38, 0x40, 0x3f },  // 57 W
+  { 0x63, 0x14, 0x08, 0x14, 0x63 },  // 58 X
+  { 0x07, 0x08, 0x70, 0x08, 0x07 },  // 59 Y
+  { 0x61, 0x51, 0x49, 0x45, 0x43 },  // 5a Z
+  { 0x00, 0x7f, 0x41, 0x41, 0x00 },  // 5b [
+  { 0x02, 0x04, 0x08, 0x10, 0x20 },  // 5c backslash
+  { 0x00, 0x41, 0x41, 0x7f, 0x00 },  // 5d ]
+  { 0x04, 0x02, 0x01, 0x02, 0x04 },  // 5e ^
+  { 0x40, 0x40, 0x40, 0x40, 0x40 },  // 5f _
+  { 0x00, 0x01, 0x02, 0x04, 0x00 },  // 60 `
+  { 0x20, 0x54, 0x54, 0x54, 0x78 },  // 61 a
+  { 0x7f, 0x48, 0x44, 0x44, 0x38 },  // 62 b
+  { 0x38, 0x44, 0x44, 0x44, 0x20 },  // 63 c
+  { 0x38, 0x44, 0x44, 0x48, 0x7f },  // 64 d
+  { 0x38, 0x54, 0x54, 0x54, 0x18 },  // 65 e
+  { 0x08, 0x7e, 0x09, 0x01, 0x02 },  // 66 f
+  { 0x0c, 0x52, 0x52, 0x52, 0x3e },  // 67 g
+  { 0x7f, 0x08, 0x04, 0x04, 0x78 },  // 68 h
+  { 0x00, 0x44, 0x7d, 0x40, 0x00 },  // 69 i
+  { 0x20, 0x40, 0x44, 0x3d, 0x00 },  // 6a j
+  { 0x7f, 0x10, 0x28, 0x44, 0x00 },  // 6b k
+  { 0x00, 0x41, 0x7f, 0x40, 0x00 },  // 6c l
+  { 0x7c, 0x04, 0x18, 0x04, 0x78 },  // 6d m
+  { 0x7c, 0x08, 0x04, 0x04, 0x78 },  // 6e n
+  { 0x38, 0x44, 0x44, 0x44, 0x38 },  // 6f o
+  { 0x7c, 0x14, 0x14, 0x14, 0x08 },  // 70 p
+  { 0x08, 0x14, 0x14, 0x18, 0x7c },  // 71 q
+  { 0x7c, 0x08, 0x04, 0x04, 0x08 },  // 72 r
+  { 0x48, 0x54, 0x54, 0x54, 0x20 },  // 73 s
+  { 0x04, 0x3f, 0x44, 0x40, 0x20 },  // 74 t
+  { 0x3c, 0x40, 0x40, 0x20, 0x7c },  // 75 u
+  { 0x1c, 0x20, 0x40, 0x20, 0x1c },  // 76 v
+  { 0x3c, 0x40, 0x30, 0x40, 0x3c },  // 77 w
+  { 0x44, 0x28, 0x10, 0x28, 0x44 },  // 78 x
+  { 0x0c, 0x50, 0x50, 0x50, 0x3c },  // 79 y
+  { 0x44, 0x64, 0x54, 0x4c, 0x44 },  // 7a z
+  { 0x00, 0x08, 0x36, 0x41, 0x00 },  // 7b {
+  { 0x00, 0x00, 0x7f, 0x00, 0x00 },  // 7c |
+  { 0x00, 0x41, 0x36, 0x08, 0x00 },  // 7d }
+  { 0x10, 0x08, 0x08, 0x10, 0x08 },  // 7e ~
+};
+
+#ifndef MEMORY_SAVING
+const PROGMEM unsigned char digits16x16[][32] = {
+{0x00,0xE0,0xF8,0xFC,0xFE,0x1E,0x07,0x07,0x07,0x07,0x1E,0xFE,0xFC,0xF8,0xF0,0x00,0x00,0x07,0x0F,0x3F,0x3F,0x7C,0x70,0x70,0x70,0x70,0x7C,0x3F,0x1F,0x1F,0x07,0x00},/*0*/
+{0x00,0x00,0x00,0x06,0x07,0x07,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,0x00},/*1*/
+{0x00,0x38,0x3C,0x3E,0x3E,0x0F,0x07,0x07,0x07,0xCF,0xFF,0xFE,0xFE,0x38,0x00,0x00,0x00,0x40,0x40,0x60,0x70,0x78,0x7C,0x7E,0x7F,0x77,0x73,0x71,0x70,0x70,0x00,0x00},/*2*/
+{0x00,0x18,0x1C,0x1E,0x1E,0x0F,0xC7,0xC7,0xE7,0xFF,0xFE,0xBE,0x9C,0x00,0x00,0x00,0x00,0x0C,0x1C,0x3C,0x3C,0x78,0x70,0x70,0x70,0x79,0x7F,0x3F,0x1F,0x0F,0x00,0x00},/*3*/
+{0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x1E,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x06,0x07,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x7F,0x7F,0x7F,0x7F,0x06,0x06,0x00},/*4*/
+{0x00,0x00,0x00,0x00,0xF0,0xFF,0xFF,0xFF,0xE7,0xE7,0xE7,0xE7,0xC7,0x87,0x00,0x00,0x00,0x00,0x38,0x78,0x71,0x70,0x70,0x70,0x70,0x70,0x39,0x3F,0x3F,0x1F,0x0F,0x00},/*5*/
+{0x00,0x80,0xE0,0xF0,0xF8,0xFC,0x7F,0x7F,0x6F,0x67,0xE1,0xE1,0xC0,0x80,0x00,0x00,0x00,0x0F,0x1F,0x3F,0x3F,0x78,0x70,0x70,0x70,0x70,0x78,0x3F,0x3F,0x1F,0x0F,0x00},/*6*/
+{0x00,0x07,0x07,0x07,0x07,0x07,0xC7,0xE7,0xF7,0xFF,0x7F,0x3F,0x1F,0x07,0x03,0x01,0x00,0x20,0x38,0x7C,0x7E,0x3F,0x0F,0x07,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*7*/
+{0x00,0x00,0x00,0x1C,0xBE,0xFE,0xFF,0xE7,0xC3,0xC3,0xE7,0xFF,0xFE,0xBE,0x1C,0x00,0x00,0x00,0x0E,0x3F,0x3F,0x7F,0x71,0x60,0x60,0x60,0x71,0x7F,0x3F,0x3F,0x0F,0x00},/*8*/
+{0x00,0x78,0xFC,0xFE,0xFE,0x8F,0x07,0x07,0x07,0x07,0x8F,0xFE,0xFE,0xFC,0xF8,0x00,0x00,0x00,0x00,0x01,0x43,0x43,0x73,0x7B,0x7F,0x7F,0x1F,0x0F,0x07,0x03,0x00,0x00},/*9*/
+};
+
+const PROGMEM unsigned char font8x16_terminal[][16] = {
+{0x00,0x00,0x00,0x00,0x7C,0x00,0xFE,0x1B,0xFE,0x1B,0x7C,0x00,0x00,0x00,0x00,0x00},/*"!",0*/
+{0x00,0x00,0x0E,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x1E,0x00,0x0E,0x00,0x00,0x00},/*""",1*/
+{0x20,0x01,0xFC,0x0F,0xFC,0x0F,0x20,0x01,0x20,0x01,0xFC,0x0F,0xFC,0x0F,0x20,0x01},/*"#",2*/
+{0x38,0x06,0x7C,0x0C,0x44,0x08,0xFF,0x3F,0xFF,0x3F,0x84,0x08,0x8C,0x0F,0x18,0x07},/*"$",3*/
+{0x1C,0x18,0x14,0x1E,0x9C,0x07,0xE0,0x01,0x78,0x1C,0x1E,0x14,0x06,0x1C,0x00,0x00},/*"%",4*/
+{0xBC,0x1F,0xFE,0x10,0x42,0x10,0xC2,0x10,0xFE,0x1F,0x3C,0x0F,0x80,0x19,0x80,0x10},/*"&",5*/
+{0x00,0x00,0x00,0x00,0x10,0x00,0x1E,0x00,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",6*/
+{0x00,0x00,0x00,0x00,0xF0,0x07,0xFC,0x1F,0x0E,0x38,0x02,0x20,0x00,0x00,0x00,0x00},/*"(",7*/
+{0x00,0x00,0x00,0x00,0x02,0x20,0x0E,0x38,0xFC,0x1F,0xF0,0x07,0x00,0x00,0x00,0x00},/*")",8*/
+{0x80,0x00,0xA0,0x02,0xE0,0x03,0xC0,0x01,0xC0,0x01,0xE0,0x03,0xA0,0x02,0x80,0x00},/*"*",9*/
+{0x80,0x00,0x80,0x00,0x80,0x00,0xE0,0x03,0xE0,0x03,0x80,0x00,0x80,0x00,0x80,0x00},/*"+",10*/
+{0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x78,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00},/*",",11*/
+{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00},/*"-",12*/
+{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00},/*".",13*/
+{0x00,0x18,0x00,0x1E,0x80,0x07,0xE0,0x01,0x78,0x00,0x1E,0x00,0x06,0x00,0x00,0x00},/*"/",14*/
+{0xF8,0x07,0xFC,0x0F,0x06,0x18,0xC2,0x10,0xC2,0x10,0x06,0x18,0xFC,0x0F,0xF8,0x07},/*"0",15*/
+{0x00,0x00,0x08,0x10,0x0C,0x10,0xFE,0x1F,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x00},/*"1",16*/
+{0x04,0x1C,0x06,0x1E,0x02,0x13,0x82,0x11,0xC2,0x10,0x62,0x10,0x3E,0x18,0x1C,0x18},/*"2",17*/
+{0x04,0x08,0x06,0x18,0x02,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xFE,0x1F,0xBC,0x0F},/*"3",18*/
+{0xC0,0x01,0xE0,0x01,0x30,0x01,0x18,0x01,0x0C,0x11,0xFE,0x1F,0xFE,0x1F,0x00,0x11},/*"4",19*/
+{0x7E,0x08,0x7E,0x18,0x42,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xC2,0x1F,0x82,0x0F},/*"5",20*/
+{0xF8,0x0F,0xFC,0x1F,0x46,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xC0,0x1F,0x80,0x0F},/*"6",21*/
+{0x06,0x00,0x06,0x00,0x02,0x00,0x02,0x1F,0xC2,0x1F,0xF2,0x00,0x3E,0x00,0x0E,0x00},/*"7",22*/
+{0xBC,0x0F,0xFE,0x1F,0x42,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xFE,0x1F,0xBC,0x0F},/*"8",23*/
+{0x3C,0x00,0x7E,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0x42,0x18,0xFE,0x0F,0xFC,0x07},/*"9",24*/
+{0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x0C,0x30,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
+{0x00,0x00,0x00,0x00,0x00,0x20,0x60,0x3C,0x60,0x1C,0x00,0x00,0x00,0x00,0x00,0x00},/*";",27*/
+{0x80,0x00,0xC0,0x01,0x60,0x03,0x30,0x06,0x18,0x0C,0x0C,0x18,0x04,0x10,0x00,0x00},/*"<",28*/
+{0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x02},/*"=",29*/
+{0x04,0x10,0x0C,0x18,0x18,0x0C,0x30,0x06,0x60,0x03,0xC0,0x01,0x80,0x00,0x00,0x00},/*">",30*/
+{0x04,0x00,0x06,0x00,0x02,0x00,0x82,0x1B,0xC2,0x1B,0x62,0x00,0x3E,0x00,0x1C,0x00},/*"?",31*/
+{0xFC,0x0F,0xFE,0x1F,0x02,0x10,0x82,0x11,0xC2,0x13,0xE2,0x13,0xFE,0x13,0xFC,0x03},/*"@",32*/
+{0xF0,0x1F,0xF8,0x1F,0x0C,0x01,0x06,0x01,0x06,0x01,0x0C,0x01,0xF8,0x1F,0xF0,0x1F},/*"A",33*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x42,0x10,0x42,0x10,0x42,0x10,0xFE,0x1F,0xBC,0x0F},/*"B",34*/
+{0xF8,0x07,0xFC,0x0F,0x06,0x18,0x02,0x10,0x02,0x10,0x02,0x10,0x06,0x18,0x0C,0x0C},/*"C",35*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x02,0x10,0x02,0x10,0x06,0x18,0xFC,0x0F,0xF8,0x07},/*"D",36*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x42,0x10,0x42,0x10,0xE2,0x10,0x06,0x18,0x06,0x18},/*"E",37*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x42,0x10,0x42,0x00,0xE2,0x00,0x06,0x00,0x06,0x00},/*"F",38*/
+{0xF8,0x07,0xFC,0x0F,0x06,0x18,0x02,0x10,0x82,0x10,0x82,0x10,0x86,0x0F,0x8C,0x1F},/*"G",39*/
+{0xFE,0x1F,0xFE,0x1F,0x40,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0xFE,0x1F,0xFE,0x1F},/*"H",40*/
+{0x00,0x00,0x02,0x10,0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x02,0x10,0x02,0x10,0x00,0x00},/*"I",41*/
+{0x00,0x0C,0x00,0x1C,0x00,0x10,0x00,0x10,0x02,0x10,0xFE,0x1F,0xFE,0x0F,0x02,0x00},/*"J",42*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0xE0,0x00,0xB0,0x01,0x18,0x03,0x0E,0x1E,0x06,0x1C},/*"K",43*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x02,0x10,0x00,0x10,0x00,0x10,0x00,0x18,0x00,0x18},/*"L",44*/
+{0xFE,0x1F,0xFE,0x1F,0x18,0x00,0xF0,0x00,0xF0,0x00,0x18,0x00,0xFE,0x1F,0xFE,0x1F},/*"M",45*/
+{0xFE,0x1F,0xFE,0x1F,0x38,0x00,0x70,0x00,0xE0,0x00,0xC0,0x01,0xFE,0x1F,0xFE,0x1F},/*"N",46*/
+{0xFC,0x0F,0xFE,0x1F,0x02,0x10,0x02,0x10,0x02,0x10,0x02,0x10,0xFE,0x1F,0xFC,0x0F},/*"O",47*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x42,0x10,0x42,0x00,0x42,0x00,0x7E,0x00,0x3C,0x00},/*"P",48*/
+{0xFC,0x0F,0xFE,0x1F,0x02,0x10,0x02,0x1C,0x02,0x38,0x02,0x70,0xFE,0x5F,0xFC,0x0F},/*"Q",49*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x42,0x00,0x42,0x00,0xC2,0x00,0xFE,0x1F,0x3C,0x1F},/*"R",50*/
+{0x1C,0x0C,0x3E,0x1C,0x62,0x10,0x42,0x10,0x42,0x10,0xC2,0x10,0x8E,0x1F,0x0C,0x0F},/*"S",51*/
+{0x06,0x00,0x06,0x00,0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x02,0x10,0x06,0x00,0x06,0x00},/*"T",52*/
+{0xFE,0x0F,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0xFE,0x1F,0xFE,0x0F},/*"U",53*/
+{0xFE,0x03,0xFE,0x07,0x00,0x0C,0x00,0x18,0x00,0x18,0x00,0x0C,0xFE,0x07,0xFE,0x03},/*"V",54*/
+{0xFE,0x07,0xFE,0x1F,0x00,0x1C,0xC0,0x07,0xC0,0x07,0x00,0x1C,0xFE,0x1F,0xFE,0x07},/*"W",55*/
+{0x0E,0x1C,0x1E,0x1E,0x30,0x03,0xE0,0x01,0xE0,0x01,0x30,0x03,0x1E,0x1E,0x0E,0x1C},/*"X",56*/
+{0x1E,0x00,0x3E,0x00,0x60,0x10,0xC0,0x1F,0xC0,0x1F,0x60,0x10,0x3E,0x00,0x1E,0x00},/*"Y",57*/
+{0x06,0x1E,0x06,0x1F,0x82,0x11,0xC2,0x10,0x62,0x10,0x32,0x10,0x1E,0x18,0x0E,0x18},/*"Z",58*/
+{0x00,0x00,0x00,0x00,0xFE,0x1F,0xFE,0x1F,0x02,0x10,0x02,0x10,0x00,0x00,0x00,0x00},/*"[",59*/
+{0x00,0x18,0x00,0x1E,0x80,0x07,0xE0,0x01,0x78,0x00,0x1E,0x00,0x06,0x00,0x00,0x00},/*"/",60*/
+{0x00,0x00,0x00,0x00,0x02,0x10,0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x00,0x00,0x00,0x00},/*"]",61*/
+{0x20,0x00,0x30,0x00,0x18,0x00,0x0C,0x00,0x18,0x00,0x30,0x00,0x20,0x00,0x00,0x00},/*"^",62*/
+{0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80},/*"_",63*/
+{0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
+{0x00,0x0E,0x20,0x1F,0x20,0x11,0x20,0x11,0x20,0x11,0xE0,0x0F,0xC0,0x1F,0x00,0x10},/*"a",65*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x0F,0x20,0x10,0x20,0x10,0x60,0x10,0xC0,0x1F,0x80,0x0F},/*"b",66*/
+{0xC0,0x0F,0xE0,0x1F,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x18,0x40,0x08},/*"c",67*/
+{0x80,0x0F,0xC0,0x1F,0x60,0x10,0x20,0x10,0x22,0x10,0xFE,0x0F,0xFE,0x1F,0x00,0x10},/*"d",68*/
+{0xC0,0x0F,0xE0,0x1F,0x20,0x11,0x20,0x11,0x20,0x11,0x20,0x11,0xE0,0x19,0xC0,0x09},/*"e",69*/
+{0x00,0x00,0x20,0x10,0xFC,0x1F,0xFE,0x1F,0x22,0x10,0x22,0x00,0x06,0x00,0x04,0x00},/*"f",70*/
+{0xC0,0x4F,0xE0,0xDF,0x20,0x90,0x20,0x90,0x20,0x90,0xC0,0xFF,0xE0,0x7F,0x20,0x00},/*"g",71*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x40,0x00,0x20,0x00,0x20,0x00,0xE0,0x1F,0xC0,0x1F},/*"h",72*/
+{0x00,0x00,0x20,0x10,0x20,0x10,0xEC,0x1F,0xEC,0x1F,0x00,0x10,0x00,0x10,0x00,0x00},/*"i",73*/
+{0x00,0x60,0x00,0xC0,0x20,0x80,0x20,0x80,0xEC,0xFF,0xEC,0x7F,0x00,0x00,0x00,0x00},/*"j",74*/
+{0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x80,0x01,0x80,0x03,0xC0,0x06,0x60,0x1C,0x20,0x18},/*"k",75*/
+{0x00,0x00,0x02,0x10,0x02,0x10,0xFE,0x1F,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x00},/*"l",76*/
+{0xE0,0x1F,0xE0,0x1F,0x60,0x00,0xC0,0x0F,0xC0,0x0F,0x60,0x00,0xE0,0x1F,0xC0,0x1F},/*"m",77*/
+{0x20,0x00,0xE0,0x1F,0xC0,0x1F,0x20,0x00,0x20,0x00,0x20,0x00,0xE0,0x1F,0xC0,0x1F},/*"n",78*/
+{0xC0,0x0F,0xE0,0x1F,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xE0,0x1F,0xC0,0x0F},/*"o",79*/
+{0x20,0x80,0xE0,0xFF,0xC0,0xFF,0x20,0x90,0x20,0x10,0x20,0x10,0xE0,0x1F,0xC0,0x0F},/*"p",80*/
+{0xC0,0x0F,0xE0,0x1F,0x20,0x10,0x20,0x10,0x20,0x90,0xC0,0xFF,0xE0,0xFF,0x20,0x80},/*"q",81*/
+{0x20,0x10,0xE0,0x1F,0xC0,0x1F,0x60,0x10,0x20,0x00,0x20,0x00,0x60,0x00,0x40,0x00},/*"r",82*/
+{0xC0,0x08,0xE0,0x19,0x20,0x11,0x20,0x11,0x20,0x13,0x20,0x12,0x60,0x1E,0x40,0x0C},/*"s",83*/
+{0x20,0x00,0x20,0x00,0xFC,0x0F,0xFE,0x1F,0x20,0x10,0x20,0x18,0x00,0x08,0x00,0x00},/*"t",84*/
+{0xE0,0x0F,0xE0,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0xE0,0x0F,0xE0,0x1F,0x00,0x10},/*"u",85*/
+{0xE0,0x03,0xE0,0x07,0x00,0x0C,0x00,0x18,0x00,0x18,0x00,0x0C,0xE0,0x07,0xE0,0x03},/*"v",86*/
+{0xE0,0x0F,0xE0,0x1F,0x00,0x18,0x00,0x0F,0x00,0x0F,0x00,0x18,0xE0,0x1F,0xE0,0x0F},/*"w",87*/
+{0x20,0x10,0x60,0x18,0xC0,0x0C,0x80,0x07,0x80,0x07,0xC0,0x0C,0x60,0x18,0x20,0x10},/*"x",88*/
+{0xE0,0x8F,0xE0,0x9F,0x00,0x90,0x00,0x90,0x00,0x90,0x00,0xD0,0xE0,0x7F,0xE0,0x3F},/*"y",89*/
+{0x60,0x18,0x60,0x1C,0x20,0x16,0x20,0x13,0xA0,0x11,0xE0,0x10,0x60,0x18,0x20,0x18},/*"z",90*/
+{0x00,0x00,0x00,0x00,0x80,0x00,0xFC,0x1F,0x7E,0x3F,0x02,0x20,0x02,0x20,0x00,0x00},/*"{",91*/
+{0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x3E,0x7C,0x3E,0x00,0x00,0x00,0x00,0x00,0x00},/*"|",92*/
+{0x00,0x00,0x02,0x20,0x02,0x20,0x7E,0x3F,0xFC,0x1F,0x80,0x00,0x00,0x00,0x00,0x00},/*"}",93*/
+};
+#endif
+
+void LCD_Common::printInt(uint16_t value, int8_t padding)
+{
+    uint16_t den = 10000;
+    for (int8_t i = 5; i > 0; i--) {
+        byte v = (byte)(value / den);
+        value -= v * den;
+        den /= 10;
+        if (v == 0 && padding && den) {
+            if (padding >= i) {
+                writeDigit((m_flags & FLAG_PAD_ZERO) ? 0 : -1);
+            }
+            continue;
+        }
+        padding = 0;
+        writeDigit(v);
+    }
+}
+
+void LCD_Common::printLong(uint32_t value, int8_t padding)
+{
+    uint32_t den = 1000000000;
+    for (int8_t i = 10; i > 0; i--) {
+        byte v = (byte)(value / den);
+        value -= v * den;
+        den /= 10;
+        if (v == 0 && padding && den) {
+            if (padding >= i) {
+                writeDigit((m_flags & FLAG_PAD_ZERO) ? 0 : -1);
+            }
+            continue;
+        }
+        padding = 0;
+        writeDigit(v);
+    }
+}
+
+void LCD_SSD1306::setCursor(byte column, byte line)
+{
+    m_col = column;
+    m_row = line;
+    ssd1306_command(0xB0 + m_row);//set page address
+    ssd1306_command(m_col & 0xf);//set lower column address
+    ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+}
+
+size_t LCD_SSD1306::write(uint8_t c)
+{
+    if (c == '\n') {
+        setCursor(0, m_row + ((m_font == FONT_SIZE_SMALL) ? 1 : 2));
+        return 1;
+    } else if (c == '\r') {
+        m_col = 0;
+        return 1;
+    }
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+#ifndef MEMORY_SAVING
+    if (m_font == FONT_SIZE_SMALL) {
+#endif
+        Wire.beginTransmission(_i2caddr);
+        Wire.write(0x40);
+        if (c > 0x20 && c < 0x7f) {
+            c -= 0x21;
+            for (byte i = 0; i < 5; i++) {
+                byte d = pgm_read_byte(&font5x8[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.write(0);
+        } else {
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 11 : 6; i > 0; i--) {
+                Wire.write(0);
+            }
+        }
+        Wire.endTransmission();
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 11 : 6;
+        if (m_col >= 128) {
+            m_col = 0;
+            m_row ++;
+        }
+#ifndef MEMORY_SAVING
+    } else {
+        if (c > 0x20 && c < 0x7f) {
+            c -= 0x21;
+
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = 0; i <= 14; i += 2) {
+                byte d = pgm_read_byte(&font8x16_terminal[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = 1; i <= 15; i += 2) {
+                byte d = pgm_read_byte(&font8x16_terminal[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 16 : 8; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 16 : 8; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 17 : 9;
+        if (m_col >= 128) {
+            m_col = 0;
+            m_row += 2;
+        }
+    }
+#endif
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+    return 1;
+}
+
+void LCD_SSD1306::writeDigit(byte n)
+{
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    if (m_font == FONT_SIZE_SMALL) {
+        Wire.beginTransmission(_i2caddr);
+        Wire.write(0x40);
+        if (n <= 9) {
+            n += '0' - 0x21;
+            for (byte i = 0; i < 5; i++) {
+                Wire.write(pgm_read_byte(&font5x8[n][i]));
+            }
+            Wire.write(0);
+        } else {
+            for (byte i = 0; i < 6; i++) {
+                Wire.write(0);
+            }
+        }
+        Wire.endTransmission();
+        m_col += 6;
+    } else if (m_font == FONT_SIZE_MEDIUM) {
+        write(n <= 9 ? ('0' + n) : ' ');
+#ifndef MEMORY_SAVING
+    } else if (m_font == FONT_SIZE_LARGE) {
+        if (n <= 9) {
+            byte i;
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x16[n][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (; i < 32; i ++) {
+                byte d = pgm_read_byte(&digits16x16[n][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 30 : 16;
+#endif
+    } else {
+        if (n <= 9) {
+            byte i;
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3 + 1]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 2);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3 + 2]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            ssd1306_command(0xB0 + m_row);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 1);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            ssd1306_command(0xB0 + m_row + 2);//set page address
+            ssd1306_command(m_col & 0xf);//set lower column address
+            ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 30 : 16;
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+void LCD_SSD1306::draw(const PROGMEM byte* buffer, byte width, byte height)
+{
+    ssd1306_command(SSD1306_SETLOWCOLUMN | 0x0);  // low col = 0
+    ssd1306_command(SSD1306_SETHIGHCOLUMN | 0x0);  // hi col = 0
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
+
+    const PROGMEM byte *p = buffer;
+    height >>= 3;
+    width >>= 3;
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    for (byte i = 0; i < height; i++) {
+      // send a bunch of data in one xmission
+        ssd1306_command(0xB0 + i + m_row);//set page address
+        ssd1306_command(m_col & 0xf);//set lower column address
+        ssd1306_command(0x10 | (m_col >> 4));//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte k = 0; k < width; k++, p++) {
+                Wire.write(pgm_read_byte(p));
+            }
+            Wire.endTransmission();
+        }
+    }
+    m_col += width;
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+void LCD_SSD1306::clearLine(byte line)
+{
+    ssd1306_command(SSD1306_SETLOWCOLUMN | 0x0);  // low col = 0
+    ssd1306_command(SSD1306_SETHIGHCOLUMN | 0x0);  // hi col = 0
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
+
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+
+    // send a bunch of data in one xmission
+    ssd1306_command(0xB0 + line);//set page address
+    ssd1306_command(0);//set lower column address
+    ssd1306_command(0x10);//set higher column address
+
+    for(byte j = 0; j < 8; j++){
+        Wire.beginTransmission(_i2caddr);
+        Wire.write(0x40);
+        for (byte k = 0; k < 16; k++) {
+            Wire.write(0);
+        }
+        Wire.endTransmission();
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+void LCD_SSD1306::clear(byte x, byte y, byte width, byte height)
+{
+    ssd1306_command(SSD1306_SETLOWCOLUMN | 0x0);  // low col = 0
+    ssd1306_command(SSD1306_SETHIGHCOLUMN | 0x0);  // hi col = 0
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
+
+    height >>= 3;
+    width >>= 3;
+    y >>= 3;
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    for (byte i = 0; i < height; i++) {
+      // send a bunch of data in one xmission
+        ssd1306_command(0xB0 + i + y);//set page address
+        ssd1306_command(x & 0xf);//set lower column address
+        ssd1306_command(0x10 | (x >> 4));//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte k = 0; k < width; k++) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+    setCursor(0, 0);
+}
+
+void LCD_SSD1306::setContrast(byte Contrast)
+{
+    ssd1306_command(SSD1306_SETCONTRAST);
+	ssd1306_command(Contrast);
+}
diff --git a/nanotimer/MicroLCD.h b/nanotimer/MicroLCD.h
new file mode 100644
index 0000000..51781ab
--- /dev/null
+++ b/nanotimer/MicroLCD.h
@@ -0,0 +1,119 @@
+/*************************************************************************
+* Arduino Text & Bitmap Display Library for multiple models of monochrome LCD display
+* Distributed under GPL v2.0
+* Copyright (c) 2013-2014 Stanley Huang <stanleyhuangyc@gmail.com>
+* All rights reserved.
+* For more information, please visit http://arduinodev.com
+*************************************************************************/
+
+#include <Arduino.h>
+
+//#define MEMORY_SAVING
+
+typedef enum {
+    FONT_SIZE_SMALL = 0,
+    FONT_SIZE_MEDIUM,
+    FONT_SIZE_LARGE,
+    FONT_SIZE_XLARGE
+} FONT_SIZE;
+
+#define FLAG_PAD_ZERO 1
+#define FLAG_PIXEL_DOUBLE_H 2
+#define FLAG_PIXEL_DOUBLE_V 4
+#define FLAG_PIXEL_DOUBLE (FLAG_PIXEL_DOUBLE_H | FLAG_PIXEL_DOUBLE_V)
+
+extern const PROGMEM unsigned char font5x8[][5];
+extern const PROGMEM unsigned char digits8x8[][8] ;
+extern const PROGMEM unsigned char digits16x16[][32];
+extern const PROGMEM unsigned char digits16x24[][48];
+extern const PROGMEM unsigned char font8x16_doslike[][16];
+extern const PROGMEM unsigned char font8x16_terminal[][16];
+
+class LCD_Common
+{
+public:
+    LCD_Common():m_font(FONT_SIZE_SMALL),m_flags(0) {}
+    void setFontSize(FONT_SIZE size) { m_font = size; }
+    void setFlags(byte flags) { m_flags = flags; }
+    virtual void backlight(bool on) {}
+    virtual void draw(const PROGMEM byte* buffer, byte width, byte height) {}
+    void printInt(uint16_t value, int8_t padding = -1);
+    void printLong(uint32_t value, int8_t padding = -1);
+protected:
+    virtual void writeDigit(byte n) {}
+    byte m_font;
+    byte m_flags;
+};
+
+class LCD_Null : public LCD_Common, public Print
+{
+public:
+    byte getLines() { return 0; }
+    byte getCols() { return 0; }
+    void clearLine(byte line) {}
+    void clear() {}
+    void begin() {}
+    void setCursor(byte column, byte line) {}
+    size_t write(uint8_t c) { return 0; }
+};
+
+#include "SSD1306.h"
+
+class LCD_SSD1306 : public LCD_Common, public SSD1306, public Print
+{
+public:
+    void setCursor(byte column, byte line);
+	void setContrast(byte Contrast);
+    void draw(const PROGMEM byte* buffer, byte width, byte height);
+    size_t write(uint8_t c);
+    void clear(byte x = 0, byte y = 0, byte width = 128, byte height = 64);
+    void clearLine(byte line);
+    byte getLines() { return 21; }
+    byte getCols() { return 8; }
+private:
+    void writeDigit(byte n);
+    byte m_col;
+    byte m_row;
+};
+
+class LCD_SH1106 : public LCD_Common, public Print
+{
+public:
+    void begin();
+    void setCursor(byte column, byte line);
+    void draw(const PROGMEM byte* buffer, byte width, byte height);
+    size_t write(uint8_t c);
+    void clear(byte x = 0, byte y = 0, byte width = 128, byte height = 64);
+    void clearLine(byte line);
+    byte getLines() { return 21; }
+    byte getCols() { return 8; }
+private:
+    void WriteCommand(unsigned char ins);
+    void WriteData(unsigned char dat);
+    void writeDigit(byte n);
+    byte m_col;
+    byte m_row;
+};
+
+#include "PCD8544.h"
+
+class LCD_PCD8544 : public LCD_Common, public PCD8544
+{
+public:
+    byte getLines() { return 6; }
+    byte getCols() { return 14; }
+    void backlight(bool on)
+    {
+        pinMode(7, OUTPUT);
+        digitalWrite(7, on ? HIGH : LOW);
+    }
+    void clearLine(byte line)
+    {
+        setCursor(0, line);
+        for (byte i = 14; i > 0; i--) write(' ');
+    }
+    void draw(const PROGMEM byte* buffer, byte width, byte height);
+private:
+    void writeDigit(byte n);
+};
+
diff --git a/nanotimer/PCD8544.cpp b/nanotimer/PCD8544.cpp
new file mode 100644
index 0000000..6b339fe
--- /dev/null
+++ b/nanotimer/PCD8544.cpp
@@ -0,0 +1,316 @@
+/*
+ * PCD8544 - Interface with Philips PCD8544 (or compatible) LCDs.
+ *
+ * Copyright (c) 2010 Carlos Rodrigues <cefrodrigues@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#include "PCD8544.h"
+
+#include <Arduino.h>
+#include <avr/pgmspace.h>
+
+extern const PROGMEM unsigned char font5x8[][5];
+
+/*
+ * If this was a ".h", it would get added to sketches when using
+ * the "Sketch -> Import Library..." menu on the Arduino IDE...
+ */
+
+PCD8544::PCD8544(unsigned char sclk, unsigned char sdin,
+                 unsigned char dc, unsigned char reset,
+                 unsigned char sce):
+    pin_sclk(sclk),
+    pin_sdin(sdin),
+    pin_dc(dc),
+    pin_reset(reset),
+    pin_sce(sce)
+{}
+
+
+void PCD8544::begin(unsigned char model)
+{
+    this->column = 0;
+    this->line = 0;
+
+    // Sanitize the custom glyphs...
+    memset(this->custom, 0, sizeof(this->custom));
+
+    // All pins are outputs (these displays cannot be read)...
+    pinMode(this->pin_sclk, OUTPUT);
+    pinMode(this->pin_sdin, OUTPUT);
+    pinMode(this->pin_dc, OUTPUT);
+    pinMode(this->pin_reset, OUTPUT);
+    pinMode(this->pin_sce, OUTPUT);
+
+    // Reset the controller state...
+    digitalWrite(this->pin_reset, HIGH);
+    digitalWrite(this->pin_sce, HIGH);
+    digitalWrite(this->pin_reset, LOW);
+    delay(100);
+    digitalWrite(this->pin_reset, HIGH);
+
+    // Set the LCD parameters...
+    this->send(PCD8544_CMD, 0x21);  // extended instruction set control (H=1)
+    this->send(PCD8544_CMD, 0x13);  // bias system (1:48)
+
+    if (model == CHIP_ST7576) {
+        this->send(PCD8544_CMD, 0xe0);  // higher Vop, too faint at default
+        this->send(PCD8544_CMD, 0x05);  // partial display mode
+    } else {
+        this->send(PCD8544_CMD, 0xc2);  // default Vop (3.06 + 66 * 0.06 = 7V)
+    }
+
+    this->send(PCD8544_CMD, 0x20);  // extended instruction set control (H=0)
+    this->send(PCD8544_CMD, 0x09);  // all display segments on
+
+    // Clear RAM contents...
+    this->clear();
+
+    // Activate LCD...
+    this->send(PCD8544_CMD, 0x08);  // display blank
+    this->send(PCD8544_CMD, 0x0c);  // normal mode (0x0d = inverse mode)
+    delay(100);
+
+    // Place the cursor at the origin...
+    this->send(PCD8544_CMD, 0x80);
+    this->send(PCD8544_CMD, 0x40);
+}
+
+
+void PCD8544::stop()
+{
+    this->clear();
+    this->setPower(false);
+}
+
+
+void PCD8544::clear()
+{
+    this->setCursor(0, 0);
+
+    for (unsigned short i = 0; i < PCD8544_WIDTH * (PCD8544_HEIGHT/8); i++) {
+        this->send(PCD8544_DATA, 0x00);
+    }
+
+    this->setCursor(0, 0);
+}
+
+
+void PCD8544::clearLine()
+{
+    this->setCursor(0, this->line);
+
+    for (unsigned char i = 0; i < PCD8544_WIDTH; i++) {
+        this->send(PCD8544_DATA, 0x00);
+    }
+
+    this->setCursor(0, this->line);
+}
+
+
+void PCD8544::setPower(bool on)
+{
+    this->send(PCD8544_CMD, on ? 0x20 : 0x24);
+}
+
+
+inline void PCD8544::display()
+{
+    this->setPower(true);
+}
+
+
+inline void PCD8544::noDisplay()
+{
+    this->setPower(false);
+}
+
+
+void PCD8544::setInverse(bool inverse)
+{
+    this->send(PCD8544_CMD, inverse ? 0x0d : 0x0c);
+}
+
+
+void PCD8544::home()
+{
+    this->setCursor(0, this->line);
+}
+
+
+void PCD8544::setCursor(unsigned char column, unsigned char line)
+{
+    if (column > PCD8544_WIDTH) {
+        column = 0;
+        line++;
+    }
+    if (line > PCD8544_HEIGHT / 8)
+        line = 0;
+
+    this->column = column;
+    this->line = line;
+
+    this->send(PCD8544_CMD, 0x80 | column);
+    this->send(PCD8544_CMD, 0x40 | line);
+}
+
+
+void PCD8544::createChar(unsigned char chr, const unsigned char *glyph)
+{
+    // ASCII 0-31 only...
+    if (chr >= ' ') {
+        return;
+    }
+
+    this->custom[chr] = glyph;
+}
+
+
+size_t PCD8544::write(uint8_t chr)
+{
+    // ASCII 7-bit only...
+    if (chr >= 0x7f) {
+        return 0;
+    }
+
+    if (chr == '\n') {
+        column = 0;
+        line = (line + 1) % (PCD8544_HEIGHT/9 + 1);
+        return 0;
+    } else if (chr == '\r') {
+        column = 0;
+        return 0;
+    }
+
+    const unsigned char *glyph;
+    unsigned char pgm_buffer[5];
+
+    if (chr >= ' ') {
+        // Regular ASCII characters are kept in flash to save RAM...
+        memcpy_P(pgm_buffer, &font5x8[chr - ' '], sizeof(pgm_buffer));
+        glyph = pgm_buffer;
+    } else {
+        // Custom glyphs, on the other hand, are stored in RAM...
+        if (custom[chr]) {
+            glyph = custom[chr];
+        } else {
+            // Default to a space character if unset...
+            memcpy_P(pgm_buffer, &font5x8[0], sizeof(pgm_buffer));
+            glyph = pgm_buffer;
+        }
+    }
+
+    // Output one column at a time...
+    for (unsigned char i = 0; i < 5; i++) {
+        this->send(PCD8544_DATA, glyph[i]);
+    }
+
+    // One column between characters...
+    this->send(PCD8544_DATA, 0x00);
+
+    // Update the cursor position...
+    this->column = (this->column + 6) % PCD8544_WIDTH;
+
+    if (this->column == 0) {
+        this->line = (this->line + 1) % (PCD8544_HEIGHT/9 + 1);
+    }
+
+#if ARDUINO >= 100
+    return 1;
+#endif
+}
+
+void PCD8544::draw8x8(const unsigned char *data)
+{
+    // Output one column at a time...
+    for (unsigned char i = 0; i < 8; i++) {
+        this->send(PCD8544_DATA, data[i]);
+    }
+    this->setCursor(column + 8, line);
+}
+
+void PCD8544::draw16x16(const unsigned char *data)
+{
+    unsigned char scolumn = this->column;
+    unsigned char sline = this->line;
+    // Output one column at a time...
+    for (unsigned char i = 0; i < 16; i++) {
+        this->send(PCD8544_DATA, data[i]);
+    }
+    this->setCursor(scolumn, sline + 1);
+    for (unsigned char i = 0; i < 16; i++) {
+        this->send(PCD8544_DATA, data[i + 16]);
+    }
+    // Update the cursor position...
+    this->setCursor(scolumn + 16, sline);
+}
+
+void PCD8544::drawColumn(unsigned char lines, unsigned char value)
+{
+    unsigned char scolumn = this->column;
+    unsigned char sline = this->line;
+
+    // Keep "value" within range...
+    if (value > lines*8) {
+        value = lines*8;
+    }
+
+    // Find the line where "value" resides...
+    unsigned char mark = (lines*8 - 1 - value)/8;
+
+    // Clear the lines above the mark...
+    for (unsigned char line = 0; line < mark; line++) {
+        this->setCursor(scolumn, sline + line);
+        this->send(PCD8544_DATA, 0x00);
+    }
+
+    // Compute the byte to draw at the "mark" line...
+    unsigned char b = 0xff;
+    for (unsigned char i = 0; i < lines*8 - mark*8 - value; i++) {
+        b <<= 1;
+    }
+
+    this->setCursor(scolumn, sline + mark);
+    this->send(PCD8544_DATA, b);
+
+    // Fill the lines below the mark...
+    for (unsigned char line = mark + 1; line < lines; line++) {
+        this->setCursor(scolumn, sline + line);
+        this->send(PCD8544_DATA, 0xff);
+    }
+
+    // Leave the cursor in a consistent position...
+    this->setCursor(scolumn + 1, sline);
+}
+
+
+void PCD8544::send(unsigned char type, unsigned char data)
+{
+    digitalWrite(this->pin_dc, type);
+
+    digitalWrite(this->pin_sce, LOW);
+    shiftOut(this->pin_sdin, this->pin_sclk, MSBFIRST, data);
+    digitalWrite(this->pin_sce, HIGH);
+}
+
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/nanotimer/PCD8544.h b/nanotimer/PCD8544.h
new file mode 100644
index 0000000..c96bc7e
--- /dev/null
+++ b/nanotimer/PCD8544.h
@@ -0,0 +1,117 @@
+/*
+ * PCD8544 - Interface with Philips PCD8544 (or compatible) LCDs.
+ *
+ * Copyright (c) 2010 Carlos Rodrigues <cefrodrigues@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#ifndef PCD8544_H
+#define PCD8544_H
+
+
+#if ARDUINO < 100
+#include <WProgram.h>
+#else
+#include <Arduino.h>
+#endif
+
+// Chip variants supported...
+#define CHIP_PCD8544 0
+#define CHIP_ST7576  1
+
+#define PCD8544_WIDTH 84
+#define PCD8544_HEIGHT 48
+
+#define PCD8544_CMD  LOW
+#define PCD8544_DATA HIGH
+
+class PCD8544: public Print {
+    public:
+        // All the pins can be changed from the default values...
+        PCD8544(unsigned char sclk  = 2,   /* clock       (display pin 2) */
+                unsigned char sdin  = 3,   /* data-in     (display pin 3) */
+                unsigned char dc    = 4,   /* data select (display pin 4) */
+                unsigned char reset = 6,   /* reset       (display pin 8) */
+                unsigned char sce   = 5);  /* enable      (display pin 5) */
+
+        // Display initialization (dimensions in pixels)...
+        void begin(unsigned char model=CHIP_PCD8544);
+        void stop();
+
+        // Erase everything on the display...
+        void clear();
+        void clearLine();  // ...or just the current line
+
+        // Control the display's power state...
+        void setPower(bool on);
+
+        // For compatibility with the LiquidCrystal library...
+        void display();
+        void noDisplay();
+
+        // Activate white-on-black mode (whole display)...
+        void setInverse(bool inverse);
+
+        // Place the cursor at the start of the current line...
+        void home();
+
+        // Place the cursor at position (column, line)...
+        void setCursor(unsigned char column, unsigned char line);
+
+        // Assign a user-defined glyph (5x8) to an ASCII character (0-31)...
+        void createChar(unsigned char chr, const unsigned char *glyph);
+
+        // Write an ASCII character at the current cursor position (7-bit)...
+#if ARDUINO < 100
+        virtual void write(uint8_t chr);
+#else
+        virtual size_t write(uint8_t chr);
+#endif
+
+        // Draw a chart element at the current cursor position...
+        void drawColumn(unsigned char lines, unsigned char value);
+
+        void draw8x8(const unsigned char *data);
+        void draw16x16(const unsigned char *data);
+
+    protected:
+        // Current cursor position...
+        unsigned char column;
+        unsigned char line;
+        // Send a command or data to the display...
+        void send(unsigned char type, unsigned char data);
+
+    private:
+        unsigned char pin_sclk;
+        unsigned char pin_sdin;
+        unsigned char pin_dc;
+        unsigned char pin_reset;
+        unsigned char pin_sce;
+
+        // User-defined glyphs (below the ASCII space character)...
+        const unsigned char *custom[' '];
+};
+
+
+#endif  /* PCD8544_H */
+
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/nanotimer/SH1106.cpp b/nanotimer/SH1106.cpp
new file mode 100644
index 0000000..79e9873
--- /dev/null
+++ b/nanotimer/SH1106.cpp
@@ -0,0 +1,429 @@
+#include <Arduino.h>
+#include <Wire.h>
+#include "MicroLCD.h"
+
+#define I2C_ADDR 0x78 >> 1
+
+void LCD_SH1106::WriteCommand(unsigned char ins)
+{
+  Wire.beginTransmission(I2C_ADDR);//0x78 >> 1
+  Wire.write(0x00);//0x00
+  Wire.write(ins);
+  Wire.endTransmission();
+}
+
+void LCD_SH1106::WriteData(unsigned char dat)
+{
+  Wire.beginTransmission(I2C_ADDR);//0x78 >> 1
+  Wire.write(0x40);//0x40
+  Wire.write(dat);
+  Wire.endTransmission();
+}
+
+void LCD_SH1106::setCursor(unsigned char x, unsigned char y)
+{
+    m_col = x + 2;
+    m_row = y;
+    WriteCommand(0xb0 + m_row);
+    WriteCommand(m_col & 0xf);//set lower column address
+    WriteCommand(0x10 | (m_col >> 4));//set higher column address
+}
+
+void LCD_SH1106::clear(byte x, byte y, byte width, byte height)
+{
+    height >>= 3;
+    width >>= 3;
+    y >>= 3;
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    for (byte i = 0; i < height; i++) {
+      // send a bunch of data in one xmission
+        WriteCommand(0xB0 + i + y);//set page address
+        WriteCommand((x + 2) & 0xf);//set lower column address
+        WriteCommand(0x10 | (x >> 4));//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte k = 0; k < width; k++) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+    setCursor(0, 0);
+}
+
+void LCD_SH1106::clearLine(byte line)
+{
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+
+    WriteCommand(0xB0 + line);//set page address
+    WriteCommand(0);//set lower column address
+    WriteCommand(0x10);//set higher column address
+
+    for(byte j = 0; j < 8; j++){
+        Wire.beginTransmission(I2C_ADDR);
+        Wire.write(0x40);
+        for (byte k = 0; k < 128 / 8; k++) {
+            Wire.write(0);
+        }
+        Wire.endTransmission();
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+size_t LCD_SH1106::write(uint8_t c)
+{
+    if (c == '\n') {
+        setCursor(0, m_row + ((m_font == FONT_SIZE_SMALL) ? 1 : 2));
+        return 1;
+    } else if (c == '\r') {
+        m_col = 0;
+        return 1;
+    }
+
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+#ifndef MEMORY_SAVING
+    if (m_font == FONT_SIZE_SMALL) {
+#endif
+        Wire.beginTransmission(I2C_ADDR);
+        Wire.write(0x40);
+        if (c > 0x20 && c < 0x7f) {
+            c -= 0x21;
+            for (byte i = 0; i < 5; i++) {
+                byte d = pgm_read_byte(&font5x8[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.write(0);
+        } else {
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 11 : 6; i > 0; i--) {
+                Wire.write(0);
+            }
+        }
+        Wire.endTransmission();
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 11 : 6;
+        if (m_col >= 128) {
+            m_col = 0;
+            m_row ++;
+        }
+#ifndef MEMORY_SAVING
+    } else {
+        if (c > 0x20 && c < 0x7f) {
+            c -= 0x21;
+
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = 0; i <= 14; i += 2) {
+                byte d = pgm_read_byte(&font8x16_terminal[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = 1; i <= 15; i += 2) {
+                byte d = pgm_read_byte(&font8x16_terminal[c][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 16 : 8; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 16 : 8; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 17 : 9;
+        if (m_col >= 128) {
+            m_col = 0;
+            m_row += 2;
+        }
+    }
+#endif
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+    return 1;
+}
+
+void LCD_SH1106::writeDigit(byte n)
+{
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    if (m_font == FONT_SIZE_SMALL) {
+        Wire.beginTransmission(I2C_ADDR);
+        Wire.write(0x40);
+        if (n <= 9) {
+            n += '0' - 0x21;
+            for (byte i = 0; i < 5; i++) {
+                Wire.write(pgm_read_byte(&font5x8[n][i]));
+            }
+            Wire.write(0);
+        } else {
+            for (byte i = 0; i < 6; i++) {
+                Wire.write(0);
+            }
+        }
+        Wire.endTransmission();
+        m_col += 6;
+    } else if (m_font == FONT_SIZE_MEDIUM) {
+        write(n <= 9 ? ('0' + n) : ' ');
+#ifndef MEMORY_SAVING
+    } else if (m_font == FONT_SIZE_LARGE) {
+        if (n <= 9) {
+            byte i;
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x16[n][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (; i < 32; i ++) {
+                byte d = pgm_read_byte(&digits16x16[n][i]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 30 : 16;
+#endif
+    } else {
+        if (n <= 9) {
+            byte i;
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3 + 1]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 2);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (i = 0; i < 16; i ++) {
+                byte d = pgm_read_byte(&digits16x24[n][i * 3 + 2]);
+                Wire.write(d);
+                if (m_flags & FLAG_PIXEL_DOUBLE_H) Wire.write(d);
+            }
+            Wire.endTransmission();
+        } else {
+            WriteCommand(0xB0 + m_row);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 1);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+
+            WriteCommand(0xB0 + m_row + 2);//set page address
+            WriteCommand(m_col & 0xf);//set lower column address
+            WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte i = (m_flags & FLAG_PIXEL_DOUBLE_H) ? 32 : 16; i > 0; i--) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+        m_col += (m_flags & FLAG_PIXEL_DOUBLE_H) ? 30 : 16;
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+void LCD_SH1106::draw(const PROGMEM byte* buffer, byte width, byte height)
+{
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+
+    const PROGMEM byte *p = buffer;
+    height >>= 3;
+    width >>= 3;
+    for (byte i = 0; i < height; i++) {
+      // send a bunch of data in one xmission
+        WriteCommand(0xB0 + i + m_row);//set page address
+        WriteCommand(m_col & 0xf);//set lower column address
+        WriteCommand(0x10 | (m_col >> 4));//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(I2C_ADDR);
+            Wire.write(0x40);
+            for (byte k = 0; k < width; k++, p++) {
+                Wire.write(pgm_read_byte(p));
+            }
+            Wire.endTransmission();
+        }
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+    m_col += width;
+}
+
+void LCD_SH1106::begin()
+{
+  Wire.begin();
+
+  WriteCommand(0xAE);    /*display off*/
+
+  WriteCommand(0x02);    /*set lower column address*/
+  WriteCommand(0x10);    /*set higher column address*/
+
+  WriteCommand(0x40);    /*set display start line*/
+
+  WriteCommand(0xB0);    /*set page address*/
+
+  WriteCommand(0x81);    /*contract control*/
+  WriteCommand(0x80);    /*128*/
+
+  WriteCommand(0xA1);    /*set segment remap*/
+
+  WriteCommand(0xA6);    /*normal / reverse*/
+
+  WriteCommand(0xA8);    /*multiplex ratio*/
+  WriteCommand(0x3F);    /*duty = 1/32*/
+
+  WriteCommand(0xad);    /*set charge pump enable*/
+  WriteCommand(0x8b);     /*external VCC   */
+
+  WriteCommand(0x30);    /*0X30---0X33  set VPP   9V liangdu!!!!*/
+
+  WriteCommand(0xC8);    /*Com scan direction*/
+
+  WriteCommand(0xD3);    /*set display offset*/
+  WriteCommand(0x00);   /*   0x20  */
+
+  WriteCommand(0xD5);    /*set osc division*/
+  WriteCommand(0x80);
+
+  WriteCommand(0xD9);    /*set pre-charge period*/
+  WriteCommand(0x1f);    /*0x22*/
+
+  WriteCommand(0xDA);    /*set COM pins*/
+  WriteCommand(0x12);
+
+  WriteCommand(0xdb);    /*set vcomh*/
+  WriteCommand(0x40);
+
+  WriteCommand(0xAF);    /*display ON*/
+
+  clear();
+}
diff --git a/nanotimer/SSD1306.cpp b/nanotimer/SSD1306.cpp
new file mode 100644
index 0000000..75662e8
--- /dev/null
+++ b/nanotimer/SSD1306.cpp
@@ -0,0 +1,277 @@
+#include <avr/pgmspace.h>
+//#include <util/delay.h>
+#include <stdlib.h>
+#include <Wire.h>
+#include "SSD1306.h"
+
+SSD1306::SSD1306(int8_t SCLK, int8_t DC, int8_t RST, int8_t CS) {
+  cs = CS;
+  rst = RST;
+  dc = DC;
+  sclk = SCLK;
+}
+
+// initializer for I2C - we only indicate the reset pin!
+  SSD1306::SSD1306(int8_t reset) {
+  sclk = dc = cs = -1;
+  rst = reset;
+}
+
+
+void SSD1306::begin(uint8_t vccstate, uint8_t i2caddr) {
+  _i2caddr = i2caddr;
+
+
+  // set pin directions
+    // I2C Init
+    Wire.begin(); // Is this the right place for this?
+
+  // Setup reset pin direction (used by both SPI and I2C)
+  pinMode(rst, OUTPUT);
+  digitalWrite(rst, HIGH);
+  // VDD (3.3V) goes high at start, lets just chill for a ms
+  delay(1);
+  // bring reset low
+  digitalWrite(rst, LOW);
+  // wait 10ms
+  delay(10);
+  // bring out of reset
+  digitalWrite(rst, HIGH);
+  // turn on VCC (9V?)
+   #if defined SSD1306_128_32
+    // Init sequence for 128x32 OLED module
+    ssd1306_command(SSD1306_DISPLAYOFF);                    // 0xAE
+    ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
+    ssd1306_command(0x80);                                  // the suggested ratio 0x80
+    ssd1306_command(SSD1306_SETMULTIPLEX);                  // 0xA8
+    ssd1306_command(0x1F);
+    ssd1306_command(SSD1306_SETDISPLAYOFFSET);              // 0xD3
+    ssd1306_command(0x0);                                   // no offset
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0);            // line #0
+    ssd1306_command(SSD1306_CHARGEPUMP);                    // 0x8D
+    if (vccstate == SSD1306_EXTERNALVCC)
+      { ssd1306_command(0x10); }
+    else
+      { ssd1306_command(0x14); }
+    ssd1306_command(SSD1306_MEMORYMODE);                    // 0x20
+    ssd1306_command(0x00);                                  // 0x0 act like ks0108
+	ssd1306_command(SSD1306_SEGREMAP | 0x1);
+    ssd1306_command(SSD1306_COMSCANDEC);
+    ssd1306_command(SSD1306_SETCOMPINS);                    // 0xDA
+    ssd1306_command(0x02);
+    ssd1306_command(SSD1306_SETCONTRAST);                   // 0x81
+    ssd1306_command(0x8F);
+    ssd1306_command(SSD1306_SETPRECHARGE);                  // 0xd9
+    if (vccstate == SSD1306_EXTERNALVCC)
+      { ssd1306_command(0x22); }
+    else
+      { ssd1306_command(0xF1); }
+    ssd1306_command(SSD1306_SETVCOMDETECT);                 // 0xDB
+    ssd1306_command(0x40);
+    ssd1306_command(SSD1306_DISPLAYALLON_RESUME);           // 0xA4
+    ssd1306_command(SSD1306_NORMALDISPLAY);                 // 0xA6
+  #endif
+
+  #if defined SSD1306_128_64
+    // Init sequence for 128x64 OLED module
+    ssd1306_command(SSD1306_DISPLAYOFF);                    // 0xAE
+    ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
+    ssd1306_command(0x80);                                  // the suggested ratio 0x80
+    ssd1306_command(SSD1306_SETMULTIPLEX);                  // 0xA8
+    ssd1306_command(0x3F);
+    ssd1306_command(SSD1306_SETDISPLAYOFFSET);              // 0xD3
+    ssd1306_command(0x0);                                   // no offset
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0);            // line #0
+    ssd1306_command(SSD1306_CHARGEPUMP);                    // 0x8D
+    if (vccstate == SSD1306_EXTERNALVCC)
+      { ssd1306_command(0x10); }
+    else
+      { ssd1306_command(0x14); }
+    ssd1306_command(SSD1306_MEMORYMODE);                    // 0x20
+    ssd1306_command(0x00);                                  // 0x0 act like ks0108
+    ssd1306_command(SSD1306_SEGREMAP | 0x1);
+    ssd1306_command(SSD1306_COMSCANDEC);
+    ssd1306_command(SSD1306_SETCOMPINS);                    // 0xDA
+    ssd1306_command(0x12);
+    ssd1306_command(SSD1306_SETCONTRAST);                   // 0x81
+    if (vccstate == SSD1306_EXTERNALVCC)
+      { ssd1306_command(0x9F); }
+    else
+      { ssd1306_command(0xCF); }
+    ssd1306_command(SSD1306_SETPRECHARGE);                  // 0xd9
+    if (vccstate == SSD1306_EXTERNALVCC)
+      { ssd1306_command(0x22); }
+    else
+      { ssd1306_command(0xF1); }
+    ssd1306_command(SSD1306_SETVCOMDETECT);                 // 0xDB
+    ssd1306_command(0x40);
+    ssd1306_command(SSD1306_DISPLAYALLON_RESUME);           // 0xA4
+    ssd1306_command(SSD1306_NORMALDISPLAY);                 // 0xA6
+  #endif
+
+  ssd1306_command(SSD1306_DISPLAYON);//--turn on oled panel
+
+  // clear screen
+    delay(5);
+
+    ssd1306_command(SSD1306_SETLOWCOLUMN | 0x0);  // low col = 0
+    ssd1306_command(SSD1306_SETHIGHCOLUMN | 0x0);  // hi col = 0
+    ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
+
+    for (byte i = 0; i < SSD1306_LCDHEIGHT / 8; i++) {
+      // send a bunch of data in one xmission
+        ssd1306_command(0xB0 + i);//set page address
+        ssd1306_command(0);//set lower column address
+        ssd1306_command(0x10);//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte k = 0; k < SSD1306_LCDWIDTH / 8; k++) {
+                Wire.write(0);
+            }
+            Wire.endTransmission();
+        }
+    }
+}
+
+
+void SSD1306::invertDisplay(uint8_t i) {
+  if (i) {
+    ssd1306_command(SSD1306_INVERTDISPLAY);
+  } else {
+    ssd1306_command(SSD1306_NORMALDISPLAY);
+  }
+}
+
+void SSD1306::ssd1306_command(uint8_t c) {
+    // I2C
+    uint8_t control = 0x00;   // Co = 0, D/C = 0
+    Wire.beginTransmission(_i2caddr);
+    Wire.write(control);
+    Wire.write(c);
+    Wire.endTransmission();
+}
+
+// startscrollright
+// Activate a right handed scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrollright(uint8_t start, uint8_t stop){
+	ssd1306_command(SSD1306_RIGHT_HORIZONTAL_SCROLL);
+	ssd1306_command(0X00);
+	ssd1306_command(start);
+	ssd1306_command(0X00);
+	ssd1306_command(stop);
+	ssd1306_command(0X01);
+	ssd1306_command(0XFF);
+	ssd1306_command(SSD1306_ACTIVATE_SCROLL);
+}
+
+// startscrollleft
+// Activate a right handed scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrollleft(uint8_t start, uint8_t stop){
+	ssd1306_command(SSD1306_LEFT_HORIZONTAL_SCROLL);
+	ssd1306_command(0X00);
+	ssd1306_command(start);
+	ssd1306_command(0X00);
+	ssd1306_command(stop);
+	ssd1306_command(0X01);
+	ssd1306_command(0XFF);
+	ssd1306_command(SSD1306_ACTIVATE_SCROLL);
+}
+
+// startscrolldiagright
+// Activate a diagonal scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrolldiagright(uint8_t start, uint8_t stop){
+	ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
+	ssd1306_command(0X00);
+	ssd1306_command(SSD1306_LCDHEIGHT);
+	ssd1306_command(SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
+	ssd1306_command(0X00);
+	ssd1306_command(start);
+	ssd1306_command(0X00);
+	ssd1306_command(stop);
+	ssd1306_command(0X01);
+	ssd1306_command(SSD1306_ACTIVATE_SCROLL);
+}
+
+// startscrolldiagleft
+// Activate a diagonal scroll for rows start through stop
+// Hint, the display is 16 rows tall. To scroll the whole display, run:
+// display.scrollright(0x00, 0x0F)
+void SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop){
+	ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
+	ssd1306_command(0X00);
+	ssd1306_command(SSD1306_LCDHEIGHT);
+	ssd1306_command(SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
+	ssd1306_command(0X00);
+	ssd1306_command(start);
+	ssd1306_command(0X00);
+	ssd1306_command(stop);
+	ssd1306_command(0X01);
+	ssd1306_command(SSD1306_ACTIVATE_SCROLL);
+}
+
+void SSD1306::stopscroll(void){
+	ssd1306_command(SSD1306_DEACTIVATE_SCROLL);
+}
+
+void SSD1306::ssd1306_data(uint8_t c) {
+    // I2C
+    uint8_t control = 0x40;   // Co = 0, D/C = 1
+    Wire.beginTransmission(_i2caddr);
+    Wire.write(control);
+    Wire.write(c);
+    Wire.endTransmission();
+}
+
+void SSD1306::fill(unsigned char dat)
+{
+    unsigned char i,j;
+
+    ssd1306_command(0x00);//set lower column address
+    ssd1306_command(0x10);//set higher column address
+    ssd1306_command(0xB0);//set page address
+
+#ifdef TWBR
+    uint8_t twbrbackup = TWBR;
+    TWBR = 18; // upgrade to 400KHz!
+#endif
+    for (byte i=0; i<(SSD1306_LCDHEIGHT/8); i++)
+    {
+        // send a bunch of data in one xmission
+        ssd1306_command(0xB0 + i);//set page address
+        ssd1306_command(0);//set lower column address
+        ssd1306_command(0x10);//set higher column address
+
+        for(byte j = 0; j < 8; j++){
+            Wire.beginTransmission(_i2caddr);
+            Wire.write(0x40);
+            for (byte k = 0; k < 16; k++) {
+                Wire.write(dat);
+            }
+            Wire.endTransmission();
+        }
+    }
+#ifdef TWBR
+    TWBR = twbrbackup;
+#endif
+}
+
+void SSD1306::draw8x8(byte* buffer, uint8_t x, uint8_t y)
+{
+    // send a bunch of data in one xmission
+    ssd1306_command(0xB0 + y);//set page address
+    ssd1306_command(x & 0xf);//set lower column address
+    ssd1306_command(0x10 | (x >> 4));//set higher column address
+
+    Wire.beginTransmission(_i2caddr);
+    Wire.write(0x40);
+    Wire.write(buffer, 8);
+    Wire.endTransmission();
+}
diff --git a/nanotimer/SSD1306.h b/nanotimer/SSD1306.h
new file mode 100644
index 0000000..ae7f339
--- /dev/null
+++ b/nanotimer/SSD1306.h
@@ -0,0 +1,112 @@
+#include "Arduino.h"
+
+#define SSD1306_I2C_ADDRESS   0x3C	// 011110+SA0+RW - 0x3C or 0x3D
+// Address for 128x32 is 0x3C
+// Address for 128x32 is 0x3D (default) or 0x3C (if SA0 is grounded)
+
+/*=========================================================================
+    SSD1306 Displays
+    -----------------------------------------------------------------------
+    The driver is used in multiple displays (128x64, 128x32, etc.).
+    Select the appropriate display below to create an appropriately
+    sized framebuffer, etc.
+
+    SSD1306_128_64  128x64 pixel display
+
+    SSD1306_128_32  128x32 pixel display
+
+    You also need to set the LCDWIDTH and LCDHEIGHT defines to an
+    appropriate size
+
+    -----------------------------------------------------------------------*/
+   #define SSD1306_128_64
+//   #define SSD1306_128_32
+/*=========================================================================*/
+
+#if defined SSD1306_128_64 && defined SSD1306_128_32
+  #error "Only one SSD1306 display can be specified at once in SSD1306.h"
+#endif
+#if !defined SSD1306_128_64 && !defined SSD1306_128_32
+  #error "At least one SSD1306 display must be specified in SSD1306.h"
+#endif
+
+#if defined SSD1306_128_64
+  #define SSD1306_LCDWIDTH                  128
+  #define SSD1306_LCDHEIGHT                 64
+#endif
+#if defined SSD1306_128_32
+  #define SSD1306_LCDWIDTH                  128
+  #define SSD1306_LCDHEIGHT                 32
+#endif
+
+#define SSD1306_SETCONTRAST 0x81
+#define SSD1306_DISPLAYALLON_RESUME 0xA4
+#define SSD1306_DISPLAYALLON 0xA5
+#define SSD1306_NORMALDISPLAY 0xA6
+#define SSD1306_INVERTDISPLAY 0xA7
+#define SSD1306_DISPLAYOFF 0xAE
+#define SSD1306_DISPLAYON 0xAF
+
+#define SSD1306_SETDISPLAYOFFSET 0xD3
+#define SSD1306_SETCOMPINS 0xDA
+
+#define SSD1306_SETVCOMDETECT 0xDB
+
+#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
+#define SSD1306_SETPRECHARGE 0xD9
+
+#define SSD1306_SETMULTIPLEX 0xA8
+
+#define SSD1306_SETLOWCOLUMN 0x00
+#define SSD1306_SETHIGHCOLUMN 0x10
+
+#define SSD1306_SETSTARTLINE 0x40
+
+#define SSD1306_MEMORYMODE 0x20
+
+#define SSD1306_COMSCANINC 0xC0
+#define SSD1306_COMSCANDEC 0xC8
+
+#define SSD1306_SEGREMAP 0xA0
+
+#define SSD1306_CHARGEPUMP 0x8D
+
+#define SSD1306_EXTERNALVCC 0x1
+#define SSD1306_SWITCHCAPVCC 0x2
+
+// Scrolling #defines
+#define SSD1306_ACTIVATE_SCROLL 0x2F
+#define SSD1306_DEACTIVATE_SCROLL 0x2E
+#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
+#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
+#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
+#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
+#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
+
+class SSD1306 {
+public:
+  SSD1306(int8_t SCLK, int8_t DC, int8_t RST, int8_t CS);
+  SSD1306(int8_t RST = 4);
+
+  void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS);
+  void ssd1306_command(uint8_t c);
+  void ssd1306_data(uint8_t c);
+
+  void invertDisplay(uint8_t i);
+  void draw8x8(byte* buffer, byte x, byte y);
+
+  void startscrollright(uint8_t start, uint8_t stop);
+  void startscrollleft(uint8_t start, uint8_t stop);
+
+  void startscrolldiagright(uint8_t start, uint8_t stop);
+  void startscrolldiagleft(uint8_t start, uint8_t stop);
+  void stopscroll(void);
+
+  void fill(unsigned char dat);
+  void clearBuffer();
+
+protected:
+  uint8_t _i2caddr;
+private:
+  int8_t sclk, dc, rst, cs;
+};
diff --git a/nanotimer/config.h b/nanotimer/config.h
new file mode 100644
index 0000000..42b7e70
--- /dev/null
+++ b/nanotimer/config.h
@@ -0,0 +1,32 @@
+#ifndef CONFIG_H_INCLUDED
+#define CONFIG_H_INCLUDED
+
+/**************************************
+* Data logging/streaming out
+**************************************/
+#define ENABLE_DATA_OUT 0
+#define ENABLE_DATA_LOG 0
+#define USE_SOFTSERIAL 0
+//this defines the format of log file
+#define LOG_FORMAT FORMAT_CSV
+
+/**************************************
+* Choose SD pin here
+**************************************/
+#define SD_CS_PIN 10
+
+/**************************************
+* Choose LCD model here
+**************************************/
+//LCD_SSD1306 lcd;
+LCD_SH1106 lcd;
+//LCD_Null lcd;
+
+/**************************************
+* Other options
+**************************************/
+//#define OBD_MIN_INTERVAL 50 /* ms */
+//#define DEBUG Serial
+#define DEBUG_BAUDRATE 9600
+
+#endif // CONFIG_H_INCLUDED
diff --git a/nanotimer/datalogger.h b/nanotimer/datalogger.h
new file mode 100644
index 0000000..2c4ebfe
--- /dev/null
+++ b/nanotimer/datalogger.h
@@ -0,0 +1,377 @@
+typedef enum {
+    LOG_TYPE_DEFAULT = 0,
+    LOG_TYPE_0_60,
+    LOG_TYPE_0_100,
+    LOG_TYPE_100_200,
+    LOG_TYPE_400M,
+    LOG_TYPE_LAPS,
+    LOG_TYPE_ROUTE,
+} LOG_TYPES;
+
+#define FLAG_CAR 0x1
+#define FLAG_CYCLING 0x2
+#define FLAG_OBD 0x10
+#define FLAG_GPS 0x20
+#define FLAG_ACC 0x40
+
+#define FORMAT_BIN 0
+#define FORMAT_CSV 1
+
+typedef struct {
+    uint32_t time;
+    uint16_t pid;
+    uint8_t flags;
+    uint8_t checksum;
+    float value;
+} LOG_DATA;
+
+typedef struct {
+    uint32_t time;
+    uint16_t pid;
+    uint8_t flags;
+    uint8_t checksum;
+    float value[3];
+} LOG_DATA_COMM;
+
+typedef struct {
+    uint32_t time; /* e.g. 1307281259 */
+    uint16_t pid;
+    uint8_t message;
+    uint8_t checksum;
+    uint16_t fileIndex;
+    uint16_t fileSize; /* KB */
+    uint16_t logFlags;
+    uint8_t logType;
+    uint8_t data[5];
+} LOG_DATA_FILE_INFO;
+
+typedef struct {
+    uint32_t time;
+    uint16_t pid;
+    uint8_t message;
+    uint8_t checksum;
+    uint8_t data[12];
+} LOG_DATA_COMMAND;
+
+typedef struct {
+    uint32_t id;
+    uint32_t dataOffset;
+    uint8_t ver;
+    uint8_t logType;
+    uint16_t flags;
+    uint32_t dateTime; //4, YYMMDDHHMM, e.g. 1305291359
+    /*
+    uint8_t devid[8];
+    uint8_t vin[24];
+    uint8_t unused[84];
+    */
+} HEADER;
+
+#define HEADER_LEN 128 /* bytes */
+
+#define PID_GPS_COORDINATES 0xF00A
+#define PID_GPS_ALTITUDE 0xF00C
+#define PID_GPS_SPEED 0xF00D
+#define PID_GPS_HEADING 0xF00E
+#define PID_GPS_SAT_COUNT 0xF00F
+#define PID_GPS_TIME 0xF010
+
+#define PID_ACC 0xF020
+#define PID_GYRO 0xF021
+
+#define PID_MESSAGE 0xFE00
+#define PID_HEART_BEAT 0xFFEE
+
+#define MSG_FILE_LIST_BEGIN 0x1
+#define MSG_FILE_LIST_END 0x2
+#define MSG_FILE_INFO 0x3
+#define MSG_FILE_REQUEST 0x4
+
+#if LOG_FORMAT == FORMAT_BIN
+#define FILE_NAME_FORMAT "/DAT%05d.LOG"
+#else
+#define FILE_NAME_FORMAT "/DAT%05d.CSV"
+#endif
+
+#if ENABLE_DATA_OUT && !USE_OBD_BT
+#if defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
+    SoftwareSerial mySerial(A8, A9); /* for BLE Shield on MEGA*/
+#elif defined(__AVR_ATmega644P__)
+    SoftwareSerial mySerial(9, 10); /* for Microduino */
+#else
+    SoftwareSerial mySerial(A2, A3); /* for BLE Shield on UNO*/
+#endif
+#endif
+
+void btInit(int baudrate);
+void btSend(byte* data, byte length);
+
+class CDataLogger {
+public:
+    void initSender()
+    {
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btInit(9600);
+#else
+        mySerial.begin(9600);
+#endif
+#endif
+#if ENABLE_DATA_LOG && LOG_FORMAT == FORMAT_CSV
+        m_lastDataTime = 0;
+#endif
+    }
+#if ENABLE_DATA_OUT
+    void sendFileInfo(File& file)
+    {
+        if (file.size() < HEADER_LEN) return;
+
+        LOG_DATA_FILE_INFO info = {0};
+        info.fileIndex = atol(file.name() + 3);
+        if (info.fileIndex == 0) return;
+
+        HEADER hdr;
+        if (file.readBytes((char*)&hdr, sizeof(hdr)) != sizeof(hdr)) return;
+
+        info.pid = PID_MESSAGE;
+        info.message = MSG_FILE_INFO;
+        info.fileSize = file.size();
+        info.time = hdr.dateTime;
+        info.logType = hdr.logType;
+        info.logFlags = hdr.flags;
+        info.checksum = getChecksum((char*)&info, sizeof(info));
+#if USE_OBD_BT
+        btSend((uint8_t*)&info, sizeof(info));
+#else
+        mySerial.write((uint8_t*)&info, sizeof(info));
+#endif
+    }
+    void sendCommand(byte message, void* data = 0, byte bytes = 0)
+    {
+        LOG_DATA_COMMAND msg = {0, PID_MESSAGE, message};
+        if (data) memcpy(msg.data, data, bytes);
+        msg.checksum = getChecksum((char*)&msg, sizeof(msg));
+#if USE_OBD_BT
+        btSend((uint8_t*)&msg, sizeof(msg));
+#else
+        mySerial.write((uint8_t*)&msg, sizeof(msg));
+#endif
+    }
+    bool receiveCommand(LOG_DATA_COMMAND& msg)
+    {
+#if !USE_OBD_BT
+        if (!mySerial.available())
+            return false;
+
+        if (mySerial.readBytes((char*)&msg, sizeof(msg)) != sizeof(msg))
+            return false;
+
+        uint8_t checksum = msg.checksum;
+        msg.checksum = 0;
+        if (getChecksum((char*)&msg, sizeof(msg)) != msg.checksum) {
+            return false;
+        }
+#endif
+        return true;
+    }
+#endif
+    void logData(uint16_t pid, int value)
+    {
+        LOG_DATA_COMM ld = {dataTime, pid, 1, 0, value};
+        ld.checksum = getChecksum((char*)&ld, 12);
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btSend((uint8_t*)&ld, 12);
+#else
+        mySerial.write((uint8_t*)&ld, 12);
+#endif
+#endif
+#if ENABLE_DATA_LOG
+#if LOG_FORMAT == FORMAT_BIN
+        sdfile.write((uint8_t*)&ld, 12);
+        dataSize += 12;
+#else
+        dataSize += sdfile.print(dataTime - m_lastDataTime);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(pid, HEX);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value);
+        dataSize += sdfile.write('\n');
+        m_lastDataTime = dataTime;
+#endif
+#endif
+    }
+    void logData(uint16_t pid, float value)
+    {
+        LOG_DATA_COMM ld = {dataTime, pid, 1, 0, value};
+        ld.checksum = getChecksum((char*)&ld, 12);
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btSend((uint8_t*)&ld, 12);
+#else
+        mySerial.write((uint8_t*)&ld, 12);
+#endif
+#endif
+#if ENABLE_DATA_LOG
+#if LOG_FORMAT == FORMAT_BIN
+        sdfile.write((uint8_t*)&ld, 12);
+        dataSize += 12;
+#else
+        dataSize += sdfile.print(dataTime - m_lastDataTime);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(pid, HEX);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value);
+        dataSize += sdfile.write('\n');
+        m_lastDataTime = dataTime;
+#endif
+#endif
+    }
+    void logData(uint16_t pid, float value1, float value2)
+    {
+        LOG_DATA_COMM ld = {dataTime, pid, 2, 0, {value1, value2}};
+        ld.checksum = getChecksum((char*)&ld, 16);
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btSend((uint8_t*)&ld, 16);
+#else
+        mySerial.write((uint8_t*)&ld, 16);
+#endif
+#endif
+#if ENABLE_DATA_LOG
+#if LOG_FORMAT == FORMAT_BIN
+        sdfile.write((uint8_t*)&ld, 16);
+        dataSize += 16;
+#else
+        dataSize += sdfile.print(dataTime - m_lastDataTime);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(pid, HEX);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value1, 6);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value2, 6);
+        dataSize += sdfile.write('\n');
+        m_lastDataTime = dataTime;
+#endif
+#endif
+    }
+    void logData(uint16_t pid, uint32_t value1, uint32_t value2)
+    {
+        LOG_DATA_COMM ld = {dataTime, pid, 2, 0, {value1, value2}};
+        ld.checksum = getChecksum((char*)&ld, 16);
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btSend((uint8_t*)&ld, 16);
+#else
+        mySerial.write((uint8_t*)&ld, 16);
+#endif
+#endif
+#if ENABLE_DATA_LOG
+#if LOG_FORMAT == FORMAT_BIN
+        sdfile.write((uint8_t*)&ld, 16);
+        dataSize += 16;
+#else
+        dataSize += sdfile.print(dataTime - m_lastDataTime);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(pid, HEX);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value1);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value2);
+        dataSize += sdfile.write('\n');
+        m_lastDataTime = dataTime;
+#endif
+#endif
+    }
+    void logData(uint16_t pid, int value1, int value2, int value3)
+    {
+        LOG_DATA_COMM ld = {dataTime, pid, 3, 0, {value1, value2, value3}};
+        ld.checksum = getChecksum((char*)&ld, 20);
+#if ENABLE_DATA_OUT
+#if USE_OBD_BT
+        btSend((uint8_t*)&ld, 20);
+#else
+        mySerial.write((uint8_t*)&ld, 20);
+#endif
+#endif
+#if ENABLE_DATA_LOG
+#if LOG_FORMAT == FORMAT_BIN
+        sdfile.write((uint8_t*)&ld, 20);
+        dataSize += 20;
+#else
+        dataSize += sdfile.print(dataTime - m_lastDataTime);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(pid, HEX);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value1);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value2);
+        dataSize += sdfile.write(',');
+        dataSize += sdfile.print(value3);
+        dataSize += sdfile.write('\n');
+        m_lastDataTime = dataTime;
+#endif
+#endif
+    }
+#if ENABLE_DATA_LOG
+    uint16_t openFile(LOG_TYPES logType, uint16_t logFlags = 0, uint32_t dateTime = 0)
+    {
+        uint16_t fileIndex;
+        char filename[24] = "/FRMATICS";
+
+        if (SD.exists(filename)) {
+            for (fileIndex = 1; fileIndex; fileIndex++) {
+                sprintf(filename + 9, FILE_NAME_FORMAT, fileIndex);
+                if (!SD.exists(filename)) {
+                    break;
+                }
+            }
+            if (fileIndex == 0)
+                return 0;
+        } else {
+            SD.mkdir(filename);
+            fileIndex = 1;
+            sprintf(filename + 9, FILE_NAME_FORMAT, 1);
+        }
+
+        sdfile = SD.open(filename, FILE_WRITE);
+        if (!sdfile) {
+            return 0;
+        }
+
+#if LOG_FORMAT == FORMAT_BIN
+        HEADER hdr = {'UDUS', HEADER_LEN, 1, logType, logFlags, dateTime};
+        sdfile.write((uint8_t*)&hdr, sizeof(hdr));
+        for (byte i = 0; i < HEADER_LEN - sizeof(hdr); i++)
+            sdfile.write((uint8_t)0);
+        dataSize = HEADER_LEN;
+#endif
+        return fileIndex;
+    }
+    void closeFile()
+    {
+        sdfile.close();
+    }
+    void flushFile()
+    {
+        sdfile.flush();
+    }
+#endif
+    uint32_t dataTime;
+    uint32_t dataSize;
+private:
+    static byte getChecksum(char* buffer, byte len)
+    {
+        uint8_t checksum = 0;
+        for (byte i = 0; i < len; i++) {
+          checksum ^= buffer[i];
+        }
+        return checksum;
+    }
+#if ENABLE_DATA_LOG
+    File sdfile;
+#if LOG_FORMAT == FORMAT_CSV
+    uint32_t m_lastDataTime;
+#endif
+#endif
+};
diff --git a/nanotimer/nanotimer.cbp b/nanotimer/nanotimer.cbp
new file mode 100644
index 0000000..b71cb5e
--- /dev/null
+++ b/nanotimer/nanotimer.cbp
@@ -0,0 +1,598 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_project_file>
+	<FileVersion major="1" minor="6" />
+	<Project>
+		<Option title="pbox" />
+		<Option pch_mode="2" />
+		<Option compiler="avrgcc" />
+		<Build>
+			<Target title="Simulator">
+				<Option output="bin/Release/obdlogger_sim.exe" prefix_auto="1" extension_auto="0" />
+				<Option object_output="obj/Release/" />
+				<Option type="1" />
+				<Option compiler="GCC" />
+				<Compiler>
+					<Add option="-Os" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-DARDUSIM" />
+					<Add option="-D__AVR_ATmega2560__" />
+					<Add option="-x c++" />
+					<Add directory="$(ARDUINO_DIR)/arduino/cores" />
+					<Add directory="$(ARDUINO_DIR)/arduino/variants/standard" />
+					<Add directory="$(ARDUINO_DIR)/include" />
+				</Compiler>
+				<Linker>
+					<Add option="-lardusim" />
+				</Linker>
+				<Environment>
+					<Variable name="ARDUINO_DIR" value="$(APP_PATH)\ardusim" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Uno">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-Os" />
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Uno" />
+					<Variable name="BOARD_ID" value="uno" />
+					<Variable name="MCU" value="atmega328p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="115200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Leonardo">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega32U4__" />
+					<Add option="-DUSB_VID=0x2341" />
+					<Add option="-DUSB_PID=0x8036" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/leonardo" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Leonardo" />
+					<Variable name="BOARD_ID" value="leonardo" />
+					<Variable name="MCU" value="atmega32u4" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Esplora">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega32U4__" />
+					<Add option="-DUSB_VID=0x2341" />
+					<Add option="-DUSB_PID=0x8037" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/leonardo" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Esplora" />
+					<Variable name="BOARD_ID" value="esplora" />
+					<Variable name="MCU" value="atmega32u4" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Micro">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega32U4__" />
+					<Add option="-DUSB_VID=0x2341" />
+					<Add option="-DUSB_PID=0x803C" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/micro" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Micro" />
+					<Variable name="BOARD_ID" value="micro" />
+					<Variable name="MCU" value="atmega32u4" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Duemilanove (328)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Duemilanove (328)" />
+					<Variable name="BOARD_ID" value="duemilanove328" />
+					<Variable name="MCU" value="atmega328p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Duemilanove (168)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega168__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Duemilanove (168)" />
+					<Variable name="BOARD_ID" value="duemilanove168" />
+					<Variable name="MCU" value="atmega168" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="19200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Nano (328)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Nano (328)" />
+					<Variable name="BOARD_ID" value="nano328" />
+					<Variable name="MCU" value="atmega328p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Nano (168)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega168__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Nano (168)" />
+					<Variable name="BOARD_ID" value="nano168" />
+					<Variable name="MCU" value="atmega168" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="19200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Mini (328)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Mini (328)" />
+					<Variable name="BOARD_ID" value="mini328" />
+					<Variable name="MCU" value="atmega328p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Mini (168)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega168__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Mini (168)" />
+					<Variable name="BOARD_ID" value="mini168" />
+					<Variable name="MCU" value="atmega168" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="19200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Pro Mini (328)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Pro Mini (328)" />
+					<Variable name="BOARD_ID" value="promini328" />
+					<Variable name="MCU" value="atmega328p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Pro Mini (168)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega168__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Pro Mini (168)" />
+					<Variable name="BOARD_ID" value="promini168" />
+					<Variable name="MCU" value="atmega168" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="19200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Mega 2560/ADK">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-O3" />
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega2560__" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/mega" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Mega 2560\ADK" />
+					<Variable name="BOARD_ID" value="mega2560" />
+					<Variable name="MCU" value="atmega2560" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="115200" />
+					<Variable name="UPLOAD_PORT" value="COM37" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Mega 1280">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega1280__" />
+					<Add option="-O2" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/mega" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Mega 1280" />
+					<Variable name="BOARD_ID" value="mega1280" />
+					<Variable name="MCU" value="atmega1280" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="57600" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Arduino Mega 8">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega328P__" />
+					<Add option="-Os" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Arduino Mega 8" />
+					<Variable name="BOARD_ID" value="mega8" />
+					<Variable name="MCU" value="atmega8" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="19200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+			<Target title="Microduino Core+ (644P)">
+				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
+				<Option type="1" />
+				<Option compiler="avrgcc" />
+				<Compiler>
+					<Add option="-O3" />
+					<Add option="-x c++" />
+					<Add option="-mmcu=$(MCU)" />
+					<Add option="-DF_CPU=16000000L" />
+					<Add option="-D__AVR_ATmega644P__" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
+					<Add directory="$(ARDUINO_DIR)/libraries" />
+					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/plus" />
+				</Compiler>
+				<Linker>
+					<Add option="-mmcu=$(MCU)" />
+					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+				</Linker>
+				<ExtraCommands>
+					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
+					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
+					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
+					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
+				</ExtraCommands>
+				<Environment>
+					<Variable name="BOARD" value="Microduino Core+ (644P)" />
+					<Variable name="BOARD_ID" value="uduino644p" />
+					<Variable name="MCU" value="atmega644p" />
+					<Variable name="MCU_CLOCK" value="16" />
+					<Variable name="UPLOAD_BAUDRATE" value="115200" />
+					<Variable name="UPLOAD_PORT" value="" />
+				</Environment>
+			</Target>
+		</Build>
+		<Compiler>
+			<Add directory="." />
+		</Compiler>
+		<Unit filename="config.h" />
+		<Unit filename="datalogger.h" />
+		<Unit filename="images.h" />
+		<Unit filename="pbox.ino">
+			<Option compile="1" />
+			<Option link="1" />
+		</Unit>
+		<Extensions>
+			<code_completion />
+			<debugger />
+			<envvars />
+		</Extensions>
+	</Project>
+</CodeBlocks_project_file>
diff --git a/nanotimer/nanotimer.ino b/nanotimer/nanotimer.ino
new file mode 100644
index 0000000..a5ed6a4
--- /dev/null
+++ b/nanotimer/nanotimer.ino
@@ -0,0 +1,395 @@
+/*************************************************************************
+* OBD-II based performance timer and logger
+* Distributed under GPL v2.0
+* Copyright (c) 2014 Stanley Huang <stanleyhuangyc@gmail.com>
+* All rights reserved.
+*************************************************************************/
+
+#include <Arduino.h>
+#include <Wire.h>
+#include <OBD.h>
+#include <SD.h>
+#include "MicroLCD.h"
+#include "config.h"
+#if USE_SOFTSERIAL
+#include <SoftwareSerial.h>
+#endif
+#include "datalogger.h"
+
+// logger states
+#define STATE_SD_READY 0x1
+#define STATE_OBD_READY 0x2
+#define STATE_SLEEPING 0x20
+
+static uint32_t lastFileSize = 0;
+static int lastSpeed = -1;
+static uint32_t lastSpeedTime = 0;
+static int speed = 0;
+static uint32_t distance = 0;
+static uint16_t fileIndex = 0;
+static uint32_t startTime = 0;
+
+#define STAGE_IDLE 0
+#define STAGE_WAIT_START 1
+#define STAGE_MEASURING 2
+
+static byte stage = STAGE_IDLE;
+
+#define SPEED_THRESHOLD_1 60 /* kph */
+#define SPEED_THRESHOLD_2 100 /* kph */
+#define SPEED_THRESHOLD_3 200 /* kph */
+#define DISTANCE_THRESHOLD 400 /* meters */
+
+static uint16_t times[4] = {0};
+
+class COBDLogger : public COBD, public CDataLogger
+{
+public:
+    COBDLogger():state(0) {}
+    void setup()
+    {
+#if ENABLE_DATA_LOG
+        if (!(state & STATE_SD_READY)) {
+            if (checkSD()) {
+                state |= STATE_SD_READY;
+                showStates();
+            }
+        }
+#endif
+
+        do {
+            showStates();
+        } while (!init());
+
+        state |= STATE_OBD_READY;
+
+        showStates();
+
+#if ENABLE_DATA_LOG
+        uint16_t index = openFile(LOG_TYPE_DEFAULT);
+        lcd.print("File ID:");
+        if (index) {
+            lcd.setFlags(FLAG_PAD_ZERO);
+            lcd.printInt(index, 5);
+            lcd.setFlags(0);
+        } else {
+            lcd.print("N/A");
+        }
+#endif
+
+        initTimerScreen();
+    }
+    void loop()
+    {
+        static byte index = 0;
+        static byte index2 = 0;
+        static byte index3 = 0;
+
+        timerLoop();
+
+#if ENABLE_DATA_LOG
+        // flush SD data every 1KB
+        if (dataSize - lastFileSize >= 1024 && stage != STAGE_MEASURING) {
+            flushFile();
+            lastFileSize = dataSize;
+            // display logged data size
+        }
+#endif
+
+        if (errors >= 2) {
+            reconnect();
+        }
+    }
+#if ENABLE_DATA_LOG
+    bool checkSD()
+    {
+        Sd2Card card;
+        SdVolume volume;
+        lcd.setCursor(0, 0);
+        lcd.setFontSize(FONT_SIZE_MEDIUM);
+        state &= ~STATE_SD_READY;
+        pinMode(SS, OUTPUT);
+        if (card.init(SPI_HALF_SPEED, SD_CS_PIN)) {
+            const char* type;
+            char buf[20];
+
+            switch(card.type()) {
+            case SD_CARD_TYPE_SD1:
+                type = "SD1";
+                break;
+            case SD_CARD_TYPE_SD2:
+                type = "SD2";
+                break;
+            case SD_CARD_TYPE_SDHC:
+                type = "SDHC";
+                break;
+            default:
+                type = "SDx";
+            }
+
+            lcd.clear();
+            lcd.print(type);
+            lcd.write(' ');
+            if (!volume.init(card)) {
+                lcd.print("No FAT!");
+                return false;
+            }
+
+            uint32_t volumesize = volume.blocksPerCluster();
+            volumesize >>= 1; // 512 bytes per block
+            volumesize *= volume.clusterCount();
+            volumesize >>= 10;
+
+            sprintf(buf, "%dGB", (int)((volumesize + 511) / 1000));
+            lcd.print(buf);
+        } else {
+            lcd.clear();
+            lcd.print("SD");
+            lcd.draw(cross, 32, 0, 16, 16);
+            return false;
+        }
+
+        if (!SD.begin(SD_CS_PIN)) {
+            lcd.setCursor(48, 0);
+            lcd.print("Bad SD");
+            return false;
+        }
+
+        state |= STATE_SD_READY;
+        return true;
+    }
+#endif
+private:
+    void dataIdleLoop()
+    {
+        if (state & STATE_SLEEPING) return;
+
+        if (getState() == OBD_CONNECTED)
+            return;
+
+        // called while initializing
+        char buf[10];
+        unsigned int t = (millis() - startTime) / 1000;
+        sprintf(buf, "%02u:%02u", t / 60, t % 60);
+        lcd.setFontSize(FONT_SIZE_SMALL);
+        lcd.setCursor(97, 7);
+        lcd.print(buf);
+    }
+    void timerLoop()
+    {
+        uint32_t elapsed = millis() - startTime;
+        uint16_t n;
+
+        int speed;
+        if (!read(PID_SPEED, speed))
+            return;
+
+        dataTime = millis();
+        logData(0x100 | PID_SPEED, speed);
+
+        int rpm = 0;
+        if (read(PID_RPM, rpm)) {
+            dataTime = millis();
+            logData(0x100 | PID_RPM, rpm);
+        }
+
+        lcd.setFontSize(FONT_SIZE_XLARGE);
+        // estimate distance
+        distance += (uint32_t)(speed + lastSpeed) * (dataTime - lastSpeedTime) / 2 / 3600;
+
+        if (lastSpeed != speed) {
+            lcd.setCursor(0, 4);
+            lcd.printInt((unsigned int)speed % 1000, 3);
+            lastSpeed = speed;
+        }
+
+        lastSpeedTime = dataTime;
+
+        if (stage == STAGE_WAIT_START) {
+            if (speed > 0) {
+                stage = STAGE_MEASURING;
+                startTime = lastSpeedTime;
+
+                uint32_t t = dataTime;
+                dataTime = lastSpeedTime;
+                logData(0x100 | PID_SPEED, lastSpeed);
+                dataTime = t;
+                logData(0x100 | PID_SPEED, speed);
+
+                lastSpeed = 0;
+                distance = 0;
+
+                memset(times, 0, sizeof(times));
+
+                initTimerScreen();
+            }
+        } else if (stage == STAGE_MEASURING) {
+            // display elapsed time (mm:ss:mm)
+            n = elapsed / 1000;
+            if (n < 100) {
+                lcd.setCursor(0, 0);
+                lcd.printInt(n, 2);
+                n = (elapsed % 1000) / 100;
+                lcd.setFontSize(FONT_SIZE_MEDIUM);
+                lcd.setCursor(32, 1);
+                lcd.write('.');
+                lcd.write('0' + n);
+            }
+            if (times[2] == 0 && speed >= SPEED_THRESHOLD_3) {
+                times[2] = elapsed / 100;
+                stage = STAGE_IDLE;
+                lcd.clearLine(0);
+                lcd.clearLine(1);
+                lcd.clearLine(2);
+                showTimerResults();
+                lcd.setFontSize(FONT_SIZE_MEDIUM);
+                lcd.setCursor(0, 0);
+                lcd.print("DONE!");
+            } else if (times[1] == 0 && speed >= SPEED_THRESHOLD_2) {
+                times[1] = elapsed / 100;
+                showTimerResults();
+            } else if (times[0] == 0 && speed >= SPEED_THRESHOLD_1) {
+                times[0] = elapsed / 100;
+                showTimerResults();
+            } else if (speed == 0) {
+                // speed go back to 0
+                stage = STAGE_IDLE;
+            }
+            if (distance > 0) {
+                lcd.setFontSize(FONT_SIZE_SMALL);
+                lcd.setCursor(62, 6);
+                if (distance >= 400) {
+                    lcd.printInt(400, 3);
+                    if (!times[3]) {
+                        times[3] = elapsed / 100;
+                        showTimerResults();
+                    }
+                } else {
+                    lcd.printInt(distance, 3);
+                }
+            }
+            // log speed data
+            logData(0x100 | PID_SPEED, speed);
+            // log additional data
+            int rpm;
+            if (read(PID_RPM, rpm)) {
+                dataTime = millis();
+                logData(0x100 | PID_RPM, rpm);
+            }
+        } else {
+            if (speed == 0) {
+                stage = STAGE_WAIT_START;
+                initTimerScreen();
+                lcd.setFontSize(FONT_SIZE_MEDIUM);
+                lcd.setCursor(0, 0);
+                lcd.println(" GET");
+                lcd.println("READY");
+                delay(500);
+            }
+        }
+    }
+    void reconnect()
+    {
+#if ENABLE_DATA_LOG
+        closeFile();
+#endif
+        lcd.clear();
+        lcd.setFontSize(FONT_SIZE_MEDIUM);
+        lcd.print("Reconnecting");
+        startTime = millis();
+        state &= ~STATE_OBD_READY;
+        state |= STATE_SLEEPING;
+        //digitalWrite(SD_CS_PIN, LOW);
+        for (int i = 0; !init(); i++) {
+            if (i == 10) lcd.clear();
+        }
+        state &= ~STATE_SLEEPING;
+        fileIndex++;
+        setup();
+    }
+    byte state;
+
+    // screen layout related stuff
+    void showStates()
+    {
+        lcd.setFontSize(FONT_SIZE_SMALL);
+        lcd.setCursor(0, 3);
+        if (state & STATE_OBD_READY) {
+            lcd.println("OBD connected!   ");
+        } else {
+            lcd.println("Connecting OBD...");
+        }
+    }
+    void showTimerResults()
+    {
+        lcd.setFontSize(FONT_SIZE_SMALL);
+        lcd.setCursor(56, 0);
+        lcd.print(" 0~60:  --");
+        lcd.setCursor(56, 2);
+        lcd.print("0~100:  --");
+        lcd.setCursor(56, 4);
+        lcd.print("0~200:  --");
+        lcd.setCursor(56, 6);
+        lcd.print(" 400m:  --");
+        lcd.setFontSize(FONT_SIZE_MEDIUM);
+        char buf[8];
+        if (times[0]) {
+            sprintf(buf, "%2d.%1d", times[0] / 10, times[0] % 10);
+            Serial.println(times[0]);
+            lcd.setCursor(92, 0);
+            lcd.print(buf);
+        }
+        if (times[1]) {
+            sprintf(buf, "%2d.%1d", times[1] / 10, times[1] % 10);
+            Serial.println(buf);
+            lcd.setCursor(92, 2);
+            lcd.print(buf);
+        }
+        if (times[2]) {
+            sprintf(buf, "%2d.%1d", times[2] / 10, times[2] % 10);
+            Serial.println(buf);
+            lcd.setCursor(92, 4);
+            lcd.print(buf);
+        }
+        if (times[3]) {
+            sprintf(buf, "%2d.%1d", times[3] / 10, times[3] % 10);
+            Serial.println(buf);
+            lcd.setCursor(92, 6);
+            lcd.print(buf);
+        }
+    }
+    void initTimerScreen()
+    {
+        lcd.clear();
+        showTimerResults();
+        lcd.setFontSize(FONT_SIZE_SMALL);
+        lcd.setCursor(24, 7);
+        lcd.print("km/h");
+    }
+};
+
+static COBDLogger logger;
+
+void setup()
+{
+#ifdef DEBUG
+    DEBUG.begin(DEBUG_BAUDRATE);
+#endif
+
+    lcd.begin();
+    lcd.setFontSize(FONT_SIZE_MEDIUM);
+    lcd.println("PerformanceBox");
+
+    logger.begin();
+    logger.initSender();
+
+#if ENABLE_DATA_LOG
+    logger.checkSD();
+#endif
+    logger.setup();
+}
+
+void loop()
+{
+    logger.loop();
+}
diff --git a/pbox/README.txt b/pbox/README.txt
deleted file mode 100644
index 217971f..0000000
--- a/pbox/README.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This is the source code for the Arduino OBD-II data logger, which displays (on a 128x64 OLED display module) and records (to a SD card) a selected set of OBD-II data. For hardware configuration and wiring guide, please refer to:
-http://obd.arduinodev.com
-
-The recorded data is stored in CSV format and the file can be illustrated into a graphic chart by a free service at:
-http://freematics.com/chart/
-
-To compile the code with Arduino IDE, please copy all library files from the libraries directory to Arduino's libraries directory.
-
-To open the project file (obdlogger.cbp), please download CodeBlocks Arduino Edition (http://arduinodev.com/codeblocks).
-
-The source code is distributed under GPL license.
diff --git a/pbox/config.h b/pbox/config.h
deleted file mode 100644
index bb026a4..0000000
--- a/pbox/config.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef CONFIG_H_INCLUDED
-#define CONFIG_H_INCLUDED
-
-/**************************************
-* Data logging/streaming out
-**************************************/
-#define ENABLE_DATA_OUT 0
-#define ENABLE_DATA_LOG 1
-#define USE_SOFTSERIAL 0
-//this defines the format of log file
-#define LOG_FORMAT FORMAT_CSV
-
-/**************************************
-* Default working mode
-**************************************/
-#define MODE_DEFAULT MODE_LOGGER /* MODE_LOGGER/MODE_TIMER */
-//#define MODE_SWITCH_PIN 8
-
-/**************************************
-* Choose SD pin here
-**************************************/
-//#define SD_CS_PIN SS // generic
-//#define SD_CS_PIN 4 // ethernet shield
-//#define SD_CS_PIN 7 // microduino
-#define SD_CS_PIN 10 // SD breakout
-
-/**************************************
-* Config GPS here
-**************************************/
-#define USE_GPS 0
-#define GPS_BAUDRATE 38400 /* bps */
-//#define GPS_OPEN_BAUDRATE 4800 /* bps */
-
-/**************************************
-* Choose LCD model here
-**************************************/
-LCD_ILI9341 lcd;
-//LCD_SSD1306 lcd;
-//LCD_Null lcd;
-
-/**************************************
-* Other options
-**************************************/
-#define USE_MPU6050 0
-//#define OBD_MIN_INTERVAL 50 /* ms */
-#define GPS_DATA_TIMEOUT 2000 /* ms */
-//#define DEBUG Serial
-#define DEBUG_BAUDRATE 9600
-
-#endif // CONFIG_H_INCLUDED
diff --git a/pbox/datalogger.h b/pbox/datalogger.h
deleted file mode 100644
index 2c4ebfe..0000000
--- a/pbox/datalogger.h
+++ /dev/null
@@ -1,377 +0,0 @@
-typedef enum {
-    LOG_TYPE_DEFAULT = 0,
-    LOG_TYPE_0_60,
-    LOG_TYPE_0_100,
-    LOG_TYPE_100_200,
-    LOG_TYPE_400M,
-    LOG_TYPE_LAPS,
-    LOG_TYPE_ROUTE,
-} LOG_TYPES;
-
-#define FLAG_CAR 0x1
-#define FLAG_CYCLING 0x2
-#define FLAG_OBD 0x10
-#define FLAG_GPS 0x20
-#define FLAG_ACC 0x40
-
-#define FORMAT_BIN 0
-#define FORMAT_CSV 1
-
-typedef struct {
-    uint32_t time;
-    uint16_t pid;
-    uint8_t flags;
-    uint8_t checksum;
-    float value;
-} LOG_DATA;
-
-typedef struct {
-    uint32_t time;
-    uint16_t pid;
-    uint8_t flags;
-    uint8_t checksum;
-    float value[3];
-} LOG_DATA_COMM;
-
-typedef struct {
-    uint32_t time; /* e.g. 1307281259 */
-    uint16_t pid;
-    uint8_t message;
-    uint8_t checksum;
-    uint16_t fileIndex;
-    uint16_t fileSize; /* KB */
-    uint16_t logFlags;
-    uint8_t logType;
-    uint8_t data[5];
-} LOG_DATA_FILE_INFO;
-
-typedef struct {
-    uint32_t time;
-    uint16_t pid;
-    uint8_t message;
-    uint8_t checksum;
-    uint8_t data[12];
-} LOG_DATA_COMMAND;
-
-typedef struct {
-    uint32_t id;
-    uint32_t dataOffset;
-    uint8_t ver;
-    uint8_t logType;
-    uint16_t flags;
-    uint32_t dateTime; //4, YYMMDDHHMM, e.g. 1305291359
-    /*
-    uint8_t devid[8];
-    uint8_t vin[24];
-    uint8_t unused[84];
-    */
-} HEADER;
-
-#define HEADER_LEN 128 /* bytes */
-
-#define PID_GPS_COORDINATES 0xF00A
-#define PID_GPS_ALTITUDE 0xF00C
-#define PID_GPS_SPEED 0xF00D
-#define PID_GPS_HEADING 0xF00E
-#define PID_GPS_SAT_COUNT 0xF00F
-#define PID_GPS_TIME 0xF010
-
-#define PID_ACC 0xF020
-#define PID_GYRO 0xF021
-
-#define PID_MESSAGE 0xFE00
-#define PID_HEART_BEAT 0xFFEE
-
-#define MSG_FILE_LIST_BEGIN 0x1
-#define MSG_FILE_LIST_END 0x2
-#define MSG_FILE_INFO 0x3
-#define MSG_FILE_REQUEST 0x4
-
-#if LOG_FORMAT == FORMAT_BIN
-#define FILE_NAME_FORMAT "/DAT%05d.LOG"
-#else
-#define FILE_NAME_FORMAT "/DAT%05d.CSV"
-#endif
-
-#if ENABLE_DATA_OUT && !USE_OBD_BT
-#if defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
-    SoftwareSerial mySerial(A8, A9); /* for BLE Shield on MEGA*/
-#elif defined(__AVR_ATmega644P__)
-    SoftwareSerial mySerial(9, 10); /* for Microduino */
-#else
-    SoftwareSerial mySerial(A2, A3); /* for BLE Shield on UNO*/
-#endif
-#endif
-
-void btInit(int baudrate);
-void btSend(byte* data, byte length);
-
-class CDataLogger {
-public:
-    void initSender()
-    {
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btInit(9600);
-#else
-        mySerial.begin(9600);
-#endif
-#endif
-#if ENABLE_DATA_LOG && LOG_FORMAT == FORMAT_CSV
-        m_lastDataTime = 0;
-#endif
-    }
-#if ENABLE_DATA_OUT
-    void sendFileInfo(File& file)
-    {
-        if (file.size() < HEADER_LEN) return;
-
-        LOG_DATA_FILE_INFO info = {0};
-        info.fileIndex = atol(file.name() + 3);
-        if (info.fileIndex == 0) return;
-
-        HEADER hdr;
-        if (file.readBytes((char*)&hdr, sizeof(hdr)) != sizeof(hdr)) return;
-
-        info.pid = PID_MESSAGE;
-        info.message = MSG_FILE_INFO;
-        info.fileSize = file.size();
-        info.time = hdr.dateTime;
-        info.logType = hdr.logType;
-        info.logFlags = hdr.flags;
-        info.checksum = getChecksum((char*)&info, sizeof(info));
-#if USE_OBD_BT
-        btSend((uint8_t*)&info, sizeof(info));
-#else
-        mySerial.write((uint8_t*)&info, sizeof(info));
-#endif
-    }
-    void sendCommand(byte message, void* data = 0, byte bytes = 0)
-    {
-        LOG_DATA_COMMAND msg = {0, PID_MESSAGE, message};
-        if (data) memcpy(msg.data, data, bytes);
-        msg.checksum = getChecksum((char*)&msg, sizeof(msg));
-#if USE_OBD_BT
-        btSend((uint8_t*)&msg, sizeof(msg));
-#else
-        mySerial.write((uint8_t*)&msg, sizeof(msg));
-#endif
-    }
-    bool receiveCommand(LOG_DATA_COMMAND& msg)
-    {
-#if !USE_OBD_BT
-        if (!mySerial.available())
-            return false;
-
-        if (mySerial.readBytes((char*)&msg, sizeof(msg)) != sizeof(msg))
-            return false;
-
-        uint8_t checksum = msg.checksum;
-        msg.checksum = 0;
-        if (getChecksum((char*)&msg, sizeof(msg)) != msg.checksum) {
-            return false;
-        }
-#endif
-        return true;
-    }
-#endif
-    void logData(uint16_t pid, int value)
-    {
-        LOG_DATA_COMM ld = {dataTime, pid, 1, 0, value};
-        ld.checksum = getChecksum((char*)&ld, 12);
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btSend((uint8_t*)&ld, 12);
-#else
-        mySerial.write((uint8_t*)&ld, 12);
-#endif
-#endif
-#if ENABLE_DATA_LOG
-#if LOG_FORMAT == FORMAT_BIN
-        sdfile.write((uint8_t*)&ld, 12);
-        dataSize += 12;
-#else
-        dataSize += sdfile.print(dataTime - m_lastDataTime);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(pid, HEX);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value);
-        dataSize += sdfile.write('\n');
-        m_lastDataTime = dataTime;
-#endif
-#endif
-    }
-    void logData(uint16_t pid, float value)
-    {
-        LOG_DATA_COMM ld = {dataTime, pid, 1, 0, value};
-        ld.checksum = getChecksum((char*)&ld, 12);
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btSend((uint8_t*)&ld, 12);
-#else
-        mySerial.write((uint8_t*)&ld, 12);
-#endif
-#endif
-#if ENABLE_DATA_LOG
-#if LOG_FORMAT == FORMAT_BIN
-        sdfile.write((uint8_t*)&ld, 12);
-        dataSize += 12;
-#else
-        dataSize += sdfile.print(dataTime - m_lastDataTime);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(pid, HEX);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value);
-        dataSize += sdfile.write('\n');
-        m_lastDataTime = dataTime;
-#endif
-#endif
-    }
-    void logData(uint16_t pid, float value1, float value2)
-    {
-        LOG_DATA_COMM ld = {dataTime, pid, 2, 0, {value1, value2}};
-        ld.checksum = getChecksum((char*)&ld, 16);
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btSend((uint8_t*)&ld, 16);
-#else
-        mySerial.write((uint8_t*)&ld, 16);
-#endif
-#endif
-#if ENABLE_DATA_LOG
-#if LOG_FORMAT == FORMAT_BIN
-        sdfile.write((uint8_t*)&ld, 16);
-        dataSize += 16;
-#else
-        dataSize += sdfile.print(dataTime - m_lastDataTime);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(pid, HEX);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value1, 6);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value2, 6);
-        dataSize += sdfile.write('\n');
-        m_lastDataTime = dataTime;
-#endif
-#endif
-    }
-    void logData(uint16_t pid, uint32_t value1, uint32_t value2)
-    {
-        LOG_DATA_COMM ld = {dataTime, pid, 2, 0, {value1, value2}};
-        ld.checksum = getChecksum((char*)&ld, 16);
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btSend((uint8_t*)&ld, 16);
-#else
-        mySerial.write((uint8_t*)&ld, 16);
-#endif
-#endif
-#if ENABLE_DATA_LOG
-#if LOG_FORMAT == FORMAT_BIN
-        sdfile.write((uint8_t*)&ld, 16);
-        dataSize += 16;
-#else
-        dataSize += sdfile.print(dataTime - m_lastDataTime);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(pid, HEX);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value1);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value2);
-        dataSize += sdfile.write('\n');
-        m_lastDataTime = dataTime;
-#endif
-#endif
-    }
-    void logData(uint16_t pid, int value1, int value2, int value3)
-    {
-        LOG_DATA_COMM ld = {dataTime, pid, 3, 0, {value1, value2, value3}};
-        ld.checksum = getChecksum((char*)&ld, 20);
-#if ENABLE_DATA_OUT
-#if USE_OBD_BT
-        btSend((uint8_t*)&ld, 20);
-#else
-        mySerial.write((uint8_t*)&ld, 20);
-#endif
-#endif
-#if ENABLE_DATA_LOG
-#if LOG_FORMAT == FORMAT_BIN
-        sdfile.write((uint8_t*)&ld, 20);
-        dataSize += 20;
-#else
-        dataSize += sdfile.print(dataTime - m_lastDataTime);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(pid, HEX);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value1);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value2);
-        dataSize += sdfile.write(',');
-        dataSize += sdfile.print(value3);
-        dataSize += sdfile.write('\n');
-        m_lastDataTime = dataTime;
-#endif
-#endif
-    }
-#if ENABLE_DATA_LOG
-    uint16_t openFile(LOG_TYPES logType, uint16_t logFlags = 0, uint32_t dateTime = 0)
-    {
-        uint16_t fileIndex;
-        char filename[24] = "/FRMATICS";
-
-        if (SD.exists(filename)) {
-            for (fileIndex = 1; fileIndex; fileIndex++) {
-                sprintf(filename + 9, FILE_NAME_FORMAT, fileIndex);
-                if (!SD.exists(filename)) {
-                    break;
-                }
-            }
-            if (fileIndex == 0)
-                return 0;
-        } else {
-            SD.mkdir(filename);
-            fileIndex = 1;
-            sprintf(filename + 9, FILE_NAME_FORMAT, 1);
-        }
-
-        sdfile = SD.open(filename, FILE_WRITE);
-        if (!sdfile) {
-            return 0;
-        }
-
-#if LOG_FORMAT == FORMAT_BIN
-        HEADER hdr = {'UDUS', HEADER_LEN, 1, logType, logFlags, dateTime};
-        sdfile.write((uint8_t*)&hdr, sizeof(hdr));
-        for (byte i = 0; i < HEADER_LEN - sizeof(hdr); i++)
-            sdfile.write((uint8_t)0);
-        dataSize = HEADER_LEN;
-#endif
-        return fileIndex;
-    }
-    void closeFile()
-    {
-        sdfile.close();
-    }
-    void flushFile()
-    {
-        sdfile.flush();
-    }
-#endif
-    uint32_t dataTime;
-    uint32_t dataSize;
-private:
-    static byte getChecksum(char* buffer, byte len)
-    {
-        uint8_t checksum = 0;
-        for (byte i = 0; i < len; i++) {
-          checksum ^= buffer[i];
-        }
-        return checksum;
-    }
-#if ENABLE_DATA_LOG
-    File sdfile;
-#if LOG_FORMAT == FORMAT_CSV
-    uint32_t m_lastDataTime;
-#endif
-#endif
-};
diff --git a/pbox/images.h b/pbox/images.h
deleted file mode 100644
index 0a0f0b6..0000000
--- a/pbox/images.h
+++ /dev/null
@@ -1,5 +0,0 @@
-const PROGMEM uint8_t tick[16 *16 / 8] =
-{0x00,0x80,0xC0,0xE0,0xC0,0x80,0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0x78,0x30,0x00,0x00,0x01,0x03,0x07,0x0F,0x1F,0x1F,0x1F,0x0F,0x07,0x03,0x01,0x00,0x00,0x00,0x00};
-
-const PROGMEM uint8_t cross[16 *16 / 8] =
-{0x00,0x0C,0x1C,0x3C,0x78,0xF0,0xE0,0xC0,0xE0,0xF0,0x78,0x3C,0x1C,0x0C,0x00,0x00,0x00,0x30,0x38,0x3C,0x1E,0x0F,0x07,0x03,0x07,0x0F,0x1E,0x3C,0x38,0x30,0x00,0x00};
diff --git a/pbox/unologger.cbp b/pbox/unologger.cbp
deleted file mode 100644
index 1058588..0000000
--- a/pbox/unologger.cbp
+++ /dev/null
@@ -1,600 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
-<CodeBlocks_project_file>
-	<FileVersion major="1" minor="6" />
-	<Project>
-		<Option title="obdlogger" />
-		<Option pch_mode="2" />
-		<Option compiler="avrgcc" />
-		<Build>
-			<Target title="Simulator">
-				<Option output="bin/Release/obdlogger_sim.exe" prefix_auto="1" extension_auto="0" />
-				<Option object_output="obj/Release/" />
-				<Option type="1" />
-				<Option compiler="GCC" />
-				<Compiler>
-					<Add option="-Os" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-DARDUSIM" />
-					<Add option="-D__AVR_ATmega2560__" />
-					<Add option="-x c++" />
-					<Add directory="$(ARDUINO_DIR)/arduino/cores" />
-					<Add directory="$(ARDUINO_DIR)/arduino/variants/standard" />
-					<Add directory="$(ARDUINO_DIR)/include" />
-				</Compiler>
-				<Linker>
-					<Add option="-lardusim" />
-				</Linker>
-				<Environment>
-					<Variable name="ARDUINO_DIR" value="$(APP_PATH)\ardusim" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Uno">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-Os" />
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Uno" />
-					<Variable name="BOARD_ID" value="uno" />
-					<Variable name="MCU" value="atmega328p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="115200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Leonardo">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega32U4__" />
-					<Add option="-DUSB_VID=0x2341" />
-					<Add option="-DUSB_PID=0x8036" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/leonardo" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Leonardo" />
-					<Variable name="BOARD_ID" value="leonardo" />
-					<Variable name="MCU" value="atmega32u4" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Esplora">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega32U4__" />
-					<Add option="-DUSB_VID=0x2341" />
-					<Add option="-DUSB_PID=0x8037" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/leonardo" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Esplora" />
-					<Variable name="BOARD_ID" value="esplora" />
-					<Variable name="MCU" value="atmega32u4" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Micro">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega32U4__" />
-					<Add option="-DUSB_VID=0x2341" />
-					<Add option="-DUSB_PID=0x803C" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/micro" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Micro" />
-					<Variable name="BOARD_ID" value="micro" />
-					<Variable name="MCU" value="atmega32u4" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Duemilanove (328)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Duemilanove (328)" />
-					<Variable name="BOARD_ID" value="duemilanove328" />
-					<Variable name="MCU" value="atmega328p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Duemilanove (168)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega168__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Duemilanove (168)" />
-					<Variable name="BOARD_ID" value="duemilanove168" />
-					<Variable name="MCU" value="atmega168" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="19200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Nano (328)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Nano (328)" />
-					<Variable name="BOARD_ID" value="nano328" />
-					<Variable name="MCU" value="atmega328p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Nano (168)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega168__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Nano (168)" />
-					<Variable name="BOARD_ID" value="nano168" />
-					<Variable name="MCU" value="atmega168" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="19200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Mini (328)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Mini (328)" />
-					<Variable name="BOARD_ID" value="mini328" />
-					<Variable name="MCU" value="atmega328p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Mini (168)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega168__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/eightanaloginputs" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Mini (168)" />
-					<Variable name="BOARD_ID" value="mini168" />
-					<Variable name="MCU" value="atmega168" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="19200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Pro Mini (328)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Pro Mini (328)" />
-					<Variable name="BOARD_ID" value="promini328" />
-					<Variable name="MCU" value="atmega328p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Pro Mini (168)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega168__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Pro Mini (168)" />
-					<Variable name="BOARD_ID" value="promini168" />
-					<Variable name="MCU" value="atmega168" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="19200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Mega 2560/ADK">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-O3" />
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega2560__" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/mega" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Mega 2560\ADK" />
-					<Variable name="BOARD_ID" value="mega2560" />
-					<Variable name="MCU" value="atmega2560" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="115200" />
-					<Variable name="UPLOAD_PORT" value="COM37" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Mega 1280">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega1280__" />
-					<Add option="-O2" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/mega" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Mega 1280" />
-					<Variable name="BOARD_ID" value="mega1280" />
-					<Variable name="MCU" value="atmega1280" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="57600" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Arduino Mega 8">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega328P__" />
-					<Add option="-Os" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/standard" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Arduino Mega 8" />
-					<Variable name="BOARD_ID" value="mega8" />
-					<Variable name="MCU" value="atmega8" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="19200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-			<Target title="Microduino Core+ (644P)">
-				<Option output="bin/Release/obdlogger_${BOARD_ID}.elf" prefix_auto="1" extension_auto="0" />
-				<Option type="1" />
-				<Option compiler="avrgcc" />
-				<Compiler>
-					<Add option="-O3" />
-					<Add option="-x c++" />
-					<Add option="-mmcu=$(MCU)" />
-					<Add option="-DF_CPU=16000000L" />
-					<Add option="-D__AVR_ATmega644P__" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/cores/arduino" />
-					<Add directory="$(ARDUINO_DIR)/libraries" />
-					<Add directory="$(ARDUINO_DIR)/hardware/arduino/variants/plus" />
-				</Compiler>
-				<Linker>
-					<Add option="-mmcu=$(MCU)" />
-					<Add option='&quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add option='&quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-				</Linker>
-				<ExtraCommands>
-					<Add before='$(TARGET_COMPILER_DIR)ArduinoUploader &quot;$(PROJECT_DIR).&quot; $(BOARD_ID) - $(MCU_CLOCK) 0 &quot;$(PROJECT_DIR)bin/lib_${BOARD_ID}_${MCU_CLOCK}.a&quot; &quot;$(PROJECT_DIR)bin/core_${BOARD_ID}_${MCU_CLOCK}.a&quot;' />
-					<Add after='avr-objcopy -O ihex -R .eeprom -R .eesafe &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).hex&quot;' />
-					<Add after='avr-objcopy --no-change-warnings -j .eeprom --change-section-lma .eeprom=0 -O ihex &quot;$(TARGET_OUTPUT_FILE)&quot; &quot;$(TARGET_OUTPUT_FILE).eep.hex&quot;' />
-					<Add after='avr-size --mcu=$(MCU) --format=avr &quot;$(TARGET_OUTPUT_FILE)&quot;' />
-				</ExtraCommands>
-				<Environment>
-					<Variable name="BOARD" value="Microduino Core+ (644P)" />
-					<Variable name="BOARD_ID" value="uduino644p" />
-					<Variable name="MCU" value="atmega644p" />
-					<Variable name="MCU_CLOCK" value="16" />
-					<Variable name="UPLOAD_BAUDRATE" value="115200" />
-					<Variable name="UPLOAD_PORT" value="" />
-				</Environment>
-			</Target>
-		</Build>
-		<Compiler>
-			<Add directory="." />
-		</Compiler>
-		<Unit filename="MicroLCD.cpp" />
-		<Unit filename="MicroLCD.h" />
-		<Unit filename="SH1106.cpp" />
-		<Unit filename="config.h" />
-		<Unit filename="datalogger.h" />
-		<Unit filename="images.h" />
-		<Unit filename="obdlogger.ino">
-			<Option compile="1" />
-			<Option link="1" />
-		</Unit>
-		<Extensions>
-			<code_completion />
-			<debugger />
-		</Extensions>
-	</Project>
-</CodeBlocks_project_file>
diff --git a/pbox/unologger.ino b/pbox/unologger.ino
deleted file mode 100644
index fbca988..0000000
--- a/pbox/unologger.ino
+++ /dev/null
@@ -1,839 +0,0 @@
-/*************************************************************************
-* Arduino GPS/OBD-II/G-Force Data Logger
-* Distributed under GPL v2.0
-* Copyright (c) 2013 Stanley Huang <stanleyhuangyc@gmail.com>
-* All rights reserved.
-*************************************************************************/
-
-#include <Arduino.h>
-#include <Wire.h>
-#include <OBD.h>
-#include <SPI.h>
-#include <SD.h>
-#include <MPU6050.h>
-#include "MultiLCD.h"
-#include "images.h"
-#include "config.h"
-#if USE_SOFTSERIAL
-#include <SoftwareSerial.h>
-#endif
-#include "datalogger.h"
-
-// logger states
-#define STATE_SD_READY 0x1
-#define STATE_OBD_READY 0x2
-#define STATE_GPS_FOUND 0x4
-#define STATE_GPS_READY 0x8
-#define STATE_ACC_READY 0x10
-#define STATE_SLEEPING 0x20
-
-#define MODE_LOGGER 0
-#define MODE_TIMER 1
-
-#if USE_GPS
-// GPS logging can only be enabled when there is additional hardware serial UART
-#if defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
-#define GPSUART Serial2
-#elif defined(__AVR_ATmega644P__)
-#define GPSUART Serial
-#endif
-
-#ifdef GPSUART
-
-#include <TinyGPS.h>
-
-#define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F"
-#define PMTK_SET_NMEA_UPDATE_5HZ "$PMTK220,200*2C"
-#define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F"
-#define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28"
-#define PMTK_SET_BAUDRATE "$PMTK251,115200*1F"
-
-TinyGPS gps;
-
-static long lastLat = 0;
-static long lastLon = 0;
-static long curLat = 0;
-static long curLon = 0;
-static uint32_t lastGPSDataTime = 0;
-static uint16_t speedGPS = 0;
-
-#endif // GPSUART
-#endif
-
-static uint32_t lastFileSize = 0;
-static int lastSpeed = -1;
-static uint32_t lastSpeedTime = 0;
-static int speed = 0;
-static uint32_t distance = 0;
-static uint16_t fileIndex = 0;
-static uint32_t startTime = 0;
-
-#define STAGE_IDLE 0
-#define STAGE_WAIT_START 1
-#define STAGE_MEASURING 2
-
-static byte mode = MODE_DEFAULT;
-static byte stage = STAGE_IDLE;
-
-#define SPEED_THRESHOLD_1 60 /* kph */
-#define SPEED_THRESHOLD_2 100 /* kph */
-#define SPEED_THRESHOLD_3 200 /* kph */
-#define DISTANCE_THRESHOLD 400 /* meters */
-
-static uint16_t times[4] = {0};
-
-static byte pidTier1[]= {PID_RPM, PID_SPEED, PID_ENGINE_LOAD, PID_THROTTLE};
-static byte pidTier2[] = {PID_INTAKE_MAP, PID_MAF_FLOW, PID_TIMING_ADVANCE};
-static byte pidTier3[] = {PID_COOLANT_TEMP, PID_INTAKE_TEMP, PID_AMBIENT_TEMP, PID_FUEL_LEVEL};
-
-#define TIER_NUM1 sizeof(pidTier1)
-#define TIER_NUM2 sizeof(pidTier2)
-#define TIER_NUM3 sizeof(pidTier3)
-
-class COBDLogger : public COBD, public CDataLogger
-{
-public:
-    COBDLogger():state(0) {}
-    void setup()
-    {
-#if USE_GPS
-        lastGPSDataTime = 0;
-#endif
-        showStates();
-
-#if USE_MPU6050
-        Wire.begin();
-        if (MPU6050_init() == 0) {
-            state |= STATE_ACC_READY;
-            showStates();
-        }
-#endif
-
-#ifdef GPSUART
-        unsigned long t = millis();
-        do {
-            if (GPSUART.available()) {
-                char c = GPSUART.read();
-                if (c == '\r') {
-                    state |= STATE_GPS_FOUND;
-                    break;
-                }
-            }
-        } while (millis() - t <= 5000);
-#endif
-
-        do {
-            showStates();
-        } while (!init());
-
-        state |= STATE_OBD_READY;
-
-        showStates();
-
-        uint16_t flags = FLAG_CAR | FLAG_OBD;
-        if (state & STATE_GPS_FOUND) flags |= FLAG_GPS;
-        if (state & STATE_ACC_READY) flags |= FLAG_ACC;
-#if ENABLE_DATA_LOG
-        uint16_t index = openFile(LOG_TYPE_DEFAULT, flags);
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(86, 0);
-        if (index) {
-            lcd.write('[');
-            lcd.setFlags(FLAG_PAD_ZERO);
-            lcd.printInt(index, 5);
-            lcd.setFlags(0);
-            lcd.write(']');
-        } else {
-            lcd.print("NO LOG");
-        }
-        delay(100);
-#endif
-
-#ifndef MEMORY_SAVING
-        //showECUCap();
-        //delay(1000);
-#endif
-
-#if ENABLE_DATA_LOG
-        // open file for logging
-        if (!(state & STATE_SD_READY)) {
-            if (checkSD()) {
-                state |= STATE_SD_READY;
-                showStates();
-            }
-        }
-#endif
-
-        initScreen();
-    }
-    void loop()
-    {
-        static byte index = 0;
-        static byte index2 = 0;
-        static byte index3 = 0;
-
-#ifdef GPSUART
-        if (millis() - lastGPSDataTime > GPS_DATA_TIMEOUT || gps.satellites() < 3) {
-            // GPS not ready
-            state &= ~STATE_GPS_READY;
-        } else {
-            // GPS ready
-            state |= STATE_GPS_READY;
-        }
-#endif
-
-
-        if (mode == MODE_TIMER) {
-            timerLoop();
-        } else {
-            logOBDData(pidTier1[index++]);
-            if (index == TIER_NUM1) {
-                index = 0;
-                if (index2 == TIER_NUM2) {
-                    index2 = 0;
-                    logOBDData(pidTier3[index3]);
-                    index3 = (index3 + 1) % TIER_NUM3;
-                } else {
-                    logOBDData(pidTier2[index2++]);
-                }
-            }
-
-            char buf[10];
-            sprintf(buf, "%4ukm", (uint16_t)(distance / 1000));
-            lcd.setFont(FONT_SIZE_SMALL);
-            lcd.setCursor(92, 6);
-            lcd.print(buf);
-
-#if USE_MPU6050
-            if (state & STATE_ACC_READY) {
-                processAccelerometer();
-            }
-#endif
-        }
-
-#if ENABLE_DATA_LOG
-        // flush SD data every 1KB
-        if (dataSize - lastFileSize >= 1024 && stage != STAGE_MEASURING) {
-            flushFile();
-            lastFileSize = dataSize;
-            // display logged data size
-            if (mode == MODE_LOGGER) {
-                char buf[7];
-                sprintf(buf, "%4uKB", (int)(dataSize >> 10));
-                lcd.setFont(FONT_SIZE_SMALL);
-                lcd.setCursor(92, 7);
-                lcd.print(buf);
-            }
-        }
-#endif
-
-        if (errors >= 2) {
-            reconnect();
-        }
-    }
-#if ENABLE_DATA_LOG
-    bool checkSD()
-    {
-        Sd2Card card;
-        SdVolume volume;
-        lcd.setCursor(0, 0);
-        lcd.setFont(FONT_SIZE_MEDIUM);
-        state &= ~STATE_SD_READY;
-        pinMode(SS, OUTPUT);
-        if (card.init(SPI_HALF_SPEED, SD_CS_PIN)) {
-            const char* type;
-            char buf[20];
-
-            switch(card.type()) {
-            case SD_CARD_TYPE_SD1:
-                type = "SD1";
-                break;
-            case SD_CARD_TYPE_SD2:
-                type = "SD2";
-                break;
-            case SD_CARD_TYPE_SDHC:
-                type = "SDHC";
-                break;
-            default:
-                type = "SDx";
-            }
-
-            lcd.clear();
-            lcd.print(type);
-            lcd.write(' ');
-            if (!volume.init(card)) {
-                lcd.print("No FAT!");
-                return false;
-            }
-
-            uint32_t volumesize = volume.blocksPerCluster();
-            volumesize >>= 1; // 512 bytes per block
-            volumesize *= volume.clusterCount();
-            volumesize >>= 10;
-
-            sprintf(buf, "%dGB", (int)((volumesize + 511) / 1000));
-            lcd.print(buf);
-        } else {
-            lcd.clear();
-            lcd.print("SD");
-            lcd.draw(cross, 32, 0, 16, 16);
-            return false;
-        }
-
-        if (!SD.begin(SD_CS_PIN)) {
-            lcd.setCursor(48, 0);
-            lcd.print("Bad SD");
-            return false;
-        }
-
-        state |= STATE_SD_READY;
-        return true;
-    }
-#endif
-    void initScreen()
-    {
-        if (mode == MODE_LOGGER)
-            initLoggerScreen();
-        else
-            initTimerScreen();
-    }
-private:
-    void dataIdleLoop()
-    {
-        if (state & STATE_SLEEPING) return;
-
-#ifdef GPSUART
-        // detect GPS signal
-        if (GPSUART.available())
-            processGPS();
-
-        if (lastGPSDataTime) {
-            state |= STATE_GPS_READY;
-        }
-#endif
-
-        if (getState() == OBD_CONNECTED)
-            return;
-
-        // called while initializing
-        char buf[10];
-        unsigned int t = (millis() - startTime) / 1000;
-        sprintf(buf, "%02u:%02u", t / 60, t % 60);
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(97, 7);
-        lcd.print(buf);
-#if USE_MPU6050
-        if (state & STATE_ACC_READY) {
-            accel_t_gyro_union data;
-            MPU6050_readout(&data);
-            char buf[8];
-            lcd.setFont(FONT_SIZE_SMALL);
-            int temp = (data.value.temperature + 12412) / 340;
-            sprintf(buf, "TEMP%3dC", temp);
-            lcd.setCursor(80, 2);
-            lcd.print(buf);
-
-            sprintf(buf, "AX%3d", data.value.x_accel / 160);
-            lcd.setCursor(98, 3);
-            lcd.print(buf);
-            sprintf(buf, "AY%3d", data.value.y_accel / 160);
-            lcd.setCursor(98, 4);
-            lcd.print(buf);
-            sprintf(buf, "AZ%3d", data.value.z_accel / 160);
-            lcd.setCursor(98, 5);
-            lcd.print(buf);
-
-            sprintf(buf, "GX%3d", data.value.x_gyro / 256);
-            lcd.setCursor(64, 3);
-            lcd.print(buf);
-            sprintf(buf, "GY%3d", data.value.y_gyro / 256);
-            lcd.setCursor(64, 4);
-            lcd.print(buf);
-            sprintf(buf, "GZ%3d", data.value.z_gyro / 256);
-            lcd.setCursor(64, 5);
-            lcd.print(buf);
-            //delay(50);
-        }
-#endif
-    }
-#ifdef GPSUART
-    void processGPS()
-    {
-        // process GPS data
-        char c = GPSUART.read();
-        if (!gps.encode(c))
-            return;
-
-        // parsed GPS data is ready
-        static uint32_t lastAltTime = 0;
-
-        dataTime = millis();
-
-        speedGPS = (uint16_t)(gps.speed() * 1852 / 100);
-        if (speedGPS > 1000) speedGPS = 0;
-        logData(PID_GPS_SPEED, speedGPS);
-
-        // no need to log GPS data when vehicle has not been moving
-        // that's when previous speed is zero and current speed is also zero
-        byte sat = gps.satellites();
-        if (sat >= 3 && sat < 100) {
-            // lastSpeed will be updated
-            //ShowSensorData(PID_SPEED, speed);
-
-            gps.get_position(&curLat, &curLon, 0);
-
-            // calclate distance
-            if (lastLat) {
-                int16_t latDiff = curLat - lastLat;
-                int16_t lonDiff = curLon - lastLon;
-                distance = sqrt(latDiff * latDiff + lonDiff * lonDiff);
-            }
-
-            logData(PID_GPS_COORDINATES, (float)curLat / 100000, (float)curLon / 100000);
-
-            if (dataTime - lastAltTime > 10000) {
-                uint32_t time;
-                uint32_t date;
-                gps.get_datetime(&date, &time, 0);
-                logData(PID_GPS_TIME, time, date);
-                logData(PID_GPS_ALTITUDE, (float)gps.altitude());
-            }
-
-            if (mode == MODE_LOGGER) {
-                lcd.setFont(FONT_SIZE_SMALL);
-                if (((unsigned int)dataTime >> 11) & 1) {
-                    char buf[16];
-                    sprintf(buf, "LAT:%d.%05ld  ", (int)(curLat / 100000), curLat % 100000);
-                    lcd.setCursor(0, 6);
-                    lcd.print(buf);
-                    sprintf(buf, "LON:%d.%05ld  ", (int)(curLon / 100000), curLon % 100000);
-                    lcd.setCursor(0, 7);
-                    lcd.print(buf);
-                } else {
-                    char buf[16];
-                    lcd.setCursor(0, 6);
-                    sprintf(buf, "SAT:%u  ", (unsigned int)sat);
-                    lcd.print(buf);
-                    lcd.setCursor(0, 7);
-                    uint32_t time;
-                    gps.get_datetime(0, &time, 0);
-                    sprintf(buf, "TIME:%08ld ", time);
-                    lcd.print(buf);
-                }
-            }
-        }
-        lastGPSDataTime = dataTime;
-    }
-#endif
-#if USE_MPU6050
-    void processAccelerometer()
-    {
-        accel_t_gyro_union data;
-        MPU6050_readout(&data);
-        dataTime = millis();
-        // log x/y/z of accelerometer
-        logData(PID_ACC, data.value.x_accel, data.value.y_accel, data.value.z_accel);
-        //showGForce(data.value.y_accel);
-        // log x/y/z of gyro meter
-        logData(PID_GYRO, data.value.x_gyro, data.value.y_gyro, data.value.z_gyro);
-
-#ifdef DEBUG
-        DEBUG.print(dataTime);
-        DEBUG.print(" X:");
-        DEBUG.print(data.value.x_accel);
-        DEBUG.print(" Y:");
-        DEBUG.print(data.value.y_accel);
-        DEBUG.print(" Z:");
-        DEBUG.println(data.value.z_accel);
-#endif
-    }
-#endif
-    void logOBDData(byte pid)
-    {
-        int value;
-#ifdef OBD_MIN_INTERVAL
-        uint32_t start = millis();
-#endif
-
-        // send a query to OBD adapter for specified OBD-II pid
-        if (read(pid, value)) {
-            dataTime = millis();
-            showLoggerData(pid, value);
-            // log data to SD card
-            logData(0x100 | pid, value);
-        }
-
-        // if OBD response is very fast, go on processing other data for a while
-#ifdef OBD_MIN_INTERVAL
-        while (millis() - start < OBD_MIN_INTERVAL) {
-            dataIdleLoop();
-        }
-#endif
-    }
-    void timerLoop()
-    {
-        uint32_t elapsed = millis() - startTime;
-        uint16_t n;
-
-        int speed;
-        if (!read(PID_SPEED, speed))
-            return;
-
-        dataTime = millis();
-
-        lcd.setFont(FONT_SIZE_XLARGE);
-        if (lastSpeed != speed) {
-            lcd.setCursor(0, 4);
-            lcd.printInt((unsigned int)speed % 1000, 3);
-            lastSpeed = speed;
-        }
-
-        if (!(state & STATE_GPS_READY)) {
-            // estimate distance
-           distance += (uint32_t)(speed + lastSpeed) * (dataTime - lastSpeedTime) / 2 / 3600;
-        }
-        lastSpeedTime = dataTime;
-
-        if (stage == STAGE_WAIT_START) {
-            if (speed > 0) {
-                stage = STAGE_MEASURING;
-                startTime = lastSpeedTime;
-
-                uint32_t t = dataTime;
-                dataTime = lastSpeedTime;
-                logData(0x100 | PID_SPEED, lastSpeed);
-                dataTime = t;
-                logData(0x100 | PID_SPEED, speed);
-
-#if USE_GPS
-                lastLat = curLat;
-                lastLon = curLon;
-#endif
-                lastSpeed = 0;
-                distance = 0;
-
-                memset(times, 0, sizeof(times));
-
-                initTimerScreen();
-            }
-        } else if (stage == STAGE_MEASURING) {
-            // display elapsed time (mm:ss:mm)
-            n = elapsed / 1000;
-            if (n < 100) {
-                lcd.setCursor(0, 0);
-                lcd.printInt(n, 2);
-                n = (elapsed % 1000) / 100;
-                lcd.setFont(FONT_SIZE_MEDIUM);
-                lcd.setCursor(32, 1);
-                lcd.write('.');
-                lcd.write('0' + n);
-            }
-#if USE_GPS
-            if (speedGPS > speed) speed = speedGPS;
-#endif
-            if (times[2] == 0 && speed >= SPEED_THRESHOLD_3) {
-                times[2] = elapsed / 100;
-                stage = STAGE_IDLE;
-                lcd.clearLine(0);
-                lcd.clearLine(1);
-                lcd.clearLine(2);
-                showTimerResults();
-                lcd.setFont(FONT_SIZE_MEDIUM);
-                lcd.setCursor(0, 0);
-                lcd.print("DONE!");
-            } else if (times[1] == 0 && speed >= SPEED_THRESHOLD_2) {
-                times[1] = elapsed / 100;
-                showTimerResults();
-            } else if (times[0] == 0 && speed >= SPEED_THRESHOLD_1) {
-                times[0] = elapsed / 100;
-                showTimerResults();
-            } else if (speed == 0) {
-                // speed go back to 0
-                stage = STAGE_IDLE;
-            }
-            if (distance > 0) {
-                lcd.setFont(FONT_SIZE_SMALL);
-                lcd.setCursor(62, 6);
-                if (distance >= 400) {
-                    lcd.printInt(400, 3);
-                    if (!times[3]) {
-                        times[3] = elapsed / 100;
-                        showTimerResults();
-                    }
-                } else {
-                    lcd.printInt(distance, 3);
-                }
-            }
-            // log speed data
-            logData(0x100 | PID_SPEED, speed);
-            // log additional data
-            int rpm;
-            if (read(PID_RPM, rpm)) {
-                dataTime = millis();
-                logData(0x100 | PID_RPM, rpm);
-            }
-        } else {
-            if (speed == 0) {
-                stage = STAGE_WAIT_START;
-                initTimerScreen();
-                lcd.setFont(FONT_SIZE_MEDIUM);
-                lcd.setCursor(0, 0);
-                lcd.println(" GET");
-                lcd.println("READY");
-                delay(500);
-            }
-        }
-    }
-#ifndef MEMORY_SAVING
-    void showECUCap()
-    {
-        char buffer[24];
-        byte pidlist[] = {PID_RPM, PID_SPEED, PID_THROTTLE, PID_ENGINE_LOAD, PID_CONTROL_MODULE_VOLTAGE, PID_MAF_FLOW, PID_INTAKE_MAP, PID_FUEL_LEVEL, PID_FUEL_PRESSURE, PID_COOLANT_TEMP, PID_INTAKE_TEMP, PID_AMBIENT_TEMP, PID_TIMING_ADVANCE, PID_BAROMETRIC};
-        const char* namelist[] = {"RPM", "SPEED", "THROTTLE", "ENG.LOAD", "CTRL VOLT", "MAF", "MAP", "FUEL LV.", "FUEL PRE.", "COOLANT", "INTAKE","AMBIENT", "IGNITION", "BARO"};
-        byte i = 0;
-        lcd.clear();
-        lcd.setFont(FONT_SIZE_SMALL);
-        for (; i < sizeof(pidlist) / sizeof(pidlist[0]) / 2; i++) {
-            lcd.setCursor(0, i);
-            sprintf(buffer, "%s:%c", namelist[i], isValidPID(pidlist[i]) ? 'Y' : 'N');
-            lcd.print(buffer);
-        }
-        for (byte row = 0; i < sizeof(pidlist) / sizeof(pidlist[0]); i++, row++) {
-            lcd.setCursor(64, row);
-            sprintf(buffer, "%s:%c", namelist[i], isValidPID(pidlist[i]) ? 'Y' : 'N');
-            lcd.print(buffer);
-        }
-    }
-#endif
-    void reconnect()
-    {
-#if ENABLE_DATA_LOG
-        closeFile();
-#endif
-        lcd.clear();
-        lcd.setFont(FONT_SIZE_MEDIUM);
-        lcd.print("Reconnecting");
-        startTime = millis();
-        state &= ~(STATE_OBD_READY | STATE_ACC_READY);
-        state |= STATE_SLEEPING;
-        //digitalWrite(SD_CS_PIN, LOW);
-        for (int i = 0; !init(); i++) {
-            if (i == 10) lcd.clear();
-        }
-        state &= ~STATE_SLEEPING;
-        fileIndex++;
-        write('\r');
-        setup();
-    }
-    byte state;
-
-    // screen layout related stuff
-    void showStates()
-    {
-#ifdef DEBUG
-        DEBUG.print(millis());
-        DEBUG.print(" OBD:");
-        DEBUG.print((state & STATE_OBD_READY) ? 'Y' : 'N');
-        DEBUG.print(" ACC:");
-        DEBUG.print((state & STATE_ACC_READY) ? 'Y' : 'N');
-        DEBUG.print(" GPS:");
-        DEBUG.println((state & STATE_GPS_FOUND) ? 'Y' : 'N');
-#endif // DEBUG
-        lcd.setFont(FONT_SIZE_MEDIUM);
-        lcd.setCursor(0, 2);
-        lcd.print("OBD");
-        lcd.draw((state & STATE_OBD_READY) ? tick : cross, 32, 16, 16, 16);
-        lcd.setCursor(0, 4);
-        lcd.print("ACC");
-        lcd.draw((state & STATE_ACC_READY) ? tick : cross, 32, 32, 16, 16);
-        lcd.setCursor(0, 6);
-        if (!(state & STATE_GPS_READY)) {
-            lcd.print("GPS");
-            lcd.draw((state & STATE_GPS_FOUND) ? tick : cross, 32, 48, 16, 16);
-        }
-    }
-    void showLoggerData(byte pid, int value)
-    {
-#ifdef DEBUG
-        DEBUG.print(millis());
-        DEBUG.print(" PID[");
-        DEBUG.print(pid, HEX);
-        DEBUG.print("]=");
-        DEBUG.println(value);
-#endif
-        char buf[8];
-        switch (pid) {
-        case PID_RPM:
-            lcd.setCursor(64, 0);
-            lcd.setFont(FONT_SIZE_XLARGE);
-            lcd.printInt((unsigned int)value % 10000, 4);
-            break;
-        case PID_SPEED:
-            if (lastSpeed != value) {
-                lcd.setCursor(0, 0);
-                lcd.setFont(FONT_SIZE_XLARGE);
-                lcd.printInt((unsigned int)value % 1000, 3);
-                lastSpeed = value;
-            }
-            break;
-        case PID_THROTTLE:
-            lcd.setCursor(24, 5);
-            lcd.setFont(FONT_SIZE_SMALL);
-            lcd.printInt(value % 100, 3);
-            break;
-        case PID_INTAKE_TEMP:
-            if (value < 1000) {
-                lcd.setCursor(102, 5);
-                lcd.setFont(FONT_SIZE_SMALL);
-                lcd.printInt(value, 3);
-            }
-            break;
-        }
-    }
-    void showGForce(int g)
-    {
-        byte n;
-        /* 0~1.5g -> 0~8 */
-        g /= 85 * 25;
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(0, 3);
-        if (g == 0) {
-            lcd.clearLine(1);
-        } else if (g < 0 && g >= -10) {
-            for (n = 0; n < 10 + g; n++) {
-                lcd.write(' ');
-            }
-            for (; n < 10; n++) {
-                lcd.write('<');
-            }
-            lcd.print("        ");
-        } else if (g > 0 && g < 10) {
-            lcd.print("        ");
-            for (n = 0; n < g; n++) {
-                lcd.write('>');
-            }
-            for (; n < 10; n++) {
-                lcd.write(' ');
-            }
-        }
-    }
-    void initLoggerScreen()
-    {
-        lcd.clear();
-        lcd.backlight(true);
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(24, 3);
-        lcd.print("km/h");
-        lcd.setCursor(110, 3);
-        lcd.print("rpm");
-        lcd.setCursor(0, 5);
-        lcd.print("THR:   %");
-        lcd.setCursor(80, 5);
-        lcd.print("AIR:   C");
-    }
-    void showTimerResults()
-    {
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(56, 0);
-        lcd.print(" 0~60:  --");
-        lcd.setCursor(56, 2);
-        lcd.print("0~100:  --");
-        lcd.setCursor(56, 4);
-        lcd.print("0~200:  --");
-        lcd.setCursor(56, 6);
-        lcd.print(" 400m:  --");
-        lcd.setFont(FONT_SIZE_MEDIUM);
-        char buf[8];
-        if (times[0]) {
-            sprintf(buf, "%2d.%1d", times[0] / 10, times[0] % 10);
-            Serial.println(times[0]);
-            lcd.setCursor(92, 0);
-            lcd.print(buf);
-        }
-        if (times[1]) {
-            sprintf(buf, "%2d.%1d", times[1] / 10, times[1] % 10);
-            Serial.println(buf);
-            lcd.setCursor(92, 2);
-            lcd.print(buf);
-        }
-        if (times[2]) {
-            sprintf(buf, "%2d.%1d", times[2] / 10, times[2] % 10);
-            Serial.println(buf);
-            lcd.setCursor(92, 4);
-            lcd.print(buf);
-        }
-        if (times[3]) {
-            sprintf(buf, "%2d.%1d", times[3] / 10, times[3] % 10);
-            Serial.println(buf);
-            lcd.setCursor(92, 6);
-            lcd.print(buf);
-        }
-    }
-    void initTimerScreen()
-    {
-        lcd.clear();
-        showTimerResults();
-        lcd.setFont(FONT_SIZE_SMALL);
-        lcd.setCursor(24, 7);
-        lcd.print("km/h");
-    }
-};
-
-static COBDLogger logger;
-
-void setup()
-{
-#ifdef DEBUG
-    DEBUG.begin(DEBUG_BAUDRATE);
-    DEBUG.println("OBD logger debug console");
-#endif
-
-    lcd.begin();
-    lcd.backlight(true);
-    lcd.setFont(FONT_SIZE_MEDIUM);
-    lcd.println("OBD Logger");
-    lcd.println("Initializing");
-
-    logger.begin();
-    logger.initSender();
-
-#ifdef GPSUART
-#ifdef GPS_OPEN_BAUDRATE
-    GPSUART.begin(GPS_OPEN_BAUDRATE);
-    delay(10);
-    GPSUART.println(PMTK_SET_BAUDRATE);
-    GPSUART.end();
-#endif
-    GPSUART.begin(GPS_BAUDRATE);
-    // switching to 10Hz mode, effective only for MTK3329
-    //GPSUART.println(PMTK_SET_NMEA_OUTPUT_ALLDATA);
-    GPSUART.println(PMTK_SET_NMEA_UPDATE_10HZ);
-#endif
-
-#ifdef MODE_SWITCH_PIN
-    pinMode(MODE_SWITCH_PIN, INPUT);
-#endif
-
-#if ENABLE_DATA_LOG
-    logger.checkSD();
-#else
-    lcd.clear();
-#endif
-    logger.setup();
-
-#ifdef MODE_SWITCH_PIN
-    if (digitalRead(MODE_SWITCH_PIN) == 0) {
-        delay(500);
-        if (digitalRead(MODE_SWITCH_PIN) == 0) {
-            mode = 1 - mode;
-            logger.initScreen();
-            while (digitalRead(MODE_SWITCH_PIN) == 0);
-        }
-    }
-#endif
-}
-
-void loop()
-{
-    logger.loop();
-}
-- 
cgit v1.2.3