Raspberry Pi IoT In C - The DS18B20 Temperature Sensor
Written by Harry Fairhead   
Tuesday, 17 May 2022
Article Index
Raspberry Pi IoT In C - The DS18B20 Temperature Sensor
Initialization
A Temperature Function

Initialization

Every transaction with the a 1‑wire device starts with an initialization handshake. The master holds the bus low for at least 480µs, a pause of 15µs to 60µs follows, and then any and all of the devices on the bus pull the line low for 60µs to 240µs.

We have already implemented the presence function and so can simply use it again:

int main(int argc, char **argv) {
    const struct sched_param priority = {1};
    sched_setscheduler(0, SCHED_FIFO, &priority);
    mlockall(MCL_CURRENT | MCL_FUTURE);
    if (!bcm2835_init())
        return 1;
    bcm2835_gpio_fsel(RPI_BPLUS_GPIO_J8_07, 
BCM2835_GPIO_FSEL_INPT); if(presence(RPI_BPLUS_GPIO_J8_07)==0){ read temperature };

If you find this doesn't work, make sure that you are running the program as root as described at the start of Chapter 2.

If you try this partial program and have a logic analyzer with a 1‑wire protocol analyzer, you will see something like:

ds181
Seeing a presence pulse is the simplest and quickest way to be sure that your hardware is working. From this point it is just a matter of using the functions developed in the previous chapters to work with the commands defined in the datasheet.

Initiating Temperature Conversion

As there is only one device on the 1‑wire bus, we can use the Skip ROM command (0xCC) to signal it to be active:

writeByte(RPI_BPLUS_GPIO_J8_07, 0xCC);

You can see the pattern of bits sent on a logic analyzer:

ds182

Our next task is to send a Convert command (0x44) to start the DS18B20 making a temperature measurement. Depending on the resolution selected, this can take as long as 750ms.

How the device tells the master that the measurement has completed depends on the mode in which it is operating, but using an external power line, i.e. not using parasitic mode, the device sends a 0 bit in response to a bit read until it is completed when it sends a 1.

This is how 1‑wire devices that need time to get data read slow down the master until they are ready. The master can read a single bit as often as it likes and the slave will respond with a 0 until it is ready with the data.

As we already have a readBit function this is easy. The software polls for the completion by reading the bus until it gets a 1 bit:

int convert(uint8_t pin) {
  int i;
  writeByte(pin, 0x44);
  for (i = 0; i < 5000; i++) {
	bcm2835_delayMicroseconds(100000);
	if (readBit(pin) == 1)break;
  }     
  return i;
}

You can of course test the return value to check that the result has been obtained. If convert returns 5000 then the loop timed out. When the function returns, the new temperature measurement is stored in the device's scratchpad memory and now all we have to do is read this.

Reading The Scratchpad

The scratchpad memory has nine bytes of storage in total and does things like control the accuracy of conversion and provide status information. In our simple example the only two bytes of any great interest are the first two, which hold the result of a temperature conversion. However, as we are going to check the CRC for error correction we need to read all nine bytes.

All we have to do is issue a Read Scratchpad 0xBE command and then read the nine bytes that the device returns. To send the new command we have to issue a new initialization pulse and a Skip ROM 0xCC command followed by a Read Scratchpad command 0xBE:

presence(RPI_BPLUS_GPIO_J8_07);
writeByte(RPI_BPLUS_GPIO_J8_07, 0xCC);
writeByte(RPI_BPLUS_GPIO_J8_07, 0xBE);

Now the data is ready to read. We can read all nine bytes of it or just the first two that we are interested in. The device will keep track of which bytes have been read. If you come back later and read more bytes you will continue the read from where you left off. If you issue another initialization pulse then the device aborts the data transmission.

As we do want to check the CRC for errors, we will read all nine bytes:

int i;
uint8_t data[9];
for (i = 0; i < 9; i++) {
	data[i] = readByte(RPI_BPLUS_GPIO_J8_07);
}

Now we have all of the data stored in the scratchpad and the CRC byte we can check for errors:

uint8_t crc = crc8(data, 9);

As before, crc will be 0 if there are no transmission errors. The crc8 function was given at the end of the previous chapter.

Getting The Temperature

To obtain the temperature measurement we need to work with the first two bytes, which are the least and most significant bytes of the 12-bit temperature reading:

int t1 = data[0];
int t2 = data[1];

t1 holds the low-order bits and t2 the high-order bits.

All we now have to do is to put the two bytes together as a 16-bit two’s complement integer. As the Pi supports a 16-bit int type, we can do this very easily:

int16_t temp1 = (t2 << 8 | t1);

Notice that this only works because int16_t really is a 16-bit integer. If you were to use:

int temp1= (t2<<8 | t1);

temp1 would be correct for positive temperatures but it would give the wrong answer for negative values because the sign bit isn't propagated into the top 16 bits. If you want to use a 32-bit integer, you will have to propagate the sign bit manually:

if(t2 & 0x80) temp1=temp1 | 0xFFFF0000;

Finally, we have to convert the temperature to a scaled floating-point value. As the returned data gives the temperature in centigrade with the low-order four bits giving the fractional part, it has to be scaled by a factor of 1/16:

float temp = (float) temp1 / 16;

Now we can print the CRC and the temperature:

printf("CRC %hho \n\r ", crc);
printf("temperature = %f C \n", temp);



Last Updated ( Tuesday, 17 May 2022 )