arduino-appleii/APPLEII/cassette.ino

169 lines
4.9 KiB
C++

// 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);
}