Skip to content

File baton_link.c

File List > src > baton_link.c

Go to the documentation of this file

/*******************************************************************************
 * Baton-C: Deterministic Master/Slave Messaging over a Single-Wire UART
 *
 * Project:     Baton-C - Deterministic Master/Slave Messaging over a Single-Wire UART
 * Description: Half-duplex link: receive ring, parser with inter-byte timeout,
 *              transmitter with bus-gap wait, echo check and timeout
 *
 * Author:      Anton Vynohradov
 * Email:       avynohradov@systemfromscratch.com
 *
 * License:     Apache License 2.0
 *
 * Copyright (c) 2026 Anton Vynohradov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 ******************************************************************************/

/* ==============================================================================
 * INCLUDES
 * =============================================================================*/

#include <string.h>

#include "baton_link.h"

/* ==============================================================================
 * DEFINES & MACROS
 * =============================================================================*/

#define TX_IDLE 0U
#define TX_WAIT_GAP 1U
#define TX_BUSY 2U

#define RX_MASK (BATON_RX_BUF_SIZE - 1U)

#define TX_TIMEOUT_MARGIN_MS 5U

/* ==============================================================================
 * PRIVATE FUNCTION DECLARATIONS (static)
 * =============================================================================*/

static int tx_finish(struct baton_link* link, uint32_t now_ms);
static int rx_parse(struct baton_link* link, uint8_t byte, uint32_t now_ms);
static bool rx_echo(struct baton_link* link, uint8_t byte, uint32_t now_ms);
static int tx_start(struct baton_link* link, uint32_t now_ms);

/* ==============================================================================
 * PRIVATE FUNCTION IMPLEMENTATIONS
 * =============================================================================*/

static int tx_finish(struct baton_link* link, uint32_t now_ms)
{
    link->tx_state = TX_IDLE;
    link->last_tx_end_ms = now_ms;
    if (link->echo_error)
    {
        link->counters.collisions++;
    }

    return BATON_LINK_TX_DONE;
}

static int rx_parse(struct baton_link* link, uint8_t byte, uint32_t now_ms)
{
    int event = BATON_LINK_NONE;
    int parsed;

    if (((uint32_t) (now_ms - link->last_rx_ms) > (uint32_t) link->config.inter_byte_timeout_ms)
        && (link->parser.state != BATON_PARSER_IDLE))
    {
        link->counters.rx_timeouts++;
        baton_parser_init(&link->parser);
    }
    link->last_rx_ms = now_ms;

    parsed = baton_parser_feed(&link->parser, byte);
    if (parsed > 0)
    {
        link->counters.rx_frames++;
        event = BATON_LINK_FRAME;
    }
    else if (parsed < 0)
    {
        link->counters.crc_errors++;
    }
    else
    {
        /* frame incomplete: wait for more bytes */
    }

    return event;
}

static bool rx_echo(struct baton_link* link, uint8_t byte, uint32_t now_ms)
{
    bool complete = false;

    if (byte != link->tx_data[link->echo_index])
    {
        link->echo_error = true;
    }
    link->echo_index++;
    if (link->echo_index >= link->tx_length)
    {
        (void) tx_finish(link, now_ms);
        complete = true;
    }

    return complete;
}

static int tx_start(struct baton_link* link, uint32_t now_ms)
{
    int event = BATON_LINK_NONE;
    const uint32_t gap = (uint32_t) link->config.tx_gap_ms;

    if ((!baton_link_rx_busy(link, now_ms)) && ((uint32_t) (now_ms - link->last_rx_ms) >= gap)
        && ((uint32_t) (now_ms - link->last_tx_end_ms) >= gap))
    {
        link->tx_state = TX_BUSY;
        link->tx_done_flag = false;
        link->echo_index = 0U;
        link->echo_error = false;
        link->tx_start_ms = now_ms;
        link->counters.tx_frames++;
        if (link->config.write(link->config.context, link->tx_data, (size_t) link->tx_length) < 0)
        {
            link->counters.tx_timeouts++;
            event = tx_finish(link, now_ms);
        }
    }

    return event;
}

/* ==============================================================================
 * PUBLIC FUNCTION IMPLEMENTATIONS
 * =============================================================================*/

int baton_link_init(struct baton_link* link, const struct baton_link_config* config,
                    uint32_t now_ms)
{
    int result = BATON_PARSE_ERROR;

    if ((link != NULL) && (config != NULL) && (config->write != NULL) && (config->baudrate != 0U)
        && (config->inter_byte_timeout_ms != 0U))
    {
        (void) memset(link, 0, sizeof(*link));
        link->config = *config;
        baton_parser_init(&link->parser);
        link->tx_state = TX_IDLE;
        link->last_rx_ms = now_ms;
        link->last_tx_end_ms = now_ms;
        result = 0;
    }

    return result;
}

void baton_link_rx_isr(struct baton_link* link, uint8_t byte)
{
    if (link != NULL)
    {
        const uint16_t head = link->rx_head;
        uint16_t next = head;

        next++;
        next &= RX_MASK;

        if (next == link->rx_tail)
        {
            link->rx_overflows++;
        }
        else
        {
            link->rx_buffer[head] = byte;
            link->rx_head = next;
        }
    }
}

void baton_link_tx_done_isr(struct baton_link* link)
{
    if (link != NULL)
    {
        link->tx_done_flag = true;
    }
}

int baton_link_receive(struct baton_link* link, uint32_t now_ms)
{
    int event = BATON_LINK_NONE;

    if (link != NULL)
    {
        uint16_t tail = link->rx_tail;
        bool done = false;

        while ((!done) && (tail != link->rx_head))
        {
            const uint8_t byte = link->rx_buffer[tail];

            tail++;
            tail &= RX_MASK;
            link->rx_tail = tail;

            /* While TX_BUSY, echo_index < tx_length: the last echo byte ends the transmission. */
            if ((link->tx_state == TX_BUSY) && link->config.rx_echo)
            {
                if (rx_echo(link, byte, now_ms))
                {
                    event = BATON_LINK_TX_DONE;
                    done = true;
                }
            }
            else
            {
                event = rx_parse(link, byte, now_ms);
                done = (event != BATON_LINK_NONE);
            }
        }
    }

    return event;
}

int baton_link_transmit(struct baton_link* link, uint32_t now_ms)
{
    int event = BATON_LINK_NONE;

    if (link != NULL)
    {
        if (link->tx_state == TX_WAIT_GAP)
        {
            event = tx_start(link, now_ms);
        }
        else if (link->tx_state == TX_BUSY)
        {
            const uint32_t limit = baton_link_frame_ms(link, (uint32_t) link->tx_length)
                                   + (uint32_t) link->config.tx_gap_ms + TX_TIMEOUT_MARGIN_MS;

            if ((!link->config.rx_echo) && link->tx_done_flag)
            {
                event = tx_finish(link, now_ms);
            }
            else if ((uint32_t) (now_ms - link->tx_start_ms) > limit)
            {
                link->counters.tx_timeouts++;
                event = tx_finish(link, now_ms);
            }
            else
            {
                /* still transmitting */
            }
        }
        else
        {
            /* idle */
        }
    }

    return event;
}

int baton_link_send(struct baton_link* link, const uint8_t* data, size_t length)
{
    int result = BATON_PARSE_ERROR;

    if ((link != NULL) && (data != NULL) && (length > 0U) && (length <= BATON_FRAME_MAX))
    {
        link->tx_data = data;
        link->tx_length = (uint16_t) length;
        link->tx_state = TX_WAIT_GAP;
        result = 0;
    }

    return result;
}

void baton_link_cancel(struct baton_link* link)
{
    if ((link != NULL) && (link->tx_state == TX_WAIT_GAP))
    {
        link->tx_state = TX_IDLE;
    }
}

const struct baton_frame* baton_link_frame(const struct baton_link* link)
{
    const struct baton_frame* frame = NULL;

    if (link != NULL)
    {
        frame = &link->parser.frame;
    }

    return frame;
}

bool baton_link_rx_busy(const struct baton_link* link, uint32_t now_ms)
{
    bool busy = false;

    if (link != NULL)
    {
        busy = (link->parser.state != BATON_PARSER_IDLE)
               && ((uint32_t) (now_ms - link->last_rx_ms)
                   <= (uint32_t) link->config.inter_byte_timeout_ms);
    }

    return busy;
}

uint32_t baton_link_frame_ms(const struct baton_link* link, uint32_t bytes)
{
    uint32_t ms = 0U;

    if (link != NULL)
    {
        ms = ((bytes * 10000U) + link->config.baudrate - 1U) / link->config.baudrate;
    }

    return ms;
}