Compare commits

...

6 commits

6 changed files with 260 additions and 64 deletions

View file

@ -6,6 +6,7 @@
#include "baudot.h"
#include "esp_log.h"
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
static const char *TAG = "baudot";
/*
@ -152,17 +153,29 @@ size_t baudot_to_unicode(baudot_context_t *ctx, uint8_t *input, uint8_t input_le
if (c == BAUDOT_FIGS)
{
ctx->figures = 1;
ctx->tx_figures = 1;
ESP_LOGD(TAG, "figures switch: %d", ctx->figures);
}
else if (c == BAUDOT_LTRS)
{
ctx->figures = 0;
ctx->tx_figures = 0;
ESP_LOGD(TAG, "figures switch: %d", ctx->figures);
}
else
{
unsigned int mapped = baudot_map[c | (ctx->figures << 5)];
ESP_LOGD(TAG, "%02x -> %08x", c, mapped);
if (mapped == '\r')
{
ctx->column = 0;
}
else if (mapped != '\0' && c != '\n')
{
ctx->column += 1;
}
if (mapped < 256)
{
output[out_pos++] = mapped;
@ -278,19 +291,44 @@ size_t unicode_to_baudot(baudot_context_t *ctx, uint8_t *input, uint8_t input_le
}
uint8_t found = 0;
for (int x = 0; x < sizeof(baudot_map); x++)
for (int i = 0; i < sizeof(baudot_map); i++)
{
// start lookup from current part of baudot map
int x = i ^ (ctx->tx_figures << 5);
if (baudot_map[x] == c)
{
if (ctx->figures != x >> 5)
// switch to figures + workaround for broken carriage return on our machine :(
if (ctx->tx_figures != x >> 5 || (c == '\r' && !ctx->tx_figures))
{
output[out_pos++] = ctx->figures ? BAUDOT_LTRS : BAUDOT_FIGS;
ctx->figures = !ctx->figures;
ESP_LOGD(TAG, "figures switch: %d", ctx->figures);
output[out_pos++] = ctx->tx_figures ? BAUDOT_LTRS : BAUDOT_FIGS;
ctx->tx_figures = !ctx->tx_figures;
ESP_LOGD(TAG, "figures switch: %d", ctx->tx_figures);
}
ESP_LOGD(TAG, "emitting: %d", x & 0b11111);
output[out_pos++] = x & 0b11111;
found = 1;
if (c == '\r')
{
ctx->column = 0;
}
else if (c != '\0' && c != '\n')
{
ctx->column += 1;
}
if (ctx->column >= 69)
{
// emit newline
output[out_pos++] = 0x1b;
ctx->tx_figures = 1;
output[out_pos++] = 0x08;
output[out_pos++] = 0x02;
ctx->column = 0;
}
break;
}
}

View file

@ -6,8 +6,10 @@
typedef struct
{
uint8_t figures;
uint8_t tx_figures;
uint8_t unicode_len;
uint32_t unicode_codepoint;
uint8_t column;
} baudot_context_t;
size_t baudot_to_unicode(baudot_context_t *ctx, uint8_t *input, uint8_t input_len, uint8_t *output, uint8_t output_len);

View file

@ -17,6 +17,7 @@ const uint32_t TTY_EN_GPIO = 2;
rmtuart_ctx_t rmtuart = {
.tx_gpio = TTY_TX_GPIO,
.rx_gpio = TTY_RX_GPIO,
.rx_mode = RX_MODE_GPIO,
.baudrate = 50,
.data_bits = 5,
.stop_bits = 1,
@ -39,7 +40,7 @@ void softuart_test()
while (1)
{
ESP_LOGI(TAG, "sending...");
uint8_t data[] = {1, 2, 3, 4, 0b10101 /* 21 */, 0b01010 /* 10 */};
uint8_t data[] = {1, 1, 1, 2, 3, 4, 0b10101 /* 21 */, 0b01010 /* 10 */};
xStreamBufferSend(softuart.tx_buffer, &data, sizeof(data), pdMS_TO_TICKS(1000));
for (int t = 0; t < 20; t++)
{
@ -57,23 +58,67 @@ void softuart_test()
}
}
void rmtuart_test()
#include <string.h>
void unicode_test()
{
baudot_context_t ctx = {0, 0, 0};
uint8_t baudot[] = {0x03 /* a */, 0x19 /* b */, 0x0e /* c */, 0x1b /* fig */, 0x1a /* ę */, 0x0d /* ą */, 0x14 /* ł */, 0x1f /* letters */};
uint8_t unicode[64] = {};
uint8_t baudot2[64] = {};
char *unicode2 = "zażółć gęślą jaźń ZAŻÓŁĆ GĘŚLĄ JAŹŃ";
ESP_LOGI(TAG, "Converting baudot...");
int baudot_len = unicode_to_baudot(&ctx, (uint8_t *)unicode2, strlen(unicode2), baudot2, sizeof(baudot2));
ESP_LOGI(TAG, "Got baudot: %d", (int)baudot_len);
}
void rmtuart_echo_test()
{
rmtuart_start(&rmtuart);
while (true)
{
ESP_LOGI(TAG, "Done");
for (int i = 0; i < 1; i++)
{
rmtuart_transmit_word(&rmtuart, 1);
// rmtuart_transmit_word(&rmtuart, 2);
// rmtuart_transmit_word(&rmtuart, 3);
// rmtuart_transmit_word(&rmtuart, 4);
// rmtuart_transmit_word(&rmtuart, 0b10101);
// rmtuart_transmit_word(&rmtuart, 0b01010);
xStreamBufferReset(rmtuart.tx_buffer);
xStreamBufferReset(rmtuart.rx_buffer);
uint8_t tx_data[] = {1, 2, 3, 4, 5, 0b10000, 0b10001, 0b10101, 0b11111};
int tx_size = sizeof(tx_data);
xStreamBufferSend(rmtuart.tx_buffer, &tx_data, tx_size, pdMS_TO_TICKS(1000));
ESP_LOGI(TAG, "%d bytes sent", sizeof(tx_data));
uint8_t rx_data[sizeof(tx_data) * 2] = {};
uint8_t *rx_ptr = rx_data;
int chunklen = 0;
int rx_size = 0;
while ((chunklen = xStreamBufferReceive(rmtuart.rx_buffer, rx_ptr, sizeof(rx_data) - rx_size, pdMS_TO_TICKS(rx_size < tx_size ? 5000 : 500))) > 0 && sizeof(rx_data) - rx_size > 0)
{
ESP_LOGI(TAG, "%d/%d bytes received (%02x)", chunklen, rx_size, rx_data[rx_size]);
rx_size += chunklen;
rx_ptr += chunklen;
}
ESP_LOGI(TAG, "Receive finished, got %d bytes", rx_size);
if (rx_size != tx_size)
{
ESP_LOGE(TAG, "Invalid receive size");
}
else
{
for (int i = 0; i < sizeof(tx_data); i++)
{
if (tx_data[i] != rx_data[i])
{
ESP_LOGW(TAG, "Byte %d difference: %02x vs %02x", i, tx_data[i], rx_data[i]);
}
}
}
ESP_LOGI(TAG, "Done");
}
vTaskDelay(pdMS_TO_TICKS(1000));
// unicode_test();
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
@ -81,6 +126,9 @@ void app_main(void)
{
ESP_LOGI(TAG, "starting up");
// rmtuart_echo_test();
// return;
rmtuart_start(&rmtuart);
network.rx_buffer = rmtuart.rx_buffer;

View file

@ -59,7 +59,11 @@ static void tcp_server_task(network_ctx_t *ctx)
ESP_LOGI(TAG, "Socket listening");
baudot_context_t unicode_ctx = {0};
baudot_context_t unicode_ctx = {};
unicode_ctx.figures = 0;
unicode_ctx.tx_figures = 0;
unicode_ctx.unicode_codepoint = 0;
unicode_ctx.unicode_len = 0;
while (1)
{
@ -95,6 +99,10 @@ static void tcp_server_task(network_ctx_t *ctx)
uint8_t rx_data[32] = {0};
size_t rx_data_len = xStreamBufferReceive(ctx->rx_buffer, &rx_data, sizeof(rx_data), pdMS_TO_TICKS(100));
for (int i = 0; i < rx_data_len; i++)
{
ESP_LOGI(TAG, "TTY->TCP: %02x", rx_data[i]);
}
uint8_t rx_unicode_data[64] = {1};
size_t rx_unicode_data_len = baudot_to_unicode(&unicode_ctx, rx_data, rx_data_len, rx_unicode_data, sizeof(rx_unicode_data));
@ -138,16 +146,24 @@ static void tcp_server_task(network_ctx_t *ctx)
}
else if (read_buffer_len > 0)
{
ESP_LOGI(TAG, "Sending %d bytes", read_buffer_len);
uint8_t tx_baudot_data[64] = {0};
ESP_LOGI(TAG, "Sending %d bytes to TTY", read_buffer_len);
uint8_t tx_baudot_data[100] = {0};
size_t tx_baudot_data_len = unicode_to_baudot(&unicode_ctx, read_buffer, read_buffer_len, tx_baudot_data, sizeof(tx_baudot_data));
xStreamBufferSend(ctx->tx_buffer, &tx_baudot_data, tx_baudot_data_len, pdMS_TO_TICKS(1000));
if (tx_baudot_data_len > 0)
{
for (int i = 0; i < tx_baudot_data_len; i++)
{
ESP_LOGI(TAG, "TCP->TTY: %02x", tx_baudot_data[i]);
}
xStreamBufferSend(ctx->tx_buffer, &tx_baudot_data, tx_baudot_data_len, pdMS_TO_TICKS(60000));
}
}
}
if (client_sockets[i] && rx_unicode_data_len > 0)
{
ESP_LOGI(TAG, "Sending %d bytes to %d: %s", rx_unicode_data_len, client_sockets[i], rx_unicode_data);
ESP_LOGI(TAG, "Sending %d bytes to TCP/%d: %s", rx_unicode_data_len, client_sockets[i], rx_unicode_data);
send(client_sockets[i], &rx_unicode_data, rx_unicode_data_len, 0);
}
}

View file

@ -1,14 +1,19 @@
//
// This is a fairly naive RMT-based UART implementation - built mostly because native ESP32 UART is unable to clock down to 50bps.
// This is a fairly naive RMT/Timer-based UART implementation - built mostly because native ESP32 UART is unable to clock down to 50bps.
//
// Transmission works fairly well.
// Reception is slightly flawed - multiple consecutive words are buffered in a limited hardware buffer - approx. 10-20 characters can be received until its overflow.
// RMT-based transmission works fairly well.
//
// RMT-based reception is slightly flawed - multiple consecutive words are buffered in a limited hardware buffer - approx. 10-20 characters can be received until its overflow.
// Alternative GPIO/Timer-based reception (enabled using rx_mode = RX_MODE_GPIO) works well - no buffering/buffer limit.
//
// NOTE: This is intentionally half-duplex, ie. receive will ignore incoming words during transmission to prevent echo. This should be fairly easy to patch out if unwanted.
//
// NOTE: This has only been tested in 5-bit-per-word mode at 50bps, reception at any other rate may need rmtuart_receive_words adjustment.
//
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "rmtuart.h"
static const char *TAG = "rmtuart";
@ -29,7 +34,9 @@ void rmtuart_transmit_words(rmtuart_ctx_t *ctx)
size_t read_len = xStreamBufferReceive(ctx->tx_buffer, &word, 1, pdMS_TO_TICKS(1000));
if (read_len == 1)
{
ctx->tx_busy = true;
rmtuart_transmit_word(ctx, word);
ctx->tx_busy = false;
ESP_LOGD(TAG, "Transmit done %02x", word);
}
else
@ -62,7 +69,7 @@ void rmtuart_receive_words(rmtuart_ctx_t *ctx)
uint16_t *raw_helper = (uint16_t *)&raw_symbols;
// bit length in RMT ticks
int bit_length = 128; // FIXME: derive this from RMT clock divider...
int bit_ticks = ctx->resolution_hz / ctx->baudrate;
int sym_idx = 0;
int time_offs = 64;
int bitcnt = 0;
@ -85,7 +92,7 @@ void rmtuart_receive_words(rmtuart_ctx_t *ctx)
}
bitcnt += 1;
time_offs += bit_length;
time_offs += bit_ticks;
if (bitcnt == ctx->data_bits + 1)
{
@ -145,7 +152,6 @@ void rmtuart_transmit_word(rmtuart_ctx_t *ctx, uint32_t word)
.loop_count = 0, // no transfer loop
};
// Let's just assume data_bits + stop_bits + 1 < 15
uint16_t bit_ticks = ctx->resolution_hz / ctx->baudrate;
int data_len = 1;
@ -173,50 +179,124 @@ void rmtuart_transmit_word(rmtuart_ctx_t *ctx, uint32_t word)
ESP_ERROR_CHECK(rmt_tx_wait_all_done(ctx->tx_chan, portMAX_DELAY));
}
static void IRAM_ATTR gpio_isr_handler(rmtuart_ctx_t *ctx)
{
if (!ctx->tx_busy && ctx->bit_cnt == 0 && gpio_get_level(ctx->rx_gpio) == 1)
{
ctx->bit_cnt = 1;
ctx->buf = 0;
esp_timer_start_once(ctx->rx_bit_timer, 1.5 * (1000000 / ctx->baudrate));
}
}
static void gpio_bit_callback(rmtuart_ctx_t *ctx)
{
if (ctx->bit_cnt <= ctx->data_bits)
{
ctx->buf = ctx->buf | (!gpio_get_level(ctx->rx_gpio)) << (ctx->bit_cnt - 1);
}
if (ctx->bit_cnt <= ctx->data_bits)
{
ctx->bit_cnt += 1;
esp_timer_start_once(ctx->rx_bit_timer, (1000000 / ctx->baudrate));
}
else
{
uint8_t buf[32] = {ctx->buf};
xStreamBufferSendFromISR(ctx->rx_buffer, &buf, 1, 0);
ctx->bit_cnt = 0;
}
}
void rmtuart_start(rmtuart_ctx_t *ctx)
{
// NOTE: these values may need to be adjusted if baudrate != 50
ctx->clk_src = RMT_CLK_SRC_REF_TICK;
ctx->resolution_hz = 128 * 50;
ESP_LOGI(TAG, "Create RMT RX channel");
rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = ctx->clk_src,
.resolution_hz = ctx->resolution_hz,
.mem_block_symbols = 64,
.gpio_num = ctx->rx_gpio,
};
ctx->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &ctx->rx_chan));
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(ctx->rx_chan, &cbs, ctx->receive_queue));
ESP_ERROR_CHECK(rmt_enable(ctx->rx_chan));
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_tx_channel_config_t tx_chan_config = {
// TODO automatic clocksource/resolution adjustment
// .clk_src = RMT_CLK_SRC_APB,
.clk_src = RMT_CLK_SRC_REF_TICK, // select source clock
.resolution_hz = ctx->resolution_hz,
.gpio_num = ctx->tx_gpio,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &ctx->tx_chan));
rmt_copy_encoder_config_t encoder_config = {};
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, &ctx->copy_encoder));
ESP_ERROR_CHECK(rmt_enable(ctx->tx_chan));
ctx->resolution_hz = 128 * ctx->baudrate;
// TODO automatic clocksource/resolution adjustment
ctx->rx_buffer = xStreamBufferCreate(128, 1);
ctx->tx_buffer = xStreamBufferCreate(128, 1);
xTaskCreate((TaskFunction_t)rmtuart_transmit_words, "rmtuart_transmit_words", 2048, ctx, 40, NULL);
xTaskCreate((TaskFunction_t)rmtuart_receive_words, "rmtuart_receive_words", 4096, ctx, 40, NULL);
if (ctx->rx_gpio >= 0)
{
if (ctx->rx_mode == RX_MODE_RMT)
{
ESP_LOGI(TAG, "Create RMT RX channel");
rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = ctx->clk_src,
.resolution_hz = ctx->resolution_hz,
.mem_block_symbols = 64,
.gpio_num = ctx->rx_gpio,
};
ctx->receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &ctx->rx_chan));
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(ctx->rx_chan, &cbs, ctx->receive_queue));
ESP_ERROR_CHECK(rmt_enable(ctx->rx_chan));
}
else
{
ctx->bit_cnt = 0;
ctx->buf = 0;
ESP_LOGI(TAG, "Create GPIO RX channel");
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_ANYEDGE;
io_conf.pin_bit_mask = 1 << ctx->rx_gpio;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(ctx->rx_gpio, (gpio_isr_t)gpio_isr_handler, (void *)ctx);
const esp_timer_create_args_t rx_bit_timer_config = {
.callback = &gpio_bit_callback,
.arg = ctx,
.name = "gpio_rx_gpio",
};
ESP_ERROR_CHECK(esp_timer_create(&rx_bit_timer_config, &ctx->rx_bit_timer));
}
}
if (ctx->tx_gpio >= 0)
{
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = ctx->clk_src,
.resolution_hz = ctx->resolution_hz,
.gpio_num = ctx->tx_gpio,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &ctx->tx_chan));
rmt_copy_encoder_config_t encoder_config = {};
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, &ctx->copy_encoder));
ESP_ERROR_CHECK(rmt_enable(ctx->tx_chan));
}
if (ctx->rx_gpio >= 0)
{
if (ctx->rx_mode == RX_MODE_RMT)
{
xTaskCreate((TaskFunction_t)rmtuart_receive_words, "rmtuart_receive_words", 4096, ctx, 40, NULL);
}
}
if (ctx->tx_gpio >= 0)
{
xTaskCreate((TaskFunction_t)rmtuart_transmit_words, "rmtuart_transmit_words", 2048, ctx, 40, NULL);
}
ESP_LOGI(TAG, "Init finished");
}

View file

@ -4,14 +4,21 @@
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/stream_buffer.h"
#include "freertos/queue.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "esp_timer.h"
typedef struct
{
int tx_gpio;
int rx_gpio;
enum
{
RX_MODE_RMT = 0,
RX_MODE_GPIO,
} rx_mode;
int baudrate;
int data_bits;
@ -28,6 +35,11 @@ typedef struct
rmt_encoder_handle_t copy_encoder;
QueueHandle_t receive_queue;
int bit_cnt;
uint8_t buf;
esp_timer_handle_t rx_bit_timer;
bool tx_busy;
} rmtuart_ctx_t;
void rmtuart_start(rmtuart_ctx_t *ctx);