diff --git a/firmware/rtc/.gitignore b/firmware/rtc/.gitignore new file mode 100644 index 0000000..8954bf5 --- /dev/null +++ b/firmware/rtc/.gitignore @@ -0,0 +1,2 @@ +Mac128kRTC.axf +MacPlusRTC.axf diff --git a/firmware/rtc/MacRTC.cpp b/firmware/rtc/MacRTC.cpp index 3e75504..f8f3bc3 100644 --- a/firmware/rtc/MacRTC.cpp +++ b/firmware/rtc/MacRTC.cpp @@ -1,7 +1,13 @@ -/* Public Domain Release: CC0 1.0 Universal. +/* Public Domain Dedication: CC0 1.0 Universal. For more information, please see + + Developed with reference to a Reddit posting and Mini vMac source. + + * 2020-08-05: + + * 2020-09-04: */ #include @@ -64,25 +70,39 @@ const int group1Base = 0x10; const int group2Base = 0x08; #endif -volatile boolean lastSerClock = 0; -volatile byte serialBitNum = 0; -volatile byte address = 0; -volatile byte xaddr = 0; // xPRAM extended address byte -volatile byte serialData = 0; - enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND, SENDING_DATA, RECEIVING_DATA, RECEIVING_XCMD_ADDR, RECEIVING_XCMD_DATA }; -volatile SerialStateType serialState = SERIAL_DISABLED; -// Number of seconds since midnight, January 1, 1904. Clock is -// initialized to January 1st, 1984? Or is this done by the ROM when -// the validity status is invalid? -volatile unsigned long seconds = 60 * 60 * 24 * (365 * 4 + 1) * 20; +enum PramAddrResult { INVALID_CMD, SECONDS_CMD, + WRTEST_CMD, WRPROT_CMD, SUCCESS_ADDR }; + +volatile SerialStateType serialState = SERIAL_DISABLED; +volatile boolean lastSerClock = 0; +volatile byte serialBitNum = 0; +volatile byte address = 0; +volatile byte serialData = 0; + +// Number of seconds since midnight, January 1, 1904. The serial +// register interface exposes this data as little endian. TODO +// VERIFY: Clock is initialized to January 1st, 1984? Or is this done +// by the ROM when the validity status is invalid? +volatile unsigned long seconds = 60UL * 60 * 24 * (365 * 4 + 1) * 20; volatile byte pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data +volatile byte writeProtect = 0; #define shiftReadPB(output, bitNum, portBit) \ - bitWrite(output,bitNum, (PINB&_BV(portBit)) ? 1 : 0) + bitWrite(output, bitNum, (PINB&_BV(portBit)) ? 1 : 0) + +void digitalWritePB(uint8_t pin, uint8_t val) { + uint8_t bit = _BV(pin); + cli(); + if (val == 0) + PORTB &= ~bit; + else + PORTB |= bit; + sei(); +} void setup() { cli(); // Disable interrupts while we set things up @@ -99,21 +119,21 @@ void setup() { DDRB &= ~SERIAL_DATA_PIN; PORTB |= SERIAL_DATA_PIN; - wdt_disable(); // Disable watchdog - bitSet(ACSR,ACD); // Disable Analog Comparator, don't need it, saves power - bitSet(PRR,PRTIM1); // Disable Timer 1, only using Timer 0, Timer 1 uses around ten times as much current - bitSet(PRR,PRUSI); // Disable Universal Serial Interface, using Apple's RTC serial interface on pins 6 and 7 - bitSet(PRR,PRADC); // Disable Analog/Digital Converter + wdt_disable(); // Disable watchdog + bitSet(ACSR, ACD); // Disable Analog Comparator, don't need it, saves power + bitSet(PRR, PRTIM1); // Disable Timer 1, only using Timer 0, Timer 1 uses around ten times as much current + bitSet(PRR, PRUSI); // Disable Universal Serial Interface, using Apple's RTC serial interface on pins 6 and 7 + bitSet(PRR, PRADC); // Disable Analog/Digital Converter - bitSet(GIMSK,PCIE); // Pin Change Interrupt Enable - bitSet(PCMSK,PCINT0); // turn on RTC enable interrupt + bitSet(GIMSK, PCIE); // Pin Change Interrupt Enable + bitSet(PCMSK, PCINT0); // turn on RTC enable interrupt //set up timer - bitSet(GTCCR,TSM); // Turns off timers while we set it up - bitSet(TIMSK,TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable - TCCR0B = 0b111; // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second - TCNT0 = 0; // Clear the counter - bitClear(GTCCR,TSM); // Turns timers back on + bitSet(GTCCR, TSM); // Turns off timers while we set it up + bitSet(TIMSK, TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable + TCCR0B = 0b111; // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second + TCNT0 = 0; // Clear the counter + bitClear(GTCCR, TSM); // Turns timers back on sei(); //We're done setting up, enable those interrupts again } @@ -153,6 +173,42 @@ void handleRTCEnableInterrupt() { } } +/* + * For 20-byte PRAM equivalent commands, compute the actual PRAM + * address by modifying the `address` variable in-place. Note that + * `address` must have been already modified to remove the excess + * bits. A status code is returned for commands that need special + * processing: + * + * INVALID_CMD: Invalid command byte. + * SECONDS_CMD: Special command: read seconds. + * WRTEST_CMD: Special command: test write register. + * WRPROT_CMD: Special command: write-protect register. + * SUCCESS_ADDR: Successful address computation. + */ +uint8_t decodePramCmd(boolean writeRequest) { + if (address < 8) { + // Little endian clock data byte + return SECONDS_CMD; + } else if (address < 12) { + // Group 2 register + address = (address&0x03) + group2Base; + } else if (address < 16) { + if (writeRequest) { + if (address == 12) // test write + return WRTEST_CMD; + if (address == 13) // write-protect + return WRPROT_CMD; + } + return INVALID_CMD; + } else { + // Group 1 register + address = (address&0x0f) + group1Base; + } + + return SUCCESS_ADDR; +} + void loop() { if((PINB&(1<= 8) { + clearState(); + } else if(serClockRising) { + boolean writeRequest; switch(serialState) { - - case RECEIVING_COMMAND: - shiftReadPB(address,7-serialBitNum,SERIAL_DATA_PIN); + shiftReadPB(address, 7 - serialBitNum, SERIAL_DATA_PIN); serialBitNum++; - if(serialBitNum > 7) { - boolean writeRequest = address&(1<<7); // the MSB determines if it's a write request or not - address &= ~(1<<7); // Discard the first bit, it's not part of the address + if (serialBitNum <= 7) + break; + + // The MSB determines if it's a write request or not. + writeRequest = !(address&(1<<7)); + if ((address&0x78) == 0x38) { +#if NoXPRAM + // Invalid command. + clearState(); + break; +#else + // This is an extended command, read the second address + // byte. + serialState = RECEIVING_XCMD_ADDR; serialBitNum = 0; - if(writeRequest) { - serialState = RECEIVING_DATA; - serialBitNum = 0; - } else { - if (address < 4) { - serialData = (seconds>>(8*address))&0xff; - } if(!(address&0b0110000)) { // Apparently this address range is off-limits for reading - serialData = pram[address]; - } - serialState = SENDING_DATA; - serialBitNum = 0; - // Set the pin to output mode - cli(); - DDRB |= SERIAL_DATA_PIN; + break; +#endif + } else if (writeRequest) { + // Read the data byte before continuing. + serialState = RECEIVING_DATA; + serialBitNum = 0; + break; + } else { + boolean finished = false; + // Discard the first bit and the last two bits, it's not + // pertinent to command interpretation. + address = (address&~(1<<7))>>2; + // Decode the command/address. + switch (decodePramCmd(writeRequest)) { + case SECONDS_CMD: + // Read little endian clock data byte. + cli(); // Ensure that reads are atomic. + address = (address&0x03)<<3; + serialData = (seconds>>(address))&0xff; sei(); + break; + case SUCCESS_ADDR: + serialData = pram[address]; + break; + case INVALID_CMD: + default: + finished = true; + break; + } + if (finished) { + clearState(); + break; } } + + // If we didn't break out early, send the output byte. + serialState = SENDING_DATA; + serialBitNum = 0; + // Set the pin to output mode + cli(); + DDRB |= SERIAL_DATA_PIN; + sei(); break; case RECEIVING_DATA: - shiftReadPB(serialData,7-serialBitNum,SERIAL_DATA_PIN); + shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN); serialBitNum++; - if(serialBitNum > 7) { - if(address < 4) { - cli(); // Don't update the seconds counter while we're updating it, bad stuff could happen - seconds = (seconds & ~(((long)0xff)<>2; + // Decode the command/address. + switch (decodePramCmd(writeRequest)) { + case SECONDS_CMD: + if (!writeProtect) { + // Write little endian clock data byte. + cli(); // Ensure that writes are atomic. + address = (address&0x03)<<3; + seconds &= ~(0xff<<(address)); + seconds |= serialData<<(address); sei(); - } else { - pram[address] = serialData; } - clearState(); + break; + case WRPROT_CMD: + // Update the write-protect register. + writeProtect = (serialData & 0x80) ? 1 : 0; + break; + case SUCCESS_ADDR: + if (!writeProtect) + pram[address] = serialData; + break; + case WRTEST_CMD: // test write, do nothing + case INVALID_CMD: + default: + break; } + + // Finished with the write command. + clearState(); break; case SENDING_DATA: - { - uint8_t bit = _BV(SERIAL_DATA_PIN); - uint8_t val = bitRead(serialData,7-serialBitNum); - cli(); - if (val == 0) - PORTB &= ~bit; - else - PORTB |= bit; - sei(); - } + digitalWritePB(SERIAL_DATA_PIN, bitRead(serialData, 7 - serialBitNum)); serialBitNum++; - if(serialBitNum > 7) { - clearState(); - } + /* if (serialBitNum <= 7) + break; */ + + /* NOTE: The last output cycle is treated specially, hold the + data line as an output until the falling edge of the serial + clock, then switch back to an input and reset the serial + communication state. */ break; +#if !defined(NoXPRAM) || !NoXPRAM case RECEIVING_XCMD_ADDR: + shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN); + serialBitNum++; + if (serialBitNum <= 7) + break; + + // The MSB determines if it's a write request or not. + writeRequest = !(address&(1<<7)); + // Assemble the extended address. + address = ((address&0x07)<<5) | ((serialData&0x7c)>>2); + + if (writeRequest) { + serialState = RECEIVING_XCMD_DATA; + serialBitNum = 0; + break; + } + + // Read and send the PRAM register. + serialData = pram[address]; + serialState = SENDING_DATA; + serialBitNum = 0; + // Set the pin to output mode + cli(); + DDRB |= SERIAL_DATA_PIN; + sei(); break; case RECEIVING_XCMD_DATA: + shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN); + serialBitNum++; + if (serialBitNum <= 7) + break; + + // Write the PRAM register. + pram[address] = serialData; + // Finished with the write command. + clearState(); + break; +#endif + + default: + // Invalid command. + clearState(); break; } } @@ -253,7 +404,7 @@ ISR(PCINT0_vect) { handleRTCEnableInterrupt(); } -ISR(TIMER0_OVF) { +ISR(TIMER0_OVF_vect) { halfSecondInterrupt(); } diff --git a/firmware/rtc/Makefile b/firmware/rtc/Makefile new file mode 100644 index 0000000..1c5dcce --- /dev/null +++ b/firmware/rtc/Makefile @@ -0,0 +1,10 @@ +all: Mac128kRTC.axf MacPlusRTC.axf + +Mac128kRTC.axf: MacRTC.cpp + avr-gcc -o $@ -Os -mmcu=attiny85 -DNoXPRAM=1 $< + +MacPlusRTC.axf: MacRTC.cpp + avr-gcc -o $@ -Os -mmcu=attiny85 $< + +clean: + rm -f Mac128kRTC.axf MacPlusRTC.axf diff --git a/firmware/rtc/domake.sh b/firmware/rtc/domake.sh deleted file mode 100755 index 26af9c9..0000000 --- a/firmware/rtc/domake.sh +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh -avr-gcc -c -Os -mmcu=attiny85 MacRTC.cpp -avr-gcc -mmcu=attiny85 -o MacRTC.axf MacRTC.o