Eyal tries to make (almost) anything

MAS.863, 2019

Embedded Programming

Finally, something in this class I know about!

Well, I actually never did embedded programming (except for basic Arduino + processing), but still C was the first language I learned and I am very fond of it.

A reminder, back in week 5, I designed and milled a PCB with a Attiny44 and a push button and LED connected to it, but haven’t really programmed it to do anything. The goal of this week was to make it do something, and so I did.

Step 1: Make the LED shine

First, let’s see if we can make the LED work. Looking back at my PCB design, it seems the LED is connected to PA3. Thus, by looking through code from previous years, I derived the following piece which worked out fine:

#include <avr/io.h>

#define LED (1 << PA3)

int main(void) {
  // set LED as output
  DDRA |= LED;
  // pull LED to high
  PORTA |= LED;
  //
  // main loop
  //
  while (1) {
  }
}

I used the same Makefile from Neil’s code, no changes needed here.

Step 2: Make the button make the LED shine

Now, let’s see if we can make the button control the LED. This was a bit less straight forward, as both my button and my LED were on PORTA. A lot of previous examples had these components on different ports, which made it a bit simpler code-wise. After some meddling, I eventually got the code right:

#include <avr/io.h>

#define LED (1 << PA3)
#define BUTTON (1 << PA2)

int main(void) {
  // set LED as output
  DDRA |= LED;
  // set button as input
  DDRA &= ~BUTTON;
  // pull LED and button to high
  PORTA |= LED;
  PORTA |= BUTTON;
  //
  // main loop
  //
  while (1) {
    if (PINA & BUTTON) {
      PORTA &= ~LED;
    } else {
      PORTA |= LED;
    }
  }
}

The confusing parts were DDRA &= ~BUTTON and PORTA &= ~LED, which basically sets that specific bit to zero.

And here is a video showing the amazing button controllable LED:

OK, this satisfies the minimum requirement, but let’s see if we can make something more interesting with just a button and LED. My idea was to create a simple version of “Simon” game. Hence, the board will spit out a sequence of lights and the player needs to repeat that sequence. Since I have only one, single-color LED, the sequence is made of short and long flashes.

For that, I need a couple of more steps:

Step 3: Make the button make the LED shine, AFTER press

While in the previous version, the LED lit when the button was down. I wanted to have a function which recognizes a click. For that, I had to write up a function:

#define BUTTON_UP 0
#define BUTTON_DOWN 1

static char button_status = BUTTON_UP;

int button_pressed(void) {
  if (PINA & BUTTON) {
    // button is up
    if (button_status & BUTTON_DOWN) {
      button_status = BUTTON_UP;
      return 1;
    }
  } else {
    // button is down
    button_status = BUTTON_DOWN;
  }
  return 0;
}

This function returns 1 exactly once, on the first cycle after a click was made.

Now, that I can turn on the light after a button was clicked, I tried to play an arbitrary sequence of short and long blinks. This required some extra work. I used a for loop, and to compile it, I had to add ` -std=c99 to the CFLAGS in the Makefile. To generate a random sequence, I started with C rand(), although I will replace that later. I also used _delay_ms` to make short and long blinks, but that only worked properly once I added Neil’s lines that setup the clock:

// set clock divider to /1
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

Now the function that plays a sequence is:

#include <stdlib.h>
#include <util/delay.h>

static unsigned int sequence = 0;
static unsigned int game_level = 1;
static unsigned int count = 0;

void play_sequence() {
  sequence = rand();

  _delay_ms(200);

  for (int i = 0; i < game_level; i++) {
    PORTA |= LED;
    _delay_ms(150);
    if (sequence & (1 << i))
      _delay_ms(350);
    PORTA &= ~LED;
    if (i + 1 < game_level) {
      _delay_ms(500);
    }
  }

  game_status = STATUS_WAITING_FOR_INPUT;
  count = 0;
}

Basically, i’m looking at the first game_level bits of sequence, and if bit i is 1, that blink will be long (500ms), otherwise short (150ms). game_level is a variable I will increase as the game gets harder. I also added the variables count and game_status to differentiate between the first click, and which click is it.

The code in the main loop is:

if (button_pressed()) {
  if (game_status == STATUS_INIT) {
    play_sequence();
  } else if (game_status == STATUS_WAITING_FOR_INPUT) {
    count++;

    // if reached the count, go to the next level
    if (count >= game_level) {
      game_level++;

      if (game_level <= MAX_LEVEL) {
        play_sequence();
      } else {
        // game over!
        game_level = 1;
        game_status = STATUS_INIT;
      }
    }
  }
}
PORTA &= ~LED;

This code is a simple version of Simon, which just counts the number of clicks. You actually can’t fail, as enough clicks will get you to the next level. But its a stepping stone.

In action:

Step 5: Make Simon

Ok, this is where it started to get “interesting”. To differentiate between short and long user taps, I needed someway to capture time. Luckily, the Attiny44 microcontroller has a Timer/Counter, and yes I actually had to look at the data sheet to understand exactly what’s going on. I also read some tutorials about the Timer/Counter, which are written in a more human-understandable language compared to the datasheet.

We have two timers, 0 - 8bit, and 1 - 16bit. The timers have many different operating modes, but I just needed the most simple, default one (Normal), so no extra configuration needed. By default, these timers increment the counter (TCNT0/1) every clock cycle, but we can set up a prescaler of up to 1024 for each timer, using:

TCCR1B = (1<<CS10) | (1<<CS12)

Our board is running at 20Mhz, so for 8bit - I can only count until 256 * 1 / (20 / 1024. * 1000) = 13ms. That’s way to fast for a human finger. With 16bit, I can count until 65536 * 1 / (20 / 1024. * 1000) = 3.36sec. Much better.

So when the user presses the button down, I reset the timer counter (TCNT1 = 0). When the button is raised up, I save the counter (last_timer = TCNT1). I compare this counter for some experimentally chosen threshold (5000 felt right, by a similar calculation, that’s 256ms) to decide whther the press was long or short.

Another cool feature - I can use the time measurement as a true random number generator! Assuming human clicks are somewhat random at the microsecond level, I take the last click time and use the least significant bits in that 16bit number as the literal sequence of short and long blinks.

So, here is the entire final code for my Simon:

#include <avr/io.h>
#include <util/delay.h>

#define LED (1 << PA3)
#define BUTTON (1 << PA2)

#define BUTTON_UP 0
#define BUTTON_DOWN 1

#define MAX_LEVEL 8

#define STATUS_INIT 0
#define STATUS_WAITING_FOR_INPUT 1

// for 20MHz, 5000 is 256ms
#define TAP_SHORT_MAX 5000
#define TAP_LONG_MIN 5000

static unsigned int game_level = 1;
static char button_status = BUTTON_UP;
static char game_status = STATUS_INIT;
static unsigned int sequence = 0;
static unsigned int count = 0;
static unsigned int last_timer = 0;

int button_pressed(void) {
  if (PINA & BUTTON) {
    // button is up
    if (button_status & BUTTON_DOWN) {
      button_status = BUTTON_UP;
      last_timer = TCNT1;
      return 1;
    }
  } else {
    // button is down
    if (!button_status) {
      // first press
      TCNT1 = 0;
    }
    button_status = BUTTON_DOWN;
  }
  return 0;
}

void play_sequence() {
  sequence = last_timer;

  _delay_ms(200);

  for (int i = 0; i < game_level; i++) {
    PORTA |= LED;
    _delay_ms(150);
    if (sequence & (1 << i))
      _delay_ms(350);
    PORTA &= ~LED;
    if (i + 1 < game_level) {
      _delay_ms(500);
    }
  }

  game_status = STATUS_WAITING_FOR_INPUT;
  count = 0;
}

void lose(void) {
  for (int i = 0; i < 15; i++) {
    PORTA |= LED;
    _delay_ms(50);
    PORTA &= ~LED;
    _delay_ms(50);
  }
}

void win(void) {
  PORTA |= LED;
  _delay_ms(3000);
}

int main(void) {
  //
  // set clock divider to /1
  // took this code from Neil. not sure what it does but it seems important to make_delay_ms work properly
  //
  CLKPR = (1 << CLKPCE);
  CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);

  // set LED as output
  DDRA |= LED;
  // set button as input
  DDRA &= ~BUTTON;
  // pull LED and button to high
  PORTA |= LED;
  PORTA |= BUTTON;

  // set timer prescaler to /1024
  // assuming a clock speed of 20Mhz, a 16 bit timer can count until:
  // 1 / (20 / 1024) * 65536 = 3.3sec~
  // good enough for us, since i only use to to measure taps
  TCCR1B = (1<<CS10) | (1<<CS12);
  TCNT1 = 0;
  //
  // main loop
  //
  while (1) {
    if (button_pressed()) {
      if (game_status == STATUS_INIT) {
        play_sequence();
      } else if (game_status == STATUS_WAITING_FOR_INPUT) {
        // check if the press is correct
        if (((sequence & (1 << count)) && last_timer > 5000) ||
            (!(sequence & (1 << count)) && last_timer < 5000)) {
          // success!
          count++;

          // if reached the count, go to the next level
          if (count >= game_level) {
            game_level++;

            if (game_level <= MAX_LEVEL) {
              play_sequence();
            } else {
              // game over!
              win();
              game_level = 1;
              game_status = STATUS_INIT;
            }
          }
        } else {
          lose();
          game_level = 1;
          game_status = STATUS_INIT;
        }
      }
    }
    PORTA &= ~LED;
  }
}

Final result…! (flashing blinks means I got it wrong, and the game restarts)

All of the original code is available here