Retrofitting an RTC, part 2

A project log for Irreproducible clock

A clock you won't have a display for

Ken YapKen Yap 11/07/2021 at 21:170 Comments

In the last log I coded a bitbang driver for the I2C interface. I now have a working implementation that uses the STM8's silicon. The work consisted of understanding the protocol diagrams in the I2C section of the STM8L reference manual, which is for the STM8L series but also applies to the STM8S series.

Complication arises from the fact the the silicon has a receive pipeline, so some actions have to be taken ahead of reading the data register to generate the correct I2C handshake. Obviously the pipeline exists for speed reasons, for example when reading bulk data from I2C flash memory.

Page 107 of the manual shows different algorithms for the cases where N > 2, N = 2, and N = 1, where N is the number of bytes to read. I also had to consult the example code in stm8s_eval_i2c_ee.c which is a driver for EEPROM. It turns out that it's not sufficient to use just I2C_CheckEvent, one must also use I2C_GetFlagStatus. I have only implemented the N > 2 case as I want 7 bytes from the RTC, so the routine is not general purpose. Bear this in mind if you want to reuse the routine.

The labelled events in the protocol diagram correspond to enum values for the I2C_CheckEvent call defined in the SPL I2C includes. Strictly speaking I should have a timeout on expected events so that MCU doesn't get stuck if an event doesn't arrive. Or perhaps I should use the Watchdog Timer. Anyway I now have two working versions of the I2C driver which can be selected at build time. Since there is still plenty of flash space and MCU power, it makes little difference in practice which one is incorporated. The silicon I2C driver is canonical and doesn't rely on using TIM1 for the delays; you know that the silicon is working as efficiently as designed.

The board was mounted on an A5 acrylic clipboard and I can now finally close off this project.

Here is the gory receive routine:

void rtc_getnow(void)
        // send pointer of 0
        while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
        I2C_Send7bitAddress(DS3231ADDR << 1, I2C_DIRECTION_TX);
        while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED))

        I2C_GenerateSTART(ENABLE);                      // generates a restart
        while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT))
        I2C_Send7bitAddress(DS3231ADDR << 1, I2C_DIRECTION_RX);
        // read sizeof(now) (7) bytes
        I2C_AcknowledgeConfig(I2C_ACK_CURR);            // ACK
        uint8_t i = 0;
        while (i < sizeof(now) - 3) {
                while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED))
                now[i] = I2C_ReceiveData();
        // third last byte
        // wait for BTF
        while (I2C_GetFlagStatus(I2C_FLAG_TRANSFERFINISHED) == RESET)
        I2C_AcknowledgeConfig(I2C_ACK_NONE);            // NAK
        now[i] = I2C_ReceiveData();                     // N-2
        // second last byte
        now[i] = I2C_ReceiveData();                     // N-1
        // last byte
        // wait for RXNE
        while (I2C_GetFlagStatus(I2C_FLAG_RXNOTEMPTY) == RESET)
        now[i] = I2C_ReceiveData();                     // N