Article Index

Initialization 

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

We have implemented this in a previous chapter and can simply use the presence function:

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_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT);

if(presence(RPI_GPIO_P1_07)==0){
    read temperature
};

I you try this and find it doesn't work then 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:

 

 

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 function developed in the previous chapter to work with the commands defined int he data sheet.

Initiating A Temperature Conversion

After discovering that there is at least one device connected to the bus the master has to issue a ROM command. In many cases the ROM command used first will be the Search ROM command which enumerates the 64-bit codes of all of the devices on the bus. After collecting all of these codes the master can used the Match ROM commands with a specific 64-bit code to select the device the master wants to talk to. 

While it is perfectly possible to implement the Search ROM procedure it is simpler to work with the single device by using commands which ignore the 64-bit code and address all of the devices on the bus at the same time. Of course this only works as long as there is only one device on the bus. 

If there is only one device, as in this case, then we can use the Skip ROM command 0xCC to tell all i.e the one,  devices on the bus to be active. 

Using this we can send a Skip ROM command using:

writeByte(RPI_GPIO_P1_07, 0xCC);

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

 

Our next task is to send a Convert command 0x44.

This starts 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 it is operating in but using an external power line, i.e. not using parasitic mode, the device sends a zero bit in response to a bit read until it is completed when it sends a 1. 

This is new and it 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 zero until it is read 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 < 1000; 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 500 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. However 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 if we are going to check the CRC for error correct we need to read all nine bytes. 

All we have to do issue a Read Scratchpad 0xBE command and then read the nine bytes that the device returns. 

However, 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_GPIO_P1_07);
   writeByte(RPI_GPIO_P1_07, 0xCC);
   writeByte(RPI_GPIO_P1_07, 0xBE);

Now the data is ready to read.

We can read all nine bytes of it or just the first two. The device will keep track of where the read is so if you come back later and read more bytes you will get the first one not read. 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_GPIO_P1_07);
   }

Now we have all of the data stored in the scratch pad and the CRC byte. We can now check for errors:

  uint8_t crc = crc8(data, 9);

As before crc will be zero if there are no transmission errors.

Getting the Temperature

We only really need the first two bytes which are the least and most significant bytes of the 11-bit temperature reading as a 16-bit, 2-complement integer. 

  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 do is to put the two bytes together as a 16-bit integer. As the Pi supports a 16-bit int 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);

Then 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 then 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 - divide by 16 to get centigrade.

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);

A Function to get the temperature

Packaging all of this into a single function is easy:

float getTemperature(uint8_t pin) {
    if (presence(pin) == 1) return -1000;
    writeByte(pin, 0xCC);
    convert(pin);
    presence(pin);
    writeByte(pin, 0xCC);
    writeByte(pin, 0xBE);
    int i;
    uint8_t data[9];
    for (i = 0; i < 9; i++) {
        data[i] = readByte(pin);
    }
    uint8_t crc = crc8(data, 9);
    if(crc!=0) return -2000;
    int t1 = data[0];
    int t2 = data[1];
    int16_t temp1 = (t2 << 8 | t1);
    float temp = (float) temp1 / 16;
    return temp;
}

Notice that the function returns -1000 if there is no device and -2000 if there is a CRC error. These are values outside of the range of temperature that can be measured.