forked from Apple-2-HW/arduino-appleii
169 lines
4.9 KiB
Arduino
169 lines
4.9 KiB
Arduino
|
// Share speaker pin for output
|
||
|
#define CASSETTE_READ_PIN A5
|
||
|
#define SPEAKER_PIN 5
|
||
|
|
||
|
#ifndef cbi
|
||
|
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
|
||
|
#endif
|
||
|
#ifndef sbi
|
||
|
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
|
||
|
#endif
|
||
|
|
||
|
void cassette_header(unsigned short periods) {
|
||
|
// Header Tone
|
||
|
for(int i = 0; i < periods*128; ++i) {
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds(650);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds(650);
|
||
|
}
|
||
|
// Sync pulse, one half cycle at 2500hz and then 2000hz
|
||
|
// 2500 hz
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds(200);
|
||
|
// 2000 hz
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds(250);
|
||
|
}
|
||
|
|
||
|
void cassette_write_byte(unsigned char val) {
|
||
|
// Shift it out, MSB first
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x80) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x80) ? 500 : 250);
|
||
|
//bit 6
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x40) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x40) ? 500 : 250);
|
||
|
//bit 5
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x20) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x20) ? 500 : 250);
|
||
|
//bit 4
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x10) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x10) ? 500 : 250);
|
||
|
//bit 3
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x08) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x08) ? 500 : 250);
|
||
|
//bit 2
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x04) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x04) ? 500 : 250);
|
||
|
//bit 1
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x02) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x02) ? 500 : 250);
|
||
|
//bit 0
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delayMicroseconds((val&0x01) ? 500 : 250);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
delayMicroseconds((val&0x01) ? 500 : 250);
|
||
|
}
|
||
|
|
||
|
void cassette_write_block(unsigned short A1, unsigned short A2) {
|
||
|
unsigned char checksum = 0xFF, val = 0;
|
||
|
for(unsigned short addr = A1; addr <= A2; ++addr) {
|
||
|
val = read8(addr);
|
||
|
cassette_write_byte(val);
|
||
|
checksum ^= val;
|
||
|
}
|
||
|
cassette_write_byte(checksum);
|
||
|
// High idle for a little while, make sure all bits cleared
|
||
|
digitalWrite(SPEAKER_PIN, HIGH);
|
||
|
delay(10);
|
||
|
digitalWrite(SPEAKER_PIN, LOW);
|
||
|
}
|
||
|
|
||
|
// Used to track center voltage
|
||
|
float cassette_center_voltage = 512;
|
||
|
// implement zero crossing detector
|
||
|
boolean cassette_read_state() {
|
||
|
static boolean zerocross_state = false;
|
||
|
// get value
|
||
|
short adc = (analogRead(CASSETTE_READ_PIN) - (short)cassette_center_voltage);
|
||
|
// bias drift correction
|
||
|
cassette_center_voltage += adc*0.05f;
|
||
|
// ~7mv hysteresis
|
||
|
if(zerocross_state && adc < -7) zerocross_state = false;
|
||
|
else if(!zerocross_state && adc > 7) zerocross_state = true;
|
||
|
return zerocross_state;
|
||
|
}
|
||
|
|
||
|
// figure out the duration of zero crossing
|
||
|
short cassette_read_transition() {
|
||
|
unsigned long start_time;
|
||
|
static boolean last = false;
|
||
|
boolean cur = last;
|
||
|
// loop until state transition
|
||
|
for(start_time = micros();cur == last;) cur = cassette_read_state();
|
||
|
// update transition tracking
|
||
|
last = cur;
|
||
|
//return duration of transition us
|
||
|
return micros() - start_time;
|
||
|
}
|
||
|
|
||
|
// Based loosely on steve wozniaks original algorithm
|
||
|
boolean cassette_read_block(unsigned short A1, unsigned short A2) {
|
||
|
short bitperiod;
|
||
|
unsigned char val, checksum = 0xFF, datachecksum = 0x00;
|
||
|
|
||
|
// Calibrate the zero crossing detector
|
||
|
for(short i = 0; i < 10000; ++i) cassette_read_state();
|
||
|
|
||
|
// find tape in edge
|
||
|
cassette_read_transition();
|
||
|
cassette_read_transition();
|
||
|
|
||
|
// Small delay to allow things to settle
|
||
|
delay(500);
|
||
|
|
||
|
//wait for sync bit, short zero
|
||
|
while(cassette_read_transition() > 300);
|
||
|
|
||
|
// skip second cycle of sync bit
|
||
|
cassette_read_transition();
|
||
|
|
||
|
// start reading data
|
||
|
for(unsigned short addr = A1; addr <= A2; ++addr) {
|
||
|
// zero our byte of memory
|
||
|
val = 0;
|
||
|
for(unsigned char i = 8; i != 0; --i) {
|
||
|
bitperiod = (cassette_read_transition() + cassette_read_transition()) / 2;
|
||
|
if(bitperiod > 300) val |= _BV(i-1);
|
||
|
}
|
||
|
// write byte to memory
|
||
|
write8(addr, val);
|
||
|
// update checksum
|
||
|
checksum ^= val;
|
||
|
}
|
||
|
|
||
|
// Read checksum
|
||
|
for(unsigned char i = 8; i != 0; --i) {
|
||
|
bitperiod = (cassette_read_transition() + cassette_read_transition()) / 2;
|
||
|
if(bitperiod > 300) datachecksum |= _BV(i-1);
|
||
|
}
|
||
|
|
||
|
//return whether the data passes error checking
|
||
|
return (datachecksum == checksum);
|
||
|
}
|
||
|
|
||
|
void cassette_begin() {
|
||
|
// ADC prescale, 77khz
|
||
|
sbi(ADCSRA,ADPS2);
|
||
|
cbi(ADCSRA,ADPS1);
|
||
|
cbi(ADCSRA,ADPS0);
|
||
|
// internal pullup on analog pin
|
||
|
digitalWrite(CASSETTE_READ_PIN, HIGH);
|
||
|
// use 1.1v internal analog reference
|
||
|
analogReference(INTERNAL);
|
||
|
}
|