Daily Archives: 2020-02-15

VoCore2: Temperature and Humidity Sensor(I2C)

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