Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pcf8523 interrupts #124

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,33 @@ MIT license, check license.txt for more information
All text above must be included in any redistribution

To install, use the Arduino Library Manager and search for "RTClib" and install the library.

### Pcf8523 Timers (Adalogger FeatherWing RTC+SD)
The Pcf8523's timers are similar to other timers found in microcontrollers and RTCs. A timer starts at some value, and counts down with some frequency. When the timer hits 0, its interrupt flag is set. If the interrupt signal is enabled, the flag drives an interrupt pin.

#### Timer Theory of Operation
This RTC has three variable timers: countdown timer A, watchdog timer A, and countdown timer B. Two of these can be run at a time.

##### Timers and Timing
A timer counts down from a value (1-255), at a counting frequency (see the enumeration PCF8523TimerClockFreq). Altogether, a timer counts out the period P = (value / frequency) from the time it is started. This means that the RTC can time an operation from a few hundred microseconds (value=1 at frequency=4kHz, yields 244 microseconds) to several days (value=255 at frequency=1/3600Hz or 1 hour, yields 10.625 days).

Countdown timers A and B count down to 0, and then start over counting from their original value. Watchdog timer A counts down from some value, but does not begin counting again when zero is reached. WDT A is a mode of timer A, and cannot be run concurrently with countdown timer A. When a timer is disabled, its value is cleared rather than paused.

##### Timer Interrupts
Each timer has an interrupt; an interrupt has a flag and a signal. When a timer's value reaches 0, its interrupt flag is set. If its interrupt signal is enabled, timer A's flag drives the INT1 pin, and timer B's flag drives the INT1 and INT2 pins.

For countdown timers A and B, these pins remain set until the flag is cleared (by writing 0 into the flag), or until the signal is disabled.

Watchdog timer A's interrupt flag differs slightly: the flag set in the same way, but is automatically cleared with the next read of the interrupt register (control 2). Reading the timer state, the interrupt state, or writing the interrupt state will accomplish this register-clearing read as a side-effect.

The INT1 line is shared with the CLKOUT/square wave function. Only one function can be used, and enabling the interrupt signal will disable the square wave generation function.

#### Wiring and Configuring the Interrupt Lines

The INT1 and INT2 pins are open collectors, and transition from high-impedance to low-impedance to ground when any interrupt signal becomes active. When an INT pin is connected to a pulled-up GPIO pin, the GPIO pin's digital state will read low when the interrupt is active, and high otherwise.

#### Pinouts and Form-factors

INT1 is available on Adafruit's feather and standalone boards for the pcf, labeled as "INT" on the feather (https://www.adafruit.com/product/2922) and "SQW" on the standalone board (https://www.adafruit.com/product/3295). INT2 is available only in the TSSOP14 form-factor of the pcf.

This means that timer A and B's interrupt output are indistinguishable on a hardware level on Adafruit's boards.
177 changes: 177 additions & 0 deletions RTClib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,183 @@ void RTC_PCF8523::calibrate(Pcf8523OffsetMode mode, int8_t offset) {
Wire.endTransmission();
}

#define read_PCF8523_register(reg) read_i2c_register(PCF8523_ADDRESS, reg) ///< Reads the register (reg), with the PCF address given to i2c
#define write_PCF8523_register(reg, val) write_i2c_register(PCF8523_ADDRESS, reg, val) ///< Writes the register with a value (reg, value), with the PCF address given to i2c

extern const Pcf8523TimerDetails timer_details_table[];

/**************************************************************************/
/*!
@brief Constructor for timer, from member timer and interrupt fields

@param timer_value The starting countdown value of the timer, from 0-255
@param timer_freq The countdown clock frequency of the timer
@param timer_enabled Whether the timer is enabled
@param irupt_flag Whether the timer has completed since this flag was last cleared
@param irupt_signal_enabled Whether the timer's interrupt drives its
@associated pin(s)

@note The irupt_flag can only be cleared or preserved, when set by a timer
going off; it cannot be set with a write, if unset.
*/
/**************************************************************************/
Pcf8523TimerState::Pcf8523TimerState(uint8_t timer_value, PCF8523TimerClockFreq timer_freq, bool timer_enabled, bool irupt_flag, bool irupt_signal_enabled) {
value = timer_value;
freq = timer_freq;
enabled = timer_enabled;

this->irupt_flag = irupt_flag;
irupt_enabled = irupt_signal_enabled;
}

/**************************************************************************/
/*!
@brief Copy constructor.
@param copy Pcf8523TimerState to copy.
*/
/**************************************************************************/
Pcf8523TimerState::Pcf8523TimerState(const Pcf8523TimerState &copy) {
value = copy.value;
freq = copy.freq;
enabled = copy.enabled;

irupt_flag = copy.irupt_flag;
irupt_enabled = copy.irupt_enabled;
}

/**************************************************************************/
/*!

@brief Reads the interrupt state into dest for the given interrupt.
@param irupt Which interrupt to read.
@param dest The IruptState struct where the current interrupt state is written.
*/
/**************************************************************************/
void RTC_PCF8523::read_irupt(Pcf8523Timer irupt, Pcf8523IruptState *dest) {
/* read the interrupt's register fields into the struct */

const Pcf8523TimerDetails *timer_details = &(timer_details_table[irupt]);

const uint8_t irupt_reg = read_PCF8523_register(timer_details->irupt_control_register);

dest->irupt_flag = irupt_reg & timer_details->irupt_flag_mask;
dest->irupt_enabled = irupt_reg & timer_details->irupt_en_mask;
}

/**************************************************************************/
/*!

@brief Sets the interrupt state for the given interrupt.
@param irupt Which interrupt to read.
@param src The IruptState struct that programs the interrupt.
*/
/**************************************************************************/
void RTC_PCF8523::write_irupt(Pcf8523Timer irupt, Pcf8523IruptState *src) {
/* write the given interrupt into its register fields */

const Pcf8523TimerDetails *timer_details = &(timer_details_table[irupt]);

uint8_t irupt_reg = read_PCF8523_register(timer_details->irupt_control_register);

// clear out flag/en, and selectively re-add
irupt_reg &= ~(timer_details->irupt_flag_mask | timer_details->irupt_en_mask);

if (src->irupt_enabled) {
irupt_reg |= timer_details->irupt_en_mask;
}

if (src->irupt_flag) {
irupt_reg |= timer_details->irupt_flag_mask;
}

write_PCF8523_register(timer_details->irupt_control_register, irupt_reg);
}

/**************************************************************************/
/*!

@brief Programs the selected timer from the given TimerState object.
@param timer Which timer to program.
@param src The TimerState object that programs the timer.

@note If the interrupt enable field is set, the square wave/CLKOUT
function will be disabled, and the SqwPinMode set to "OFF."
Instead, the INT function will be used.
*/
/**************************************************************************/
void RTC_PCF8523::write_timer(Pcf8523Timer timer, const Pcf8523TimerState &src) {
/* setup the given timer in TimerState src */

const Pcf8523TimerDetails *details = &(timer_details_table[timer]);

// turn off the timer before changing values
uint8_t clkout_ctrl = read_PCF8523_register(details->timer_en_register);
clkout_ctrl &= ~(details->timer_dis_mask);
write_PCF8523_register(details->timer_en_register, clkout_ctrl);

// set the timer value and frequencies
write_PCF8523_register(details->timer_value_register, src.value);
write_PCF8523_register(details->timer_freq_register, (uint8_t) src.freq);

// set the irupt flag/enable bits -- delegate to other function
Pcf8523IruptState irupt;
irupt.irupt_flag = src.irupt_flag;
irupt.irupt_enabled = src.irupt_enabled;

write_irupt(timer, &irupt);

// enable the timer, if set
if (src.enabled) {
clkout_ctrl = read_PCF8523_register(details->timer_en_register);

// turn off the CLKOUT function if the interrupt is enabled,
// enable the timer
if (src.irupt_enabled) {
clkout_ctrl |= PCF8523_CLKOUT_DIS;
}
clkout_ctrl |= details->timer_en_mask;

write_PCF8523_register(details->timer_en_register, clkout_ctrl);
}
}

/**************************************************************************/
/*!

@brief Reads the state of selected timer into the returned TimerState object.
@param timer Which timer to read.
@return TimerState The timer and interrupt current state
*/
/**************************************************************************/
Pcf8523TimerState RTC_PCF8523::read_timer(Pcf8523Timer timer) {
/* return the timer contents into TimerState */

const Pcf8523TimerDetails *details = &(timer_details_table[timer]);

// retrieve the timer enabled bit
uint8_t clkout_ctrl = read_PCF8523_register(details->timer_en_register);
const bool enabled = clkout_ctrl & details->timer_en_mask;

// retrieve the frequency scalar from the timer's register
const PCF8523TimerClockFreq freq = (PCF8523TimerClockFreq) read_PCF8523_register(details->timer_freq_register);

// retrieve the timer value from the timer's register
const uint8_t value = read_PCF8523_register(details->timer_value_register);

// read the irupt flag/enable bits -- delegate to other function
Pcf8523IruptState irupt;
read_irupt(timer, &irupt);

return Pcf8523TimerState(
value,
freq,
enabled,
irupt.irupt_flag,
irupt.irupt_enabled
);
}


/**************************************************************************/
/*!
@brief Start I2C for the DS3231 and test succesful connection
Expand Down
67 changes: 67 additions & 0 deletions RTClib.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class TimeSpan;
#define PCF8523_CONTROL_1 0x00 ///< Control and status register 1
#define PCF8523_CONTROL_2 0x01 ///< Control and status register 2
#define PCF8523_CONTROL_3 0x02 ///< Control and status register 3
#define PCF8523_TIMER_A_FRCTL 0x10 ///< Timer A frequency divider register
#define PCF8523_TIMER_A_VALUE 0x11 ///< Timer A count value register
#define PCF8523_TIMER_B_FRCTL 0x12 ///< Timer B source clock frequency control
#define PCF8523_TIMER_B_VALUE 0x13 ///< Timer B value (number clock periods)
#define PCF8523_OFFSET 0x0E ///< Offset register
Expand All @@ -55,6 +57,8 @@ class TimeSpan;
#define SECONDS_FROM_1970_TO_2000 \
946684800 ///< Unixtime for 2000-01-01 00:00:00, useful for initialization

#define PCF8523_CLKOUT_DIS ((0x7) << 3) ///< bit pattern for disabling the CLKOUT function

/**************************************************************************/
/*!
@brief Simple general-purpose date/time class (no TZ / DST / leap
Expand Down Expand Up @@ -350,6 +354,63 @@ enum Pcf8523OffsetMode {
PCF8523_OneMinute = 0x80 /**< Offset made every minute */
};

/** Timers and interrupts that can be set or read */
enum Pcf8523Timer {
PCF8523_Timer_Countdown_A, ///< Timer A, countdown mode
PCF8523_Timer_WDT_A, ///< Timer A, watchdog timer mode
PCF8523_Timer_Countdown_B ///< Timer B (countdown mode)
};

/** Current state of an interrupt */
typedef struct {
bool irupt_flag; ///< whether the timer has gone off
bool irupt_enabled; ///< whether the flag state is tied to the interrupt pin state
} Pcf8523IruptState;

/**************************************************************************/
/*!
@brief Current state of a timer

This class can be read from or written into the RTC. An update can be done
by reading, modifying, and then writing.

This class stores basic information in its public members about the timer
and its interrupt status: its countdown value and frequency, whether it is
running (enabled); its interrupt flag, and whether the interrupt is set to
drive associated pins.
*/
/**************************************************************************/
class Pcf8523TimerState {
public:
Pcf8523TimerState(uint8_t timer_value, PCF8523TimerClockFreq timer_freq, bool timer_enabled, bool irupt_flag, bool irupt_signal_enabled);
Pcf8523TimerState(const Pcf8523TimerState &copy);

bool enabled; ///< whether the timer is running
uint8_t value; ///< the current value of the timer
PCF8523TimerClockFreq freq; ///< the clock divider used
bool irupt_flag; ///< the interrupt flag
bool irupt_enabled; ///< the interrupt signal enable
};

/** registers and masks for interacting with a timer */
typedef struct {
uint8_t timer_en_register; ///< timer enable register
uint8_t timer_en_mask; ///< enable bit mask
uint8_t timer_dis_mask; ///< timer disable bit mask
uint8_t timer_value_register; ///< timer value register
uint8_t timer_freq_register; ///< timer frequency register
uint8_t irupt_control_register; ///< interrupt control register
uint8_t irupt_flag_mask; ///< flag bit mask
uint8_t irupt_en_mask; ///< interrupt enable bit mask
} Pcf8523TimerDetails;

/** look-up table for each timer, in enumerated order */
const Pcf8523TimerDetails timer_details_table[] = {
{ PCF8523_CLKOUTCONTROL, bit(1), (bit(1) | bit(2)), PCF8523_TIMER_A_VALUE, PCF8523_TIMER_A_FRCTL, PCF8523_CONTROL_2, bit(6), bit(1) }, /**< Timer A */
{ PCF8523_CLKOUTCONTROL, bit(2), (bit(1) | bit(2)), PCF8523_TIMER_A_VALUE, PCF8523_TIMER_A_FRCTL, PCF8523_CONTROL_2, bit(7), bit(2) }, /**< WDT A */
{ PCF8523_CLKOUTCONTROL, bit(0), bit(0), PCF8523_TIMER_B_VALUE, PCF8523_TIMER_B_FRCTL, PCF8523_CONTROL_2, bit(5), bit(0) } /**< Timer B */
};

/**************************************************************************/
/*!
@brief RTC based on the PCF8523 chip connected via I2C and the Wire library
Expand All @@ -372,6 +433,12 @@ class RTC_PCF8523 {
void disableCountdownTimer(void);
void deconfigureAllTimers(void);
void calibrate(Pcf8523OffsetMode mode, int8_t offset);

void write_timer(Pcf8523Timer timer, const Pcf8523TimerState &src);
Pcf8523TimerState read_timer(Pcf8523Timer timer);

void write_irupt(Pcf8523Timer irupt, Pcf8523IruptState *src);
void read_irupt(Pcf8523Timer irupt, Pcf8523IruptState *dest);
};

/**************************************************************************/
Expand Down
55 changes: 55 additions & 0 deletions examples/pcf8523_monitor_timer/pcf8523_monitor_timer.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Sets a timer for 10 seconds, and watches a pin (MONITOR_PIN) for
* its interrupt signal.
*
* The RTC's INT line is pulled down when the timer goes off and the
* interrupt is active.
*/
#include <Wire.h>
#include "RTClib.h"

#define MONITOR_PIN 5

RTC_PCF8523 rtc;

void setup () {
Serial.begin(9600);
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
while (1);
Serial.flush();
abort();

This is for consistency with the other examples and also, more importantly, in order to avoid undefined behavior.

}

pinMode(MONITOR_PIN, INPUT_PULLUP);

Pcf8523TimerState state = Pcf8523TimerState(
10, // timer value
PCF8523_FrequencySecond, // timer frequency
true, // timer set to run
false, // timer interrupt flag (when timer has gone off)
true // timer flag -> signal enable
);

rtc.write_timer(PCF8523_Timer_Countdown_B, state);
}

void loop () {
Pcf8523TimerState state = rtc.read_timer(PCF8523_Timer_Countdown_B);
Serial.print("timer value: ");
Serial.print(state.value, DEC);
Serial.print(", enabled: ");
Serial.print(state.enabled ? "yes": "no");
Serial.print(", freq: ");
Serial.print(state.freq, DEC);
Serial.println();
Serial.print("irupt flag: ");
Serial.print(state.irupt_flag, DEC);
Serial.print(", enabled: ");
Serial.print(state.irupt_enabled, DEC);
Serial.println();

Serial.print("Interrupt pin: ");
Serial.println(digitalRead(MONITOR_PIN) ? "HIGH": "LOW");

Serial.println();
delay(1000);
}
Loading