/*
* FourInARow.c
*
* Created: 2019-04-09 12:44:38
* Author : fe4242bo-s
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "Components/Display/Display.h"
#include "Components/NeoPixels/NeoPixel.h"
#include "GameManager.h"
//#include "Constants/GameStrings.h"
/*
NOTES TO PROGRAMMER!
- Hardcoded literals can be found in Constants/...
- When passing a char array into Display with SendString(...) you can add Q{command} for special operations.
- Some of these special operations are: Qn (jump to new line on LCD), Qt (display countdown timer on LCD), #CONSTANT padding CONSTANT amount in text.
- Maximum #n command is ONCE (obviously)
- Maximum #t command is ONCE Do not support more than 1 timer at the moment.
- Game buttons connected to --> 14,15,16,17
*/
extern void Reset();
extern void Send_One();
extern void Send_Zero();
int pressed = 0;
int currentLight = 0;
ISR(PCINT0_vect) {
clearPixels();
setPixel(currentLight,10,10,10);
if (PINA & (1 << PINA0)) {
if (pressed) {
pressed = 0;
}
else {
pressed = 1;
if(currentLight == 14) {
UpdatePoints();
} else {
EndGame();
}
}
}
PCIFR |= PCIF0; // Do NOT remove! Will make sure we trigger button interrupt only once!
}
int main(void)
{
cli();
LCD_init();
InitializeGame();
PCICR |= 1 << PCIE0;
sei();
PCMSK0 |= 1 << PCINT0;
// NEOPIXELS AFTER THIS
DDRD = 1 << PORTD7;
PORTD &= ~(1 << PORTD7);
while (1)
{
for(int i = currentLight; i<16 ;i++){
if (pressed) break;
currentLight = i;
setPixel(i, diffRValue(),diffGValue(),0);
difficultyDelay();
setPixel(i,0,0,0);
if (pressed) setPixel(i,10,10,10); // Fixes the case where the pixel is set to zero if a button press is faster than the diffDelay.
}
if (!pressed) currentLight = 0;
}
}
/*
* GameManager.c
*
* Created: 2019-05-08 13:26:03
* Author: fe4242bo-s
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <stdint-gcc.h>
#include <util/delay.h>
#include "Components/Display/Display.h"
uint32_t points = 0;
uint32_t highscore = 0;
difficulty = 1;
/** Initializes game and expects that display is already initialized. Make sure to initialize display before game initialization. */
void InitializeGame() {
points = 0;
highscore = ReadFromEEPROM(0xFF);
updateText();
}
/** Write data to permanent memory. */
void WriteToEEPROM(unsigned int uiAddress, unsigned char ucData) {
while(EECR & (1<<EEPE));
EEAR = uiAddress;
EEDR = ucData;
EECR |= (1<<EEMPE);
EECR |= (1<<EEPE);
}
/** Read data from permanent memory. */
void ReadFromEEPROM(unsigned int uiAddress) {
while(EECR & (1<<EEPE));
EEAR = uiAddress;
EECR |= (1<<EERE);
return EEDR;
}
/** Will reset the game back to it's initial state. */
void ResetGame() {
points = 0;
difficulty = 1;
updateText();
}
/** Updates the points on the LCD display. Will increment points. */
void UpdatePoints() {
points = (points + 1);
if(points>highscore){
highscore = points;
WriteToEEPROM(0xFF,highscore);
}
if(points%5==0 && difficulty != 7){difficulty++;}
updateText();
}
/** Internal function that will do the updating of the text. */
void updateText(){
ClearDisplay();
char txt09[1+9]="";
char txt10[2+9]="";
if (points<10){
sprintf(txt09,"Score: %dQn",points);
SendString(txt09,1+9);
}
else{
sprintf(txt10,"Score: %dQn",points);
SendString(txt10,2+9);
}
txt09[1+11]="";
txt10[2+11]="";
if (highscore<10){
sprintf(txt09,"Hi-Score: %d",highscore);
SendString(txt09,1+10);
}
else{
sprintf(txt10,"Hi-Score: %d",highscore);
SendString(txt10,2+10);
}
}
/** Delay that is based on difficulty. Meaning that the use of this during a neopixel animation will make the neopixels spin faster the harder it gets! */
void difficultyDelay(){
for (int i = 8;i>difficulty;i--)
{
_delay_ms(40);
}
}
/** R portion of the RGB value based on difficulty. */
uint8_t diffRValue(){
uint8_t r = 0;
for (int i = 0; i < difficulty; i++)
{
if ((r == 100)) return r;
r += 10;
}
return r;
}
/** G portion of the RGB value based on difficulty. */
uint8_t diffGValue(){
uint8_t g = 80;
for (int i = 0; i < difficulty; i++)
{
if ((g == 0)) return g;
g -= 10;
}
return g;
}
/** Ends the game. Updates High score and resets the game. */
void EndGame() {
if(points>highscore){
highscore = points;
}
ResetGame();
}
/*
* Display.h
*
* Created: 2019-04-09 12:46:33
* Author: fe4242bo-s
*/
#ifndef DISPLAY_H_
#define DISPLAY_H_
// Comments for the subroutines can be found in their .c file.
extern void SendCommand(unsigned char cmd);
extern void SendChar(unsigned char data);
extern void SendString(unsigned char data[], int size);
extern void ClearDisplay();
void SkipToNextRow(int size, int atIndex);
void SendError();
void DisplayRoundTimer(char data[], int atIndex , int size); // Don't know the size of array, so we pass it around instead.
uint8_t ExtractEscapeCommand(char data[], int atIndex, int size);
extern void init_LED();
void EnableTimerInterrupt();
extern void EditLCDText(uint8_t index, char newData[], int size);
#endif /* DISPLAY_H_ */
/*
* Display.c
*
* Created: 2019-04-09 12:47:28
* Author: fe4242bo-s
*/
#define F_CPU 16000000
#define REGISTER_SELECT 7
#define READ_WRITE_SELECT 6
#define ENABLE_SELECT 1
#define SECOND_ROW_FIRST_COLUMN_LCD_ADDRESS 40
#define INCREASE_ARRAY_BY_GAME_TIME_BITS 2
#define DDRAM_ADDRESS_COMMAND 7
#define LCD_SECOND_ROW_FIRST_ADDRESS 40
// DDRD == Data port
// DDRC == Control port
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
uint8_t newIndex = 0;
int timerCountActive = 0; // If it is active, the string currently shown should be updated.
int interruptsEnabled = 0;
int timerIndex = 0; // The index of where the last timer was shown
int timerTextSize = 0; // Size of the text that the timer is shown in
int timercount = 60;
int linebreakIndex = 0; // index where linebreak occurs
char timerCountStr[2];
/** Compare Match Timer interrupt used to get precise timings. This is used to send updates to the LCD at precisely at 1 update per second. */
ISR(TIMER1_COMPA_vect) {
timercount--;
if (timercount != 0) {
sprintf(timerCountStr, "%d", timercount);
EditLCDText(timerIndex,timerCountStr, 2);
}
if (timercount == 10) {
ClearDisplay();
SendString("Yay!",4);
}
}
/** Send command to the display (RS = 0) */
void SendCommand(char cmd) {
PORTB = cmd;
PORTC &= ~(1<<REGISTER_SELECT);
PORTC &= ~(1<<READ_WRITE_SELECT);
PORTC |= (1<<ENABLE_SELECT);
_delay_us(1);
PORTC &= ~(1<<ENABLE_SELECT);
_delay_ms(1);
}
/** Send data to the display (RS = 1) */
void SendChar(unsigned char data) {
PORTB = data;
PORTC |= 1<<REGISTER_SELECT;
PORTC &= ~(1<<READ_WRITE_SELECT);
PORTC |= (1<<ENABLE_SELECT);
_delay_us(1);
PORTC &= ~(1<<ENABLE_SELECT);
_delay_us(50);
}
// Hops to row 2.
void SkipToNextRow(int size, int atIndex) {
SendCommand((1<<DDRAM_ADDRESS_COMMAND) + LCD_SECOND_ROW_FIRST_ADDRESS);
}
/** Displays the time for a round. */
void DisplayRoundTimer(char data[], int atIndex , int size) {
timerIndex = atIndex;
timerTextSize = size;
data[atIndex] = '6';
data[atIndex + 1] = '0';
timerCountActive = 1;
}
/** Clears all data from the LCD DDRAM. */
ClearDisplay() {
SendCommand(0b00000001); // TODO: Setup macros for binary commands.
}
/** Extracts the escape command and performs actions based on what command was sent in. Returns the next index of char to read once the action is done. */
uint8_t ExtractEscapeCommand(char data[], int atIndex, int size) {
if (data[atIndex + 1] == 'n') {
SkipToNextRow(size,atIndex);
linebreakIndex = atIndex;
return atIndex = atIndex + 2;
}
else if (data[atIndex + 1] == 't') {
DisplayRoundTimer(data,atIndex,size);
return atIndex - 1; // The escape character has been replaced by timer, write out new chars.
}
else { // if it isn't a letter then it's likely a number indicating the padding, NOTE: Padding should be a integer value between 0-9
if (isdigit(data[atIndex + 1])) {
// TODO: Program logic of padding.
} else {
}
}
return atIndex;
}
/** Sends a string to the LCD and displays it. */
void SendString(unsigned char data[], int size) {
if (timerCountActive) {
TCCR1B &= ~(1 << CS12);
timerCountActive = 0;
interruptsEnabled = 0;
}
for (int i = 0; i < size;i++)
{
if (data[i] == 'Q') {
newIndex = ExtractEscapeCommand(data,i,size);
i = newIndex;
continue;
}
PORTB = data[i];
PORTC |= 1<<REGISTER_SELECT;
PORTC &= ~(1<<READ_WRITE_SELECT);
PORTC |= (1<<ENABLE_SELECT);
_delay_us(1);
PORTC &= ~(1<<ENABLE_SELECT);
_delay_ms(1);
}
if (timerCountActive && !interruptsEnabled) {
EnableTimerInterrupt();
}
}
/** Changes a portion of the text displayed on the LCD at the specified index. */
void EditLCDText(uint8_t index, char newData[], int size) { // Second row is from 40 to 67
if (index > 16) {
SendCommand((1<<DDRAM_ADDRESS_COMMAND) + LCD_SECOND_ROW_FIRST_ADDRESS + (timerIndex - linebreakIndex) - 3); // -3 offset to skip escape chars.
} else {
// First row...
}
for (int i = 0; i < size; i++)
{
SendChar(newData[i]);
}
}
/** Enables timer interrupts. */
EnableTimerInterrupt() {
sei();
TCCR1B |= 1 << WGM12; // CTC select
TCCR1B |= 1 << CS12; // prescaler 256
TIMSK1 |= 1 << OCIE1A; // the Timer/Counter1 Output Compare A Match interrupt is enabled
OCR1A = (F_CPU/(1*2*256) - 1); // 0.48 is the time for calculation, calculated to achieve appx 1 interrupt per second. 16_000_000/(0.48*2*256) equal appx = 65_000/ 2^16 (16 bit timer)
interruptsEnabled = 1;
}
/** Initialize the display */
void LCD_init(void) {
DDRC = 0b11000010;
DDRB = 0xFF;
SendCommand(0b00111110); // Function set / 8 bit
SendCommand(0b00001110); // Display on
SendCommand(0b00000010); // Entry Mode // auto increase
//SendCommand(0b00000010); // return home
SendCommand(0b00000001); // Clear display
}
/*
* NeoPixelTiming.s
*
* Created: 2019-04-17 10:30:49
* Author: fe4242bo-s
*/
.global Reset
.global Send_One
.global Send_Zero
#define DATA_PORT 0x0B
#define DATA_DDR 0x0A
Reset:
nop x 396
ret
Send_One:
ldi r22, 0b10000000
out DATA_PORT, r22
nop
nop
nop
nop
ldi r22, 0b00000000 // 700/125 = 5.6 clock cycles
out DATA_PORT, r22
nop // 600/125 = 4.8 clock cycles
ret
Send_Zero:
ldi r18, 0b10000000
out 0X0B, r18
nop
ldi r18, 0
out 0X0B, r18
nop
nop
ret