-
Notifications
You must be signed in to change notification settings - Fork 3
I2C
I2C is a standard interface that also goes by names like "two wire interface" and "TWI." There is a single clock line called SCL driven by a master device (in our case, the OBC), and a data line called SDA. The SDA line is bidirectional.
An I2C communication sequence starts with the master sending out an address. Each device on the I2C bus has a unique address. When a device sees its address on the line, it will acknowledge. From there, the master can start issuing commands to request data from the slave device.
Here's a good overview of the interface details: http://www.ti.com/lit/an/slva704/slva704.pdf
Here's a more in-depth document about real world I2C quirks: http://processors.wiki.ti.com/index.php/I2C_Tips
We use I2C to communicate with the sun sensor multiplexers, power system, and several temperature sensors across the satellite.
The addresses of each device on the satellite can be found here: https://docs.google.com/spreadsheets/d/1k9-GxSDjCNQlImQ5CRE3hHjOckTxFv60mS3J6f4K6jU/edit#gid=198838120 (I2C Addressing page)
The SFUSat I2C functions can be found in sfu_i2c.h
. These functions wrap the low-level I2C driver functions generated from HALCoGen, and allow us to handle errors. To communicate successfully with a device, you still need to use some of the base HALCoGen functions. More on this later.
The reference implementation for a feature involving I2C can be found in stlm75.h
and stlm75.c
. The STLM75 is the temperature sensor, and these files constitute the driver for it.
Note how the code for this is laid out:
-
read_temp_raw()
is the lowest level function and basically just deals with I2C -
read_temp()
wrapsread_temp_raw
in a mutex, and brings up errors from the lower level driver.
Please architect your drivers in a similar manner. You must use the xI2CMutex
which comes from sfu_i2c.h
For other drivers, you will need to follow the stlm75 read_temp_raw() example for setting the slave address, direction, etc. You can probably use the same exact flow, but just change the command you're sending and possibly the setCount
, which tells the I2C hardware how many bytes to send or expect to receive. You can right click on a function name and choose "Open Declaration" to go to it, and the HALCoGen ones describe what they're doing.
Details about how to format the messages for your device will be in the data sheet. For example, to read the temperature sensor, the 7 most significant bits are the address, and the LSB is a read/write bit (1 = read). Then, the device will respond with two bytes of data, of which the 9 most significant bits are useful temperature information.
This all boils down to creating nice functions that do a specific function with the I2C-connected hardware, such as getting the temperature. Focus should be placed on handling errors, and making your function easy to use (by #defining macros for different sensor names, for example).
-
Create a new
.h
and.c
file for your device. A good name suggestion is the part number. -
Implement a low level I2C function for a common command pattern (such as sending a command and reading back two bytes of data). Something like
read_temp_raw()
. You will need to consult the device's data sheet for this. -
Now that you have that, implement a function that wraps function #1 with the mutex, and possibly gives the ability for the user to pass in a command (if your device has multiple things it can do). Also check the errors as is done in
read_temp()
. -
If your device has multiple things it can do, wrap function #2 with a function for each common use. An example would be individual functions for reading battery level and for initializing the power system.
-
No need to go crazy making many separate functions. It's okay for us to pass a #defined command into a function that will send it to the device. However, if it's something we'll be doing very frequently, make a new function for it so it's obvious and clean.
-
You can search for Arduino drivers for your device or type of device. While there's sometimes some questionable code out there, you can often pick up on weird device specific quirks that may not be obvious without seeing a driver implemented. You can make many of these if there are different command/response sequences.
-
We like #defining all magic numbers, such as device addresses, commands and register settings. Do this in your .h file if the #define should be accessible from the application.
-
Think about
i2cSetCount()
-
Bring up the I2C error codes into your top level (most highly abstracted function).