/***************************************************
Name: RobDioramaTeensySDV25BETA Use for testing/flogging, then copy to new version
By:
Start Date: 29Apr21
Last Rev: 18Jul21
MCU: Teensy3.2
Hardware: Teensy3.2, Adafruit GLCD (TFT), Aideepen MP3 player module
Description: Using GLCD/TFT for hockey scoreboard for hockey diorama
GLCD code and GFX library from Adafruit
Modified timer code from Odometer
Touch screen (TFT) only used as screen, not touch-enabled
Vers History: V1.0 Start
V1.1 Added score rectangles, added credits
V1.2 Added pb for scores
V1.3 Added debounce library, used for pb switches and code to put scores in boxes
V2.0 Replaced Arduino UNO with Teensy 3.2. Using Adafruit, not Alan Senior library
Uses tft.print(buf) instead of tft.drawString(buf, x, y, size) which has been deprecated
V2.2 Added FE logo function.
(Added SD card to display bitmaps on screen, not working)
V2.3 Beta SD card displaying bitmaps on key press.
V2.4 Sound module and amplifier added
GLCD stopped working. Replaced with Adafruit GLCD with integral SD card
V2.5 Added pushbuttons to select function
Added interrupt service routine, using pin 5 ("score") to reset Teensy if in slideshow
Added Aideepen Sound module to play MP3 SFX through amp module
*/
/*******************************************************************/
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
GFXcanvas1 canvas(320,240);
//#include <ILI9341_t3.h>
//#include <font_ArialBlack.h>
#include <SPI.h>
#include <SD.h>
#include <ezButton.h>//debounce library
//#include <Fonts/Dialog_plain_16.h>//Custom font conversion at http://oleddisplay.squix.ch/
//#include <Fonts/Open_24_Display_St20pt7b.h>
#define TFT_DC 10
#define TFT_CS 15
#define TFT_RST 9
#define SD_CS 20 //chip select for SD card standard SPI pins MOSI 11, SCK 13 in library
/************Defines for Aideepen MP3 Sound Card***********************/
#include <SoftwareSerial.h>
#define Teensy_RX 0//connect to Tx of module
#define Teensy_TX 1//connect to Rx of module
SoftwareSerial mySerial(Teensy_RX, Teensy_TX);
static int8_t Send_buf[8] = {0} ;
#define CMD_SEL_DEV 0X09
#define DEV_TF 0X02
#define CMD_PLAY_W_VOL 0X22
// SFX stolen from the web, voice using free Text to Speech converter
#define charge 0x1702//SFX, next five
#define horn 0x1704
#define neverbehind 0x1701//not implemented
#define oneminute 0x1703
#define notallowed 0x1705//not implemented
int x = 0; //variable to update interrupt for scoreboard reset
ezButton canpb(2);//this is D2, not pin 2 - actually pin 4
ezButton oilpb(3);//D3, pin 5
ezButton slide(4);//D4, pin 6
ezButton score(5);//D5, pin 7//interrupt is on this pin. All digital pins on Teensy have interrupt capability
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
//ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC,TFT_RST);
//Countdown variables
int countDirection = -1; // direction of count: 1 for up, -1 for down, 0 for stopped
int nowMinutes=1; // "minutes" part of time
int nowSeconds=30; // "seconds" part of time
unsigned long millisNow = 0; // value of millis() at most recent 1/10 second
int setmin = nowMinutes;//needed for reset
int setsec = nowSeconds;
const unsigned long millisTick = 1000; // number of milliseconds per 1/10 second
int countCan = 0; //reset to zero every power cycle
int countOil = 0;
/***********************Setup***********************************/
void setup(void) {
Serial.begin(9600);//for troubleshooting
mySerial.begin(9600);//for MP3 play
delay(500);//Wait chip initialization is complete
sendCommand(CMD_SEL_DEV, DEV_TF);//select the TF card
delay(200);//wait for 200ms
attachInterrupt(5, Reset, FALLING);//this is used to reset Teensy using pin 5
canpb.setDebounceTime(50); // set debounce time to 50 milliseconds
oilpb.setDebounceTime(50);
canpb.setCountMode(COUNT_RISING);
oilpb.setCountMode(COUNT_RISING);
tft.begin();
if (!SD.begin(SD_CS))//check of SD card is available
{ Serial.println("failed!"); }
Serial.println("OK!");
tft.setRotation(3);//can't use with slideshow
Scoreboard(); //static text, run once, stay on.
sendCommand(CMD_PLAY_W_VOL, charge);
delay(4000);
//credits();//credits for the design, start by switch? Maybe existing PB, held down to activate
}
/*****************************Loop******************************/
void loop() {
canpb.loop(); // MUST call the loop() function first for debounce switches
oilpb.loop();
slide.loop();
score.loop();
//int canpbState = canpb.getState();
// int oilpbState = oilpb.getState();
int slideState = slide.getState();
int scoreState = score.getState();
if (slideState == LOW)//choose slideshow or scoreboard button
Slideshow();
else if (scoreState == LOW)
{ tft.setRotation(3);
Scoreboard();
}
int countCan = canpb.getCount();
int countOil = oilpb.getCount();
//if (countOil >= countCan)
//sendCommand(CMD_PLAY_W_VOL, notallowed);
//delay (3000);
// countOil=countOil--;
tft.setTextSize(4);
tft.setCursor(70, 105); //put score in Montreal box
tft.println(countCan);
tft.setCursor(240, 105); //put score in Oilers box
tft.println(countOil);
//Start Countdown
if ((millis() - millisNow) >= millisTick) {
nowSeconds += countDirection;
millisNow += millisTick;
// trade 60 seconds for 1 minute
if (nowSeconds >= 60) {
nowSeconds -= 60;//remove 60 seconds
nowMinutes++;//add one minute
}
// trade 1 minute for 60 seconds
if (nowSeconds < 0) {
nowMinutes--;//remove minute
nowSeconds += 60;
}//add 60 seconds
}
if ((nowMinutes == 1) && (nowSeconds == 0)) {
sendCommand(CMD_PLAY_W_VOL, oneminute);//"1 minute to play" SFX
}
//countdown is over
if ((nowMinutes == 0) && (nowSeconds == 0)) {
delay(100);
nowMinutes = setmin;
nowSeconds = setsec;
delay (500);
sendCommand(CMD_PLAY_W_VOL, horn);//time up, sound horn
delay(3000);
}
updateTimeDisplay();
}
/********************sendCommand function for MP3 sound card*****************/
void sendCommand(int8_t command, int16_t dat)
{ delay(20);
Send_buf[0] = 0x7e; //starting byte
Send_buf[1] = 0xff; //version
Send_buf[2] = 0x06; //the number of bytes of the command without starting byte and ending byte
Send_buf[3] = command; //
Send_buf[4] = 0x00;//0x00 = no feedback, 0x01 = feedback
Send_buf[5] = (int8_t)(dat >> 8);//datah
Send_buf[6] = (int8_t)(dat); //datal
Send_buf[7] = 0xef; //ending byte
for (uint8_t i = 0; i < 8; i++) //
{ mySerial.write(Send_buf[i]) ; }
}
/*********************ISR Reset**********************************/
void Reset() {
SCB_AIRCR = 0x05FA0004;//PB on pin D5 gets us here to reset MCU
}
/********************Function Scoreboard************************/
void Scoreboard() {//all static, run once at startup Note: was unsigned long instead of void
//tft.setFont(&Dialog_plain_16);//try different fonts
//tft.setFont (&Roboto_Medium_11);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(4);//def 4 for default font
tft.setCursor(0, 0); //x,y
tft.println(" FOUND FORUM");
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(3);//def 3 for default font
tft.setCursor(0, 40); //x,y
tft.println("Canadiens Oilers");
//tft.setFont();//reset font. Use when using non default fonts
//Draw rectangles for scores
tft.drawRect(50, 90, 70, 50, ILI9341_RED); //(x,y,w,h,color) Canadiens on left
tft.drawRect(220, 90, 70, 50, ILI9341_RED); //Oilers right
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
//tft.setTextSize(3);
//return 0;
}
/************************Function UpdateTimeDisplay****************/
void updateTimeDisplay () {
char buf[8];
//tft.setFont(&Open_24_Display_St20pt7b);
//tft.setFont (&DSEG7_Classic_Mini_Regular_18);//seven segment font not updated
//tft.setFont (&DSEG7_Modern_Regular_18);
//tft.setFont(ArialBlack_60);
tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK); //need black background or letters pile up
tft.setCursor(-20, 170); //x,y was -40,230 for open_24 font
tft.setTextSize(7);//def 7 for large regular font
sprintf(buf, " %02d:%02d", nowMinutes, nowSeconds);
tft.print(buf);
//tft.setTextSize(3);//reset for score
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);//reset for score
}
/*****************************Function Slideshow************************/
void Slideshow()
{ tft.fillScreen(ILI9341_BLACK);
bmpDraw("Goalie.bmp", 0, 0); //okay
delay (5000);
tft.fillScreen(ILI9341_BLACK);
bmpDraw("RobChris.bmp", 0, 0);//okay
delay (5000);
tft.fillScreen(ILI9341_BLACK);
bmpDraw("RobBob.bmp", 0, 0);//okay
delay (5000);
tft.fillScreen(ILI9341_BLACK);
tft.fillScreen(ILI9341_BLACK);
bmpDraw("FElogo.bmp", 0, 0);//okay
delay (5000);
tft.fillScreen(ILI9341_BLACK);
}
#define BUFFPIXEL 40 //increased from 20
/*********************Function: draw bitmaps on GLCD*************************/
void bmpDraw(char *filename, uint8_t x, uint16_t y) {
File bmpFile;
int bmpWidth, bmpHeight; // W+H in pixels
uint8_t bmpDepth; // Bit depth (currently must be 24)
uint32_t bmpImageoffset; // Start of image data in file
uint32_t rowSize; // Not always = bmpWidth; may have padding
uint8_t sdbuffer[3 * BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
boolean goodBmp = false; // Set to true on valid header parse
boolean flip = true; // BMP is stored bottom-to-top
int w, h, row, col;
uint8_t r, g, b;
uint32_t pos = 0, startTime = millis();
if ((x >= tft.width()) || (y >= tft.height())) return;
//All print commands display on monitor if connected. Not needed here but good for troubleshooting
Serial.println();
Serial.print(F("Loading image '"));
Serial.print(filename);
Serial.println('\'');
// Open requested file on SD card
if ((bmpFile = SD.open(filename)) == NULL) {
Serial.print(F("File not found"));
return;
}
// Parse BMP header
if (read16(bmpFile) == 0x4D42) { // BMP signature
Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
(void)read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
// Read DIB header
Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if (read16(bmpFile) == 1) { // # planes -- must be '1'
bmpDepth = read16(bmpFile); // bits per pixel
Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
Serial.print(F("Image size: "));
Serial.print(bmpWidth);
Serial.print('x');
Serial.println(bmpHeight);
// BMP rows are padded (if needed) to 4-byte boundary
rowSize = (bmpWidth * 3 + 3) & ~3;
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
// Crop area to be loaded
w = bmpWidth;
h = bmpHeight;
if ((x + w - 1) >= tft.width()) w = tft.width() - x;
if ((y + h - 1) >= tft.height()) h = tft.height() - y;
// Set TFT address window to clipped image bounds
tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
for (row = 0; row < h; row++) { // For each scanline...
if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
else // Bitmap is stored top-to-bottom
pos = bmpImageoffset + row * rowSize;
if (bmpFile.position() != pos) { // Need seek?
bmpFile.seek(pos);
buffidx = sizeof(sdbuffer); // Force buffer reload
}
for (col = 0; col < w; col++) { // For each pixel...
if (buffidx >= sizeof(sdbuffer)) { // Read more pixel data
bmpFile.read(sdbuffer, sizeof(sdbuffer));
buffidx = 0; // Set index to beginning
}
// Convert pixel from BMP to TFT format, push to display
b = sdbuffer[buffidx++];
g = sdbuffer[buffidx++];
r = sdbuffer[buffidx++];
tft.pushColor(tft.color565(r, g, b));
} // end pixel
} // end scanline
Serial.print(F("Loaded in "));
Serial.print(millis() - startTime);
Serial.println(" ms");
} // end goodBmp
}
}
bmpFile.close();
if (!goodBmp) Serial.println(F("BMP format not recognized."));
}
uint16_t read16(File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}