Project will not compile - says Multiple definitions of variables

BriComp

Well-known member
Hi,
I hope someone can help me.
EDIT: The code is independent of Teensy model and is demonstrated with Arduino 1.8.19.
I have attached software below which demonstrates the problem. When I compile the software I get the following two errors:
Code:
ld.exe: HHMRadioC\\HHMRadioC.cpp.o:c:\arduino programs\libraries\hhmradioc\hhmradioc.h:101: multiple definition of receivePacket; Demonstrate_Error.cpp.o:c:\arduino programs\libraries\hhmradioc\hhmradioc.h:101: first defined here
ld.exe: HHMRadioC\\HHMRadioC.cpp.o:c:\arduino programs\libraries\hhmradioc\hhmradioc.h:100: multiple definition of transmitPacket; Demonstrate_Error.cpp.o:c:\arduino programs\libraries\hhmradioc\hhmradioc.h:100: first defined here

collect2.exe*: error: ld returned 1 exit status

Error linking for board Teensy 4.0 (teensy40)
Build failed for project 'Demonstrate_Error'

The code and libraries that produce the error are as below. It's HHMRadioC.h that creates the problem.
I have removed most of the code from the HHMRadio.cpp as the error is still demonstrated with out it.

Demonstrate_Error.ino
Code:
#include <Arduino.h>
#include <HHMRadioC.h>

#ifndef excludeRtc
#include <TimeLib.h>        // https://github.com/PaulStoffregen/Time
#include <RTC_RV_3028_C7.h>     // https://github.com/constiko/RV-3028_C7-Arduino_Library
RV3028 rtc;
#endif

HHMRadioC radio;

void GetCompileTime() {
    int h, m, s, d, mo, y;

    h = ((int)__TIME__[0] - (int)'0') * 10 + ((int)__TIME__[1] - (int)'0');
    m = ((int)__TIME__[3] - (int)'0') * 10 + ((int)__TIME__[4] - (int)'0');
    s = ((int)__TIME__[6] - (int)'0') * 10 + ((int)__TIME__[7] - (int)'0');
    if (__DATE__[0] == 'J') {
        if (__DATE__[1] == 'a') {
            mo = 1;
        }
        else if (__DATE__[2] == 'n') {
            mo = 6;
        }
        else mo = 7;
    }
    else if (__DATE__[0] == 'F') {
        mo = 2;
    }
    else if (__DATE__[0] == 'M') {
        if (__DATE__[2] == 'r')
            mo = 3;
        else
            mo = 5;
    }
    else if (__DATE__[0] == 'A') {
        mo = 8;
    }
    else if (__DATE__[0] == 'S') {
        mo = 9;
    }
    else if (__DATE__[0] == 'O') {
        mo = 10;
    }
    else if (__DATE__[0] == 'N') {
        mo = 11;
    }
    else if (__DATE__[0] == 'D') {
        mo = 12;
    }
    if (__DATE__[4] != ' ') d = ((int)__DATE__[4] - (int)'0') * 10; else d = 0;
    d += ((int)__DATE__[5] - (int)'0');
    y = ((int)__DATE__[7] - (int)'0') * 1000 + ((int)__DATE__[8] - (int)'0') * 100 + ((int)__DATE__[9] - (int)'0') * 10 + ((int)__DATE__[10] - (int)'0');;
    setTime(h, m, s, d, mo, y);  // Set Teensy Time
}

static time_t getUnixTime() { return rtc.getUNIX(); }

uint8_t setRtcError = 0;

bool SetUpTime(bool trickleCharge = false) {

    bool isOk = true;

    Wire1.begin();
    if (rtc.begin(Wire1) == false) {
        Serial.println("Something went wrong, check wiring");
        return false;
    }
    else
    {
        Serial.println("RTC online!");
    }

    if (rtc.timeUpdateRequired) {
        Serial.println("Setting time from Teensy Compile Time");
        if (!SetRtcDateTime()) {
            Serial.print("Unable to set RTC Date / Time. Status Byte");
            Serial.println(setRtcError, HEX);
        }
    }

    setSyncProvider(getUnixTime);

    isOk = (timeStatus() == timeSet);

    if (!isOk)
        Serial.println("Unable to sync with the RTC");
    else
        Serial.println("RTC has set the system time");

    return isOk;
};

bool SetRtcDateTime() {
    GetCompileTime();
#ifdef debugSrdt
    Serial.print("now() = "); Serial.println(now());
    Serial.print("hour() = "); Serial.println(hour());
    Serial.print("minute() = "); Serial.println(minute());
    Serial.print("day() = "); Serial.println(day());
    Serial.print("month() = "); Serial.println(month());
    Serial.print("year() = "); Serial.println(year());
#endif
    if (!rtc.setUNIX( (uint32_t) now() )) { Serial.println("Unable to rtc.SetUNIX(now())");        setRtcError = 1; }
    if (!rtc.setHours( hour()           )) { Serial.println("Unable to rtc.setHours(hour())");    setRtcError |= 0b10; }
    if (!rtc.setMinutes( minute()       )) { Serial.println("Unable to rtc.minutes(minute())");    setRtcError |= 0b100; }
    if (!rtc.setSeconds( second()       )) { Serial.println("Unable to rtc.seconds(second())");    setRtcError |= 0b100; }
    if (!rtc.setDate( day()               )) { Serial.println("Unable to rtc.day(day())");            setRtcError |= 0b1000; }
    if (!rtc.setMonth(    month()           )) { Serial.println("Unable to rtc.month(month())");        setRtcError |= 0b10000; }
    if (!rtc.setYear( year()           )) { Serial.println("Unable to rtc.year(year())");        setRtcError |= 0b100000; }
    return (setRtcError == 0);
}

#define smstDBz
bool SendMessageSetTime(uint8_t toWhome) {
    transmitPacket.data.command = bSetLocalTime;
#ifdef smstDB
    Serial.print("Cmdz-"); Serial.print(transmitPacket.data.command, HEX); Serial.println(" ");
#endif
    radio.generateCommandMessage(bSetLocalTime);
    return radio.sendMessage(toWhome);
}

#define smrtDBz
bool SendMessageReportTime(uint8_t toWhome) {
//    transmitPacket.data.command = bSendLocalTime;
//    Serial.print("Cmdz-"); Serial.print(transmitPacket.data.command, HEX); Serial.println(" ");
    radio.generateCommandMessage(bSendLocalTime);
#ifdef smrtDB
    Serial.print("Cmdz-"); Serial.print(transmitPacket.data.command, HEX); Serial.println(" ");
#endif
    return radio.sendMessage(toWhome);
}

#define ptDB
void PrintTime( time_t t ){
    TimeElements tm;
#ifdef ptDB
    Serial.print("PrintTime: t "); Serial.println(t, HEX);
#endif
    breakTime(t, tm);
    Serial.print(tm.Day);     Serial.print("/");
    Serial.print(tm.Month);  Serial.print("/");
    Serial.print(tm.Year);   Serial.print("  ");
    Serial.print(tm.Hour);   Serial.print(":");
    Serial.print(tm.Minute); Serial.print(":");
    Serial.print(tm.Second); Serial.print("  ");
}

void setup() {

    Serial.begin(9600);
    while (!Serial && millis() < 3000);

    Serial1.begin(9600);

    TargetRadioTypes    radioSort        = thermostatRadio;    // boilerRadio, thermostatRadio, masterRadio
    RadioLocationIdTYPE radioLocationId = christopherBedroomId;

    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);

    radio.init(radioSort);
    if (!radio.setupRadio(radioLocationId, MODE_NORMAL, OPT_WAKEUP500, false))
        Serial.println("Unable to");
    else
        Serial.println("Done");
    Serial.println(" Initialise radio");

    Serial.println("Finished SetUp");

    digitalWrite(LED_BUILTIN, LOW);
//    Serial.print("Target Address "); Serial.print()
}

void loop(){
    Serial.print("Getting time from Target:   MyTime: "); PrintTime(now());
    Serial.print("Sending message requesting time ");
    if (SendMessageReportTime(tBoilerTestHarnessId)) Serial.println("OK"); else Serial.println("Doh");
    if (radio.gotRadioMessage(1000)) {
//        memcpy(&ReceivePacket, &receivePacket, sizeof(receivePacket));
//        ReceivePacket = receivePacket;
        Serial.print(   receivePacket.data.command, HEX);          Serial.print(" ");
        Serial.print(   receivePacket.data.header.ckSum, HEX);      Serial.print(" ");
        Serial.print(   receivePacket.data.header.senderId, HEX); Serial.print(" ");
        Serial.println( receivePacket.data.header.time, HEX);
        Serial.print("  Received Time: "); Serial.print(receivePacket.data.header.time,HEX);
        Serial.print("  TargetTime: ");    PrintTime(receivePacket.data.header.time);
    }else
    {
        Serial.print("No Reply from Target Radio");
    }
    Serial.println();
    delay(3000);
}

HHMRadioC.h
Code:
#pragma once

#ifndef HHMRadioC_H
#define HHMRadioC_H

/**************************************************************
*                                                              *
*                Converted from HHMRadio.h                      *
*        Instead of Master / Slave has Sender and Receiver     *
*                                                              *
***************************************************************/
#include <Arduino.h>

#include <HHM_EBYTE_E220.h>
#include "C:\Arduino Programs\HomeHeatingManagement\HHMDefines.h"

// connect to any of the Teensy Serial ports

/*#define RadioSerial Serial1

#define PIN_M0 2
#define PIN_M1 3
#define PIN_AX 4
#define auxPin PIN_AX
*/

#define Radio_MODE_TYPE MODE_TYPE

//addrType masterAddr, slaveAddr;
//#define addresses addrE220

enum ReceiveOrTransmitType {
    receive,
    transmit
};

enum TargetRadioTypes {
    boilerRadio, thermostatRadio, masterRadio
};

/*
* Error code generated by this library when incorrect number of characters returned by Nextion
*/

const uint8_t radioInvalidNumCharsSent        = 0x3F;

#pragma pack(push,1)

/***************************************************************************
 *                      Message Construction Types                       *
 ***************************************************************************/
/* The following now in HHMDefines.h
struct addrType {
    byte H;
    byte L;
    byte Chan;
};
*/
typedef struct {
    uint16_t        ckSum;
    uint8_t            senderId;
    time_t            time;
} HeaderType;

typedef struct {
    uint8_t            command;
    HeaderType        header;
    union {
        uint8_t                     valveChannel;
        uint16_t                 intTemp;  // int(temperature * 100 + 0.5)
        uint16_t                 t2;
        nextionEepromDataDayType eepromHwData;
        uint16_t                 largestData;
    };
} RootDataType;

const uint8_t        cSumPos           = 1;
const uint8_t        cSumPosPlusOne = 2;
const uint8_t        headerSize       = sizeof(HeaderType); // minus size of command

//const int sizeOfMasterToSlaveCommand = sizeof(MasterToSlaveCommandType);

const int sizeOfMinMsgDta = sizeof(HeaderType) + sizeof( byte );

typedef struct {
    radioAddrType       addr;
    union {
        RootDataType    data;
        uint8_t            dataArray[sizeof(RootDataType)];
    };
} TransmitPacketType;

typedef struct ReceivePacketType {
    union {
        RootDataType    data;
        uint8_t            dataArray[sizeof(RootDataType)];
    };
} ReceivePacketType;

TransmitPacketType    transmitPacket;
ReceivePacketType    receivePacket;

#pragma pack(pop)

class HHMRadioC {

    public:

        const uint8_t   instructionSuccess = 1;

        uint8_t            errorCode = instructionSuccess;

    //    void AddError(uint8_t a, uint8_t b) {}

        bool waitForRadioToComeAlive();

        void init(TargetRadioTypes target);

        bool setupRadio(uint8_t myId, MODE_TYPE modeAfterSetup, byte wakeUp, bool sleep);
        /*Could be         masterAddr
            OR            slaveId
            OR             sender
            OR             myId
        */

        void generateCommandMessage(byte cmnd);

        bool sendMessage(uint8_t whoTo);
        bool messageReply();

        bool receiveMessage();

        bool radioDataReady();

        bool gotRadioMessage(uint32_t timeout = 0);

        bool needToRespondToMessage();

        bool sendAck = true;

        void clearIpBuffer();

    private:
  
        bool            needsResponse = true;
        uint32_t        airCharDataRate;

};

#endif

HHMRadioC.cpp
Code:
#include "Arduino.h"
#include <HHMRadioC.h>

#define radioDb

static uint8_t auxPin;

Stream* _s;

uint32_t waitTilRadioTxBufferEmpty;

void AddError(uint8_t a, uint8_t b) {}

bool HHMRadioC::waitForRadioToComeAlive() {
    elapsedMillis rTime = 0;
    while ((digitalReadFast(auxPin) == LOW) && (rTime <= 4000)) {}
    return (rTime >= 4000);
}

void HHMRadioC::init(TargetRadioTypes target) {
}

#define srDB
bool HHMRadioC::setupRadio(uint8_t myId, MODE_TYPE modeAfterSetup, byte wakeUp, /*uint8_t pwrStatus, */bool sleep) {

    return true;// !fault;
}
#define cmdlDBz
uint8_t CalcMsgDataLen( uint8_t comd ) {
    uint8_t len = 0;
    return len;
};

/* THIS WAS RUBBISH ---- FIXED IT */
#define cmcDBz
uint16_t CalcMsgChecksum( ReceiveOrTransmitType tsmtOrRcv ) {
    uint16_t cSum = 0;
    return cSum;
}

void HHMRadioC::generateCommandMessage(byte cmnd) {
    transmitPacket.data.command = cmnd;
}

#define mrDB
bool HHMRadioC::messageReply() {
    return true;
};

#define smDB
bool HHMRadioC::sendMessage(uint8_t whoTo) {
//    transmitPacket.addr = radioAddr[whoTo];
#ifdef smDB

#endif
    return HHMRadioC::messageReply();
};

#define rmDBz
bool HHMRadioC::receiveMessage() {
    bool okState = false;
    return okState;
}

bool HHMRadioC::radioDataReady() {
    return true;// (transceiver.available() >= sizeOfMinMsgDta);
}

bool HHMRadioC::gotRadioMessage(uint32_t timeout) {

    bool            gotData = false;
    return gotData;
};

bool HHMRadioC::needToRespondToMessage() {
    return false;// needsResponse;
}
    void HHMRadioC::clearIpBuffer() {
//        transceiver.ClearBuffer();
    };

RTC_RV_3028_C7.h
Code:
/******************************************************************************
RV-3028-C7.h
RV-3028-C7 Arduino Library
Constantin Koch
July 25, 2019
https://github.com/constiko/RV-3028_C7-Arduino_Library

Resources:
Uses Wire.h for I2C operation

Development environment specifics:
Arduino IDE 1.8.9

This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact constantinkoch@outlook.com.
Distributed as-is; no warranty is given.
******************************************************************************/

#pragma once

#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#include <Wire.h>



//The 7-bit I2C ADDRESS of the RV3028
#define RV3028_ADDR                        (uint8_t)0x52


//REGISTERS
//Clock registers
#define RV3028_SECONDS                  0x00
#define RV3028_MINUTES                  0x01
#define RV3028_HOURS                    0x02
//Calendar registers
#define RV3028_WEEKDAY                    0x03
#define RV3028_DATE                     0x04
#define RV3028_MONTHS                    0x05
#define RV3028_YEARS                    0x06

//Alarm registers
#define RV3028_MINUTES_ALM                 0x07
#define RV3028_HOURS_ALM                   0x08
#define RV3028_DATE_ALM                    0x09

//Periodic Countdown Timer registers
#define RV3028_TIMERVAL_0                0x0A
#define RV3028_TIMERVAL_1                0x0B
#define RV3028_TIMERSTAT_0                0x0C
#define RV3028_TIMERSTAT_1                0x0D

//Configuration registers
#define RV3028_STATUS                    0x0E
#define RV3028_CTRL1                    0x0F
#define RV3028_CTRL2                    0x10
#define RV3028_GPBITS                    0x11
#define RV3028_INT_MASK                    0x12

//Eventcontrol/Timestamp registers
#define RV3028_EVENTCTRL                0x13
#define RV3028_COUNT_TS                    0x14
#define RV3028_SECONDS_TS                0x15
#define RV3028_MINUTES_TS                0x16
#define RV3028_HOURS_TS                    0x17
#define RV3028_DATE_TS                    0x18
#define RV3028_MONTH_TS                    0x19
#define RV3028_YEAR_TS                    0x1A

//Unix Time registers
#define RV3028_UNIX_TIME0                0x1B
#define RV3028_UNIX_TIME1                0x1C
#define RV3028_UNIX_TIME2                0x1D
#define RV3028_UNIX_TIME3                0x1E

//RAM registers
#define RV3028_USER_RAM1                0x1F
#define RV3028_USER_RAM2                0x20

//Password registers
#define RV3028_PASSWORD0                0x21
#define RV3028_PASSWORD1                0x22
#define RV3028_PASSWORD2                0x23
#define RV3028_PASSWORD3                0x24

//EEPROM Memory Control registers
#define RV3028_EEPROM_ADDR                0x25
#define RV3028_EEPROM_DATA                0x26
#define RV3028_EEPROM_CMD                0x27

//ID register
#define RV3028_ID                        0x28

//EEPROM Registers
#define EEPROM_Clkout_Register            0x35
#define RV3028_EEOffset_8_1                0x36    //bits 8 to 1 of EEOffset. Bit 0 is bit 7 of register 0x37
#define EEPROM_Backup_Register            0x37


//BITS IN IMPORTANT REGISTERS

//Bits in Status Register
#define STATUS_EEBUSY    7
#define STATUS_CLKF        6
#define STATUS_BSF        5
#define STATUS_UF        4
#define STATUS_TF        3
#define STATUS_AF        2
#define STATUS_EVF        1
#define STATUS_PORF        0

//Bits in Control1 Register
#define CTRL1_TRPT        7
#define CTRL1_WADA        5//Bit 6 not implemented
#define CTRL1_USEL        4
#define CTRL1_EERD        3
#define CTRL1_TE        2
#define    CTRL1_TD1        1
#define CTRL1_TD0        0

//Bits in Control2 Register
#define CTRL2_TSE        7
#define CTRL2_CLKIE        6
#define CTRL2_UIE        5
#define CTRL2_TIE        4
#define CTRL2_AIE        3
#define CTRL2_EIE        2
#define CTRL2_12_24        1
#define CTRL2_RESET        0

//Bits in Hours register
#define HOURS_AM_PM            5

//Bits in Alarm registers
#define MINUTESALM_AE_M        7
#define HOURSALM_AE_H        7
#define DATE_AE_WD            7

//Commands for EEPROM Command Register (0x27)
#define EEPROMCMD_First                    0x00
#define EEPROMCMD_Update                0x11
#define EEPROMCMD_Refresh                0x12
#define EEPROMCMD_WriteSingle            0x21
#define EEPROMCMD_ReadSingle            0x22

//Bits in EEPROM Backup Register
#define EEPROMBackup_TCE_BIT            5                //Trickle Charge Enable Bit
#define EEPROMBackup_FEDE_BIT            4                //Fast Edge Detection Enable Bit (for Backup Switchover Mode)
#define EEPROMBackup_BSM_SHIFT            2                //Backup Switchover Mode shift
#define EEPROMBackup_TCR_SHIFT            0                //Trickle Charge Resistor shift

#define EEPROMBackup_BSM_CLEAR            0b11110011        //Backup Switchover Mode clear
#define EEPROMBackup_TCR_CLEAR            0b11111100        //Trickle Charge Resistor clear
#define    TCR_3K                            0b00            //Trickle Charge Resistor 3kOhm
#define    TCR_5K                            0b01            //Trickle Charge Resistor 5kOhm
#define    TCR_9K                            0b10            //Trickle Charge Resistor 9kOhm
#define    TCR_15K                            0b11            //Trickle Charge Resistor 15kOhm


// Clock output register (0x35)
#define EEPROMClkout_CLKOE_BIT            7                //1 = CLKOUT pin is enabled. – Default value on delivery
#define EEPROMClkout_CLKSY_BIT            6
// Bits 5 and 4 not implemented
#define EEPROMClkout_PORIE                  3                //0 = No interrupt, or canceled, signal on INT pin at POR. – Default value on delivery
                                                                        //1 = An interrupt signal on INT pin at POR. Retained until the PORF flag is cleared to 0 (no automatic cancellation).
#define EEPROMClkout_FREQ_SHIFT            0                //frequency shift
#define FD_CLKOUT_32k                      0b000              //32.768 kHz – Default value on delivery
#define FD_CLKOUT_8192                    0b001             //8192 Hz
#define FD_CLKOUT_1024                    0b010              //1024 Hz
#define FD_CLKOUT_64                      0b011           //64 Hz
#define FD_CLKOUT_32                      0b100              //32 Hz
#define FD_CLKOUT_1                          0b101              //1 Hz
#define FD_CLKOUT_TIMER                    0b110              //Predefined periodic countdown timer interrupt
#define FD_CLKOUT_LOW                      0b111             //CLKOUT = LOW


#define IMT_MASK_CEIE                    3                //Clock output when Event Interrupt bit.
#define IMT_MASK_CAIE                    2                //Clock output when Alarm Interrupt bit.
#define IMT_MASK_CTIE                    1                //Clock output when Periodic Countdown Timer Interrupt bit.
#define IMT_MASK_CUIE                    0                //Clock output when Periodic Time Update Interrupt bit.


#define TIME_ARRAY_LENGTH 7 // Total number of writable values in device

enum time_order {
    TIME_SECONDS,    // 0
    TIME_MINUTES,    // 1
    TIME_HOURS,      // 2
    TIME_WEEKDAY,    // 3
    TIME_DATE,       // 4
    TIME_MONTH,      // 5
    TIME_YEAR,       // 6
};

class RV3028
{
public:

    RV3028(void);

    bool begin(TwoWire &wirePort = Wire, bool set_24Hour = true, bool disable_TrickleCharge = true, bool set_LevelSwitchingMode = true, bool reset_Status = true);

    bool setTime(uint8_t sec, uint8_t min, uint8_t hour, uint8_t weekday, uint8_t date, uint8_t month, uint16_t year);
    bool setTime(uint8_t * time, uint8_t len);
    bool setSeconds(uint8_t value);
    bool setMinutes(uint8_t value);
    bool setHours(uint8_t value);
    bool setWeekday(uint8_t value);
    bool setDate(uint8_t value);
    bool setMonth(uint8_t value);
    bool setYear(uint16_t value);
    bool setToCompilerTime(); //Uses the hours, mins, etc from compile time to set RTC

    bool updateTime(); //Update the local array with the RTC registers

    char* stringDateUSA(); //Return date in mm-dd-yyyy
    char* stringDate(); //Return date in dd-mm-yyyy
    char* stringTime(); //Return time hh:mm:ss with AM/PM if in 12 hour mode
    char* stringTimeStamp(); //Return timeStamp in ISO 8601 format yyyy-mm-ddThh:mm:ss

    uint8_t getSeconds();
    uint8_t getMinutes();
    uint8_t getHours();
    uint8_t getWeekday();
    uint8_t getDate();
    uint8_t getMonth();
    uint16_t getYear();


    bool is12Hour(); //Returns true if 12hour bit is set
    bool isPM(); //Returns true if is12Hour and PM bit is set
    void set12Hour();
    void set24Hour();

    bool setUNIX(uint32_t value);//Set the UNIX Time (Real Time and UNIX Time are INDEPENDENT!)
    uint32_t getUNIX();

    void enableAlarmInterrupt(uint8_t min, uint8_t hour, uint8_t date_or_weekday, bool setWeekdayAlarm_not_Date, uint8_t mode, bool enable_clock_output = false);
    void enableAlarmInterrupt();
    void disableAlarmInterrupt();
    bool readAlarmInterruptFlag();
    void clearAlarmInterruptFlag();

    void setTimer(bool timer_repeat, uint16_t timer_frequency, uint16_t timer_value, bool setInterrupt, bool start_timer, bool enable_clock_output = false);
    void enableTimer();
    void disableTimer();
    void enableTimerInterrupt();
    void disableTimerInterrupt();
    bool readTimerInterruptFlag();
    void clearTimerInterruptFlag();

    void enablePeriodicUpdateInterrupt(bool every_second, bool enable_clock_output = false);
    void disablePeriodicUpdateInterrupt();
    bool readPeriodicUpdateInterruptFlag();
    void clearPeriodicUpdateInterruptFlag();

    void enableTrickleCharge(uint8_t tcr = TCR_15K); //Trickle Charge Resistor default 15k
    void disableTrickleCharge();
    bool setBackupSwitchoverMode(uint8_t val);

    void enableClockOut(uint8_t freq);
    void enableInterruptControlledClockout(uint8_t freq);
    void disableClockOut();
    bool readClockOutputInterruptFlag();
    void clearClockOutputInterruptFlag();

    uint8_t status(); //Returns the status byte
    void clearInterrupts();

    //Values in RTC are stored in Binary Coded Decimal. These functions convert to/from Decimal
    uint8_t BCDtoDEC(uint8_t val);
    uint8_t DECtoBCD(uint8_t val);

    uint8_t readRegister(uint8_t addr);
    bool writeRegister(uint8_t addr, uint8_t val);
    bool readMultipleRegisters(uint8_t addr, uint8_t * dest, uint8_t len);
    bool writeMultipleRegisters(uint8_t addr, uint8_t * values, uint8_t len);

    bool writeConfigEEPROM_RAMmirror(uint8_t eepromaddr, uint8_t val);
    uint8_t readConfigEEPROM_RAMmirror(uint8_t eepromaddr);
    bool writeUserEEPROM(uint8_t eepromaddr, uint8_t val);
    uint8_t readUserEEPROM(uint8_t eepromaddr);
    bool waitforEEPROM();
    void reset();

    void setBit(uint8_t reg_addr, uint8_t bit_num);
    void clearBit(uint8_t reg_addr, uint8_t bit_num);
    bool readBit(uint8_t reg_addr, uint8_t bit_num);
    bool timeUpdateRequired = true;
private:
    uint8_t _time[TIME_ARRAY_LENGTH];
    TwoWire *_i2cPort;
};

//POSSIBLE ENHANCEMENTS :
//ENHANCEMENT: Battery Interrupt / check battery voltage
//ENHANCEMENT: External Event Interrupt

RTC_RV_3028_C7.cpp
Code:
/******************************************************************************
RV-3028-C7.h
RV-3028-C7 Arduino Library
Constantin Koch
July 25, 2019
https://github.com/constiko/RV-3028_C7-Arduino_Library

Development environment specifics:
Arduino IDE 1.8.9

This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact constantinkoch@outlook.com.
Distributed as-is; no warranty is given.
******************************************************************************/

#include "RTC_RV_3028_C7.h"

//****************************************************************************//
//
//  Settings and configuration
//
//****************************************************************************//

// Parse the __DATE__ predefined macro to generate date defaults:
// __Date__ Format: MMM DD YYYY (First D may be a space if <10)
// <MONTH>                                                                 
#define BUILD_MONTH_JAN ((__DATE__[0] == 'J') && (__DATE__[1] == 'a')) ? 1 : 0
#define BUILD_MONTH_FEB (__DATE__[0] == 'F') ? 2 : 0
#define BUILD_MONTH_MAR ((__DATE__[0] == 'M') && (__DATE__[1] == 'a') && (__DATE__[2] == 'r')) ? 3 : 0
#define BUILD_MONTH_APR ((__DATE__[0] == 'A') && (__DATE__[1] == 'p')) ? 4 : 0
#define BUILD_MONTH_MAY ((__DATE__[0] == 'M') && (__DATE__[1] == 'a') && (__DATE__[2] == 'y')) ? 5 : 0
#define BUILD_MONTH_JUN ((__DATE__[0] == 'J') && (__DATE__[1] == 'u') && (__DATE__[2] == 'n')) ? 6 : 0
#define BUILD_MONTH_JUL ((__DATE__[0] == 'J') && (__DATE__[1] == 'u') && (__DATE__[2] == 'l')) ? 7 : 0
#define BUILD_MONTH_AUG ((__DATE__[0] == 'A') && (__DATE__[1] == 'u')) ? 8 : 0
#define BUILD_MONTH_SEP (__DATE__[0] == 'S') ? 9 : 0
#define BUILD_MONTH_OCT (__DATE__[0] == 'O') ? 10 : 0
#define BUILD_MONTH_NOV (__DATE__[0] == 'N') ? 11 : 0
#define BUILD_MONTH_DEC (__DATE__[0] == 'D') ? 12 : 0
#define BUILD_MONTH BUILD_MONTH_JAN | BUILD_MONTH_FEB | BUILD_MONTH_MAR | \
BUILD_MONTH_APR | BUILD_MONTH_MAY | BUILD_MONTH_JUN | \
BUILD_MONTH_JUL | BUILD_MONTH_AUG | BUILD_MONTH_SEP | \
BUILD_MONTH_OCT | BUILD_MONTH_NOV | BUILD_MONTH_DEC
// <DATE>                                                                 
#define BUILD_DATE_0 ((__DATE__[4] == ' ') ? 0 : (__DATE__[4] - 0x30))
#define BUILD_DATE_1 (__DATE__[5] - 0x30)
#define BUILD_DATE ((BUILD_DATE_0 * 10) + BUILD_DATE_1)
// <YEAR>                                                                 
#define BUILD_YEAR (((__DATE__[7] - 0x30) * 1000) + ((__DATE__[8] - 0x30) * 100) + \
((__DATE__[9] - 0x30) * 10)  + ((__DATE__[10] - 0x30) * 1))

// Parse the __TIME__ predefined macro to generate time defaults:
// __TIME__ Format: HH:MM:SS (First number of each is padded by 0 if <10)
// <HOUR>                                                                 
#define BUILD_HOUR_0 ((__TIME__[0] == ' ') ? 0 : (__TIME__[0] - 0x30))
#define BUILD_HOUR_1 (__TIME__[1] - 0x30)
#define BUILD_HOUR ((BUILD_HOUR_0 * 10) + BUILD_HOUR_1)
// <MINUTE>                                                                 
#define BUILD_MINUTE_0 ((__TIME__[3] == ' ') ? 0 : (__TIME__[3] - 0x30))
#define BUILD_MINUTE_1 (__TIME__[4] - 0x30)
#define BUILD_MINUTE ((BUILD_MINUTE_0 * 10) + BUILD_MINUTE_1)
// <SECOND>                                                                 
#define BUILD_SECOND_0 ((__TIME__[6] == ' ') ? 0 : (__TIME__[6] - 0x30))
#define BUILD_SECOND_1 (__TIME__[7] - 0x30)
#define BUILD_SECOND ((BUILD_SECOND_0 * 10) + BUILD_SECOND_1)

RV3028::RV3028(void)
{

}

bool RV3028::begin(TwoWire &wirePort, bool set_24Hour, bool disable_TrickleCharge, bool set_LevelSwitchingMode, bool reset_Status)
{
    //We require caller to begin their I2C port, with the speed of their choice
    //external to the library
    //_i2cPort->begin();
    _i2cPort = &wirePort;

    delay(1);
    if (set_24Hour) { set24Hour(); delay(1); }
    if (disable_TrickleCharge) { disableTrickleCharge(); delay(1); }
    timeUpdateRequired = readBit(RV3028_STATUS, STATUS_PORF);

    return((set_LevelSwitchingMode ? setBackupSwitchoverMode(3) : true) && (reset_Status ? writeRegister(RV3028_STATUS, 0x00) : true));
}

bool RV3028::setTime(uint8_t sec, uint8_t min, uint8_t hour, uint8_t weekday, uint8_t date, uint8_t month, uint16_t year)
{
    _time[TIME_SECONDS] = DECtoBCD(sec);
    _time[TIME_MINUTES] = DECtoBCD(min);
    _time[TIME_HOURS] = DECtoBCD(hour);
    _time[TIME_WEEKDAY] = DECtoBCD(weekday);
    _time[TIME_DATE] = DECtoBCD(date);
    _time[TIME_MONTH] = DECtoBCD(month);
    _time[TIME_YEAR] = DECtoBCD(year - 2000);

    bool status = false;

    if (is12Hour())
    {
        set24Hour();
        status = setTime(_time, TIME_ARRAY_LENGTH);
        set12Hour();
    }
    else
    {
        status = setTime(_time, TIME_ARRAY_LENGTH);
    }
    return status;
}

// setTime -- Set time and date/day registers of RV3028 (using data array)
bool RV3028::setTime(uint8_t * time, uint8_t len)
{
    if (len != TIME_ARRAY_LENGTH)
        return false;

    return writeMultipleRegisters(RV3028_SECONDS, time, len);
}

bool RV3028::setSeconds(uint8_t value)
{
    _time[TIME_SECONDS] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setMinutes(uint8_t value)
{
    _time[TIME_MINUTES] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setHours(uint8_t value)
{
    _time[TIME_HOURS] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setWeekday(uint8_t value)
{
    _time[TIME_WEEKDAY] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setDate(uint8_t value)
{
    _time[TIME_DATE] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setMonth(uint8_t value)
{
    _time[TIME_MONTH] = DECtoBCD(value);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

bool RV3028::setYear(uint16_t value)
{
    _time[TIME_YEAR] = DECtoBCD(value - 2000);
    return setTime(_time, TIME_ARRAY_LENGTH);
}

//Takes the time from the last build and uses it as the current time
//Works very well as an arduino sketch
bool RV3028::setToCompilerTime()
{
    _time[TIME_SECONDS] = DECtoBCD(BUILD_SECOND);
    _time[TIME_MINUTES] = DECtoBCD(BUILD_MINUTE);
    _time[TIME_HOURS] = DECtoBCD(BUILD_HOUR);

    //Build_Hour is 0-23, convert to 1-12 if needed
    if (is12Hour())
    {
        uint8_t hour = BUILD_HOUR;

        bool pm = false;

        if (hour == 0)
            hour += 12;
        else if (hour == 12)
            pm = true;
        else if (hour > 12)
        {
            hour -= 12;
            pm = true;
        }

        _time[TIME_HOURS] = DECtoBCD(hour); //Load the modified hours

        if (pm == true) _time[TIME_HOURS] |= (1 << HOURS_AM_PM); //Set AM/PM bit if needed
    }

    // Calculate weekday (from here: http://stackoverflow.com/a/21235587)
    // 0 = Sunday, 6 = Saturday
    uint16_t d = BUILD_DATE;
    uint16_t m = BUILD_MONTH;
    uint16_t y = BUILD_YEAR;
    uint16_t weekday = (d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4 - y / 100 + y / 400) % 7 + 1;
    _time[TIME_WEEKDAY] = DECtoBCD(weekday);

    _time[TIME_DATE] = DECtoBCD(BUILD_DATE);
    _time[TIME_MONTH] = DECtoBCD(BUILD_MONTH);
    _time[TIME_YEAR] = DECtoBCD(BUILD_YEAR - 2000); //! Not Y2K (or Y2.1K)-proof :(

    return setTime(_time, TIME_ARRAY_LENGTH);
}

//Move the hours, mins, sec, etc registers from RV-3028-C7 into the _time array
//Needs to be called before printing time or date
//We do not protect the GPx registers. They will be overwritten. The user has plenty of RAM if they need it.
bool RV3028::updateTime()
{
    if (readMultipleRegisters(RV3028_SECONDS, _time, TIME_ARRAY_LENGTH) == false)
        return(false); //Something went wrong

    if (is12Hour()) _time[TIME_HOURS] &= ~(1 << HOURS_AM_PM); //Remove this bit from value

    return true;
}

//Returns a pointer to array of chars that are the date in mm/dd/yyyy format because they're weird
char* RV3028::stringDateUSA()
{
    static char date[14]; //Max of mm/dd/yyyy with \0 terminator
    sprintf(date, "%02hhu/%02hhu/20%02hhu", BCDtoDEC(_time[TIME_MONTH]), BCDtoDEC(_time[TIME_DATE]), BCDtoDEC(_time[TIME_YEAR]));
    return(date);
}

//Returns a pointer to array of chars that are the date in dd/mm/yyyy format
char*  RV3028::stringDate()
{
    static char date[14]; //Max of dd/mm/yyyy with \0 terminator
    sprintf(date, "%02hhu/%02hhu/20%02hhu", BCDtoDEC(_time[TIME_DATE]), BCDtoDEC(_time[TIME_MONTH]), BCDtoDEC(_time[TIME_YEAR]));
    return(date);
}

//Returns a pointer to array of chars that represents the time in hh:mm:ss format
//Adds AM/PM if in 12 hour mode
char* RV3028::stringTime()
{
    static char time[14]; //Max of hh:mm:ssXM with \0 terminator

    if (is12Hour() == true)
    {
        char half = 'A';
        if (isPM()) half = 'P';

        sprintf(time, "%02hhu:%02hhu:%02hhu%cM", BCDtoDEC(_time[TIME_HOURS]), BCDtoDEC(_time[TIME_MINUTES]), BCDtoDEC(_time[TIME_SECONDS]), half);
    }
    else
        sprintf(time, "%02hhu:%02hhu:%02hhu", BCDtoDEC(_time[TIME_HOURS]), BCDtoDEC(_time[TIME_MINUTES]), BCDtoDEC(_time[TIME_SECONDS]));

    return(time);
}

char* RV3028::stringTimeStamp()
{
    static char timeStamp[29]; //Max of yyyy-mm-ddThh:mm:ss.ss with \0 terminator

    if (is12Hour() == true)
    {
        char half = 'A';
        if (isPM()) half = 'P';

        sprintf(timeStamp, "20%02hhu-%02hhu-%02hhu  %02hhu:%02hhu:%02hhu%cM", BCDtoDEC(_time[TIME_YEAR]), BCDtoDEC(_time[TIME_MONTH]), BCDtoDEC(_time[TIME_DATE]), BCDtoDEC(_time[TIME_HOURS]), BCDtoDEC(_time[TIME_MINUTES]), BCDtoDEC(_time[TIME_SECONDS]), half);
    }
    else
        sprintf(timeStamp, "20%02hhu-%02hhu-%02hhu  %02hhu:%02hhu:%02hhu", BCDtoDEC(_time[TIME_YEAR]), BCDtoDEC(_time[TIME_MONTH]), BCDtoDEC(_time[TIME_DATE]), BCDtoDEC(_time[TIME_HOURS]), BCDtoDEC(_time[TIME_MINUTES]), BCDtoDEC(_time[TIME_SECONDS]));

    return(timeStamp);
}

uint8_t RV3028::getSeconds()
{
    return BCDtoDEC(_time[TIME_SECONDS]);
}

uint8_t RV3028::getMinutes()
{
    return BCDtoDEC(_time[TIME_MINUTES]);
}

uint8_t RV3028::getHours()
{
    return BCDtoDEC(_time[TIME_HOURS]);
}

uint8_t RV3028::getWeekday()
{
    return BCDtoDEC(_time[TIME_WEEKDAY]);
}

uint8_t RV3028::getDate()
{
    return BCDtoDEC(_time[TIME_DATE]);
}

uint8_t RV3028::getMonth()
{
    return BCDtoDEC(_time[TIME_MONTH]);
}

uint16_t RV3028::getYear()
{
    return BCDtoDEC(_time[TIME_YEAR]) + 2000;
}

//Returns true if RTC has been configured for 12 hour mode
bool RV3028::is12Hour()
{
    uint8_t controlRegister2 = readRegister(RV3028_CTRL2);
    return(controlRegister2 & (1 << CTRL2_12_24));
}

//Returns true if RTC has PM bit set and 12Hour bit set
bool RV3028::isPM()
{
    uint8_t hourRegister = readRegister(RV3028_HOURS);
    if (is12Hour() && (hourRegister & (1 << HOURS_AM_PM)))
        return(true);
    return(false);
}

//Configure RTC to output 1-12 hours
//Converts any current hour setting to 12 hour
void RV3028::set12Hour()
{
    //Do we need to change anything?
    if (is12Hour() == false)
    {
        uint8_t hour = BCDtoDEC(readRegister(RV3028_HOURS)); //Get the current hour in the RTC

                                                             //Set the 12/24 hour bit
        uint8_t setting = readRegister(RV3028_CTRL2);
        setting |= (1 << CTRL2_12_24);
        writeRegister(RV3028_CTRL2, setting);

        //Take the current hours and convert to 12, complete with AM/PM bit
        bool pm = false;

        if (hour == 0)
            hour += 12;
        else if (hour == 12)
            pm = true;
        else if (hour > 12)
        {
            hour -= 12;
            pm = true;
        }

        hour = DECtoBCD(hour); //Convert to BCD

        if (pm == true) hour |= (1 << HOURS_AM_PM); //Set AM/PM bit if needed

        writeRegister(RV3028_HOURS, hour); //Record this to hours register
    }
}

//Configure RTC to output 0-23 hours
//Converts any current hour setting to 24 hour
void RV3028::set24Hour()
{
    //Do we need to change anything?
    if (is12Hour() == true)
    {
        //Not sure what changing the CTRL2 register will do to hour register so let's get a copy
        uint8_t hour = readRegister(RV3028_HOURS); //Get the current 12 hour formatted time in BCD
        bool pm = false;
        if (hour & (1 << HOURS_AM_PM)) //Is the AM/PM bit set?
        {
            pm = true;
            hour &= ~(1 << HOURS_AM_PM); //Clear the bit
        }

        //Change to 24 hour mode
        uint8_t setting = readRegister(RV3028_CTRL2);
        setting &= ~(1 << CTRL2_12_24); //Clear the 12/24 hr bit
        writeRegister(RV3028_CTRL2, setting);

        //Given a BCD hour in the 1-12 range, make it 24
        hour = BCDtoDEC(hour); //Convert core of register to DEC

        if (pm == true) hour += 12; //2PM becomes 14
        if (hour == 12) hour = 0; //12AM stays 12, but should really be 0
        if (hour == 24) hour = 12; //12PM becomes 24, but should really be 12

        hour = DECtoBCD(hour); //Convert to BCD

        writeRegister(RV3028_HOURS, hour); //Record this to hours register
    }
}

//ATTENTION: Real Time and UNIX Time are INDEPENDENT!
bool RV3028::setUNIX(uint32_t value)
{
    uint8_t unix_reg[4];
    unix_reg[0] = value;
    unix_reg[1] = value >> 8;
    unix_reg[2] = value >> 16;
    unix_reg[3] = value >> 24;

    return writeMultipleRegisters(RV3028_UNIX_TIME0, unix_reg, 4);
}

//ATTENTION: Real Time and UNIX Time are INDEPENDENT!
uint32_t RV3028::getUNIX()
{
    uint8_t unix_reg[4];
    readMultipleRegisters(RV3028_UNIX_TIME0, unix_reg, 4);
    return ((uint32_t)unix_reg[3] << 24) | ((uint32_t)unix_reg[2] << 16) | ((uint32_t)unix_reg[1] << 8) | unix_reg[0];
}

/*********************************
Set the alarm mode in the following way:
0: When minutes, hours and weekday/date match (once per weekday/date)
1: When hours and weekday/date match (once per weekday/date)
2: When minutes and weekday/date match (once per hour per weekday/date)
3: When weekday/date match (once per weekday/date)
4: When hours and minutes match (once per day)
5: When hours match (once per day)
6: When minutes match (once per hour)
7: All disabled � Default value
If you want to set a weekday alarm (setWeekdayAlarm_not_Date = true), set 'date_or_weekday' from 0 (Sunday) to 6 (Saturday)
********************************/
void RV3028::enableAlarmInterrupt(uint8_t min, uint8_t hour, uint8_t date_or_weekday, bool setWeekdayAlarm_not_Date, uint8_t mode, bool enable_clock_output)
{
    //disable Alarm Interrupt to prevent accidental interrupts during configuration
    disableAlarmInterrupt();
    clearAlarmInterruptFlag();

    //ENHANCEMENT: Add Alarm in 12 hour mode
    set24Hour();

    //Set WADA bit (Weekday/Date Alarm)
    if (setWeekdayAlarm_not_Date)
        clearBit(RV3028_CTRL1, CTRL1_WADA);
    else
        setBit(RV3028_CTRL1, CTRL1_WADA);

    //Write alarm settings in registers 0x07 to 0x09
    uint8_t alarmTime[3];
    alarmTime[0] = DECtoBCD(min);                //minutes
    alarmTime[1] = DECtoBCD(hour);                //hours
    alarmTime[2] = DECtoBCD(date_or_weekday);    //date or weekday
    //shift alarm enable bits
    if (mode > 0b111) mode = 0b111; //0 to 7 is valid
    if (mode & 0b001)
        alarmTime[0] |= 1 << MINUTESALM_AE_M;
    if (mode & 0b010)
        alarmTime[1] |= 1 << HOURSALM_AE_H;
    if (mode & 0b100)
        alarmTime[2] |= 1 << DATE_AE_WD;
    //Write registers
    writeMultipleRegisters(RV3028_MINUTES_ALM, alarmTime, 3);

    //enable Alarm Interrupt
    enableAlarmInterrupt();

    //Clock output?
    if (enable_clock_output)
        setBit(RV3028_INT_MASK, IMT_MASK_CAIE);
    else
        clearBit(RV3028_INT_MASK, IMT_MASK_CAIE);
}

void RV3028::enableAlarmInterrupt()
{
    setBit(RV3028_CTRL2, CTRL2_AIE);
}

//Only disables the interrupt (not the alarm flag)
void RV3028::disableAlarmInterrupt()
{
    clearBit(RV3028_CTRL2, CTRL2_AIE);
}

bool RV3028::readAlarmInterruptFlag()
{
    return readBit(RV3028_STATUS, STATUS_AF);
}

void RV3028::clearAlarmInterruptFlag()
{
    clearBit(RV3028_STATUS, STATUS_AF);
}

/*********************************
Countdown Timer Interrupt
********************************/
void RV3028::setTimer(bool timer_repeat, uint16_t timer_frequency, uint16_t timer_value, bool set_interrupt, bool start_timer, bool enable_clock_output)
{
    disableTimer();
    disableTimerInterrupt();
    clearTimerInterruptFlag();

    writeRegister(RV3028_TIMERVAL_0, timer_value & 0xff);
    writeRegister(RV3028_TIMERVAL_1, timer_value >> 8);

    uint8_t ctrl1_val = readRegister(RV3028_CTRL1);
    if (timer_repeat)
    {
        ctrl1_val |= 1 << CTRL1_TRPT;
    }
    else
    {
        ctrl1_val &= ~(1 << CTRL1_TRPT);
    }
    switch (timer_frequency)
    {
    case 4096:        // 4096Hz (default)        // up to 122us error on first time
        ctrl1_val &= ~3; // Clear both the bits
        break;

    case 64:        // 64Hz                    // up to 7.813ms error on first time
        ctrl1_val &= ~3; // Clear both the bits
        ctrl1_val |= 1;
        break;

    case 1:            // 1Hz                    // up to 7.813ms error on first time
        ctrl1_val &= ~3; // Clear both the bits
        ctrl1_val |= 2;
        break;

    case 60000:        // 1/60Hz                // up to 7.813ms error on first time
        ctrl1_val |= 3; // Set both bits
        break;

    }

    if (set_interrupt)
    {
        enableTimerInterrupt();
    }
    if (start_timer)
    {
        ctrl1_val |= (1 << CTRL1_TE);
    }
    writeRegister(RV3028_CTRL1, ctrl1_val);

    //Clock output?
    if (enable_clock_output)
        setBit(RV3028_INT_MASK, IMT_MASK_CTIE);
    else
        clearBit(RV3028_INT_MASK, IMT_MASK_CTIE);
}


void RV3028::enableTimerInterrupt()
{
    setBit(RV3028_CTRL2, CTRL2_TIE);
}

void RV3028::disableTimerInterrupt()
{
    clearBit(RV3028_CTRL2, CTRL2_TIE);
}

bool RV3028::readTimerInterruptFlag()
{
    return readBit(RV3028_STATUS, STATUS_TF);
}

void RV3028::clearTimerInterruptFlag()
{
    clearBit(RV3028_STATUS, STATUS_TF);
}

void RV3028::enableTimer()
{
    setBit(RV3028_CTRL1, CTRL1_TE);
}

void RV3028::disableTimer()
{
    clearBit(RV3028_CTRL1, CTRL1_TE);
}

/*********************************
Periodic Time Update Interrupt
********************************/
void RV3028::enablePeriodicUpdateInterrupt(bool every_second, bool enable_clock_output)
{
    disablePeriodicUpdateInterrupt();
    clearPeriodicUpdateInterruptFlag();

    if (every_second)
    {
        clearBit(RV3028_CTRL1, CTRL1_USEL);
    }
    else
    {    // every minute
        setBit(RV3028_CTRL1, CTRL1_USEL);
    }

    setBit(RV3028_CTRL2, CTRL2_UIE);

    //Clock output?
    if (enable_clock_output)
        setBit(RV3028_INT_MASK, IMT_MASK_CUIE);
    else
        clearBit(RV3028_INT_MASK, IMT_MASK_CUIE);
}

void RV3028::disablePeriodicUpdateInterrupt()
{
    clearBit(RV3028_CTRL2, CTRL2_UIE);
}

bool RV3028::readPeriodicUpdateInterruptFlag()
{
    return readBit(RV3028_STATUS, STATUS_UF);
}

void RV3028::clearPeriodicUpdateInterruptFlag()
{
    clearBit(RV3028_STATUS, STATUS_UF);
}

/*********************************
Enable the Trickle Charger and set the Trickle Charge series resistor (default is 15k)
TCR_3K  =  3kOhm
TCR_5K  =  5kOhm
TCR_9K  =  9kOhm
TCR_15K = 15kOhm
*********************************/
void RV3028::enableTrickleCharge(uint8_t tcr)
{
    if (tcr > 3) return;

    //Read EEPROM Backup Register (0x37)
    uint8_t EEPROMBackup = readConfigEEPROM_RAMmirror(EEPROM_Backup_Register);
    //Set TCR Bits (Trickle Charge Resistor)
    EEPROMBackup &= EEPROMBackup_TCR_CLEAR;        //Clear TCR Bits
    EEPROMBackup |= tcr << EEPROMBackup_TCR_SHIFT;    //Shift values into EEPROM Backup Register
    //Write 1 to TCE Bit
    EEPROMBackup |= 1 << EEPROMBackup_TCE_BIT;
    //Write EEPROM Backup Register
    writeConfigEEPROM_RAMmirror(EEPROM_Backup_Register, EEPROMBackup);
}

void RV3028::disableTrickleCharge()
{
    //Read EEPROM Backup Register (0x37)
    uint8_t EEPROMBackup = readConfigEEPROM_RAMmirror(EEPROM_Backup_Register);
    //Write 0 to TCE Bit
    EEPROMBackup &= ~(1 << EEPROMBackup_TCE_BIT);
    //Write EEPROM Backup Register
    writeConfigEEPROM_RAMmirror(EEPROM_Backup_Register, EEPROMBackup);
}


/*********************************
0 = Switchover disabled
1 = Direct Switching Mode
2 = Standby Mode
3 = Level Switching Mode
*********************************/
bool RV3028::setBackupSwitchoverMode(uint8_t val)
{
    if (val > 3)return false;
    bool success = true;

    //Read EEPROM Backup Register (0x37)
    uint8_t EEPROMBackup = readConfigEEPROM_RAMmirror(EEPROM_Backup_Register);
    if (EEPROMBackup == 0xFF) success = false;
    //Ensure FEDE Bit is set to 1
    EEPROMBackup |= 1 << EEPROMBackup_FEDE_BIT;
    //Set BSM Bits (Backup Switchover Mode)
    EEPROMBackup &= EEPROMBackup_BSM_CLEAR;        //Clear BSM Bits of EEPROM Backup Register
    EEPROMBackup |= val << EEPROMBackup_BSM_SHIFT;    //Shift values into EEPROM Backup Register
    //Write EEPROM Backup Register
    if (!writeConfigEEPROM_RAMmirror(EEPROM_Backup_Register, EEPROMBackup)) success = false;

    return success;
}


/*********************************
Clock Out functions
********************************/
void RV3028::enableClockOut(uint8_t freq)
{
    if (freq > 7) return; // check out of bounds
    //Read EEPROM CLKOUT Register (0x35)
    uint8_t EEPROMClkout = readConfigEEPROM_RAMmirror(EEPROM_Clkout_Register);
    //Ensure CLKOE Bit is set to 1
    EEPROMClkout |= 1 << EEPROMClkout_CLKOE_BIT;
    //Shift values into EEPROM Backup Register
    EEPROMClkout |= freq << EEPROMClkout_FREQ_SHIFT;
    //Write EEPROM Backup Register
    writeConfigEEPROM_RAMmirror(EEPROM_Clkout_Register, EEPROMClkout);
}

void RV3028::enableInterruptControlledClockout(uint8_t freq)
{
    if (freq > 7) return; // check out of bounds
    //Read EEPROM CLKOUT Register (0x35)
    uint8_t EEPROMClkout = readConfigEEPROM_RAMmirror(EEPROM_Clkout_Register);
    //Shift values into EEPROM Backup Register
    EEPROMClkout |= freq << EEPROMClkout_FREQ_SHIFT;
    //Write EEPROM Backup Register
    writeConfigEEPROM_RAMmirror(EEPROM_Clkout_Register, EEPROMClkout);

    //Set CLKIE Bit
    setBit(RV3028_CTRL2, CTRL2_CLKIE);
}

void RV3028::disableClockOut()
{
    //Read EEPROM CLKOUT Register (0x35)
    uint8_t EEPROMClkout = readConfigEEPROM_RAMmirror(EEPROM_Clkout_Register);
    //Clear CLKOE Bit
    EEPROMClkout &= ~(1 << EEPROMClkout_CLKOE_BIT);
    //Write EEPROM CLKOUT Register
    writeConfigEEPROM_RAMmirror(EEPROM_Clkout_Register, EEPROMClkout);

    //Clear CLKIE Bit
    clearBit(RV3028_CTRL2, CTRL2_CLKIE);
}

bool RV3028::readClockOutputInterruptFlag()
{
    return readBit(RV3028_STATUS, STATUS_CLKF);
}

void RV3028::clearClockOutputInterruptFlag()
{
    clearBit(RV3028_STATUS, STATUS_CLKF);
}


//Returns the status byte
uint8_t RV3028::status(void)
{
    return(readRegister(RV3028_STATUS));
}

void RV3028::clearInterrupts() //Read the status register to clear the current interrupt flags
{
    writeRegister(RV3028_STATUS, 0);
}





/*********************************
FOR INTERNAL USE
********************************/
uint8_t RV3028::BCDtoDEC(uint8_t val)
{
    return ((val / 0x10) * 10) + (val % 0x10);
}

// BCDtoDEC -- convert decimal to binary-coded decimal (BCD)
uint8_t RV3028::DECtoBCD(uint8_t val)
{
    return ((val / 10) * 0x10) + (val % 10);
}

uint8_t RV3028::readRegister(uint8_t addr)
{
    _i2cPort->beginTransmission(RV3028_ADDR);
    _i2cPort->write(addr);
    _i2cPort->endTransmission();

    _i2cPort->requestFrom(RV3028_ADDR, (uint8_t)1);
    if (_i2cPort->available()) {
        return _i2cPort->read();
    }
    else {
        return (0xFF); //Error
    }
}

bool RV3028::writeRegister(uint8_t addr, uint8_t val)
{
    _i2cPort->beginTransmission(RV3028_ADDR);
    _i2cPort->write(addr);
    _i2cPort->write(val);
    if (_i2cPort->endTransmission() != 0)
        return (false); //Error: Sensor did not ack
    return(true);
}

bool RV3028::readMultipleRegisters(uint8_t addr, uint8_t * dest, uint8_t len)
{
    _i2cPort->beginTransmission(RV3028_ADDR);
    _i2cPort->write(addr);
    if (_i2cPort->endTransmission() != 0)
        return (false); //Error: Sensor did not ack

    _i2cPort->requestFrom(RV3028_ADDR, len);
    for (uint8_t i = 0; i < len; i++)
    {
        dest[i] = _i2cPort->read();
    }

    return(true);
}

bool RV3028::writeMultipleRegisters(uint8_t addr, uint8_t * values, uint8_t len)
{
    _i2cPort->beginTransmission(RV3028_ADDR);
    _i2cPort->write(addr);
    for (uint8_t i = 0; i < len; i++)
    {
        _i2cPort->write(values[i]);
    }

    if (_i2cPort->endTransmission() != 0)
        return (false); //Error: Sensor did not ack
    return(true);
}

bool RV3028::writeConfigEEPROM_RAMmirror(uint8_t eepromaddr, uint8_t val)
{
    bool success = waitforEEPROM();

    //Disable auto refresh by writing 1 to EERD control bit in CTRL1 register
    uint8_t ctrl1 = readRegister(RV3028_CTRL1);
    ctrl1 |= 1 << CTRL1_EERD;
    if (!writeRegister(RV3028_CTRL1, ctrl1)) success = false;
    //Write Configuration RAM Register
    writeRegister(eepromaddr, val);
    //Update EEPROM (All Configuration RAM -> EEPROM)
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_First);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_Update);
    if (!waitforEEPROM()) success = false;
    //Reenable auto refresh by writing 0 to EERD control bit in CTRL1 register
    ctrl1 = readRegister(RV3028_CTRL1);
    if (ctrl1 == 0x00)success = false;
    ctrl1 &= ~(1 << CTRL1_EERD);
    writeRegister(RV3028_CTRL1, ctrl1);
    if (!waitforEEPROM()) success = false;

    return success;
}

uint8_t RV3028::readConfigEEPROM_RAMmirror(uint8_t eepromaddr)
{
    bool success = waitforEEPROM();

    //Disable auto refresh by writing 1 to EERD control bit in CTRL1 register
    uint8_t ctrl1 = readRegister(RV3028_CTRL1);
    ctrl1 |= 1 << CTRL1_EERD;
    if (!writeRegister(RV3028_CTRL1, ctrl1)) success = false;
    //Read EEPROM Register
    writeRegister(RV3028_EEPROM_ADDR, eepromaddr);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_First);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_ReadSingle);
    if (!waitforEEPROM()) success = false;
    uint8_t eepromdata = readRegister(RV3028_EEPROM_DATA);
    if (!waitforEEPROM()) success = false;
    //Reenable auto refresh by writing 0 to EERD control bit in CTRL1 register
    ctrl1 = readRegister(RV3028_CTRL1);
    if (ctrl1 == 0x00)success = false;
    ctrl1 &= ~(1 << CTRL1_EERD);
    writeRegister(RV3028_CTRL1, ctrl1);

    if (!success) return 0xFF;
    return eepromdata;
}

bool RV3028::writeUserEEPROM(uint8_t eepromaddr, uint8_t val)
{
    bool success = waitforEEPROM();

    //Disable auto refresh by writing 1 to EERD control bit in CTRL1 register
    uint8_t ctrl1 = readRegister(RV3028_CTRL1);
    ctrl1 |= 1 << CTRL1_EERD;
    if (!writeRegister(RV3028_CTRL1, ctrl1)) success = false;
    //Write addr to EEADDR
    writeRegister(0x25, eepromaddr);
    //Write value to EEDATA
    writeRegister(0x26, val);
    //Update EEPROM (All Configuration RAM -> EEPROM)
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_First);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_WriteSingle);
    if (!waitforEEPROM()) success = false;
    //Reenable auto refresh by writing 0 to EERD control bit in CTRL1 register
    ctrl1 = readRegister(RV3028_CTRL1);
    if (ctrl1 == 0x00)success = false;
    ctrl1 &= ~(1 << CTRL1_EERD);
    writeRegister(RV3028_CTRL1, ctrl1);
    if (!waitforEEPROM()) success = false;

    return success;
}

uint8_t RV3028::readUserEEPROM(uint8_t eepromaddr)
{
    bool success = waitforEEPROM();

    //Disable auto refresh by writing 1 to EERD control bit in CTRL1 register
    uint8_t ctrl1 = readRegister(RV3028_CTRL1);
    ctrl1 |= 1 << CTRL1_EERD;
    if (!writeRegister(RV3028_CTRL1, ctrl1)) success = false;
    //Read EEPROM Register
    writeRegister(RV3028_EEPROM_ADDR, eepromaddr);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_First);
    writeRegister(RV3028_EEPROM_CMD, EEPROMCMD_ReadSingle);
    if (!waitforEEPROM()) success = false;
    uint8_t eepromdata = readRegister(RV3028_EEPROM_DATA);
    if (!waitforEEPROM()) success = false;
    //Reenable auto refresh by writing 0 to EERD control bit in CTRL1 register
    ctrl1 = readRegister(RV3028_CTRL1);
    if (ctrl1 == 0x00)success = false;
    ctrl1 &= ~(1 << CTRL1_EERD);
    writeRegister(RV3028_CTRL1, ctrl1);

    if (!success) return 0xFF;
    return eepromdata;
}

//True if success, false if timeout occured
bool RV3028::waitforEEPROM()
{
    unsigned long timeout = millis() + 500;
    while ((readRegister(RV3028_STATUS) & 1 << STATUS_EEBUSY) && millis() < timeout);

    return millis() < timeout;
}

void RV3028::reset()
{
    setBit(RV3028_CTRL2, CTRL2_RESET);
}


void RV3028::setBit(uint8_t reg_addr, uint8_t bit_num)
{
    uint8_t value = readRegister(reg_addr);
    value |= (1 << bit_num); //Set the bit
    writeRegister(reg_addr, value);
}

void RV3028::clearBit(uint8_t reg_addr, uint8_t bit_num)
{
    uint8_t value = readRegister(reg_addr);
    value &= ~(1 << bit_num); //Clear the bit
    writeRegister(reg_addr, value);
}

bool RV3028::readBit(uint8_t reg_addr, uint8_t bit_num)
{
    uint8_t value = readRegister(reg_addr);
    value &= (1 << bit_num);
    return value;

}
 
Last edited:
Code:
TransmitPacketType    transmitPacket;
ReceivePacketType    receivePacket;

The H file defines these two variables, and that H file is #include'd in both the CPP file and the INO file, so that's why you're getting duplicate definitions. It looks like the CPP files uses those variables, so what I would do is copy those variable definitions to the CPP file and put "extern" in front of the definitions in the H file. That way you can access them from your INO file if that's necessary.
 
Code:
TransmitPacketType    transmitPacket;
ReceivePacketType    receivePacket;

The H file defines these two variables, and that H file is #include'd in both the CPP file and the INO file, so that's why you're getting duplicate definitions. It looks like the CPP files uses those variables, so what I would do is copy those variable definitions to the CPP file and put "extern" in front of the definitions in the H file. That way you can access them from your INO file if that's necessary.
Thanks, I will try that tomorrow. I did try putting the definitions and variables in their own .h file but that gave the same problem. It seems nuts that you can't just make them global definitions and variables.
 
You have to distinguish between definition and declaration.

"TransmitPacketType transmitPacket" is a definition. Every time that appears in a CPP or INO file, whether directly or via #include of an H file, the compiler thinks you want to define that variable, so it should always be in just one CPP or INO file.

"extern TransmitPacketType transmitPacket" is a declaration. That's what you want in your H file. When you #include the H file in the CPP or INO file, you're telling the CPP or INO file that such a variable will be defined somewhere.
 
It seems nuts that you can't just make them global definitions and variables.

You can indeed. That's what "extern" does.

If you look at the header files that define commonly used global names like Serial1 defined in HardwareSerial.h, you'll see it has "extern" in the header file. Only 1 place, inside HardwareSerial1.cpp is it defined without extern, because that's the actual code which implements Serial1. Everywhere else that has Serial1 only with "extern" uses it without creating a duplicate instance.
 
Last edited:
If you put inline in front of the variables then you can leave them in the header:
Code:
inline TransmitPacketType    transmitPacket;
inline ReceivePacketType    receivePacket;
 
Thanks @PaulStoffregen and @joepasquariello I gave that a try and it works...you knew it would!!

My favourite programming language (of the past) was Modula2.

It allowed modules (libraries) and each module consisted of a .DEF file, for defining variables and Procedures/Functions Headers accessible external to the module, and .MOD files containing the executable code and local variables accessible to the Module.

I liken (obviously erroneously) the .h files to the .DEF files.

In Modula2 any module could access any other modules variables and procedures defined in the .DEF files without any necessity for extern or similar compiler/linker directive.

Oh that C++ was the same, it seems silly to me that anything declared in a .h file is not available generally to the remainder of the program without causing a problem. I am sure that you will be able to tell me why I am wrong but I do pine for the simplicity and power of Modula2.

@shawn I will look at that solution also. Thanks.
 
I know I am late to the party :D

If you want these variables to be global, then extern is the way to go!

If you put inline in front of the variables then you can leave them in the header:
I have been using c++ for years and have no idea on what inline of a variable does?
I know that with inline of a function, that the code is inserted directly into wherever it is called,
and you won't get error messages for duplicates...

Now if you want these to only be global within a compiled unit (read that .cpp file or the composite of .ino files)
you can mark them as static, like:
Code:
static TransmitPacketType    transmitPacket;
static ReceivePacketType    receivePacket;
But if this header file is included in multiple compile units, there will be a transmitPacket for each of these compiled
files that have different addresses and nothing shared with the other copies of it.
 
But if this header file is included in multiple compile units, there will be a transmitPacket for each of these compiled
files that have different addresses and nothing shared with the other copies of it.
Yes I found that out. I had to place a function in the .h file to return the values.
 
Thanks @PaulStoffregen and @joepasquariello I gave that a try and it works...you knew it would!!

My favourite programming language (of the past) was Modula2.

It allowed modules (libraries) and each module consisted of a .DEF file, for defining variables and Procedures/Functions Headers accessible external to the module, and .MOD files containing the executable code and local variables accessible to the Module.

I liken (obviously erroneously) the .h files to the .DEF files.

In Modula2 any module could access any other modules variables and procedures defined in the .DEF files without any necessity for extern or similar compiler/linker directive.

Oh that C++ was the same, it seems silly to me that anything declared in a .h file is not available generally to the remainder of the program without causing a problem. I am sure that you will be able to tell me why I am wrong but I do pine for the simplicity and power of Modula2.

@shawn I will look at that solution also. Thanks.
I was also a Modula2 user. The good old days!. The H file is analogous to the DEF file, but with caveats. Modula2 was "object-oriented", so the thing you were defining in the DEF file was like a C++ class. Your transmitPacket and receivePacket were in the H file that defines your class, but there were not defined INSIDE your class. They were external, and that's why the compiler/linker complained about duplicates. I was going to ask if you could just move those definitions inside the class, and then there would only be one, but that would have required more changes.
 
I was going to ask if you could just move those definitions inside the class, and then there would only be one, but that would have required more changes.
Believe it or not that had flitted through my mind an hour or so ago, but I thought it is going now, so left it as it is with extern.
I may well change it so that to my mind it is not so cluttered (...with extern, etc).

I am fighting another problem at the moment where Functions just seem to be jumped over, but I am using Serial.print etc for debugging and I suspect that characters or whole lines are being dropped.
 
The inline specifier allows a definition in a header included in multiple places, and the compiler will ensure there’s only one defined.
Thanks, you learn something new every day. Added in C++17... Sort of a confusing name for it as with functions, you end up with copies of the code... But...
 
Believe it or not that had flitted through my mind an hour or so ago, but I thought it is going now, so left it as it is with extern.
I may well change it so that to my mind it is not so cluttered (...with extern, etc).

I am fighting another problem at the moment where Functions just seem to be jumped over, but I am using Serial.print etc for debugging and I suspect that characters or whole lines are being dropped.
Sometimes it is helpful to add Serial.flush() immediately after your debugging Serial.print() statements. This causes the print to occur immediately so you don't get fooled/confused about how far your program got before the problem occurred.
 
Yes that had gone through my mind, but I now think the problem may be electrical. I am driving a Ebyte module off the LC 5v lines. The final system will be externally powered but for test purposes I was just using the usb power through the LC voltage regulator and I think that the power is just on the border line. Just working on and off. Tomorrow I will hook up a Pi power supply. Time will tell but my gut says that will be the solution.
 
Back
Top