250 lines
9.0 KiB
C++
250 lines
9.0 KiB
C++
#include "serial_task.h"
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
|
|
QueueHandle_t queueSD = NULL;
|
|
QueueHandle_t queueWeb = NULL;
|
|
QueueHandle_t queueTX = NULL;
|
|
|
|
volatile uint32_t serialBaud = DEFAULT_BAUD_RATE;
|
|
volatile uint8_t serialDataBits = 8;
|
|
volatile char serialParity = 'N';
|
|
volatile uint8_t serialStopBits = 1;
|
|
volatile uint8_t serialPort = 0; // 0=UART2, 1=UART0(USB)
|
|
|
|
// Pointer to the active monitoring serial port
|
|
HardwareSerial* monSerial = &Serial2;
|
|
|
|
static SemaphoreHandle_t serialMutex = NULL;
|
|
|
|
void getTimestamp(char *buf, size_t len) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
struct tm timeinfo;
|
|
localtime_r(&tv.tv_sec, &timeinfo);
|
|
int ms = tv.tv_usec / 1000;
|
|
snprintf(buf, len, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
|
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
|
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, ms);
|
|
}
|
|
|
|
static uint32_t getSerialConfig(uint8_t dataBits, char parity, uint8_t stopBits) {
|
|
if (dataBits == 8 && parity == 'N' && stopBits == 1) return SERIAL_8N1;
|
|
if (dataBits == 8 && parity == 'E' && stopBits == 1) return SERIAL_8E1;
|
|
if (dataBits == 8 && parity == 'O' && stopBits == 1) return SERIAL_8O1;
|
|
if (dataBits == 8 && parity == 'N' && stopBits == 2) return SERIAL_8N2;
|
|
if (dataBits == 8 && parity == 'E' && stopBits == 2) return SERIAL_8E2;
|
|
if (dataBits == 8 && parity == 'O' && stopBits == 2) return SERIAL_8O2;
|
|
if (dataBits == 7 && parity == 'N' && stopBits == 1) return SERIAL_7N1;
|
|
if (dataBits == 7 && parity == 'E' && stopBits == 1) return SERIAL_7E1;
|
|
if (dataBits == 7 && parity == 'O' && stopBits == 1) return SERIAL_7O1;
|
|
if (dataBits == 6 && parity == 'N' && stopBits == 1) return SERIAL_6N1;
|
|
if (dataBits == 6 && parity == 'E' && stopBits == 1) return SERIAL_6E1;
|
|
if (dataBits == 6 && parity == 'O' && stopBits == 1) return SERIAL_6O1;
|
|
if (dataBits == 5 && parity == 'N' && stopBits == 1) return SERIAL_5N1;
|
|
if (dataBits == 5 && parity == 'E' && stopBits == 1) return SERIAL_5E1;
|
|
if (dataBits == 5 && parity == 'O' && stopBits == 1) return SERIAL_5O1;
|
|
return SERIAL_8N1;
|
|
}
|
|
|
|
// ============================================================
|
|
// Switch serial port: 0=UART2(GPIO16/17), 1=UART0(USB)
|
|
// ============================================================
|
|
void switchSerialPort(uint8_t port) {
|
|
if (serialMutex) xSemaphoreTake(serialMutex, portMAX_DELAY);
|
|
|
|
if (port == 1) {
|
|
// Switch to UART0 (USB) for testing
|
|
// Note: UART0 is already running (Serial.begin in setup)
|
|
// Just point monSerial to it. Debug output still goes to Serial TX,
|
|
// but RX task only reads what PC sends (RX buffer), so no conflict.
|
|
Serial2.flush();
|
|
Serial2.end();
|
|
monSerial = &Serial;
|
|
serialPort = 1;
|
|
Serial.println("[Serial] Switched to UART0 (USB) - test mode");
|
|
Serial.println("[Serial] Send data from PC terminal to test");
|
|
} else {
|
|
// Switch to UART2 (GPIO16/17) for field use
|
|
uint32_t config = getSerialConfig(serialDataBits, serialParity, serialStopBits);
|
|
Serial2.begin(serialBaud, config, SERIAL2_RX_PIN, SERIAL2_TX_PIN);
|
|
Serial2.setRxBufferSize(DEFAULT_RX_BUFFER);
|
|
monSerial = &Serial2;
|
|
serialPort = 0;
|
|
Serial.printf("[Serial] Switched to UART2 (TX=%d, RX=%d)\n",
|
|
SERIAL2_TX_PIN, SERIAL2_RX_PIN);
|
|
}
|
|
|
|
// Drain any old data from new port's RX buffer
|
|
while (monSerial->available()) monSerial->read();
|
|
|
|
if (serialMutex) xSemaphoreGive(serialMutex);
|
|
}
|
|
|
|
// ============================================================
|
|
// Reconfigure baud/parity/etc (applies to current port)
|
|
// ============================================================
|
|
void reconfigureSerial(uint32_t baud, uint8_t dataBits, char parity, uint8_t stopBits) {
|
|
if (serialMutex) xSemaphoreTake(serialMutex, portMAX_DELAY);
|
|
|
|
monSerial->flush();
|
|
|
|
if (serialPort == 0) {
|
|
// UART2 - full reconfigure
|
|
Serial2.end();
|
|
delay(50);
|
|
uint32_t config = getSerialConfig(dataBits, parity, stopBits);
|
|
Serial2.begin(baud, config, SERIAL2_RX_PIN, SERIAL2_TX_PIN);
|
|
Serial2.setRxBufferSize(DEFAULT_RX_BUFFER);
|
|
} else {
|
|
// UART0 - reconfigure USB serial baud
|
|
Serial.end();
|
|
delay(50);
|
|
Serial.begin(baud);
|
|
}
|
|
|
|
serialBaud = baud;
|
|
serialDataBits = dataBits;
|
|
serialParity = parity;
|
|
serialStopBits = stopBits;
|
|
|
|
Serial.printf("[Serial] Reconfigured: %lu %d%c%d (port=%s)\n",
|
|
baud, dataBits, parity, stopBits,
|
|
serialPort == 0 ? "UART2" : "USB");
|
|
|
|
if (serialMutex) xSemaphoreGive(serialMutex);
|
|
}
|
|
|
|
// ============================================================
|
|
// Init
|
|
// ============================================================
|
|
void serialTaskInit() {
|
|
queueSD = xQueueCreate(QUEUE_SD_SIZE, sizeof(LogEntry*));
|
|
queueWeb = xQueueCreate(QUEUE_WEB_SIZE, sizeof(LogEntry*));
|
|
queueTX = xQueueCreate(QUEUE_TX_SIZE, sizeof(LogEntry*));
|
|
|
|
serialMutex = xSemaphoreCreateMutex();
|
|
|
|
// Default: UART2
|
|
uint32_t config = getSerialConfig(serialDataBits, serialParity, serialStopBits);
|
|
Serial2.begin(serialBaud, config, SERIAL2_RX_PIN, SERIAL2_TX_PIN);
|
|
Serial2.setRxBufferSize(DEFAULT_RX_BUFFER);
|
|
monSerial = &Serial2;
|
|
serialPort = 0;
|
|
|
|
Serial.printf("[Serial] Init: %lu %d%c%d on UART2 (TX=%d, RX=%d)\n",
|
|
serialBaud, serialDataBits, (char)serialParity, serialStopBits,
|
|
SERIAL2_TX_PIN, SERIAL2_RX_PIN);
|
|
Serial.println("[Serial] Port switchable via Settings (UART2/USB)");
|
|
|
|
xTaskCreatePinnedToCore(serialRxTask, "SerialRX", TASK_STACK_SERIAL,
|
|
NULL, TASK_PRIORITY_SERIAL, NULL, 1);
|
|
|
|
xTaskCreatePinnedToCore(serialTxTask, "SerialTX", TASK_STACK_SERIAL,
|
|
NULL, TASK_PRIORITY_SERIAL - 1, NULL, 1);
|
|
}
|
|
|
|
// ============================================================
|
|
// Flush assembled line to SD and Web queues
|
|
// ============================================================
|
|
static void flushLineToQueues(char *lineBuf, int &linePos) {
|
|
if (linePos <= 0) return;
|
|
lineBuf[linePos] = '\0';
|
|
|
|
LogEntry *entry = (LogEntry*)pvPortMalloc(sizeof(LogEntry));
|
|
if (entry) {
|
|
getTimestamp(entry->timestamp, sizeof(entry->timestamp));
|
|
entry->direction = 'R';
|
|
memcpy(entry->data, lineBuf, linePos + 1);
|
|
entry->dataLen = linePos;
|
|
|
|
if (xQueueSend(queueSD, &entry, 0) != pdTRUE) {
|
|
LogEntry *old;
|
|
if (xQueueReceive(queueSD, &old, 0) == pdTRUE) vPortFree(old);
|
|
xQueueSend(queueSD, &entry, 0);
|
|
}
|
|
|
|
LogEntry *webEntry = (LogEntry*)pvPortMalloc(sizeof(LogEntry));
|
|
if (webEntry) {
|
|
memcpy(webEntry, entry, sizeof(LogEntry));
|
|
if (xQueueSend(queueWeb, &webEntry, 0) != pdTRUE) vPortFree(webEntry);
|
|
}
|
|
}
|
|
linePos = 0;
|
|
}
|
|
|
|
// ============================================================
|
|
// RX Task - reads from monSerial (UART2 or UART0)
|
|
// ============================================================
|
|
void serialRxTask(void *param) {
|
|
static char lineBuf[LOG_LINE_MAX_LEN];
|
|
int linePos = 0;
|
|
TickType_t lastActivity = xTaskGetTickCount();
|
|
|
|
Serial.println("[Task] SerialRX started on core " + String(xPortGetCoreID()));
|
|
|
|
while (true) {
|
|
HardwareSerial* port = monSerial; // Local copy for thread safety
|
|
int available = port->available();
|
|
|
|
if (available > 0) {
|
|
int space = (int)(LOG_LINE_MAX_LEN - linePos - 1);
|
|
if (space <= 0) { flushLineToQueues(lineBuf, linePos); continue; }
|
|
|
|
int toRead = (available < space) ? available : space;
|
|
for (int i = 0; i < toRead; i++) {
|
|
char c = port->read();
|
|
if (c == '\n' || c == '\r') {
|
|
if (linePos > 0) flushLineToQueues(lineBuf, linePos);
|
|
continue;
|
|
}
|
|
// Only accept printable ASCII (0x20~0x7E) and TAB (0x09)
|
|
if ((c >= 0x20 && c <= 0x7E) || c == '\t') {
|
|
lineBuf[linePos++] = c;
|
|
}
|
|
}
|
|
lastActivity = xTaskGetTickCount();
|
|
} else {
|
|
if (linePos > 0 && (xTaskGetTickCount() - lastActivity) > pdMS_TO_TICKS(100)) {
|
|
flushLineToQueues(lineBuf, linePos);
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// TX Task - writes to monSerial (UART2 or UART0)
|
|
// ============================================================
|
|
void serialTxTask(void *param) {
|
|
Serial.println("[Task] SerialTX started on core " + String(xPortGetCoreID()));
|
|
|
|
while (true) {
|
|
LogEntry *entry;
|
|
if (xQueueReceive(queueTX, &entry, pdMS_TO_TICKS(50)) == pdTRUE) {
|
|
HardwareSerial* port = monSerial;
|
|
|
|
if (serialMutex) xSemaphoreTake(serialMutex, portMAX_DELAY);
|
|
port->write((uint8_t*)entry->data, entry->dataLen);
|
|
port->flush();
|
|
if (serialMutex) xSemaphoreGive(serialMutex);
|
|
|
|
LogEntry *sdEntry = (LogEntry*)pvPortMalloc(sizeof(LogEntry));
|
|
if (sdEntry) {
|
|
memcpy(sdEntry, entry, sizeof(LogEntry));
|
|
sdEntry->direction = 'T';
|
|
if (xQueueSend(queueSD, &sdEntry, pdMS_TO_TICKS(10)) != pdTRUE) vPortFree(sdEntry);
|
|
}
|
|
|
|
LogEntry *webEntry = (LogEntry*)pvPortMalloc(sizeof(LogEntry));
|
|
if (webEntry) {
|
|
memcpy(webEntry, entry, sizeof(LogEntry));
|
|
webEntry->direction = 'T';
|
|
if (xQueueSend(queueWeb, &webEntry, 0) != pdTRUE) vPortFree(webEntry);
|
|
}
|
|
|
|
vPortFree(entry);
|
|
}
|
|
}
|
|
}
|