sht21: driver for the SHT21 humidity & temp sensor

Change-Id: Ia71168e394a7b260019e74973db6c9d75d3d4482
This commit is contained in:
Thomas Cort 2013-08-19 09:54:20 -04:00
parent 845aabfe65
commit 3bdd1ae659
8 changed files with 719 additions and 2 deletions

View file

@ -31,7 +31,8 @@ case $#:$1 in
eepromb2s54 eepromb2s55 eepromb2s56 eepromb2s57 \
eepromb3s50 eepromb3s51 eepromb3s52 eepromb3s53 \
eepromb3s54 eepromb3s55 eepromb3s56 eepromb3s57 \
tsl2550b1s39 tsl2550b2s39 tsl2550b3s39
tsl2550b1s39 tsl2550b2s39 tsl2550b3s39 \
sht21b1s40 sht21b2s40 sht21b3s40
;;
0:|1:-\?)
cat >&2 <<EOF
@ -41,6 +42,7 @@ Where key is one of the following:
fb0 # Make /dev/fb0
i2c-1 i2c-2 i2c-3 # Make /dev/i2c-[1-3]
tsl2550b{1,3}s39 # TSL2550 Ambient Light Sensors
sht21b{1,3}s40 # SHT21 Relative Humidity and Temperature Sensors
fd0 fd1 ... # Floppy devices for drive 0, 1, ...
fd0p0 fd1p0 ... # Make floppy partitions fd0p[0-3], fd1p[0-3], ...
c0d0 c0d1 ... # Make disks c0d0, c0d1, ...
@ -315,6 +317,13 @@ do
$e mknod tsl2550b${b}s39 c ${m} 0
$e chmod 444 tsl2550b${b}s39
;;
sht21b[1-3]s40)
b=`expr $dev : 'sht21b\\(.*\\)s40'` #bus number
m=`expr ${b} + 49`
$e mknod sht21b${b}s40 c ${m} 0
$e chmod 444 sht21b${b}s40
;;
*)
echo "$0: don't know about $dev" >&2
ex=1

View file

@ -111,6 +111,7 @@
./usr/sbin/i2c minix-sys
./usr/sbin/lan8710a minix-sys
./usr/sbin/random minix-sys
./usr/sbin/sht21 minix-sys
./usr/sbin/tda19988 minix-sys
./usr/sbin/tps65217 minix-sys
./usr/sbin/tps65950 minix-sys

View file

@ -24,7 +24,7 @@ SUBDIR= ahci amddev atl2 at_wini audio dec21140A dp8390 dpeth \
.if ${MACHINE_ARCH} == "earm"
SUBDIR= cat24c256 fb gpio i2c mmc lan8710a log readclock \
tda19988 tps65217 tps65950 tsl2550 tty random
sht21 tda19988 tps65217 tps65950 tsl2550 tty random
.endif
.endif # ${MKIMAGEONLY} != "yes"

14
drivers/sht21/Makefile Normal file
View file

@ -0,0 +1,14 @@
# Makefile for the sht21 humidity and temp sensor found on the Weather Cape.
PROG= sht21
SRCS= sht21.c
DPADD+= ${LIBI2CDRIVER} ${LIBCHARDRIVER} ${LIBSYS} ${LIBTIMERS}
LDADD+= -li2cdriver -lchardriver -lsys -ltimers
MAN=
BINDIR?= /usr/sbin
CPPFLAGS+= -I${NETBSDSRCDIR}
.include <minix.service.mk>

67
drivers/sht21/README.txt Normal file
View file

@ -0,0 +1,67 @@
SHT21 Driver (Relative Humidity and Temperature Sensor)
=======================================================
Overview
--------
This is the driver for the relative humidity and temperature sensor commonly
found on the WeatherCape expansion board for the BeagleBone.
Interface
---------
This driver implements the character device interface. It supports reading
through /dev/sht21b{1,3}s40. When read from, it returns a string containing
a data label, a colon, and the sensor value.
Example output of `cat /dev/sht21b3s40`:
TEMPERATURE : 35.014
HUMIDITY : 25.181
Temperature is expressed in Celsius (a.k.a. centigrade). Valid values are
-40.000 to 125.000.
Humidity is expressed as a percentage. Valid values are 0.000 to 100.000.
Limitations
-----------
Intense activity causes the chip to heat up, affecting the temperature reading.
In order to prevent the chip from self-heating more than 0.1C, the sensor
values will only be read once per second. Subsequent reads within the same
second will return cached temperature and humidity values.
The measurement resolution is configurable in the chip, but this driver just
uses the default maximum resolutions (12-bit for Humidity, 14-bit for
temperature). It could probably be implemented with an ioctl() or by passing
an argument via the service command, but it doesn't seem too useful at this
time. See the data sheet for the trade-off between faster conversion time and
lower resolution.
In testing, the temperature sensor reported a value several degrees higher
than an indoor thermometer placed nearby. It doesn't appear to be a bug in the
driver as the Linux driver reports similar temperature. Additionally, the
BMP085 temperature sensor on the same cape reports a temperature about 2
degrees lower than the SHT21. This could be due to heat produced by the
BeagleBone heating the cape slightly or maybe just a bad chip on the test
board.
Testing the Code
----------------
The driver should have been started by a script in /etc/rc.capes/ If not,
this is how you start up an instance:
cd /dev && MAKEDEV sht21b3s40
/bin/service up /usr/sbin/sht21 -label sht21.3.40 -dev /dev/sht21b3s40 \
-args 'bus=3 address=0x40'
Getting the sensor value:
cat /dev/sht21b3s40
Killing an instance:
/bin/service down sht21.3.40

618
drivers/sht21/sht21.c Normal file
View file

@ -0,0 +1,618 @@
/* Driver for the SHT21 Relative Humidity and Temperature Sensor */
#include <minix/ds.h>
#include <minix/drivers.h>
#include <minix/i2c.h>
#include <minix/i2cdriver.h>
#include <minix/chardriver.h>
#include <minix/log.h>
#include <time.h>
/*
* Device Commands
*/
/*
* The trigger commands start a measurement. 'Hold' ties up the bus while the
* measurement is being performed while 'no hold' requires the driver to poll
* the chip until the data is ready. Hold is faster and requires less message
* passing while no hold frees up the bus while the measurement is in progress.
* The worst case conversion times are 85 ms for temperature and 29 ms for
* humidity. Typical conversion times are about 75% of the worst case times.
*
* The driver uses the 'hold' versions of the trigger commands.
*/
#define CMD_TRIG_T_HOLD 0xe3
#define CMD_TRIG_RH_HOLD 0xe5
#define CMD_TRIG_T_NOHOLD 0xf3
#define CMD_TRIG_RH_NOHOLD 0xf5
/* Read and write the user register contents */
#define CMD_WR_USR_REG 0xe6
#define CMD_RD_USR_REG 0xe7
/* Resets the chip */
#define CMD_SOFT_RESET 0xfe
/* Status bits included in the measurement need to be masked in calculation */
#define STATUS_BITS_MASK 0x0003
/*
* The user register has some reserved bits that the device changes over
* time. The driver must preserve the value of those bits when writing to
* the user register.
*/
#define USR_REG_RESERVED_MASK ((1<<3)|(1<<4)|(1<<5))
/* End of Battery flag is set when the voltage drops below 2.25V. */
#define USR_REG_EOB_MASK (1<<6)
/* When powered up and communicating, the register should have only the
* 'Disable OTP Reload' bit set
*/
#define EXPECTED_PWR_UP_TEST_VAL (1<<1)
/* Define some constants for the different sensor types on the chip. */
enum sht21_sensors
{ SHT21_T, SHT21_RH };
/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
static struct log log = {
.name = "sht21",
.log_level = LEVEL_INFO,
.log_func = default_log
};
/* device slave address is fixed at 0x40 */
static i2c_addr_t valid_addrs[2] = {
0x40, 0x00
};
/* Buffer to store output string returned when reading from device file. */
#define BUFFER_LEN 64
char buffer[BUFFER_LEN + 1];
/* the bus that this device is on (counting starting at 1) */
static uint32_t bus;
/* slave address of the device */
static i2c_addr_t address;
/* endpoint for the driver for the bus itself. */
static endpoint_t bus_endpoint;
/* Sampling causes self-heating. To limit the self-heating to < 0.1C, the
* data sheet suggests limiting sampling to 2 samples per second. Since
* the driver samples temperature and relative humidity at the same time,
* it's measure function does at most 1 pair of samples per second. It uses
* this timestamp to see if a measurement was taken less than 1 second ago.
*/
static time_t last_sample_time = 0;
/*
* Cache temperature and relative humidity readings. These values are returned
* when the last_sample_time == current_time to keep the chip activity below
* 10% to help prevent self-heating.
*/
static int32_t cached_t = 0.0;
static int32_t cached_rh = 0.0;
/*
* An 8-bit CRC is used to validate the readings.
*/
#define CRC8_POLYNOMIAL 0x131
#define CRC8_INITIAL_CRC 0x00
/* main driver functions */
static int sht21_init(void);
static int soft_reset(void);
static int usr_reg_read(uint8_t * usr_reg_val);
static int sensor_read(enum sht21_sensors sensor, int32_t * measurement);
static int measure(void);
/* CRC functions */
static uint8_t crc8(uint8_t crc, uint8_t byte);
static int checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc);
/* libchardriver callbacks */
static struct device *sht21_prepare(dev_t UNUSED(dev));
static int sht21_transfer(endpoint_t endpt, int opcode, u64_t position,
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
unsigned int UNUSED(flags));
static int sht21_other(message * m);
/* SEF functions */
static int sef_cb_lu_state_save(int);
static int lu_state_restore(void);
static int sef_cb_init(int type, sef_init_info_t * info);
static void sef_local_startup(void);
/* Entry points to this driver from libchardriver. */
static struct chardriver sht21_tab = {
.cdr_open = do_nop,
.cdr_close = do_nop,
.cdr_ioctl = nop_ioctl,
.cdr_prepare = sht21_prepare,
.cdr_transfer = sht21_transfer,
.cdr_cleanup = nop_cleanup,
.cdr_alarm = nop_alarm,
.cdr_cancel = nop_cancel,
.cdr_select = nop_select,
.cdr_other = sht21_other
};
static struct device sht21_device = {
.dv_base = 0,
.dv_size = 0
};
/*
* Sends the chip a soft reset command and waits 15 ms for the chip to reset.
*/
static int
soft_reset(void)
{
int r;
minix_i2c_ioctl_exec_t ioctl_exec;
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
/* Write to chip */
ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
ioctl_exec.iie_addr = address;
/* No command bytes for writing to this chip */
ioctl_exec.iie_cmdlen = 0;
/* Set the byte to write */
ioctl_exec.iie_buf[0] = CMD_SOFT_RESET;
ioctl_exec.iie_buflen = 1;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
log_warn(&log, "soft_reset() failed (r=%d)\n", r);
return -1;
}
/* soft reset takes up to 15 ms to complete. */
micro_delay(15000);
log_debug(&log, "Soft Reset Complete\n");
return OK;
}
/*
* Obtain the contents of the usr register and store it in usr_reg_val.
*/
static int
usr_reg_read(uint8_t * usr_reg_val)
{
int r;
minix_i2c_ioctl_exec_t ioctl_exec;
if (usr_reg_val == NULL) {
log_warn(&log, "usr_reg_read() called with NULL pointer\n");
return -1;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
/* Read from chip */
ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
ioctl_exec.iie_addr = address;
/* Send the read from user register command */
ioctl_exec.iie_cmd[0] = CMD_RD_USR_REG;
ioctl_exec.iie_cmdlen = 1;
/* Read the register contents into iie_buf */
ioctl_exec.iie_buflen = 1;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
log_warn(&log, "usr_reg_read() failed (r=%d)\n", r);
return -1;
}
*usr_reg_val = ioctl_exec.iie_buf[0];
log_trace(&log, "Read 0x%x from USR_REG\n", *usr_reg_val);
return OK;
}
/*
* Performs a soft reset and reads the contents of the user register to ensure
* that the chip is in a good state and working properly.
*/
static int
sht21_init(void)
{
int r;
uint8_t usr_reg_val;
r = soft_reset();
if (r != OK) {
return -1;
}
r = usr_reg_read(&usr_reg_val);
if (r != OK) {
return -1;
}
/* Check for End of Battery flag. */
if ((usr_reg_val & USR_REG_EOB_MASK) == USR_REG_EOB_MASK) {
log_warn(&log, "End of Battery Alarm\n");
return -1;
}
/* Check that the non-reserved bits are in the default state. */
if ((usr_reg_val & ~USR_REG_RESERVED_MASK) != EXPECTED_PWR_UP_TEST_VAL) {
log_warn(&log, "USR_REG has non-default values after reset\n");
log_warn(&log, "Expected 0x%x | Actual 0x%x",
EXPECTED_PWR_UP_TEST_VAL,
(usr_reg_val & ~USR_REG_RESERVED_MASK));
return -1;
}
return OK;
}
/*
* Read from the sensor, check the CRC, convert the ADC value into the final
* representation, and store the result in measurement.
*/
static int
sensor_read(enum sht21_sensors sensor, int32_t * measurement)
{
int r;
uint8_t cmd;
uint8_t val_hi, val_lo;
uint16_t val;
uint8_t expected_crc;
minix_i2c_ioctl_exec_t ioctl_exec;
switch (sensor) {
case SHT21_T:
cmd = CMD_TRIG_T_HOLD;
break;
case SHT21_RH:
cmd = CMD_TRIG_RH_HOLD;
break;
default:
log_warn(&log, "sensor_read() called with bad sensor type.\n");
return -1;
}
if (measurement == NULL) {
log_warn(&log, "sensor_read() called with NULL pointer\n");
return -1;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
/* Read from chip */
ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
ioctl_exec.iie_addr = address;
/* Send the trigger command */
ioctl_exec.iie_cmd[0] = cmd;
ioctl_exec.iie_cmdlen = 1;
/* Read the results */
ioctl_exec.iie_buflen = 3;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
log_warn(&log, "sensor_read() failed (r=%d)\n", r);
return -1;
}
expected_crc = ioctl_exec.iie_buf[2];
r = checksum(ioctl_exec.iie_buf, 2, expected_crc);
if (r != OK) {
return -1;
}
val_hi = ioctl_exec.iie_buf[0];
val_lo = ioctl_exec.iie_buf[1];
val = ((val_hi << 8) | val_lo);
val &= ~STATUS_BITS_MASK; /* clear status bits */
log_debug(&log, "Read VAL:0x%x CRC:0x%x\n", val, expected_crc);
/* Convert the ADC value to the actual value. */
if (cmd == CMD_TRIG_T_HOLD) {
*measurement = (int32_t)
((-46.85 + ((175.72 / 65536) * ((float) val))) * 1000.0);
log_debug(&log, "Measured Temperature %d mC\n", *measurement);
} else if (cmd == CMD_TRIG_RH_HOLD) {
*measurement =
(int32_t) ((-6.0 +
((125.0 / 65536) * ((float) val))) * 1000.0);
log_debug(&log, "Measured Humidity %d m%%\n", *measurement);
}
return OK;
}
static int
measure(void)
{
int r;
time_t sample_time;
int32_t t, rh;
log_debug(&log, "Taking a measurement...");
sample_time = time(NULL);
if (sample_time == last_sample_time) {
log_debug(&log, "measure() called too soon, using cache.\n");
return OK;
}
r = sensor_read(SHT21_T, &t);
if (r != OK) {
return -1;
}
r = sensor_read(SHT21_RH, &rh);
if (r != OK) {
return -1;
}
/* save measured values */
cached_t = t;
cached_rh = rh;
last_sample_time = time(NULL);
log_debug(&log, "Measurement completed\n");
return OK;
}
/*
* Return an updated checksum for the given crc and byte.
*/
static uint8_t
crc8(uint8_t crc, uint8_t byte)
{
int i;
crc ^= byte;
for (i = 0; i < 8; i++) {
if ((crc & 0x80) == 0x80) {
crc = (crc << 1) ^ CRC8_POLYNOMIAL;
} else {
crc <<= 1;
}
}
return crc;
}
/*
* Compute the CRC of an array of bytes and compare it to expected_crc.
* If the computed CRC matches expected_crc, then return OK, otherwise EINVAL.
*/
static int
checksum(uint8_t * bytes, int nbytes, uint8_t expected_crc)
{
int i;
uint8_t crc;
crc = CRC8_INITIAL_CRC;
log_debug(&log, "Checking CRC\n");
for (i = 0; i < nbytes; i++) {
crc = crc8(crc, bytes[i]);
}
if (crc == expected_crc) {
log_debug(&log, "CRC OK\n");
return OK;
} else {
log_warn(&log,
"Bad CRC -- Computed CRC: 0x%x | Expected CRC: 0x%x\n",
crc, expected_crc);
return EINVAL;
}
}
static struct device *
sht21_prepare(dev_t UNUSED(dev))
{
return &sht21_device;
}
static int
sht21_transfer(endpoint_t endpt, int opcode, u64_t position,
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
unsigned int UNUSED(flags))
{
int bytes, r;
r = measure();
if (r != OK) {
return EIO;
}
memset(buffer, '\0', BUFFER_LEN + 1);
snprintf(buffer, BUFFER_LEN, "%-16s: %d.%03d\n%-16s: %d.%03d\n",
"TEMPERATURE", cached_t / 1000, cached_t % 1000, "HUMIDITY",
cached_rh / 1000, cached_rh % 1000);
log_trace(&log, "%s", buffer);
bytes = strlen(buffer) - position < iov->iov_size ?
strlen(buffer) - position : iov->iov_size;
if (bytes <= 0) {
return OK;
}
switch (opcode) {
case DEV_GATHER_S:
r = sys_safecopyto(endpt, (cp_grant_id_t) iov->iov_addr, 0,
(vir_bytes) (buffer + position), bytes);
iov->iov_size -= bytes;
break;
default:
return EINVAL;
}
return r;
}
static int
sht21_other(message * m)
{
int r;
switch (m->m_type) {
case NOTIFY_MESSAGE:
if (m->m_source == DS_PROC_NR) {
log_debug(&log,
"bus driver changed state, update endpoint\n");
i2cdriver_handle_bus_update(&bus_endpoint, bus,
address);
}
r = OK;
break;
default:
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
r = EINVAL;
break;
}
return r;
}
static int
sef_cb_lu_state_save(int UNUSED(state))
{
ds_publish_u32("bus", bus, DSF_OVERWRITE);
ds_publish_u32("address", address, DSF_OVERWRITE);
return OK;
}
static int
lu_state_restore(void)
{
/* Restore the state. */
u32_t value;
ds_retrieve_u32("bus", &value);
ds_delete_u32("bus");
bus = (int) value;
ds_retrieve_u32("address", &value);
ds_delete_u32("address");
address = (int) value;
return OK;
}
static int
sef_cb_init(int type, sef_init_info_t * UNUSED(info))
{
int r;
if (type == SEF_INIT_LU) {
/* Restore the state. */
lu_state_restore();
}
/* look-up the endpoint for the bus driver */
bus_endpoint = i2cdriver_bus_endpoint(bus);
if (bus_endpoint == 0) {
log_warn(&log, "Couldn't find bus driver.\n");
return EXIT_FAILURE;
}
/* claim the device */
r = i2cdriver_reserve_device(bus_endpoint, address);
if (r != OK) {
log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
address, r);
return EXIT_FAILURE;
}
r = sht21_init();
if (r != OK) {
log_warn(&log, "Device Init Failed\n");
return EXIT_FAILURE;
}
if (type != SEF_INIT_LU) {
/* sign up for updates about the i2c bus going down/up */
r = i2cdriver_subscribe_bus_updates(bus);
if (r != OK) {
log_warn(&log, "Couldn't subscribe to bus updates\n");
return EXIT_FAILURE;
}
i2cdriver_announce(bus);
log_debug(&log, "announced\n");
}
return OK;
}
static void
sef_local_startup(void)
{
/*
* Register init callbacks. Use the same function for all event types
*/
sef_setcb_init_fresh(sef_cb_init);
sef_setcb_init_lu(sef_cb_init);
sef_setcb_init_restart(sef_cb_init);
/*
* Register live update callbacks.
*/
/* Agree to update immediately when LU is requested in a valid state. */
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
/* Support live update starting from any standard state. */
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
/* Register a custom routine to save the state. */
sef_setcb_lu_state_save(sef_cb_lu_state_save);
/* Let SEF perform startup. */
sef_startup();
}
int
main(int argc, char *argv[])
{
int r;
env_setargs(argc, argv);
r = i2cdriver_env_parse(&bus, &address, valid_addrs);
if (r < 0) {
log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
log_warn(&log, "Example -args 'bus=1 address=0x40'\n");
return EXIT_FAILURE;
} else if (r > 0) {
log_warn(&log,
"Invalid slave address for device, expecting 0x40\n");
return EXIT_FAILURE;
}
sef_local_startup();
chardriver_task(&sht21_tab, CHARDRIVER_SYNC);
return 0;
}

View file

@ -641,6 +641,11 @@ service tsl2550
ipc SYSTEM RS DS i2c;
};
service sht21
{
ipc SYSTEM RS DS i2c;
};
service vbox
{
system

View file

@ -69,6 +69,9 @@ enum dev_style { STYLE_NDEV, STYLE_DEV, STYLE_DEVA, STYLE_TTY, STYLE_CTTY,
#define TSL2550B1S39_MAJOR 47 /* 47 = /dev/tsl2550b1s39 (tsl2550) */
#define TSL2550B2S39_MAJOR 48 /* 48 = /dev/tsl2550b2s39 (tsl2550) */
#define TSL2550B3S39_MAJOR 49 /* 49 = /dev/tsl2550b3s39 (tsl2550) */
#define SHT21B1S40_MAJOR 50 /* 50 = /dev/sht21b1s40 (sht21) */
#define SHT21B2S40_MAJOR 51 /* 51 = /dev/sht21b2s40 (sht21) */
#define SHT21B3S40_MAJOR 52 /* 52 = /dev/sht21b3s40 (sht21) */
/* Minor device numbers for memory driver. */