I2C Sensors using Kinetis K40

Document created by GUO XIAOLI Employee on Jul 12, 2012Last modified by johnmc on Feb 5, 2013
Version 7Show Document
  • View in full screen mode

This tutorial will introduce you to I2C and provide a framework that you can use to start communicating with various devices that use I2C. This tutorial is meant for the Kinetis K40 and will probably not work on any other Kinetis chip. DISCLAIMER: This has not been fully tested and may not work for you and for all devices. The header file provided does not handle errors and should not be used for critical projects.

 

Introduction to I2C signaling

 

I2C is a simple two wire communication system used to connect various devices together, such as Sensory devices and microprocessors using 8 bit packets. I2C requires two wires: the first is called SDA and is used for transferring data, the second is called SCL and it is the clock used to drive the data to and from devices. I2C uses an open drain design which requires a pull up resistor to logic voltage (1.8,3.3,5) on both SDA and SCL for proper operation. I2C is a master-slave system where the master drives the clock and initiates communication.

 

The I2C protocol has 5 parts.

 

  1. The Start signal which is defined as pulling the SDA line low followed by pulling SCL low.
  2. The slave device address including the Read/Write bit
  3. The register address that you will be writing to/ reading from
  4. the data
  5. The acknowledgement signal which is sent from the receiving device after 8 bits of data has been transferred successfully.
  6. the Stop signal, which is defined by SDA going high before SCL goes high.

 

1. The start signal is sent from the Master to intiate communication on the bus. The start and stop signals are the only time that SDA can change out of sync with SCL. Once the start signal is sent no other device can talk on the bus until the stop signal is sent. If for whatever reason another device tries to talk on the bus then there will be an error and the K40 can detect this.

 

2. The slave device is a 7 bit (sometimes 10bit but this will not be covered in this tutorial) address provided by the device and is specific to the device. The type of data operation (read/write) is determined by the 8th bit. A 1 will represent a write and a 0 a read operation.

 

3. The register addresses are provided by the device's specifications.

 

4. The data you will send to a device if you are writing or the data that you receive from the device when reading. This will always be 8 bits.

 

5. After 8 bits of data has been transferred successfully the receiving device will pull the SDA line low to signify that it received the data. If the transmitting device does not detect an acknowledgement then there will be an error. The K40 will be able to detect this.

 

6. The stop signal is sent from the Master to terminate communication on the bus. Some devices require this signal to operate properly but it is required if there will be more than one master on the bus (which will not be covered in this tutorial)

 


I2C header file

 

This header file only has functions for reading and writing one byte at a time. Most devices support reading and writing more than one byte at a time without sending the Stop signal. Typically the device will keep incrementing the register's address to the next one during read/writes when there is no stop signal present. See your device's user manual for more information.


/* 
 * i2c.h 
 * 
 * Created on: Apr 5, 2012 
 * Author: Ian Kellogg  
 * Credits: Freescale for K40-I2C example 
 */  
#ifndef I2C_H_  
#define I2C_H_  
#include "derivative.h"  
#define i2c_EnableAck() I2C1_C1 &= ~I2C_C1_TXAK_MASK 
#define i2c_DisableAck() I2C1_C1 |= I2C_C1_TXAK_MASK  
#define i2c_RepeatedStart() I2C1_C1 |= I2C_C1_RSTA_MASK  
#define i2c_Start() I2C1_C1 |= I2C_C1_TX_MASK;\  
  I2C1_C1 |= I2C_C1_MST_MASK  
#define i2c_Stop() I2C1_C1 &= ~I2C_C1_MST_MASK;\  
  I2C1_C1 &= ~I2C_C1_TX_MASK  
#define i2c_EnterRxMode() I2C1_C1 &= ~I2C_C1_TX_MASK;\  
  I2C1_C1 |= I2C_C1_TXAK_MASK  
#define i2c_write_byte(data) I2C1_D = data  
#define i2c_read_byte() I2C1_D  
#define MWSR 0x00 /* Master write */  
#define MRSW 0x01 /* Master read */  
/* 
* Name: init_I2C 
* Requires: nothing 
* Returns: nothing 
* Description: Initalizes I2C and Port E for I2C1 as well as sets the I2C bus clock 
*/  
void init_I2C()  
{  
  SIM_SCGC4 |= SIM_SCGC4_I2C1_MASK; //Turn on clock to I2C1 module  
  SIM_SCGC5 |= SIM_SCGC5_PORTE_MASK; // turn on Port E which is used for I2C1  
  /* Configure GPIO for I2C1 function */  
  PORTE_PCR1 = PORT_PCR_MUX(6) | PORT_PCR_DSE_MASK;  
  PORTE_PCR0 = PORT_PCR_MUX(6) | PORT_PCR_DSE_MASK;  
  I2C1_F = 0xEF; /* set MULT and ICR This is roughly 10khz See manual for different settings*/  
  I2C1_C1 |= I2C_C1_IICEN_MASK; /* enable interrupt for timing signals*/  
}  
/* 
* Name: i2c_Wait 
* Requires: nothing 
* Returns: boolean, 1 if acknowledgement was received and 0 elsewise  
* Description: waits until 8 bits of data has been transmitted or recieved 
*/  
short i2c_Wait() {  
  while((I2C1_S & I2C_S_IICIF_MASK)==0) {  
  }   
  // Clear the interrupt flag  
  I2C1_S |= I2C_S_IICIF_MASK;  
}  
/* 
* Name: I2C_WriteRegister 
* Requires: Device Address, Device Register address, Data for register 
* Returns: nothing 
* Description: Writes the data to the device's register 
*/  
void I2C_WriteRegister (unsigned char u8Address, unsigned char u8Register, unsigned char u8Data) {  
  /* shift ID in right position */  
  u8Address = (u8Address << 1)| MWSR;  
  /* send start signal */  
  i2c_Start();  
  /* send ID with W/R bit */  
  i2c_write_byte(u8Address);  
  i2c_Wait();  
  // write the register address  
  i2c_write_byte(u8Register);  
  i2c_Wait();  
  // write the data to the register  
  i2c_write_byte(u8Data);  
  i2c_Wait();  
  i2c_Stop();  
}  
/* 
* Name: I2C_ReadRegister_uc 
* Requires: Device Address, Device Register address 
* Returns: unsigned char 8 bit data received from device 
* Description: Reads 8 bits of data from device register and returns it 
*/  
unsigned char I2C_ReadRegister_uc (unsigned char u8Address, unsigned char u8Register ){  
  unsigned char u8Data;  
  unsigned char u8AddressW, u8AddressR;  
  /* shift ID in right possition */  
  u8AddressW = (u8Address << 1) | MWSR; // Write Address  
  u8AddressR = (u8Address << 1) | MRSW; // Read Address  
  /* send start signal */  
  i2c_Start();  
  /* send ID with Write bit */  
  i2c_write_byte(u8AddressW);  
  i2c_Wait();  
  // send Register address  
  i2c_write_byte(u8Register);  
  i2c_Wait();  
  // send repeated start to switch to read mode  
  i2c_RepeatedStart();  
  // re send device address with read bit  
  i2c_write_byte(u8AddressR);  
  i2c_Wait();  
  // set K40 in read mode  
  i2c_EnterRxMode();  
  u8Data = i2c_read_byte();  
  // send stop signal so we only read 8 bits  
  i2c_Stop();  
  return u8Data;  
}  
/* 
* Name: I2C_ReadRegister 
* Requires: Device Address, Device Register address, Pointer for returned data 
* Returns: nothing 
* Description: Reads device register and puts it in pointer's variable 
*/  
void I2C_ReadRegister (unsigned char u8Address, unsigned char u8Register, unsigned char *u8Data ){  
  /* shift ID in right possition */  
  u8Address = (u8Address << 1) | MWSR; // write address  
  u8Address = (u8Address << 1) | MRSW; // read address  
  /* send start signal */  
  i2c_Start();  
  /* send ID with W bit */  
  i2c_write_byte(u8Address);  
  i2c_Wait();  
  // send device register  
  i2c_write_byte(u8Register);  
  i2c_Wait();  
  // repeated start for read mode  
  i2c_RepeatedStart();  
  // resend device address for reading  
  i2c_write_byte(u8Address);  
  i2c_Wait();  
  // put K40 in read mode  
  i2c_EnterRxMode();  
  // clear data register for reading  
  *u8Data = i2c_read_byte();  
  i2c_Wait();  
  // send stop signal so we only read 8 bits  
  i2c_Stop();    
}  
#endif  

 



Example of I2C communication using Freescale MMA8452Q 3-axis accelerometer

 

This example is very simplistic and will do nothing more than read the WHO AM I register to make sure that I2C is working correctly. To see more check out the Freescale MMA8452Q Example

 

 

/* Main. C */

#include <stdio.h>

#include "derivative.h" /* include peripheral declarations */

#include "i2c.h"

 

int main(void)

{

     // checking the WHO AM I register is a great way to test if communication is working

     // The MMA8452Q has a selectable address which is either 0X1D or 0X1C depending on the SA0 pin

     // For the MMA8452Q The WHO AM I register should always return 0X2A
     if (I2C_ReadRegister_uc (0x1D,0x0D) != 0x2A {

          printf ("Device was not found\n");

     } else {

          printf ("Device was found! \n");

     }

}

Attachments

    Outcomes