Friday, March 13, 2015

PCF8563 - Real Time Clock - I2C Driver - ESP8266 CBDB



   It's time for TIME!

   I was delaying this article about RTC's despite de fact that was higly requested with the hope that the ordered DS3231 modules will arrive in time. Didn't happen, so, until they will hit my MailBox I am presenting you an alternate solution, an RTC chip used before in many projects without any problems: PCF8563 from NXP.

PCF8563 - Real-Time Clock (RTC) and calendar

    The PCF8563 is a CMOS Real-Time Clock (RTC) and Calendar optimized for low power
consumption. A programmable clock output, interrupt output and voltage-low detector are also provided. All addresses and data are transferred serially via a two-line bidirectional I2C-bus with a maximum bus speed of 400 kbit/s. 

   It is maybe not so fancy as DS3231 but can do the job very well and is way more cheaper that DS3231. Like 1.21$ versus 7-8$ on Digikey. I know they are cheaper DS3231 modules on Ebay but if you want to integrate a RTC on your own projects it's something mayber to consider.

    Features: 

     •  Provides year, month, day, weekday, hours, minutes, and seconds based on a
        32.768 kHz quartz crystal
     •  Century flag
     •  Clock operating voltage: 1.0 V to 5.5 V at room temperature
     •  Low backup current; typical 0.25uA at Vdd =3.0V and Tamb=25C
     •   400 kHz two-wire I2C-bus interface (at VDD= 1.8 V to 5.5 V)
     •   Programmable clock output for peripheral devices (32.768 kHz, 1.024 kHz, 32 Hz, and 1Hz)
     •   Alarm and timer functions
     •   Integrated oscillator capacitor
     •   Internal Power-On Reset (POR)
     •   I2C-bus slave address: read A3h and write A2h
     •   Open-drain interrupt pin

 For more details, please see PCF8563 Datasheet

  As been available in SO8 package, we will use again an DIP adaptor that will make it easy to integrate it on our CBDB Board expansion slots:

PCF8563 RTC Module

    Yes, that's it the entire RTC Module: PCF8563, 32.768khz Quarts cristal oscillator and a decoupling cap. Not bad at all, on a tiny DIP8 size board!

     And on the CBDB Board :




     You can see now better the difference between the MSOP8 MCP9808 Temperature sensor, the SO8 PCF8563 RTC and the SOT23 12BIT DAC, MCP4726:

CBDB Board with EXT modules installed

   What we will need:
  • CBDB Board
  • USB adapter (take a look on Part 1 for details how to connect them together)
  • PCF8563 Module from above

    For programming and uploading the driver and the software we will continue to use the LuaUploader as before.
 

Driver implementation

    As PCF8563 has a I2C compatible compatible interface, driver building it following more or less the same  process  as before for I2C devices.

    Few important consideration about PCF8563:

    The PCF8563 contains sixteen 8-bit registers with an auto-incrementing register address,
an on-chip 32.768 kHz oscillator with one integrated capacitor, a frequency divider which
provides the source clock for the Real-Time Clock (RTC) and calender, a programmable
clock output, a timer, an alarm, a voltage-low detector, and a 400 kHz I2C-bus interface.

    All 16 registers are designed as addressable 8-bit parallel registers although not all bits
are implemented. The first two registers (memory address 00h and 01h) are used as
control and/or status registers. The memory addresses 02h through 08h are used as
counters for the clock function (seconds up to years counters). Address locations 09h
through 0Ch contain alarm registers which define the conditions for an alarm.

     Address 0Dh controls the CLKOUT output frequency. 0Eh and 0Fh are the Timer_control
and Timer registers, respectively.

    When one of the RTC registers is written or read, the contents of all time counters are
frozen. Therefore, faulty writing or reading of the clock and calendar during a carry
condition is prevented.

    The Seconds, Minutes, Hours, Days, Months, Years as well as the Minute_alarm,
Hour_alarm, and Day_alarm registers are all coded in Binary Coded Decimal (BCD)
format so we need some BCD to DEC and DEC to BCD functions.


1. Data conversion functions:

  1.1 Decimal to BCD:

        function decToBcd(val)
             local d = string.format("%d",tonumber(val / 10))
             local d1 = tonumber(d*10)
             local d2 = val - d1
            return tonumber(d*16+d2)
         end

  
 1.2  BCD to Decimal:

      function bcdToDec(val)
           local hl=bit.rshift(val, 4)
           local hh=bit.band(val,0xf)
          local hr = string.format("%d%d", hl, hh)
          return string.format("%d%d", hl, hh)
     end


2. Init I2C bus/interface:

        address = 0x51, -- A2, A1, A0 = 0
        id = 0


        init = function (self, sda, scl)
               self.id = 0
              i2c.setup(self.id, sda, scl, i2c.SLOW)
       end

 
3. ReadTime function:

   readTime = function (self)
       wkd = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }
       i2c.start(self.id)
       i2c.address(self.id, self.address, i2c.TRANSMITTER)
       i2c.write(self.id, 0x02)
       i2c.stop(self.id)
       i2c.start(self.id)
       i2c.address(self.id, self.address, i2c.RECEIVER)
       c=i2c.read(self.id, 7)
       i2c.stop(self.id)
       return  bcdToDec(bit.band(string.byte(c,1),0x7f)),
               bcdToDec(bit.band(string.byte(c,2),0x7f)),
               bcdToDec(bit.band(string.byte(c,3),0x3f)),
               bcdToDec(bit.band(string.byte(c,4),0x3f)),
               wkd[tonumber(bcdToDec(bit.band(string.byte(c,5),0x7)))],
               bcdToDec(bit.band(string.byte(c,6),0x1f)),
               bcdToDec(string.byte(c,7))
   end


4. SetTime function:

   setTime = function (self, second, minute, hour, day, date, month, year)
       i2c.start(self.id)
       i2c.address(self.id, self.address, i2c.TRANSMITTER)
       i2c.write(self.id, 0x02)
       i2c.write(self.id, decToBcd(second))
       i2c.write(self.id, decToBcd(minute))
       i2c.write(self.id, decToBcd(hour))
       i2c.write(self.id, decToBcd(day))
       i2c.write(self.id, decToBcd(date))
       i2c.write(self.id, decToBcd(month))
       i2c.write(self.id, decToBcd(year))
       i2c.stop(self.id)
   end


For testing,  pack it together and save the code on ESP as 'pcf8563.lua', restart ESP and run:

-- Set Initial Time and Date
require('pcf8563')                                -- call for new created PCF8563 Module Driver
sda, scl = 2, 1                                      --  declare your I2C interface PIN's
pcf8563:init(sda, scl)                          
-- initialize I2C Bus

 pcf8563:setTime(0,34,13,12,4,3,15)   -- setTime(s,min,hour,day,weekday,month, year)
-- get Time and Date
require('pcf8563')
sda, scl = 2, 1
pcf8563:init(sda, scl)


s, m, h, d, dt, mn, y = pcf8563:readTime()        --ReadTime function call
=string.format("%s - %s/%s/20%s",dt, d, mn, y)
=string.format(" %s:%s:%s", h, m, s)





      As you have seen above, the RTC can be initialized from the input prompt without problems.
 If you want a more automated process to syncronize your Time and Date then you need to look for other options like a NTP Client or to sync it with the Google page help for example. As implementing NTP protocol is a different story itself, let'e see how hard is to pick timestamps from Google page header.


  Time sync with Google page

   How  we will do it:  Connect to Google server, fetch the page header that contain the Timestamp and do some string manipulation to obtain the Date & Time information in the format that we need to be able to update our Real Time Clock:

 getTime = function()
     conn=net.createConnection(net.TCP, 0)
     conn:on("connection",function(conn, payload)
            conn:send("HEAD / HTTP/1.1\r\n"..
                      "Host: google.com\r\n"..
                      "Accept: */*\r\n"..
                      "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)"..
                      "\r\n\r\n")
            end)           
     conn:on("receive", function(conn, payload)
          tstamp = string.sub(payload,string.find(payload,"Date: ")+6,string.find(payload,"Date: ")+35)
          --print('Timestamp : '..tstamp)
          conn:close()
     end)
    conn:connect(80,'google.com')
    conn = nil

    t = {}
    i=0
    for token in string.gmatch(tstamp, "[^%s]+") do
       t[i] = token
       --print(t[i])
       i=i+1
    end

    wkd = {["Sun"]=1, ["Mon"]=2, ["Tue"]=3, ["Wed"]=4, ["Thu"]=5, ["Fri"]=6, ["Sat"]=7 }
    mnt = {["Jan"]=1, ["Feb"]=2, ["Mar"]=3, ["Apr"]=4, ["May"]=5, ["Jun"]=6, ["Jul"]=7, ["Aug"]=8, ["Sep"]=9, ["Oct"]=10, ["Nov"]=11,["Dec"]=12}
    wk1=string.sub(t[0],0,3)
    dtd=wkd[wk1]
    d1=t[1]
    mn1=t[2]
    mnl=mnt[mn1]
    y1=string.sub(t[3],3,4)
    h1=2+string.sub(t[4],0,2)
    m1=string.sub(t[4],4,5)
    s1=string.sub(t[4],7,8)

    --print("Date : "..wk1..", "..d1.."/"..mn1.."/"..t[3])
    --print("Time : "..h1..":"..m1..":"..s1)

    return s1,m1,h1,d1,dtd,mnl,y1
end



 For testing,  pack it together and save the code on ESP as 'gettime.lua', restart ESP and run:

 --GetTime from Google Server and set RTC Time
require('gettime')
s2,m2,h2,d2,dtd2,l2,y2=getTime()
    print("Date : "..dtd..", "..d1.."/"..mn1.."/"..y1)
    print("Time : "..h1..":"..m1..":"..s1)


-- Set Time and Date
require('pcf8563')
sda, scl = 2, 1
pcf8563:init(sda, scl)
pcf8563:setTime(s1,m1,h1,d1,dtd,mnl,y1)




If you run in trouble because of lack of memory, before running, just compile the programs, it will help a lot:

      node.compile("gettime.lua")
      node.compile("pcf8563.lua")


And the Youtube demo:



2 comments:

Vitor_A said...

Hi, great work
I was looking to interface a ds3231 but some some dirty solution to get time from google web site. This module was designed to be web enabled, so why do I need a rtc if I can query google for time. And here is the code:

conn=net.createConnection(net.TCP, 0)
conn:on("connection",function(conn, payload)
conn:send("HEAD / HTTP/1.1\r\n"..
"Host: google.com\r\n"..
"Accept: */*\r\n"..
"User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)"..
"\r\n\r\n")
end)
conn:on("receive", function(conn, payload)
print('\nRetrieved in '..((tmr.now()-t)/1000)..' milliseconds.')
print('Google says it is '..string.sub(payload,string.find(payload,"Date: ")
+6,string.find(payload,"Date: ")+35))
end)
t = tmr.now()
conn:connect(80,'google.com')

Keep ur great work
Regards,Vito

Unknown said...

Thank you for your feedback
.
As you can see above something similar is used to sync RTC to Google time.

Why having a RTC?

The answer is simple: you can have an application that is not 100% of time online, you can have an application where you can shutdown the ESP8266 power-hungry module for power saving for long precise periods of time and power it on only for short burst data sending and so on, pick your favourite scenario.

It's not so useless as it might think :)

Post a Comment