Interrupt driven SHT1x humidity sensor

The Sensirion humidity and temperature sensor has a two wire serial interface to read out the temperature and humidity. It also has a status register which can be set up, e.g. to choose the number of bits of the measured values . This post is about reading the sensors using the  power on defaults.

The read-out sequence is similar to IIC communication: send a start pattern, a command then wait, read the two byte result and a check sum. I’m not going to describe  this here, check the data sheet . My application uses a STM32f103 ARM processor, but the code should be easy to adaptable to any micro controller. I use two  general purpose I/O pins that were available, not an ICC port. The SHT1x interface is similar to an IIC bus and this code should be easy to adapt for a bit-bang driven IIC bus.

Usually, when bit-banging an interface. much of the controller’s resources are used waiting for the next thing to happen. When using interrupts the controller only uses resources at the instants where things happen. In this case a timer is used to yield an interrupt, say every 100 ?sec.

The Sensirion sensor is driven by a clock signal and an open collector data signal. The program configures the clock pin to be a push pull output and the data pin as an open collector pin. This pin is pulled high by an external 10 k? resistor. During the readout  either the controller or the sensor pulls the data line low.

The sensor read out is controlled by a simple state machine, the state and number of interrupts handled while in this state is kept together with other global variables. First define three macros to set the clock ans data pins high or low and read the data pin:

#define DATA_PIN 7
#define CLK_PIN 12

#define humClk *((volatile uint32_t*)(PERIPH_BB_BASE+\
				      ((uint32_t)&HCLK_PORT->ODR - \
				       PERIPH_BASE)*32 + CLK_PIN*4))

#define humData *((volatile uint32_t*)(PERIPH_BB_BASE+\
				       ((uint32_t)&HDIO_PORT->ODR - \
					PERIPH_BASE)*32 + DATA_PIN*4))

#define humPin *((volatile uint32_t*)(PERIPH_BB_BASE+\
				      ((uint32_t)&HDIO_PORT->IDR - \
				       PERIPH_BASE)*32 + DATA_PIN*4))

These macros use the ARM processor bit-band pointers, when read from this address, bit 0 of the word read reflects the pin state, when writing to a bit-band address, the pin will be set to the value of the bit 0 of the argument.

A few structure definitions:

typedef enum humcommands {
	hum= 0xa0,
	temp= 0xc0
} humcommands;

typedef enum humStates{
	idle,
	start,
	cmd,
	ackCmd,
	ackData,
	ackSleep,
	wait,
	getbyte,
	reset
} humStates;

humstates is a list of the possible states of the interrupt handler, humcommands the implemented commands for the sensor – to read temperature and humidity. The global variables are:

static humStates seq[]={ idle, start, cmd, ackCmd, wait, getbyte, ackData, getbyte, 
ackData, getbyte, ackSleep, idle }; 
static volatile int seqCounter;      // present state index 
static volatile humStates state;     // present state
static volatile int irqcounter;      // counts interrupts in present state
static volatile uint8_t cmdData;     // the command byte 
static volatile int32_t value;       // the value read from the sensor

The seq array gives the sequence of states for one readout. The state machine starts and stops in idle, where nothing happens. Starts with a start bit pattern to wake up the sensor, sends a command, acknowledges the sensor response, waits about 200 msec for the sensor to measure the state and reads back three bytes before acknowledging the sensor back to sleep.

So, here comes the state machine, this function is called by the timer interrupt handler (which also clears the interrupt flag)

void humIrqHandler(void)
{
	uint16_t v;
	switch (state) {
	case start:
		switch(irqcounter) {
		case 0:
			GpioLedOn(LED2);
			humClk= 1; break;
		case 1:
			humData= 0; break;
		case 2: humClk= 0; break;
		case 3: humClk= 1; break;
		case 4: humData= 1; break;
		case 5: 
		default:
			humData= 1;
			humClk= 0; 
			irqcounter= 0;
			state= seq[++seqCounter];
			return;
		}
		break;

	case cmd:
		switch(irqcounter % 3) {
		case 0: humData= cmdData; cmdData >>= 1;break; 
		case 1: humClk= 1; break;
		case 2: humClk= 0; break;
		}
		if (irqcounter==23) humData= 1;
		if (irqcounter > 23) {
			irqcounter= 0;
			state= seq[++seqCounter];
			humData= 1;
			return;
		}
		break;

	case ackCmd:
		GpioLedOff(LED2);
		v= humPin;
		switch(irqcounter) {
		case 0: humClk= 1; break;
		case 1: humClk= 0;
			if (v==0) {
				humData= 1;
				irqcounter= 0;
				state= seq[++seqCounter];
			}
			else {
				value= -1;
				humData= 1;
				state= idle;
				seqCounter= 0;
			}
			return;
		}
		break;

	case wait:
		v= humPin;
		if (irqcounter  > 5) {
			if (v == 0) {
				irqcounter= 0;
				value= 0;
				state= seq[++seqCounter];
			}
			else {
				if (irqcounter > 200) {
					value= -2;
					humClk= 0;
					humData= 1;
					state= idle;
					seqCounter= 0;
				}
			}
			return;
		}
		break;

	case getbyte:
		humData= 1;
		v=  humPin;
		switch(irqcounter & 1) {
		case 0: 
			humClk= 1;
			value= (value<<1) | v; 	break; 	
	        case 1: humClk= 0; break;
 		} 		
                if (irqcounter > 14) {
			irqcounter= 0;
			state= seq[++seqCounter];
			humData= 1;
			return;
		}
		break;

	case ackData:
		switch(irqcounter %3) {
		case 0: humData= 0; break;
		case 1: humClk= 1; break;
		case 2: 
			humClk= 0; 
			humData= 1;
			state= seq[++seqCounter];
			irqcounter= 0;
			return;
		}
		break;

	case ackSleep:
		switch(irqcounter) {
		case 0: humData= 1; break;
		case 1: humClk= 1; break;
		default:
		case 2: humClk= 0;
			state= seq[++seqCounter];
			irqcounter= 0;
			return;
		}
		break;

	case idle:
		humClk= 0;
		humData= 1;
		seqCounter= 0;
		break;

	case reset:
		humData= 1;
		if (irqcounter & 1) {
			humClk= 0;
		}
		else {
			humClk= 1;
		}
		if (irqcounter > 20) {
			irqcounter= 0;
			state= idle;
			seqCounter= 0;
			humClk= 0;
		}
		break;
	}
	irqcounter++;

}

Compare this with the timing diagrams given in the sensor data sheet. There are two error exits from this machine. If the sensor does not ack the command a -1 is returned, indicating a faulty sensor or connection. A time out during the wait state will return a -2. The time out is 200 msec. This example uses a 1 msec timer interrupt period, adjust the time out value if another interrupt interval is used.

The interrupt interval can be set much faster than 1 msec. If using a short interrupt interval to do the clocking at a high rate, one may want to set a longer interval, say 25 msec during the wait state, to save resources. Such reconfiguration of the timer period can be done at the exit of the ackCmd and wait states.

To read a value, either humidity or temperature, call this function

void humRead(humcommands command)
{
	cmdData= command;
	humClk= 0;
	humData= 1;
	irqcounter= 0;
	seqCounter= 0;
	state= seq[++seqCounter];
	TimerHumidityStart();
	while(state != idle);
        TimerHumidityStop();
}

This is a bad test function as it is waiting for the reading to finish, then there is no point in using interrupts to read the sensor. So an intelligent way to read a value would be something like:

void humReadStart(humcommands command)
{
	cmdData= command;
	humClk= 0;
	humData= 1;
	irqcounter= 0;
	seqCounter= 0;
	state= seq[++seqCounter];
	TimerHumidityStart();
}

int32_t humReadResult(void)
{
        if (state == idle) return -3;
	TimerHumidityStop();
        return value;
}

First start the transfer, and then somewhere in your application check if the result is ready, if not try again later.

0 Responses to “Interrupt driven SHT1x humidity sensor”


  • No Comments

Leave a Reply