John Bradnam
Published © GPL3+

PCB Reflow Hot Plate

A SMD Hot Plate made from a Printed Circuit Board. It has multiple heat profiles and is capable of reaching over 220 degrees Celsius.

IntermediateFull instructions provided8 hours4,115
PCB Reflow Hot Plate

Things used in this project

Hardware components

Microchip ATtiny1614 Microprocessor
×1
OLED Display 128x64 0.96 inch, I2C Interface
DIYables OLED Display 128x64 0.96 inch, I2C Interface
×1
AO4406 N-Channel MOSFET
SOIC-8 package
×1
LED 0805
1 Red and 1 Blue
×2
2N3904
SOT-23 BEC package
×1
1117-50 5V Regulator
SOT223-T package
×1
100K ohm NTC 3950 Thermistor
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
12mmx12mm version with button tops
×2
SMT-916 speaker
×1
Other components
3 x 10k 0805 resistors, 2 x 1k 0805 resistors, 1 x SOD323 1N4148 diode, 1 x SMA 1N4007 diode, 3 x 10uF 1206 capacitors, 1 x 1uF 0805 capacitor, 1 x 47uF/16V 3528 Tantalum capacitor, 1 x SMD DC power socket
×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

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Gerber files for PCB

Eagle files

Schematic & PCB in Eagle format

Code

PCBHotPlateV4.ino

Arduino
/**************************************************************************
 SMD Hot Plate

 2022-09-09 John Bradnam (jbrad2089@gmail.com)
   V1: Create program for ATtiny1614
   V2: Added PID code from electronoobs
       Added Ambient temperature support
   V3: Modified for PCB Hot Plate

 --------------------------------------------------------------------------
 Arduino IDE:
 --------------------------------------------------------------------------
  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 20MHz
  millis()/micros(): "Enabled (default timer)"
  Programmer: jtag2updi (megaTinyCore)

  ATTiny1614 Pins mapped to Ardunio Pins
 
              +--------+
          VCC + 1   14 + GND
  (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
        1 PA5 + 3   12 + PA2 9  (MISO)
  (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
        3 PA7 + 5   10 + PA0 11 (UPDI)
  (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
  (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
              +--------+
  
 **************************************************************************/

//Comment out next line to disable heater
#define ENABLE_HEATER
//Comment out next line to disable ambient sensor
//#define ENABLE_AMBIENT

#include "SSD1306_I2C.h"
#include <thermistor.h>      //http://electronoobs.com/eng_arduino_thermistor.php

#define TEMP_PIN 0    //PA4
#define AMBIENT_PIN 1 //PA5
#define HEAT_PIN 5    //PB2
#define FAN_PIN 10    //PA3
#define SWITCHES 8    //PA1
#define SPEAKER 2     //PA6

enum SWITCH {NONE, SELECT, START};
enum MODE {STOP, RUN, PAUSE};

#define EPSILON 2                 //Degrees from required to current before reacting
thermistor therm(TEMP_PIN,0);     //PA4 has 3950 Thermistor
thermistor amb(AMBIENT_PIN,0);    //PA5 has 3950 Thermistor (optional - Can leave off)

#define STATUS_VALUE_X 106
#define STATUS_VALUE_Y 0
#define TIME_VALUE_X 106
#define TIME_VALUE_Y 10
#define TEMP_VALUE_X 106
#define TEMP_VALUE_Y 20

//Color TFT 160x128
//Mono OLED 128x64
#define GRAPH_X_MIN 16  
#define GRAPH_X_DIV 9
#define GRAPH_X_GAP 12
#define GRAPH_X_TEXT 0
#define GRAPH_X_MAX (SSD1306_SCREEN_WIDTH - 1)
#define GRAPH_Y_MIN 53
#define GRAPH_Y_DIV 6
#define GRAPH_Y_GAP 8
#define GRAPH_Y_TEXT (GRAPH_Y_MIN + 3)
#define GRAPH_Y_MAX (SSD1306_SCREEN_HEIGHT - 1)

#define TABLES_IN_DATA_SPACE

typedef struct {
  int temp;     //Temperature to reach
  int period;   //Seconds to reach temperature
} TARGET;

#define NUMBER_OF_PERIODS 6
#ifdef TABLES_IN_DATA_SPACE
  const TARGET plot1[NUMBER_OF_PERIODS] = {{150,90},{150,180},{240,240},{240,260},{0,420},{0,0}};
  const TARGET plot2[NUMBER_OF_PERIODS] = {{150,50},{180,140},{240,175},{240,185},{120,250},{0,350}};
  const TARGET plot3[NUMBER_OF_PERIODS] = {{150,60},{200,120},{250,160},{250,190},{0,260},{0,0}};
#else
  const TARGET plot1[NUMBER_OF_PERIODS] PROGMEM = {{150,90},{150,180},{240,240},{240,260},{0,420},{0,0}};
  const TARGET plot2[NUMBER_OF_PERIODS] PROGMEM = {{150,50},{180,140},{240,175},{240,185},{120,250},{0,350}};
  const TARGET plot3[NUMBER_OF_PERIODS] PROGMEM = {{150,60},{200,120},{250,160},{250,190},{0,260},{0,0}};
#endif


#define NUMBER_OF_PLOTS 3
const TARGET* plots[NUMBER_OF_PLOTS] = {plot1, plot2, plot3};

volatile unsigned int minutes;  // In minutes
volatile unsigned int seconds;  // In seconds
volatile bool updateDisplay;    // Force display update
int currentPlot;                // Currently selected heating plot
float temperature;              // Current temperature
float ambient;                  // Outside temperature
volatile MODE currentMode;      // Current mode
char buf[16];                   // Used to format strings

/////////////////////PID VARIABLES///////////////////////
#define PID_REFRESH_RATE 50
#define MIN_PID_VALUE 0
#define MAX_PID_VALUE 255       //Max PID value. You can change this. (128 3A 200C, 180 3.5A 220C, 220 4.2A 222C )
uint32_t pidTimeout = 0;        //Used to hold next PID period

float Kp = 4;                   //Mine was 2 - How fast the system responds (too high cause overshoot)
float Ki = 0.0025;              //Mine was 0.0025 - How fast the steady state error is removed
float Kd = 9;                   //Mine was 9 - How far into the future to predict the rate of change
float PID_Output = 0;
float PID_P, PID_I, PID_D;
float PID_ERROR, PREV_ERROR;
/////////////////////////////////////////////////////////

//---------------------------------------------------
// Hardware setup
void setup(void) 
{
  pinMode(TEMP_PIN, INPUT);
  pinMode(AMBIENT_PIN, INPUT);
  pinMode(HEAT_PIN, OUTPUT);
  digitalWrite(HEAT_PIN, LOW);
  pinMode(FAN_PIN, OUTPUT);
  analogWrite(FAN_PIN, 0);
  pinMode(SWITCHES, INPUT);
  pinMode(SPEAKER, OUTPUT);
  digitalWrite(SPEAKER, LOW);

  initDisplay(SSD1306_SWITCHCAPVCC);
  
  currentMode = STOP;
  minutes = 0;
  seconds = 0;
  RTCSetup();
  
  //Assume 10degC above current temperature is cold temperature
#ifdef ENABLE_AMBIENT
  ambient = amb.analog2temp() + 10;
#else
  ambient = 50.0;   //Safe temperature
#endif

  drawGraphFrame();
  updateDisplay = true;
  currentPlot = 0;
  plotGraph(currentPlot);
}

//---------------------------------------------------
// Primary loop
void loop() 
{
  if (millis() > pidTimeout)
  {
    pidTimeout = millis() + PID_REFRESH_RATE; 
    temperature = therm.analog2temp();
    if (currentMode == RUN || currentMode == PAUSE)
    {
      int s = minutes * 60 + seconds;
      int t = timeToTemperature(currentPlot, s);
      if (s != 0 && t == 0 && temperature <= ambient)
      {
        //Finished
        digitalWrite(HEAT_PIN, LOW);
        digitalWrite(FAN_PIN, LOW);
        currentMode = STOP;
        tone(SPEAKER, 440);
        delay(2000);
        noTone(SPEAKER);
      }
      else if (s != 0 && t == 0)
      {
        //switch on fan until temp drops below ambient
        digitalWrite(FAN_PIN, (temperature >= ambient) ? HIGH : LOW);
      }
      else
      {
        //Calculate PID
        PID_ERROR = t - temperature;
        PID_P = Kp*PID_ERROR;
        PID_I = PID_I+(Ki*PID_ERROR);      
        PID_D = Kd * (PID_ERROR-PREV_ERROR);
        PID_Output = max(min(PID_P + PID_I + PID_D, MAX_PID_VALUE), MIN_PID_VALUE);
#ifdef ENABLE_HEATER
        analogWrite(HEAT_PIN, PID_Output);  //Change the Duty Cycle applied to the SSR
#endif          
        PREV_ERROR = PID_ERROR;

        //switch on fan if not heating
        digitalWrite(FAN_PIN, (temperature >= ambient && PID_Output == MIN_PID_VALUE) ? HIGH : LOW);
      }
    }
    else
    {
      //switch on fan while pasued or stopped if temp more than ambient
      digitalWrite(FAN_PIN, (temperature >= ambient) ? HIGH : LOW);
    }
  }
  
  if (updateDisplay)
  {
    updateDisplay = false;
    
    useLargeDigits(true);

    //Status
    MoveTo(STATUS_VALUE_X, STATUS_VALUE_Y);
    switch(currentMode)
    {
      case STOP: strcpy(buf,"STOP"); break;
      case RUN: strcpy(buf,"START"); break;
      case PAUSE: strcpy(buf,"PAUSE"); break;
    }
    spadr(buf,9);
    PlotText(buf);
    
    //Temperature
    temperature = floor(therm.analog2temp() + 0.5);
    //temperature = float(timeToTemperature(currentPlot,minutes * 60 + seconds));
    MoveTo(TEMP_VALUE_X, TEMP_VALUE_Y);
    dtostrf(temperature, 3, 0, buf);
    strcat(buf,"*c ");
    spadr(buf,7);
    PlotText(buf);

    if (currentMode != STOP)
    {
      plotCurrentTemperature(temperature, minutes * 60 + seconds);
    }

    //Time
    MoveTo(TIME_VALUE_X, TIME_VALUE_Y);
    sprintf(buf,"%02d:%02d ",minutes,seconds);
    spadr(buf,5);
    PlotText(buf);

    useLargeDigits(false);
    refreshDisplay();
    
  }

  SWITCH sw = readSwitches(true);
  if (currentMode == STOP || currentMode == PAUSE)
  {
    switch(sw)
    {
      case SELECT:
        if (currentMode == PAUSE)
        {
          currentMode = STOP;   //Stop if paused
          minutes = 0;
          seconds = 0;
          digitalWrite(HEAT_PIN, LOW);
        }
        else
        {
          currentPlot++;
          if (currentPlot == NUMBER_OF_PLOTS)
          {
            currentPlot = 0;
          }
        }
        clearDisplay();
        drawGraphFrame();
        plotGraph(currentPlot);
        updateDisplay = true;
        break;
  
      case START:
        if (currentMode == STOP)
        {
          minutes = 0;
          seconds = 0;
          clearDisplay();          
          drawGraphFrame();
          plotGraph(currentPlot);
        }
        currentMode = RUN;
        digitalWrite(FAN_PIN, HIGH);      //Run fan
        updateDisplay = true;
        break;

      case NONE:
        break;
    }
  }
  else if (sw == START)
  {
    //Currently running
    currentMode = PAUSE;
    updateDisplay = true;
  }
  delay(100);
}

//---------------------------------------------------------------------
// Real-Time Clock Setup
void RTCSetup() 
{
  // Initialize RTC
  while (RTC.STATUS > 0);                           // Wait until registers synchronized

  //Use the internal oscillator
  RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                    // 32.768kHz Internal Oscillator  
  
  RTC.PITINTCTRL = RTC_PI_bm;                           //Periodic Interrupt: enabled
  RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm; //RTC Clock Cycles 32768, resulting in 32.768kHz/32768 = 1Hz and enable
}

//---------------------------------------------------------------------
//RTC interrupt occurs every second
ISR(RTC_PIT_vect)
{
  if (currentMode == RUN)
  {
    if (seconds < 59)
    {
      seconds++;
    }
    else
    {
      seconds = 0;
      minutes++;
    }
  }
  updateDisplay = true;
  RTC.PITINTFLAGS = RTC_PI_bm;          //Clear flag by writing '1'
}

//---------------------------------------------------------------------
//Read current switches state
// - wait - True to wait for button released if pressed
// - Returns NONE, START or SELECT
SWITCH readSwitches(bool wait)
{
  SWITCH sw = NONE;
  int value = analogRead(SWITCHES);
  if (value < 1000)
  {
    delay(10);    //debounce
    if (value == analogRead(SWITCHES))
    {
      sw = (value < 100) ? START : SELECT;
      if (wait)
      {
        //wait for release
        while (analogRead(SWITCHES) < 1000)
        {
          delay(50);
        }
      }
    }
  }
  return sw;
}

//---------------------------------------------------------------------
// Draw grid and labels on X and Y axis
void drawGraphFrame()
{

  //Axis
  //drawFastHLine(0, GRAPH_Y_MIN, GRAPH_X_MAX, SSD1306_WHITE);
  
  MoveTo(0, GRAPH_Y_MIN); DrawTo(GRAPH_X_MAX, GRAPH_Y_MIN);
  MoveTo(GRAPH_X_MIN, 0); DrawTo(GRAPH_X_MIN, GRAPH_Y_MAX);
  
  for(int x = 0; x < GRAPH_X_DIV; x++)
  {
    if (x != 0)
    {
      //Tick mark
      MoveTo(GRAPH_X_MIN + (x * GRAPH_X_GAP),GRAPH_Y_MIN); DrawTo(GRAPH_X_MIN + (x * GRAPH_X_GAP),GRAPH_Y_MIN-3);
      //Text
      if (x == (GRAPH_X_DIV - 1))
      {
        MoveTo(GRAPH_X_MIN + (x * GRAPH_X_GAP) - 6,GRAPH_Y_TEXT);
        PlotText("(min)");
      }
      else
      {
        sprintf(buf,"%d",x);
        MoveTo(GRAPH_X_MIN + (x * GRAPH_X_GAP) - 2,GRAPH_Y_TEXT);
        PlotText(buf);
      }
    }
  }
  for(int y = 0; y < GRAPH_Y_DIV; y++)
  {
    if (y != 0)
    {
      //Tick mark
      MoveTo(GRAPH_X_MIN, GRAPH_Y_MIN - (y * GRAPH_Y_GAP)); DrawTo(GRAPH_X_MIN + 3, GRAPH_Y_MIN - (y * GRAPH_Y_GAP));
      //Text
      MoveTo(GRAPH_X_TEXT, GRAPH_Y_MIN - (y * GRAPH_Y_GAP) - 4);
      if (y > 1)
      {
        sprintf(buf,"%3d",y*50);
        PlotText(buf);
      }
    }
  }

  //refreshDisplay();  
}

//---------------------------------------------------------------------
//Plot current graph
// plot - Graph to plot
void plotGraph(int plot)
{
  const TARGET* p = plots[plot];
  int te, se;
  int ss = 0;
  int ts = 0;
  for (int i = 0; i < NUMBER_OF_PERIODS; i++)
  {
    #ifdef TABLES_IN_DATA_SPACE
      te = p->temp;
      se = p->period;
    #else
      te = pgm_read_word(&p->temp);
      se = pgm_read_word(&p->period);
    #endif
    if (te != 0 || se != 0 || i == 0)
    {
      MoveTo(toGraphX(ss),toGraphY(ts));
      DrawTo(toGraphX(se),toGraphY(te));
    }
    ts = te;
    ss = se;
    p++;
  }
}

//---------------------------------------------------------------------
//Convert a time in seconds to the X position on the graph
// x - time in seconds
// returns X position on graph
int toGraphX(int x)
{
  return (GRAPH_X_MIN + (x * GRAPH_X_GAP) / 60);
}

//---------------------------------------------------------------------
//Convert a temperature to the Y position on the graph
// y - temperature in degrees C
// returns Y position on graph
int toGraphY(int y)
{
  return (GRAPH_Y_MIN - (y * GRAPH_Y_GAP) / 50);
}

//---------------------------------------------------------------------
//Highlight the current temparture
// t - Current temperature
// s - Current time in seconds
void plotCurrentTemperature(float t, int s)
{
  MoveTo(toGraphX(s),toGraphY(round(t)));
  DrawCircle(2);  
}      

//---------------------------------------------------------------------
//Highlight the expected temparture
// plot - Graph being plotted
// s - Current time in seconds
// Returns false when reached end
bool plotExpectedTemperature(int plot, int s)
{
  int t = timeToTemperature(plot, s);
  MoveTo(toGraphX(s),toGraphY(t));
  DrawCircle(2);  
  return (s == 0 || t != 0);
}      

//---------------------------------------------------------------------
//Convert a time in seconds to a temperature
// plot - Graph being plotted
// s - Current time in seconds
// Returns temperature expected
int timeToTemperature(int plot, int s)
{
  const TARGET* p = plots[plot];
  long te, se;
  long ss = 0;
  long ts = 0;
  for (int i = 0; i < NUMBER_OF_PERIODS; i++)
  {
    #ifdef TABLES_IN_DATA_SPACE
      te = p->temp;
      se = p->period;
    #else
      te = pgm_read_word(&p->temp);
      se = pgm_read_word(&p->period);
    #endif
    if (te == 0 && se == 0)
    {
      return 0;
    }
    else if (s <= se)
    {
      //found target
      return ts + (((long)s - ss) * (te - ts)) / (se - ss);
    }
    ts = te;
    ss = se;
    p++;
  }
  return 0;
}

//---------------------------------------------------------------------
//Pad string right with spaces
// - p pointer to start of string
// - l length of final string
void spadr(char* p, int l)
{
  int k = strlen(p);
  int i = k;
  for(; i < l; i++)
  {
    p[i] = ' ';
  }
  p[i] = '\0';
}

SSD1306_I2C.h

C Header File
/*
 * SSD1306 OLED display library for ATtiny1614
 * based on SSD1306_I2C library
 * by John Bradnam
 */

#pragma once

#include <Wire.h>
#include "fontVSx8.h"

#define SSD1306_SCREEN_WIDTH 128          // OLED display width, in pixels
#define SSD1306_SCREEN_HEIGHT 64          // OLED display height, in pixels


#define SSD1306_BLACK 0   ///< Draw 'off' pixels
#define SSD1306_WHITE 1   ///< Draw 'on' pixels
#define SSD1306_INVERSE 2 ///< Invert pixels

#define SSD1306_MEMORYMODE 0x20          ///< See datasheet
#define SSD1306_COLUMNADDR 0x21          ///< See datasheet
#define SSD1306_PAGEADDR 0x22            ///< See datasheet
#define SSD1306_SETCONTRAST 0x81         ///< See datasheet
#define SSD1306_CHARGEPUMP 0x8D          ///< See datasheet
#define SSD1306_SEGREMAP 0xA0            ///< See datasheet
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
#define SSD1306_DISPLAYALLON 0xA5        ///< Not currently used
#define SSD1306_NORMALDISPLAY 0xA6       ///< See datasheet
#define SSD1306_INVERTDISPLAY 0xA7       ///< See datasheet
#define SSD1306_SETMULTIPLEX 0xA8        ///< See datasheet
#define SSD1306_DISPLAYOFF 0xAE          ///< See datasheet
#define SSD1306_DISPLAYON 0xAF           ///< See datasheet
#define SSD1306_COMSCANINC 0xC0          ///< Not currently used
#define SSD1306_COMSCANDEC 0xC8          ///< See datasheet
#define SSD1306_SETDISPLAYOFFSET 0xD3    ///< See datasheet
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5  ///< See datasheet
#define SSD1306_SETPRECHARGE 0xD9        ///< See datasheet
#define SSD1306_SETCOMPINS 0xDA          ///< See datasheet
#define SSD1306_SETVCOMDETECT 0xDB       ///< See datasheet

#define SSD1306_SETLOWCOLUMN 0x00        ///< Not currently used
#define SSD1306_SETHIGHCOLUMN 0x10       ///< Not currently used
#define SSD1306_SETSTARTLINE 0x40        ///< See datasheet
#define SSD1306_DEACTIVATE_SCROLL 0x2E   ///< Stop scroll

#define SSD1306_EXTERNALVCC 0x01         ///< External display voltage source
#define SSD1306_SWITCHCAPVCC 0x02        ///< Gen. display voltage from 3.3V
#define SSD1306_I2CADDR 0x3C

// SOME DEFINES AND STATIC VARIABLES USED INTERNALLY -----------------------

#define WIRE_MAX 32 ///< Use common Arduino core default

#define ssd1306_swap(a, b)                                                     \
  (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) ///< No-temp-var swap operation

#define TRANSACTION_START Wire.setClock(400000UL);  //Set before I2C transfer
#define TRANSACTION_END Wire.setClock(100000UL); //Restore after I2C xfer 

// VARIABLES ---------------------------------------------------------------

uint8_t screenBuf[SSD1306_SCREEN_WIDTH * ((SSD1306_SCREEN_HEIGHT + 7) / 8)]; ///Buffer data used for display buffer.
int xPos; // Current plot X position  
int yPos; // Current plot Y position
bool largeDigits = false;

// FUNCTIONS ---------------------------------------------------------------

void ssd1306_command1(uint8_t c);
void ssd1306_commandList(const uint8_t *c, uint8_t n);
void initDisplay(uint8_t vcs);
void drawPixel(int16_t x, int16_t y, uint16_t color);
void clearDisplay(void); 
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color);
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
void drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color);
bool getPixel(int16_t x, int16_t y);
void MoveTo(int x, int y);
void DrawTo(int x, int y);
void DrawCircle(int radius); 
uint8_t ReverseByte(uint8_t x);
int PlotChar(int c, int x, int y);
void PlotText(String s);
void useLargeDigits(bool use);
void refreshDisplay(void);

// LOW-LEVEL UTILS ---------------------------------------------------------

void ssd1306_command1(uint8_t c) 
{
  Wire.beginTransmission(SSD1306_I2CADDR);
  Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
  Wire.write(c);
  Wire.endTransmission();
}

void ssd1306_commandList(const uint8_t *c, uint8_t n) 
{
  Wire.beginTransmission(SSD1306_I2CADDR);
  Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
  uint16_t bytesOut = 1;
  while (n--) {
    if (bytesOut >= WIRE_MAX) {
      Wire.endTransmission();
      Wire.beginTransmission(SSD1306_I2CADDR);
      Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
      bytesOut = 1;
    }
    Wire.write(pgm_read_byte(c++));
    bytesOut++;
  }
  Wire.endTransmission();
}

// ALLOCATE & INIT DISPLAY -------------------------------------------------

/*!
    @param  vcs
            VCC selection. Pass SSD1306_SWITCHCAPVCC to generate the display
            voltage (step up) from the 3.3V source, or SSD1306_EXTERNALVCC
            otherwise. Most situations with Adafruit SSD1306 breakouts will
            want SSD1306_SWITCHCAPVCC.
    @return true on successful allocation/init, false otherwise.
            Well-behaved code should check the return value before
            proceeding.
    @note   MUST call this function before any drawing or updates!
*/
void initDisplay(uint8_t vcs) 
{
  //Create display buffer and clear  
  clearDisplay();

  Wire.begin();

  TRANSACTION_START

  // Init sequence
  static const uint8_t PROGMEM init1[] = {SSD1306_DISPLAYOFF,         // 0xAE
                                          SSD1306_SETDISPLAYCLOCKDIV, // 0xD5
                                          0x80, // the suggested ratio 0x80
                                          SSD1306_SETMULTIPLEX}; // 0xA8
  ssd1306_commandList(init1, sizeof(init1));
  ssd1306_command1(SSD1306_SCREEN_HEIGHT - 1);

  static const uint8_t PROGMEM init2[] = {SSD1306_SETDISPLAYOFFSET, // 0xD3
                                          0x0,                      // no offset
                                          SSD1306_SETSTARTLINE | 0x0, // 0x40 | line #0
                                          SSD1306_CHARGEPUMP};        // 0x8D
  ssd1306_commandList(init2, sizeof(init2));

  ssd1306_command1((vcs == SSD1306_EXTERNALVCC) ? 0x10 : 0x14);

  static const uint8_t PROGMEM init3[] = {SSD1306_MEMORYMODE, // 0x20
                                          0x0, // 0x0 act like ks0108
                                          SSD1306_SEGREMAP | 0x1, // 0xA0 | 0x1
                                          SSD1306_COMSCANDEC}; // 0C8
  ssd1306_commandList(init3, sizeof(init3));

	// (SSD1306_SCREEN_WIDTH == 128) && (SSD1306_SCREEN_HEIGHT == 64))
  ssd1306_command1(SSD1306_SETCOMPINS); // 0xDA
  ssd1306_command1(0x12);
  ssd1306_command1(SSD1306_SETCONTRAST); // 0x81
  ssd1306_command1((vcs == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF);

  ssd1306_command1(SSD1306_SETPRECHARGE); // 0xD9
  ssd1306_command1((vcs == SSD1306_EXTERNALVCC) ? 0x22 : 0xF1);
  static const uint8_t PROGMEM init5[] = {
      SSD1306_SETVCOMDETECT, // 0xDB
      0x40,
      SSD1306_DISPLAYALLON_RESUME, // 0xA4
      SSD1306_NORMALDISPLAY,       // 0xA6
      SSD1306_DEACTIVATE_SCROLL,   // 0x2E
      SSD1306_DISPLAYON};          // 0xAF Main screen turn on
  ssd1306_commandList(init5, sizeof(init5));

  TRANSACTION_END
}

// DRAWING FUNCTIONS -------------------------------------------------------

/*!
    @brief  Set/clear/invert a single pixel. This is also invoked by the
            Adafruit_GFX library in generating many higher-level graphics
            primitives.
    @param  x
            Column of display -- 0 at left to (screen width - 1) at right.
    @param  y
            Row of display -- 0 at top to (screen height -1) at bottom.
    @param  color
            Pixel color, one of: SSD1306_BLACK, SSD1306_WHITE or
            SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void drawPixel(int16_t x, int16_t y, uint16_t color) 
{
  if ((x >= 0) && (x < SSD1306_SCREEN_WIDTH) && (y >= 0) && (y < SSD1306_SCREEN_HEIGHT)) 
  {
    // Pixel is in-bounds. Rotate coordinates if needed.
    /*
    switch (getRotation()) 
    {
      case 1:
        ssd1306_swap(x, y);
        x = SSD1306_SCREEN_WIDTH - x - 1;
        break;
      case 2:
        x = SSD1306_SCREEN_WIDTH - x - 1;
        y = SSD1306_SCREEN_HEIGHT - y - 1;
        break;
      case 3:
        ssd1306_swap(x, y);
        y = SSD1306_SCREEN_HEIGHT - y - 1;
        break;
    }
    */
    switch (color) 
    {
      case SSD1306_WHITE:
        screenBuf[x + (y / 8) * SSD1306_SCREEN_WIDTH] |= (1 << (y & 7));
        break;
      case SSD1306_BLACK:
        screenBuf[x + (y / 8) * SSD1306_SCREEN_WIDTH] &= ~(1 << (y & 7));
        break;
      case SSD1306_INVERSE:
        screenBuf[x + (y / 8) * SSD1306_SCREEN_WIDTH] ^= (1 << (y & 7));
        break;
    }
  }
}

/*!
    @brief  Clear contents of display buffer (set all pixels to off).
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void clearDisplay(void) 
{
  memset(&screenBuf, 0, SSD1306_SCREEN_WIDTH * ((SSD1306_SCREEN_HEIGHT + 7) / 8));
}

/*!
    @brief  Draw a horizontal line. This is also invoked by the Adafruit_GFX
            library in generating many higher-level graphics primitives.
    @param  x
            Leftmost column -- 0 at left to (screen width - 1) at right.
    @param  y
            Row of display -- 0 at top to (screen height -1) at bottom.
    @param  w
            Width of line, in pixels.
    @param  color
            Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) 
{
  bool bSwap = false;
  /*
  switch (rotation) 
  {
    case 1:
      // 90 degree rotation, swap x & y for rotation, then invert x
      bSwap = true;
      ssd1306_swap(x, y);
      x = SSD1306_SCREEN_WIDTH - x - 1;
      break;
    case 2:
      // 180 degree rotation, invert x and y, then shift y around for height.
      x = SSD1306_SCREEN_WIDTH - x - 1;
      y = SSD1306_SCREEN_HEIGHT - y - 1;
      x -= (w - 1);
      break;
    case 3:
      // 270 degree rotation, swap x & y for rotation,
      // then invert y and adjust y for w (not to become h)
      bSwap = true;
      ssd1306_swap(x, y);
      y = SSD1306_SCREEN_HEIGHT - y - 1;
      y -= (w - 1);
      break;
  }
  */

  if (bSwap)
    drawFastVLineInternal(x, y, w, color);
  else
    drawFastHLineInternal(x, y, w, color);
}

/*!
    @brief  Draw a horizontal line with a width and color. Used by public
   methods drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param w
                   Width of line, in pixels.
        @param color
               Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
   SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) 
{

  if ((y >= 0) && (y < SSD1306_SCREEN_HEIGHT)) { // Y coord in bounds?
    if (x < 0) {                  // Clip left
      w += x;
      x = 0;
    }
    if ((x + w) > SSD1306_SCREEN_WIDTH) { // Clip right
      w = (SSD1306_SCREEN_WIDTH - x);
    }
    if (w > 0) { // Proceed only if width is positive
      uint8_t *pBuf = &screenBuf[(y / 8) * SSD1306_SCREEN_WIDTH + x], mask = 1 << (y & 7);
      switch (color) 
      {
        case SSD1306_WHITE:
          while (w--) {
            *pBuf++ |= mask;
          };
          break;
        case SSD1306_BLACK:
          mask = ~mask;
          while (w--) {
            *pBuf++ &= mask;
          };
          break;
        case SSD1306_INVERSE:
          while (w--) {
            *pBuf++ ^= mask;
          };
          break;
      }
    }
  }
}

/*!
    @brief  Draw a vertical line. This is also invoked by the Adafruit_GFX
            library in generating many higher-level graphics primitives.
    @param  x
            Column of display -- 0 at left to (screen width -1) at right.
    @param  y
            Topmost row -- 0 at top to (screen height - 1) at bottom.
    @param  h
            Height of line, in pixels.
    @param  color
            Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) 
{
  bool bSwap = false;
  /*
  switch (rotation) 
  {
    case 1:
      // 90 degree rotation, swap x & y for rotation,
      // then invert x and adjust x for h (now to become w)
      bSwap = true;
      ssd1306_swap(x, y);
      x = SSD1306_SCREEN_WIDTH - x - 1;
      x -= (h - 1);
      break;
    case 2:
      // 180 degree rotation, invert x and y, then shift y around for height.
      x = SSD1306_SCREEN_WIDTH - x - 1;
      y = SSD1306_SCREEN_HEIGHT - y - 1;
      y -= (h - 1);
      break;
    case 3:
      // 270 degree rotation, swap x & y for rotation, then invert y
      bSwap = true;
      ssd1306_swap(x, y);
      y = SSD1306_SCREEN_HEIGHT - y - 1;
      break;
  }
  */

  if (bSwap)
    drawFastHLineInternal(x, y, h, color);
  else
    drawFastVLineInternal(x, y, h, color);
}

/*!
    @brief  Draw a vertical line with a width and color. Used by public method
   drawFastHLine,drawFastVLine
        @param x
                   Leftmost column -- 0 at left to (screen width - 1) at right.
        @param __y
                   Row of display -- 0 at top to (screen height -1) at bottom.
        @param __h height of the line in pixels
        @param color
                   Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
   SSD1306_INVERSE.
    @return None (void).
    @note   Changes buffer contents only, no immediate effect on display.
            Follow up with a call to display(), or with other graphics
            commands as needed by one's own application.
*/
void drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) 
{

  if ((x >= 0) && (x < SSD1306_SCREEN_WIDTH)) { // X coord in bounds?
    if (__y < 0) {               // Clip top
      __h += __y;
      __y = 0;
    }
    if ((__y + __h) > SSD1306_SCREEN_HEIGHT) { // Clip bottom
      __h = (SSD1306_SCREEN_HEIGHT - __y);
    }
    if (__h > 0) { // Proceed only if height is now positive
      // this display doesn't need ints for coordinates,
      // use local byte registers for faster juggling
      uint8_t y = __y, h = __h;
      uint8_t *pBuf = &screenBuf[(y / 8) * SSD1306_SCREEN_WIDTH + x];

      // do the first partial byte, if necessary - this requires some masking
      uint8_t mod = (y & 7);
      if (mod) {
        // mask off the high n bits we want to set
        mod = 8 - mod;
        // note - lookup table results in a nearly 10% performance
        // improvement in fill* functions
        // uint8_t mask = ~(0xFF >> mod);
        static const uint8_t PROGMEM premask[8] = {0x00, 0x80, 0xC0, 0xE0,
                                                   0xF0, 0xF8, 0xFC, 0xFE};
        uint8_t mask = pgm_read_byte(&premask[mod]);
        // adjust the mask if we're not going to reach the end of this byte
        if (h < mod)
          mask &= (0XFF >> (mod - h));

        switch (color) 
        {
          case SSD1306_WHITE:
            *pBuf |= mask;
            break;
          case SSD1306_BLACK:
            *pBuf &= ~mask;
            break;
          case SSD1306_INVERSE:
            *pBuf ^= mask;
            break;
        }
        pBuf += SSD1306_SCREEN_WIDTH;
      }

      if (h >= mod) { // More to go?
        h -= mod;
        // Write solid bytes while we can - effectively 8 rows at a time
        if (h >= 8) {
          if (color == SSD1306_INVERSE) {
            // separate copy of the code so we don't impact performance of
            // black/white write version with an extra comparison per loop
            do {
              *pBuf ^= 0xFF; // Invert byte
              pBuf += SSD1306_SCREEN_WIDTH; // Advance pointer 8 rows
              h -= 8;        // Subtract 8 rows from height
            } while (h >= 8);
          } else {
            // store a local value to work with
            uint8_t val = (color != SSD1306_BLACK) ? 255 : 0;
            do {
              *pBuf = val;   // Set byte
              pBuf += SSD1306_SCREEN_WIDTH; // Advance pointer 8 rows
              h -= 8;        // Subtract 8 rows from height
            } while (h >= 8);
          }
        }

        if (h) { // Do the final partial byte, if necessary
          mod = h & 7;
          // this time we want to mask the low bits of the byte,
          // vs the high bits we did above
          // uint8_t mask = (1 << mod) - 1;
          // note - lookup table results in a nearly 10% performance
          // improvement in fill* functions
          static const uint8_t PROGMEM postmask[8] = {0x00, 0x01, 0x03, 0x07,
                                                      0x0F, 0x1F, 0x3F, 0x7F};
          uint8_t mask = pgm_read_byte(&postmask[mod]);
          switch (color) 
          {
            case SSD1306_WHITE:
              *pBuf |= mask;
              break;
            case SSD1306_BLACK:
              *pBuf &= ~mask;
              break;
            case SSD1306_INVERSE:
              *pBuf ^= mask;
              break;
          }
        }
      }
    } // endif positive height
  }   // endif x in bounds
}

/*!
    @brief  Return color of a single pixel in display buffer.
    @param  x
            Column of display -- 0 at left to (screen width - 1) at right.
    @param  y
            Row of display -- 0 at top to (screen height -1) at bottom.
    @return true if pixel is set (usually SSD1306_WHITE, unless display invert
   mode is enabled), false if clear (SSD1306_BLACK).
    @note   Reads from buffer contents; may not reflect current contents of
            screen if display() has not been called.
*/
bool getPixel(int16_t x, int16_t y) 
{
  if ((x >= 0) && (x < SSD1306_SCREEN_WIDTH) && (y >= 0) && (y < SSD1306_SCREEN_HEIGHT)) 
  {
    /*
    // Pixel is in-bounds. Rotate coordinates if needed.
    switch (getRotation()) 
    {
      case 1:
        ssd1306_swap(x, y);
        x = SSD1306_SCREEN_WIDTH - x - 1;
        break;
      case 2:
        x = SSD1306_SCREEN_WIDTH - x - 1;
        y = SSD1306_SCREEN_HEIGHT - y - 1;
        break;
      case 3:
        ssd1306_swap(x, y);
        y = SSD1306_SCREEN_HEIGHT - y - 1;
        break;
    }
    */
    return (screenBuf[x + (y / 8) * SSD1306_SCREEN_WIDTH] & (1 << (y & 7)));
  }
  return false; // Pixel out of bounds
}

// Move current plot position to x,y
void MoveTo(int x, int y) 
{
  xPos = x;
  yPos = y;
}

// Draw a line to x,y
void DrawTo(int x, int y) 
{
  int sx, sy, e2, err;
  int dx = abs(x - xPos);
  int dy = abs(y - yPos);
  if (xPos < x) sx = 1; else sx = -1;
  if (yPos < y) sy = 1; else sy = -1;
  err = dx - dy;
  for (;;) {
    drawPixel(xPos, yPos, SSD1306_WHITE);
    if (xPos==x && yPos==y) return;
    e2 = err<<1;
    if (e2 > -dy) { err = err - dy; xPos = xPos + sx; }
    if (e2 < dx) { err = err + dx; yPos = yPos + sy; }
  }
}

void DrawCircle(int radius) 
{
  int x1 = xPos, y1 = yPos, dx = 1, dy = 1;
  int x = radius - 1, y = 0;
  int err = dx - (radius<<1);
  while (x >= y) {
    drawPixel(x1-x, y1+y, SSD1306_WHITE); drawPixel(x1+x, y1+y, SSD1306_WHITE);
    drawPixel(x1-y, y1+x, SSD1306_WHITE); drawPixel(x1+y, y1+x, SSD1306_WHITE);
    drawPixel(x1-y, y1-x, SSD1306_WHITE); drawPixel(x1+y, y1-x, SSD1306_WHITE);
    drawPixel(x1-x, y1-y, SSD1306_WHITE); drawPixel(x1+x, y1-y, SSD1306_WHITE);
    if (err > 0) {
      x = x - 1; dx = dx + 2;
      err = err - (radius<<1) + dx;
    } else {
      y = y + 1; err = err + dy;
      dy = dy + 2;
    }
  }
}

uint8_t ReverseByte(uint8_t x) 
{
  x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
  x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
  x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
  return x;    
}

//Plot character and return width
int PlotChar(int c, int x, int y) 
{
  if (largeDigits && c >= 0x30 && c <= 0x39)
  {
    c = c + 0x26;  //(0x56 - 0x30) or 'V' - '0' gives offset to big numbers
  }
  c = c - 32;
  int cnt = pgm_read_byte(&ssd1306xled_fontVSx8[c][0]);  //Get width of character
  for (int8_t i = 0; i < cnt; i++) { // Char bitmap = 5 columns
    uint8_t line = pgm_read_byte(&ssd1306xled_fontVSx8[c][i+1]);
    for (int8_t j = 0; j < 8; j++, line >>= 1) {
      drawPixel(x + i, y + j, (line & 1) ? SSD1306_WHITE : SSD1306_BLACK);
    }
  }
  return cnt;
}

// Plot text starting at the current plot position
void PlotText(String s) 
{
  int p = 0;
  while (1) {
    char c = s[p++];
    if (c == 0) return;
    xPos += PlotChar(c, xPos, yPos);  //Add width of character to X position
  }
}

void useLargeDigits(bool use)
{
  largeDigits = use;
}

// REFRESH DISPLAY ---------------------------------------------------------

/*!
    @brief  Push data currently in RAM to SSD1306 display.
    @return None (void).
    @note   Drawing operations are not visible until this function is
            called. Call after each graphics command, or after a whole set
            of graphics commands, as best needed by one's own application.
*/
void refreshDisplay(void) 
{
  TRANSACTION_START
  static const uint8_t PROGMEM dlist1[] = {
      SSD1306_PAGEADDR,
      0,                      // Page start address
      0xFF,                   // Page end (not really, but works here)
      SSD1306_COLUMNADDR, 0}; // Column start address
  ssd1306_commandList(dlist1, sizeof(dlist1));
  ssd1306_command1(SSD1306_SCREEN_WIDTH - 1); // Column end address

#if defined(ESP8266)
  // ESP8266 needs a periodic yield() call to avoid watchdog reset.
  // With the limited size of SSD1306 displays, and the fast bitrate
  // being used (1 MHz or more), I think one yield() immediately before
  // a screen write and one immediately after should cover it.  But if
  // not, if this becomes a problem, yields() might be added in the
  // 32-byte transfer condition below.
  yield();
#endif
  uint16_t count = SSD1306_SCREEN_WIDTH * ((SSD1306_SCREEN_HEIGHT + 7) / 8);
  uint8_t *ptr = screenBuf;
  Wire.beginTransmission(SSD1306_I2CADDR);
  Wire.write((uint8_t)0x40);
  uint16_t bytesOut = 1;
  while (count--) {
    if (bytesOut >= WIRE_MAX) {
      Wire.endTransmission();
      Wire.beginTransmission(SSD1306_I2CADDR);
      Wire.write((uint8_t)0x40);
      bytesOut = 1;
    }
    Wire.write(*ptr++);
    bytesOut++;
  }
  Wire.endTransmission();
  TRANSACTION_END
#if defined(ESP8266)
  yield();
#endif
}

fontVSx8.h

C Header File
/*
 * SSD1306xLED - Drivers for SSD1306 controlled dot matrix OLED/PLED 128x64 displays
 *
 * @created: 2014-08-12
 * @author: Neven Boyanov
 *
 * Source code available at: https://bitbucket.org/tinusaur/ssd1306xled
 *
 */

// ----------------------------------------------------------------------------

#include <avr/pgmspace.h>

// ----------------------------------------------------------------------------

/* Variable ASCII 8 pixel high font */
/* first byte contains width        */
const uint8_t ssd1306xled_fontVSx8[96][7] PROGMEM = {
  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sp
  0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, // !
  0x04, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, // "
  0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14, 0x00, // #
  0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x00, // $
  0x00, 0x62, 0x64, 0x08, 0x13, 0x23, 0x00, // %
  0x00, 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, // &
  0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // '
  0x03, 0x0E, 0x11, 0x00, 0x00, 0x00, 0x00, // (
  0x03, 0x11, 0x0E, 0x00, 0x00, 0x00, 0x00, // )
  0x03, 0x02, 0x05, 0x02, 0x00, 0x00, 0x00, // *
  0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // +
  0x00, 0x00, 0x00, 0xA0, 0x60, 0x00, 0x00, // ,
  0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // -
  0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // .
  0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // /
  0x05, 0x0E, 0x11, 0x11, 0x0E, 0x00, 0x00, // 0
  0x03, 0x02, 0x1F, 0x00, 0x00, 0x00, 0x00, // 1
  0x05, 0x12, 0x19, 0x15, 0x12, 0x00, 0x00, // 2
  0x05, 0x0A, 0x11, 0x15, 0x0A, 0x00, 0x00, // 3
  0x05, 0x0C, 0x0A, 0x1F, 0x80, 0x00, 0x00, // 4
  0x04, 0x17, 0x15, 0x0D, 0x00, 0x00, 0x00, // 5
  0x05, 0x0C, 0x16, 0x15, 0x08, 0x00, 0x00, // 6
  0x04, 0x01, 0x1D, 0x03, 0x00, 0x00, 0x00, // 7
  0x05, 0x0A, 0x15, 0x15, 0x0A, 0x00, 0x00, // 8
  0x05, 0x02, 0x15, 0x0D, 0x06, 0x00, 0x00, // 9
  0x02, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, // :
  0x00, 0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ;
  0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, // <
  0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // =
  0x00, 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, // >
  0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ?
  0x00, 0x32, 0x49, 0x59, 0x51, 0x3E, 0x00, // @
  0x04, 0x1E, 0x05, 0x1E, 0x00, 0x00, 0x00, // A
  0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B
  0x05, 0x1C, 0x22, 0x22, 0x14, 0x00, 0x00, // C
  0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D
  0x04, 0x1F, 0x15, 0x15, 0x00, 0x00, 0x00, // E
  0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, // F
  0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00, // G
  0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H
  0x00, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I
  0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J
  0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K
  0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L
  0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, // M
  0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N
  0x04, 0x1F, 0x11, 0x1F, 0x00, 0x00, 0x00, // O
  0x04, 0x1F, 0x05, 0x02, 0x00, 0x00, 0x00, // P
  0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q
  0x04, 0x1F, 0x05, 0x1A, 0x00, 0x00, 0x00, // R
  0x04, 0x17, 0x15, 0x1D, 0x00, 0x00, 0x00, // S
  0x04, 0x01, 0x1F, 0x01, 0x00, 0x00, 0x00, // T
  0x04, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00, // U
  0x05, 0x1E, 0x21, 0x21, 0x1E, 0x00, 0x00, // V 0
  0x03, 0x02, 0x3F, 0x00, 0x00, 0x00, 0x00, // W 1
  0x05, 0x22, 0x31, 0x29, 0x26, 0x00, 0x00, // X 2
  0x05, 0x12, 0x21, 0x25, 0x1A, 0x00, 0x00, // Y 3
  0x05, 0x0C, 0x0A, 0x3F, 0x08, 0x00, 0x00, // Z 4
  0x05, 0x17, 0x25, 0x25, 0x19, 0x00, 0x00, // [ 5
  0x05, 0x1C, 0x26, 0x25, 0x18, 0x00, 0x00, //   6
  0x05, 0x01, 0x39, 0x05, 0x03, 0x00, 0x00, // ] 7
  0x05, 0x1A, 0x25, 0x25, 0x1A, 0x00, 0x00, // ^ 8
  0x05, 0x06, 0x29, 0x19, 0x0E, 0x00, 0x00, // _ 9
  0x00, 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // '
  0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a
  0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b
  0x05, 0x1C, 0x22, 0x22, 0x14, 0x00, 0x00, // c
  0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d
  0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e
  0x00, 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f
  0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00, // g
  0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h
  0x02, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, // i
  0x00, 0x40, 0x80, 0x84, 0x7D, 0x00, 0x00, // j
  0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, // k
  0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l
  0x06, 0x1E, 0x02, 0x1E, 0x02, 0x1C, 0x00, // m
  0x05, 0x1E, 0x02, 0x02, 0x1C, 0x00, 0x00, // n
  0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o
  0x00, 0xFC, 0x24, 0x24, 0x24, 0x18, 0x00, // p
  0x00, 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00, // q
  0x00, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r
  0x00, 0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s
  0x00, 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t
  0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u
  0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v
  0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w
  0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x
  0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C, 0x00, // y
  0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z
};

// ----------------------------------------------------------------------------

ThermistorLibrary.zip

Arduino
No preview (download only).

Credits

John Bradnam

John Bradnam

141 projects • 167 followers
Thanks to Arnov Sharma.

Comments