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_ds1621
 

I2C with DS1621

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 digital temperature sensor 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 DS1621 digital temperature sensor .

Getting Started

You will need the following:

  • a DS1621 digital temperature sensor
  • two 1K to 2.2K resistors

According to the digital temperature sensor datasheet, the sensor contains 8 pins:

  • power (Vdd)
  • ground
  • serial clock
  • serial data
  • 3 address pins
  • thermostat output

Wire the I2C Bus

  • On the PIC, connect the SCK pin (pin 18) to the DS1621 SCL (pin 2).
  • On the PIC, connect the SDA pin (pin 23) to the DS1621 SDA (pin 1).
  • 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. 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. Now, on to the sensor.

DS1621

The DS1621 temperature sensor communicates using I2C as a “slave”. As you can see, there are 2 pins for this, pins 1 and 2. Pin 1 is the DATA line. Pin 2 is the CLOCK line.

ds1621-pinout.jpg

According to the I2C specifications, both lines must be pulled HIGH using a resistor. In this example, a 1K resistor is used for both lines. I’ve used 2.2K and worked fine.

On page 7 of 16 of the DS1621 datasheet, a table shows the timing pattern used in order to communicate with the device. We will use the functions located in the C18 libraries file which is apart of the ‘i2c.h’ library.

On page 10 and 11 of 16 of the DS1621 datasheet, they show all registers inside the sensor IC.

We will set the CONFIGURATION register first.

CONFIGURATION Register

Let’s add the code to set the configuration register. On page 12 of 16, the datasheet explains the order of commands needed to set the configuration register. We will begin an I2C sequence.

 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 communication
12:         IdleI2C();
13:     WriteI2C( 0x90 );  // sends address to the
14:                        // device
15:         IdleI2C();
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 transmitted data sequences will only be ‘looked at’ by this device on the bus. All other devices will ignore the following data.

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. According to page 8 of 16 of the DS1621 datasheet, all the addresses start with 1001. This means the internal address bits for all DS1621 devices are 1001. The rest of the 7-bit address is determined by what address pins are wired to either ground (GND) or power (VCC).

So for our example, we are using 0x90 as the sensor address. This gives us a 7-bit binary number consisting of ‘1001’ internal and A0, A1, A2 pins external all tied to ground.

Another example would be 0x92. In this case, the internal address bits would be ‘1001’, the external A2, A1 pins be connected to ground and A0 pin be connected to power.

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 CONFIGURATION 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 communication
12:       IdleI2C();
13:   WriteI2C( 0x90 ); // sends address to the
14:                     // device
15:       IdleI2C();
16:   WriteI2C( 0xAC ); // sends a control byte to
17:                     // the device to acces
18:                     // the configuration register
19:       IdleI2C();
20: }

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

According to pages 10 and 11 of 16 of the DS1621 datasheet, a table shows which registers are located in the IC. Each register has a hex value associated with it. Notice that the configuration register is ACh (or 0xAC). Therefore, to access the register, the PIC writes 0xAC as shown above.

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

According to pages 5 and 6 of 16 of the DS1621 datasheet, a table shows all the bits of the CONFIGURATION register and explains each one. Every bit of this register will change (configure) the behavior of this device in some way. On the DS1621 configuration register, only the last 2 bits can be changed by the programmer: POL and 1SHOT. For our example, we will use 0x02 as the byte to store in this register. You can set this differently later.

 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 communication
12:       IdleI2C();
13:     WriteI2C( 0x90 ); // sends address to the
14:                       // device
15:       IdleI2C();
16:     WriteI2C( 0xAC ); // sends a control byte to
17:                       // the device to acces
18:                       // the configuration register
19:       IdleI2C();
20:     WriteI2C( 0x02 ); // sends configuration byte –
21:                       // continuous conversion,
22:                       // active high polarity
23:       IdleI2C();
24: }

Now we are done with the configuration. At this point, do NOT add the StopI2C() command. Continue on to temperature conversions.

BTW, it is also possible to read this register at any time. For now, we’ll move on.

Start Temperature Conversion

Next, we need to tell the DS1621 to begin converting analog temperature readings to digital values inside the IC. According to page 12 of 16, we will begin with a Restart command and an address write command.

 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 communication
12:       IdleI2C();
13:     WriteI2C( 0x90 ); // sends address to the
14:                       // device
15:       IdleI2C();
16:     WriteI2C( 0xAC ); // sends a control byte to
17:                       // the device to acces
18:                       // the configuration register
19:       IdleI2C();
20:     WriteI2C( 0x02 ); // sends configuration byte –
21:                       // continuous conversion,
22:                       // active high polarity
23:       IdleI2C();
24:
25:     RestartI2C();       // begin I2C communication
26:       IdleI2C();
27:     WriteI2C( 0x90 );   // sends address to the
28:                         // device
29:       IdleI2C();
30: }

Here, we want to start temperature conversions. According to page 11 of 16, the value EEh (or 0xEE) will activate the temperature convesions. When done, the StopI2C() command will cease all I2C communications.

 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 communication
12:       IdleI2C();
13:   WriteI2C( 0x90 ); // sends address to the
14:                     // device
15:       IdleI2C();
16:   WriteI2C( 0xAC ); // sends a control byte to
17:                     // the device to acces
18:                     // the configuration register
19:       IdleI2C();
20:   WriteI2C( 0x02 ); // sends configuration byte –
21:                     // continuous conversion,
22:                     // active high polarity
23:       IdleI2C();
24:
25:   RestartI2C();       // briefly restart I2C communications
26:       IdleI2C();
27:   WriteI2C( 0x90 );   // sends address to the
28:                       // device
29:       IdleI2C();
30:   WriteI2C(0xEE);     // send command to begin temperature conversions
31:     IdleI2C();
32:   StopI2C();          // stop all I2C communications
33: }

Once the command for temperature conversions begins, it will continue as long as the sensor is powered.

The configuration code and start conversions code only has to be run once per device. Therefore, more than one temp sensor? Then must repeat this for all DS1621 devices.

Let’s get the temperature and place it in the PIC’s memory.

Read Temperature to PIC

First thing we need to do is have a place to put the temperature bytes in our code. I created 2 variables.

4:  char temperatureHI = 0;
5:  char temperatureLO = 0;

These ‘chars’ will contain 8-bit values when together, will represent a 9-bit temperature value. Since a single 8-bit register won’t hold 9-bits, we need 2 of these variable to hold both parts.

If you examine page 4 of 16 of the DS1621 datasheet, a table shows where the temperature value will be stored (D7 through D15). Without getting into a lengthy discussion on temperature accuracy and bit shifting, we will read 2 bytes (all 16bits) into the PIC however only analyze the upper portion (temperatureHI). Why? Well, for this example, it’s all the accuracy you will need without building a lot of complex bit shifting code. Notice D15 is the sign bit allowing for signed values.

Let’s begin with StartI2C().

I’ve removed our previous code from the following examples below. Be sure to continue this following code after the configuration code and temperature conversion code.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();
10:         IdleI2C();
11: }

Next, we want to address the device again. Notice the 8th bit of 0x90 is LOW.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();
10:         IdleI2C();
11:     WriteI2C( 0x90 );
12:         IdleI2C();
13: }

According to page 10 of 16 of the DS1621 datasheet, the TEMPERATURE register is located at address AAh (0xAA). So, let’s access it.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();           // start I2C communication
10:         IdleI2C();
11:     WriteI2C( 0x90 );     // address the chip
12:         IdleI2C();
13:     WriteI2C( 0xAA );     // access the temperature register
14:         IdleI2C();
15: }

Next, initiate a restart.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();           // start I2C communication
10:         IdleI2C();
11:     WriteI2C( 0x90 );     // address the chip
12:         IdleI2C();
13:     WriteI2C( 0xAA );     // access the temperature register
14:         IdleI2C();
15:     RestartI2C();         // Initiate a RESTART command
16:         IdleI2C();
17: }

Now, we’re going to read the temperature of the sensor. Notice the 8th bit of the address is now HIGH.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();           // start I2C communication
10:         IdleI2C();
11:     WriteI2C( 0x90 );     // address the chip
12:         IdleI2C();
13:     WriteI2C( 0xAA );     // access the temperature register
14:         IdleI2C();
15:     RestartI2C();         // Initiate a RESTART command
16:         IdleI2C();
17:     WriteI2C( 0x91 );     // address device w/read
18:         IdleI2C();
19: }

At this stage, the PIC will begin to pulse the clock line as usual, however, the DS1621 will take over the data line and begin sending data to the PIC’s I2C receive register. When done, the value will be stored in temperatureHI. When done, it’s the PIC’s turn to acknowledge (ACK) the device, letting the device know it has received the data. This ACK tells the device the PIC is prepared to receive a second byte.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();           // start I2C communication
10:         IdleI2C();
11:     WriteI2C( 0x90 );     // address the chip
12:         IdleI2C();
13:     WriteI2C( 0xAA );     // access the temperature register
14:         IdleI2C();
15:     RestartI2C();         // Initiate a RESTART command
16:         IdleI2C();
17:     WriteI2C( 0x91 );     // address device w/read
18:         IdleI2C();
19:     temperatureHI = ReadI2C(); // Returns the MSB byte
20:                               // and stores it in
21:                               // 'temperatureHI'
22:         IdleI2C();
23:     AckI2C();             // Send back Acknowledge
24:         IdleI2C();
25: }

Since the DS1621 device is made to send ALL 16 bits, we must run another ReadI2C command again. What I do is store the second set of data into another variable called temperatureLO although I’m not going to use it. I only care about the value in temperatureHI.

When done, the PIC sends a Not-Acknowledge (NotAck) to tell the device to stop sending data. Finish with a Stop command.

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     ... // (previous configuration code here)
 8:
 9:     StartI2C();           // start I2C communication
10:         IdleI2C();
11:     WriteI2C( 0x90 );     // address the chip
12:         IdleI2C();
13:     WriteI2C( 0xAA );     // access the temperature register
14:         IdleI2C();
15:     RestartI2C();         // Initiate a RESTART command
16:         IdleI2C();
17:     WriteI2C( 0x91 );     // address device w/read
18:         IdleI2C();
19:     temperatureHI = ReadI2C(); // Returns the MSB byte
20:                               // and stores it in
21:                               // 'temperatureHI'
22:         IdleI2C();
23:     AckI2C();             // Send back Acknowledge
24:         IdleI2C();
25:     temperatureLO = ReadI2C(); // returns the LSB of
26:                               // the temperature
27:         IdleI2C();
28:     NotAckI2C();          // send a not-acknowledge
29:         IdleI2C();
30:     StopI2C();            // stop all I2C communication
31: }

Finally, you have configured the device and have received a temperature (with +/- sign) into a variable called temperatureHI. The temperature value will be measured in Celcius.

Entire Code

 1: #include <i2c.h>
 2: int main()
 3: {
 4:     char temperatureHI = 0;
 5:     char temperatureLO = 0;
 6:
 7:     TRISC = 0x00; // turn on tri-state register and
 8:                   // make all output pins
 9:     PORTC = 0x00; // make all output pins LOW
10:
11:     OpenI2C( MASTER, SLEW_OFF);
12:     SSPADD = 0x3F;
13:
14:     StartI2C();       // begin I2C communication
15:       IdleI2C();
16:     WriteI2C( 0x90 ); // sends address to the
17:                       // device
18:       IdleI2C();
19:     WriteI2C( 0xAC ); // sends a control byte to
20:                       // the device to acces
21:                       // the configuration register
22:       IdleI2C();
23:     WriteI2C( 0x02 ); // sends configuration byte –
24:                       // continuous conversion,
25:                       // active high polarity
26:       IdleI2C();
27:
28:     RestartI2C();     // briefly restart I2C communications
29:       IdleI2C();
30:     WriteI2C( 0x90 ); // sends address to the
31:                       // device
32:       IdleI2C();
33:     WriteI2C(0xEE);   // send command to begin temperature conversions
34:       IdleI2C();
35:     StopI2C();        // stop all I2C communications
36:
37:     StartI2C();       // start I2C communication
38:       IdleI2C();
39:     WriteI2C( 0x90 ); // address the chip
40:       IdleI2C();
41:     WriteI2C( 0xAA ); // access the temperature register
42:       IdleI2C();
43:     RestartI2C();     // Initiate a RESTART command
44:       IdleI2C();
45:     WriteI2C( 0x91 ); // address device w/read
46:       IdleI2C();
47:     temperatureHI = ReadI2C(); // Returns the MSB byte
48:                                // and stores it in
49:                                // 'temperatureHI'
50:       IdleI2C();
51:     AckI2C();         // Send back Acknowledge
52:       IdleI2C();
53:     temperatureLO = ReadI2C(); // returns the LSB of
54:                                // the temperature
55:         IdleI2C();
56:     NotAckI2C();      // send a not-acknowledge
57:       IdleI2C();
58:     StopI2C();        // stop all I2C communication
59: }
duty free alcohol airport duty free cigs uk buy duty free cuban cigars where to buy cosmetics duty free fragrances buy tobacco duty free