Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

GCRealTimeClock.cpp

Committer:
jmitc91516
Date:
2017-07-31
Revision:
8:26e49e6955bd
Parent:
1:a5258871b33d

File content as of revision 8:26e49e6955bd:

/*
    This class consists of static functions that handle the real time clock [RTC] on the GC.
    It contains only static functions, and cannot be instantiated.
*/

#include "GCRealTimeClock.h"


/*
    Gets the various time fields from the relevant GC registers
    using the "QRTC000r" command, where 'r' is the register number
    for each field. Returns the command responses, as strings,
    in the buffers specified.
    
    Note that, to avoid rollover (see comments in function 'GetGCClockTime'),
    we do minimal processing between getting each response.

    Args: a pointer to the USBHostGC instance that corresponds to the GC,
          a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
          pointers to the buffers to contain the time fields - it is up to the caller
          to make sure these are large enough for a GC response 
          
    Returns true if it succeeded, false if it failed (e.g. the GC gave an error, etc)
*/
bool GCRealTimeClock::GetTimeFieldsFromGC(USBDeviceConnected* usbDevice, 
                                          USBHostGC* usbHostGC, 
                                          char *gcSecondsResponse, 
                                          char *gcMinutesResponse, 
                                          char *gcHoursResponse, 
                                          char *gcDateResponse, 
                                          char *gcMonthResponse, 
                                          char *gcYearResponse)
{
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0000", gcSecondsResponse);
    if(gcSecondsResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0001", gcMinutesResponse);
    if(gcMinutesResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0002", gcHoursResponse);
    if(gcHoursResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0004", gcDateResponse);
    if(gcDateResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0005", gcMonthResponse);
    if(gcMonthResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}
    usbHostGC->SetDeviceReport(usbDevice, "QRTC0006", gcYearResponse);
    if(gcYearResponse[0] == 'E') {
        // Assume "EPKT"
        return false;
    }
    
    // If we get here, all the above must have succeeded
    return true;
}

/*
    Constructs a time value from the GC responses returned by 'GetTimeFieldsFromGC()'.
    Fills a 'tm' struct - see the <ctime> (time.h) system include file.
    
    We expect the responses to look like "DRTCnnnn", where "nnnn" is the value 
    of the relevant register, so we expect the register value to start in array element 4.
    
    For a full description of the register values, see the DS1307 Real-Time Clock documentation,
    which can currently be found here:
    
        V:\Development\500 Series GC\Main Instrument\Documentation\Electronic\DS1307 IIC RTCC Chip.pdf
    
    [V: is the Ellutia File Server.]
    
    *** The register values are in binary coded decimal format. ***


    Args: a pointer to a 'tm' struct to be filled with the time value
          pointers to the buffers that contain each of the time fields
          returned by the GC in our 'GetTimeFieldsFromGC()' function
          
    No return code.
*/
void GCRealTimeClock::MakeTimeValueFromGCTimeFields(struct tm *timeValue,
                                                    char *gcSecondsResponse, 
                                                    char *gcMinutesResponse, 
                                                    char *gcHoursResponse, 
                                                    char *gcDateResponse, 
                                                    char *gcMonthResponse, 
                                                    char *gcYearResponse)
{
    int secondsValue;
    sscanf(&gcSecondsResponse[4], "%d", &secondsValue);
    
    int secondsUnitsDigit = secondsValue & 0xF; 
    int secondsTensDigit  = (secondsValue & 0x70) >> 4;
    timeValue->tm_sec = secondsUnitsDigit + (secondsTensDigit * 10);
    
    
    int minutesValue;
    sscanf(&gcMinutesResponse[4], "%d", &minutesValue);
    
    int minutesUnitsDigit = minutesValue & 0xF; 
    int minutesTensDigit  = (minutesValue & 0x70) >> 4;
    timeValue->tm_min = minutesUnitsDigit + (minutesTensDigit * 10);
    
    
    int hoursValue;
    sscanf(&gcHoursResponse[4], "%d", &hoursValue);
    
    // Note that, while the GC real-time clock can work in 12 or 24 hour mode,
    // the 'tm' struct can only handle 24 hour clock values.
    int hoursUnitsDigit = hoursValue & 0xF;
    int hoursTensDigit = 0;
    if(hoursValue & 0x40) {
        // 12 hour mode - hours range is 01-12 - 
        // but tm struct needs hours to start at zero, not one
        hoursTensDigit = (hoursValue & 0x10) >> 4;
        timeValue->tm_hour = hoursUnitsDigit + (hoursTensDigit * 10) - 1;
        if(hoursValue & 0x20) {
            // pm (else am)
            timeValue->tm_hour += 12;
        }
    } else {
        // 24 hour mode - hours range is 00-23
        hoursTensDigit = (hoursValue & 0x30) >> 4;
        timeValue->tm_hour = hoursUnitsDigit + (hoursTensDigit * 10);
    }
    

    int dateValue;
    sscanf(&gcDateResponse[4], "%d", &dateValue);
    
    int dateUnitsDigit = dateValue & 0xF; 
    int dateTensDigit  = (dateValue & 0x30) >> 4;
    timeValue->tm_mday = dateUnitsDigit + (dateTensDigit * 10);
    
    
    int monthValue;
    sscanf(&gcMonthResponse[4], "%d", &monthValue);
    
    // GC month value range is 1-12 - but 'tm' struct month range is 0-11
    int monthUnitsDigit = monthValue & 0xF; 
    int monthTensDigit  = (monthValue & 0x10) >> 4;
    timeValue->tm_mon = monthUnitsDigit + (monthTensDigit * 10) - 1;
    
    
    int yearValue;
    sscanf(&gcYearResponse[4], "%d", &yearValue);
    
    // GC year value is 00-99 - but 'tm' struct year value
    // is the number of years since 1900
    int yearUnitsDigit = yearValue & 0xF; 
    int yearTensDigit  = (yearValue & 0xF0) >> 4;
    timeValue->tm_year = yearUnitsDigit + (yearTensDigit * 10) + 100;
}


/*
    Get the time and date from the GC's real time clock,
    and return it in a time_t variable provided by the caller
    
    Args: a pointer to the USBHostGC instance that corresponds to the GC,
          a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
          a pointer to a time_t variable, which we will set to the GC time
          
    Returns true if it succeeded, false if it failed (e.g. the GC gave an error, etc)
*/
bool GCRealTimeClock::GetGCClockTime(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, time_t *clockTime)
{
    // The seconds, minutes, hours, date (i.e. day of the month), month and year are kept in separate registers
    // in the GC, so we get them separately. This means we must guard against 'rollover' - 
    // e.g. the GC time when we get the minutes is 11:59, but when we get the hours 
    // it is 12:00, so we think the time is 12:59. 
    //
    // We do this in two ways:
    //
    //  1 - we do minimal processing between getting each value (see also function 'GetTimeFieldsFromGC')
    //  2 - we get the time twice - if the first value is greater than the second, 
    //      we assume that the first must have rolled over, so we use the second,
    //      otherwise we use the first (they surely cannot both rollover unless
    //      they are exactly 60 seconds apart, which is very unlikely - or 60 minutes, 
    //      1 day, etc, which is even more unlikely). Also note that, since we get 
    //      the components in ascending order of 'size', any rollover will always 
    //      make the time value greater than it should be, not less.
    

    // Date and time value 1
    char seconds1[20];
    char minutes1[20];
    char hours1[20];
    char date1[20];
    char month1[20];
    char year1[20];
    if(!GetTimeFieldsFromGC(usbDevice, usbHostGC, seconds1, minutes1, hours1, date1, month1, year1)) {
        return false;
    }

    // *** Do nothing between these two 'GetTimeFieldsFromGC' calls ***
    
    // Date and time value 2
    char seconds2[20];
    char minutes2[20];
    char hours2[20];
    char date2[20];
    char month2[20];
    char year2[20];
    if(!GetTimeFieldsFromGC(usbDevice, usbHostGC, seconds2, minutes2, hours2, date2, month2, year2)) {
        return false;
    }
    
    
    struct tm time1;
    MakeTimeValueFromGCTimeFields(&time1, seconds1, minutes1, hours1, date1, month1, year1);

    time_t time_t1 = mktime(&time1);
    
    
    struct tm time2;
    MakeTimeValueFromGCTimeFields(&time2, seconds2, minutes2, hours2, date2, month2, year2);

    time_t time_t2 = mktime(&time2);
    
    
    // time_t1 should be less than time_t2, since we obtained the values from which we calculated it (slightly) earlier.
    // If so, use time_t1. If not, assume one or more of time_t1's constituent values must have been affected by rollover, 
    // and use time_t2. (Note that if time_t2 was affected by rollover, its value will (even more) be greater than time_t1,
    // so we will ignore it anyway.)

    *clockTime = (time_t1 < time_t2) ? time_t1 : time_t2;    
    
    return true;
}


/*
    Get the time and date from the GC's real time clock,
    and set the LPC4088 RTC to match.
    
    Args: a pointer to the USBHostGC instance that corresponds to the GC,
          a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
          
    Returns true if it succeeded, false if it failed (e.g. the GC gave an error, etc)
*/
bool GCRealTimeClock::SetLPC4088RealTimeClockToMatchGC(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    time_t timeToSet;
    
    if(GetGCClockTime(usbDevice, usbHostGC, &timeToSet)) {
    
        set_time(timeToSet);
        
        return true;
    }
    
    // 'else' ...
    return false;
}