Perl script to check if HTU21 works.

A year and a half ago I got strange obsessive idea to make a “computational cluster” out of old tablets.
I actually wanted to replace my ASUS EB1505 nettop, which works as my home server, with a bunch of tablets running Linux.
I imagined how I designate separate roles to each tablet, make different firewall rules for each of them.
How I have a swarm of small independent devices and low power consumption at the same time…
How each device has it’s own UPS, LCD display…

All these dreams have failed 🙂 Failed in many ways if not in all of possible ways…
But it was fun nevertheless. I had never built a linux kernel from source before. Had never built u-boot let along tamper with it.

The active phase of learning, designing and constructing lasted for about four months.
After that for a few months I tested these systems and tried to make them reliable.
I failed.
Eventually I accepted that I can’t make a usable server on a tablet hardware.
At least if these tablets are Ainol Novo7 Aurora and Ainol Aurora 2 i.e. tablets, based on Allwinner A10 and AMLogic AML8726-MX SoC.

My observations lead me to the conclusion that these devices may have an incredible uptime of 2-3 years if you do not connect external hard drive to them and if they basically do nothing.

Eventually I found a task that is suitable for them and especially for their very good 7 inch IPS LCD screens.
I began to use them as a clock.

Perhaps, if I knew that it is impossible (for me) to do a server of these tablets I would make this clock as an Android App.
It would have graphical interface etc.
But the chain of events lead me to the clock, made as a linux console application.
old tablet (allwinner-amlogic) linux console clock htu21
But wait… This is a separate topic.
I just want to say, that I have an ARM tablet with 7-inch IPS screen, running Linux without GUI and I decided to use it as a clock.
I get time from NTP servers, I get outside temperature from openweathermap and I get inside temperature and humidity from HTU21 sensor, attached to I2C bus of the tablet.
I was lucky and two of my tablets have additional, unsoldered pads on the motherboard with I2C bus so I could connect my HTU21 relatively easily.

After ten minutes of swearing and cursing, which accompanied my attempts to solder two wires to 0.5mm pads with 0.25mm gaps between them, I would love to just have a simple script to check that my HTU21 is responding.
It was especially desirable for AML8726-MX SoC which has vendor-provided lopsided old kernel with unusual response to i2c scan.

I searched for such a script but couldn’t find one. Perhaps there are some, written in Python, but I’m not familiar with this language and its means of module installation.

So I wrote a Perl script which is based on i2c-tools package, It also uses Time::HiRes module to get delays less than 1 sec.
The algorithm I use is mimicked from the HTU21 Arduino library by Daniel Wiese.

Here it is:

Perl script
#! /usr/bin/perl
 
use strict;
use utf8;
use Time::HiRes qw(usleep);
 
 
my $HTU_I2C_ADDR = 0x40;
my $HTU_I2C_BUS = 1;
 
my $TRIGGER_TEMP_MEAS_NH = 0xF3;
my $TRIGGER_HUM_MEAS_NH = 0xF5;
my $TRIGGER_READ_USER_REG = 0xE7;
my $SOFT_RESET = 0xFE;
 
my $HTU_T = 0;
my $HTU_RH = 0;
 
 
`i2cset -y $HTU_I2C_BUS $HTU_I2C_ADDR $SOFT_RESET`;
usleep(15000);
`i2cset -y $HTU_I2C_BUS $HTU_I2C_ADDR $TRIGGER_READ_USER_REG`;
usleep(5000);
 
my $r = `i2cget -y $HTU_I2C_BUS $HTU_I2C_ADDR`; # read user reg
 
(print "Got response $r instead of 0x02 after Soft Reset. Terminating.\n"), exit 0  if $r != '0x02';
print "Soft Reset OK\n";
 
usleep(50000);
 
 
 
while (1){
 
    if(HTUmeasure()){
        print $HTU_T, " C  ", $HTU_RH, " % \n";
    }else{
        print "HTU error\n";
    }
 
    sleep 5;
}
 
 
sub HTUgetT{
 
    my @data = (0,0,0);
 
    `i2cset -y $HTU_I2C_BUS $HTU_I2C_ADDR $TRIGGER_TEMP_MEAS_NH`; # measure temperature
 
    usleep(100000);
 
    my $resp = `i2ctransfer -y $HTU_I2C_BUS r3\@$HTU_I2C_ADDR`; # read three bytes  i2ctransfer -y 1 r3@0x40
    @data = split(/\s+/,$resp);
 
    return 0 if not checkCRC8(@data);
 
    my $St = (($data[0] << 8) | ($data[1] & 0xFC)) % 65536;
    $HTU_T = -46.85 + 175.72 * $St / 65536.0;
 
    return 1;
}
 
 
sub HTUgetRH{
 
    my @data = (0,0,0);
 
    `i2cset -y $HTU_I2C_BUS $HTU_I2C_ADDR $TRIGGER_HUM_MEAS_NH`; # measure humidity
 
    usleep(20000);
 
    my $resp = `i2ctransfer -y $HTU_I2C_BUS r3\@$HTU_I2C_ADDR`; #read three bytes i2ctransfer -y 1 r3@0x40
    @data = split(/\s+/,$resp);
 
    return 0 if not checkCRC8(@data);
 
     my $Srh = (($data[0] << 8) | ($data[1] & 0xFC))  % 65536;
     $HTU_RH = -6.0 + 125.0 * $Srh / 65536.0;
     $HTU_RH += (25.0 - $HTU_T) * (-0.15);      # temperature compensation
     $HTU_RH = 0.0 if $HTU_RH < 0;
     $HTU_RH = 100.0 if $HTU_RH > 100;
 
     return 1;
}
 
 
sub HTUmeasure{
    $HTU_T = "--";
    $HTU_RH = "--";
 
    return 0 if(!HTUgetT());
    return 0 if(!HTUgetRH());
    return 1;
}
 
 
sub checkCRC8{
    my $in_crc = pop(@_);
    my $crc = 0;
 
  for(my $i = 0; $i < 2; $i++) {
    $crc = $crc ^ $_[$i];
 
    for(my $b = 7; $b >= 0; $b--) {
      if($crc & 0x80){ $crc = (($crc << 1) ^ 0x31 ) ;}
      else {$crc = ($crc << 1)}
      $crc = $crc % 256;
    }
  }
 
  return $crc == $in_crc;
}

Just a few notes here.
1) If something goes wrong during the communication you will see an error messages from i2c-tools on your STDERR output.
2) Personally I was able to detect my HTU21 device using i2cdetect, but it was impossible to communicate with it until I removed some touchscreen driver from the kernel config and recompiled the kernel. This driver anticipated to have its IC on the same 0x40 I2C address.
3) I saw a mention on the internet that HTU21 driver is present in the Linux kernel since 4.4 (CONFIG_HTU21). The project on the photo above is running a vendor-provided kernel 3.10.x so I think there is no HTU21 driver there, but I think I will check the kernel module for my other tablets which run recent kernel versions.