DS1307 RTC Emulator: triple library, one hack


I have already tried once in this article. An emulation of DS1307 chip, to hack in some way other devices, tricking them to talk with the DS1307 chip. I’ve tried to see if was meaningful to emulate a well-known RTC protocol in an MCU instead using the additional dedicated DS1307 RTC chip.

What I have learned so far

I have experienced the real need of software organization, portability and layering. With this goal, I’ve previously developed a firmware for a Microchip PIC microcontroller, exploiting its RTC hardware and writing a layer that allow the MCU to be interfaced with systems that are designed to talk to the DS1307 (which instead was the PIC), without the need to put my hands on their code at all, since it was already designed and tested fot the DS1307 chips that I was emulating. Now I am oriented to use the emulator in more MCUs, so being really platform independent. Again, testing is not an issue, since there are tons of libraries developed for the DS1307, that I can use as testers.

The old library can be modified and used for other microcontrollers (MCUs), with few modifications, but the portability was rough. Moreover, only with an already present RTC hardware in the MCU was possible to emulate an RTC protocol.

In this new article, I will show a DS1307 emulator written in a more portable way. It was tested on an Atmel MCU and with no RTC hardware, but only a timer. Thus a bit of precision estimation shall be done, at the expense of greater portability, because all MCUs have a timer.

The new library: technical specifications and host requirements

This library is absolutely non-blocking with the eventually present user code, that can exist simultaneously. This means that interrupt capabilities must be supported on the used MCU. And all MCUs have this capability.

The host MCU shall contain/support at least:

  • Data Memory: 180 Bytes of RAM
  • Program Memory: 2kB of Flash/EEPROM
  • Oscillator: one external crystal
  • 3 GPIO (one RTC output pin, two I2C pins)
  • I2C bus, physical or emulated on 2 GPIO pins
  • 1 Timer (better if supports counting in sleep mode)
  • (Suggested) I2C physical
  • (Suggested) External crystal oscillator AND internal RC oscillator
  • (Suggested) 5V tolerant pins
  • (Suggested) More memory space for additional user code

The user firmware can:

  • Read/write data internally with DS1307 protocol with no bus or other components/masters required: this means that the emulation is also inside the MCU hosting the emulator itself.
  • Let another MCU (master) to read/write with DS1307 protocol using I2C bus: this is the real emulation, where the MCU hosting the emulator library is connected on the bus, and is detected as the DS1307 and behaves like it.

Behavioral known discrepancies and fixing

At this moment the DS1307 emulator can do everything stated in RTC datasheet. Except that is improved, fixed the unpredicted behavior in case of illogical date and time written in DS1307 emulated register: in this library it mismatches the days and date, but nothing more strange.

But there is a discrepancy, it can output on a digital pin a restricted set of output waves at the moment:

  • 1Hz tick indicator
  • Digital write high
  • Digital write low

Every other configurations like output 32768Hz, 8192Hz, 4096Hz are resulting in a digital write low. While the low power capability depends on the clock source and the supported functionalities of the selected timer.

Firmware organization

The firmware was improved written in two main sections, the rtcProtocol and rtcHal files. The protocol handles the entire logical behavior of the emulated device, and is the core of the project. It can be used practically on any MCU. The Hal contains the calls to the device library, therefore the content of functions in this Hal library can vary, depending on the library available for the used MCU, the user preference and so on.

Here the basic blocks:

Figure 1: firmware block organization

The application (in picture 1) is the code running on the MCU that could need the access of data. This library is non blocking and concurrent. This means that once you have set up the firmware, you could use it also internally and don’t bother of performance reduction due to external requests from masters in the I2C bus. In other words, using this RTC emulator your MCU can do a lot of other things rather than being only an emulated RTC device. The infinte loop in the main is therefore not required to be fullfilled, since everything works in ISR.

The hardware abstraction layer is everything is contained in the rtcHal file, and contains all the calls to the hardware. This is the only part that needs to be modified when changing the MCU adopted, and is this part is confined in the User Defined Library space.

The RTC application is the core of the emulator and never changes when changing the MCU. If RTC data is needed from the application rather than from bus requests,  can be used be means of the API Data. Otherwise, serves the I2C master requests, as the DS1307 original chip does. Accessing through the API Data, if before ending a read/write cycle a bus request arise, will be returned no valid data because is temporary reserved to the application.

How to use it: API and user HAL calls

Library inclusion by one header file: rtc_protocol.h
The behavior is developed by studying the functional details of the original chip. That means a data handling in the same way, in order to guarantee the same data security. To be compatible, the firmware actually buffers all the time dependent data before write on the core variables, buffers all data before is is actually read and other actions like this to avoid rollovers and data corruption. So the library can be exploited by interacting with the API and User Defined Library (UDL).

API.  Are provided mainly 6 user functions:

  1. rtcProtocol_init() -> this call initialize all the system variables and hardware. Must be called once before use any functionality.
  2. rtcProtocol_freezeUserData() -> prepare the data safely avoiding rollover
  3. rtcProtocol_writeUserData(unsigned char byte) -> write one byte in the RTC
  4. unsigned char data = rtcProtocol_readUserData() -> return one byte to data
  5. rtcProtocol_setUserData() -> need to be called in order to apply RTC the data adjustment
  6. rtcProtocol_tickIncrementISR() -> Every HALF SECOND, this call will increment the internal RTC tick

Follows an example on using it.
Read RTC data safely by issuing APIs in this order:

  1. rtcProtocol_freezeUserData();
  2. rtcProtocol_writeUserData(address);
  3. rtcProtocol_setUserData();
  4. rtcProtocol_freezeUserData();
  5. var = rtcProtocol_readUserData();
  6. recall point 5 as many times is needed to read all the RTC bytes
  7. rtcProtocol_setUserData();

Write RTC data safely by issuing APIs in this order:

  1. rtcProtocol_freezeUserData();
  2. rtcProtocol_writeUserData(address);
  3. rtcProtocol_writeUserData(data);
  4. recall point 4 as many times is needed to write all the required RTC bytes
  5. rtcProtocol_setUserData();

While in an independent way the rtcProtocol_tickIncrementISR() is called every 500ms.

For details of when and what write the address and write/read data you can read the official page at Maxim Integrated. Remember that the DS1307 initial conditions are also those one in the emulator library. Learn how to use the DS1307, and you can use the emulator!

UDL.  Are provided few APIs that can be fullfilled with user defined code:

These functions must be fullfilled with the required drivers in order to drive the GPIO, timer and bus. The bus will work on interrupt in order to not block the RTC tick. It is not needed to know exactly when and how their are called, but just know what functionality put in what API. And this is written below:

  1. rtcHal_setPinDigitalMode(void) -> initialize the used defined pin as a digital output pin. Like the pinMode(OUTPUT) in Arduino. This is not a mandatory if a digital output is not needed.
  2. rtcHal_resetPinDigitalMode(void) -> disable the pullup/pulldown capability of the pin and set it as input. Put something like pinMode(INPUT) in Arduino. This is not a mandatory if a digital output is not needed.
  3. rtcHal_setPinDigitalValue(uint8_t value) -> write on a user defined pin the digital value. Like the digitalWrite() in Arduino. This is not a mandatory if a digital output is not needed. This can be used only if the previous ones are fullfilled.
  4. rtcHal_stopRtcTick(void) -> here must be present code that stops the timer. This is mandatory.
  5. rtcHal_startRtcTick(void) -> here must be present code that starts the timer. This is mandatory.

Remember that with the UDL, some other headers might be needed for the user code delcaration, if external functions are used.

ComboLib: integrated by default with a Timer and I2C library

Two Wire Interface

Using the library to interact with master requests, as an DS1307 does, the API functions shall be placed in a proper sequence. This, without a I2C library which rely on a FSM, is a bit tricky. For this reason this library contains also the I2C. If another I2C library is used, the following shall be considered to be fully compatible with the RTC fucntionality:

  1. Configure the host MCU to respond to the same DS1307 address: 0x68
  2. Call the rtcProtocol_freezeUserData() after the start I2C bus condition
  3. Call the rtcProtocol_setUserData() after a stop or restart I2C bus condition
  4. When a byte is received, before any further I2C bus command or condition (new byte, stop req, start req) is received, send it to the rtcProtocol_writeUserData(unsigned char byte).
  5. When a byte is required from master, send the value returned from rtcProtocol_readUserData() before any further bus command or condition.


This library is released with the 3 timers handlers. In this version I have used the Timer1 which trigger an interrupt when reaching a certain compare value. The achieved precision depends on divider errors and crystal precision. To have the DS1307 functionalities, I set up the tick to be 2Hz instead one and make some controls on one tick, and increment time on the other.

So, for a 16MHz quartz oscillator, with a 16bit Timer1 and 2Hz timer tick frequency I have a prescaler of 256 and compare value of 31249:

compare = \frac{16\cdot10^6 MHz}{256 \cdot 2Hz} - 1 = 31249

where the -1 is due to the extra counting when timer goes from 31249 to 0.

The resolution is the one of the quartz which can be similat to the 32768Hz used for the RTC, but consumes more power. The other error is the real tick obtained, but in this case is not present, since 31249 is not an approximation but an integer number:

f_{real} = \frac{16\cdot10^6 MHz}{256 \cdot (31249+1)} = 2 Hz

Other error sources could be the elaoration time, but since the timer uses the system clock without interfering with the CPU resources. When the timer reaches the 31249 value, triggers a signal that interfere with the CPU generatin an IST, but timer will continue immediately from 0. This means that if we have an elaboration time lower than 500ms, there is virtually no risk on losing ticks, both of timer or 2Hz RTC.

Using the Asyncronous Timer can be also a good choice, allowing to use the low power 32768Hz crystal, but the CPU shall be clocked from this or the internal one, and one can’t use the original 16MHz timer, assuming an Arduino is used.

If you want to use your own timer application, make sure that when the timer ticks, calls the rtcProtocol tick increment API, below the example of my implementation, which call the API in the compare interrupt:



On Github are provided few Arduino sketches in order to test the RTC read, write, output pin modes and NV-RAM. Another test is made with the RaspberryPi, following instructions [written here].

Support and download

Github. This library is under constant improvement.
That’s all.