mirror of
https://github.com/quorten/macsehw.git
synced 2024-06-04 01:29:31 +00:00
Finally feature-complete code for ATTiny85 RTC.
The code compiles, but there is NO GUARANTEE it is actually functional. The code still needs to be tested and could use a bit of careful reading code review to certify that it does not contain errors.
This commit is contained in:
parent
d18cff3e80
commit
1e91a8b426
2
firmware/rtc/.gitignore
vendored
Normal file
2
firmware/rtc/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Mac128kRTC.axf
|
||||||
|
MacPlusRTC.axf
|
|
@ -1,7 +1,13 @@
|
||||||
/* Public Domain Release: CC0 1.0 Universal.
|
/* Public Domain Dedication: CC0 1.0 Universal.
|
||||||
|
|
||||||
For more information, please see
|
For more information, please see
|
||||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||||
|
|
||||||
|
Developed with reference to a Reddit posting and Mini vMac source.
|
||||||
|
|
||||||
|
* 2020-08-05: <https://www.reddit.com/r/VintageApple/comments/91e5cf/couldnt_find_a_replacement_for_the_rtcpram_chip/e2xqq60/>
|
||||||
|
|
||||||
|
* 2020-09-04: <https://www.gryphel.com/d/minivmac/minivmac-36.04/minivmac-36.04.src.tgz>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <avr/io.h>
|
#include <avr/io.h>
|
||||||
|
@ -64,25 +70,39 @@ const int group1Base = 0x10;
|
||||||
const int group2Base = 0x08;
|
const int group2Base = 0x08;
|
||||||
#endif
|
#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,
|
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND,
|
||||||
SENDING_DATA, RECEIVING_DATA,
|
SENDING_DATA, RECEIVING_DATA,
|
||||||
RECEIVING_XCMD_ADDR, RECEIVING_XCMD_DATA };
|
RECEIVING_XCMD_ADDR, RECEIVING_XCMD_DATA };
|
||||||
volatile SerialStateType serialState = SERIAL_DISABLED;
|
|
||||||
|
|
||||||
// Number of seconds since midnight, January 1, 1904. Clock is
|
enum PramAddrResult { INVALID_CMD, SECONDS_CMD,
|
||||||
// initialized to January 1st, 1984? Or is this done by the ROM when
|
WRTEST_CMD, WRPROT_CMD, SUCCESS_ADDR };
|
||||||
// the validity status is invalid?
|
|
||||||
volatile unsigned long seconds = 60 * 60 * 24 * (365 * 4 + 1) * 20;
|
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 pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data
|
||||||
|
volatile byte writeProtect = 0;
|
||||||
|
|
||||||
#define shiftReadPB(output, bitNum, portBit) \
|
#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() {
|
void setup() {
|
||||||
cli(); // Disable interrupts while we set things up
|
cli(); // Disable interrupts while we set things up
|
||||||
|
@ -99,21 +119,21 @@ void setup() {
|
||||||
DDRB &= ~SERIAL_DATA_PIN;
|
DDRB &= ~SERIAL_DATA_PIN;
|
||||||
PORTB |= SERIAL_DATA_PIN;
|
PORTB |= SERIAL_DATA_PIN;
|
||||||
|
|
||||||
wdt_disable(); // Disable watchdog
|
wdt_disable(); // Disable watchdog
|
||||||
bitSet(ACSR,ACD); // Disable Analog Comparator, don't need it, saves power
|
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, 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, PRUSI); // Disable Universal Serial Interface, using Apple's RTC serial interface on pins 6 and 7
|
||||||
bitSet(PRR,PRADC); // Disable Analog/Digital Converter
|
bitSet(PRR, PRADC); // Disable Analog/Digital Converter
|
||||||
|
|
||||||
bitSet(GIMSK,PCIE); // Pin Change Interrupt Enable
|
bitSet(GIMSK, PCIE); // Pin Change Interrupt Enable
|
||||||
bitSet(PCMSK,PCINT0); // turn on RTC enable interrupt
|
bitSet(PCMSK, PCINT0); // turn on RTC enable interrupt
|
||||||
|
|
||||||
//set up timer
|
//set up timer
|
||||||
bitSet(GTCCR,TSM); // Turns off timers while we set it up
|
bitSet(GTCCR, TSM); // Turns off timers while we set it up
|
||||||
bitSet(TIMSK,TOIE0); // Set Timer/Counter0 Overflow Interrupt Enable
|
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
|
TCCR0B = 0b111; // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second
|
||||||
TCNT0 = 0; // Clear the counter
|
TCNT0 = 0; // Clear the counter
|
||||||
bitClear(GTCCR,TSM); // Turns timers back on
|
bitClear(GTCCR, TSM); // Turns timers back on
|
||||||
|
|
||||||
sei(); //We're done setting up, enable those interrupts again
|
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() {
|
void loop() {
|
||||||
if((PINB&(1<<RTC_ENABLE_PIN))) {
|
if((PINB&(1<<RTC_ENABLE_PIN))) {
|
||||||
clearState();
|
clearState();
|
||||||
|
@ -166,80 +222,175 @@ void loop() {
|
||||||
boolean serClockFalling = lastSerClock && !curSerClock;
|
boolean serClockFalling = lastSerClock && !curSerClock;
|
||||||
lastSerClock = curSerClock;
|
lastSerClock = curSerClock;
|
||||||
|
|
||||||
// TODO FIXME: We need to implement an artificial delay between
|
/* Normally we only perform an action on the rising edge of the
|
||||||
// the clock's rising edge and the update of the data line output
|
serial clock. The main exception is cleanup at the last cycle
|
||||||
// because of a bug in the ROM. Is 10 microseconds a good wait
|
of serial output, there we wait until the falling edge before
|
||||||
// time? Or, here's what we can do. We keep the old value for as
|
switching the direction of the data pin back to an input. */
|
||||||
// long as the clock is high, and we only load the new value
|
|
||||||
// immediately once the clock goes low, i.e. that's how we handle
|
|
||||||
// the trailing edge event.
|
|
||||||
|
|
||||||
if(serClockRising) {
|
if (serClockFalling &&
|
||||||
|
serialState == SENDING_DATA &&
|
||||||
|
serialBitNum >= 8) {
|
||||||
|
clearState();
|
||||||
|
} else if(serClockRising) {
|
||||||
|
boolean writeRequest;
|
||||||
switch(serialState) {
|
switch(serialState) {
|
||||||
|
|
||||||
|
|
||||||
case RECEIVING_COMMAND:
|
case RECEIVING_COMMAND:
|
||||||
shiftReadPB(address,7-serialBitNum,SERIAL_DATA_PIN);
|
shiftReadPB(address, 7 - serialBitNum, SERIAL_DATA_PIN);
|
||||||
serialBitNum++;
|
serialBitNum++;
|
||||||
if(serialBitNum > 7) {
|
if (serialBitNum <= 7)
|
||||||
boolean writeRequest = address&(1<<7); // the MSB determines if it's a write request or not
|
break;
|
||||||
address &= ~(1<<7); // Discard the first bit, it's not part of the address
|
|
||||||
|
// 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;
|
serialBitNum = 0;
|
||||||
if(writeRequest) {
|
break;
|
||||||
serialState = RECEIVING_DATA;
|
#endif
|
||||||
serialBitNum = 0;
|
} else if (writeRequest) {
|
||||||
} else {
|
// Read the data byte before continuing.
|
||||||
if (address < 4) {
|
serialState = RECEIVING_DATA;
|
||||||
serialData = (seconds>>(8*address))&0xff;
|
serialBitNum = 0;
|
||||||
} if(!(address&0b0110000)) { // Apparently this address range is off-limits for reading
|
break;
|
||||||
serialData = pram[address];
|
} else {
|
||||||
}
|
boolean finished = false;
|
||||||
serialState = SENDING_DATA;
|
// Discard the first bit and the last two bits, it's not
|
||||||
serialBitNum = 0;
|
// pertinent to command interpretation.
|
||||||
// Set the pin to output mode
|
address = (address&~(1<<7))>>2;
|
||||||
cli();
|
// Decode the command/address.
|
||||||
DDRB |= SERIAL_DATA_PIN;
|
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();
|
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;
|
break;
|
||||||
|
|
||||||
case RECEIVING_DATA:
|
case RECEIVING_DATA:
|
||||||
shiftReadPB(serialData,7-serialBitNum,SERIAL_DATA_PIN);
|
shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN);
|
||||||
serialBitNum++;
|
serialBitNum++;
|
||||||
if(serialBitNum > 7) {
|
if (serialBitNum <= 7)
|
||||||
if(address < 4) {
|
break;
|
||||||
cli(); // Don't update the seconds counter while we're updating it, bad stuff could happen
|
|
||||||
seconds = (seconds & ~(((long)0xff)<<address)) | (((long)serialData)<<address);
|
// 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:
|
||||||
|
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();
|
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;
|
break;
|
||||||
|
|
||||||
case SENDING_DATA:
|
case SENDING_DATA:
|
||||||
{
|
digitalWritePB(SERIAL_DATA_PIN, bitRead(serialData, 7 - serialBitNum));
|
||||||
uint8_t bit = _BV(SERIAL_DATA_PIN);
|
|
||||||
uint8_t val = bitRead(serialData,7-serialBitNum);
|
|
||||||
cli();
|
|
||||||
if (val == 0)
|
|
||||||
PORTB &= ~bit;
|
|
||||||
else
|
|
||||||
PORTB |= bit;
|
|
||||||
sei();
|
|
||||||
}
|
|
||||||
serialBitNum++;
|
serialBitNum++;
|
||||||
if(serialBitNum > 7) {
|
/* if (serialBitNum <= 7)
|
||||||
clearState();
|
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;
|
break;
|
||||||
|
|
||||||
|
#if !defined(NoXPRAM) || !NoXPRAM
|
||||||
case RECEIVING_XCMD_ADDR:
|
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;
|
break;
|
||||||
|
|
||||||
case RECEIVING_XCMD_DATA:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,7 +404,7 @@ ISR(PCINT0_vect) {
|
||||||
handleRTCEnableInterrupt();
|
handleRTCEnableInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
ISR(TIMER0_OVF) {
|
ISR(TIMER0_OVF_vect) {
|
||||||
halfSecondInterrupt();
|
halfSecondInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
firmware/rtc/Makefile
Normal file
10
firmware/rtc/Makefile
Normal file
|
@ -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
|
|
@ -1,3 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
avr-gcc -c -Os -mmcu=attiny85 MacRTC.cpp
|
|
||||||
avr-gcc -mmcu=attiny85 -o MacRTC.axf MacRTC.o
|
|
Loading…
Reference in New Issue
Block a user