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.
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 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)
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
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");
}
}
This code contains too many mistakes.
DELETE lines: 49, 50, 51, 52, 55, 106, 108, 111, 112.
SWAP lines 11 & 12, DisableAck and EnableAck.
Replace line 112 with this code: i2c_DisableAck();
And modify function I2C_ReadRegister likewise than I2C_ReadRegister_uc :smileywink:
How I can transmit number "0" to slave device??? It is not working!
Transfer does not start! :smileysad: Slave device is MMA7660 and I want to read X axis.
All numbers except 0 start transmission, I dont know why??
It is working without ACK/NACK, but not correctly with ACK/NACK :smileyconfused: