i2c-tools is not that easy to use in some situation. Recently I need to make sure my room is wet enough to kill virus, so I develop a simple tool based on SHT20 — a temperature and humidity sensor, but I find I can not use i2c-tools to test SHT20 I2C bus, it missed some key features.
I have to spend couple of hours write another one.
usage: i2ctest [dev path] [address] [r/w] [length/data]
example:
show device on i2c-0:
i2ctest /dev/i2c-0
read 3 byte from i2c-0, address 0x40:
i2ctest /dev/i2c-0 0x40 r 3
write 2 byte(0xe2 0x88) to i2c-0, address 0x21:
i2ctest /dev/i2c-0 0x21 w '0xe2 0x88'
For my usage, query i2c devices:
root@OpenWrt:/tmp# i2ctest /dev/i2c-0
0x10: BUSY
0x40: READY
0x10 is BUSY because the ES8388 driver already taken the address.
0x40 is the SHT20 address. From SHT20 datasheet, write 0xF3 first then read 3 bytes from it, we will get temperature.
root@OpenWrt:/tmp# i2ctest /dev/i2c-0 0x40 w 0xf3
write: 1 byte(s)
root@OpenWrt:/tmp# i2ctest /dev/i2c-0 0x40 r 3
00000000: 64 98 56 d.V
Calculate from the data: T = 0x6498 * 175.72 / 65536 – 46.85 = 22.198C
Here is the source code for i2ctest.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define min(a, b) ((a) < (b) ? (a) : (b))
void hex_print(const unsigned char* s, int size, FILE *fp)
{
int i = 0, n = 0, c = 0, p = 0;
// default print to stdout.
if (fp == NULL)
fp = stdout;
while(1) {
n = size > 0x10 ? 0x10 : size;
size -= n;
p += fprintf(fp, "%08X: ", c);
for(i = 0; i < n; i++) {
if(i == 8) {
p += fprintf(fp, " ");
}
p += fprintf(fp, "%02X ", *(s + c + i));
}
for(i = n; i < 0x10; i++) {
if(i == 8) {
p += fprintf(fp, " ");
}
p += fprintf(fp, " ");
}
p += fprintf(fp, " ");
for(i = 0; i < n; i++) {
if(*(s + c + i) >= 0x20 && *(s + c + i) <= 0x7E) {
p += fprintf(fp, "%c", *(s + c + i));
} else {
p += fprintf(fp, ".");
}
}
p += fprintf(fp, "\n");
if(size <= 0) {
break;
}
c += n;
}
}
// convert string to unsigned int.
// hex: 0x, oct: 0, bin: ...b, dec: normal.
unsigned int atox(char* s)
{
char *p = s, *e = s;
unsigned int r = 0;
while(*e != '\0')
e++;
if(*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X')) {
// hex: skip first 0x or 0X header.
p += 2;
while(p != e) {
r <<= 4;
r += ((*p >= '0' && *p <= '9') ? (*p - '0') : ((*p >= 'A' && *p <= 'F') ?
*p - 'A' + 0xA : ((*p >= 'a' && *p <= 'f') ? *p - 'a' + 0xA : 0)));
p++;
}
} else if((e - p) >= 2 && *(e - 1) == 'b') {
// bin: ignore last b.
e--;
while(p != e) {
r <<= 1;
r += (*p == '0' ? 0 : 1);
p++;
}
} else if(*p == '0') {
// oct: skip first 0 header.
p += 1;
while(p != e) {
r <<= 3;
r += ((*p >= '0' && *p <= '7') ? (*p - '0') : 0);
p++;
}
} else {
// dec: we do not need to deal the number.
while(p != e) {
r *= 10;
r += ((*p >= '0' && *p <= '9') ? (*p - '0') : 0);
p++;
}
}
return r;
}
int decode_data(unsigned char *out, int max, const char *in)
{
int used = 0;
const char *p = in;
char num[5] = {0};
while (*p) {
if (*p == ' ') {
p++;
continue;
}
if (*p != '0')
return 0;
if (used >= max)
return used;
memcpy(num, p, 4);
out[used++] = atox(num);
p += 4;
}
return used;
}
int main(int argc, char *argv[])
{
unsigned char buf[0x100] = {0};
int i2c, code, size;
if (argc != 2 && argc != 5) {
printf("usage: i2ctest [dev path] [address] [r/w] [length/data]\n");
printf("\n\nexample:\n");
printf("\nshow device on i2c-0:\n"
"\ti2ctest /dev/i2c-0\n");
printf("\nread 3 byte from i2c-0, address 0x40:\n"
"\ti2ctest /dev/i2c-0 0x40 r 3\n");
printf("\nwrite 2 byte(0xe2 0x88) to i2c-0, address 0x21:\n"
"\ti2ctest /dev/i2c-0 0x21 w '0xe2 0x88'\n\n");
return 0;
}
i2c = open(argv[1], O_RDWR);
if (i2c < 0) {
printf("error: can not open device %s\n", argv[1]);
return -1;
}
if (argc == 2) {
for (code = 0; code < 0x7f; code++) {
ioctl(i2c, I2C_SLAVE, (unsigned char)code);
if (errno == EBUSY)
printf("0x%02x: BUSY\n", code);
size = write(i2c, buf + code, 1);
if (size > 0)
printf("0x%02x: READY\n", code);
}
return 0;
}
code = ioctl(i2c, I2C_SLAVE, (unsigned char)atox(argv[2]));
if (code < 0) {
printf("error: can not set address to %s\n", argv[2]);
return -1;
}
switch (argv[3][0]) {
case 'r': {
size = min(sizeof(buf), atox(argv[4]));
size = read(i2c, buf, size);
hex_print(buf, size, stdout);
break; }
case 'w': {
size = decode_data(buf, sizeof(buf), argv[4]);
size = write(i2c, buf, size);
printf("write: %d byte(s)\n", size);
break; }
default: {
printf("error: unsupport mode %c.\n", argv[3][0]);
return -1; }
}
close(i2c);
return 0;
}
compile it: mipsel-openwrt-linux-gcc i2ctest.c -o i2ctest