A Spark of Life: More DigiSpark fun

WP_20151019_22_42_59_Pro__highresI recently wrote about the cool little DigiSpark ATTiny 85 controller board. This little USB wonder, like the AdaFruit Trinket, is based on the ATTiny85 Microcontroller and has limited I/O, memory and is cheap, very cheap.

One thing it is not short on, however, is uses. This little thing can be used for a variety of things, including games and control applications.  For any use, though, you will need some kind of input and some kind of output.  In a previous post, I postulated about porting my ‘Battlestar Galactica’ game to the DigiSpark from the Trinket.  So, here are the results of that endeavor and some other things as well.

For the backstory of the game and why I wrote it, check out this post. I’ll wait.

Read it?  Cool.WP_20151019_22_44_34_Pro__highres

Ok, so there are few differences between the Trinket and the DigiSpark, other than size.  The way you program it is very similar and the pins are nearly identical.  The way you talk to things, is, however, a little different.

The Trinket needed SoftwareSerial. We don’t here, we just use DigitalRead and DigitalWrite and Serial.write to read the button and write to the screen.

The display is a 16×2 Serial LCD from Parallax.  The button is actually a ‘controller’ I built a couple of years ago for my game console. I did not use it for that, opting for the Wii Nunchuck instead.  So, the controller has three buttons that would be used for left, right and ‘fire’ or action.  Here, they just return a 0 if pressed and a 1 when ‘open’.  The code periodically polls the controller for a press and then acts on the press to evaluate a hit or miss.

WP_20151019_22_44_06_Pro__highres

Now, one of the problems I had was the lack of GROUND pins. So, I made a ground bus, a four pin strip with a wire connecting all four pins. I then connected this to ground on the DS and the button and the LCD as well are connected to this bus. I have one free ground pin.  Well, what I did not realize was that you can use any pin for ground.  While I did not change my connections, I find this pretty cool.

TIP! To use a free pin as ground:

(From the wiki)

You can use an I/O pin as a ground/+5V if you have some left and the current that flow in it is low (a few milliamps if you want the pin to stay around 0V/+5V. If you don’t care about the exact voltage you can go up to 40mA per pin according to the datasheet but it’s good to keep a safety margin).

Configure the pin as an output and set its value to either LOW or HIGH (for ground or +5V

#define button 0
#define gnd 2
 
void setup() {                
  pinMode (button, INPUT);
  digitalWrite (button, HIGH); // enable pullup
  pinMode(gnd, OUTPUT);
  digitalWrite (gnd, LOW); // use this pin as a ground
}

For much more information and tips, go to the wiki, located here.

The game code is below.

Come back for more on this cool little device.  Since it is capable of serial i/o, I’m thinking of using the Bluetooth Serial module and connecting the DS to one of my HalfByte Consoles running the graphical serial software for video display from the DS.

WP_20151019_22_45_35_Pro__highresWP_20151019_22_43_27_Pro__highres

Game Code:

#include <TinyPinChange.h>

 // globals
 int  cylonpos;
 int  oldcylonpos;
 int  cyDirection;
 int  button;
 int  posStart;
 int  lin2Start;
 int  bsgpos;
 
 // special characters
 int  bsg;
 int  cylon;
 
 // total number of torpedos the ship has left
 int  tShots=10;
 
 // the setup routine runs once when you press reset:
 void waitforbutton();
 void moveCylon();
 
 void setup() {  // initialize the digital pin as an output.  
   Serial.begin(9600); 
   Serial.write(12);
   Serial.write(17);
   
   //define the ship character
   Serial.write(249);
   Serial.write(0x4);
   Serial.write(0xE);
   Serial.write(0xE);
   Serial.write(0x15);
   Serial.write(0x1F);
   Serial.write(0x15);
   Serial.write(0xE);
   Serial.write(0xE);
   
   // baddies
   Serial.write(251);

   Serial.write(uint8_t(0x0));//to handle a goofy issue with '0'
   Serial.write(uint8_t(0x0));
   Serial.write(0xE);
   Serial.write(0x15);
   Serial.write(0x1F);
   Serial.write(uint8_t(0x0));
   Serial.write(uint8_t(0x0));
   Serial.write(uint8_t(0x0));
 
 // the button is on pin 0  
   pinMode(0,INPUT);
   
 // the initial direction is right
   cyDirection=1;
   
 } // the loop routine runs over and over again forever:
 void loop() {
  int score;
  score=0;
  
  Serial.write(12); // clear the screen and show the instructions
  Serial.println("Shoot the Cylon\n");
  Serial.println("press btn strt");
  
  // wait for the button
  waitforbutton();
  
  // define somethings before we start the game loop
  tShots=10;
  cylon=3;
  bsg=1;
  lin2Start=148; // the baddies appear on line 1
  posStart=128;  // the good guys on line 2
  randomSeed(analogRead(0)); // gen a random seed
  
  bsgpos = 8;  // our ship starts in the middle of the sector
  cylonpos=random(0,15); // baddies warp in at a random spot
  oldcylonpos=cylonpos;
  
  Serial.write(12);  // clear screen
  Serial.write(17);  // turn on backlight so we can see
  Serial.write(posStart + cylonpos);  // uh oh...the Cylon appeared!
 
  Serial.write(cylon);
  Serial.write(lin2Start+bsgpos);
  Serial.write(bsg); // and here we are
  
  // begin the game 
  while(1){
    /* Game loop */
    int button;
    Serial.write(22); // kill the cursor
    moveCylon();
    delay(200);
    button=digitalRead(0);  // check the button
    
    if(button==0){ // torpedo fired?
      if(cylonpos==bsgpos){ // Hit?
        Serial.write(132); // center 'Hit' on screen
        Serial.print("** HIT **");
        score ++;  // update score
        Serial.write(213); // noise (C at half note, 3rd scale)
        Serial.write(215); // play the note
        Serial.write(224);
        Serial.write(225);
        if(score==10){  // did we win?
           Serial.write(12); // clear screen
           Serial.write(128);// start at beginning
           Serial.print("You have saved\nthe Galactica.");
           delay(2000);
           loop();  // start game again
        }
        delay(3000); // did not win yet
        Serial.write(12); // clear screen
        Serial.write(lin2Start); // move cursor to beginning of line
        Serial.write("Score:");  // put up score
        Serial.print(score);
        Serial.write(lin2Start+bsgpos); // Since we shot the Cylon, we have to leave the sector, so...
        Serial.write(0x20); // go into hyperdrive and erase the BSG
        bsgpos=random(9,15);  // calculate new position
        Serial.write(lin2Start+bsgpos); //...and, we are here
        Serial.write(bsg);
        Serial.write(posStart+cylonpos);
        Serial.write(0x20);
        cylonpos=random(0,15); // Damn, they found us!
        oldcylonpos=cylonpos;
        moveCylon();
      } 
      
      tShots --; // decrement our torpedo inventory
      if(tShots<1){ // out of ammo?
        Serial.write(12);  // clear screen
        Serial.write(posStart); // start at beginning of line
        Serial.print("The Cylons win.\n");
        int count=1;
        // show animation of our ship exploding
        while (count<10){
          Serial.write(lin2Start+bsgpos);
          Serial.write(bsg);
          delay(500);
          Serial.write(lin2Start+bsgpos);
          Serial.write('*');
          delay(500);
          count++;
        }        
        loop(); // Play again
      }
    }  
  }
}
void moveCylon(){
  // sneaky Cylons...they show up anywhere!
  Serial.write(posStart+oldcylonpos);
  Serial.write(0x20);
  if (random(0,6)>3){
      //Cylon warped
      cylonpos=random(0,15);
      oldcylonpos=cylonpos;
  }
  
  cylonpos += cyDirection; // figure out which way to move
  if (cylonpos>15){
   cylonpos=15;
   cyDirection=-1;
  }
  
  if (cylonpos<0){
    cylonpos=0;
    cyDirection=1;
  }
  // erase from old spot
  Serial.write(posStart+oldcylonpos);
  Serial.write(0x20);
  Serial.write(posStart+cylonpos);
  Serial.write(cylon);
  oldcylonpos=cylonpos;
} 

void waitforbutton(){
   
  int button;
  button=digitalRead(0);
  while(button==1){     //wait for button press
    button=digitalRead(0);
    delay(1000);
  }  
  Serial.write(1);  
  delay(1000);
  Serial.write(12); 
}
Advertisements

A Video Game on my Trinket, take 2

Several posts ago, I wrote about writing a video game for the Trinket from Adafruit. Well, since then, I have added scoring, sound and a bit more animation (very crude, mind you, I can only do so much in the small memory and limited screen.)

The setup uses the Trinket, Parallax’s 2×16 LCD panel and a switch connected to Pin 0. The LCD panel is connected to Pin 2.  You will need to import the SoftwareSerial library for the LCD.

The premise of the game is based on the Battlestar Galactica TV show from 2003-2010.  The Galactica was engaged in heavy battle and barely managed to escape before certain destruction. All of her gun turrets, save the forward torpedo launcher, are damaged and non-functional, meaning she can only fight head on.  The Viper squads are still engaging the enemy.  A group of Cylon raiders have found the mighty Battlestar and are taking her on, one by one. Oh, you only have ten torpedo’s left, so use them wisely.

The game ends when you use up your ten torpedo’s.

Below is the revised listing for the game.  Apologies to Glen Larsen.

 

Listing 1.

#include <SoftwareSerial.h>

SoftwareSerial s(0,2);
 
// globals
int  cylonpos;
int  oldcylonpos;
int  cyDirection;
int  button;
int  posStart;
int  lin2Start;
int  bsgpos;
 
// special characters
int  bsg;
int  cylon;
 
// total number of torpedos the ship has left
int  tShots=10;
 
// the setup routine runs once when you press reset:
void waitforbutton();
void moveCylon();
 
void setup() {  // initialize the digital pin as an output. 
   s.begin(9600);
   s.write(12);
   s.write(17);
  
   //define the ship character
   s.write(249);
   s.write(0x4);
   s.write(0xE);
   s.write(0xE);
   s.write(0x15);
   s.write(0x1F);
   s.write(0x15);
   s.write(0xE);
   s.write(0xE);
  
   // baddies
   s.write(251);

   s.write(uint8_t(0x0));//to handle a goofy issue with ‘0’
   s.write(uint8_t(0x0));
   s.write(0xE);
   s.write(0x15);
   s.write(0x1F);
   s.write(uint8_t(0x0));
   s.write(uint8_t(0x0));
   s.write(uint8_t(0x0));
 
// the button is on pin 0 
   pinMode(0,INPUT);
  
// the initial direction is right
   cyDirection=1;
  
} // the loop routine runs over and over again forever:
void loop() {
  int score;
  score=0;
 
  s.write(12); // clear the screen and show the instructions
  s.println("Shoot the Cylon\n");
  s.println("press btn 2 strt");
 
  // wait for the button
  waitforbutton();
 
  // define somethings before we start the game loop
  tShots=10;
  cylon=3;
  bsg=1;
  lin2Start=148; // the baddies appear on line 1
  posStart=128;  // the good guys on line 2
  randomSeed(analogRead(0)); // gen a random seed
 
  bsgpos = 8;  // our ship starts in the middle of the sector
  cylonpos=random(0,15); // baddies warp in at a random spot
  oldcylonpos=cylonpos;
 
  s.write(12);  // clear screen
  s.write(17);  // turn on backlight so we can see
  s.write(posStart + cylonpos);  // uh oh…the Cylon appeared!
 
  s.write(cylon);
  s.write(lin2Start+bsgpos);
  s.write(bsg); // and here we are
 
  // begin the game
  while(1){
    /* Game loop */
    int button;
    s.write(22); // kill the cursor
    moveCylon();
    delay(200);
    button=digitalRead(0);  // check the button
    if(button==HIGH){ // torpedo fired?
      if(cylonpos==bsgpos){ // Hit?
        s.write(132); // center ‘Hit’ on screen
        s.print("** HIT **");
        score ++;  // update score
        s.write(213); // noise (C at half note, 3rd scale)
        s.write(215); // play the note
        s.write(224);
        s.write(225);
        if(score==10){  // did we win?
           s.write(12); // clear screen
           s.write(128);// start at beginning
           s.print("You have saved\nthe Galactica.");
           delay(2000);
           loop();  // start game again
        }
        delay(3000); // did not win yet
        s.write(12); // clear screen
        s.write(lin2Start); // move cursor to beginning of line
        s.write("Score:");  // put up score
        s.print(score);
        s.write(lin2Start+bsgpos); // Since we shot the Cylon, we have to leave the sector, so…
        s.write(0x20); // go into hyperdrive and erase the BSG
        bsgpos=random(9,15);  // calculate new position
        s.write(lin2Start+bsgpos); //…and, we are here
        s.write(bsg);
        s.write(posStart+cylonpos);
        s.write(0x20);
        cylonpos=random(0,15); // Damn, they found us!
        oldcylonpos=cylonpos;
        moveCylon();
      }
     
      tShots –; // decrement our torpedo inventory
      if(tShots<1){ // out of ammo?
        s.write(12);  // clear screen
        s.write(posStart); // start at beginning of line
        s.print("The Cylons win.\n");
        int count=1;
        // show animation of our ship exploding
        while (count<10){
          s.write(lin2Start+bsgpos);
          s.write(bsg);
          delay(500);
          s.write(lin2Start+bsgpos);
          s.write(‘*’);
          delay(500);
          count++;
        }       
        loop(); // Play again
      }
    } 
  }
}
void moveCylon(){
  // sneaky Cylons…they show up anywhere!
  s.write(posStart+oldcylonpos);
  s.write(0x20);
  if (random(0,6)>3){
      //Cylon warped
      cylonpos=random(0,15);
      oldcylonpos=cylonpos;
  }
 
  cylonpos += cyDirection; // figure out which way to move
  if (cylonpos>15){
   cylonpos=15;
   cyDirection=-1;
  }
 
  if (cylonpos<0){
    cylonpos=0;
    cyDirection=1;
  }
  // erase from old spot
  s.write(posStart+oldcylonpos);
  s.write(0x20);
  s.write(posStart+cylonpos);
  s.write(cylon);
  oldcylonpos=cylonpos;
}

void waitforbutton(){
  SoftwareSerial s(0,1);
 
  int button;
  button=digitalRead(0);
  while(button==LOW){
    button=digitalRead(0);
    delay(1);
  } 
  s.write(1); 
  delay(1000);
  s.write(12);
}

Coding for small platforms is nearly a lost art OR why I love tiny computers

There was a time when a computer with 4K of memory was something to behold. Indeed, 4K was enough for a BASIC interpreter AND have room left over for user programs. Most I/O was serial, so ALL of that space was for code. The ‘operating system’ was something called a ‘monitor’ or the BASIC itself. Either way, the I/O code was ‘built’ into either the BASIC or the monitor. And, if you had a video terminal, well, you were THE stuff.

Just a few years later, that 4K grew to 16K then 64K and, suddenly, we are talking MEGAbytes. Wow. Well, funny thing happened, code went from small, tight and mostly efficient to BIG, heavy and bloated.  Code in that 4K system that put a character on the screen took, maybe six bytes suddenly took a DLL, which was probably ten times that or more.  Programmers got complacent, including this one.

I recall one job I had, I was maybe 17, that required me to write an accounting screen for a paging system (remember the pocket pager?) I had 2k for the data entry screen AND the billing code. Plus, the datastream to/from the paging system was like 32 bytes.  In that 32 bytes, I had to squeeze in the client number (10 digits), number of pages, and about two dozen status bits and more. I did it. The software went into a dozen or ‘head end’ systems (hey, it was a SMALL market) but it was my first ‘professional’ gig.

My career took me down the Microsoft/Lotus path. I had all kinds of wonders to code with, Visual Studio, Visual Basic, Lotus Notes, web.  My code, while functional and often clever, was sloppy. I forgot what it was to REALLY code.

Well, I had a shot a programming for the Palm Pilot, which reminded me of my old days, but it didn’t last long and I was back to programming for machines with memory measured in the gigabytes.

Imagine my delight when I discovered the world of Microcontrollers.  These things have big capability, but tiny (and I mean tiny) memory spaces. My current microcontroller project, the Adafruit Trinket, based on the ATMel ATtiny85, has 5,2k of available programming memory and 512 BYTES of RAM (as well as 512 bytes of EEPROM.)

Having found some cool 2×16 LCD panels for five bucks each at Radio Shack, these Trinkets and a bunch of parts, I set out to make a game for my step son.  I’m still working on a game concept that will keep his interest, but I did a proof of concept game based on Space Invaders (my all time favorite game.) So, how can one make a Space Invaders on a TWO line by SIXTEEN character display? Well…you need to be creative. AND…you have to fit it, along with a necessary library, into under 5.1K.

So…what did I do? Well, found an old notebook I kept from my TRS-80 days. Looked at some old Tiny Basic game code and…coded.

BSG001First, I needed an idea. The display I am using, from Parallax, allows for eight special characters.  So, I played around with a few designs and one of them looked like the Battlestar Galactica. After some thought, I had the idea: The Galactica’s Viper squadrons are out on patrol. The Galactica was in a fierce battle that has rendered it nearly immovable, it can only hyperjump. It’s turrets are all damaged, save the forward bank, she can only shoot from her forward cannons.  A Cylon has discovered the Galactica and is taking runs at it.  Problem is, the Cylon is jumping randomly, making a direct hit nearly impossible.  If, however, the Galactica does manage to destroy the Cylon, the Galactica then hyperjumps too.  Then, it all happens again.

Cool, huh?IMG_2758

So, I set out to code it. I put code together to read a switch, move a character back and forth and come up with an interrupt mechanism for the button.

Turns out, it was simpler than I thought.  I had some hiccups with the Arduino IDE (which involved upgrading the toolchain, upgrading part of it again, some choice words…) but got them ironed out and, now, the Colonials are fighting the nasty Cylons again.

First, the source code:

#include <SoftwareSerial.h>

SoftwareSerial s(0,2);
 
// globals
int  cylonpos;
int  oldcylonpos;
int  cyDirection;
int  button;
int  posStart;
int  lin2Start;
int  bsgpos;
// special characters
int  bsg;
int  cylon;
 
// the setup routine runs once when you press reset:
void waitforbutton();
void moveCylon();
 
void setup() {  // initialize the digital pin as an output. 
   s.begin(9600);
   s.write(12);
   s.write(17);
   //define the character
   s.write(249);
   s.write(0x4);
   s.write(0xE);
   s.write(0xE);
   s.write(0x15);
   s.write(0x1F);
   s.write(0x15);
   s.write(0xE);
   s.write(0xE);
  
   // baddies
   s.write(251);

   s.write(uint8_t(0x0));//to handle a goofy issue with ‘0’
   s.write(uint8_t(0x0));
   s.write(0xE);
   s.write(0x15);
   s.write(0x1F);
   s.write(uint8_t(0x0));
   s.write(uint8_t(0x0));
   s.write(uint8_t(0x0));
  
   pinMode(0,INPUT);
   cyDirection=1;
  
} // the loop routine runs over and over again forever:
void loop() {
  int score;
  score=0;
 
  s.write(12);
  s.println("Shoot the Cylon\n");
  s.println("press btn 2 strt");
 
  waitforbutton();
 
  cylon=3;
  bsg=1;
  lin2Start=148;
  posStart=128;
  randomSeed(analogRead(0)); // gen a random seed
 
  bsgpos = 8;
  cylonpos=random(0,15);
  oldcylonpos=cylonpos;
 
  s.write(12);
  s.write(17);
  s.write(posStart + cylonpos);
 
  s.write(cylon);
  s.write(lin2Start+bsgpos);
  s.write(bsg);
 
  while(1){
    /* Game loop */
    int button;
    s.write(22); // kill the cursor
    moveCylon();
    delay(200);
    button=digitalRead(0);
    if(button==HIGH){
      if(cylonpos==bsgpos){
        s.write(132); // center ‘Hit’ on screen
        s.print("** HIT **");
        score ++;  // update score
        delay(3000);
        s.write(12); // clear screen
        s.write(lin2Start); // move cursor to beginning of line
        s.write("Score:");  // put up score
        s.print(score);
        s.write(lin2Start+bsgpos); // Since we shot the Cylon, we have to leave the sector, so…
        s.write(0x20); // go into hyperdrive and erase the BSG
        bsgpos=random(9,15);  // calculate new position
        s.write(lin2Start+bsgpos); //…and, we are here
        s.write(bsg);
        s.write(posStart+cylonpos);
        s.write(0x20);
        cylonpos=random(0,15); // Damn, they found us!
        oldcylonpos=cylonpos;
        moveCylon();
      }
    } 
  }
}
void moveCylon(){
  // sneaky Cylons…they show up anywhere!
  s.write(posStart+oldcylonpos);
  s.write(0x20);
  if (random(0,6)>3){
      //Cylon warped
      cylonpos=random(0,15);
      oldcylonpos=cylonpos;
  }
 
  cylonpos += cyDirection; // figure out which way to move
  if (cylonpos>15){
   cylonpos=15;
   cyDirection=-1;
  }
 
  if (cylonpos<0){
    cylonpos=0;
    cyDirection=1;
  }
  // erase from old spot
  s.write(posStart+oldcylonpos);
  s.write(0x20);
  s.write(posStart+cylonpos);
  s.write(cylon);
  oldcylonpos=cylonpos;
}

void waitforbutton(){
  SoftwareSerial s(0,1);
 
  int button;
  button=digitalRead(0);
  while(button==LOW){
    button=digitalRead(0);
    delay(1);
  } 
  s.write(1); 
  delay(1000);
  s.write(12);
}

SInce the Trinket is limited, I/O wise, I have only five pins, two of which are shared with the USB programmer and one is shared with the red programming LED, it really limits what you can add. At least, for those of us who are electronically challenged. I have my LCD on pin 2 and the switch on pin 0.  The LCD is controlled with a single line, it also has a five volt line (which goes to the USB power) and ground. The switch is connected to power and a 10k ohm resistor. It connects to pin 0. In the LOOP code, there is a While loop that handles the game logic. Inside the loop, it checks the pin for HIGH, which means the button was pressed. It then checks the position of the BSG and the Cylon. If there is a hit, it tells the user that they hit the Cylon and then increments the score, recalculates the positions and does it all again.  Simple, but kind of hard since the Cylon jumps so much.

While the game is not sophisticated, it does show what you can do in 5K of memory, a limited input mechanism and very limited display.  Now, about the game for my step son…