Adding interrupts on an lcd game

Hello,

I have an lcd ping pong game made.
However, my lecturer said that I should add something with asynchronous programming and interrupts. He suggested making a timer.

So I am considering adding a timer to my code so that the round would automatically end if it goes on for more than i.e. 60 seconds.

However, I have no idea how to do this.
Could anyone tell me how timers work and how should I go about adding one to my game?

If needed here is my code for reference:

#include <LiquidCrystal.h>
#include <math.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int px = 0, py = 0, ex = 15, ey = 0;
double bx = 8, by = 0.5, bVelX, bVelY;
int player = 0, bot = 0, gameCount = 0;
bool game;

byte ballMid[2][8] = 
{
  {
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B01110,
    B10001
  },
  {
    B10001,
    B01110,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000
  }
};

void setup() 
{
  lcd.begin(16, 2);
  lcd.createChar(5, ballMid[0]);
  lcd.createChar(6, ballMid[1]);
  pinMode(6, INPUT);

  randomSeed(analogRead(0));
  
  bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  bVelY = random(10, 30) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  
  Serial.begin(9600);
}

void loop() 
{
  game = update();
  
  lcd.clear(); 
  render();
  
  if (!game || gameCount <= 0)
  {
    delay(gameCount <= 0 ? 3000: 1000);
    gameCount++;
  }
  
  delay(60);
}
 
bool update()
{
  py = digitalRead(6);
  ey = (bx > 6 && random(0, 7) == 0 ? by : ey);
  
  if (bx >= 15 || bx <= 0)
  {
    if ((bx <= 0 && (int) (by = round(by)) == py) || (bx > 15 && (int) by == ey))
    {
      bVelX = random(40, 60) / 100.0 * -sign(bVelX);
    }
    else
    {
      if (bx < 8)
        bot++;
      else
        player++;
      
      bx = random(6, 9);
      by = random(0, 2);
      bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
      bVelY = random(10, 30) / 100.0 * -sign(bVelY);
      return false;
    }
  }
  if (by > 2 || by < 0)
    bVelY = random(10, 30) / 100.0 * -sign(bVelY);
  
  bx += bVelX;
  if (bx > 2 && bx < 14)
    by += bVelY;
  else
    by = round(clamp(by, 0, 1));
  
  return true;
}

void render()
{
  if (game && gameCount > 0)
  {
    lcd.setCursor(6, 0);
    lcd.print(player);
    lcd.print("/");
    lcd.print(bot);
  }
  
  if (by < 0.8 && by > 0.2)
  {
    lcd.setCursor(bx, 0);
    lcd.write(5);
    lcd.setCursor(bx, 1);
    lcd.write(6);
  }
  else
  {
    lcd.setCursor(bx, by);
    lcd.print("o");
  }
  
  lcd.setCursor(px, py);
  lcd.print("|");
  
  lcd.setCursor(ex, ey);
  lcd.print("|");
}

double clamp(double val, double min, double max)
{
  return val > max ? max : val < min ? min : val;
}

double sign(double val) {
  if (val < 0) 
    return -1;
  else if (val==0) 
    return 0;
  return 1;
}

just google "Arduino Timers Tutorial" and you'll get plenty of options and there are also libraries (eg TimerOne - Arduino Reference or TimerTwo) that can make your life easier

Hello.

I have a ping pong game and I am trying to add a 60 second timer to it.
So that if the round is not finished within 60 seconds it would end automatically.

I have setup the timer but I can't figure out how to check for the interrupt mid game (within the loop function).

I am adding my whole code but the main part is until the loop function.

Thank you for your help!

#include <LiquidCrystal.h>
#include <math.h>
volatile int i=0;

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int px = 0, py = 0, ex = 15, ey = 0;
double bx = 8, by = 0.5, bVelX, bVelY;
int player = 0, bot = 0, gameCount = 0;
bool game;

byte ballMid[2][8] = 
{
  {
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B01110,
    B10001
  },
  {
    B10001,
    B01110,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000
  }
};

void setup() 
{
  lcd.begin(16, 2);
  lcd.createChar(5, ballMid[0]);
  lcd.createChar(6, ballMid[1]);
  pinMode(6, INPUT);

  //timer1 for overflow interrupt
  noInterrupts();
  TCCR1A = 0;             //initialize timer1
  TCCR1B = 0;
  TCCR1B |= 0b00000100;   //256 prescaler
  TIMSK1 |= 0b00000001;   //enable overflow interrupt
  interrupts();

  randomSeed(analogRead(0));
  
  bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  bVelY = random(10, 30) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  
  Serial.begin(9600);
}

ISR(TIMER1_OVF_vect)  //timer overflow ISR: update counter
{
  i++; if(i==60) i=0;
}

void loop() 
{
  game = update();
  
  lcd.clear(); 
  render();
  
  if (!game || gameCount <= 0)
  {
    delay(gameCount <= 0 ? 3000: 1000);
    gameCount++;
  }
  
  delay(60);
}
 
bool update()
{
  py = digitalRead(6);
  ey = (bx > 6 && random(0, 7) == 0 ? by : ey);
  
  if (bx >= 15 || bx <= 0)
  {
    if ((bx <= 0 && (int) (by = round(by)) == py) || (bx > 15 && (int) by == ey))
    {
      bVelX = random(40, 60) / 100.0 * -sign(bVelX);
    }
    else
    {
      if (bx < 8)
        bot++;
      else
        player++;
      
      bx = random(6, 9);
      by = random(0, 2);
      bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
      bVelY = random(10, 30) / 100.0 * -sign(bVelY);
      return false;
    }
  }
  if (by > 2 || by < 0)
    bVelY = random(10, 30) / 100.0 * -sign(bVelY);
  
  bx += bVelX;
  if (bx > 2 && bx < 14)
    by += bVelY;
  else
    by = round(clamp(by, 0, 1));
  
  return true;
}

void render()
{
  if (game && gameCount > 0)
  {
    lcd.setCursor(6, 0);
    lcd.print(player);
    lcd.print("/");
    lcd.print(bot);
  }
  
  if (by < 0.8 && by > 0.2)
  {
    lcd.setCursor(bx, 0);
    lcd.write(5);
    lcd.setCursor(bx, 1);
    lcd.write(6);
  }
  else
  {
    lcd.setCursor(bx, by);
    lcd.print("o");
  }
  
  lcd.setCursor(px, py);
  lcd.print("|");
  
  lcd.setCursor(ex, ey);
  lcd.print("|");
}

double clamp(double val, double min, double max)
{
  return val > max ? max : val < min ? min : val;
}

double sign(double val) {
  if (val < 0) 
    return -1;
  else if (val==0) 
    return 0;
  return 1;
}

Use millis() to track start time and current time.

I cannot see the value of "i" being used anywhere. If your purpose with the timer is to count time without using millis, then you should have another "volatile int seconds" which is incremented each time "i" is zeroed. In loop() you can set "seconds" to zero to reset the timer or check if it reaches 60.

You should also mind that "i" will increment 16Mhz / 256 = 62500 times per second. You will need an "unsigned int" in order to manage this:

volatile uint16_t counter = 0;
volatile uint16_t seconds = 0;

ISR(...)
{
  if (++counter == 62500)
  {
    counter = 0;
    seconds++;
  }
}

loop()
{
  if (something_happened) seconds = 0;
  else if (seconds >= 60) reset_game();
}

@quinapr1l0 ,

TOPIC MERGED.

Please do not post multiple questions on the same subject.

Could you take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum.

Thank you.

I tried to implement this but it didn't seem to be working.

So I tried to print out the seconds in the serial monitor and it just constantly shows
0000000000000000000000000

What is the issue here?

Updated code:

#include <LiquidCrystal.h>
#include <math.h>
volatile int i=0;
volatile uint16_t counter = 0;
volatile uint16_t seconds = 0;

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int px = 0, py = 0, ex = 15, ey = 0;
double bx = 8, by = 0.5, bVelX, bVelY;
int player = 0, bot = 0, gameCount = 0;
bool game;

byte ballMid[2][8] = 
{
  {
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B01110,
    B10001
  },
  {
    B10001,
    B01110,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000
  }
};

void setup() 
{
  lcd.begin(16, 2);
  lcd.createChar(5, ballMid[0]);
  lcd.createChar(6, ballMid[1]);
  pinMode(6, INPUT);

  //timer1 for overflow interrupt
  noInterrupts();
  TCCR1A = 0;             //initialize timer1
  TCCR1B = 0;
  TCCR1B |= 0b00000100;   //256 prescaler
  TIMSK1 |= 0b00000001;   //enable overflow interrupt
  interrupts();

  randomSeed(analogRead(0));
  
  bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  bVelY = random(10, 30) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
  
  Serial.begin(9600);
}

ISR(TIMER1_OVF_vect)  //timer overflow ISR: update counter
{
    counter++;
  if (counter == 62500)
  {
    counter = 0;
    seconds++;
  }
}

void loop() 
{
  Serial.print(seconds);
  game = update();
  lcd.clear(); 
  render();
  
  
  if (!game || gameCount <= 0)
  {
    delay(gameCount <= 0 ? 3000: 1000);
    gameCount++;
  }
  
  delay(60);
  
}
 
bool update()
{
  if (seconds == 2) {return false;}
  py = digitalRead(6);
  ey = (bx > 6 && random(0, 7) == 0 ? by : ey);
  
  if (bx >= 15 || bx <= 0)
  {
    if ((bx <= 0 && (int) (by = round(by)) == py) || (bx > 15 && (int) by == ey))
    {
      bVelX = random(40, 60) / 100.0 * -sign(bVelX);
    }
    else
    {
      if (bx < 8)
        bot++;
      else
        player++;
      
      bx = random(6, 9);
      by = random(0, 2);
      bVelX = random(30, 60) / 100.0 * (random(0, 2) == 0 ? 1 : -1);
      bVelY = random(10, 30) / 100.0 * -sign(bVelY);
      return false;
    }
  }
  if (by > 2 || by < 0)
    bVelY = random(10, 30) / 100.0 * -sign(bVelY);
  
  bx += bVelX;
  if (bx > 2 && bx < 14)
    by += bVelY;
  else
    by = round(clamp(by, 0, 1));
  
  return true;
}

void render()
{
  if (game && gameCount > 0)
  {
    lcd.setCursor(6, 0);
    lcd.print(player);
    lcd.print("/");
    lcd.print(bot);
  }
  
  if (by < 0.8 && by > 0.2)
  {
    lcd.setCursor(bx, 0);
    lcd.write(5);
    lcd.setCursor(bx, 1);
    lcd.write(6);
  }
  else
  {
    lcd.setCursor(bx, by);
    lcd.print("o");
  }
  
  lcd.setCursor(px, py);
  lcd.print("|");
  
  lcd.setCursor(ex, ey);
  lcd.print("|");
}

double clamp(double val, double min, double max)
{
  return val > max ? max : val < min ? min : val;
}

double sign(double val) {
  if (val < 0) 
    return -1;
  else if (val==0) 
    return 0;
  return 1;
}

What is the issue here?

You followed wrong advice, and your understanding of timers is not very good.

TCCR1A = 0; //initialize timer1
  TCCR1B = 0;
  TCCR1B |= 0b00000100;   //256 prescaler
  TIMSK1 |= 0b00000001;   //enable overflow interrupt

With this setup, how frequently do you think the overflow interrupt will trigger?
How many overflow counts will it take to get to 60 seconds?

Which isr() seems more appropriate for using that count?

ISR(TIMER1_OVF_vect)  //timer overflow ISR: update counter
{
  i++; if(i==60) i=0;
}

ISR(TIMER1_OVF_vect)  //timer overflow ISR: update counter
{
    counter++;
  if (counter == 62500)
  {
    counter = 0;
    seconds++;
  }
}

Well not sure if I was following the correct advice before.

But I was watching a video and the mentioned that with 256 prescaler an overflow will happen every second. Is that correct?

Not sure how much the counter should be incremented by for that tho.
Or would it be correct to assume that if the overflow happens every second then I should simply do seconds++ in isr() ?

A problem with a basic overflow interrupt in WG mode 0 is that it occurs at count 65536. So even if you slow the timer clock via prescaling by 256 to 62.5kHz an overflow interrupt is going to occur once every 1.0486-seconds.

I think you'd be better served to take advantage of the many modes the AVR timers have; for example, using WGM=15 you can set up the timer with what amounts to a custom overflow value. You can set your prescaler to give a clock rate of 62.5kHz and set the overflow to happen after 62500 counts using the output compare register. An example using a gutted version of your code:

volatile uint16_t seconds = 0;

void setup() 
{
    //timer1 for compareA
    TCCR1A = _BV(WGM11) | _BV(WGM10);             //initialize timer1 FastPWM WGM=15
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS12 );   //256 prescaler
    OCR1A = 62499;                                //OCR1A after 62500 ticks at 16MHz/256 ticks/sec  
    TIMSK1 |= _BV(OCIE1A);   //enable OCR1A
    
    pinMode( LED_BUILTIN, OUTPUT );
    digitalWrite( LED_BUILTIN, LOW );  
    Serial.begin(9600);
  
}//setup

ISR(TIMER1_COMPA_vect)  //timer overflow ISR: update counter
{
    digitalWrite( LED_BUILTIN, digitalRead( LED_BUILTIN ) ^ HIGH );
    seconds++;      
    
}//ISR - Timer1 compare A

void loop() 
{
    uint16_t
        nowSec;
    static uint16_t
        lastSec = 0xffff;

    noInterrupts();
    nowSec = seconds;
    interrupts();
    if( nowSec != lastSec )
    {
        Serial.println(nowSec);
        lastSec = nowSec;
        
    }//if        
  
}//loop
 

Note also the use of a "critical section" around reading the seconds counter in loop. Because the seconds counter is 2-bytes long you're not assured an interrupt won't happen in between reading the first and second bytes; disabling interrupts briefly to make the read prevents this.

Also, properly crafted code won't have any delay() calls unless for very specific reasons. Try to think of ways to code using millis() (e.g.) to prevent blocking the processor.

I think I am on to something here.

I just tried to do it with this isr

ISR(TIMER1_OVF_vect)  //timer overflow ISR: update counter
{
  i++; if(i==60) i=0;
}

Now it seems to be counting the seconds sort of correct at least in the serial monitor.
Going to try to implement this to my game now and see what happens