John Bradnam
Published © GPL3+

Matrix Spirit Level

A dual axis spirit level that uses a MPU-6050 Motion Tracking Sensor and is displayed on a 8x8 LED matrix.

IntermediateFull instructions provided10 hours588

Things used in this project

Hardware components

Microchip AVR128DB32 Microprocessor
×1
GY-521 MPU-6050 Module
×1
8x8 LED 30x30mm matrix
LEDMS88R 3mm dot size, Cathode rows, Anode columns, Preferable square LEDs
×1
10K Trim Potentiometer
SMD variant
×3
0.1uF 0805 Capacitor
×1
3mm Mercury Switch
×1
Battery, 3.7 V
Battery, 3.7 V
120mAh
×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

Eagle files

Schematic & PCB in Eagle format

Code

MatrixLevelV1.ino

Arduino
/*
 * Matrix Level
 * Author: John Bradnam
 * 
 * 2022-12-20 jbrad2089@gmail.com
 *  - Create initial codebase
 * 
 * Board: AVR DB-series (no bootloader)
 * Chip: AVR128DB32
 * 
 *                    (2) (1) (0)            (26)(25)
 *                     |   |   |   |   |   |   |   |
 *                +----+---+---+---+---+---+---+---+----+
 *                |   PA2 PA1 PA0 GND VDD UPD PF6 PF5   |
 *           (3)--+ PA3                             PF4 +--(24)
 *           (4)--+ PA4                             PF3 +--(23)
 *           (5)--+ PA5                             PF2 +--(22)
 *           (6)--+ PA6         AVR128DB32          PF1 +--(21)
 *           (7)--+ PA7                             PF0 +--(20)
 *           (8)--+ PC0                             GND +--
 *           (9)--+ PC1                             VDD +--
 *          (10)--+ PC2                             PD7 +--(19)
 *                |   PC3 VDD PD1 PD2 PD3 PD4 PD5 PD6   |
 *                +----+---+---+---+---+---+---+---+----+
 *                     |   |   |   |   |   |   |   |
 *                   (11)    (13)(14)(15)(16)(17)(18) 
 *                   
 *  PF6 is a INPUT only                 
 */

#include <Wire.h>
#include <MPU6050_light.h>

//Comment out if using a V5 or higher board
//#define V4
 
#define SDA 2      //PA2
#define SCL 3      //PA3
#define C8 4       //PA4
#define C7 5       //PA5
#define R2 6       //PA6
#define C1 7       //PA7
#define R4 8       //PC0
#define C6 9       //PC1
#define C4 10      //PC2
#define R1 11      //PC3
#define HORZ 13    //PD1
#define VERT 14    //PD2
#ifdef V4
  #define BRIGHT 18  //PD6
  #define R3 19      //PD7
  #define R6 20      //PF0
  #define C5 21      //PF1
  #define R8 22      //PF2
  #define C3 23      //PF3
  #define C2 24      //PF4
  #define R7 25      //PF5
  #define R5 17      //PD5
#else
  #define BRIGHT 17  //PD5
  #define R3 18      //PD6
  #define R6 19      //PF7
  #define C5 20      //PF0
  #define R8 21      //PF1
  #define C3 22      //PF2
  #define C2 23      //PF3
  #define R7 24      //PF4
  #define R5 25      //PF5
#endif

// GY-521

MPU6050 mpu(Wire);
#define GY521_UPDATE_TIME 10
unsigned long mpuTimerOut;

// Matrix

#define TCB_COMPARE 1000          //Refresh value for Matrix display
#define BRIGHTNESS 4              //Initial brightness level (0 to 15)
#define MATRIX_SIZE 8             //Number of columns or rows in display

volatile uint8_t matrix[MATRIX_SIZE];      //Holds byte representation of columns for 8 rows
volatile uint8_t nextRow = 0;              //Holds next digit to display
volatile uint8_t brightness = BRIGHTNESS;  //Current Brightness level (0 to 15)
volatile uint8_t bamCounter = 0;           //Bit Angle Modulation variable to keep track of things
volatile uint8_t bamBit = 0x01;            //Used to store bit to test against brightness value

volatile uint8_t rowPins[8] = { R1, R2, R3, R4, R5, R6, R7, R8 };
volatile uint8_t colPins[8] = { C1, C2, C3, C4, C5, C6, C7, C8 };

//Character set
#define SPACE_3X5 10
#define ANIM_1 11   // Foward slash
#define ANIM_2 12   // Hyphen
#define ANIM_3 13   // Back slash 
#define ANIM_4 14   // Vertical bar
#define FONT_COL_MAX 3
#define FONT_ROW_MAX 5
#define VSHIFT 1    //Pixels up from bottom
#define HSHIFT 3    //Pixels in from left
const byte font3x5[15][FONT_COL_MAX] PROGMEM = {
  {0x1F,0x11,0x1F}, {0x00,0x1F,0x00}, {0x1D,0x15,0x17}, {0x15,0x15,0x1F}, {0x07,0x04,0x1F},
  {0x17,0x15,0x1D}, {0x1F,0x15,0x1D}, {0x01,0x1D,0x03}, {0x1F,0x15,0x1F}, {0x07,0x05,0x1F},
  {0x00,0x00,0x00}, {0x10,0x0E,0x01}, {0x04,0x04,0x04}, {0x01,0x0E,0x10}, {0x00,0x1F,0x0}    
};

volatile uint8_t animationSymbol;

//-------------------------------------------------------------------------
//Initialise Hardware

void setup() 
{
  pinMode(HORZ, INPUT);       //Not used
  pinMode(VERT, INPUT);       //Not used
  pinMode(BRIGHT, INPUT);     //Determines brightness

  //Initialise Matrix pins
  for (int i = 0; i < MATRIX_SIZE; i++)
  {
    matrix[i] = 0;    //Clear display buffer
    pinMode(rowPins[i], OUTPUT);
    digitalWrite(rowPins[i], HIGH);
    pinMode(colPins[i], OUTPUT);
    digitalWrite(colPins[i], LOW);
  }

  //Set up display refresh timer
  TCB1.CCMP = TCB_COMPARE;
  TCB1.INTCTRL = TCB_CAPT_bm;
  TCB1.CTRLA = TCB_ENABLE_bm;

  // Initialize RTC
  while (RTC.STATUS > 0);                           // Wait until registers synchronized
  RTC.PER = 255;                                    // Set period 250mS
  RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                // 32.768kHz Internal Oscillator  
  RTC.INTCTRL = RTC_OVF_bm;                         // Enable overflow interrupt
  RTC.CTRLA = RTC_PRESCALER_DIV32_gc;               // Prescaler /32 and disable

  //Enable interrupts
  sei();

  //Initialise GY-521
  Wire.begin();
  mpu.begin();
  
  //Count down to calibration
  for (int i = 5; i > 0; i--)
  {
    displayDigit(i,HSHIFT);
    delay(1000);
  }

  //Enable RTC for animation while calibrating
  animationSymbol = ANIM_1;
  RTC.CTRLA |= RTC_RTCEN_bm;                      // Enable RTC
  //Calculating gyro offset, do not move MPU6050;
  mpu.calcGyroOffsets();                          // This does the calibration
  RTC.CTRLA &= ~RTC_RTCEN_bm;                     // Disable RTC

}

//-------------------------------------------------------------------------
// Timer B Interrupt handler interrupt each mS
//  Refresh display by outputing the rows

ISR(TCB1_INT_vect)
{

  //This is 4 bit 'Bit angle Modulation' or BAM, 
  if (bamCounter == (MATRIX_SIZE * 1) || bamCounter == (MATRIX_SIZE * 3) || bamCounter == (MATRIX_SIZE * 7))
  {
    bamBit = bamBit << 1;
  }
  bamCounter++;

  //Turn off all rows
  for (int i = 0; i < MATRIX_SIZE; i++)
  {
    digitalWrite(rowPins[i], HIGH);
    digitalWrite(colPins[i], LOW);
  }

  //Only show if bamBit matches brightness level
  if (brightness & bamBit)
  {
    uint8_t mask = 0x01;
    for (int i = 0; i < MATRIX_SIZE; i++)
    {
      if (matrix[nextRow] & mask)
      {
        digitalWrite(colPins[i], HIGH);
      }
      mask = mask << 1;
    }
    //Turn on LEDs in next row
    digitalWrite(rowPins[nextRow], LOW);
  }
  
  //Setup for next row
  nextRow = (nextRow + 1) & 0x07;

  //Check if bamCount overflowed, reset if necessary
  if (bamCounter == (MATRIX_SIZE * 15)) 
  {
    bamCounter = 0;
    bamBit = 0x01;
  }

  //Clear interrupt flag
  TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB0.CNT)
}

//---------------------------------------------------------------
// RTC Interrupt Handler
//  - used for animation only

ISR(RTC_CNT_vect)
{
  displayDigit(animationSymbol,HSHIFT);
  animationSymbol = (animationSymbol == ANIM_4) ? ANIM_1 : animationSymbol + 1;
  RTC.INTFLAGS = RTC_OVF_bm;                         // Reset overflow interrupt
}

//-------------------------------------------------------------------------
// Handle interactions

void loop() 
{
  mpu.update();  
  if (millis() > mpuTimerOut)
  {
    uint8_t x = mapAngleToBits(mpu.getAngleX());
    uint8_t xl = x & 0x0F;
    uint8_t xh = x >> 4;
    
    uint8_t y = mapAngleToBits(mpu.getAngleY());
    uint8_t yl = 1 << (y & 0x0F);
    uint8_t yh = ((y & 0xF0) == 0xF0) ? 0x0F : 1 << (y >> 4);

    //Clear maxtrix
    for (int i = 0; i < MATRIX_SIZE; i++)
    {
      matrix[i] = 0;    //Clear display buffer
    }
    matrix[xl] |= yl;
    if (xh != 0x0F)
    {
      matrix[xh] |= yl;
      if (yh != 0x0F)
      {
        matrix[xl] |= yh;
        matrix[xh] |= yh;
      }
    }
    else if (yh != 0x0F)
    {
      matrix[xl] |= yh;
    }
    
    brightness = map(analogRead(BRIGHT),0,1023,0,15);
    
    mpuTimerOut = millis() + GY521_UPDATE_TIME;
  }
}


//-----------------------------------------------------------------------------------
// Map angle to bit location
//  a - angle
//  returns bit 1 location in low nibble and bit 2 location in high nibble
/*
 * 0 1 2 3 4 5 6 7 angle -> returns
 * x . . . . . . . <=41   -> 0xF0
 * x x . . . . . . 42     -> 0x10
 * . x x . . . . . 43     -> 0x21
 * . . x x . . . . 44     -> 0x32 
 * . . . x x . . . 45     -> 0x43 
 * . . . . x x . . 46     -> 0x54 
 * . . . . . x x . 47     -> 0x65 
 * . . . . . . x x 48     -> 0x76 
 * . . . . . . . x >=49   -> 0xF7 
 */

uint8_t mapAngleToBits(float a)
{
  uint8_t deg = max(min((int)floor(a+0.5),45),-45) + 45;
  deg = min(max(deg,41),49);
  if (deg == 41)
  {
    return 0xF0;
  }
  else if (deg == 49)
  {
    return 0xF7;
  }
  else
  {
    return (deg - 42) | ((deg - 41) << 4);
  }
}

//-----------------------------------------------------------------------------------
// Display number matrix display
//  num - 0 to 99
//  leadingZeros - true to display leading zero
//  flash = true to show digit

void displayNumber(int num, bool leadingZeros, bool flash)
{
  num = max(min(num, 99), 0);
  for (int i = 0, shift = 4; i < 2; i++, shift-=4)
  {
    if (flash && (num > 0 || i == 0 || leadingZeros))
    {
      displayDigit(num % 10, shift);      
    }
    else
    {
      displayDigit(SPACE_3X5, shift);
    }
    num = num / 10;
  }
}

//-----------------------------------------------------------------------------------
// Display 3x5 digit matrix display
//  digit - 0 to 9 or SPACE_3X5 or ANIM_1 to ANIM_4
//  col - 0 or 4

void displayDigit(uint8_t digit, uint8_t col)
{
  uint8_t bits;
  uint8_t mask;
  uint8_t colBit;
  
  colBit = 1 << col;
  for (int8_t c = 0; c < FONT_COL_MAX; c++)
  {
    bits = pgm_read_byte(&font3x5[digit][c]);
    mask = 0x10;
    //Get bits in the next column and output to buffer
    for(int8_t r = 0; r < FONT_ROW_MAX; r++)
    {
      if (bits & mask)
      {
        matrix[7-(r+VSHIFT)] |= colBit;
      }
      else
      {
        matrix[7-(r+VSHIFT)] &= ~colBit;
      }
      mask = mask >> 1;
    }
    colBit = colBit << 1;
  }
}

//-----------------------------------------------------------------------------------
// Display pixel on the matrix display
//  pos - row * 8 + col (0 is top left)
//  on - true to show, false to erase

void displayPixel(int pos, bool on)
{
  int row = pos >> 3;
  int col = 1 << (pos & 0x07);
  if (on)
  {
    matrix[row] = matrix[row] | col;
  }
  else
  {
    matrix[row] = matrix[row] & ~col;
  }
}

 

Credits

John Bradnam

John Bradnam

141 projects • 167 followers

Comments