diff --git a/boards/nucleo-f091/Makefile.features b/boards/nucleo-f091/Makefile.features index b5463f990f9831c1238a2c189315253cd1029fcf..6642803532a6ba56dd4b44d1c2cdfb8d3c56b7ce 100644 --- a/boards/nucleo-f091/Makefile.features +++ b/boards/nucleo-f091/Makefile.features @@ -1,5 +1,6 @@ FEATURES_PROVIDED += cpp FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio +FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_uart FEATURES_MCU_GROUP = cortex_m0 diff --git a/boards/nucleo-f091/include/periph_conf.h b/boards/nucleo-f091/include/periph_conf.h index e9d9d02777245b65b605fafa021fff42f82df06d..578931409c296bf287e4ba0ae4190fc6eeb2d427 100644 --- a/boards/nucleo-f091/include/periph_conf.h +++ b/boards/nucleo-f091/include/periph_conf.h @@ -156,6 +156,18 @@ extern "C" { #define GPIO_5_IRQ EXTI4_15_IRQn /** @} */ +/** + * @name RTC configuration + * @{ + */ +/** + * Nucleos with MB1136 C-02 or MB1136 C-03 -sticker on it have the required LSE + * oscillator provided on the X2 slot. + * See Nucleo User Manual UM1724 section 5.6.2. + */ +#define RTC_NUMOF (1U) +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/stm32f0/periph/rtc.c b/cpu/stm32f0/periph/rtc.c new file mode 100644 index 0000000000000000000000000000000000000000..232782204504dd2bb351e762e83d7a99b1d7fd64 --- /dev/null +++ b/cpu/stm32f0/periph/rtc.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2015 Lari Lehtomäki + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_stm32f0 + * @{ + * @file + * @brief Low-level RTC driver implementation + * @author Lari Lehtomäki <lari@lehtomaki.fi> + * @} + */ + + +#include <time.h> +#include "cpu.h" +#include "periph/rtc.h" +#include "periph_conf.h" +#include "sched.h" +#include "thread.h" + +/* guard file in case no RTC device was specified */ +#if RTC_NUMOF + +#define RTC_WRITE_PROTECTION_KEY1 (0xCA) +#define RTC_WRITE_PROTECTION_KEY2 (0x53) +#define RTC_SYNC_PRESCALER (0xff) /**< prescaler for 32.768 kHz oscillator */ +#define RTC_ASYNC_PRESCALER (0x7f) /**< prescaler for 32.768 kHz oscillator */ + +#define MCU_YEAR_OFFSET (100) /**< struct tm counts years since 1900 + but RTC has only two-digit year + hence the offset of 100 years. */ + +typedef struct { + rtc_alarm_cb_t cb; /**< callback called from RTC interrupt */ + void *arg; /**< argument passed to the callback */ +} rtc_state_t; + +static rtc_state_t rtc_callback; + +static uint8_t byte2bcd(uint8_t value); + +/** + * @brief Initializes the RTC to use LSE (external 32.768 kHz oscillator) as a + * clocl source. Verify that your board has this oscillator. If other clock + * source is used, then also the prescaler constants should be changed. + */ +void rtc_init(void) +{ + + /* Enable write access to RTC registers */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + PWR->CR |= PWR_CR_DBP; + + /* Reset RTC domain */ + RCC->BDCR |= RCC_BDCR_BDRST; + RCC->BDCR &= ~(RCC_BDCR_BDRST); + + /* Enable the LSE clock (external 32.768 kHz oscillator) */ + RCC->BDCR &= ~(RCC_BDCR_LSEON); + RCC->BDCR &= ~(RCC_BDCR_LSEBYP); + RCC->BDCR |= RCC_BDCR_LSEON; + while ( (RCC->BDCR & RCC_BDCR_LSERDY) == 0 ); + + /* Switch RTC to LSE clock source */ + RCC->BDCR &= ~(RCC_BDCR_RTCSEL); + RCC->BDCR |= RCC_BDCR_RTCSEL_LSE; + + /* Enable the RTC */ + RCC->BDCR |= RCC_BDCR_RTCEN; + + /* Unlock RTC write protection */ + RTC->WPR = RTC_WRITE_PROTECTION_KEY1; + RTC->WPR = RTC_WRITE_PROTECTION_KEY2; + + /* Enter RTC Init mode */ + RTC->ISR = 0; + RTC->ISR |= RTC_ISR_INIT; + while ( (RTC->ISR & RTC_ISR_INITF) == 0 ); + + /* Set 24-h clock */ + RTC->CR |= RTC_CR_FMT; + /* Timestamps enabled */ + RTC->CR |= RTC_CR_TSE; + + /* Configure the RTC PRER */ + RTC->PRER = RTC_SYNC_PRESCALER; + RTC->PRER |= (RTC_ASYNC_PRESCALER << 16); + + /* Exit RTC init mode */ + RTC->ISR &= (uint32_t)~RTC_ISR_INIT; + + /* Enable RTC write protection */ + RTC->WPR = 0xff; + +} + +int rtc_set_time(struct tm *time) +{ + /* Enable write access to RTC registers */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + PWR->CR |= PWR_CR_DBP; + + /* Unlock RTC write protection */ + RTC->WPR = RTC_WRITE_PROTECTION_KEY1; + RTC->WPR = RTC_WRITE_PROTECTION_KEY2; + + /* Enter RTC Init mode */ + RTC->ISR |= RTC_ISR_INIT; + while ( (RTC->ISR & RTC_ISR_INITF) == 0 ); + + + RTC->DR = ( (((uint32_t)byte2bcd(time->tm_year - MCU_YEAR_OFFSET) << 16) & (RTC_DR_YT | RTC_DR_YU) ) | + (((uint32_t)byte2bcd(time->tm_mon+1)<< 8) & (RTC_DR_MT | RTC_DR_MU) ) | + (((uint32_t)byte2bcd(time->tm_mday) << 0) & (RTC_DR_DT | RTC_DR_DU) ) ); + + RTC->TR = ( (((uint32_t)byte2bcd(time->tm_hour) << 16) & (RTC_TR_HT | RTC_TR_HU) ) | + (((uint32_t)byte2bcd(time->tm_min) << 8) & (RTC_TR_MNT| RTC_TR_MNU)) | + (((uint32_t)byte2bcd(time->tm_sec) << 0) & (RTC_TR_ST | RTC_TR_SU) ) ); + + /* Exit RTC init mode */ + RTC->ISR &= (uint32_t)~RTC_ISR_INIT; + /* Enable RTC write protection */ + RTC->WPR = 0xFF; + return 0; +} + +int rtc_get_time(struct tm *time) +{ + time->tm_year = MCU_YEAR_OFFSET; + time->tm_year +=(((RTC->DR & RTC_DR_YT) >> 20) * 10) + ((RTC->DR & RTC_DR_YU) >> 16); + time->tm_mon = (((RTC->DR & RTC_DR_MT) >> 12) * 10) + ((RTC->DR & RTC_DR_MU) >> 8) - 1; + time->tm_mday = (((RTC->DR & RTC_DR_DT) >> 4) * 10) + ((RTC->DR & RTC_DR_DU) >> 0); + time->tm_hour = (((RTC->TR & RTC_TR_HT) >> 20) * 10) + ((RTC->TR & RTC_TR_HU) >> 16); + if ( RTC->TR & RTC_TR_PM ) + time->tm_hour += 12; + time->tm_min = (((RTC->TR & RTC_TR_MNT) >> 12) * 10) + ((RTC->TR & RTC_TR_MNU) >> 8); + time->tm_sec = (((RTC->TR & RTC_TR_ST) >> 4) * 10) + ((RTC->TR & RTC_TR_SU) >> 0); + return 0; +} + +int rtc_set_alarm(struct tm *time, rtc_alarm_cb_t cb, void *arg) +{ + /* Enable write access to RTC registers */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + PWR->CR |= PWR_CR_DBP; + + /* Unlock RTC write protection */ + RTC->WPR = RTC_WRITE_PROTECTION_KEY1; + RTC->WPR = RTC_WRITE_PROTECTION_KEY2; + + /* Enter RTC Init mode */ + RTC->ISR |= RTC_ISR_INIT; + while ( (RTC->ISR & RTC_ISR_INITF) == 0 ); + + RTC->CR &= ~(RTC_CR_ALRAE); + while ( (RTC->ISR & RTC_ISR_ALRAWF) == 0 ); + RTC->ALRMAR &= ~(RTC_ALRMAR_MSK1 | RTC_ALRMAR_MSK2 | RTC_ALRMAR_MSK3 | RTC_ALRMAR_MSK4); + RTC->ALRMAR = ( (((uint32_t)byte2bcd(time->tm_mday) << 24) & (RTC_ALRMAR_DT | RTC_ALRMAR_DU) ) | + (((uint32_t)byte2bcd(time->tm_hour) << 16) & (RTC_ALRMAR_HT | RTC_ALRMAR_HU) ) | + (((uint32_t)byte2bcd(time->tm_min) << 8) & (RTC_ALRMAR_MNT| RTC_ALRMAR_MNU)) | + (((uint32_t)byte2bcd(time->tm_sec) << 0) & (RTC_ALRMAR_ST | RTC_ALRMAR_SU) ) ); + /* Enable Alarm A */ + RTC->CR |= RTC_CR_ALRAE; + RTC->CR |= RTC_CR_ALRAIE; + RTC->ISR &= ~(RTC_ISR_ALRAF); + + /* Exit RTC init mode */ + RTC->ISR &= (uint32_t)~RTC_ISR_INIT; + /* Enable RTC write protection */ + RTC->WPR = 0xFF; + + EXTI->IMR |= EXTI_IMR_MR17; + EXTI->RTSR |= EXTI_RTSR_TR17; + NVIC_SetPriority(RTC_IRQn, 10); + NVIC_EnableIRQ(RTC_IRQn); + + rtc_callback.cb = cb; + rtc_callback.arg = arg; + + return 0; +} + +int rtc_get_alarm(struct tm *time) +{ + time->tm_year = MCU_YEAR_OFFSET; + time->tm_year +=(((RTC->DR & RTC_DR_YT) >> 20) * 10) + ((RTC->DR & RTC_DR_YU) >> 16); + time->tm_mon = (((RTC->DR & RTC_DR_MT) >> 12) * 10) + ((RTC->DR & RTC_DR_MU) >> 8) - 1; + time->tm_mday = (((RTC->ALRMAR & RTC_ALRMAR_DT) >> 28) * 10) + ((RTC->ALRMAR & RTC_ALRMAR_DU) >> 24); + time->tm_hour = (((RTC->ALRMAR & RTC_ALRMAR_HT) >> 20) * 10) + ((RTC->ALRMAR & RTC_ALRMAR_HU) >> 16); + if ( (RTC->ALRMAR & RTC_ALRMAR_PM) && (RTC->CR & RTC_CR_FMT) ) + time->tm_hour += 12; + time->tm_min = (((RTC->ALRMAR & RTC_ALRMAR_MNT) >> 12) * 10) + ((RTC->ALRMAR & RTC_ALRMAR_MNU) >> 8); + time->tm_sec = (((RTC->ALRMAR & RTC_ALRMAR_ST) >> 4) * 10) + ((RTC->ALRMAR & RTC_ALRMAR_SU) >> 0); + return 0; +} + +void rtc_clear_alarm(void) +{ + /* Disable Alarm A */ + RTC->CR &= RTC_CR_ALRAE; + RTC->CR &= RTC_CR_ALRAIE; + + rtc_callback.cb = NULL; + rtc_callback.arg = NULL; +} + +void rtc_poweron(void) +{ + /* RTC on STM32F0 is online even on sleep modes. No need to power on. */ +} + +void rtc_poweroff(void) +{ + /* Enable write access to RTC registers */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + PWR->CR |= PWR_CR_DBP; + + /* Reset RTC domain */ + RCC->BDCR |= RCC_BDCR_BDRST; + RCC->BDCR &= ~(RCC_BDCR_BDRST); + /* Disable the RTC */ + RCC->BDCR &= ~RCC_BDCR_RTCEN; + /* Disable LSE clock */ + RCC->BDCR &= ~(RCC_BDCR_LSEON); +} + +void isr_rtc(void) +{ + + if ((RTC->ISR & RTC_ISR_ALRAF) && (rtc_callback.cb != NULL)) { + rtc_callback.cb(rtc_callback.arg); + RTC->ISR &= ~RTC_ISR_ALRAF; + } + if (sched_context_switch_request) { + thread_yield(); + } +} + +/** + * Convert a number from unsigned to BCD + * + * @param[in] value to be converted + * @return BCD representation of the value + */ +static uint8_t byte2bcd(uint8_t value) +{ + uint8_t bcdhigh = 0; + + while (value >= 10) + { + bcdhigh++; + value -= 10; + } + + return ((uint8_t)(bcdhigh << 4) | value); +} + +#endif /* RTC_NUMOF */ diff --git a/tests/periph_rtc/Makefile b/tests/periph_rtc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a907d722dec5e2f1296232a001c94a5e5ada76a9 --- /dev/null +++ b/tests/periph_rtc/Makefile @@ -0,0 +1,8 @@ +export APPLICATION = periph_rtc +include ../Makefile.tests_common + +FEATURES_REQUIRED = periph_rtc + +DISABLE_MODULE += auto_init + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_rtc/README.md b/tests/periph_rtc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1b90d594aafb3b2c5e4b1928118c432a558c0ff9 --- /dev/null +++ b/tests/periph_rtc/README.md @@ -0,0 +1,7 @@ +Expected result +=============== +When everything works as expected, you should see a alarm message after 10 seconds from start-up. + +Background +========== +Test for the low-level RTC driver. diff --git a/tests/periph_rtc/main.c b/tests/periph_rtc/main.c new file mode 100644 index 0000000000000000000000000000000000000000..7ff24b0899ed5c763dbca4d2fce83782429f94a8 --- /dev/null +++ b/tests/periph_rtc/main.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 Lari Lehtomäki + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test for low-level Real Time clock drivers + * + * This test will initialize the real-time timer and trigger an alarm printing + * 'Hello' every 10 seconds + * + * @author Lari Lehtomäki <lari@lehtomaki.fi> + * + * @} + */ + +#include <stdio.h> +#include <time.h> + +#include "periph_conf.h" +#include "periph/rtc.h" +#include "hwtimer.h" + +#define TM_YEAR_OFFSET (1900) + +#if RTC_NUMOF < 1 +#error "No RTC found. See the board specific periph_conf.h." +#endif + +void cb(void *arg) +{ + (void)arg; + + puts("Alarm!"); + + struct tm time; + rtc_get_alarm(&time); + time.tm_sec += 10; + if ( time.tm_sec > 60 ) + rtc_clear_alarm(); + rtc_set_alarm(&time, cb, 0); +} + + +int main(void) +{ + puts("\nRIOT RTC low-level driver test"); + puts("This test will display 'Alarm' in 10 seconds\n"); + + puts("Initializing the RTC driver"); + rtc_init(); + + struct tm time; + time.tm_year = 2011 - TM_YEAR_OFFSET; // years are counted from 1900 + time.tm_mon = 11; // 0 = January, 11 = December + time.tm_mday = 13; + time.tm_hour = 14; + time.tm_min = 15; + time.tm_sec = 15; + + printf("Setting clock to %04d-%02d-%02d %02d:%02d:%02d\n", + time.tm_year + TM_YEAR_OFFSET, + time.tm_mon + 1, + time.tm_mday, + time.tm_hour, + time.tm_min, + time.tm_sec); + rtc_set_time(&time); + + hwtimer_wait(100); + + rtc_get_time(&time); + printf("Clock set to %04d-%02d-%02d %02d:%02d:%02d\n", + time.tm_year + TM_YEAR_OFFSET, + time.tm_mon + 1, + time.tm_mday, + time.tm_hour, + time.tm_min, + time.tm_sec); + + + time.tm_sec += 10; + + printf("Setting alarm to %04d-%02d-%02d %02d:%02d:%02d\n", + time.tm_year + TM_YEAR_OFFSET, + time.tm_mon + 1, + time.tm_mday, + time.tm_hour, + time.tm_min, + time.tm_sec); + rtc_set_alarm(&time, cb, 0); + + hwtimer_wait(100); + + rtc_get_alarm(&time); + printf("Alarm set to %04d-%02d-%02d %02d:%02d:%02d\n", + time.tm_year + TM_YEAR_OFFSET, + time.tm_mon + 1, + time.tm_mday, + time.tm_hour, + time.tm_min, + time.tm_sec); + + puts("The alarm should trigger every 10 seconds for 4 times."); + return 0; +}