How to use 1-wire Protocol to interface DS18B20
This is the 7th tutorial in the series on the UART peripheral of STM32 Microcontrollers. In this series we will cover different ways of transmitting and receiving data over the UART protocol. We will also see different UART modes available in the STM32 microcontrollers and how to use them.
In the previous tutorial of this series we saw how to communicate between STM32 MCUs using a single wire (Half Duplex Mode). This tutorial will also cover the same (Half Duplex Mode) but we will extend this capability of the UART peripheral to interface a sensor, that works with single wire protocol.
I am talking about the famous DS18B20 Temperature sensor. This sensor uses the standard 1-wire protocol for communication with the MCU. I said “standard” because there are some other sensors like DHT11 or DHT22, which also uses the so called 1-wire protocol but they are not standard, and hence it is extremely had to make them work using the UART.
I have already covered how to interface DS18B20 using the GPIO mode. Today we will approach this sensor using the UART peripheral and see the resulting temperature value on the cubeIDE’s debugger.
CubeMX Configuration
Below is the UART configuration in the single wire mode.
- The USART1 is configured in the Single wire mode (Half Duplex).
- The USART parameters should be configured with 8 data bits, 1 stop bit and no parity. The baud rate will be set during the runtime, so it does not matter here.
- I have enabled the DMA for both Receive and Transmit. The DMA is used in the Normal mode with the Data width set to byte.
Connection
Below is the image showing the connection between the STM32 and the DS18B20 sensor.
In single wire mode, we only have one pin for the TX. The sensor is connected to the TX pin of the USART1.
The sensor is powered by 5V from the MCU itself and there is a pull up resistor of 4.7K, connected between the data line and the 5V.
How to send bits to the sensor
We need to send the data to the sensor and read the data from it. The sensor require different timing signal for a bit to recognize it as a bit 0 or bit 1. We will cover this in depth below.
MCU send bit ‘0’
As per the information given in the DS18B20 datasheet, for the sensor to recognize a signal as the bit 0, the data line should transit from HIGH to LOW and then it should be LOW for around 60-120us.
Now if we use the UART at the baud rate of 115200, each bit would take around 8.68us. We are using 8 Data bit, so along with the Start bit we have a total of 9 bits in a single byte. These 9 bits would take around 78us in total.
The START bit is always LOW in UART. So to transmit the bit 0, we will send the byte 0x00. This will transmit 9 Low bits (Start bit + 8 Data bits) and will keep the data line Low for around 78us. It will satisfy the criteria for the bit 0.
Below is the image showing the data line on the Logic Analyzer. This image shows the data line timing, when we send the data byte 0x00.
As you can see the line transits from High to Low. This is the START bit followed by the 8 Low data bits. The total time taken by the data byte is around 78us. This time is even less when we connect an actual sensor to the data line.
So the point is, to send the bit 0 to the sensor, the MCU needs to send the byte 0x00.
MCU send bit ‘1’
As per the information given in the DS18B20 datasheet, for the sensor to recognize a signal as the bit 1, the data line should transit from HIGH to LOW and then it should be LOW for around 1us. After that the line should be released and the pull up resistor will pull the line High for the rest of the time.
Since each bit at the baud rate of 115200 takes around 8.68us, it is not possible for the MCU to pull the line low for just 1us, it will at least be low for 8.68us.
One interesting point to note is that the DS18B20 sensor does not sample the line immediately after 1us. Instead it will only sample 15us after the line was first pulled low. We will send the data 0xFF, such that only the START bit is low and the rest of the bits are high. This will keep the line low for only around 9-10us and then the line will be pulled High with the help of the pull up resistor.
By the time the sensor samples the line (after 15us) the line would already be high.
Below is the image showing the data line on the Logic Analyzer. This image shows the data line timing, when we send the data byte 0xFF.
As you can see the line transits from High to Low. This is the START bit followed by the 8 High data bits. The time for which the line is low is around 9us. This time is even less when we connect an actual sensor to the data line. For the rest of the time the line is High. So when the sensor will sample the line (after 15us), it will already be high.
So the point is, to send the bit 1 to the sensor, the MCU needs to send the byte 0xFF.
Send the Reset Condition
To send the reset condition, the master needs to pull the line low for a minimum of 480us and then release it. When the DS18B20 detects this rising edge, it waits 15μs to 60μs and then transmits a presence pulse by pulling the line low for 60μs to 240μs.
With the baud rate of 115200, the total time taken by 9 bits (Start + 8Data bits) is less than 80us. So we can not possibly pull the line Low continuously for at least 480us. This is why we will use the baud rate of 9600 to send the reset condition.
If the master send the byte 0xF0 with the baud rate of 9600, there will be 5 Low bits (Start + 4 Data bits). The total time for which the line will remain low will be around 520us.
Below is the image showing the output of sending the byte 0xF0 with the baud rate of 9600.
As you can see the 1-wire analyzer does detect the pulse as the Reset condition.
When the sensor detects the reset pulse, it will send the presence pulse by pulling the line Low again. It does that immediately after seeing that the line is released by the master. As a result, the byte 0xF0 is changed to something else. You can see this in the image below.
- The master starts transmitting the reset pulse by pulling the line Low. It wants to transmit the byte 0xF0.
- After transmitting the first 4 bits (0 0 0 0), the master releases the line, so that it can transmit last 4 bits (1 1 1 1).
- The sensor sees the line is released, so it waits for 15-60us and pulls the line back Low for around 60-240us.
- As a result of this, the byte 0xF0 has been change to some other byte (0xC0 in this case).
So the point is, to send the reset condition and read the presence pulse, we will send the byte 0xF0 at the baud rate of 9600. We will then read the data received by the UART. If the data is different than 0xF0, we will come to a conclusion that the sensor must have pulled the line Low and hence it sent the presence pulse.
How to Read the bits
After receiving the appropriate commands, the sensor will start sending the temperature data bytes. The MCU needs to read these bits and extract the temperature from it.
The master will first pull the line Low for around 1us and then release it. If the sensor wants to send the bit 0, it will pull the line Low again, and if the master wants to send the bit 1, it will leave the line released. This can be seen in the image below.
The master can sample the line before 15us. If the line is Low, it can assume that it is a bit 0 and if the line is High, it can assume that the bit is 1.
To pull the line low for just around 1us, the master will send the byte 0xFF at the baud rate of 115200. This data will only contain a Low start bit whereas for the rest of the time, the line will remain High. When the sensor sees the line is released after the Start bit, it will either pull it Low again to transmit the bit 0 or leave it High to transmit the bit 1.
Since the master wants to read an entire byte, it needs to keep sending the byte 0xFF a total of 8 times. For each byte, the sensor can respond with either bit 0 or bit 1.
You can see in the image above, the master transmitted 8 bytes containing 0xFF. The master transmitted the bit 0 by pulling the line Low for some of these bytes while the rest of the bytes are untouched and we got the same data that we sent.
So the point is, to read the data from the sensor, the master will send 8 bytes data containing the byte 0xFF. It will then read the data received by the UART. If the data is different than 0xFF, the sensor must have pulled the line Low and hence the bit is a 0. Whereas if the data is same as 0xFF, the bit is a 1.
The Code
The DS18B20_Start function will be used to send the Reset to the sensor and detect the presence pulse.
int DS18B20_Start(void)
{
uint8_t data = 0xF0;
uart_init(9600);
HAL_UART_Transmit(&huart1, &data, 1, 100); // low for 500+ms
if (HAL_UART_Receive(&huart1, &data, 1, 1000) != HAL_OK) return -1; // failed.. check connection
uart_init(115200);
if (data == 0xF0) return -2; // no response.. check connection
return 1; // response detected
}
We will first initialize the UART with the baud rate of 9600. Then transmit the data byte 0xF0 as discussed above. We will then receive 1 byte of data from the UART and store it in the data variable. The rest of the UART operations will take place at the baud rate of 115200, so initialize the UART with this baud.
If the received data is same as 0xF0, this means that the presence pulse has not been detected and we will return an error. Otherwise if the sensor has pulled the line Low, the received data will be different from 0xF0. This means the presence pulse is detected, so we will return success.
The DS18B20_Write function will be used to write the data byte to the sensor. The parameter of this function is the data byte that we want to send to the sensor.
void DS18B20_Write (uint8_t data)
{
uint8_t buffer[8];
for (int i=0; i<8; i++)
{
if ((data & (1<<i))!=0) // if the bit is high
{
buffer[i] = 0xFF; // write 1
}
else // if the bit is low
{
buffer[i] = 0; // write 0
}
}
HAL_UART_Transmit(&huart1, buffer, 8, 100);
}
We need to send 8 bits to the sensor. Each bit will be sent as a byte and hence we will send 8 bytes in total. This is why the array buffer is defined to store 8 bytes.
We will extract each bit from the data. If the bit is a 1, we will store the byte 0xFF to that position and if the bit is a 0, we will store 0x00 to that position. This has already been explained above in the writing process.
After storing the relevant bytes to the buffer array, we will send the array to the UART.
The function DS18B20_Read will be used to read the data byte sent by the sensor. This function returns the data byte received.
uint8_t DS18B20_Read (void)
{
uint8_t buffer[8];
uint8_t value = 0;
for (int i=0; i<8; i++)
{
buffer[i] = 0xFF;
}
HAL_UART_Transmit_DMA(&huart1, buffer, 8);
HAL_UART_Receive_DMA(&huart1, RxData, 8);
while (isRxed == 0);
for (int i=0;i<8;i++)
{
if (RxData[i]==0xFF) // if the pin is HIGH
{
value |= 1<<i; // read = 1
}
}
isRxed = 0;
return value;
}
I have already explained in the Reading Process that the master needs to send 8 bytes containing the value 0xFF. Since we want to record the response of the sensor to each byte transmitted, it is better to send the data using the DMA. This way the response will be recorded in simultaneous with the data transmission.
The variable isRxed will be set inside the DMA callback when all the 8 data bytes has been received. Once that happens, we will start converting the received data bytes into the bits.
If the received data is same as 0xFF, that implies that the bit is a 1 otherwise if the received data is something else, the bit is a 0. We will analyze the received data buffer, and store the bit 1 or bit 0 at the respective position in the value variable.
After all the received data bytes has been processed, we will reset the variable isRxed to 0 and return the value.
The main function
Inside the main function, we will continuously read the temperature after a particular interval. This is why the code is inside the while loop.
int main ()
{
...
...
while (1)
{
Presence = DS18B20_Start ();
DS18B20_Write (0xCC); // skip ROM
DS18B20_Write (0x44); // convert t
Presence = DS18B20_Start ();
DS18B20_Write (0xCC); // skip ROM
DS18B20_Write (0xBE); // Read Scratch-pad
Temp_LSB = DS18B20_Read();
Temp_MSB = DS18B20_Read();
Temp = ((Temp_MSB<<8))|Temp_LSB;
Temperature = (float)Temp/16.0; // resolution is 0.0625
HAL_Delay(2000);
}
}
We will read the data as according to the information given in the datasheet.
Basically the master will send the reset sequence and detect the presence pulse. Then it will issue the skip rom command followed by the convert t command.
It will again send the reset condition and read the presence pulse. It will then send the skip rom command followed by the read scratchpad command.
Then the master will request 2 bytes data from the sensor. It will first read the Least Significant Byte followed by the Most Significant Byte of the temperature data.
The sensor is configured with 12 bit resolution by default. Hence we will have a total of 12 bits of the data. The 2 bytes of the temperature data will be combined to form a single 12 bit data.
With the resolution of 12 bit, the temperature is set to 0.0625 °C per bit value. Hence to convert the 12 bit temperature data to actual temperature, we will multiply the 12 bit value with 0.0625 or divide by 16.
Result
Below is the image showing the values on the cubeIDE debugger window.
You can see the presence value is 1, which means that the sensor was detected. The final Temperature is around 18.5°C.
To see the full working where the sensor responds to the change it temperature, check out the video below.