Demo the function of RTC module AM1805

Dependencies:   mbed-dev

Fork of I2C_HelloWorld_Mbed by mbed official

AM1805.cpp

Committer:
marcusC
Date:
2015-12-24
Revision:
2:16b8f527b5c7
Parent:
1:c45df8c46fa8

File content as of revision 2:16b8f527b5c7:

/* AM1805 Sample code: external RTC module is used by host MCU */

#include "mbed.h"
#include "AM1805.h"

#define I2C_SDA p28
#define I2C_SCL p27
#define AM1805_I2C_ADDRESS 0xD2
/* Register Map */
#define AM1805_REG_HUNDREDTHS   0x00
#define AM1805_REG_ALM_HUN      0x08
#define AM1805_REG_STATUS       0x0F
#define AM1805_REG_CTRL1        0x10
#define AM1805_REG_CTRL2        0x11
#define AM1805_REG_INTMASK      0x12
#define AM1805_REG_SLEEPCTRL    0x17
#define AM1805_REG_TIM_CTRL     0x18
#define AM1805_REG_CDTIM        0x19
#define AM1805_REG_TIMINIT      0x1A
#define AM1805_REG_WDT          0x1B
#define AM1805_REG_OSCSTATUS    0x1D
#define AM1805_REG_ID0          0x28
#define AM1805_REG_EXTADDR_REG  0x3F
/* Register Value */
#define AM1805_VALUE_ID0 0x18

#define AM1805_DEBUG 0

I2C i2c(I2C_SDA, I2C_SCL);
#if AM1805_DEBUG
Serial pc(p13,p14);
#endif

static uint8_t get_extension_address(uint8_t address)
{
    uint8_t xadd;
    char temp;

    am1805_register_read(AM1805_REG_EXTADDR_REG, &temp, 1);
    temp = temp & 0xC0;

    if (address < 64) { xadd = 0x8; }
    else if (address < 128) { xadd = 0x9; }
    else if (address < 192) { xadd = 0xA; }
    else { xadd = 0xB; }
    return (xadd | temp);
}

/* Set one or more bits in the selected register, selected by 1's in the mask */
static void setreg(char address, uint8_t mask)
{
    char temp;

    am1805_register_read(address, &temp, 1);
    temp |= mask;
    am1805_register_write(address, temp);
}

/* Clear one or more bits in the selected register, selected by 1's in the mask */
static void clrreg(char address, uint8_t mask)
{
    char temp;
    
    am1805_register_read(address, &temp, 1);
    temp &= ~mask;
    am1805_register_write(address, temp);
}

static bool am1805_verify_product_id(void)
{
    char who_am_i[1];
    am1805_register_read(AM1805_REG_ID0, &who_am_i[0], 1);    
#if AM1805_DEBUG    
    pc.printf("ID:%x\r\n",who_am_i[0]);
#endif    
    if (who_am_i[0] != AM1805_VALUE_ID0) 
        return false;
    else 
        return true;
}

bool am1805_init(void)
{   
  bool transfer_succeeded = false;

  i2c.frequency(400000);
       
  /* Read and verify product ID */
  transfer_succeeded = am1805_verify_product_id();

  return transfer_succeeded;
}

void am1805_register_read(char register_address, char *destination, uint8_t number_of_bytes)
{
  i2c.write(AM1805_I2C_ADDRESS, &register_address, 1, 1);
  i2c.read(AM1805_I2C_ADDRESS, destination, number_of_bytes);    
}

void am1805_register_write(char register_address, uint8_t value)
{   
    char tx_data[2];
    
    tx_data[0] = register_address;
    tx_data[1] = value;
    i2c.write(AM1805_I2C_ADDRESS, tx_data, 2);      

}

void am1805_burst_write(uint8_t *tx_data, uint8_t number_of_bytes)
{
    i2c.write(AM1805_I2C_ADDRESS, (char *)tx_data, number_of_bytes);
}

void am1805_config_input_interrupt(input_interrupt_t index_Interrupt)
{
    switch(index_Interrupt) {
        case XT1_INTERRUPT:
            /* Set input interrupt pin EX1T */
            clrreg(AM1805_REG_STATUS,0x01);             // Clear EX1
            setreg(AM1805_REG_INTMASK,0x01);            // Set EX1E    
            break;
        case XT2_INTERRUPT:
            /* Set input interrupt pin WDI */
            clrreg(AM1805_REG_STATUS,0x02);             // Clear EX2
            setreg(AM1805_REG_INTMASK,0x02);            // Set EX2E       
            break;
        default:
#if AM1805_DEBUG    
            pc.printf("Wrong Input Interrupt Index\r\n");
#endif         
            break;        
    }
}


// Read a byte from local RAM
uint8_t am1805_read_ram(uint8_t address)
{
    uint8_t xadd;
    char temp;
    uint8_t reg_ram = 0;

    xadd = get_extension_address(address);                  // Calc XADDR value from address
    am1805_register_write(AM1805_REG_EXTADDR_REG, xadd);    // Load the XADDR register
    reg_ram = (address & 0x3F) | 0x40;                      // Read the data
    am1805_register_read(reg_ram, &temp, 1);
#if AM1805_DEBUG  
    pc.printf("Read from addr:%x Data:%x\r\n",address,temp);
#endif    
    return (uint8_t)temp;                
}

// Write a byte to local RAM
void am1805_write_ram(uint8_t address, uint8_t data)
{
    uint8_t xadd;
    uint8_t reg_ram = 0;

    xadd = get_extension_address(address);                  // Calc XADDR value from address
    am1805_register_write(AM1805_REG_EXTADDR_REG, xadd);    // Load the XADDR register
    reg_ram = (address & 0x3F) | 0x40;  
    am1805_register_write(reg_ram, data);                   // Write the data
}

/*
 * hundredth : 0 ~ 99
 * second : 0 ~ 59
 * minute : 0 ~ 59
 * weekday : 0 ~ 6
 * month : 1 ~ 12
 * year : 0 ~ 99
 * mode : 0 ~ 2
 */
void am1805_set_time(time_reg_struct_t time_regs)
{
    char temp;
    uint8_t temp_buff[9];

    /* 
     * Determine whether 12 or 24-hour timekeeping mode is being used
     * and set the 1224 bit appropriately
     */
    if (time_regs.mode == 2)        // 24-hour day
    {
        clrreg(AM1805_REG_CTRL1, 0x40);
    }
    else if (time_regs.mode == 1)   // 12-hour day PM
    {
        time_regs.hour |= 0x20;     // Set AM/PM
        setreg(AM1805_REG_CTRL1, 0x40);
    }
    else                            // 12-hour day AM
    {
        setreg(AM1805_REG_CTRL1, 0x40);
    }

    /* Set the WRTC bit to enable counter writes. */
    setreg(AM1805_REG_CTRL1, 0x01);

    /* Set the correct century */
    if (time_regs.century == 0)
    {
        clrreg(AM1805_REG_STATUS, 0x80);
    }
    else
    {
        setreg(AM1805_REG_STATUS, 0x80);
    }

    /* Write all of the time counters */
    temp_buff[0] = AM1805_REG_HUNDREDTHS;
    temp_buff[1] = time_regs.hundredth;
    temp_buff[2] = time_regs.second;
    temp_buff[3] = time_regs.minute;
    temp_buff[4] = time_regs.hour;
    temp_buff[5] = time_regs.date;
    temp_buff[6] = time_regs.month;
    temp_buff[7] = time_regs.year;
    temp_buff[8] = time_regs.weekday;

    /* Write the values to the AM18XX */
    am1805_burst_write(temp_buff, sizeof(temp_buff));

    /* Load the final value of the WRTC bit based on the value of protect */
    am1805_register_read(AM1805_REG_CTRL1, &temp, 1);
    temp &= 0x7E;                   // Clear the WRTC bit and the STOP bit
    //temp_buff[1] |= (0x01 & (~protect));    // Invert the protect bit and update WRTC
    am1805_register_write(AM1805_REG_CTRL1, temp);
       
    return;    
}

void am1805_get_time(time_reg_struct_t *time_regs)
{
    char temp_buff[8];
    char time_mode;

    /* Read the counters. */
    am1805_register_read(AM1805_REG_HUNDREDTHS, temp_buff, 8);

    time_regs->hundredth = temp_buff[0];
    time_regs->second = temp_buff[1];
    time_regs->minute = temp_buff[2];
    time_regs->hour = temp_buff[3];
    time_regs->date = temp_buff[4];
    time_regs->month = temp_buff[5];
    time_regs->year = temp_buff[6];
    time_regs->weekday = temp_buff[7];

    /* Get the current hours format mode 12:24 */
    am1805_register_read(AM1805_REG_CTRL1, &time_mode, 1);
    if ((time_mode & 0x40) == 0)
    {
        /* 24-hour mode. */
        time_regs->mode = 2;
        time_regs->hour = time_regs->hour & 0x3F;           // Get tens:ones
    }
    else
    {
        /* 12-hour mode.  Get PM:AM. */
        time_regs->mode = (time_regs->hour & 0x20) ? 1 : 0;  // PM : AM
        time_regs->hour &= 0x1F;                            // Get tens:ones
    }

    time_regs->hundredth = temp_buff[0];
    time_regs->second = temp_buff[1];
    time_regs->minute = temp_buff[2];
    time_regs->hour = temp_buff[3];
    time_regs->date = temp_buff[4];
    time_regs->month = temp_buff[5];
    time_regs->year = temp_buff[6];
    time_regs->weekday = temp_buff[7];

#if AM1805_DEBUG 
    pc.printf("hundredth:%x\r\n",time_regs->hundredth);
    pc.printf("second:%x\r\n",time_regs->second);
    pc.printf("minute:%x\r\n",time_regs->minute);
    pc.printf("hour:%x\r\n",time_regs->hour);
    pc.printf("date:%x\r\n",time_regs->date);
    pc.printf("month:%x\r\n",time_regs->month);
    pc.printf("year:%x\r\n",time_regs->year);
    pc.printf("weekday:%x\r\n",time_regs->weekday);
#endif     
}

void am1805_config_alarm(time_reg_struct_t time_regs, alarm_repeat_t repeat, interrupt_mode_t intmode, interrupt_pin_t pin)
{ 
    char temp;
    uint8_t temp_buff[9];

    /* Determine whether a 12-hour or a 24-hour time keeping mode is being used */
    if (time_regs.mode == 1)
    {
        /* A 12-hour day PM */
        time_regs.hour = time_regs.hour | 0x20;   // Set AM/PM
    }

    /* Write all of the time counters */
    temp_buff[0] = AM1805_REG_ALM_HUN;
    temp_buff[1] = time_regs.hundredth;
    temp_buff[2] = time_regs.second;
    temp_buff[3] = time_regs.minute;
    temp_buff[4] = time_regs.hour;
    temp_buff[5] = time_regs.date;
    temp_buff[6] = time_regs.month;
    temp_buff[7] = time_regs.weekday;

    clrreg(AM1805_REG_TIM_CTRL, 0x1C);      // Clear the RPT field
    clrreg(AM1805_REG_INTMASK, 0x64);       // Clear the AIE bit and IM field
    clrreg(AM1805_REG_STATUS, 0x04);        // Clear the ALM flag

    if (pin == PIN_FOUT_nIRQ)
    {
        /* Interrupt on FOUT/nIRQ */
        am1805_register_read(AM1805_REG_CTRL2, &temp, 1);   // Get the Control2 Register       
        temp = (temp & 0x03);               // Extract the OUT1S field        
        if (temp != 0)                      // Not already selecting nIRQ
        {
            setreg(AM1805_REG_CTRL2, 0x03);    // Set OUT1S to 3           
        }        
    }
    if (pin == PIN_PSW_nIRQ2)
    {
        /* Interrupt on PSW/nIRQ2 */
        am1805_register_read(AM1805_REG_CTRL2, &temp, 1);   // Get the Control2 Register
        temp &= 0x1C;                       // Extract the OUT2S field
        if (temp != 0)                      // Not already selecting nIRQ
        {
            clrreg(AM1805_REG_CTRL2, 0x1C);    // Clear OUT2S
            setreg(AM1805_REG_CTRL2, 0x0C);    // Set OUT2S to 3
        }
    }

    if (repeat == ONCE_PER_10TH_SEC)
    {
        /* 10ths interrupt */
        temp_buff[1] |= 0xF0;
        repeat = ONCE_PER_SECOND;                   // Select correct RPT value
    }
    if (repeat == ONCE_PER_100TH_SEC)
    {
        /* 100ths interrupt */
        temp_buff[1] = 0xFF;
        repeat = ONCE_PER_SECOND;                   // Select correct RPT value
    }
    if (repeat != 0)                                // Don't initiate if repeat = 0
    {
        temp = (repeat << 2);                       // Set the RPT field to the value of repeat
        setreg(AM1805_REG_TIM_CTRL, temp);          // Was previously cleared
        setreg(AM1805_REG_INTMASK, (intmode << 5)); // Set the alarm interrupt mode
        setreg(AM1805_REG_INTMASK, 0x60); // Set the alarm interrupt mode
        am1805_burst_write(temp_buff, 8);           // Execute the burst write
        setreg(AM1805_REG_INTMASK, 0x04);           // Set the AIE bit        
    }
    else
        setreg(AM1805_REG_INTMASK, 0x60);           // Set IM field to 0x3 (reset value) to minimize current draw
    
    return;
}

void am1805_config_countdown_timer(count_down_range_t range, int32_t period, count_down_repeat_t repeat, interrupt_pin_t pin)
{
    uint8_t tm;
    uint8_t trpt;
    uint8_t tfs;
    uint8_t te;
    char temp;
    char tctrl;
    int32_t timer;
    char oscmode; 

    /* 0 = XT, 1 = RC */
    am1805_register_read(AM1805_REG_OSCSTATUS, &oscmode, 1);   
    oscmode = (oscmode & 0x10) ? 1 : 0;
    
    /* disable count down timer */
    if (pin == INTERNAL_FLAG)
    {
        te = 0;
    }
    else
    {
        te = 1;
        if (repeat == SINGLE_LEVEL_INTERRUPT)
        {
            /* Level interrupt */
            tm = 1;                                     // Level
            trpt = 0;                                   // No repeat
            if (range == PERIOD_US)
            {
                /* Microseconds */
                if (oscmode == 0)
                {
                    /* XT Mode */
                    if (period <= 62500)                // Use 4K Hz
                    {
                        tfs = 0;
                        timer = (period * 4096);
                        timer = timer / 1000000;
                        timer = timer - 1;
                    }
                    else if (period <= 16384000)        // Use 64 Hz
                    {
                        tfs = 1;
                        timer = (period * 64);
                        timer /= 1000000;
                        timer = timer - 1;
                    }
                    else                                // Use 1 Hz
                    {
                        tfs = 2;
                        timer = period / 1000000;
                        timer = timer - 1;
                    }
                }
                else
                {
                    /* RC Mode */
                    if (period <= 2000000) {            // Use 128 Hz
                        tfs = 0;
                        timer = (period * 128);
                        timer /= 1000000;
                        timer = timer - 1;
                    }
                    else if (period <= 4000000) {       // Use 64 Hz
                        tfs = 1;
                        timer = (period * 64);
                        timer /= 1000000;
                        timer = timer - 1;
                    }
                    else {                              // Use 1 Hz
                        tfs = 2;
                        timer = period / 1000000;
                        timer = timer - 1;
                    }
                }
            }
            else
            {
                /* Seconds */
                if (period <= 256)
                {
                    /* Use 1 Hz */
                    tfs = 2;
                    timer = period - 1;
                }
                else
                {
                    /* Use 1/60 Hz */
                    tfs = 3;
                    timer = period / 60;
                    timer = timer - 1;
                }
            }
        }
        else
        {
            /* Pulse interrupts */
            tm = 0;                 // Pulse
            trpt = repeat & 0x01;   // Set up repeat
            if (repeat < REPEAT_PLUSE_1_128_SEC)
            {
                tfs = 0;
                if (oscmode == 0)
                {
                        timer = (period * 4096);
                        timer /= 1000000;
                        timer = timer - 1;
                }
                else
                {
                        timer = (period * 128);
                        timer /= 1000000;
                        timer = timer - 1;
                }
            }
            else if (repeat < REPEAT_PLUSE_1_64_SEC)
            {
                tfs = 1;
                timer = (period * 128);
                timer /= 1000000;
                timer = timer - 1;
            }
            else if (period <= 256)
            {
                /* Use 1 Hz */
                tfs = 2;
                timer = period - 1;
            }
            else
            {
                /* Use 1/60 Hz */
                tfs = 3;
                timer = period / 60;
                timer = timer - 1;
            }
        }
    }
    
    am1805_register_read(AM1805_REG_TIM_CTRL, &tctrl, 1);               // Get TCTRL, keep RPT, clear TE
    tctrl = tctrl & 0x1C;
    am1805_register_write(AM1805_REG_TIM_CTRL, tctrl);

    tctrl = tctrl | (te * 0x80) | (tm * 0x40) | (trpt * 0x20) | tfs;    // Merge the fields
    
    if (pin == PIN_FOUT_nIRQ)                                           // generate nTIRQ interrupt on FOUT/nIRQ (asserted low)
    {
         clrreg(AM1805_REG_CTRL2, 0x3);                                 // Clear OUT1S
    }
    if (pin == PIN_PSW_nIRQ2)                                           // generate nTIRQ interrupt on PSW/nIRQ2 (asserted low)
    {
         am1805_register_read(AM1805_REG_CTRL2, &temp, 1);              // Get OUT2S
         if ((temp & 0x1C) != 0)
         {
             temp = (temp & 0xE3) | 0x14;                               // If OUT2S != 0, set OUT2S to 5
         }
         am1805_register_write(AM1805_REG_CTRL2, temp);                 // Write back
    }
    if (pin != 0)
    {
        clrreg(AM1805_REG_STATUS,0x08);                     // Clear TIM
        setreg(AM1805_REG_INTMASK,0x08);                    // Set TIE
        am1805_register_write(AM1805_REG_CDTIM, timer);     // Initialize the timer
        am1805_register_write(AM1805_REG_TIMINIT, timer);   // Initialize the timer repeat
        am1805_register_write(AM1805_REG_TIM_CTRL, tctrl);  // Start the timer        
    }
    
    return ;    
}

/** Parameter:
 *  timeout - minimum timeout period in 7.8 ms periods (0 to 7)
 *  mode - sleep mode (nRST modes not available in AM08xx)
 *      0 => nRST is pulled low in sleep mode
 *      1 => PSW/nIRQ2 is pulled high on a sleep
 *      2 => nRST pulled low and PSW/nIRQ2 pulled high on sleep
 *  error ?returned value of the attempted sleep command
 *      0 => sleep request accepted, sleep mode will be initiated in timeout seconds
 *      1 => illegal input values
 *      2 => sleep request declined, interrupt is currently pending
 *      3 => sleep request declined, no sleep trigger interrupt enabled
**/
void am1805_set_sleep(uint8_t timeout, uint8_t mode)
{
    uint8_t slres = 0;
    char temp = 0;

#if AM1805_DEBUG    
    am1805_register_read(AM1805_REG_CTRL2, &temp, 1);       // Get SLST bit (temp & 0x08)
    
    if ( ( temp & 0x08 ) == 0)
    {
        pc.printf("Previous Sleep Failed\r\n");
    } else {
        pc.printf("Previous Sleep Successful\r\n");    
    }
    clrreg(AM1805_REG_CTRL2,0x08);                     // Clear SLST
    
    am1805_register_read(AM1805_REG_CTRL2, &temp, 1);       // Get SLST bit (temp & 0x08)
    
    if ( ( temp & 0x08 ) == 0)
    {
        pc.printf("Clear Succ\r\n");
    } else {
        pc.printf("Clear Fail\r\n");    
    }
    clrreg(AM1805_REG_CTRL2,0x08);                     // Clear SLST   
#endif
 
    if (mode > 0)
    {
        /* Sleep to PSW/nIRQ2 */
        am1805_register_read(AM1805_REG_CTRL2, &temp, 1);   // Read OUT2S
        temp = (temp & 0xE3) | 0x18;                        // MUST NOT WRITE OUT2S WITH 000
        am1805_register_write(AM1805_REG_CTRL2, temp);      // Write value to OUT2S
        slres = 0;
    } 
    
    temp = timeout | (slres << 6) | 0x80;                   // Assemble SLEEP register value
    am1805_register_write(AM1805_REG_SLEEPCTRL, temp);      // Write to the register    

#if AM1805_DEBUG
    /* Determine if SLEEP was accepted */
    am1805_register_read(AM1805_REG_CTRL2, &temp, 1);       // Get SLP bit (temp & 0x80)
    
    if ( ( temp & 0x80 ) == 0)
    {
        char reg_wdi_value = 0;
        /* SLEEP did not happen - determine why and return reason. */
        am1805_register_read(AM1805_REG_INTMASK, &temp, 1);         // Get status register interrupt enables
        am1805_register_read(AM1805_REG_WDT, &reg_wdi_value, 1);    // Get WDT register
        if ((( temp & 0x0F ) == 0) & (((reg_wdi_value & 0x7C) == 0) || ((reg_wdi_value & 0x80) == 0x80)))
        {
            pc.printf("No trigger interrupts enabled\r\n");
        }
        else
        {
            pc.printf("Interrupt pending\r\n");
        }
    }
    else
    {
        pc.printf("SLEEP request successful\r\n");
    }
#endif
}