UART task

this module manages the UART connection to the PSOC, it is responsible for sending and receiving data from the PSOC in the form of packets.

its interface is primarily in the higher level "command.h" module, which provides a set of functions to send and receive packets, which works as a form of RPC between the PSOC and the ESP32.

the packet format is implemented by TinyFrame, for more information on the packet format, see the TinyFrame documentation.

Public Types

uart_channel_t

a simple enum to describe the UART channels.

typedef enum {
    UART_CHANNEL_PSOC = 0,
} uart_channel_t;

async_callback

a callback function type, called when a response is received from the PSOC. a simplification of the callbacks dispatched by tinyframe, rather than having seperate callbacks for timeouts and responses, we have a single callback which is called with a flag indicating if their was a timeout if their is a timeout, resp will be NULL, and resp_len will be 0.

typedef void (*async_callback)(void* user_data, bool timeout, const uint8_t* resp, size_t resp_len);

Public Variables

uart_enabled

a boolean flag which if set to false, will disable reading and writing from the UART by tinyframe, aswell as make all RPC calls in the command module return a busy error.

bool uart_enabled = true;

Public Functions

TF_WriteImpl

interface function with tinyframe, writes a buffer to the UART.

void TF_WriteImpl(TinyFrame* tf, const uint8_t* buff, uint32_t len)
{
    if(uart_enabled)
    {
        channel_info_t* channel_info = (channel_info_t*)tf->userdata;
        uart_write_bytes(channel_info->channel, buff, len);
    }
}

TF_ClaimTx

interface function with tinyframe, claims the UART semaphore. the command module can be called from multiple tasks, running on different cores, so this semaphore is used to prevent packet corruption.

/** Claim the TX interface before composing and sending a frame */
bool TF_ClaimTx(TinyFrame* tf)
{
    channel_info_t* channel_info = (channel_info_t*)tf->userdata;
    return xSemaphoreTake(channel_info->mutex, 200) == pdTRUE;
}

TF_ReleaseTx

interface function with tinyframe, releases the UART semaphore.

/** Free the TX interface after composing and sending a frame */
void TF_ReleaseTx(TinyFrame* tf)
{
    channel_info_t* channel_info = (channel_info_t*)tf->userdata;
    xSemaphoreGive(channel_info->mutex);
}

TF_CksumStart

interface function with tinyframe, starts the checksum calculation. based on crc16-ccitt, specificaly from the CRC-16 in the UTILS module.

MUST MATCH THE CRC16-CCITT IMPLEMENTATION IN THE PSOC.

/** Initialize a checksum */
TF_CKSUM TF_CksumStart(void)
{
    return 0xFFFF;
}

TF_CksumAdd

interface function with tinyframe, adds a byte to the checksum.

MUST MATCH THE CRC16-CCITT IMPLEMENTATION IN THE PSOC.

/** Update a checksum with a byte */
TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{
    int i;
    int xor_flag;

    /* For each bit in the data byte, starting from the leftmost bit */
    for (i = 7; i >= 0; i--) {
        /* If leftmost bit of the CRC is 1, we will XOR with
         * the polynomial later */
        xor_flag = cksum & 0x8000;

        /* Shift the CRC, and append the next bit of the
         * message to the rightmost side of the CRC */
        cksum <<= 1;
        cksum |= (byte & (1 << i)) ? 1 : 0;

        /* Perform the XOR with the polynomial */
        if (xor_flag)
            cksum ^= 0x1021;
    }

    return cksum;
}

TF_CksumEnd

interface function with tinyframe, ends the checksum calculation.

MUST MATCH THE CRC16-CCITT IMPLEMENTATION IN THE PSOC.

/** Finalize the checksum calculation */
TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{
    int i;

    /* Augment 16 zero-bits */
    for (i = 0; i < 2; i++) {
        cksum = TF_CksumAdd(cksum, 0);
    }

    return cksum;
}

uart_task

the main task for the UART module, registered with FreeRTOS externaly. calls the uart_tick function in a loop, and resets the watchdog timer.

void uart_task(void* arg)
{
    while (true)
    {
        uart_tick();
        esp_task_wdt_reset();
        vTaskDelay(1);
    }
}

uart_task_init

initializes the UART module. sets up GPIO and TinyFrame for each uart channel. uart channels are described in the channels array as channel_info_t structs.

void uart_task_init(void)
{
    size_t n_channels = sizeof(channels) / sizeof(channel_info_t);
    for (int n = 0; n < n_channels; n++)
    {
        uart_init_channel(&channels[n]);
    }
}

send_response

sends a response packet to the PSOC. a response packet is a packet with the same frame id as the request packet, and the response flag set.

void send_response(uart_channel_t channel, uint8_t id, const uint8_t* buffer, size_t length)
{
    if(!uart_enabled){
        return;
    }
    TF_Msg msg = {
        .frame_id = id,
        .is_response = true,
        .data = buffer,
        .len = length,
        .type = PACKET_TYPE_RESP,
    };
    TF_Respond(&channels[channel].handler, &msg);
}

uart_send_async

sends a command packet to the PSOC, takes as an argument a async_callback callback, which will be called when the response is received, or when the timeout is reached.

void uart_send_async(uart_channel_t channel, packet_cmd_t command, const uint8_t* data, size_t data_len, async_callback cb, void* user_data, uint32_t timeout)
{
    if(!uart_enabled){
        return;
    }
    TF_Msg message = {
        .data = data,
        .len = data_len,
        .type = PACKET_TYPE_CMD,
        .userdata = cb,
        .userdata2 = user_data,
    };

    if(cb){
        TF_Query(&channels[channel].handler, &message, uart_send_litsener, uart_timeout_litsener, timeout);
    }
    else{
        TF_Send(&channels[channel].handler, &message);
    }
}

uart_send

sends a command packet to the PSOC, and blocks waiting for a response. uses xTaskNotifyWait and xTaskNotify to wait for the response. not currently used, so testing is needed before use.

bool uart_send(uart_channel_t channel, packet_cmd_t command, const uint8_t* data, size_t data_len, uint8_t* resp, size_t resp_len, uint32_t timeout)
{
    if(!uart_enabled){
        return false;
    }
    sync_args_t args = {
        .response = resp,
        .response_length = resp_len,
        .handle = xTaskGetCurrentTaskHandle(),
    };

    TF_Msg message = {
        .data = data,
        .len = data_len,
        .type = PACKET_TYPE_CMD,
        .userdata = &args,
        .userdata2 = NULL,
    };

    TF_Query(&channels[channel].handler, &message, uart_sync_send_litsener, NULL, timeout);

    if (xTaskNotifyWait(0, 0, NULL, timeout) == pdFALSE)
    {
        TF_RemoveIdListener(&channels[channel].handler, message.frame_id);
        return false;
    }
    return args.response;
}

Private Types

channel_info_t

a struct which holds the information for the configuration of a UART channel.

typedef struct
{
    uint location; // location of this channel in the channels struct, usefull for callbacks
    uart_port_t channel; // the uart controller to use
    uart_config_t hw_config; // the hardware configuration for the uart controller
    gpio_num_t rx_pin; // the rx pin for the uart controller
    gpio_num_t tx_pin; // the tx pin for the uart controller
    QueueHandle_t mutex; // mutex for use by tinyframe
    // storing this in the channel info means that we can send
    // messages on multiple UART channels at the same time

    TF_Peer mode; // whether the ESP is the master or slave on this chnnel
    // we support both, so if we ever need the ESP to be a slave, we can
    TinyFrame handler; // the tinyframe handler for this channel
    // again, storing this per channel means we can simultaneously talk
    // to multiple devices
} channel_info_t;

sync_args_t

a struct which holds the arguments for the uart_send function (synchronous sending). used to pass the task handle response buffer and maximum response length to the callback.

as noted above, currently not used, so testing is needed before use.

currently this is allocated on the stack, which SHOULD BE fine because the callback SHOULD BE called before the function returns. however, this is a potential source of bugs, and should be reconsidered in the future.

typedef struct {
    TaskHandle_t handle;
    void* response;
    size_t response_length;
} sync_args_t;

Private Variables

channels

an array of channel_info_t structs, each struct describes a UART channel, currently their is only one channel for the PSOC. in the future if possible, we should move the debug UART to use this module. because then we can send structured binary data to the debug UART, not just log messages.

channel_info_t channels[] = {
    // psoc channel
    {
        .location = 0,
        .channel = UART_NUM_1,
        .rx_pin = PIN_PSOC_RX,
        .tx_pin = PIN_PSOC_TX,
        .hw_config =
            {
                .baud_rate = 115200,
                .data_bits = UART_DATA_8_BITS,
                .parity = UART_PARITY_DISABLE,
                .stop_bits = UART_STOP_BITS_1,
                .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
                .source_clk = UART_SCLK_APB,
            },
        .mutex = NULL,
        .mode = TF_MASTER,
        .handler = {0},
    }
};

Private Functions

uart_tick_channel

called by uart_tick for each channel, if uart is not enabled, it will return early. reads any data from the UART, and sends it to tinyframe. then ticks tinyframe so it can dispatch any callbacks.

static void uart_tick_channel(channel_info_t* channel)
{
    if(!uart_enabled){
        return;
    }
    size_t available = 0;
    uart_get_buffered_data_len(channel->channel, &available);
    uint8_t buf[64] = {0};
    if (available)
    {
        size_t read = uart_read_bytes(channel->channel, buf, MIN(available, sizeof(buf)), 0);
        TF_Accept(&channel->handler, buf, read);
    }
    TF_Tick(&channel->handler);
}

static TF_Result uart_send_litsener(TinyFrame tf, TF_Msg msg){ async_callback callback = (async_callback) msg->userdata; if(callback){ callback(msg->userdata2, false, msg->data, msg->len); } return TF_CLOSE; } called by the uart_task in a loop, ticks all the channels.

static void uart_tick()
{
    size_t n_channels = sizeof(channels) / sizeof(channel_info_t);
    for (int n = 0; n < n_channels; n++)
    {
        uart_tick_channel(&channels[n]);
    }
}

command_litsener

a callback called when tinyframe receives a command packet. unpacks the arguments then calls the command module to handle the command.

static TF_Result command_listener(TinyFrame *tf, TF_Msg *msg){
    channel_info_t* info = tf->userdata;
    handle_command(msg->data, msg->len, msg->frame_id, info->location);
    return TF_CLOSE;
}

uart_init_channel

initializes a UART channel, sets up the GPIO and TinyFrame for the channel. initializes the TinyFrame, then adds the command_listener callback to tinyframe as the handler for all command packets.

static void uart_init_channel(channel_info_t* channel)
{
    uart_driver_install(channel->channel, 300, 300, 0, NULL, 0);
    uart_param_config(channel->channel, &channel->hw_config);
    uart_set_pin(channel->channel, channel->tx_pin, channel->rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    TF_InitStatic(&channel->handler, channel->mode);
    channel->handler.userdata = channel;
    channel->mutex = xSemaphoreCreateMutex();
    TF_AddTypeListener(&channel->handler, PACKET_TYPE_CMD, command_listener);
}

uart_send_litsener

a callback called when tinyframe receives a response packet. unpacks the arguments then calls the async_callback callback.

static TF_Result uart_send_litsener(TinyFrame *tf, TF_Msg *msg){
    async_callback callback = (async_callback) msg->userdata;
    if(callback){
        callback(msg->userdata2, false, msg->data, msg->len);
    }
    return TF_CLOSE;
}

uart_timeout_litsener

a callback called when tinyframe times out waiting for a response. unpacks the arguments then calls the async_callback callback with a timeout flag.

static TF_Result uart_timeout_litsener(TinyFrame *tf, void* userdata, void* userdata2){
    async_callback callback = (async_callback) userdata;
    if(callback){
        callback(userdata2, true, NULL, 0);
    }
    return TF_CLOSE;
}

uart_sync_send_litsener

a callback which handles the response to a synchronous send. unpacks the arguments copys the response to the response buffer, then notifies the task that the response is ready with xTaskNotify.

static TF_Result uart_sync_send_litsener(TinyFrame *tf, TF_Msg *msg)
{
    if (msg->userdata == NULL)
    {
        return TF_CLOSE;  // we have been called incorrectly, doing nothing is the least destructive option
    }
    sync_args_t* args = (sync_args_t*)msg->userdata;
    if(args->response){
        memcpy(args->response, msg->data, MIN(msg->len, args->response_length));
    }
    xTaskNotify(args->handle, 0, eNoAction);
    return TF_CLOSE;
}