view hid-usb-to-ir.c @ 2:95b8b8a0b2d0 v0.3

add proximity measurement
author Peter Meerwald <p.meerwald@bct-electronic.com>
date Thu, 03 Jan 2013 13:09:20 +0100
parents c6e3b362f9cf
children
line wrap: on
line source

/*
 * Demo program for the Silabs IR Gesture USB Reference Design,
 * http://www.silabs.com/products/sensors/Pages/HID-USB-to-IR-Reference-Design.aspx
 *
 * Reads the part number of the cp2112 bridge,
 * http://www.silabs.com/products/interface/usbtouart/Pages/HID-USB-to-SMBus-Bridge.aspx,
 * and the part/revision/sequencer version of the si1143 via SMBus/I2C,
 * finally lets the four blue LEDs dance :).
 *
 * Copyright (c) 2012 Peter Meerwald, <pmeerw@pmeerw.net>
 * Released under GPLv3 license, see http://www.gnu.org/licenses/gpl-3.0.html.
 *
 * Depends on the HID API multi-platform library, available from
 * http://www.signal11.us/oss/hidapi/.
 *
 * Build with
 *   gcc -Wall -g -o hid-usb-to-ir hid-usb-to-ir.c `pkg-config hidapi-hidraw --libs`
 *
 * May need root permission to access USB device (or appropriate udev rule).
 *
 * Based upon information from Silabs AN495 v0.2.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "hidapi/hidapi.h"

#define SI114X_SLAVE_ADDR 0x5a

#define SI114x_REG_PART_ID 0x00
#define SI114x_REG_REV_ID 0x01
#define SI114x_REG_SEQ_ID 0x02
#define SI114X_REG_HWKEY 0x07
#define SI114X_REG_MEAS_RATE 0x08
#define SI114X_REG_ALS_RATE 0x09
#define SI114X_REG_PS_RATE 0x0a
#define SI114X_REG_PS_LED21 0x0f
#define SI114X_REG_PS_LED3 0x10
#define SI114X_REG_PARAM_WR 0x17
#define SI114X_REG_COMMAND 0x18
#define SI114X_REG_ALSVIS_DATA0 0x22
#define SI114X_REG_ALSVIS_DATA1 0x23
#define SI114X_REG_ALSIR_DATA0 0x24
#define SI114X_REG_ALSIR_DATA1 0x25
#define SI114X_REG_PS1_DATA0 0x26
#define SI114X_REG_PS1_DATA1 0x27
#define SI114X_REG_PS2_DATA0 0x28
#define SI114X_REG_PS2_DATA1 0x29
#define SI114X_REG_PS3_DATA0 0x2a
#define SI114X_REG_PS3_DATA1 0x2b
#define SI114X_REG_AUX_DATA0 0x2c
#define SI114X_REG_AUX_DATA1 0x2d

/* Commands for COMMAND */
#define SI114X_CMD_NOP                  0x00
#define SI114X_CMD_RESET                0x01
#define SI114X_CMD_BUSADDR              0x02
#define SI114X_CMD_PS_FORCE             0x05
#define SI114X_CMD_ALS_FORCE            0x06
#define SI114X_CMD_PSALS_FORCE          0x07
#define SI114X_CMD_PS_PAUSE             0x09
#define SI114X_CMD_ALS_PAUSE            0x0a
#define SI114X_CMD_PSALS_PAUSE          0x0b
#define SI114X_CMD_PS_AUTO              0x0d
#define SI114X_CMD_ALS_AUTO             0x0e
#define SI114X_CMD_PSALS_AUTO           0x0f
#define SI114X_CMD_PARAM_QUERY          0x80
#define SI114X_CMD_PARAM_SET            0xa0
#define SI114X_CMD_PARAM_AND            0xc0
#define SI114X_CMD_PARAM_OR             0xe0

/* Parameter offsets */
#define SI114X_PARAM_CHLIST             0x01
#define SI114X_PARAM_PSLED12_SELECT     0x02
#define SI114X_PARAM_PSLED3_SELECT      0x03
#define SI114X_PARAM_PS_ADC_COUNTER     0x0a
#define SI114X_PARAM_PS_ADC_GAIN        0x0b
#define SI114X_PARAM_PS_ADC_MISC        0x0c

/* Channel enable masks for CHLIST parameter */
#define SI114X_CHLIST_EN_PS1            0x01
#define SI114X_CHLIST_EN_PS2            0x02
#define SI114X_CHLIST_EN_PS3            0x04
#define SI114X_CHLIST_EN_ALSVIS         0x10
#define SI114X_CHLIST_EN_ALSIR          0x20
#define SI114X_CHLIST_EN_AUX            0x40

/* Measurement rate settings */
#define SI114X_MEAS_RATE_FORCED         0x00
#define SI114X_MEAS_RATE_10MS           0x84
#define SI114X_MEAS_RATE_20MS           0x94
#define SI114X_MEAS_RATE_100MS          0xb9
#define SI114X_MEAS_RATE_496MS          0xdf
#define SI114X_MEAS_RATE_1984MS         0xff

/* ALS rate settings relative to measurement rate */
#define SI114X_ALS_RATE_OFF             0x00
#define SI114X_ALS_RATE_1X              0x08
#define SI114X_ALS_RATE_10X             0x32
#define SI114X_ALS_RATE_100X            0x69

/* PS rate settings relative to measurement rate */
#define SI114X_PS_RATE_OFF              0x00
#define SI114X_PS_RATE_1X               0x08
#define SI114X_PS_RATE_10X              0x32
#define SI114X_PS_RATE_100X             0x69

#define CP2112_GETSET_GPIO_CONFIG 0x02
#define CP2112_GET_GPIO 0x03
#define CP2112_SET_GPIO 0x04
#define CP2122_GET_VERSION_INFO 0x05
#define CP2112_GETSET_SMBUS_CONFIG 0x06
#define CP2112_DATA_WRITE_READ_REQ 0x11
#define CP2112_DATA_READ_RESP 0x13
#define CP2112_DATA_WRITE 0x14
#define CP2112_TRANSFER_STATUS_REQ 0x15
#define CP2112_TRANSFER_STATUS_RESP 0x16

#define CP2112_LED_DOWN 0x01 /* LED DS2, GPIO 0 */
#define CP2112_LED_UP 0x02 /* LED DS3, GPIO 1 */
#define CP2112_LED_RIGHT 0x08 /* LED DS8, GPIO 3 */
#define CP2112_LED_LEFT 0x80 /* LED DS4, GPIO 7 */
#define CP2112_LEDS_MASK (CP2112_LED_DOWN | CP2112_LED_UP | \
    CP2112_LED_RIGHT | CP2112_LED_LEFT)

/* get cp2112 part number and version */
static int cp2112_get_version(hid_device *hd, unsigned char data[2]) {
	unsigned char buf[3] = { CP2122_GET_VERSION_INFO, };

	int ret = hid_get_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_get_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	data[0] = buf[1]; /* part number */
	data[1] = buf[2]; /* version */

	return 0;
}

/* configures cp2112 to automatically send read data, see AN495 section 4.6 */
static int cp2112_set_auto_send_read(hid_device *hd, int on_off) {
	unsigned char buf[14] = { CP2112_GETSET_SMBUS_CONFIG, };
	int ret = hid_get_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_get_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	buf[6] = on_off;

	ret = hid_send_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_send_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	return 0;
}

/* see if the cp2112 is idle */
static int cp2112_is_idle(hid_device *hd) {
	unsigned char status_req[2] = { CP2112_TRANSFER_STATUS_REQ, 0x01, };
	if (hid_write(hd, status_req, sizeof(status_req)) < 0) {
		fprintf(stderr, "hid_write() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	unsigned char status_resp[7] = { 0x00, };
	if (hid_read(hd, status_resp, sizeof(status_resp)) < 0) {
		fprintf(stderr, "hid_read() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	if (status_resp[0] == CP2112_TRANSFER_STATUS_RESP) {
		if (status_resp[1] == 0x00) /* is idle? */
			return 1;
	}

	return 0;
}

/* write a data byte to register reg of the si1143 */
static int cp2112_write_byte(hid_device *hd, unsigned char reg, unsigned char data) {
	/* 0x5a is the 7-bit SMBus slave address of the si1143 */
	unsigned char buf_out[5] = { CP2112_DATA_WRITE, SI114X_SLAVE_ADDR<<1, 0x02, };
	buf_out[3] = reg;
	buf_out[4] = data;
	int ret = hid_write(hd, buf_out, sizeof(buf_out));
	if (ret < 0) {
		fprintf(stderr, "hid_write() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	return 0;
}

static int cp2112_write_param_byte(hid_device *hd, unsigned char param, unsigned char data) {
	if (cp2112_write_byte(hd, SI114X_REG_PARAM_WR, data) < 0)
		return -1;
	if (cp2112_write_byte(hd, SI114X_REG_COMMAND, param | SI114X_CMD_PARAM_SET) < 0)
		return -1;
	return 0;
}

/* read a data byte from register reg of the si1143 */
static int cp2112_read_byte(hid_device *hd, unsigned char reg, unsigned char data[1]) {
	/* 0x5a is the 7-bit SMBus slave address of the si1143 */
	unsigned char buf_out[6] = { CP2112_DATA_WRITE_READ_REQ,
	    SI114X_SLAVE_ADDR<<1, 0x00, 0x01, 0x01, };
	buf_out[5] = reg;
	int ret = hid_write(hd, buf_out, sizeof(buf_out));
	if (ret < 0) {
		fprintf(stderr, "hid_write() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	/* FIXME: may loop forever, error handling needed */
	while (1) {

		unsigned char buf_in[8] = { 0x00, };
		ret = hid_read(hd, buf_in, sizeof(buf_in));
		if (ret < 0) {
			fprintf(stderr, "hid_read_() failed: %ls\n",
				hid_error(hd));
			return -1;
		}


		if (buf_in[0] == CP2112_TRANSFER_STATUS_REQ)
			continue;

		if (buf_in[0] == CP2112_DATA_READ_RESP) {
			if (buf_in[1] == 0x02 &&  buf_in[2] == 0x00)
				continue; /* no data yet */
			if (buf_in[1] == 0x02 && buf_in[2] == 0x01) {
				data[0] = buf_in[3]; /* data available */
				return 0;
			}
		}
	}

	return -1;
}

static int cp2112_read_word(hid_device *hd, unsigned char reg, unsigned short data[1]) {
	unsigned char lsb, msb;

	if (cp2112_read_byte(hd, reg, &lsb) < 0)
		return -1;
	if (cp2112_read_byte(hd, reg+1, &msb) < 0)
		return -1;

	data[0] = (msb << 8) | lsb;
	return 0;
}

/* configure cp2112 GPIO pins */
static int cp2112_config_gpio(hid_device *hd) {
	unsigned char buf[5] = { CP2112_GETSET_GPIO_CONFIG, };
	int ret = hid_get_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_get_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	buf[1] = 0x8b; /* output direction for GPIOs 0, 1, 3, 7 */
	buf[2] = 0x8b; /* push-pull for GPIOs 0, 1, 3, 7 */
	buf[3] = 0x00; /* no special functions, i.e. use pins as GPIO */

	ret = hid_send_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_send_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	return 0;
}

/* set cp2112 GPIO pins */
static int cp2112_set_gpio(hid_device *hd, unsigned char mask, unsigned char value) {
	unsigned char buf[3] = { CP2112_SET_GPIO, };

	buf[1] = ~value; /* set GPIO pins: 0 .. turn LED on, 1 .. off */
	buf[2] = mask; /* mask of pins to set, ignore others */

	int ret = hid_send_feature_report(hd, buf, sizeof(buf));
	if (ret < 0) {
		fprintf(stderr, "hid_send_feature_report() failed: %ls\n",
			hid_error(hd));
		return -1;
	}

	return 0;
}

int main() {
	if (hid_init() < 0) {
		fprintf(stderr, "hid_init() failed, exit.\n");
		exit(EXIT_FAILURE);
	}

	/* open Silabs IR Gesture USB reference design by USB product:vendor id */
	hid_device *hd = hid_open(0x10c4, 0xea90, NULL);
	if (hd == NULL) {
		fprintf(stderr, "hid_open() failed\n");
		exit(EXIT_FAILURE);
	}

	unsigned char version_data[2];
	if (cp2112_get_version(hd, version_data) < 0)
		exit(EXIT_FAILURE);
	printf("cp2112 part number 0x%02x, device version %d\n", 
		version_data[0], version_data[1]);

	if (cp2112_set_auto_send_read(hd, 1) < 0)
		exit(EXIT_FAILURE);

	/* write 0x17 to si114x HW_KEY register as per datasheet */
	if (cp2112_write_byte(hd, SI114X_REG_HWKEY, 0x17) < 0)
		exit(EXIT_FAILURE);

	unsigned char part, rev, seq;
	if (cp2112_read_byte(hd, SI114x_REG_PART_ID, &part) < 0)
		exit(EXIT_FAILURE);

	if (cp2112_read_byte(hd, SI114x_REG_REV_ID, &rev) < 0)
		exit(EXIT_FAILURE);

	if (cp2112_read_byte(hd, SI114x_REG_SEQ_ID, &seq) < 0)
		exit(EXIT_FAILURE);
	printf("si114x part number 0x%02x, revision 0x%02x, sequencer 0x%02x\n",
		part, rev, seq);

	if (cp2112_write_byte(hd, SI114X_REG_COMMAND, SI114X_CMD_RESET) < 0)
		exit(EXIT_FAILURE);
	usleep(20000);

	if (cp2112_write_byte(hd, SI114X_REG_HWKEY, 0x17) < 0)
		exit(EXIT_FAILURE);
	usleep(20000);

	cp2112_is_idle(hd);

	/* set LED currents to maximum */
	if (cp2112_write_byte(hd, SI114X_REG_PS_LED3, 0x04))
		exit(EXIT_FAILURE);
	if (cp2112_write_byte(hd, SI114X_REG_PS_LED21, 0x32))
		exit(EXIT_FAILURE);

    if (cp2112_write_param_byte(hd, SI114X_PARAM_PS_ADC_MISC, 0x00 | 0x04))
		exit(EXIT_FAILURE);

    if (cp2112_write_param_byte(hd, SI114X_PARAM_PSLED12_SELECT, 0x21))
		exit(EXIT_FAILURE);

	if (cp2112_write_param_byte(hd, SI114X_PARAM_PSLED3_SELECT, 0x04))
		exit(EXIT_FAILURE);

   	if (cp2112_write_param_byte(hd, SI114X_PARAM_CHLIST, 
		SI114X_CHLIST_EN_ALSVIS |
		SI114X_CHLIST_EN_PS3 |
		SI114X_CHLIST_EN_PS2 |
		SI114X_CHLIST_EN_PS1))
		exit(EXIT_FAILURE);		

   	if (cp2112_write_param_byte(hd, SI114X_PARAM_PS_ADC_GAIN, 0x01))
		exit(EXIT_FAILURE);		

   	if (cp2112_write_param_byte(hd, SI114X_PARAM_PS_ADC_COUNTER, 0x06 << 4))
		exit(EXIT_FAILURE);		

	/* in autonomous mode, wakeup every 100 ms */
    if (cp2112_write_byte(hd, SI114X_REG_MEAS_RATE, SI114X_MEAS_RATE_100MS))
		exit(EXIT_FAILURE);		

	/* measure ALS every time device wakes up */
	if (cp2112_write_byte(hd, SI114X_REG_ALS_RATE, SI114X_ALS_RATE_1X))
		exit(EXIT_FAILURE);		

	/* measure proximity every time device wakes up */
	if (cp2112_write_byte(hd, SI114X_REG_PS_RATE, SI114X_PS_RATE_1X))
		exit(EXIT_FAILURE);		

	if (cp2112_write_byte(hd, SI114X_REG_COMMAND, SI114X_CMD_PSALS_AUTO))
		exit(EXIT_FAILURE);		

	cp2112_is_idle(hd);

	if (cp2112_config_gpio(hd) < 0)
		exit(EXIT_FAILURE);

	while (1) {
		unsigned short als, ps1, ps2, ps3;
		if (cp2112_set_gpio(hd, CP2112_LEDS_MASK, CP2112_LED_DOWN) < 0)
			exit(EXIT_FAILURE);
		usleep(50*1000);
		if (cp2112_set_gpio(hd, CP2112_LEDS_MASK, CP2112_LED_RIGHT) < 0)
			exit(EXIT_FAILURE);
		usleep(50*1000);
		if (cp2112_set_gpio(hd, CP2112_LEDS_MASK, CP2112_LED_UP) < 0)
			exit(EXIT_FAILURE);
		usleep(50*1000);
		if (cp2112_set_gpio(hd, CP2112_LEDS_MASK, CP2112_LED_LEFT) < 0)
			exit(EXIT_FAILURE);
		usleep(50*1000);

		if (cp2112_read_word(hd, SI114X_REG_ALSVIS_DATA0, &als) < 0)
			exit(EXIT_FAILURE);
		if (cp2112_read_word(hd, SI114X_REG_PS1_DATA0, &ps1) < 0)
			exit(EXIT_FAILURE);
		if (cp2112_read_word(hd, SI114X_REG_PS2_DATA0, &ps2) < 0)
			exit(EXIT_FAILURE);
		if (cp2112_read_word(hd, SI114X_REG_PS3_DATA0, &ps3) < 0)
			exit(EXIT_FAILURE);

		printf("%d %d %d %d\n", ps1, ps2, ps3, als);
	}

	/* never get here */
	printf("done\n");

	hid_close(hd);
	hid_exit();

	return EXIT_SUCCESS;
}

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.