Reputation: 9
First I'm noob in this stuff, but learning and really want to get this working. I bought a raspberrypi and a bno055 bosch accelerometer. It comes with a bno055.c, bno055.h and a bno055_support.c file. After getting into programming and c and studying/trying out it seems somehow I need to define how to do I2C read and write. It needs to be setup so you can define the amount of bytes read/written. Below you can find the two functions as predefined :
/* \Brief: The API is used as I2C bus write
* \Return : Status of the I2C write
* \param dev_addr : The device address of the sensor
* \param reg_addr : Address of the first register,
* will data is going to be written
* \param reg_data : It is a value hold in the array,
* will be used for write the value into the register
* \param cnt : The no of byte of data to be write
*/
s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
s32 BNO055_iERROR = BNO055_INIT_VALUE;
u8 array[I2C_BUFFER_LEN];
u8 stringpos = BNO055_INIT_VALUE;
array[BNO055_INIT_VALUE] = reg_addr;
for (stringpos = BNO055_INIT_VALUE; stringpos < cnt; stringpos++)
array[stringpos + BNO055_I2C_BUS_WRITE_ARRAY_INDEX] =
*(reg_data + stringpos);
}
/*
* Please take the below APIs as your reference for
* write the data using I2C communication
* "BNO055_iERROR = I2C_WRITE_STRING(DEV_ADDR, ARRAY, CNT+1)"
* add your I2C write APIs here
* BNO055_iERROR is an return value of I2C read API
* Please select your valid return value
* In the driver BNO055_SUCCESS defined as 0
* and FAILURE defined as -1
* Note :
* This is a full duplex operation,
* The first read data is discarded, for that extra write operation
* have to be initiated. For that cnt+1 operation done
* in the I2C write string function
* For more information please refer data sheet SPI communication:
*/
return (s8)BNO055_iERROR;
}
/* \Brief: The API is used as I2C bus read
* \Return : Status of the I2C read
* \param dev_addr : The device address of the sensor
* \param reg_addr : Address of the first register,
* will data is going to be read
* \param reg_data : This data read from the sensor,
* which is hold in an array
* \param cnt : The no of byte of data to be read
*/
s8 BNO055_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
s32 BNO055_iERROR = BNO055_INIT_VALUE;
u8 array[I2C_BUFFER_LEN] = {BNO055_INIT_VALUE};
u8 stringpos = BNO055_INIT_VALUE;
array[BNO055_INIT_VALUE] = reg_addr;
/* Please take the below API as your reference
* for read the data using I2C communication
* add your I2C read API here.
* "BNO055_iERROR = I2C_WRITE_READ_STRING(DEV_ADDR,
* ARRAY, ARRAY, 1, CNT)"
* BNO055_iERROR is an return value of SPI write API
* Please select your valid return value
* In the driver BNO055_SUCCESS defined as 0
* and FAILURE defined as -1
*/
for (stringpos = BNO055_INIT_VALUE; stringpos < cnt; stringpos++)
*(reg_data + stringpos) = array[stringpos];
return (s8)BNO055_iERROR;
}
My question is there somebody out, that can coach me through this challenge? I'm learning about, https://www.kernel.org/doc/Documentation/i2c/dev-interface, but stuck here for the moment. Thx in advance for reading / replying.
Upvotes: 0
Views: 24167
Reputation: 39
Here is a simple program which reads and writes I2C registers, using the linux kernel I2C driver. This is tested on a raspberry pi.
#include <stdio.h>
#include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t
#include <linux/i2c-dev.h> // I2C_SLAVE
#include <fcntl.h> // open(), O_RDONLY
#include <unistd.h> // read(), write(), usleep()
#include <sys/ioctl.h> // ioctl()
#define I2C_BUS_NUMBER 1
#define I2C_DEVICE_NUMBER 0x1C
#define I2C_REGISTER_NUMBER 4
int open_i2c_device ( int i2c_bus_number , int i2c_device_number ) {
char filename[12] ;
snprintf ( filename , 11 , "/dev/i2c-%i", i2c_bus_number) ;
int file_descriptor = open ( filename, O_RDWR ) ;
if ( file_descriptor < 0 ) {
printf ( "failed to open %s, open() returned %i\n" , filename , file_descriptor ) ;
}
if ( ioctl ( file_descriptor , I2C_SLAVE, i2c_device_number ) < 0 ) {
printf ( "failed to find device %i on i2c bus %i\n" , i2c_device_number , i2c_bus_number ) ;
}
return ( file_descriptor ) ;
}
void i2c_write_register ( int file_descriptor , uint8_t register_to_write_to , uint8_t data_to_write_to_register ) {
uint8_t message[2] ;
message[0] = register_to_write_to ;
message[1] = data_to_write_to_register ;
int i = write ( file_descriptor , message , 2 ) ;
if ( i != 2 ) {
printf ( "error: i2c write returned %i instead of 2\n" , i ) ;
}
}
uint8_t i2c_read_register ( int file_descriptor , uint8_t register_to_read_from ) {
uint8_t message[1] ;
message[0] = register_to_read_from ;
int i = write ( file_descriptor , message , 1 ) ;
if ( i != 1 ) {
printf ( "error: i2c write returned %i instead of 1\n" , i ) ;
}
i = read ( file_descriptor , message , 1 ) ;
if ( i != 1 ) {
printf ( "error: i2c read returned %i instead of 1\n" , i ) ;
}
return ( message[0] ) ;
}
int main () {
int file_descriptor_for_I2C_device = open_i2c_device ( I2C_BUS_NUMBER , I2C_DEVICE_NUMBER ) ;
i2c_write_register ( file_descriptor_for_I2C_device , I2C_REGISTER_NUMBER , 0xA1 ) ;
uint8_t i = i2c_read_register ( file_descriptor_for_I2C_device , I2C_REGISTER_NUMBER ) ;
printf ( "register %02hhx is %02hhx\n" , I2C_REGISTER_NUMBER, i) ;
}
Usually ordinary users are not allowed to access I2C, so you probably need to run I2C programs with sudo.
You might need to load the linux kernel I2C driver module, and change some parameters like I2C speed.
Documentation for the I2C linux kernel driver: https://www.kernel.org/doc/Documentation/i2c/dev-interface
For accessing I2C, you need three numbers: the number of the I2C bus, the number of the I2C device, and the number of the I2C register. All three of these numbers are sometimes called address. If someone or some documentation refers to I2C address, ask which number they are talking about.
If you do not know what I2C bus number to use, run the command sudo i2cdetect -l
If you do not know what I2C device number to use, run the command sudo i2cdetect -y B, where B is the I2c bus number.
If you do not know what I2C register number to use, run the command sudo i2cdump -y B D where B is the I2C bus number and D is the I2C device number.
The I2C protocol includes some special combinations of clock and data pulses which start a transmission, and which acknowledge a transmission. If a program is implementing I2C by switching gpio pins between LOW and HIGH, the program needs to do start and acknowledge. But if a program is using the I2C linux kernel driver, then the I2C linux kernel driver does start and acknowledge, so the program should NOT do start and acknowledge.
The linux kernel driver creates a /dev/i2c* for each I2C bus. Your program should open() the /dev/i2c*, then use ioctl() to associate a single I2C device number with the I2C bus. So what if you have more than one I2C device number for an I2C bus? The documentation does not say. Maybe your program should open() the /dev/i2c* multiple times, then associate a different I2C device number with each file descriptor. Maybe your program should change which I2C device number is associated with the I2C bus with every read or write.
The I2C protocol is that to do a read, first start doing a write, select which register to write, abort the write without writing any data, then read.
The I2C protocol is to begin every transmission with a device number. If a program is implementing I2C by switching gpio pins between LOW and HIGH, the program needs to begin every transmission with the I2C device number. But if a program is using the I2C linux kernel driver, then the I2C linux kernel driver adds the I2C device number to every transmission (which is why the program must use ioctl() to provide the correct I2C device number to the I2C linux kernel driver), so the program should NOT begin a transmission with the I2C device number.
Notice that the I2C protocol and the I2C linux kernel driver protocol are different. Some details about the I2C protocol do not apply to the I2C linux kernel driver protocol, and vice versa.
The I2C bus probably runs at a slower speed than the cpu. If a program writes some data to the I2C controller, it probably takes some time for the I2C controller to write the data to the I2C bus, and more time to read data back. So when reading an I2C register, I thought maybe there should be a delay or timeout. But these functions work without a delay or timeout, so I guess the linux kernel I2C driver takes care of that. Maybe the linux kernel driver knows how long I2C access should take, and delays read() for that amount of time. On a raspberry pi with a 400 khz I2C bus, I found about 5 milliseconds to read 1 register. I think the code executes faster than that, so it is probably waiting for the I2C data. If you want your program to do something else while waiting for I2C data, try multithreading. Or maybe you could code a loop which checks multiple sensors, and the first pass through the loop the code sends the request, the second pass through the loop the code reads the response, the third pass through the loop the code sends another request, etc.
Upvotes: 0
Reputation: 1
For example
s8 BNO055_I2C_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
u8 array[I2C_BUFFER_LEN] = { BNO055_INIT_VALUE };
array[BNO055_INIT_VALUE] = reg_addr;
if (write(file, array, 1) != 1) {
return -1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
s8 res = read(file, reg_data, cnt);
if (res > 0) return 0;
else return -1;
}
s8 BNO055_I2C_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
char buf[1 + cnt];
buf[0] = reg_addr;
memcpy(&buf[1], reg_data, cnt);
if (write(file, buf, cnt+1) != cnt+1) {
return -1;
}
return 0;
}
Upvotes: 0
Reputation: 2050
I recently wrote a library similar to what you're describing for the MMA8451 i2c accelerometer.
Essentially i2c controllers in Linux get assigned a device node (e.g. /dev/i2c-1
). You'll open this device node as a file like this:
int file = open(path, O_RDWR); //path = /dev/i2c-1
Once you have your file handle you can read and write i2c registers using ioctl's. The i2c kernel module supports the I2C_RDWR ioctl which lets you interact with i2c registers.
To read a register you do something like this:
int mma8451_get_i2c_register(int file, unsigned char addr, unsigned char reg, unsigned char *val) {
unsigned char inbuf, outbuf;
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[2];
outbuf = reg;
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = &outbuf;
messages[1].addr = addr;
messages[1].flags = I2C_M_RD;
messages[1].len = sizeof(inbuf);
messages[1].buf = &inbuf;
packets.msgs = messages;
packets.nmsgs = 2;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
return 0;
}
*val = inbuf;
return 1;
}
To write a register you do something like this:
int mma8451_set_i2c_register(int file, unsigned char addr, unsigned char reg, unsigned char value) {
unsigned char outbuf[2];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
messages[0].addr = addr;
messages[0].flags = 0;
messages[0].len = sizeof(outbuf);
messages[0].buf = outbuf;
outbuf[0] = reg;
outbuf[1] = value;
packets.msgs = messages;
packets.nmsgs = 1;
if(ioctl(file, I2C_RDWR, &packets) < 0) {
return 0;
}
return 1;
}
Edit: The I2C_RDWR
ioctl takes a i2c_rdwr_ioctl_data
structure as an argument. It's described like this:
Another common data structure is
struct i2c_rdwr_ioctl_data
This is the structure as used in the I2C_RDWR ioctl call
struct i2c_rdwr_ioctl_data { struct i2c_msg __user *msgs; /* pointers to i2c_msgs */ __u32 nmsgs; /* number of i2c_msgs */ };
(Defined in linux/i2c-dev.h) This structure points to the array of i2c_msg to process and defines the number of i2c_msg in the array.
Usage: If the program is to write one byte (example - the index byte), followed by reading one byte, two struct i2c_msg data structures will be needed. One for the write, and another for the read. These two data structures should be declared as an array of two i2c_msg data structures. They will be processed in the order they appear in the array.
The i2c_rdwr_ioctl_data
structure contains a pointer to an array of i2c_msg
structures. These structures contain the actual messages you want to send or receive. For for example my accelerometer in order to read a register I first needed to write the register I wanted to read to the device and then I could read it (hence why there's two i2c_msg
's in my read function). If I was simply writing a register I only needed one.
You'll want to refer to the data sheet for your BNO055 to figure out exactly which registers do what.
As for your example, it looks like it comes from bno055_support.c. It looks like this is just a set of stubs you're meant to implement. It looks like it's basically a mock for a real interface. So what's important is the interface, not the actual code (so don't worry about cnt
). The important bits are here:
s8 I2C_routine(void)
{
bno055.bus_write = BNO055_I2C_bus_write;
bno055.bus_read = BNO055_I2C_bus_read;
bno055.delay_msec = BNO055_delay_msek;
bno055.dev_addr = BNO055_I2C_ADDR1;
return BNO055_INIT_VALUE;
}
This sets the function pointers on your device structure to the write functions you're going to define and sets the address of your device and delay. From there you need to implement functions that match this interface:
#define BNO055_BUS_WRITE_FUNC(dev_addr, reg_addr, reg_data, wr_len)\
bus_write(dev_addr, reg_addr, reg_data, wr_len)
#define BNO055_BUS_READ_FUNC(dev_addr, reg_addr, reg_data, r_len)\
bus_read(dev_addr, reg_addr, reg_data, r_len)
The functions I gave you above should be pretty close stand-ins. Good luck!
Upvotes: 6