Merry Christmas from our house to your's! |
Some posts about my random techo projects at home - some Arduino, Atari, 3D printing, RPi, etc ....
Friday, December 23, 2016
3D Christmas 2016 - The Print
The model described in my last post was printed and nickel plated by my friend who suggested the idea for the ornament. He also asked me to put a 3/4" hole in the bottom - this makes it possible to rest the ornament on a tree branch with a tree light poking inside. Here's the final outcome:
Sunday, December 11, 2016
3D Christmas 2016 - The Model
This year's Christmas ornament is a 3D model of our house. My 3D-printing friend at church suggested the idea and offered to print and plate it. That's cool. In this post I describe creating this 3D model in SketchUp. I put up an OBJ file over at Thingiverse.
Finally, I usually use Solid Inspector in SketchUp to fix it, but always have to run my models through this cloud repair tool. I stumbled upon Windows 10's new 3D Builder - it did a great job of making the model manifold.
I've used SketchUp occasionally for several years and decided to start there because it's easy to enter accurate dimensions. I stumbled upon the match photo feature, which is exactly what I needed but didn't know it. This feature allowed me to import photos of my house and match the coordinate system to the picture by determining the origin, rotation, scale, and perspective. I followed the directions here. Then I traced the outline with the line tool starting with major lines on the principal axes. I drew some construction lines to allow me to locate the apex and trace the A-frame of the roof. I simplified the overhang because the print will be quite small. I'm not going for accurate construction drawings here, just something that looks nice and representative. This picture shows my starting photo with the nearly finished model:
I used the long gutter and siding seams to align the vanishing point lines, which set the coordinate system perspective and rotation. The origin is moved to the far corner of the garage because it seemed easy to put there. Once the model is started, more photos of the other corners are brought in. For each photo, I set the vanishing lines, origin, and then the scale to visually match up the existing model with the photo as best I can. Here's the other front corner:
I drew the chimney after completing all four sides of the house. The windows and doors are last. Since it's not possible to print detailed lattices in the windows because they are too small, I was advised to put in what I could with about an 0.8mm thickness. The windows are recessed and the shutters are extruded out. I did end up putting the panel details on the garage doors. I don't include any of the utility features, for example the power meter or chimney cover used for keeping the critters out.
Finally, I usually use Solid Inspector in SketchUp to fix it, but always have to run my models through this cloud repair tool. I stumbled upon Windows 10's new 3D Builder - it did a great job of making the model manifold.
Saturday, November 5, 2016
Calculator Games
Last year over Tvacation, my nephew was grounded from his XBox. I broke out my girl's TI 84-Plus CSE calculator and learned TI Calculator BASIC. Reminded me of my Tandy PC-4 pocket computer that I programmed in BASIC to multiply matrices (Algebra II homework). I wrote four games worth mentioning:
- BOUNCE - a single player Pong/Breakout type game
- INVADERS - a Space Invaders variant
- SCROLL - a driving game (it scrolls vertically, thus the name)
- SNAKE - eat Pi's to grow, but don't run into yourself
The BASIC listings and upload files are over on GitHub. I guess the old adage "any port in a storm" goes without saying - the nephew ate these up for lack of his fancy 3D FPS.
Here's a game play video of all four programs.
Notes
Bounce
The BOUNCE game is pretty basic. The ball only moves at 45 degree angles, and the angle is determined by where it is bounced off the paddle. Thus, it's possible to get it in an infinite loop and to rack up the score. The primary reason to write this game was training in TI Calculator BASIC.
Invaders
This is a fun one - a single row of space aliens animated by alternating "M" and "W" characters. There's no way in BASIC to look at the screen memory, so I had to keep track of the aliens in an array.
Scroll
Unimaginative name. I wrote the beginnings of a game like this in 6th grade on an Atari 400, but didn't understand enough to add the car logic. In this version, it took a little experimentation, but decided to only do collision detection on the nose of the car. Again, I had to keep track of the location of the track in an array. TI Calculator BASIC has list data types convenient for that.
Snake
The list datatype was extremely important for this game. In fact, I think it made it easy to program because the list length can grow just as the snake grows. There are two lists used to keep track of the row and column locations of each segment of the snake. When the snake head eats a PI symbol, one segment is added to the snake. Without lists, one would need to preallocate arrays and keep track of the tail. Maybe not that big of a deal, but there's a certain elegance in the TI Calculator BASIC solution.
Monday, October 31, 2016
Pumpkin Pi
Pumpkin with PI symbol and Arduino-controlled Neopixel LEDs
Neopixels are individually addressable RGB LEDs. I bought an 8x1 array to try them out and decided they would make a good pumpkin light. Add an Arduino Nano, 4 AA's, a capacitor and a resistor and voila. Turns out you could see them from the street quite well.
The capacitor and resistor were added per Adafruit's Best Practices page for Neopixels.
Friday, August 26, 2016
Vintage Atari CX22 Trak-Ball with USB interface.
The trackball, or Trak-Ball as Atari dubbed it, was a fascinating controller to me in the arcades. There was a mystery at work under the controller panel transferring the rolling motion of the ball to the character on the screen. They were too expensive to own for my Atari computer, so I played Centipede (not very well) at the arcade. I didn't really understand how trackballs worked until college. That's when I found out the mouse was just an upside down trackball with the optical encoder wheels and all. I recently picked up an Atari CX22 Trak-Ball at a vintage gaming show and turned it into a USB device. I plan on taking it to work to control my Mac just for the sake of irony. In the meantime, it's great for a mean game of Missile Command on the emulator.
Thank-you Dan Kramer for designing this awesome piece of hardware!
Atari made a few Trak-Ball models: the CX22 (for the 2600 and 8-bits) in the classic brown and beige colors, the CX80 with the mid-80's XL motif, and the CX53 for the 5200. The original CX22 only worked as a joystick simulator and a revision included a selector switch to allow true trackball control. This true trackball mode can be used to play Missile Command on the 8-bit computers by pressing control-T. Totally different and much closer to the arcade experience than the joystick. I put some links to trackball history and documentation at the bottom of this post - make sure you check out the concept drawings at the online Atari museum in the last link.
Thanks for stopping by!
Thank-you Dan Kramer for designing this awesome piece of hardware!
Atari made a few Trak-Ball models: the CX22 (for the 2600 and 8-bits) in the classic brown and beige colors, the CX80 with the mid-80's XL motif, and the CX53 for the 5200. The original CX22 only worked as a joystick simulator and a revision included a selector switch to allow true trackball control. This true trackball mode can be used to play Missile Command on the 8-bit computers by pressing control-T. Totally different and much closer to the arcade experience than the joystick. I put some links to trackball history and documentation at the bottom of this post - make sure you check out the concept drawings at the online Atari museum in the last link.
Decoding the Trak-Ball
The CX22 service manual gives a nice theory of operation for the trackball. The ball spins two rollers which rotate optical encoder wheels in each axis. The encoders have two signals in quadrature phase (like a sine and a cosine waves) used to determine the speed and direction. The direction is determine by finding which signal leads the other. To have a joystick mode, the CX22 does this with discrete CMOS logic chips. The quadrature square waves are fed to a CD4013B D-type Flip Flop - one signal acts as clock and the other data. When the data lags the clock, a logic 1 is latched in at the falling clock edge. When the data leads, the result is logic 0. In trackball mode, the clock and direction out are selected by the switch and sent to the controller port. In joystick mode, the clock triggers a one-shot 4538B chip configured for a 9-ms pulse width. The one-shot output gates the direction signal sent to the output. Thus, there's at least one 9-ms joystick direction pulse that is extended for each encoder wheel clock tick occurring before the 9-ms is up.USB Device
Using the HID-Project library for the Arduino, I configured my Leonardo board to act like a USB mouse. The code is incredibly simple because the direction decoding is already performed by the CX22 hardware. (The Arduino could certainly decode the signals if the clocks were sent directly - here's a tutorial with multiple software approaches to quadrature decoding. Good to keep in your back pocket for building a spinner controller.) The code polls the clock signals and sends incremental mouse movements over USB to the OS. It also polls the digital input for the fire button(s) and sends mouse clicks. For some visual feedback, I blink the on board LED every time motion is detected.
Here's my sketch:
#include <HID-Project.h>
#include <HID-Settings.h>
const int XINC = 6;
const int YINC = 6;
int xdir = LOW;
int xmot = LOW;
int ydir = LOW;
int ymot = LOW;
void setup() {
// put your setup code here, to run once:
for (int i=0; i<6; i++) {
pinMode(i,INPUT_PULLUP);
}
pinMode(13,OUTPUT);
Mouse.begin();
}
void loop() {
// poll the xmot and ymot looking for state changes
// when state change, increment mouse movement
int xold=xmot;
int yold=ymot;
ydir=digitalRead(0)*2-1;
ymot=digitalRead(1);
xdir=digitalRead(2)*2-1;
xmot=digitalRead(3);
if (xmot!=xold) {
Mouse.move(xdir*XINC,0);
digitalWrite(13,1);
}
if (ymot!=yold) {
Mouse.move(0,ydir*YINC);
digitalWrite(13,1);
}
int f=!digitalRead(4);
if (f) {
Mouse.press();
digitalWrite(13,1);
} else {
Mouse.release();
digitalWrite(13,0);
}
//Serial.println(x);
}
#include <HID-Settings.h>
const int XINC = 6;
const int YINC = 6;
int xdir = LOW;
int xmot = LOW;
int ydir = LOW;
int ymot = LOW;
void setup() {
// put your setup code here, to run once:
for (int i=0; i<6; i++) {
pinMode(i,INPUT_PULLUP);
}
pinMode(13,OUTPUT);
Mouse.begin();
}
void loop() {
// poll the xmot and ymot looking for state changes
// when state change, increment mouse movement
int xold=xmot;
int yold=ymot;
ydir=digitalRead(0)*2-1;
ymot=digitalRead(1);
xdir=digitalRead(2)*2-1;
xmot=digitalRead(3);
if (xmot!=xold) {
Mouse.move(xdir*XINC,0);
digitalWrite(13,1);
}
if (ymot!=yold) {
Mouse.move(0,ydir*YINC);
digitalWrite(13,1);
}
int f=!digitalRead(4);
if (f) {
Mouse.press();
digitalWrite(13,1);
} else {
Mouse.release();
digitalWrite(13,0);
}
//Serial.println(x);
}
Thanks for stopping by!
Trackball Links
Owner's Manual: http://www.trailingedge.com/atari8/AtariTrakBallom.pdf
Really cool concept drawings: http://www.atarimuseum.com/videogames/consoles/2600/trakconcept.html
Tuesday, August 9, 2016
Playing Kaboom! on an Atari 8-bit Emulator with Real Paddle Controllers
I'm on a quest to educate the Boy about the history of video games with the hope he'll want to learn something about computers other than downloading iOS apps or playing FPS's on Xbox. Potentiometer controllers (paddles) showed up on the earliest home systems like the Odyssey and Pong. We got to play an original Magnavox Odyssey at a Smithsonian event. That was an interesting system with multiple pot's for each player - one for moving the player and one for adding "english" for directing a shot. At another show, we played an original coin-op Pong machine - those controls are sensitive!
I'm pretty happy with the results. My code uses the full range of the paddles, butI've read the 2600 used only a limit motion range. I need to ask the 8-bit community about that. I need to trim it down to act more like the real hardware which uses only about 2/3 of the range. (Thanks to the guys over on the Atari 8-bit Computers FB group!)
I picked up a pair of Atari-style Gemini paddles for $5 at that gaming show and hooked them up to my Windows machine using an Arduino Leonardo running a gamepad HID library. The boy was intrigued because they are so different from an Xbox controller or a mouse; and I got him to play Kaboom! on an Atari 800XL emulator. Definitely different from Breakout or Pong:
Tell me that's not at least as fun as many casual iOS games. I really like the reveal of the 1812 Overture as one advances in the game. Other paddle games are discussed over at AtariAge.
My $5 deal:
My $5 deal:
Technical Details
The paddles are 1-megaohm linear potentiometers tied to 5V. To measure the resistance, the original Atari system would measure how long it took to charge a capacitor. It actually counted the number of TV scan lines. The higher the resistance, the longer it took. This was a straightforward way to do it with a purely digital readout system and the 8-bits have a custom POKEY chip that does the work. (The POKEY designer Doug Neubauer tells a couple entertaining stories.) This is called an analog-to-digital converter by many - it is, but not in the sense of modern ADC's. It's a current-measuring ADC (with the current source being the potentiometer series resistance biased by +5V) vs. a typical voltage-measuring ADC. Many modern microcontrollers have voltage ADC's built in, so my implementation uses a voltage divider instead of an RC charging circuit. Despite some erroneous circuit diagrams found on the interwebs, the pot in the paddle has one end floating. Thus, the readout circuit needs an external drop resistor to make up the voltage divider. Here's my circuit:
The microcontoller is a Leonardo because it runs an ATmega 32u4 microcontroller with an on board USB interface. Two ADC channels are used to read the voltages from the dividers. Digital inputs D0 and D1 are used to read the trigger buttons. The software relies on the HID Project. I started using the absolute mouse device, but switched to the 16-bit axes on the gamepad device. My method isn't new; however, I added some integer math to compute the resistance value from the voltage that I haven't seen elsewhere. Otherwise, the reading will be highly nonlinear. Atari went out of their way to use linear potentiometers in the design (vs. logarithmic which are used in audio volume controls), so it's only fitting to keep the system linear. The code also includes a startup calibration to find the minimum voltage when the paddle is turned completely counter-clockwise.
Here's my sketch:
#include <HID-Project.h>
#include <HID-Settings.h>
int oldx;
int x=0;
int calx=0;
int oldy;
int y=0;
int caly=0;
int readpaddle(int p) {
int a=0;
for (int x=0; x<32; x++) {
a+=analogRead(p);
}
return a;
}
int paddlemap(int a) {
return 65535-uint32_t(uint32_t(1072693248)/uint32_t(a));
}
void setup() {
// put your setup code here, to run once:
pinMode(0,INPUT_PULLUP);
pinMode(1,INPUT_PULLUP);
delay(2000);
Serial.begin(9600);
Serial.println("calibrating");
for (int i=0; i<10; i++) {
int temp=paddlemap(readpaddle(0));
calx=max(temp,calx);
Serial.println(calx);
temp=paddlemap(readpaddle(1));
caly=max(temp,caly);
Serial.println(caly); }
Gamepad.begin();
}
void loop() {
boolean changed=false;
oldx=x;
oldy=y;
int tempx=paddlemap(readpaddle(0));
tempx=max(tempx,calx);
x=map(tempx,calx,32767,-32768,32767);
delay(7); //need to clear residual from ADC and limit update rate
int tempy=paddlemap(readpaddle(1));
tempy=max(tempy,caly);
y=map(tempy,caly,32767,-32768,32767);
if (abs(x-oldx)>63 || abs(y-oldy)>63){
changed=true;
}
Gamepad.releaseAll();
if (!digitalRead(0)){
Gamepad.press(1);
changed=true;
}
if (!digitalRead(1)){
Gamepad.press(2);
changed=true;
}
if (changed){
Gamepad.xAxis(x);
Gamepad.yAxis(y);
Gamepad.write();
}
//Serial.println(x);
}
I'm pretty happy with the results. My code uses the full range of the paddles, but
Thursday, July 21, 2016
Summer Retro Game and Movie List - An Abridged Armada Chronology
While more cerebral types publish summer reading lists (you know who you are), I'm opting for an easier way out. Summer is half over and there's not enough time to squeeze in all that reading if you haven't started yet! Instead I offer a list of retro video games and 80's and 90's movies based on a list found in Ernst Cline's Armada, which was compiled by the protagonist's lost father under the heading of "Chronology." In the story, the Chronology is provided as circumstantial evidence pointing to a vast government-entertainment complex conspiracy to covertly prepare and train the populace to fight in a coming alien invasion. Sounds plausible. But even if you don't subscribe, that's no excuse to avoid this historical retrospective in the last few remaining weeks of summer.
I'm updating the list with my thoughts, experiences and links as I work my way through it. If you play along, please leave me a comment on the list page.
I'm updating the list with my thoughts, experiences and links as I work my way through it. If you play along, please leave me a comment on the list page.
Sunday, June 26, 2016
Lightsaber Crystals
This is a pseudo guest post by my son (aka "the Boy"). He and a friend were in the backyard dueling with their "build-your-own" lightsabers from Disney World. In an epic exchange, one of the lightsabers fell apart and a little door popped open revealing three plastic crystals in the flashlight handle. When you pull them out in different combinations, the energy vibration sound changes. We've had these toys for a few years and this is the first we've known of this. There are a few comments on blogs around the web, but not a whole lot of info is out there. "Dad, will you put this on your blog so we can get the word out?" So here we are.
To find the crystals, take off the grip pieces from the flashlight handle, like you're going to change the batteries. Right next to the battery compartment, is the crystal chamber. Here's step-by-step photographs showing the sequence:
The crystals pull out by the top edge where there's a little ridge. They go back in easily the same way. If you have trouble reinserting them, rotate them around and make sure they are in the right slot. It's not too hard to force them in the wrong way making it difficult to pull them back out. Here's a close up of the compartment with and without the crystals:
Extra credit if you draw the logic circuit to implement the truth table.
To find the crystals, take off the grip pieces from the flashlight handle, like you're going to change the batteries. Right next to the battery compartment, is the crystal chamber. Here's step-by-step photographs showing the sequence:
The crystals pull out by the top edge where there's a little ridge. They go back in easily the same way. If you have trouble reinserting them, rotate them around and make sure they are in the right slot. It's not too hard to force them in the wrong way making it difficult to pull them back out. Here's a close up of the compartment with and without the crystals:
With three crystals, there are eight possible combinations, but turns out there are only three different sounds. Here's a truth table with links to the sound files.
Saturday, June 11, 2016
HO Trains and DCC++: Part 2
Controlling a DCC++ base station using JMRI and WiThrottle on an iPad.
At breakfast this morning the Boy asked, “Dad, can we fix Gordon today?” Gordon, from Thomas and Friends, didn’t work when the trains were rediscovered. So today, we took him apart to diagnose the problem. He simply wouldn’t run. Turns out the fix was easy: the contact leafs that rub against the drive wheels to pick up power needed a little extra bending to make a low resistance connection. Once we did that, we also had to run him out a little - I suppose the lubricant in the gear box needed to be redistributed. That and little machine oil and he was up and going. The Boy wanted to put this on the blog. Here is a picture of Gordon in a state of disassembly.
Last time, I was setting up a DCC++ base station to control our one DCC locomotive. Since then, I installed JMRI on a netbook running Xubuntu to have some better control. JMRI is a Java-based train control system that supports DCC++ for output. My netbook was running XP and wouldn't support a recent JMRI version, so I installed Xubuntu as a dual boot. JMRI installation on Xubuntu was pretty straightforward and I also have it auto-starting now on login. It has a server for network control, to which an iOS app called WiThrottle (free version) can connect. Installed that on an iPad and now can control the locomotive over wifi.
Not a bad outcome.
The next challenge is sound on board.
At breakfast this morning the Boy asked, “Dad, can we fix Gordon today?” Gordon, from Thomas and Friends, didn’t work when the trains were rediscovered. So today, we took him apart to diagnose the problem. He simply wouldn’t run. Turns out the fix was easy: the contact leafs that rub against the drive wheels to pick up power needed a little extra bending to make a low resistance connection. Once we did that, we also had to run him out a little - I suppose the lubricant in the gear box needed to be redistributed. That and little machine oil and he was up and going. The Boy wanted to put this on the blog. Here is a picture of Gordon in a state of disassembly.
Last time, I was setting up a DCC++ base station to control our one DCC locomotive. Since then, I installed JMRI on a netbook running Xubuntu to have some better control. JMRI is a Java-based train control system that supports DCC++ for output. My netbook was running XP and wouldn't support a recent JMRI version, so I installed Xubuntu as a dual boot. JMRI installation on Xubuntu was pretty straightforward and I also have it auto-starting now on login. It has a server for network control, to which an iOS app called WiThrottle (free version) can connect. Installed that on an iPad and now can control the locomotive over wifi.
Not a bad outcome.
The next challenge is sound on board.
Monday, June 6, 2016
HO Trains and DCC++: Part 1
The Boy has rediscovered the model trains. Several years ago we started by resurrecting my 1990’s N-scale layout, which is 2’-x-4’ and could slide under the couch. This was followed by Dad’s post-war Lionel O-27, which was augmented by modern Fast Track and my 1970’s banjo crossing. The Fast Track was a game changer - the stuff works perfectly. It’s reliable, easy to join and doesn’t cause derailing. This was the first time the young-version Boy didn’t get frustrated while running trains. We added 21st century Thomas with a whistle. Finally we added an HO scale Thomas and Friends set. He was able to put together the track with this set himself. It also made several airplane trips in a carry-on back and forth to the Grandparents. But, then the XBox replaced the trains.
Most recently, the Boy pulled out the HO scale trains, built his own layout, and began running a Union Pacific (UP) diesel, boxcar and caboose. He’s using my 1980’s power supply, which was advanced at the time because it had a low-pass filter on the throttle to simulate gradual acceleration and deceleration of the train. Modern control, however, uses Digital Command and Control (DCC). The UP diesel has a DCC decoder. When we bought it, I figured I’d buy a command and base station soon after, but never found the opportune time.
Then last month the Boy has been talking about DCC nonstop and then he discovered locomotives with a Bluetooth interface. Time to do something about it. After researching DCC and reading the NMRA standard, I realized it could be implemented on an Arduino. Using my Google foo, lo and behold, I stumbled upon DCC++. I didn’t think it was the right time to invest in trains with dedicated Bluetooth interface and I have a couple Arduino Unos sitting around - this is the ticket.
Most recently, the Boy pulled out the HO scale trains, built his own layout, and began running a Union Pacific (UP) diesel, boxcar and caboose. He’s using my 1980’s power supply, which was advanced at the time because it had a low-pass filter on the throttle to simulate gradual acceleration and deceleration of the train. Modern control, however, uses Digital Command and Control (DCC). The UP diesel has a DCC decoder. When we bought it, I figured I’d buy a command and base station soon after, but never found the opportune time.
Then last month the Boy has been talking about DCC nonstop and then he discovered locomotives with a Bluetooth interface. Time to do something about it. After researching DCC and reading the NMRA standard, I realized it could be implemented on an Arduino. Using my Google foo, lo and behold, I stumbled upon DCC++. I didn’t think it was the right time to invest in trains with dedicated Bluetooth interface and I have a couple Arduino Unos sitting around - this is the ticket.
Install DCC++
The DCC++ base station code can be downloaded from GitHub here. I used the standard Arduino IDE v1.6.5 to install it. The first time around compiling, it failed with the error:
Accessories.cpp:66:20: fatal error: EEPROM.h:
No such file or directory #include <EEPROM.h>
Turns out in Arduino, all #includes no matter where they are used must be declared in the .ino file. I learned this interesting tidbit here. After adding #include <EEPROM.h> to the .ino file it works fine. I posted an issue to the GitHub site, so I imagine it'll be fixed. This is the first time I've contributed to someone else's code, although it is a pretty minor bug fix.
Prepare the Arduino Motor Shield
Per instructions here, I cut the indicated traces. The picture below shows the Vin trace cut - this disconnects the external power supply used to drive the motors through the motor shield from the Vin pin on the Arduino.
There are also a couple of jumpers to be installed. The final configuration is shown below.
Test the Uno
Before powering the motor shield, much less trying to control a train, I wanted to make sure I could talk to the DCC++ software on the Uno. Using Hyperterminal, I connected to the Arduino and sent the <s> status command. The response is shown in the picture.
Test the motor shield
Now that the software was working, I wanted to test the shield before using a train. I measured the voltage out when powered on. Using the <1> command turns the power on, which should be a 5-kHz square wave with almost 12-V amplitude (there might be some voltage drop in the driver chip). Note the LED’s by the terminal block are now also illuminated greenish-yellow. My digital volt meter is pretty decent at measuring root-mean-square (RMS) AC voltage and indicated 7.9 V for an expected 12/sqrt(2) = 8.5 V. (9/21/16: I was re-reading this and realized this statement is not correct. The RMS of a square wave is the amplitude. The sqrt(2) factor is for a sine wave. The low voltage reading on the voltmeter could be due it's frequency response. Or, it's possible there's a lot of loss in the driver. I need a scope to diagnose it.) That’ll do.
First run - throttle control
Now it was finally time to try it out. The DCC++ wiki gives an example command to move the train: <t 1 03 20 1>. This command tells locomotive “03,” which is the default encoder address, to move forward at speed 20. It worked. It worked without any fiddling. It’s nice when things just work. Though it is kind of anticlimactic.
In Part 2, I’ll talk about our experience setting up iPad control.
Saturday, May 21, 2016
ADC IIR LPF SPI TFT LCD GUI - Part 1
I have a project in which I need to read a signal, do some processing and display the results using a bar graph. After some fiddling with an Arduino UNO and researching the AVR microcontrollers they use, I settled on a two-MCU architecture. I use an Arduino Nano to acquire and process the data and an Arduino Mega to display the results and interface with the user. How many acronyms can be squeeze into a title?
ADC - analog-to-digital converter
IIR - infinite impulse response
LPF - low pass filter
SPI - serial peripheral interface
TFT - thin film transistor
LCD - liquid crystal display
GUI - graphical user interface
My first problem: how to get two Arduino’s to chat with each other. My work is based upon (copied from) a thorough and accurate forum post. The motivation to use SPI came from wanting to learn more about it. In researching the TVout library, I found someone generated an NTSC signal with the hardware based SPI master signal. Given my current fascination with resurrecting my Atari days, I had an idea of trying to recreate a simplified ANTIC-like system on an Arduino in black and white. SPI seems the way to go there. But, back to my project.
The system here uses a Nano to read an analog voltage and apply a low pass filter. The Mega polls the Nano for the LPF output over SPI and displays the value on a bar graph. I offloaded the ADC work to the Nano thinking I would eventually use an interrupt-driven ADC to get a constant sampling rate. Because the TFT polls various analog channels to read the touchscreen, it seemed best to just off-load the other ADC work to a different microcontroller. The TFT fits on the Mega and leaves the header with the SPI interface unobstructed.
The SPI connections on the Nano are located on the ICSP header or pins D11-D13 and Slave Select is D10. The connections on the Mega are on the bottom header pins D50-D53. Connections are one-to-one. That is MISO:MISO, MOSI:MOSI, SCK:SCK, and SS:SS. The Master-In-Slave-Out MISO signal transfers data from the Nano to the Mega because the Nano is set up as SPI Slave and the Mega as Master. Vice-versa for MOSI. Serial Clock (SCK) sends a 2 MHz clock from the Mega to the Nano. And Slave Select (SS) signals the Nano to listen on SCK & MOSI for data and to send data on MISO synchronized to SCK. I use 2 Mbps because single-ended communications over long wires can’t go all that fast.
To low-pass filter the data, I use an exponential moving average filter implemented as an infinite impulse response (IIR) digital filter. This is very efficient because it requires only a weighted average of the current sample with the previous output. For a C++ implementation, I followed the integer implementation here. I had to add some additional 64-bit integer type casting to the coefficients in the filter equation to get it to operate correctly.. I also changed how the filter coefficient is implemented as a 16-bit unsigned integer (0-65535). The original author designed it to represent floats ranging from 1/65535 to 1 and I changed it to the range 0 to 65535/65536. Just personal preference. You can see the effect of the filter in the video - I adjust the potentiometer abruptly and it takes some time for the bargraph to totally respond.
ADC - analog-to-digital converter
IIR - infinite impulse response
LPF - low pass filter
SPI - serial peripheral interface
TFT - thin film transistor
LCD - liquid crystal display
GUI - graphical user interface
My first problem: how to get two Arduino’s to chat with each other. My work is based upon (copied from) a thorough and accurate forum post. The motivation to use SPI came from wanting to learn more about it. In researching the TVout library, I found someone generated an NTSC signal with the hardware based SPI master signal. Given my current fascination with resurrecting my Atari days, I had an idea of trying to recreate a simplified ANTIC-like system on an Arduino in black and white. SPI seems the way to go there. But, back to my project.
The system here uses a Nano to read an analog voltage and apply a low pass filter. The Mega polls the Nano for the LPF output over SPI and displays the value on a bar graph. I offloaded the ADC work to the Nano thinking I would eventually use an interrupt-driven ADC to get a constant sampling rate. Because the TFT polls various analog channels to read the touchscreen, it seemed best to just off-load the other ADC work to a different microcontroller. The TFT fits on the Mega and leaves the header with the SPI interface unobstructed.
The SPI connections on the Nano are located on the ICSP header or pins D11-D13 and Slave Select is D10. The connections on the Mega are on the bottom header pins D50-D53. Connections are one-to-one. That is MISO:MISO, MOSI:MOSI, SCK:SCK, and SS:SS. The Master-In-Slave-Out MISO signal transfers data from the Nano to the Mega because the Nano is set up as SPI Slave and the Mega as Master. Vice-versa for MOSI. Serial Clock (SCK) sends a 2 MHz clock from the Mega to the Nano. And Slave Select (SS) signals the Nano to listen on SCK & MOSI for data and to send data on MISO synchronized to SCK. I use 2 Mbps because single-ended communications over long wires can’t go all that fast.
To low-pass filter the data, I use an exponential moving average filter implemented as an infinite impulse response (IIR) digital filter. This is very efficient because it requires only a weighted average of the current sample with the previous output. For a C++ implementation, I followed the integer implementation here. I had to add some additional 64-bit integer type casting to the coefficients in the filter equation to get it to operate correctly.. I also changed how the filter coefficient is implemented as a 16-bit unsigned integer (0-65535). The original author designed it to represent floats ranging from 1/65535 to 1 and I changed it to the range 0 to 65535/65536. Just personal preference. You can see the effect of the filter in the video - I adjust the potentiometer abruptly and it takes some time for the bargraph to totally respond.
Master Code
// Jeff Piepmeier - May 2016
//
// Main program adapted from SPI demo
// at http://www.gammon.com.au/spi
// by Nick Gammon April 2011
//set up TFT display
#include <SPFD5408_Adafruit_GFX.h>
#include <SPFD5408_Adafruit_TFTLCD.h>
#include <SPI.h>
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
// variables for bar graph
int newHeight;
int oldHeight = 0;
int heightDiff;
void setup (void)
{
// Serial.begin (115200);
// Serial.println ("SPI demo");
// SS = slave select, built in AVR output pin reference
digitalWrite(SS, HIGH); // ensure SS stays high for now
tft.reset();
tft.begin(0x9341);
tft.fillScreen(0x0000); // make the screen black
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin ();
// Slow down the master a bit
//SPI.setClockDivider(SPI_CLOCK_DIV4);
// use 2 Mbps decided by testing. 4 Mbps has too many bit errors over
// jumper wires. Single-ended signals are not well suited for high-speed over wires
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
} // end of setup
void loop (void)
{
byte a=0; // variable to store SPI input data from Nano
// get a value from the SPI - we're the master so have to ask for it from slave
// enable Slave Select
digitalWrite(SS, LOW);
a = SPI.transfer ('a');
// disable Slave Select
digitalWrite(SS, HIGH);
newHeight=(int)(((long)a*319)/256); // hardcode display height of 320 lines
heightDiff = oldHeight-newHeight; // only draw new part of bar graph for faster display
if (heightDiff>0) { tft.fillRect(80, newHeight+1, 80, heightDiff+1, 0x0000); }
else if (heightDiff<0) { tft.fillRect(80, oldHeight-1, 80, -heightDiff+1, 0xFFFF); }
oldHeight=newHeight; // remember how high bar is
// Serial.println (a, DEC);
} // end of loop
//
// Main program adapted from SPI demo
// at http://www.gammon.com.au/spi
// by Nick Gammon April 2011
//set up TFT display
#include <SPFD5408_Adafruit_GFX.h>
#include <SPFD5408_Adafruit_TFTLCD.h>
#include <SPI.h>
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
// variables for bar graph
int newHeight;
int oldHeight = 0;
int heightDiff;
void setup (void)
{
// Serial.begin (115200);
// Serial.println ("SPI demo");
// SS = slave select, built in AVR output pin reference
digitalWrite(SS, HIGH); // ensure SS stays high for now
tft.reset();
tft.begin(0x9341);
tft.fillScreen(0x0000); // make the screen black
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin ();
// Slow down the master a bit
//SPI.setClockDivider(SPI_CLOCK_DIV4);
// use 2 Mbps decided by testing. 4 Mbps has too many bit errors over
// jumper wires. Single-ended signals are not well suited for high-speed over wires
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
} // end of setup
void loop (void)
{
byte a=0; // variable to store SPI input data from Nano
// get a value from the SPI - we're the master so have to ask for it from slave
// enable Slave Select
digitalWrite(SS, LOW);
a = SPI.transfer ('a');
// disable Slave Select
digitalWrite(SS, HIGH);
newHeight=(int)(((long)a*319)/256); // hardcode display height of 320 lines
heightDiff = oldHeight-newHeight; // only draw new part of bar graph for faster display
if (heightDiff>0) { tft.fillRect(80, newHeight+1, 80, heightDiff+1, 0x0000); }
else if (heightDiff<0) { tft.fillRect(80, oldHeight-1, 80, -heightDiff+1, 0xFFFF); }
oldHeight=newHeight; // remember how high bar is
// Serial.println (a, DEC);
} // end of loop
Slave Code
// Main program is adapted from SPI demo at
// http://www.gammon.com.au/spi
// Written by Nick Gammon
// April 2011
//
// IIR Exponential Moving Average (EMA) Low Pass Filter (LPF)
// adapted from C++ code at
// http://stratifylabs.co/embedded%20design%20tips/2013/10/04/Tips-An-Easy-to-Use-Digital-Filter/
// filter coefficient float-to-uint16 conversion - min 0, max x=1 means 65535/65536=.9999847
#define DSP_EMA_I32_ALPHA(x) ( (uint16_t)(x * 65535) )
volatile byte command = 0;
volatile byte out1 = 0;
void setup (void)
{
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
digitalWrite(MISO, LOW); //ensure is low to start
// turn on SPI in slave mode
SPCR |= _BV(SPE);
// turn on interrupts
SPCR |= _BV(SPIE);
} // end of setup
//http://stratifylabs.co/embedded%20design%20tips/2013/10/04/Tips-An-Easy-to-Use-Digital-Filter/
int32_t dsp_ema_i32(int32_t in, int32_t average, uint16_t alpha){
int64_t tmp0;
tmp0 = (int64_t)in * (int64_t)(alpha) + (int64_t)average * (int64_t)(65536 - alpha);
return (int32_t)((tmp0 + 32768) / 65536);
}
// SPI interrupt routine
ISR (SPI_STC_vect)
{
command = SPDR; // not yet used here
SPDR = out1;
} // end of interrupt service routine (ISR) SPI_STC_vect
void loop (void)
{
uint16_t adcReading = 0;
static int32_t avg1 = 0;
adcReading=analogRead(0); //10 bit unsigned, shift up to 31 bits for signed long int
avg1=dsp_ema_i32( (int32_t)adcReading << 21, avg1, DSP_EMA_I32_ALPHA(0.0005));
out1=byte(avg1 >> 23 ); // shift down to single byte
} // end of loop
Sunday, March 27, 2016
Easter 3D Print (and Splitting a 3D Model in Half)
Holidays are a good excuse to find something whimsical to 3D print. I found this pair of egg legs over on Thingiverse and chuckled. The legs are pose-able and hold an egg. Wife sketched an Humpty Dumpty on an egg and suggested I shoot the model in the garden. He can stand (above) or sit (below).
When I loaded up the model, I discovered the pieces would be difficult to print. Turns out the original designer is a grad student at MIT Media Lab and has access to a fancy industrial grade fabricator. On hobby printers, 3D objects are easiest to print when they have one flat face to serve as a base. For example, pyramids are easy to print and good for testing a printer setup. Otherwise, when an object has a part that hangs in mid air, the printing software can insert extra material for support. Sometimes though, some objects are just unprintable.
This model has 5 pieces: feet, lower legs, left & right upper legs, and a torso. The feet were easy. The lower legs printed OK with support. I rotated the upper legs 90 degrees and printed them with support. The "egg holding torso" print, however, failed miserably. To solve the problem, I was able to make my own flat face on the object by slicing it in half, printing the two halves, and super-gluing them together.
Surprisingly, there are few solutions for cutting 3D model objects in half to be googled-up. I ended up using Meshmixer, with which I've had varying success in the past. This time, however, it worked perfectly. Import the STL file and select "plane cut" under the "edit" menu. The default plane was oriented exactly along the major axes, splitting the part in half easily.
Print two, glue, build, pose, photograph, write a blog post. Happy Easter.
When I loaded up the model, I discovered the pieces would be difficult to print. Turns out the original designer is a grad student at MIT Media Lab and has access to a fancy industrial grade fabricator. On hobby printers, 3D objects are easiest to print when they have one flat face to serve as a base. For example, pyramids are easy to print and good for testing a printer setup. Otherwise, when an object has a part that hangs in mid air, the printing software can insert extra material for support. Sometimes though, some objects are just unprintable.
This model has 5 pieces: feet, lower legs, left & right upper legs, and a torso. The feet were easy. The lower legs printed OK with support. I rotated the upper legs 90 degrees and printed them with support. The "egg holding torso" print, however, failed miserably. To solve the problem, I was able to make my own flat face on the object by slicing it in half, printing the two halves, and super-gluing them together.
Surprisingly, there are few solutions for cutting 3D model objects in half to be googled-up. I ended up using Meshmixer, with which I've had varying success in the past. This time, however, it worked perfectly. Import the STL file and select "plane cut" under the "edit" menu. The default plane was oriented exactly along the major axes, splitting the part in half easily.
Print two, glue, build, pose, photograph, write a blog post. Happy Easter.
Sunday, March 20, 2016
GRAVITEN (An Atari Gravitar rip-off in 10 lines of BASIC)
UPDATE April 3, 2016: 2nd Place!
GRAVITEN is my second entry to the 2016 NOMAM 10-liner BASIC game competition. Like NUTS!, this one is in the PUR-120 category. It's inspired by Gravitar's Red Planet 3 stage by Atari. I got the idea last Friday night playing Gravitar on my Atari 2600 10-in-1 joystick game. I had tried playing it as a kid, but found it too hard. Download my version here and run it on the Altirra emulator (800XL). (Direct link to ATR file.) If you play, please post your high-score in the comments!
Pilot your ship around the spiral to the planet's core. Reach the core and advance to the next level. Complete each level quickly to maximize points. But, be careful! Touch the wall and you lose a life.You get 3 lives to start and 1 additional life per level. Even numbered levels have atmospheric drag (indicated by gray background in the text window) and odd are in vacuum (indicated by black background). Gravity increases every two levels, starting at zero. Play through all 8 levels to complete the mission. Controls: up to thrust, back to flip, left/right to rotate.
The Code
0DIMA$(96):A$=" ......................... {a whole bunch of ATASCII} ......................... ":A=.39269908:DIMA(1,15)
1F.B=0TO15:A(0,B)=SIN(A*B):A(1,B)=COS(A*B):N.B:GR.8:GR.5:POKE752,1:SE.0,3,4:SE.2,0,4:C.1:PAI.0,0:C.0:C=580:?"GRAVITEN"
2?"Stand by ";:A=.0174532925:F.B=0TOC STEP9:D=38*B/C*COS(B*A)+45:E=25*B/C*SIN(B*A)+15:F.F=1TO5:CI.D,E,F:N.F:?".";:N.B
3CLS:C.1:PL.0,0:DR.79,0:PL.19,39:DR.79,39:C.2:CI.46,15,1:G=44032:M.ADR(A$),G,80:POKE704,15:POKE54279,G/256:POKE53277,3
4POKE559,46:DO:?:?,"press FIRE to start";:W.STRIG(0):WE.:CLS:H=1e3:I=0:J=3:K=70:L=18:M=8:N=0:O=0:?:?I,J;:W.J:POKE657,22
5?H;" ";:M.G+512,G+513,127:M.M*5+G,G+512+INT(L),5:POKE53248,INT(K):P=Q:Q=STICK(0):R=(Q&4=4)-(Q&8=8):S=(P&2=2)&(Q&2=0)
6T=Q&1=0:M=(M+16+R+8*S)MOD16:IFT:SO.0,250,10,10:N=A(0,M)*.05+N:O=A(1,M)*.05+O:END.:IFI MOD2=0:N=N-.01*N:O=O-.01*O:END.
7N=I DIV2*2e-5*(135-K)+N:O=O-I DIV2*2e-5*(44-L):K=K+N:L=L-O:POKE53278,1:PA.1:U=PEEK(53252):IFU:K=70:L=18:M=8:N=0:O=0
8SO.0,50,U*8-6,15:PA.9:CLS:IFU=1:J=J-1:IFJ=0:?,"TRY AGAIN":END.:END.:IFU=2:H=(I+1)*1e3+H:IFI=7:?,"MISSION COMPLETE!":EX.
9END.:I=I+1:J=J+1:SE.0,I*2+3,4:SE.2,0,4-I MOD2*4:END.:?:?I,J,H;:END.:SO.0,0,0,0:H=H-1:WE.:?,"SCORE:";H:SO.0,0,0,0:LOOP
1F.B=0TO15:A(0,B)=SIN(A*B):A(1,B)=COS(A*B):N.B:GR.8:GR.5:POKE752,1:SE.0,3,4:SE.2,0,4:C.1:PAI.0,0:C.0:C=580:?"GRAVITEN"
2?"Stand by ";:A=.0174532925:F.B=0TOC STEP9:D=38*B/C*COS(B*A)+45:E=25*B/C*SIN(B*A)+15:F.F=1TO5:CI.D,E,F:N.F:?".";:N.B
3CLS:C.1:PL.0,0:DR.79,0:PL.19,39:DR.79,39:C.2:CI.46,15,1:G=44032:M.ADR(A$),G,80:POKE704,15:POKE54279,G/256:POKE53277,3
4POKE559,46:DO:?:?,"press FIRE to start";:W.STRIG(0):WE.:CLS:H=1e3:I=0:J=3:K=70:L=18:M=8:N=0:O=0:?:?I,J;:W.J:POKE657,22
5?H;" ";:M.G+512,G+513,127:M.M*5+G,G+512+INT(L),5:POKE53248,INT(K):P=Q:Q=STICK(0):R=(Q&4=4)-(Q&8=8):S=(P&2=2)&(Q&2=0)
6T=Q&1=0:M=(M+16+R+8*S)MOD16:IFT:SO.0,250,10,10:N=A(0,M)*.05+N:O=A(1,M)*.05+O:END.:IFI MOD2=0:N=N-.01*N:O=O-.01*O:END.
7N=I DIV2*2e-5*(135-K)+N:O=O-I DIV2*2e-5*(44-L):K=K+N:L=L-O:POKE53278,1:PA.1:U=PEEK(53252):IFU:K=70:L=18:M=8:N=0:O=0
8SO.0,50,U*8-6,15:PA.9:CLS:IFU=1:J=J-1:IFJ=0:?,"TRY AGAIN":END.:END.:IFU=2:H=(I+1)*1e3+H:IFI=7:?,"MISSION COMPLETE!":EX.
9END.:I=I+1:J=J+1:SE.0,I*2+3,4:SE.2,0,4-I MOD2*4:END.:?:?I,J,H;:END.:SO.0,0,0,0:H=H-1:WE.:?,"SCORE:";H:SO.0,0,0,0:LOOP
There are a couple of things worth pointing out in this code: the spiral path generation (in line 2) and the ship dynamics (in lines 6 & 7). The path is carved out of a solid field of color - this allows me to use missile-playfield collision detection to find out when the ship hits the wall. A TurboBASIC XL PAINT command is used to fill the screen. The spiral path is generated using a pair of parametric equations describing the polar coordinate equation r = aΘ. For each point on the curve, five concentric circles are drawn in the background color, which erase the foreground to create the path. A couple of lines are drawn to repair the top and bottom and to enclose the whole payfield in COLOR 1. Finally, a small box is plotted with the CIRCLE command in COLOR 2 for the goal.
The ship dynamics are borrowed from the earliest video games (Spacewar! and much later Asteroids), which based their game play on similar physics. Those two and other games used thrusters, gravity and drag in their game play. In Gravitar, there's gravity located in different places depending upon the scene. Here, the gravity is centered on the core (goal) and approximated so that it weakens as one progresses into the planet. The strength of the gravity is scaled by the level so higher levels become increasingly more difficult. I made it fairly weak because I'm not a very good gamer. In Asteroids, the ship encounters atmospheric drag - maybe it's the dust and debris ablated from the main rocks. I enable drag every other level. It's slightly easier to play with drag - it acts as a damper to over zealous thrust. You can almost point the ship where you want to go and hit the thursters. Without drag, once the ship starts moving in a direction, it doesn't change until a counter acting thrust is applied or gravity is present.
Thanks for reading.
Thanks for reading.
Sunday, February 28, 2016
NUTS! - Atari BASIC 10-Liner Contest 2016 Entry
Update April 3, 2016: 3rd Place!
Climb the trees, jump to gather acorns, but beware the blue jays! Earn points by climbing (press fire) and jumping left and right (move the joystick) to collect acorns, which only fall when you are climbing. The acorns are worth more the higher you climb, but there are more blue jays, too. The game is over when you run into a blue jay. Your high-score is recorded so you can try and beat it the next round. Grab the ATR here and play it on your favorite emulator (I use Altirra).
This is only my second BASIC program in modern times and is my entry to the NOMAM 2016 BASIC 10-Liner Contest. I like the 10-line constraint and find it a fun (for now) challenge to do my own code optimization squeezing it into 10 x120-character lines. For this program, I wrote it up in stages, getting each feature to work. I saved doing the pixel art for near the end. In the middle of development, I tested on the boy. He made me remove a timer:
So, I removed the countdown timer and added an algorithm to increase difficulty with advancement. (More birds the more you climb.) After I compressed it down with single-character variable names, abbreviated statements, and some hand-optimized coding, I had space left over. I added sound effects in the remaining character count. I was surprised by how much more enjoyable the game play was with sound effects. Read on for the code. Here's a video of the final product.
"Dad, gamers hate timers in a runner, which is what this is."
Here's the code listing in 10 lines each 120 characters or fewer:
0 DIMS$(76):S$="{...a mess of ATASCII...}":Q=ADR(S$):R=PEEK(106)
1 POKE106,R-8:GR.1:POKE106,R:CLS:POKE54279,R-4:POKE559,46:DP.53256,257:DP.53258,257:POKE53277,3:DP.708,$12C4:P=(R-4)*256
2 W=53248:DP.W,$6868:DP.W+2,$7888:DP.704,$850A:DP.706,$1D85:M.Q+64,DPEEK(560)+7,12:M.57344,(R-8)*256,1024:POKE756,R-8
3 M.Q+56,(R-8)*256+264,8:?#6;"NUTS!","HI:";J:?#6;"SCORE:":F.X=0TO10:?#6;" aaaaaa aaaaaa":N.X:F=1:S=16:H=104:B=1:E=0
4 D=12:G=16:K=0:Z=1:DO:POKE53278,1:A=2*STRIG(0):IFF=0:F=PTRIG(0)-PTRIG(1):END.:IFA=0:-M.P+664,P+666,78:-M.P+792,P+794,78
5 SO.1,0,Z,2:Z=Z=0:K=K+1:S=S-4:IFS<0:S=15:END.:IFB:D=D-2:M.Q+22-B*6+D,P+728-64*B,2:B=B*(D>0):EL.:B=(RND>(1-L))-(RND<L)
6 D=12:L=K/5E3:END.:END.:IFE:G=G-4+A:M.Q+40+G,P+920,4-A:E=G>0:EL.:G=16:E=(2-A)*(RND>.9):END.:-M.P+920,P+924-A,78:IFF=-1
7 IFH=104:F=0:EL.:H=H-8:M.Q,P+589,8:END.:END.:IFF=1:IFH=136:F=0:EL.:H=H+8:M.Q+8,P+589,8:END.:END.:POKEW,H:SO.0,0,0,0
8 X=PEEK(53260):SO.1,0,0,0:IFX&8:SO.0,50,10,15:K=K+K DIV 5:-M.P,P+964,28:ELSE:IFX&6:EXIT:END.:END.:POS.6,1:?#6;K;
9 SO.2,H,10+(F=0),6:PAUSE 0:POKE54277,S:LOOP:IFK>J:J=K:END.:SO.2,0,0,0:G.1
1 POKE106,R-8:GR.1:POKE106,R:CLS:POKE54279,R-4:POKE559,46:DP.53256,257:DP.53258,257:POKE53277,3:DP.708,$12C4:P=(R-4)*256
2 W=53248:DP.W,$6868:DP.W+2,$7888:DP.704,$850A:DP.706,$1D85:M.Q+64,DPEEK(560)+7,12:M.57344,(R-8)*256,1024:POKE756,R-8
3 M.Q+56,(R-8)*256+264,8:?#6;"NUTS!","HI:";J:?#6;"SCORE:":F.X=0TO10:?#6;" aaaaaa aaaaaa":N.X:F=1:S=16:H=104:B=1:E=0
4 D=12:G=16:K=0:Z=1:DO:POKE53278,1:A=2*STRIG(0):IFF=0:F=PTRIG(0)-PTRIG(1):END.:IFA=0:-M.P+664,P+666,78:-M.P+792,P+794,78
5 SO.1,0,Z,2:Z=Z=0:K=K+1:S=S-4:IFS<0:S=15:END.:IFB:D=D-2:M.Q+22-B*6+D,P+728-64*B,2:B=B*(D>0):EL.:B=(RND>(1-L))-(RND<L)
6 D=12:L=K/5E3:END.:END.:IFE:G=G-4+A:M.Q+40+G,P+920,4-A:E=G>0:EL.:G=16:E=(2-A)*(RND>.9):END.:-M.P+920,P+924-A,78:IFF=-1
7 IFH=104:F=0:EL.:H=H-8:M.Q,P+589,8:END.:END.:IFF=1:IFH=136:F=0:EL.:H=H+8:M.Q+8,P+589,8:END.:END.:POKEW,H:SO.0,0,0,0
8 X=PEEK(53260):SO.1,0,0,0:IFX&8:SO.0,50,10,15:K=K+K DIV 5:-M.P,P+964,28:ELSE:IFX&6:EXIT:END.:END.:POS.6,1:?#6;K;
9 SO.2,H,10+(F=0),6:PAUSE 0:POKE54277,S:LOOP:IFK>J:J=K:END.:SO.2,0,0,0:G.1
Below is the expanded code with commentary following each group of statements.
DIM S$(76)
S$="...{a bunch of ATASCII}..."
Q=ADR(S$)
Sets up a string full of ATASCII characters containing the pixel graphics for the acorn, squirrel, bluejay, and tree bark. The end contains characters making up part of the display list to enable vertical scrolling. The address of the string is stored in Q, which I use many times copy parts of the string into memory locations.
R=PEEK(106)
1 POKE 106,R-8
GRAPHICS 1
POKE 106,R
CLS
Sets up the screen and creates blank memory for storing sprites and custom character set. The line #1 is used a jump at the end a game to restart another round.
POKE 54279,R-4: POKE 559,46: DPOKE 53256,257: DPOKE 53258,257: POKE 53277,3: DPOKE 708,$12C4: P=(R-4)*256: W=53248: DPOKE W,$6868: DPOKE W+2,$7888: DPOKE 704,$850A: DPOKE 706,$1D85
Sets up player-missile (sprite) graphics and colors. Turbo BASIC XL's double poke (abbreviated DP.) is great for saving code space when you need to set two adjacent 1-byte registers.
MOVE Q+64,DPEEK(560)+7,12
Modify the display list to make all but the first two rows Graphics 2 mode with vertical scrolling.
MOVE 57344,(R-8)*256,1024: POKE 756,R-8: MOVE Q+56,(R-8)*256+264,8
Copy the default character set into RAM and put a custom character into the "A" location. Point the hardware here.
? #6;"NUTS!","HI:";J: ? #6;"SCORE:": FOR X=0 TO 10: ? #6;" aaaaaa aaaaaa": NEXT X
Draw the playfield onto the screen. The uppercase letters are printed in green. The lowercase "a" points to my special tree bark character with the brown color.
F=1: S=16: H=104: B=1: E=0: D=12: G=16: K=0: Z=1
Initialize a bunch of state variables, e.g. H is the horizontal position of the squirrel.
DO
Begin the main loop!
POKE 53278,1
Clear the collision register.
A=2*STRIG(0)
IF F=0:F=PTRIG(0)-PTRIG(1):ENDIF
Get joystick input. Only read the left/right direction if the squirrel is not jumping (F=0).
IF A=0
Now begins a large set of actions if the user has the fire button pressed:
-MOVE P+664,P+666,78: -MOVE P+792,P+794,78
These two move commands scroll the blue jays down the screen.
SOUND 1,0,Z,2: Z=Z=0
Play the climbing sound and toggle it on and off with flag Z. This takes advantage of the odd numbered distortion values being no sound.
K=K+1
Increase the score by one for climbing.
S=S-4: IF S<0: S=15: ENDIF
Smooth scroll 1/4 of a character to make it look like the squirrel is going up the tree.
IF B: D=D-2: MOVE Q+22-B*6+D,P+728-64*B,2: B=B*(D>0)
If there's a bird being scrolled onto the tree ... then put two lines of the sprite onto the screen at a time. Turn off B when the whole bird makes it.
ELSE: B=(RND>(1-L))-(RND<L): D=12: L=K/5000
Otherwise test to see if there's a new bird. Compute the likelihood based on the score.
ENDIF: ENDIF
End of the bird conditional (IF B). End of the climbing conditional (IF A=0).
IF E: G=G-4+A: MOVE Q+40+G,P+920,4-A: E=G>0
If there's an acorn entering the screen ... introduce either 2 or 4 lines of the acorn at a time depending upon the climbing state.
ELSE: G=16: E=(2-A)*(RND>0.9)
Otherwise figure out if we need another acorn, but only if the squirrel is climbing. This prevents a player from parking out and collecting acorns with no other challenge.
ENDIF : -MOVE P+920,P+924-A,78
Scroll the acorns down the screen.
IF F=-1
IF H=104
F=0
ELSE
H=H-8: MOVE Q,P+589,8
ENDIF
ENDIF
IF F=1
IF H=136
F=0
ELSE
H=H+8: MOVE Q+8,P+589,8
ENDIF
ENDIF
POKE W,H
This section jumps the squirrel left and right. It moves the player in 8 columns increments and selects which pixel graphic to display (either left or right facing squirrel). This was some of the first loop code I wrote. There may be a more compact way to do this with math and logical expressions, but these two IF-THEN structures do the trick and don't take up too much space. I suspect this is a bit faster because there's no multiplications that would be required in a more compact approach.
SOUND 0,0,0,0: SOUND 1,0,0,0
Part of the sound effect logic - here the jumping and climbing sounds are turned off.
X=PEEK(53260)
IF X&8
SOUND 0,50,10,15: K=K+K DIV 5: -MOVE P,P+964,28
ELSE :IF X&6
EXIT :ENDIF
ENDIF
Check the collision register for either an acorn or blue jay hit. If an acorn, the play a tone, increment the score (by 20%), and erase the acorn. If a blue jay, exit the DO loop.
POSITION 6,1: ? #6;K
Update the score.
SOUND 2,H,10+(F=0),6
Play a sound with pitch based on the horizontal position of the squirrel when it is jumping. These sound effects were squeezed in at the end, which accounts for the inconsistent way they are implemented.
PAUSE 0
POKE 54277,S
Call a PAUSE routine to sync up the code with the vertical blank (PAUSE counts v-blanks to keep time). This prevents flicker on the bottom row and tearing of the squirrel sprite while jumping.
LOOP
End of the game loop - go back and DO it all again.
IF K>J: J=K: ENDIF
When a squirrel hits a blue jay, the EXIT shifts execution to here. Update the high score.
SOUND 2,0,0,0
Turn off the jumping sound. The other sounds are already turned off.
GOTO 1
Restart the game without resetting the high score.
Subscribe to:
Posts (Atom)