mirror of
https://github.com/quorten/macsehw.git
synced 2024-05-29 05:41:34 +00:00
625 lines
20 KiB
C
625 lines
20 KiB
C
/* Macintosh RTC chip drop-in replacement.
|
|
|
|
Written in 2020 by Andrew Makousky
|
|
|
|
Public Domain Dedication:
|
|
|
|
To the extent possible under law, the author(s) have dedicated all
|
|
copyright and related and neighboring rights to this software to
|
|
the public domain worldwide. This software is distributed without
|
|
any warranty.
|
|
|
|
You should have received a copy of the CC0 Public Domain Dedication
|
|
along with this software. If not, see
|
|
<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>
|
|
*/
|
|
|
|
// 8 MHz clock is recommended for a physical device. For real-time
|
|
// simulation, a slower 400 kHz clock is needed.
|
|
#ifndef F_CPU
|
|
#define F_CPU 8000000
|
|
//#define F_CPU 400000 // DEBUG
|
|
#endif
|
|
|
|
#include <avr/io.h>
|
|
#include <avr/wdt.h>
|
|
#include <avr/interrupt.h>
|
|
#include <avr/sleep.h>
|
|
|
|
/********************************************************************/
|
|
// Simplified Arduino.h definitions.
|
|
typedef enum { false, true } bool; // Compatibility with C++.
|
|
typedef bool boolean;
|
|
typedef uint8_t byte;
|
|
|
|
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
|
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
|
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
|
|
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
|
|
// END simplified Arduino.h definitions.
|
|
/********************************************************************/
|
|
|
|
/****************************************
|
|
* *
|
|
* A drop-in replacement for the custom *
|
|
* RTC chip in early Apple Macintosh *
|
|
* computers, using an ATtiny85. *
|
|
* Uses an external 32.768kHz crystal *
|
|
* on pins 2 and 3 as a clock source.* *
|
|
* SEE ELECTRICAL SPECIFICATIONS. *
|
|
* __ __ *
|
|
* 1SEC -|1 \/ 8|- VCC *
|
|
* XTAL2 -|2 7|- RTC.CLK *
|
|
* XTAL1 -|3 6|- RTC.DATA *
|
|
* GND -|4____5|- !RTC *
|
|
* *
|
|
****************************************/
|
|
|
|
/*********************************************
|
|
* ATMEL ATTINY85 *
|
|
* *
|
|
* +-\/-+ *
|
|
* Ain0 (D 5) PB5 1| |8 Vcc *
|
|
* Ain3 (D 3) PB3 2| |7 PB2 (D 2) Ain1 *
|
|
* Ain2 (D 4) PB4 3| |6 PB1 (D 1) pwm1 *
|
|
* GND 4| |5 PB0 (D 0) pwm0 *
|
|
* +----+ *
|
|
*********************************************/
|
|
|
|
const int ONE_SEC_PIN = 5; // A 1Hz square wave on PB5
|
|
const int RTC_ENABLE_PIN = 0; // Active low chip enable on PB0
|
|
const int SERIAL_DATA_PIN = 1; // Bi-directional serial data line on PB1
|
|
const int SERIAL_CLOCK_PIN = 2; // Serial clock input on PB2
|
|
|
|
/* ELECTRICAL SPECIFICATIONS:
|
|
|
|
* When the Macintosh is powered off, the RTC is powered by the
|
|
clock battery. The battery supply voltage is anywhere from 3.6V
|
|
to 3V, though depleted batteries can sink below 3V.
|
|
|
|
* When the Macintosh is powered on, a diode supplies power to the
|
|
RTC from the main logic board's power rails. This means the RTC
|
|
runs off of 5V power during power-on operation. If necessary, we
|
|
can take advantage of this to run the AVR core clock at 16 MHz.
|
|
|
|
* All dedicated input lines already have a pull-up resistor, so
|
|
there is no need to enable the AVR's internal pull-up resistors.
|
|
|
|
* The bi-directional serial data line is also wired to a pull-up
|
|
resistor. This means we can use open-drain signaling to avoid
|
|
the risk of the output drivers getting burned out if both sides
|
|
inadvertently configure themseslves as outputs at the same time.
|
|
|
|
* What about the one-second interrupt pin? Since this is wired to
|
|
a dedicated input line, it's okay to leave this as a "totem-pole"
|
|
buffered output.
|
|
|
|
* The serial data clock needs to be able to operate at a frequency
|
|
of at least 1 kHz, maybe up to 20 kHz.
|
|
|
|
* Because of the requirement on the serial clock speed, the AVR
|
|
core clock speed should be around 8 MHz, given that it can take
|
|
about 100 cycles to process one edge of the serial data clock.
|
|
|
|
* Because the AVR core clock speed needs to operate faster than the
|
|
32.768 kHz crystal oscillator clock frequency, the external clock
|
|
would ideally be used as the crystal oscillator input to an
|
|
asynchronous timer. Unfortunately, the ATTiny85 does not have
|
|
the necessary circuitry or ASSR control register.
|
|
|
|
If you are willing to forgo the cosmetics of a pin-compatible DIP
|
|
package, you can instead use the ATTiny87 which has an AS0
|
|
asynchronous timer. Though it has extra pins, it comes in a
|
|
smaller form factor, so you can just mount it on a custom adapter
|
|
circuit board that breaks out the desired pins to through-hole
|
|
and ignores/grounds the unnecessary pins.
|
|
|
|
* TODO: Determine the target standby power consumption.
|
|
*/
|
|
|
|
#if NoXPRAM
|
|
// Models earlier than the Plus had 20 bytes of PRAM
|
|
#define PRAM_SIZE 20
|
|
const int group1Base = 0x00;
|
|
const int group2Base = 0x10;
|
|
#else
|
|
// Mac Plus used the xPRAM chip with 256 bytes
|
|
#define PRAM_SIZE 256
|
|
const int group1Base = 0x10;
|
|
const int group2Base = 0x08;
|
|
#endif
|
|
|
|
// Timer constants
|
|
#if F_CPU == 8000000
|
|
#define PRESCALER_MASK 0b101 /* 1/1024 */
|
|
#define LIM_OFLOWS 15
|
|
#define LIM_REMAIN 66
|
|
#define NUMER_FRAC_REMAIN 1
|
|
// `fracRemain` denominator is assumed to be a power-of-two, this
|
|
// makes calculations more efficient.
|
|
#define DENOM_FRAC_REMAIN 4
|
|
#define MASK_FRAC_REMAIN (DENOM_FRAC_REMAIN-1)
|
|
|
|
#elif F_CPU == 400000
|
|
#define PRESCALER_MASK 0b100 /* 1/256 */
|
|
#define LIM_OFLOWS 3
|
|
#define LIM_REMAIN 13
|
|
#define NUMER_FRAC_REMAIN 1
|
|
// `fracRemain` denominator is assumed to be a power-of-two, this
|
|
// makes calculations more efficient.
|
|
#define DENOM_FRAC_REMAIN 4
|
|
#define MASK_FRAC_REMAIN (DENOM_FRAC_REMAIN-1)
|
|
|
|
#else
|
|
#error "Invalid clock frequency selection"
|
|
#endif
|
|
|
|
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND,
|
|
SENDING_DATA, RECEIVING_DATA,
|
|
RECEIVING_XCMD_ADDR, RECEIVING_XCMD_DATA };
|
|
|
|
enum PramAddrResult { INVALID_CMD, SECONDS_CMD,
|
|
WRTEST_CMD, WRPROT_CMD, SUCCESS_ADDR };
|
|
|
|
volatile boolean lastRTCEnable = 0;
|
|
volatile boolean lastSerClock = 0;
|
|
volatile boolean serClockRising = false;
|
|
volatile boolean serClockFalling = false;
|
|
|
|
volatile byte serialState = SERIAL_DISABLED;
|
|
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?
|
|
|
|
TODO INVESTIGATE: Does `simavr` not initialize non-zero variables?
|
|
Or is this a quirk with `avr-gcc`? */
|
|
volatile unsigned long seconds = 60UL * 60 * 24 * (365 * 4 + 1) * 20;
|
|
volatile byte writeProtect = 0;
|
|
volatile byte pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data
|
|
|
|
// Extra timer precision book-keeping.
|
|
volatile byte numOflows = 0;
|
|
volatile byte fracRemain = 0;
|
|
|
|
/* Explanation of the 1-second timer calculations.
|
|
|
|
First divide the AVR core clock frequency by two since we count
|
|
half-second cycles.
|
|
|
|
8000000 / 2 = 4000000
|
|
|
|
Find quotient and remainder of timer frequency divider.
|
|
|
|
4000000 / 1024 = 3906 + 256/1024 = 3906 + 1/4
|
|
|
|
Now, divide by 256 to find out how many 8-bit timer overflows we
|
|
need to process.
|
|
|
|
3906 / 256 = 15 + 66/256
|
|
|
|
The remainder is the fractional overflow to process. We achieve
|
|
this by setting the counter register to 256 - remainder after 15
|
|
overflows. On the 16th overflow, we then just let the register
|
|
wrap to zero.
|
|
|
|
But we're not finished yet, we still have the other remainder to
|
|
adjust for. Here's how we do it.
|
|
|
|
1/4 / 256 = (1/4)/256
|
|
|
|
Okay, so what does that mean? That means we have 1/4 of a counter
|
|
tick to accumulate every half-second cycle in `fracRemain`. After 4
|
|
half-second cycles, the error is one full counter tick to add to
|
|
the fractional overflow. So, every 4 half-second cycles, we use 67
|
|
as the remainder rather than 66.
|
|
*/
|
|
|
|
#define shiftReadPB(output, bitNum, portBit) \
|
|
bitWrite(output, bitNum, ((PINB&_BV(portBit))) ? 1 : 0)
|
|
|
|
// Configure a pin to be an open-drain output, currently does nothing
|
|
// as using digitalWriteOD() does all required setup and leaving the
|
|
// pin as an input in the meantime is fine.
|
|
#define configOutputOD(pin)
|
|
|
|
// Digital write in an open-drain fashion: set as output-low for zero,
|
|
// set as input-no-pullup for one.
|
|
void digitalWriteOD(uint8_t pin, uint8_t val)
|
|
{
|
|
uint8_t bit = _BV(pin);
|
|
// cli();
|
|
if (val == 0) {
|
|
DDRB |= bit;
|
|
// PORTB &= ~bit;
|
|
} else {
|
|
DDRB &= ~bit;
|
|
// PORTB &= ~bit;
|
|
}
|
|
// sei();
|
|
}
|
|
|
|
void setup(void)
|
|
{
|
|
cli(); // Disable interrupts while we set things up
|
|
|
|
// TODO FIXME: Because `simavr` does not initialize non-zero global
|
|
// variables, we must repeat the initialization here.
|
|
seconds = 60UL * 60 * 24 * (365 * 4 + 1) * 20;
|
|
|
|
// OUTPUT: The 1Hz square wave (used for interrupts elsewhere in the system)
|
|
DDRB |= (1<<ONE_SEC_PIN);
|
|
// INPUT: The processor pulls this pin low when it wants access
|
|
DDRB &= ~(1<<RTC_ENABLE_PIN);
|
|
PORTB &= ~(1<<RTC_ENABLE_PIN);
|
|
lastRTCEnable = PINB&(1<<RTC_ENABLE_PIN); // Initialize last value
|
|
// INPUT: The serial clock is driven by the processor
|
|
DDRB &= ~(1<<SERIAL_CLOCK_PIN);
|
|
PORTB &= ~(1<<SERIAL_CLOCK_PIN);
|
|
lastSerClock = PINB&(1<<SERIAL_CLOCK_PIN); // Initialize last value
|
|
// INPUT: We'll need to switch this to output when sending data
|
|
DDRB &= ~(1<<SERIAL_DATA_PIN);
|
|
PORTB &= ~(1<<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
|
|
|
|
bitSet(GIMSK, PCIE); // Pin Change Interrupt Enable
|
|
bitSet(PCMSK, PCINT0); // turn on RTC enable interrupt
|
|
bitSet(PCMSK, PCINT2); // turn on serial clock 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 = PRESCALER_MASK; // Set prescaler
|
|
TCNT0 = 0; // Clear the counter
|
|
bitClear(GTCCR, TSM); // Turns timers back on
|
|
|
|
sei(); //We're done setting up, enable those interrupts again
|
|
|
|
}
|
|
|
|
void clearState(void)
|
|
{
|
|
// Return the pin to input mode
|
|
// cli();
|
|
DDRB &= ~(1<<SERIAL_DATA_PIN);
|
|
// PORTB &= ~(1<<SERIAL_DATA_PIN);
|
|
// sei();
|
|
serialState = SERIAL_DISABLED;
|
|
serialBitNum = 0;
|
|
address = 0;
|
|
serialData = 0;
|
|
}
|
|
|
|
/*
|
|
* An interrupt to both increment the seconds counter and generate the
|
|
* square wave
|
|
*/
|
|
void oflowInterrupt(void)
|
|
{
|
|
numOflows++;
|
|
if (numOflows == LIM_OFLOWS) {
|
|
// Configure a timer interrupt to handle the final remainder cycle
|
|
// wait. We simply subtract the value from 256, which is the same
|
|
// as going negative two's complement and using 8-bit wrap-around.
|
|
fracRemain += NUMER_FRAC_REMAIN;
|
|
TCNT0 = (fracRemain >= DENOM_FRAC_REMAIN) ?
|
|
-(LIM_REMAIN + 1) : -LIM_REMAIN;
|
|
fracRemain &= MASK_FRAC_REMAIN;
|
|
} else if (numOflows == LIM_OFLOWS + 1) {
|
|
// Reset the timer-related flags now that we've reached a
|
|
// half-second.
|
|
numOflows = 0;
|
|
PINB = 1<<ONE_SEC_PIN; // Flip the one-second pin
|
|
if (!(PINB&(1<<ONE_SEC_PIN))) { // If the one-second pin is low
|
|
seconds++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The actual serial communication can be done in the main loop, this
|
|
* way the clock still gets incremented.
|
|
*/
|
|
void handleRTCEnableInterrupt(void)
|
|
{
|
|
boolean curRTCEnable = PINB&(1<<RTC_ENABLE_PIN);
|
|
if (lastRTCEnable && !curRTCEnable){ // Simulates a falling interrupt
|
|
serialState = RECEIVING_COMMAND;
|
|
}
|
|
/* Else if a rising edge to disable the RTC interrupts a serial
|
|
communication in progress, we still wake up to clear the serial
|
|
state then go back to sleep. */
|
|
lastRTCEnable = curRTCEnable;
|
|
}
|
|
|
|
/*
|
|
* Same deal over here, the actual serial communication can be done in
|
|
* the main loop, this way the clock still gets incremented.
|
|
*/
|
|
void handleSerClockInterrupt(void)
|
|
{
|
|
boolean curSerClock = PINB&(1<<SERIAL_CLOCK_PIN);
|
|
if (!lastSerClock && curSerClock) {
|
|
serClockRising = true;
|
|
serClockFalling = false;
|
|
} else if (lastSerClock && !curSerClock) {
|
|
serClockRising = false;
|
|
serClockFalling = true;
|
|
}
|
|
/* Else leave it up to the main loop code to clear the edge trigger
|
|
flags. */
|
|
lastSerClock = curSerClock;
|
|
}
|
|
|
|
/*
|
|
* For 20-byte PRAM equivalent commands, compute the actual PRAM
|
|
* address by modifying the `address` variable in-place. 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)
|
|
{
|
|
// Discard the first bit and the last two bits, it's not pertinent
|
|
// to address interpretation.
|
|
address = (address&~(1<<7))>>2;
|
|
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;
|
|
// Addresses 14 and 15 are used for the encoding of the first
|
|
// byte of an extended command.
|
|
}
|
|
return INVALID_CMD;
|
|
} else {
|
|
// Group 1 register
|
|
address = (address&0x0f) + group1Base;
|
|
}
|
|
|
|
return SUCCESS_ADDR;
|
|
}
|
|
|
|
void loop(void)
|
|
{
|
|
if ((PINB&(1<<RTC_ENABLE_PIN))) {
|
|
clearState();
|
|
set_sleep_mode(0); // Sleep mode 0 == default, timers still running.
|
|
sleep_mode();
|
|
} else {
|
|
/* Normally we only perform an action on the rising edge of the
|
|
serial clock. The main exception is cleanup at the last cycle
|
|
of serial output, there we wait one full cycle and then until
|
|
the falling edge before switching the direction of the data pin
|
|
back to an input. */
|
|
if (serClockFalling &&
|
|
serialState == SENDING_DATA &&
|
|
serialBitNum >= 9) {
|
|
clearState();
|
|
} else if (serClockRising) {
|
|
boolean writeRequest;
|
|
switch(serialState) {
|
|
case RECEIVING_COMMAND:
|
|
shiftReadPB(address, 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));
|
|
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;
|
|
break;
|
|
#endif
|
|
} else if (writeRequest) {
|
|
// Read the data byte before continuing.
|
|
serialState = RECEIVING_DATA;
|
|
serialBitNum = 0;
|
|
break;
|
|
} else {
|
|
boolean finished = false;
|
|
// Decode the command/address.
|
|
switch (decodePramCmd(false)) {
|
|
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
|
|
configOutputOD(SERIAL_DATA_PIN);
|
|
break;
|
|
|
|
case RECEIVING_DATA:
|
|
shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN);
|
|
serialBitNum++;
|
|
if (serialBitNum <= 7)
|
|
break;
|
|
|
|
// Decode the command/address.
|
|
switch (decodePramCmd(true)) {
|
|
case SECONDS_CMD:
|
|
if (!writeProtect) {
|
|
// Write little endian clock data byte.
|
|
cli(); // Ensure that writes are atomic.
|
|
address = (address&0x03)<<3;
|
|
seconds &= ~((unsigned long)0xff<<address);
|
|
seconds |= (unsigned long)serialData<<address;
|
|
sei();
|
|
}
|
|
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:
|
|
if (serialBitNum <= 7)
|
|
digitalWriteOD(SERIAL_DATA_PIN,
|
|
bitRead(serialData, 7 - serialBitNum));
|
|
serialBitNum++;
|
|
/* if (serialBitNum <= 7)
|
|
break; */
|
|
|
|
/* NOTE: The last output cycle is treated specially, hold the
|
|
data line as an output for at least one full next cycle,
|
|
then until the falling edge of the serial clock, then
|
|
switch back to an input and reset the serial communication
|
|
state. It's for bug compatibility with the ROM, but with a
|
|
little bit of sanity too. */
|
|
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) {
|
|
// Read the data byte before continuing.
|
|
serialState = RECEIVING_XCMD_DATA;
|
|
serialBitNum = 0;
|
|
serialData = 0;
|
|
break;
|
|
}
|
|
|
|
// Read and send the PRAM register.
|
|
serialData = pram[address];
|
|
serialState = SENDING_DATA;
|
|
serialBitNum = 0;
|
|
// Set the pin to output mode
|
|
configOutputOD(SERIAL_DATA_PIN);
|
|
break;
|
|
|
|
case RECEIVING_XCMD_DATA:
|
|
shiftReadPB(serialData, 7 - serialBitNum, SERIAL_DATA_PIN);
|
|
serialBitNum++;
|
|
if (serialBitNum <= 7)
|
|
break;
|
|
|
|
// Write the PRAM register.
|
|
if (!writeProtect)
|
|
pram[address] = serialData;
|
|
// Finished with the write command.
|
|
clearState();
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
// Invalid state.
|
|
clearState();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Clear the edge trigger flags now that the events have been
|
|
// processed.
|
|
serClockRising = false;
|
|
serClockFalling = false;
|
|
|
|
// Go to sleep until the next serial clock rising or falling edge.
|
|
set_sleep_mode(0); // Sleep mode 0 == default, timers still running.
|
|
sleep_mode();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Actually attach the interrupt functions
|
|
*/
|
|
ISR(PCINT0_vect)
|
|
{
|
|
handleRTCEnableInterrupt();
|
|
handleSerClockInterrupt();
|
|
}
|
|
|
|
ISR(TIMER0_OVF_vect)
|
|
{
|
|
oflowInterrupt();
|
|
}
|
|
|
|
// Arduino main function.
|
|
int main(void)
|
|
{
|
|
setup();
|
|
|
|
for (;;) {
|
|
loop();
|
|
}
|
|
|
|
return 0;
|
|
}
|