PCF8523 Interrupt Alarm Coding (Paid Gig) [SOLVED]

High Level Requirements
I need to be able to set an alarm and trigger a hardware interrupt via the alarm from a PCF8523 RTC PCF8523 RTC.

Details
It turns out there's already a forked version of the RTCLib that has alarm capability for the PCF8523; however, the hardware interrupt is not triggered. I've also included a test sketch that demonstrates the capability that I'm trying to achieve; however, I can't seem to get the sketch to trigger the interrupt.

Assumptions
I also still need to maintain the RTC set/get time functions.

Deliverables
A "fixed" version of the RTCLib for the PCF8523 coupled with the test sketch code that demonstrates a working version of the hardware interrupt for the PCF8523 and date/time functionality.

Any questions let me know.

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

RTC_PCF8523 rtc;
Pcf8523TimerState state;
volatile uint32_t counter = 0;  // this counter should only be incremented one time

void onAlarm() {
  noInterrupts();
  digitalWrite(LED_BUILTIN, HIGH);
  ++counter;
  interrupts();
}

void setup () {
  Serial.begin(115200);
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  if (! rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(MONITOR_PIN, INPUT_PULLUP);
  attachInterrupt(MONITOR_PIN, onAlarm, CHANGE);

  /*
    struct type signatures:

    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;

    typedef struct {
    bool enabled;                  // whether the timer is running
    uint8_t value;                 // the current value of the timer
    Pcf8523FrequencyDivision freq; // the clock divider used
    Pcf8523IruptState irupt_state; // the timer's interrupt state
    } Pcf8523TimerState;
  */

  // should trigger an interrupt in 10s
  state.enabled = true;
  state.value = 10;
  state.freq = PCF8523_Freq_second;
  state.irupt_state.irupt_flag = false;
  state.irupt_state.irupt_enabled = true;

  //rtc.writeSqwPinMode(PCF8523_OFF);
  rtc.write_timer(PCF8523_Timer_Countdown_A, &state);


}

void loop () {
  rtc.read_timer(PCF8523_Timer_Countdown_A, &state);
  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_state.irupt_flag, DEC);
  Serial.print(", enabled: ");
  Serial.print(state.irupt_state.irupt_enabled, DEC);
  Serial.println();

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

  Serial.print("Interrupt ctr: " );
  Serial.println(counter);

  Pcf8523SqwPinMode sqw = rtc.readSqwPinMode();
  Serial.print("readSqwPinMode: " );
  Serial.println(sqw);

  Serial.println();

  DateTime now = rtc.now();

  Serial.print(now.year(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.day(), DEC);
  Serial.print(' ');
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.println();

  // reset the alarm
  if (counter > 0) {
    Serial.println("Alarm has been triggered. Clearing the alarm");
    state.enabled = false;
    state.value = 0;
    state.freq = PCF8523_Freq_second;
    state.irupt_state.irupt_flag = false;
    state.irupt_state.irupt_enabled = false;

    //rtc.writeSqwPinMode(PCF8523_OFF);
    rtc.write_timer(PCF8523_Timer_Countdown_A, &state);
    counter = 0;
    
    // once this happens, the output of the register state should be interrupt disabled and never triggered
  }

  delay(1000);
}

czu001:
High Level Requirements
I need to be able to wake up an Arduino via external interrupt using the alarm capability of the PCF8523 RTC. The board I am using is the Adafruit nRF52840 Express; however, all of the GPIO are interrupt capable so the Arduino code should be the same.

Do you realise that this board is not an Arduino board?
That it does not even have an Atmel processor on board?
And that whatever you read about Arduino/ATmega328 is not applicable at all to this board?

Honestly, I don't even know whether this ARM Cortex M4F processor has sleep abilities. It's a completely different animal than what we normally work with here.

The nRF52840 is designed for battery use and it does have fairly good sleep capability. It also has an internal RTC, so I'm not sure why you think you need an external chip to do this. Is there some functionality you need that it doesn't support?

Do you realise that this board is not an Arduino board?

Yes. I should've just left out the part about waking up/sleeping the mCU since all I'm really after is controlling the alarm capability of the RTC.

And that whatever you read about Arduino/ATmega328 is not applicable at all to this board?

True but Arduino is considered a platform not just a family of chips. The concepts are the same; however, the implementation may be different.

It also has an internal RTC

It does, but it's not available in the sd_power_system_off() mode. An external RTC is required for that mode.

I revised the description. It turns out I could not get the library to trigger the hardware interrupt. I also included a test sketch that should work once the RTCLib functionality is "fixed" for the alarm interrupts. Any takers please let me know.

czu001:
I revised the description.

Glad you mention it or no-one would have noticed. Still it's considered bad practice; just add the new part to a reply.

czu001:
It turns out there's already a forked version of the RTCLib that has alarm capability for the PCF8523; however, the hardware interrupt is not triggered

Those alarm pins are typically open drain; I notice you do enable the internal pull-up, that's good.
Are you sure the internal pull-up is still on when the MCU is asleep? If not, add an external pull-up.
Are you sure the alarm goes off? Measure this with your multimeter. You should see the change here.
If the above are confirmed, your alarm part is working fine and the problem is in the programming.

Are you sure the internal pull-up is still on when the MCU is asleep?

I also added a test sketch to the description. I don't put the MCU to sleep (yet). The sketch simply sets the alarm for 10s, counts down to 0, and then a hardware interrupt should be triggered (it sets a flag).

The register being read by the RTCLib library is returning TRUE for the alarm register being set; however, the hardware interrupt is not actually firing nor does the INT pin actually change. The pin is supposed to go from LOW to HIGH, but it's staying LOW.

the problem is in the programming

I think that's the issue. I looked at the RTCLib and I think it's something to do with not setting the Control Register_1 properly, but all of the bitwise stuff confuses me.

I also tried on a Atmega32u4 chip and I got the same result, so it's definitely something in the library.

I see lots of threads like these for leveraging the PCF8523 for alarms. If I can get someone to actually get this working, I'd be glad to publish it. I think the github fork is very close.

czu001:
The pin is supposed to go from LOW to HIGH, but it's staying LOW.

Are you sure about that? The active state of an open drain output is LOW. Indeed that is also what is mentioned in ch. 8.3 of the datasheet. If the pin is low already, you probably have to clear some registers for it to become inactive.

In ch. 8.4 it also mentions an AIE bit, you have to set this for alarm interrupts to actually affect the INT1 pin.

In ch. 8.4 it also mentions an AIE bit, you have to set this for alarm interrupts to actually affect the INT1 pin.

I think that's the part that's missing from the library (the control register 1 stuff I was referring to). But I'm struggling to understand how it's being set in the code (I'm honestly not used to bitwise operations, and this code has a lot of it).

I think in particular it's in these lines

I am playing around with the PCF8523 and I used another library. However there was no alarm interrupt configuration function either so I added the following function:

void PCF8523::config(){
    write_reg(PCF8523_CONTROL_1, 0x82);
}

that configures control 1 register at 10000010 which suits my application.

Hope that helps

I wrote the RTClib fork being referenced; I have been working on finalizing it for merging with Adafruit.

There is nothing wrong with its timer A interrupt signal; note also that timer and alarm have different meanings on this RTC, as there is, in fact, an alarm.

There are a few issues I noted in the example code posted:

--the first is that the monitor example included with the fork was significantly simpler, and didn't use attachInterrupt. Getting a minimal example working first is good practice. This is important, because the "MONITOR_PIN" is A1; an analog pin may not be supported for a pin change interrupt. It is definitely not supported on the leonardo.

--"attachInterrupt" should use the recommended syntax: attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) (recommended)

--the ISR function passed in, onAlarm(), calls noInterrupts/interrupts() on entry and exit. These functions are meant for the main context, and are called to prevent preemption by an interrupt (like onAlarm).

Updating with the resolution would likely be helpful to others who encounter similar issues.