BeagleBoneBlack + Sensirion SHT71. Advanced humidity-temperature measurement system.
This article provides a way to realize an advanced and reliable system to monitor the humidity and temperature of a weathered environment but where the accuracy of temperature and humidity measure is a must.
Why do I choose these elements ?
The BeagleBoneBlack is the state of art, the best single board computer, from my point of view, for cpu power, number of pins you can use (great board for prototyping), several electronic buses (see CAN, I2C, SPI ), cheap and finally linux based.The CPU in revicsion C is the TI SITARA AM3359, ARM CORTEX A8: a digital beast.
The cons: it arrived at home with the Angstrom distribution of linux. Sincerily, I haven’t appreciated that distro, so I migrated to linux ubuntu distro, with arm hf; I downloaded it from http://www.armhf.com.
A distro with HF(hard float ) feature is adapt to run binary files developed with an external linux os, external ide tool (eclipse), and then, external toolchain. Otherwise if you compile a C souce code, for example, with a IDE like Eclipse, with a toolchain created for the BBB, in a external linux environment you’ll got a strange error when you’ll launch the binary on the BeagleBone.
Even if, personally, this time, I adopted the old way: I wrote all my code using Xcode(this time I didn’t use linux specific header), sent all files by means of FileZilla, compiled these files on board, launched the binary. No I’m not crazy, it’s a common process I’m used to; I’m depending on apple mac and xcode.
If you like an arm HF distro and want install that see this link:
http://www.armhf.com/boards/beaglebone-black/bbb-sd-install/
I wasted a lot of time following other wrong instructions.
Following the wiring and the architectures hardware and software.
The Sensirion sht71 is one of over the top all-in-one sensors, to achieve the measuring of temperature and humidity. It’s, actually, a microcontroller with status register, joined to a couple of sensors having a digital output and using i2c protocol.
The cons: absolutely expensive. If you buy just one piece, in Italy, you can buy with same money amount a chinese 7’’ tablet. Incredible but true.
written by Ivan Cerrato
LINUX
BeagleBoneBlack
SHT71
I2C bus
next
back to home
Architecture-hardware.
The system architecture, in the basic version, has 2 elements: the BBB and a sensirion SHT71. But do to nature of the BBB, with 46 pins each expansion port you can add several sensors to the board.
Deeper.Latest trend about the pins binding is called DT. The devices tree is a methodology, a pattern, a manner by means of you can override a former devices structure with a new layer.
In other words the board comes with a structures of devices, but you can overwrite it by means of a .dts file.
dts stands for device tree source. the dts file has to be compiled and put in a particular directory, called bone_capemgr.* ; the .* is because it depends from the real configuartion of the board; it can change.
Compilation of a dts file: dtc -O dtb -o xxxx-IO-00A0.dtbo -b O -@ xxxx-IO-00A0.dts
Moving the compiled file, *.dtbo, to the configuration in the slot:
echo xxxx-IO > /sys/devices/bone_capemgr.*/slots
Also the BeagleBoneBlack, but the preceding BeagleBone also, has the pins mux.
What does it mean? It means that every pin can have several way of working. A pin, depending from chosen pin, can acts as i2c clock, i2c data, spi, pwm, uart and so on.
Differing from BBB with the Angstrom distro, in the distro Linux ubuntu-armhf 3.14.4.1-bone-armhf.com, personally I used, the capemgr has gone. For that concerning the dts overwriting in this distro you can refer to Robert Nelson git site:
https://github.com/RobertCNelson/rscm
For all mode pin mux I downloaded the table P8 and P9 from the Derek Molloy site:
http://derekmolloy.ie
The project.
For the wiring implementation I chose 4 pins from the P9.
P9_01 GND
P9_03 VDDP9_15 GPIO1_16 for the i2c clock
P9_23 GPIO1_17 for the i2c data
//P9_15, GPIO1_16
#define PIN_I2C_CLOCK 1*32+16
//P9_23, GPIO1_17
#define PIN_I2C_DATA 1*32+17
The data pin of the sht71 has to be pulled-up trough a 10 K resistor to the VDD. The following picture shows the simple wirings.
Note: in several cases when I attached the bbb to my mac with a usb cable, the mac crashed and restarted …without a logical reason. For that if you can use the board with the ethernet cable and use the power supply.
To use a pin, with the BBB, remeber that the symbol P9_XX is just a human notation; in our case to
P9_15 corresponds GPIO1_16; the physycal value to address the pin is
x * 32 + 16, where x is the value from GPIO1_16 (x can be 0,1,2,3 according to the used register ), and 16 is the value GPIO1_16 that is the last value appearing on the pin register.The value obtained from the simple computation is used from C code, to physically address the pins.
back
Architecture-software.
To have an advanced control on the i2c bus, I preferred copy from internet and modify all necessary functions to open, close, read, write, starting measure and so manually. I’m not sure, but I think you cannot use standard hi-level i2c bus function to employ the sht7x, because you cannot implement, for example, sequence of reset or the measure-start.The main file is get_t_h.c where you can find the chunk of code that starts the process of measure. The measure is done once.
int get_bbb_th()
{
float _temp = 0.0;
float _humi = 0.0;
char buf[1024];
_zm1(h_1)
_zm1(t_1)
_zm1(h_2)
_zm1(t_2)
printf("\n--------------------------------------------\n");
get_bbb_temp_humi_1(&_temp, &_humi);
_zm1(buf)
sprintf(buf, "%5.1f", _humi);
printf("\n--> humidity: %s %% \n", buf);
memcpy(h_1, buf, 8);
_zm1(buf)
sprintf(buf, "%5.1f", _temp);
printf("\n--> temperature: %s %% \n", buf);
memcpy(t_1, buf, 8);
printf("\n--------------------------------------------\n");
return true;
}
The i2c functions are in the i2c_bbb.h and i2c_bbb.c files
At start of i2c_bbb.c you see the declaration for the clock and data pins(the constants are defined in the global.h)
//P9_15, GPIO1_16
int gpio_clock = PIN_I2C_CLOCK;
//P9_23, GPIO1_17
int gpio_data = PIN_I2C_DATA;
Ok, to use a pin you must:
1-export it
2-set its direction
3-set the value
4-unexport
Following, these functions:
void export_pin(int gpio)
{
_zm1(buf)
fd = open("/sys/class/gpio/export", O_WRONLY);
#ifdef _DEBUG
printf("export_pin, fd: %d, gpio: %d\n", fd, gpio);
#endif
if (fd<0) {
printf("export_pin %d %s\n", errno, strerror(errno) );
}
sprintf(buf, "%d", gpio);
write(fd, buf, strlen(buf));
close(fd);
}
void unexport_pin(int gpio)
{
_zm1(buf)
fd = open("/sys/class/gpio/unexport", O_WRONLY);
#ifdef _DEBUG
printf("unexport_pin, fd: %d, gpio: %d\n", fd, gpio);
#endif
if (fd<0) {
printf("unexport_pin %d %s\n", errno, strerror(errno) );
}
sprintf(buf, "%d", gpio);
write(fd, buf, strlen(buf));
close(fd);
}
void set_direction_out(int gpio)
{
sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY);
#ifdef _DEBUG
printf("set_direction_out, fd: %d, gpio: %d\n", fd, gpio);
#endif
if (fd<0) {
printf("set_direction_out %d %s\n", errno, strerror(errno) );
}
// Set out direction
write(fd, "out", 3);
close(fd);
}
void set_direction_in(int gpio)
{
_zm1(buf)
sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY);
#ifdef _DEBUG
printf("set_direction_in, fd: %d, gpio: %d\n", fd, gpio);
#endif
if (fd<0) {
printf("set_direction_in %d %s\n", errno, strerror(errno) );
}
// Set in direction
write(fd, "in", 3);
close(fd);
}
void set_value(int gpio, const char * value)
{
_zm1(buf)
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
fd = open(buf, O_WRONLY);
#ifdef _DEBUG
printf("set_value, fd: %d, gpio: %d, value: %s\n", fd, gpio, value);
#endif
if (fd<0) {
printf("set_value %d %s\n", errno, strerror(errno) );
}
// Set GPIO status
write(fd, value, 1);
close(fd);
}
and the functions to get and set the value on the bus
int get_value(int gpio)
{
_zm1(buf)
int ret_value = -1;
char value;
sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
fd = open(buf, O_RDONLY);
#ifdef _DEBUG
printf("get_value, fd: %d, gpio: %d\n", fd, gpio);
#endif
if (fd<0) {
printf("get_value %d %s\n", errno, strerror(errno) );
}
//lseek(fp, 0, SEEK_SET);
read(fd, &value, 1);
ret_value = value - '0';
close(fd);
return ret_value;
}
void i2c_data_set_to_value(int value)
{
set_direction_out(gpio_data);
#ifdef _DEBUG
printf("i2c_data_set_to_value - value: %d\n", value);
#endif
if(1==value)
{
set_to_1(gpio_data);
} else{
set_to_0(gpio_data);
}
}
The files sht71_bbb.h and sht71_bbb.c contain all functions relative to sht71 according to datasheet (you can download it from the download section).
Following the main function implements the measure, the reset sequence and the transmission start:
//----------------------------------------------------------------------------------
void get_bbb_temp_humi_1(float* temp, float* humi)
//----------------------------------------------------------------------------------
// sample program that shows how to use SHTxx functions
// 1. connection reset
// 2. measure humidity_1 [ticks](12 bit) and temperature_1 [ticks](14 bit)
// 3. calculate humidity_1 [%RH] and temperature_1 [∞C]
// 4. calculate dew point [∞C]
// 5. print temperature_1, humidity_1, dew point
{
#ifdef _DEBUG
printf("get_bbb_temp_humi_1 - starting\n");
#endif
float _temp = 0.0;
float _humi = 0.0;
value_u humi_val;
value_u temp_val;
float dew_point;
unsigned char error;
unsigned char checksum;
i2c_bbb_open();
s_bbb_connectionreset();
error=0;
error+=s_bbb_measure((unsigned int*) &humi_val.i, &checksum, HUMI); //measure humidity_1
error+=s_bbb_measure((unsigned int*) &temp_val.i, &checksum, TEMP); //measure temperature_1
//printf("\n>>> raw humi_val.i:%dC raw temp_val.i:%d%% <<<\n", humi_val.i, temp_val.i);
if(error!=0) //in case of an error: connection reset
{
s_bbb_connectionreset();
} else {
humi_val.f=(float)humi_val.i; //converts integer to float
temp_val.f=(float)temp_val.i; //converts integer to float
calc_bbb_sth71(&humi_val.f, &temp_val.f); //calculate humidity_1, temperature_1
dew_point=calc_bbb_dewpoint(humi_val.f,temp_val.f); //calculate dew point
//printf("\n>>> temp:%5.1fC humi:%5.1f%% dew point:%5.1fC <<<\n",temp_val.f,humi_val.f,dew_point);
//usleep(500000);
//system("clear");
}
_temp = temp_val.f;
_humi = humi_val.f;
printf("\n--> temp:%5.1fC humi:%5.1f%% dew point:%5.1fC \n", _temp, _humi, dew_point);
*temp = _temp;
*humi = _humi;
i2c_bbb_close();
#ifdef _DEBUG
printf("get_bbb_temp_humi_1 - ending\n");
#endif
}
//----------------------------------------------------------------------------------
void s_bbb_transstart(void)
//----------------------------------------------------------------------------------
// generates a transmission start
// _____ ________
// DATA: |_______|
// ___ ___
// SCK : ___| |___| |______
{
#ifdef _DEBUG
printf("s_bbb_transstart - starting\n");
#endif
i2c_data_set_to_1(); //Initial state
i2c_clock_set_to_0(); //Initial state
usleep(2) ;
i2c_clock_set_to_1();
usleep(2) ;
i2c_data_set_to_0();
usleep(2) ;
i2c_clock_set_to_0();
usleep(6);
i2c_clock_set_to_1();
usleep(2) ;
i2c_data_set_to_1();
usleep(2) ;
i2c_clock_set_to_0();
#ifdef _DEBUG
printf("s_bbb_transstart - ending\n");
#endif
}
//----------------------------------------------------------------------------------
void s_bbb_connectionreset(void)
//----------------------------------------------------------------------------------
// communication reset: DATA-line=1 and at least 9 SCK cycles followed by transstart
// _____________________________________________________ ________
// DATA: |_______|
// _ _ _ _ _ _ _ _ _ ___ ___
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______| |___| |______
{
#ifdef _DEBUG
printf("s_bbb_connectionreset - starting\n");
#endif
unsigned char i;
i2c_data_set_to_1(); //Initial state
i2c_clock_set_to_0();
for(i=0;i<9;i++) //9 SCK cycles
{
i2c_clock_set_to_1();
i2c_clock_set_to_0();
}
#ifdef _DEBUG
printf("s_bbb_connectionreset - ending\n");
#endif
}
In the global.h you can see the line
#define _DEBUG
if you comment that, you’ll not see the program life cycle but only the final printf with the result of measures relative to temperature and humidity.
global.h contains also some useful macro that I’m used to employ in my code.
The function calc_bbb_sth71 makes the computation of the humidity and temperature according to the bit lenght meaure adopted, the VDD, in our case ~3.3 V, and non linearity fo the measures. See SHT7x datasheet.
//----------------------------------------------------------------------------------------
void calc_bbb_sth71(float *p_humidity ,float *p_temperature)
//----------------------------------------------------------------------------------------
// calculates temperature_1 [∞C] and humidity_1 [%RH]
// input : humi [Ticks] (12 bit)
// temp [Ticks] (14 bit)
// output: humi [%RH]
// temp [∞C]
{
const float C1=-4.0; // for 12 Bit
const float C2=+0.0405; // for 12 Bit
const float C3=-0.0000028; // for 12 Bit
const float T1=+0.01; // for 14 Bit @ 5V
const float T2=+0.00008; // for 14 Bit @ 5V
float rh=*p_humidity; // rh: Humidity [Ticks] 12 Bit
float t=*p_temperature; // t: Temperature [Ticks] 14 Bit
float rh_lin; // rh_lin: Humidity linear
float rh_true; // rh_true: Temperature compensated humidity_1
float t_C; // t_C : Temperature [∞C]
//
// variable corrected according to VDD of the sht71
//
// t_C = t*0.01 - 40; //calc. temperature_1 from ticsks to [∞C]
//
// from datasheet
//
// VDD d1(°C) d1 (°F)
// 3.5V -39.7 -39.5
//
t_C = t*0.01 - 39.7;
rh_lin=C3*rh*rh + C2*rh + C1; //calc. humidity_1 from ticks to [%RH]
rh_true=(t_C-25)*(T1+T2*rh)+rh_lin; //calc. temperature_1 compensated humidity_1 [%RH]
if(rh_true>100)rh_true=100; //cut if the value is outside of
if(rh_true<0.1)rh_true=0.1; //the physical possible range
*p_temperature=t_C; //return temperature_1 [∞C]
*p_humidity=rh_true; //return humidity_1[%RH]
}
Following the make file, that you can use to compile the project on the BBB;
CC= g++
CFLAGS = -c -D_XOPEN_SOURCE -Wall -fpermissive
LIBS= -lpthread -lm -lrt
SRC= get_t_h.c i2c_bbb.c sht71_bbb.cOBJ= get_t_h.o i2c_bbb.o sht71_bbb.o
all: get_t_hget_t_h: $(OBJ)
$(CC) $(OBJ) -o $@ $(LIBS)get_t_h.c.o: get_t_h.c.c
$(CC) $(CFLAGS) get_t_h.c.csht71_bbb.o: sht71_bbb.c
$(CC) $(CFLAGS) sht71_bbb.ci2c_bbb.o: i2c_bbb.c
$(CC) $(CFLAGS) i2c_bbb.cclean:
rm -f ./*.o get_t_h
On the BBB:
Warning:
after compiled you must launch the binary as root otherwise you won’t be able to use the pins (make system call) and you won’t be able to see the correct measures.
The measure:
In the download section you’ll see bbb_get_t_h_bit_sequence.pdf where you’ll find the detailed analysis of the sequence of bits when the _DEBUG flag is activated in the global.h.
You’ll note the start sequence, the measure start sequence, the write, the read, the reading of the acknoledgebit and other. On the left side of the pdf you’ll see the bit value and on the right side you’ll see the description of the sequence.
I hope you’ll find it useful, from a learning point of view. Following a picture of the pdf file.
Also, you’ll find the P8 and P9 table from http://derekmolloy.ie.
_P8_BeagleboneBlack_HeaderTable.pdf
get_t_h.zip
Sensirion_SHT7x_Datasheet_V5.pdf
[if gte mso 9]>
0
0
1
40
232
extech
1
1
271
14.0
Normal
0
false
false
false
EN-US
JA
X-NONE
_P9_BeagleboneBlack_HeaderTable.pdf
bbb_get_t_h_bit_sequence.pdf