Fix a bunch of issues with ATTiny85 RTC implementation.
Decouple from Arduino libraries, use a small header file instead. Now compiles with plain avr-gcc. Still needs more work.
This commit is contained in:
parent
097581b80a
commit
1f8d0f8163
|
@ -1,6 +1,9 @@
|
|||
#include <avr/io.h>
|
||||
#include <avr/wdt.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <EEPROM.h>
|
||||
#include <avr/sleep.h>
|
||||
|
||||
#include "arduino_sdef.h"
|
||||
|
||||
/****************************************
|
||||
* *
|
||||
|
@ -17,101 +20,53 @@
|
|||
* *
|
||||
****************************************/
|
||||
|
||||
const int ONE_SEC_PIN = 1; // A 1Hz square wave on PB5
|
||||
const int RTC_ENABLE_PIN = 5; // Active low chip enable on PB0
|
||||
const int SERIAL_DATA_PIN = 6; // Bi-directional serial data line on PB1
|
||||
const int SERIAL_CLOCK_PIN = 7; // Serial clock input on PB2
|
||||
/*********************************************
|
||||
* ATMEL ATTINY85 / ARDUINO *
|
||||
* *
|
||||
* +-\/-+ *
|
||||
* 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 PRAM_SIZE = 256; // Mac Plus used the xPRAM chip with 256 bytes, time is a separate 4 additional bytes
|
||||
//const int PRAM_SIZE = 20; // Models earlier than the Plus had 20 bytes of PRAM
|
||||
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
|
||||
|
||||
#if NoXPRAM
|
||||
// Models earlier than the Plus had 20 bytes of PRAM
|
||||
const int PRAM_SIZE = 20;
|
||||
#else
|
||||
// Mac Plus used the xPRAM chip with 256 bytes
|
||||
const int PRAM_SIZE = 256;
|
||||
#endif
|
||||
|
||||
volatile boolean lastSerClock = 0;
|
||||
volatile byte serialBitNum = 0;
|
||||
volatile byte address = 0;
|
||||
volatile byte serialData = 0;
|
||||
|
||||
|
||||
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND, SENDING_DATA, RECEIVING_DATA };
|
||||
enum SerialStateType { SERIAL_DISABLED, RECEIVING_COMMAND,
|
||||
SENDING_DATA, RECEIVING_DATA };
|
||||
volatile SerialStateType serialState = SERIAL_DISABLED;
|
||||
|
||||
volatile unsigned long seconds = 0;
|
||||
volatile byte pram[PRAM_SIZE] = {}; // 256 Bytes of PRAM, the first four of which count the number of seconds since 1/1/1904
|
||||
|
||||
/*
|
||||
* The following is potential locations of various bits of PRAM data, none of this is in any way certain:
|
||||
* Sound volume is in pram[0x08]
|
||||
* Alert sound is in param[0x7c - 0x7d]
|
||||
* Machine location and timezone is in pram[0xE4 - 0xEF]
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* An interrupt to both increment the seconds counter and generate the square wave
|
||||
*/
|
||||
void halfSecondInterrupt() {
|
||||
PINB = 1<<PINB0; // Flip the one-second pin
|
||||
if(!(PINB & (1<<PINB0))) { // 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() {
|
||||
serialBitNum = 0;
|
||||
address = 0;
|
||||
serialData = 0;
|
||||
if(!(PINB&(1<<RTC_ENABLE_PIN))){ // Simulates a falling interrupt
|
||||
serialState = RECEIVING_COMMAND;
|
||||
// enableRTC = true;
|
||||
} else { // Simulates a rising interrupt
|
||||
clearState();
|
||||
}
|
||||
}
|
||||
|
||||
void clearState() {
|
||||
DDRB &= ~(1<<DDB1); // Return the pin to input mode
|
||||
PORTB |= (1<<PORTB1); // Set pullup resistor
|
||||
serialState = SERIAL_DISABLED;
|
||||
serialBitNum = 0;
|
||||
address = 0;
|
||||
serialData = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ATtiny has EEPROM, lets use it to store the contents of PRAM in case of power failure,
|
||||
* this is an improvement over the original, still a good idea to keep the chip powered by a
|
||||
* battery or supercapacitor so that the clock continues to advance.
|
||||
*
|
||||
*/
|
||||
void savePRAM() {
|
||||
noInterrupts(); // Don't update the seconds counter while we're saving it to ROM, probably unnecessary
|
||||
for(int i = 0; i < 4; i++) {
|
||||
EEPROM.update(i,(seconds>>(8*i))&0xff);
|
||||
}
|
||||
interrupts(); // Go ahead and interrupt us while we save the rest
|
||||
for(int i = 0; i < PRAM_SIZE; i++) {
|
||||
EEPROM.update(i+4,pram[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void goToSleep() {
|
||||
bitClear(MCUCR,SM0); // The two SM bits must be set to 00 to enter idle mode
|
||||
bitClear(MCUCR,SM1); // Sleeping in other modes will disable the timer
|
||||
bitSet(MCUCR,SE);
|
||||
__asm__("sleep" "\n\t");
|
||||
bitClear(MCUCR,SE);
|
||||
}
|
||||
// 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;
|
||||
volatile byte pram[PRAM_SIZE] = {}; // PRAM initialized as zeroed data
|
||||
|
||||
void setup() {
|
||||
noInterrupts(); // Disable interrupts while we set things up
|
||||
|
||||
pinMode(ONE_SEC_PIN, OUTPUT); // The 1Hz square wave (used, I think, for interrupts elsewhere in the system)
|
||||
pinMode(RTC_ENABLE_PIN, INPUT_PULLUP); // The processor pulls this pin low when it wants access
|
||||
pinMode(SERIAL_CLOCK_PIN, INPUT_PULLUP); // The serial clock is driven by the processor
|
||||
pinMode(SERIAL_DATA_PIN, INPUT_PULLUP); // We'll need to switch this to output when sending data
|
||||
|
||||
|
||||
pinModePB(ONE_SEC_PIN, OUTPUT); // The 1Hz square wave (used, I think, for interrupts elsewhere in the system)
|
||||
pinModePB(RTC_ENABLE_PIN, INPUT_PULLUP); // The processor pulls this pin low when it wants access
|
||||
pinModePB(SERIAL_CLOCK_PIN, INPUT_PULLUP); // The serial clock is driven by the processor
|
||||
pinModePB(SERIAL_DATA_PIN, INPUT_PULLUP); // We'll need to switch this to output when sending data
|
||||
|
||||
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
|
||||
|
@ -121,38 +76,77 @@ void setup() {
|
|||
bitSet(GIMSK,PCIE); // Pin Change Interrupt Enable
|
||||
bitSet(PCMSK,PCINT0); // turn on RTC enable interrupt
|
||||
|
||||
// for(int i = 0; i < 4; i++) {
|
||||
// seconds += ((unsigned long)EEPROM.read(i))<<(8*i);
|
||||
// }
|
||||
// for(int i = 0; i < PRAM_SIZE; i--) { // Preload PRAM with saved values
|
||||
// pram[i] = EEPROM.read(i+4);
|
||||
// }
|
||||
|
||||
//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
|
||||
|
||||
|
||||
interrupts(); //We're done setting up, enable those interrupts again
|
||||
}
|
||||
|
||||
void clearState() {
|
||||
// Return the pin to input mode, set pullup resistor
|
||||
pinModePB(SERIAL_DATA_PIN, INPUT_PULLUP);
|
||||
serialState = SERIAL_DISABLED;
|
||||
lastSerClock = 0;
|
||||
serialBitNum = 0;
|
||||
address = 0;
|
||||
serialData = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* An interrupt to both increment the seconds counter and generate the
|
||||
* square wave
|
||||
*/
|
||||
void halfSecondInterrupt() {
|
||||
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() {
|
||||
if(!(PINB&(1<<RTC_ENABLE_PIN))){ // Simulates a falling interrupt
|
||||
serialState = RECEIVING_COMMAND;
|
||||
// enableRTC = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if(digitalRead(RTC_ENABLE_PIN)) {
|
||||
if((PINB&(1<<RTC_ENABLE_PIN))) {
|
||||
clearState();
|
||||
goToSleep();
|
||||
} else if(digitalRead(SERIAL_CLOCK_PIN)) {
|
||||
switch(serialState) {
|
||||
|
||||
set_sleep_mode(0); // Sleep mode 0 == default, timers still running.
|
||||
sleep_mode();
|
||||
} else {
|
||||
// Compute rising and falling edge trigger flags for the serial
|
||||
// clock.
|
||||
boolean curSerClock = PINB&(1<<SERIAL_CLOCK_PIN);
|
||||
boolean serClockRising = !lastSerClock && curSerClock;
|
||||
boolean serClockFalling = lastSerClock && !curSerClock;
|
||||
lastSerClock = curSerClock;
|
||||
|
||||
// TODO FIXME: We need to implement an artificial delay between
|
||||
// the clock's rising edge and the update of the data line output
|
||||
// because of a bug in the ROM. Is 10 microseconds a good wait
|
||||
// time?
|
||||
|
||||
if(serClockRising) {
|
||||
switch(serialState) {
|
||||
|
||||
case RECEIVING_COMMAND:
|
||||
bitWrite(address,7-serialBitNum,digitalRead(SERIAL_DATA_PIN));
|
||||
bitWrite(address,7-serialBitNum,digitalReadPB(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
|
||||
serialBitNum = 0;
|
||||
if(writeRequest) {
|
||||
if(writeRequest) {
|
||||
serialState = RECEIVING_DATA;
|
||||
serialBitNum = 0;
|
||||
} else {
|
||||
|
@ -163,13 +157,13 @@ void loop() {
|
|||
}
|
||||
serialState = SENDING_DATA;
|
||||
serialBitNum = 0;
|
||||
pinMode(SERIAL_DATA_PIN, OUTPUT); // Set the pin to output mode
|
||||
pinModePB(SERIAL_DATA_PIN, OUTPUT); // Set the pin to output mode
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case RECEIVING_DATA:
|
||||
bitWrite(serialData,7-serialBitNum,digitalRead(SERIAL_DATA_PIN));
|
||||
bitWrite(serialData,7-serialBitNum,digitalReadPB(SERIAL_DATA_PIN));
|
||||
serialBitNum++;
|
||||
if(serialBitNum > 7) {
|
||||
if(address < 4) {
|
||||
|
@ -179,17 +173,17 @@ void loop() {
|
|||
} else {
|
||||
pram[address] = serialData;
|
||||
}
|
||||
// savePRAM();
|
||||
clearState();
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case SENDING_DATA:
|
||||
digitalWrite(SERIAL_DATA_PIN,bitRead(serialData,7-serialBitNum));
|
||||
digitalWritePB(SERIAL_DATA_PIN,bitRead(serialData,7-serialBitNum));
|
||||
serialBitNum++;
|
||||
if(serialBitNum > 7) {
|
||||
clearState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -204,3 +198,14 @@ ISR(PCINT0_vect) {
|
|||
ISR(TIMER0_OVF) {
|
||||
halfSecondInterrupt();
|
||||
}
|
||||
|
||||
// Arduino main function.
|
||||
int main(void) {
|
||||
setup();
|
||||
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef ARDUINO_SDEF_H
|
||||
#define ARDUINO_SDEF_H
|
||||
|
||||
/********************************************************************/
|
||||
// Simplified Arduino.h definitions.
|
||||
typedef bool boolean;
|
||||
typedef uint8_t byte;
|
||||
|
||||
#define HIGH 0x1
|
||||
#define LOW 0x0
|
||||
|
||||
#define INPUT 0x0
|
||||
#define OUTPUT 0x1
|
||||
#define INPUT_PULLUP 0x2
|
||||
|
||||
#define interrupts() sei()
|
||||
#define noInterrupts() cli()
|
||||
|
||||
#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.
|
||||
|
||||
/********************************************************************/
|
||||
// Simplified wiring_digital.c definitions.
|
||||
// Only suitable for single source code file projects.
|
||||
|
||||
void pinModePB(uint8_t portbit, uint8_t mode)
|
||||
{
|
||||
uint8_t bit = _BV(portbit);
|
||||
|
||||
if (mode == INPUT) {
|
||||
uint8_t oldSREG = SREG;
|
||||
cli();
|
||||
DDRB &= ~bit;
|
||||
PORTB &= ~bit;
|
||||
SREG = oldSREG;
|
||||
} else if (mode == INPUT_PULLUP) {
|
||||
uint8_t oldSREG = SREG;
|
||||
cli();
|
||||
DDRB &= ~bit;
|
||||
PORTB |= bit;
|
||||
SREG = oldSREG;
|
||||
} else {
|
||||
uint8_t oldSREG = SREG;
|
||||
cli();
|
||||
DDRB |= bit;
|
||||
SREG = oldSREG;
|
||||
}
|
||||
}
|
||||
|
||||
void digitalWritePB(uint8_t portbit, uint8_t val)
|
||||
{
|
||||
uint8_t bit = _BV(portbit);
|
||||
|
||||
uint8_t oldSREG = SREG;
|
||||
cli();
|
||||
|
||||
if (val == LOW) {
|
||||
PORTB &= ~bit;
|
||||
} else {
|
||||
PORTB |= bit;
|
||||
}
|
||||
|
||||
SREG = oldSREG;
|
||||
}
|
||||
|
||||
int digitalReadPB(uint8_t portbit)
|
||||
{
|
||||
uint8_t bit = _BV(portbit);
|
||||
|
||||
if (PINB & bit) return HIGH;
|
||||
return LOW;
|
||||
}
|
||||
// END simplified wiring_digital.c definitions.
|
||||
|
||||
#endif /* not ARDUINO_SDEF_H */
|
|
@ -0,0 +1,3 @@
|
|||
#! /bin/sh
|
||||
avr-gcc -c -Os -mmcu=attiny85 MacRTC.cpp
|
||||
avr-gcc -mmcu=attiny85 -o MacRTC.axf MacRTC.o
|
Loading…
Reference in New Issue