Skip to content

Commit

Permalink
Add timeout mechanism and update README.md.
Browse files Browse the repository at this point in the history
  • Loading branch information
pgreenland committed Dec 29, 2022
1 parent 9eac546 commit cef41d8
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 27 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ It therefore doesn't require a battery, storing the PRAM contents in the ATTiny'

It also doesn't require a crystal, using the ATTiny's integrated RC oscillator as its clock source.

One unfortunate limitation that I'm aware of, is that it can't be used in macs which have a RTC battery present.
This is due to the system shutdown, appearing to the RTC as the start of a transfer, for which it has no timeout mechanism.
I've previously attempted to add one but timings are already tight and the additional few instructions knocked things out of whack.
If used in a system with an RTC battery, the clock will not increment while the system is powered down and the battery will likely be depleted faster than expected as the microcontroller will be wide awake waiting for the next bit of serial data.
It should work (although hasn't been tested) in a mac with a working RTC battery. If desired the EEPROM backup of the settings and time can be disabled via commenting out the definition `ENABLE_EEPROM`.

An effort has been made to limit the EEPROM writes. With only updates being written, and update only being considered when the RTC is marked as read-only by the host. However it isn't clear how long the EEPROM will last in the ATTiny's using this approach. Time will tell.

It's based on the good work by other folks:

Expand Down Expand Up @@ -54,8 +53,12 @@ See my blog for additional notes on how the software works / its development:

### Note

The programmer I'm using is a usbasp programmer, search eBay for USBasp, they all seem pretty much the same. Remember to head over to [USBasp](https://www.fischl.de/usbasp/) to drop them a tip. Alternatively any programmer supported by avrdude can be selected via configuration in the CMake file.

As part of setting fuses the reset pin is reconfigured as GPIO.

Once this has been done the only way to re-program the chip is by using high voltage programming.

During development, as a workaround I've been re-assigning one of the unused crystal pins to fullfil the role of the PB5, with the help of a small adapter. Thereby allowing standard ICSP programming with no high voltage funny business required.

After the initial programming I found [this guide](https://www.electronics-lab.com/recover-bricked-attiny-using-arduino-as-high-voltage-programmer/) helpful. Using a spare Arduino and minimal components to rewrite the fuses using HV programming, such that the reset pin is again enabled and regular ICSP programming can be performed again.
93 changes: 70 additions & 23 deletions main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* ------- Config ------- */

/* Enable EEPROM - if defined RTC will backup/restore its data to EEPROM, allowing it to be used in a mac without an RTC battery. Disable for authentic RTC behavior */
#define ENABLE_EEPROM

/* Define to have write only test register readable, returning 0xA5 and 0x5A on alternate reads */
//#define TEST_REG_READ_DUMMY_VALUE

Expand Down Expand Up @@ -179,6 +182,9 @@ static const uint8_t uiCounterMatch = 125;
#define mIsClockHigh() (0 != (PINB & _BV(uiPinClock)))
#define mIsDataHigh() (0 != (PINB & _BV(uiPinData)))

/* Check if timeout timer has expired */
#define mSerialTimeout() (0 != (WDTCR & _BV(WDIF)))

/* Check if base address is special extended address value */
#define mIsAddrExtended(base) (0x38 == ((base) & 0x78))

Expand Down Expand Up @@ -233,6 +239,8 @@ static void processRegularCmdWrite(uint8_t uiAddrBase, uint8_t uiData);
static uint8_t processExtendedCmdRead(uint8_t uiAddrBase, uint8_t uiAddrExt);
static void processExtendedCmdWrite(uint8_t uiAddrBase, uint8_t uiAddrExt, uint8_t uiData);

#ifdef ENABLE_EEPROM

/* PRAM EEPROM buffer */
__attribute__((section(".eeprom")))
static uint8_t abyPRAM[PRAM_SIZE] = {0x00};
Expand All @@ -241,9 +249,13 @@ static uint8_t abyPRAM[PRAM_SIZE] = {0x00};
__attribute__((section(".eeprom")))
static uint32_t uiStoredSeconds = 0;

#endif

/* PRAM shadow buffer (working copy of EEPROM data in RAM) */
static uint8_t abyPRAMShadow[PRAM_SIZE];

#ifdef ENABLE_EEPROM

/*
** Dirty flag - Set when PRAM shadow write protection applied. Ideally this would
** only be set if a value was written, however this way we get a free flush of the
Expand All @@ -252,6 +264,8 @@ static uint8_t abyPRAMShadow[PRAM_SIZE];
*/
static volatile bool bShadowDirty;

#endif

/* Write protection status */
static bool bWriteProtected = true;

Expand All @@ -273,18 +287,21 @@ int main(void)
timerInit();
gpioInit();

#ifdef ENABLE_EEPROM
/* Load PRAM shadow from EEPROM */
eeprom_read_block(abyPRAMShadow, abyPRAM, sizeof(abyPRAMShadow));

/* Load last time from EEPROM */
uSeconds.uiValue = eeprom_read_dword(&uiStoredSeconds);
#endif

/* Enable interrupts */
sei();

/* Sleep whenever possible, updating EEPROM if dirty */
for (;;)
{
#ifdef ENABLE_EEPROM
if (bShadowDirty)
{
/* Clear dirty flag first in case its set again */
Expand All @@ -303,6 +320,7 @@ int main(void)
/* Update clock in EEPROM from RAM */
eeprom_update_dword(&uiStoredSeconds, uiTmpSeconds);
}
#endif

/* Go back to sleep */
sleep_mode();
Expand Down Expand Up @@ -349,16 +367,27 @@ ISR(PCINT0_vect)
/* Rising edge on enable (chip deselected), stop early */
if (mIsEnableHigh())
{
/* Ensure timing pin reset */
mTimingPinHigh();

/* Disable watchdog interrupt (stopping timer) and clear pending flag */
wdt_reset();
WDTCR = _BV(WDIF);

/* Bail */
return;
}

/* Reset watchdog and enable interrupt (to start timer) */
wdt_reset();
WDTCR = _BV(WDIE) | _BV(WDIF);

/* Falling edge on enable (chip selected), receive address bits */
uiBit = 8;
while (0 != uiBit)
{
/* Sample bit on rising edge, while monitoring enable (except after last bit) */
while (!mIsClockHigh()) if (mIsEnableHigh()) return;
while (!mIsClockHigh()) if (mIsEnableHigh() || mSerialTimeout()) return;
mTimingPinLow();
uiAddrBase = (uiAddrBase << 1) | mIsDataHigh();
mTimingPinHigh();
Expand All @@ -375,7 +404,7 @@ ISR(PCINT0_vect)
while (0 != uiBit)
{
/* Sample bit on rising edge, while monitoring enable */
while (!mIsClockHigh()) if (mIsEnableHigh()) return;
while (!mIsClockHigh()) if (mIsEnableHigh() || mSerialTimeout()) return;
mTimingPinLow();
uiAddrExt = (uiAddrExt << 1) | mIsDataHigh();
mTimingPinHigh();
Expand All @@ -393,7 +422,7 @@ ISR(PCINT0_vect)
while (0 != uiBit)
{
/* Sample bit on rising edge, while monitoring enable */
while (!mIsClockHigh()) if (mIsEnableHigh()) return;
while (!mIsClockHigh()) if (mIsEnableHigh() || mSerialTimeout()) return;
mTimingPinLow();
uiData = (uiData << 1) | mIsDataHigh();
mTimingPinHigh();
Expand Down Expand Up @@ -431,43 +460,59 @@ ISR(PCINT0_vect)
}
mTimingPinHigh();

/* Send data */
/* Send data, cache current DDR value */
uint8_t uiDDRVal = DDRB;

/*
** Invert data bits to assist assembly routine
** When data bit is 1 we want to set ddr reg bit to 0 and vice versa
*/
uiData = ~uiData;

/* Shift out bits */
uiBit = 8;
while (0 != uiBit)
{
/* Change data when clock low, before waiting for rising edge, while monitoring enable */
while (mIsClockHigh()) if (mIsEnableHigh()) goto AbortSend;
while (mIsClockHigh()) if (mIsEnableHigh()) break;
mTimingPinLow();
if (uiData & 0x80)
{
/* Switch pin to input mode, allowing it to be pulled up externally */
mSetDataPinHigh();
}
else
{
/* Switch pin to output mode, pulling it low */
mSetDataPinLow();
}
asm volatile(
/* Shift data bit out of data register */
"bst %[out], 7\n\t"
"bld %[ddrb_tmp], %[data_pin]\n\t"
"out %[ddrb], %[ddrb_tmp]\n\t"
:
/* Outputs */
[ddrb_tmp] "+r" (uiDDRVal)
:
/* Inputs */
[out] "r" (uiData),
[ddrb] "I" (_SFR_IO_ADDR(DDRB)),
[data_pin] "I" (uiPinData)
:
/* Clobbers - none */
);
mTimingPinHigh();
uiData <<= 1;
uiBit--;
if (0 != uiBit) while (!mIsClockHigh()) if (mIsEnableHigh()) return;
if (0 != uiBit) while (!mIsClockHigh()) if (mIsEnableHigh() || mSerialTimeout()) break;
}

/* Give host a chance to sample last bit, wait for enable to go high */
while (!mIsEnableHigh());
while (!mIsEnableHigh() && !mSerialTimeout());

AbortSend:
/* Switch pin to input mode, allowing data line to float after completion / abort */
mSetDataPinHigh();
/* Switch pin to input mode, allowing data line to float after completion / abort */
mSetDataPinHigh();
}
}

static void powerSaveInit(void)
ISR(WDT_vect)
{
/* Disable watchdog */
wdt_disable();
/* Ignore watchdog interrupt, managed by ISR above */
}

static void powerSaveInit(void)
{
/* Disable Analog Comparator */
ACSR |= _BV(ACD);

Expand Down Expand Up @@ -571,8 +616,10 @@ static inline void processRegularCmdWrite(uint8_t uiAddrBase, uint8_t uiData)
/* Extract new write protect status */
bool bNewWriteProtected = mDataByteToWriteProtect(uiData);

#ifdef ENABLE_EEPROM
/* Set dirty flag when transitioning from read-write to read-only */
if (!bWriteProtected && bNewWriteProtected) bShadowDirty = true;
#endif

/* Update write protect flag */
bWriteProtected = bNewWriteProtected;
Expand Down

0 comments on commit cef41d8

Please sign in to comment.