Advertisement
StarkRG

MacPlusRTC

Jul 23rd, 2018
1,297
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 7.26 KB | None | 0 0
  1. #include <avr/wdt.h>
  2. #include <avr/interrupt.h>
  3. #include <EEPROM.h>
  4.  
  5. /****************************************
  6.  *                                      *
  7.  * A drop-in replacement for the custom *
  8.  * RTC chip in early Apple Macintosh    *
  9.  * computers, using an ATtiny85.        *
  10.  * Uses an external 32.768kHz crystal   *
  11.  * on pins 2 and 3 as a clock source.   *
  12.  *            __  __                    *
  13.  *     1SEC -|1 \/ 8|- VCC              *
  14.  *    XTAL2 -|2    7|- RTC.CLK          *
  15.  *    XTAL1 -|3    6|- RTC.DATA         *
  16.  *      GND -|4____5|- !RTC             *
  17.  *                                      *
  18.  ****************************************/
  19.  
  20. const int      ONE_SEC_PIN = 1;   // A 1Hz square wave on PB5
  21. const int  RTC_ENABLE_PIN =  5;   // Active low chip enable on PB0
  22. const int  SERIAL_DATA_PIN = 6;   // Bi-directional serial data line on PB1
  23. const int SERIAL_CLOCK_PIN = 7;   // Serial clock input on PB2
  24.  
  25. const int PRAM_SIZE = 256;        // Mac Plus used the xPRAM chip with 256 bytes (not sure if time is part of pram)
  26. //const int PRAM_SIZE = 24;         // Models earlier than the Plus had 20(?) bytes of PRAM
  27.  
  28. volatile byte serialBitNum = 0;
  29. volatile byte address = 0;
  30. volatile byte serialData = 0;
  31.  
  32.  
  33. enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND, SENDING_DATA, RECEIVING_DATA };
  34. volatile SerialStateType serialState = SERIAL_DISABLED;
  35.  
  36. volatile unsigned long seconds = 0;
  37. volatile byte pram[PRAM_SIZE] = {}; // 256 Bytes of PRAM, the first four of which count the number of seconds since 1/1/1904
  38.  
  39. /*
  40.  * The following is potential locations of various bits of PRAM data, none of this is in any way certain:
  41.  *    Sound volume is in pram[0x08]
  42.  *    Alert sound is in param[0x7c - 0x7d]
  43.  *    Machine location and timezone is in pram[0xE4 - 0xEF]
  44.  */
  45.  
  46.  
  47. /*
  48.  * An interrupt to both increment the seconds counter and generate the square wave
  49.  */
  50. void halfSecondInterrupt() {
  51.   PINB = 1<<PINB0;  // Flip the one-second pin
  52.   if(!(PINB & (1<<PINB0))) { // If the one-second pin is low
  53.     seconds++;
  54.   }
  55. }
  56.  
  57. /*
  58.  * The actual serial communication can be done in the main loop, this way the clock still gets incremented
  59.  */
  60. void handleRTCEnableInterrupt() {
  61.   serialBitNum = 0;
  62.   address = 0;
  63.   serialData = 0;
  64.   if(!(PINB&(1<<RTC_ENABLE_PIN))){ // Simulates a falling interrupt
  65.     serialState = RECEIVING_COMMAND;
  66. //    enableRTC = true;
  67.   } else {                         // Simulates a rising interrupt
  68.     clearState();
  69.   }
  70. }
  71.  
  72. void clearState() {
  73.     DDRB &= ~(1<<DDB1);   // Return the pin to input mode
  74.     PORTB |= (1<<PORTB1); // Set pullup resistor
  75.     serialState = SERIAL_DISABLED;
  76.     serialBitNum = 0;
  77.     address = 0;
  78.     serialData = 0;
  79. }
  80.  
  81. /*
  82.  * The ATtiny has EEPROM, lets use it to store the contents of PRAM in case of power failure,
  83.  * this is an improvement over the original, still a good idea to keep the chip powered by a
  84.  * battery or supercapacitor so that the clock continues to advance.
  85.  *
  86.  */
  87. void savePRAM() {
  88.   noInterrupts(); // Don't update the seconds counter while we're saving it to ROM, probably unnecessary
  89.   for(int i = 0; i < 4; i++) {
  90.     EEPROM.update(i,(seconds>>(8*i))&0xff);
  91.   }
  92.   interrupts(); // Go ahead and interrupt us while we save the rest
  93.   for(int i = 0; i < PRAM_SIZE; i++) {
  94.     EEPROM.update(i+4,pram[i]);
  95.   }
  96. }
  97.  
  98.  
  99. void goToSleep() {
  100.   bitClear(MCUCR,SM0);  // The two SM bits must be set to 00 to enter idle mode
  101.   bitClear(MCUCR,SM1);  // Sleeping in other modes will disable the timer
  102.   bitSet(MCUCR,SE);
  103.   __asm__("sleep" "\n\t");
  104.   bitClear(MCUCR,SE);
  105. }
  106.  
  107. void setup() {
  108.   noInterrupts(); // Disable interrupts while we set things up
  109.  
  110.   pinMode(ONE_SEC_PIN, OUTPUT);             // The 1Hz square wave (used, I think, for interrupts elsewhere in the system)
  111.   pinMode(RTC_ENABLE_PIN, INPUT_PULLUP);    // The processor pulls this pin low when it wants access
  112.   pinMode(SERIAL_CLOCK_PIN, INPUT_PULLUP);  // The serial clock is driven by the processor
  113.   pinMode(SERIAL_DATA_PIN, INPUT_PULLUP);   // We'll need to switch this to output when sending data
  114.  
  115.   wdt_disable();      // Disable watchdog
  116.   bitSet(ACSR,ACD);   // Disable Analog Comparator, don't need it, saves power
  117.   bitSet(PRR,PRTIM1); // Disable Timer 1, only using Timer 0, Timer 1 uses around ten times as much current
  118.   bitSet(PRR,PRUSI);  // Disable Universal Serial Interface, using Apple's RTC serial interface on pins 6 and 7
  119.   bitSet(PRR,PRADC);  // Disable Analog/Digital Converter
  120.  
  121.   bitSet(GIMSK,PCIE);   // Pin Change Interrupt Enable
  122.   bitSet(PCMSK,PCINT0); // turn on RTC enable interrupt
  123.  
  124. //  for(int i = 0; i < 4; i++) {
  125. //    seconds += ((unsigned long)EEPROM.read(i))<<(8*i);
  126. //  }
  127. //  for(int i = 0; i < PRAM_SIZE; i--) { // Preload PRAM with saved values
  128. //    pram[i] = EEPROM.read(i+4);
  129. //  }
  130.  
  131.   //set up timer
  132.   bitSet(GTCCR,TSM);    // Turns off timers while we set it up
  133.   bitSet(TIMSK,TOIE0);  // Set Timer/Counter0 Overflow Interrupt Enable
  134.   TCCR0B = 0b111;       // Set prescaler, 32,768Hz/64 = 512Hz, fills up the 8-bit counter (256) once every half second
  135.   TCNT0 = 0;            // Clear the counter
  136.   bitClear(GTCCR,TSM);  // Turns timers back on
  137.  
  138.   interrupts(); //We're done setting up, enable those interrupts again
  139. }
  140.  
  141. void loop() {
  142.   if(digitalRead(RTC_ENABLE_PIN)) {
  143.     clearState();
  144.     goToSleep();
  145.   } else if(digitalRead(SERIAL_CLOCK_PIN)) {
  146.     switch(serialState) {
  147.      
  148.       case RECEIVING_COMMAND:
  149.         bitWrite(address,7-serialBitNum,digitalRead(SERIAL_DATA_PIN));
  150.         serialBitNum++;
  151.         if(serialBitNum > 7) {
  152.           boolean writeRequest = address&(1<<7);  // the MSB determines if it's a write request or not
  153.           address &= ~(1<<7); // Discard the first bit, it's not part of the address
  154.           serialBitNum = 0;
  155.           if(writeRequest) {
  156.             serialState = RECEIVING_DATA;
  157.             serialBitNum = 0;
  158.           } else {
  159.             if (address < 4) {
  160.               serialData = (seconds>>(8*address))&0xff;
  161.             } if(!(address&0b0110000)) { // Apparently this address range is off-limits for reading
  162.               serialData = pram[address-4];
  163.             }
  164.             serialState = SENDING_DATA;
  165.             serialBitNum = 0;
  166.             pinMode(SERIAL_DATA_PIN, OUTPUT); // Set the pin to output mode
  167.           }
  168.         }
  169.         break;
  170.        
  171.       case RECEIVING_DATA:
  172.         bitWrite(serialData,7-serialBitNum,digitalRead(SERIAL_DATA_PIN));
  173.         serialBitNum++;
  174.         if(serialBitNum > 7) {
  175.           if(address < 4) {
  176.             noInterrupts(); // Don't update the seconds counter while we're updating it, bad stuff could happen
  177.             seconds = (seconds & ~(((long)0xff)<<address)) | (((long)serialData)<<address);
  178.             interrupts();
  179.           } else {
  180.             pram[address-4] = serialData;
  181.           }
  182. //          savePRAM();
  183.           clearState();
  184.         }
  185.         break;
  186.        
  187.       case SENDING_DATA:
  188.         digitalWrite(SERIAL_DATA_PIN,bitRead(serialData,7-serialBitNum));
  189.         serialBitNum++;
  190.         if(serialBitNum > 7) {
  191.           clearState();
  192.         }
  193.     }
  194.   }
  195. }
  196.  
  197. /*
  198.  * Actually attach the interrupt functions
  199.  */
  200. ISR(PCINT0_vect) {
  201.     handleRTCEnableInterrupt();
  202. }
  203.  
  204. ISR(TIMER0_OVF) {
  205.     halfSecondInterrupt();
  206. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement