WebbotLib AVR library
WebbotLib It just does it
  C++ documentation  C documentation

i2cBus.h

Provides support for communicating with other I2C slave devices/sensors.
NB if you are using a sensor, or other device, that is supported directly by this library then you don't need to understand any of this. This library 'just does it' for you. So this module is only really of interest to people who are trying to use low level I2C functions. If your device is not listed then you can create a generic slave device using MAKE_GENERIC_I2C_DEVICE which just requires the unique number of the device.
Here is a somewhat simplistic view of I2C to save you reading the data sheet.
The I2C allows you to connect one-or-many I2C devices onto the bus. Each I2C device has its own unique address (like a telephone number). The processor can only talk to one device at a time - in the same way as you have a list of phone numbers but you can only talk to one person at a time (before anyone asks - there is an I2C version of a 'conference call' but let's keep it simple!).
An I2C conversation, just like the telephone, requires you to call a 'number', say something, listen to the answer, and then hang up. A more complex conversation means doing this several times. Obviously each 'number' is unique. How do I know the 'number/address'? Well: check the data sheet for the device. Some devices only have one fixed address (meaning you can only connect one of them at most) whereas others allow you to change the address either via software or via switches.
The person who is making the call is called the 'master' and the person receiving the call is a 'slave'. The current implementation only supports one master on the same bus and you can only use a given bus either as a master or as a slave but not both.
I2C Master
To indicate that you are the one and only master on the bus. You will need to do the following:
1. Create a list of the slave devices that are connected to the bus
2. Define the bus using MAKE_I2C_HARDWARE_BUS or MAKE_I2C_SOFTWARE_BUS providing the list (ie the 'telephone directory' of all the slave devices that are connected to it.
3. Call i2cBusInit to initialise the bus ready for use
At this point you may get an error if the devices don't have unique addresses.
Note that you can either use the built-in hardware I2C bus or you can defined as many software I2C busses as you like. You might want to do this, say, because not all of the slave devices have a unique number. So you can split the devices across several busses to make sure that each bus has a unique set of numbers.
The I2C sub-system is set up to use 100kbps as the default communication speed. If you wish to change this then you can call i2cBusSetBitRate in appInitSoftware - ie after you have initialised the bus.
The I2C interface requires pull-up resistors on the SCL and SDA lines. For a hardware bus the library achieves this by using the internal pull-up resistors on the processor pins however for a software bus you must add the extra resistors (4.7k should work).
Warning: if your code talks to an I2C device but you have not connected the device then there is a possibility that the library can hang! So if you don't have the device connected then remove any communications with the device.
Once the bus has been initialised you can start talking to the devices. Most slave devices have a list of 'registers' which can be read and/or written to (check the datasheet of the device). In this case you can just use the library functions to read/write the registers.
If your device needs more complex communications then the receive, send and transfer calls may do what you need but, as a last resort, you can use the low-level start, get, put, stop functions to code the communications yourself.
I2C_SLAVE
If your processor is an I2C slave (ie it only accepts in-coming calls) then you can only use the I2C hardware bus - there is no software option.
Implementing a slave is quite easy. You just have to call i2CSlaveInit to initialise the bus and declare your own address (telephone number). You then register a callback handler which is called when an incoming message is received, and another which is called to send back a response. Quite what these handlers do, and the format of the messages, is up to you !

 

Function

 


MAKE_GENERIC_I2C_DEVICE(uint8_t i2cAddr)

Create a generic slave device with the given address.
This should be used when the device is not directly supported by this library. The only parameter is the I2C address of the device. For example: if the device has an address of 0xA0 then:
GENERIC_I2C_DEVICE myDevice = MAKE_GENERIC_I2C_DEVICE(0xA0);

MAKE_I2C_HARDWARE_BUS(I2C_DEVICE_LIST* devices)

Create a hardware I2C bus and specify the list of devices that are attached.
Assuming that you have defined a CMPS03 and one of your own devices at 0xA0 by using:-
CMPS03_I2C compass = MAKE_CMPS03_I2C_At(0xC0);
GENERIC_I2C_DEVICE myDevice = MAKE_GENERIC_I2C_DEVICE(0xA0);
then the first step is to build a list of the devices that will be on the bus:-
static I2C_DEVICE_LIST PROGMEM i2c_list[] = {
    &compass.i2cInfo,
    &myDevice.i2cInfo
};
This is just a comma separated list of device entries where each entry has an '&' followed by the name of the device and ending with '.i2cInfo'. Note the use of PROGMEM.
Now that we have a list of devices we can create the hardware bus:
I2C_HARDWARE_BUS i2c = MAKE_I2C_HARDWARE_BUS(i2c_list);
The only remaining thing to do is to initialise the bus in appInitHardware passing the address of the device list:
i2cBusInit(&i2c);
All done. You can now start talking to the devices.

MAKE_I2C_SOFTWARE_BUS(I2C_DEVICE_LIST* devices, const IOPin* scl, const IOPin* sda)

Create a software I2C bus and specify the list of devices that are attached as well as the IO pins to use for the SCL and SDA wires. NB You MUST add a 4k7 pull up resistor from each pin to the regulated Vcc supply to the processor.
This function is almost identical to the hardware version but just with the addition of specifying the extra IO pins. But, for completeness, here is an example:
Assuming that you have defined a CMPS03 and one of your own devices at 0xA0 by using:-
CMPS03_I2C compass = MAKE_CMPS03_I2C_At(0xC0);
GENERIC_I2C_DEVICE myDevice = MAKE_GENERIC_I2C_DEVICE(0xA0);
then the first step is to build a list of the devices that will be on the bus:-
static I2C_DEVICE_LIST PROGMEM i2c_list[] = {
    &compass.i2cInfo,
    &myDevice.i2cInfo
};
This is just a comma separated list of device entries where each entry has an '&' followed by the name of the device and ending with '.i2cInfo'. Note the use of PROGMEM.
Now that we have a list of devices we can create the software bus using pins D0 for SCL and D1 for SDA :
I2C_SOFTWARE_BUS i2c = MAKE_I2C_SOFTWARE_BUS(i2c_list, D0, D1);
The only remaining thing to do is to initialise the bus in appInitHardware passing the address of the device list:
i2cBusInit(&i2c);
All done. You can now start talking to the devices.

i2cBusInit(I2C_ABSTRACT_BUS* bus)

Initialise a hardware or software I2C bus to make it ready for use.
This should be called from appInitHardware. It may result in an error if the list of devices contains duplicate I2C addresses.

i2cBusSetBitRate(I2C_ABSTRACT_BUS* bus, uint16_t bitrateKHz)

Set the communication speed of the I2C interface to the value of (the parameter x 1000 bits per second).
By default the speed is set to 100Kbps which is equivalent to calling this routine with a parameter of 100. Note that it is ignored by a software bus as it just goes as fast as it can.
If you you have multiple devices connected then you should set the bus speed to the value required by the slowest device. ie if you have 3 devices and they can cope with: 10kbps, 100kbps and 400kbps respectively - then you should use a bus speed of 10kbps.

boolean i2cMasterReadRegisters(const I2C_DEVICE* device, uint8_t startReg, size_t numBytes, uint8_t* response)

A complete communication session to read a number of sequential registers from the device. Returns TRUE if successful or FALSE if there was an error.
The first parameter specifies the I2C device.
The second parameter specifies the first register to read from.
The third parameter is the number of consecutive registers to read.
The last parameter specifies the list of data bytes to store the returned values.
For example: given a device at 0x32 and you want to read registers 10 and 11 then you would use:
I2C_GENERIC_DEVICE myDevice = MAKE_I2C_GENERIC_DEVICE(0x32);
uint8_t response[2];
i2cMasterReadRegisters(&myDevice.i2cInfo, 10, 2, response);
Note how the C operator 'sizeof' can be used to make sure that the number of bytes being read is actually the same as the number of bytes in the 'response' array:-
i2cMasterReadRegisters(&myDevice.i2cInfo, 10, sizeof(response), response);

boolean i2cMasterWriteRegisters(const I2C_DEVICE* device, uint8_t startReg, size_t numBytes, const uint8_t* data)

Performs a complete communication session with a device to write to a series of registers.
Returns TRUE if successful or FALSE if there was an error.
The first parameter specifies the device.
The second parameter specifies the first register to write to.
The third parameter is the number of consecutive registers to write to.
The last parameter specifies the list of data bytes to be written out.
For example: given a device at 0x32 and you want to set registers 10 and 11 to the values 1 and 2 then you would use:
I2C_GENERIC_DEVICE myDevice = MAKE_I2C_GENERIC_DEVICE(0x32);
uint8_t data[] = {1,2};
i2cMasterWriteRegisters(&myDevice.i2cInfo, 10, 2, data);
Note how the C operator 'sizeof' can be used to make sure that the number of bytes being written is actually the same as the number of bytes in the 'data' array:-
i2cMasterWriteRegisters(&myDevice.i2cInfo, 10, sizeof(data), data);

boolean i2cMasterWriteRegister(const I2C_DEVICE* device, uint8_t reg, uint8_t value)

A complete communication session to write a single byte value to a single I2C device register. Returns TRUE if succeeded or FALSE if there was an error.
This is a simplified form of the i2cMasterWriteRegisters(const I2C_DEVICE* device, uint8_t startReg, size_t numBytes, const uint8_t* data) command if your are only writing to a single (one byte) register.

boolean i2cMasterSend(const I2C_DEVICE* device, size_t length, const uint8_t *data)

Performs a complete communication with a slave device to write a number of bytes.
The return value specifies if it was successful (TRUE) or not (FALSE).
This is the same as the following pseudo code:
// Connect in write mode
i2cStart( &myDevice, TRUE);
.. one or more i2cPut to write the data ...
// Hang up
i2cStop( myDevice.bus);

boolean i2cMasterSendWithPrefix(const I2C_DEVICE* device, size_t prefixLen, const uint8_t* prefix, size_t length, const uint8_t* data)

Performs a complete communication with a slave device to write a number of bytes.
The return value specifies if it was successful (TRUE) or not (FALSE).
This is the same as the following pseudo code:
// Connect in write mode
i2cStart( &myDevice, TRUE);
.. one or more i2cPut to write the prefix data ...
.. one or more i2cPut to write the main data ...
// Hang up
i2cStop( myDevice.bus);

boolean i2cMasterReceive(const I2C_DEVICE* device, size_t length, uint8_t *data)

Performs a complete communication with a slave device to read a number of bytes.
The return value specifies if it was successful (TRUE) or not (FALSE).
This is the same as the following pseudo code:
// Connect in read mode
i2cStart( &myDevice, FALSE);
.. one or more i2cGet to read the data ...
// Hang up
i2cStop( myDevice.bus);

boolean i2cMasterTransfer(const I2C_DEVICE* device, size_t wlen, const uint8_t *wdata, size_t rlen, uint8_t * rdata)

Performs a complete communication with a slave device to write a number of bytes and then read a number of bytes.
The return value specifies if it was successful (TRUE) or not (FALSE).
This is the same as the following pseudo code:
// Connect in write mode
i2cStart( &myDevice, TRUE);
.. one or more i2cPut to write the data ...
// Connect in read mode
i2cStart( &myDevice, FALSE);
.. one or more i2cGet to read the data ...
// Hang up
i2cStop( myDevice.bus);

boolean i2cStart(const I2C_DEVICE* device, boolean writeMode)

A low level function to start communicating with a given slave device if you are writing your own I2C communications from scratch.
The first parameter specifies the device to contact and the second parameter specifies if you are in write/talk mode (TRUE) or read/listen mode (FALSE).
This will try to start communicating with the given device. It's like dialling a phone number. If there is no reply then the function will return FALSE, otherwise it will return TRUE.
Once a communication link has been established you can use the i2cGet (if you are in read/listen mode) or i2cPut (if you are in write/talk mode).
You can swap between read and write mode by re-issuing another i2cStart to the same device with another mode (called a 'repeated start').
At the end of the conversation, and before you can contact a different device, you must hang up the phone by calling i2cStop.

boolean i2cPut(const I2C_ABSTRACT_BUS* i2c, uint8_t b)

This is a low-level function to write a byte across the bus.
The first parameter specifies the bus you want to send the data across and the second parameter is the data byte to be sent.
The function returns TRUE if successful or FALSE if not.
Note that this can only be used after a successful call to i2cStart has been used to place the bus into write mode.

uint8_t i2cGet(const I2C_ABSTRACT_BUS* i2c, boolean isLastByte)

This is a low-level function to read a byte from the bus.
The first parameter specifies the bus you want to read the data from and the second parameter specifies whether you will be reading more bytes (FALSE) or this is the last one (TRUE).
The function returns the data byte.
Note that this can only be used after a successful call to i2cStart has been used to place the bus into read mode.

i2cStop(const I2C_ABSTRACT_BUS* i2c)

This is a low-level call to indicate that the communication session has finished (ie 'hang up the phone').
Once an i2cStart has been issued then you must terminate the call with an i2cStop.

i2cSlaveInit(uint8_t deviceAddr, boolean generalCall, cBuffer* rx, cBuffer* tx)

Initialise the bus as a slave.
This should be called from appInitHardware.
The first parameter is the unique device address which should be an even number between 2 and 254.
The second parameter inidicates whether you are interested in responding to general calls which can be broadcast to all the devices on the bus.
The last two parameter specify the data buffers used to store received data and the response data to be returned. These should be initialised to store the longest expected message. See buffer.h

i2cSlaveSetReceiveHandler(void (*i2cSlaveRx_func)(cBuffer* data))

Register a callback routine that will be called once the slave has received a message.
For example:
void myRxHandler(cBuffer* data){
... process the message in the buffer ...
}
You can then register the above routine to be called by writing:
i2cSlaveSetReceiveHandler(&myRxHandler);

i2cSlaveSetTransmitHandler(void (*i2cSlaveTx_func)(cBuffer* data))

Register a callback routine that will be called to write the response bytes.
For example:
void myTxHandler(cBuffer* data){
... write the response into the data buffer ...
}
You can then register the above routine to be called by writing:
i2cSlaveSetTransmitHandler(&myTxHandler);

Valid XHTML 1.0 Transitional