Category Archives: Physical Computing

Code

//Snake source: https://www.youtube.com/watch?v=IkTK6WMlMs4 & http://pastebin.com/yiDBzjvZ

#include <LiquidCrystal.h>
#include <Time.h>

// LCD variables
LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
int adc_key_val[5] = {50, 200, 400, 600, 800 };
int NUM_KEYS = 5;
int adc_key_in;
int key = -1;
int oldkey = -1;
int pressedKey = -1;

// project variables
int pinIn = 3;
int pinOut = 2;
int reading;
int previous = LOW;
long lastTime;
long checkTime = 100;
bool snakeMode = false;
bool clockMode = false;

// clock variables
bool settingClock, settingTime, settingDate;
bool settingHour, settingMinute;
bool settingDay, settingMonth, settingYear;
int h, m, s, d, n, y, dMax;

//snake variables
unsigned long time, timeNow;
int gameSpeed;
boolean skip, gameOver, gameStarted;
int olddir;
int selectedLevel, levels;

boolean x[16][80];
byte myChar[8];
byte nullChar[8] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
boolean special;

struct partdef
{
  int row, column, dir; //0 - up, 1 - down, 2 - right, 3 - left
  struct partdef *next;
};
typedef partdef part;

part *head, *tail;
int i, j, collected;
long pc, pr;

void startF()
{
  gameOver = false;
  gameStarted = false;
  selectedLevel = 1;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Select to Start");
  collected = 0;
  gameSpeed = 8;
  createSnake(3);
  time = 0;
}

void setup()
{
  pinMode(pinIn, INPUT_PULLUP);
  pinMode(pinOut, OUTPUT);
  Serial.begin(9600);
  lcd.begin(16, 2);
  setTime(12, 00, 00, 01, 01, 2016);
  levels = 5; //number of lvls
  checkMode();
}

void loop()
{
  while (snakeMode)
  {
    if (!gameOver && !gameStarted)
    {
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            olddir = head->dir;
            if (key == 1 && selectedLevel < levels) selectedLevel++;
            if (key == 2 && selectedLevel > 1) selectedLevel--;
            if (key == 4)
            {
              lcd.clear();
              selectedLevel--;
              newPoint();
              gameStarted = true;
            }
            else
            {
              lcd.setCursor(14, 0);
              lcd.print(selectedLevel);
            }
          }
        }
      }
    }
    if (!gameOver && gameStarted)
    {
      skip = false; //skip the second moveAll() function call if the first was made
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            olddir = head->dir;
            if (key == 0 && head->dir != 3) head->dir = 2;
            if (key == 1 && head->dir != 1) head->dir = 0;
            if (key == 2 && head->dir != 0) head->dir = 1;
            if (key == 3 && head->dir != 2) head->dir = 3;

            if (olddir != head->dir)
            {
              skip = true;
              delay(1000 / gameSpeed);
              moveAll();
              drawMatrix();
            }
          }
        }
      }
      if (!skip)
      {
        timeNow = millis();
        if (timeNow - time > 1000 / gameSpeed)
        {
          moveAll();
          drawMatrix();
          time = millis();
        }
      }
    }
    if (gameOver)
    {
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            startF();
          }
        }
      }
    }
    checkMode();
  }
  while (clockMode)
  {
    if (settingTime)
      setTime();
    else if (settingDate)
      setDate();
    else
      printTimeDate();

    readingInput();

    lcd.noBlink();
    checkMode();
  }
}

void checkMode()
{
  lcd.clear();
  reading = digitalRead(pinIn);
  Serial.println(reading);
  if (reading == 1)
  {
    snakeMode = true;
    clockMode = false;
    startF();
  }
  else if (reading == 0)
  {
    snakeMode = false;
    clockMode = true;
  }
}

bool isKeyPressed()
{
  if (clockMode)
  {
    adc_key_in = analogRead(0);    // read the value from the sensor
    key = get_key(adc_key_in);  // convert into key press
    if (key != oldkey)   // if keypress is detected
    {
      delay(50);  // wait for debounce time
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);    // convert into key press
      if (key != oldkey)
      {
        oldkey = key;
        return true;
      }
    }
    delay(100);
    return false;
  }
}

void printTimeDate()
{
  lcd.setCursor(4, 0);
  if (hour() < 10)
    lcd.print(0);
  lcd.print(hour());
  lcd.print(":");
  if (minute() < 10)
    lcd.print(0);
  lcd.print(minute());
  lcd.print(":");
  if (second() < 10)
    lcd.print(0);
  lcd.print(second());
  lcd.setCursor(1, 1);
  lcd.print(dayShortStr(weekday()));
  lcd.setCursor(5, 1);
  if (day() < 10)
    lcd.print(0);
  lcd.print(day());
  lcd.print("/");
  if (month() < 10)
    lcd.print(0);
  lcd.print(month());
  lcd.print("/");
  lcd.print(year());
}

void setTime()
{
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Set time:");
  lcd.setCursor(4, 1);
  if (h < 10)
    lcd.print(0);
  lcd.print(h);
  lcd.print(":");
  if (m < 10)
    lcd.print(0);
  lcd.print(m);
  lcd.print(":00");
  if (settingHour)
    lcd.setCursor(5, 1);
  else if (settingMinute)
    lcd.setCursor(8, 1);
  lcd.blink();
}

void setDate()
{
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Set date:");
  lcd.setCursor(3, 1);
  if (d < 10)
    lcd.print(0);
  lcd.print(d);
  lcd.print("/");
  if (n < 10)
    lcd.print(0);
  lcd.print(n);
  lcd.print("/");
  lcd.print(y);
  if (settingDay)
    lcd.setCursor(4, 1);
  else if (settingMonth)
    lcd.setCursor(7, 1);
  else if (settingYear)
    lcd.setCursor(12, 1);
  lcd.blink();
}

void readingInput()
{
  if (isKeyPressed())
  {
    if (key == 0) // right key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          settingMinute = true;
          settingHour = false;
        }
        else if (settingMinute)
        {
          settingHour = true;
          settingMinute = false;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          settingMonth = true;
          settingDay = false;
        }
        else if (settingMonth)
        {
          settingYear = true;
          settingMonth = false;
        }
        else if (settingYear)
        {
          settingDay = true;
          settingYear = false;
        }
      }
    }
    if (key == 1) // up key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          h++;
          if (h > 23)
            h = 0;
        }
        else if (settingMinute)
        {
          m++;
          if (m > 59)
            m = 0;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          d++;
          if (d > dMax)
            d = 1;
        }
        else if (settingMonth)
        {
          n++;
          if (n > 12)
            n = 1;
          updateMaxDay();
        }
        else if (settingYear)
        {
          y++;
          updateMaxDay();
        }
      }
    }
    if (key == 2) // down key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          h--;
          if (h < 0)
            h = 23;
        }
        else if (settingMinute)
        {
          m--;
          if (m < 0)
            m = 59;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          d--;
          if (d < 1)
            d = dMax;
        }
        else if (settingMonth)
        {
          n--;
          if (n < 1)
            n = 12;
          updateMaxDay();
        }
        else if (settingYear)
        {
          y--;
          updateMaxDay();
        }
      }
    }
    if (key == 3) // left key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          settingMinute = true;
          settingHour = false;
        }
        else if (settingMinute)
        {
          settingHour = true;
          settingMinute = false;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          settingYear = true;
          settingDay = false;
        }
        else if (settingYear)
        {
          settingMonth = true;
          settingYear = false;
        }
        else if (settingMonth)
        {
          settingDay = true;
          settingMonth = false;
        }
      }
    }
    if (key == 4) // select key
    {
      updateMaxDay();
      lcd.clear();
      if (!settingClock)
      {
        settingTime = true;
        settingClock = true;
        settingHour = true;
        h = hour();
        m = minute();
        s = 0;
        d = day();
        n = month();
        y = year();
      }
      else if (settingTime)
      {
        settingHour = false;
        settingDay = true;
        settingTime = false;
        settingDate = true;
      }
      else if (settingDate)
      {
        settingDay = false;
        settingDate = false;
        settingClock = false;
        setTime(h, m, s, d, n, y);
      }
    }
    key = -1;
  }
}

void updateMaxDay()
{
  if (n == 2)
  {
    if (y % 4 == 0)
      dMax = 29;
    else
      dMax = 28;
  }
  else if (n == 4 || n == 6 || n == 9 || n == 11)
    dMax = 30;
  else
    dMax = 31;
}

// Convert ADC value to key number
int get_key(unsigned int input)
{
  int k;
  for (k = 0; k < NUM_KEYS; k++)
  {
    if (input < adc_key_val[k])
    {
      return k;
    }
  }
  if (k >= NUM_KEYS)k = -1;  // No valid key pressed
  return k;
}

void drawMatrix()
{
  int cc = 0;
  if (!gameOver)
  {
    x[pr][pc] = true;
    for (int r = 0; r < 2; r++)
    {
      for (int c = 0; c < 16; c++)
      {
        special = false;
        for (int i = 0; i < 8; i++)
        {
          byte b = B00000;
          if (x[r * 8 + i][c * 5 + 0]) {
            b += B10000;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 1]) {
            b += B01000;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 2]) {
            b += B00100;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 3]) {
            b += B00010;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 4]) {
            b += B00001;
            special = true;
          }
          myChar[i] = b;
        }
        if (special)
        {
          lcd.createChar(cc, myChar);
          lcd.setCursor(c, r);
          lcd.write(byte(cc));
          cc++;
        }
        else
        {
          lcd.setCursor(c, r);
          lcd.write(254);
        }
      }
    }
  }
}

void freeList()
{
  part *p, *q;
  p = tail;
  while (p != NULL)
  {
    q = p;
    p = p->next;
    free(q);
  }
  head = tail = NULL;
}

void gameOverFunction()
{
  delay(1000);
  lcd.clear();
  freeList();
  lcd.setCursor(3, 0);
  lcd.print("Game Over!");
  lcd.setCursor(4, 1);
  lcd.print("Score: ");
  lcd.print(collected);
  delay(1000);
}

void growSnake()
{
  part *p;
  p = (part*)malloc(sizeof(part));
  p->row = tail->row;
  p->column = tail->column;
  p->dir = tail->dir;
  p->next = tail;
  tail = p;
}

void newPoint()
{
  part *p;
  p = tail;
  boolean newp = true;
  while (newp)
  {
    pr = random(16);
    pc = random(80);
    newp = false;
    while (p->next != NULL && !newp)
    {
      if (p->row == pr && p->column == pc) newp = true;
      p = p->next;
    }
  }
  if (collected < 13 && gameStarted) growSnake();
}

void moveHead()
{
  switch (head->dir) // 1 step in direction
  {
    case 0: head->row--; break;
    case 1: head->row++; break;
    case 2: head->column++; break;
    case 3: head->column--; break;
    default : break;
  }
  if (head->column >= 80) head->column = 0;
  if (head->column < 0) head->column = 79;
  if (head->row >= 16) head->row = 0;
  if (head->row < 0) head->row = 15;

  part *p;
  p = tail;
  while (p != head && !gameOver) // self collision
  {
    if (p->row == head->row && p->column == head->column) gameOver = true;
    p = p->next;
  }
  if (gameOver)
    gameOverFunction();
  else
  {
    x[head->row][head->column] = true;

    if (head->row == pr && head->column == pc) // point pickup check
    {
      collected++;
      if (gameSpeed < 25) gameSpeed += 3;
      newPoint();
    }
  }
}

void moveAll()
{
  part *p;
  p = tail;
  x[p->row][p->column] = false;
  while (p->next != NULL)
  {
    p->row = p->next->row;
    p->column = p->next->column;
    p->dir = p->next->dir;
    p = p->next;
  }
  moveHead();
}

void createSnake(int n) // n = size of snake
{
  for (i = 0; i < 16; i++)
    for (j = 0; j < 80; j++)
      x[i][j] = false;

  part *p, *q;
  tail = (part*)malloc(sizeof(part));
  tail->row = 7;
  tail->column = 39 + n / 2;
  tail->dir = 3;
  q = tail;
  x[tail->row][tail->column] = true;
  for (i = 0; i < n - 1; i++) // build snake from tail to head
  {
    p = (part*)malloc(sizeof(part));
    p->row = q->row;
    p->column = q->column - 1; //initial snake id placed horizoltally
    x[p->row][p->column] = true;
    p->dir = q->dir;
    q->next = p;
    q = p;
  }
  if (n > 1)
  {
    p->next = NULL;
    head  = p;
  }
  else
  {
    tail->next = NULL;
    head = tail;
  }
}

Conclusion

I think it’s safe to say that I have badly managed this project and haven’t fully completed everything that I needed to have done in order to pass this course. I can make no excuses, I simply didn’t put enough time into this project at the beginning and it has left me scrambling to get at least something I can present at the last minute. I don’t know if my initial idea was too ambitious, because I managed to complete one small part of my idea pretty well but that was mostly a coding exercise after I failed to build a separate display unit and instead relying on a pre-built shield for the Arduino. The clock does work well but unfortunately I didn’t get around to the alarm system for it.

I could fill this page with things I didn’t do, but I’ll just focus on what I really should have done. First, I didn’t even attempt to make a proper enclosure for the device mainly because I wasn’t sure how and I ran out of time anyway. And really when it comes down to it, I only made a small part of what I said I would make. If (or more likely when) I have to do this again I would make far better use of my time and plan to do a bit of work on the project every week. I don’t know if I’d ever feel comfortable using solder and PCBs but I could at least try, as I also could try at making some sort of enclosure,  be it 3D printed or otherwise hand made.

The final post for this project will have a full code listing.

Tilt

This update will cover the attempts I had to get the device working with a tilt sensor. Even before I got started I could tell that this could prove almost fruitless especially now that I’m using the LCD Shield which fits directly onto the Arduino and even though it leaves some pins for other uses, it uses a lot of power and makes harder to build stuff away from the microcontroller. I tried anyway, but taking power from the device to a breadboard just meant that there wasn’t any to power the display. I guess that is how the shield operates – if you use the outer pins then they override the use of what ever is below them.

So I tried to directly put a tilt switch onto the shield/Arduino though pins 2 & 3, as they aren’t in use by the LCD screen. I actually tried a couple of analog pins first but the power levels were too inconstant to get a clear reading. But with the digital pins I was at least to get a reading of either 1 or 0. With that I tried a small test program to switch displaying one of two messages onto the LCD display but the end results of the tests weren’t promising as there was a lot of flickering and neither message was clearly readable.

Still I had to try with the full bits of code that I wanted to switch between and after a lot of tweaking of how and when to check the pin input I manage to get it to display the clock portion of the program very well but it’s not able to fully display and run the snake part. That could be because the Snake program I am using requires a fair about of memory to run or it could be clashes between the keypad reading functions. Whatever the reason is, I won’t be able to solve it now nor will I be able to add more to the project as I am out of time. There will be one more post, concluding this project.

Game

For the game portion of the project I look online for examples that people have previously created and I found a really good Snake game for a 16×2 LED display that made use of single pixels in the display but I really struggled to understand most of the code so I set about creating a very simple version of Snake that would just use the 32 available character spaces. The first task was to get was to draw and animate the snake which proved very difficult. I could get a few characters to go along the screen in a straight line but I couldn’t get them to change directions correctly or even at all.

As I was very quickly running out of time for the project I decided to use the Snake game code that I found online so that I can at least show some of the functionality I am aiming for. Here’s a link for the video source of the game: https://www.youtube.com/watch?v=IkTK6WMlMs4 and a link to the source code source: http://pastebin.com/yiDBzjvZ.

Now in the little time I have remaining I will try and get the tilt working so that the two functions of the project can be shown.

Clock

This is how I got a working clock. First I need to see if there was an Arduino library that could help me and I found one at http://playground.arduino.cc/Code/Time, which can be used to store and return time data. As I didn’t have any sort of external device to return the exact current time, like a Real-Time Clock (RTC) Chip, I would need a way for the time and date to be changed manually. But first I needed it to display a current time which was done by using the library mentioned earlier as it had a function to set a time with passed variables, so I set a default time of 12:00:00 and a date of 01/01/2016. The library also has very useful functions that could return the hour, minute, second, day, month and year values of the set date so using them I made a function that would display whatever time was set:

void printTimeDate() 
{ 
  lcd.setCursor(4, 0); 
  if (hour() < 10)
    lcd.print(0);
  lcd.print(hour());
  lcd.print(":");
  if (minute() < 10)
    lcd.print(0);
  lcd.print(minute()); 
  lcd.print(":"); 
  if (second() < 10) 
    lcd.print(0); 
  lcd.print(second()); 
  lcd.setCursor(1, 1); 
  lcd.print(dayShortStr(weekday())); 
  lcd.setCursor(5, 1); 
  if (day() < 10) 
    lcd.print(0); 
  lcd.print(day()); 
  lcd.print("/"); 
  if (month() < 10) 
    lcd.print(0); 
  lcd.print(month()); 
  lcd.print("/"); 
  lcd.print(year()); 
}

That provided a date and time  readout that looked exactly how I wanted it:

clock1

The next stage was to make the set date and time changeable. This required using the keypad buttons on the LED shield, which are readable through a single analogue data pin. So by checking if a button has been pressed and then determining which button was pressed allowed me to use the select button the draw another screen on the display, this one for setting the time. Another press of the button would take you to the set date screen. For each screen a press of the left or right buttons would change what value is being modified, indicated by a blinking cursor. The up and down buttons change that value within appropriate constraints. Pressing the select button for a third time returns to the time/date screen but after updating the set time/date with the manually entered values.

Now I have a functioning and modifiable clock I started work on the game part of the project.

Display

Choosing the right display unit for my device was problematic right up until the end of the end of the  project. The first unit I chose was a 20×4 LED display module with a blue backlight but I had a lot of problems just trying to work out how to connect it especially as I was hesitant about the use of soldering irons and the like. I did try to connect wires to it where appropriate but ended up damaging the unit completely.

So I looked for another display that would be easier to use and found a 16×2 LCD display shield with a built in keypad. Now I wasn’t sure if using something like this was relying on too much pre-built technology but I feel that my strengths lie in programming and not the actual building on the device and I decided to focus on my strengths. If that costs me marks that is fine by me.

Testing of the new display went  very well and also allowed me a chance to test the use of the 9V battery adapter, as I will need the device to be powered by a battery and not my computer via a USB lead. The next will involve working out what can be displayed on the screen and determining if I can use this to display a simple game.

16x2-led-test1

Components

This post is for talking about the components I need for this project. The staple components are required (wires, resistors and such like) but I’ll need something to output an alarm sound. I’ve previously used a small speaker during one of the lab assignments but I was unhappy with the sound it outputted so this time I’ll be using a piezo element type speaker which will allow for a cleaner sound and allow for a more melodious alarm for the clock.

A big element of this project is that the clock has two function states, dictated by the orientation of the device. To achieve this I need to use a tilt switch, which basically is a tube that has a metal ball inside of it and when the ball is at the bottom of the tube the ‘switch’ is connected and a circuit can be formed and so when the device is tilted the ball is no longer completing the circuit. For this project I will need to have a circuit running to a pin on the Arduino that is checking for the amount of current arriving at it so that one loop of code is running when the current is at a certain level and when that changes, another loop of code gets to run instead, which switches between the two functions. More than one tilt switch may be needed in order to have more exacting control over the change in functions.

Here’s a video of my testing of a tilt switch:

Next time I’ll talk about what I intend to use for the device’s display.

Physical Computing Project Proposal

This is my proposal for the major project portion of the Physical Computing course. The project idea has the working title of “You Snooze, You Lose” and can be described as a anti-snooze alarm clock.

The basic premise of the project is to create an alarm clock with the added feature of having a simple built in game. Initially I am thinking of implementing a Snake like game, but that may change. One requirement of the game is that it must be controllable by two buttons or less. It is also my intention for the inclusion of the game to not simply be a gimmick but to form the key feature of the project which is to force the user to play a few minutes/levels of the game after pressing the snooze button on the alarm a set number of times.

Why would I want to do this? Well I feel that the temptation to repeatedly hit snooze on your alarm is high and even when the alarm is fully off, it can still be slow process in waking up. The benefits this project has are two-fold. One, the number of snoozes are limited, though changable via programming (which could be an extra feature, the allowance of updates to the program). The second benefit is that I feel playing a game for a few minutes as you’re waking up can help get your brain ‘booted up’ to full speed in a faster time than normal.

I hope to finish the project with a fully working prototype although the shell of the device may simply just be functional, depending on how much time there is to design a better box for the prototype near the end of the project. My plan for the next few weeks, over the break between terms, is to begin pre-production on the project, selecting and ordering parts; practicing and testing the parts; learning how they function and how they work together. Future blog posts will cover these steps.