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:
Andrew Makousky 2020-09-04 06:28:46 -05:00
parent 097581b80a
commit 1f8d0f8163
3 changed files with 192 additions and 106 deletions

View File

@ -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;
}

View File

@ -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 */

3
firmware/rtc/domake.sh Executable file
View File

@ -0,0 +1,3 @@
#! /bin/sh
avr-gcc -c -Os -mmcu=attiny85 MacRTC.cpp
avr-gcc -mmcu=attiny85 -o MacRTC.axf MacRTC.o