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 @@ + + + + + + + + + + + MCU 1 + UART TXUART RXGPIO SENSE + + +TXRX +CANHCANL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MCU 2 + UART TXUART RXGPIO SENSE + + +TXRX +CANHCANL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MCU n + UART TXUART RXGPIO SENSE + + +TXRX +CANHCANL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +120 +120 + 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 */ +/** @} */