Commodore 64: Fixing RS-232 Serial Limitations
This is going to be a bit of a rant, so I apologise in advance. I have just spent a good 75 hours getting the C64 to talk to an Arduino via RS-232 and each step of the way has been painful. Initially, communications were sorted and data made it across to the Arduino. All wasn't as it should be as I realised that the character mappings (PETSCII vs ASCII) meant that the data had to be translated. Once that was sorted, it was a matter of sending data back. Again, character mappings were required. Past this, I then wanted to send 64-bits of raw data. Not characters, but 8-bits-to-a-byte raw data as I wanted up to 64 sensors and therefore only 8 bytes to transmit this. Turns out that the C64 is hard-coded to transmute the serial value zero to something else...
Diagnosing the issue
I'd already built up a large-ish application on the C64 for controlling the trains. There was also a large amount of code on the Arduino side. Due to this, any support libraries or includes or other interrupt-hacking routines could've been interjecting and mangling data. After a lot of to-ing and fro-ing, compiling on windows and switching to SD card... I got jack of the speed of which I was able to debug on real hardware.
Fortunately, VICE came to my rescue. Not only does it have a debugger, but it also emulates the RS-232 User Port Serial and shit... it even reproduced the error! Let me show you how to set that up...
Configuring VICE's User Port Serial
Open VICE and choose Settings -> Catridge/IO Settings -> RS232 userport settings.... Enable the RS232 userport emulation, leave it as device 1 and set the baud rate to 2400. Hit OK.
Back to Settings -> RS232 settings.... We want to edit RS232 device 1. Fill in the text box with the relevant IP and port of the machine you wish to communicate with. If you're going to use my TCP server below, then enter 127.0.0.1:25232.
Close VICE. Next time you open it, VICE will attempt to connect to a TCP Server listening on your localhost IP on port 25232. You can configure this to whatever you want, but we are going to use the default. VICE will then treat the connection as RS-232 and provide any data received to the internal C64 user port. It will also send out any data sent to the user port via this channel.
Now that we're done with the settings, we need to give it something to connect to.
TCP Server in C#
Download the code here. I've rigged up a very simple C# TCP server for VICE to connect to. The TCP server must always be loaded first, otherwise VICE will continue silently and never send out any data. The code is also overly-primitive and, upon closing VICE, the TCP server will need to be restarted prior to starting another instance of VICE.
Once running, you can use the 1-8 keys to set the bits of the byte to be sent. Alternatively, pressing any keys of the alphabet will set the byte to that letter in ASCII.
Finally, hitting Enter will transmit the byte.
C64 RS-232 Serial Test Program
Grab the Serial Test Application here that I wrote to test the RS-232 serial port on the C64 using Johan's driver. There's a batch file in there that you can use to run the program. Just make sure it knows where VICE is.
Make sure the TCP Server is loaded first, then run the batch program. It's set to auto-start the binary. If this doesn't work, then you can open VICE and via Settings -> Peripheral Settings... configure the directory where the binary is. You can then LOAD it as per usual.
Once loaded, you'll be in the app. It's pretty straightforward and will just print out the data that has been received.
Make sure that the TCP Server has shown that the connection has been established. You can now press the keys as per the instructions above on the TCP Server and send data. At the bottom is a timer and a number of bytes received. As each byte comes in, it'll be displayed up the top. It'll also be numerically represented down below, next to in:, above the clock.
At this point, type random letters from the alphabet and send them to the C64. You'll note that lowercase get translated to uppercase thanks to the ASCII to PETSCII translation. Tinker with the 1-8 keys to set the relevant bits in the byte to send and then hit enter. Watch that 1 = 1, 2 = 2, etc... until you try to send a raw zero... bugger.
Debugging the problem
Ok, we've now worked out that, even in the land of emulation, sending a zero to the RS-232 serial port on the C64 produces an 0x0d. This rules out the 'client'... both the Arduino and my TCP Server send raw zeroes and both VICE and a real C64 render the wrong character once received. From here, it could be the custom driver, the cc65 support libraries or something much more evil.
Debugging on real hardware is something I'm scared of... I'm so used to multiple windows and multi-tasking that I'd like to do it in a more comfortable environment. Thankfully VICE has a monitor that can help us. Open this up via the File menu and prepare to delve into the land of assembler! Note that, when open, the C64 emulation is paused.
Once the emulator and application are loaded, choose View -> Disassembly Window. You'll get a little window showing the entire memory of the emulated C64, disassembled. This means that VICE, with it's knowledge of C64 6502 CPU op-codes, has translated the raw memory into known commands. Fortunately, it also has information on the mapping of the memory and therefore does a very good job of this translation. Unfortunately, there is no search command... so we will need to scour over this to find what we are looking for.
What are we looking for?
The million-dollar question. The serial test app that we've compiled for the C64 (and executed in VICE) is based on the sample code by Johan. It also includes his 2400-baud serial driver. Looking at his source code, we can see the routines that are called when we call the ser_get function from our main loop. If you browse over to his source, you'll find the relevant lines in driver/c64-up2400.s.
GET: ldx #2 jsr CHKIN jsr rshavedata beq GET_NO_DATA jsr BASIN ldx #$00 sta (ptr1,x) jsr CLRCH lda #<SER_ERR_OK tax rts
The snippet above was copied on the 24th of June, 2016. This code has hopefully been updated by now, but what you see above is the function, as it was back at that time, which reads data in from the RS-232 Serial port. I can't fully explain each line, but what we can do is find this chunk of code in the disassembly window. Unfortunately, we'll be doing this by hand as I don't know a better method to search for it.
After a lot of scrolling... this code has been found in memory location $238A. ldx #2 is seen as LDX #$02. The further jumps are visible, but their happy names are gone. Instead we see the memory addresses of each of those functions. You can see that I've also clicked the row in question. This sets a breakpoint, represented as a red highlight on the row.
Triggering the debugger
From here, we can actually close the debugger. Make sure that the red row is set prior to closing the main Monitor window. If you just did this, then you'll find that the debugger window popped back up straight away and now our red breakpoint is a lovely shade of teal. Teal? What happened to Red? In the disassembly list, a blue line indicates the location of the next line to be executed. Ours is teal because it's a set breakpoint AND it's the next line to execute. If you're bored, click the line and it'll turn blue. Click it again and it should return to teal.
Ok, we've triggered it... but why? No data was sent down the channel! It turns out that, if you look at the source above, there's a hint on the line with GET_NO_DATA. The ser_get is called from our code on each program loop; it returns a SER_ERR_NO_DATA when there is no data... so this line we've broken on is too early in the driver code. We really want to set a breakpoint at the first line executed when there is data.
So step through the code. To do this, press the 8th button on the toolbar in the Monitor window. Press it a few more times and watch the execution. We're currently 'stepping over' functions; the two JSRs might well send the program counter off into other parts of memory and execute code, but we don't care. Once you're on the BEQ GET_NO_DATA line, press it again. Note that we now jump down to the location of GET_NO_DATA and exit out. We can assume that this jump is the significant point where, when data exists, the execution continues straight through and does not jump.
Now that we know where this junction is, we want to clear our previous breakpoint and set one on the line directly after BEQ. Set the breakpoint and close the Monitor. It should not pop back up this time.
Viewing the data coming in via Serial
Ok, we've set our breakpoint in the driver and it hasn't triggered yet; let's hope it does when data is received. To test this theory, we'll need to use the TCP Server from above. If it's not already running, close VICE first and then start the Server. You need to have the TCP Server running prior to VICE for the connection to be correctly established. Once everything is running, ensure that the server has reported that the connection is established. Confirm your breakpoint is still in place; VICE doesn't save these, so you'll need to go hunting again if you lost the position. Once everything is configured, make sure you close the Monitor window to allow the C64 emulation to proceed. When the Monitor window is open, the CPU emulation is halted!
Once ready, focus on the TCP Server window and press the a key. Make sure it reports back that the byte is set to 97, which is ASCII. Press the enter key and you should trigger the breakpoint in VICE.
Ok, we've got it, our breakpoint was hit and we presumably have data somewhere? Our breakpoint is actually on a JSR through to the address $FFCF. At this point in time, this address is unfamiliar. All our previous driver code has been around the $2300 mark. Due to this, we're going to step over it. The next call is an LDA which uses a pointer + offset to load a value into register A. Stepping over this, we see the following in the Monitor window:
(C:$2394) n .C:2397 A2 00 LDX #$00 - A:61 X:03 Y:00 SP:f2 ..-..... 6728381
Interesting... A:61 hey? That's the ASCII HEX value for a lower-case a! So there it is, the value has come through and we can see it in memory. You can close the debugger now and play around with data on the TCP server to see how it arrives on the C64.
Note that as soon as you try to send a zero from the TCP Server, it'll appear as an 0x0d. At this point, we've honed in on the location where the char is read in, but we don't know what code is hosted up in the $FF00 range?
C64 Memory Mapping
Time to go deeper. Thank god we're working on a seriously old system that has been thoroughly documented. You'll find the memory map of the Commodore 64 here. If you scroll down the page, you'll find that the area in question contains the Kernal (Kernel?). At this point... we might as well retire.
Retire? Yes. The KERNAL is ROM, you know... READ-ONLY MEMORY. It's the raw system code burnt onto chips on the motherboard and it is a fixed entity. Those bytes can't be hacked... anything we try to do will require reproduction of kernal commands in the 'user' area. Should we try this? Sure... but first it'd be nice to know what's going on.
C64 Kernal Disassembly
Again, thanks to the age of this system, there's a full disassembly of the C64 Kernal here. We know that we're jumping in to location $FFCF, so browse down to that area.
FFCF 6C 24 03 JMP ($0324) ; (F157) input char on current device
Oh look, it's just another JMP. Fortunately it's commented and we know to browse through to $F157. Now we are in murky waters... not many comments here. We get a heading of ; input a character, but then not much else. The assembly, to the naked eye, looks like a switch statement. It seems to be going through checking which device to read from. In our case, we can actually step through it in the debugger. If you step into the JSR #$FFCF then you'll be able to watch it jump as you send in characters from the TCP Server.
The basic trail jumps through: $FFCF -> $F157 -> $F166 -> $F173 -> $F1B8 (a.k.a. read a byte from RS-232 bus) via F177. At $F1BD, there's a comparison of the incoming byte to 0x00. If there's no match, then the code jumps out to $F1B3 and returns the value. If there is a zero, then the following occurs:
F1C1 AD 97 02 LDA $0297 F1C4 29 60 AND #$60 F1C6 D0 E9 BNE $F1B1
If the AND fails to compare, then the jump happens at $F1C6 to $F1B1. Looking at $F1B1 made me cry. The code implicitly inserts an 0x0d, overwriting the byte that was read into the buffer and then returns it.
I don't quite know what the LDA from $0297 is and why it is AND'd with #$60. I'm sure there's some RFC or some prehistoric rule back in the late 1980s that said if a modem or other serial device returned a zero, then it actually meant it to be a carriage return. Maybe it was a BBS thing? I'll continue digging and attempt to comment this area of code, but for now... we know that it's futile. The KERNAL ROM is fighting us and thinks it knows better!
A valid workaround
Righto... what do we do here? I initially thought it was a complete loss and gave up. Further Sapporo made me realise that this call was made from our custom driver. What if we specifically mention that our custom driver is built to handle 'zero' byte data and implement a work-around? If we copy out the code from the Kernal and re-produce it in the driver, then we can effectively resolve this (I wont say bug, I'm sure they had their reasons!) issue.
So, the trick here is to grab the portion of code from the full disassembly of the C64 Kernal and build it into a function in Johan's driver.
We know the entry point is $FFCF. This is a JMP to the switch statement which chooses the device. We know that this is the RS-232 driver, so we can skip that part and copy the code from $F1B8 to $F1C8. I've pasted this in below.
; read a byte from RS-232 bus F1B8 20 4E F1 JSR $F14E F1BB B0 F7 BCS $F1B4 F1BD C9 00 CMP #$00 F1BF D0 F2 BNE $F1B3 F1C1 AD 97 02 LDA $0297 F1C4 29 60 AND #$60 F1C6 D0 E9 BNE $F1B1 F1C8 F0 EE BEQ $F1B8
That CMP #$00 is the pain. Let's just jump to $F1B3 all the time. Actually... $F1B3 is just CLC and RTS. Let's just write that. We also can't directly BCS to $F1B4, so we'll need to JSR to a closer function and then call JMP. If we JMP directly then we'll lose our position in the stack.
F1B8 20 4E F1 JSR $F14E F1BB B0 F7 BCS $F1B4 ;re-write this to a BCS and JMP ... ... CLC ... ... RTS
With my patch above, I've removed (what seems to be) a re-try loop in the code. If it falls all the way through to $F1C8 then it returns to $F1B8 and tries to read a character again. I haven't seen this state occur in real life, but I'll keep an eye out and try and work out when this actually occurs. It seems that the AND #$60 must check for an error state which I'm yet to encounter.
I don't actually know the assembled opcodes off-hand. We will write this as standard assembly into the c64-up2400.S driver source file and then it'll write the opcodes on compilation. So, from line 139 we slap in:
;---------------------------------------------------------------------------- ; OTHERFUNC: Shortcut to Kernal code ; OTHERFUNC: jmp $F1B4 ;---------------------------------------------------------------------------- ; OURBASIN: This is a minimised call to get the character from the buffer. ; The Kernal code does not allow zero bytes (0x00)... this does. ; OURBASIN: jsr $F14E bcs OTHERFUNC clc rts ;---------------------------------------------------------------------------- ; GET: Will fetch a character from the receive buffer and store it into the ; variable pointer to by ptr1. If no data is available, SER_ERR_NO_DATA is ; return. ; GET: ldx #2 jsr CHKIN jsr rshavedata beq GET_NO_DATA jsr OURBASIN ldx #$00 sta (ptr1,x) jsr CLRCH lda #<SER_ERR_OK tax rts
I'll give the kernal function a real name soon. Right now the basic point is that we write our own BASIN function that is just a tiny subset of the greater procedure and then skip the part where it inserts that shitty little 0x0d.
Either way... compiling this (see notes on that here) saw the bloody thing work! I'm going to get in touch with Johan now and determine what needs to be tidied up to get this trick included in the trunk.
That was fun!
Serial Train Controller (C64+Arduino)
I'd previously attempted to use the parallel port for train control but have now switched to the Serial port for communications. I've also slapped an Arduino in between the layout and the controlling (Terminal) device. The Arduino will be programmed to understand data coming in from the Serial port, which can be fed from any device which is capable of outputting an RS-232 signal. For the example below, I'll be doing this from a Commodore 64. See this article to learn how to build a Serial Port for your C64.
Designing a communication language
Since we'll be using the RS-232 standard, we're able to define a language that any Terminal can speak to control the Arduino. This will be based on ASCII for command verbs and then raw byte data where targets and values are required. For instance, we could set throttle number 1 to 150 or point number 4 to left. Without being too tricky, 3 bytes is adequate to communicate these commands: C|T|V. If we wanted the data messages to be 'human readable' (so you can print them straight out to a file or other serial line), then we should send through 7 bytes C|TTT|VVV, where the target and value are padded out with zeroes. Fortunately, we only need these two to be 8-bit values, which is the definition of a byte. We'll simply have to convert them to ASCII if we ever want to print or display them.
Next, we'll need a command separator. I've chosen the exclamation mark '!'. Hence, when sending commands, our messages will be 4 bytes long. The recipients will need to wait data, ensure there is at least a full 4-byte message with a terminator and then start processing.
Important Note: As is described down below, I found out the hard way that not all text is equal. I've naively mentioned 'ASCII' two paragraphs above and jumped straight onto the C64, compiling strings and sending them down the wire. For some reason, although I full-well knew that I was coding in PETSCII, I neglected to think that it would actually send it down the line! Long-story-short, when the C64 sends a capital A, it actually sends a character that does not map to ASCII. Also, when it sends a lower-case A, it is actually seen in ASCII as an upper-case A!
Moral of the story? Make sure you understand what character set each device is transmitting and receiving!
Talking back to the Terminal
Although everything written above indicates that most of the communication will be one-directional; this won't always be the case! Some of the commands in the table below will actually be asking for data from the layout. This will be in the form of sensor blocks, where optical or occupation sensors will exist on the layout and be wired to binary input data. The Arduino will be hooked up to shift registers which, when daisy-chained, will be capable of 'watching' up to 64 inputs.
To get this data back to the terminal, we'll send through a message of 10 bytes. This will start with the letter S and be followed by 8 bytes of binary data indicating the state of all connected sensors. Finally a '\0' will be appended to indicate the end of the message. The Arduino wont expect a response to this; if the Terminal has failed to receive, then it can simply request the data again.
The Command Table
Now that we have our expectation of 3 data bytes and one terminator per message, we can start to define all of the commands expect to send.
Command | Target | Data |
---|---|---|
T
Set Throttle |
1 or 2
The Arduino will have two separate PWM throttles. |
0 to 255
The throttles will supply 12v DC PWM. The wave-cycle will be dependent on the value from 0-255. This maps to 0-12v. Hence 128 should be roughly ~6v. |
D
Set Direction |
1 or 2
Each throttle has it's own direction. |
F or R
Forward or Reverse. |
P
Toggle Point |
1 to 8
8 separate points will be connected. |
L or R
We'll use ASCII here to make life easier. Left or Right can be specified. |
S
Read Sensors |
0
We don't need to specify a block... we'll get the whole 8 bytes back regardless. |
0
No value required here. We'll send a zero for padding. |
Code for the Arduino
The Arduino will need to keep an eye on the serial port and act on commands when they appear on the channel. Data from the serial port will come in as singular bytes, so they'll need to be written into a buffer and processed once a whole message is found. In case the Arduino struggles, it'll need to be able to understand what a whole message is and discard anything that looks incomplete.
I'll skip the bits on PWM throttles, reading sensors and LED Matrices here... I'll describe all that in another article. Currently you can find individual articles on each of these topics on this site if you need the information straight away.
#include <SoftwareSerial.h> SoftwareSerial mySerial(8,9); // RX, TX void setup() { // set the data rate for the SoftwareSerial port mySerial.begin(2400); } ... void processCommand() { int x = 0; switch (serial_buff[0]) { case 'T': setThrottle(serial_buff[1], serial_buff[2]); break; case 'D': changeDir(serial_buff[1], serial_buff[2]); break; case 'P': //adjust point break; case 'S': //read sensors and return the data. break; } for (int x = 0; x < SERIAL_MAX; x++) serial_buff[x] = '\0'; last_pos = 0; } ... void loop() { //if there's data and we've not read the end of the current message. if (mySerial.available() && (last_pos == 0 || last_char != '!')) { last_char = mySerial.read(); serial_buff[last_pos] = last_char; last_pos++; if (last_pos > SERIAL_MAX) { //then we need to do something drastic } if (last_char == '!') processCommand(); } }
The snippet above checks if there's data and if we don't already have data. If the buffer is empty, then it'll read a character into it. If that character happens to be '\0' then it'll prevent further reading and process the message.
Controlling with a Commodore 64
cc65 has all the libraries we need to get the serial interface up and running; see more on that here. We'll use a text-based interface and control everything with the keyboard. At a later date I'll write a GEOS-based GUI.
I attempted to use the Tiny Graphics Interface libraries that cc65 provides. Unfortunately, that would've also meant writing a text renderer or graphical font library as the basic 'text out' for TGI isn't implemented on the C64. Staying with text-mode was to be easier and PETSCII has enough cool symbols to draw throttles and the like.
The screen displays the throttle, just one for now, and the direction. It also provides a clock and a schedule. The user can add and edit items in the schedule and, when in run mode, these will be executed accordingly.
void sendCommand(unsigned char c, unsigned char t, unsigned char v) { ser_put(c); ser_put(t); ser_put(v); ser_put(33); } ... void main() { ... sendCommand('d', 1, current_direction); sendCommand('t', 1, current_throttle); ... }
I've left out most of the guff ... I'll include the full source soon. For the meantime, you'll see that I've put individual characters to the serial channel for reading at the Ardunio end. Specifically they are lower-case! You'll also note that I write 33 instead of the literal character !. The reason is, of course, that the exclamation mark in PETSCII is not the same as ASCII.
What's Next?
This works. The train happily moves back and forth using the signals sent from the C64! It's overly boring though and based on the clock. I want sensors read back to be able to trigger events... so I'll hook up the multiplexing and post again shortly.
Commodore 64: Serial Interface to Arduino
So, in my previous post, I was heading towards building an archaic circuit to control trains with the User Port. This would've worked, had I spent a lot more time and built a very complex circuit. Instead I've now chosen a new path... let's hook the C64 up to an Arduino and do most of the work there. The C64 can be the display and input/output for controlling the trains.
Interfacing both components
The C64 User Port has both a 'parallel port' with 8 i/o pins and a serial port. I initially wanted to use the parallel pins, but came to the conclusion that I'd have to write my own language on both sides and deal with the data transfer timings and clock synchronisation. Instead, it'll be easier to use industry-standard RS-232!
I suppose this is a bit of a cop-out. I wanted to build something that was dated to the level of technology that existed back when these machines were in their prime... unfortunately my electronic knowledge isn't up to scratch... so getting to a variable 12v output wasn't overly easy. It also would not have been PWM. Due to all this, including the Arduino into the mix isn't such a bad idea. Plus, everyone I'd asked for help told me to do this... even sending me links to my own blog posts :)
DTE vs. DCE
Serial plugs have a single channel, with each end having one transmit (TX) and one receive (RX) pin. Each end will send data down the cable via the TX pin and expect to receive data on the RX pin. Standard serial cables are 'straight through', meaning that the TX pin is connected to the TX pin and likewise with RX. Doesn't quite make sense, does it? How are two separate devices meant to eachother if they are both transmitting down the same singular TX wire and hearing silence on the RX?
This all becomes clear once you realise that devices fit into two categories: DTE (data terminal equipment) and DCE (data circuit-terminating equipment, also known as data communication equipment.) In the end, these two devices boil down to one being the master (the DTE) and one being the slave (the DCE.)
Of course, you can purchase 'cross-over' cables for serial connections. These are known as null-modem cables and allow you to hook two DTEs together. Say, for example, you want to transfer a file from your PC to your Amiga, or somesuch!
In my previous serial project, when I connected the IBM receipt printer to the Arduino, I needed the Arduino to be the master, and so I hacked around until I had correctly configured it as a DTE. This time around we want the Arduino to be the DCE. Due to this, be careful to follow the pinouts and wiring from the serial port to the MAX232 in the circuits below!
Note: For further reading/wiring on RS-232, there's a good article at avr Programmers and another at Advantech.
C64 Serial Port
The User Port on the C64 also has serial connections. These are TTL and so need to be brought up to the RS-232 5v standard. The MAX232 IC will do this for us quite easily. We'll also use one at the other end for the Arduino.
UPDATED (2024): The CTS and RTS wires were incorrectly ordered on the original diagram. The following diagram is now correct, but I've decided to leave the comments at the bottom of this article which state otherwise.
The circuit is derived from 8bitfiles.net. This circuit was also correct in that the pins are wired up as DTE. This means that you could use it, as-is, to also hook to a modem or any other DCE device.
The MAX232 needs few extra components. Fortunately, these extra components are identical on both ends, so buy everything in duplicate! The capacitors are all 1.0uf electrolytic. I used 1k resisters on the LEDs so as not to draw too much current from the User Port.
Arduino Serial Port
This is nearly the same circuit as the C64 end. The funniest thing happened here... if you google enough 'arduino max232' you'll just loop back around to my post, from ages ago on interfacing an IBM printer to the Arduino. Just make sure you don't use that circuit! It's DTE, we need DCE as per below! I've left out the RTS/CTS as I don't intend on using any form of handshaking in this project. It's still in the circuit above for the C64 so that you can use the port for other purposes.
As per usual, make sure you DO NOT apply 5v in the wrong direction... I did and it ruined a few caps and possibly the IC. Garbage then came through the serial port. If this ever happens, then throw out the components and start again; you'll never be able to trust them.
Also make sure that you use the 5v pin on the Arduino. AREF is NOT a valid voltage source.
Hooking it all together
Build both circuits above and give one a male and the other a female db-9 connector. The DCE device usually gets the female, so put this on the Arduino-side!
If you want to roll your own cable, then grab some grey IDC and two crimp-style plugs. Just make sure that you have pin 1 matched to pin 1. If you're splitting the cable, then paint a wire (or use a marker) to ensure that you get the orientation correct. It's really easy to confuse pin 1.
From above, you can see the pin numbering. I slid the second port all the way to the end, prior to crimping, to ensure that the numbers matched up. Using the red '#1 wire' on the cable worked wonders too.
Testing with Strike Terminal 2013 Final
Download Strike Term 2013 Final from here and then get it to your C64. I copied the D64 to my SD2IEC and loaded it up. Hit M and select User port. Hit b and switch it to 1200 Baud (or other baud, depending on what you've configured in the Arduino.)
Once ready, hit f5 and then hit enter on the default server. This'll start sending modem AT commands down the serial and they should start showing up at the other end. Either open the Arduino Serial Monitor... or edit the code to display it. I bought some 8x8 LED Matrices to render the data coming in.
There were no real caveats here... everything just worked! Press f3 to get to the terminal. Hit commodore+e for local echo and then commodore+i to 'send id'. You should now be able to type freely... everything will be sent down the wire.
At that point I only had one matrix... so the last char typed was displayed.
Writing C code to use the Serial Port
Nanoflite has written a 2400 baud User Port Serial Driver for cc65. I originally tried to use this at 1200 baud, as that's what I'd been using everywhere and heard it was the max the User Port was capable of. It turns out that this driver only supports 2400 baud! Download it and put the source somewhere.
Switch to the driver directory and compile it:
cl65 -t c64 --module -o c64-up2400.ser c64-up2400.s
Copy this to the folder that has your source file it. I slightly modified the example.
#include <stdlib.h> #include <stdio.h> #include <conio.h> #include <serial.h> #define DRIVERNAME "c64-up2400.ser" static const struct ser_params serialParams = { SER_BAUD_2400, /* Baudrate */ SER_BITS_8, /* Number of data bits */ SER_STOP_1, /* Number of stop bits */ SER_PAR_NONE, /* Parity setting */ SER_HS_NONE /* Type of handshake to use */ }; int main (void) { int xx; clrscr(); puts("C64 serial ..."); // Load driver ser_load_driver(DRIVERNAME); // Open port ser_open(&serialParams); // Enable serial ser_ioctl(1, NULL); for (xx = 0; xx < 10; xx++) { ser_put('C'); ser_put('6'); ser_put('4'); ser_put('.'); ser_put('.'); ser_put('.'); } return EXIT_SUCCESS; }
Compile this:
cl65 -t c64 -O -o trainctl2 trainctl2.c
I then put it on the SD2IEC and loaded it via LOAD "0:TRAINCTL2",8 followed by a RUN.
Shit... worked... this is great! Next it's time to put a PWM throttle onto the Arduino and control it from the Commodore... I'll tinker with graphical programming in C also.
Trains… a backlog.
I'm sure I should've posted these events as they happened... but one thing lead to another and... I didn't. The folders were starting to cover my desktop so badly that I've now decided to just cull 95% of the photos and keep the only very best... or are required for story-telling.
Below are the happenings since some time around October last year.
Cs on the North-East
The C class is a good-looking locomotive. Delegated to freight, they never get their chances on express passenger services. One train buff in Australia happened to gather enough support to fund a passenger tour hauled by a C class locomotive. This tour ran from Melbourne through to Albury and return.
I caught it at Wandong, just north of Melbourne. Happened to be a nice sweeping curved and I totally fluked it. There were a few other trains in the vicinity also.
Steamrail Kaniva Loop Tour
This tour was run on both gauges. We had V/Line use it's P-Class locomotives first, which got us to Ararat. We then transfered over to the Standard Guage and had a TL pull us all the way out to Kaniva. I'm sure this town used to have ... people ... but not much happens nowadays!
The highlight? The roof panel split off on the way back... must've been fiberglass debris everywhere.
Hunter Valley Steamfest
This is a huge annual event centered on Maitland, NSW. All heritage groups get together and bring their shiniest locomotives to the event. There's always a "Great Train Race" and this year the 6029 Garratt appeared. Canberra actually entered 2 of the 4 trains in the race. Fortunately, there's also 100s of other trains in the area; it's the backbone of the Hunter Valley coal train network.
The steamers, prior to the race, were running shuttles left-right-and-center. Most had a diesel on the rear to prevent any repercussions if a breakdown were to occur.
Then, it happened.
A damn cool sight... and supposedly a world-record! The rest was just coal trains and more freight.
..and now I'm somewhere up-to-date. Enjoy.
Commodore 64: Datasette Maintenance
So, Double Dragon had issues loading on a Tape Drive I'd acquired. It had come in a mouldy box, so I had a hunch that the drive itself would need a thorough clean and alignment.
There are multiple alignment tools, downloadable as disk images or PRG files. I could copy these onto my SD2IEC, but there was an issue: the SD2IEC gained power from the Datasette port which was now in use by the Datasette drive itself!
Powering the SD2IEC from the Datasette Cable
Fortunately, the Datasette plug has a screw that makes accessing the internal pins very easy. I opened it up, taking note of the cracked, flimsy plastic, and inspected the contents.
The green wire, pin 2, is the +5v that we're after. Bare back some plastic on the wire so we can solder to it. I found a male+female standard round DC power socket to use. Make sure the female is on the 'power' side, otherwise you'll have a potential for shorts if there is exposed bare metal with current flowing through it. Of course, the outer metal is ground, but still better to be safer than sorry.
From here I soldered on the plug and grinded out some plastic from where the main cable feeds in. This allowed the plug to hang out the end. Not the cleanest job, but it worked quite well. I sorted wanted to feed it out the side where the ground wire is... but I hadn't left enough length.
Next, on the SD2IEC end, bare some wire also. Grab the plug and solder it on, then use some form of insulator to tidy it all up. Nothing a little duct-tape can't fix.
Everything was plugged in and good to go!
Cleaning the head
Everyone recommends alcohol (isopropyl) wipes for this. The wipes have the benefit of leaving little residue and drying cleanly. You'll find that KFC wipes are also usable.
Take the swab out of the packet and wipe the heads, specifically the central metal one. I didn't actually know which way to wipe, or how much pressure to apply. So just be gentle and attempt to remove any visible dirt. Don't put a tape back in until everything is try.
Aliging the head
There are a few options here. Download Cassette Azimuth, also known as 'Recorder Justage', and Minimal Head Align. Both do the same thing, the former is more complex.
I copied them both onto the SD2IEC and then loaded them via the file browser. Cassette Azimuth is easy to work with. Load it up and then hit play on your tape. If nothing is happening then you'll need to start adjusting the player already. If you see data, and it's erratic, then you'll also need to adjust. The goal is to have straight vertical lines.
To actually do the adjusting, there is a tiny screw-hole above the rewind button that a small jeweller's screw driver will fit through. When the tape drive is playing, the hole lines up with the head adjustment screw. Turn this screw all the way clockwise (not too much pressure!) and then turn back in small increments as required. Pause between turns to let the screen update with the new readings.
You can hit F1 to get the guide-lines for the data. I couldn't work out if the data lines were meant to draw over the top, or in between, or where... but at least I got them vertical!
Here's the same process with Minimal Head Align. The app is much more raw; it starts off with a screen full of garbage which starts refreshing once you start feeding it data from the tape drive.
Back to Double Dragon
I had assumed that all the loading issues were from a dirty/misaligned head... so I thought I'd try the game again now that the tape was producing cleaner, more vertical lines on the test programs. I didn't have much faith... but it worked! It took just as long as last time, but this time I got to the first level!
The graphics are intense... hah. Controlling the character was hard at the start... then I realised it was because my controller probably hadn't been used in a decade. I'd found a sega mastersystem controller at a second-hand shop over the weekend. Works perfectly.
So as the game was loading, it got to the point right before the title and asked me to reset the counter to 0. Turns out this is so that, when you die, you know where to rewind the tape to.
From there you rewind to zero, press play... wait for the load and then have another go. I might try source the game on floppies to see if the extra data capacity allowed for a different version. Hmm... then again if this review is anything to go by, then there's no hope.
Amiga 1200: Installing AmigaOS 3.9
After having Amiga OS 3.1 on the 1200 for a while, I'd decided it was time to upgrade to Amiga OS 3.9. I had instantly realised there was a problem with this; the installation media is on a CD! There was no way I was going digging for an accelerator card with SCSI, or a PCMCIA CD Drive, so I resorted to emulation to get the OS installed. Therefore, henceforth are the steps required to build a compactflash card with a real OS for use on real hardware. Godspeed.
Please note, a lot of this was gleamed from watching How to add a 8 GB Compact Flash hard drive to your Amiga 1200 (works with 4 GB too) and I want to thank Alex Rosato for providing this video. Please watch it if you have any issues with the steps here.
I also need to thank GothicKane for his video Upgrading Workbench 3.1 to Amiga OS 3.9. Both videos combined were required to get my A1200 up to AmigaOS 3.9.
Finally, the requirements are as follows: An Amiga 1200 with Kickstart 3.1, an 8gb CF Card + adapter and an accelerator card or memory expansion of at least 4mb. The base 2mb in the Amiga 1200 is not enough to run Amiga OS 3.9 successfully!
Choosing a CF Card
There's no real requirement here. Most posts will indicate that your card needs to be no bigger than 4gig. This tutorial will expect that your card is 8gig. I bought the smallest card available from a local camera shop (seems to be the only retail shops left with stock!) and 8gig was the minimum. I therefore had to go to extra lengths to get the partitions created. But it's now working perfectly.
Next you'll need a CF Card Reader for your ('non-amiga') computer. I've got a crappy USB all-in-one and it works fine.
Partitions
Off-the-shelf CF cards will come with FAT partitions on them. On Windows 10, drop to command prompt (Start, cmd) and then type diskpart. You'll get UAC prompt, accept it. You'll now need to be VERY CAREFUL. Type list disk and work out the number of your CF Card.
Microsoft DiskPart version 10.0.10586 Copyright (C) 1999-2013 Microsoft Corporation. On computer: WHITEBOARD-PC DISKPART> list disk Disk ### Status Size Free Dyn Gpt -------- ------------- ------- ------- --- --- Disk 0 Online 465 GB 0 B Disk 1 Online 1863 GB 0 B Disk 2 Online 1863 GB 879 GB Disk 3 No Media 0 B 0 B Disk 4 Online 7847 MB 0 B Disk 5 No Media 0 B 0 B Disk 6 No Media 0 B 0 B DISKPART>
As you can see, the all-in-one reader provides too many disks to choose from. Either way, I know I have an 8gig card in the slot, so it's Disk 4 that we're after. Therefore, type select disk 4. From here we need to view the partitions, so type list partition.
DISKPART> select disk 1 Disk 1 is now the selected disk. DISKPART> list partition Partition ### Type Size Offset ------------- ---------------- ------- ------- Partition 1 Primary 7847 MB 0 B DISKPART>
Once you're sure that you've got the right disk, and that you see the correct list of partitions to remove, you can either delete them one-by-one or use the command clean. I actually take objection to Microsoft indicating that 'clean' should clear all partitions. It sounds quite innocent when, in fact, it is obliterating all your data!
Either way, the goal is to end up with zero partitions on your CF card.
Installing and Configuring WinUAE
Our emulator-of-choice for the Amiga is WinUAE. Download it from here. Once installed, you'll need to always open it via Run As Administrator. WinUAE will need higher system privileges to access the low-level area of your CF Card, so don't forget to always run it in this manner. If you don't, then chances are you'll see "Access Denied" next to the CF card in the list further on.
Here's the fun part... to get WinUAE booted, you'll need the following items:
- Kickstart ROM 3.1
- Workbench 3.1 Disks (ADF)
- AmigaOS 3.9 CD
I.. won't tell you how to get them... but it's not hard... any of those terms, with the relevant verbs appended, when typed into google, should get you the binary 1s and 0s that you seek.
Next are some supporting files to allow greater-than-4gb disks to be useful. Create a folder somewhere on your disk and call it work. Download and extract (you can use 7-zip for this) the following to this directory:
Once you have the required loot then you can configure the rest of WinUAE to get a bootable emulator.
Creating Amiga Partitions
Start the emulator with its new configuration... patience will be required throughout the rest of this process. You should end up at the workbench desktop with a crap-tonne of floppies mounted. Firsly we'll drill into the work folder, where we have our after-market goodies, and partition the new disk.
At this point, I'm going to assume you extracted the goodies into this folder, if you haven't, then go do so now! Browse to work and open the HDInstTools drawer (that's what they're called on Workbench) and then the HDInstTools application. There should be one item listed here: the CF Disk as 0 0 0 with manufacturer of UAE-IDE.
Select this disk, make sure it's highlighted, and then press the File System button. At this stage there shouldn't be any filesystems listed, so choose Add... and select: work:Smartfilesystem/AmigaOS3.x/L/SmartFilesystem. Once selected, change the DOSType to SFS\0 (PRESSING ENTER AFTER CHANGING THE VALUE!.) Now press the Use button and you'll return to the FileSystem list. Press Use again to return to the main HDInstTools menu.
From here, since we now have a new filesystem declared, we can create partitions under it. Select Partition drive... and then hit Add Partition. A partition the full size of the disk will be created, but we don't want this. So hit Edit partition... so we can configure it further.
Drag the size slider to somewhere below 4gig. I would actually recommend below 2gig. Here I chose 1gig for this first partition. Set the File System to Custom and then manually type in SFS\0 for the DOSType. Press ENTER after typing in the new code whilst the text cursor is still in the text field! The cursor will move to the next field when enter is pressed. This way you'll be sure that the value has been applied. Next, change MaxTransfer to 0x001FE00 and Buffers to 100. Hit enter after entering each value! Then press the Use button to confirm changes to your first partition.
At this point, add as many other partitions as you want using the exact same method as above. They can be any size after this, but make sure that, if you're ever going to boot on an older Kickstart (pre v3.1) or older OS that you keep them at sane values. 2gig is a good theoretical maximum. There's also no harm in multiple 2gig drives for games, utilities, videos, mods, mp3s, downloads, etc...
Once you've got your partitions configured, hit Use and then Save changes to drive. At this point you'll be told that you need to restart. Hit F12 to get to WinUAE's configuration and save your settings. Finally, press the restart button in the bottom-left of the WinUAE control panel.
If all goes well, you'll be back at workbench with a DH0:NDOS on the desktop also. If this isn't visible then you'll need to go back to the start and open HDInstTool and see what configuration has stuck and what hasn't. Work out the gaps and run through the process again.
If you can see DH0:NDOS, then it's time to format it. Open up the Workbench3.1 disk, browse to System and then open the Shell. Browse to the smartfilesystem folder (cd work:SmartFileSystem) and then format the disk with the following command: sfsformat drive DH0: name OS. You can call the partition anything you want here.
If OS then appears on your desktop then you're set to install your operating system!
Installing Workbench 3.1
This is a pretty straight-forward install. You can save a lot of time by making sure the first 4 disks are already in the drives. You'll want wbench,install,extras and locale in first. Once ready, go to the Install disk and run the English installer.
A lot of the time you'll be hitting Proceed as we'll be using a lot of the default settings. Choose Intermediate Install and then go with the defaults. Make sure that the location is correct. You can see that my default location wasn't correct and that I had to select the proper OS: drive.
Once that's all out of the way, the installation will proceed. Let it do its thing until it asks for the next disk. This will be the Fonts disk. It'll want the Storage disk after this, so put both in to the df2 and df3 slots. Hit F12 to get to the WinUAE Configuration window and then swap the disks in.
Let it chug along further until you get the message to restart. Hit F12, eject all of the floppies and then reboot the emulator.
Congratulations, Workbench 3.1 is installed.
Other patches
Firstly, add the latest version of Installer to your new WB. This will allow your to install applications that require the newest version. Drag the executable to OS:C/.
NSD is the 'new style devices' driver. Install this by double-clicking on it.
Now it's time for the next version of WB.
Workbench 3.9
And finally... The latest release for the A1200. This is also very simple. Just make sure you have the 8mb of FastRAM configured and the ISO in the drive. Firstly, we need to set up the CD Drive. So head into WinUAE Configuration and then to the CD/HD tab. Here you need to Add SCSI/IDE CD Drive. Choose the defaults and hit Add CD Drive. Load the ISO into the slot at the bottom of the window. Also make sure that CDFS automount is enabled!
Reboot WinUAE and you'll have a CD on your desktop. Find and run the installer.
And now... the installation. Very straight-forward... hit Proceed in most circumstances. Just make sure you choose a OS3.9 full installation over OS3.0 or empty HD. Also make sure you get the target disk correct!
Restart and you're there! Browse to OS:Prefs and double-click on ScreeMode to change the screen resolution and color depth.
Boing Bags
These are service packs for the Amiga. Download Boing Bags 1-4 here. Install them in order, started from 1. Make sure you install the ROM update from BB2.
Extra features
The OS3.9 CD has a few goodies on it. Don't forget to browse it and copy anything you might want onto your HDD. It gets painful down the track if you have safely secured your CD Card into your A1200 and then have to fight floppy disks to transfer data across.
I'll post again on OS3.9 when I work out what you can do with it. Learning starts now...
Commodore 64: Tape Drives
This was really a lesson in jumping in the deep-end. I'd bought Double Dragon for the C64 recently on eBay, somewhat blindly, and found out that it was on datasette. It had been relegated to museum shelf until I happened across a cheap tape drive. Below is the story of learning how these things work.
The Datasette drive arrives
The seller had mentioned 'original box' and 'complete'. Indeed it was. I take it, though, that the box had been in the back of the shed for quite some time. The manual pages were mould-glued shut and the box was probably releasing spores every time I touched it. Due to this, I took photos for historical-sake and then destroyed the evidence; keeping only the actual drive.
The first game: Double Dragon
Double Dragon (for C64), released by Melbourne House (more information here), is a side-scrolling beat-em-up. The name of the company is confusing, as it sounds like it might be Australian, but was actually started in London. There is/was an Australian branch though, but it was known as Beam Software.
I'd played this game with my brother decades ago on the 286/386. Actually, I think nearly every Australian kid who grew up in the 80s/90s would've played this on a platform somewhere... maybe sega/nintendo/x86/amiga/atari/etc... Or even at the arcade! Either way, I wanted to check this out on the Commodore 64.
Intricacies of a Datasette Drive
This device has a single cable that plugs onto the PCB edge-connector 'port' at the back of the C64. It's a similar connection to the User Port; very raw and very cheap for Commodore to produce en-mass. The cable also has a grounding wire, but I don't quite know where you'd connect this to?
As you can see, everything was there, ready to hook up and run. I did just that, plugging the tape drive in, the power and TV. Switching it all on brought me to the usual BASIC prompt. I had heard the tape drive make a few faint clicks. The tape needed rewinding, so I let that run it's course (hah, how I don't miss having to do that!)
I'd found out, by accidently typing only LOAD previously, that this was the command to load tapes. After hitting enter, you get a prompt to press the Play button on the tape drive. So, at this point, I did exactly that. As I hit Play on the Datasette, the tape spun, then slowed down... then .... stopped......... Sure, this 30 year-old equipment had been stagnating in someones shed for decades... probably worn-out and dead.
As I was pressing the eject button, the tape started playing again... demons? ghost in the machine? not quite... turns out that a slight bit of pressure on the eject button releases the bottom-right 'guide' that keeps tension on the magnetic tape in the tape case as the tape is playing. This component was able to apply WAY too much tension and therefore effectively 'braked' the tape and cause the motor/band to slip.
'Band', you say? Yes. Band. No belts with teeth here... the drive mechanism is a cute little motor with a rubber band driving the other pulleys. There is then a further rubber band to drive the counter. 'How do I know this?', you ask? ... I took the thing apart as soon as I could!
The bands looked pretty stock-standard. A quick google showed zero results for replacements... so... searching around the house I found some probably-too-small substitutes. Having a band too tight will probably pull on components and put waaaay too much pressure on old equipment that may not be rated for it; but hey, you can only try!
Replacing the main band required 3 screws to be removed. See above. Once removed or loosened, you can slip the main band out.
I then put my two replacements in. Lo'and'behold... the bloody tape started playing. The results? The screen went blue for 15.5 counts, the tape paused and then a message came up: Found Double Dragon! Woah.. then a slight pause and then the tape continued. Then it was ... rubbish?
I gave up after 20s of garbage picture and noise... must be a crap tape or damaged head. But... it did recognise the data on the tape... I mean, it knew the name?
Another Tape: The Android
Along with the datasette drive, I also purchased a 5-tape set of miscellaneous games. I whipped the first one out, named Android and tried it. The C64 successully saw that it was indeed The Android and then ... more crap loading. Loosing heart, full bladder, too much Sapporo... I excused myself, letting it play...
Returned a while later and.. WHAT THE!?... It loaded!? So... wait... those loading images are expected!? That crappy video that would trigger an epileptic fit and that sound that's drilling into my inner-ear is normal?
Android Control (different name on tape vs. name on 'found' vs. name on title?) loaded, I played it for 5 seconds... but I'll try that again later.
Back to Double Dragon
So, now knowing that I need to sit back and wait... I slapped Double Dragon back in again. It took the same 15.5 counts to the Found Double Dragon message and then 58 counts to the next message.
Ok, we're getting somewhere... I hit space...
DAMN! 3 minutes of waiting... first time around the tape actually reached the end... that actually indicates a data issue. Second time around I got the error above. So Android will load, but Double Dragon wont! Might have to clean the heads... Will report back when I have time to try again.
Amiga 1200: Indivision AGA MK2cr
After toying with video output on the Amiga 1200, I'd come to the realisation that old technology just wont display well on brand new TVs. Due to this I therefore researched what everyone else on the interwebs did to get a better resolution from these old beasts.
Results came up quickly and all signs pointed to this device, the Indivision AGA MK2cr by Individual Computers (buy it here.)
This device piggy-backs onto the Lisa chip (the Amiga's Video processing chip), taking the output and then using it's own CPU to convert this to a more recent standard that newer TVs and Monitors can reproduce. It has upgradeable firmware and configurable options. It's stated that, out-of-the-box, you won't need to configure a single boolean value to get it running.
The price isn't low.. but all the reviews I'd read indicated that the value for money ratio was high. I therefore set out in pursuit to find one and came across some hits on Amibay, but watched as faster hawks snapped the prey up VERY quickly.
eBay showed some results also; and then it happened... the exact seller of whom I bought the 1200 from provided the device up for sale. I couldn't resist... Steve understands eBay and had treated me perfectly on the previous sale and so I grabbed this item. Turns out I got a few extra bits too, of which I'll report about later.
Unboxing
The item arrived in record time and it felt like Christmas had come early once again. I knew I had to take my time with this though... opening a ~30 year old piece of equipment (although you could tell it had previously already been modified) required patience and a steady hand.
The box came with all the required components. No software is provided as the device works primarily via its own firmware to detect the video signals being produced by the Lisa chip and convert them out to DVI-capable resolutions.
Installing it
All in all, this was a piece of cake. As mentioned, this Amiga 1200 had been toyed with before and so there was no hassle of breaking warranty seals and the like. Case screws were undone and then the top plate was removed.
I'd previously wondered what the extra DB-25 port was on the left-hand-side (as you look at the back of the machine) and this became clear on opening the case. It's a SCSI port for an accelerator that contains a SCSI interface. As you can see, no such accelerator is in the slot, so the internal end of the cable was sitting loose.
Over near the Lisa chip, I found the CF card to be sitting directly above it. Turns out that there's enough clearance above the MK2cr to have the CF sitting on top, so I unstuck it, flipped it over and stuck the new hardware in place. This went on with a nice click. Even pressure was applied to the center area, where the 4 holes are. Once in place, the cable was run (very tightly, it's the perfect length!) to the location where I'd removed the SCSI plug. Fortunately, I received a 3D printed plate to hold the DVI connector in place. I did need aftermarket screws to secure the plug in the housing; the nuts that were removed from the end of the DVI plug weren't long enough to go through the plastic plate.
First Impression
This always counts... this device was marketed as 'no configuration' ... reminds me of the old 'plug-n-pray' days. Sometimes I miss ISA/EISA cards and their jumpers of which I had supreme control over. Anyway, I hooked up a cheap VGA cable via a DVI-to-VGA adapter and plugged it into the TV. Powering it up... I nearly cried.
The ghosting was immense ... and I truly wasn't expecting this from a Digital product. A quick google lead me to believe that the DVI-to-VGA and then the VGA cable were trashing the signal. I also noted that the resolution being output was 1280x1024. That's a great resolution; but a shitty VGA cable might have issues. I quickly tested the shit VGA cable and adapter on a VGA monitor... it looked better, but still not up to the standard I was expecting.
So, I opened the warchest and found a chunkier cable that had a DVI on one end and a VGA on the other... on the LCD Monitor still showed minor ghosting, as follows...
But on the TV, the ghosting was hardly noticable... Score! This is the pixel-for-pixel that I was looking for.
At this stage I was content with the output... It has since occurred to me that I could convert the DVI to HDMI and keep the signal totally digital. I'll try that and report back soon.
Configuration Options
The 'overscan' area can be customised. On bigger screens, it will default to the background colour of Workbench. You can confgure this using BorderBlank.
Next, resolutions. The MK2cr supports the 'HighRes' driver. Download it here. To install it, move the HighGFX file from the archive to your "sys:devs/monitors" directory and reboot. Higher resolutions should now be visible in the ScreenMode prefs.
If you want to toy with the devices configuration itself, then firstly grab the Flashing tool and v2.6 firmware to see if you have the latest installed. Once you're up to date, you can use the v1.5 Config Tool to configure the device.
From what I understand, you choose the mode on the left and then re-map it to a resolution from the VGA column. Then hit Test/Use/Save. Will update when I tinker further...
The resolutions are awesome. You can find the HD720 driver in the HIGHGFX package, drag that to your dev/monitors folder also. I switched my TV to 16:9 scaling and the resolution looked great!
Time for model train controller programming and games!
Parallel Port: Digital to Analog
Most parallel ports on most computers (Amiga, C64, PC, etc...) have (at least) 8 true digital pins that one can interface with for either input or output. This can be extended using shift registers or multiplexers; I've written up an example of this here.
With a lot more pins at one's disposal, more items can be controlled; as long as what you want to control is in the digital realm. To control a standard DC motor, for example, you'll need to be in the analog world and we'll describe how to get there below.
Digital to Analog Converters
The basic principal is to use a number of digital inputs and map these to relevant resistance values on an output. If you had 4 pins available, then you could determine the maximum resistance you needed and divide by the number of pins. You'd then make sure that, as each pin was brought HIGH, that the resistance summed towards the final value that you required.
You can either use a bunch of resistors to do this, or an integrated DAC circuit.
R/2R Ladder
This circuit consists of a bunch of resisters in parallel/serial, using properties of such combinations to provide a stepped resistance output. This is known as an R/2R Ladder and is a popular method for converting digital signals to analog.
We're going to implement one using 10k/20k resistors and an LM358 opamp to buffer the output. This part of the circuit is borrowed from the 8-bit digital to analog converter circuit over at IKALOGIC.
Combining it into our Parallel Port Interface
The 8 outputs from the 595 need to be de-coupled from the LEDs and provided as the inputs to the resistor ladder.
As that we can send any value to the 595, you don't really need to be careful as to which end is the MSB, but do remind yourself of it as it'll become important when writing the software.
You should now be able to check the voltage on pins 1 or 2 to see the variance between 0 and 5v when you're switching the bits on and off.
To my surprise, the output voltage was nearly exactly double the byte value being output by both the Commodore 64 and the Windows Parallel Port.
Once the test breadboard produced the result I wanted, I confirmed it all by soldering it together on the PCB. Not the ugliest mess I've created, but not very far off! And... it works.
What's next?
Now that we have an analog interface, we can control a PWM throttle. To do this, we'll go back to the Arduino world and steal a motor controller. I've previously worked with H-Bridges before and will use a module to make this easy. Controlling direction will be easy also!