Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
Looking around for something to build, I came across a project called Fully IR Customizable Electronic Set of Eight Dices by raul7321. After analyzing many different board games, it was highly doubtful that many of the configurable faces would ever be used. So I decided to keep the general concept, drop the IR stuff and redesign the hardware and software.
Like the original version, there are eight configurable elements. These can each be one of the following:
COIN TOSSHere there are two choices depicted either as a thumbs up or a thumbs down.
Here there are three choices depicted either as a fist (rock), open hand (paper) or two fingers (scissors)
A standard dice denoting one of six possible choices.
There is a choice of two different card modes that an element can be configured for.
All elements that were configured using the Ace of Hearts will share the same pack of cards. This means for each hand, these cards will always show a different card face. They are returned to the deck before the next hand is dealt (eg Poker).
All elements that were configured using the Ace of Spades will use a concatenated deck. This means on each deal, cards will be drawn from that concatenated deck. However they are not returned to the deck after being dealt. There is a random marker that when encountered causes all the cards to go back in the decks and decks are re-shuffled. (eg Blackjack)
Using the Board Game BuddyThe unit is controlled via a single button. When the unit is off, pressing the button will switch the unit on. If no action is taken, after 10 seconds it will switch itself off. It still remembers the last screen viewed and will show that screen when turned back on.
A short press of the button will animate all the active elements before settling on a random selection.
A long press of the button will bring up the configuration menu. A flashing cursor becomes visible in configuration mode. A short press of the button will advance the cursor to the next element and after the last element it will exit the configuration menu.
While the cursor is flashing, holding down the button will cycle through each of the above options. Just release the button to select that option.
Hardware designThe original version used a Arduino Pro Mini which is pretty power hungry. I decided to replace it with a ATtiny1614 microprocessor. This processor has a very low power consumption in sleep mode.
I made a custom PCB to hold the microprocessor and associated connections. It is physically smaller than a Arduino Pro Mini.
The 3D printed case is not as deep as the original and the base screws on rather than being a pressure fit.
The case is 3D printed using a 0.15mm layer height. Open the STL files in your slicer software or if you don't have a 3D printer, provide them to your local print shop.
The Eagle files are included in case you want to get the board commercially made or you can make it yourself. I used the Toner method to make mine.
Start by adding the ATtiny1614 microprocessor to the board. I find it easier to use solder paste rather than use solder from a reel when soldering SMD components.
Add the JST battery socket and a pin header for programmer (more on this later).
Solder a 4 pin male pin header to the OLED display. Put the OLED display in the case and place the PCB over the extended pins. If you have through-hole plating, you can just solder the pins in place and trim them. If like my board it is single-sided, take note with a marker how far the pins extend through the holes on the PCB. Now remove the PCB and OLED display and solder them together. Trim the pins flush with the top side of the board.
The pushbutton switch is held in place using hot glue.
Programming the ATtiny1614Unlike the earlier ATtiny series such as the ATtiny85, the ATtiny1614 uses the RESET pin to program the CPU. To program it you need a UPDI programmer. I made one using a Arduino Nano. You can find complete build instructions at Create Your Own UPDI Programmer. It also contains the instructions for adding the megaTinyCore boards to your IDE.
Once the board has been installed in the IDE, select it from the Tools menu.
Select the ATtiny1614 board in your IDE
Select Board, Chip, Clock speed, and the COM port that the Arduino Nano is connected to.
The Programmer needs to be set to jtag2updi (megaTinyCore).
Open the sketch and upload it to the ATtiny1614.
ConclusionThe software changes allow the Board Game Buddy to be used with most board and casino style games. The inclusion of packs of cards make it far more versatile. The software turned out to be a lot more challenging than I expected mainly due to getting the graphics to display correctly. I think that the overall result was worth the effort.
/*============================================================================================================
Eight Dice Set
CPU: ATtiny1614
Display: 0.91" OLED
ATtiny1614 Code: jbrad2089@gmail.com
Concept: raul7321 (https://www.instructables.com/Fully-IR-Customizable-Eight-dice-Set/)
Modes:
COIN: 2 choices - Thumbs Up or Thumbs down
RPS: 3 choices - Rock, Paper Sissors
DICE: 6 choices - Standard dice
CARD_RED: 52 choices - All red decks share a single deck that is there won't be any duplicate
cards shown t the same time. Cards are put back in the eck for the next
draw.
CAD_BLACK: 52 choices - All black decks are concatenated and black cards are drawn from that
concatenated deck. Cards are not pur back into the deck. There is a
random marker that when encountered causes all the cards to go back
in the decks and decks are resuffled.
BOARD: ATtiny1614/1604/814/804/414/404/214/204
Chip: ATtiny1614
Clock Speed: 20MHz
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)
+--------+
PA0 to PA7 can be analog or digital
PWM on D0, D1, D6, D7, D10
==============================================================================================================*/
#include <EEPROM.h>
#include "symbols.h"
#include "button.h"
#include "SSD1306_ATtiny1614.h"
#define ROLL 0 //(PA4)
#define RANDOM 1 //(PA5)
//Uncomment next line to clear out EEPROM and reset all high scores
//#define RESET_EEPROM
#define NO_CURSOR -1
int cursor;
bool inMenu;
enum MODES {MODE_OFF, COIN, RPS, DICE, CARD_RED, CARD_BLACK, MODE_LAST};
#define ELEMENTS 8
//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
uint32_t magic;
MODES mode[ELEMENTS];
int value[ELEMENTS];
long seed;
long blackSeed;
long blackCount;
} EEPROM_DATA;
EEPROM_DATA EepromData; //Current EEPROM settings
MODES lastMode[ELEMENTS];
bool blackDecks[52 * ELEMENTS];
int maxBlackCards;
long blackRandomNext;
/* Timing constants that ontrol how the elements animate */
#define START_DELAY_TIME 10
#define INCREMENT_DELAY_TIME 5
#define MAX_DELAY_BEFORE_STOP 100
#define MIN_SPIN_TIME 1000
#define MAX_SPIN_TIME 3000
/* spinElement holds the timing information for each element */
struct spinElement
{
unsigned long delayTime;
unsigned long spinTime;
unsigned long frameTime;
bool stopped;
};
spinElement spin[ELEMENTS];
#define SLEEP_TIMEOUT 10000 //Amount of time no switch is pressed before going to sleep
uint32_t sleepTimeout = 0; //Used to timeout mode switch function to sleep function
#define FLASH_TIMEOUT 300 //Flash time for cursor
uint32_t flashTimeout = 0; //Used to timeout mode cursor flash
bool flashInvert = false; //Invert state
#define LONG_PRESS_PERIOD 500
void rollButtonRaised(void);
Button* rollButton;
uint32_t rollLongPeriod;
//--------------------------------------------------------------------
//Handle pin change interrupt when button is pressed
//This wakes up the ATtiny1614 from its slumber
void SwitchInterrupt()
{
}
//--------------------------------------------------------------------
//Program Setup
void setup()
{
pinMode(ROLL, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ROLL),SwitchInterrupt,CHANGE);
ssd1306_init();
//Initialise button
rollButton = new Button(ROLL);
//Setup cards
readEepromData();
randomSeed(EepromData.seed);
blackRandomNext = EepromData.blackSeed;
createBlackCardDecks();
//Finish setup
setupAfterSleep();
}
//--------------------------------------------------------------------
//Reinitialise program after going to sleep
void setupAfterSleep()
{
ssd1306_fillscreen(0x00);
updateScreen(NO_CURSOR);
sleepTimeout = millis() + SLEEP_TIMEOUT;
inMenu = false;
}
//--------------------------------------------------------------------
//Program Loop
void loop()
{
if (inMenu)
{
doMenu();
}
else if (millis() >= sleepTimeout)
{
system_sleep();
}
else if (rollButton->Pressed())
{
if (rollButton->DownTime() >= LONG_PRESS_PERIOD)
{
//Enter menu mode
cursor = 0;
inMenu = true;
flashTimeout = millis() + FLASH_TIMEOUT;
rollButton->Repeat(rollButtonPressed);
for (int i = 0; i < ELEMENTS; i++)
{
lastMode[i] = EepromData.mode[i]; //Store each mode to test for changes
switch (EepromData.mode[i])
{
case COIN: EepromData.value[i] = 1; break;
case RPS: EepromData.value[i] = 2; break;
case DICE: EepromData.value[i] = 2; break;
case CARD_RED: EepromData.value[i] = 0; break; //Ace of Hearts
case CARD_BLACK: EepromData.value[i] = 13 * 2; break; //Ace of Spades
}
}
}
else
{
//Perform a roll
doRoll();
sleepTimeout = millis() + SLEEP_TIMEOUT;
}
}
else
{
delay(100);
}
}
//--------------------------------------------------------------------
//Roll all items
void doRoll()
{
//Reset wheels for the spin
int stopCount = 0;
for (uint8_t j = 0; j < ELEMENTS; j++)
{
spin[j].stopped = (EepromData.mode[j] == MODE_OFF);
if (spin[j].stopped)
{
stopCount++;
}
else
{
spin[j].delayTime = START_DELAY_TIME;
spin[j].spinTime = random(MIN_SPIN_TIME, MAX_SPIN_TIME);
spin[j].frameTime = millis() + spin[j].delayTime;
}
}
bool hasBlackDecks = false;
while (stopCount < ELEMENTS)
{
//Update each element
for (uint8_t j = 0; j < ELEMENTS; j++)
{
hasBlackDecks |= (EepromData.mode[j] == CARD_BLACK);
if (!spin[j].stopped && millis() > spin[j].frameTime)
{
spin[j].frameTime = millis() + spin[j].delayTime;
switch (EepromData.mode[j])
{
case COIN: EepromData.value[j] = random(2); break;
case RPS: EepromData.value[j] = random(3); break;
case DICE: EepromData.value[j] = random(6); break;
case CARD_RED: EepromData.value[j] = redRandom(j); break;
case CARD_BLACK: EepromData.value[j] = random(52); break;
}
if (millis() > spin[j].spinTime)
{
//Stop if delayTime exceeds MAX_DELAY_BEFORE_STOP
if (spin[j].delayTime > MAX_DELAY_BEFORE_STOP)
{
spin[j].stopped = true;
stopCount++;
}
else
{
spin[j].delayTime = spin[j].delayTime + INCREMENT_DELAY_TIME;
}
}
}
updateScreen(NO_CURSOR);
}
yield();
}
//Black cards are special as they come from a concatenated deck and are not put back
if (hasBlackDecks)
{
getBlackCards();
//need to record cards removed from decks
EepromData.seed = random(2147483647);
writeEepromData();
}
updateScreen(NO_CURSOR);
}
//--------------------------------------------------------------------
//Ensure all red card picks are unique across every red pack
// element - current element to choose a card for
int redRandom(int element)
{
int card;
bool match = true;
while (match)
{
card = random(52);
match = false;
for (int i = 0; i < ELEMENTS; i++)
{
if (i != element && EepromData.mode[i] == CARD_RED && EepromData.value[i] == card)
{
match = true;
break;
}
}
}
return card;
}
//--------------------------------------------------------------------
//Populate all black cards from the black decks
void getBlackCards()
{
//First clear out all black cards
for (int i = 0; i < ELEMENTS; i++)
{
if (EepromData.mode[i] == CARD_BLACK)
{
EepromData.value[i] = -1;
}
}
//Now assign the next card from the black decks
for (int i = 0; i < ELEMENTS; i++)
{
if (EepromData.mode[i] == CARD_BLACK)
{
EepromData.value[i] = getNextBlackCard();
}
}
}
//--------------------------------------------------------------------
//Populate all black cards from the black decks
int getNextBlackCard()
{
int card = 0;
int tries = 0;
do
{
card = getBlackRandom() % maxBlackCards;
tries++;
}
while (blackDecks[card] && tries < 1000);
//If we couldn't find a empty slot after 1000 attempts, shuffle the decks
if (blackDecks[card])
{
//Shuffle deck
shuffleDecks();
card = getNextBlackCard();
}
else
{
//Stop card from being chosen again
blackDecks[card] = true;
}
//card can be from any of the decks
return card % 52;
}
//--------------------------------------------------------------------
//Black deck cards are drawn without putting back in the pack (eg blackjack)
void createBlackCardDecks()
{
//Count how many actual black decks we have
maxBlackCards = 0;
for (int i = 0; i < ELEMENTS; i++)
{
if (EepromData.mode[i] == CARD_BLACK)
{
maxBlackCards = maxBlackCards + 52;
}
}
if (maxBlackCards > 0)
{
//Clear out deck table
for (int i = 0; i < (52 * ELEMENTS); i++)
{
blackDecks[i] = false;
}
//Fill up the table with cards already drawn from the pack
long count = EepromData.blackCount;
EepromData.blackCount = 0;
for (long i = 0; i < count; i++)
{
blackDecks[getBlackRandom() % maxBlackCards] = true;
}
}
}
//--------------------------------------------------------------------
// Clear black deck tables ready for a new set of cards
void shuffleDecks()
{
//Shuffle deck
EepromData.blackCount = 0;
EepromData.blackSeed = random(2147483647);
blackRandomNext = EepromData.blackSeed;
createBlackCardDecks();
}
//--------------------------------------------------------------------
//Random number generator taken from avr-libc-2.0.0\libc\stdlib\random.c
long getBlackRandom()
{
/*
* Compute x = (7^5 * x) mod (2^31 - 1)
* wihout overflowing 31 bits:
* (2^31 - 1) = 127773 * (7^5) + 2836
* From "Random number generators: good ones are hard to find",
* Park and Miller, Communications of the ACM, vol. 31, no. 10,
* October 1988, p. 1195.
*/
long hi, lo, x;
x = blackRandomNext;
/* Can't be initialized with 0, so use another value. */
if (x == 0)
x = 123459876L;
hi = x / 127773L;
lo = x % 127773L;
x = 16807L * lo - 2836L * hi;
if (x < 0)
x += 0x7fffffffL;
blackRandomNext = x;
EepromData.blackCount++;
return x;
}
//--------------------------------------------------------------------
//Enter the menu
void doMenu()
{
if (millis() >= flashTimeout)
{
flashInvert = !flashInvert;
flashTimeout = millis() + FLASH_TIMEOUT;
updateScreen((flashInvert) ? -1 : cursor);
}
rollLongPeriod = millis() + LONG_PRESS_PERIOD;
if (rollButton->Pressed() && (millis() < rollLongPeriod))
{
//This is a short press - long presses are handled by the repeat function
cursor = cursor + 1;
if (cursor < ELEMENTS)
{
updateScreen((flashInvert) ? -1 : cursor);
}
else
{
//Exiting menu mode
rollButton->Repeat(NULL);
updateScreen(-1);
inMenu = false;
bool changed = false;
bool blackChanged = false;
bool hasBlackDecks = false;
for (int i = 0; i < ELEMENTS; i++)
{
if (lastMode[i] != EepromData.mode[i])
{
blackChanged |= (lastMode[i] == CARD_BLACK);
blackChanged |= (EepromData.mode[i] == CARD_BLACK);
changed = true;
}
}
if (changed)
{
if (blackChanged)
{
shuffleDecks();
}
//When modes have changed store it to EEPROM
EepromData.seed = random(2147483647);
writeEepromData();
}
sleepTimeout = millis() + SLEEP_TIMEOUT;
}
}
delay(100);
}
//-----------------------------------------------------------------------------------
//Invoked via the repeat function of the roll button
void rollButtonPressed()
{
if (millis() >= rollLongPeriod)
{
//Only do something on a long press
EepromData.mode[cursor] = (EepromData.mode[cursor] == CARD_BLACK) ? MODE_OFF : (MODES)((int)EepromData.mode[cursor] + 1);
switch (EepromData.mode[cursor])
{
case COIN: EepromData.value[cursor] = 1; break;
case RPS: EepromData.value[cursor] = 2; break;
case DICE: EepromData.value[cursor] = 2; break;
case CARD_RED: EepromData.value[cursor] = 0; break; //Ace of Hearts
case CARD_BLACK: EepromData.value[cursor] = 13 * 2; break; //Ace of Spades
}
updateScreen(cursor);
}
}
//--------------------------------------------------------------------
//Update the screen with the current symbols
// c = -1 for no cursor, 0 to 7 to invert symbol
void updateScreen(int c)
{
int x;
int y;
for (int i = 0; i < ELEMENTS; i++)
{
y = (i >= 4) ? 4 : 0;
x = (i & 0x03) << 5;
switch (EepromData.mode[i])
{
case MODE_OFF: drawEmptySpace(x, y, (i == c)); break;
case COIN: drawThumbsUpDown(x, y, EepromData.value[i], (i == c)); break;
case RPS: drawRockPaperSissors(x, y, EepromData.value[i], (i == c)); break;
case DICE: drawDice(x, y, EepromData.value[i], (i == c)); break;
case CARD_RED:
case CARD_BLACK: drawCard(x, y, EepromData.value[i], (i == c)); break;
}
}
}
//--------------------------------------------------------------------
//Draw thumbs up / thumbs down
// x = X position of mid point
// y = Y position of bottom point
void drawEmptySpace(int x, int y, bool invert)
{
ssd1306_draw_bmp(x, y, 24, 3, space, invert);
}
//--------------------------------------------------------------------
//Draw thumbs up / thumbs down
// x = X position of mid point
// y = Y position of bottom point
// v = 0 or 1
void drawThumbsUpDown(int x, int y, int v, bool invert)
{
ssd1306_draw_bmp(x, y, 24, 3, thumbs[v], invert);
}
//--------------------------------------------------------------------
//Draw rock / paper /sissors
// x = X position of mid point
// y = Y position of bottom point
// v = 0 or 1 or 2
void drawRockPaperSissors(int x, int y, int v, bool invert)
{
ssd1306_draw_bmp(x, y, 24, 3, rps[v], invert);
}
//--------------------------------------------------------------------
//Draw standard dice
// x = X position of mid point
// y = Y position of bottom point
// v = 0 to 5
void drawDice(int x, int y, int v, bool invert)
{
ssd1306_draw_bmp(x, y, 24, 3, dice[v], invert);
}
//--------------------------------------------------------------------
//Draw card
// x = X position of mid point
// y = Y position of bottom point
// v = 0 to 51
void drawCard(int x, int y, int v, bool invert)
{
int s = v / 13;
int f = v % 13;
ssd1306_draw_merge_bmp(x, y, 24, 3, cards[f], cards[s + 13], invert);
}
//--------------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData(void)
{
//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(void)
{
#ifndef RESET_EEPROM
EEPROM.get(EEPROM_ADDRESS,EepromData);
if (EepromData.magic != EEPROM_MAGIC)
{
#endif
EepromData.magic = EEPROM_MAGIC;
EepromData.seed = analogRead(RANDOM);
EepromData.blackSeed = analogRead(RANDOM);
EepromData.blackCount = 0;
/*
for (int x = 0; x < 8; x++)
{
EepromData.mode[x] = CARD_BLACK;
EepromData.value[x] = 13 * 3;
}
*/
EepromData.mode[0] = COIN; EepromData.value[0] = 0;
EepromData.mode[1] = COIN; EepromData.value[1] = 1;
EepromData.mode[2] = CARD_RED; EepromData.value[2] = 0;
EepromData.mode[3] = CARD_BLACK; EepromData.value[3] = 13 * 3;
EepromData.mode[4] = RPS; EepromData.value[4] = 0;
EepromData.mode[5] = RPS; EepromData.value[5] = 1;
EepromData.mode[6] = RPS; EepromData.value[6] = 2;
EepromData.mode[7] = DICE; EepromData.value[7] = 4;
writeEepromData();
#ifndef RESET_EEPROM
}
#endif
}
//--------------------------------------------------------------------
//Shut down OLED and put ATtiny to sleep
//Will wake up when LEFT button is pressed
void system_sleep()
{
interrupts();
ssd1306_sleep();
//Wait until on button is released
while (digitalRead(ROLL) == LOW)
{
delay(100);
}
setupAfterSleep();
}
#pragma once
const uint8_t space[72] PROGMEM = {
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,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,0x00,0x00,0x00,0x00,0x00,0x00
};
const uint8_t thumbs[2][72] PROGMEM = {{
0xF8,0x08,0x08,0x08,0x08,0x08,0xF8,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x48,0x48,0x58,0x60,0x80,0x00,
0x3F,0x20,0x20,0x20,0x20,0x20,0x3F,0x10,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x80,0x80,0x92,0x92,0x92,0x92,0x92,0xFF,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x3E,0x20,0x20,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
},{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x7C,0x04,0x04,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFC,0x04,0x04,0x04,0x04,0x04,0xFC,0x08,0x0C,0x06,0x03,0x01,0x00,0x00,0x00,0x01,0x01,0x49,0x49,0x49,0x49,0x49,0xFF,0x00,
0x1F,0x10,0x10,0x10,0x10,0x10,0x1F,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x12,0x12,0x1A,0x06,0x01,0x00
}};
const uint8_t rps[3][72] PROGMEM = {{
0x80,0xE0,0x70,0xF0,0xF0,0x70,0x38,0x18,0x1C,0x0C,0x0E,0x06,0x66,0x66,0x6E,0xEC,0xDC,0xF8,0xF0,0xC0,0xC0,0x80,0x00,0x00,
0xFF,0xC1,0xFC,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x18,0x18,0x18,0x18,0x38,0x3D,0xEF,0x00,
0x00,0x00,0x00,0x03,0x07,0x0E,0x0C,0x1C,0x18,0x38,0x30,0x30,0x30,0x30,0x30,0x31,0x31,0x33,0x33,0x1F,0x1F,0x07,0x01,0x00
},{
0x00,0xC0,0xE0,0x20,0x60,0xC0,0xC0,0x00,0x00,0x80,0xC0,0x70,0x38,0x1C,0x84,0xC4,0xE4,0xBC,0xC0,0x40,0x40,0xC0,0xC0,0x00,
0x00,0x81,0xFF,0x1C,0x00,0x00,0x0F,0x06,0x03,0x01,0x00,0x00,0x1C,0x0E,0x03,0x63,0x71,0x31,0x38,0xBC,0x9C,0x96,0xD3,0x70,
0x00,0x07,0x1D,0x33,0x2C,0x28,0x30,0x30,0x20,0x20,0x30,0x30,0x18,0x18,0x0C,0x06,0x06,0x03,0x03,0x01,0x01,0x00,0x00,0x00
},{
0x00,0x80,0xE0,0x30,0x90,0x70,0x30,0x30,0x10,0x10,0x10,0xD0,0x90,0xB0,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x0F,0x10,0x7E,0xC1,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x11,0x11,0x31,0x31,0x71,0xD1,0xB3,0xA2,0x26,0x3C,
0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x06,0x06,0x06,0x06,0x07,0x03,0x03,0x03,0x02,0x06,0x04,0x0C,0x08,0x09,0x0F,0x00
}};
const uint8_t dice[6][72] PROGMEM = {{
0x00,0xFC,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0xFC,
0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x3E,0x3E,0x3E,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x1F,0x20,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x20,0x1F
},{
0x00,0xFC,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x7D,0x7D,0x7D,0x39,0x02,0xFC,
0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x1F,0x20,0x4E,0x5F,0x5F,0x5F,0x4E,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x20,0x1F
},{
0x00,0xFC,0x02,0x39,0x7D,0x7D,0x7D,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x02,0xFC,
0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x3E,0x3E,0x3E,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x1F,0x20,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x4E,0x5F,0x5F,0x5F,0x4E,0x20,0x1F
},{
0x00,0xFC,0x02,0x39,0x7D,0x7D,0x7D,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x7D,0x7D,0x7D,0x39,0x02,0xFC,
0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x1F,0x20,0x4E,0x5F,0x5F,0x5F,0x4E,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x4E,0x5F,0x5F,0x5F,0x4E,0x20,0x1F
},{
0x00,0xFC,0x02,0x39,0x7D,0x7D,0x7D,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x7D,0x7D,0x7D,0x39,0x02,0xFC,
0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x3E,0x3E,0x3E,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x00,0x1F,0x20,0x4E,0x5F,0x5F,0x5F,0x4E,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x4E,0x5F,0x5F,0x5F,0x4E,0x20,0x1F
},{
0x00,0xFC,0x02,0x39,0x7D,0x7D,0x7D,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x7D,0x7D,0x7D,0x39,0x02,0xFC,
0x00,0xFF,0x00,0x1C,0x3E,0x3E,0x3E,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1C,0x3E,0x3E,0x3E,0x1C,0x00,0xFF,
0x00,0x1F,0x20,0x4E,0x5F,0x5F,0x5F,0x4E,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x4E,0x5F,0x5F,0x5F,0x4E,0x20,0x1F
}};
#define HEART 13
#define DIAMOND 14
#define SPADE 15
#define CLUB 16
const uint8_t cards[17][72] PROGMEM = {{
//Ace
0xFE,0x03,0xF1,0xF9,0x1D,0x0D,0x0D,0x0D,0x0D,0x1D,0xF9,0xF1,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0xFF,0xFF,0x03,0x03,0x03,0x03,0x03,0x03,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Two
0xFE,0x03,0x31,0x39,0x1D,0x0D,0x0D,0x0D,0x8D,0xDD,0xF9,0x71,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0xE0,0xF0,0xF8,0xDC,0xCE,0xC7,0xC3,0xC1,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Three
0xFE,0x03,0x31,0x39,0x1D,0x0D,0x0D,0x0D,0x8D,0xDD,0xF9,0x71,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x30,0x70,0xE0,0xC3,0xC3,0xC3,0xC7,0xEF,0x7C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Four
0xFE,0x03,0x01,0x81,0xC1,0xE1,0x71,0x39,0xFD,0xFD,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x0F,0x0F,0x0D,0x0C,0x0C,0x0C,0xFF,0xFF,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Five
0xFE,0x03,0x7D,0xFD,0xCD,0x8D,0x8D,0x8D,0x8D,0x8D,0x0D,0x0D,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x30,0x70,0xE1,0xC1,0xC1,0xC1,0xC1,0xE3,0x7F,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Six
0xFE,0x03,0xF1,0xF9,0x1D,0x0D,0x0D,0x0D,0x0D,0x3D,0x79,0x71,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x3F,0x7F,0xEF,0xC6,0xC6,0xC6,0xC6,0xEE,0x7C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Seven
0xFE,0x03,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0x0D,0xCD,0xFD,0x3D,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x00,0x00,0x00,0x00,0xF8,0xFE,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Eight
0xFE,0x03,0x71,0xF9,0xDD,0x8D,0x0D,0x0D,0x8D,0xDD,0xF9,0x71,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x38,0x7C,0xEF,0xC7,0xC3,0xC3,0xC7,0xEF,0x7C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Nine
0xFE,0x03,0x71,0xF9,0xDD,0x8D,0x8D,0x8D,0x8D,0xDD,0xF9,0xF1,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x38,0x78,0xF1,0xC1,0xC1,0xC1,0xC1,0xE3,0x7F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Ten
0xFE,0x03,0x31,0x31,0xFD,0xFD,0x01,0x01,0xC1,0xC1,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x0C,0x0C,0x0F,0x0F,0x3F,0x3F,0xC0,0xC0,0x3F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Jack
0xFE,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0xFD,0xFD,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x3C,0x7C,0xE0,0xC0,0xC0,0xC0,0xC0,0xE0,0x7F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Queen
0xFE,0x03,0xF1,0xF9,0x1D,0x0D,0x0D,0x0D,0x0D,0x1D,0xF9,0xF1,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x0F,0x1F,0x38,0x30,0x30,0x38,0x38,0x78,0xFF,0xCF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//King
0xFE,0x03,0xFD,0xFD,0x81,0xC1,0xE1,0x71,0x39,0x1D,0x0D,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0xFF,0xFF,0x07,0x0F,0x1C,0x38,0x70,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xC0,0x7F
},{
//Heart
0xFE,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xF8,0xFC,0xF8,0xF0,0xF0,0xF8,0xFC,0xF8,0xE0,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x81,0x83,0x8F,0x9F,0xBF,0xBF,0x9F,0x8F,0x83,0x81,0xC0,0x7F
},{
//Diamond
0xFE,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xC0,0xF0,0xF8,0xFC,0xFC,0xF8,0xF0,0xC0,0x80,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x81,0x83,0x8F,0x9F,0xBF,0xBF,0x9F,0x8F,0x83,0x81,0xC0,0x7F
},{
//Spade
0xFE,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0xF0,0xF0,0xFC,0xFC,0xF0,0xF0,0xC0,0xC0,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x83,0xB3,0xB3,0xBF,0xBF,0xB3,0xB3,0x83,0x80,0xC0,0x7F
},{
//Club
0xFE,0x03,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x03,0xFE,
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0xC8,0x9C,0xFC,0xFC,0x9C,0xC8,0xE0,0xC0,0x00,0xFF,
0x7F,0xC0,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x83,0x87,0xB3,0xB1,0xBF,0xBF,0xB1,0xB3,0x87,0x83,0xC0,0x7F
}};
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"
#define DEBOUNCE_DELAY 10
//Repeat speed - (slow with no increase)
#define REPEAT_START_SPEED 1000
#define REPEAT_INCREASE_SPEED 0
#define REPEAT_MAX_SPEED 50
/*
//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50
*/
class Button
{
public:
//Simple constructor
Button(int pin);
Button(int name, int pin);
Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true);
//Background function called when in a wait or repeat loop
void Background(void (*pBackgroundFunction)());
//Repeat function called when button is pressed
void Repeat(void (*pRepeatFunction)());
//Test if button is pressed
bool IsDown(void);
//Test whether button is pressed and released
//Will call repeat function if one is provided
bool Pressed();
//Return button state (HIGH or LOW) - LOW = Pressed
int State();
//Return length of time button was held down
long DownTime();
//Return button name
int Name();
private:
int _name;
int _pin;
bool _range;
int _low;
int _high;
long _downTime;
bool _activeLow;
void (*_repeatCallback)(void);
void (*_backgroundCallback)(void);
};
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"
Button::Button(int pin)
{
_name = pin;
_pin = pin;
_range = false;
_low = 0;
_high = 0;
_backgroundCallback = NULL;
_repeatCallback = NULL;
pinMode(_pin, INPUT_PULLUP);
}
Button::Button(int name, int pin)
{
_name = name;
_pin = pin;
_range = false;
_low = 0;
_high = 0;
_backgroundCallback = NULL;
_repeatCallback = NULL;
pinMode(_pin, INPUT_PULLUP);
}
Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow)
{
_name = name;
_pin = pin;
_range = true;
_low = analogLow;
_high = analogHigh;
_activeLow = activeLow;
_backgroundCallback = NULL;
_repeatCallback = NULL;
pinMode(_pin, INPUT);
}
//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
_backgroundCallback = pBackgroundFunction;
}
//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
_repeatCallback = pRepeatFunction;
}
bool Button::IsDown()
{
if (_range)
{
int value = analogRead(_pin);
return (value >= _low && value < _high);
}
else
{
return (digitalRead(_pin) == LOW);
}
}
//Tests if a button is pressed and released
// returns true if the button was pressed and released
// if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
bool pressed = false;
if (IsDown())
{
unsigned long wait = millis() + DEBOUNCE_DELAY;
while (millis() < wait)
{
if (_backgroundCallback != NULL)
{
_backgroundCallback();
}
}
if (IsDown())
{
//Set up for repeat loop
if (_repeatCallback != NULL)
{
_repeatCallback();
}
unsigned long speed = REPEAT_START_SPEED;
unsigned long time = millis() + speed;
unsigned long start = millis();
while (IsDown())
{
if (_backgroundCallback != NULL)
{
_backgroundCallback();
}
if (_repeatCallback != NULL && millis() >= time)
{
_repeatCallback();
unsigned long faster = speed - REPEAT_INCREASE_SPEED;
if (faster >= REPEAT_MAX_SPEED)
{
speed = faster;
}
time = millis() + speed;
}
}
_downTime = millis() - start;
pressed = true;
}
}
return pressed;
}
//Return current button state
int Button::State()
{
if (_range)
{
int value = analogRead(_pin);
if (_activeLow)
{
return (value >= _low && value < _high) ? LOW : HIGH;
}
else
{
return (value >= _low && value < _high) ? HIGH : LOW;
}
}
else
{
return digitalRead(_pin);
}
}
//Return time button was down
long Button::DownTime()
{
return _downTime;
}
//Return current button name
int Button::Name()
{
return _name;
}
/*--------------------------------------------------------
* I2C SSD1306 128x64 OLED support routines for ATtiny1614
*
* @created: 2020-05-12
* @author: John Bradnam
*
* Base on ATTiny85 source code available at: https://bitbucket.org/tinusaur/ssd1306xled
*/
#ifndef SSD1306_ATtiny1614_h
#define SSD1306_ATtiny1614_h
#include <avr/sleep.h>
//---------------------- ATtiny1614 ----------------------
#define SSD1306_SCL PIN0_bm // ----> [SCL] Pin 3 on the SSD1306 display board
#define SSD1306_SDA PIN1_bm // ----> [SDA] Pin 4 on the SSD1306 display board
#define SSD1306_SA 0x78 // Slave address
#define DIGITAL_WRITE_HIGH(PORT) PORTB.OUTSET = PORT
#define DIGITAL_WRITE_LOW(PORT) PORTB.OUTCLR = PORT
//----------------- Forward references -------------------
void ssd1306_init(void);
void ssd1306_xfer_start(void);
void ssd1306_xfer_stop(void);
void ssd1306_send_byte(uint8_t byte);
void ssd1306_send_command(uint8_t command);
void ssd1306_send_data_start(void);
void ssd1306_send_data_stop(void);
void ssd1306_setpos(uint8_t x, uint8_t y);
void ssd1306_fillscreen(uint8_t fill_Data);
void ssd1306_draw_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t bitmap[], bool invert);
void ssd1306_draw_merge_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t bitmap1[], const uint8_t bitmap2[], bool invert);
#ifdef INCLUDE_TEXT_FUNCTIONS
void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]);
void ssd1306_char_font6x8(char ch) ;
void ssd1306_string_font6x8(char *s);
void ssd1306_char_f8x16(uint8_t x, uint8_t y, const char ch[]);
#endif
//Extra routines commonly used in ATtiny Arcade Games
void ssd1306_beep(uint8_t pin, int bCount, int bDelay);
void ssd1306_sleep();
//----------------- Functions -------------------
// Some code based on "IIC_wtihout_ACK" by http://www.14blog.com/archives/1358
const uint8_t ssd1306_init_sequence [] PROGMEM = { // Initialization Sequence
0xAE, // Display OFF (sleep mode)
0x20, 0b00, // Set Memory Addressing Mode
// 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
// 10=Page Addressing Mode (RESET); 11=Invalid
0xB0, // Set Page Start Address for Page Addressing Mode, 0-7
0xC8, // Set COM Output Scan Direction
0x00, // ---set low column address
0x10, // ---set high column address
0x40, // --set start line address
0x81, 0x3F, // Set contrast control register
0xA1, // Set Segment Re-map. A0=address mapped; A1=address 127 mapped.
0xA6, // Set display mode. A6=Normal; A7=Inverse
0xA8, 0x3F, // Set multiplex ratio(1 to 64)
0xA4, // Output RAM to Display
// 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
0xD3, 0x00, // Set display offset. 00 = no offset
0xD5, // --set display clock divide ratio/oscillator frequency
0xF0, // --set divide ratio
0xD9, 0x22, // Set pre-charge period
0xDA, 0x12, // Set com pins hardware configuration
0xDB, // --set vcomh
0x20, // 0x20,0.77xVcc
0x8D, 0x14, // Set DC-DC enable
0xAF // Display ON in normal mode
};
void ssd1306_init(void)
{
PORTB.DIRSET = SSD1306_SDA; // Set port as output
PORTB.DIRSET = SSD1306_SCL; // Set port as output
for (uint8_t i = 0; i < sizeof (ssd1306_init_sequence); i++)
{
ssd1306_send_command(pgm_read_byte(&ssd1306_init_sequence[i]));
}
}
void ssd1306_xfer_start(void)
{
DIGITAL_WRITE_HIGH(SSD1306_SCL); // Set to HIGH
DIGITAL_WRITE_HIGH(SSD1306_SDA); // Set to HIGH
DIGITAL_WRITE_LOW(SSD1306_SDA); // Set to LOW
DIGITAL_WRITE_LOW(SSD1306_SCL); // Set to LOW
}
void ssd1306_xfer_stop(void)
{
DIGITAL_WRITE_LOW(SSD1306_SCL); // Set to LOW
DIGITAL_WRITE_LOW(SSD1306_SDA); // Set to LOW
DIGITAL_WRITE_HIGH(SSD1306_SCL); // Set to HIGH
DIGITAL_WRITE_HIGH(SSD1306_SDA); // Set to HIGH
}
void ssd1306_send_byte(uint8_t byte)
{
uint8_t i;
for(i=0; i<8; i++)
{
if((byte << i) & 0x80)
DIGITAL_WRITE_HIGH(SSD1306_SDA);
else
DIGITAL_WRITE_LOW(SSD1306_SDA);
DIGITAL_WRITE_HIGH(SSD1306_SCL);
DIGITAL_WRITE_LOW(SSD1306_SCL);
}
DIGITAL_WRITE_HIGH(SSD1306_SDA);
DIGITAL_WRITE_HIGH(SSD1306_SCL);
DIGITAL_WRITE_LOW(SSD1306_SCL);
}
void ssd1306_send_command(uint8_t command)
{
ssd1306_xfer_start();
ssd1306_send_byte(SSD1306_SA); // Slave address, SA0=0
ssd1306_send_byte(0x00); // write command
ssd1306_send_byte(command);
ssd1306_xfer_stop();
}
void ssd1306_send_data_start(void)
{
ssd1306_xfer_start();
ssd1306_send_byte(SSD1306_SA);
ssd1306_send_byte(0x40); //write data
}
void ssd1306_send_data_stop(void)
{
ssd1306_xfer_stop();
}
void ssd1306_setpos(uint8_t x, uint8_t y)
{
//X = 0..127, Y = 0..7
ssd1306_xfer_start();
ssd1306_send_byte(SSD1306_SA); //Slave address,SA0=0
ssd1306_send_byte(0x00); //write command
ssd1306_send_byte(0xb0+y); //Set Page Start Address for Page Addressing Mode 10110xxx
ssd1306_send_byte(((x&0xf0)>>4)|0x10); //Set Lower Column Start Address for Page Addressing Mode 0000xxxx
ssd1306_send_byte((x&0x0f)|0x01); //Set Higher Column Start Address for Page Addressing Mode 0001xxxx
ssd1306_xfer_stop();
}
void ssd1306_fillscreen(uint8_t fill_Data)
{
uint8_t m,n;
for(m=0;m<8;m++)
{
ssd1306_send_command(0xb0+m); //Set Page Start Address for Page Addressing Mode 10110xxx
ssd1306_send_command(0x00); //Set Lower Column Start Address for Page Addressing Mode 0000xxxx
ssd1306_send_command(0x10); //Set Higher Column Start Address for Page Addressing Mode 0001xxxx
ssd1306_send_data_start();
for(n=0;n<128;n++)
{
ssd1306_send_byte(fill_Data);
}
ssd1306_send_data_stop();
}
}
void ssd1306_draw_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t bitmap[], bool invert)
{
uint8_t x0 = x;
uint8_t x1 = x + w;
uint8_t y0 = y;
uint8_t y1 = y + h;
uint8_t mask = (invert) ? 0xFF : 0x00;
uint16_t j = 0;
for (y = y0; y < y1; y++)
{
ssd1306_setpos(min(max(x0,0),127),min(max(y,0),63));
ssd1306_send_data_start();
for (x = x0; x < x1; x++)
{
ssd1306_send_byte(pgm_read_byte(&bitmap[j++]) ^ mask);
}
ssd1306_send_data_stop();
}
}
void ssd1306_draw_merge_bmp(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t bitmap1[], const uint8_t bitmap2[], bool invert)
{
uint8_t x0 = x;
uint8_t x1 = x + w;
uint8_t y0 = y;
uint8_t y1 = y + h;
uint8_t b;
uint8_t mask = (invert) ? 0xFF : 0x00;
uint16_t j = 0;
for (y = y0; y < y1; y++)
{
ssd1306_setpos(min(max(x0,0),127),min(max(y,0),63));
ssd1306_send_data_start();
for (x = x0; x < x1; x++)
{
b = pgm_read_byte(&bitmap1[j]);
b |= pgm_read_byte(&bitmap2[j++]);
ssd1306_send_byte(b ^ mask);
}
ssd1306_send_data_stop();
}
}
// ssd1306_draw_bmp_rect(0, 0, 128, 8, img1_128x64c1);
void ssd1306_draw_bmp_rect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[])
{
uint16_t j = 0;
uint8_t y, x;
if (y1 % 8 == 0)
y = y1 / 8;
else
y = y1 / 8 + 1;
for (y = y0; y < y1; y++)
{
ssd1306_setpos(x0,y);
ssd1306_send_data_start();
for (x = x0; x < x1; x++)
{
ssd1306_send_byte(pgm_read_byte(&bitmap[j++]));
}
ssd1306_send_data_stop();
}
}
#ifdef INCLUDE_TEXT_FUNCTIONS
void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[])
{
uint8_t c,i,j=0;
while(ch[j] != '\0')
{
c = ch[j] - 32;
/*
if (c >0) c = c - 12;
if (c >15) c = c - 6;
if (c>40) c=c-9;
*/
if(x>126)
{
x=0;
y++;
}
ssd1306_setpos(x,y);
ssd1306_send_data_start();
for(i=0;i<6;i++)
{
ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c*6+i]));
}
ssd1306_send_data_stop();
x += 6;
j++;
}
}
void ssd1306_char_font6x8(char ch)
{
uint8_t i;
uint8_t c = ch - 32;
ssd1306_send_data_start();
for (i= 0; i < 6; i++)
{
ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c * 6 + i]));
}
ssd1306_send_data_stop();
}
void ssd1306_string_font6x8(char *s)
{
while (*s)
{
ssd1306_char_font6x8(*s++);
}
}
#ifdef ssd1306xled_font8x16
void ssd1306_char_f8x16(uint8_t x, uint8_t y, const char ch[])
{
uint8_t c, j, i = 0;
while (ch[j] != '\0')
{
c = ch[j] - 32;
if (x > 120)
{
x = 0;
y++;
}
ssd1306_setpos(x, y);
ssd1306_send_data_start();
for (i = 0; i < 8; i++)
{
ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font8x16[c * 16 + i]));
}
ssd1306_send_data_stop();
ssd1306_setpos(x, y + 1);
ssd1306_send_data_start();
for (i = 0; i < 8; i++)
{
ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font8x16[c * 16 + i + 8]));
}
ssd1306_send_data_stop();
x += 8;
j++;
}
}
#endif
#endif
//--------------------- Extra -------------------------------
//Shut down OLED and put ATtiny to sleep
//Will wake up when LEFT button is pressed
void ssd1306_sleep()
{
ssd1306_fillscreen(0x00);
ssd1306_send_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
ssd1306_send_command(0xAF);
}
#endif
Comments