demo/uart_blaster/uart_blaster.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
#include "uart_blaster_internal.h"
#define MAX_RX_IRQ_EVENTS 8
#define MAX_TX_BUFFERS 3
#define LOG_UPDATE_PERIOD_MS 1000
#define START_CMD "#START!!"
#define RESET_CMD "#RESET!!"
#define IS_START_CMD(buffer) ( \
(buffer[1] == 'S') && \
(buffer[2] == 'T') && \
(buffer[3] == 'A') && \
(buffer[4] == 'R') && \
(buffer[5] == 'T') && \
(buffer[6] == '!') && \
(buffer[7] == '!') \
)
#define IS_RESET_CMD(buffer) ( \
(buffer[1] == 'R') && \
(buffer[2] == 'E') && \
(buffer[3] == 'S') && \
(buffer[4] == 'E') && \
(buffer[5] == 'T') && \
(buffer[6] == '!') && \
(buffer[7] == '!') \
)
typedef enum
{
BLASTER_STATE_STOPPED = 0,
BLASTER_STATE_PAUSED = 1,
BLASTER_STATE_STARTED = 2
} blaster_state_t;
typedef struct tx_buffer
{
struct tx_buffer *next;
uint8_t *data;
uint32_t size;
} tx_buffer_t;
static struct
{
uint32_t tx_counter;
uint32_t rx_counter;
uint32_t bytes_sent;
uint32_t bytes_received;
uint32_t bytes_dropped;
uint32_t rx_ave_counter;
uint32_t tx_ave_counter;
uint32_t tx_ave_timestamp;
uint32_t rx_ave_timestamp;
tx_buffer_t *tx_head;
tx_buffer_t *tx_tail;
gos_semaphore_t tx_buffer_sem;
char pending[8];
uint8_t pending_index;
uint8_t *buffers;
uint32_t tx_buffer_max_length;
uint8_t *rx_buffer;
uint32_t rx_buffer_length;
blaster_state_t state;
gos_uart_t uart;
bool sent_start;
bool wait_for_start;
uart_blaster_flag_t flags;
} context;
/*************************************************************************************************/
gos_result_t uart_blaster_init(void)
{
gos_result_t result;
memset(&context, 0, sizeof(context));
gos_rtos_semaphore_init(&context.tx_buffer_sem);
if(GOS_FAILED(result, gos_event_enable_irq_events(MAX_RX_IRQ_EVENTS)))
{
BLASTER_ERROR("Failed to enable IRQ event, err:%d", result);
}
return result;
}
/*************************************************************************************************/
gos_result_t uart_blaster_start(void)
{
if(context.state == BLASTER_STATE_STOPPED)
{
BLASTER_INFO("Initializing");
if(GOS_FAILED(result, blaster_init()))
{
BLASTER_ERROR("Failed to init blaster, err:%d", result);
goto exit;
}
else
{
context.state = BLASTER_STATE_PAUSED;
}
}
if(context.state == BLASTER_STATE_PAUSED)
{
start_blasting();
}
else if(context.state == BLASTER_STATE_STARTED)
{
stop_blasting();
}
exit:
return result;
}
/*************************************************************************************************/
gos_result_t uart_blaster_stop(void)
{
if(context.state == BLASTER_STATE_STOPPED)
{
BLASTER_WARN("Already stopped");
}
else
{
stop_blasting();
}
return GOS_SUCCESS;
}
/*************************************************************************************************/
gos_result_t uart_blaster_pause(void)
{
if(context.state == BLASTER_STATE_STOPPED)
{
BLASTER_WARN("Not started");
}
else if(context.state == BLASTER_STATE_PAUSED)
{
start_blasting();
}
else if(context.state == BLASTER_STATE_STARTED)
{
pause_blasting();
}
return GOS_SUCCESS;
}
/*************************************************************************************************/
void uart_blaster_log(uart_blaster_log_level_t level, const char *cmd, ...)
{
if(level > blaster_settings->log_level)
{
return;
}
va_list args;
va_start(args, cmd);
gos_vlog(cmd, args);
va_end(args);
}
/*************************************************************************************************/
static gos_result_t blaster_init(void)
{
gos_result_t result;
uint8_t *old_buffers = context.buffers;
uint8_t *buffers = NULL;
uint8_t *buffer_ptr;
const uint32_t max_tx_length = ALIGN_8(blaster_settings->tx_length);
uint32_t alloc_size = ALIGN_4(blaster_settings->rx_buffer_size) +
ALIGN_4(blaster_settings->rx_length) +
(sizeof(tx_buffer_t) + max_tx_length + 1)*MAX_TX_BUFFERS;
context.buffers = NULL;
if(GOS_FAILED(result, gos_malloc("blaster_buf", &buffers, alloc_size)))
{
BLASTER_ERROR("Failed to alloc buffer, err:%d", result);
goto exit;
}
buffer_ptr = buffers;
const gos_buffer_t uart_buffer =
{
.data = buffer_ptr,
.size = blaster_settings->rx_buffer_size
};
buffer_ptr += ALIGN_4(blaster_settings->rx_buffer_size);
if(blaster_settings->flags & UART_BLASTER_FLAG_CONFIG_UART_ENABLED)
{
BLASTER_INFO("Configuring UART");
if(GOS_FAILED(result, gos_uart_configure(blaster_settings->uart_id, &blaster_settings->uart, &uart_buffer)))
{
BLASTER_ERROR("Failed to init uart, err:%d", result);
goto exit;
}
}
else
{
BLASTER_WARN("Not configuring UART");
}
context.buffers = buffers;
context.rx_buffer = buffer_ptr;
context.rx_buffer_length = blaster_settings->rx_length;
buffer_ptr += ALIGN_4(blaster_settings->rx_length);
tx_buffer_t *tx_buffer = (tx_buffer_t*)buffer_ptr;
buffer_ptr += sizeof(tx_buffer_t)*MAX_TX_BUFFERS;
context.tx_buffer_max_length = max_tx_length;
for(int i = 0; i < MAX_TX_BUFFERS; ++i)
{
tx_buffer[i].next = &tx_buffer[i+1];
tx_buffer[i].data = buffer_ptr;
tx_buffer[i].size = 0;
buffer_ptr += max_tx_length + 1;
}
tx_buffer[MAX_TX_BUFFERS-1].next = tx_buffer;
context.tx_head = tx_buffer;
context.tx_tail = tx_buffer;
context.flags = blaster_settings->flags;
context.uart = blaster_settings->uart_id;
context.tx_counter = 0;
context.rx_counter = 0;
context.tx_ave_timestamp = 0;
context.rx_ave_timestamp = 0;
context.rx_ave_counter = 0;
context.tx_ave_counter = 0;
context.pending_index = 0;
context.bytes_dropped = 0;
context.bytes_received = 0;
context.bytes_sent = 0;
context.sent_start = false;
exit:
gos_free(old_buffers);
if(result != GOS_SUCCESS)
{
gos_free(buffers);
}
return result;
}
/*************************************************************************************************/
static void populate_tx_buffer(void *unused)
{
tx_buffer_t *buffer = context.tx_tail;
uint8_t *ptr = buffer->data;
const uint8_t *end_buffer = ptr + context.tx_buffer_max_length;
if(context.state == BLASTER_STATE_STOPPED)
{
return;
}
if(!context.sent_start)
{
BLASTER_DEBUG("Sending START cmd");
context.tx_counter = 0;
context.sent_start = true;
memcpy(ptr, START_CMD, 8);
ptr += 8;
}
for(; ptr < end_buffer; ptr += 8)
{
char *c = (char*)(ptr + 7);
int remaining = 7;
uint32_t number = context.tx_counter++;
for(; number > 0; --remaining)
{
const uint8_t v = (number % 16);
*c-- = (v <= 9) ? '0' + v : 'A' + (v - 10);
number /= 16;
}
for(; remaining > 0; --remaining)
{
*c-- = '0';
}
*c = '#';
}
buffer->size = (uint32_t)(ptr - buffer->data);
context.tx_tail = buffer->next;
gos_rtos_semaphore_set(&context.tx_buffer_sem);
}
/*************************************************************************************************/
static void send_tx_packet_handler(void *unused)
{
gos_result_t result;
uint32_t bytes_sent;
// Immediately start populating the next buffer
if(GOS_FAILED(result, gos_rtos_semaphore_get(&context.tx_buffer_sem, GOS_WAIT_FOREVER)))
{
BLASTER_ERROR("Failed to wait for TX buffer to be populated: %d", result);
}
else if(context.state == BLASTER_STATE_STOPPED)
{
}
else if(GOS_FAILED(result, gos_uart_transmit_bytes(context.uart, context.tx_head->data, context.tx_head->size,
&bytes_sent, GOS_WAIT_FOREVER)))
{
BLASTER_ERROR("Failed to send data: %d", result);
}
else
{
context.tx_head = context.tx_head->next;
context.bytes_sent += bytes_sent;
context.tx_ave_counter += bytes_sent;
if(blaster_settings->log_level >= UART_BLASTER_LOG_LEVEL_DEBUG)
{
const uint32_t now = gos_rtos_get_time();
if((now - context.tx_ave_timestamp) > LOG_UPDATE_PERIOD_MS)
{
char total_str[32];
char rate_str[32];
get_data_rate_str(rate_str, now, context.tx_ave_counter, context.tx_ave_timestamp);
get_total_bytes_str(total_str, context.bytes_sent);
BLASTER_DEBUG("TX: %s, %s", total_str, rate_str);
context.tx_ave_timestamp = now;
context.tx_ave_counter = 0;
}
}
if(context.state == BLASTER_STATE_STARTED)
{
uint32_t delay = blaster_settings->tx_delay;
if(blaster_settings->flags & UART_BLASTER_FLAG_RANDOM_TX_DELAY_ENABLED)
{
delay = gos_get_random_uint32(0, delay);
}
if(delay == 0)
{
gos_event_issue(send_tx_packet_handler, NULL, GOS_EVENT_FLAG_NONE );
}
else
{
gos_event_register_timed(send_tx_packet_handler, NULL, delay, GOS_EVENT_FLAG_NONE );
}
}
}
}
/*************************************************************************************************/
static void receive_rx_packet_handler(void *unused)
{
gos_result_t result;
uint32_t bytes_available = 0;
gos_uart_peek_bytes(context.uart, NULL, &bytes_available);
if(bytes_available == 0)
{
return;
}
const uint32_t size_size = MIN(context.rx_buffer_length, bytes_available);
if(GOS_FAILED(result, gos_uart_receive_bytes(context.uart, context.rx_buffer, size_size,
&bytes_available, GOS_WAIT_FOREVER)))
{
BLASTER_ERROR("Failed to receive data, err:%d", result);
}
else if(bytes_available == 0)
{
}
else if(blaster_settings->flags & UART_BLASTER_FLAG_VALIDATE_RX_DATA)
{
uint32_t bytes_dropped = 0;
uint32_t bytes_received = 0;
const uint8_t *ptr = context.rx_buffer;
const uint8_t *end_buffer = ptr + bytes_available;
char buffer[9] = {0,0,0,0,0,0,0,0,0};
uint8_t idx = 0;
if(context.pending_index > 0)
{
memcpy(buffer, context.pending, context.pending_index);
idx = context.pending_index;
}
for(; ptr < end_buffer; ++ptr)
{
buffer[idx++] = *ptr;
if(idx == 1)
{
if(buffer[0] != '#')
{
++bytes_dropped;
idx = 0;
}
}
else if(buffer[idx-1] == '#')
{
bytes_dropped += idx;
buffer[0] = '#';
idx = 1;
}
else if(idx == 8)
{
if(IS_START_CMD(buffer))
{
BLASTER_DEBUG("START cmd");
if(!context.sent_start && (context.flags & UART_BLASTER_FLAG_TX_WAIT_FOR_START))
{
start_tx_loop();
}
context.wait_for_start = false;
context.rx_counter = 0;
context.rx_ave_counter = 0;
context.rx_ave_timestamp = 0;
context.bytes_dropped = 0;
context.bytes_received = 0;
idx = 0;
continue;
}
else if(context.wait_for_start)
{
idx = 0;
continue;
}
else if(IS_RESET_CMD(buffer))
{
BLASTER_DEBUG("RESET cmd");
context.tx_counter = 0;
context.rx_counter = 0;
idx = 0;
continue;
}
const uint32_t num = str_hex_to_uint32(&buffer[1]);
if(num == UINT32_MAX)
{
}
else if(num != context.rx_counter)
{
bytes_dropped = (num - context.rx_counter)*8;
context.rx_counter = num + 1;
}
else
{
++context.rx_counter;
bytes_received += 8;
}
idx = 0;
}
}
if(idx > 0)
{
memcpy(context.pending, buffer, idx);
context.pending_index = idx;
}
else
{
context.pending_index = 0;
}
context.bytes_received += bytes_received;
context.rx_ave_counter += bytes_received;
context.bytes_dropped += bytes_dropped;
if(blaster_settings->log_level >= UART_BLASTER_LOG_LEVEL_DEBUG)
{
const uint32_t now = gos_rtos_get_time();
if((now - context.rx_ave_timestamp) > LOG_UPDATE_PERIOD_MS)
{
char total_str[32];
char rate_str[32];
get_data_rate_str(rate_str, now, context.rx_ave_counter, context.rx_ave_timestamp);
get_total_bytes_str(total_str, context.bytes_received);
BLASTER_DEBUG("RX: %s, %s, %d dropped", total_str, rate_str, context.bytes_dropped);
context.rx_ave_timestamp = now;
context.rx_ave_counter = 0;
}
}
}
uint32_t delay = blaster_settings->rx_delay;
if(blaster_settings->flags & UART_BLASTER_FLAG_RANDOM_RX_DELAY_ENABLED)
{
delay = gos_get_random_uint32(0, delay);
}
if(delay > 0)
{
}
return;
}
/*************************************************************************************************/
static gos_result_t uart_rx_callback(void *unused)
{
uint32_t bytes_read = 0;
gos_uart_peek_bytes(context.uart, NULL, &bytes_read);
if(context.wait_for_start || bytes_read >= context.rx_buffer_length / 4)
{
// if the number of RX bytes pending is greater than UART_RX_THRESHOLD_LENGTH
// then invoke the rx event.
gos_event_issue(receive_rx_packet_handler, NULL, GOS_EVENT_FLAGS1(IN_NETWORK_WORKER));
}
return GOS_SUCCESS;
}
/*************************************************************************************************/
static gos_result_t uart_tx_callback(void *unused)
{
if(context.state == BLASTER_STATE_STOPPED)
{
return GOS_ABORTED;
}
else
{
return GOS_SUCCESS;
}
}
/*************************************************************************************************/
static void start_blasting(void)
{
BLASTER_INFO("Starting");
context.state = BLASTER_STATE_STARTED;
context.wait_for_start = true;
context.sent_start = false;
if(context.flags & UART_BLASTER_FLAG_TX_ENABLED)
{
gos_uart_set_tx_processor(context.uart, uart_tx_callback);
if(!(context.flags & UART_BLASTER_FLAG_TX_WAIT_FOR_START))
{
start_tx_loop();
}
else
{
BLASTER_DEBUG("TX wait for START");
}
}
else
{
gos_result_t result;
BLASTER_DEBUG("TX disabled, sending START cmd");
context.sent_start = true;
if(GOS_FAILED(result, gos_uart_transmit_bytes(context.uart, START_CMD, 8, NULL, GOS_WAIT_FOREVER)))
{
BLASTER_ERROR("Failed to send START cmd, err:%d", result);
}
}
if(context.flags & UART_BLASTER_FLAG_RX_ENABLED)
{
gos_uart_set_rx_callback(context.uart, uart_rx_callback);
}
else
{
BLASTER_DEBUG("RX disabled");
}
}
/*************************************************************************************************/
static void start_tx_loop(void)
{
BLASTER_DEBUG("Starting TX");
gos_rtos_semaphore_reset(&context.tx_buffer_sem);
gos_event_issue(populate_tx_buffer, NULL, GOS_EVENT_FLAG_IN_EVENT_THREAD);
gos_event_issue(send_tx_packet_handler, NULL, GOS_EVENT_FLAG_NONE);
}
/*************************************************************************************************/
static void stop_blasting(void)
{
BLASTER_INFO("Stopping");
context.state = BLASTER_STATE_STOPPED;
gos_event_unregister(send_tx_packet_handler, NULL);
gos_event_unregister(populate_tx_buffer, NULL);
gos_event_unregister(receive_rx_packet_handler, NULL);
gos_rtos_semaphore_set(&context.tx_buffer_sem);
// Wait a moment to clear the TX buffer
gos_uart_clear_rx_callback(context.uart, uart_rx_callback);
gos_uart_clear_tx_processor(context.uart, uart_tx_callback);
}
/*************************************************************************************************/
static void pause_blasting(void)
{
BLASTER_INFO("Pausing");
context.state = BLASTER_STATE_PAUSED;
gos_event_unregister(send_tx_packet_handler, NULL);
gos_event_unregister(receive_rx_packet_handler, NULL);
gos_uart_clear_rx_callback(context.uart, uart_rx_callback);
gos_uart_clear_tx_processor(context.uart, uart_tx_callback);
}
/*************************************************************************************************/
static void get_data_rate_str(char *str, uint32_t now, uint32_t byte_count, uint32_t prev_timestamp)
{
const char *unit;
uint32_t divider;
uint32_t elapsed_ms = now - prev_timestamp;
if(elapsed_ms == 0)
{
elapsed_ms = 1;
}
const uint32_t rate = (byte_count * 1000) / elapsed_ms;
get_scale(rate, &divider, &unit);
sprintf(str, "%3d.%06d %s/s", (unsigned int)(rate / divider), (unsigned int)(rate % divider), unit);
}
/*************************************************************************************************/
static void get_total_bytes_str(char *str, uint32_t total_bytes)
{
const char *unit;
uint32_t divider;
get_scale(total_bytes, &divider, &unit);
sprintf(str, "%3d.%06d %s", (unsigned int)(total_bytes / divider), (unsigned int)(total_bytes % divider), unit);
}
/*************************************************************************************************/
static void get_scale(uint32_t count, uint32_t *divider, const char **unit)
{
if(count < 1000)
{
*divider = 1;
*unit = "bytes";
}
else if(count < 1000000)
{
*divider = 1000;
*unit = "kB";
}
else if(count < 1000000000)
{
*divider = 1000000;
*unit = "MB";
}
else
{
*divider = 1000000000;
*unit = "GB";
}
}