STM32F107VC/Using the RTC Real Time Clock

From Teknologisk videncenter
Jump to: navigation, search

Preparing for programming the STM32F107VC Real Time Clock.

/**************************************************************************
  #     #
  ##   ##  ######  #####    ####     ##    #    #   #####  ######   ####
  # # # #  #       #    #  #    #   #  #   ##   #     #    #       #    #
  #  #  #  #####   #    #  #       #    #  # #  #     #    #####   #
  #     #  #       #####   #       ######  #  # #     #    #       #
  #     #  #       #   #   #    #  #    #  #   ##     #    #       #    #
  #     #  ######  #    #   ####   #    #  #    #     #    ######   ####
 
***************************************************************************
Author..: Henrik Thomsen <heth@mercantec.dk>
Company.: House of Technology at Mercantec ( http://www.mercantec.dk )
date....: 2012 Jan. 14
***************************************************************************
Abstract: This module contains utility functions to operate the RTC
- Real Time Clock - on a ST Microelectronics STM32F107VC micro-controller.
The target board Keil MCBSTM32C contains battery and 32,768 Khz X-tal.

Purpose.: 1: First time initialization of the RTC or after battery replacement
          2: Setting/changing/reading the time, date and timezone
	  3: Converting between epoch time and human time
	     epoch time: seconds elapsed since 1970 jan 1 - 00:00:00
	     human time: year, month, date, weekday, hour, minute and seconds
	  4: Setting/canceling alarms
	  5: Handling leap-years 
	    (and perhaps leap-seconds - Not implemented/decided yet)
***************************************************************************
ToDo....: Missing timezone handling
          Missing alarm handling
	  Missing leap-second implementation
          Code is messy. Clean up necessary.
***************************************************************************
Modification log: 


                
***************************************************************************
License:  Free open software but WITHOUT ANY WARRANTY.
Terms..:  see http://www.gnu.org/licenses
***************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include "rtc.h"
/****************************** Defines ***********************************/
#define TRUE  1
#define FALSE 0
#define COUNT_TO 30000000 // Almost randomly delay count (Experimentation)
/*************************** Used registers *******************************/
//Define where in the memory the RTC start address peripheral is located
#define RTC_BASE         0x40002800 // See reference manual page 52
 
//Define the RTC register map. See reference manual section 18.4.7 page 474
uint32_t volatile * const rtc_crh  = (uint32_t *) (RTC_BASE + 0x0); 
uint32_t volatile * const rtc_crl  = (uint32_t *) (RTC_BASE + 0x4);
uint32_t volatile * const rtc_prlh = (uint32_t *) (RTC_BASE + 0x8);
uint32_t volatile * const rtc_prll = (uint32_t *) (RTC_BASE + 0xc);
uint32_t volatile * const rtc_divh = (uint32_t *) (RTC_BASE + 0x10);
uint32_t volatile * const rtc_divl = (uint32_t *) (RTC_BASE + 0x14);
uint32_t volatile * const rtc_cnth = (uint32_t *) (RTC_BASE + 0x18);
uint32_t volatile * const rtc_cntl = (uint32_t *) (RTC_BASE + 0x1c);
uint32_t volatile * const rtc_alrh = (uint32_t *) (RTC_BASE + 0x20);
uint32_t volatile * const rtc_alrl = (uint32_t *) (RTC_BASE + 0x24);

//Define where in the memory the RCC start address peripheral is located
#define RCCBASE         0x40021000
//unsigned int volatile * const rcc_apb1en = (unsigned int *) 0x4002101c;
uint32_t volatile * const rcc_apb1en = (uint32_t *) (RCCBASE + 0x1c);
uint32_t volatile * const rcc_bdcr = (uint32_t *) (RCCBASE + 0x20);

//Define where in the memory the PWR start address peripheral is located
#define PWR_BASE         0x40007000
uint32_t volatile * const pwr_cr = (uint32_t *) PWR_BASE + 0x0;

/****************************** Constants *********************************/
//Days in normal year, jan,feb .. dec (Not leap year)
uint8_t const daysmd[12] =  {31,28,31,30,31,30,31,31,30,31,30,31};

char const * const rtctxts[] = {
    "RTC: Illegal year. Valid range 1970 to 2106.",
    "RTC: Illegal month. Valid range 0 to 11. (0 = January, 1 = febrary ...)",
    "RTC: Illegal date in month.",
    "RTC: Illegal date in month. Not a leap year",
    "RTC: Illegal hour. Valid range 00 to 23",
    "RTC: Illegal minut. Valid range 00 to 59",
    "RTC: Illegal second. Valid range 00 to 59",
    "RTC: ERROR: RTC never ready",
    "RTC: ERROR in rtcsetcnt 1: RTOFF never 1",
    "RTC: ERROR in rtcsetcnt 2: RTOFF never 1",
    "RTC: ERROR could not set counter in 10 attempts",
    "RTC: Enabling and programming the RTC (Real Time Clock) for first time use",
    "RTC: ERROR in rtccnt: RSF never 1",

};
/*************************** FUNCTION HEADER ******************************
Name....: rtc_protect
Author..: heth
***************************************************************************
Abstract: Enable or disable STM32F107VC RTC write protect
          The RTC is automatically write protected against parasitic write
          access. To write to the RTC registers write protect must be disabled.
          
Inputs..: uint8_t wr_enable  0     : Disable writeprotect 
                             >=1   : Enable writeprotect
Outputs.: none 
ToDo....: Wanted enhancements
***************************************************************************
Modification log:
**************************************************************************/
void rtc_protect(uint8_t wr_enable) {
	if ( wr_enable == TRUE ) {
			*pwr_cr 	&= ~(1 << 8); 	// Enable Write protect
	} else {
			*pwr_cr 	|= 1 << 8;	// Disable write protect
	}
}

/*************************** FUNCTION HEADER ******************************
Name....: rtc_init
Author..: heth
***************************************************************************
Abstract: Initialize STM32F107VC rtc
           - If rtc not started initialize it for first time use
           - If rtc already started and running - just enable it
Inputs..: none
Outputs.: Returns 0 on succes - pointer to textstring on error 
ToDo....: Wanted enhancements
***************************************************************************
Modification log:
**************************************************************************/
char const *rtc_init(void) {
	uint32_t i;
	*rcc_apb1en |= 1 << 27;  	// BKPEN = 1 (Backup interface clock enable)
	*rcc_apb1en |= 1 << 28; 	// PWREN = 1 (Power interface clock enable)
	*pwr_cr     |= 0x3 << 5;  // PLS[2:0] = 011 Threshold 2,5 volt (VDD = 3.3 volt)
	*pwr_cr			|= 1 << 4;		// Enable Power voltage detector
	
	if ( (*rcc_bdcr &= 1 << 15) == 0 || (*rcc_bdcr &= 1 << 1) == 0 ) { // If RTC not running
		// Programm the RTC for first time use - or after battery swap
			rtc_protect(FALSE);			// Disable write protect
			*rcc_bdcr	|= 0x1 << 8;	// Select external XTAL - LSE
			*rcc_bdcr	|= 1;		 			// LSEON
			*rcc_bdcr	|= 1 << 15;		// RTC on
			for (i=0; (i < COUNT_TO) && ( (*rcc_bdcr & (1 << 1) ) == 0 ); i++);// Wait LSE ready
			if (i == COUNT_TO) {
				// ERROR: LSE oscillator never ready 
				rtc_protect(TRUE);	// Enable write protect
				return( rtctxts[7] ); 
			}
	
			*rtc_prlh = 0x7fff;			// Setting prescaler to 32767 (0x7fff)
			// Clock not set - eventually prompt user for time here
			rtc_protect(TRUE);	// Enable write protect
	}
			//5. Poll RSF, wait until its value goes to ‘1’ to ensure registers are synchronized.


	return(0);
}
// ToDO: Prettyfy to end
/* 1. Poll RTOFF, wait until its value goes to ‘1’
2. Set the CNF bit to enter configuration mode
3. Write to one or more RTC registers
4. Clear the CNF bit to exit configuration mode
5. Poll RTOFF, wait until its value goes to ‘1’ to check the end of the write operation.
*/

char const *rtcsetcnt( uint32_t value) {
	uint32_t i;
	uint8_t attempts;
	
	/* To avoid overflow from from rtc_cntl to rtc_cnth when writing
	to the registers. A max of 10 attempts setting the clock.*/
	attempts = 0;
	do {
		rtc_protect(FALSE); 		// Disable Write protect PWR_CR.DBP=1

		// 1. Poll RTOFF, wait until its value goes to ‘1’
		for( i=0; ( i < COUNT_TO ) && !( *rtc_crl & 1<<5); i++ ); 
		if ( i == COUNT_TO ) { return( rtctxts[8] ); }	//ERROR RTCOFF never ready

		// 2. Set the CNF bit to enter configuration mode
		*rtc_crl |= 1 << 4;

		// 3: Write to counter high and low. Check high not changed in loop
		*rtc_cnth = (value >> 16) & 0xffff;
		*rtc_cntl = value & 0xffff;
	
		// 4. Clear the CNF bit to exit configuration mode
		*rtc_crl &= ~(1 << 4); // The write operation starts when CNF is cleared
	
		//5. Poll RTOFF, wait until its value goes to ‘1’ to check the end of the write operation.
		for( i=0; ( i < COUNT_TO ) && !( *rtc_crl & 1<<5) ;i++ );
		if ( i == COUNT_TO ) { return( rtctxts[9] ); }	//ERROR RTCOFF never ready

		*pwr_cr &= ~(1 << 8);	// Enable write protect

		//if ( (*rtc_cnth == (value >> 16) & 0xffff) && ( *rtc_cntl == (value &= 0xffff)) ) {
		if ( rtccnt() == value ) { // Did we succed in writing the counter
			return(0);			   //Yes we did
		}
		attempts++;
	} while ( attempts < 10 );
	return( rtctxts[10] );
}
 
/*************************** FUNCTION HEADER ******************************
Name....: rtccnt
Author..: heth
***************************************************************************
Abstract: Reads the 32 bit counter of the RTC on STM ARM STM32F107VC uc
          The 32 bit counter is contained in two 16 bit registers rtc_cnth 
          (highest 16 bits) and rtc_cntl (lowest 16 bits)
Inputs..: none 
Outputs.: Returns uint32_t containing rtc counter value
Status..: Local
ToDo....: 
***************************************************************************
Modification log:
**************************************************************************/
uint32_t rtccnt( void ) {	
	unsigned int low,high,i;
	unsigned char count;
	
	count = 0;
	
	// The do-while block below checks if the RTC counter - which are living
	// in another time domain - changes higher 16 bits before we read lower 16
	// bits. (If lower contains 0xffff and high contains for example 0x0100
	// If we read 0x0100 from higher and before we read lower it increments 
	// from 0xffff to 0x0000 we would erroneos belive the counter was
	// 0x0100If low transits to 0x0000 before we reads 
	// higher 16 bits - 0x0100 0000 instead of 0x0100 fffff or 0x0101 0000
	do {
		*rtc_crl &= ~(1<<3);	//Clear RSF and wait for synchronization 
		for( i=0; ( i < COUNT_TO ) && !( *rtc_crl & 1<<3) ;i++ ); // Wait for Registers synchronized
		if ( i == COUNT_TO ) { return( 4 ); }	//ERROR RSF never ready - 4 seconds means this error
		
		high = (int) *rtc_cnth & 0xffff;
		low  = (int) *rtc_cntl & 0xffff;
		
		count++;

	} while ( ( high != (*rtc_cnth & 0xffff) ) && ( count <= 2 ) ); 
	// If high and low reads wrong in two attempts - somwthing is wrong ;-( 
	if ( count >= 2 ) { // 
		// Error condition RTC unstable..
		// Put error reporting/recovery code here....
		printf("ERROR: RTC rtccnt() error. Count reach %i. (High = %i low = %i )\n",count,high, low );
		return(0); //Time not valid return epoch
	}
	return( (high << 16) | low );
}

/* time2epoch  calculates epoch from initialized TimeStruct structure t.
t must contain year, mon, mday, hour, min, sec
returns pointer to string if error 0 if succesfull.
Returned string is a null-terminated error description.
Only first discovered error returned  */

char const *time2epoch( struct TimeStruct *t) {
        int i;
        t->epoch = 0; // initialize to 1. jan 1970 00:00:00

        /* Add year in seconds */
        if ( ( t->year < 1970 ) || ( t->year  > 2106 ) ) { // Check valid year
                return( rtctxts[0]);
        }
        for ( i = 1970;  t->year > i; i++ ) { // Counting from 1970 to current year

                if ( i % 4 == 0 ) { // Is year a possible leap year
                        t->epoch += SECONDSDAY; // Possible leap year add a day in seconds
                                                                                                // Note 1970 is not a leap year. t->epoch newer negative
                        if (  ( i % 100 == 0 ) && ( i % 400 != 0 ) ) { // 400 years rule
                                t->epoch -= SECONDSDAY;    // Divisble by 100 but not 400 remove the leap day again
                        }
                }
                t->epoch += SECONDSYEAR;  // add a normal 365 day year
        }

        /* Add month(s) in year in seconds */
        if ( t->mon  > 11 ) { // Check valid month
                return( rtctxts[1] );
        }
        for (i=0; t->mon > i; i++) {

        	/* If february and leapyear add one day */
        	if ( ( i == 1) && ( t->year % 4 == 0 ) ) { // Possible leap year
            	    t->epoch += SECONDSDAY; // Possible leap year substract a day in seconds

                	if (  ( t->year % 100 == 0 ) && ( t->year % 400 != 0 ) ) { // 400 years rule
                	t->epoch -= SECONDSDAY;    // Divisble by 100 but not 400 remove the leap day again
            	}
        	}
        t->epoch += daysmd[i]*SECONDSDAY;
        }

        /* Add day in month in seconds */
        if ( ( t->mday < 1 ) || ( t->mday > daysmd[ t->mon ] ) ) { // Check valid date
                if (  (t->year % 4 != 0) || ( t->mon != 1 ) ) {  // Not a leap year allowing 29. of february
                        return( rtctxts[2] );
                }
                // february and possible leap year passing here. (Check for 100 and 400 years rule)
                if ( (t->year % 100 == 0 ) && ( t->year % 400 != 0 ) ) {
                        if ( t->mday == 29 ) {
                                return( rtctxts[3] );
                        } else {
                                return( rtctxts[2] );
                        }
                }
                // February and leap-year passing here
            if ( t->mday > 29 ) {
                return( rtctxts[2] );
                }
        // February 29. in a leap year is legal
        }
        t->epoch += ( ( t->mday - 1 ) * SECONDSDAY); // Substract one day. Day not passed yet!

        /* Add hours in seconds */
        if (   t->hour  > 23  ) { // Check valid hour 00 -> 23
                return( rtctxts[4] );
        }
        t->epoch += t->hour * 60 * 60;

        /* Add minutes in seconds */
        if (  t->min  > 59  ) { // Check valid minute 00 -> 59
                return( rtctxts[5] );
        }
        t->epoch += t->min * 60;

        /* Add  seconds */
        if (  t->sec  > 59  ) { // Check valid second 00 -> 59
                return( rtctxts[6] );
        }
        t->epoch += t->sec;

        /* Adjust for timezone */
        t->epoch -= t->tz;
        return(0);
}

/* No errorchecks performed. */
void epoch2time( struct TimeStruct *t) {
			int i;
			uint32_t epoch;
        
        epoch = t->epoch + t->tz; // Adjust to local timezone
        t->year=1970;   // Initialize structure

        /* Find weekday */
        /* 0 = Sunday, 1 = Monday....*/
        /* epoch 1. jan 1970 was a thursday = 4 */
        t->wday = ( (epoch / SECONDSDAY) + 4 ) % 7;

        /* Find year */
        while ( epoch >= SECONDSYEAR ) {  
            if ( t->year % 4 == 0 ) { // Is year a possible leap year
            	if (  ( t->year % 100 == 0 ) && ( t->year % 400 != 0 ) ) { // 400 years rule
                   	epoch -= SECONDSYEAR;    // Divisble by 100 but not 400. Not a leap year
					t->year++;
                } else { // A leap year
				/* Need to consider when it is 31. december in a leap year.
				   epoch will be >= SECONDSYEAR */
					if ( epoch >= ( SECONDSYEAR + SECONDSDAY ) ) { // If it is a full leapyear
						epoch -= SECONDSYEAR; // A full year
						epoch -= SECONDSDAY;  // A leap day
						t->year++;
					} else {
						break;  // 31. december in a leap year.  SECONDSYEAR + SECONDSDAY <= epoch >= SECONDSYEAR
					}
				}	
            } else { // Not a leap year
            	epoch -= SECONDSYEAR;  // Substract a normal 365 day year
            	t->year++; // Add a year
			}
        }
        /* Find month */
        for (t->mon=0; epoch >= daysmd[t->mon]*SECONDSDAY; t->mon++) { // 0=jan, 1=feb...
          /* If february and leapyear substract one day. Except if it is the 29. of february */
					if ( (( t->year % 4 == 0 ) && ( t->year % 100 != 0 )) || ( t->year % 400 == 0 ) ) { // A leap year
						if ( t->mon == 1 ) {
							if ( epoch > (daysmd[t->mon]*SECONDSDAY) + SECONDSDAY ) { // Are we NOT in february
								epoch -= SECONDSDAY;
							} else {
								break; // 29'th of february in a leap year
							}
						}
					}	
          epoch -= daysmd[t->mon]*SECONDSDAY;
        }


        /* find date */
        t->mday=1;
        for (i=0; epoch >= SECONDSDAY; i++ ) {
                t->mday++;
                epoch -= SECONDSDAY;
        }

        /* find hour */
        t->hour=0;
        while ( epoch >= 60*60 ) {
                t->hour++;
                epoch -= 60*60;
        }

        /* find minute */
        t->min=0;
        while ( epoch >= 60 ) {
                t->min++;
                epoch -= 60;
        }

        /* Remaining must be second */
        t->sec = epoch;

}

header file

#include <stdint.h>
extern char const * const rtctxts[];

/* Days in month's non-leap year jan, feb...*/
extern const uint8_t daysmd[];
/* Calculting current time from epoch in seconds.
Epoch: 1/1-1970 00:00:00

Leap year: In a leap year february has 29 days instead of normal 28.
 A leap year is defined as a year evenly divisble by 4 or 400. Execpt for years
 evenly divisble by 100 but not 400.
*/

#define SECONDSDAY  (24*60*60)
#define SECONDSYEAR (365*SECONDSDAY)

struct TimeStruct {
        uint32_t    epoch;      // Seconds since 1. jan 1970 00:00:00
				uint32_t     tz;         // Timezone +/- x seconds from GMT
        uint32_t    year;       // year                 1970 to 2106
        uint32_t     sec;        // seconds                          00 to 59
        uint32_t     min;        // minutes                          00 to 59
        uint32_t     hour;       // hours                            00 to 23
        uint32_t     mday;       // day of the month         1 to 31
        uint32_t     mon;        // month                            0 to 11
        uint32_t     wday;       // day of the week          0=sunday, 1=monday...
        uint32_t    isdst;       // is Daylight Saving Time (DST)
                                 // isdst: 0 = not dst 1 = dst  2=Not used
        uint32_t     dstOnMon;   // Month (0-11) when DST starts
        uint32_t     dstOnMday;  // Day of month when DST starts
        uint32_t     dstOnHour;  // Hour of the day when DST starts
        uint32_t     dstOnMin;   // Minute of the hour when DST starts
        uint32_t     dstOffMon;  // Month (0-11) when DST ends
        uint32_t     dstOffMday; // Day of month when DST ends
        uint32_t     dstOffHour; // Hour of the day when DST ends
        uint32_t     dstOffMin;  // Minute of the hour when DST ends
};

extern char const *time2epoch( struct TimeStruct *t);
extern void epoch2time( struct TimeStruct *t);
extern char const *rtc_init(void);
extern unsigned int rtccnt( void );
extern char const *rtcsetcnt( uint32_t value);

Initializing the RTC

Goals
Enabling the RTC
One seconds TICK
No interrupts enable
Initializing second counter to present time (Epoch 1/1-1970 00:00:00)
Using external XTAL
Write protect against accidental writes

Enabling the RTC

Power on Init

  1. To enable the RTC it is necessary to enable the Power interface in the RCC->APB1ENR register by setting the PWREN bit to 1. [1]
  2. To select the voltage threshold when the RTC switch to battery power. E2PROM minimum operating voltage is 2.5 V. In the PWR->CR register set PLS[2:0] to 011[2]
  3. To enable the Power voltage detector in the PWR->CR register set PVDE to 1.[3]

Checking if RTC running

  1. In the RCC->BDCR Register check if the RTCEN bit is 1 and the LSEON is 1 and LSERDY is 1 - If not the RTC is not running and need programming. (See below)[4]

Programming the RTC

  1. To disable write protection to the Backup domain control register - enabling configuration of the RTC in the PWR->CR register setting the DBP bit to 1. [2]
  2. To select LSE (Low Speed External XTAL) as clocksource in the RCC->BDCR register bits RTCSEL[1:0] = 01 [4]
  3. To turn on the LSE in the RCC->BDCR register bit LSEON = 1.[5]
  4. Wait in while loop (timed out for error check) for the LSE to be ready in RCC->BDCR register bit LSERDY.[6]
  5. Setting the prescaler of the RTC counter - assuming a 32,768 KHz XTAL - in register RTC->PRLH = 0 and RTC->PRLL = 0x7fff.[7]
  6. Setting the Counter to the current time in seconds since epoch. (Set the RTC->CNTH before the RTC->CNTL avoiding RTC->CNTL = 0xffff incrementing RTC->CNTH before writing to it.) [8]
  7. To enable write protection to the Backup domain control register - disableing configuration of the RTC in the PWR->CR register setting the DBP bit to 0. [9]

Calibrating the RTC

IN the Backup Register[10] See RTC Calibration Application Note

Example

Links

References

  1. Reference manual section 8.3.8 page 144
  2. 2.0 2.1 Reference manual section 5.4.1 page 75
  3. Reference manual section 5.4.1 page 75
  4. 4.0 4.1 Reference manual section 8.3.9 page 146
  5. Reference manual section 8.3.9 page 146
  6. Reference manual section 8.3.9 page 146
  7. Reference manual section 18.4.3 page 470
  8. Reference manual section 18.4.5 page 472
  9. Reference manual section 5.4.1 page 75
  10. Reference manual section 6 page 79