Hackster is hosting Hackster Holidays, Finale: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Tuesday!Stream Hackster Holidays, Finale on Tuesday!
John Bradnam
Published © GPL3+

3D Printer Bed Leveler Tool

A quick bed leveling tool for 3D printers.

IntermediateFull instructions provided24 hours2,998

Things used in this project

Hardware components

Microchip ATtiny1614 Microprocessor
×1
I2C 128 x 64 0.91in OLED display
×1
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
with button tops
×2
TP4056 1A Lithium Battery Charging Board
×1
Polymer Lithium Ion Battery (LiPo) 3.7V 120mAh
×1
Grove - Round Force Sensor (FSR402)
Seeed Studio Grove - Round Force Sensor (FSR402)
×1
Passive Components
Resistors 0805 SMD: 1 x 10k Capacitors 0806 SMD: 1 x 0.1uF
×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

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

BedLevelingToolV1.ino

C/C++
// Bed Leveling Tool
// Based on a design by Dominick Lee (https://www.hackster.io/solutionhacker/3d-print-bed-leveling-tool-using-m5stickc-035185)
//
// 
// V1: jlb (jbrad2089@gmail.com)
//  - Wrote software for a ATtiny1614 and 128x32 I2C OLED display
//

/**
 * 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)
 *             +--------+
 */

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <avr/sleep.h>
#include <EEPROM.h>

#define CAL_BTN 0   //PA4 D0
#define ON_BTN 1    //PA5 D1
#define SENSOR 9    //PA2 D9

#define OLED_I2C_ADDRESS 0x3C // OLED display I2C address
#define SCREEN_WIDTH 128      // OLED display width, in pixels
#define SCREEN_HEIGHT 32      // OLED display height, in pixels

#define COLUMN_WIDTH 8       // Width of column on screen
#define COLUMN_HEIGHT SCREEN_WIDTH

#define LEVEL_WIDTH 4         // Width of requested level arrow
#define LEVEL_HEIGHT 4        // Height of requested level arrow

#define ARROW_X 22
#define ARROW_WIDTH 8
#define ARROW_HEIGHT 32

#define EPISLON 4             //Height of Thumbd Up area

//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
  uint32_t magic;
  int requestedLevel;     //Pressure recorded when cal button is pressed
} EEPROM_DATA;

EEPROM_DATA EepromData;       //Current EEPROM settings

int pressureLevel;      //Current pressure reading


// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//Thumbs Up (24 bits x 19 rows)
#define BITMAP_W 24
#define BITMAP_H 19
const uint8_t thumbsUp[] PROGMEM = {
  0x1f, 0xff, 0x7f, 0x10, 0x00, 0xc1, 0x20, 0x00, 0x41, 0x3c, 0x00, 0x41, 0x40, 0x00, 0x41, 0x40, 
  0x00, 0x41, 0x7e, 0x00, 0x41, 0x40, 0x00, 0x41, 0x40, 0x00, 0x41, 0x7e, 0x01, 0xc1, 0x40, 0x03, 
  0x7f, 0x40, 0x06, 0x00, 0x7f, 0x8c, 0x00, 0x00, 0x88, 0x00, 0x00, 0x98, 0x00, 0x00, 0x90, 0x00, 
  0x00, 0x90, 0x00, 0x00, 0x90, 0x00, 0x00, 0x60, 0x00
};

//000111111111111101111111 0x1f, 0xff, 0x7f
//000100000000000011000001 0x10, 0x0,  0xc1
//001000000000000001000001 0x20, 0x0,  0x41
//001111000000000001000001 0x3c, 0x0,  0x41
//010000000000000001000001 0x40, 0x0,  0x41
//010000000000000001000001 0x40, 0x0,  0x41
//011111100000000001000001 0x7e, 0x0,  0x41
//010000000000000001000001 0x40, 0x0,  0x41
//010000000000000001000001 0x40, 0x0,  0x41
//011111100000000111000001 0x7e, 0x1,  0xc1
//010000000000001101111111 0x40, 0x3,  0x7f
//010000000000011000000000 0x40, 0x6,  0x0
//011111111001100000000000 0x7f, 0x8c, 0x0
//000000001000100000000000 0x0,  0x88, 0x0
//000000001001100000000000 0x0,  0x98, 0x0
//000000001001000000000000 0x0,  0x90, 0x0
//000000001001000000000000 0x0,  0x90, 0x0
//000000001001000000000000 0x0,  0x90, 0x0
//000000000110000000000000 0x0,  0x60, 0x0

//-----------------------------------------------------------------------------
//Handle pin change interrupt when ON button is pressed
// - This will wake up the microprocessor
// - Code will continue after the sleep_mode() command in the systemSleep() function.
void SwitchInterrupt()
{
}

//-----------------------------------------------------------------------------
//Initialise Hardware
void setup() 
{
  //Serial.begin(9600);
  //ON button Interrupt will wake up from sleep mode
  pinMode(ON_BTN, INPUT_PULLUP);
  pinMode(CAL_BTN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ON_BTN),SwitchInterrupt,CHANGE);

  while (!display.begin(0, OLED_I2C_ADDRESS))
  { 
    //Serial.println(F("SSD1306 allocation failed"));
    systemSleep();
  }

  display.setRotation(2);

  //Eprom
  readEepromData();
}

//-----------------------------------------------------------------------------
//Main Program Loop
void loop() 
{
  while (digitalRead(ON_BTN) == HIGH)
  {
    //Display current pressure as a bar
    int pressureLevel = map(analogRead(SENSOR), 0, 1024, 0, COLUMN_HEIGHT);
    display.clearDisplay();
    display.drawRect(0, 0, COLUMN_HEIGHT, COLUMN_WIDTH, SSD1306_WHITE);
    display.fillRect(0, 2, pressureLevel, COLUMN_WIDTH - 4, SSD1306_WHITE);

    //Test if calibration button pressed 
    if (digitalRead(CAL_BTN) == LOW)
    {
      delay(10);    //debounce
      if (digitalRead(CAL_BTN) == LOW)
      {
        //Store the current level
        EepromData.requestedLevel = pressureLevel;
        writeEepromData();
        displayCalibrationArrow(EepromData.requestedLevel);
        display.display();
        //Wait until button is released
        while (digitalRead(CAL_BTN) == LOW)
        {
          delay(100);
        }
      }
    }

    //Show any calibration arrow
    if (EepromData.requestedLevel != 0)
    {
      displayCalibrationArrow(EepromData.requestedLevel);
      if (abs(pressureLevel - EepromData.requestedLevel) < EPISLON)
      {
        displayThumbsUp();
      }
      else if (pressureLevel > EepromData.requestedLevel)
      {
        //Gap too small, lower the bed to increase the gap
        displayDownArrow();
      }
      else
      {
        //Gap too big, raise the bed to decrease the gap
        displayUpArrow();
      }
    }
    
    display.display();
    delay(100);
  }
  //Wait until ON button is released
  while (digitalRead(ON_BTN) == LOW)
  {
    delay(100);
  }
  //Shut down unit
  systemSleep();
  setup();
}

//-----------------------------------------------------------------------------
// display the calibration arrow on the sceen
void displayCalibrationArrow(int y)
{
  display.fillTriangle(y, COLUMN_WIDTH, y - LEVEL_HEIGHT / 2, COLUMN_WIDTH + LEVEL_WIDTH, y + LEVEL_HEIGHT / 2, COLUMN_WIDTH + LEVEL_WIDTH, SSD1306_WHITE);
}

//-----------------------------------------------------------------------------
// display the lower bed arrow on the sceen
void displayDownArrow()
{
  display.fillTriangle(0, ARROW_X, ARROW_WIDTH, ARROW_X - ARROW_WIDTH / 2, ARROW_WIDTH, ARROW_X + ARROW_WIDTH / 2, SSD1306_WHITE);
  display.fillRect(ARROW_WIDTH, ARROW_X - ARROW_WIDTH / 4, ARROW_HEIGHT - ARROW_WIDTH, ARROW_WIDTH / 2 + 1, SSD1306_WHITE);
}

//-----------------------------------------------------------------------------
// display the raise bed arrow on the sceen
void displayUpArrow()
{
  display.fillTriangle(COLUMN_HEIGHT, ARROW_X, COLUMN_HEIGHT - ARROW_WIDTH, ARROW_X - ARROW_WIDTH / 2, COLUMN_HEIGHT - ARROW_WIDTH, ARROW_X + ARROW_WIDTH / 2, SSD1306_WHITE);
  display.fillRect(COLUMN_HEIGHT - ARROW_HEIGHT, ARROW_X - ARROW_WIDTH / 4, ARROW_HEIGHT - ARROW_WIDTH, ARROW_WIDTH / 2 + 1, SSD1306_WHITE);
}

//-----------------------------------------------------------------------------
// display the stop icon on the sceen
void displayThumbsUp()
{
  display.drawBitmap((COLUMN_HEIGHT - BITMAP_H) / 2, ARROW_X - BITMAP_W / 2, thumbsUp, BITMAP_W, BITMAP_H, SSD1306_WHITE);
}

//-----------------------------------------------------------------------------
//Shut down OLED and put ATtiny to sleep
//Will wake up when LEFT button is pressed
void systemSleep() 
{
  interrupts();
  display.clearDisplay();
  display.display();
  display.ssd1306_command(0xAE);
  //cbi(ADCSRA,ADEN);                   // switch Analog to Digitalconverter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
  sleep_enable();
  sleep_mode();                         // System actually sleeps here
  sleep_disable();                      // System continues execution here when watchdog timed out
  //sbi(ADCSRA,ADEN);                   // switch Analog to Digitalconverter ON
  display.ssd1306_command(0xAF);
  //Wait until on button is released
  while (digitalRead(ON_BTN) == LOW)
  {
    delay(100);
  }
}

//---------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData()
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  EEPROM.put(EEPROM_ADDRESS,EepromData);
}

//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData()
{
  //Eprom
  EEPROM.get(EEPROM_ADDRESS,EepromData);
  if (EepromData.magic != EEPROM_MAGIC)
  {
    EepromData.magic = EEPROM_MAGIC;
    EepromData.requestedLevel = 0;
    writeEepromData();
  }
}

Credits

John Bradnam
147 projects • 181 followers
Thanks to Dominick Lee.

Comments