Atari 2600 Controller Shield PCB Revisited – Part 3
Following on from Atari 2600 Controller Shield PCB Revisited – Part 2 someone on Mastodon made the point that the reason they tended to use RC circuits to read paddles “back in the day” was due to the expense of ADCs.
Which triggered a bit of an “oh yeah” moment.
The whole point was not to worry about the analog levels at all, and just measure the time it takes for the pin to read HIGH again.
So this looks back at removing the whole ADC thing with a simple “if (digitalRead(pin))” condition!
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to Arduino, see the Getting Started pages.
The Code
The overarching principles are the same as for Atari 2600 Controller Shield PCB Revisited – Part 2 but instead of all the bespoke code to read the analog to digital converter, I’m relying on the following:
- A digital input pin has a threshold for which the input is considered HIGH.
- We can wait for the input reading to register as HIGH instead of looking for absolute thresholds of an analog value.
- For an ATMega328P the threshold is 0.6 x VCC or around 3V. This is equivalent to just over 610 on a 0 to 1023 scale of an equivalent analog reading.
Taking this into account and using largely the same ideas as before, I can reuse most of the code but with the following timing and threshold values instead:
- Start scaling (the 0 point): 10
- End scaling (the 1023 point): 350
The timer TICK is still 100uS and the “breakout” point is still 1000.
When it comes to reading the digital INPUT, I’m using PORT IO once again for speed and expediency.
for (int i=0; i<4; i++) {
if ((PINC & (1<<i)) == 0) {
// Still not HIGH yet
}
}
Here is the complete, now greatly simplified, basic code:
#include <TimerOne.h>
#define RAW_START 10
#define RAW_END 350
#define RAW_BREAK 1000
#define RAW_TICK 100
unsigned padState;
unsigned padCount[4];
unsigned atariValue[4];
void atariAnalogSetup() {
Timer1.initialize(RAW_TICK);
Timer1.attachInterrupt(atariAnalogScan);
padState = 0;
}
void atariAnalogScan (void) {
if (padState == 0) {
DDRC = DDRC | 0x0F; // A0-A3 set to OUTPUT
PORTC = PORTC & ~(0x0F); // A0-A3 set to LOW (0)
padState++;
} else if (padState == 1) {
DDRC = DDRC & ~(0x0F); // A0-A3 set to INPUT
for (int i=0; i<4; i++) {
padCount[i] = 0;
}
padState++;
} else if (padState > RAW_BREAK) {
for (int i=0; i<4; i++) {
atariValue[i] = 1023 - map(constrain(padCount[i],RAW_START,RAW_END),RAW_START,RAW_END,0,1023);
}
padState = 0;
} else {
for (int i=0; i<4; i++) {
if ((PINC & (1<<i)) == 0) {
padCount[i]++;
}
}
padState++;
}
}
int atariAnalogRead (int pin) {
return atariValue[pin-A0];
}
void setup() {
Serial.begin(9600);
atariAnalogSetup();
}
void loop() {
Serial.print(padState);
Serial.print("\t[ ");
for (int i=0; i<4; i++) {
Serial.print(atariAnalogRead(A0+i));
Serial.print("\t");
Serial.print(padCount[i]);
Serial.print("\t][ ");
}
Serial.print("\n");
}
Closing Thoughts
Sometimes one really can’t see the “wood for the trees” and this was one of those occasions. I was so took up with thinking about how a modern system might think about a problem without thinking about the original reason for the particular solution.
It makes so much more sense thinking about it in these terms now. All it took was an observation from another, namely:
“So I know the RC timer is the classic way to sense analog paddles but they also didn’t have cheap ADCs back then.”
Many thanks “Chip” for that observation 🙂
Kevin
#arduinoUno #atari #atari2600 #include #potentiometer #TICKs