networking cleanup, basic rmt uart implementation

This commit is contained in:
informatic 2024-06-03 21:18:04 +02:00
parent 0d1affdd2b
commit 8003a341a9
No known key found for this signature in database
17 changed files with 2963 additions and 267 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.pio
src/config.priv.h

View file

@ -3,4 +3,5 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
list(APPEND EXTRA_COMPONENT_DIRS components/esp32-rmt-uart)
project(uart_echo)

25
README.md Normal file
View file

@ -0,0 +1,25 @@
esp32-dalek
===========
This is another reincarnation of HSWAW Dalek IO interface - this time around based on ESP32.
* `//hw` - contains basic IO schematic
* `//src` (...and the rest) - contains ESP-IDF+platformio-based ESP32 project
## Building and running
Create `src/config.priv.h` based on `src/config.priv.h.dist` with proper network credentials, then check `src/main.c` for GPIO assignments.
```sh
pio run -t upload -t monitor -e esp32dev
```
## Usage
ESP32 exposes plain TCP server on port 1337 (check USB-UART output for currently assigned IP address) and passes ASCII/Unicode input/output data through to TTY line. Multiple clients receive copy of all the data and can send data simultaneously. All data sent to the teletype is echoed back (to all clients).
Motor should start up only when there's an active TCP connection - wait a couple of seconds before sending data to ensure motor is up and running.
No text wrapping is implemented - limit to ~80 columns on client-side.
Check Baudot ↔ ASCII/Unicode conversion routines in `src/baudot.c` for supported characters.

View file

@ -467,7 +467,7 @@
)
)
)
(symbol "Transistor_BJT:BC547"
(symbol "Transistor_BJT:2N2219"
(pin_names
(offset 0) hide)
(exclude_from_sim no)
@ -482,7 +482,7 @@
(justify left)
)
)
(property "Value" "BC547"
(property "Value" "2N2219"
(at 5.08 0 0)
(effects
(font
@ -491,7 +491,7 @@
(justify left)
)
)
(property "Footprint" "Package_TO_SOT_THT:TO-92_Inline"
(property "Footprint" "Package_TO_SOT_THT:TO-39-3"
(at 5.08 -1.905 0)
(effects
(font
@ -502,7 +502,7 @@
(hide yes)
)
)
(property "Datasheet" "https://www.onsemi.com/pub/Collateral/BC550-D.pdf"
(property "Datasheet" "http://www.onsemi.com/pub_link/Collateral/2N2219-D.PDF"
(at 0 0 0)
(effects
(font
@ -512,7 +512,7 @@
(hide yes)
)
)
(property "Description" "0.1A Ic, 45V Vce, Small Signal NPN Transistor, TO-92"
(property "Description" "800mA Ic, 50V Vce, NPN Transistor, TO-39"
(at 0 0 0)
(effects
(font
@ -530,7 +530,7 @@
(hide yes)
)
)
(property "ki_fp_filters" "TO?92*"
(property "ki_fp_filters" "TO?39*"
(at 0 0 0)
(effects
(font
@ -539,19 +539,7 @@
(hide yes)
)
)
(symbol "BC547_0_1"
(polyline
(pts
(xy 0 0) (xy 0.635 0)
)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
(symbol "2N2219_0_1"
(polyline
(pts
(xy 0.635 0.635) (xy 2.54 2.54)
@ -612,11 +600,11 @@
)
)
)
(symbol "BC547_1_1"
(symbol "2N2219_1_1"
(pin passive line
(at 2.54 5.08 270)
(at 2.54 -5.08 90)
(length 2.54)
(name "C"
(name "E"
(effects
(font
(size 1.27 1.27)
@ -631,9 +619,9 @@
)
)
)
(pin input line
(pin passive line
(at -5.08 0 0)
(length 5.08)
(length 5.715)
(name "B"
(effects
(font
@ -650,9 +638,9 @@
)
)
(pin passive line
(at 2.54 -5.08 90)
(at 2.54 5.08 270)
(length 2.54)
(name "E"
(name "C"
(effects
(font
(size 1.27 1.27)
@ -887,7 +875,7 @@
)
)
(junction
(at 81.28 72.39)
(at 81.28 81.28)
(diameter 0)
(color 0 0 0 0)
(uuid "362a7e93-dd37-44ee-935b-def9920a1ee4")
@ -903,12 +891,12 @@
(uuid "487432ff-8dc9-44d3-8ef1-e7e755cb8a6b")
)
(no_connect
(at 86.36 74.93)
(at 86.36 83.82)
(uuid "9f504306-72dd-474d-812c-51b1fedad886")
)
(wire
(pts
(xy 113.03 69.85) (xy 114.3 69.85)
(xy 113.03 78.74) (xy 114.3 78.74)
)
(stroke
(width 0)
@ -928,7 +916,7 @@
)
(wire
(pts
(xy 101.6 69.85) (xy 105.41 69.85)
(xy 101.6 78.74) (xy 105.41 78.74)
)
(stroke
(width 0)
@ -938,7 +926,7 @@
)
(wire
(pts
(xy 81.28 74.93) (xy 81.28 72.39)
(xy 81.28 83.82) (xy 81.28 81.28)
)
(stroke
(width 0)
@ -958,7 +946,7 @@
)
(wire
(pts
(xy 77.47 69.85) (xy 86.36 69.85)
(xy 77.47 78.74) (xy 86.36 78.74)
)
(stroke
(width 0)
@ -966,6 +954,16 @@
)
(uuid "8311f28f-7487-4ee1-828f-8f4bf3f89135")
)
(wire
(pts
(xy 114.3 58.42) (xy 107.95 58.42)
)
(stroke
(width 0)
(type default)
)
(uuid "8f237521-f2e7-489d-9484-0d3c25957522")
)
(wire
(pts
(xy 63.5 43.18) (xy 86.36 43.18)
@ -988,7 +986,7 @@
)
(wire
(pts
(xy 81.28 48.26) (xy 81.28 72.39)
(xy 81.28 48.26) (xy 81.28 81.28)
)
(stroke
(width 0)
@ -1006,6 +1004,16 @@
)
(uuid "b1627a60-a8c0-415d-a03e-c95aa5febd17")
)
(wire
(pts
(xy 107.95 58.42) (xy 107.95 48.26)
)
(stroke
(width 0)
(type default)
)
(uuid "c51c655e-48dd-4b27-a1de-67756dda803e")
)
(wire
(pts
(xy 107.95 48.26) (xy 114.3 48.26)
@ -1018,7 +1026,7 @@
)
(wire
(pts
(xy 69.85 74.93) (xy 81.28 74.93)
(xy 69.85 83.82) (xy 81.28 83.82)
)
(stroke
(width 0)
@ -1028,7 +1036,7 @@
)
(wire
(pts
(xy 69.85 64.77) (xy 69.85 49.53)
(xy 69.85 73.66) (xy 69.85 49.53)
)
(stroke
(width 0)
@ -1038,7 +1046,7 @@
)
(wire
(pts
(xy 81.28 72.39) (xy 86.36 72.39)
(xy 81.28 81.28) (xy 86.36 81.28)
)
(stroke
(width 0)
@ -1088,7 +1096,7 @@
)
(text "common → motor => 75v open, 120mA closed\ncommon → data => -71v open, -70mA closed"
(exclude_from_sim no)
(at 44.45 32.766 0)
(at 46.736 32.004 0)
(effects
(font
(size 1.27 1.27)
@ -1096,6 +1104,27 @@
)
(uuid "cbb12b6d-75e2-42bd-a3dc-93a90b966123")
)
(text "YOLO x-D"
(exclude_from_sim no)
(at 82.296 45.72 0)
(effects
(font
(size 1.27 1.27)
(color 0 0 0 0.04)
)
)
(uuid "cfcd1392-acb5-4ab5-860e-aeeb678d8b7d")
)
(text "Yes, this is an optodarlington, fight me."
(exclude_from_sim no)
(at 78.994 87.884 0)
(effects
(font
(size 1.27 1.27)
)
)
(uuid "dccd118c-a738-49f0-9230-e99b26b1850f")
)
(global_label "TTY_ENABLE"
(shape bidirectional)
(at 63.5 55.88 180)
@ -1186,7 +1215,7 @@
)
(global_label "ESP32_TX"
(shape bidirectional)
(at 114.3 69.85 0)
(at 114.3 78.74 0)
(fields_autoplaced yes)
(effects
(font
@ -1196,7 +1225,7 @@
)
(uuid "fc6bed03-bb4a-4ab8-bef6-b1a148721365")
(property "Intersheetrefs" "${INTERSHEET_REFS}"
(at 127.5887 69.85 0)
(at 127.5887 78.74 0)
(effects
(font
(size 1.27 1.27)
@ -1208,16 +1237,15 @@
)
(symbol
(lib_id "Device:R")
(at 107.95 52.07 0)
(at 118.11 58.42 90)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "16c0fddc-2686-406b-91db-36c724d7d8ef")
(property "Reference" "R1"
(at 110.49 50.7999 0)
(at 119.634 53.34 90)
(effects
(font
(size 1.27 1.27)
@ -1226,7 +1254,7 @@
)
)
(property "Value" "10k"
(at 110.49 53.3399 0)
(at 119.888 55.88 90)
(effects
(font
(size 1.27 1.27)
@ -1235,7 +1263,7 @@
)
)
(property "Footprint" ""
(at 106.172 52.07 90)
(at 118.11 60.198 90)
(effects
(font
(size 1.27 1.27)
@ -1244,7 +1272,7 @@
)
)
(property "Datasheet" "~"
(at 107.95 52.07 0)
(at 118.11 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1253,7 +1281,7 @@
)
)
(property "Description" "Resistor"
(at 107.95 52.07 0)
(at 118.11 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1276,94 +1304,17 @@
)
)
)
(symbol
(lib_id "Transistor_BJT:BC547")
(at 72.39 69.85 180)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "2a7df132-da68-4c81-898b-4fcafece4f65")
(property "Reference" "Q1"
(at 67.31 68.5799 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Value" "BC547"
(at 67.31 71.1199 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Footprint" "Package_TO_SOT_THT:TO-92_Inline"
(at 67.31 67.945 0)
(effects
(font
(size 1.27 1.27)
(italic yes)
)
(justify left)
(hide yes)
)
)
(property "Datasheet" "https://www.onsemi.com/pub/Collateral/BC550-D.pdf"
(at 72.39 69.85 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
(hide yes)
)
)
(property "Description" "0.1A Ic, 45V Vce, Small Signal NPN Transistor, TO-92"
(at 72.39 69.85 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(pin "2"
(uuid "fbb8de1c-0fc2-487e-abbc-4c83e59b37a0")
)
(pin "1"
(uuid "3591438f-606b-4456-af27-21c04e142fb0")
)
(pin "3"
(uuid "74838997-3fef-414a-80f9-835c68328064")
)
(instances
(project "hw"
(path "/a3d15243-7f01-4c75-a5ec-0d2fb509d510"
(reference "Q1")
(unit 1)
)
)
)
)
(symbol
(lib_id "power:+3.3V")
(at 107.95 55.88 180)
(at 121.92 58.42 270)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "2c2f1f40-1927-4a0f-aa86-4ffc81c35823")
(property "Reference" "#PWR02"
(at 107.95 52.07 0)
(at 118.11 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1372,7 +1323,7 @@
)
)
(property "Value" "+3.3V"
(at 107.95 60.96 0)
(at 128.27 58.42 90)
(effects
(font
(size 1.27 1.27)
@ -1380,7 +1331,7 @@
)
)
(property "Footprint" ""
(at 107.95 55.88 0)
(at 121.92 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1389,7 +1340,7 @@
)
)
(property "Datasheet" ""
(at 107.95 55.88 0)
(at 121.92 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1398,7 +1349,7 @@
)
)
(property "Description" "Power symbol creates a global label with name \"+3.3V\""
(at 107.95 55.88 0)
(at 121.92 58.42 0)
(effects
(font
(size 1.27 1.27)
@ -1484,9 +1435,85 @@
)
)
)
(symbol
(lib_id "Transistor_BJT:2N2219")
(at 72.39 78.74 180)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(dnp no)
(fields_autoplaced yes)
(uuid "648a6d44-3f55-473e-ae6b-55d16dc9f873")
(property "Reference" "Q2"
(at 67.31 77.4699 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Value" "2N2222"
(at 67.31 80.0099 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Footprint" "Package_TO_SOT_THT:TO-39-3"
(at 67.31 76.835 0)
(effects
(font
(size 1.27 1.27)
(italic yes)
)
(justify left)
(hide yes)
)
)
(property "Datasheet" "http://www.onsemi.com/pub_link/Collateral/2N2219-D.PDF"
(at 72.39 78.74 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
(hide yes)
)
)
(property "Description" "800mA Ic, 50V Vce, NPN Transistor, TO-39"
(at 72.39 78.74 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(pin "1"
(uuid "7ac990e8-1783-4e7b-8d5a-38ed74fbf256")
)
(pin "3"
(uuid "8de0d214-126a-4b2d-809d-d5dee07e2fe0")
)
(pin "2"
(uuid "88d25663-6500-4c05-9899-9ffd1cb9a6b7")
)
(instances
(project "hw"
(path "/a3d15243-7f01-4c75-a5ec-0d2fb509d510"
(reference "Q2")
(unit 1)
)
)
)
)
(symbol
(lib_id "power:GND")
(at 101.6 74.93 90)
(at 101.6 83.82 90)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
@ -1495,7 +1522,7 @@
(fields_autoplaced yes)
(uuid "77e5c414-3808-4813-95b8-5ca7e2a806b8")
(property "Reference" "#PWR03"
(at 107.95 74.93 0)
(at 107.95 83.82 0)
(effects
(font
(size 1.27 1.27)
@ -1504,7 +1531,7 @@
)
)
(property "Value" "GND"
(at 105.41 74.9299 90)
(at 105.41 83.8199 90)
(effects
(font
(size 1.27 1.27)
@ -1513,7 +1540,7 @@
)
)
(property "Footprint" ""
(at 101.6 74.93 0)
(at 101.6 83.82 0)
(effects
(font
(size 1.27 1.27)
@ -1522,7 +1549,7 @@
)
)
(property "Datasheet" ""
(at 101.6 74.93 0)
(at 101.6 83.82 0)
(effects
(font
(size 1.27 1.27)
@ -1531,7 +1558,7 @@
)
)
(property "Description" "Power symbol creates a global label with name \"GND\" , ground"
(at 101.6 74.93 0)
(at 101.6 83.82 0)
(effects
(font
(size 1.27 1.27)
@ -1553,7 +1580,7 @@
)
(symbol
(lib_id "Device:R")
(at 109.22 69.85 90)
(at 109.22 78.74 90)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
@ -1562,15 +1589,15 @@
(fields_autoplaced yes)
(uuid "a5b47767-87a2-47ab-b4bd-3c6358237c5b")
(property "Reference" "R2"
(at 109.22 63.5 90)
(at 109.22 72.39 90)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" "R"
(at 109.22 66.04 90)
(property "Value" "220"
(at 109.22 74.93 90)
(effects
(font
(size 1.27 1.27)
@ -1578,7 +1605,7 @@
)
)
(property "Footprint" ""
(at 109.22 71.628 90)
(at 109.22 80.518 90)
(effects
(font
(size 1.27 1.27)
@ -1587,7 +1614,7 @@
)
)
(property "Datasheet" "~"
(at 109.22 69.85 0)
(at 109.22 78.74 0)
(effects
(font
(size 1.27 1.27)
@ -1596,7 +1623,7 @@
)
)
(property "Description" "Resistor"
(at 109.22 69.85 0)
(at 109.22 78.74 0)
(effects
(font
(size 1.27 1.27)
@ -1704,7 +1731,7 @@
)
(symbol
(lib_id "Isolator:4N35")
(at 93.98 72.39 180)
(at 93.98 81.28 180)
(unit 1)
(exclude_from_sim no)
(in_bom yes)
@ -1713,7 +1740,7 @@
(fields_autoplaced yes)
(uuid "f12d17c2-5fd1-492b-b9da-05404c187a71")
(property "Reference" "U2"
(at 93.98 63.5 0)
(at 93.98 72.39 0)
(effects
(font
(size 1.27 1.27)
@ -1721,7 +1748,7 @@
)
)
(property "Value" "4N35"
(at 93.98 66.04 0)
(at 93.98 74.93 0)
(effects
(font
(size 1.27 1.27)
@ -1729,7 +1756,7 @@
)
)
(property "Footprint" "Package_DIP:DIP-6_W7.62mm"
(at 99.06 67.31 0)
(at 99.06 76.2 0)
(effects
(font
(size 1.27 1.27)
@ -1740,7 +1767,7 @@
)
)
(property "Datasheet" "https://www.vishay.com/docs/81181/4n35.pdf"
(at 93.98 72.39 0)
(at 93.98 81.28 0)
(effects
(font
(size 1.27 1.27)
@ -1750,7 +1777,7 @@
)
)
(property "Description" "Optocoupler, Phototransistor Output, with Base Connection, Vce 70V, CTR 100%, Viso 5000V, DIP6"
(at 93.98 72.39 0)
(at 93.98 81.28 0)
(effects
(font
(size 1.27 1.27)

View file

@ -10,6 +10,7 @@
[env]
platform = espressif32
framework = espidf
upload_speed = 921600
monitor_speed = 115200
monitor_filters =
direct
@ -17,6 +18,3 @@ monitor_filters =
[env:esp32dev]
board = esp32dev
[env:dfrobot_beetle_esp32c3]
board = dfrobot_beetle_esp32c3

1830
sdkconfig.esp32dev Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
idf_component_register(SRCS "main.c"
idf_component_register(SRCS "main.c" "baudot.c" "softuart.c" "network.c" "rmtuart.c"
INCLUDE_DIRS ".")

182
src/baudot.c Normal file
View file

@ -0,0 +1,182 @@
// This performs unicode-to-baudot and baudot-to-unicode conversion.
// Character table has been adjusted to what is present in (Polish-market) Zbrojovka Brno T100
//
// Same context should be used for both transmit and receive - this stores figures/letters shift state
// which should be in sync with the teletype.
#include "baudot.h"
#include "esp_log.h"
static const char *TAG = "baudot";
/*
'\0', '\0' 000-00
'E', '3' 000-01
'\n', '\n' 000-10
'A', '-' 000-11
' ', ' ' 001-00
'S', '\'' 001-01
'I', '8' 001-10
'U', '7' 001-11
'\r', '\r' 010-00
'D', '\5' 010-01
'R', '4' 010-10
'J', '\7' 010-11
'N', ',' 011-00
'F', '!' 011-01
'C', ':' 011-10
'K', '(' 011-11
'T', '5' 100-00
'Z', '+' 100-01
'L', ')' 100-10
'W', '2' 100-11
'H', '$' 101-00
'Y', '6' 101-01
'P', '0' 101-10
'Q', '1' 101-11
'O', '9' 110-00
'B', '?' 110-01
'G', '&' 110-10
'\0', '\0' 110-11
'M', '.' 111-00
'X', '/' 111-01
'V', ';' 111-10
'\0', '\0' 111-11
*/
#define BAUDOT_FIGS 0b11011
#define BAUDOT_LTRS 0b11111
uint32_t baudot_map[] = {
// Letters
'\0',
'E',
'\n',
'A',
' ',
'S',
'I',
'U',
'\r',
'D',
'R',
'J',
'N',
'F',
'C',
'K',
'T',
'Z',
'L',
'W',
'H',
'Y',
'P',
'Q',
'O',
'B',
'G',
'\0',
'M',
'X',
'V',
'\0',
// Figures
'\0',
'3',
'\n',
'-',
' ',
'\'',
'8',
'7',
'\r',
'\5',
'4',
'\7',
',',
'ą', // originally: !
':',
'(',
'5',
'+',
')',
'2',
'ł', // originally: $
'6',
'0',
'1',
'9',
'?',
'ę', // originally: &
'\0',
'.',
'/',
'=', // originally: ;
'\0',
};
size_t baudot_to_unicode(baudot_context_t *ctx, uint8_t *input, uint8_t input_len, uint8_t *output, uint8_t output_len)
{
int out_pos = 0;
for (int i = 0; i < input_len; i++)
{
uint8_t c = input[i];
if (c == BAUDOT_FIGS)
{
ctx->figures = 1;
ESP_LOGI(TAG, "figures switch: %d", ctx->figures);
}
else if (c == BAUDOT_LTRS)
{
ctx->figures = 0;
ESP_LOGI(TAG, "figures switch: %d", ctx->figures);
}
else
{
output[out_pos++] = baudot_map[c | (ctx->figures << 5)];
ESP_LOGI(TAG, "index: %d, emitting: %ld", c | (ctx->figures << 5), baudot_map[c | (ctx->figures << 5)]);
if (out_pos >= output_len)
{
ESP_LOGI(TAG, "overflow");
return out_pos;
}
}
}
return out_pos;
}
size_t unicode_to_baudot(baudot_context_t *ctx, uint8_t *input, uint8_t input_len, uint8_t *output, uint8_t output_len)
{
int out_pos = 0;
for (int i = 0; i < input_len; i++)
{
uint8_t c = (input[i] >= 'a' && input[i] <= 'z') ? (input[i] - 'a' + 'A') : input[i];
uint8_t found = 0;
for (int x = 0; x < sizeof(baudot_map); x++)
{
if (baudot_map[x] == c)
{
if (ctx->figures != x >> 5)
{
output[out_pos++] = ctx->figures ? BAUDOT_LTRS : BAUDOT_FIGS;
ctx->figures = !ctx->figures;
ESP_LOGI(TAG, "figures switch: %d", ctx->figures);
}
ESP_LOGI(TAG, "emitting: %d", x & 0b11111);
output[out_pos++] = x & 0b11111;
found = 1;
break;
}
}
if (!found)
{
ESP_LOGW(TAG, "unable to map: %d", c);
// TODO: logging
}
}
return out_pos;
}

12
src/baudot.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
typedef struct
{
uint8_t figures;
} 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);
size_t unicode_to_baudot(baudot_context_t *ctx, uint8_t *input, uint8_t input_len, uint8_t *output, uint8_t output_len);

4
src/config.priv.h.dist Normal file
View file

@ -0,0 +1,4 @@
#pragma once
#define CONFIG_WIFI_ESSID "wifi-essid"
#define CONFIG_WIFI_PSK "wifi-password"

View file

@ -1,135 +1,90 @@
/* UART Echo Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "rom/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
/**
* This is an example which echos any data it receives on configured UART back to the sender,
* with hardware flow control turned off. It does not use UART driver event queue.
*
* - Port: configured UART
* - Receive (Rx) buffer: on
* - Transmit (Tx) buffer: off
* - Flow control: off
* - Event queue: off
* - Pin assignment: see defines below (See Kconfig)
*/
#include "baudot.h"
#include "softuart.h"
#include "network.h"
#include "rmtuart.h"
#define ECHO_TEST_TXD (CONFIG_EXAMPLE_UART_TXD)
#define ECHO_TEST_RXD (CONFIG_EXAMPLE_UART_RXD)
#define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)
static const char *TAG = "main";
#define ECHO_UART_PORT_NUM (CONFIG_EXAMPLE_UART_PORT_NUM)
#define ECHO_UART_BAUD_RATE (CONFIG_EXAMPLE_UART_BAUD_RATE)
#define ECHO_TASK_STACK_SIZE (CONFIG_EXAMPLE_TASK_STACK_SIZE)
// TODO: move this to config
const uint32_t TTY_TX_GPIO = 22;
const uint32_t TTY_RX_GPIO = 23;
const uint32_t TTY_EN_GPIO = 2;
static const char *TAG = "UART TEST";
rmtuart_ctx_t rmtuart = {
.tx_gpio = TTY_TX_GPIO,
.rx_gpio = TTY_RX_GPIO,
.baudrate = 50,
.data_bits = 5,
.stop_bits = 1,
};
#define BUF_SIZE (1024)
network_ctx_t network = {
.tty_en_gpio = TTY_EN_GPIO,
};
static void echo_task(void *arg)
void softuart_test()
{
/* Configure parameters of an UART driver,
* communication pins and install the driver */
uart_config_t uart_config = {
.baud_rate = 50, // ECHO_UART_BAUD_RATE,
.data_bits = UART_DATA_5_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
softuart_ctx_t softuart = {
.tx_gpio = TTY_TX_GPIO,
.rx_gpio = TTY_RX_GPIO,
};
int intr_alloc_flags = 0;
#if CONFIG_UART_ISR_IN_IRAM
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
softuart_start(&softuart);
ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));
// Configure a temporary buffer for the incoming data
uint8_t *data = (uint8_t *) malloc(BUF_SIZE);
while (1) {
ESP_LOGI(TAG, "hello!");
data[0] = 0xaa;
uart_write_bytes(ECHO_UART_PORT_NUM, (const char*) data, 1);
// Read data from the UART
// int len = uart_read_bytes(ECHO_UART_PORT_NUM, data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
// Write data back to the UART
//uart_write_bytes(ECHO_UART_PORT_NUM, (const char *) data, len);
//if (len) {
// data[len] = '\0';
// ESP_LOGI(TAG, "Recv str: %s", (char *) data);
//}
}
}
#define BLINK_GPIO 22
void bitbang_word(uint8_t word)
{
const TickType_t bitDelay = 20 / portTICK_PERIOD_MS;
const TickType_t stopDelay = 20 / portTICK_PERIOD_MS;
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay( bitDelay );
for (uint8_t i = 0 ; i < 5; i++) {
// ESP_LOGI(TAG, "[%d]: %d", i, !(word & (1 << (4-i))));
gpio_set_level(BLINK_GPIO, !(word & (1 << i)));
vTaskDelay( bitDelay );
}
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay( stopDelay );
}
static void bitbang_task(void* arg)
{
const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;
ESP_LOGI(TAG, "hello!");
gpio_pad_select_gpio(BLINK_GPIO);
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
for( ;; )
while (1)
{
bitbang_word(0b11011);
bitbang_word(0b01001);
//bitbang_word(0b11111);
//bitbang_word(0b01101);
//bitbang_word(0b11000);
//bitbang_word(0b01101);
//bitbang_word(0b11000);
//bitbang_word(0b00100);
//bitbang_word(0b10110);
//bitbang_word(0b00011);
//bitbang_word(0b10110);
//bitbang_word(0b00011);
ESP_LOGI(TAG, "done");
vTaskDelay( xDelay );
ESP_LOGI(TAG, "sending...");
uint8_t data[] = {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++)
{
uint8_t rx_data[32] = {0};
size_t rx_data_len = xStreamBufferReceive(softuart.rx_buffer, &rx_data, sizeof(rx_data), pdMS_TO_TICKS(100));
if (rx_data_len > 0)
{
// ESP_LOGI(TAG, "%d bytes received:", rx_data_len);
for (size_t i = 0; i < rx_data_len; i++)
{
ESP_LOGI(TAG, "data[%d] = %02x (%d)", i, rx_data[i], rx_data[i]);
}
}
}
}
}
void rmtuart_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);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
xTaskCreate(bitbang_task, "bitbang_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL);
// xTaskCreate(echo_task, "uart_echo_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL);
ESP_LOGI(TAG, "starting up");
rmtuart_start(&rmtuart);
network.rx_buffer = rmtuart.rx_buffer;
network.tx_buffer = rmtuart.tx_buffer;
network_start(&network);
}

275
src/network.c Normal file
View file

@ -0,0 +1,275 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "lwip/sockets.h"
#include "network.h"
#include "baudot.h"
#include "config.priv.h"
static const char *TAG = "network";
static void tcp_server_task(network_ctx_t *ctx)
{
// gpio_pad_select_gpio(ctx->tty_en_gpio);
gpio_set_level(ctx->tty_en_gpio, 0);
gpio_set_direction(ctx->tty_en_gpio, GPIO_MODE_OUTPUT);
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(1337);
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (listen_sock < 0)
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err != 0)
{
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
return;
}
err = listen(listen_sock, 1);
if (err != 0)
{
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
}
fd_set readfds;
int client_sockets[MAX_CLIENTS];
for (int i = 0; i < MAX_CLIENTS; i++)
{
client_sockets[i] = 0;
}
int max_sd = listen_sock;
ESP_LOGI(TAG, "Socket listening");
baudot_context_t unicode_ctx = {0};
while (1)
{
// clear the socket set
FD_ZERO(&readfds);
// add master socket to set
FD_SET(listen_sock, &readfds);
max_sd = listen_sock;
int client_count = 0;
// add child sockets to set
for (int i = 0; i < MAX_CLIENTS; i++)
{
// socket descriptor
int sd = client_sockets[i];
// if valid socket descriptor then add to read list
if (sd > 0)
{
FD_SET(sd, &readfds);
client_count += 1;
}
// highest file descriptor number, need it for the select function
if (sd > max_sd)
{
max_sd = sd;
}
}
gpio_set_level(ctx->tty_en_gpio, client_count > 0 ? 1 : 0);
uint8_t rx_data[32] = {0};
size_t rx_data_len = xStreamBufferReceive(ctx->rx_buffer, &rx_data, sizeof(rx_data), pdMS_TO_TICKS(100));
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));
/*
for (int i = 0; i < rx_data_len; i++)
{
ESP_LOGI(TAG, "raw[%d] = %02x", i, rx_data[i]);
}
for (int i = 0; i < rx_unicode_data_len; i++)
{
ESP_LOGI(TAG, "unicode[%d] = %02x", i, rx_unicode_data[i]);
}
*/
struct timeval tv = {0, 10000};
int activity = select(max_sd + 1, &readfds, NULL, NULL, &tv);
// ESP_LOGI(TAG, "select() = %d; rx_data_len = %d", activity, rx_data_len);
if ((activity < 0) && (errno != EINTR))
{
ESP_LOGE(TAG, "select error");
}
// else its some IO operation on some other socket
for (int i = 0; i < MAX_CLIENTS; i++)
{
int sd = client_sockets[i];
if (client_sockets[i] > 0 && FD_ISSET(sd, &readfds))
{
// Check if it was for closing , and also read the
// incoming message
int read_buffer_len;
uint8_t read_buffer[32];
if ((read_buffer_len = read(sd, read_buffer, sizeof(read_buffer))) == 0)
{
ESP_LOGI(TAG, "Client disconnected");
close(sd);
client_sockets[i] = 0;
}
else if (read_buffer_len > 0)
{
ESP_LOGI(TAG, "Sending %d bytes", read_buffer_len);
uint8_t tx_baudot_data[64] = {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 (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);
send(client_sockets[i], &rx_unicode_data, rx_unicode_data_len, 0);
}
}
// If something happened on the master socket ,
// then its an incoming connection
if (FD_ISSET(listen_sock, &readfds))
{
struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t addr_len = sizeof(source_addr);
int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
if (sock < 0)
{
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
break;
}
int keepAlive = 1;
int keepIdle = 5;
int keepInterval = 5;
int keepCount = 3;
// Set tcp keepalive option
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
char addr_str[128];
if (source_addr.ss_family == PF_INET)
{
inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
}
bool connected = false;
// add new socket to array of sockets
for (int i = 0; i < MAX_CLIENTS; i++)
{
// if position is empty
if (client_sockets[i] == 0)
{
client_sockets[i] = sock;
connected = true;
ESP_LOGI(TAG, "Adding to list of sockets as %d", i);
break;
}
}
if (!connected)
{
ESP_LOGW(TAG, "max clients exceeded");
close(sock);
}
else
{
ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);
}
}
}
}
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI(TAG, "wifi connecting...");
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
esp_wifi_connect();
ESP_LOGI(TAG, "connect to the AP fail");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
}
}
void network_start(network_ctx_t *ctx)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_WIFI_ESSID,
.password = CONFIG_WIFI_PSK,
.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,
// .sae_pwe_h2e = ESP_WIFI_SAE_MODE,
// .sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
xTaskCreate((TaskFunction_t)tcp_server_task, "tcp_server_task", 4096, ctx, 10, NULL);
}

15
src/network.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include "softuart.h"
#define MAX_CLIENTS 32
typedef struct
{
int tty_en_gpio;
// softuart_ctx_t *softuart;
StreamBufferHandle_t rx_buffer;
StreamBufferHandle_t tx_buffer;
} network_ctx_t;
void network_start(network_ctx_t *network);

218
src/rmtuart.c Normal file
View file

@ -0,0 +1,218 @@
//
// This is a fairly naive RMT-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.
//
// 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 "rmtuart.h"
static const char *TAG = "rmtuart";
static bool rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
void rmtuart_transmit_words(rmtuart_ctx_t *ctx)
{
while (true)
{
uint8_t word;
size_t read_len = xStreamBufferReceive(ctx->tx_buffer, &word, 1, pdMS_TO_TICKS(1000));
if (read_len == 1)
{
rmtuart_transmit_word(ctx, word);
ESP_LOGI(TAG, "Done %d", word);
}
else
{
ESP_LOGI(TAG, "Nothing to transmit");
}
}
}
void rmtuart_receive_words(rmtuart_ctx_t *ctx)
{
uint32_t bit_length_ns = 1000000000 / ctx->baudrate;
rmt_receive_config_t receive_config = {
.signal_range_min_ns = bit_length_ns / 128,
.signal_range_max_ns = bit_length_ns * (ctx->data_bits + 2), // we don't care about stop bits here
};
rmt_symbol_word_t raw_symbols[64];
rmt_rx_done_event_data_t rx_data;
ESP_ERROR_CHECK(rmt_receive(ctx->rx_chan, raw_symbols, sizeof(raw_symbols), &receive_config));
while (1)
{
// wait for RX done signal
if (xQueueReceive(ctx->receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS)
{
// TODO: I think we should probably do some double buffering fuckery here maybe?
ESP_ERROR_CHECK(rmt_receive(ctx->rx_chan, raw_symbols, sizeof(raw_symbols), &receive_config));
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 sym_idx = 0;
int time_offs = 64;
int bitcnt = 0;
int bytecnt = 0;
uint8_t payload[32] = {0};
while (sym_idx < rx_data.num_symbols * 2)
{
int len = raw_helper[sym_idx] & 0x7fff;
int val = raw_helper[sym_idx] >> 15;
// ESP_LOGI(TAG, "sym[%d] = %d, %d - %d", sym_idx, len, val, time_offs);
// Last 0 value is always 0-length - extend it so that we can fill the trailing bits of the last byte
if (len > time_offs || (len == 0 && bitcnt <= ctx->data_bits + 1))
{
// ESP_LOGI(TAG, "%d: bit: %d", bitcnt, val);
if (bitcnt >= 1 && bitcnt <= ctx->data_bits + 1)
{
// ESP_LOGI(TAG, "payload[%d] |= %d", bytecnt, (!val << (bitcnt - 1)));
payload[bytecnt] |= (!val << (bitcnt - 1));
}
bitcnt += 1;
time_offs += bit_length;
if (bitcnt == ctx->data_bits + 1)
{
// ESP_LOGI(TAG, "payload[%d] = %d", bytecnt, payload[bytecnt]);
// jump to next rising edge
time_offs = 64;
if (val)
{
sym_idx += 2;
}
else
{
sym_idx += 1;
}
bytecnt += 1;
payload[bytecnt] = 0;
bitcnt = 0;
}
}
else
{
// We are past current period, move to the next symbol
time_offs -= len;
sym_idx += 1;
}
}
ESP_LOGI(TAG, "data received! symbols: %d", rx_data.num_symbols);
for (int sym = 0; sym < rx_data.num_symbols; sym++)
{
ESP_LOGI(TAG, "%d: %d@%d, %d@%d", sym, raw_symbols[sym].duration0, raw_symbols[sym].level0, raw_symbols[sym].duration1, raw_symbols[sym].level1);
}
if (bytecnt)
{
int sent = xStreamBufferSend(ctx->rx_buffer, &payload, bytecnt, pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "bytecnt: %d; sent: %d", bytecnt, sent);
}
else
{
ESP_LOGI(TAG, "bytecnt: %d", bytecnt);
}
}
}
}
void rmtuart_transmit_word(rmtuart_ctx_t *ctx, uint32_t word)
{
rmt_transmit_config_t tx_config = {
.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;
uint16_t data[16] = {
bit_ticks | (1 << 15),
};
for (int bit = 0; bit < ctx->data_bits; bit++)
{
data[data_len++] = bit_ticks | (!((word >> bit) & 1) << 15);
}
for (int bit = 0; bit < ctx->stop_bits; bit++)
{
data[data_len++] = bit_ticks | (0 << 15);
}
// RMT data needs to be 32-bit aligned - add a dummy item
if (data_len % 2 == 1)
{
data[data_len++] = 0;
}
ESP_ERROR_CHECK(rmt_transmit(ctx->tx_chan, ctx->copy_encoder, data, data_len * 2, &tx_config));
ESP_ERROR_CHECK(rmt_tx_wait_all_done(ctx->tx_chan, portMAX_DELAY));
}
void rmtuart_start(rmtuart_ctx_t *ctx)
{
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->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);
ESP_LOGI(TAG, "Init finished");
}

34
src/rmtuart.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/stream_buffer.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
typedef struct
{
int tx_gpio;
int rx_gpio;
int baudrate;
int data_bits;
int stop_bits;
StreamBufferHandle_t rx_buffer;
StreamBufferHandle_t tx_buffer;
rmt_clock_source_t clk_src;
int resolution_hz;
rmt_channel_handle_t tx_chan;
rmt_channel_handle_t rx_chan;
rmt_encoder_handle_t copy_encoder;
QueueHandle_t receive_queue;
} rmtuart_ctx_t;
void rmtuart_start(rmtuart_ctx_t *ctx);
void rmtuart_transmit_word(rmtuart_ctx_t *ctx, uint32_t word);

103
src/softuart.c Normal file
View file

@ -0,0 +1,103 @@
#include "softuart.h"
// #include "rom/gpio.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char *TAG = "softuart";
void softuart_bitbang_word(softuart_ctx_t *ctx, uint8_t word)
{
const TickType_t bitDelay = 20 / portTICK_PERIOD_MS;
const TickType_t stopDelay = 30 / portTICK_PERIOD_MS;
gpio_set_level(ctx->tx_gpio, 1);
vTaskDelay(bitDelay);
for (uint8_t i = 0; i < 5; i++)
{
// ESP_LOGI(TAG, "[%d]: %d", i, !(word & (1 << (4-i))));
gpio_set_level(ctx->tx_gpio, !(word & (1 << i)));
vTaskDelay(bitDelay);
}
gpio_set_level(ctx->tx_gpio, 0);
vTaskDelay(stopDelay);
}
uint8_t softuart_receive_word(softuart_ctx_t *ctx)
{
const TickType_t idleDelay = 10 / portTICK_PERIOD_MS;
const TickType_t bitDelay = 20 / portTICK_PERIOD_MS;
while (gpio_get_level(ctx->rx_gpio) == 1)
{
vTaskDelay(idleDelay);
}
while (gpio_get_level(ctx->rx_gpio) == 0)
{
vTaskDelay(idleDelay);
}
uint8_t word = 0;
// 5 data bits, 1 start bit, 1 stop bit
for (uint8_t i = 0; i < 6; i++)
{
word |= (!gpio_get_level(ctx->rx_gpio)) << i;
vTaskDelay(bitDelay);
}
return (word >> 1) & 0b11111;
}
static void softuart_receive_task(softuart_ctx_t *ctx)
{
// gpio_install_isr_service(0);
// gpio_pad_select_gpio(ctx->rx_gpio);
gpio_set_direction(ctx->rx_gpio, GPIO_MODE_INPUT);
gpio_pulldown_en(ctx->rx_gpio);
while (true)
{
uint8_t word = softuart_receive_word(ctx);
int sent = xStreamBufferSend(ctx->rx_buffer, &word, 1, pdMS_TO_TICKS(1));
if (sent != 1)
{
ESP_LOGW(TAG, "rx overflow! %02x", word);
}
}
// gpio_set_intr_type(TTY_RX_GPIO, GPIO_INTR_POSEDGE);
// gpio_isr_handler_add(TTY_RX_GPIO, softuart_receive, 0);
// gpio_intr_enable(TTY_RX_GPIO);
}
static void softuart_transmit_task(softuart_ctx_t *ctx)
{
// gpio_pad_select_gpio(ctx->tx_gpio);
gpio_set_level(ctx->tx_gpio, 0);
gpio_set_direction(ctx->tx_gpio, GPIO_MODE_OUTPUT);
for (;;)
{
uint8_t word;
size_t read_len = xStreamBufferReceive(ctx->tx_buffer, &word, 1, pdMS_TO_TICKS(1000));
if (read_len == 1)
{
softuart_bitbang_word(ctx, word);
// ESP_LOGI(TAG, "done");
}
else
{
ESP_LOGI(TAG, "Nothing to transmit");
}
}
}
void softuart_start(softuart_ctx_t *ctx)
{
ctx->rx_buffer = xStreamBufferCreate(128, 1);
ctx->tx_buffer = xStreamBufferCreate(128, 1);
xTaskCreate((TaskFunction_t)softuart_transmit_task, "softuart_transmit_task", 2048, ctx, 40, NULL);
xTaskCreate((TaskFunction_t)softuart_receive_task, "softuart_receive_task", 2048, ctx, 40, NULL);
}

16
src/softuart.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/stream_buffer.h"
typedef struct
{
int tx_gpio;
int rx_gpio;
StreamBufferHandle_t rx_buffer;
StreamBufferHandle_t tx_buffer;
} softuart_ctx_t;
void softuart_start(softuart_ctx_t *ctx);