Sunday, March 4, 2018

DEFENDER - 2018 BASIC 10-Liners Game Contest Entry

My first (and maybe only) entry to the 2018 10-Liner BASIC Game competition is an Atari Turbo-BASIC XL implementation of the 1981 arcade game DEFENDER.
It is entered into the EXTREM-256 line-length category. Defender is a side-scroller, which is why I chose to try it. I had been trying to figure out how to do fast horizontal scrolling with BASIC and eventually landed on the technique used here. Find it over on GitHub or download the auto-loading ATR file.

The Atari ANTIC video processor is driven by a micro program called a Display List (DL). The DL has one entry per line of graphics and defines a number of things including the graphics mode and an optional starting address for the data. As described in De Re Atari, one uses an expanded screen memory and merely specifies the starting address for each line of graphics to be displayed. In an assembly language program, the starting addresses can be updated during a vertical blank. The trouble with BASIC is that computing and poking addresses inside FOR-NEXT loop is not nearly fast enough to be satisfactory. I realized instead, I could build a precomputed look up table (LUT) and store it in RAM. Then using Turbo-BASIC's MOVE command, I could rapidly update the display list with the appropriate data to get fast side scrolling.

Once I had the side scrolling, everything else slowly fell into place. I created a random mountain generator. I wanted a radar display. Then I had to compromise on the AI's because the computational load was getting a little high for the game loop. I developed the AI's in graphics mode 0 with only 40 columns of playfield. This was enough to see the overall behavior and get a satisfactory result. Here's a screenshot of the final prototype. See the "(" kidnapping the "$"?
At this point, my program was 12 lines long. I settled on static bad guys except for special abductors who descend to kidnap your citizens. If you successfully terminate an abductor in the act, the citizen gracefully floats to earth. Be careful not to takeout a citizen accidentally - friendly fire is gruesome but challenging.

The Code

Here's the obfuscated 10-liner produced by DSMC's tbxl-parser.
The most straightforward code to analyze is here. It doesn't use any compression techniques and the AI code is separated out for each character type. This code parses into too many lines. The first task I did was to copy the compression techniques developed by Victor Parada for Space Ranger. This got be down another line. Then I tackled the AI logic, which had lots of duplicated commands to update the playfield and radar display. I matched up the AI's with the most common logic and screen update code and combined them together. This got me down to 10 lines plus the ?"game over" statement. I then found two POKES which I combined into a single DPOKE. That did it. 

Here's the final source file with expanded comments:
'-----------------------------------------------------------
'
'                       DEFENDER
'
'For Atari 800XL 8-Bit BASIC 10-Liner
'One-player game with joystick
'
'Jeff Piepmeier
'January 31, 2018
'
'--Parsed with TurboBASIC XL Parser Tool 
$options +optimize
'-- Tested on Altirra
'-- bitmap graphics developed with Mad Studio
'-- POKE/MOVE compression technique copied/adapted 
'from Victor Parada's Space Ranger
'-----------------------------------------------------------

First is a very long string of ATASCII for storing addresses and data. The first 6 characters are a very short display list described below. Then there are 8 pokes and some player-missile data followed by some POKEY sound settings.

'a whole bunch of data for POKES and MOVES
A=ADR("\46\62\BF\41\50\A0\6F\A0\00\07\D4\B0\C0\02\08\1D\D0\03\6F\02\31\2F\02\3E\F4\02\B8\62\BF\E2\04\08\D0\01\03\03\03\04\C4\02\0E\36\D6\88\03\AC\A0\41\50\A0\04\56\A0\00\4E\50\A1\08\20\B8\30\38\30\20\70\20\20\70\08\28\B8\7C\D6\D6\7C\38\7C\54\92\14\2A\B3\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\90\06\04\B2\20\70\F8\7E\FF\70\06\0E\B2\04\0E\1F\7E\FF\0E\04\00\D2\C0\AF\C1\AF\00")

'make a string of spaces to use a couple different places
DIM A$(4)
A$="    "

I use graphics mode 15 because it's well suited to the radar display and it zeros out a whole bunch of RAM. There's enough RAM to contain the radar, playfield, PM graphics and custom character set. Here's the memory map I made to keep track of everything:
C000-------------
Text Screen
BF60-------------
BC00-------------
Character Set
B800-------------
Player 3
B700-------------
Player 2
B600-------------
Player 1
B500-------------
Player 0
B400-------------
Missiles
B300-------------
B140-------------
Character GFX
A4C0-------------
Bitmap GFX
A150-------------
A0AB-------------
Display List
A050-------------
9FD0-------------
Look Up Table
7F00-------------

'base the memory map on graphics mode 15
GR.15

I move the text window to the first line of the screen and use graphics mode 2. The LUT takes so long to build, I decide to speed it up some by temporarily using a wait for vertical scan command right after the first line of text. This probably speeds things up 30-40%. I wanted to have a countdown displayed so the user would know the program was working.

'START: build new display list
'SHORTEN DISPLAY LIST SO STEAL FEWER CPU CYCLES one line of graphics 1 mapped to text window; wait for vertical scan
'MOVE ADR("\46\62\BF\41\50\A0"),$A053,6
MOVE A,$A053,6
A=A+6

The side-scrolling feature needs a look-up table (LUT) whose entries can be quickly copied into the display list to create the scroll. This nest loop computes the LUT and stores in RAM below the video.

'write coarse-scrolling LUT to RAM below video
FIELDWIDTH = 160
LUT=$7F00
ADDR=LUT
LINEONE=42176 : REM start of character graphics in video ram
FOR COLS=0 TO 139
    ? 139-COLS;A$;
    FOR ROWS=0 TO 19
        POKE ADDR,86 : REM GR.1 CHARACTER MODE; DP. TAKES 1 LESS CHAR THAN POKE
        DPOKE ADDR+1,LINEONE+COLS+ROWS*FIELDWIDTH
        ADDR=ADDR+3
    NEXT ROWS
    POKE 657,2
NEXT COLS

There are several POKE commands needed to set up the PM graphics - the addresses and data are stored in the giant string in the first line.

REM COMPRESSED CODE GOES HERE
FOR I = 1 TO 8
    POKE DPEEK(A),PEEK(A+2)
    A=A+3
NEXT I

The ROM character set is copied into RAM so it can be modified. The modifications are done by copying custom characters stored above into RAM and by remapping some of the characters to different locations. The remapping is done below.

'copy the ROM character set for modification
MOVE $E000,$B800,512

Copy data out of the string above in to RAM. This code is "borrowed" from http://www.vitoco.cl/atari/10liner/RANGER/. The first byte in the record is the number of bytes to be copied. The second two are the target 16-bit address. The remainder are the data. The process is repeated until a 0 is encountered. The very last move turns on two sound channels to create a beating low-E sound similar to the start of the arcade Defender.

REM # BYTES, TARGET ADDRESS (LO/HI), DATA
B=PEEK(A)
WHILE B
  MOVE A+3,DPEEK(A+1),B
  A=A+B+3
  B=PEEK(A)
WEND

I adapted this technique to be able to copy memory from one location to another. I move the ATASCII line art characters to a different location in the character set and I copy the bad guy character to multiple locations in the map. I need multiple copies because I use the character code to store the AI identities in the screen memory itself. This avoids needed to keep track of too many things with arrays.

'copy some memory around, including remapping some characters
A=ADR("\30\E2\08\B8\10\68\E2\18\B8\08\28\B8\30\B8\18\00\B8\48\B8\08\03\B2\10\B9\08\62\BF\63\BF\02")
FOR I=1 TO 6
    MOVE DPEEK(A),DPEEK(A+2),PEEK(A+4)
    A=A+5
NEXT I
REM END COMPRESSED CODE

I wanted to have the mountains in the play field and though having the randomly generated might be more compact that storing a predetermined set. This loop randomly picks up/straight/down directions on each iteration and draws the mountains on the playfield and radar. The ATASCII line art characters were remapped so the screen codes are 1, 2 and 3. This makes poking them into RAM straightforward. Also, they needed to be moved into the lower 64 characters to be accessible in graphics mode 2. Finally, note here and everywhere else below there are both POKE and PLOT commands to put things on the playfield and radar display. This dual display code in part forces me into the EXTREM-256 category.

'draw the mountains
QUARTER = 39 : REM 79-FIELDWIDTH/4, used in the radar display
COLOR 2
Y=1
FOR I=0 TO (FIELDWIDTH-1)
    'choose random slope
    R=RAND(3)+1
    'check for top or bottom boundaries
    R=R+(Y=0)*(R=1)-(Y=5)*(R=2)
    'increase height if needed
    Y=Y-(R=1)
    'insert character into last 5 rows of map
    POKE $A4C0+(Y+14)*FIELDWIDTH+I,R+64 
    PLOT QUARTER+I/2,15+Y
    'decrease height if needed
    Y=Y+(R=2)
NEXT I

This little bit draws the outline for the radar display. The radar display was an important feature to have for me. Besides just being darn cool, it allows the player to see if his citizens are being kidnapped.

'draw the RADAR outline
COLOR 3
PLOT 0,21 : DRAWTO 159,21
DRAWTO 78-40,21 : DRAWTO 78-40,0
DRAWTO 80+40,0 : DRAWTO 80+40,21

To speed up AI processing, I store abductors and citizens in a 2-D array. The abductors are initially hidden and coded with a value of 9 in the screen memory. The players are placed on the screen above the mountains so they can drop down. I take advantage of the AI behavior so I don't have to remember where the mountains are.

'generate abductors and victims
NA = 9
DIM AI(NA,2)
FOR I=0 TO NA-1
    REPEAT 
        PX = RAND(FIELDWIDTH) : REM X LOCATION OF PEOPLE
        PPX = LINEONE+13*FIELDWIDTH+PX 
    UNTIL PEEK(PPX)=0
    AI(I,1) = PPX : REM PLAYFIELD ADDRESS
    POKE PPX,$04 : REM INSERT PEOPLE
    AI(I,2) = LINEONE+PX
    POKE LINEONE+PX,NA : REM INSERT LATENT ABDUCTOR, NA HAPPENS TO = 9 which is char code
NEXT I

I initially tried to have the other bad guys move around, but it just was too slow, took too much code space and didn't add much to the game play. So they are static and simply inserted into the screen. There's a small chance of 1:1440 that one will be placed right where the hero's ship starts. It's only happened to me once during testing. I didn't try to squeeze in logic to avoid that - I suppose a single POKE of a 0 into that location would do the job. Alas.

'generate some static bad guys
'always slim chance one lands on ship at start, but that's a feature
FOR I=1 TO 20
    REPEAT 
        X = 10+RAND(FIELDWIDTH-20)
    UNTIL PEEK(LINEONE+X)=0
    Y = 1+RAND(12)
    BB = LINEONE+X+Y*FIELDWIDTH
    POKE BB, $85 : REM PUT DOWN BADDIES
    PLOT QUARTER+((BB-LINEONE) MOD FIELDWIDTH)/2,Y+1
NEXT I

This bit initializes the game play: sets the starting location and direction of the ship; initialize the citizen and lives counters (the score starts at 0 already since you don't have to initialize variables in BASIC). The countdown timer is set to 5000.

'start the ship in the middle of the play field
XWLD = 70
'point the ship to the left
DX=-1
PY=$3E : REM POSITION THE Y-LOCATION SO SHIP LOCATIONS OVERLAP CHARACTER LOCATIONS
'SCORE=0
PEOPLE=NA
LIVES=3
POKE $D000,120
PM=$B440:REM PM*$100+$200 TO POINT TO FIRST PLAYER
T0=TIME+5E3

And turn off the sound before play starts. I had so much code below, I didn't even try to create sound effects.

'turn off the sound
SOUND

Whew, finally made it to the main game loop. Interestingly enough, I found all my games take about half the lines for the setup and the other half for the game loop. Defender is not so different - the game loop starts in the middle of 5th line. I have REM statements on many of the lines and will only add expanded comments where they add.

REPEAT
    TT=T0-TIME : rem countdown timer
    POKE 657,6 : rem position cursor for time display
    ?TT;A$; 
    POKE 657,13 : rem position cursor for VICTIMS and SCORE display
    ?PEOPLE;A$;SCORE;

    REM PROCESS FIRE BUTTON
    F0=F
    F=STRIG(0)
    FIRE=F0&(1-F) : REM DISABLE RAPID FIRE
    FY=PY*FIRE+FY*(1-FIRE) : REM REMEMBER Y-LOCATION OF LASER
    FOR I=1 TO 3
        POKE PM+I*$100+$08+FY,FIRE*$FF : REM ADD OR REMOVE LASER IN PLAYER : REM DP. TAKES 1 LESS CHAR THAN POKE : +$40 IS BAKED INTO PM
        POKE $D000+I,112+10*DX+18*DX*I :  REM POSITION LASER
        POKE 704+I,8+16*RAND(16) : REM SET A RANDOM COLOR
    NEXT I
    REM PROCESS HITS and COLLISIONS - combine together to avoid duplicate commands

This is the first bit of AI that I combined together to reduce the amount of code. I hope I can remember what is does!

First, check to see if either the firebutton was pressed of if there is a player-playfield collision.
    IF PEEK($D004)&4+FIRE
Once we're in, why we are in is stored in I
        I=FIRE
When the lasers are fired, we have to loop to clear out the bad guys.
        REPEAT 
The subject location is computed and stored in FPOS. Note the variable I is now being used as a counter.
            FPOS=TRUNC(LINEONE+RDRY*FIELDWIDTH+XWLD+10.5+I*DX)
Here the item in screen memory is grabbed. Only the lower 4 bits matter as the upper four indicate color.
            WHAT=PEEK(FPOS)&$0F
Check to make sure we don't shoot the mountains.
            IF WHAT>3
These next two lines were repeated a lot in the original code. Having to update both the playfield and the radar display takes space!
                'erase characters
                POKE FPOS,0
                C.0 : PLOT QUARTER + ((FPOS-LINEONE) MOD FIELDWIDTH)/2,RDRY+1
                'if hit a bad guy...
All the bad guys are characters > 4. I suppose a latent adbductor==9 could be destroyed, but I've tried and it's probably unlikely to occur.
                IF WHAT>4
                    SCORE=SCORE+100
                    IF I=0
                        'collided with a bad guy!
                        POKE $D000,0
                        LIVES=LIVES-1
                        POKE $BF62+LIVES,0 : REM DP. TAKES 1 LESS CHAR THAN POKE
                        VX=0
                        PAUSE 60
                        POKE $D000,120
                    ENDIF
                ELSE
                    'OOPS! hit a citizen!
                    PEOPLE=PEOPLE-1
If the citizen was being abducted, then turn the abductor $88 into a static bad guy $85.
                    IF PEEK(FPOS-FIELDWIDTH)=$88
                        POKE FPOS-FIELDWIDTH,$85
                    ENDIF
                ENDIF
            ENDIF
        I=I+1
If we entered the loop because of a collision, the I was 0 and is now 1. That will exit. If we entered because we fired, then I was 1 and is incremented until I=10.
        UNTIL (I=1) ! (I=10) : REM OR IMOD9=1
    ENDIF
    POKE $D01E,1 : REM HITCLR
    
    REM PROCESS STICK INPUT
    S=STICK(0)
    UD=(S&2=0)*(PY<145)-(S&1=0)*(PY>0)
    PY=PY+4*UD : REM LIMITS 46144 , 46288 B440-B4D0
    LR=(S&8=0)-(S&4=0) : REM CREATE +/1 VALUES FOR LEFT/RIGHT
I use an IIR discrete time filter to create acceleration/deceleration effects.
    VX = VX/2 + LR/2 : REM USE IIR FILTER FOR ACCELERATION/DRAG EFFECT
    DX = DX*(LR=0)+LR : REM DIRECTION INDICATOR
    XWLD = (XWLD + VX + 139) MOD 139 : REM UPDATE HORIZONTAL POSITION IN WORLD COORDINATES
Because the X coordinate is floating point, I can do both coarse and fine scrolling. The fraction and truncation commands are taken advantage of here:
    XF=FRAC(XWLD)*8 : REM FINE SCROLL VALUE
    XT=LUT+TRUNC(XWLD)*60 : REM COARSE SCROLL VALUE

The missile portion of the PM graphics are used to indicate the location on the radar. These few lines move the ship.
    REM MISSILE SPRITES FOR RADAR POSITION INDICTATOR
    POKE $B32A+RDRY,$90 : REM RADAR INDICATOR OLD POSITION
    RDRX = TRUNC(XWLD/2)+87 : REM CONVERT WORLD COORDINATE TO RADAR COORDINATE
    RDRY = TRUNC((PY+6)/8) : REM RADAR Y FOR SHIP -6 TO 146 -> 0 TO 19
    POKE $B32A+RDRY,$91 : REM PUT SHIP ON RADAR

Next we update the display list, but wait for a vertical blank to avoid flicker. I tried this on real hardware and it seems to work well.
    PAUSE 0 : REM WAIT FOR VERTICAL BLANK TO REDUCE FLICKER
    POKE $D404,12-XF : REM FINE SCROLL
    MOVE XT,$A070,60 : REM COPY IN DL FOR COARSE SCROLL
    DPOKE $D006,257*RDRX+11 : REM MOVE INDICATORS IN RADAR DISPLAY
    'POKE $D007,RDRX
    POKE $D004,RDRX+5 : REM MOVE THE SHIP IN THE RADAR DISPLAY
    MOVE $B205-5*DX,PM+PY,14 : REM PUT SHIP ON THE SCREEN (+$40 IS BAKED INTO PM)

This last set of conditionals processes the AI behaviors. Combining multiple cases into one logic flow was critical to fitting in the 10 lines. Here goes...    
    REM PROCESS AI
    REM PROCESS PEOPLE THEN ABDUCTORS
There are abductors and citizens. Using the FOR loop, I process the citizens first, then the abductors.
    FOR I=1 TO 2
The pointer to screen memory location is retrieved
    LL=AI(IDX,I)
If it is valid, then proceed. Otherwise, it must have been destroyed earlier.
        IF LL>0
            'something is there
Find out what the pointer is pointing to.
            PLL=PEEK(LL)
If it is a latent abductor $09, then flip a coin to see if it appears $87.
            IF PLL=9
                'if waiting abductor, maybe it now appears
                IF RND<.5
The screen code $87 is used to indicate a descending abductor.
                    POKE LL,$87
                ENDIF
            ELSE
If it wasn't an abductor, maybe it's something else
                IF PLL<>5
                    'if not a static bad guy ...
Look below the subject
                    LLP=LL+FIELDWIDTH
                    PLP = PEEK(LLP)
And above the subject
                    LLM = LL-FIELDWIDTH
Erase the subject so we can redraw it after moving it. Combining all the AI logic here avoids having to repeat these commands several times.
                    X = QUARTER + ((LL-LINEONE) MOD FIELDWIDTH)/2
                    Y = 1 + (LL-LINEONE) DIV FIELDWIDTH
                    COLOR 0 : PLOT X,Y
The screen code $88 indicates an ascending abductor that must have a kidnap victim.
                    IF PLL=$88
                        'if an ascending abductor it must also have a citizen
Erase the kidnap victim on the radar. 
                        PLOT X,Y+1 : REM IS COLOR 0
Find out if the abductor is still going up or maybe can escape at the top:
                        IF LLM>LINEONE
                            'still going up
Erase the citizen from the play field, move the ship up one line. 
                            POKE LLP,0
                            POKE LLM,PLL
Remember where the abductor is.
                            AI(IDX,2)=LLM
Replot the citizen below the ship.
                            POKE LL,4
Remember where the citizen is.
                            AI(IDX,1)=LL
Redraw them on the radar.
                            COLOR 1 : PLOT X,Y
                            COLOR 3 : PLOT X,Y-1
                        ELSE
If the abductor escapes, erase them, set their locations to invalid, and reduce the citizen count.
                            'escaped with a citizen!
                            POKE LLP,0
                            POKE LL,0
                            AI(IDX,1)=-1
                            AI(IDX,2)=-1
                            PEOPLE=PEOPLE-1
                        ENDIF
                    ENDIF
If the subject is a citizen or descending abductor, deal with them now
                    IF (PLL=4)!(PLL=$87)
                        IF PLP=4
If there a citizen below the abductor, then kidnap them!
                            'descending abductor
                            POKE LL,PLL+1
                        ELSE
                            'falling citizen who was rescued or descending abductor!
This logic was tricky and I only figured it out by working out the individual one separately at first. If there's space below the subject and either space above the subject or the subject is an abductor then...
                            IF (PLP=0)&( (PEEK(LLM)=0)!(PLL=$87) )
Erase the subject and move it down one line. That works for either one.
                                POKE LL,0
                                POKE LLP,PLL
Update the location of the subject.
                                AI(IDX,I)=LLP
                                Y=Y+1
                            ENDIF
Put the subject back on the radar. The subject is $87, then use color 3.
                            COLOR 1 + (PLL&2) : PLOT X,Y
                        ENDIF
                    ENDIF
                ENDIF
            ENDIF
        ENDIF : REM LL>0
    NEXT I
Move onto the next set of AI's
    IDX=(IDX+1) MOD NA
And keep on going until out of time or the hero is dead.
UNTIL (TT<0) ! (LIVES=0)
POKE 657,2
?"game over";
That's it!

Thanks for reading! I hope you get a chance to play and have fun.

Wednesday, January 10, 2018

Using the SIO2Arduino with Atari 130XE

The SIO2Arduino is a DIY disk drive emulator which runs on an Arduino board and hooks up to the Atari 8-bit via the SIO port. My own experience of installing the sketch on an Arduino Mega was the topic of my last post. Here, I describe hooking the Arduino up to SIO and using it to boot the Atari into TurboBASIC XL.

I suggest one gets their SD card reader and LCD display working independently prior to hooking up to Arduino up to the Atari. The software works fine without an Atari present - of course it will dump error messages to the serial monitor. All the connections are described on the original SIO2Arduino webpage. I posted my activities on the Antic Podcast Facebook Page and Michael Glaser of the Atari XEGS Cart-by-Cart Podcast joined in with his experience. It was nice to have a partner in crime. He made this nice pin-to-pin connection cheat sheet for hooking up the Mega 2560.

The idea of attaching the Arduino to my Atari made me pretty nervous. The last thing I wanted to do was blow the I/O pins on the POKEY. The creator of SIO2Arduino points out the standard colors of the Atari Data In (orange) and Data Out (green) wires in the SIO cable in his hookup guide. Fortunately, one of the SIO cables I have has machine screws instead of rivets attaching the connector back shells. Removing half of the back shell let me double check my connections. I found that the pins on the jumper wires from the Sunfounder Arduino clone kits are a nice fit for the sprung sockets in an SIO plug. Here's a photo of these connections.
Once I triple checked my connections, I powered up the Arduino and then the Atari. Magic!

One of the peculiar behaviors I found was directories need to have less than 256 files. SDrive can scroll thru them, but SIO2Arduino will mod-256 the file number and you won't load the file you intended.

I am an avid fan of the BASIC 10-Liner Game Competition. As Kevin Savetz says, "It's the most wonderful time of the year." At last, I can test my own programs on real hardware - after developing them with modern tools, of course. The first thing I tried was mounting and booting TurboBASIC XL. That was successful. I could even write a short program ("Hello World" anyone?) and save it to the disk image. Sweet! Then I played a couple rounds of my ersatz Interceptor - pretty nice on real hardware.

If you have a few minutes more to waste, check out my video of it in action.

Thanks for visiting.

Monday, January 1, 2018

Setting up the SIO2Arduino for Atari 8-bits

I recently acquired an Atari 130XE 8-bit personal computer, but it did not come with any storage devices. I had heard about the SIO2Arduino project (probably on the Antic podcast) and thought I'd give it a try. The SIO2Arduino uses an Arduino microcontroller board to emulate an Atari floppy drive plugged into the Atari's Serial Input Output (SIO) port. Disk images are stored on a microSD card and selected using a button on the Arduino or through the SDrive software on the Atari. All I was missing for this great project was a microSD card breakout board. I ordered the Adafruit version because it was clear it had good 3.3V/5V logic level translation. I had a Mega2560 board bought for another project and ended up not using. The SIO2Arduino software supports an LCD display when used with the Mega, which I had from on old starter kit. Here's a photo of the final result in all its prototype glory:

[NOTE: This post covers using the original master branch of the software with an Arduino Mega. There is another branch that allows using an LCD with an Uno or Nano.]

The tallest hurdle for setting this up is finding the right Arduino IDE and libraries. Before assembling any hardware (and while waiting for my SD card board to arrive), I wanted to make sure I could compile the software. The software relies on an old SdFat library which is no longer to be found (by me anyway) and using the current version stops compilation in a recent IDE (1.8.4 at the time I tried). Github user Zemac was kind enough to post a solution in an open Issue on the SIO2Arduino github repository. I quote it here for your convenience:

If you want this code to run on the current Arduino IDE and need sdfat old library (ver.21.9.2013). It was also necessary to insert a code library SPI.h.Library and sketch can be found at this link. The code is already set on the control button and there is the added library SPI.h. The code is functional in the Arduino IDE 1.6.5.https://drive.google.com/file/d/0BwX94lwwuDfzcEdrUm1ub29VQm8/view?usp=sharing 
Hello Zemáč 
From Czech Republic
Since Zemac was successful with IDE 1.6.5, I decided to stick with that and not try to build the software under 1.8.4. To do so, I followed the steps below. I think there is a better way to do this using the arduino builder, but this got me up and running quickly:
  • Download the older version (1.6.5) as the Windows .zip file (not the Windows installer) and uncompress it into its own directory arduino-1.6.5-r5
  • Download and unzip the Arduino.zip file from Zemac's Google Drive link into the arduino-1.6.5-r5 directory. This creates a directory called Arduino containing all the necessary files (both SIO2Arduino and libraries). 
  • Start the Arduino IDE. Select "Preferences" under the "File" menu and set the "Sketchbook location" to arduino-1.6.5-r5\Arduino.
  • Select "SIO2Arduino" from the Sketchbook selector under the File menu.
The SIO2Arduino sketch and its supporting files should open in the IDE. From there, one follows the directions found on the original SIO2Arduino page. I set mine up in the config.h file as follows:

  • Uncomment the #define ARDUINO_MEGA statement and comment out the others.
  • Uncomment the #define LCD_DISPLAY statement
  • Turn on debug output by uncommenting #define DEBUG
Select Mega from the Tools/Board menu and then hit the check mark to compile. Once you have a good configuration, build up the hardware.

Since the SD card break out board was new to me, I tested it out with the newer IDE and SD demo software before trying to load the SIO2Arduino. I used the Adafruit tutorial as a guide. Note, you'll have to change the wiring to use the SPI pins and the appropriate SD CS line on the Mega. After working through the SD card demo, the pin definitions in the config.h file should look familiar. I also tested out my LCD display (with parallel interface) ahead of time because I hadn't used it for a couple years. The Arduino tutorial helps there. There are newer displays that use a serial interface - I'm not sure if those will work with SIO2Arduino. Once I had both the SD card and LCD display working, I configured things for the SIO2Arduino and uploaded the code. I checked out the terminal to see the debug output - it indicated it could read the files, but of course there was no Atari attached.

I'll probably cover hooking it up to my Atari in a later post. 

Happy New Year!








Sunday, December 24, 2017

Dollhouse from Recycled CD Cases

The wife saw this neat design on Thingiverse and wanted to make one for the little bit. I printed a joint in PLA and tried it out. While I'm sure it works just fine, I thought it was a tad delicate for the hands of a toddler. One of the several designers to remix it added a small angle to the ends of the legs for grip. I thought that feature plus thickening up the whole thing would make a sturdy design. Here's my remix on Thingiverse.

While examining the CD case, I found the case itself has a little trim feature along the corners. I ended up drawing a profile that included this feature in Fusion 360. A straight piece I test printed went on a CD case with a satisfying snap. All the joints are extrusions of this profile.

I realized during assembly that the joints need cutouts where the trim pieces interfere with the extruded profile. Nonetheless, I was able to get the whole house assembled only needing to reprint 1 piece. However, there are one or two that might be a little stressed because of the force required to insert the CD's. If I where to build another of these, I would model the CD case in Fusion 360 and remove interfering material. I think that would make the whole thing easier to assemble.

I printed it in Taulman Bridge nylon, which is stronger but more flexible than PLA. (I learned about nylon filament during the boy's science fair project, but that is a different story.) Everyone is happy with the final result.




Wednesday, August 16, 2017

Sparkfun 8x7 LED Array Now With Vintage 8-bit ATASCII Font

Just a quick blog post since I've been quiet all summer (working, traveling, and riding roller coasters). I received an 8x7 LED array the wife bought as a mystery item on Sparkfun's Almost Free Day last year. I soldered it up to a Nano and loaded the library and demo code. Worked right out of the gate. The library has a nice scrolling text demo, but the the provided character set is kind of boring.

So, I decided to add the ATASCII character set to the code. This is a hand-tweaked version of the original, which I obtained from the ROM using an emulator and a BASIC program to print out the values. The display is 8 rows x 7 columns and most of the ATASCII characters are 6 x 6. The ATASCII data are stored row-by-row because the ANTIC graphics chip pushes the bits to the GTIA display chip as television raster lines. I had to transpose the individual character bitmaps because the demo software from Sparkfun displays as columns for easy horizontal scrolling. I eliminated some blank columns on the narrow characters to make it a bit more proportional, which improves readability on the scroll. I also nudged the lower case characters up or down a line to look better. Finally, I replaced the Sparkfun logo bitmap with a Fuji. The code is on my GitHub.

Here's the final product:

The music is by Adam Gilmore from the Atari 8-bit game Zybex.


Sunday, June 4, 2017

Mars, Middle School, and 3D Models

"Dad, can you print out a 3D model of NE Syrtis Major?"
"Where's that?"
"On Mars. NASA might land the next rover there. Can't you just find a model and print it? I need it for science tomorrow. We're doing group projects."

A quick check on Thingiverse revealed no such model - not surprising. This presents quite the dilemma: Let the boy learn a lesson not to procrastinate or work on a really cool 3D printing project? The project was too cool to pass up but I have a tiny bit of guilt enabling the boy's poor planning.

Two problems to tackle first. Where is Syrtis Major and where do I get elevation data? The latter turns out to be really easy. I remember when NASA mapped Mars with a laser altimeter. A little googling and I find a digital elevation model downloadable as a TIFF file. Next problem is locating Syrtis Major. Some more googling and I download a nice paper with an image and latitude/longitude values. The candidate landing site in NE Srytis is topographically boring (i.e., flat) for obvious reasons. But the whole Srytis Major province is perched 3 miles above Isidis Planitia and a 2000-mile diagonal map is dramatic. That's what we'll target. 

I wrote a Matlab script (see below) to read in the TIFF file, subset out the area, smooth and decimate the surface down to something manageable. Someone wrote a nice mesh to STL exporter. My first crack at it would take 32 hours to print and since this is a fire fighting exercise, I smoothed it down even more and made the model only 6" on a side to get a 14 hour predicted print time. Right about when the school bus comes. The horizontal scale is 16,000,000:1 and the vertical is 220,000:1, which is about a 70x exaggeration in the elevation.

In my experience, many 3D models have errors so they are not manifold. Windows 10 has a built in app called 3D Builder that is great at correcting errors. I usually run my STL files through it before trying to slice them for printing. Here's a rendering of the model in 3D Builder:
The STL file is up on Thingiverse. I sliced it with the 0.2-mm normal setting in Prusa 3D Sli3er, loaded to my Octoprint server and started the job. Here's the outcome. Not bad!


Matlab script to generate the source STL file:

%%
a=imread('Mars_MGS_MOLA_DEM_mosaic_global_463m.tiff');;
%% maps
% https://astrogeology.usgs.gov/search/details/Mars/GlobalSurveyor/MOLA/Mars_MGS_MOLA_DEM_mosaic_global_463m/cub
% Minimum Latitude -90
% Maximum Latitude 90
% Minimum Longitude -180
% Maximum Longitude 180
% Direct Spatial Reference Method Raster
% Object Type Pixel
% Lines (pixels) 23040
% Samples (pixels) 46080
% Bit Type 16
% Radius A 3396000
% Radius C 3396000
% Bands 1
% Pixel Resolution (meters/pixel) 463.0836
% Scale (pixels/degree) 128
% Horizontal Coordinate System Units Meters
% Map Projection Name Simple Cylindrical
% Latitude Type Planetocentric
% Longitude Direction Positive East
% Longitude Domain -180 to 180
imshow(a(1:100:end,1:100:end))
%% subset
% http://onlinelibrary.wiley.com/doi/10.1029/2003JE002143/abstract
% http://www.planetary.brown.edu/pdfs/2763.pdf
% Figure 1: The map covers an area from -10S to 30N and from 270W to 315W.
% 270 W is -90 E and 315 W is -45 E
lat=linspace(-90,90,size(a,1));
lon=linspace(-180,180,size(a,2));
x=interp1(lat,1:size(a,1),-[30 -10],'nearest');
y=interp1(lon,1:size(a,2),-[-45 -90],'nearest');
b=a(x(1):x(2),y(1):y(2));
%%
figure
N=5;
pcolor(flipud(b(1:N:end,1:N:end)))
shading flat
%% smooth
S=25;
c = double(imgaussfilt(b,S));
%% plot
figure
N=2*S;
mesh((c(1:N:end,1:N:end)))
shading flat
%% make surface to export
N=2*S;
d=c(1:N:end,1:N:end);
d=d-min(d(:));
d(d<0)=0;
d(1:end,1)=0;
d(1:end,end)=0;
d(1,1:end)=0;
d(end,1:end)=0;
d=d*35/max(d(:));
[u,v]=meshgrid(1:size(d,2),1:size(d,1));
u=u*150/max(u(:));
v=v*150/max(v(:));
%%
mesh(u,v,d); axis equal
%%  export
% https://www.mathworks.com/matlabcentral/fileexchange/20922-stlwrite-filename--varargin-
stlwrite('mars4.stl',u,v,d)

Saturday, May 13, 2017

ADC IIR LPF SPI TFT LCD GUI - Part 2

Since I posted ADC IIR LPF SPI TFT LCD GUI - Part 1, I thought I should finally post a Part 2. In the meantime, I found the NodeMCU wifi microcontrollers and have been learning to use those with the esp8266 Arduino framework. I built a simple demo that uses websockets to print the ADC value in the browser. I think this is a great way to put an interactive display in a project. So, I've decided not to spend energy on programming a TFT LCD GUI that doesn't really have an outlet beyond the project. Instead I can spend the time learning just enough HTML5, CSS and JavaScript to be dangerous.

This experience has taught me never again to put "Part 1" in a title.


Sketch


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <Arduino.h>
#include <Hash.h>

#include <WebSocketsServer.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <ESP8266mDNS.h>

// declarations
WiFiServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);
uint16_t adcreading = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println();
  connect();
  server.begin();
  if(MDNS.begin("adc")) {
        Serial.println("MDNS responder started");
    }
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
  Serial.printf("Web server started, open %s in a web browser\n", WiFi.localIP().toString().c_str());
}

//connect to wifi network
void connect() {
  Serial.printf("Connecting ");
  WiFi.begin("yourssid", "password");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");
}

// server side
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
    switch(type) {
        case WStype_DISCONNECTED:
            break;
        case WStype_CONNECTED:
            webSocket.sendTXT(num, "connected");
            delay(500);
            break;
        case WStype_TEXT:
            String temp=String(adcreading);
            webSocket.sendTXT(num, temp);
            delay(10);
            break;
     }
}

// client side
// prepare a web page to be send to a client (web browser)
String htmlHeader()
{
  String htmlPage =
     String("HTTP/1.1 200 OK\r\n") +
            "Content-Type: text/html\r\n" +
            "Connection: close\r\n" +  // the connection will be closed after completion of the response
            "\r\n" +
            "<!DOCTYPE HTML>\r\n";
     return htmlPage;
}

String styleSheet()
{
  String stylePage =
    String("<head>\r\n") +
    "<script>\r\n" +
    "var connection = new WebSocket('ws://adc.local:81');\r\n"+
    "connection.onmessage = function (e) {\r\n" +
    "document.getElementById('adcval').innerHTML = e.data;\r\n" +
    "connection.send('anything');\r\n" +
    "}\r\n" +
    "</script>\r\n" +
    "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n" +
    "<style>\r\n" +
    "h1 { \r\n" +
        "font-family: verdana;\r\n" +
        "text-align: center;\r\n" +
        "font-size: 30px;\r\n" +
    "}\r\n" +
    "</style>\r\n" +
    "</head>\r\n";
  return stylePage;
}

// main loop
void loop()
{

  WiFiClient client = server.available();
  // wait for a client (web browser) to connect
  if (client)
  {
    Serial.println("\n[Client connected]");
    while (client.connected())
    {
      // read line by line what the client (web browser) is requesting
      if (client.available())
      {
        String line = client.readStringUntil('\r');
        Serial.println(line);
        // wait for end of client's request, that is marked with an empty line
        if (line.length() == 1 && line[0] == '\n')
        {
          client.println(htmlHeader());
          client.println("<html>");
          client.println(styleSheet());
          client.println("<h1 id='adcval'>");
          client.println("waiting ...");
          client.println("</h1>");
          client.println("</html>");
          break;
        }
      }
    }
    delay(100); // give the web browser time to receive the data
    // and wait a little to close the connection:
    client.stop();
    Serial.println("[Client disonnected]");
  }
  webSocket.loop();
  adcreading = analogRead(A0); delay(10); // need delay to stop analogRead from blocking the web server
}