IntRTCC
PIC18 RTCC Module
This module allows you to use the on-board internal RTCC of many of the newer PIC18F devices.
From the datasheet:
The key features of the Real-Time Clock and Calendar (RTCC) module are:
- Time: hours, minutes and seconds
- 24-hour format (military time)
- Calendar: weekday, date, month and year
- Alarm configurable
- Year range: 2000 to 2099
- Leap year correction
- BCD format for compact firmware
- Optimized for low-power operation
- User calibration with auto-adjust
- Calibration range: ?2.64 seconds error per month
- Requirements: external 32.768 kHz clock crystal
- Alarm pulse or seconds clock output on RTCC pin
The RTCC module is intended for applications, where accurate time must be maintained for an extended period with minimum to no intervention from the CPU. The module is optimized for low-power usage in order to provide extended battery life while keeping track of time. The module is a 100-year clock and calendar with automatic leap year detection. The range of the clock is from 00:00:00 (midnight) on January 1, 2000 to 23:59:59 on December 31, 2099. Hours are measured in 24-hour (military time) format. The clock provides a granularity of one second with half-second visibility to the user.
The main limitation of the internal RTCC over an external solution is there is no independent battery backup for the module (Hint! hint! Microchip. A BATVCC input would be useful!) When the PIC power goes, the RTCC stops and loses its time. The module is unaffected by all other reset conditions however.
The module can be configured to run off an external 32.768 kHz crystal on the Timer1 input (SOSC) or from the internal RC oscillator (if present).
Usage
Structures
These two structures are provided to simplify access to the various registers.
TTime
Second as byte Minute as byte Hour as byte
TDate
Day as byte Month as byte Year as byte DayOfWeek as byte
Subroutines
The following subroutines are provided to control the module.
sub RTCEnable(pState as boolean)
pState - State. Can be TRUE (run RTCC) or FALSE (stop RTCC).
Starts or Stops the RTCC module running (if an appropriate clock is present).
sub AlarmEnable(pState as boolean)
pState - State. Can be TRUE (Alarm Enabled) or FALSE (Alarm Disabled).
Enables or Disables the Alarm function of the module.
compound sub Read(ReadItem)
ReadItem - Item to Read.
Reads realtime clock registers. Readitem can be a TTime Structure or TDate Structure, in which the current values are updated.
compound sub Write(ReadItem)
WriteItem - Item to Write.
Writes realtime clock registers. Writeitem can be a TTime Structure or TDate Structure, from which the updated values are written.
compound sub GetAlarm(ReadItem)
ReadItem - Item to Read.
Reads Alarm value registers. Readitem can be a TTime Structure or TDate Structure, in which the current values are updated.
compound sub Write(ReadItem)
WriteItem - Item to Write.
Writes Alarm value registers. Writeitem can be a TTime Structure or TDate Structure, from which the updated values are written.
sub SetAlarmMode(pMask As Byte, pChime As Boolean = false, pRepeat As Byte = $00)
pMask - Sets the Alarm trigger mask. Can be: EveryHalfSec EverySecond Every10Secs EveryMinute Every10Mins EveryHour EveryDay EveryWeek EveryMonth EveryYear pChime - Sets whether the Alarm should repeat indefinitely, ignoring the pRepeat value. pRepeat - Sets a limited number of times the Alarm should repeat (up to 255).
Configures the Alarm settings.
sub OutputEnable(pState As Boolean, pMode As Byte = OUTAlarm)
pState - State. Can be TRUE (OE pin is enabled) or FALSE (OE Pin is disabled). pMode - Output mode. Can be: OUTClock - Source clock (INTRC or EXT32). OUTSecond - Toggles at 1 Hz (as per RTCC). OUTAlarm - Toggles on Alarm event.
Sets whether the RTCC Output PIN is enabled, and if so, which mode the Pin operates in.
sub Initialize() Initializes the module to a default time and starts the module running.
Sample code
The sample code presented demonstrates most of the basic functionality of the module. It has been tested on a 18F27J53 device in both External and Internal Oscillator modes. No external hardware besides power (3v3) and a USB connection are required.
Device = 18F27J53 Clock = 48 Public Config OSC = INTOSCPLLO, ' internal osc 8MHz PLLDIV = 2, ' usb 48MHz CPUDIV = OSC1, ' 48MHz cpu clock FCMEN = OFF, IESO = OFF, WDTEN = OFF, WDTPS = 2048, STVREN = ON, XINST = OFF 'DEBUG = OFF '#option OSCSOURCE = EXT32 // set to EXT32 for external 32.768 kHz Crystal on Timer1 #option OSCSOURCE = INTRC // set to INTRC for internal 32.768 kHz Oscillator // Enable this option to use with the Microchip USB HID bootloader '#option org_reset = $1000 Include "system.bas" Include "IntRTCC.bas" // Needs to be near top as it has a config line? Include "usbcdc.bas" Include "convert.bas" Include "string.bas" Dim Timer As TTime Dim Time As TTime Dim Date As TDate Dim ResponseString As String Dim OutputMode As Byte Sub MenuDisplay() CDC.Write("D: Set Date", 13, 10) CDC.Write("T: Set Time", 13, 10) CDC.Write("I: Initalise RTCC", 13, 10) CDC.Write("P: Set Output pin mode", 13, 10) CDC.Write("A: Set One-off Alarm Time", 13, 10) CDC.Write("M: Set Minute Chime",13, 10) CDC.Write("S: Stop Alarm", 13, 10) End Sub Sub Menu(pMenuOption As String) If pMenuOption <> " " Then CDC.Write(">: Option: " + pMenuOption, 13, 10) EndIf Select pMenuOption Case " " // Menu MenuDisplay() Case "D" CDC.Write("Set Day (1-31): ") // Get Day CDC.Read(ResponseString) Date.Day = StrToDec(ResponseString) // Put it into the Date Structure CDC.Write(DecToStr(Date.Day), 13, 10) CDC.Write("Set Month (1-12): ") // Get Month CDC.Read(ResponseString) Date.Month = StrToDec(ResponseString) // Put it into the Date Structure CDC.Write(DecToStr(Date.Month), 13, 10) CDC.Write("Set Year (0-99): ") // Get year CDC.Read(ResponseString) Date.Year = StrToDec(ResponseString) // Put it into the Date Structure CDC.Write(DecToStr(Date.Year), 13, 10) CDC.Write("Set Day of week (0-Sun, 1-Mon, 2-Tues, 3-Weds, 4-Thurs, 5-Fri, 6-Sat): ") CDC.Read(ResponseString) Date.DayOfWeek = StrToDec(ResponseString) // Put it into the Date Structure CDC.Write(DecToStr(Date.DayOfWeek), 13, 10) IntRTCC.Write(Date) // Set the Date parameters using the Date Structure Case "T" CDC.Write("Set 24 Hour (0-23): ") // Get Hours CDC.Read(ResponseString) Time.Hour = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Hour), 13, 10) CDC.Write("Set Minutes (0-59): ") // Get Hours CDC.Read(ResponseString) Time.Minute = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Minute), 13, 10) CDC.Write("Set Seconds (0-59): ") // Get Hours CDC.Read(ResponseString) Time.Second = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Second), 13, 10) IntRTCC.Write(Time) // Set the Time parameters using the Time Structure Case "I" IntRTCC.Initialize() // Initialise to module defaults Case "P" CDC.Write("Set Output Pin (0-Off, 1-Output Clock, 2-Output Seconds, 3-OutputAlarm): ") CDC.Read(ResponseString) Select StrToDec(ResponseString) Case 0 OutputEnable(false) // Get Hours CDC.Write("Output Off", 13, 10) Case 1 OutputEnable(true, OUTClock) // Output the source clock (intRC or T0) CDC.Write("Output Clock", 13, 10) Case 2 OutputEnable(true, OUTSecond) // Output seconds (toggle every second) CDC.Write("Output Seconds", 13, 10) Case 3 OutputEnable(true, OUTAlarm) // Output the Alarm CDC.Write("Output Alarm", 13, 10) End Select Case "A" CDC.Write("Set 24 Hour (0-23): ") // Get Hours CDC.Read(ResponseString) Time.Hour = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Hour), 13, 10) CDC.Write("Set Minutes (0-59): ") // Get Minutes CDC.Read(ResponseString) Time.Minute = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Minute), 13, 10) CDC.Write("Set Seconds (0-59): ") // Get Seconds CDC.Read(ResponseString) Time.Second = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Second), 13, 10) IntRTCC.SetAlarm(Time) // Set the Alarm parameters using the Time Structure SetAlarmMode(EveryDay) // Will trigger once at the set time of day OutputEnable(true, OUTAlarm) // Set the output pin to Output the alarm signal AlarmEnable(true) // Enable the alarm Case "M" CDC.Write("Set Seconds (0-59): ") // Get time CDC.Read(ResponseString) Time.Second = StrToDec(ResponseString) // Put it into the Time Structure CDC.Write(DecToStr(Time.Second), 13, 10) IntRTCC.SetAlarm(Time) // Set the Alarm parameters using the Time Structure SetAlarmMode(EveryMinute, true) // Will trigger repeatedly each minute (Pin should toggle every minute at seconds set) OutputEnable(true, OUTAlarm) // Set the output pin to Output the alarm signal AlarmEnable(true) // Enable the alarm Case "S" AlarmEnable(false) // Disable the Alarm End Select End Sub #if OSCSOURCE = EXT32 // Start up the timer1 Oscillator if using it T1CON.3 = 1 #endif ReadTerminator = #13 OutputMode = OUTAlarm Clear(Timer) Output(PORTB.4) // RTCC output pin // wait for connection... Repeat Until Attached IntRTCC.RTCEnable(true) // Enable the RTCC While true If DataAvailable Then // Get responses from CDC terminal CDC.Read(ResponseString) Menu(Str.Uppercase(ResponseString)) EndIf IntRTCC.Read(Time, Date) // Read the RTCC into the Time & Date structures If Time <> Timer Then // Displays updated time every second when the second field changes Timer = Time CDC.Write(DecToStr(Time.Hour, 2),":",DecToStr(Time.Minute,2),":",DecToStr(Time.Second,2), " ") CDC.Write(DaysOfWeek(Date.DayOfWeek), " ", DecToStr(Date.Day,2),"/",DecToStr(Date.Month,2),"/",DecToStr(Date.Year,2), 13, 10) EndIf Wend
IntRTCC Module
{ ***************************************************************************** * Name : IntRTCC.BAS * * Author : Nathan Herbert * * Notice : Copyright (c) 2012 * * : All Rights Reserved * * Date : 27/09/2012 * * Version : 1.0 * * Notes : * * : * ***************************************************************************** } Module IntRTCC #option OSCSOURCE = INTRC #if IsOption(OSCSOURCE) And Not (OSCSOURCE in (INTRC, EXT32)) #error OSCSOURCE, "Invalid option. OSCSOURCE type not recognized. INTRC or EXT32 Only." #endif #if OSCSOURCE = INTRC Config RTCOSC = INTOSCREF #warning "Internal RC Accuracy is poor, even when calibrated." #else Config RTCOSC = T1OSCREF #endif Include "convert.bas" // time data structure... Public Structure TTime Second As Byte // Second (0..59) Minute As Byte // Minute (0..59) Hour As Byte // Hour (0..11 or 0..23) End Structure // date data structure... Public Structure TDate Day As Byte // Date (0..31) Month As Byte // Month (1..12) Year As Byte // Year (0..99) DayOfWeek As Byte // day of the week (0..6) End Structure Dim RTCEn As RTCCFG.Booleans(7), RTCWren As RTCCFG.Booleans(5), RTCSync As RTCCFG.Booleans(4), RTCOe As RTCCFG.Booleans(2), RTCCalib As RTCCAL, RTCPinConfig As PADCFG1, RTCConfig As RTCCFG, RTCValLow As RTCVALL, RTCValHigh As RTCVALH, MemControl As EECON2, AlarmEn As ALRMCFG.booleans(7), AlarmChime As ALRMCFG.booleans(6), AlarmConfig As ALRMCFG, AlarmRepeat As ALRMRPT, AlarmValLow As ALRMVALL, AlarmValHigh As ALRMVALH Const // RTCPTR and ALMPTR pointer values // RTCVAL<15:8> VALYear = %00100011, VALDay = %00100010, VALHours = %00100001, VALSeconds = %00100000, // RTCVAL<7:0> VALMonth = %00100010, VALWeekday = %00100001, VALMinutes = %00100000 Public Const DaysOfWeek(7) As String = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat"), // Output Configuration OUTClock = %00000100, OUTSecond = %00000010, OUTAlarm = %00000000, // Alarm settings DoW Month Day Hours Minutes Seconds (X = don't care) EveryHalfSec = %00000000, // X X X X X X X X X X X EverySecond = %00000100, // X X X X X X X X X X S Every10Secs = %00001000, // X X X X X X X X X X S EveryMinute = %00001100, // X X X X X X X X X S S Every10Mins = %00010000, // X X X X X X X X M S S EveryHour = %00010100, // X X X X X X X M M S S EveryDay = %00011000, // X X X X X H H M M S S EveryWeek = %00011100, // D X X X X H H M M S S EveryMonth = %00100000, // X X X D D H H M M S S EveryYear = %00100100 // X M M D D H H M M S S { **************************************************************************** * Name : RTCWriteEnable (PRIVATE) * * Purpose : Enables or Disables writes to RTCC registers * **************************************************************************** } Sub RTCWriteEnable(pState As Boolean) // The lock sequence seems to need to be ASM in order to // ensure the commands occur in the time required. // An interrupt occuring during the sequence would // disrupt it. So rather than disable the interrupts, // I prefer to just repeat the sequence until occurs // uninterrupted. Repeat If pState = true Then ' Lock sequence Asm movlb 0x0F movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf RTCCFG,5 End Asm Else ' Unlock sequence Asm movlb 0x0F movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bcf RTCCFG,5 End Asm EndIf Until RTCWren = pState End Sub { **************************************************************************** * Name : SetConfig * * Purpose : * **************************************************************************** } Sub SetRTCPointer(pState As Byte) RTCConfig = RTCConfig And %11111100 // Clear the PTR bits RTCConfig = RTCConfig Or pState // Set PTR bits to required End Sub { **************************************************************************** * Name : SetConfig * * Purpose : * **************************************************************************** } Sub SetAlarmPointer(pState As Byte) AlarmConfig = AlarmConfig And %11111100 // Clear the PTR bits AlarmConfig = AlarmConfig Or pState // Set PTR bits to required End Sub { **************************************************************************** * Name : RTCEnable * * Purpose : Enables or Disables the RTCC * **************************************************************************** } Public Sub RTCEnable(pState As Boolean) RTCWriteEnable(true) RTCEn = pState RTCWriteEnable(false) End Sub { **************************************************************************** * Name : AlarmEnable * * Purpose : Enables or Disables the Alarm * **************************************************************************** } Public Sub AlarmEnable(pState As Boolean) AlarmEn = pState End Sub { **************************************************************************** * Name : ReadItem (OVERLOAD) * * Purpose : Returns Time in the referenced variable * **************************************************************************** } Sub ReadItem(ByRef pTime As TTime) Dim dummy As Byte, Temp As TTime, Result As Boolean // Read at least twice as per datasheet to ensure we did not read during a rollover result = false Repeat SetRTCPointer(VALHours) // Set pointer to start position Temp.Hour = BCDToDec(RTCValLow) // Read Hours dummy = RTCValHigh // Read Dummy to decrement pointer Temp.Second = BCDToDec(RTCValLow) // Read Seconds Temp.Minute = BCDToDec(RTCValHigh) // Read Minutes SetRTCPointer(VALHours) // Set pointer to start position pTime.Hour = BCDToDec(RTCValLow) // Read Hours dummy = RTCValHigh // Read Dummy to decrement pointer pTime.Second = BCDToDec(RTCValLow) // Read Seconds pTime.Minute = BCDToDec(RTCValHigh) // Read Minutes // Do both reads match? If Temp = pTime Then result = true EndIf Until result = true End Sub { **************************************************************************** * Name : ReadItem (OVERLOAD) * * Purpose : Returns Date in the referenced variable * **************************************************************************** } Sub ReadItem(ByRef pDate As TDate) Dim dummy As Byte, Temp As TDate, Result As Boolean // Read twice as per datasheet to ensure we did not read during a rollover result = false Repeat SetRTCPointer(VALYear) // Set pointer to start position Temp.Year = BCDToDec(RTCValLow) // Read Year dummy = RTCValHigh // Read Dummy to decrement pointer Temp.Day = BCDToDec(RTCValLow) // Read Day Temp.Month = BCDToDec(RTCValHigh) // Read month and decrement pointer Temp.DayOfWeek = BCDToDec(RTCValHigh) // Read Day of Week SetRTCPointer(VALYear) // Set pointer to start position pDate.Year = BCDToDec(RTCValLow) // Read Year dummy = RTCValHigh // Read Dummy to decrement pointer pDate.Day = BCDToDec(RTCValLow) // Read Day pDate.Month = BCDToDec(RTCValHigh) // Read month and decrement pointer pDate.DayOfWeek = BCDToDec(RTCValHigh) // Read Day of Week // Do both reads match? If Temp = pDate Then result = true EndIf Until result = true End Sub { **************************************************************************** * Name : Read * * Purpose : Returns Time or Date in the referenced variable * **************************************************************************** } Public Compound Sub Read(ReadItem) { **************************************************************************** * Name : WriteItem (OVERLOAD) * * Purpose : Sets the Time in BCD format * **************************************************************************** } Sub WriteItem(pTime As TTime) Dim RTCCurrent As Boolean RTCCurrent = RTCEn // Save current state RTCEnable(false) // Ensure the RTCC is not running RTCWriteEnable(true) // Enable writes SetRTCPointer(VALHours) // Set pointer to start position RTCValLow = DecToBCD(pTime.Hour) // Get Hours SetRTCPointer(VALMinutes) // Set pointer to Minutes / Seconds RTCValLow = DecToBCD(pTime.Second) // Get Seconds RTCValHigh = DecToBCD(pTime.Minute) // Get Minutes RTCEnable(RTCCurrent) // Reset current state (Will disable writes) End Sub { **************************************************************************** * Name : WriteItem (OVERLOAD) * * Purpose : Sets the Date in BCD format * **************************************************************************** } Sub WriteItem(pDate As TDate) Dim RTCCurrent As Boolean RTCCurrent = RTCEn // Save current state RTCEnable(false) // Ensure the RTCC is not running RTCWriteEnable(true) // Enable writes SetRTCPointer(VALYear) // Set pointer to start position RTCValLow = DecToBCD(pDate.Year) // Get Year SetRTCPointer(VALMonth) // Set pointer to Month / Day RTCValLow = DecToBCD(pDate.Day) // Get Day RTCValHigh = DecToBCD(pDate.Month) // Get Month & decrement RTCValHigh = DecToBCD(pDate.DayOfWeek) // Get Day of week RTCEnable(RTCCurrent) // Reset current state (Will disable writes) End Sub { **************************************************************************** * Name : Write * * Purpose : Sets the Time or Date * **************************************************************************** } Public Compound Sub Write(WriteItem) { **************************************************************************** * Name : ReadAlarm (OVERLOAD) * * Purpose : Returns Alarm Time in the referenced variable * **************************************************************************** } Sub ReadAlarm(ByRef pTime As TTime) Dim dummy As Byte SetAlarmPointer(VALHours) // Set pointer to start position pTime.Hour = BCDToDec(AlarmValLow) // Read Hours dummy = AlarmValHigh // Read Dummy to decrement pointer pTime.Second = BCDToDec(AlarmValLow) // Read Seconds pTime.Minute = BCDToDec(AlarmValHigh) // Read Minutes End Sub { **************************************************************************** * Name : ReadAlarm (OVERLOAD) * * Purpose : Returns Alarm Date in the referenced variable * **************************************************************************** } Sub ReadAlarm(ByRef pDate As TDate) Dim dummy As Byte SetAlarmPointer(VALYear) // Set pointer to start position pDate.Year = BCDToDec(AlarmValLow) // Read Year dummy = AlarmValHigh // Read Dummy to decrement pointer pDate.Day = BCDToDec(AlarmValLow) // Read Day pDate.Month = BCDToDec(AlarmValHigh) // Read month and decrement pointer pDate.DayOfWeek = BCDToDec(AlarmValHigh) // Read Day of Week End Sub { **************************************************************************** * Name : Read * * Purpose : Returns Time or Date in the referenced variable * **************************************************************************** } Public Compound Sub GetAlarm(ReadAlarm) { **************************************************************************** * Name : WriteAlarm (OVERLOAD) * * Purpose : Sets the Alarm Time in BCD format * **************************************************************************** } Sub WriteAlarm(pTime As TTime) Dim AlarmCurrent As Boolean AlarmCurrent = AlarmEn // Get current state AlarmEnable(false) // Ensure alarm is disabled RTCWriteEnable(true) // Enable writes SetAlarmPointer(VALHours) // Set pointer to start position AlarmValLow = DecToBCD(pTime.Hour) // Get Hour SetAlarmPointer(VALMinutes) // Set pointer to Minutes / Seconds AlarmValLow = DecToBCD(pTime.Second) // Get Seconds AlarmValHigh = DecToBCD(pTime.Minute) // Get Minutes RTCWriteEnable(false) // Disable writes AlarmEnable(AlarmCurrent) // Reset current alarm state End Sub { **************************************************************************** * Name : WriteAlarm (OVERLOAD) * * Purpose : Sets the Alarm Date in BCD format * **************************************************************************** } Sub WriteAlarm(pDate As TDate) Dim AlarmCurrent As Boolean AlarmCurrent = AlarmEn // Get current state AlarmEnable(false) // Ensure alarm is disabled RTCWriteEnable(true) // Enable writes SetAlarmPointer(VALYear) // Set pointer to start position AlarmValLow = DecToBCD(pDate.Year) // Get Year SetAlarmPointer(VALMonth) // Set pointer to Month / Day AlarmValLow = DecToBCD(pDate.Day) // Get Day AlarmValHigh = DecToBCD(pDate.Month) // Get Month & decrement AlarmValHigh = DecToBCD(pDate.DayOfWeek) // Get Day of week RTCWriteEnable(false) // Disable writes AlarmEnable(AlarmCurrent) // Reset current alarm state End Sub { **************************************************************************** * Name : SetAlarm * * Purpose : Sets the Alarm, with its options * **************************************************************************** } Public Compound Sub SetAlarm(WriteAlarm) { **************************************************************************** * Name : SetAlarmMode * * Purpose : Sets the Alarm to the desired mode * **************************************************************************** } Public Sub SetAlarmMode(pMask As Byte, pChime As Boolean = false, pRepeat As Byte = $00) // Datasheet says ALRMCFG, ALRMRPT and CHIME should only be changed when RTCSync = 0 Repeat Until RTCSync = false AlarmChime = pChime // Chime? (Repeat the alarm forever, ignores pRepeat) AlarmRepeat = pRepeat // Repeat a limited number of times? $00 = Alarm once. // Set the Alarm mask (see constants above for logic) AlarmConfig = AlarmConfig And %11000011 // Zero the mask bits AlarmConfig = AlarmConfig Or pMask // Set the mask bits End Sub { **************************************************************************** * Name : OutputEnable * * Purpose : Sets the Output enable to the desired mode * **************************************************************************** } Public Sub OutputEnable(pState As Boolean, pMode As Byte = OUTAlarm) // Disable Pin to prevent glitching RTCOe = false // Set desired mode RTCPinConfig = RTCPinConfig And %11111001 RTCPinConfig = RTCPinConfig Or pMode // Set pin state RTCOe = pState End Sub { **************************************************************************** * Name : Initialize * * Purpose : Initializes the module * **************************************************************************** } Public Sub Initialize() Dim pTime As TTime, pDate As TDate pDate.Day = 1 // 1st pDate.Month = 1 // January pDate.Year = 12 // 2012 pDate.DayOfWeek = 0 // Sunday pTime.Hour = 0 // 12 midnight pTime.Minute = 0 pTime.Second = 0 Write(pTime, pDate) // Write the defaults RTCEnable(true) // Enable the Module End Sub