Se usi il read()
e write()
metodi, chiamando ioctl
con I2C_SLAVE
una volta è sufficiente. Puoi anche usare I2C_SLAVE_FORCE
se il dispositivo è già in uso.
Tuttavia non ho ancora trovato un modo coerente per leggere registri specifici per ogni dispositivo utilizzando il read()/write()
metodi.
Esistono tre metodi principali per comunicare con i dispositivi i2c dallo spazio utente.
1. IOCTL I2C_RDWR
Questo metodo consente la lettura/scrittura simultanea e l'invio di una sequenza ininterrotta di messaggi. Non tutti i dispositivi i2c supportano questo metodo.
Prima di eseguire l'i/o con questo metodo, dovresti controllare se il dispositivo supporta questo metodo usando un ioctl I2C_FUNCS
funzionamento.
Usando questo metodo, non è necessario eseguire un ioctl I2C_SLAVE
operazione:viene eseguita dietro le quinte utilizzando le informazioni incorporate nei messaggi.
2. IOCTLSMBUS
Questo metodo di i/o è più potente ma il codice risultante è più prolisso. Questo metodo può essere utilizzato se il dispositivo non supporta I2C_RDWR
metodo.
Usando questo metodo, fai è necessario eseguire un ioctl I2C_SLAVE
operazione (o, se il dispositivo è occupato, un I2C_SLAVE_FORCE
funzionamento).
3. I/O SYSFS
Questo metodo utilizza le chiamate di sistema di i/o file di base read()
e write()
. Con questo metodo non è possibile eseguire operazioni sequenziali ininterrotte. Questo metodo può essere utilizzato se il dispositivo non supporta I2C_RDWR
metodo.
Usando questo metodo, fai è necessario eseguire un ioctl I2C_SLAVE
operazione (o, se il dispositivo è occupato, un I2C_SLAVE_FORCE
funzionamento).
Non riesco a pensare a nessuna situazione in cui questo metodo sarebbe preferibile ad altri, a meno che tu non abbia bisogno che il chip venga trattato come un file.
Esempio IOCTL completo
Non ho testato questo esempio, ma mostra il flusso concettuale della scrittura su un dispositivo i2c.-- rilevare automaticamente se usare ioctl I2C_RDWR
o tecnica 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;
}
Non sono troppo sicuro se questo aiuta perché non uso ioctl I2C_RDWR ma ho usato con successo il seguente codice:
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, ___);
Tutto quello che faccio è impostare I2C_SLAVE_FORCE una volta all'inizio e dopo posso leggere e scrivere quanto voglio.
PS - Questo è solo un esempio di codice e ovviamente dovresti controllare i ritorni di tutte queste funzioni. Sto usando questo codice per comunicare con un chip I/O digitale. Le due funzioni i2c_* sono solo wrapper che chiamano ioctl(fd, I2C_SMBUS, &args); dove args è un tipo struct i2c_smbus_ioctl_data.