John Bradnam
Published © GPL3+

Servo Tester and Sequencer

Test up to 12 servos with features such as setting movement range, velocity or acceleration, playback a sequence of positions & more

IntermediateFull instructions provided12 hours515
Servo Tester and Sequencer

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
MT3608 Boost module
×1
8mm x 8mm Switch
1 x push on/push off switch, 1 x momentary switch
×1
Other components
2 x 220uF 16V capacitor (E2.5 - 6), 2 x 4K7 0805 resistors, 4 x 10k 0805 resistors, 4 x 22nF 0805 capacitors, 2 x 0.1uF 0805 capacitors, 1x 1N4007 DO14 diode
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1
Adafruit Monochrome 1.3" 128x64 OLED graphic display
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

User Manual

STL Files

Files for 3D printing

Schematics

Eagle files

Schematics & PCBs in Eagle format

Schematic - CPU board

PCB - CPU board

Schematic - Servo board

PCB - Servo board

Code

ServoEditV2.ino

C/C++
#include <Arduino.h>
#include <Wire.h>
#include "SimpleSH1106.h"
#define SerialPrintHex(i,digits) {for(int8_t n=(digits-1)*4;n>=0;n-=4) Serial.print((i>>n)&15,HEX);}

#include "servoedit4.h"
#include "servoedit3.h"

/*
 * 2022-12-07 jbrad2089@gmail.com
 *  - Swapped pinCCW & pinCW physical pins
 *  - Enabled HasRunBtn
*/

#define HasRunBtn

enum TMyEvent {
    meNone,
    meClick,
    meWheelMove,
#ifdef HasRunBtn
    meRunBtn,
#endif
    meCommand};
TMyEvent MyEvent = meNone;
int8_t WheelDelta = +1;

uint8_t CurServo = 0;
float KnobSpeed = 1;

enum TParamValue {pvTarget,pvDelay,pvMinMax,pvPinGrpVmax,pvAmax};

const int pinBtn = A2;
const int pinCCW = A1;
const int pinCW  = A0;
#ifdef HasRunBtn
const int pinRunBtn = A3;
#endif

//==============================================================
// DrawTrackBar
//   draws a "trackbar" at the bottom of the screen
//   a: the value
//   amin: min value
//   amax: max value
//==============================================================
void DrawTrackBar(float a, float amin, float amax)
{
  const int8_t xmin = 5;
  const int8_t xmax = 123;
  int8_t ax = round(((xmax-3)-(xmin+3))*(a-amin) / (amax-amin) + (xmin+3));

  setupPageCol(6,xmin);
  DrawBarSH1106(0xE0);
  for (int8_t x=xmin+1;x<=xmax-1;x++)
    if ((x >= ax-2) && (x <= ax+2))
      DrawBarSH1106(0xFF); else
      DrawBarSH1106(0xA0);
  DrawBarSH1106(0xE0);

  setupPageCol(7,xmin);
  for (int8_t x=xmin;x<=xmax;x++)
    if ((x >= ax-2) && (x <= ax+2))
      DrawBarSH1106(0x1F); else
      DrawBarSH1106(0);
}

//==============================================================
// DrawOLEDStrF
//   draws a string that has been declared in PROGMEM using the F() macro
//   replace a $ char with the integer number in f
//   replace a & char with the f with one digit after the d.p.
//   ss: the string 
//   f: a numeric value to be printed
//   x: x-coord of topleft of string
//   page: y-coord of topleft of string = page*8
//   Font: the font defined in servoedit3.h
//==============================================================
uint8_t DrawOLEDStrF(const __FlashStringHelper* ss, float f, uint8_t x, int8_t page, word Font)
{
  uint16_t p = ss;
  while (1) {
    char c  =  pgm_read_byte_near(p);

    switch (c) {
      case 0: return x;
      case '$': x += DrawIntSH1106(round(f),x,page,Font); break;
      case '&': {
          int i = round(f*10);
          x += DrawIntSH1106(i / 10,x,page,Font);
          x += DrawCharSH1106('.',x, page, Font);
          x += DrawIntSH1106(i % 10,x,page,Font);
        } break;
      default: x += DrawCharSH1106(c,x, page, Font);
    }
    p++;
  }  
}

//==============================================================
// DrawOLEDStr1
//   draws a string at coords 2,0 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr1(const __FlashStringHelper* s, float f)
{
  clearSH1106();
  return DrawOLEDStrF(s,f,2,0,SmallFont);
}

//==============================================================
// DrawOLEDStr2
//   draws a string at coords 2,16 using LargeCondensedFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr2(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,2,2,LargeCondensedFont);
}

//==============================================================
// DrawOLEDStr3a
//   draws a string at coords 2,40 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr3a(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,2,4+1,SmallFont);
}

//==============================================================
// DrawOLEDStr3b
//   draws a string at coords 64,40 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr3b(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,64,4+1,SmallFont);
}

//==============================================================
// DrawOLEDStr4a
//   draws a string at coords 2,48 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr4a(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,2,5+1,SmallFont);
}

//==============================================================
// DrawOLEDStr4b
//   draws a string at coords 65,48 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr4b(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,64,5+1,SmallFont);
}

//==============================================================
// DrawOLEDStr5a
//   draws a string at coords 2,56 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr5a(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,2,7,SmallFont);
}

//==============================================================
// DrawOLEDStr5b
//   draws a string at coords 65,56 using SmallFont
//   calls DrawOLEDStrF - same rules apply
//   s: the string 
//   f: a numeric value to be printed
//==============================================================
uint8_t DrawOLEDStr5b(const __FlashStringHelper* s, float f)
{
  return DrawOLEDStrF(s,f,64,7,SmallFont);
}

//==============================================================
// DrawOLEDParams
//   draws the parameters of the current servo at the bottom of the screen
//==============================================================
void DrawOLEDParams() {
  DrawOLEDStr3a(F("Pos $"),Servos[CurServo].Target);
  uint8_t x = DrawOLEDStr3b(F("($"),Servos[CurServo].ServoMin);
  DrawOLEDStrF(F(" - $)"),Servos[CurServo].ServoMax,x,4+1,SmallFont);
  
  DrawOLEDStr4a(F("Vmax $"),Servos[CurServo].Vmax);
  DrawOLEDStr4b(F("Amax &"),Servos[CurServo].Amax);
  DrawOLEDStr5a(F("Group $"),Servos[CurServo].Group);
  DrawOLEDStr5b(F("Pin $"),Servos[CurServo].PinNum);
}

//==============================================================
// ShowError
//   show error message for 1 sec
//   s: error message
//   f: numeric value
//==============================================================
void ShowError(const __FlashStringHelper* s, float f)
{
  DrawOLEDStr1(F("Error"),0);
  DrawOLEDStr2(s,f);
  delay(1000);
}

//==============================================================
// RelaxAll
//   detach all Servos 
//==============================================================
void RelaxAll() {
  for (int8_t servo=0; servo<NumServos; servo++) 
    Servos[servo].aServo.detach();
  DrawOLEDStr1(F("Button held down"),0);
  DrawOLEDStr2(F("Relax All"),CurServo+1);
  DrawOLEDStr4a(F("Relax all servos"),CurServo+1);
  while (Quadrature() >= 4) {} // wait for up
  clearSH1106();
}

//==============================================================
// ADCQuadrature
//   gets a value from the rotary encoder
//   encoder uses resistors to convert signals into an analogue voltage
//   returns 4; else // button pressed
//   returns 0,1,2,3 quadrature signals in binary
//   one notch is one whole cycle
//   3 is the rest state 
//   does a little bebouncing
//==============================================================
int8_t ADCQuadrature() {
  byte n = 0;
  int8_t state;

  do {
    static int8_t prevState = -1;
    int16_t i = analogRead(A7);
    
    if (i < 104) state = 4; else // button pressed
    if (i < 358) state = 2; else
    if (i < 632) state = 3; else // rest state
    if (i < 846) state = 0; else
                 state = 1;     
  
    if (prevState == state)
      n++; else
      n = 0;
    prevState = state;
  } while (n < 3); // debouncing count
  return state;
}

//==============================================================
// Quadrature
//   gets a value from the rotary encoder
//   encoder uses 3 digital pins
//   returns 4; else // button pressed
//   returns 0,1,2,3 quadrature signals in binary
//   one notch is one whole cycle
//   3 is the rest state 
//   does a little bebouncing
//==============================================================
int8_t Quadrature() {
  byte n = 0;
  int8_t state;

  do {
    static int8_t prevState = -1;
    if (digitalRead(pinBtn) == LOW) state = 4; else // button pressed
    if (digitalRead(pinCW) == LOW) {
      if (digitalRead(pinCCW) == LOW) 
        state = 3; else // rest state
        state = 1;       
    } else {
      if (digitalRead(pinCCW) == LOW) 
        state = 2; else 
        state = 0;       
    }
  
    if (prevState == state)
      n++; else
      n = 0;
    prevState = state;
  } while (n < 3); // debouncing count
  return state;
}

//==============================================================
// CheckEncoder
//   runs a FSM to decode the quadrature signals into up/down counts
//   FSM is from eevblog.com
//     https://www.eevblog.com/forum/projects/easy-rotary-encoder-implementation-good-easy-to-implement-code/?action=dlattach;attach=107187
//   returns an event state
//     meNone: nothing happened
//     meClick: button clicked
//     meWheelMove: wheel moved
//==============================================================
TMyEvent CheckEncoder() {
  enum TEncState {Idle11,CW_01,CW_00,CW_10,ERR01,ERR00,ERR10,CCW10,CCW00,CCW01, EncStateHigh };
  struct TEncStateRec {
    TEncState next;
    int8_t incr;};  
  TEncStateRec NextEncState[EncStateHigh][4] = {
      /*Idle11*/   {{ERR00,  0},    {CW_01,  0},    {CCW10,  0},    {Idle11,  0}},
      /*CW_01 */   {{CW_00,  0},    {CW_01,  0},    {ERR10,  0},    {Idle11,  0}},
      /*CW_00 */   {{CW_00,  0},    {CW_01,  0},    {CW_10,  0},    {Idle11, +1}},
      /*CW_10 */   {{CW_00,  0},    {CW_01, +1},    {CW_10,  0},    {Idle11, +1}},
      /*ERR01 */   {{CW_00,  0},    {ERR01,  0},    {CCW10,  0},    {Idle11, -1}},
      /*ERR00 */   {{ERR00,  0},    {CCW01,  0},    {CW_10,  0},    {Idle11,  0}},
      /*ERR10 */   {{CCW00,  0},    {CW_01,  0},    {ERR10,  0},    {Idle11, +1}},
      /*CCW10 */   {{CCW00,  0},    {ERR01,  0},    {CCW10,  0},    {Idle11,  0}},
      /*CCW00 */   {{CCW00,  0},    {CCW01,  0},    {CCW10,  0},    {Idle11, -1}},
      /*CCW01 */   {{CCW00,  0},    {CCW01,  0},    {CCW10, -1},    {Idle11, -1}}};
  static TEncState EncState = Idle11;
  static int8_t count = 0;  
  static int32_t downTime = 0;  

  do {  
    int8_t state = Quadrature();     
    static bool prevbtn = false;  
  
    if (state >= 4) { // button down;
      if (!prevbtn) {
        prevbtn = true;
        downTime = millis();
        return meClick;
      }
      
      if (millis() - downTime > 1000) 
        RelaxAll();
      
      return meNone;
    } else {
      prevbtn = false;

      count += NextEncState[EncState][state].incr;  
      EncState = NextEncState[EncState][state].next;
    }
#ifdef HasRunBtn
  } while ((EncState != Idle11) && (Serial.available() == 0) && (digitalRead(pinRunBtn) == HIGH));
#else
  } while ((EncState != Idle11) && (Serial.available() == 0));
#endif
      
  if (count) {
    WheelDelta = sign(count); 
    count = 0;
    return meWheelMove;
  }
  return meNone;
}

//==============================================================
// WaitForEvent
//   waits until the encoder has 
//   returns an event state
//     meClick: button clicked
//     meWheelMove: wheel moved
//       +1 or -1 is in WheelDelta
//       KnobSpeed is bigger if the wheel is turning faster
//     meCommand: a serial command arrived
//     meRunBtn: the Run button was pressed
//==============================================================
TMyEvent WaitForEvent(bool bBreakOnCommand) {
  static uint32_t prevwd = 0;
  const int tmax = 500;
  const int tmin = 300;
  const float kmax = 100.0;
  const float kmin = 0.1;
  float t;
  static float t1 = 0;
  uint32_t prevt = millis();

  do {
    MyEvent = CheckEncoder();
    
    switch (MyEvent) {
      case meClick: KnobSpeed = 1; break;        
      case meWheelMove:         
        if (prevwd != WheelDelta)
          t1 = tmax;
        prevwd = WheelDelta;
        t = millis();
        t = t-prevt+1;
        t1 = t1 + (t-t1)*0.1;
        t1 = max(t1,t);
        t1 = constrain(t1,tmin,tmax);
        KnobSpeed = ((tmax - t1)*tmin*(kmax - kmin) / (t1*(tmax - tmin))) + kmin;
        KnobSpeed = constrain(KnobSpeed,1,kmax);
        break;
    }
    
    if (bBreakOnCommand && (Serial.available() > 0))
      MyEvent = meCommand;
      
    #ifdef HasRunBtn
      if (bBreakOnCommand && (digitalRead(pinRunBtn) == LOW))
        MyEvent = meRunBtn;
    #endif
  } while (MyEvent == meNone);
  return MyEvent;
}

//==============================================================
// ExecMenu_Position_Set_Value
//   displays and executes the menu screen to set the target position of the current servo
//==============================================================
void ExecMenu_Position_Set_Value()
{
  DrawOLEDStr1(F("Position $ Value"),CurServo+1);
  do {
    DrawOLEDStr2(F("$  "),round(Servos[CurServo].Target));
    DrawTrackBar(Servos[CurServo].Target,Servos[CurServo].ServoMin,Servos[CurServo].ServoMax);

    switch (WaitForEvent(false)) {
      case meWheelMove:
        SetServoPosition(CurServo, Constrain(Servos[CurServo].Target+round(WheelDelta*KnobSpeed),Servos[CurServo].ServoMin,Servos[CurServo].ServoMax));
        break;
    }
  } while (MyEvent != meClick);
}

//==============================================================
// ServoSetDefaults
//   sets the default parameter values for a servo
//==============================================================
void ServoSetDefaults(uint8_t srv, bool setPos)
{
  Servos[srv].Group = 0;
  Servos[srv].PinNum = srv+2;
  Servos[srv].ServoMax = 2500;
  Servos[srv].ServoMin = 500;
  Servos[srv].Velocity = 0;
  Servos[srv].Vmax = 10.0;
  Servos[srv].Amax = 0.1;

  Servos[srv].Target = 1500;
  if (setPos)
  {
    Servos[srv].Position = 1500;
  }
}

//==============================================================
// SetAllDefaults
//   sets the default parameter values for all servos
//==============================================================
void SetAllDefaults()
{
  for (int8_t srv=0; srv<NumServosMax; srv++)
    ServoSetDefaults(srv,false);
}

//==============================================================
// MoveServosToFinish
//   execute MoveServos until all are at targets
//   returns true if user cancelled by holding the button down
//==============================================================
bool MoveServosToFinish() {
  uint32_t prevt = millis();
  while (!MoveServos()) {
    if (digitalRead(pinBtn) == HIGH) 
      prevt = millis(); else
    if (millis()-prevt > 1000) {
      RelaxAll();
      return true;
    }
  } 
  return false;
}

//==============================================================
// EEPROMexec
//   executes the command triplets in the EEPROM
//     changes are written into the Params array
//   servo: 
//     servo>=0: sets Params for one servo
//   "target" command triplets tell a servo to move
//     "target" triplets are numbered from 0 to (number of "target" triplets)-1
//   bDoMoves: for any "target" command, wait to finish its move
//   bStopOnFirstMove: return when the first ekTarget is executed
//
//   typical calls are
//      EEPROMexec(-1,false,false);
//        set the parameters and targets for all servos
//      EEPROMexec(-1,true,false);
//        play the whole file
//      EEPROMexec(servo,false,false);
//        set up the parameters for 'servo'
//      EEPROMexec(servo,false,true);
//        initialise the servo
//==============================================================
void EEPROMexec(int8_t servo, bool bDoMoves, bool bStopOnFirstMove)
{
  uint16_t addr;
  TTriplet t;
  uint8_t srv,dly;
  TEEPROMkind epk;

  addr = 0;
  if (servo < 0)
    SetAllDefaults(); else
    ServoSetDefaults(servo,false);

  while (true)
  {
    t = GetEEPROMTriplet(addr);
    epk = UnpackEEPROMkind(t);
    if (epk == ekEOF)
      break;
    srv = UnpackEEPROMservo(t);
    srv = Min(srv,NumServosMax-1);
    NumServos = max(srv+1,NumServos);

    if ((srv == servo) || (servo < 0))
      UnpackEEPROMparams(t,&(Servos[srv]));

    if (epk == ekTarget) {
      if ((srv == servo) || (servo < 0))
      {
        dly = UnpackEEPROMdelay(t);

        if (bDoMoves) {
          if (dly > 2)
          {
            DrawOLEDStr1(F("Play Memory"),0);
            DrawOLEDStr2(F("Playing $"),addr/3);
          }

          if (dly < 128) {
            uint32_t prevt = millis();
            uint32_t prevdown = millis();
            do {
              MoveServos();
              if (digitalRead(pinBtn) == HIGH) 
                prevdown = millis(); else
              if (millis()-prevdown > 1000) {
                RelaxAll(); // emergency stop
                return;
              }
            } while (millis()-prevt < dly*100.0); 
          } else
          {
            if (MoveServosToFinish()) 
              return; // emergency stop
            delay((dly-128)*100);
          }
        }
        
        if (bStopOnFirstMove)
          break;
      }
    }
    addr += 3;
  }
  if (bDoMoves)
    MoveServosToFinish();
}

//==============================================================
// TargetHasChanged
//   returns: is the current target value of the servo different from that predicted by the commands in the EEPROM?
//==============================================================
bool TargetHasChanged(int8_t servo)
{
  TServo temp;
  temp = Servos[servo];
  EEPROMexec(servo,false,false);
  bool result = Servos[servo].Target != temp.Target;
  Servos[servo] = temp;
  return result;
}

//==============================================================
// MaxChangedServo
//   if the current target value of any different from that prodicted by the commands in the EEPROM
//     returns the servo number of the maximum changed servo
//   else returns -1
//==============================================================
int8_t MaxChangedServo()
{
  uint8_t servo;
  int8_t result = -1;

  for (servo=0; servo<NumServos; servo++)
    if (TargetHasChanged(servo))
      result = servo;
  return result;
}

//==============================================================
// AppendOneServoToMemory
//   servo: the servo to save
//   aDelay: whether to wait after a servo move
//     aDelay = 0..100 means wait for aDelay/10.0 seconds
//     aDelay = 128..228 means wait until all outstanding are finished) wait (aDelay-128)/10.0 seconds
// 
//   save the current params of servo s
//   "run" the EEPROM for servo s (but don"t exec movements)
//   if (the servo position or params have changed) save them
// 
//   if (the servo is not anywhere in the EEPROM) save at least one parameter
//     so that the EEPROM specifies how many servos there are
//   
//   returns true = succeeded, i.e. the EEPROM wasn't full
//==============================================================
bool AppendOneServoToMemory(int8_t servo, uint8_t aDelay)
{
  TServo temp = Servos[servo];
  EEPROMexec(servo,false,false);

  bool result = true;
  if ((Servos[servo].ServoMin != temp.ServoMin) || (Servos[servo].PinNum != temp.PinNum)) result = result && EEPROMappendMinPos(servo,temp.ServoMin,temp.PinNum);
  if ((Servos[servo].ServoMax != temp.ServoMax) || (Servos[servo].Group   != temp.Group)) result = result && EEPROMappendMaxPos(servo,temp.ServoMax,temp.Group);
  if ((Servos[servo].Vmax != temp.Vmax)         || (Servos[servo].Amax    != temp.Amax) ) result = result && EEPROMappendVmaxAmax(servo,temp.Vmax,temp.Amax);
  if (Servos[servo].Target != temp.Target                                               ) result = result && EEPROMappendTarget(servo,round(temp.Target),aDelay);
  Servos[servo] = temp;
  if (! EEPROMhasServo(servo))
    result = result && EEPROMappendMinPos(servo,temp.ServoMin,temp.PinNum);
  return result;
}

//==============================================================
// SaveAllToMemory
//   save all servos to memory
//   aDelay: whether to wait after a servo move
//     aDelay = 0..100 means wait for aDelay/10.0 seconds
//     aDelay = 128..228 means wait until finished then wait (aDelay-128)/10.0 seconds
//==============================================================
void SaveAllToMemory(uint8_t aDelay)
{
  int8_t servo,maxservo;
  uint16_t Top;
  bool b;


  b = true;
  Top = EEPROMTop();
  maxservo = MaxChangedServo();
  for (servo=0; servo<NumServos; servo++)
    if (servo == maxservo)
      b = b && AppendOneServoToMemory(servo,aDelay); else
      b = b && AppendOneServoToMemory(servo,0);
  if (Top == EEPROMTop())
    ShowError(F("No Change"),0);
  if (! b)
    ShowError(F("Memory Full"),0); 
}

//==============================================================
// ExecMenu_Clear
//   displays and executes the menu screen to query clearing the EEPROM
//==============================================================
void ExecMenu_Clear()
{
  enum TClearMode {cmClearEEPROM, cmClearServos, cmCancel};
  TClearMode ClearMode  = cmClearServos;

  do {
    DrawOLEDStr1(F("Clear Memory"),0);
    switch (ClearMode) {
      case cmClearEEPROM: DrawOLEDStr2(F("Clr Memory"),0); DrawOLEDStr3a(F("Clear Memory"),0); break;
      case cmClearServos: DrawOLEDStr2(F("Clr Servos"),0); DrawOLEDStr3a(F("Clear Memory"),0); DrawOLEDStr4a(F("Clear Servos"),0); break;
      case cmCancel:      DrawOLEDStr2(F("Cancel"),0); break;
    }

    switch (WaitForEvent(false)) {
      case meWheelMove:
        if (((WheelDelta > 0) && (ClearMode < cmCancel)) || ((WheelDelta < 0) && (ClearMode > cmClearEEPROM)))
          ClearMode = (TClearMode)(((byte)ClearMode)+WheelDelta);
        break;  
    }
  } while (MyEvent != meClick);


  switch (ClearMode) {
    case cmClearServos:
        CurServo = 0;
        NumServos = 1;
        ServoSetDefaults(0,true);
        break;
    case cmCancel:      return;
  }
  InitialiseEEPROM();
}

//==============================================================
// EditParamValue
//   displays and executes the menu screen to edit the EEPROM
//==============================================================
float EditParamValue(const __FlashStringHelper* s1, const __FlashStringHelper* s2, float a, float amin, float amax,
  uint8_t servo, uint8_t addr, TParamValue ParamValue)
{
  float d;

  while (true) {
    DrawOLEDStr1(s1,0);
    if (ParamValue == pvDelay) {
      if (a >= 128)
        DrawOLEDStr2(F("Fin+&s"),(a-128)/10.0); else
        DrawOLEDStr2(F("& sec"),a/10.0);
    } else
      DrawOLEDStr2(s2,a);    
    DrawOLEDStr5a(F("Servo = $"),  servo);
    DrawOLEDStr5b(F("Addr = $"),  addr);
    switch (WaitForEvent(false)) 
      case meWheelMove: {
        if ((ParamValue == pvTarget) || (ParamValue == pvMinMax) || (ParamValue == pvDelay))
          d = round(WheelDelta*KnobSpeed); else
          d = WheelDelta;          
        if (ParamValue == pvAmax)
          d /= 10.0;
        a = constrain(a+d,amin,amax);
        if (ParamValue == pvTarget)
          SetServoPosition(servo, a);
        break;
      case meClick: return a;
    }
  }
}

//==============================================================
// ExecMenu_EditMemory
//   displays and executes the menu screen to edit the EEPROM
//==============================================================
void ExecMenu_EditMemory(bool bDelete)
{
  int16_t addr = 0;
  TServo Params;
  TTriplet t;
  uint8_t servo,dly;
  int16_t top;
        
  while (true) {
    do {
      uint8_t x;
      top = EEPROMTop() / 3;
      if (bDelete) 
        x = DrawOLEDStr1(F("Delete from memory "),0); else
        x = DrawOLEDStr1(F("Edit Memory "),0);
      if ((addr == -2) || (addr == top))
        DrawOLEDStr2(F("Quit"),0); else
      if (((addr == -1) || (addr == top+1)) && bDelete)
        DrawOLEDStr2(F("Clear"),0); else
      {
        DrawOLEDStrF(F("$"),addr,x,0,SmallFont);
        t = GetEEPROMTriplet(addr*3);
        servo = UnpackEEPROMservo(t);
        UnpackEEPROMparams(t, &Servos[servo]);
        Params = Servos[servo];
        DrawOLEDStr5a(F("Servo = $"), servo);

        switch (UnpackEEPROMkind(t)) {
          case ekTarget:    DrawOLEDStr2(F("Target"),0);  DrawOLEDStr3a(F("Target = $"),      Params.Target);   DrawOLEDStr4a(F("Delay = &"),UnpackEEPROMdelay(t)/10.0); break;
          case ekMinPos:    DrawOLEDStr2(F("Min Pos"),0); DrawOLEDStr3a(F("Min Position = $"),Params.ServoMin); DrawOLEDStr4a(F("Pin = $"),  Params.PinNum);   break;
          case ekMaxPos:    DrawOLEDStr2(F("Max Pos"),0); DrawOLEDStr3a(F("Max Position = $"),Params.ServoMax); DrawOLEDStr4a(F("Group = $"),Params.Group);    break;
          case ekVmaxAmax:  DrawOLEDStr2(F("V/A max"),0); DrawOLEDStr3a(F("Vmax = $"),        Params.Vmax);     DrawOLEDStr4a(F("Amax = &"), Params.Amax);     break;
          default:          DrawOLEDStr2(F("Error"),0); break;
        }

        if (MoveServosToFinish())
          return; // emergency stop
      }
  
      switch (WaitForEvent(false)) {
        case meWheelMove: 
          if (WheelDelta > 0) {
            if (addr == top+1)
              addr = 0; else
              addr++;
          } else {
            if (addr == 0)
              addr = top+1; else
              addr--;
          }
      }
    } while (MyEvent != meClick);

    if ((addr == -2) || (addr == top))
      return;
    if ((addr == -1) || (addr == top+1)) {
      ExecMenu_Clear();
      return;
    }

    if (bDelete) {
      EEPROMDeleteTriplet(addr*3);
    } else {
      switch (UnpackEEPROMkind(t)) {
        case ekTarget:
          Params.Target = EditParamValue(F("Target Position"),F("$"),Params.Target,Params.ServoMin,Params.ServoMax,servo,addr,pvTarget);
          dly = EditParamValue(F("Delay"),F(""),UnpackEEPROMdelay(t),0,255,servo,addr,pvDelay);
          EEPROMsaveTriplet(EEPROMTargetToTriplet(servo, Params.Target,dly), addr*3);
          break;

        case ekMinPos:
          Params.ServoMin = EditParamValue(F("Minimum Position"),F("Min=$"),Params.ServoMin,200,2500,servo,addr,pvMinMax);
          Params.PinNum = EditParamValue(F("Pin Number"),F("Pin=$"),Params.PinNum,2,12,servo,addr,pvPinGrpVmax);
          EEPROMsaveTriplet(EEPROMMinPosToTriplet(servo, Params.ServoMin, Params.PinNum), addr*3);
          break;

        case ekMaxPos:
          Params.ServoMax = EditParamValue(F("Maximum Position"),F("Max=$"),Params.ServoMax,500,2500,servo,addr,pvMinMax);
          Params.Group = EditParamValue(F("Group"),F("Group=$"),Params.Group,0,11,servo,addr,pvPinGrpVmax);
          EEPROMsaveTriplet(EEPROMMaxPosToTriplet(servo, Params.ServoMax, Params.Group), addr*3);
          break;

        case ekVmaxAmax:
          Params.Vmax = EditParamValue(F("Vmax"),F("Vmax=$"),Params.Vmax,0,255,servo,addr,pvPinGrpVmax);
          Params.Amax = EditParamValue(F("Amax"),F("Amax=&"),Params.Amax,0,25.5,servo,addr,pvAmax);
          EEPROMsaveTriplet(EEPROMVmaxAmaxToTriplet(servo, Params.Vmax, Params.Amax), addr*3);
          break;
      }
    }
  }
}

//==============================================================
// ExecMenu_Learn_Wait
//   displays and executes the menu screen to 
//     save the servos to memory with/without a wait
//     edit the EEPROM
//       edit step(s)
//       delete step(s)
//       clear 
//==============================================================
void ExecMenu_Learn_Wait()
{
  enum TWaitMode {wcFinish, wcFinishDelay, wcDelay, wcNoDelay, wcCancel, wcEdit, wcDelete};
  static TWaitMode WaitMode = wcFinish;
  static uint8_t aDelay = 0;


  if (MaxChangedServo() < 0) 
    WaitMode = wcNoDelay;
  
  do {
    if (EEPROMTop() == 0) 
      WaitMode = min(WaitMode,wcCancel);
    switch (WaitMode) {
      case wcFinish:
      case wcFinishDelay:
      case wcDelay: if (MaxChangedServo() < 0) WaitMode = wcNoDelay;
    }

    switch (WaitMode) {
      case wcNoDelay:
      case wcFinish:
      case wcFinishDelay:
      case wcDelay:     DrawOLEDStr1(F("Learn ($% full) Wait"),EEPROMpercentFull()); break;
      default:          DrawOLEDStr1(F("Learn ($% full)"),EEPROMpercentFull());
    }

    switch (WaitMode) {
      case wcNoDelay:        DrawOLEDStr2(F("No Wait"),0); DrawOLEDStr4a(F("Set target and continue"),0); break;
      case wcFinish:         DrawOLEDStr2(F("Until Finish"),0); DrawOLEDStr4a(F("Wait until reaches target"),0); break;
      case wcFinishDelay:    DrawOLEDStr2(F("Finish+Dly"),0); DrawOLEDStr4a(F("Reach target then wait more"),0); break;
      case wcDelay:          DrawOLEDStr2(F("Fixed Delay"),0); DrawOLEDStr4a(F("Wait for a fixed time"),0); break;
      case wcCancel:         DrawOLEDStr2(F("Cancel"),0); break;
      case wcEdit:           DrawOLEDStr2(F("Edit"),0); DrawOLEDStr4a(F("Change the stored program"),0); break;
      case wcDelete:         DrawOLEDStr2(F("Delete"),0); DrawOLEDStr4a(F("Delete line(s)"),0); break;
    }

    switch (WaitForEvent(false)) {
      case meWheelMove:
        if (((WheelDelta > 0) && (WaitMode < wcDelete)) || ((WheelDelta < 0) && (WaitMode > wcFinish)))
          WaitMode = (TWaitMode)(((byte)WaitMode)+WheelDelta);
        break;
    }
  } while (MyEvent != meClick);

  switch (WaitMode) {
    case wcFinishDelay:
    case wcDelay:
      do {
        if (WaitMode == wcFinishDelay)
          DrawOLEDStr1(F("Learn: Finished+Delay"),0); else
          DrawOLEDStr1(F("Learn: Wait Delay"),0);
        if (aDelay == 0)
          DrawOLEDStr2(F("No Wait"),0); else
          DrawOLEDStr2(F("Wait &sec"),aDelay/10.0);

        switch (WaitForEvent(false)) {
          case meWheelMove: aDelay = Constrain(aDelay+WheelDelta,0,100); break;
        }
      } while (MyEvent != meClick);
      break;
  }

  switch (WaitMode) {
    case wcNoDelay:        SaveAllToMemory(0); break;
    case wcFinish:         SaveAllToMemory(128); break;
    case wcFinishDelay:    SaveAllToMemory(aDelay+128); break;
    case wcDelay:          SaveAllToMemory(aDelay); break;
    case wcEdit:           ExecMenu_EditMemory(false); break;
    case wcDelete:         ExecMenu_EditMemory(true); break;
  }
}

//==============================================================
// ExecMenu_Setup_Servo
//   displays and executes the menu screen to set the parameters of a servo
//   bSelectPin=true: show the "Pin" menu
//==============================================================
void ExecMenu_Setup_Servo(bool bSelectPin)
{
  enum TState {
        stSetup_Pin,
        stSetup_Min,
        stSetup_Max,
        stSetup_Vmax,
        stSetup_Amax,
        stSetup_Group};
  static TState state = stSetup_Pin;
  uint8_t srv;
  bool more;

  if (bSelectPin)
  {
    state = stSetup_Pin;
  } else {
    more = true;
    while (more)
    {
      DrawOLEDStr1(F("Setup Servo $"),CurServo+1);
      switch (state) {
        case stSetup_Pin:   DrawOLEDStr2(F("Pin"),0); break;
        case stSetup_Min:   DrawOLEDStr2(F("Min"),0); break;
        case stSetup_Max:   DrawOLEDStr2(F("Max"),0); break;
        case stSetup_Vmax:  DrawOLEDStr2(F("Vmax"),0); break;
        case stSetup_Amax:  DrawOLEDStr2(F("Amax"),0); break;
        case stSetup_Group: DrawOLEDStr2(F("Group"),0); break;
      }

      switch (WaitForEvent(false)) {
        case meClick: more = false; break;
        case meWheelMove: 
          if (((WheelDelta > 0) && (state < stSetup_Group)) || ((WheelDelta < 0) && (state > stSetup_Pin)))
            state = (TState)(((byte)state)+WheelDelta);
          break;
      }
    }
  }

  if (state == stSetup_Pin)
    Servos[CurServo].aServo.detach();

  do {
    switch (state) {
      case stSetup_Pin:    DrawOLEDStr1(F("Servo $ Pin"),  CurServo+1); DrawOLEDStr2(F("Pin = $"),  Servos[CurServo].PinNum); break;
      case stSetup_Min:    DrawOLEDStr1(F("Servo $ Min"),  CurServo+1); DrawOLEDStr2(F("Min = $"),  Servos[CurServo].ServoMin);  DrawTrackBar(Servos[CurServo].ServoMin,500,2500); break;
      case stSetup_Max:    DrawOLEDStr1(F("Servo $ Max"),  CurServo+1); DrawOLEDStr2(F("Max = $"),  Servos[CurServo].ServoMax);  DrawTrackBar(Servos[CurServo].ServoMax,500,2500); break;
      case stSetup_Vmax:   DrawOLEDStr1(F("Servo $ Vmax"), CurServo+1); DrawOLEDStr2(F("Vmax=$"),   Servos[CurServo].Vmax);      DrawTrackBar(Servos[CurServo].Vmax,0,255); break;
      case stSetup_Amax:   DrawOLEDStr1(F("Servo $ Amax"), CurServo+1); DrawOLEDStr2(F("Amax=&"),   Servos[CurServo].Amax);      DrawTrackBar(Servos[CurServo].Amax,0,25.5); break;
      case stSetup_Group:  DrawOLEDStr1(F("Servo $ Group"),CurServo+1); DrawOLEDStr2(F("Group = $"),Servos[CurServo].Group); break;
    }

    switch (WaitForEvent(false)) {
      case meWheelMove:
        switch (state) {
          case stSetup_Pin:   Servos[CurServo].PinNum =   Constrain(Servos[CurServo].PinNum   +WheelDelta,2,13); break;
          case stSetup_Min:   Servos[CurServo].ServoMin = Constrain(Servos[CurServo].ServoMin +round(WheelDelta*KnobSpeed),500,2500); break;
          case stSetup_Max:   Servos[CurServo].ServoMax = Constrain(Servos[CurServo].ServoMax +round(WheelDelta*KnobSpeed),500,2500); break;
          case stSetup_Vmax:  Servos[CurServo].Vmax =     Constrain(Servos[CurServo].Vmax     +WheelDelta*Max(KnobSpeed/5,1),0,255); break;
          case stSetup_Amax:  Servos[CurServo].Amax =     constrain(Servos[CurServo].Amax     +WheelDelta*Max(KnobSpeed/5,1)*0.1,0,25.5); break;
          case stSetup_Group: Servos[CurServo].Group =    Constrain(Servos[CurServo].Group    +WheelDelta,0,11); break;
        }
        break;
    }
  } while (MyEvent != meClick);

  if (state == stSetup_Pin)
  {
    for (srv=0; srv<NumServos; srv++)
      if ((srv != CurServo) && (Servos[CurServo].PinNum == Servos[srv].PinNum))
        ShowError(F("Dup Pin (0x)"),srv);
  }
}

//==============================================================
// ExecMenu_NumServos
//   displays and executes the menu screen to add or delete servos from the list of servos
...

This file has been truncated, please download it to see its full contents.

servoedit3.h

C Header File
/*************************************************************************************************************************************************
 * EEPROM
 *   stores "Triplets",
 *   each Triplet sets a parameter of a servo
 *   there can be up to 341 Triplets (341*3 = 1023) 
 *   Triplets can be "executed" to run the stored "program"
 *   a "Target" Triplet sets the target position of a servo and hence is specially important
 *     "Target" Triplets are numbered from 0..n
 *   
 *   memory layout is little-endian
 *   memory layout of Triplets is (in binary)
 *     ssss0ttttttttnnnnnnnnnnn   (ekTarget)   set target of servo ssss to nnnnnnnnnnn + 500 microseconds (500..2547) then wait tttttttt/10.0 seconds
 *     ssss1000pppp0nnnnnnnnnnn   (ekMinPos)   set min position = nnnnnnnnnnn + 500 microseconds (500..2547) ; pin = pppp
 *     ssss1010gggg0nnnnnnnnnnn   (ekMaxPos)   set max position = nnnnnnnnnnn + 500 microseconds (500..2547) ; group = gggg;
 *     ssss1100vvvvvvvvaaaaaaaa   (ekVmaxAmax) Vmax[ssss] = vvvvvvvvvv uS per interval; Amax[ssss] = aaaaaaaaaa/10.0 uS per interval;
 *     111111111111111111111111   (ekEOF)      end of memory
 *   memory has 1024 bytes so the last byte cannot be used
 *
 *     initially there is assumed to be 1 servo
 *     a servo is added for each different ssss that is seen
 *     each new servo is assumed
 *       max velocity      = 0
 *       max acceleration  = 0
 *       min position      = 500 microseconds
 *       max position      = 2500 microseconds
 *
 *     tttttttt = 0..100 means wait for tttttttt/10.0 seconds
 *     tttttttt = 128..228 means wait for completion then wait (tttttttt-128)/10.0 seconds
 *************************************************************************************************************************************************/

#ifndef servoedit3_h
#define servoedit3_h

#include "servoedit4.h"
#include <EEPROM.h>

typedef uint32_t TTriplet;
enum TEEPROMkind {ekTarget,ekMinPos,ekMaxPos,ekVmaxAmax,ekEOF};

TTriplet GetEEPROMTriplet(uint16_t addr);
TTriplet UnpackEEPROMTarget(uint16_t TargetNum);
void InitialiseEEPROM();
uint16_t EEPROMnumTargets();
void EEPROMDeleteTriplet(uint16_t TargetNum);
void EEPROMDeleteServos(uint8_t servomin, uint8_t servomax, uint16_t addr);
bool EEPROMappendTarget(uint8_t servo, uint16_t target, uint8_t wait);
bool EEPROMappendMinPos(uint8_t servo, uint16_t pos, uint8_t pin);
bool EEPROMappendMaxPos(uint8_t servo, uint16_t pos, uint8_t group);
bool EEPROMappendVmaxAmax(uint8_t servo, float Vmax, float Amax);
void EEPROMsaveTriplet(TTriplet t, uint16_t addr);
uint16_t EEPROMpercentFull();
TEEPROMkind UnpackEEPROMkind(TTriplet t);
void UnpackEEPROMparams(TTriplet t, TServo *Params);
uint8_t UnpackEEPROMservo(TTriplet t);
uint8_t UnpackEEPROMdelay(TTriplet t);
bool EEPROMhasServo(uint8_t servo);
void CheckEEPROMGood();
uint16_t EEPROMTop();
void SendEEPROMplain();
void SendEEPROM();
void RecvEEPROM(); 
TTriplet EEPROMTargetToTriplet(uint32_t servo, uint32_t target, uint32_t wait);
TTriplet EEPROMMinPosToTriplet(uint32_t servo, uint32_t pos, uint32_t pin);
TTriplet EEPROMMaxPosToTriplet(uint32_t servo, uint32_t pos, uint32_t group);
TTriplet EEPROMVmaxAmaxToTriplet(uint32_t servo, float Vmax, float Amax);

struct TTriplet3 {
  byte a,b,c;
};

//==============================================================
// GetEEPROMTriplet
//   returns the Triplet at an address
//==============================================================
TTriplet GetEEPROMTriplet(uint16_t addr)
{
  TTriplet result;
  TTriplet3 t3;
  if (addr > 1020)
    return 0xFFFFFF; else
  {
    result = 0;
    EEPROM.get(addr,t3);
    memcpy(&result,&t3,sizeof(t3));
    return result;
  }
}

//==============================================================
// GetEEPROMTriplet3
//   returns the Triplet3 at an address
//==============================================================
TTriplet3 GetEEPROMTriplet3(uint16_t addr)
{
  TTriplet3 t3;
  if (addr > 1020) 
    t3.a = t3.b = t3.c = 0xFF; else
    EEPROM.get(addr,t3);
  return t3;
}

//==============================================================
// PutEEPROMTriplet
//   updates a Triplet to the EEPROM
//==============================================================
void PutEEPROMTriplet(int16_t addr, TTriplet t)
{
  if (addr <= 1020) {
    TTriplet3 t3;
    memcpy(&t3,&t,sizeof(t3));
    EEPROM.put(addr,t3);
  }
}

//==============================================================
// PutEEPROMTriplet3
//   updates a Triplet to the EEPROM
//==============================================================
void PutEEPROMTriplet3(int16_t addr, TTriplet3 t)
{
  if (addr <= 1020) {
    EEPROM.put(addr,t);
  }
}

//==============================================================
// UnpackEEPROMkind
//   returns kind of triplet
//     ekTarget    set target of servo
//     ekMinPos    set min position and pin num
//     ekMaxPos    set max position and group
//     ekVmaxAmax  set Vmax and Amax
//     ekEOF       end of memory
//==============================================================
TEEPROMkind UnpackEEPROMkind(TTriplet t)
{
  if (t == 0xFFFFFF)
    return ekEOF; else
  {
    if ((t & 0x080000) == 0)
      return ekTarget; else
    switch (t & 0xF0000) {      
      case 0x80000:  return ekMinPos;
      case 0xA0000:  return ekMaxPos;
      case 0xC0000:  return ekVmaxAmax;
      default:        return ekEOF;
    }
  }
}

//==============================================================
// GetEEPROMkind
//   gets a triplet from the EEPROM
//   returns kind of triplet
//==============================================================
TEEPROMkind GetEEPROMkind(uint16_t addr) {
  return UnpackEEPROMkind( GetEEPROMTriplet(addr));
}

//==============================================================
// EEPROMTargetToAddr
//   converts a "Target" Triplet number to an EEPROM address
//   "Target" Triplets are numbered from 0..n
//   returns EEPROM address
//==============================================================
int16_t EEPROMTargetToAddr(uint16_t TargetNum)
{
  for (int16_t result = 0; GetEEPROMkind(result) != ekEOF; result += sizeof(TTriplet3))
    if (GetEEPROMkind(result) == ekTarget)
    {
      if (TargetNum == 0)
        return result;
      TargetNum--;
    } 
  return -1;
}

//==============================================================
// EEPROMTop
//   returns the addr of the first EOF byte in the EEPROM
//==============================================================
uint16_t EEPROMTop()
{
  int16_t result;
  for (result = 0; GetEEPROMkind(result) != ekEOF; result += sizeof(TTriplet3)) {}
  return result;
}

//==============================================================
// UnpackEEPROMTarget
//   returns a "Target" Triplet 
//   "Target" Triplets are numbered from 0..n
//==============================================================
TTriplet UnpackEEPROMTarget(uint16_t TargetNum)
{
  int16_t addr;
  addr = EEPROMTargetToAddr(TargetNum);
  if (addr >= 0)
    return GetEEPROMTriplet(addr); else
    return 0xFFFFFF;
}

//==============================================================
// CheckEEPROMGood
//   if there are errors in the EEPROM then initialise it
//==============================================================
void CheckEEPROMGood()
{
  TTriplet t;
  uint8_t srv;
  bool ok = true;

  for (int16_t addr = 0; GetEEPROMkind(addr) != ekEOF; addr += sizeof(TTriplet3))
  {
    t = GetEEPROMTriplet(addr);
    if (UnpackEEPROMkind(t) == ekEOF)
      return; 
  
    srv = UnpackEEPROMservo(t);
    switch (UnpackEEPROMkind(t)) {
      case ekTarget:
      case ekMinPos:
      case ekMaxPos:
      case ekVmaxAmax: ok = ok && (srv < NumServosMax); break;
      ekEOF: return; 
      default: ok = false;
    }
  }
  if (not ok)
    InitialiseEEPROM();
}

//==============================================================
// InitialiseEEPROM
//   prt an EOF mark at the start of the EEPROM
//==============================================================
void InitialiseEEPROM()
{
  PutEEPROMTriplet(0,0xFFFFFF);
//  Serial.println("Initialising EEPROM");
}

//==============================================================
// EEPROMnumTargets
//   returns number of "target" Triplets in the EEPROM
//==============================================================
uint16_t EEPROMnumTargets()
{
  uint16_t result = 0;
  for (int16_t addr = 0; GetEEPROMkind(addr) != ekEOF; addr += sizeof(TTriplet3))
  {
    if (GetEEPROMkind(addr) == ekTarget)
      result++;
  }
  return result;
}

//==============================================================
// EEPROMhasServo
//   returns whether servo mentioned in EEPROM 
//==============================================================
bool EEPROMhasServo(uint8_t servo)
{
  for (int16_t addr = 0; GetEEPROMkind(addr) != ekEOF; addr += sizeof(TTriplet3))
    if (GetEEPROMkind(addr) == servo)
      return true;
  return false;
}

//==============================================================
// EEPROMDeleteServos
//   delete all the Triplets which refer to servos 
//     which reference a servo between servomin and servomax
//     and >= addr
//==============================================================
void EEPROMDeleteServos(uint8_t servomin, uint8_t servomax, uint16_t addr)
{
  int16_t srv,dst;
  dst = addr;
  servomin = servomin << 4;
  servomax = servomax << 4;

  for (int16_t src = addr; GetEEPROMkind(src) != ekEOF; src += sizeof(TTriplet3))
  {
    srv = GetEEPROMkind(src);
    if ((srv < servomin) or (srv > servomax))
    {
      EEPROM.update(dst,EEPROM.read(src  )); dst++;
      EEPROM.update(dst,EEPROM.read(src+1)); dst++;
      EEPROM.update(dst,EEPROM.read(src+2)); dst++;
    }
  }
  PutEEPROMTriplet(dst,0xFFFFFF);
}

//==============================================================
// EEPROMDeleteTriplet
//   delete a Triplet
//==============================================================
void EEPROMDeleteTriplet(uint16_t dst)
{ 
  while (GetEEPROMkind(dst+sizeof(TTriplet3)) != ekEOF)         
  {
    EEPROM.update(dst,EEPROM.read(dst+3)); dst++;
    EEPROM.update(dst,EEPROM.read(dst+3)); dst++;
    EEPROM.update(dst,EEPROM.read(dst+3)); dst++;
  }
  PutEEPROMTriplet(dst,0xFFFFFF);
}

//==============================================================
// EEPROMsaveTriplet
//   writes a triplet to the EEPROM
//==============================================================
void EEPROMsaveTriplet(TTriplet t, uint16_t addr)
{
  PutEEPROMTriplet(addr,t);
}

//==============================================================
// EEPROMappendTriplet
//   appends a triplet to the EEPROM
//   puts an EOF byte after it
//   returns true if there was enough space
//==============================================================
bool EEPROMappendTriplet(TTriplet t)
{
//Serial.print(F("EEPROMappendTriplet ")); Serial.println(t,HEX); 
  uint16_t addr;
  addr = EEPROMTop();
  if (addr < EEPROM.length()-1)
  {
    PutEEPROMTriplet(addr,t);
    PutEEPROMTriplet(addr+sizeof(TTriplet3),0xFFFFFF);
    return true;
  } else
    return false;
}

//==============================================================
// EEPROMTargetToTriplet
//   packs a servo number, target position and wait time into a triplet
//==============================================================
TTriplet EEPROMTargetToTriplet(uint32_t servo, uint32_t target, uint32_t wait)
{
  return
    ((servo & 0x0F) << 20) +
    ((wait & 0xFF) << 11) +
    ((target-500) & 0x7FF);
}

//==============================================================
// EEPROMMinPosToTriplet
//   packs a servo number, min position and pin number into a triplet
//   ssss1000 pppp0nnn nnnnnnnn   set min position == nnnnnnnnnnn + 500 microseconds (500..2547) ; pin == pppp
//==============================================================
TTriplet EEPROMMinPosToTriplet(uint32_t servo, uint32_t pos, uint32_t pin)
{
  return
    ((servo & 0x0F) << 20) +
    0x80000+
    ((pin & 0x0F) << 12) +
    ((pos-500) & 0x7FF);
}

//==============================================================
// EEPROMMaxPosToTriplet
//   packs a servo number, max position and group number into a triplet
//   ssss1010 gggg0nnn nnnnnnnn   set max position == nnnnnnnnnnn + 500 microseconds (500..2547) ; group == gggg;
//==============================================================
TTriplet EEPROMMaxPosToTriplet(uint32_t servo, uint32_t pos, uint32_t group)
{
  return 
    ((servo & 0x0F) << 20) +
    0xA0000+
    ((group & 0x0F) << 12) +
    ((pos-500) & 0x7FF);
}

//==============================================================
// EEPROMVmaxAmaxToTriplet
//   packs a servo number, Vmax and Amax into a triplet
//   ssss1100 vvvvvvvv aaaaaaaa   Vmax[ssss] == vvvvvvvvvv uS per interval; Amax[ssss] == aaaaaaaaaa/10.0 uS per interval;
//==============================================================
TTriplet EEPROMVmaxAmaxToTriplet(uint32_t servo, float Vmax, float Amax)
{
  return
    ((servo & 0x0F) << 20) +
    0xC0000+
    ((round(Vmax) & 0xFF) << 8) +
    (round(Amax*10.0) & 0xFF);
}

//==============================================================
// EEPROMpercentFull
//   returns how full is the EEPROM as a percentage
//==============================================================
uint16_t EEPROMpercentFull()
{
  return EEPROMTop()*100 / (EEPROM.length()-1);
}

//==============================================================
// EEPROMappendTarget
//   appends a servo number, target position and wait time to the EEPROM
//   returns true if there was enough space
//==============================================================
bool EEPROMappendTarget(uint8_t servo, uint16_t target, uint8_t wait)
{
//Serial.print(F("EEPROMappendTarget ")); Serial.print(servo); Serial.print(' '); Serial.print(target); Serial.print(' '); Serial.println(wait); 
  return EEPROMappendTriplet(EEPROMTargetToTriplet(servo,target,wait));
}

//==============================================================
// EEPROMappendMinPos
//   appends a servo number, min position and pin number to the EEPROM
//   returns true if there was enough space
//==============================================================
bool EEPROMappendMinPos(uint8_t servo, uint16_t pos, uint8_t pin)
{
  return EEPROMappendTriplet(EEPROMMinPosToTriplet(servo,pos,pin));
}

//==============================================================
// EEPROMappendMaxPos
//   appends a servo number, max position and group number to the EEPROM
//   returns true if there was enough space
//==============================================================
bool EEPROMappendMaxPos(uint8_t servo, uint16_t pos, uint8_t group)
{
  return EEPROMappendTriplet(EEPROMMaxPosToTriplet(servo,pos,group));
}

//==============================================================
// EEPROMappendVmaxAmax
//   appends a servo number, Vmax and Amax to the EEPROM
//   returns true if there was enough space
//==============================================================
bool EEPROMappendVmaxAmax(uint8_t servo, float Vmax, float Amax)
{
  return EEPROMappendTriplet(EEPROMVmaxAmaxToTriplet(servo,Vmax,Amax));
}

//==============================================================
// UnpackEEPROMservo
//   returns which servo a triplet refers to
//==============================================================
uint8_t UnpackEEPROMservo(TTriplet t)
{
  return (t >> 20) & 0x0F;
}

//==============================================================
// UnpackEEPROMdelay
//   the "wait" part of a "target" triplet
//   returns 
//==============================================================
uint8_t UnpackEEPROMdelay(TTriplet t)
{
  return (t >> 11) & 0xFF;
}

//==============================================================
// UnpackEEPROMparams
//   uses a triplet to update some of the Params of a servo
//==============================================================
void UnpackEEPROMparams(TTriplet t, TServo *Params)
{
  switch (UnpackEEPROMkind(t)) {
    case ekTarget:   Params->Target = (t & 0x7FF)+500; break;
    case ekMinPos:   Params->ServoMin = (t & 0x7FF)+500; Params->PinNum = (t >> 12) & 0x0F; break;
    case ekMaxPos:   Params->ServoMax = (t & 0x7FF)+500; Params->Group = (t >> 12) & 0x0F; break;
    case ekVmaxAmax: Params->Vmax = (t >> 8) & 0xFF; Params->Amax = (t & 0xFF)/10.0; break;
  }
}

//==============================================================
// SendEEPROM
//   send EEPROM contents to PC over serial
//     SendEEPROM: plain bytes as binary
//     SendEEPROMbytes(): bytes as hex digits
//     SendEEPROMtext(): with translation
//==============================================================
void SendEEPROM() {
  int16_t addr = 0;

  while (GetEEPROMkind(addr) != ekEOF)
  {
    Serial.write(EEPROM.read(addr)); addr++;
    Serial.write(EEPROM.read(addr)); addr++;
    Serial.write(EEPROM.read(addr)); addr++;
  }
  Serial.write((byte)0xFF); 
  Serial.write((byte)0xFF); 
  Serial.write((byte)0xFF); 
}

void SendEEPROMbytes() {
  int16_t addr = 0;
  while (GetEEPROMkind(addr) != ekEOF)
  {
    Serial.print(EEPROM.read(addr) / 16,HEX); Serial.print(EEPROM.read(addr) % 16,HEX); Serial.print(' '); addr++;
    Serial.print(EEPROM.read(addr) / 16,HEX); Serial.print(EEPROM.read(addr) % 16,HEX); Serial.print(' '); addr++;
    Serial.print(EEPROM.read(addr) / 16,HEX); Serial.print(EEPROM.read(addr) % 16,HEX); Serial.print(' '); addr++;
    if (addr % 18 == 0) Serial.println(' ');
  }
  Serial.println("FF");
}

void SendEEPROMtext() {  
  for (int16_t addr = 0; GetEEPROMkind(addr) != ekEOF; addr += sizeof(TTriplet3))
  {
    TServo Params;
    SerialPrintHex(addr,3);  Serial.print(' '); 
    TTriplet t = GetEEPROMTriplet(addr);    
    SerialPrintHex(t,6);  Serial.print(F(" servo="));
    Serial.print(UnpackEEPROMservo(t)); Serial.print(' '); 
    UnpackEEPROMparams(t, &Params);

    switch (UnpackEEPROMkind(t)) {
      case ekTarget:    Serial.print(F("Target "));   Serial.print(Params.Target);    Serial.print(F(" delay=")); if (UnpackEEPROMdelay(t) >= 128) Serial.print("Fin+"); Serial.print((UnpackEEPROMdelay(t) & 0x7F)/10.0); break;
      case ekMinPos:    Serial.print(F("MinPos "));   Serial.print(Params.ServoMin);  Serial.print(F(" PinNum="));  Serial.print(Params.PinNum); break;
      case ekMaxPos:    Serial.print(F("MaxPos "));   Serial.print(Params.ServoMax);  Serial.print(F(" Group="));   Serial.print(Params.Group); break;
      case ekVmaxAmax:  Serial.print(F("Vmax="));     Serial.print(Params.Vmax);      Serial.print(F(" Amax="));    Serial.print(Params.Amax); break;
    }
    Serial.println(""); 
  }
  SerialPrintHex(addr,3);  Serial.println(" FF");
}

void SendEEPROMplain() {
  Serial.println(F("EEPROM"));
  SendEEPROMtext();
}

//==============================================================
// RecvEEPROM
//   receive EEPROM from PC to Nano
//   next bytes received are n EEPROM triplets followed by FF
//   Nano replies with "OK" (byte 0xAA) after each byte from PC
//==============================================================
void RecvEEPROM() {
  TTriplet3 t;
  
  for (int16_t addr = 0; addr <= 1020; addr += sizeof(TTriplet3)) {
    Serial.readBytes(&t.c,1);
    Serial.readBytes(&t.b,1);
    Serial.readBytes(&t.a,1);
    PutEEPROMTriplet3(addr, t);
  }
  Serial.write(0xAA);
}

#endif

servoedit4.h

C Header File
/*************************************************************************************************************************************************
 * Nano servo driver
 *
 *************************************************************************************************************************************************/

#ifndef servoedit4_h
#define servoedit4_h

#include "Servo.h"

const int NumServosMax = 12;

uint8_t NumServos = 1;

struct TServo {
    uint8_t Group;
    uint8_t PinNum;
    uint16_t ServoMin,ServoMax;
    float Target;
    float Position;
    float Velocity;
    float Vmax;
    float Amax;
    Servo aServo;
  };

TServo Servos[NumServosMax];

bool MoveServos();
void SetServoPosition(uint8_t servo, int16_t pos);

bool AtTargets = true;

#define SERVO_FREQ 50 // 50 Hz updates

const int VelPeriod = 50; // how often the servo position is recalculated

//-----------------------------------------------------------
// sign()
//   sign of number
//   a function not a macro so you don't calulate the parameter twice
//-----------------------------------------------------------
float sign(float a) {
  if (a < 0) return -1;
  if (a > 0) return +1;
  return 0;
}

//-----------------------------------------------------------
// sqr()
//   square funtion
//   it's a function not a macro like sq() 
//   so args are not calculated twice
//-----------------------------------------------------------
float sqr(float a) {
  return a*a;
}

//-----------------------------------------------------------
// Min()
//   Min funtion
//   it's a function not a macro like min() 
//   so args are not calculated twice
//-----------------------------------------------------------
int16_t Min(int16_t a, int16_t b) {
  return min(a,b);
}

//-----------------------------------------------------------
// Max()
//   Min funtion
//   it's a function not a macro like max() 
//   so args are not calculated twice
//-----------------------------------------------------------
int16_t Max(int16_t a, int16_t b) {
  return max(a,b);
}

//-----------------------------------------------------------
// Constrain()
//   Constrain funtion
//   it's a function not a macro like constrain() 
//   so args are not calculated twice
//-----------------------------------------------------------
int16_t Constrain(int16_t a, int16_t lo, int16_t hi) {
  return constrain(a,lo,hi);
}

//-----------------------------------------------------------
// ConstrainF()
//   ConstrainF funtion
//   it's a function not a macro like constrain() 
//   so args are not calculated twice
//-----------------------------------------------------------
float ConstrainF(float a, float lo, float hi) {
  return constrain(a,lo,hi);
}

//-----------------------------------------------------------
// MoveServos()
//   returns if less than 50mS have passed since last call
//   change servo position so they're closer to their target
//   sets AtTargets to say whether all servos are at their targets
//   AtTargets will also be false if less than 50mS have passed since last call
//   returns value of AtTargets 
//-----------------------------------------------------------
bool MoveServos() {
//  Serial.write(0xFF); // to flash the Tx LED  
  AtTargets = false;
  
  static unsigned long previousMillis = 0;
  if (millis() - previousMillis < VelPeriod)
    return false;
  previousMillis = millis();
  
  float g[NumServosMax];
  float a1,tmax,sign1,p0;
  const float Vmax0 = 1000;

  AtTargets = true;

  for (byte grp=0; grp < NumServos; grp++) {
    tmax = 0;
    for (byte servo=0; servo < NumServos; servo++)
    if (Servos[servo].Group == grp) {
      if (Servos[servo].Vmax == 0)
        g[servo] = abs(Servos[servo].Target-Servos[servo].Position)/Vmax0; else
        g[servo] = abs(Servos[servo].Target-Servos[servo].Position)/Servos[servo].Vmax;
      if (Servos[servo].Amax > 0) {
        sign1 = sign(sqr(Servos[servo].Velocity) + Servos[servo].Amax*2*(2*abs(Servos[servo].Velocity) + sign(Servos[servo].Velocity)*(Servos[servo].Position-Servos[servo].Target)));
        g[servo] = Max(g[servo],
          (sign1*abs(Servos[servo].Velocity) + sqrt(abs(2*sign1*Servos[servo].Velocity*abs(Servos[servo].Velocity) + 4*Servos[servo].Amax*(Servos[servo].Position-Servos[servo].Target))))/Servos[servo].Amax);
      }
      tmax = Max(tmax,g[servo]);
    }

    for (byte servo=0; servo < NumServos; servo++)
    if (Servos[servo].Group == grp) {
      if (grp == 0)
        tmax = g[servo];
        
      if ((tmax < 2) or ((abs(Servos[servo].Velocity) <= Servos[servo].Amax+0.0001) and (abs(Servos[servo].Position+Servos[servo].Velocity-Servos[servo].Target) <= 1))) {
        Servos[servo].Position = Servos[servo].Target;
        Servos[servo].Velocity = 0;
      } else
      if (tmax > 0) {
        g[servo] = g[servo]/tmax;
        a1 = Servos[servo].Amax*sqr(g[servo]);
  
        if (a1 == 0) {
          Servos[servo].Velocity = (Servos[servo].Target-Servos[servo].Position)/tmax;
        } else {
          p0 = 2*Servos[servo].Velocity + abs(Servos[servo].Velocity)*Servos[servo].Velocity/(2*a1);
          a1 = sign(Servos[servo].Target-Servos[servo].Position-p0)*a1;
          if (a1*(Servos[servo].Target-Servos[servo].Position) < 0) 
          {
            a1  =  sign(a1)*abs(Servos[servo].Velocity)*Servos[servo].Velocity/(2*(Servos[servo].Target-Servos[servo].Position));
            a1 = constrain(a1,-Servos[servo].Amax,Servos[servo].Amax);
          }
  
          Servos[servo].Velocity = Servos[servo].Velocity+a1;
          if (Servos[servo].Vmax > 0) {
            float v1 = Servos[servo].Vmax*g[servo];
            Servos[servo].Velocity = constrain(Servos[servo].Velocity,-v1,v1);
          }
        }

        if (Servos[servo].Vmax > 0) 
          Servos[servo].Velocity = constrain(Servos[servo].Velocity,-Servos[servo].Vmax,Servos[servo].Vmax);
        Servos[servo].Position = Servos[servo].Position+Servos[servo].Velocity;
      }
      
      AtTargets = AtTargets && (Servos[servo].Position == Servos[servo].Target);
    }
  }

  for (byte servo=0; servo < NumServos; servo++) {
    Servos[servo].Position = constrain(Servos[servo].Position,Servos[servo].ServoMin,Servos[servo].ServoMax);
    Servos[servo].aServo.writeMicroseconds(Servos[servo].Position);
    if (!Servos[servo].aServo.attached())
      Servos[servo].aServo.attach(Servos[servo].PinNum);
  }
  return AtTargets;
}

//-----------------------------------------------------------
// SetServoPosition
//   sets Target and Position immediately
//-----------------------------------------------------------
void SetServoPosition(uint8_t servo, int16_t pos) {
  Servos[servo].Target = pos;
  Servos[servo].Position = pos;
  Servos[servo].aServo.writeMicroseconds(pos);
  if (!Servos[servo].aServo.attached())
    Servos[servo].aServo.attach(Servos[servo].PinNum);
}

#endif

SimpleSH1106.h

C Header File
/***************************************************
  Arduino TFT graphics library for the SH1106

  typical use is:
      #include <Wire.h>
      #include "SimpleSH1106.h"
      Wire.begin(); // join i2c bus as master
      TWBR = 5; // freq=615kHz period=1.625uS
      initSH1106();
      DrawImageSH1106(20,1,imgSmiley);

  typical I2C speed values are:
      TWBR = 1; // freq=888kHz period=1.125uS
      TWBR = 2; // freq=800kHz period=1.250uS
      TWBR = 3; // freq=727kHz period=1.375uS
      TWBR = 4; // freq=666kHz period=1.500uS
      TWBR = 5; // freq=615kHz period=1.625uS
      TWBR = 10; // freq=444kHz period=2.250uS
      TWBR = 20; // freq=285kHz period=3.500uS
      TWBR = 30; // freq=210kHz period=4.750uS
      TWBR = 40; // freq=166kHz period=6.000uS
      TWBR = 50; // freq=137kHz period=7.250uS

***************************************************/

#ifndef SimpleSH1106_h
#define SimpleSH1106_h

#include <Wire.h>
#include <Arduino.h>

const int addr = 0x3C;

void DrawByteSH1106(byte x, byte page, byte i);
void setupPageCol(int page, int col);
void setupCol(int col);
void setupPage(int page);
void clearSH1106();
void initSH1106();
void DrawBarSH1106(byte bar);
int DrawImageSH1106(int16_t x, int16_t p, const uint8_t *bitmap);
int DrawCharSH1106(uint8_t c, byte x, byte page, word Font);
int DrawStringSH1106(char *s, byte x, byte page, word Font);
int DrawIntSH1106(long i, byte x, byte page, word Font);

extern bool BoldSH1106;

const byte SmallFont[] PROGMEM = {
  ' ', // first char
  1, // height in pages
  4, 0x00,0x00,0x00,0x00,  //
  1, 0x5F,  // !
  3, 0x03,0x00,0x03,  // "
  5, 0x14,0x7F,0x14,0x7F,0x14,  // #
  5, 0x24,0x4A,0xFF,0x52,0x24,  // $
  6, 0x46,0x26,0x10,0x08,0x64,0x62,  // %
  5, 0x3A,0x45,0x4A,0x30,0x48,  // &
  1, 0x03,  // '
  2, 0x7E,0x81,  // (
  2, 0x81,0x7E,  // )
  2, 0x03,0x03,  // *
  3, 0x10,0x38,0x10,  // +
  1, 0xC0,  // ,
  2, 0x10,0x10,  // -
  1, 0x40,  // .
  2, 0x78,0x0F,  // /
  4, 0x3E,0x41,0x41,0x3E,  // 0
  2, 0x02,0x7F,  // 1
  4, 0x62,0x51,0x49,0x46,  // 2
  4, 0x22,0x41,0x49,0x36,  // 3
  4, 0x18,0x16,0x7F,0x10,  // 4
  4, 0x2F,0x45,0x45,0x39,  // 5
  4, 0x3E,0x49,0x49,0x32,  // 6
  4, 0x01,0x71,0x0D,0x03,  // 7
  4, 0x36,0x49,0x49,0x36,  // 8
  4, 0x26,0x49,0x49,0x3E,  // 9
  1, 0x48,  // :
  1, 0xC8,  // ;
  3, 0x10,0x28,0x44,  // <
  3, 0x28,0x28,0x28,  // =
  3, 0x44,0x28,0x10,  // >
  4, 0x02,0x51,0x09,0x06,  // ?
  8, 0x3C,0x42,0x99,0xA5,0x9D,0xA1,0x22,0x1C,  // @
  6, 0x60,0x1C,0x13,0x13,0x1C,0x60,  // A
  6, 0x7F,0x49,0x49,0x49,0x49,0x36,  // B
  6, 0x3E,0x41,0x41,0x41,0x41,0x22,  // C
  6, 0x7F,0x41,0x41,0x41,0x41,0x3E,  // D
  5, 0x7F,0x49,0x49,0x49,0x41,  // E
  5, 0x7F,0x09,0x09,0x09,0x01,  // F
  6, 0x3E,0x41,0x41,0x49,0x29,0x7A,  // G
  6, 0x7F,0x08,0x08,0x08,0x08,0x7F,  // H
  1, 0x7F,  // I
  4, 0x30,0x40,0x40,0x3F,  // J
  5, 0x7F,0x08,0x14,0x22,0x41,  // K
  4, 0x7F,0x40,0x40,0x40,  // L
  8, 0x7F,0x03,0x0C,0x30,0x30,0x0C,0x03,0x7F,  // M
  6, 0x7F,0x03,0x0C,0x30,0x40,0x7F,  // N
  6, 0x3E,0x41,0x41,0x41,0x41,0x3E,  // O
  6, 0x7F,0x09,0x09,0x09,0x09,0x06,  // P
  6, 0x3E,0x41,0x41,0x51,0x61,0xBE,  // Q
  6, 0x7F,0x09,0x09,0x09,0x09,0x76,  // R
  5, 0x26,0x49,0x49,0x49,0x32,  // S
  5, 0x01,0x01,0x7F,0x01,0x01,  // T
  5, 0x3F,0x40,0x40,0x40,0x3F,  // U
  5, 0x03,0x1C,0x60,0x1C,0x03,  // V
  7, 0x03,0x1C,0x60,0x18,0x60,0x1C,0x03,  // W
  5, 0x63,0x14,0x08,0x14,0x63,  // X
  5, 0x03,0x04,0x78,0x04,0x03,  // Y
  5, 0x61,0x51,0x49,0x45,0x43,  // Z
  2, 0xFF,0x81,  // [
  2, 0x1E,0x70,
  2, 0x81,0xFF,  // ]
  3, 0x02,0x01,0x02,  // ^
  4, 0x00,0x00,0x00,0x00,  // _
  2, 0x01,0x02,  // `
  4, 0x20,0x54,0x54,0x78,  // a
  4, 0x7F,0x44,0x44,0x38,  // b
  4, 0x38,0x44,0x44,0x28,  // c
  4, 0x38,0x44,0x44,0x7F,  // d
  4, 0x38,0x54,0x54,0x18,  // e
  2, 0x7E,0x09,  // f
  4, 0x18,0xA4,0xA4,0x7C,  // g
  4, 0x7F,0x08,0x04,0x78,  // h
  1, 0x7D,  // i
  1, 0xFD,  // j
  4, 0x7F,0x18,0x24,0x40,  // k
  1, 0x7F,  // l
  7, 0x7C,0x04,0x04,0x7C,0x04,0x04,0x78,  // m
  4, 0x7C,0x04,0x04,0x78,  // n
  5, 0x38,0x44,0x44,0x44,0x38,  // o
  4, 0xFC,0x24,0x24,0x18,  // p
  4, 0x18,0x24,0x24,0xFC,  // q
  2, 0x7C,0x04,  // r
  4, 0x48,0x54,0x54,0x24,  // s
  2, 0x3E,0x44,  // t
  4, 0x3C,0x40,0x40,0x7C,  // u
  4, 0x1C,0x60,0x60,0x1C,  // v
  5, 0x1C,0x60,0x18,0x60,0x1C,  // w
  3, 0x6C,0x10,0x6C,  // x
  3, 0x9C,0xA0,0x7C,  // y
  3, 0x64,0x54,0x4C,  // z
  2, 0x91,0x6E,  // {
  1, 0xFE,  // |
  2, 0x6E,0x91,  // }
  4, 0x04,0x02,0x04,0x02,  // ~
  1, 0xFE,  // 
  0};

const byte DigitsFont[] PROGMEM = {
  '-', //first char
  1, // height in pages
  4, 0x10,0x10,0x10,0x10,       // -
  1, 0x40,                      // .
  3, 0x60,0x1C,0x03,            // /
  4, 0x3E,0x41,0x41,0x3E,       // 0
  2, 0x02,0x7F,                 // 1
  4, 0x62,0x51,0x49,0x46,       // 2
  4, 0x22,0x41,0x49,0x36,       // 3
  4, 0x18,0x16,0x7F,0x10,       // 4
  4, 0x2F,0x45,0x45,0x39,       // 5
  4, 0x3E,0x49,0x49,0x32,       // 6
  4, 0x01,0x71,0x0D,0x03,       // 7
  4, 0x36,0x49,0x49,0x36,       // 8
  4, 0x26,0x49,0x49,0x3E,       // 9
  0}; // end

const byte LargeDigitsFont[] PROGMEM = {
  '+', // first char
  2, // height in pages
  12, 0x00,0x00,0x00,0x00,0x00,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,  // +
  4, 0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x40,  // ,
  6, 0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0x06,  // -
  2, 0x00,0x00,0xC0,0xC0,  // .
  6, 0x00,0x00,0xC0,0xFC,0xFE,0x0E,0xE0,0xFC,0x7F,0x07,0x00,0x00,  // /
  10, 0xF0,0xFC,0x1E,0x0E,0x06,0x06,0x0E,0x1E,0xFC,0xF0,0x1F,0x7F,0xF0,0xE0,0xC0,0xC0,0xE0,0xF0,0x7F,0x1F,  // 0
  5, 0x70,0x38,0x1C,0xFE,0xFE,0x00,0x00,0x00,0xFF,0xFF,  // 1
  10, 0x18,0x1C,0x0E,0x06,0x06,0x06,0x0E,0x8E,0xFC,0xF8,0xC0,0xE0,0xF0,0xD8,0xDC,0xCE,0xC7,0xC3,0xC1,0xC0,  // 2
  10, 0x18,0x1C,0x0E,0x06,0x86,0x86,0xCE,0xFC,0x78,0x00,0x30,0x70,0xE0,0xC0,0xC1,0xC1,0xC1,0xE3,0x7F,0x3E,  // 3
  10, 0x00,0x00,0xC0,0xE0,0x70,0x38,0xFC,0xFE,0x00,0x00,0x0E,0x0F,0x0F,0x0C,0x0C,0x0C,0xFF,0xFF,0x0C,0x0C,  // 4
  10, 0xC0,0xFE,0xFE,0xCE,0xC6,0xC6,0xC6,0xC6,0x86,0x00,0x31,0x71,0xE1,0xC0,0xC0,0xC0,0xC0,0xF1,0x7F,0x3F,  // 5
  10, 0xF0,0xF8,0xBC,0xCE,0xC6,0xC6,0xC6,0xCE,0x9C,0x18,0x1F,0x7F,0x73,0xE1,0xC0,0xC0,0xC0,0xF1,0x7F,0x3F,  // 6
  10, 0x06,0x06,0x06,0x06,0x86,0xC6,0xF6,0x3E,0x1E,0x06,0x00,0x00,0xF0,0xFE,0x3F,0x03,0x00,0x00,0x00,0x00,  // 7
  10, 0x00,0x78,0xFC,0xCE,0x86,0x86,0xCE,0xFC,0x78,0x00,0x3E,0x7F,0xE3,0xC1,0xC1,0xC1,0xC1,0xE3,0x7E,0x3E,  // 8
  10, 0xF8,0xFC,0x1E,0x0E,0x06,0x06,0x06,0x9C,0xFC,0xF0,0x31,0x73,0xE7,0xC6,0xC6,0xC6,0xE7,0x7B,0x3F,0x1F,  // 9
  0};

const byte imgSmiley[] PROGMEM = {
  21, // width
  3, // pages
  4,0,192,48,8,130,4,130,130,133,1,130,130,130,4,7,8,48,192,0,31,96,128,130,0,1,67,130,132,1,3,131,0,
  1,3,130,132,1,67,130,0,3,128,96,31,130,0,2,1,2,130,4,130,8,133,17,130,8,130,4,2,2,1,130,0};

const byte MediumFont[] PROGMEM = {
  ' ', // first char
  2, // height in pages
  7, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  //
  1, 0xFE,0x17,  // !
  4, 0x1E,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,  // "
  9, 0x20,0x20,0xF0,0x2E,0x20,0x20,0xF0,0x2E,0x20,0x02,0x1E,0x03,0x02,0x02,0x1E,0x03,0x02,0x02,  // #
  7, 0x38,0x44,0x42,0xFF,0x82,0x82,0x0C,0x04,0x08,0x10,0x3F,0x10,0x08,0x07,  // $
  12, 0x3C,0x42,0x42,0x42,0x3C,0x80,0x70,0x0C,0x82,0x80,0x80,0x00,0x00,0x00,0x00,0x18,0x06,0x01,0x00,0x0F,0x10,0x10,0x10,0x0F,  // %
  9, 0x00,0x9C,0x62,0xC2,0x22,0x1C,0x00,0x00,0x00,0x07,0x08,0x10,0x10,0x11,0x0A,0x04,0x0A,0x11,  // &
  1, 0x1E,0x00,  // '
  3, 0xE0,0x1C,0x02,0x0F,0x70,0x80,  // (
  3, 0x02,0x1C,0xE0,0x80,0x70,0x0F,  // )
  5, 0x04,0x34,0x0E,0x34,0x04,0x00,0x00,0x00,0x00,0x00,  // *
  7, 0x80,0x80,0x80,0xF0,0x80,0x80,0x80,0x00,0x00,0x00,0x07,0x00,0x00,0x00,  // +
  1, 0x00,0x70,  // ,
  4, 0x00,0x00,0x00,0x00,0x02,0x02,0x02,0x02,  // -
  1, 0x00,0x10,  // .
  4, 0x00,0x80,0x78,0x06,0x18,0x07,0x00,0x00,  // /
  7, 0xF8,0x04,0x02,0x02,0x02,0x04,0xF8,0x07,0x08,0x10,0x10,0x10,0x08,0x07,  // 0
  4, 0x10,0x08,0x04,0xFE,0x00,0x00,0x00,0x1F,  // 1
  7, 0x08,0x04,0x02,0x02,0x02,0xC6,0x38,0x10,0x18,0x14,0x12,0x11,0x10,0x10,  // 2
  7, 0x08,0x04,0x42,0x42,0x62,0xBC,0x00,0x0C,0x08,0x10,0x10,0x10,0x08,0x07,  // 3
  8, 0x00,0x80,0x40,0x30,0x08,0x04,0xFE,0x00,0x03,0x02,0x02,0x02,0x02,0x02,0x1F,0x02,  // 4
  7, 0x70,0x2E,0x22,0x22,0x22,0x42,0x82,0x04,0x08,0x10,0x10,0x10,0x08,0x07,  // 5
  7, 0xF8,0x44,0x22,0x22,0x22,0x44,0x88,0x07,0x08,0x10,0x10,0x10,0x08,0x07,  // 6
  7, 0x02,0x02,0x02,0xC2,0x32,0x0E,0x02,0x00,0x00,0x1C,0x03,0x00,0x00,0x00,  // 7
  7, 0x18,0xA4,0x42,0x42,0x42,0xA4,0x18,0x07,0x08,0x10,0x10,0x10,0x08,0x07,  // 8
  7, 0x78,0x84,0x02,0x02,0x02,0x84,0xF8,0x04,0x08,0x11,0x11,0x11,0x08,0x07,  // 9
  1, 0x10,0x10,  // :
  1, 0x10,0x70,  // ;
  7, 0x80,0x40,0x40,0x20,0x20,0x20,0x10,0x00,0x01,0x01,0x02,0x02,0x02,0x04,  // <
  7, 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x02,0x02,0x02,0x02,0x02,0x02,0x02,  // =
  7, 0x10,0x20,0x20,0x20,0x40,0x40,0x80,0x04,0x02,0x02,0x02,0x01,0x01,0x00,  // >
  7, 0x18,0x04,0x02,0x02,0x82,0x44,0x38,0x00,0x00,0x00,0x17,0x00,0x00,0x00,  // ?
  15, 0xC0,0x30,0x08,0x84,0x44,0x22,0x12,0x12,0x12,0x22,0xF2,0x04,0x04,0x18,0xE0,0x0F,0x10,0x20,0x47,0x48,0x90,0x90,0x90,0x88,0x9F,0x90,0x90,0x48,0x44,0x23,  // @
  9, 0x00,0x00,0xE0,0x1C,0x02,0x1C,0xE0,0x00,0x00,0x18,0x07,0x01,0x01,0x01,0x01,0x01,0x07,0x18,  // A
  9, 0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0xA6,0x18,0x1F,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x07,  // B
  10, 0xF0,0x08,0x04,0x02,0x02,0x02,0x02,0x02,0x04,0x08,0x03,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x08,0x04,  // C
  10, 0xFE,0x02,0x02,0x02,0x02,0x02,0x02,0x04,0x08,0xF0,0x1F,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x03,  // D
  9, 0xFE,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x02,0x1F,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,  // E
  8, 0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x02,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // F
  10, 0xF0,0x08,0x04,0x02,0x02,0x82,0x82,0x84,0x88,0x80,0x03,0x04,0x08,0x10,0x10,0x10,0x10,0x08,0x04,0x03,  // G
  9, 0xFE,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFE,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,  // H
  1, 0xFE,0x1F,  // I
  6, 0x00,0x00,0x00,0x00,0x00,0xFE,0x0E,0x10,0x10,0x10,0x10,0x0F,  // J
  9, 0xFE,0x00,0x80,0x40,0xE0,0x10,0x08,0x04,0x02,0x1F,0x01,0x00,0x00,0x00,0x01,0x06,0x08,0x10,  // K
  7, 0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x10,0x10,0x10,0x10,0x10,0x10,  // L
  11, 0xFE,0x0C,0x30,0xC0,0x00,0x00,0x00,0xC0,0x30,0x0C,0xFE,0x1F,0x00,0x00,0x00,0x07,0x18,0x07,0x00,0x00,0x00,0x1F,  // M
  9, 0xFE,0x04,0x18,0x20,0xC0,0x00,0x00,0x00,0xFE,0x1F,0x00,0x00,0x00,0x00,0x01,0x06,0x08,0x1F,  // N
  10, 0xF0,0x08,0x04,0x02,0x02,0x02,0x02,0x04,0x08,0xF0,0x03,0x04,0x08,0x10,0x10,0x10,0x10,0x08,0x04,0x03,  // O
  9, 0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x44,0x38,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // P
  10, 0xF0,0x08,0x04,0x02,0x02,0x02,0x02,0x04,0x08,0xF0,0x03,0x04,0x08,0x10,0x10,0x14,0x14,0x08,0x1C,0x17,  // Q
  9, 0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0x44,0x38,0x1F,0x00,0x00,0x00,0x00,0x01,0x06,0x08,0x10,  // R
  9, 0x18,0x24,0x42,0x42,0x42,0x82,0x82,0x84,0x08,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x08,0x07,  // S
  9, 0x02,0x02,0x02,0x02,0xFE,0x02,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,  // T
  9, 0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x07,0x08,0x10,0x10,0x10,0x10,0x10,0x08,0x07,  // U
  9, 0x06,0x38,0xC0,0x00,0x00,0x00,0xC0,0x38,0x06,0x00,0x00,0x01,0x06,0x18,0x06,0x01,0x00,0x00,  // V
  15, 0x06,0x78,0x80,0x00,0x00,0xE0,0x1C,0x02,0x1C,0xE0,0x00,0x00,0x80,0x78,0x06,0x00,0x00,0x07,0x18,0x07,0x00,0x00,0x00,0x00,0x00,0x07,0x18,0x07,0x00,0x00,  // W
  11, 0x00,0x02,0x04,0x18,0xA0,0x40,0xA0,0x18,0x04,0x02,0x00,0x10,0x08,0x04,0x03,0x00,0x00,0x00,0x03,0x04,0x08,0x10,  // X
  9, 0x02,0x0C,0x10,0x60,0x80,0x60,0x10,0x0C,0x02,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,  // Y
  9, 0x00,0x02,0x02,0x02,0xC2,0x22,0x1A,0x06,0x02,0x10,0x18,0x16,0x11,0x10,0x10,0x10,0x10,0x10,  // Z
  3, 0xFE,0x02,0x02,0xFF,0x80,0x80,  // [
  4, 0x06,0x78,0x80,0x00,0x00,0x00,0x07,0x18,
  3, 0x02,0x02,0xFE,0x80,0x80,0xFF,  // ]
  7, 0x40,0x30,0x0C,0x02,0x0C,0x30,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ^
  9, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,  // _
  2, 0x02,0x04,0x00,0x00,  // `
  7, 0x40,0x20,0x10,0x10,0x90,0x90,0xE0,0x0E,0x11,0x11,0x11,0x10,0x08,0x1F,  // a
  7, 0xFE,0x20,0x10,0x10,0x10,0x20,0xC0,0x1F,0x08,0x10,0x10,0x10,0x08,0x07,  // b
  6, 0xC0,0x20,0x10,0x10,0x10,0x20,0x07,0x08,0x10,0x10,0x10,0x08,  // c
  7, 0xC0,0x20,0x10,0x10,0x10,0x20,0xFE,0x07,0x08,0x10,0x10,0x10,0x08,0x1F,  // d
  7, 0xC0,0x20,0x10,0x10,0x10,0x20,0xC0,0x07,0x09,0x11,0x11,0x11,0x09,0x05,  // e
  4, 0x10,0xFC,0x12,0x12,0x00,0x1F,0x00,0x00,  // f
  7, 0xC0,0x20,0x10,0x10,0x10,0x20,0xF0,0x47,0x88,0x90,0x90,0x90,0x48,0x3F,  // g
  6, 0xFE,0x20,0x10,0x10,0x10,0xE0,0x1F,0x00,0x00,0x00,0x00,0x1F,  // h
  1, 0xF2,0x1F,  // i
  2, 0x00,0xF2,0x80,0x7F,  // j
  7, 0xFE,0x00,0x00,0x80,0x40,0x20,0x10,0x1F,0x02,0x01,0x01,0x06,0x08,0x10,  // k
  1, 0xFE,0x1F,  // l
  11, 0xF0,0x20,0x10,0x10,0x10,0xE0,0x20,0x10,0x10,0x10,0xE0,0x1F,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0x1F,  // m
  6, 0xF0,0x20,0x10,0x10,0x10,0xE0,0x1F,0x00,0x00,0x00,0x00,0x1F,  // n
  7, 0xC0,0x20,0x10,0x10,0x10,0x20,0xC0,0x07,0x08,0x10,0x10,0x10,0x08,0x07,  // o
  7, 0xF0,0x20,0x10,0x10,0x10,0x20,0xC0,0xFF,0x08,0x10,0x10,0x10,0x08,0x07,  // p
  7, 0xC0,0x20,0x10,0x10,0x10,0x20,0xF0,0x07,0x08,0x10,0x10,0x10,0x08,0xFF,  // q
  4, 0xF0,0x20,0x10,0x10,0x1F,0x00,0x00,0x00,  // r
  6, 0xE0,0x10,0x10,0x10,0x10,0x20,0x08,0x11,0x11,0x11,0x11,0x0E,  // s
  4, 0x10,0xFC,0x10,0x10,0x00,0x1F,0x10,0x10,  // t
  6, 0xF0,0x00,0x00,0x00,0x00,0xF0,0x0F,0x10,0x10,0x10,0x08,0x1F,  // u
  7, 0x30,0xC0,0x00,0x00,0x00,0xC0,0x30,0x00,0x00,0x07,0x18,0x07,0x00,0x00,  // v
  11, 0x30,0xC0,0x00,0x00,0xC0,0x30,0xC0,0x00,0x00,0xC0,0x30,0x00,0x07,0x18,0x07,0x00,0x00,0x00,0x07,0x18,0x07,0x00,  // w
  7, 0x10,0x20,0xC0,0x00,0xC0,0x20,0x10,0x10,0x08,0x06,0x01,0x06,0x08,0x10,  // x
  7, 0x70,0x80,0x00,0x00,0x00,0xC0,0x30,0x00,0x81,0x8E,0x70,0x0E,0x01,0x00,  // y
  7, 0x10,0x10,0x10,0x10,0xD0,0x30,0x10,0x10,0x18,0x16,0x11,0x10,0x10,0x10,  // z
  5, 0x00,0x00,0xFC,0x02,0x02,0x01,0x01,0x7E,0x80,0x80,  // {
  1, 0xFE,0xFF,  // |
  5, 0x02,0x02,0xFC,0x00,0x00,0x80,0x80,0x7E,0x01,0x01,  // }
  8, 0x80,0x40,0x40,0x40,0x80,0x80,0x80,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ~
  0};

const byte LargeCondensedFont[] PROGMEM = {
  ' ', // first char
  3, // height in pages
  9, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  //
  3, 0x0C,0xFC,0x0C,0x80,0xBF,0x80,0x03,0x03,0x03,  // !
  7, 0x0C,0xFC,0x0C,0x00,0x0C,0xFC,0x0C,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // "
  12, 0x00,0x80,0xC0,0xC0,0xFC,0xC4,0xC0,0xC0,0xFC,0xC4,0xC0,0x40,0x10,0x18,0xF8,0x3F,0x19,0x18,0xF8,0x3F,0x18,0x18,0x08,0x00,0x00,0x02,0x03,0x00,0x00,0x02,0x03,0x00,0x00,0x00,0x00,0x00,  // #
  9, 0xE0,0xF8,0xF8,0x0C,0xFF,0x0C,0x38,0x18,0x00,0xC0,0xE1,0x83,0x03,0xFF,0x06,0xFE,0xFC,0x70,0x00,0x01,0x01,0x03,0x0F,0x03,0x01,0x01,0x00,  // $
  14, 0xF0,0xF8,0x04,0x04,0xF8,0xF0,0x00,0x00,0x80,0x20,0x08,0x00,0x00,0x00,0x00,0x01,0x02,0x02,0x41,0x10,0x00,0x00,0xF0,0xF8,0x04,0x04,0xF8,0xF0,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x02,0x01,0x00,  // %
  13, 0x00,0x00,0x30,0xF8,0xF8,0x0C,0x0C,0x8C,0xF8,0x70,0x00,0x00,0x00,0x70,0xF8,0xFC,0x86,0x03,0x07,0x0F,0x39,0xF0,0xE0,0xF0,0x1E,0x00,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x01,0x01,0x03,0x03,  // &
  3, 0xFC,0xFC,0xFC,0x01,0x01,0x01,0x00,0x00,0x00,  // '
  5, 0x00,0xC0,0xF8,0x3C,0x04,0x3E,0xFF,0xFF,0x00,0x00,0x00,0x03,0x0F,0x3C,0x20,  // (
  5, 0x04,0x3C,0xF8,0xC0,0x00,0x00,0x00,0xFF,0xFF,0x7E,0x20,0x3C,0x1F,0x03,0x00,  // )
  8, 0x90,0x90,0x60,0x6C,0x6C,0x60,0x90,0x90,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // *
  10, 0x00,0x00,0x00,0x00,0xE0,0xE0,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x7F,0x7F,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // +
  3, 0x00,0x00,0x00,0x80,0x80,0x80,0x03,0x0F,0x03,  // ,
  5, 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,  // -
  3, 0x00,0x00,0x00,0x80,0x80,0x80,0x03,0x03,0x03,  // .
  9, 0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x70,0x1C,0x00,0x00,0x80,0xE0,0x3C,0x07,0x01,0x00,0x00,0x38,0x0E,0x03,0x00,0x00,0x00,0x00,0x00,0x00,  // /
  
  10, 0x80,0xF0,0xF8,0x1C,0x0C,0x0C,0x1C,0xF8,0xF0,0x80,0x1F,0xFF,0xFF,0x80,0x00,0x00,0x80,0xFF,0xFF,0x1F,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x00,  // 0
  10, 0x20,0x30,0x10,0xF8,0xFC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,  // 1
  10, 0x00,0x30,0x78,0x18,0x0C,0x0C,0x0C,0xF8,0xF8,0xE0,0x00,0x80,0xC0,0x60,0x70,0x38,0x1E,0x0F,0x03,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,  // 2
  10, 0x18,0x38,0x1C,0x0C,0x0C,0x0C,0xF8,0xF8,0xE0,0x00,0x80,0xC0,0x80,0x06,0x06,0x06,0xFD,0xF9,0x70,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x00,0x00,  // 3
  10, 0x00,0x00,0x80,0xE0,0x38,0xFC,0xFC,0xFC,0x00,0x00,0x30,0x3E,0x37,0x30,0x30,0xFF,0xFF,0xFF,0x30,0x30,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,  // 4
  10, 0x00,0x00,0xFC,0x0C,0x0C,0x8C,0x8C,0x0C,0x0C,0x00,0x40,0xC2,0xE7,0x83,0x01,0x01,0x83,0xFF,0xFF,0x3C,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x00,  // 5
  10, 0x00,0xE0,0xF8,0x18,0x0C,0x0C,0x1C,0x78,0x38,0x20,0x1F,0xFF,0xFF,0x84,0x02,0x03,0x03,0xFE,0xFE,0x78,0x00,0x00,0x01,0x01,0x03,0x03,0x03,0x01,0x01,0x00,  // 6
  10, 0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xEC,0x3C,0x0C,0x00,0x00,0x00,0x80,0xF8,0xFF,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,  // 7
  10, 0x60,0xF8,0xF8,0x0C,0x0C,0x0C,0x0C,0xF8,0xF8,0x60,0x70,0xF9,0xFD,0x0F,0x06,0x06,0x0F,0xFF,0xFD,0x70,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x00,  // 8
  10, 0xE0,0xF0,0xF8,0x1C,0x0C,0x0C,0x1C,0xF8,0xF0,0x80,0x01,0x87,0xC7,0x0E,0x0C,0x04,0x82,0xFF,0xFF,0x0F,0x00,0x01,0x01,0x03,0x03,0x03,0x01,0x01,0x00,0x00,  // 9

  3, 0xC0,0xC0,0xC0,0x81,0x81,0x81,0x03,0x03,0x03,  // :
  3, 0xC0,0xC0,0xC0,0x81,0x81,0x81,0x03,0x0F,0x03,  // ;
  10, 0x00,0x00,0x80,0x80,0xC0,0x40,0x60,0x20,0x30,0x10,0x07,0x07,0x0D,0x08,0x18,0x10,0x30,0x20,0x60,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // <
  10, 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // =
  10, 0x10,0x30,0x20,0x60,0x40,0xC0,0x80,0x80,0x00,0x00,0x40,0x60,0x20,0x30,0x10,0x18,0x08,0x0D,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // >
  9, 0x20,0x78,0x18,0x0C,0x0C,0x0C,0xF8,0xF8,0xE0,0x00,0x00,0x00,0xB8,0xBC,0xBE,0x07,0x03,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,  // ?
  14, 0x00,0xE0,0x30,0x18,0x08,0x80,0xC4,0xC4,0x84,0x40,0x08,0x10,0xE0,0x80,0x0F,0x7F,0xC0,0x80,0x1E,0x7F,0x60,0x20,0x3F,0x60,0x60,0x30,0x1F,0x07,0x00,0x00,0x00,0x01,0x01,0x00,0x02,0x02,0x02,0x02,0x00,0x00,0x01,0x00,  // @
  11, 0x00,0x00,0xC0,0xFC,0xFC,0x1C,0xFC,0xFC,0xC0,0x00,0x00,0x00,0xF0,0xFF,0x3F,0x30,0x30,0x30,0x3F,0xFF,0xF0,0x00,0x02,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x02,  // A
  11, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0xF8,0xF8,0xE0,0x00,0xFF,0xFF,0xFF,0x06,0x06,0x06,0x06,0x8F,0xF9,0xF8,0x70,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x01,0x01,0x01,0x00,  // B
  11, 0x00,0xE0,0xF8,0x18,0x0C,0x0C,0x0C,0x18,0xF8,0x70,0x40,0x0F,0x7F,0xFF,0x80,0x00,0x00,0x00,0x80,0xF0,0xE0,0x20,0x00,0x00,0x01,0x01,0x03,0x03,0x03,0x01,0x01,0x00,0x00,  // C
  11, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x18,0x38,0xF0,0xE0,0x80,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x80,0xC0,0xFF,0x7F,0x0F,0x03,0x03,0x03,0x03,0x03,0x03,0x01,0x01,0x00,0x00,0x00,  // D
  10, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0xFF,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,  // E
  9, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0xFF,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,  // F
  12, 0x00,0xE0,0xF0,0x18,0x08,0x0C,0x0C,0x0C,0x18,0x78,0x30,0x00,0x0F,0x7F,0xFF,0xC0,0x00,0x00,0x0C,0x0C,0x8C,0xFC,0xFC,0xFC,0x00,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x03,0x03,  // G
  10, 0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x06,0x06,0x06,0x06,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x03,0x03,0x03,  // H
  3, 0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x03,0x03,0x03,  // I
  5, 0x00,0x00,0xFC,0xFC,0xFC,0x00,0x00,0xFF,0xFF,0x7F,0x03,0x03,0x03,0x01,0x00,  // J
  10, 0xFC,0xFC,0xFC,0x00,0xC0,0xF0,0x7C,0x1C,0x0C,0x00,0xFF,0xFF,0xFF,0x07,0x07,0x0F,0x7E,0xF8,0xE0,0x80,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x01,0x03,0x03,  // K
  9, 0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,  // L
  14, 0xFC,0xFC,0xFC,0x1C,0xF8,0x00,0x00,0x00,0x00,0xF8,0x1C,0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x00,0x03,0x3F,0xF0,0xE0,0x3F,0x01,0x00,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,  // M
  10, 0xFC,0xFC,0xFC,0x38,0xC0,0x00,0x00,0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x00,0x01,0x1E,0xE0,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x03,0x03,0x03,  // N
  12, 0x00,0xE0,0xF8,0x18,0x0C,0x0C,0x0C,0x0C,0x18,0xF8,0xE0,0x00,0x0F,0x7F,0xFF,0x80,0x00,0x00,0x00,0x00,0x80,0xFF,0x7F,0x0F,0x00,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x00,0x00,  // O
  11, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0x18,0xF8,0xF0,0xE0,0xFF,0xFF,0xFF,0x0C,0x0C,0x0C,0x0C,0x06,0x07,0x03,0x01,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // P
  11, 0x80,0xF0,0xF8,0x18,0x0C,0x0C,0x0C,0x18,0xF8,0xF0,0x80,0x1F,0xFF,0xFF,0x80,0x00,0x00,0x00,0x80,0xFF,0x7F,0x0F,0x00,0x00,0x01,0x01,0x03,0x0F,0x1F,0x31,0x30,0x30,0x00,  // Q
  12, 0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0x18,0xF8,0xF8,0xE0,0x00,0xFF,0xFF,0xFF,0x06,0x06,0x06,0x06,0x1F,0xFF,0xF3,0x80,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x02,  // R
  10, 0xE0,0xF8,0xF8,0x0C,0x0C,0x0C,0x1C,0x78,0x30,0x20,0x40,0xC1,0xE3,0x83,0x07,0x06,0x0E,0xFC,0xFC,0x70,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x00,  // S
  10, 0x0C,0x0C,0x0C,0xFC,0xFC,0xFC,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,  // T
  10, 0xFC,0xFC,0xFC,0x00,0x00,0x00,0x00,0xFC,0xFC,0xFC,0x3F,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0x3F,0x00,0x01,0x01,0x03,0x03,0x03,0x03,0x01,0x01,0x00,  // U
  11, 0x04,0xFC,0xFC,0xC0,0x00,0x00,0x00,0xC0,0xFC,0xFC,0x04,0x00,0x00,0x1F,0xFF,0xF8,0xC0,0xF8,0xFF,0x1F,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,  // V
  17, 0x04,0xFC,0xF0,0x00,0x00,0x00,0x80,0xF8,0x3C,0xFC,0xC0,0x00,0x00,0x00,0xE0,0xFC,0x04,0x00,0x00,0x1F,0xFC,0xE0,0xF8,0x1F,0x01,0x00,0x01,0x3F,0xF8,0xE0,0xFC,0x0F,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x01,0x00,0x00,0x00,  // W
  10, 0x04,0x1C,0x7C,0xE0,0x80,0xC0,0xF0,0x7C,0x1C,0x00,0x80,0xE0,0xF8,0x7F,0x1F,0x3F,0xFD,0xE0,0x80,0x00,0x03,0x03,0x01,0x00,0x00,0x00,0x01,0x03,0x03,0x02,  // X
  9, 0x1C,0x7C,0xF8,0xC0,0x80,0xC0,0xF8,0x7C,0x1C,0x00,0x00,0x01,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,  // Y
  10, 0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xFC,0xFC,0x3C,0x0C,0x00,0xC0,0xF0,0x7C,0x3F,0x0F,0x03,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,  // Z
  4, 0xFC,0xFC,0x04,0x04,0xFF,0xFF,0x00,0x00,0x3F,0x3F,0x20,0x20,  // [
  9, 0x1C,0x70,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x0F,0x38,0xE0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1E,0x30,
  4, 0x04,0x04,0xFC,0xFC,0x00,0x00,0xFF,0xFF,0x20,0x20,0x3F,0x3F,  // ]
  9, 0x00,0xC0,0x70,0x18,0x0C,0x18,0x70,0xC0,0x00,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ^
  12, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,  // _
  3, 0x04,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,0x00,  // `
  9, 0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0xE0,0xF3,0xF3,0x18,0x18,0x18,0xFF,0xFF,0xFF,0x00,0x01,0x03,0x03,0x01,0x01,0x01,0x03,0x03,  // a
  9, 0xFC,0xFC,0xFC,0x80,0xC0,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x3C,0x03,0x03,0x00,0x01,0x03,0x03,0x01,0x01,0x00,  // b
  8, 0x00,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x3C,0xFF,0xFF,0x00,0x00,0x00,0xE7,0xE7,0x00,0x01,0x01,0x03,0x03,0x03,0x01,0x01,  // c
  9, 0x00,0x80,0x80,0xC0,0xC0,0x80,0xFC,0xFC,0xFC,0x3C,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x01,0x03,0x03,0x01,0x00,0x03,0x03,0x03,  // d
  9, 0x00,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x3C,0xFF,0xFF,0x0C,0x0C,0x0C,0xCF,0xCF,0x0C,0x00,0x00,0x01,0x03,0x03,0x03,0x01,0x01,0x00,  // e
  6, 0xC0,0xF0,0xF8,0xFC,0xCC,0xCC,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,  // f
  10, 0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0xE0,0x30,0x30,0xC6,0xFF,0xFF,0xB0,0xB0,0xB0,0x9F,0x9F,0x06,0x00,0x0C,0x1F,0x1F,0x31,0x31,0x31,0x31,0x1F,0x1F,0x0E,  // g
  9, 0xFC,0xFC,0xFC,0x80,0x80,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,  // h
  3, 0xCE,0xCE,0xCE,0xFF,0xFF,0xFF,0x03,0x03,0x03,  // i
  4, 0x00,0xCE,0xCE,0xCE,0x00,0xFF,0xFF,0xFF,0x30,0x3F,0x1F,0x0F,  // j
  9, 0xFC,0xFC,0xFC,0x00,0x00,0x80,0xC0,0xC0,0x00,0xFF,0xFF,0xFF,0x1C,0x3E,0xFF,0xE1,0x80,0x00,0x03,0x03,0x03,0x00,0x00,0x01,0x03,0x03,0x02,  // k
  3, 0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x03,0x03,0x03,  // l
  15, 0xC0,0xC0,0xC0,0x00,0x80,0xC0,0xC0,0x80,0x00,0x80,0x80,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,  // m
  9, 0xC0,0xC0,0xC0,0x00,0x80,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,  // n
  9, 0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x3C,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x3C,0x00,0x01,0x01,0x03,0x03,0x03,0x01,0x01,0x00,  // o
  9, 0xC0,0xC0,0xC0,0x00,0x80,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x3C,0x3F,0x3F,0x3F,0x01,0x03,0x03,0x01,0x01,0x00,  // p
  9, 0x00,0x80,0x80,0xC0,0xC0,0x80,0x00,0xC0,0xC0,0x3C,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x01,0x03,0x03,0x01,0x01,0x3F,0x3F,0x3F,  // q
  6, 0xC0,0xC0,0xC0,0x00,0x80,0xC0,0xFF,0xFF,0xFF,0x03,0x01,0x01,0x03,0x03,0x03,0x00,0x00,0x00,  // r
  8, 0x00,0x80,0x80,0xC0,0xC0,0x80,0x80,0x00,0x07,0x8F,0xCF,0x18,0x18,0xFB,0xF1,0xE0,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,  // s
  7, 0xC0,0xC0,0xF0,0xF8,0xF8,0xC0,0xC0,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,  // t
  9, 0xC0,0xC0,0xC0,0x00,0x00,0x00,0xC0,0xC0,0xC0,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0x00,0x01,0x03,0x03,0x01,0x00,0x03,0x03,0x03,  // u
  9, 0x40,0xC0,0xC0,0x00,0x00,0x00,0xC0,0xC0,0x40,0x00,0x03,0x3F,0xFE,0xE0,0xFC,0x3F,0x03,0x00,0x00,0x00,0x00,0x01,0x03,0x01,0x00,0x00,0x00,  // v
  13, 0x40,0xC0,0xC0,0x00,0x00,0xC0,0xC0,0xC0,0x00,0x00,0x00,0xC0,0x40,0x00,0x1F,0xFF,0xFC,0xE0,0xFF,0x07,0xFF,0xFF,0xE0,0xFC,0x0F,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x03,0x03,0x03,0x00,0x00,  // w
  9, 0x40,0xC0,0xC0,0x00,0x00,0x00,0xC0,0xC0,0x40,0x00,0x81,0xE7,0xFF,0x7E,0xFF,0xE7,0x81,0x00,0x02,0x03,0x03,0x01,0x00,0x00,0x03,0x03,0x02,  // x
  9, 0x40,0xC0,0xC0,0x00,0x00,0x00,0xC0,0xC0,0x40,0x00,0x03,0x3F,0xFC,0xE0,0xFE,0x1F,0x03,0x00,0x30,0x30,0x30,0x1D,0x07,0x00,0x00,0x00,0x00,  // y
  8, 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0xC0,0xF0,0xFC,0x3F,0x0F,0x03,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,  // z
  4, 0x00,0xF0,0xFC,0x04,0x18,0xFF,0xE7,0x00,0x00,0x0F,0x3F,0x20,  // {
  3, 0xFC,0xFC,0xFC,0xFF,0xFF,0xFF,0x3F,0x3F,0x3F,  // |
  4, 0x04,0xFC,0xF0,0x00,0x00,0xE7,0xFF,0x18,0x20,0x3F,0x0F,0x00,  // }
  10, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0x03,0x03,0x02,0x02,0x06,0x06,0x06,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ~
  0};

const byte LargeFont[] PROGMEM = {
  ' ', // first char
  3, // height in pages
  11, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  //  
  2, 0xFE,0xFE,0x0F,0x0F,0x07,0x07,  // !
  6, 0xFE,0xFE,0x00,0x00,0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // "
  14, 0x00,0x00,0xC0,0xC0,0xC0,0xF0,0xDE,0xC0,0xC0,0xC0,0xF8,0xCE,0xC0,0xC0,0x30,0x30,0x30,0xF0,0x3E,0x33,0x30,0x30,0xF8,0x3F,0x30,0x30,0x00,0x00,0x00,0x00,0x06,0x03,0x00,0x00,0x00,0x07,0x01,0x00,0x00,0x00,0x00,0x00,  // #
  11, 0x80,0xE0,0x60,0x20,0x30,0xFE,0x30,0x20,0x20,0x60,0x00,0x01,0x07,0x04,0x0C,0x08,0xFF,0x08,0x18,0x10,0xF0,0xC0,0x03,0x02,0x02,0x06,0x06,0x7F,0x06,0x02,0x03,0x03,0x00,  // $
  14, 0x38,0x7C,0xC6,0xC6,0xC6,0x7C,0x38,0x00,0x00,0x00,0x80,0x80,0x40,0x00,0x00,0x20,0x10,0x10,0x08,0x00,0x04,0xC2,0xE2,0x31,0x30,0x30,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x06,0x06,0x06,0x03,0x01,  // %
  14, 0x00,0x00,0x38,0xFC,0x86,0x06,0x06,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x70,0xFC,0x06,0x03,0x03,0x06,0x0C,0x38,0x70,0xC0,0x80,0xC0,0x7C,0x1C,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x06,0x03,0x03,0x01,0x03,0x06,0x00,  // &
  2, 0xFE,0xFE,0x00,0x00,0x00,0x00,  // '
  4, 0x00,0xE0,0x38,0x06,0x3E,0xFF,0x00,0x00,0x00,0x03,0x0E,0x30,  // (
  4, 0x06,0x38,0xE0,0x00,0x00,0x00,0xFF,0x3E,0x30,0x0E,0x03,0x00,  // )
  7, 0x90,0x00,0x60,0xFE,0x60,0x00,0x90,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // *
  12, 0x00,0x00,0x00,0x00,0x00,0xE0,0xE0,0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x0C,0x0C,0x0C,0xFF,0xFF,0x0C,0x0C,0x0C,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,  // +
  4, 0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x20,0x3F,0x0F,0x03,  // ,
  6, 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,  // -
  3, 0x00,0x00,0x00,0x80,0x80,0x80,0x07,0x07,0x07,  // .
  9, 0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0x38,0x0E,0x00,0x00,0xC0,0x70,0x1E,0x03,0x00,0x00,0x00,0x1C,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,  // /
  11, 0x80,0xF0,0x1C,0x0C,0x06,0x06,0x06,0x0C,0x1C,0xF0,0x80,0x1F,0xFF,0x80,0x00,0x00,0x06,0x00,0x00,0x80,0xFF,0x1F,0x00,0x00,0x03,0x03,0x06,0x06,0x06,0x03,0x03,0x00,0x00,  // 0
  10, 0x0C,0x04,0x04,0x06,0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x07,0x07,0x06,0x06,0x06,0x06,  // 1
  11, 0x0C,0x04,0x04,0x06,0x06,0x06,0x06,0x04,0x0C,0xF8,0xF0,0x00,0x00,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x00,0x06,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,  // 2
  11, 0x00,0x0C,0x04,0x04,0x06,0x06,0x06,0x04,0x0C,0xF8,0xF0,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0x0F,0xF9,0xE0,0x03,0x02,0x02,0x06,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // 3
  12, 0x00,0x00,0x00,0x80,0xC0,0x30,0x18,0x06,0xFE,0xFE,0x00,0x00,0x70,0x78,0x66,0x63,0x60,0x60,0x60,0x60,0xFF,0xFF,0x60,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,  // 4
  11, 0x00,0xFE,0xFE,0x86,0x86,0x86,0x86,0x06,0x06,0x06,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,0x78,0x03,0x02,0x06,0x06,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // 5
  11, 0x80,0xF0,0x18,0x0C,0x84,0x86,0x86,0x06,0x04,0x04,0x00,0x1F,0xFF,0x02,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,0x78,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // 6
  11, 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xC6,0xF6,0x1E,0x06,0x00,0x00,0x00,0x00,0xE0,0x78,0x0E,0x03,0x00,0x00,0x00,0x00,0x00,0x04,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,  // 7
  11, 0x70,0xF8,0x0C,0x04,0x06,0x06,0x06,0x04,0x0C,0xF8,0x70,0xE0,0xF9,0x0D,0x06,0x06,0x06,0x06,0x06,0x0D,0xF9,0xE0,0x00,0x03,0x03,0x02,0x06,0x06,0x06,0x02,0x03,0x03,0x00,  // 8
  11, 0xE0,0xF8,0x0C,0x04,0x06,0x06,0x06,0x04,0x0C,0xF8,0x80,0x01,0x07,0x0C,0x08,0x18,0x18,0x18,0x08,0x84,0xFF,0x1F,0x00,0x02,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,0x00,  // 9
  3, 0x80,0x80,0x80,0x87,0x87,0x87,0x07,0x07,0x07,  // :
  4, 0x00,0x80,0x80,0x80,0x00,0x87,0x87,0x87,0x20,0x3F,0x0F,0x03,  // ;
  12, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x18,0x18,0x3C,0x24,0x66,0x42,0x42,0xC3,0x81,0x81,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x03,  // <
  12, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // =
  12, 0xC0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x81,0x81,0xC3,0x42,0x42,0x66,0x24,0x3C,0x18,0x18,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // >
  9, 0x08,0x0C,0x04,0x06,0x06,0x06,0x8C,0xFC,0x70,0x00,0x00,0x00,0x38,0x3E,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,  // ?
  14, 0x00,0x80,0xC0,0x60,0x30,0x10,0x18,0x18,0x18,0x18,0x10,0x30,0xE0,0x80,0x78,0xFF,0x01,0x00,0x00,0x78,0xFE,0x86,0x03,0x03,0x02,0x86,0xFF,0xFF,0x00,0x03,0x0E,0x18,0x30,0x30,0x21,0x61,0x63,0x63,0x61,0x21,0x03,0x03,  // @
  12, 0x00,0x00,0x00,0x80,0xF8,0x1E,0x1E,0xF8,0x80,0x00,0x00,0x00,0x00,0xC0,0xF8,0x7F,0x61,0x60,0x60,0x61,0x7F,0xF8,0xC0,0x00,0x04,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x04,  // A
  11, 0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x0C,0xFC,0xF0,0x00,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x07,0x09,0xF8,0xE0,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // B
  11, 0x00,0xF0,0x18,0x0C,0x04,0x06,0x06,0x06,0x06,0x04,0x04,0x0F,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x06,0x02,0x02,  // C
  11, 0xFE,0xFE,0x06,0x06,0x06,0x04,0x04,0x0C,0x18,0xF0,0x80,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x1F,0x07,0x07,0x06,0x06,0x06,0x02,0x02,0x03,0x01,0x00,0x00,  // D
  11, 0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,  // E
  11, 0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // F
  12, 0x00,0xF0,0x18,0x0C,0x04,0x06,0x06,0x06,0x06,0x04,0x04,0x00,0x0F,0xFF,0xC0,0x00,0x00,0x00,0x00,0x0C,0x0C,0x0C,0xFC,0xFC,0x00,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x06,0x02,0x03,0x01,  // G
  11, 0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFE,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xFF,0xFF,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,  // H
  10, 0x06,0x06,0x06,0x06,0xFE,0xFE,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x07,0x07,0x06,0x06,0x06,0x06,  // I
  10, 0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x7F,0x03,0x03,0x02,0x06,0x06,0x06,0x06,0x03,0x03,0x00,  // J
  12, 0xFE,0xFE,0x00,0x00,0x80,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x00,0xFF,0xFF,0x06,0x03,0x03,0x0E,0x18,0x70,0xC0,0x80,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x06,0x04,  // K
  11, 0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,  // L
  12, 0xFE,0xFE,0x0E,0x78,0x80,0x00,0x00,0x80,0x78,0x0E,0xFE,0xFE,0xFF,0xFF,0x00,0x00,0x03,0x1C,0x1C,0x03,0x00,0x00,0xFF,0xFF,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,  // M
  11, 0xFE,0xFE,0x1E,0x78,0xC0,0x00,0x00,0x00,0x00,0xFE,0xFE,0xFF,0xFF,0x00,0x00,0x01,0x0F,0x38,0xE0,0x80,0xFF,0xFF,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x07,0x07,  // N
  12, 0x80,0xF0,0x1C,0x0C,0x06,0x06,0x06,0x06,0x0C,0x1C,0xF0,0x80,0x1F,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x1F,0x00,0x00,0x03,0x03,0x06,0x06,0x06,0x06,0x03,0x03,0x00,0x00,  // O
  11, 0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x04,0x0C,0xF8,0xF0,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x02,0x03,0x01,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // P
  12, 0x80,0xF0,0x1C,0x0C,0x06,0x06,0x06,0x06,0x0C,0x1C,0xF0,0x80,0x1F,0xFF,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x1F,0x00,0x00,0x03,0x03,0x06,0x06,0x06,0x0E,0x1F,0x1B,0x11,0x00,  // Q
  12, 0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x04,0x0C,0xF8,0xF0,0x00,0xFF,0xFF,0x06,0x06,0x06,0x06,0x06,0x0E,0x1B,0x71,0xC0,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x07,  // R
  11, 0xE0,0xF8,0x0C,0x04,0x06,0x06,0x06,0x04,0x04,0x0C,0x00,0x00,0x01,0x03,0x02,0x06,0x06,0x04,0x04,0x0C,0xF8,0xE0,0x03,0x02,0x02,0x06,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // S
  12, 0x06,0x06,0x06,0x06,0x06,0xFE,0xFE,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,  // T
  11, 0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFE,0x7F,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x7F,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // U
  12, 0x02,0x3E,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x3E,0x02,0x00,0x00,0x03,0x1F,0xF8,0x80,0x80,0xF8,0x1F,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x07,0x01,0x00,0x00,0x00,0x00,  // V
  14, 0x02,0xFE,0x80,0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x80,0xFE,0x02,0x00,0x01,0xFF,0xC0,0xC0,0x3E,0x01,0x01,0x3E,0xC0,0xC0,0xFF,0x01,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,  // W
  10, 0x06,0x1C,0x70,0xC0,0x80,0x80,0xE0,0x70,0x1C,0x06,0x00,0x80,0xE0,0x39,0x0F,0x0F,0x38,0xE0,0x80,0x00,0x06,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x06,  // X
  12, 0x06,0x1C,0x38,0xE0,0x80,0x00,0x00,0x80,0xE0,0x38,0x1C,0x06,0x00,0x00,0x00,0x00,0x03,0xFF,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,  // Y
  12, 0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xC6,0xE6,0x3E,0x1E,0x06,0x00,0x80,0xC0,0x70,0x38,0x0E,0x07,0x01,0x00,0x00,0x00,0x00,0x06,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,  // Z
  5, 0xFE,0xFE,0x06,0x06,0x06,0xFF,0xFF,0x00,0x00,0x00,0x3F,0x3F,0x30,0x30,0x30,  // [
  9, 0x0E,0x38,0xE0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1E,0x70,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x1C,
  5, 0x06,0x06,0x06,0xFE,0xFE,0x00,0x00,0x00,0xFF,0xFF,0x30,0x30,0x30,0x3F,0x3F,  // ]
  11, 0xC0,0x60,0x30,0x18,0x0E,0x06,0x0E,0x18,0x30,0x60,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ^
  14, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,  // _
  4, 0x01,0x03,0x06,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // `
  11, 0x00,0x80,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x80,0x00,0xC0,0xF1,0x30,0x10,0x18,0x18,0x18,0x18,0x19,0xFF,0xFE,0x00,0x03,0x02,0x06,0x06,0x06,0x02,0x02,0x01,0x07,0x07,  // a
  11, 0xFE,0xFE,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x7C,0x07,0x07,0x01,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // b
  10, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0xC0,0x80,0x80,0x38,0xFF,0x83,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x06,0x06,0x06,0x06,0x02,0x02,  // c
  11, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x00,0xFE,0xFE,0x7C,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x02,0x01,0x07,0x07,  // d
  11, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0xC0,0x80,0x00,0x00,0x38,0xFF,0x19,0x18,0x18,0x18,0x18,0x18,0x19,0x1F,0x1C,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x06,0x02,0x03,0x00,  // e
  11, 0xC0,0xC0,0xC0,0xC0,0xF8,0xFC,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,  // f
  11, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x00,0xC0,0xC0,0x7C,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0x00,0x61,0x43,0xC2,0xC6,0xC6,0xC6,0x42,0x61,0x3F,0x0F,  // g
  11, 0xFE,0xFE,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFE,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,  // h
  10, 0x00,0xC0,0xC0,0xC0,0xCE,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x07,0x07,0x06,0x06,0x06,0x06,  // i
  7, 0x00,0xC0,0xC0,0xC0,0xC0,0xCE,0xCE,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0x60,0x7F,0x1F,  // j
  10, 0xFE,0xFE,0x00,0x00,0x00,0x00,0x80,0xC0,0xC0,0x40,0xFF,0xFF,0x18,0x0C,0x1E,0x37,0xE3,0x81,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x01,0x07,0x06,  // k
  11, 0x06,0x06,0x06,0x06,0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x06,0x06,0x06,0x06,  // l
  12, 0xC0,0xC0,0x80,0xC0,0xC0,0x80,0x00,0x80,0xC0,0xC0,0x80,0x00,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFE,0x07,0x07,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x07,0x07,  // m
  11, 0xC0,0xC0,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFE,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07,  // n
  11, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x00,0x7C,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x7C,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // o
  11, 0xC0,0xC0,0x00,0x80,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x00,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0x7C,0xFF,0xFF,0x01,0x02,0x06,0x06,0x06,0x02,0x03,0x01,0x00,  // p
  11, 0x00,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0x00,0xC0,0xC0,0x7C,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0x00,0x01,0x03,0x02,0x06,0x06,0x06,0x02,0x01,0xFF,0xFF,  // q
  9, 0xC0,0xC0,0x00,0x80,0x80,0xC0,0xC0,0xC0,0x80,0xFF,0xFF,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // r
  11, 0x00,0x80,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,0x80,0x00,0x07,0x0F,0x18,0x18,0x18,0x10,0x10,0x30,0x30,0xE1,0xC0,0x03,0x02,0x02,0x06,0x06,0x06,0x06,0x02,0x03,0x03,0x00,  // s
  11, 0xC0,0xC0,0xC0,0xC0,0xFC,0xFC,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x06,0x06,0x06,0x06,0x06,  // t
  11, 0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x03,0x03,0x06,0x06,0x06,0x02,0x02,0x01,0x07,0x07,  // u
  11, 0x40,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x40,0x00,0x03,0x1F,0xF8,0xC0,0x00,0xC0,0xF8,0x1F,0x03,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x07,0x00,0x00,0x00,0x00,  // v
  14, 0x40,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x40,0x00,0x07,0xFE,0xC0,0x00,0xE0,0x1C,0x1C,0xE0,0x00,0xC0,0xFE,0x07,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,0x00,0x07,0x07,0x00,0x00,0x00,  // w
  9, 0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0x00,0x01,0xC7,0x7E,0x3C,0x7E,0xC7,0x01,0x00,0x06,0x03,0x01,0x00,0x00,0x00,0x01,0x03,0x06,  // x
  11, 0x40,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x40,0x00,0x01,0x0F,0x7C,0xE0,0x80,0xC0,0x78,0x0F,0x03,0x00,0x00,0xC0,0xC0,0xC0,0x73,0x1F,0x03,0x00,0x00,0x00,0x00,  // y
  11, 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x80,0xE0,0x70,0x38,0x1C,0x06,0x03,0x01,0x00,0x06,0x07,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,  // z
  9, 0x00,0x00,0x00,0x00,0xF8,0xFC,0x06,0x06,0x06,0x18,0x18,0x18,0x3C,0xE7,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x3F,0x60,0x60,0x60,  // {
  2, 0xFE,0xFE,0xFF,0xFF,0xFF,0xFF,  // |
  9, 0x06,0x06,0x06,0xFC,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC3,0xE7,0x3C,0x18,0x18,0x18,0x60,0x60,0x60,0x3F,0x1F,0x00,0x00,0x00,0x00,  // }
  12, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x0C,0x0C,0x0C,0x08,0x08,0x18,0x18,0x18,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  // ~
  0};

#endif

SimpleSH1106.cpp

C/C++
/***************************************************
  Arduino TFT graphics library for the SH1106

***************************************************/

#include "SimpleSH1106.h"

#include <avr/pgmspace.h>
#include <Wire.h>

bool BoldSH1106 = false;

const int colOffset = 2;

//==============================================================
// DrawByteSH1106
//   draws a byte at x,page
//   sets up the row and column then sends the byte
//   quite slow
//==============================================================
void DrawByteSH1106(byte x, byte page, byte i) {
  setupPageCol(page, x);
  Wire.write(i);
  Wire.endTransmission();
}

//==============================================================
// setupPageCol
//   sets up the row and column 
//   then gets ready to send one or more bytes
//   should be followed by endTransmission
//==============================================================
void setupPageCol(int page, int col) {
  col += colOffset;
  Wire.beginTransmission(addr);
  Wire.write(0x00); // the following bytes are commands
  Wire.write(0xB0 + page); // set page
  Wire.write(0x00+(col & 15)); // lower columns address
  Wire.write(0x10+(col >> 4)); // upper columns address
  Wire.endTransmission();

  Wire.beginTransmission(addr);
  Wire.write(0x40); // the following bytes are data
}

//==============================================================
// setupCol
//   sets up the column
//==============================================================
void setupCol(int col) {
  col += colOffset;
  Wire.beginTransmission(addr);
  Wire.write(0x00); // the following bytes are commands
  Wire.write(0x00+(col & 15)); // lower columns address
  Wire.write(0x10+(col >> 4)); // upper columns address
  Wire.endTransmission();
}

//==============================================================
// setupPage
//   sets up the page
//==============================================================
void setupPage(int page) {
  Wire.beginTransmission(addr);
  Wire.write(0x00); // the following bytes are commands
  Wire.write(0xB0 + page); // set page
  Wire.endTransmission();
}

//==============================================================
// clearSH1106
//   fills the screen with zeros
//==============================================================
void clearSH1106() {
int x,p;
const int n = 26;
  for(p = 0; p <= 7; p++)
    for(x = 0; x <= 127; x++) {
      if (x%n == 0) setupPageCol(p, x);
      Wire.write(0);
      if ((x%n == n-1) || (x == 127)) Wire.endTransmission();
    }
}

//==============================================================
// initSH1106
//   initialises the SH1106 registers
//==============================================================
void initSH1106() {
  Wire.endTransmission();
  Wire.beginTransmission(addr);
  Wire.write(0x00); // the following bytes are commands
  Wire.write(0xAE); // display off
  Wire.write(0xD5); Wire.write(0x80); // clock divider
  Wire.write(0xA8); Wire.write(0x3F); // multiplex ratio (height - 1)
  Wire.write(0xD3); Wire.write(0x00); // no display offset
  Wire.write(0x40); // start line address=0
  Wire.write(0x33); // charge pump max
  Wire.write(0x8D); Wire.write(0x14); // enable charge pump
  Wire.write(0x20); Wire.write(0x02); // memory adressing mode=horizontal (only for 1306??) maybe 0x00
  Wire.write(0xA1); // segment remapping mode
  Wire.write(0xC8); // COM output scan direction
  Wire.write(0xDA); Wire.write(0x12);   // com pins hardware configuration
  Wire.write(0x81); Wire.write(0xFF); // contrast control // could be 0x81
  Wire.write(0xD9); Wire.write(0xF1); // pre-charge period or 0x22
  Wire.write(0xDB); Wire.write(0x40); // set vcomh deselect level or 0x20
  Wire.write(0xA4); // output RAM to display
  Wire.write(0xA6); // display mode A6=normal, A7=inverse
  Wire.write(0x2E); // stop scrolling
  Wire.write(0xAF); // display on
  Wire.endTransmission();
  
  clearSH1106();
}

//==============================================================
// DrawBarSH1106
//   draws a single byte
//   a 'bar' is a byte on the screen - a col of 8 pix
//   assumes you've set up the page and col
//==============================================================
void DrawBarSH1106(byte bar) {
  Wire.beginTransmission(addr);
  Wire.write(0x40); // the following bytes are data
  Wire.write(bar);
  Wire.endTransmission();
}
  
//==============================================================
// DrawImageSH1106 
//   at x,p*8
//   unpacks RLE and draws it
//   returns width of image
//==============================================================
int DrawImageSH1106(int16_t x, int16_t p, const uint8_t *bitmap) {
  #define DrawImageSH1106Bar {\
    if ((page >= 0) && (page <= 7) && (ax >= 0) && (ax <= 127)) {\
      n++;\
      if ((page != curpage) || (n > 25)){\
        if (curpage >= 0) \
          Wire.endTransmission();\
        setupPageCol(page, ax);\
        curpage = page;\
        n = 0;\
      }\
      Wire.write(bar);\
    }  \
    ax++;\
    if (ax > x+wid-1) {\
      page++;\
      ax = x;\
    }\
  }

  int wid,pages,j,k,page,ax,bar,curpage,n;

  wid = pgm_read_byte(bitmap++);
  pages = pgm_read_byte(bitmap++);

  page = p;
  ax = x;
  curpage = -1;

  while (page <= p+pages-1) {
    j = pgm_read_byte(bitmap++);
    if (j > 127) {
      for (k = 129;k<=j;k++) {
        bar = pgm_read_byte(bitmap);
        DrawImageSH1106Bar  
      }
      bitmap++;
    } else {
      for (k = 1;k<=j;k++) {
        bar = pgm_read_byte(bitmap++);
        DrawImageSH1106Bar
      }
    }
  }
  Wire.endTransmission();

  return wid;
}

//==============================================================
// DrawCharSH1106
//   draws a char at x,page
//   only 8-bit or less fonts are allowed
//   returns width of char + 1 (letter_gap)
//==============================================================
int DrawCharSH1106(uint8_t c, byte x, byte page, word Font) {
word n, i, j, h, result, b, prevB;
  result = 0;
  prevB = 0;
  j  =  pgm_read_byte_near(Font); // first char
  Font++;
  if (c < j) return 0;

  h  =  pgm_read_byte_near(Font); // height in pages must be 1..3
  Font++;

  while (c > j) {
    b  =  pgm_read_byte_near(Font);
    Font++;
    if (b == 0) return 0;
    Font += b*h;
    c--;                                               
  }

  n  =  pgm_read_byte_near(Font);
  Font++;
  result = n+h; // letter_gap

  while (h > 0) {
    setupPageCol(page, x);
    for (i=0; i<n; i++) {
      b  =  pgm_read_byte_near(Font);
      Font++;
      if (BoldSH1106)
        Wire.write(b | prevB);
      else
        Wire.write(b);
      prevB = b;
    }

    if (BoldSH1106) {
      Wire.write(prevB);
      result++;
    }

    Wire.write(0);

    h--;
    page++;
    Wire.endTransmission();
  }
  return result;
}

//==============================================================
// DrawStringSH1106
//   draws a string at x,page
//   returns width drawn
//==============================================================
int DrawStringSH1106(char *s, byte x, byte page, word Font) {
  int start = x;
  if (page <= 7)
    while (*s) {
      x += DrawCharSH1106(*s, x, page, Font);
      s++;
    }
  return x-start;
}

//==============================================================
// DrawIntSH1106
//   draws an int at x,page
//   returns width drawn
//==============================================================
int DrawIntSH1106(long i, byte x, byte page, word Font) {
  int start = x;
  if (i < 0) {
    i=-i;
    x += DrawCharSH1106('-',x,page,Font);
  }

  bool HasDigit = false;
  long n =  1000000000;
  if (i == 0) {
    x += DrawCharSH1106('0',x,page,Font);
  } else {
    while (n > 0) {
      if ((i >= n) or HasDigit) {
        x += DrawCharSH1106('0' + (i / n),x,page,Font);
        HasDigit = true;
      }
      i %= n;
      n /= 10;
    }
  }
  return x-start;
}

Credits

John Bradnam

John Bradnam

145 projects • 178 followers
Thanks to Peter Balch.

Comments