/**
* ATtiny1614 Cube Clock
* John Bradnam (jbrad2089@gmail.com)
*
* 2021-09-06 - Create Matrix display functions for clock
*
*/
#pragma once
#include <LedControl.h>
#include "Memory.h"
#include "Switches.h"
//MAX7219
#define CLK 1 //PA5
#define LOAD 2 //PA6
#define DIN 3 //PA7
enum FONTSIZE {FONT3X5,FONT5X7};
#define SCROLL_SPEED 40 //Speed at which text is scrolled
String scrollText; //Used to store scrolling text
volatile int8_t scrollDelay; //Used to store scroll delay
volatile int8_t scrollCharPos; //Current character position in text being scrolled
volatile int8_t scrollCharCol; //Next column in character to display
volatile int8_t scrollOffScreen; //Extra columns required to scroll last character off screen
volatile FONTSIZE scrollFont; //Font used when scrolling text
volatile bool forceHorizontal; //Set TRUE to override vertical mode during setup
byte displayBuffer[8]; //Double buffer for fast updates
//----------------------------------------------------------------------
// Font definitions
#define ASCII_OFFSET 32
#define SPACE_5X7 64
#define BRIGHT_1 32
#define TM_5X7 40
#define DM_5X7 41
#define MD_5X7 42
#define T4_5X7 43
#define T2_5X7 44
#define FS_5X7 45
#define FL_5X7 46
#define FV_5X7 47
const uint8_t font5x7 [61][8] PROGMEM = {
{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // - Brightness Level 1
{0x80, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ! - Brightness Level 3
{0x80, 0xC0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00}, // " - Brightness Level 5
{0x80, 0xC0, 0xE0, 0xF0, 0x00, 0x00, 0x00, 0x00}, // # - Brightness Level 7
{0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0x00, 0x00, 0x00}, // $ - Brightness Level 9
{0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0x00, 0x00}, // % - Brightness Level 11
{0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0x00}, // & - Brightness Level 13
{0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF}, // ' - Brightness Level 15
{0x01, 0x0F, 0x01, 0xE0, 0x10, 0x60, 0x10, 0xE0}, // ( - TM
{0x1C, 0x14, 0x1F, 0xE0, 0x10, 0x60, 0x10, 0xE0}, // ) - DM
{0x0E, 0x01, 0x06, 0x01, 0x0E, 0xE0, 0xA0, 0xF8}, // * - MD
{0x12, 0x19, 0x15, 0x52, 0x60, 0x50, 0xF8, 0x40}, // + - 24
{0x12, 0x1F, 0x10, 0x00, 0x90, 0xC8, 0xA8, 0x90}, // , - 12
{0x1F, 0x05, 0x05, 0x01, 0x90, 0xA8, 0xA8, 0x48}, // - - FS
{0x1F, 0x05, 0x05, 0x01, 0xF8, 0x80, 0x80, 0x80}, // . - FL
{0x1F, 0x05, 0x05, 0x01, 0x78, 0x80, 0x80, 0x78}, // / - FV
{0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00, 0x00}, // 0
{0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, // 1
{0x42, 0x61, 0x51, 0x49, 0x46, 0x00, 0x00, 0x00}, // 2
{0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, 0x00, 0x00}, // 3
{0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00, 0x00}, // 4
{0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00, 0x00}, // 5
{0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, 0x00, 0x00}, // 6
{0x01, 0x71, 0x09, 0x05, 0x03, 0x00, 0x00, 0x00}, // 7
{0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, 0x00}, // 8
{0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00, 0x00}, // 9
{0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00}, // :
{0x60, 0x10, 0x08, 0x04, 0x03, 0x00, 0x00, 0x00}, // ; - /
{0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00}, // <
{0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00}, // =
{0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00}, // >
{0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00, 0x00}, // ?
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // @ - Space
{0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00, 0x00, 0x00}, // A
{0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, 0x00}, // B
{0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00, 0x00}, // C
{0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00}, // D
{0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00, 0x00}, // E
{0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00, 0x00}, // F
{0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00, 0x00, 0x00}, // G
{0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00, 0x00}, // H
{0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00}, // I
{0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00, 0x00}, // J
{0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00}, // K
{0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00}, // L
{0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, 0x00, 0x00}, // M
{0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00, 0x00}, // N
{0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00, 0x00}, // O
{0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00}, // P
{0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00, 0x00}, // Q
{0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, 0x00, 0x00}, // R
{0x46, 0x49, 0x49, 0x49, 0x31, 0x00, 0x00, 0x00}, // S
{0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00, 0x00}, // T
{0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00, 0x00}, // U
{0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00, 0x00}, // V
{0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00, 0x00}, // W
{0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00, 0x00}, // X
{0x07, 0x08, 0x70, 0x08, 0x07, 0x00, 0x00, 0x00}, // Y
{0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00, 0x00}, // Z
{0x1E, 0x05, 0x1E, 0xE0, 0x10, 0x60, 0x10, 0xE0}, // [ - AM
{0x1F, 0x05, 0x07, 0xE0, 0x10, 0x60, 0x10, 0xE0} // \ - PM
};
//Custom characters are stored in PAM
#define CUSTOM_1 93 //]
#define CUSTOM_2 94 //^
uint8_t customChars[2][8];
#define COLON_3X5 10
#define SLASH_3X5 11
#define A_3X5 12
#define P_3X5 13
#define M_3X5 14
#define SPACE_3X5 15
#define VSHIFT 1 //Pixels up from bottom
const byte font3x5[16][3] 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,0x0A,0x00}, {0x18,0x04,0x03}, {0x1E,0x05,0x1E}, {0x1F,0x05,0x07}, {0x1F,0x06,0x1F},
{0x00,0x00,0x00}
};
//-----------------------------------------------------------------------------------
// Forward references
void setupDisplay();
void displayDate(DATES f, bool on);
void displayFormat(FORMATS f, bool on);
void displayFont(FONTS f, bool on);
void displayBrightness(uint8_t b, bool on);
void displayNumber(long num, bool leadingZeros, bool flash);
void displayCharacter(char ch);
void createCustomNumber(int custom, long num, bool leadingZeros, bool flash);
void createCustomDigit(int custom, int num, int hshift, int vshift);
void clearCustomCharacter(int custom);
bool isScrollComplete();
void drawString(String s, FONTSIZE f);
void scrollTerminate();
void scrollTextLeft();
void scrollTextUp();
void displaySetPixel(int8_t r, int8_t c, bool on);
bool displayGetPixel(int8_t r, int8_t c);
void displayClear();
void displayIntensity(int brightness);
void displayRefresh();
//-----------------------------------------------------------------------------------
//Initialise matrix and setup timer for matrix scrolling
LedControl lc = LedControl(DIN, CLK, LOAD, 1);
void setupDisplay()
{
forceHorizontal = false;
lc.shutdown(0, false); //The MAX7219 is in power-saving mode on startup,
displayIntensity(EepromData.brightness);
lc.clearDisplay(0); //Clear the display
//Set up display refresh timer
//CLK_PER = 3.3MHz (303nS)
TCB1.CCMP = 49152; //Refresh value for display (67Hz)
TCB1.INTCTRL = TCB_CAPT_bm;
TCB1.CTRLA = TCB_ENABLE_bm;
}
//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each mS - output segments
ISR(TCB1_INT_vect)
{
//Handle scrolling of text
if (scrollDelay != 0)
{
scrollDelay--;
if (scrollDelay == 0)
{
scrollDelay = SCROLL_SPEED;
if (!forceHorizontal && EepromData.clockFont == FONT_VERT)
{
scrollTextUp();
}
else
{
scrollTextLeft();
}
}
}
//Clear interrupt flag
TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB1.CNT)
}
//-----------------------------------------------------------------------------------
// Display a date layout
// f - TIME_ONLY or TIME_DMY or TIME_MDY
void displayDate(DATES f, bool on)
{
if (!on)
{
displayCharacter(SPACE_5X7);
}
else
{
switch(f)
{
case TIME_ONLY: displayCharacter(TM_5X7); break; //TM
case TIME_DMY: displayCharacter(DM_5X7); break; //DM
case TIME_MDY: displayCharacter(MD_5X7); break; //MD
}
}
}
//-----------------------------------------------------------------------------------
// Display a format layout
// f - HOUR_24 or HOUR_12
void displayFormat(FORMATS f, bool on)
{
if (!on)
{
displayCharacter(SPACE_5X7);
}
else
{
switch(f)
{
case HOUR_24: displayCharacter(T4_5X7); break; //24
case HOUR_12: displayCharacter(T2_5X7); break; //12
}
}
}
//-----------------------------------------------------------------------------------
// Display a font layout
// f - FONT_HORZ_SMALL or FONT_HORZ_LARGE or FONT_VERT
void displayFont(FONTS f, bool on)
{
if (!on)
{
displayCharacter(SPACE_5X7);
}
else
{
switch(f)
{
case FONT_HORZ_SMALL: displayCharacter(FS_5X7); break; //FS
case FONT_HORZ_LARGE: displayCharacter(FL_5X7); break; //FL
case FONT_VERT: displayCharacter(FV_5X7); break; //FV
}
}
}
//-----------------------------------------------------------------------------------
// Display the brightness level
// b - brightness 0 to 7
void displayBrightness(uint8_t b, bool on)
{
if (!on)
{
displayCharacter(SPACE_5X7);
}
else
{
displayCharacter(BRIGHT_1 + b);
}
}
//-----------------------------------------------------------------------------------
// Display a number as two 7 segment digits
// num - 0 to 99
// leadingZeros - true to have leading zeros
// flash - true to show digit, false to show blank
void displayNumber(long num, bool leadingZeros, bool flash)
{
scrollDelay = 0; //Switch off scrolling
createCustomNumber(CUSTOM_1, num, leadingZeros, flash);
displayCharacter(CUSTOM_1);
}
//-----------------------------------------------------------------------------------
// Display a custom character on the screen
void displayCharacter(char ch)
{
uint8_t fromBits;
uint8_t fromMask;
uint8_t toBits;
uint8_t toMask;
fromMask = 0x01;
for(int r = 0; r < 8; r++)
{
toBits = 0;
toMask = 0x80;
for (int c = 0; c < 8; c++)
{
if (ch >= CUSTOM_1)
{
fromBits = customChars[ch - CUSTOM_1][c];
}
else
{
fromBits = pgm_read_byte(&font5x7[ch - ASCII_OFFSET][c]);
}
if (fromBits & fromMask)
{
toBits = toBits | toMask;
}
toMask = toMask >> 1;
}
displayBuffer[r] = toBits;
fromMask = fromMask << 1;
}
displayRefresh();
}
//-----------------------------------------------------------------------------------
// Display a number as two 7 segment digits
// custom - (CUSTOM_1 to CUSTOM_2) One of the custom character slots
// num - 0 to 99
// leadingZeros - true to have leading zeros
// flash - true to show digit, false to show blank
void createCustomNumber(int custom, long num, bool leadingZeros, bool flash)
{
clearCustomCharacter(custom);
num = max(min(num, 99), 0);
for (int i = 0, shift = 4; i < 2; i++, shift-=4)
{
if (flash && (num > 0 || i == 0 || leadingZeros))
{
createCustomDigit(custom, num % 10, shift, VSHIFT+1);
}
else
{
createCustomDigit(custom, SPACE_3X5, shift, VSHIFT+1);
}
num = num / 10;
}
}
//-----------------------------------------------------------------------------------
// Display a digit
// custom - (CUSTOM_1 to CUSTOM_4) One of the custom characrer slots
// num - 0 to 10
// hshift - columns to shift left
// vshift - rows to shift down
void createCustomDigit(int custom, int num, int hshift, int vshift)
{
uint8_t numBits;
uint8_t numMask;
uint8_t cusMask;
uint8_t cusBits;
for (int col = 0; col < 3; col++)
{
numBits = pgm_read_byte_near(&font3x5[num][col]);
numMask = 0x01;
cusMask = 0x01 << vshift;
cusBits = customChars[custom-CUSTOM_1][col+hshift]; //Read existing column
for (int row = 0; row < 5; row++)
{
if (numBits & numMask)
{
cusBits = cusBits | cusMask;
}
else
{
cusBits = cusBits & ~cusMask;
}
numMask = numMask << 1;
cusMask = cusMask << 1;
}
customChars[custom-CUSTOM_1][col+hshift] = cusBits; //Update column
}
}
//-----------------------------------------------------------------------------------
// Clear a customer character buffer
// custom - (CUSTOM_1 to CUSTOM_2) One of the custom characrer slots
void clearCustomCharacter(int custom)
{
for (int i = 0; i < 8; i++)
{
customChars[custom-CUSTOM_1][i] = 0;
}
}
//---------------------------------------------------------------
// Test if text scroll has completed
bool isScrollComplete()
{
return (scrollDelay == 0);
}
//---------------------------------------------------------------
//Draw string
// s = String to display
// f = font to use
void drawString(String s, FONTSIZE f)
{
while (!isScrollComplete())
{
//Wait until last lot of text has scrolled off the screen
updateOrientation(); //Read the mecury switches to get cube orientation
}
s.toUpperCase();
scrollText = s;
scrollFont = f;
scrollCharPos = 0;
scrollCharCol = 0;
scrollOffScreen = 0;
scrollDelay = SCROLL_SPEED; //Starts scrolling
}
//---------------------------------------------------------------
// Terminate aby scrolling
void scrollTerminate()
{
scrollDelay = 0;
}
//---------------------------------------------------------------
//Scroll text left
void scrollTextLeft()
{
uint8_t bits;
uint8_t mask;
int8_t colMax;
int8_t rowMax;
char ch;
//Scroll screen buffer left
for (int8_t c = 0; c < 8; c++)
{
for (int8_t r = 0; r < 8; r++)
{
displaySetPixel(r, c, (c != 7 && displayGetPixel(r, c + 1)));
}
}
switch(scrollFont)
{
case FONT3X5:
colMax = 3;
rowMax = 5;
//character column after last column is blank for letter spacing
if (scrollOffScreen == 0 && scrollCharCol < colMax)
{
ch = scrollText[scrollCharPos];
if (ch >= '0' && ch <= '9' || ch == ':' || ch == ';' || ch == 'A' || ch == 'P' || ch == 'M' || ch == ' ' || ch == '_')
{
uint8_t c = ch - 48;
switch(ch)
{
case ' ': c = SPACE_3X5; break;
case ':': c = COLON_3X5; break;
case ';': c = SLASH_3X5; break;
case 'A': c = A_3X5; break;
case 'P': c = P_3X5; break;
case 'M': c = M_3X5; break;
case '_': c = SPACE_3X5; colMax = 0; break;
}
bits = pgm_read_byte(&font3x5[c][scrollCharCol]);
mask = 0x10;
//Get bits in the next column and output to buffer
for(int8_t r = 0; r < rowMax; r++)
{
if (bits & mask)
{
displaySetPixel(7 - r - VSHIFT, 7, true);
}
mask = mask >> 1;
}
}
}
break;
case FONT5X7:
ch = scrollText[scrollCharPos];
colMax = (ch > 90) ? 8 : 5;
rowMax = (ch > 90) ? 8 : 7;
mask = (ch > 90) ? 0x80 : 0x40;
//character column after last column is blank for letter spacing
if (scrollOffScreen == 0 && scrollCharCol < colMax)
{
if (ch >= 40 && ch <= 94)
{
if (ch == CUSTOM_1 || ch == CUSTOM_2)
{
bits = customChars[ch - CUSTOM_1][scrollCharCol];
}
else
{
bits = pgm_read_byte(&font5x7[ch - ASCII_OFFSET][scrollCharCol]);
}
//Get bits in the next column and output to buffer
for(int8_t r = 0; r < rowMax; r++)
{
if (bits & mask)
{
displaySetPixel(7 - r, 7, true);
}
mask = mask >> 1;
}
}
}
break;
}
if (scrollOffScreen > 0)
{
scrollOffScreen--;
if (scrollOffScreen == 0)
{
//Stop scrolling
scrollDelay = 0;
}
}
else
{
scrollCharCol++;
if (scrollCharCol == (colMax+1))
{
scrollCharCol = 0;
scrollCharPos++;
if (scrollCharPos == scrollText.length())
{
//All text has been outputted, just wait until it is scrolled of the screen
scrollOffScreen = 8;
}
}
}
displayRefresh();
}
//---------------------------------------------------------------
//Scroll text up
void scrollTextUp()
{
uint8_t bits;
uint8_t mask;
int8_t colMax;
int8_t rowMax;
char ch;
//Scroll screen buffer up
for (int8_t r = 0; r < 8; r++)
{
for (int8_t c = 0; c < 8; c++)
{
displaySetPixel(r, c, (r != 7 && displayGetPixel(r + 1, c)));
}
}
//Seventh character row is blank for letter spacing
ch = scrollText[scrollCharPos];
colMax = (ch > 90) ? 8 : 5;
rowMax = (ch > 90) ? 8 : 7;
if (scrollOffScreen == 0 && scrollCharCol < rowMax)
{
char ch = scrollText[scrollCharPos];
if (ch >= 40 && ch <= 94)
{
mask = 1 << scrollCharCol;
//Get bits in the next column and output to buffer
for(int8_t c = 0; c < colMax; c++)
{
if (ch == CUSTOM_1 || ch == CUSTOM_2)
{
bits = customChars[ch - CUSTOM_1][c];
}
else
{
bits = pgm_read_byte(&font5x7[ch - ASCII_OFFSET][c]);
}
if (bits & mask)
{
displaySetPixel(7, c, true);
}
}
}
}
if (scrollOffScreen > 0)
{
scrollOffScreen--;
if (scrollOffScreen == 0)
{
//Stop scrolling
scrollDelay = 0;
}
}
else
{
scrollCharCol++;
if (scrollCharCol == 8)
{
scrollCharCol = 0;
scrollCharPos++;
if (scrollCharPos == scrollText.length())
{
//All text has been outputted, just wait until it is scrolled of the screen
scrollOffScreen = 8;
}
}
}
displayRefresh();
}
//---------------------------------------------------------------
//Sets the bit in the 8 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7) (0 is far left)
// on = true to switch bit on, false to switch bit off
void displaySetPixel(int8_t r, int8_t c, bool on)
{
byte mask = 1 << (7-c);
if (on)
{
displayBuffer[r] = displayBuffer[r] | mask;
}
else
{
displayBuffer[r] = displayBuffer[r] & ~mask;
}
}
//---------------------------------------------------------------
//Get the color of the bit that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 7)
// Returns true if on
bool displayGetPixel(int8_t r, int8_t c)
{
byte mask = 1 << (7-c);
return (displayBuffer[r] & mask);
}
//---------------------------------------------------------------
//Clear the display buffer
void displayClear()
{
memset(&displayBuffer,0,8);
}
//---------------------------------------------------------------
//Set the screen brightness
// b = 0 to 7
void displayIntensity(int b)
{
lc.setIntensity(0, b * 2 + 1);
}
//---------------------------------------------------------------
//Transfers the display buffer to the matrix
void displayRefresh()
{
byte row = 0;
byte rowMask = 0;
byte colMask = 0;
switch (scrollOrient)
{
case LEFT_TO_RIGHT:
for(int i = 0; i < 8; i++)
{
lc.setRow(0,i,displayBuffer[i]);
}
break;
case TOP_TO_BOTTOM:
colMask = 0x01;
for(int c = 0; c < 8; c++)
{
row = 0;
rowMask = 0x80;
for (int r = 0; r < 8; r++)
{
if (displayBuffer[r] & colMask)
{
row = row | rowMask;
}
rowMask = rowMask >> 1;
}
lc.setRow(0,c,row);
colMask = colMask << 1;
}
break;
case RIGHT_TO_LEFT:
for(int r = 0; r < 8; r++)
{
row = 0;
rowMask = 0x01;
colMask = 0x80;
for(int c = 0; c < 8; c++)
{
if (displayBuffer[7-r] & colMask)
{
row = row | rowMask;
}
rowMask = rowMask << 1;
colMask = colMask >> 1;
lc.setRow(0,r,row);
}
}
break;
case BOTTOM_TO_TOP:
colMask = 0x80;
for(int c = 0; c < 8; c++)
{
row = 0;
rowMask = 0x80;
for (int r = 0; r < 8; r++)
{
if (displayBuffer[7-r] & colMask)
{
row = row | rowMask;
}
rowMask = rowMask >> 1;
}
lc.setRow(0,c,row);
colMask = colMask >> 1;
}
break;
}
}
Comments