I2C PIC16F690 and RTC DS3231 help

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
I want to be able to read and write to a DS3231 real time clock module using a PIC16F690. I have read about using the 'bit banging' method as when reading about the PICs I2C hardware control, it seems very long and complicated.

Has anyone successfully read and written to a DS3231? In the datasheet for it, it says that if the power is removed from the microprocessor while a read or write is in progress, the 2 devices will be 'out of sync' as the DS3231 (as it is battery backed) could still be halfway through a data transfer the next time the microprocessor tries to talk to it.

I program in assembly language so would appreciate any help and links directing me to any source for this.

Thanks :)
 

ErnieM

Joined Apr 24, 2011
8,377
Anything done in assembly is "long and complicated." That POIC has an SSP module to handle I2C for you for the most part, you still have to initiate each part of the transfer (start, address, data, stop) and know when to copy any data received.

Being halfway thru a transfer is not an issue as the PIC will issue a start signal to reset any device out there.

Wikipedia is as good a place as any to learn how this protocol works. You will have to dig up example code in assembly yourself, I have no links to that.
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
Thanks for the reply, I was reading that the PIC16F690 does not support I2C master mode on it's SSP hardware, but the datasheet says that it does, so I guess the post I was reading on the internet is wrong.

Anyway, the datasheet for the DS3231 says:

"The I2C interface is accessible whenever either VCC or
VBAT is at a valid level. If a microcontroller connected to
the DS3231 resets because of a loss of VCC or other
event, it is possible that the microcontroller and DS3231
I2C communications could become unsynchronized,
e.g., the microcontroller resets while reading data from
the DS3231. When the microcontroller resets, the
DS3231 I2C interface may be placed into a known state
by toggling SCL until SDA is observed to be at a high
level. At that point the microcontroller should pull SDA
low while SCL is high, generating a START condition."

Going by what this is saying, before I put the Start condition on the SDA and SCL lines, I need to toggle the SCL until it 'synchronises'?

I am not a novice at programming and successfully wrote asm code a few years back to communicate with a Sony cam-corder via the LANC protocol, but I'm a little rusty.

I have read pages and pages of Microchip datasheets showing how to implement the I2C module in various PIC microprocessors, but they is hundreds of lines of code to do what should be a fairly easy task.

I did find this implementation of I2C, will this work for my needs?

http://www.piclist.com/techref/microchip/i2c-dv.htm

Anyone actually used a PIC in 'bit bang' mode to access a DS3231
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
Any assembly language program needs "hundreds of lines of code to do what should be a fairly easy task" which is why I am always constantly surprised to see people excited to be working in it.

I've seen I2C latch like that but only when I was doing debugging of an I2C device. I used the same work around the data sheet suggests.

Knowing that part did that I would pass and get something else with same or better function.

As far as the code you found, if you try and try and read it over and over you should be able to adapt it for your needs. I2C has some subtle points where you have to frame the conversation in discrete chunks.
 

THE_RB

Joined Feb 11, 2008
5,438
You can generate a dummy start followed by a dummy stop. Then a start and the proper comms followed by the stop.

The first (dummy) start/stop will force the I2C slave to reset or resync as needed before you do the proper comms.
 

MaxHeadRoom

Joined Jul 18, 2013
28,702
I want to be able to read and write to a DS3231 real time clock module using a PIC16F690. I have read about using the 'bit banging' method as when reading about the PICs I2C hardware control, it seems very long and complicated.
If Assembly programming I would recommend graduating to the 18F series μp's.
Not only for ease of programming a few more powerful instructions.
There is minimal programming to do when implementing the I2c and there is also App notes such as AN989 etc for sample code etc.
You don't need to resort to bit banging.
For the 16f628
http://www.winpicprog.co.uk/pic_tutorial6.htm
Max.
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
You can generate a dummy start followed by a dummy stop. Then a start and the proper comms followed by the stop.

The first (dummy) start/stop will force the I2C slave to reset or resync as needed before you do the proper comms.
The worst case slave lock-up I've seen in debug is the slave stuck transmitting a zero for an ACK, but any transmit of zero has the same effect: you cannot generate any bus commands.

That's why you need to toggle SCK till SDA comes back.

Note that only applies when the slave is stuck with the power on. I never had to use my work-around once I left debug mode because a later generation I2C interface resets with the power and ignores the battery back up. Vdd would reset anything stuck.

That's another reason to avoid the DS3231. The PCF2129A has similar features, fewer bugs, and it's half the price.
 
Last edited:

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
Thanks for the help. The app note AN989 looks interesting and there isn't much code to get my head around.

It says it's for interfacing with a 24C EEPROM, I take it that the DS3231 (as it is I2C) can be read and written in the same way. I do have the datasheet for the DS3231 and it looks pretty straight forward as long as the comms work correctly in my PIC.

I still want to use a low pin count chip, so would the PIC18F13K22 do the job? It looks as though it has a MSSP control module, so I should be able to port the code in the app note to it fairly easily shouldn't I?

Sorry for all the questions, Thanks again :)
 
Last edited:

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
ErnieM, I'm only using the DS3231 as it comes as a module at a stupidly cheap price from China (about £2). It comes with battery, battery holder, crystal etc. It wasn't until I bought a couple (which I'm still waiting for) that I realised there are many others to choose from.

Just trying to get my head around it all before the modules arrive so I can get stuck in. I've got a couple of projects to use with them. A multiplexed Nixie tube clock and an LED HDD clock. Just want to make sure they keep the correct time. I did make a binary style clock a few years ago using the internal oscillator of a PIC16F690, but it would be several minutes a week out.
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
As I read more into this, I think I may go down the road of 'bit banging' as it can be used on any PIC I wish to use, even the 8 pin PIC12F series I also use on a regular basis. I have found this site with some easy to read and understand code:

http://davbucci.chez-alice.fr/index.php?argument=elettronica/pic_i2c/pic_i2c.inc&language=English

By looking at the code, to read a particular register in the DS3231 it seems that all that I would need to do is:

-Reading-
1 - call the 'i2cstart' routine
2 - put the DS3231 address into the COM register and call 'i2csend'
3 - put the register I want to read into the COM register and call 'i2csend'
4 - then call the 'i2crecieve' routine
5 - then call either the 'i2csendack' or 'i2cnoack' routine
6 - call the 'i2cstop' routine

to write to an address in the DS3231:

-Writing-
1 - call the 'i2cstart' routine
2 - put the DS3231 address into the COM register and call 'i2csend'
3 - put the register I want to read into the COM register and call 'i2csend'
4 - put the byte I want to write into the COM register and call 'i2csend'
5 - call the 'i2cstop' routine

The DS3231 says that it can be serially read and written, so I assume I can do actions 4 and 5 as many times as needed in 'Reading' and actions 4 as many times as needed in 'Writing'

Can someone take a look and see if what I have written looks like it should work. I've gone over it a couple of times and I think it looks as though it should.

Thanks
 

ErnieM

Joined Apr 24, 2011
8,377
You seem to have the basics of writing to the DS, but reading is more "nuanced" as to read the device you first must write to it.

"The DS3231 then begins to transmit data
starting with the register address pointed to by the
register pointer. If the register pointer is not written to
before the initiation of a read mode, the first address
that is read is the last one stored in the register pointer.
The DS3231 must receive a not acknowledge to
end a read."
So to read you start by sending the slave address, then the register address you will want to read, then sending a stop... then being the read by sending the slave write address followed by receiving the data starting at the register you just previously wrote. It confused me endlessly when I started I2C that a read starts with a write.

BTW, I love cheap gadgets from China off Ebay. I buy em all the time just to have on hand.
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
Oh I see now. I wondered from the datasheet why the address of the byte you wanted to read doesn't get sent when reading.

So when reading from the device (address 1101000), are these the steps required?

1 - Send a Start condition followed by the address with a 0 (R/W bit) at the end (11010000)
2 - Check Ack bit
3 - Send the address of the register to read (00H) (The seconds register)
4 - Check Ack bit
5 - Send the Stop condition
(the device has been set to be read)

6 - Send a Start condition followed by the address with a 1 (R/W bit) at the end 11010001
7 - Check Ack bit
8 - Now read a byte (should read 00H, the seconds register)
9 - Send NotAck bit
10 - Send Stop condition

By reading the datasheet again, it looks like to read more than 3 bytes, I would Read a byte, send Ack bit, read another byte, send Ack bit, read another byte and then send NotAck bit to end the read, then a Stop condition. So if I put 00H in the initial step 3 (setting the DS3231 pointer) I can then read 3 registers (seconds, minutes and hours) then send the Stop condition.

Does that sound right?

Thanks again for your time, I think I'm nearly there.

Just out of interest, here is the small RTC module I have bought from Hong Kong, how can they produce and sell these so cheap?

http://www.ebay.co.uk/itm/DS3231-AT...al_Components_Supplies_ET&hash=item5d47118fd3
 

ErnieM

Joined Apr 24, 2011
8,377
Yes you got it now. :D

How do they sell a $3 part mounted on a board with other devices for $1?

I would suspect shenanigans, but I have no proof.
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
I know! The stuff you buy from Hong Kong is usually cheaper (with the shipping) than what you can buy a single component for in the UK. I needed a 5v TORX optical receiver for a DAC I built last year. I think they were about £7 in the UK. Or the same thing from China was £6 for 5 of them with free shipping!!!

Anyway, back to my project.

I'm building a Nixie tube clock, and also looking at an LED HDD clock too (I like clocks). I was originally going to set up the PIC to read the RTC and then use a 1 second timer inside the PIC to increment the time and then once every 24 hours, say 2am, read the RTC again in case the PIC has 'wondered'. I was going to do this so I wasn't constantly reading the RTC every second. Then I read on the datasheet that you can set the RTC to output a 1Hz square wave on the SQW pin. So I could use this to poll my clock.

What's the normal way people use these devices?

I will have 2 buttons on the back of the clock, increment minutes and increment hours. Each time a button is pressed the minutes, or hours are incremented and the time written to the RTC module.

Just thought that writing to the DS3231 register that configures the SQW pin would be a good way of testing the comms while I'm playing around with I2C.
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
If your entire application is a clock you spend most of your time (no pun intended) waiting for the next second. It's not a difficult app. So you can do it any way you wish.

My preference would be to use the 1Hz pulse in the same way a mechanical clock used a pendulum: when it ticks increment the time, hold till the tock is over. You could keep the time local in memory or read it from the device each second. It is actually easier to read it back, you automatically get minute, hour, day, leap year updates.

I've hinted several times without response so let me ask you directly: why use assembly? Higher level languages may be had for either free or damn cheap.

Oh, and as far as clocks, everyone gets the bug and makes one. Here's mine:


Since the PIC used has a built-in clock I would just read my battery backup clock once on power on.
 

Attachments

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
I've just experimented with the interrupt on RA2 of the PIC16F690. I have got it to carry out the interrupt routine on every rising edge of RA2, so I will use the square wave output of the DS3231 as my timebase for the program.

The reason for programming is assembly language is because that's what I've always done. I like the control you have with exact timing. You know exactly how long each instruction takes etc. It may be a bit old fashion, but that's what I'm used too. Things do take a bit more coding, but you end up with the same result in the end.

I have also written programs in Objective C 2.0 for the iPhone and years ago made an in car entertainment system with touch screen in VB.net

Yeah, I was thinking of reading the RTC module on startup and using my own registers in the PIC to store the time. Then the 1Hz square wave will give me the signal to increment the seconds.

I am still was wondering about when the PIC is first powered and the ports are being setup (the TRIS registers), obviously the SLK and SDA lines on the I2C bus may change state and could accidentally start a transfer to the DS3231. And reading the datasheet, it doesn't 'time out' it will just sit there after it receives a Start signal indefinitely. I suppose I will have to do what it says in the datasheet and toggle the clock until I get a high on the SDA line and then pull the line low to create the start.

Maybe I should write a START sub routine that checks the state of the SDA line. If it's low, I toggle the SLK until SDA is high, then pull it low. This way at least I know that the next load of bits I send will actually be communicating properly with the DS3231. Does that sound right?
 
Last edited:

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
OK. I haven't recieved my DS3231 RTC module yet, but have started a little programming on the PIC16F690. Using the code by Davide Bucci, which I got from here:

http://davbucci.chez-alice.fr/index.php?argument=elettronica/pic_i2c/pic_i2c.inc&language=English

I think I have got something that should work.

All the subs should work as they are the same as Davide's.

After the program starts, I get it to read the contents of the seconds register and then I write a value to the seconds register in the DS3231.

If anyone can have a scan through to see if it looks correct I would be most grateful :)

Rich (BB code):
;-----------------------------------------------------------------------------------------------;
;        I2C                                                                                     ;
;        ---                                                                                      ;
;                                                                                                ;
;    Author: Craig Smith                                                                            ;
;    Date:    May 2014                                                                            ;
;                                                                                               ;
;   [i2c routines by Davide Bucci (sourced from internet)]                                      ;
;                                                                                                ;
;    4MHz internal resonator used                                                                 ;
;                                                                                                ;
;   I2C protocol demo to interface with DS3231 RTC module                                       ;
;                                                                                               ;
;   RA0 -   Clock signal for RTC module                                                         ;
;   RA1 -   Data signal for RTC module                                                          ;
;                                                                                                ;
;                                                                                               ;
;   Writing a byte:                                                                             ;
;   1   - Call i2cStart                                                                         ;
;   2   - Put DS3231Add into DataReg, rotate left and set bit 0 to 0 (write)                    ;
;   3   - Call i2cSend                                                                          ;
;   4   - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   5   - Put register to be written into DataReg                                               ;
;   6   - Call i2cSend                                                                          ;
;   7   - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   8   - Put byte to write into DataReg                                                        ;
;   9   - Call i2cSend                                                                          ;
;   10  - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   11  - Call i2cStop                                                                          ;
;   - To send more than 1 byte repeat steps 9 and 10 -                                          ;
;                                                                                               ;
;   Reading a byte: (First we write to the DS3231 to set the address of where to read from)     ;
;   1   - Call i2cStart                                                                         ;
;   2   - Put DS3231Add into DataReg, rotate left and set bit 0 to 0 (write)                    ;
;   3   - Call i2cSend                                                                          ;
;   4   - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   5   - Put register to be read into DataReg                                                  ;
;   6   - Call i2cSend                                                                          ;
;   7   - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   8   - Call i2cStop                                                                          ;
;   9   - Call i2cStart                                                                         ;
;   10  - Put DS3231Add into DataReg, rotate left and set bit 0 to 1 (read)                     ;
;   11  - Call i2cSend                                                                          ;
;   12  - If W reg is 00 transmition acknoleged, FF = not acknoweged                            ;
;   13  - Call i2cRead (DataReg now has the contents of the register read from the DS3231       ;
;   14  - Call i2cNoAck                                                                         ;
;   15  - Call i2cStop                                                                          ;
;   - To read more than 1 byte, do 13 then Call i2cAck, then 13, Call i2cAck... after the last  ;
;     read byte, Call i2cNoAck, then Call i2cStop                                               ;
;-----------------------------------------------------------------------------------------------;



#include <p16F690.inc>
    __config (_INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_ON & _CPD_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF)


; Control lines of the I2C interface
    SCL         equ .0
    SDA         equ .1
    I2CPort     equ PORTA
    I2CTris     equ    TRISA
    DS3231Add   equ b'01101000' ; the address of the DS3231 RTC module


    udata 0x20
    DataReg     res 1           ; Register to read/write in the DS3231
    Count       res 1           ; counter
    org 0
    goto    Start               ; jump to Start
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
Had to split the code as it was too long for one post.....

Rich (BB code):
;-----------------------------------------------------------------------------------------------;
; Subroutines are here at the top                                                                ;
;-----------------------------------------------------------------------------------------------;

;-----------------------------------------------------------------------------------------------;
; Short Delay                                                                                   ;
;-----------------------------------------------------------------------------------------------;
ShortDelay:
    nop
    nop
    nop
    nop
    nop
    return

;-----------------------------------------------------------------------------------------------;
; i2cStart gets everything ready to start sending data to the DS3231                            ;
;-----------------------------------------------------------------------------------------------;
i2cStart:
    banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    bcf     I2CTris, SCL        ; SCL as output
    banksel I2CPort

    bsf     I2CPort, SDA        ; The start condition on the I2C bus
    bsf     I2CPort, SCL        ; An high to low transition when SCL is high
    call    ShortDelay
    bcf     I2CPort, SDA
    call    ShortDelay
    bcf     I2CPort, SCL
    call    ShortDelay          ; Leave SDA and SCL low
    return


;-----------------------------------------------------------------------------------------------;
; i2cStop sets the stop condition on the 2 interface lines                                      ;
;-----------------------------------------------------------------------------------------------;
i2cStop:
    banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    Banksel I2CPort
    bcf     I2CPort, SCL
    bcf     I2CPort, SDA        ; The stop condition on the bus I2C
    call    ShortDelay
    bsf     I2CPort, SCL        ; A low to high transition when SCL is high
    call    ShortDelay
    bsf     I2CPort, SDA
    call    ShortDelay          ; SCL and SDA lines are left high
    return

;-----------------------------------------------------------------------------------------------;
; i2cSend send a byte over the i2c lines                                                        ;
;-----------------------------------------------------------------------------------------------;
i2cSend:
    movwf   DataReg             ; return 0x00 if ACK
    movlw   0x08
    movwf   Count
    banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    Banksel I2CPort

i2cSendLoop:
    bcf     I2CPort, SCL        ; Clock low: change of SDA allowed
    rlf     DataReg,f
    bcf     I2CPort, SDA
    btfsc   STATUS, C           ; Test the carry bit
    bsf     I2CPort, SDA
    call    ShortDelay
    bsf     I2CPort, SCL        ; Clock high
    call    ShortDelay
    decfsz  Count,f
    goto    i2cSendLoop

i2cWaitAck:
    bcf     I2CPort, SCL        ; Clock low
    bsf     I2CPort, SDA
    banksel I2CTris
    bsf     I2CTris, SDA        ; SDA as input
    banksel I2CPort
    call    ShortDelay
    bsf     I2CPort, SCL        ; Clock high
    call    ShortDelay
    movlw   0x00                ; Ox00 in w means ack
    btfsc   I2CPort, SDA        ; SDA low means ack
    movlw   0xFF                ; 0xFF in w means no ack
    Banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    banksel I2CPort             ; Clock is left low
    bcf     I2CPort, SCL
    call    ShortDelay
    return

;-----------------------------------------------------------------------------------------------;
; i2cRead reads a byte over the i2c lines                                                       ;
;-----------------------------------------------------------------------------------------------;
i2cRead:
    clrf    DataReg             ; Read a byte over the I2C interface
    movlw   0x08
    movwf   Count
    banksel I2CTris
    bsf     I2CTris, SDA        ; SDA as input
    banksel I2CPort

i2cReadLoop:
    bcf     I2CPort, SCL        ; Clock low: change of SDA allowed
    call    ShortDelay
    bsf     I2CPort, SCL        ; Clock high
    call    ShortDelay
    bcf     STATUS, C           ; Clear the carry
    rlf     DataReg,f
    btfsc   I2CPort, SDA        ; Test the bit being received
    bsf     DataReg,0           ; Stock the bit read in DataReg and rotate
    decfsz  Count,f
    goto    i2cReadLoop
    movf    DataReg,w
    bcf     I2CPort, SCL        ; Clock is left low
    call    ShortDelay
    return

;-----------------------------------------------------------------------------------------------;
; i2cSendAck sends an acknowledge bit                                                           ;
;-----------------------------------------------------------------------------------------------;
i2cSendAck:
    banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    banksel I2CPort
    bcf     I2CPort, SCL        ; Clock low: change of SDA allowed
    call    ShortDelay
    bcf     I2CPort, SDA        ; SDA low means ack
    call    ShortDelay
    bsf     I2CPort, SCL        ; Clock high
    call    ShortDelay
    bcf     I2CPort, SCL        ; Clock is left low
    return

;-----------------------------------------------------------------------------------------------;
; i2cNoAck sends a No acknowledge bit                                                           ;
;-----------------------------------------------------------------------------------------------;
i2cNoAck:
    banksel I2CTris
    bcf     I2CTris, SDA        ; SDA as output
    banksel I2CPort
    bcf     I2CPort, SCL        ; Clock low: change of SDA allowed
    call    ShortDelay
    bsf     I2CPort, SDA        ; SDA high means no ack
    call    ShortDelay
    bsf     I2CPort, SCL        ; Clock high
    call    ShortDelay
    bcf     I2CPort, SCL
    return                      ; Clock is left low



;-----------------------------------------------------------------------------------------------;
; Main program starts here                                                                      ;
;-----------------------------------------------------------------------------------------------;
Start:
    banksel ANSEL
    clrf    ANSEL                ; select Digital I/O on port C
    clrf    ANSELH                ; select Digital I/O on port B

    banksel OSCCON
    bsf     OSCCON,6
    bsf     OSCCON,5
    bcf     OSCCON,4            ; 4Mhz clock

    banksel TRISA
    clrf    TRISA
    clrf    TRISB
    clrf    TRISC               ; all ports outputs

    banksel 0


ReadSeconds:
    call    i2cStart
    movlw   DS3231Add           ; put DS3231 address into DataReg
    movwf   DataReg
    rlf     DataReg
    bcf     DataReg,0           ; set bit 0 to a 0 (write)

    call    i2cSend

    ; could do something with w regarding acknowledge

    movlw   .0
    movwf   DataReg             ; put DS3231 Seconds register into DataReg (address is 0)
    call    i2cSend

    ; could do something with w regarding acknowledge

    call    i2cStop

    call    i2cStart

    movlw   DS3231Add           ; put DS3231 address into DataReg
    movwf   DataReg
    rlf     DataReg
    bsf     DataReg,0           ; set bit 0 to a 0 (read)

    call    i2cSend

    ; could do something with w regarding acknowledge

    call    i2cReceive          ; DataReg now contains the value in the Seconds register in the DS3231

    call    i2cNoAck

    call    i2cStop

    ;------------------------------------------------;
    ; do something with the data held in the DataReg ;
    ;------------------------------------------------;




WriteSeconds:
    call    i2cStart
    movlw   DS3231Add           ; put DS3231 address into DataReg
    movwf   DataReg
    rlf     DataReg
    bcf     DataReg,0           ; set bit 0 to a 0 (write)

    call    i2cSend

    ; could do something with w regarding acknowledge

    movlw   .0
    movwf   DataReg             ; put DS3231 Seconds register into DataReg (address is 0)
    call    i2cSend

    ; could do something with w regarding acknowledge

    movlw   b'00010011'         ; value to send to the DS3231 Seconds register
    movwf   DataReg
    call    i2cSend

    ; could do something with w regarding acknowledge

    call    i2cStop

    ;-------------------------------------------------;
    ; DS3231 Seconds register should contain 00010011 ;
    ;-------------------------------------------------;


end
[/CODE]
 

Thread Starter

portreathbeach

Joined Mar 7, 2010
143
OK. No problem. I'll wait till I get the RTC module and experiment.

I have an old scope, so if I increase the delay for the clock pulses I should be able to see what is going on.
 
Top