1-Wire interface on STM32 with UART + DMA

Document created by Tilen MAJERLE Employee on Mar 28, 2018Last modified by Tilen MAJERLE Employee on Apr 9, 2018
Version 8Show Document
  • View in full screen mode

If I quote Wikipedia:

1-Wire is a device communications bus system designed by Dallas Semiconductor Corp. that provides low-speed data, signaling, and power over a single conductor.


1-Wire is similar in concept to I²C, but with lower data rates and longer range. It is typically used to communicate with small inexpensive devices such as digital thermometers and weather instruments. A network of 1-Wire devices with an associated master device is called a MicroLAN.

Software solution


OneWire is relatively slow protocol, it can be easy written on any MCU by software (no need of hardware) using delays. From the protocol specifications:

  • Communication IO pin must be in open-drain configuration with external pull-up ~4.7k
  • Reset pulse is 480us long
  • Writing logical bit 1 takes about ~70us with ~10us low pulse, followed by ~60us high pulse
  • Writing logical bit 0 takes about ~70us with ~65us low pulse, followed by ~5us high pulse
  • Full timing diagram is available on official OneWire protocol website.


We can see that achieving these timings with some assembly or even C delays can be very easy and straightforward, thus we can implement and use this in final design.


Problematic parts


ST offers MCUs today up to 400MHz (STM32H7) or in ultra-low-power (STM32L0/1/4) configuration. Imagine you have 1-Wire device connected (ex. temperature sensor) and you need to constantly poll and/or read temperature:

  • At 400MHz MCU, to achieve 480us you need to waste almost 200k clock cycles just to send reset pulse
    • Solution: Use HW timer with its interrupt and process everything inside timer interrupt = very painful, code style is not linear, event based style
  • With ULP MCU, you spend useful energy from battery
    • Solution: Use HW timer to wake you up and go to sleep between 2 actions
  • In case of operating system environment or having many interrupts in system, you can be sure your software timings won't match anymore due to many reasons (ex. task switching).
    • Solution: Use hardware on STM32.
  • Remember: Cortex-M CPUs were designed with operating system usage in mind.


Hardware solution


Beauty of 1-wire protocol is that we can use UART protocol for communication instead of software-based GPIO toggling. Comparing to SW solution below are for and against:

  • No need to worry about timings as HW UART will handle everything
  • We can use DMA for bulk-transfers of entire byte over 1-Wire
  • Suitable for operating system SW design
  • UART needs TX pin in open-drain mode



For bidirectional communication, we have to connect TX and RX pins together. Both pins must be configured in open-drain mode or (as usual for RX pin) in input mode. STM32 offers flexible pin assignment, thus setting TX and RX pins in open-drain without internal pull-resistor is very easy:

LL_GPIO_InitTypeDef GPIO_InitStruct;

* USART GPIO Configuration
* USART pins are configured in open-drain mode!

GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_7;

/* TX pin */
/* RX pin */

Open-drain output type allows us flexibility when we send data. MCU will only pull line low when we need to send logical 0. In case of logical 1, MCU will release line and external pull-up is responsible for high state.


Connecting TX and RX pins together

Why would we do that? Very simple! As mentioned earlier, in open-drain mode, line is controlled by pins only when we need to pull it low. This gives us ability to (for example) send byte 0xF0 on the TX line and reading if any 1-wire slave device pulled line low during line high-state (0xF part). At the end:

  • TX pin is used to drive line low only
  • RX pin is used to read the line at the same time and check if any slave pulled line low during TX pine sending logical bit 1.
* Example of TX and RX pins, connected together and to data pin of 1-wire slaves
* Agenda:
* - means pulse high due to disabled TX pin (open-drain) and pulled-high by external resistor
* _ means pulse low, asserted by TX pin or by slave
* | means transition from low-to-high or high-to-low
*                                             IDLE S 0 0 0 0 1 1 1 1 S IDLE
* TX line output from master                : ----|_ _ _ _ _|- - - - - ----
* RX line input when slave doesn't pull low : ----|_ _ _ _ _|- - - - - ----
*                     Part of signal was pulled low by slave -> ( )
* RX line input when slave pulls low        : ----|_ _ _ _ _|- -|_|- - ----

From the example above, we can determine that in case of sending 0xF0 on TX line, RX line read is equal to:

  • 0xF0 in case there is no assertion from any of slaves
  • 0xX0 in case there is assertion from any slave, where X depends on actual line being low
  • We can use this technique to determine if there is "anybody on the line" or not


1-Wire vs UART protocol

Similarity between timings vs UART is well explained in Maxim's application note. I will just mention that we need 2 different baudrates. 9600 when we want to send reset pulse and 115200 for everything else.


1-Wire is slow protocol and according to timings, sending single bit over 1-Wire is the same as sending 1 byte over UART @ 115200 bauds. The important is to know what value should be sent over UART when we want to transmit logical 1 or logical 0:

  • When we want to write logical bit 1, we have to send 0xFF over UART
  • When we want to write logical bit 0, we have to send 0x00 over UART
  • Both transmissions are on 115200 bauds.
* \brief           Write byte over 1-wire protocol
* \param[in]       b: Byte to write
* \return          Received byte over 1-wire protocol

onewire_write_byte(uint8_t b) {
    uint8_t i, r = 0, tr[8];
     * Each BIT on 1-wire level represents 1-byte on UART level at 115200 bauds.
     * To transmit entire byte over 1-wire protocol, we have to send 8-byte over UART
     * Writing logical bit 1 over 1-Wire protocol starts by pulling line low for about 6us.
     * After that, we should release line for at least 64us. Entire sequence takes about 70us in total.
     * From UART point of view, we have to send 0xFF on TX line. Start bit will take care of initial 6us low pulse and 0xFF will take care of high pulse.
     * Writing logical bit 0 over 1-Wire protocol is similar to bit 1, but here we only pull line low for 60us and then release it with STOP bit.
     * To write logical bit 0, we have to send constant 0x00 over UART.

    /* Create output data */
    for (i = 0; i < 8; i++) {
         * If we have to send high bit, set byte as 0xFF,
         * otherwise set it as low bit, 0x00

        tr[i] = (b & (1 << i)) ? 0xFF : 0x00;
     * Exchange data on UART level, 8-bytes for each bit

    ow_usart_tr(tr, tr, 8);                     /* Exchange data over UART */
     * Check received data. If we read 0xFF, our logical write 1 was successful, otherwise it was 0.

    for (i = 0; i < 8; i++) {
        if (tr[i] == 0xFF) {
            r |= 1 << i;
    return r;


Test and read sensors

For the test, I used:

  • F401RE-Nucleo kit
  • 2x USARTs
    • USART1 used for 1-Wire protocol
      • TX = PA9, RX = PA10, connected together and to data line of sensors
    • USART2 (VCP on Nucleo) used for debugging purposes, 921600 bauds
      • TX = PA2, RX = PA3, accessed via ST-Link USB
  • 10x DS18B20 1-Wire sensors connected together in parallel with external single pull-up resistor between 5V and DATA pin

Nucleo + DS18B20 sensors


Source code

Source is available on Github. You may download it from there.


I didn't go into details about UART&1-Wire. Do not hesitate to ask here in case of any question.

4 people found this helpful