### TL;DR

If you just want to use 1-wire based device and just don’t want to know any technical detail, jump to the last part.

### Reason to use DS18B20

I’m trying to add some temperature sensor to my STM32-based computer water-cooling controller.

You know, a common solution to this is to use some thermistor and try to use some ADC(Analog-Digital-Converter) to capture the voltage on that. The temperature captured by thermistor won’t be too accurate, just around 1 degree or so. If you need a temperature with accuracy of 0.0625 degree, the DS18B20 would be a good choice.

But DS18B20 has an really time sensitive 1-wire protocol. A normal solution to this problem is to use GPIO and NOP() instruction to simulate this, but I tend to use something different.

### 1-wire protocol

1. #### Overview

1-wire or OneWire is a protocol with single-master and multi-slave, under most situation, we would just use one device on the bus.

2. #### Analysis on 1-wire protocol

1. ##### Overview on 1-wire protocol

No matter whether it’s a read procedure or a write procedure, the process of 1-wire follow a similar pattern:

READ PATTERN: Start -> Write a ROM command -> (Optional Read ROM Data) -> Write a READ function command-> READ DATA -> End

WRITE PATTERN: Start -> Write a ROM command -> (Optional Read ROM Data) -> Write a WRITE function command-> WRITE DATA -> End

or in a more general version:

DATA EXCHANGE: Start -> Write a ROM function -> (Optional Read ROM Data) -> Write a EXCHANGE function -> EXCHANGE DATA -> End

2. ##### Analysis about the ROM function

Just like all the other communication protocol with multiple slave, the master need to know something about the slave devices to tell the difference between all these devices, at the same time, the slaves also need to has some information to know if one communication on the bus is oriented to itself - check the figure below.

For a typical 1-wire device, it has an 8 bytes(64-bit) address. The first byte is assigned for family code, while the following 6 bytes is a unique serial number, another 1 byte for CRC code of the first 7 bytes. If the master want to read the serial number from device, it could use the CRC to verify if the communication is right or not.

According to the above, we could know that the 6bytes in the medium could be used to distinguish devices - do a simple math $2^{48}=281,474,976,710,656$, this is more than enough for the distinguish problem.

ROM code ROM command
0x55 MATCH ROM
0xf0 SEARCH ROM
0xec ALARM SEARCH ROM
0xcc SKIP ROM

In order to play with these ROMs, there are multiple ROM commands. The following figure indicate their behavior in a simplified way.

Such overkill solution bring a problem when solving a problem, that is, when you get a 1-wire device, you won’t be able to know its serial address, you may need to use a complicate method described in the specification to read all unknown serial numbers out, check the Book of iButton chapter 5.II.C.3 and Application Note 187 to know more detail about that.

But in most implementation, programmer won’t know the serial number in advance, they also won’t try to implement such ROM SEARCH function. Instead, they just assume there is only device on the bus, using external multiplexer to add support to multi-device.

Almost any kind of multiplexer would work, I had verified that 74HC4052and 74HC4051 could work perfectly. If you need more, I think CPLD/FPGA might also work. The figure above illustrate a brief of the whole idea.

When using the solution above, we don’t have to handle the ROM issue, under most problem, the 0xCC(SKIP ROM) is used most frequently.

3. Analysis on DS18B20 specific function

Since there are only 7 internal register (3 reserved) in DS18B20, so the control flow is quite simple, just follow the pattern I mentioned in section 2.1.

Here is a brief demonstration of each function command:

Convert T(0x44): start the temperature convert and save the temp to the BYTE0 and BYTE1.

Read Scratchpad(0xbe): read all data from scratchpad(all 9 byte but you could sample just the bytes you need.)

Write Scratchpad(0x4e):write 3 byte to BYTE2, BYTE3 and BYTE4. (all 3 byte is needed) configure data in scratchpad would lost after power failed.

Copy Scratchpad(0x48): copy BYTE2, BYTE3 and BYTE4 to EERAM.(unlike scratchpad, EERAM would keep after power fails)

Recall E^2(0xb8): copy data stored in EERAM back to BYTE2-BYTE4.

Read Power Supply(0xb4): return 1 if the device is on an independent ac power, return 0 if the device is using the DQ power.

3. #### Signal character

Watch the whole communication process from the master side, the write process acquire the master to do the following thing:

1. ##### Write a bit

transmit a low level signal for at least 1us, then transmit the corresponding signal, the whole transmit time should takes longer than 60us. Check the following image,

In the left part, the master is sending a zero bit, it should send a low level for more than 1 us, then keep the level at low for at least another (60-1) us. After writing zero bit, the master should release the data line, otherwise if the data line keep at low level for more than 480us, then the device would start another initializing process.

In the right part, the process is almost the same to send a one bit, but actually the device don’t have to release the data lane, since the data line is kept at high when the line is leased.

The gray square in the image give a typical operating time, in the first 15us, the master should keep the data line at low level for at least 1us, and then keep it to the corresponding level for at least 15+30 us (which is the sampling time slot for ds18b20 device). After sampling, the master need to release data line.

The following image demonstrate how to read from the slave device, which is quite similar to the process above.

Before the master decide to read a bit from the slave device, it should send a low level signal for at least 1us, then release the data line for some time and sample the signal level on the bus. The whole reading process of reading a bit is at least 60us, after that the slave device would release the data line and make preparation for next bit.

4. #### Using UART+DMA to implement the 1-Wire Protocol

The traditional way to implement the 1-wire protocol is using GPIO and while(x) to simulate. However, using such way to simulate would waste many time cycle and the behavior may varies when the compiler changed.

Connect the device to the MCU like following:

1. ##### principle behind simulating 1-wire using UART - WRITE

The UART TTL is low-level for a start bit and a high-level for idle, just same as 1-wire.

If configured with 1 start bit (low-level), 8 data bit, 1 stop bit, then the UART would behave similar to the scale pattern of 1 bit of 1-wire.

When 1-wire protocol need to write bit, the whole time would be 60-120us, the device would sample at the time slot in 15-60us. If we set the baud rate of UART to 115200 baud per second, then every bit would occupy a time slot of 1000000/115200=8.6us (enough to make the start bit a start signal in 1-wire, and small enough to avoid interfere the sampling slot), then 1byte data would make a 8.6*9=78us slot - just within the range of 1-wire protocol.

In a word, if you send a byte 0x00 under UART 115200-8-N-1, the 1-wire device would take that as an 1 bit; if you send a byte 0xff under UART 115200-8-N-1, the 1-wire device would take it as a 0 bit.

2. ##### principle behind simulating 1-wire using UART - READ

The pattern is similar to above.

First, the UART TTL under 115200-8-N-1 send a 0xff, if the UART read a 0xff, then the bit should be a 1, other wise, it should be a 0 bit.

3. ##### principle behind simulating 1-wire using UART - RESET

Since the reset on 1-wire protocol require a much longer time, UART of 115200 baud rate can’t meet the requirement. We need to use the 9600-8-N-1 UART to send a 0xf0. Since the UART would send LSB first, the data line would send 5 continuous 0 bit (which is 1000000/9600*5=520us), enough to reset the 1-wire bus. If the UART read a 0xf0, then there is no device on the line, otherwise, the bus is reset as expected.

Except from the reset operation, the rest two operation need to operate 8 continuous byte, which means the user’s code need to respond to the UART interrupt consequently, also means complicate code and keeps interrupting the embedded OS and other processing.

So it’s best to use DMA to avoid such problem, template code is attached at the end.

1. #### The code you need is here

Here I will show you how to use my code, if you still have problem, feel free to comment below:

1. Generate the DMA and U(S)ART initialize code with CubeMX.

2. Modify the macro USART2 in ds18b20.c to any UART or USART peripheral you like.

3. Add #include "ds18b20.h"between /* USER CODE BEGIN 0 */ and /* USER CODE END 0 */ in the usart.c . Add the following block between /* USER CODE BEGIN 1 */ and /* USER CODE END 1 */ to make sure the library would receive the complete callback of UART.

4. In your main.c, use OneWire_Init() to initialize the whole library and OneWire_SetCallback() to register your own callback. The usage of the other function is listed below:

ds18b20.h:

ds18b20.c: