Exploring Edison - Bit Banging the DHT11/DHT22
Monday, 25 January 2016
Article Index
Exploring Edison - Bit Banging the DHT11/DHT22
Electronics and Software
Program Listing

The Electronics

To convert the 3.3V device to work with the 1.8V Edison we can make use of the single transistor level shifter introduced in the previous chapter:

 circuit

The pins used on the Edison connector are:

MRAA NumberPhysical PinEdison Pin
(SYSFS)
29 J19-2 V_V1P80
30 J19-3 GND
31 J19-4 GP44
43 J20-2 V_V3P30

 

It is worth recalling that for the mini-board you can't alter the output mode of a line using mraa. By default the output mode is pull up with a 50K resistor which is effectively in parallel with our 10K pull up. This means the actually pull up in use is effectively around 8K. 

If you use a 2N2222 transistor then the pin outs are:

 

2n2222

You can build the circuit on a prototype board to test the software but it is also fairly easy to build a free standing circuit on the DHT22 effectively converting it to 1.8V operation:

physical

Isolate the components from one another using heat shrink sleeving. Of course the disadvantage is that now we have a four lead device and the cable cannot be as long - if it is long remove the capacitor as the lead will provide more capacitive loading than you need.

The Software

With the hardware shown above connected to the Edison the first thing that we need to do is establish that the system is working - just a little.

The simplest way to do this is to pull the line down for 1ms and see if the device responds with a stream of pulses. These can be seen on a logic analyser or an oscilloscope - both are indispensable tools. 

If you don't have access to either tool then you will just have to skip to the next stage and see if you can read in some data. 

The simplest program that will do the job is: 

int main()
{
 mraa_gpio_context pin = mraa_gpio_init(31);
 mraa_gpio_use_mmaped(pin,1);
 mraa_gpio_dir(pin, MRAA_GPIO_OUT_HIGH);
   mraa_gpio_write(pin, 0);
 usleep(1000);
 mraa_gpio_dir(pin, MRAA_GPIO_IN);
 return MRAA_SUCCESS;
}

Setting the line initially high we then set it low, wait for 1000 microseconds and then change its direction to input ready to read the data. 

Notice that we don't have to set the mode of the output line as it can drive its side of the buss safe in the knowledge that the device will not try to drive it until it releases the low state and changes to a high impedance input.

As long as the circuit has been correctly assembled and you have a working device you should see something like:

 logic1

 

Notice that the pulling low of the line actually lasts 1,3ms which is a bit on the long side but does no harm.

If you haven't used the capacitor in the level shifting circuit then you might see on some of the pulses on the Edison's side look something like:

glitch

 

This is caused by the change in operating mode of the transistor. The capacitor acts as a high pass filter and smooths the glitch out. You might not see this effect even without the capacitor because often the circuit layout provides enough stray capacitance to smooth things out. Too much capacitance and the rise time of the pluses is compromised. 

 

ExploringEdison

 

Reading The Data

With preliminary flight checks complete it is time to read the 40-bit data stream. When doing this there are two things to keep in mind. The first is that it is only the time the line is high that matters and you need to measure just this accurately - you don't care so much about how long the line is low for.

The second is that it is usually better to collect the bits and only later process them and extract the data. To this end it is usually a good idea to save the data in a buffer: 

 int buf[41];
 int i;
 int j;
 for(j=0;j<=40;j++){
  for(i=1;i<200;i++){
   if(mraa_gpio_read(pin)==1)break;
  };
  for(i=1;i<200;i++){
   if(mraa_gpio_read(pin)==0)break;
  }
  buf[j]=i;  
}

 

You should be able to see how this works.

The outer for loop repeats to read in all 41 bits, 40 data bits and the inital start bit. The inner loop waits for the line to go high and i gives the time that the line has been low. This is of no interest. Next the second for loop waits for the line to go low. The count in i is now proportional to the time the line was high and is stored in the buffer. 

If you add 

for(j=0;j<=40;j++){
 printf("%d %d \n",j,buf[j]);
}

to the end of the program you will be able to see the counts and you should quickly be able to work out the value half way between the long one pulses and the short zero pulses. This was approximately 75 for the device in question. If there is any doubt that the pulse lengths might change or that the Edison might change then you could add some code that works out the threshold value each time it runs. This doesn't seem to be necessary in practice. 

With a threshold of 75 we can classify the pulses into long and short and store one and zero in the buffer. 

 int buf[41];
 int i;
 int j;
 for(j=0;j<=40;j++){
  for(i=1;i<200;i++){
   if(mraa_gpio_read(pin)==1)break;
  };
  for(i=1;i<200;i++){
   if(mraa_gpio_read(pin)==0)break;
  }
  buf[j]=0;
  if(i>75)buf[j]=1;
 }

You can afford to include this extra processing in the data collection loop because it happens while the line is low and we aren't interested in measuring this time accurately. 

Now we have the data in the buffer as zeros and ones. All that remains is to decode it into temperature and humidity readings. But first we will convert the bits into five bytes of data. The simplest way of doing this is to write a function that will pack eight bits into an int:

uint getByte(int b,int buf[]){
 int i;
 uint result=0;
 b=(b-1)*8+1;
 for(i=b;i<=b+7;i++){
  result= result<<1;
  result=result | buf[i];
 }
 return result;
}

The b can be set to the byte that you want to extract from the array. For example, if b=2 then the for loop runs from i=9 to i=16 i.e. the second byte stored in the array. The bit manipulation in the for loop is a fairly standard shift left and or the least significant bit into the result. 

Using this function getting the five bytes is trivial:

int byte1=getByte(1,buf);
int byte2=getByte(2,buf);
int byte3=getByte(3,buf);
int byte4=getByte(4,buf);
int byte5=getByte(5,buf);

The first two bytes are the humidity measurement, the second two the temperature and the final byte is the checksum. 

The checksum is just the sum of the first four bytes reduced to eight bits and we can test it using:

printf("Checksum %d %d \n",byte5,
     (byte1+byte2+byte3+byte4) & 0xFF);

If the two values are different there has been a transmission error. 

In this case the simplest thing to do is get another reading from the device. However notice that you shouldn't read the device more than once every 2 seconds. 

The humidity and temperature data are also easy to reconstruct as they are transmitted high byte first and times 10 the actual value. 

The humidity data is easy:

float humidity= (float) (byte1<<8 |byte2)/10.0;
printf("Humidity= %f \n",humidity);

The temperature data is slightly more difficult in that the top most bit is used to indicate a negative temperature. This means we have to test for the most significant bit and flip the sign of the temperature if it is set:

float temperature;
int neg=byte3 & 0x80;
byte3=byte3 & 0x7F;
temperature= (float) (byte3<<8 |byte4)/10.0;
if(neg>0)temperature=-temperature;
printf("Temperature= %f \n",temperature);

This complete the data processing however it is worth setting the processes priority so that it isn't interrupted by the OS while taking a reading - see the chapter on near real time Linux. 

const struct sched_param priority={1};
sched_setscheduler(0,SCHED_FIFO,&priority);



Last Updated ( Friday, 03 June 2016 )