Skip to content

Instantly share code, notes, and snippets.

@ag88
Last active March 11, 2021 09:55
Show Gist options
  • Save ag88/c4429b0e77b58e2b3d461798d6021dd5 to your computer and use it in GitHub Desktop.
Save ag88/c4429b0e77b58e2b3d461798d6021dd5 to your computer and use it in GitHub Desktop.
stm32duino libmaple f4 backup registers
/******************************************************************************
* The MIT License
*
* Copyright (c) 2010 LeafLabs, LLC.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*****************************************************************************/
/**
* @file bkp.c
* @brief Backup register support.
*/
#include <libmaple/bkp.h>
#include <libmaple/bitband.h>
#include <string.h>
/*
* returns data register address
* reg is 1 to BKP_NR_DATA_REGS
*/
static __IO uint32* data_register(uint8 reg)
{
if ( reg==0 || reg > BKP_NR_DATA_REGS) {
return 0;
}
return (uint32*)BKP + reg - 1;
}
/**
* Read a value from given backup data register.
* @param reg Data register to read, from 1 to BKP_NR_DATA_REGS (10 on
* medium-density devices, 42 on high-density devices).
*/
uint32 bkp_read(uint8 reg) {
__IO uint32* dr = data_register(reg);
if (!dr) {
ASSERT(0); /* nonexistent register */
return 0;
}
return *dr;
}
/**
* @brief Write a value to given data register.
*
* Write access to backup registers must be enabled.
*
* @param reg Data register to write, from 1 to BKP_NR_DATA_REGS (10
* on medium-density devices, 42 on high-density devices).
* @param val Value to write into the register.
* @see bkp_enable_writes()
*/
void bkp_write(uint8 reg, uint32 val) {
__IO uint32* dr = data_register(reg);
if (!dr) {
ASSERT(0); /* nonexistent register */
return;
}
*dr = val;
}
/*
* BKPSRAM functions
*/
uint8_t bkp_sramread8(uint16_t offset) {
return *((uint8_t *)(BKPSRAM_BASE) + offset);
}
void bkp_sramwrite8(uint16_t offset, uint8_t data) {
if (offset < BKPSIZE)
*((uint8_t *)(BKPSRAM_BASE) + offset) = data;
}
uint16_t bkp_sramread16(uint16_t offset) {
return *((uint16_t *)(BKPSRAM_BASE) + offset);
}
void bkp_sramwrite16(uint16_t offset, uint16_t data) {
uint16_t *p = (uint16_t *)(BKPSRAM_BASE) + offset;
if (offset * 2 < BKPSIZE)
*p = data;
}
uint32_t bkp_sramread32(uint16_t offset) {
return *((uint32_t *)(BKPSRAM_BASE) + offset);
}
void bkp_sramwrite32(uint16_t offset, uint32_t data) {
uint32_t *p = (uint32_t *)(BKPSRAM_BASE) + offset;
if (offset * 4 < BKPSIZE)
*p = data;
}
/* copies data to bkpsram
*
*/
void bkp_sramwrite(uint16_t offset, uint8_t *data, uint16_t length) {
if(length > BKPSIZE - offset)
length = BKPSIZE - offset;
memcpy((void *)(BKPSRAM_BASE + offset), data, length);
}
void bkpsram_clear() {
memset((void*)BKPSRAM_BASE, 0, BKPSIZE);
}
/******************************************************************************
* The MIT License
*
* Copyright (c) 2010 LeafLabs, LLC.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*****************************************************************************/
/**
* @file bkp.h
* @brief Backup register support.
*/
#ifndef _BKP_H_
#define _BKP_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "libmaple.h"
#include "pwr.h"
#include "rcc.h"
#define BKP_NR_DATA_REGS 20
/** Backup peripheral register map type. */
/*
* there are 20 backup registers on stm32 f401, f411, f405, f407, f427, f429 devices.
* in addition for the larger devices e.g. f405, f407, f427, f429 there is an additional
* 4KB backup sram. bkp_reg_map mainly maps the 20 backup registers.
*/
typedef struct bkp_reg_map {
__IO uint32 DR1; ///< Data register 1
__IO uint32 DR2; ///< Data register 2
__IO uint32 DR3; ///< Data register 3
__IO uint32 DR4; ///< Data register 4
__IO uint32 DR5; ///< Data register 5
__IO uint32 DR6; ///< Data register 6
__IO uint32 DR7; ///< Data register 7
__IO uint32 DR8; ///< Data register 8
__IO uint32 DR9; ///< Data register 9
__IO uint32 DR10; ///< Data register 10
__IO uint32 DR11; ///< Data register 11
__IO uint32 DR12; ///< Data register 12
__IO uint32 DR13; ///< Data register 13
__IO uint32 DR14; ///< Data register 14
__IO uint32 DR15; ///< Data register 15
__IO uint32 DR16; ///< Data register 16
__IO uint32 DR17; ///< Data register 17
__IO uint32 DR18; ///< Data register 18
__IO uint32 DR19; ///< Data register 19
__IO uint32 DR20; ///< Data register 20
} bkp_reg_map;
/** Backup peripheral register map base pointer. */
#define BKP ((struct bkp_reg_map*)(0x40002800 + 0x50))
// Backup SRAM(4 KB) base address
#define BKPSRAM_BASE 0x40024000UL
//4KB
#define BKPSIZE 4096
/*
* Register bit definitions
*/
/* Data Registers */
#define BKP_DR_D 0xFFFFFFFF
/*
* Convenience functions
*/
/**
* Enable write access to the backup registers. Backup interface must
* be initialized for subsequent register writes to work.
* @see bkp_init()
*/
__always_inline void bkp_enable_writes(void) {
*bb_perip(&PWR->CR, PWR_CR_DBP_BIT) = 1;
}
/**
* Disable write access to the backup registers.
*/
__always_inline void bkp_disable_writes(void) {
*bb_perip(&PWR->CR, PWR_CR_DBP_BIT) = 0;
}
/**
* @brief Initialize backup interface.
*
* note that bkp_init() merely enables the backup domain clocks.
* This is not adequate to use backup domain registers and sram.
*
* to access backup registers it is necessary to initiate RTC clock
* e.g.
* RTClock rt;
* void setup() {
* bkp_init();
* rt.begin();
*
* // enable writes before writing to bkp registers
* // or it will hardfault, freeze
* bkp_enable_writes();
* bkp_write(1, 100); //write 100 in bkp register 1
* bkp_disable_writes();
*
* int32_t regval = bkp_read(1); // read register 1
* }
*
* if you want to access backup SRAM in addition to bkp_init()
* it is necessary to call bkp_initsram(), e.g:
*
* void setup() {
* bkp_init();
* bkp_initsram(true);
* rt.begin();
*
* ...
* }
*
*/
__always_inline void bkp_init(void) {
rcc_clk_enable(RCC_PWR);
/* Don't call pwr_init(), or you'll reset the device.
* We just need the clock.
pwr_init();
*/
}
/*
* enable BKPSRAM
* requires bkp_init() to be called prior
*
* @param bkreg
* true enable the backup power regulator, runs on VBAT e.g. coin cell
* false BKPSRAM is lost if VDD is lost, but preserves across a reset
*/
__always_inline void bkp_initsram(bool bkreg) {
bkp_enable_writes();
//enable backup sram
RCC->AHB1ENR |= RCC_AHB1ENR_BKPSRAMEN;
if(bkreg)
PWR->CSR |= PWR_CSR_BRE;
else
PWR->CSR &= ~PWR_CSR_BRE;
}
/* functions to read/write bkp registers
* note that prior to bkp_write() it is necessary to call
* bkp_enable_writes()
*/
uint32 bkp_read(uint8 reg);
void bkp_write(uint8 reg, uint32 val);
/* functions to read write bkp sram
*
* note that the offset is indexed by word sized entries
* zero is the first offset
* e.g. bkp_sramwrite32(9, data) writes to the 10th 32 bit uint32 field
*
* prior to writing bkp_sram it is necessary to call
* bkp_enable_writes()
*
*/
uint8_t bkp_sramread8(uint16_t offset);
void bkp_sramwrite8(uint16_t offset, uint8_t data);
uint16_t bkp_sramread16(uint16_t offset);
void bkp_sramwrite16(uint16_t offset, uint16_t data);
uint32_t bkp_sramread32(uint16_t offset);
void bkp_sramwrite32(uint16_t offset, uint32_t data);
/* note this simply returns a pointer to the BKPSRAM + offset
* BKPSRAM is normally 4KB, this does not check if you were to read beyond that 4KB
*/
inline uint8_t* bkp_sramread(uint16_t offset) {
return (uint8_t *)(BKPSRAM_BASE + offset);
}
/* copies data to bkpsram
* note this truncate data that doesn't fit in BKPSIZE - offset
* */
void bkp_sramwrite(uint16_t offset, uint8_t *data, uint16_t length);
/*
* clear bkpsram entirely
*/
void bkpsram_clear();
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#include <Arduino.h>
#include <RTClock.h>
#include <usb_serial.h>
#include <libmaple/bkp.h>
#include <string.h>
RTClock rt; // initialise
void setbkpvalues();
void readbkpvalues();
void sleep(uint16_t dur);
int ledPin = BOARD_LED_PIN;
// the setup() method runs once when the sketch starts
void setup() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); //turn on the led
Serial.begin();
//wait for keypress
while(!Serial.available()) sleep(1);
while(Serial.available()) Serial.read();
Serial.println("init");
// enable backup domain clock
bkp_init();
/* enable backup sram
* bkp_init() needs to be called prior
* passing true enable the backup power regulator, runs on VBAT e.g. coin cell
* passing false BKPSRAM is lost if VDD is lost, but preserves across a reset
* if only backup sram is used rt.begin() can be skipped, but bkp_init() need to be called prior
*/
bkp_initsram(false);
// setup RTClock,
// note that an initialized RTClock is necessary to access backup registers.
// if only backup registers are accessed, bkp_initsram() can be skipped
rt.begin();
Serial.println("bkp values:");
readbkpvalues();
Serial.flush();
sleep(500);
Serial.println("setting bkp values:");
Serial.flush();
setbkpvalues();
sleep(500);
Serial.println("bkp values:");
readbkpvalues();
Serial.flush();
digitalWrite(ledPin, !digitalRead(ledPin));
}
//the loop() method runs over and over again,
//as long as maple has power
void loop() {
sleep(1000);
}
void setbkpvalues() {
const char *text = (const char*)F("a quick brown fox jumps over the lazy dog");
// before writing to bkp registers it is necessary to enable writes
// or it will hardfault / freeze
bkp_enable_writes();
// set values in backup registers
// write values 1000001 .. 1000020 in backup registers
for (uint8_t r=1; r<=20; r++) {
bkp_write(r, 100000 + r);
}
bkp_disable_writes();
// check if BKPSRAM is enabled, if not skip writing them
if(!(RCC->AHB1ENR & RCC_AHB1ENR_BKPSRAMEN))
return;
//before writing to bkpsram it is necessary to enable writes
// or it will hardfault / freeze
bkp_enable_writes();
//clear bkp sram
bkpsram_clear();
// write 100 in 2nd byte in BKP_SRAM
bkp_sramwrite8(1, 100);
// write 10000 in 2nd uint16_t word in BKP_SRAM
bkp_sramwrite16(1, 10000);
// write 1000000 in 2nd uint32_t word in BKP_SRAM
bkp_sramwrite32(1, 1000000);
//write text in BKPSRAM at offset 10
bkp_sramwrite(10, (uint8_t *)text, 41);
bkp_disable_writes();
}
void readbkpvalues() {
char buf[50];
// read backup registers
for (uint8_t r=1; r<=20; r++) {
uint32_t v = bkp_read(r);
Serial.print("reg :");
Serial.print(r);
Serial.print(':');
Serial.println(v);
}
// check if BKPSRAM is enabled, if not skip reading them
if(!(RCC->AHB1ENR & RCC_AHB1ENR_BKPSRAMEN))
return;
uint32_t val = 0;
// read 100 in 2nd byte in BKP_SRAM
val = bkp_sramread8(1);
Serial.print("BKPSRAM 2nd byte:");
Serial.println(val);
// read 10000 in 2nd uint16_t word in BKP_SRAM
val = 0;
val = bkp_sramread16(1);
Serial.print("BKPSRAM 2nd uint16 word:");
Serial.println(val);
// read 1000000 in 2nd uint32_t word in BKP_SRAM
val = 0;
val = bkp_sramread32(1);
Serial.print("BKPSRAM 2nd uint32 word:");
Serial.println(val);
memset(buf, 0, 50);
memcpy(buf, bkp_sramread(10), 41);
Serial.print("BKPSRAM text at offset 10:");
Serial.println(buf);
}
void sleep(uint16_t dur) {
//sleep 1ms - wait for systick interrupt
for(uint16_t i=0; i<dur; i++)
asm("wfi");
}
/******************************************************************************
* The MIT License
*
* Copyright (c) 2010 LeafLabs LLC.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*****************************************************************************/
/**
Inspired of the F1xx version adapted for the F4xx, not much F1xx left.
author : Martin Ayotte, 2015.
*/
#include <RTClock.h>
#ifdef RTC_DEBUG
#include <stdio.h>
#include <usb_serial.h>
char dbg_s[200];
#define PRINTF(...) { sprintf(dbg_s, __VA_ARGS__); Serial.print(dbg_s); }
#else
#define PRINTF(...)
#endif
#define PRINTF1(...) { sprintf(dbg_s, __VA_ARGS__); Serial.print(dbg_s); }
typedef struct {
uint16_t s_presc;
uint16_t as_presc;
} prescaler_t;
const prescaler_t prescalers[4] = {
{ 0, 0}, // RTCSEL_NONE
{ 255, 127}, // RTCSEL_LSE
{ 249, 127}, // RTCSEL_LSI
{7999, 124}, // RTCSEL_HSE
};
//-----------------------------------------------------------------------------
void RTClock::begin(rtc_clk_src src, uint16 sync_presc, uint16 async_presc)
{
clk_src = src;
sync_prescaler = sync_presc;
async_prescaler = async_presc;
bool lse_ison = false;
PRINTF("> RTClock::begin\n");
PRINTF("PWR->CR(1) = %08X\n", PWR->CR);
bkp_init(); // turn on peripheral clocks to PWR and BKP and reset the backup domain via RCC registers.
//if RTC is running on LSE we'd skip initialization of RTC so that the time maintained on VBAT would not be reset
lse_ison = (RCC->BDCR & RCC_BDCR_LSEON == RCC_BDCR_LSEON) && (RCC->BDCR & RCC_BDCR_RTCEN == RCC_BDCR_RTCEN)
PRINTF("bkp_enable_writes\n");
bkp_enable_writes(); // enable writes to the backup registers and the RTC registers via the DBP bit in the PWR control register
PRINTF("PWR->CR(2) = %08X\n", PWR->CR);
rcc_set_prescaler(RCC_PRESCALER_RTC, RCC_RTCCLK_DIV(CRYSTAL_FREQ)); // Set the RTCPRE to 8.
PRINTF("RCC->CFGR = %08X\n", RCC->CFGR);
PRINTF("RTC clock source: %s\n", (clk_src==RTCSEL_LSE)?"LSE":((clk_src==RTCSEL_LSI)?"LSI":((clk_src==RTCSEL_HSE)?"HSE":"NONE")));
switch (clk_src)
{
case RTCSEL_LSE: {
PRINTF("Preparing RTC for LSE mode, RCC->BDCR = %08X\n", RCC->BDCR);
if ((RCC->BDCR & RCC_BDCR_RTCSEL_MASK) == RCC_BDCR_RTCSEL_LSE)
break;
RCC->BDCR = RCC_BDCR_BDRST; // Reset the entire Backup domain
PRINTF("BCKP domain reset\n");
RCC->BDCR = (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_LSE | RCC_BDCR_LSEON);
PRINTF("RCC->BDCR = %08X\n", RCC->BDCR);
uint32 t = 0;
while (!(RCC->BDCR & RCC_BDCR_LSERDY)) {
if (++t > 10000000) {
PRINTF("RCC LSERDY Timeout ! BDCR = %08X\n", RCC->BDCR);
break;
}
}
break;
}
case RTCSEL_LSI:
{
PRINTF("Preparing RTC for LSI mode\n");
if ((RCC->BDCR & RCC_BDCR_RTCSEL_MASK) == RCC_BDCR_RTCSEL_LSI)
break;
RCC->BDCR = RCC_BDCR_BDRST; // Reset the entire Backup domain
PRINTF("BCKP domain reset\n");
RCC->BDCR = (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_LSI | RCC_BDCR_LSEBYP);
PRINTF("RCC->BDCR = %08X\r\n", RCC->BDCR);
RCC->CSR = RCC_CSR_LSION;
uint32 t = 0;
while (!(RCC->CSR & RCC_CSR_LSIRDY)) {
if (++t > 10000000) {
PRINTF("RCC LSIRDY Timeout ! CSR = %08X\n", RCC->CSR);
goto end0;
}
}
PRINTF("RCC->CSR = %08X\n", RCC->CSR);
} break;
case RTCSEL_HSE :
PRINTF("Preparing RTC for HSE mode, RCC->BDCR = %08X\n", RCC->BDCR);
if ((RCC->BDCR & RCC_BDCR_RTCSEL_MASK) == RCC_BDCR_RTCSEL_HSE)
break;
RCC->BDCR = RCC_BDCR_BDRST; // Reset the entire Backup domain
PRINTF("BCKP domain reset\n");
RCC->BDCR = (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_HSE | RCC_BDCR_LSEBYP);
PRINTF("RCC->BDCR = %08X\n", RCC->BDCR);
break;
case RTCSEL_NONE:
PRINTF("Preparing RTC for NONE mode\n");
if ((RCC->BDCR & RCC_BDCR_RTCSEL_MASK) != RCC_BDCR_RTCSEL_NONE)
RCC->BDCR = RCC_BDCR_BDRST; // Reset the entire Backup domain
//do nothing. Have a look at the clocks to see the diff between NONE and DEFAULT
goto end0;
break;
}
if ( (sync_prescaler + async_prescaler) == 0) {
sync_prescaler = prescalers[clk_src].s_presc;
async_prescaler = prescalers[clk_src].as_presc;
}
PRINTF("sync_prescaler = %d, async_prescaler = %d\n", sync_prescaler, async_prescaler);
if(!lse_ison) {
rtc_enter_config_mode();
RTC->PRER = (uint32)(async_prescaler << 16) + sync_prescaler;
RTC->DR = 0x00002101; // reset value
RTC->TR = 0x00000000; // reset value
//RCC->CR |= RTC_CR_BYPSHAD;
*bb_perip(&RTC->CR, RTC_CR_BYPSHAD_BIT) = 1; // bypass shadow regs
PRINTF("RTC PRER: %08X, CR: %08X\n", RTC->PRER, RTC->CR);
rtc_exit_config_mode();
};
end0:
PRINTF("< RTClock::begin\n");
}
//-----------------------------------------------------------------------------
void RTClock::setAlarmATime (bool hours_match, bool mins_match, bool secs_match, bool date_match)
{
rtc_enter_config_mode();
unsigned int bits = (bin2bcd(_tm.day)<<24) + (bin2bcd(_tm.hour)<<16) +
(bin2bcd(_tm.minute)<<8) + bin2bcd(_tm.second);
if (!date_match) bits |= (1 << 31);
if (!hours_match) bits |= (1 << 23);
if (!mins_match) bits |= (1 << 15);
if (!secs_match) bits |= (1 << 7);
RTC->CR &= ~(RTC_CR_ALRAE);
uint32 t = 0;
while (!(RTC->ISR & RTC_ISR_ALRAWF)) {
if (++t > 1000000) {
PRINTF("RTC ALRAWF Timeout ! ISR = %08X\n", RTC->ISR);
return;
}
}
RTC->ALRMAR = bits;
RTC->CR |= (RTC_CR_ALRAE |RTC_CR_ALRAIE); // turn on ALRAIE
rtc_exit_config_mode();
nvic_irq_enable(NVIC_RTCALARM);
nvic_irq_enable(NVIC_RTC);
rtc_enable_alarm_event();
}
//-----------------------------------------------------------------------------
void RTClock::setAlarmATime (time_t alarm_time, bool hours_match, bool mins_match, bool secs_match, bool date_match)
{
breakTime(alarm_time, &_tm);
setAlarmATime(hours_match, mins_match, secs_match, date_match);
}
//-----------------------------------------------------------------------------
void RTClock::turnOffAlarmA(void)
{
rtc_enter_config_mode();
RTC->CR &= ~(RTC_CR_ALRAIE); // turn off ALRAIE
rtc_exit_config_mode();
}
//-----------------------------------------------------------------------------
void RTClock::setAlarmBTime (bool hours_match, bool mins_match, bool secs_match, bool date_match)
{
rtc_enter_config_mode();
unsigned int bits = (bin2bcd(_tm.day) << 24) + (bin2bcd(_tm.hour) << 16) +
(bin2bcd(_tm.minute) << 8) + bin2bcd(_tm.second);
if (!date_match) bits |= (1 << 31);
if (!hours_match) bits |= (1 << 23);
if (!mins_match) bits |= (1 << 15);
if (!secs_match) bits |= (1 << 7);
RTC->CR &= ~(RTC_CR_ALRBE);
uint32 t = 0;
while (!(RTC->ISR & RTC_ISR_ALRBWF)) {
if (++t > 1000000) {
PRINTF("RTC ALRBWF Timeout ! ISR = %08X\n", RTC->ISR);
return;
}
}
RTC->ALRMBR = bits;
RTC->CR |= (RTC_CR_ALRBE | RTC_CR_ALRBIE); // turn on ALRBIE
rtc_exit_config_mode();
nvic_irq_enable(NVIC_RTCALARM);
nvic_irq_enable(NVIC_RTC);
rtc_enable_alarm_event();
}
//-----------------------------------------------------------------------------
void RTClock::setAlarmBTime (time_t alarm_time, bool hours_match, bool mins_match, bool secs_match, bool date_match)
{
breakTime(alarm_time, &_tm);
setAlarmBTime(hours_match, mins_match, secs_match, date_match);
}
//-----------------------------------------------------------------------------
void RTClock::turnOffAlarmB() {
rtc_enter_config_mode();
RTC->CR &= ~(RTC_CR_ALRBIE); // turn off ALRBIE
rtc_exit_config_mode();
}
//-----------------------------------------------------------------------------
void RTClock::setPeriodicWakeup(uint16 period)
{
PRINTF("< setPeriodicWakeup\n");
rtc_enter_config_mode();
RTC->CR &= ~(RTC_CR_WUTE);
uint32 t = 0;
while (!(RTC->ISR & RTC_ISR_WUTWF)) {
if (++t > 1000000) {
PRINTF("RTC WUTWF Timeout ! ISR = %08X\n", RTC->ISR);
return;
}
}
PRINTF("before setting RTC->WUTR\r\n");
RTC->WUTR = period; // set the period
PRINTF("RTC->WUTR = %08X\r\n", RTC->WUTR);
PRINTF("before setting RTC->CR.WUCKSEL\r\n");
RTC->CR &= ~(RTC_CR_WUCKSEL_MASK);
RTC->CR |= 4; // Set the WUCKSEL to 1Hz (0x00000004)
*bb_perip(&RTC->ISR, RTC_ISR_WUTF_BIT) = 0;
RTC->CR |= RTC_CR_WUTE;
if (period == 0)
RTC->CR &= ~(RTC_CR_WUTIE); // if period is 0, turn off periodic wakeup interrupt.
else {
PRINTF("before turn ON RTC->CR.WUTIE\r\n");
RTC->CR |= (RTC_CR_WUTIE); // turn on WUTIE
}
PRINTF("RCC->CR = %08X\r\n", RCC->CR);
rtc_exit_config_mode();
rtc_enable_wakeup_event();
nvic_irq_enable(NVIC_RTC);
PRINTF("setPeriodicWakeup >\n");
}
void RTClock::attachAlarmAInterrupt(voidFuncPtr function) {
handlerAlarmA = function;
}
void RTClock::detachAlarmAInterrupt() {
handlerAlarmA = NULL;
}
void RTClock::attachAlarmBInterrupt(voidFuncPtr function) {
handlerAlarmB = function;
}
void RTClock::detachAlarmBInterrupt() {
handlerAlarmB = NULL;
}
void RTClock::attachPeriodicWakeupInterrupt(voidFuncPtr function) {
handlerPeriodicWakeup = function;
}
void RTClock::detachPeriodicWakeupInterrupt() {
handlerPeriodicWakeup = NULL;
}
#include <RTClock.h>
/*
This is an example of how to use the RTclock of STM32F4 device
This example can also be used to set the RTC to the current epoch time:
- goto: http://www.unixtimestamp.com/
- enter the current date and time to the right field "Timestamp converter"
- press the "Convert" button
-
*/
#include <RTClock.h>
#include <Streaming.h>
//RTClock rt(RTCSEL_LSE); // initialise
RTClock rtc;
time_t tt;
tm_t tm;
const uint32_t DEFAULT_TIME = 1498944019; // 2017.07.01, 21:20:19 used as reference epoch time
#define TIME_HEADER 'T' // Header tag for serial time sync message
#define TIME_REQUEST 7 // ASCII bell character requests a time sync message
#define LED_PIN BOARD_LED_PIN
//-----------------------------------------------------------------------------
void blink ()
{
digitalWrite(LED_PIN, digitalRead(LED_PIN)?LOW:HIGH);
}
uint8_t s[20]; // for serial parsing
//-----------------------------------------------------------------------------
char * read_line()
{
while ( Serial.available() ) Serial.read(); // empty Rx buffer
//while ( Serial.available()<=0 ) ; // wait for new characters
uint8_t c, i = 0;
s[0] = 0;
while ( i<20 ) {
while( !Serial.available() ) delay(1);
c = Serial.read();
Serial.print((char) c);
if ( c=='\n' || c== '\r') {
s[i] = 0;
break;
} else if (c==8) {
i--;
continue;
}
s[i++] = c;
}
while ( Serial.available() ) Serial.read(); // flush Rx
return (char*)&s[0];
}
//-----------------------------------------------------------------------------
void processSyncMessage(void)
{
if ( Serial.available() ) {
if( *read_line()==(TIME_HEADER) ) {
uint32_t pctime = atoi((const char*)&s[1]);
Serial << ("Epoch time received: ") << pctime << endl;
if ( pctime >= DEFAULT_TIME) { // check the integer is a valid epoch time
rtc.setTime(pctime); // Set RTC to the time received via the serial port
}
}
Serial << endl;
}
}
//-----------------------------------------------------------------------------
void Change_DateTime(void)
{
uint16_t tmp;
// check and correct the weekday if necessary
/*
rtc.getTime(tm);
Serial << "Current weekday is " << (tm.weekday);
// get time elements
tt = rtc.makeTime(tm);
uint16_t tmp = rtc.weekday(tt);
if ( tmp!=tm.weekday ) {// correct weekday
rtc.setTime(tt);
Serial << " instead of " << tmp << ". Now is corrected.\n";
} else {
Serial << " - seems to be fine, no need to change it.\n";
}
*/
uint8_t chg = 0;
// get time elements
rtc.getTime(tm);
Serial << "\nCurrent RTC date: " << (1970+tm.year) << "." << (tm.month) << (".") << (tm.day) << (", weekday: ") << (tm.weekday) << endl;
Serial << "Do you want to change it? (y/n)\n";
if ( *read_line()=='y' ) {
// change here the date
change_year:
Serial << "Current year: " << (1970+tm.year) << ". Enter new year in \"YYYY\" format (numbers only) or press enter to skip.\n";
if (*read_line()==0) goto change_month;
tmp = atoi((const char*)s);
if ( tmp<1970 ) { Serial << "Please enter a valid number greater than 1970\n"; goto change_year; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_year;
tm.year = tmp-1970;
chg = 1;
change_month:
Serial << "Current month: " << tm.month << ". Enter new month in \"MM\" format [1..12] or press enter to skip.\n";
if (*read_line()==0) goto change_day;
tmp = atoi((const char*)s);
if ( tmp<1 || tmp>12 ) { Serial << "Please enter a valid number [1..12]\n"; goto change_month; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_month;
tm.month = tmp;
chg = 1;
change_day:
Serial << "Current day: " << tm.day << ". Enter new day in \"DD\" format [1..31] or press enter to skip.\n";
if (*read_line()==0) goto change_weekday;
tmp = atoi((const char*)s);
if ( tmp<1 || tmp>31 ) { Serial << "Please enter a valid number [1..31]\n"; goto change_day; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_day;
tm.day = tmp;
chg = 1;
change_weekday:
Serial << "Current weekday: " << tm.weekday << ". Enter new weekday [1(=Monday)..7(=Sunday)] or press enter to skip.\n";
if (*read_line()==0) goto change_time;
tmp = atoi((const char*)s);
if ( tmp<1 || tmp>7 ) { Serial << "Please enter a valid number [1..7]\n"; goto change_weekday; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_weekday;
tm.weekday = tmp;
chg = 1;
change_time:
Serial << "Current RTC time: " << _TIME(tm.hour, tm.minute, tm.second) << endl;
Serial << "Do you want to change it? (y/n)\n";
if ( *read_line()=='n' ) goto change_end;
change_hour:
Serial << "Current hour: " << tm.hour << ". Enter new hour [0..23] or press enter to skip.\n";
if (*read_line()==0) goto change_minute;
tmp = atoi((const char*)s);
if ( tmp>23 ) { Serial << "Please enter a valid number [0..23]\n"; goto change_hour; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_hour;
tm.hour = tmp;
chg = 1;
change_minute:
Serial << "Current minute: " << tm.minute << ". Enter new minute [0..59] or press enter to skip.\n";
if (*read_line()==0) goto change_second;
tmp = atoi((const char*)s);
if ( tmp>59 ) { Serial << "Please enter a valid number [0..59]\n"; goto change_minute; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_minute;
tm.minute = tmp;
chg = 1;
change_second:
Serial << "Current second: " << tm.second << ". Enter new second [0..59] or press enter to skip.\n";
if (*read_line()==0) goto change_end;
tmp = atoi((const char*)s);
if ( tmp>59 ) { Serial << "Please enter a valid number [0..59]\n"; goto change_second; }
Serial << "You entered: " << tmp << ". Accept value? (y/n)\n";
if ( *read_line()=='n' ) goto change_second;
tm.second = tmp;
chg = 1;
} else {
goto change_time;
}
change_end:
if ( chg ) {
// set here the RTC time.
Serial << "Changed date & time: " << (1970+tm.year) << "." << (tm.month) << (".") << (tm.day) << (", weekday: ") << (tm.weekday) << ", " << _TIME(tm.hour, tm.minute, tm.second) << endl;
Serial << "Write now to RTC? (y/n)\n";
read_line();
if ( s[0]=='y' ) {
rtc.setTime(tm);
Serial << "Data written to RTC.\n\n";
}
} else
Serial << "RTC was not changed.\n\n";
}
//-----------------------------------------------------------------------------
void setup()
{
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); //turn on LED
Serial.begin();
while(!Serial.available()) delay(1);
while(Serial.available()) Serial.read();
Serial << "This is an example of how to use the STM32F4 RTC library.\n\n";
rtc.begin();
delay(1);
Change_DateTime();
}
//-----------------------------------------------------------------------------
void loop()
{
if ( Serial.available() ) {
// adjust time according to received epoch time from PC
processSyncMessage();
}
if (tt!=rtc.now()) {
// get epoch time
tt = rtc.now();
Serial << ("- RTC epoch timestamp = ") << (tt);
// get time elements
rtc.getTime(tm);
//rtc.breakTime(tt, tm);
Serial << (" == ") << (1970+tm.year) << "." << (tm.month) << (".") << (tm.day) << (", weekday ") << (tm.weekday) << (", ");
Serial << _TIME(tm.hour, tm.minute, tm.second) << endl;
blink();
}
}
@ag88
Copy link
Author

ag88 commented Mar 3, 2021

currently only tested on stm32f401cc and stm32f407ve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment