This page describes in some detail my effort for the Retro Challence 2018/04 event.
(TL;DR = make microcontroller-firmware for a Commodore 64 ("C64") cartridge-adapter ("Bait-a-Cart"), and finish/modify/shoehorn a small C64 intro to be uploaded to, and hosted by the Bait-a-Cart.)
The C64 can run ROM-cartridges plugged into its cartridge-port. This forms a no-fuss way to run games and utilities: simply turn on the C64 to run the program on the ROM-cartridge.
Because this is so easy, ROM-cartridges can be a convenient medium where retro-machines such as the C64 are used for display or hands-on experience, e.g. at a retro-event, party, etc.
The idea of the Bait-a-Cart cartridge-adapter is to be able to show an appropriate logo, intro or text before such a ROM-cartridge starts.
To do this, the Bait-a-Cart is placed physically in between the C64 and an existing ROM-cartridge. The Bait-a-Cart plugs into the C64, and the ROM-cartridge plugs into the Bait-a-Cart:
When the C64 is turned on, the Bait-a-Cart itself mimics a ROM-cartridge containing an intermediate program (logo, intro, etc). This program is then ran until the user presses the space-bar, after which the Bait-a-Cart disables itself, and the actual ROM-cartridge is started.
This is basically the hardware-version of adding a crack-intro to a game.
(The name "Bait-a-Cart" comes from the way "bait" (in the form of an event-/club-specific logo or intro) is added to an existing ROM-cartridge. This makes for a nice attract-mode while the computer is idle.)
Although the (nearly untested) hardware for this project exists, software mostly does not. Effort during the RetroChallenge will go into making firmware ("microcontroller-software" hereafter) for the Bait-a-Cart, as well as finishing a work-in-progress C64 intro ("C64-software" hereafter), and hosting the latter on the Bait-a-Cart flash to enable the C64 to run it at boot.
So, my plan for RC2018/04 is to...
Where relevant, progress will be split up into...
There are several flaws with the PCB-design that need to be fixed in a possible future version. Just for fun, here's a list, showing that failure is all around:
If the Bait-a-Cart turns out to be a nice idea in practice, perhaps it makes sense to make a similar adapter for other home-computers and consoles:
Perhaps this project can be a gateway into getting to know new computers - no idea.
The Bait-a-Cart in action (2018-05-04):
...and again, elsewhere (2018-05-05):
Here are relevant software- and hardware-files that may be of interest to you.
NOTE / CAVEAT: USE AT YOUR OWN RISK. I know there are flaws in the Gerbers, so please don't come bugging me if your build doesn't work, doesn't fit, or brings you grief in general :-)
However, have fun:
Note that refdes-codes ("R101", C154" etc.) are not included on the silkscreen, so you'll have to figure it out from the centroid-file.
People seem to like videos, so here are some new and older ones (pre-RC2018/04) having to do with this project:
These videos are combined into a Bait-a-Cart YouTube playlist
I like to receive feedback and comments, so please, go nuts.
Testing 2 Bait-a-Carts in cascade (the 2nd one served as makeshift ROM-cartridge) didn't work reliably. The culprit were TVSes with unexpectedly high capacitance - so these were removed to fix the problem.
I couldn't wait to test with real ROM-cartridges, so ended up using 2 Bait-a-Cart adapters in cascade. One adapter was used as-is, and the second one used a modified firmware (simply mimic a ROM-cartridge, ignoring /IO1 and /IO2):
However, basic ROM-cartridge images would not run reliably off the 2nd adapter. Inspection with scope showed that the Bait-a-Cart(s) had a negative impact on signal-integrity (see "before" & "after" pics later on).
I used TVSes at the Bait-a-Cart's own cartridge-slot. The idea was to avoid ROM-cartridges being discharged (ESD) through the Bait-a-Cart upon insertion of the former. However, a look at the datasheet listed capacitance quite high for those parts (Vishay GSOT05C):
The following pic shows the C64's clock-signal (PHI2) with Bait-a-Cart plugged in, before (white trace) and after removing the TVSes (yellow trace):
Thanks, but no thanks. :-)
I'll rely on the ESD-protection of relevant components (74HCT541) then, which would probably have been fine in the first place.
For ROM-images, I tested Omega Race (Ultimax), Jupiter Lander (Ultimax), Blok Copy (16k) and P0 Snake (16k), and all worked fine when hosted on a 2nd Bait-a-Cart adapter. (The 1541Ultimate2 still didn't work when hosting a cartridge-image, though.)
Got some ROM-images off the Internet, and uploaded them to the Bait-a-Cart to test them out (and to play some games :-).
Tried "Omega Race" (both 8k and Ultimax versions), "Jupiter Lander" (Ultimax), "Blok Copy" and "P0 Snake" (both 16k homebrew games), and all worked fine. P0 Snake is pretty cool, BTW! :-)
However, the 1541Ultimate2 as hosted cartridge, set to execute a 8k test-ROM, didn't seem to work. I don't know which voodoo it does on the cartridge-port, so I'm gonna ignore this - will test with actual 8k / Ultimax cartridges in a week.
I think that's it - everything works, and I'm super-happy.
Modified the C64-software to access /IO1- and /IO2-regions when the spacebar is pressed, and added a user-interface to the microcontroller-software. Finally, made a video of the Bait-a-Cart in action.
Keypress-detect on C64 uses a very simplified keyboard-matrix scan (only scan for activity on row 7 / column 4, where the spacebar is located):
keyscan: .proc
lda #$7f ; row 7...
sta $dc00
nop
lda $dc01
and #$10 ; ...and column 4 = space
bne x
lda $de00 ; pulse /IO1...
lda $df00 ; ...and /IO2
x:
rts
.pend
This code is called on every raster-interrupt, that is, 60 times per second.
A previous test (see entry for 2018-04-05) showed that whenever the C64-code accessed I/O-regions, those events could be captured by the microcontroller-software.
The way in which the microcontroller enables/disables the Bait-a-Cart then becomes quite simple:
Modes "ON" and "OFF/PROG" correspond to simplified pictures given earlier (see entry for 2018-04-14).
The select-button selects between 4 stored 16k-images to run whenever the reset-button is pressed or the C64 is powercycled. 4 LEDs serve as feedback (LED 1 corresponds to 16k-image 1, etc.).
The currently selected image (0..3) is stored in the microcontroller's EEPROM. This way, the selection is remembered between sessions.
There's a 16k-region in flash reserved for each ROM-image, regardless of whether the image is an 8k or 16k image. To know the correct C64 memory-configuration to use for each such ROM-image (namely, "no cartridge", "8 kb cartridge", "16 kb cartridge" or "Ultimax-cartridge"), the user must input that configuration when ROM-images are uploaded to the Bait-a-Cart, using the upload-script on the PC.
While the actual content of each ROM-image is stored in internal flash, I chose to store corresponding cartridge-types in EEPROM. Both have lifetimes of about 100k cycles, so that should be fine.
The serial command-monitor was extended with commands to read and write generic byte-sized EEPROM-parameters.
The upload-script was altered to also take the cartridge-type as a number:
./upload <filename> <slot16k> <cartType:0(none),1(8k),2(16k),3(umax)> <dev>
(This will upload the ROM-image in filename to the Bait-a-Cart's flash at slot slot16k, and store cartridge-type cartType in the microcontroller's EEPROM.)
During testing, I used this small script to upload 4 similar but visually different ROM-images:
#!/bin/env bash
S=./upload
D=/dev/ttyUSB0
# filename slot16k carttype
# -------- ------- --------
$S ../c64/crt.crt 0 1 $D
$S ../c64/crt_green.crt 1 1 $D
$S ../c64/crt_blue.crt 2 1 $D
$S ../c64/crt_grey.crt 3 1 $D
As can be seen, all of these ROM-images happen to be of type "8 kb cartridge" ("0").
Nothing really, except covered the hack-wires/-capacitors/-resistors with a blob of hot-glue to protect them from Real Life:
(Active-high) ROMH instead of ROML is now finally used as 14th address-bit when then C64 accesses the Bait-a-Cart's flash, preventing what was beginning to be a PITA in workarounds. Took 15 minutes to solder. Also made rudimentary software for uploading ROM-images to the flash using a Tcl-script.
The serial command-monitor on the Bait-a-Cart already offered an ASCII-interface for flash-programming. This was used as-is, with the exception of a changed maximum blocksize (15 --> 255 bytes) to lower overhead when sending larger amounts of data.
On the PC-side, I made a Tcl script to simply read a ROM-image from disk, and send it to the Bait-a-Cart using its serial command-monitor. Usage is simple:
./upload <filename> <slot16k> <dev>
...where 'filename' is a raw cartridge-image, 'slot16k' (0-15) is one out of 16 possible slots in the Bait-a-Cart's flash, and 'dev' is the name of a serial device. Example:
./upload mycart.crt 2 /dev/ttyUSB0
...would flash contents of 'mycart.crt' into 16k-slot #2 (0x08000-0x0bfff) in the flash, using the Bait-a-Cart's USB serial interface.
The script itself is listed below.
Used versions of the existing demo-code with alternate background-colours (green, blue or grey instead of red) to verify the correct image ended up in the correct 16k-slot, and that it could be started afterwards.
Tcl is a bit like kneading sushi-rice - feels weird but nice.
#!/usr/bin/env tclsh
foreach op { + - * ** / & | << >> < <= > >= && || } { proc $op { a b } [ list expr \$a $op \$b ] }
proc I x { set x }
proc toHex20 val { format %05x $val }
proc toHex8 val { format %02x $val }
# Returns list of reply-lines
proc doCmd { cmd ch { sleepMs 0 } } {
puts "-> $cmd"
puts -nonewline $ch "$cmd\r"
after $sleepMs
set reply [ split [ read $ch ] "\n" ]
foreach s $reply { puts "<- $s" }
I $reply
}
proc eraseSector { sec ch } { doCmd "se[ toHex8 $sec ]" $ch }
proc eraseSlot16k { slot16k ch } {
puts "erasing 16kb-slot #$slot16k"
set startSec [ * $slot16k 4 ]
foreach i [ list 0 1 2 3 ] { eraseSector [ + $startSec $i ] $ch }
}
# Writes at most 255 bytes.
proc writeBlock { data ofs20 ch } {
set hex [ binary encode hex $data ]
set N [ string length $data ]
doCmd "dw[ toHex20 $ofs20 ][ toHex8 $N ]$hex" $ch
}
proc writeFile { fname slot16k ch } {
eraseSlot16k $slot16k $ch
puts "writing file '$fname' to slot $slot16k"
set f [ open $fname r ]
fconfigure $f -translation binary
set ofs20 [ * $slot16k 16384 ]
set N 64
while { ! [ eof $f ] } {
set data [ read $f $N ]
if { [ string length $data ] > 0 } {
writeBlock $data $ofs20 $ch
incr ofs20 $N
}
}
close $f
}
proc openSerDev dev {
set ch [ open $dev r+ ]
fconfigure $ch \
-mode 19200,n,8,1 \
-handshake none \
-blocking 1 \
-translation binary \
-timeout 100 \
-buffering none
doCmd "" $ch ;# flush pending output
I $ch
}
if { $argc < 3 } { puts "use <filename> <slot16k> <dev>"; exit }
lassign $argv fname slot16k dev
set ch [ openSerDev $dev ]
writeFile $fname $slot16k $ch
close $ch
Be kind to animals.
To fix the issue from 2018-04-18, soldered some resistors and caps to the microcontroller's /IO1 and /IO2 inputs:
(Ordered the wrong SMD components, and instead, just used through-hole stuff.)
Verified on the scope that signals were as expected. So now, let's finish the software. :-)
Apart from the aforementioned small "mini-cartridge" images, there is now an actual 8 kb ROM-cartridge image running from the Bait-a-Cart's flash:
Effort went into reading existing code and actual relocation/fixing.
The original code that can be seen running in the above picture, had its code and data located at 0x0801 (BASIC memory area start), with graphics located at 0x4000 and a musical tune at 0x5000. Lots of padding in between these regions resulted in an image of about 30 kb.
There was quite a lot of non-initialised and initialised writable data used in the original program, which would have to be located outside of the cartridge's ROM-region (0x8000 - 0x9fff for a 8 kb cartridge such as the one I was planning for this demo-code).
I used 64tass as 6502-assembler, which suits me well. I had already written a quick summary for this assembler earlier.
As for the data-relocation, I ended up copying the whole ro/rw data region from the ROM-image to RAM, and then starting the main cartridge-code, which would use this relocated data. All of this took an embarrassingly long time to do, because I'm not used to this stuff. But it's interesting. Most time went into reading about VIC-banks, screen- and charset-locations, making silly mistakes like forgetting ROM is read-only, and using assumptions w.r.t. the C64's possible memory-configurations.
Raw 8 kb cartridge-images can be started in VICE, which was very helpful during debugging.
Since the PC-side of the serial communication-link has not yet been implemented, I chose to convert the raw 8 kb cartridge-image to something that could be linked into the microcontroller-code (written in C).
Running...
xxd -i crt.crt | sed '1s/=/PROGMEM =/;1s/^/const /' > crt.inc
...will convert raw 8 kb cartridge-image "crt.crt" to C-code like this:
const unsigned char crt_crt[] PROGMEM = {
0x09, 0x80, 0x19, 0x80, 0xc3, 0xc2, 0xcd, 0x38, 0x30, 0x78, 0x8e, 0x16,
0xd0, 0x20, 0xa3, 0xfd, 0x20, 0x50, 0xfd, 0x20, 0x15, 0xfd, 0x20, 0x5b,
...
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned int crt_crt_len = 8192;
...ready to be compiled-in and used.
The serial command-monitor was extended with a temporary command to flash this cartridge-image to a given 16k-slot in the Bait-a-Cart's flash, and everything worked fine.
Nice! In order to debug this thing, made a serial command-monitor for issuing commands and rudimentary uploading/verifying ROM-images. This basically shows the Bait-a-Cart works as intended, albeit still through a serial command-link only instead of having a pushbutton/LED interface.
Serial input coming in on the USB-connector is parsed into commands. One such command is "help", which displays an overview of all available commands:
Build: Apr 21 2018 01:15:14
s : (e)rase<sec8>, (c)sum<sec8> ("sector")
d : (w)rite<ofs20><num4><u8>*, (r)ead<ofs20> ("data")
m : <region4><col4> ("minicart")
b : o(n)<region4><carttype4>/of(f)/(r)eset/(t)ellmode ("Bait_a_Cart")
x : erase whole flash ("xtra")
h : print this help-page ("help")
This interface allows for manual testing and controlling of the Bait-a-Cart. All available features on a row:
The last option (write mini-cartridge image, or "minicart") writes a ROM-image into the flash at a specified location. This ROM-image, when ran, toggles the C64's border-colour between black and a specified alternate colour. By using different alternate colours for different ROM-images, these can be visually identified to verify that ROM-image selection works.
The source-code for this mini-cartridge looks similar to aforementioned minimal ROM-image:
* = $8000
.word Start ; cold-start vector
.word Start ; warm-start vector
.byte $c3, $c2, $cd, $38, $30 ; "CBM80"
Start:
lda #4 ; <---
sta $d020
lda #0
sta $d020
jmp Start
* = $9fff ; fill
.byte $0
(Using a serial command-argument, the colour-code "4" indicated by the arrow can be changed into any other colour - for example, "1" for white.)
An example of such a mini-cartridge image sitting at the 1st 16k-slot in the flash (0x00000 - 0x03fff):
0x02000:
09 80 09 80 C3 C2 CD 38 30 A9 01 8D 20 D0 A9 00
8D 20 D0 4C 09 80 FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
...
As mentioned elsewhere in this text, I miswired ROML and ROMH somewhere, causing the need to swap lower and upper 8k-blocks in each ROM-image. Without this mistake, the 1st byte of the 1st ROM-image would be at flash-address 0x00000 instead of 0x02000.
Apparently, short stray pulses on /IO1 and /IO2 are generated at least by my C64. Although these are harmless for normal operation, they were mistakenly picked out by the Bait-a-Cart's microcontroller. A low-pass filter on the Bait-a-Cart side solves it.
(I only did measurements and tested a quickfix, because right now I don't actually have SMD-parts here to fix this problem on the Bait-a-Cart itself.)
For the previous progress-update (2018-04-15), I used a program to rotate a LED-pattern clockwise / counter-clockwise once when a pulse on /IO1 respectively /IO2 was detected. After running for a while, I noticed the LED-pattern moving without explicitly using PEEKs or POKEs. Changes were quite random. This probably indicated unexpected activity on /IO1 and /IO2.
As can be seen in the (partial) C64 schematic, a 74LS139 dual 1:4 demux/decoder (U15) generates both /IO1 and /IO2:
The pins circled in green are interesting: U15's pin 15 (input /EN2) enables its low-active outputs, of which 2 lines (pins 10 and 9, or /2Y2 and /2Y3) are directly connected to the cartridge-port's /IO1 and /IO2. These signals can only be low if U15's /EN2 (called "Enable" or "G" in this figure) is low:
(Table from ON Semiconductor 74LS139 datasheet, although my C64 used a Fairchild 74LS139PC.)
The select-inputs of this half of U15 are formed by A8 and A9 of the C64's address-bus. When /EN2 is low (active), /IO1 will be activated when only A9 is high, while /IO2 will be activated when both A8 and A9 are high.
Viewing /EN2, A8, A9 and either /IO1 or /IO2 on the scope, occasional short pulses on /IO1 and /IO2 can indeed be seen:
Propagation-delay of U15's select- en enable-inputs is in the order of 20-30 ns. As can be seen, the time between A9 rising and /EN2 being released is also around 20-30 ns, which will occasionally cause stray activity on either /IO2 or /IO1, depending on timing of A8 in those situations.
The Bait-a-Cart should ignore these short pulses, and should only be triggered by "real" I/O-area access-signals, which are in the order of 500 ns. Either the software or hardware could be altered to make this happen - I think a hardware-fix makes more sense.
Using a sensible low-pass filter between the C64's /IO1 and /IO2 signals and the Bait-a-Cart's corresponding microcontroller-inputs will filter out short pulses, while keeping /IO1 and /IO2 themselfes pretty much as-is, in order not to mess with ROM-cartridges plugged into the Bait-a-Cart.
Using an RC-filter of 4k7 Ohm and 22 pF (cutoff around 1.5 MHz), the Bait-a-Cart's microcontroller-input becomes quiet like the Danish countryside railroad-track at night:
The blue signal is raised a bit. This is caused by a 47k Ohm pull-up resistor, which I used to mimic the internal pull-up of the Bait-a-Cart's microcontroller-pin. (Have to think whether it makes sense in any situation to use pull-ups on these pins at all. :-)
Same setup, but with "real" I/O-area access-signals (500 ns) on /IO1 and /IO2:
(The rightmost picture is similar to the leftmost one, but zoomed out a bit.)
As can be seen, pulses still look good. The Bait-a-Cart's microcontroller will see the yellow trace as input, which is enough to trigger on.
The Bait-a-Cart can reliably capture reads/writes by the C64 to I/O regions $DE00-$DEFF (IO1) and $DF00-DFFF (IO2). This mechanism will be used to allow C64-software to disable the Bait-a-Cart's flash in favour of an existing ROM-cartridge (or the C64's own BASIC/KERNAL).
To test this, I used a small program reading/clearing the AVR's EIFR (External Interrupt Flag Register). On the C64's side, I used simple PEEKs and POKEs.
And later still...
I had a "Magic Desk I" 8 kb ROM-cartridge laying around, especially for testing-purposes.
Using a button on the Bait-a-Cart to toggle between "on"- and "off/programming" modes works just fine. In short, this hack demonstrates most of what the Bait-a-Cart was intended for. (It's all about self-confidence, I guess. :-)
This picture shows a typical setup with Bait-a-Cart and existing ROM-cartridge, inserted vertically.
Later that day, ...
...I just had to see an actual C64 run a minimal ROM-image hosted on the Bait-a-Cart.
That's not just gibberish on the screen, it's all intended. Yay! :-)
I used the following program as minimal ROM-image:
* = $8000
.word Start ; cold-start vector
.word Start ; warm-start vector
.byte $c3, $c2, $cd, $38, $30 ; "CBM80": cart-identifier
Start:
inc $d020 ; increment border-colour
jmp Start
* = $9fff ; pad until 8 kb
.byte $0
...assembled it with 64tass (or tass64):
tass64 -q -B -b -L lst.lst --tab-size=1 -o crt.crt asm.asm
...and first tested it in VICE:
x64 -autostart-warp -cart8 crt.crt
...which seemed to work:
(TBH, I can't remember now why the middle of the screen remains blank - either it is a VICE quirk, missing command-line option, video-emulation thing, KERNAL-code thing, or something else. IIRC, the video should not have been initialised when cartridge-code is found and executed. Oh well, worked as expected on real hardware.)
To test this image using actual hardware, I wrote the verbatim hex-code to the Bait-a-Cart's flash from software:
static const uint8_t a[] = {
0x09, 0x80,
0x09, 0x80,
0xc3, 0xc2, 0xcd, 0x38, 0x30,
0xee, 0x20, 0xd0,
0x4c, 0x09, 0x80
};
flash_erase_chip();
uint8_t i;
for ( i = 0; i < sizeof( a ); i++ )
{
flash_prog_byte( 0x2000 + i, a[ i ] );
}
Note the "0x2000" offset (8 kb) near the bottom - this is because the ROML signal was erroneously used instead of ROMH as 14th address-line for the flash, so we will have to swap the lower and upper 8 kb in each image from now on...
After programming, the Bait-a-Cart basically asserts /EXROM and releases /GAME towards the C64, to indicate it is pretending to be an 8 kb cartridge. The Bait-a-Cart then resets the C64, and goes to sleep while the C64 validates and runs the cartridge-image.
Instead of using magic constants in I/O-related software, cleaned it up to give way for final software. Tested the Bait-a-Cart's flash, and simplified some intended modes of operation.
Let's distinguish just 2 modes of operation:
Switching between these 2 modes effectively makes the C64 run either from the Bait-a-Cart's flash or from the existing ROM-cartridge.
Simplified pictures help to illustrate the connections between different components in each mode ("MCU" = microcontroller):
In the "on"-mode, the Bait-a-Cart's flash is used instead of the existing ROM-cartridge:
In this mode, the microcontroller is effectively decoupled from the Bait-a-Cart's flash (by tri-stating its connections to the flash's address- and data-signals). The ROM-cartridge is disabled, by making sure it can never be selected (by de-asserting the ROMDRV signal in the schematic).
In the "off/programming" mode, the ROM-cartridge is enabled instead of the Bait-a-Cart's flash:
In this mode, the Bait-a-Cart's flash is coupled to the microcontroller in order to allow for programming through serial commands, and the ROM-cartridge is connected to the C64's data-bus. (Or rather, it is allowed to be selected by the C64 by asserting the ROMDRV signal).
If interested, see the schematic for details.
The Bait-a-Cart has a 256 kb flash. The C64 cartridge-size is limited to 16 kb, disregarding bankswitch-magic. Therefore, the Bait-a-Cart's flash can hold a maximum of 16 cartridge-images of 16 kb each.
The 18-bit address-bus of the Bait-a-Cart's flash (for 256 kb in total) can be divided into 2 parts:
I made code to properly configure all "switches" shown in the above pictures in both "on"- and "off/programming"-modes, and tested the flash w.r.t. chip-ID read, byte-read, byte-write, sector-erase and chip-erase, and all worked well.
Made a small digital probe to quickly distinguish high / low / floating pins.
Toyed with this idea for a while already, but this project finally made me get off my ass and make it.
Using a dual comparator, this probe can quickly show whether a pin is floating, grounded or connected to Vcc. Perhaps it'll come in handy when checking correct working of the latches.
Just made some friendly documentation, avoiding the need for myself to constantly switch between various on-line references.
The C64's cartridge-port (or "expansion port") has 44 signals in total.
Apart from 16 address- and 8 data-lines, there are a number of control-signals. Some of these signals (e.g. /DMA, /IRQ or DOT_CLK) are not relevant for the Bait-a-Cart functionality, and are simply passed from the C64's cartridge port to an existing ROM-cartridge plugged into the Bait-a-Cart.
All of the C64's control-signals used by the Bait-a-Cart are listed below, along with their direction (from C64 towards ROM-cartridge or vice versa):
pin name C64 <--> cart descr
8 /GAME <-- for memory-reconfig; see GAME and EXROM table below
9 /EXROM <-- for memory-reconfig; see GAME and EXROM table below
11 /ROML --> access in range $8000-$9FFF, and RAM disabled (GAME and EXROM)
B /ROMH --> access within $A000-$BFFF or $E000-FFFF (GAME and EXROM), and RAM disabled
7 /IO1 --> access within $DE00-$DEFF
10 /IO2 --> access within $DF00-$DFFF
C /RST <-- (PU) pulled low: reset CPU
This section goes over each of the C64's cartridge-port signals, and describes the way in which the Bait-a-Cart uses that signal. (See the schematic for details.)
Focus is on the C64's cartridge-port signals and those of the Bait-a-Cart's own cartridge-slot. The fact that the Bait-a-Cart's microcontroller is able to program the Bait-a-Cart flash is ignored here for sake of simplicity.
Both of these signals are available on the Bait-a-Cart's own cartridge-slot (as AUX_/ROML and AUX_/ROMH), albeit gated using a ROMDRV signal from the Bait-a-Cart's microcontroller. The microcontroller can thus prevent the existing ROM-cartridge plugged into the Bait-a-Cart from ever being selected. This is exactly what happens whenever the Bait-a-Cart's flash is enabled in favour of the existing ROM-cartridge (see "Data-bus" below for more info).
Furthermore, whenever the Bait-a-Cart's flash is enabled, ...
A ROM-cartridge can pull /GAME, /EXROM or both to ground, to tell the C64 which memory-regions the cartridge implements, and where those regions should be mapped in the C64's address-space.
(Apart from the /GAME and /EXROM lines in the C64's cartridge-port, the 6510 CPU itself provides 3 pins - CHAREN, HIRAM and LORAM - to further configure the memory-configuration during run-time. These 3 pins are not discussed in this project. If interested, look at details about each available memory-configuration.)
In short, the /GAME and /EXROM pins allow for 3 cartridge-flavours (see here for details):
/GAME /EXROM descr
1 1 (no cartridge present)
1 0 8k cartridge. /ROML: $8000-$9FFF
0 0 16k cartridge. /ROML: $8000-$9FFF; /ROMH: $A000-$BFFF
0 1 "Ultimax"-style 16k cartridge. /ROML: $8000-$9FFF; /ROMH: $E000-$FFFF
Thus, disrding runtime memory-configuration using the CHAREN / HIRAM / LORAM lines, part of a normal 16k cartridge occupies the BASIC ROM region ($A000-$BFFF), while part of an Ultimax-style cartridge occupies the KERNAL ROM ($E000-$FFFF). An 8k cartridge doesn't occupy either BASIC or KERNAL ROM regions.
In the Bait-a-Cart setup, the Bait-a-Cart's microcontroller reads the ROM-cartridge's intended memory-configuration through the AUX_/GAME and AUX_/EXROM pins on its own cartridge-slot, and simply copies this configuration to /GAME and /EXROM on the C64-side whenever the Bait-a-Cart's flash is disabled.
Whenever the Bait-a-Cart's flash is enabled, the size (8k or 16k) of a hosted ROM-image is used to determine the value of /GAME and /EXROM to communicate to the C64.
The data-bus signals of the C64's cartridge-port are directly connected to corresponding data-bus pins of the Bait-a-Cart's own cartridge slot.
Furthermore, the data-signals of the Bait-a-Cart's own on-board flash are connected to this data-bus as well, via a tri-state buffer (U106 in the schematic).
Whenever the Bait-a-Cart's flash is enabled, the Bait-a-Cart...
Whenever the Bait-a-Cart's flash is disabled, the Bait-a-Cart...
Like the data-bus, the address-bus signals of the C64's cartridge-port are directly connected to corresponding address-bus pins of the Bait-a-Cart's own cartridge-slot.
The address-bus is also connected to the address-signals of the flash via a tri-state buffer (U103 and U105 in the schematic). The only function of this tri-state buffer is to allow the Bait-a-Cart's microcontroller instead of the C64 to address the Bait-a-Cart flash, in order to program or verify it. (Programming of the flash is ignored in this section.)
The C64's /IO1 and /IO2 signals are activated when the C64 addresses memory in the range $DE00-$DEFF respectively $DF00-$DFFF. These regions are often used as means to communicate with intelligent cartridges or peripherals.
Both of these signals are directly connected to corresponding pins of the Bait-a-Cart's own cartridge-slot, and to 2 inputs on the microcontroller. By simply accessing aforementioned special regions, a C64-program running off the Bait-a-Cart's flash can tell the Bait-a-Cart to disable its flash, and enable the existing ROM-cartridge instead.
This mechanism can e.g. be used to make a short advertisement-intro or info-screen which, when a key is pressed, will jump to the existing ROM-cartridge's code.
The reset-signal is pulled high by the C64, and is directly connected to the Bait-a-Cart's cartridge-slot. Either the existing ROM-cartridge or the Bait-a-Cart can pull this signal low, to force a system-reset. One of the buttons on the Bait-a-Cart is indeed intended as a reset-button, although actual behaviour is software-dependent.
Fixed a design-bug, allowing the Bait-a-Cart microcontroller to read very short memory-access pulses generated by the C64, by routing them to I/O-pins with latch-functionality.
The C64 activates cartridge-port pins /IO1 and /IO2 whenever it accesses certain I/O memory-regions under certain circumstances.
The Bait-a-Cart will interpret accesses to such a "magic" region as a request to disable itself, connect the existing ROM-cartridge to the C64, reset the C64, and thus effectively run the existing ROM-cartridge plugged into the Bait-a-Cart.
Although the C64 runs at approximately 1 MHz, a single read/write access doesn't take very long. To monitor even these short access-periods, the Bait-a-Cart microcontroller could poll the /IO1 and /IO2 lines continuously, or use an interrupt-/latch-approach.
Since the Bait-a-Cart microcontroller has other stuff to do besides monitoring these 2 pins, polling is not a good idea.
The Atmel AVR microcontrollers implement hardware-support for external interrupts on certain pins, and not on other pins. Whenever a preconfigured event (rising/falling edge or level) occurs on the pin, it can either execute an appropriate interrupt-handler, or raise a register-flag to indicate the event has occurred (i.e. "latch" the event).
In the context of the Bait-a-Cart software, it doesn't matter whether interrupt-handlers or register-flags are used - bottom line is that short events on the C64's /IO1 and /IO2 pins must not be lost.
In the schematic, the C64's /IO1 and /IO2 pins were routed to microcontroller-pins without this interrupt-capability. That would mean that the Bait-a-Cart software would have to revert to polling, or the PCB would have to be hacked to route /IO1 and /IO2 to appropriate microcontroller-pins - which is what I did.
Detail of the schematic showing the mistake:
(Instead of microcontroller pins 90 and 91, for example 8 and 9 should be used. "INTx" means that a pin has interrupt-capability.)
I used 2 wires to simply connect pin 90 to 9, and 91 to 8:
(Pins 90 and 91 can then simply be configured as floating inputs, to be ignored in software. The other 2 green wires that can be seen running off the picture's edge fix an aforementioned problem where USB/serial signals were accidentally connected to non-UART pins on the microcontroller.)
To test latch-/interrupt-functionality of C64's /IO1 and /IO2 outputs, I made a small hack to mimic the C64's /IO1 and /IO2 outputs:
(/IO1 and /IO2 are simply pulled up, and can be grounded by using the blue GND-wire.)
Nothing to see here, really...
ATmega640's EIFR (External Interrupt Flag Register) shows the latched events, should they occur. Writing bitwise "1" to bits in EIFR clears the event.
Since /IO1 and /IO2 are normally pulled up in this test-setup, I configured the microcontroller to listen for falling-edge events using EICRA and EICRB (External Interrupt Control Register A and B).
So... this is the first official effort :-)
Most time was spent reading the schematic and drawing connections on paper. Next thing I have to do is probably make simplified drawings showing the essence of the Bait-a-Cart circuit in terms of connections with the C64's cartridge-port and ROM-cartridge.
(Stuff you see here already existed before the start, including the soldered wires, ribbon-cable, tape and black cartridge-slot.)
Spent about 1.5 hours verifying the Bait-a-Cart microcontroller could still be programmed from the PC, but mainly tracing the schematic to see how C64, Bait-a-Cart and existing ROM-cartridge were connected again.
To get an idea what a typical debug-setup looks like:
That's the front-side, facing the user.
The black cartridge-slot had already been mounted earlier. (An existing ROM-cartridge can be plugged into this slot.) The small PCB in the upper right corner is a programming dongle, allowing for a host-PC to program the Bait-a-Cart microcontroller during development and debugging. (That dongle itself is a homebrew design as well, BTW. :-)
There are 4 reverse-mounted LEDs to show status and selected image. Right under the texts "SEL" (select) and "RST" (reset) there are 2 side-viewing buttons to control the Bait-a-Cart. These buttons have no function yet, except to rotate the LED-pattern to show that LEDs and buttons actually work.
Both the blue and green cables go to respectively the "programming" and "serial terminal" USB-ports on my laptop. (I use AVRDUDE as programmer, and either Minicom or Picocom as serial terminal-emulator.) At this point, there is no actual communication-protocol between host-PC and Bait-a-Cart yet, but I use the serial terminal for ad-hoc debugging and feedback.
Back-side:
I chose to put all electrical components at the back of the PCB to make it look better, and to shield them a bit from poking fingers.
The piece of ribbon-cable soldered to the PCB's programming-pads quickfixes a flaw: the programming-pads are mechanically inaccessible when the Bait-a-Cart is plugged into the C64... I should have put them a bit more towards the right-most edge of the PCB as shown here. The tape adds a bit of strain-relief.
The 2 thin green wires quickfix another aforementioned problem: I shuffled the microcontroller's pin-assignments around a bit too much, and forgot to leave the UART-pins in place... The green wires connect the FTDI bridge-chip (lower right) to the correct UART-pins on the microcontroller (big chip, lower left).
The USB-socket into which the green USB-cable is plugged at the moment is accessible when the Bait-a-Cart is plugged into the C64, as well as the 2 buttons (nearly visible, at the top edge of the PCB, near the blue USB-connector).
Please read this first before accusing me of a "false start". :-)
At this moment, the Bait-a-Cart hardware exists. It basically consists of...
It looks like this:
(Fish-logo is visible from the top when the Bait-a-Cart is plugged into a C64, and an existing ROM-cartridge is plugged into the Bait-a-Cart vertically, from the top as well, into a card-edge connector (not yet mounted when the picture was taken). Shown in the front is the actual bottom-side of the Bait-a-Cart, containing the electrical components such as the microcontroller and the parallel flash.)
If interested, take a look at the schematic. One known mistake is that the microcontroller's Tx/Rx are not connected to the FT232RL USB-to-serial bridge. This has already been fixed by 2 bodge-wires (not shown on the picture).
At this moment, only the LEDs and pushbuttons have been tested: push the button to change the LED-pattern. This implicitly also tested the microcontroller on the Bait-a-Cart.
At this moment, a small intro for the C64 has already been made - it looks like this:
It's a colour-animation of letters, with a black "pupil" nervously moving behind the letters. It doesn't feature any sound or music. See this thing in action (YouTube), or an earlier snapshot of work-in-progress.