Strict Standards: Declaration of action_plugin_googleanalytics::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /home/ulcape/www/wiki.ulcape.org/lib/plugins/googleanalytics/action.php on line 6

Strict Standards: Declaration of action_plugin_stripslashes::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /home/ulcape/www/wiki.ulcape.org/lib/plugins/stripslashes/action.php on line 0
CAPE Wiki » tutorials:pic:pic18_i2c_ds3231
 
Strict Standards: Declaration of syntax_plugin_code::render() should be compatible with DokuWiki_Syntax_Plugin::render($format, &$renderer, $data) in /home/ulcape/www/wiki.ulcape.org/lib/plugins/code/syntax.php on line 41

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

Strict Standards: Only variables should be assigned by reference in /home/ulcape/www/wiki.ulcape.org/inc/JpegMeta.php on line 999

I2C with DS3231

The 18F452 and the 18F4520 have a built-in I2C (Inter-Integrated Circuit) bus. I2C is a 2-wire synchronous serial bus. Some devices, such as sensors, communicate with microcontrollers on this 2-wire serial bus. Multiple devices, even different types of devices, can be attached to the same 2-wire I2C bus without causing collisions or errors. Each device contains a unique address allowing multiple devices attached to the same I2C bus. In our example, we will use a real-time clock (RTC) that communicates over I2C.

Before continuing, be sure to read through the I2C tutorial.

NOTE: This tutorial continues with the I2C series as a supplemental only. The IC used in this tutorial is the Dallas Semiconductor DS3231 real-time clock with integrated crystal.

Getting Started

You will need the following:

  • a DS3231 real-time clock (mounted on a test board with wire leads)
  • two 1K to 2.2K resistors

The DS3231 real-time clock is a surface mount device. Unlike the other ICs, this one cannot be forced on a breadboard. It requires having a PC board made in which the sensor is mounted (soldered) and connected to leads or wires in order to test.

According to the real time clock datasheet, the sensor contains 16 pins:

  • power
  • ground
  • serial clock
  • serial data
  • reset pin
  • interrupt/square wave pin
  • 32KHz output pin
  • battery power pin
  • 8 no-connection pins

Wire the I2C Bus

  • On the PIC, connect the SCK pin (pin 18) to the DS3231 SCL (pin 16).
  • On the PIC, connect the SDA pin (pin 23) to the DS3231 SDA (pin 15).
  • Connect a resistor from the clock line to the digital power.
  • Connect a resistor from the data line to the digital power.

In order to get this to work, I assume you are using an external crystal of 10MHz to run the PIC. If not, some adjustments will need to be made which I will address in the document. Also, you must have the latest version of MPLAB installed and the C18 compiler installed.

Open PORTC

According to the PIC18F4520 datasheet, you will notice that on the pinout, the I2C lines are located on pins 18 and 23 (CLK and DATA).

These pins happen to be located on PORT C. PORT C pins can be used for general input/output or can be configured for special uses such as I2C communication. Since I2C communication involves only 2 wires, these pins will serve as the I2C communication between the PIC and other I2C devices. The PIC will act as the “master” and all the other devices will act as the “slaves”.

First step is to “turn on” PORT C.

1: int main()
2: {
3:     TRISC = 0x00; //turn on tri-state register and
4:     //make all output pins
5:     PORTC = 0x00; //make all output pins LOW
6: }

Now you are ready to configure the I2C bus on the PIC.

I2C.H

In MPLAB, it is critical that you include the “i2c.h” file in your code. Therefore, be sure to add it at the top of the main file.

1: #include <i2c.h>
2: int main()
3: {
4:     TRISC = 0x00; //turn on tri-state register and
5:     //make all output pins
6:     PORTC = 0x00; //make all output pins LOW
7: }

You have now opened port C for use. Now, on to opening the I2C bus.

Configure OpenI2C()

Now it’s time to open and configure the I2C port on the PIC. According to the C18 Libraries file, OpenI2C is the function to call. A few things are needed to be understood. The first thing is the difference between MASTER and SLAVE. Since the PIC will control all the devices, the PIC should be set as MASTER. Since bus speed isn’t a concern, slew control can be turned off using SLEW_OFF parameter.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9: }

The second thing is to understand what the SSPADD register does inside the PIC. It contains the value which represents what baud rate (speed) communications will occur based on A) what crystal you are using and B) what speed you would like. For our example, we will assume a crystal speed of 10MHz and baud rate of 100KHz. See page 153 of 332 of the PIC datasheet for all values listed.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10: }

At this stage you have successfully initialized the I2C.

DS3231

The DS3231 RTC is an IC that keeps the current time (hours, mins, secs), date, year, month, day, and week on a single chip. These are commonly used in watches, alarm clocks, cell phones, PDAs, etc. where a calendar and/or clock is needed. There are only 2 functions commonly used with this device: set values in memory and get values from memory. We will go through both functions using examples.

The digital values in this device are stored using 4-bit binary-coded decimal (BCD). Usually programmers integrate some sort of binary manipulation to properly display and save the values using a PIC.

The datasheet lists other features such as an embedded temperature sensor and alarm features inside the DS3231. These functions are available to developers however they are outside the scope of this tutorial and therfore will not be discussed.

According to page 11 of 19 of the DS3231 datasheet, each memory register is listed. We will use the register addresses in the examples below.

Set RTC Value

First thing is to program the IC with the correct values for the calendar and the clocks. In our example, we will set the minutes. Later, we will set the day. Once these are set, all other registers can be set using the same procedures.

Minutes

Page 16 of 19 of the DS3231 datasheet illustrates the timing and sequence of the I2C communication. We begin with the StartI2C() command.

 1: #include <i2c.h>
 2: void main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10:
11:     StartI2C();         // begin I2C communications
12:         IdleI2C();
13:     WriteI2C(0xD0);   // addresses the chip
14:         IdleI2C();
15:
16: }

Let’s stop here and understand what’s going on. First thing we do is begin the communication by using StartI2C().

IdleI2C() is placed in between as protection to make sure that the PIC doesn’t get ahead of itself in code before the devices are ready. Either way, it’s a safe guard and it doesn’t hurt.

WriteI2C() is our first attempt to address an IC located on the I2C bus. Since many devices can be attached to this bus, this address ‘awakens’ the correct device. The next data sequences will only be ‘looked at’ by this device on the bus.

What is the address of an I2C device? The address of any device is usually (but not always) made up of 2 parts. First part is a set of internal address bits. The second part is made up of hardwired address bits using the pins of the IC. However, the DS3231 is unique in that the entire address in internal to the chip. There are no external address pins. According to page 16 of 19 of the DS3231 datasheet, the 7-bit address is ‘1101000’.

So where’s the 8th bit? What is the 8th bit? Good question. After every address byte sent by the PIC using I2C, the 8th bit determines if the next byte is written or read by the PIC.

Therefore, in our example we are interested in accessing the MINUTES register.

 1: #include <i2c.h>
 2: void main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10:
11:     StartI2C();         // begin I2C communications
12:         IdleI2C();
13:     WriteI2C( 0xD0 );   // addresses the chip
14:         IdleI2C();
15:     WriteI2C( 0x01 );   // write register address for minutes
16:         IdleI2C();
17:
18: }

The least significant bit (8th bit) of 0xD0 is LOW. Therefore, the PIC is writing the next byte. What is the next byte? It is 0x01.

According to page 11 of 19 of the DS3231 datasheet, a table shows which registers are located in the IC. Each register has a hex value address associated with it. Notice that the MINUTES register is 01h (or 0x01). Therefore, to access the register, the PIC writes 0x01 as shown above.

ds3231minutesregister.jpg

Now that we have addressed the correct device and addressed the correct register, it’s time to write something into this MINUTES register.

According to page 11 of 19 of the DS3231 datasheet, a table shows all the bits of the MINUTES register. The MINUTES register is composed of 2 parts: The lower nibble (bit 0 through bit 3) and the upper nibble (bit 4 through bit 6). Both nibbles are used together to represent double-digit numbers. For example, the number “12” is composed of a ‘1’ and a ‘2’. Each digit is stored in a separate nibble. The ‘1’ is stored as a BCD number in the upper nibble while the ‘2’ is stored as a BCD number in the lower nibble.

For example, to store the number “12”, the MINUTES register should be set to ‘001 0010’ in binary. The ‘001’ is BCD for ‘1’ and 0010 is BCD for ‘2’. Since there are no double-digit minutes higher than 59, only 7 bits are needed to represent any double-digit from 00 to 59.

 1: #include <i2c.h>
 2: void main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10:
11:     StartI2C();                // begin I2C communications
12:         IdleI2C();
13:     WriteI2C( 0xD0 );          // addresses the chip
14:         IdleI2C();
15:     WriteI2C( 0x01 );          // access register address for minutes
16:         IdleI2C();
17:     WriteI2C( 0b00010010 );    // write value into minutes register
18:         IdleI2C();
19:     StopI2C();                 // stop condition I2C on bus
20:
21: }

We have written the binary number 0010010 into the MINUTES register. Now, the RTC will keep track of the minutes and after 59min:59sec, the RTC will increment the HOURS register and roll-over to 00min:00secs.

Let’s continue on with the day of the week.

Day

Since nothing changes with the I2C procedure and the IC address is the same, we can use the previous code as a guide in order to set the day. Below we use the StartI2C() command and address the chip using WriteI2C().

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10:
11:     StartI2C();                // begin I2C communications
12:         IdleI2C();
13:     WriteI2C( 0xD0 );          // addresses the chip
14:         IdleI2C();
15:     WriteI2C( 0x01 );          // access register address for minutes
16:         IdleI2C();
17:     WriteI2C( 0b00010010 );    // write value into minutes register
18:         IdleI2C();
19:     StopI2C();                 // stop condition I2C on bus
20:
21:     StartI2C();                // begin I2C communications
22:         IdleI2C();
23:     WriteI2C( 0xD0 );          // addresses the chip
24:         IdleI2C();
25:
26: }

By looking at the table of registers in the datasheet, we can see the register for day of the week is 03h (0x03). The value of each day is represented with a binary number between 1 and 7. Each number represents a different day of the week. According to page 11 of 19, ‘1’ equals “Sunday”, ‘2’ equals “Monday”, etc. In our example we will set the day of the week to “Wednesday”. Let’s access the register for days of the week and store a ‘4’ in the register.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     TRISC = 0x00; //turn on tri-state register and
 5:                   //make all output pins
 6:     PORTC = 0x00; //make all output pins LOW
 7:
 8:     OpenI2C( MASTER, SLEW_OFF);
 9:     SSPADD = 0x3F;
10:
11:     StartI2C();                // begin I2C communications
12:         IdleI2C();
13:     WriteI2C( 0xD0 );          // addresses the chip
14:         IdleI2C();
15:     WriteI2C( 0x01 );          // access register address for minutes
16:         IdleI2C();
17:     WriteI2C( 0b00010010 );    // write value into minutes register
18:         IdleI2C();
19:     StopI2C();                 // stop condition I2C on bus
20:
21:     StartI2C();                // begin I2C communications
22:         IdleI2C();
23:     WriteI2C( 0xD0 );          // addresses the chip
24:         IdleI2C();
25:     WriteI2C( 0x03 );          // access register address for day of the week
26:         IdleI2C();
27:     WriteI2C( 0x04 );          // write value into day register
28:         IdleI2C();
29:     StopI2C();                 // stop condition I2C on bus
30: }

Finally, we have the minutes set and the day of the week set. Repeat this procedure for any other register settings.

Get RTC Value

The second feature of the DS3231 is to get data from the RTC into PIC memory. Assuming I2C has been previously configured, all the correct files have been included, and the RTC has values stored, we can begin. In the following example, we are going to get the value for day of the week. At the top of the main function, add an object of type char to store the 8-bit value (which holds the BCD number).

3: char result;

The I2C read routine needs to be added. Therefore, we begin as usual using the StartI2C() command and address the chip using WriteI2C().

 1: void main()
 2: {
 3:     char result;
 4:
 5:       // ... previous code here
 6:
 7:     StartI2C();                // Start condition I2C on bus
 8:         IdleI2C();
 9:     WriteI2C(0xD0);            // addresses the chip
10:         IdleI2C();
11: }

At this point, we are interested in the day of the week. By looking at the table of registers in the datasheet, we can see the register for day of the week is 03h (0x03). Therefore we will write to this address next.

 1: void main()
 2: {
 3:     char result;
 4:
 5:       // ... previous code here
 6:
 7:     StartI2C();                  // Start condition I2C on bus
 8:         IdleI2C();
 9:     WriteI2C( 0xD0 );            // addresses the chip
10:         IdleI2C();
11:     WriteI2C( 0x03 );            // write register address
12:         IdleI2C();
13:     StopI2C();                   // Stop condition I2C on bus
14: }

We have addressed the chip and the register. Now, we want to read data from this register. Begin with a StartI2C() and this time, change the LSB to a 1 in the chip address to initiate a read function.

 1: void main()
 2: {
 3:     char result;
 4:
 5:       // ... previous code here
 6:
 7:     StartI2C();                  // Start condition I2C on bus
 8:         IdleI2C();
 9:     WriteI2C( 0xD0 );            // addresses the chip
10:         IdleI2C();
11:     WriteI2C( 0x03 );            // write register address
12:         IdleI2C();
13:     StopI2C();                   // Stop condition I2C on bus
14:
15:     StartI2C();                  // Start condition I2C on bus
16:         IdleI2C();
17:     WriteI2C( 0xD1 );            // addresses the chip with a read bit
18:         IdleI2C();
19: }

The object ‘result’ will hold the 4-bit BCD number in it’s 8-bit memory. Use a ReadI2C() command followed by a NotAckI2C() and a StopI2C() commands. The read will move the contents of the RTC register 03h into the memory object ‘result’. Since the value from the RTC is a 4-bit BCD number, ‘result’ will contain 4 leading zeros. The not-acknowledge will signal the RTC that reading data has completed.

 1: void main()
 2: {
 3:     char result;
 4:
 5:       // ... previous code here
 6:
 7:     StartI2C();                  // Start condition I2C on bus
 8:         IdleI2C();
 9:     WriteI2C( 0xD0 );            // addresses the chip
10:         IdleI2C();
11:     WriteI2C( 0x03 );            // write register address
12:         IdleI2C();
13:     StopI2C();                   // Stop condition I2C on bus
14:
15:     StartI2C();                  // Start condition I2C on bus
16:         IdleI2C();
17:     WriteI2C( 0xD1 );            // addresses the chip with a read bit
18:         IdleI2C();
19:     result = ReadI2C();          // read the value from the RTC and store in result
20:         IdleI2C();
21:     NotAckI2C();                 // Not Acknowledge condition.
22:         IdleI2C();
23:     StopI2C();                   // Stop condition I2C on bus
24: }

The RTC code using the I2C bus is complete.

Entire Code

 1: #include <i2c.h>
 2: void main()
 3: {
 4:     char result;
 5:
 6:     TRISC = 0x00; //turn on tri-state register and
 7:                   //make all output pins
 8:     PORTC = 0x00; //make all output pins LOW
 9:
10:     OpenI2C( MASTER, SLEW_OFF);
11:     SSPADD = 0x3F;
12:
13:     StartI2C();                // begin I2C communications
14:         IdleI2C();
15:     WriteI2C( 0xD0 );          // addresses the chip
16:         IdleI2C();
17:     WriteI2C( 0x01 );          // access register address for minutes
18:         IdleI2C();
19:     WriteI2C( 0b00010010 );    // write value into minutes register
20:         IdleI2C();
21:     StopI2C();                 // stop condition I2C on bus
22:
23:     StartI2C();                // begin I2C communications
24:         IdleI2C();
25:     WriteI2C( 0xD0 );          // addresses the chip
26:         IdleI2C();
27:     WriteI2C( 0x03 );          // access register address for day of the week
28:         IdleI2C();
29:     WriteI2C( 0x04 );          // write value into day register
30:         IdleI2C();
31:     StopI2C();                 // stop condition I2C on bus
32:
33:     StartI2C();                // Start condition I2C on bus
34:         IdleI2C();
35:     WriteI2C( 0xD0 );          // addresses the chip
36:         IdleI2C();
37:     WriteI2C( 0x03 );          // write register address
38:         IdleI2C();
39:     StopI2C();                 // Stop condition I2C on bus
40:
41:     StartI2C();                // Start condition I2C on bus
42:         IdleI2C();
43:     WriteI2C( 0xD1 );          // addresses the chip with a read bit
44:         IdleI2C();
45:     result = ReadI2C();        // read the value from the RTC and store in result
46:         IdleI2C();
47:     NotAckI2C();               // Not Acknowledge condition.
48:         IdleI2C();
49:     StopI2C();                 // Stop condition I2C on bus
50: }
duty free alcohol airport duty free cigs uk buy duty free cuban cigars where to buy cosmetics duty free fragrances buy tobacco duty free