|
@@ -0,0 +1,300 @@
|
|
|
+#define BAUD_RATE 115200
|
|
|
+
|
|
|
+#include "tty.h"
|
|
|
+#include "config.h"
|
|
|
+#include "fw.h"
|
|
|
+
|
|
|
+#include "rom/crc.h"
|
|
|
+
|
|
|
+#include <USB.h>
|
|
|
+#include <HardwareSerial.h>
|
|
|
+
|
|
|
+#define SOH '\001'
|
|
|
+#define STX '\002'
|
|
|
+#define ETX '\003'
|
|
|
+#define EOT '\004'
|
|
|
+#define ENQ '\005'
|
|
|
+#define ACK '\006'
|
|
|
+#define XON '\021' // DC1
|
|
|
+#define WRST '\022' // DC2 - window = 0
|
|
|
+#define XOFF '\023' // DC3
|
|
|
+#define WGO '\024' // DC4 - window + 256 bytes
|
|
|
+#define NAK '\025'
|
|
|
+#define SYN '\026'
|
|
|
+#define ETB '\027' // Not in upload mode
|
|
|
+#define CAN '\030'
|
|
|
+#define EM '\031' // Packet offset too high
|
|
|
+#define FS '\034'
|
|
|
+#define GS '\035'
|
|
|
+#define RS '\036'
|
|
|
+#define US '\037'
|
|
|
+
|
|
|
+#define WGO_CHUNK 256
|
|
|
+
|
|
|
+#define STREAMBUF_SIZE 8192
|
|
|
+
|
|
|
+static char enq_str[] = "\026\035MAX80 v0\004\r\n";
|
|
|
+static const char fwupload_start[] =
|
|
|
+ "\034\001: /// MAX80 FW UPLOAD ~@~ $\035\r\n";
|
|
|
+
|
|
|
+void TTY::reset()
|
|
|
+{
|
|
|
+ rx.state = rx_state::normal;
|
|
|
+}
|
|
|
+
|
|
|
+TTY::TTY(Stream &port)
|
|
|
+{
|
|
|
+ _port = &port;
|
|
|
+ rx_sbuf = NULL;
|
|
|
+ win_sem = xSemaphoreCreateMutexStatic(&win_sem_ss);
|
|
|
+ reset();
|
|
|
+}
|
|
|
+
|
|
|
+TTY::~TTY()
|
|
|
+{
|
|
|
+ if (rx_sbuf)
|
|
|
+ vStreamBufferDelete(rx_sbuf);
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_update_window(bool rst)
|
|
|
+{
|
|
|
+ xSemaphoreTake(win_sem, portMAX_DELAY);
|
|
|
+
|
|
|
+ if (rst) {
|
|
|
+ port().write(WRST);
|
|
|
+ tx_window = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (xStreamBufferSpacesAvailable(rx_sbuf) >= tx_window + WGO_CHUNK) {
|
|
|
+ tx_window += WGO_CHUNK;
|
|
|
+ port().write(WGO); // Space for one more window
|
|
|
+ }
|
|
|
+
|
|
|
+ xSemaphoreGive(win_sem);
|
|
|
+}
|
|
|
+
|
|
|
+int TTY::rxdata(void *buf, size_t len)
|
|
|
+{
|
|
|
+ if (!rx_sbuf)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ size_t rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY);
|
|
|
+ _update_window(false);
|
|
|
+
|
|
|
+ return rcv;
|
|
|
+}
|
|
|
+
|
|
|
+int TTY::rxdata(token_t me, void *buf, size_t len)
|
|
|
+{
|
|
|
+ TTY *tty = (TTY *)me;
|
|
|
+ return tty->rxdata(buf, len);
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_upload_begin()
|
|
|
+{
|
|
|
+ if (rx_sbuf)
|
|
|
+ xStreamBufferReset(rx_sbuf);
|
|
|
+ else
|
|
|
+ rx_sbuf = xStreamBufferCreate(STREAMBUF_SIZE, 1);
|
|
|
+
|
|
|
+ if (!rx_sbuf)
|
|
|
+ goto nak;
|
|
|
+
|
|
|
+ if (firmware_update_start((read_func_t)TTY::rxdata, (token_t)this))
|
|
|
+ goto nak;
|
|
|
+
|
|
|
+ // Respond with WRST + n*WGO
|
|
|
+ _update_window(true);
|
|
|
+ rx.state = rx_state::hdr;
|
|
|
+ return;
|
|
|
+
|
|
|
+ nak:
|
|
|
+ port().write(NAK);
|
|
|
+ rx.state = rx_state::normal;
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_onerr()
|
|
|
+{
|
|
|
+ if (rx.state != rx_state::normal)
|
|
|
+ _update_window(true);
|
|
|
+}
|
|
|
+
|
|
|
+// Change this to be a buffer...
|
|
|
+void TTY::_onrx()
|
|
|
+{
|
|
|
+ int byte;
|
|
|
+ int len;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ int byte = port().read();
|
|
|
+ if (byte == -1)
|
|
|
+ break; // No more data
|
|
|
+
|
|
|
+ switch (rx.state) {
|
|
|
+ case rx_state::normal:
|
|
|
+ if (byte == fwupload_start[rx.rlen]) {
|
|
|
+ if (!fwupload_start[++rx.rlen])
|
|
|
+ _upload_begin();
|
|
|
+ } else {
|
|
|
+ rx.rlen = 0;
|
|
|
+ switch (byte) {
|
|
|
+ case ENQ:
|
|
|
+ port().write(enq_str);
|
|
|
+ byte = ETB;
|
|
|
+ break;
|
|
|
+ case ETX:
|
|
|
+ case EOT:
|
|
|
+ case CAN:
|
|
|
+ byte = ETB; // Not in file upload state
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // Normal echo
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case rx_state::hdr:
|
|
|
+ if (rx.rlen > 0 || byte == STX) {
|
|
|
+ rx.hdr_raw[rx.rlen++] = byte;
|
|
|
+ byte = -1;
|
|
|
+ if (rx.rlen == sizeof rx.hdr) {
|
|
|
+ // Start of data packet
|
|
|
+ rx.state = rx_state::data;
|
|
|
+ rx.rlen = 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ switch (byte) {
|
|
|
+ case ETX:
|
|
|
+ case EOT:
|
|
|
+ case CAN:
|
|
|
+ reset();
|
|
|
+ byte = CAN;
|
|
|
+ break;
|
|
|
+ case ENQ:
|
|
|
+ byte = SYN; // In file upload state
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // Normal echo
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case rx_state::data:
|
|
|
+ rx_data[rx.rlen++] = byte;
|
|
|
+ byte = -1;
|
|
|
+ // rx.hdr.len = packet data len - 1
|
|
|
+ if (rx.rlen > rx.hdr.len) {
|
|
|
+ int have = rx.rlen;
|
|
|
+ uint32_t crc = ~crc32_le(0, rx_data, have);
|
|
|
+
|
|
|
+ if (crc != rx.hdr.crc) {
|
|
|
+ byte = NAK;
|
|
|
+ } else if (rx.hdr.offs > rx.last_ack) {
|
|
|
+ byte = EM;
|
|
|
+ } else {
|
|
|
+ int sent = 0;
|
|
|
+
|
|
|
+ tx_window -= rx.rlen;
|
|
|
+
|
|
|
+ if (rx.hdr.offs + rx.rlen <= rx.last_ack) {
|
|
|
+ have = 0;
|
|
|
+ } else {
|
|
|
+ uint32_t skip = rx.last_ack - rx.hdr.offs;
|
|
|
+ have -= skip;
|
|
|
+ sent = xStreamBufferSend(rx_sbuf, rx_data+skip, have, 0);
|
|
|
+ rx.last_ack += sent;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sent == have)
|
|
|
+ byte = ACK;
|
|
|
+ else
|
|
|
+ _update_window(true);
|
|
|
+
|
|
|
+ rx.state = rx_state::hdr;
|
|
|
+ rx.rlen = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (byte >= 0)
|
|
|
+ port().write(byte);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_onconnect()
|
|
|
+{
|
|
|
+ port().write(XON);
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_onbreak()
|
|
|
+{
|
|
|
+ reset();
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::_ondisconnect()
|
|
|
+{
|
|
|
+ reset();
|
|
|
+}
|
|
|
+
|
|
|
+static TTY *uart_tty, *usb_tty;
|
|
|
+
|
|
|
+void TTY::usb_onevent(void *arg, esp_event_base_t event_base,
|
|
|
+ int32_t event_id, void *event_data)
|
|
|
+{
|
|
|
+ switch (event_id) {
|
|
|
+ case ARDUINO_USB_CDC_CONNECTED_EVENT:
|
|
|
+ case ARDUINO_USB_CDC_LINE_STATE_EVENT:
|
|
|
+ usb_tty->_onconnect();
|
|
|
+ break;
|
|
|
+ case ARDUINO_USB_CDC_DISCONNECTED_EVENT:
|
|
|
+ usb_tty->_ondisconnect();
|
|
|
+ break;
|
|
|
+ case ARDUINO_USB_CDC_RX_EVENT:
|
|
|
+ usb_tty->_onrx();
|
|
|
+ break;
|
|
|
+ case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT:
|
|
|
+ usb_tty->_onerr();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // Do nothing
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::uart_onrx(void)
|
|
|
+{
|
|
|
+ uart_tty->_onrx();
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::uart_onerr(hardwareSerial_error_t err)
|
|
|
+{
|
|
|
+ switch (err) {
|
|
|
+ case UART_BREAK_ERROR:
|
|
|
+ uart_tty->_onbreak();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ uart_tty->_onerr();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::init()
|
|
|
+{
|
|
|
+ enq_str[sizeof(enq_str)-5] += max80_board_version;
|
|
|
+
|
|
|
+ uart_tty = new TTY(Serial0);
|
|
|
+ Serial0.begin(BAUD_RATE);
|
|
|
+ Serial0.onReceive(uart_onrx, false);
|
|
|
+ Serial0.onReceiveError(uart_onerr);
|
|
|
+
|
|
|
+ usb_tty = new TTY(Serial);
|
|
|
+ Serial.onEvent(usb_onevent);
|
|
|
+}
|
|
|
+
|
|
|
+void TTY::ping()
|
|
|
+{
|
|
|
+}
|