Si usa el read()
y write()
métodos, llamando a ioctl
con I2C_SLAVE
una vez es suficiente. También puedes usar I2C_SLAVE_FORCE
si el dispositivo ya está en uso.
Sin embargo, aún no he encontrado una forma consistente de leer registros específicos para cada dispositivo usando el read()/write()
métodos.
Existen tres métodos principales para comunicarse con dispositivos i2c desde el espacio de usuario.
1. IOCTL I2C_RDWR
Este método permite la lectura/escritura simultánea y el envío de una secuencia ininterrumpida de mensajes. No todos los dispositivos i2c admiten este método.
Antes de realizar operaciones de E/S con este método, debe comprobar si el dispositivo es compatible con este método utilizando un ioctl I2C_FUNCS
operación.
Con este método, no necesita realizar un ioctl I2C_SLAVE
operación:se realiza entre bastidores utilizando la información incrustada en los mensajes.
2. SMBUS IOCTL
Este método de E/S es más poderoso pero el código resultante es más detallado. Este método se puede utilizar si el dispositivo no es compatible con I2C_RDWR
método.
Con este método, haces necesita realizar un ioctl I2C_SLAVE
operación (o, si el dispositivo está ocupado, un I2C_SLAVE_FORCE
operación).
3. E/S SYSFS
Este método utiliza el archivo básico de llamadas al sistema de E/S read()
y write()
. Las operaciones secuenciales ininterrumpidas no son posibles con este método. Este método se puede utilizar si el dispositivo no es compatible con I2C_RDWR
método.
Con este método, haces necesita realizar un ioctl I2C_SLAVE
operación (o, si el dispositivo está ocupado, un I2C_SLAVE_FORCE
operación).
No puedo pensar en ninguna situación en la que este método sea preferible a otros, a menos que necesite que el chip sea tratado como un archivo.
Ejemplo completo de IOCTL
No he probado este ejemplo, pero muestra el flujo conceptual de escribir en un dispositivo i2c.-- detectando automáticamente si usar el ioctl I2C_RDWR
o técnica smbus.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#define I2C_ADAPTER "/dev/i2c-0"
#define I2C_DEVICE 0x00
int i2c_ioctl_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
// the extra byte is for the regaddr
size_t buff_size = 1 + size;
buf = malloc(buff_size);
if (buf == NULL) {
return -ENOMEM;
}
buf[j ++] = regaddr;
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_msg messages[] = {
{
.addr = dev,
.buf = buf,
.len = buff_size,
},
};
struct i2c_rdwr_ioctl_data payload = {
.msgs = messages,
.nmsgs = sizeof(messages) / sizeof(messages[0]),
};
ret = ioctl(fd, I2C_RDWR, &payload);
if (ret < 0) {
ret = -errno;
}
free (buf);
return ret;
}
int i2c_ioctl_smbus_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
int i, j = 0;
int ret;
uint8_t *buf;
buf = malloc(size);
if (buf == NULL) {
return -ENOMEM;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
buf[j ++] = (data[i] & 0xff00) >> 8;
buf[j ++] = data[i] & 0xff;
}
struct i2c_smbus_ioctl_data payload = {
.read_write = I2C_SMBUS_WRITE,
.size = I2C_SMBUS_WORD_DATA,
.command = regaddr,
.data = (void *) buf,
};
ret = ioctl (fd, I2C_SLAVE_FORCE, dev);
if (ret < 0)
{
ret = -errno;
goto exit;
}
ret = ioctl (fd, I2C_SMBUS, &payload);
if (ret < 0)
{
ret = -errno;
goto exit;
}
exit:
free(buf);
return ret;
}
int i2c_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data, size_t size)
{
unsigned long funcs;
if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
return -errno;
}
if (funcs & I2C_FUNC_I2C) {
return i2c_ioctl_write (fd, dev, regaddr, data, size);
} else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) {
return i2c_ioctl_smbus_write (fd, dev, regaddr, data, size);
} else {
return -ENOSYS;
}
}
int parse_args (uint8_t *regaddr, uint16_t *data, size_t size, char *argv[])
{
char *endptr;
int i;
*regaddr = (uint8_t) strtol(argv[1], &endptr, 0);
if (errno || endptr == argv[1]) {
return -1;
}
for (i = 0; i < size / sizeof(uint16_t); i ++) {
data[i] = (uint16_t) strtol(argv[i + 2], &endptr, 0);
if (errno || endptr == argv[i + 2]) {
return -1;
}
}
return 0;
}
void usage (int argc, char *argv[])
{
fprintf(stderr, "Usage: %s regaddr data [data]*\n", argv[0]);
fprintf(stderr, " regaddr The 8-bit register address to write to.\n");
fprintf(stderr, " data The 16-bit data to be written.\n");
exit(-1);
}
int main (int argc, char *argv[])
{
uint8_t regaddr;
uint16_t *data;
size_t size;
int fd;
int ret = 0;
if (argc < 3) {
usage(argc, argv);
}
size = (argc - 2) * sizeof(uint16_t);
data = malloc(size);
if (data == NULL) {
fprintf (stderr, "%s.\n", strerror(ENOMEM));
return -ENOMEM;
}
if (parse_args(®addr, data, size, argv) != 0) {
free(data);
usage(argc, argv);
}
fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK);
ret = i2c_write(fd, I2C_DEVICE, regaddr, data);
close(fd);
if (ret) {
fprintf (stderr, "%s.\n", strerror(-ret));
}
free(data);
return ret;
}
No estoy muy seguro de si esto ayuda porque no uso ioctl I2C_RDWR pero he estado usando el siguiente código con éxito:
int fd;
fd = open("/dev/i2c-5", O_RDWR);
ioctl(fd, I2C_SLAVE_FORCE, 0x20);
i2c_smbus_write_word_data(fd, ___, ___);
i2c_smbus_read_word_data(fd, ___);
Todo lo que hago es configurar I2C_SLAVE_FORCE una vez al principio y puedo leer y escribir tanto como quiera después de eso.
PD:esto es solo una muestra de código y, obviamente, debe verificar los retornos de todas estas funciones. Estoy usando este código para comunicarme con un chip de E/S digital. Las dos funciones i2c_* son simplemente envoltorios que llaman a ioctl(fd, I2C_SMBUS, &args); donde args es un tipo de estructura i2c_smbus_ioctl_data.