Uploaded by Daniël Mantione

Commodore 1351 connecting to the c64

advertisement
Wiring the Arduino to the C64
Time to start connecting the Arduino PWM outputs to the C64. The PWM outputs of the Arduino
cannot be directly connected to the C64, some components in between are necessary. At this point,
plugging stuff directly into the Arduino became impractical, so I spent quite a few hourrs searching
where I had left my breadboard. Luckily I found it, and work could continue.
First, we need some resistance to control the speed that the capacitors in the C64 charge, and to
limit the current output the output pins. In order to determine the right restistance experimentally I
used two potmeters of 0-1000 Ω. This is much lower than the 1351 uses, but my experimentation
did show that in order to be able to transmit low values, it is necessary to be able to charge the SID's
capacitors fast, and I don't see any objection against fast charging, as long as the currents remain
reasonable.
While experimenting I was also remebered that the PWM outputs have the properly they pull down
the line when they are low: In other words, they pull the charge out of the C64's capacitors and they
pull strongly. This is undesired, as this prevents detection of the SID sync pulses, and therfore we
need a diode to prevent this.
In my box of electronic components I have two types of diodes: 1N4007 and BAT43 (a Schottky
diode). If we need to able to charge fast, a low voltage drop might come handy, so I went for the
BAT43. If you look at the voltage drop, the BAT43 is one of the best diodes that you can buy at
good prices as a hobbyist.
After some experimentation I had my design ready, and it looks as follows:
I've recreated it in Fritzing, so you can look at it in more detail. Breadboard view:
+
POTX
POTY
-
1
5
10
15
20
25
30
1
5
10
15
20
25
30
A
B
C
D
E
F
G
H
I
1
0
2
)
RX0
3
4
5
6
7
8
9
10
11
12
DIGITAL (PWM=
ICSP2
TX0
RESET
13
AREF
GND
J
L
TX
RX
Arduino
ON
TM
Schematic view:
A5
A4
A3
A2
A1
A0
VIN
VIN
2
5V
3V3
ANALOG IN
R1
100kΩ
1
Onderdeel1
POWER
GND
GND
5V
RESET
3V3
IOREF
ICSP
1
RESET
D0/RX
RESET2
D1/TX
AREF
D2
ioref
D3 PWM
A0
D5 PWM
R3
1kΩ
D4
Arduino
Uno
(Rev3)
D6 PWM
D7
1
D8
D1
BAT43
A4/SDA
D9 PWM
A5/SCL
D10 PWM/SS
0
2
A3
J1
3
A2
1
A1
R4
1kΩ
D11 PWM/MOSI
3
D13/SCK
2
1
D12/MISO
1
N/C
0
GND
D2
BAT43
R2
3.3kΩ
1
2
With the circuitry in place, it should be possible to send values from the Arduino to the C64. I typed
the following program on the Commodore 64:
10 POKE $DC0C,1
20 PRINT PEEK($D419)
30 GOTO 20
The first line disables the CIA timer interrupt on the C64. This is necessary because the CIA line
that controls the analog multiplexer that connects either control port 1 or control port 2 to the SID,
shared its control lines with the keyboard matrix select lines. The C64 timer interrupt modifies this
line in order to scan the keyboard, but as a result also switches between control port 1 or control
port 2 POTX lines. Because this happens in the middle of SID read cycles, the C64 interrupt causes
noise to read on the C64.
Because disabling the timer interrupt makes the C64 keyboard inoperable, you cannot interrupt
above program once started other than with a reset button. Because of this it is highly recommended
to use a cartridge that can recover a BASIC program after reset. I am using Final Cartridge III. A
KCS Power Cartridge will do the trick as well. To interrupt, just reset and type “OLD” for the FC3,
or “UNNEW”for the KCS. Both cartridges extend BASIC with $ for hexadecimal, which is why I
am conveniently using hexadecimal numbers in my program.
Yes, it did work! However, the results did need calibration and they were still noisy. I still had to do
some programming to get it correct.
Reducing noise and calibrating the thing
The initial results were way too noisy in order to be usefull. It did take some analysis to see where
the nosie came from. I found 3 sources. First, there was noise on the 5V line when the Arduino was
powered from USB. Powering it from the C64 did show much more stable results. Some people
might be tempted to think that this is because of the linear power supply of the C64, but no, those
original power supplies should not be used anymore. For my experiments here I use a modified
power supply with switching regulators. My laptop could be powered by battery and still the USB
5V power supply was much more noisy than the C64 5V power. While not a problem for the final
product, I did need to connect the USB for uploading sketches. It turns out that the 5V noise causes
some inaccuracy on when the comparator did exactly trigger. Through experimentation I found out
that modifying my voltage divider from 100k/15k to 100k/33k resistors did reduce this jitter a lot,
evading the problem.
Another noise source was found in the Arduino's timer interrupt. By default the Arduino's timer0
generates interrupts in order to support library functions such as delay(). However, if a comparator
interrupt is triggered while the timer interrupt is being handled, there before the comparator
interrupt handler gets executed. To eleminate this noise I did disable timer0. This renders all timing
functions inside the Arduino runtime library unusable. All timing needs to be done with our own
timer: The SID synchronisation pulse.
A third source of noise was found in timer1 itself: Because the Arduino 16MHz clock is divided by
8 in order to get to 2 MHz, after setting the timer1 counter to 0, there can be 1-8 Arduino cycles
before it is increase to 1. Luckily the timer prescaler can be reset, so besides setting the timer
counter to 0, we also need to reset the prescaler. i.e. our IRQ handler should look like this:
ISR(ANALOG_COMP_vect) {
GTCCR = _BV(PSRSYNC);
TCNT1 = 0;
}
After these adjustments, the values read on the C64 were quite stable, only they werent correct, we
need to calbrate stuff. Now because the PAL C64 runs at 985 KHz and the NTSC C64 runs at 1023
KHz, this calibration is PAL/NTSC dependent. Therfore I did add a small check to the IRQ handler:
commodore_is_pal = (TCNT1 < 512);
It's quite simple: The PAL C64 runs slower than 1MHz, so the timer will have overflown when the
comparator interrupt occurs, the value in the timer counter should be low when the interrupt occurs.
The NTSC C64 runs faster than 1 MHz, so the timer counter will not yet have overflown when the
interrupt occurs, the counter should contain a high value. So a simple check low or high can
distinguish between PAL and NTSC.
Through experimentation I found that if I set my potmeters to 340 Ω and do the following
adjustments in software:
void set_potx(u8 potx) {
/* Our timer runs at 2000KHz while a PAL C64 runs at 985 KHz.
This means that we need approximately 2 timer cycles for 1 C64 cycle, so multiply
by 2 */
u16 d;
d = 0x1f2 + 2 * potx;
/* However, because the difference is not exactly 2, this means our pulses would be slightly too
short.
Compensate. */
if (commodore_is_pal) {
if (potx>=24) d++;
if (potx>=48) d++;
if (potx>=87) d++;
if (potx>=121) d++;
if (potx>=156) d++;
if (potx>=187) d++;
if (potx>=223) d++;
if (potx>=251) d++;
} /* TODO NTSC */
OCR1A=d;
}
... I was able to get perfect results! This means that I am able to transmit values in the full range of 0
to 255 to the C64, with no noise at all. The real 1351 can only transmit values in the range of 64191 and the lowest bit is always noise, so this is a notable achievement. It is also not possible to get
the full range of values with analog paddles, Commodore's paddles get you results from about 20 to
235.
Here are some screenshots with a value of 2 and a value of 64:
...with a real 1351 you will see the noise in the least significant bit on the screen.
The oscilloscope view for both situations (green measured on POT line, yellow on pulse line before
any components):
Full Arduino program
The following program counts POTX and POTY from 0 to 255 in a loop. Arduino pin2 can be used
to pause the process: Put a wire between pin 2 and ground and the counting stops. You can watch
the program do its work by reading registers $D419 and $D41A on the Commodore 64.
/**************************************************************************************************
Commodore 1351 mouse simulation code for Arduino
Written by Daniël Mantione
**************************************************************************************************/
volatile unsigned long sid_measurement_cycles=0;
u8 posx=0;
u8 posy=0;
bool commodore_is_pal = true;
ISR(ANALOG_COMP_vect) {
/* The SID has started its discharge cycle. We now need to synchronize the PWM, but first we do a
PAL/NTSC check.
This is highly time critical code: Any modifications here before the timer is set to 0 will
require adjustment of the PWM offset in set_potx/set_poty. */
/* This is untested on NTSC! A PAL system runs at less than 1MHz and
will take longer than our 1024 timer cycles. Therfore we expect a
the interrupt occurs. An NTSC system runs at higher than 1MHz and
will take shorter than our 1024 timer cycles. Therefore we expect
interrupt occurs. */
therefore 512 SID cycles
low timer counter value when
therefore 512 SID cycles
a high timer value when the
commodore_is_pal = (TCNT1 < 512);
/* In order to synchronize the PWM with the SID we will reset the clock prescaler of the
microcontroller and reset timer 1 by writing 0 to its counter: */
GTCCR = _BV(PSRSYNC);
TCNT1 = 0;
/* Timer has been reset, so end of time critical code. */
sid_measurement_cycles++;
}
void setup_comparator() {
ACSR =
(0<<ACD) |
// Analog
(0<<ACBG) |
// Analog
(0<<ACO) |
// Analog
(1<<ACI) |
// Analog
(1<<ACIE) |
// Analog
(0<<ACIC) |
// Analog
(1<<ACIS1) | (0<ACIS0);
Comparator: Enabled
Comparator Bandgap Select: AIN0 is applied to the positive input
Comparator Output: Off
Comparator Interrupt Flag: Clear Pending Interrupt
Comparator Interrupt: Enabled
Comparator Input Capture: Disabled
// Analog Comparator Interrupt Mode: Comparator Interrupt on Falling
// Output Edge
pinMode(6, INPUT); //Avoid interfering with comparator
pinMode(7, INPUT); //Avoid interfering with comparator
}
void setup_timer1() {
/* For generating the pulses for POTX/POTY we will use timer 1 of the Atmega328p. This is a 16bit timer, which allows for high precision.*/
TIMSK1 = 0;
GTCCR |= _BV(PSRSYNC);
/* We set the clock source to none, so the timer does not run while we adjust it. The WGM12 bit
is to already select the right timer mode, otherwise OCR1A/ORC1B cannot be set correctly (read
on). */
TCCR1B = _BV(WGM12);
/* Activate PWM on Arduino pin 9 and 10. The PWM pin is high when the counter is higher than
MATCH. Select Fast PWM mode 7. */
TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM10) | _BV(WGM11);
TCNT1 = 0x000;
/* WGM12=1 to select Fast PWM mode 7. CS11 selects a clock source of clkio / 8, which results in
2MHz. A timer clock of 2MHz combined with a range of 0-1023 is acceptable for our purposes. By
selecting a clock the timer starts counting */
TCCR1B = _BV(WGM12) | _BV(CS11);
DDRB |= _BV(PORTB1) | _BV(PORTB2);
}
void setup() {
setup_comparator();
setup_timer1();
/* Arduino timer 0 is used by default to support timing functions like delay(). Its interrupt
handler may delay the analog comparator interrupt and thus cause noise. Therefore switch it
off. */
TCCR0B=0;
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
set_potx(0);
set_poty(0);
Serial.begin(9600);
Serial.println("Start");
}
void set_potx(u8 potx) {
/* Our timer runs at 2000KHz while a PAL C64 runs at 985 KHz.
This means that we need approximately 2 timer cycles for 1 C64 cycle, so multiply
by 2 */
u16 d;
d = 0x1f2 + 2 * potx;
/* However, because the difference is not exactly 2, this means our pulses would be slightly too
short.
Compensate. */
if (commodore_is_pal) {
if (potx>=24) d++;
if (potx>=48) d++;
if (potx>=87) d++;
if (potx>=121) d++;
if (potx>=156) d++;
if (potx>=187) d++;
if (potx>=223) d++;
if (potx>=251) d++;
} /* TODO NTSC */
OCR1A=d;
}
void set_poty(u8 poty) {
/* Our timer runs at 2000KHz while a PAL C64 runs at 985 KHz.
This means that we need approximately 2 timer cycles for 1 C64 cycle, so multiply
by 2 */
u16 d;
d = 0x1f2 + 2 * poty;
/* However, because the difference is not exactly 2, this means our pulses would be slightly too
short.
Compensate. */
if (commodore_is_pal) {
if (poty>=24) d++;
if (poty>=48) d++;
if (poty>=87) d++;
if (poty>=121) d++;
}
if (poty>=156) d++;
if (poty>=187) d++;
if (poty>=223) d++;
if (poty>=251) d++;
} /* TODO NTSC */
OCR1B=d;
void loop() {
int s;
if (digitalRead(2)) {
posx ++;
set_potx(posx);
posy ++;
set_poty(posy);
}
Serial.println(posx);
s=sid_measurement_cycles>>8;
/* Because timer 0 is stopped, we cannot use the normal delay() routines, so we have to
delay a different way. */
while (s==sid_measurement_cycles>>8)
{};
}
The input voltage
There is one final thing to worry about. The program above gives the exact right result when the
Arduino is powered from USB. When powered from the C64, the values are off by 1. It turns out
that the cause is the voltage: My voltage meter measures 5.11V on the Arduino when powered from
USB, just 4.91V when powered from C64.
Therefore, in order to generate really 100% perfect results, it is necessary to adjust for this. I think
this can be done by burning a little bit of the voltage with a zener diode like this:
220 Ω
0-500 Ω
POT line
4.7V
G
3
T4
BA
The Arduino outputs voltages of approximately 5V. The zener diode burns any voltage higher than
4.7V away into heat. The BAT43 has an official voltage drop of max. 0.33V, but my measurements
show about 0.2V drop. This means that any supply voltage higher than about 4.9V is burned away,
probably good enough for our purposes. We should be able to charge the SID capacitors fast
enough with 4.7V and knowing it is exactly 4.7 should eliminate the uncertainty about the exact
input voltage.
The next standard zener value below 4.7V is 4.3V. Here I have more doubts that we are able to
charge fast and high enough, but if so, this allows even more tolerance in the input voltage and
more choice in diodes.
I think will be a good idea to design the Megastick PCB with room for both a zener diode and
potmeter: It is always possible to remove the zener later and replace the potmeter with a fixed
resistor, but at least initially it will be very useful to do some manual adjustments in order to get
exactly the right value. 0-500 Ω is probably better for accuracy purposes than the 0-1000 Ω that I
used.
I was thinking about using a MOSFET as an alternative, so you can avoid the diode and its voltage
drop:
U
200 Ω
BS170
POT line
0-500 Ω
+5V
4.7V
G
... but this probably doesn't work: While the POT line is charged, the voltage between gate and
drain drops, causing the the MOSFET to turn itself off, likely too early.
Download