diff --git a/doc/doxygen/src/dose-wiring.svg b/doc/doxygen/src/dose-wiring.svg
new file mode 100644
index 0000000000..cc81797d67
--- /dev/null
+++ b/doc/doxygen/src/dose-wiring.svg
@@ -0,0 +1,520 @@
+
+
+
+
diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep
index 689b8f6ab9..9dc64efe57 100644
--- a/drivers/Makefile.dep
+++ b/drivers/Makefile.dep
@@ -193,6 +193,15 @@ ifneq (,$(filter ethos,$(USEMODULE)))
USEMODULE += tsrb
endif
+ifneq (,$(filter dose,$(USEMODULE)))
+ FEATURES_REQUIRED += periph_uart
+ USEMODULE += iolist
+ USEMODULE += netdev_eth
+ USEMODULE += random
+ USEMODULE += xtimer
+ FEATURES_REQUIRED += periph_gpio_irq
+endif
+
ifneq (,$(filter feetech,$(USEMODULE)))
USEMODULE += uart_half_duplex
endif
diff --git a/drivers/Makefile.include b/drivers/Makefile.include
index 15f1d18771..9000e6b417 100644
--- a/drivers/Makefile.include
+++ b/drivers/Makefile.include
@@ -98,6 +98,10 @@ ifneq (,$(filter encx24j600,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/encx24j600/include
endif
+ifneq (,$(filter dose,$(USEMODULE)))
+ USEMODULE_INCLUDES += $(RIOTBASE)/drivers/dose/include
+endif
+
ifneq (,$(filter feetech,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/feetech/include
endif
diff --git a/drivers/dose/Makefile b/drivers/dose/Makefile
new file mode 100644
index 0000000000..48422e909a
--- /dev/null
+++ b/drivers/dose/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/dose/dose.c b/drivers/dose/dose.c
new file mode 100644
index 0000000000..a7d220d933
--- /dev/null
+++ b/drivers/dose/dose.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2019 Juergen Fitschen
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup drivers_dose
+ * @{
+ *
+ * @file
+ * @brief Implementation of the Differentially Operated Serial Ethernet driver
+ *
+ * @author Juergen Fitschen
+ *
+ * @}
+ */
+
+#include
+
+#include "dose.h"
+#include "luid.h"
+#include "random.h"
+#include "irq.h"
+
+#include "net/netdev/eth.h"
+#include "net/eui64.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+static uint16_t crc16_update(uint16_t crc, uint8_t octet);
+static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal);
+static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal);
+static dose_signal_t state_transit_recv(dose_t *ctx, dose_signal_t signal);
+static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal);
+static void state(dose_t *ctx, uint8_t src);
+static void _isr_uart(void *arg, uint8_t c);
+static void _isr_gpio(void *arg);
+static void _isr_xtimer(void *arg);
+static void clear_recv_buf(dose_t *ctx);
+static void _isr(netdev_t *netdev);
+static int _recv(netdev_t *dev, void *buf, size_t len, void *info);
+static uint8_t wait_for_state(dose_t *ctx, uint8_t state);
+static int send_octet(dose_t *ctx, uint8_t c);
+static int _send(netdev_t *dev, const iolist_t *iolist);
+static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len);
+static int _set(netdev_t *dev, netopt_t opt, const void *value, size_t len);
+static int _init(netdev_t *dev);
+void dose_setup(dose_t *ctx, const dose_params_t *params);
+
+static uint16_t crc16_update(uint16_t crc, uint8_t octet)
+{
+ crc = (uint8_t)(crc >> 8) | (crc << 8);
+ crc ^= octet;
+ crc ^= (uint8_t)(crc & 0xff) >> 4;
+ crc ^= (crc << 8) << 4;
+ crc ^= ((crc & 0xff) << 4) << 1;
+ return crc;
+}
+
+static dose_signal_t state_transit_blocked(dose_t *ctx, dose_signal_t signal)
+{
+ uint32_t backoff;
+ (void) signal;
+
+ if (ctx->state == DOSE_STATE_RECV) {
+ /* We got here from RECV state. The driver's thread has to look
+ * if this frame should be processed. By queuing NETDEV_EVENT_ISR,
+ * the netif thread will call _isr at some time. */
+ SETBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY);
+ ctx->netdev.event_callback((netdev_t *) ctx, NETDEV_EVENT_ISR);
+ }
+
+ /* Enable GPIO interrupt for start bit sensing */
+ gpio_irq_enable(ctx->sense_pin);
+
+ /* The timeout will bring us back into IDLE state by a random time.
+ * If we entered this state from RECV state, the random time lays
+ * in the interval [1 * timeout, 2 * timeout]. If we came from
+ * SEND state, a time in the interval [2 * timeout, 3 * timeout]
+ * will be picked. This ensures that responding nodes get preferred
+ * bus access and sending nodes do not overwhelm listening nodes. */
+ if (ctx->state == DOSE_STATE_SEND) {
+ backoff = random_uint32_range(2 * ctx->timeout_base, 3 * ctx->timeout_base);
+ }
+ else {
+ backoff = random_uint32_range(1 * ctx->timeout_base, 2 * ctx->timeout_base);
+ }
+ xtimer_set(&ctx->timeout, backoff);
+
+ return DOSE_SIGNAL_NONE;
+}
+
+static dose_signal_t state_transit_idle(dose_t *ctx, dose_signal_t signal)
+{
+ (void) ctx;
+ (void) signal;
+
+ return DOSE_SIGNAL_NONE;
+}
+
+static dose_signal_t state_transit_recv(dose_t *ctx, dose_signal_t signal)
+{
+ dose_signal_t rc = DOSE_SIGNAL_NONE;
+
+ if (ctx->state != DOSE_STATE_RECV) {
+ /* We freshly entered this state. Thus, no start bit sensing is required
+ * anymore. Disable GPIO IRQs during the transmission. */
+ gpio_irq_disable(ctx->sense_pin);
+ }
+
+ if (signal == DOSE_SIGNAL_UART) {
+ /* We received a new octet */
+ int esc = (ctx->flags & DOSE_FLAG_ESC_RECEIVED);
+ if (!esc && ctx->uart_octet == DOSE_OCTECT_ESC) {
+ SETBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED);
+ }
+ else if (!esc && ctx->uart_octet == DOSE_OCTECT_END) {
+ SETBIT(ctx->flags, DOSE_FLAG_END_RECEIVED);
+ rc = DOSE_SIGNAL_END;
+ }
+ else {
+ if (esc) {
+ CLRBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED);
+ }
+ /* Since the dirty flag is set after the RECV state is left,
+ * it indicates that the receive buffer contains unprocessed data
+ * from a previously received frame. Thus, we just ignore new data. */
+ if (!(ctx->flags & DOSE_FLAG_RECV_BUF_DIRTY)
+ && ctx->recv_buf_ptr < DOSE_FRAME_LEN) {
+ ctx->recv_buf[ctx->recv_buf_ptr++] = ctx->uart_octet;
+ }
+ }
+ }
+
+ if (rc == DOSE_SIGNAL_NONE) {
+ /* No signal is returned. We stay in the RECV state. */
+ xtimer_set(&ctx->timeout, ctx->timeout_base);
+ }
+
+ return rc;
+}
+
+static dose_signal_t state_transit_send(dose_t *ctx, dose_signal_t signal)
+{
+ (void) signal;
+
+ if (ctx->state != DOSE_STATE_SEND) {
+ /* Disable GPIO IRQs during the transmission. */
+ gpio_irq_disable(ctx->sense_pin);
+ }
+
+ /* Don't trace any END octets ... the timeout or the END signal
+ * will bring us back to the BLOCKED state after _send has emitted
+ * its last octet. */
+
+ xtimer_set(&ctx->timeout, ctx->timeout_base);
+
+ return DOSE_SIGNAL_NONE;
+}
+
+static void state(dose_t *ctx, dose_signal_t signal)
+{
+ /* Make sure no other thread or ISR interrupts state transitions */
+ unsigned irq_state = irq_disable();
+
+ do {
+ /* The edges of the finite state machine can be identified by
+ * the current state and the signal that caused a state transition.
+ * Since the state only occupies the first 4 bits and the signal the
+ * last 4 bits of a uint8_t, they can be added together and hence
+ * be checked together. */
+ switch (ctx->state + signal) {
+ case DOSE_STATE_INIT + DOSE_SIGNAL_INIT:
+ case DOSE_STATE_RECV + DOSE_SIGNAL_END:
+ case DOSE_STATE_RECV + DOSE_SIGNAL_XTIMER:
+ case DOSE_STATE_SEND + DOSE_SIGNAL_END:
+ case DOSE_STATE_SEND + DOSE_SIGNAL_XTIMER:
+ signal = state_transit_blocked(ctx, signal);
+ ctx->state = DOSE_STATE_BLOCKED;
+ break;
+
+ case DOSE_STATE_BLOCKED + DOSE_SIGNAL_XTIMER:
+ signal = state_transit_idle(ctx, signal);
+ ctx->state = DOSE_STATE_IDLE;
+ break;
+
+ case DOSE_STATE_IDLE + DOSE_SIGNAL_GPIO:
+ case DOSE_STATE_IDLE + DOSE_SIGNAL_UART:
+ case DOSE_STATE_BLOCKED + DOSE_SIGNAL_GPIO:
+ case DOSE_STATE_BLOCKED + DOSE_SIGNAL_UART:
+ case DOSE_STATE_RECV + DOSE_SIGNAL_UART:
+ signal = state_transit_recv(ctx, signal);
+ ctx->state = DOSE_STATE_RECV;
+ break;
+
+ case DOSE_STATE_IDLE + DOSE_SIGNAL_SEND:
+ case DOSE_STATE_SEND + DOSE_SIGNAL_UART:
+ signal = state_transit_send(ctx, signal);
+ ctx->state = DOSE_STATE_SEND;
+ break;
+
+ default:
+ DEBUG("dose state(): unexpected state transition (STATE=0x%02d SIGNAL=0x%02d)\n", ctx->state, signal);
+ signal = DOSE_SIGNAL_NONE;
+ }
+ } while (signal != DOSE_SIGNAL_NONE);
+
+ /* Indicate state change by unlocking state mutex */
+ mutex_unlock(&ctx->state_mtx);
+ irq_restore(irq_state);
+}
+
+static void _isr_uart(void *arg, uint8_t c)
+{
+ dose_t *dev = (dose_t *) arg;
+
+ dev->uart_octet = c;
+ state(dev, DOSE_SIGNAL_UART);
+}
+
+static void _isr_gpio(void *arg)
+{
+ dose_t *dev = (dose_t *) arg;
+
+ state(dev, DOSE_SIGNAL_GPIO);
+}
+
+static void _isr_xtimer(void *arg)
+{
+ dose_t *dev = (dose_t *) arg;
+
+ state(dev, DOSE_SIGNAL_XTIMER);
+}
+
+static void clear_recv_buf(dose_t *ctx)
+{
+ unsigned irq_state = irq_disable();
+
+ ctx->recv_buf_ptr = 0;
+ CLRBIT(ctx->flags, DOSE_FLAG_RECV_BUF_DIRTY);
+ CLRBIT(ctx->flags, DOSE_FLAG_END_RECEIVED);
+ CLRBIT(ctx->flags, DOSE_FLAG_ESC_RECEIVED);
+ irq_restore(irq_state);
+}
+
+static void _isr(netdev_t *netdev)
+{
+ dose_t *ctx = (dose_t *) netdev;
+ unsigned irq_state;
+ int dirty, end;
+
+ /* Get current flags atomically */
+ irq_state = irq_disable();
+ dirty = (ctx->flags & DOSE_FLAG_RECV_BUF_DIRTY);
+ end = (ctx->flags & DOSE_FLAG_END_RECEIVED);
+ irq_restore(irq_state);
+
+ /* If the receive buffer does not contain any data just abort ... */
+ if (!dirty) {
+ DEBUG("dose _isr(): no frame -> drop\n");
+ return;
+ }
+
+ /* If we haven't received a valid END octet just drop the incomplete frame. */
+ if (!end) {
+ DEBUG("dose _isr(): incomplete frame -> drop\n");
+ clear_recv_buf(ctx);
+ return;
+ }
+
+ /* The set dirty flag prevents recv_buf or recv_buf_ptr from being
+ * touched in ISR context. Thus, it is safe to work with them without
+ * IRQs being disabled or mutexes being locked. */
+
+ /* Check for minimum length of an Ethernet packet */
+ if (ctx->recv_buf_ptr < sizeof(ethernet_hdr_t) + DOSE_FRAME_CRC_LEN) {
+ DEBUG("dose _isr(): frame too short -> drop\n");
+ clear_recv_buf(ctx);
+ return;
+ }
+
+ /* Check the dst mac addr if the iface is not in promiscuous mode */
+ if (!(ctx->opts & DOSE_OPT_PROMISCUOUS)) {
+ ethernet_hdr_t *hdr = (ethernet_hdr_t *) ctx->recv_buf;
+ if ((hdr->dst[0] & 0x1) == 0 && memcmp(hdr->dst, ctx->mac_addr.uint8, ETHERNET_ADDR_LEN) != 0) {
+ DEBUG("dose _isr(): dst mac not matching -> drop\n");
+ clear_recv_buf(ctx);
+ return;
+ }
+ }
+
+ /* Check the CRC */
+ uint16_t crc = 0xffff;
+ for (size_t i = 0; i < ctx->recv_buf_ptr; i++) {
+ crc = crc16_update(crc, ctx->recv_buf[i]);
+ }
+ if (crc != 0x0000) {
+ DEBUG("dose _isr(): wrong crc 0x%04x -> drop\n", crc);
+ clear_recv_buf(ctx);
+ return;
+ }
+
+ /* Finally schedule a _recv method call */
+ DEBUG("dose _isr(): NETDEV_EVENT_RX_COMPLETE\n");
+ ctx->netdev.event_callback((netdev_t *) ctx, NETDEV_EVENT_RX_COMPLETE);
+}
+
+static int _recv(netdev_t *dev, void *buf, size_t len, void *info)
+{
+ dose_t *ctx = (dose_t *) dev;
+
+ (void)info;
+
+ size_t pktlen = ctx->recv_buf_ptr - DOSE_FRAME_CRC_LEN;
+ if (!buf && !len) {
+ /* Return the amount of received bytes */
+ return pktlen;
+ }
+ else if (!buf && len) {
+ /* The user drops the packet */
+ clear_recv_buf(ctx);
+ return pktlen;
+ }
+ else if (len < pktlen) {
+ /* The provided buffer is too small! */
+ DEBUG("dose _recv(): receive buffer too small\n");
+ clear_recv_buf(ctx);
+ return -1;
+ }
+ else {
+ /* Copy the packet to the provided buffer. */
+ memcpy(buf, ctx->recv_buf, pktlen);
+ clear_recv_buf(ctx);
+ return pktlen;
+ }
+}
+
+static uint8_t wait_for_state(dose_t *ctx, uint8_t state)
+{
+ do {
+ /* This mutex is unlocked by the state machine
+ * after every state transition */
+ mutex_lock(&ctx->state_mtx);
+ } while (state != DOSE_STATE_ANY && ctx->state != state);
+ return ctx->state;
+}
+
+static int send_octet(dose_t *ctx, uint8_t c)
+{
+ uart_write(ctx->uart, (uint8_t *) &c, sizeof(c));
+
+ /* Wait for a state transition */
+ uint8_t state = wait_for_state(ctx, DOSE_STATE_ANY);
+ if (state != DOSE_STATE_SEND) {
+ /* Timeout */
+ DEBUG("dose send_octet(): timeout\n");
+ return -2;
+ }
+ else if (ctx->uart_octet != c) {
+ /* Mismatch */
+ DEBUG("dose send_octet(): mismatch\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int send_data_octet(dose_t *ctx, uint8_t c)
+{
+ int rc;
+
+ /* Escape special octets */
+ if (c == DOSE_OCTECT_ESC || c == DOSE_OCTECT_END) {
+ rc = send_octet(ctx, DOSE_OCTECT_ESC);
+ if (rc) {
+ return rc;
+ }
+ }
+
+ /* Send data octet */
+ rc = send_octet(ctx, c);
+
+ return rc;
+}
+
+static int _send(netdev_t *dev, const iolist_t *iolist)
+{
+ dose_t *ctx = (dose_t *) dev;
+ int8_t retries = 3;
+ size_t pktlen;
+ uint16_t crc;
+
+send:
+ crc = 0xffff;
+ pktlen = 0;
+
+ /* Switch to state SEND */
+ do {
+ wait_for_state(ctx, DOSE_STATE_IDLE);
+ state(ctx, DOSE_SIGNAL_SEND);
+ } while (wait_for_state(ctx, DOSE_STATE_ANY) != DOSE_STATE_SEND);
+
+ /* Send packet buffer */
+ for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) {
+ size_t n = iol->iol_len;
+ pktlen += n;
+ uint8_t *ptr = iol->iol_base;
+ while (n--) {
+ /* Send data octet */
+ if (send_data_octet(ctx, *ptr)) {
+ goto collision;
+ }
+
+ /* Update CRC */
+ crc = crc16_update(crc, *ptr);
+
+ ptr++;
+ }
+ }
+
+ /* Send CRC */
+ network_uint16_t crc_nw = byteorder_htons(crc);
+ if (send_data_octet(ctx, crc_nw.u8[0])) {
+ goto collision;
+ }
+ if (send_data_octet(ctx, crc_nw.u8[1])) {
+ goto collision;
+ }
+
+ /* Send END octet */
+ if (send_octet(ctx, DOSE_OCTECT_END)) {
+ goto collision;
+ }
+
+ /* We probably sent the whole packet?! */
+ ctx->netdev.event_callback((netdev_t *) ctx, NETDEV_EVENT_TX_COMPLETE);
+
+ /* Get out of the SEND state */
+ state(ctx, DOSE_SIGNAL_END);
+
+ return pktlen;
+
+collision:
+ DEBUG("dose _send(): collision!\n");
+ if (--retries < 0) {
+ ctx->netdev.event_callback((netdev_t *) ctx, NETDEV_EVENT_TX_MEDIUM_BUSY);
+ return 0;
+ }
+ goto send;
+}
+
+static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len)
+{
+ dose_t *ctx = (dose_t *) dev;
+
+ switch (opt) {
+ case NETOPT_ADDRESS:
+ if (max_len < ETHERNET_ADDR_LEN) {
+ return -EINVAL;
+ }
+ memcpy(value, ctx->mac_addr.uint8, ETHERNET_ADDR_LEN);
+ return ETHERNET_ADDR_LEN;
+ case NETOPT_PROMISCUOUSMODE:
+ if (max_len < sizeof(netopt_enable_t)) {
+ return -EINVAL;
+ }
+ if (ctx->opts & DOSE_OPT_PROMISCUOUS) {
+ *((netopt_enable_t *)value) = NETOPT_ENABLE;
+ }
+ else {
+ *((netopt_enable_t *)value) = NETOPT_DISABLE;
+ }
+ return sizeof(netopt_enable_t);
+ default:
+ return netdev_eth_get(dev, opt, value, max_len);
+ }
+
+ return 0;
+}
+
+static int _set(netdev_t *dev, netopt_t opt, const void *value, size_t len)
+{
+ dose_t *ctx = (dose_t *) dev;
+
+ switch (opt) {
+ case NETOPT_PROMISCUOUSMODE:
+ if (len < sizeof(netopt_enable_t)) {
+ return -EINVAL;
+ }
+ if (((const bool *)value)[0]) {
+ SETBIT(ctx->opts, DOSE_OPT_PROMISCUOUS);
+ }
+ else {
+ CLRBIT(ctx->opts, DOSE_OPT_PROMISCUOUS);
+ }
+ return sizeof(netopt_enable_t);
+ default:
+ return netdev_eth_set(dev, opt, value, len);
+ }
+
+ return 0;
+}
+
+static int _init(netdev_t *dev)
+{
+ dose_t *ctx = (dose_t *) dev;
+ unsigned irq_state;
+
+ /* Set state machine to defaults */
+ irq_state = irq_disable();
+ ctx->opts = 0;
+ ctx->recv_buf_ptr = 0;
+ ctx->flags = 0;
+ ctx->state = DOSE_STATE_INIT;
+ irq_restore(irq_state);
+
+ state(ctx, DOSE_SIGNAL_INIT);
+
+ return 0;
+}
+
+static const netdev_driver_t netdev_driver_dose = {
+ .send = _send,
+ .recv = _recv,
+ .init = _init,
+ .isr = _isr,
+ .get = _get,
+ .set = _set
+};
+
+void dose_setup(dose_t *ctx, const dose_params_t *params)
+{
+ static const xtimer_ticks32_t min_timeout = {.ticks32 = XTIMER_BACKOFF + XTIMER_OVERHEAD};
+
+ ctx->netdev.driver = &netdev_driver_dose;
+
+ mutex_init(&ctx->state_mtx);
+
+ ctx->uart = params->uart;
+ uart_init(ctx->uart, params->baudrate, _isr_uart, (void *) ctx);
+
+ ctx->sense_pin = params->sense_pin;
+ gpio_init_int(ctx->sense_pin, GPIO_IN, GPIO_FALLING, _isr_gpio, (void *) ctx);
+ gpio_irq_disable(ctx->sense_pin);
+
+ assert(sizeof(ctx->mac_addr.uint8) == ETHERNET_ADDR_LEN);
+ luid_get_eui48(&ctx->mac_addr);
+ DEBUG("dose dose_setup(): mac addr %02x:%02x:%02x:%02x:%02x:%02x\n",
+ ctx->mac_addr.uint8[0], ctx->mac_addr.uint8[1], ctx->mac_addr.uint8[2],
+ ctx->mac_addr.uint8[3], ctx->mac_addr.uint8[4], ctx->mac_addr.uint8[5]
+ );
+
+ /* The timeout base is the minimal timeout base used for this driver.
+ * We have to ensure it is above the XTIMER_BACKOFF. Otherwise state
+ * transitions are triggered from another state transition setting up the
+ * timeout. */
+ ctx->timeout_base = DOSE_TIMEOUT_USEC;
+ if (ctx->timeout_base < xtimer_usec_from_ticks(min_timeout)) {
+ ctx->timeout_base = xtimer_usec_from_ticks(min_timeout);
+ }
+ ctx->timeout.callback = _isr_xtimer;
+ ctx->timeout.arg = ctx;
+}
diff --git a/drivers/dose/include/dose_params.h b/drivers/dose/include/dose_params.h
new file mode 100644
index 0000000000..b3b972883f
--- /dev/null
+++ b/drivers/dose/include/dose_params.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 Juergen Fitschen
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ *
+ */
+
+/**
+ * @ingroup drivers_dose
+ * @{
+ * @file
+ * @brief Default configuration for the Differentially Operated Serial Ethernet driver
+ *
+ * @author Juergen Fitschen
+ */
+
+#ifndef DOSE_PARAMS_H
+#define DOSE_PARAMS_H
+
+#include "board.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @name Set default configuration parameters for the DOSE driver
+ * @{
+ */
+#ifndef DOSE_PARAM_UART
+#define DOSE_PARAM_UART (UART_DEV(0))
+#endif
+#ifndef DOSE_PARAM_BAUDRATE
+#define DOSE_PARAM_BAUDRATE (115200)
+#endif
+#ifndef DOSE_PARAM_SENSE_PIN
+#define DOSE_PARAM_SENSE_PIN (GPIO_PIN(0, 0))
+#endif
+
+#ifndef DOSE_PARAMS
+#define DOSE_PARAMS { .uart = DOSE_PARAM_UART, \
+ .baudrate = DOSE_PARAM_BAUDRATE, \
+ .sense_pin = DOSE_PARAM_SENSE_PIN }
+#endif
+/**@}*/
+
+/**
+ * @brief DOSE configuration
+ */
+static const dose_params_t dose_params[] =
+{
+ DOSE_PARAMS
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DOSE_PARAMS_H */
+/** @} */
diff --git a/drivers/include/dose.h b/drivers/include/dose.h
new file mode 100644
index 0000000000..ba0ee6af74
--- /dev/null
+++ b/drivers/include/dose.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 Juergen Fitschen
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+/**
+ * @defgroup drivers_dose Differentially Operated Serial Ethernet
+ * @ingroup drivers_netdev
+ * @brief Driver for connecting RIOT devices using a single bus wire
+ *
+ * About
+ * =====
+ *
+ * This driver enables RIOT nodes to communicate by Ethernet over a serial bus.
+ * This enables them to interact in an easy and cheap manner using a single
+ * bus wire with very low hardware requirements: The used microcontrollers just
+ * need to feature at least one UART and one GPIO that is able to raise
+ * interrupts.
+ *
+ * Wiring
+ * ======
+ *
+ * ![DOSE wiring](dose-wiring.svg)
+ *
+ * For bus access, you need a CAN transceiver, since the DOSE uses the PHY layer
+ * of CAN for the electrical connection of the nodes. Every transceiver IC
+ * operating with the right voltage levels should do. (If you are on a 3.3V MCU,
+ * you could use an IC such as the SN65HVD233.)
+ *
+ * Basically, UART TX and RX are connected to respective pins of the
+ * transceiver. In addition, the RX pin is also connected to the sense GPIO.
+ * It is used to detect bus allocation.
+ *
+ * How it works
+ * ============
+ *
+ * Some technical details for those interested: The Ethernet frames are sent
+ * onto the bus using `uart_write()` while observing the received echo from
+ * the bus. This way collisions are detected (received echo != transmitted
+ * octet) and retransmissions are scheduled. The frames are appended with a
+ * CRC16 to protect the system from transmission errors.
+ *
+ * @{
+ *
+ * @file
+ * @brief Driver for the Differentially Operated Serial Ethernet module
+ *
+ * @author Juergen Fitschen
+ */
+
+#ifndef DOSE_H
+#define DOSE_H
+
+#include "periph/uart.h"
+#include "periph/gpio.h"
+#include "net/netdev.h"
+#include "net/ethernet.h"
+#include "net/eui48.h"
+#include "bitarithm.h"
+#include "mutex.h"
+#include "xtimer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @name Escape octet definitions
+ * @{
+ */
+#define DOSE_OCTECT_END (0xFF) /**< Magic octet indicating the end of frame */
+#define DOSE_OCTECT_ESC (0xFE) /**< Magic octet escaping 0xFF in byte stream */
+/** @} */
+
+/**
+ * @name State definitions
+ * @brief The drivers internal state that is hold in dose_t.state
+ */
+typedef enum {
+ DOSE_STATE_INIT = 0x00, /**< Initial state that will never be reentered */
+ DOSE_STATE_BLOCKED = 0x01, /**< The driver just listens to incoming frames and blocks outgress frames */
+ DOSE_STATE_IDLE = 0x02, /**< Frames will be received or sent */
+ DOSE_STATE_RECV = 0x03, /**< Currently receiving a frame */
+ DOSE_STATE_SEND = 0x04, /**< Currently sending a frame */
+ DOSE_STATE_ANY = 0x0F /**< Special state filter used internally to observe any state transition */
+} dose_state_t;
+
+
+/**
+ * @name Signal definitions
+ * @brief A signal controls the state machine and may cause a state transition
+ */
+typedef enum {
+ DOSE_SIGNAL_NONE = 0x00, /**< No signal ... */
+ DOSE_SIGNAL_INIT = 0x10, /**< Init the state machine */
+ DOSE_SIGNAL_GPIO = 0x20, /**< Sense GPIO detected a falling edge */
+ DOSE_SIGNAL_UART = 0x30, /**< Octet has been received */
+ DOSE_SIGNAL_XTIMER = 0x40, /**< Timer timed out */
+ DOSE_SIGNAL_SEND = 0x50, /**< Enter send state */
+ DOSE_SIGNAL_END = 0x60 /**< Leave send state */
+} dose_signal_t;
+
+
+
+/**
+ * @name Flag definitions
+ * @brief Hold in dose_t.flags
+ * @{
+ */
+#define DOSE_FLAG_RECV_BUF_DIRTY (BIT0) /**< Receive buffer contains a complete unhandled frame */
+#define DOSE_FLAG_END_RECEIVED (BIT1) /**< END octet has been received */
+#define DOSE_FLAG_ESC_RECEIVED (BIT2) /**< ESC octet has been received */
+/** @} */
+
+/**
+ * @name Opt definitions
+ * @brief Hold in dose_t.opts
+ * @{
+ */
+#define DOSE_OPT_PROMISCUOUS (BIT0) /**< Don't check the destination MAC - pass every frame to upper layers */
+/** @} */
+
+#ifndef DOSE_TIMEOUT_USEC
+#define DOSE_TIMEOUT_USEC (5000) /**< Timeout that brings the driver back into idle state if the remote side died within a transaction */
+#endif
+
+#define DOSE_FRAME_CRC_LEN (2) /**< CRC16 is used */
+#define DOSE_FRAME_LEN (ETHERNET_FRAME_LEN + DOSE_FRAME_CRC_LEN) /**< dose frame length */
+
+/**
+ * @brief DOSE netdev device
+ * @extends netdev_t
+ */
+typedef struct {
+ netdev_t netdev; /**< Extended netdev structure */
+ eui48_t mac_addr; /**< This device's MAC address */
+ uint8_t opts; /**< Driver options */
+ dose_state_t state; /**< Current state of the driver's state machine */
+ mutex_t state_mtx; /**< Is unlocked every time a state is (re)entered */
+ uint8_t flags; /**< Several flags */
+ uint8_t recv_buf[DOSE_FRAME_LEN]; /**< Receive buffer for incoming frames */
+ size_t recv_buf_ptr; /**< Index of the next empty octet of the recveive buffer */
+ uart_t uart; /**< UART device to use */
+ uint8_t uart_octet; /**< Last received octet */
+ gpio_t sense_pin; /**< GPIO to sense for start bits on the UART's rx line */
+ xtimer_t timeout; /**< Timeout timer ensuring always to get back to IDLE state */
+ uint32_t timeout_base; /**< Base timeout in us */
+} dose_t;
+
+/**
+ * @brief Struct containing the required configuration
+ */
+typedef struct {
+ uart_t uart; /**< UART device to use */
+ gpio_t sense_pin; /**< GPIO to sense for start bits on the UART's rx line */
+ uint32_t baudrate; /**< Baudrate to UART device */
+} dose_params_t;
+
+/**
+ * @brief Setup a DOSE based device state
+ * @param[out] dev Handle of the device to initialize
+ * @param[in] params Parameters for device initialization
+ */
+void dose_setup(dose_t *dev, const dose_params_t *params);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* DOSE_H */
+/** @} */
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index 8ae5d6fcd0..5d3e152f24 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -271,6 +271,11 @@ void auto_init(void)
auto_init_ethos();
#endif
+#ifdef MODULE_DOSE
+ extern void auto_init_dose(void);
+ auto_init_dose();
+#endif
+
#ifdef MODULE_SLIPDEV
extern void auto_init_slipdev(void);
auto_init_slipdev();
diff --git a/sys/auto_init/netif/auto_init_dose.c b/sys/auto_init/netif/auto_init_dose.c
new file mode 100644
index 0000000000..f6b61c3b4d
--- /dev/null
+++ b/sys/auto_init/netif/auto_init_dose.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 Juergen Fitschen
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ *
+ */
+
+/**
+ * @ingroup sys_auto_init_gnrc_netif
+ * @{
+ *
+ * @file
+ * @brief Auto initialization for Differentially Operated Serial Ethernet module
+ *
+ * @author Juergen Fitschen
+ */
+
+#ifdef MODULE_DOSE
+
+#include "log.h"
+#include "debug.h"
+#include "dose.h"
+#include "dose_params.h"
+#include "net/gnrc/netif/ethernet.h"
+
+/**
+ * @brief Define stack parameters for the MAC layer thread
+ * @{
+ */
+#define DOSE_MAC_STACKSIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE)
+#ifndef DOSE_MAC_PRIO
+#define DOSE_MAC_PRIO (GNRC_NETIF_PRIO)
+#endif
+
+#define DOSE_NUM ARRAY_SIZE(dose_params)
+
+static char _netdev_eth_stack[DOSE_NUM][DOSE_MAC_STACKSIZE];
+static dose_t dose[DOSE_NUM];
+
+void auto_init_dose(void)
+{
+ /* setup netdev devices */
+ for (unsigned i = 0; i < DOSE_NUM; i++) {
+ LOG_DEBUG("[auto_init_netif] initializing dose #%d.\n", i);
+
+ dose_setup(&dose[i], &dose_params[i]);
+ gnrc_netif_ethernet_create(_netdev_eth_stack[i], DOSE_MAC_STACKSIZE,
+ DOSE_MAC_PRIO, "dose", (netdev_t *)&dose[i]);
+ }
+}
+
+#else
+typedef int dont_be_pedantic;
+#endif /* MODULE_DOSE */
+/** @} */