© Warren Gay 2018

Warren Gay, Beginning STM32, https://doi.org/10.1007/978-1-4842-3624-6_10

10. Real-Time Clock (RTC)

Warren Gay

(1)St. Catharines, Ontario, Canada

Tracking time is often important in applications. For this reason, the STM32 platform provides a built-in real-time clock (RTC) peripheral. The datasheets for the RTC appear almost trivial to use, but there are wrinkles waiting for the unwary.

This chapter will examine how to set up interrupt service routines (ISR) for a recurring one-second interrupt event as well as the optional alarm feature. Armed with this information, there is no reason for your MCU applications to lack time information.

Demonstration Projects

The demonstration programs for this chapter come from the following two project directories:

$ cd ~/stm32f103c8t6/rtos/rtc
$ cd ~/stm32f103c8t6/rtos/rtc2

Initially the focus will be on the first project, where one ISR is implemented. The second example will apply the second alarm ISR, which will be examined near the end of the chapter.

RTC Using One Interrupt

The STM32F1 platform provides up to two interrupts for the RTC. The entry-point names when using libopencm3 are as follows:

#include <libopencm3/cm3/nvic.h>  

void rtc_isr(void);
void rtc_alarm_isr(void);

Our first demonstration will use only the rtc_isr() routine because it is the simplest to set up and use. The serviced interruptible events are as follows:

  1. One-second event (one-second timer tick)

  2. Timer alarm event

  3. Timer overflow event

The RTC can define one alarm such that an interrupt will be generated when the alarm expires at some time in the future. This can be used as a coarse-grained watchdog.

Even though the RTC counter is 32 bits in size, given enough time the counter will eventually overflow. Sometimes special time accounting must be performed at this point. The pending alarm may also need adjustment.

In our first demo, all of these optional events will be funneled through the rtc_isr() routine. This is the easiest approach.

RTC Configuration

The next sections will describe the configuration of the RTC clock using the libopencm3 library functions.

RTC Clock Source

The STM32F103C8T6 RTC can use one of three possible clock sources, as follows:

  1. The LSE clock (32.768 kHz crystal oscillator), which continues to work even when the supply voltage is off, provided that the battery voltage V BAT supply is maintained. The RTCCLK rate provided is 32.768 kHz. Setup choice is RCC_LSE.

  2. The LSI clock (~40 kHz RC oscillator), but only while power is maintained. The RTCCLK rate provided is approximately 40 kHz. Setup choice is RCC_LSI.

  3. The HSE clock (8 MHz crystal oscillator), but only while power is maintained. The RTCCLK rate provided is 
$$ frac{8kern0.125em MHz}{128}=62.5 kHz $$
. Setup choice is RCC_HSE.

The clock source used by this chapter’s project is the HSE clock, which is selected by the following libopencm3 function call:

    rtc_awake_from_off(RCC_HSE);

Prescaler

Since we have chosen the RCC_HSE clock as the source for the RTC peripheral and are using the 8 MHz/128 = 62500 as the RTCCLK rate, we can now define the clock rate from the following formula:

$$ f=frac{62500}{divisor}kern0.125em Hz $$

We’ll use the divisor of 62,500 in this chapter so that the frequency is 1 Hz. This provides a one-second tick time. You could, however, choose to use a divisor like 6,250 to produce a tick at tenth-second intervals, if required. Using libopencm3, we set the divisor as follows:

    rtc_set_prescale_val(62500);

Starting Counter Value

Normally, the RTC counter would be started at zero, but there is no law that says you must. In the demo program, we’re going to initialize it about 16 seconds before the counter overflows so that we can demonstrate the timer overflow interrupt.

The counter can be initialized with the following call:

    rtc_set_counter_val(0xFFFFFFF0);

The RTC counter is 32 bits in size, so it overflows after counting to 0xFFFFFFFF. The preceding code initializes the counter to 16 counts prior to overflow.

RTC Flags

The RTC control register (RTC_CRL) contains three flags that we are interested in (using libopencm3 macro names):

  1. RTC_SEC (tick)

  2. RTC_ALR (alarm)

  3. RTC_OW (overflow)

These flags can be tested and cleared, respectively, using the following libopencm3 calls:

    rtc_check_flag(flag);
    rtc_clear_flag(flag);

Interrupt and Setup

Listing 10-1 illustrates the steps necessary to initialize the RTC for one-second tick events and to receive interrupts.

Listing 10-1 The RTC Peripheral Setup for Interrupts
0166: static void
0167: rtc_setup(void) {
0168:
0169:   rcc_enable_rtc_clock();
0170:   rtc_interrupt_disable(RTC_SEC);
0171:   rtc_interrupt_disable(RTC_ALR);
0172:   rtc_interrupt_disable(RTC_OW);
0173:
0174:   // RCC_HSE, RCC_LSE, RCC_LSI
0175:   rtc_awake_from_off(RCC_HSE);
0176:   rtc_set_prescale_val(62500);
0177:   rtc_set_counter_val(0xFFFFFFF0);
0178:
0179:   nvic_enable_irq(NVIC_RTC_IRQ);
0180:
0181:   cm_disable_interrupts();
0182:   rtc_clear_flag(RTC_SEC);
0183:   rtc_clear_flag(RTC_ALR);
0184:   rtc_clear_flag(RTC_OW);
0185:   rtc_interrupt_enable(RTC_SEC);
0186:   rtc_interrupt_enable(RTC_ALR);
0187:   rtc_interrupt_enable(RTC_OW);
0188:   cm_enable_interrupts();
0189: }

Since the RTC peripheral is disabled after reset, it is enabled in line 169 so that it can be initialized. Lines 170 to 172 disable interrupts temporarily to prevent them while the peripheral is being set up.

Line 175 chooses the RTC clock source, and the clock rate is configured in line 176 to be once per second. Line 177 initializes the RTC counter to 16 seconds before overflow to demonstrate an overflow event without waiting a very long time.

Line 179 enables the interrupt controller for the rtc_isr() interrupt handler. All interrupts are temporarily suppressed in line 181 to allow the final interrupt setup to occur without generating interrupts. Lines 182 to 184 make sure that the RTC flags are cleared. Lines 185 to 187 enable the generation of interrupts when those flags are set. Last of all, interrupts are generally enabled once again at line 188.

At this point, the RTC peripheral is ready to generate interrupts.

Interrupt Service Routine

Listing 10-2 illustrates the code used to service the RTC interrupts. Don’t let the length of the routine worry you, since there isn’t really much going on there.

Listing 10-2 The RTC Interrupt Service Routine
0057: void
0058: rtc_isr(void) {
0059:   UBaseType_t intstatus;
0060:   BaseType_t woken = pdFALSE;
0061:
0062:   ++rtc_isr_count;
0063:   if ( rtc_check_flag(RTC_OW) ) {
0064:       // Timer overflowed:
0065:       ++rtc_overflow_count;
0066:       rtc_clear_flag(RTC_OW);
0067:       if ( !alarm ) // If no alarm pending, clear ALRF
0068:           rtc_clear_flag(RTC_ALR);
0069:   }
0070:
0071:   if ( rtc_check_flag(RTC_SEC) ) {
0072:       // RTC tick interrupt:
0073:       rtc_clear_flag(RTC_SEC);
0074:
0075:       // Increment time:
0076:       intstatus = taskENTER_CRITICAL_FROM_ISR();
0077:       if ( ++seconds >= 60 ) {
0078:           ++minutes;
0079:           seconds -= 60;
0080:       }
0081:       if ( minutes >= 60 ) {
0082:           ++hours;
0083:           minutes -= 60;
0084:       }
0085:       if ( hours >= 24 ) {
0086:           ++days;
0087:           hours -= 24;
0088:       }
0089:       taskEXIT_CRITICAL_FROM_ISR(intstatus);
0090:
0091:       // Wake task2 if we can:
0092:       vTaskNotifyGiveFromISR(h_task2,&woken);
0093:       portYIELD_FROM_ISR(woken);
0094:       return;
0095:   }
0096:
0097:   if ( rtc_check_flag(RTC_ALR) ) {
0098:       // Alarm interrupt:
0099:       ++rtc_alarm_count;
0100:       rtc_clear_flag(RTC_ALR);
0101:
0102:       // Wake task3 if we can:
0103:       vTaskNotifyGiveFromISR(h_task3,&woken);
0104:       portYIELD_FROM_ISR(woken);
0105:       return;
0106:   }
0107: }

An ISR counter named rtc_isr_count is incremented in line 62. This is only used to print the fact that the ISR was called in our demo code.

Lines 63 to 69 check to see if the counter overflowed, and if so clears the flag (RTC_OW). If there is no alarm pending, it also clears the alarm flag. It appears that the alarm flag is always set when an overflow happens, whether there was an alarm pending or not. As far as I can tell, this is one of those undocumented features.

Normally, the rtc_isr() routine is entered because of a timer tick. Lines 71 to 95 service the one-second tick. After clearing the interrupt flag (RTC_SEC), a critical section is begun in line 76. This is the FreeRTOS way to disable interrupts in an ISR until you finish with the critical section (line 89 ends the critical section). Here, it is critical that the time ripples up from seconds to minutes, hours, and days without being interrupted in the middle. Otherwise, a higher-priority interrupt could occur and discover that the current time is 12:05:60 or 13:60:45.

Line 92 is a notify check for task 2 (to be examined shortly). The notification (if woken is true) occurs in line 93. The idea is that task 2 will be blocked from executing until this notification arrives. Stay tuned for more about that.

Finally, lines 97 to 106 service the RTC alarm if the alarm flag is set. Aside from incrementing rtc_alarm_count for the demo print, it clears the alarm flag RTC_ALR in line 100. Lines 103 and 104 are designed to notify task 3, which will also be examined shortly.

Servicing Interrupts

The alert reader has probably noticed that the ISR routine didn’t always service all three interrupt sources in one call. What happens if RTC_SEC, RTC_OW, and RTC_ALR are all set when the rtc_isr() routine is called but only RTC_SEC is cleared?

Any one of those flags may cause the interrupt to be raised. Since we enabled all three interrupt sources, the rtc_isr() routine will continue to be called until all flags are cleared.

Task Notification

FreeRTOS supports an efficient mechanism for allowing a task to block its own execution until another task or interrupt notifies it. Listing 10-3 illustrates the code used for task 2.

Listing 10-3 Task 2, Using Task Notify
0144: static void
0145: task2(void *args __attribute__((unused))) {
0146:
0147:   for (;;) {
0148:       // Block execution until notified
0149:       ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
0150:
0151:       // Toggle LED
0152:       gpio_toggle(GPIOC,GPIO13);
0153:
0154:       mutex_lock();
0155:       std_printf("Time: %3u days %02u:%02u:%02u isr_count: %u,"
                           " alarms: %u, overflows: %u ",
0156:           days,hours,minutes,seconds,
0157:           rtc_isr_count,rtc_alarm_count,rtc_overflow_count);
0158:       mutex_unlock();
0159:   }
0160: }

Like many tasks, it begins with an infinite loop in line 147. The first thing performed in that loop, however, is a call to ulTaskNotifyTake(), with a “wait forever” timeout (argument 2). Task 2 will grind to a halt there until it is notified. The only place it is notified is from the rtc_isr() routine in lines 92 and 93. Once the interrupt occurs, the function call in line 149 returns control and execution continues. This allows the print call in lines 155 to 157 to report the time.

When the demo runs, you will see that this notification occurs once per second as the rtc_isr() routine is called. This is a very convenient ISR to non-ISR routine synchronization. If you noticed the mutex_lock()/unlock calls, then just keep those in the back of your head for now.

Task 3 uses a similar mechanism for alarms, illustrated in Listing 10-4.

Listing 10-4 Task 3 and Its Alarm Notify
0126: static void
0127: task3(void *args __attribute__((unused))) {
0128:
0129:   for (;;) {
0130:       // Block execution until notified
0131:       ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
0132:
0133:       mutex_lock();
0134:       std_printf("*** ALARM *** at %3u days %02u:%02u:%02u ",
0135:           days,hours,minutes,seconds);
0136:       mutex_unlock();
0137:   }
0138: }

Line 131 blocks task 3’s execution until it is notified by the rtc_isr() routine in lines 103 and 104 (Listing 10-2). In this manner, task 3 remains blocked until an alarm is sensed by the ISR.

Mutexes

Sometimes, mutexes (mutual-exclusion devices) are required to lock multiple tasks from competing for a shared resource. In this demo, we need to be able to format a complete line of text before allowing another task to do the same. The use of routines mutex_lock() and mutex_unlock() prevents competing tasks from printing in the middle of our own line of text. These routines use the FreeRTOS API and are shown in Listing 10-5.

Listing 10-5 Mutex Functions
0039: static void
0040: mutex_lock(void) {
0041:   xSemaphoreTake(h_mutex,portMAX_DELAY);
0042: }


0048: static void
0049: mutex_unlock(void) {
0050:   xSemaphoreGive(h_mutex);
0051: }

The handle to the mutex is created in the main program with the following call:

0259:   h_mutex = xSemaphoreCreateMutex();

If you’re new to mutexes , the following happens when you lock (take) a mutex:

  1. If the mutex is already locked, the calling task blocks (waits) until it becomes free.

  2. When the mutex is free, an attempt will be made to lock it. If another competing task grabbed the lock first, return to step 1 to block until the mutex becomes free again.

  3. Otherwise, the lock is now owned by the caller, until the mutex is unlocked.

Once the task using the mutex is done with it, it can release the mutex. The act of unlocking the mutex may allow another blocked task to continue. Otherwise, if no other task is waiting on the mutex, the mutex is simply unlocked.

Demonstration

The main demonstration program presented in this chapter can be run using a USB-based TTL UART or directly over a USB cable. The UART choice is perhaps the best since the USB link sometimes introduces delays that mask the timing of the print statements. However, both work equally well once things get going, and the USB cable is usually more convenient.

The following statement defines which approach you want to use (in file named main.c):

0020: #define USE_USB    0    // Set to 1 for USB

It defaults to UART use. If you set it to 1, the USB device will be used instead. The setup for UART or USB occurs in the main() function of main.c (Listing 10-6).

Listing 10-6 Main Program for Initializing UART or USB
0251: int
0252: main(void) {
0253:
0254:   rcc_clock_setup_in_hse_8mhz_out_72mhz(); // Use this for "blue pill"
0255:
0256:   rcc_periph_clock_enable(RCC_GPIOC);
0257:   gpio_set_mode(GPIOC,GPIO_MODE_OUTPUT_50_MHZ,
            GPIO_CNF_OUTPUT_PUSHPULL,GPIO13);
0258:
0259:   h_mutex = xSemaphoreCreateMutex();
0260:   xTaskCreate(task1,"task1",350,NULL,1,NULL);
0261:   xTaskCreate(task2,"task2",400,NULL,3,&h_task2);
0262:   xTaskCreate(task3,"task3",400,NULL,3,&h_task3);
0263:
0264:   gpio_clear(GPIOC,GPIO13);
0265:
0266: #if USE_USB
0267:   usb_start(1,1);
0268:   std_set_device(mcu_usb); // Use USB for std I/O
0269: #else
0270:   rcc_periph_clock_enable(RCC_GPIOA); // TX=A9,RX=A10,CTS=A11,RTS=A12
0271:   rcc_periph_clock_enable(RCC_USART1);
0272:   
0273:   gpio_set_mode(GPIOA,GPIO_MODE_OUTPUT_50_MHZ,
0274:       GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,GPIO9|GPIO11);
0275:   gpio_set_mode(GPIOA,GPIO_MODE_INPUT,
0276:       GPIO_CNF_INPUT_FLOAT,GPIO10|GPIO12);
0277:   open_uart(1,115200,"8N1","rw",1,1);  // RTS/CTS flow control
0278:   // open_uart(1,9600,"8N1","rw",0,0); // UART1 9600 with no f.control
0279:   std_set_device(mcu_uart1);        // Use UART1 for std I/O
0280: #endif
0281:
0282:   vTaskStartScheduler();
0283:   for (;;);
0284:
0285:   return 0;
0286: }

The device control is initialized in lines 267 and 268 for USB and lines 270 to 279 for UART. For the UART, the baud rate 115,200 is used by default in line 277. This works well if you use hardware flow control. If for some reason your TTL serial device doesn’t support hardware flow control, then comment out line 277 and uncomment line 278 instead. At the lower baud rate of 9,600 you should be able to operate safely without flow control.

Lines 268 or 279 determine whether std_printf() et al. are directed to the USB device or to the UART device.

As we noted earlier, we used a mutex to guarantee that complete text lines would be printed to the console (USB or UART). This FreeRTOS mutex is created in line 259.

Task 1 (line 260) will be our main “console,” allowing us to type characters to affect the operation of the demo. Task 2 (line 261) will toggle the LED (PC13) for each RTC tick, as well as print the current time since startup (see earlier Listing 10-3). Finally, task 3 will print an alarm notice when the alarm has been triggered (see earlier Listing 10-4).

Listing 10-7 illustrates the console task (task 1).

Listing 10-7 The “Console Task,” Task 1
0220: static void
0221: task1(void *args __attribute__((unused))) {
0222:   char ch;
0223:
0224:   wait_terminal();
0225:   std_printf("Started! ");
0226:
0227:   rtc_setup();    // Start RTC interrupts
0228:   taskYIELD();
0229:
0230:   for (;;) {
0231:       mutex_lock();
0232:       std_printf(" Press 'A' to set 10 second alarm, "
0233:           "else any key to read time. ");
0234:       mutex_unlock();
0235:   
0236:       ch = std_getc();
0237:
0238:       if ( ch == 'a' || ch == 'A' ) {
0239:           mutex_lock();
0240:           std_printf(" Alarm configured for 10 seconds"
                                   " from now. ");
0241:           mutex_unlock();
0242:           set_alarm(10u);
0243:       }
0244:   }
0245: }

Task 1 is there to allow synchronization with the user who is connecting with the USB or UART link, using minicom. Line 224 calls wait_terminal(), which prompts the user to press any key, after which the function returns. Then, the RTC clock is initialized at line 227 and an initial taskYIELD() call is made. This helps to get everything prior to entering the task 1 loop.

The main loop of task 1 simply issues a printed message to press “A” to set a ten-second alarm. Any other key will just cause the console loop to repeat. Because the RTC timer is interrupt driven, the rtc_isr() method is called each second, or when overflow or alarm occurs. This in turn notifies task 2 or task 3 as previously discussed.

UART1 Connections

When you use the TTL UART, the connections are made according to Table 10-1. In this case, we can power the STM32 from the TTL UART device. If you are not powering the STM32 from the TTL UART, then omit the +5V connection.

Table 10-1 UART Wiring to STM32F103C8T6

GPIO

UART1

TTL UART

Description

A9 (out)

TX

RX

STM32 sends to TTL UART

A10 (in)

RX

TX

STM32 receives from TTL UART

A11 (out)

CTS

RTS

STM32 clear to send

A12 (in)

RTS

CTS

STM32 request to send

+5V

 

+5V

Powers STM32 from USB TTL UART (otherwise, omit this connection when STM32 is powered by another source).

Gnd

 

Gnd

Ground

Note

UART1 was chosen because it uses 5-volt-tolerant GPIOs.

From a wiring and terminal-emulator standpoint, the USB cable is a much simpler option.

Running the Demo

Depending upon how you configured the main.c program to run, you will be using the USB cable or the TTL UART. USB is the simplest—just plug in and go. If you’re using the UART, then you need to configure your terminal program (minicom) to match the communication parameters: baud rate, 8 data bits, no parity, and one stop bit.

Upon starting your terminal program (I’m using minicom), you should see a prompt to press any key:

Welcome to minicom 2.7

OPTIONS:
Compiled on Sep 17 2016, 05:53:15.
Port /dev/cu.usbserial-A703CYQ5, 19:13:38


Press Meta-Z for help on special keys

Press any key to start...
Press any key to start...
Press any key to start...

Press any key to get things rolling (I used the Return key):

Press any key to start...
Started!


Press 'A' to set 10 second alarm,
else any key to read time.


Time:   0 days 00:00:01 isr_count: 1, alarms: 0, overflows: 0
Time:   0 days 00:00:02 isr_count: 2, alarms: 0, overflows: 0
Time:   0 days 00:00:03 isr_count: 3, alarms: 0, overflows: 0
...
Time:   0 days 00:00:20 isr_count: 20, alarms: 0, overflows: 0
Time:   0 days 00:00:21 isr_count: 21, alarms: 0, overflows: 0
Time:   0 days 00:00:22 isr_count: 22, alarms: 0, overflows: 0
Time:   0 days 00:00:23 isr_count: 23, alarms: 0, overflows: 0
Time:   0 days 00:00:24 isr_count: 24, alarms: 0, overflows: 1
Time:   0 days 00:00:25 isr_count: 25, alarms: 0, overflows: 1
Time:   0 days 00:00:26 isr_count: 26, alarms: 0, overflows: 1
Time:   0 days 00:00:27 isr_count: 27, alarms: 0, overflows: 1

Once started, the rtc_isr() is enabled and the evidence of one-second interrupts is realized in the printed messages. The count value isr_count indicates how often the interrupt has invoked the ISR routine rtc_isr(). Notice that the overflows count increases when the isr_count is 24. This indicates that the RTC counter has overflowed and is now restarting from zero.

The elapsed time in days, hours, minutes, and seconds is shown in each message. These values were calculated in the critical section within the rtc_isr() routine.

To create an alarm, press “A.” This will start an alarm that will expire in ten seconds. The following session begins the alarm near time 00:07:40, and the alarm message appears at 00:07:50 as expected:

Time:   0 days 00:07:38 isr_count: 458, alarms: 0, overflows: 1
Time:   0 days 00:07:39 isr_count: 459, alarms: 0, overflows: 1


Alarm configured for 10 seconds from now.

Press 'A' to set 10 second alarm,
else any key to read time.


Time:   0 days 00:07:40 isr_count: 460, alarms: 0, overflows: 1
Time:   0 days 00:07:41 isr_count: 461, alarms: 0, overflows: 1
...
Time:   0 days 00:07:48 isr_count: 468, alarms: 0, overflows: 1
Time:   0 days 00:07:49 isr_count: 469, alarms: 0, overflows: 1
*** ALARM *** at   0 days 00:07:50
Time:   0 days 00:07:50 isr_count: 471, alarms: 1, overflows: 1
Time:   0 days 00:07:51 isr_count: 472, alarms: 1, overflows: 1

Listing 10-8 shows the snippet of code that starts the ten-second alarm. It uses the libopencm3 routine set_alarm() in line 242. The function call sets up a register to trigger an alarm ten seconds from the present time.

Listing 10-8 The Alarm-Triggering Code
0236:       ch = std_getc();
0237:
0238:       if ( ch == 'a' || ch == 'A' ) {
0239:           mutex_lock();
0240:           std_printf(" Alarm configured for 10 seconds"
                                   " from now. ");
0241:           mutex_unlock();
0242:           set_alarm(10u);
0243:       }

rtc_alarm_isr()

If you’ve read any of the STM32F103C8T8 datasheet information, you’re probably aware that there is a second possible ISR entry point. The datasheet is rather cryptic about this, unless you know what they mean by the “RTC global interrupt” and “EXTI Line 17” references. The entry point rtc_isr() routine is the “RTC global interrupt,” while “EXTI Line 17” means something else.

The EXTI controller refers to the external interrupt/event controller. The purpose of this controller is to allow GPIO input lines to trigger an interrupt on a signal rise or fall event. So, I think that you’d be excused if you asked “What’s external about RTC?” The demo code implementing the rtc_alarm_isr() interrupt is found in the following directory:

$ cd ~/stm32f103c8t6/rtos/rtc2

EXTI Controller

As previously mentioned, the EXTI controller allows GPIO input lines to trigger an interrupt if their input level rises or falls, depending upon the configuration (or both rise and fall). All GPIO 0’s map to interrupt EXT0. For the STM32F103C8T6, this means GPIO ports PA0, PB0, and PC0. Other STM32 devices with additional GPIO ports might also have PD0, PE0, etc. Likewise, the EXT15 interrupt is raised by GPIO ports PA15, PB15, PC15, etc.

This defines EXT0 through EXT15 as interrupt sources. But in addition to these, there are up to four more:

  • EXT16 – PVD output (programmable voltage detector)

  • EXT17 – RTC alarm event

  • EXT18 – USB wakeup event

  • EXT19 – Ethernet wakeup event (not on F103C8T6)

These are internal events that can also create interrupts. Of immediate interest is the RTC alarm event (EXT17).

Configuring EXT17

To get interrupts on rtc_alarm_isr(), we must configure event EXTI17 to raise an interrupt. This requires the following libopencm3 steps:

  1. #include <libopencm3/stm32/exti.h>

  2. exti_set_trigger(EXTI17,EXTI_TRIGGER_RISING);

  3. exti_enable_request(EXTI17);

  4. nvic_enable_irq(NVIC_RTC_ALARM_IRQ);

Step one is the additional libopencm3 include file required. Step two indicates what event we want as a trigger, and this configures the rising edge of the alarm event. Step three enables the EXTI17 interrupt in the peripheral. Finally, step four enables the interrupt controller to process the RTC_ALARM_IRQ event.

Because the alarm handling has been separated out from the rtc_isr() handler, the new rtc_alarm_isr() looks rather simple in Listing 10-9.

Listing 10-9 The rtc_alarm_isr() Routine
0098: void
0099: rtc_alarm_isr(void) {
0100:   BaseType_t woken = pdFALSE;
0101:   
0102:   ++rtc_alarm_count;
0103:   exti_reset_request(EXTI17);
0104:   rtc_clear_flag(RTC_ALR);
0105:
0106:   vTaskNotifyGiveFromISR(h_task3,&woken);
0107:   portYIELD_FROM_ISR(woken);
0108: }

The handler is almost identical to the alarm-event handling presented earlier, but there is one more step that must be observed, as follows:

0103:   exti_reset_request(EXTI17);

This libopencm3 call is necessary to reset the EXTI17 interrupt in addition to usual clearing the flag RTC_ALR in line 104.

This additional setup for EXTI17 is not particularly burdensome but can be tricky to get working from the datasheets. Now that you’ve seen the secret sauce, this should be a no brainer.

Run the RTC2 demo the same way as RTC. The only difference between the two is the interrupt handling.

Summary

This chapter has explored the configuration and use of the real-time clock. From this presentation, it is clear that the RTC is not a complicated peripheral within the STM32. The utility of having a solid and accurate time should not be underappreciated, however.

Despite the simplicity, there are areas that require careful consideration, like correct handling of timer overflows. This becomes even more critical as higher-resolution units are used, like $$ frac{1}{100} th $$ second. When the alarm feature is used, the RTC counter overflow event may also require special handling after an overflow.

Knowing how to set up EXTI17 also permits you to set up GPIO signal-change interrupts. The procedure is the same, except that you specify EXTIn for GPIOn.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.111.183