/* * This file is part of Cleanflight. * * Cleanflight is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cleanflight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cleanflight. If not, see . */ #include #include #include #include "platform.h" #include "build/build_config.h" #include "common/utils.h" #include "config/parameter_group.h" #include "config/parameter_group_ids.h" #include "fc/config.h" #include "drivers/time.h" #include "drivers/system.h" #include "drivers/serial.h" #if defined(USE_SOFTSERIAL1) || defined(USE_SOFTSERIAL2) #include "drivers/serial_softserial.h" #endif #define USE_UART (defined(USE_UART1) || defined(USE_UART2) || defined(USE_UART3) || defined(USE_UART4) || defined(USE_UART5) || defined(USE_UART6) || defined(USE_UART7) || defined(USE_UART8)) #define USE_SERIAL (USE_UART || defined(USE_SOFTSERIAL1) || defined(USE_SOFTSERIAL2)) #if USE_UART #include "drivers/serial_uart.h" #endif #ifdef SITL #include "drivers/serial_tcp.h" #endif #include "drivers/light_led.h" #if defined(USE_VCP) #include "drivers/serial_usb_vcp.h" #endif #include "io/serial.h" #include "fc/cli.h" #include "msp/msp_serial.h" #ifdef TELEMETRY #include "telemetry/telemetry.h" #endif static serialPortUsage_t serialPortUsageList[SERIAL_PORT_COUNT]; const serialPortIdentifier_e serialPortIdentifiers[SERIAL_PORT_COUNT] = { #ifdef USE_VCP SERIAL_PORT_USB_VCP, #endif #ifdef USE_UART1 SERIAL_PORT_USART1, #endif #ifdef USE_UART2 SERIAL_PORT_USART2, #endif #ifdef USE_UART3 SERIAL_PORT_USART3, #endif #ifdef USE_UART4 SERIAL_PORT_UART4, #endif #ifdef USE_UART5 SERIAL_PORT_UART5, #endif #ifdef USE_UART6 SERIAL_PORT_USART6, #endif #ifdef USE_UART7 SERIAL_PORT_USART7, #endif #ifdef USE_UART8 SERIAL_PORT_USART8, #endif #ifdef USE_SOFTSERIAL1 SERIAL_PORT_SOFTSERIAL1, #endif #ifdef USE_SOFTSERIAL2 SERIAL_PORT_SOFTSERIAL2, #endif }; static uint8_t serialPortCount; const uint32_t baudRates[] = {0, 9600, 19200, 38400, 57600, 115200, 230400, 250000, 400000, 460800, 500000, 921600, 1000000, 1500000, 2000000, 2470000}; // see baudRate_e #define BAUD_RATE_COUNT (sizeof(baudRates) / sizeof(baudRates[0])) PG_REGISTER_WITH_RESET_FN(serialConfig_t, serialConfig, PG_SERIAL_CONFIG, 0); void pgResetFn_serialConfig(serialConfig_t *serialConfig) { memset(serialConfig, 0, sizeof(serialConfig_t)); for (int i = 0; i < SERIAL_PORT_COUNT; i++) { serialConfig->portConfigs[i].identifier = serialPortIdentifiers[i]; serialConfig->portConfigs[i].msp_baudrateIndex = BAUD_115200; serialConfig->portConfigs[i].gps_baudrateIndex = BAUD_57600; serialConfig->portConfigs[i].telemetry_baudrateIndex = BAUD_AUTO; serialConfig->portConfigs[i].blackbox_baudrateIndex = BAUD_115200; } serialConfig->portConfigs[0].functionMask = FUNCTION_MSP; #if defined(USE_VCP) && defined(USE_MSP_UART) if (serialConfig->portConfigs[0].identifier == SERIAL_PORT_USB_VCP) { serialPortConfig_t * uart1Config = serialFindPortConfiguration(SERIAL_PORT_USART1); if (uart1Config) { uart1Config->functionMask = FUNCTION_MSP; } } #endif #ifdef SERIALRX_UART serialPortConfig_t *serialRxUartConfig = serialFindPortConfiguration(SERIALRX_UART); if (serialRxUartConfig) { serialRxUartConfig->functionMask = FUNCTION_RX_SERIAL; } #endif serialConfig->reboot_character = 'R'; serialConfig->serial_update_rate_hz = 100; } baudRate_e lookupBaudRateIndex(uint32_t baudRate) { uint8_t index; for (index = 0; index < BAUD_RATE_COUNT; index++) { if (baudRates[index] == baudRate) { return index; } } return BAUD_AUTO; } int findSerialPortIndexByIdentifier(serialPortIdentifier_e identifier) { for (int index = 0; index < SERIAL_PORT_COUNT; index++) { if (serialPortIdentifiers[index] == identifier) { return index; } } return -1; } serialPortUsage_t *findSerialPortUsageByIdentifier(serialPortIdentifier_e identifier) { uint8_t index; for (index = 0; index < SERIAL_PORT_COUNT; index++) { serialPortUsage_t *candidate = &serialPortUsageList[index]; if (candidate->identifier == identifier) { return candidate; } } return NULL; } serialPortUsage_t *findSerialPortUsageByPort(serialPort_t *serialPort) { uint8_t index; for (index = 0; index < SERIAL_PORT_COUNT; index++) { serialPortUsage_t *candidate = &serialPortUsageList[index]; if (candidate->serialPort == serialPort) { return candidate; } } return NULL; } typedef struct findSerialPortConfigState_s { uint8_t lastIndex; } findSerialPortConfigState_t; static findSerialPortConfigState_t findSerialPortConfigState; serialPortConfig_t *findSerialPortConfig(serialPortFunction_e function) { memset(&findSerialPortConfigState, 0, sizeof(findSerialPortConfigState)); return findNextSerialPortConfig(function); } serialPortConfig_t *findNextSerialPortConfig(serialPortFunction_e function) { while (findSerialPortConfigState.lastIndex < SERIAL_PORT_COUNT) { serialPortConfig_t *candidate = &serialConfigMutable()->portConfigs[findSerialPortConfigState.lastIndex++]; if (candidate->functionMask & function) { return candidate; } } return NULL; } typedef struct findSharedSerialPortState_s { uint8_t lastIndex; } findSharedSerialPortState_t; portSharing_e determinePortSharing(const serialPortConfig_t *portConfig, serialPortFunction_e function) { if (!portConfig || (portConfig->functionMask & function) == 0) { return PORTSHARING_UNUSED; } return portConfig->functionMask == function ? PORTSHARING_NOT_SHARED : PORTSHARING_SHARED; } bool isSerialPortShared(const serialPortConfig_t *portConfig, uint16_t functionMask, serialPortFunction_e sharedWithFunction) { return (portConfig) && (portConfig->functionMask & sharedWithFunction) && (portConfig->functionMask & functionMask); } static findSharedSerialPortState_t findSharedSerialPortState; serialPort_t *findSharedSerialPort(uint16_t functionMask, serialPortFunction_e sharedWithFunction) { memset(&findSharedSerialPortState, 0, sizeof(findSharedSerialPortState)); return findNextSharedSerialPort(functionMask, sharedWithFunction); } serialPort_t *findNextSharedSerialPort(uint16_t functionMask, serialPortFunction_e sharedWithFunction) { while (findSharedSerialPortState.lastIndex < SERIAL_PORT_COUNT) { const serialPortConfig_t *candidate = &serialConfig()->portConfigs[findSharedSerialPortState.lastIndex++]; if (isSerialPortShared(candidate, functionMask, sharedWithFunction)) { const serialPortUsage_t *serialPortUsage = findSerialPortUsageByIdentifier(candidate->identifier); if (!serialPortUsage) { continue; } return serialPortUsage->serialPort; } } return NULL; } #ifdef TELEMETRY #define ALL_TELEMETRY_FUNCTIONS_MASK (TELEMETRY_SHAREABLE_PORT_FUNCTIONS_MASK | FUNCTION_TELEMETRY_HOTT | FUNCTION_TELEMETRY_SMARTPORT) #define ALL_FUNCTIONS_SHARABLE_WITH_MSP (FUNCTION_BLACKBOX | ALL_TELEMETRY_FUNCTIONS_MASK) #else #define ALL_FUNCTIONS_SHARABLE_WITH_MSP (FUNCTION_BLACKBOX) #endif bool isSerialConfigValid(const serialConfig_t *serialConfigToCheck) { UNUSED(serialConfigToCheck); /* * rules: * - 1 MSP port minimum, max MSP ports is defined and must be adhered to. * - MSP is allowed to be shared with EITHER any telemetry OR blackbox. * (using either / or, switching based on armed / disarmed or an AUX channel if 'telemetry_switch' is true * - serial RX and FrSky / LTM / MAVLink telemetry can be shared * (serial RX using RX line, telemetry using TX line) * - No other sharing combinations are valid. */ uint8_t mspPortCount = 0; for (int index = 0; index < SERIAL_PORT_COUNT; index++) { const serialPortConfig_t *portConfig = &serialConfigToCheck->portConfigs[index]; if (portConfig->functionMask & FUNCTION_MSP) { mspPortCount++; } uint8_t bitCount = BITCOUNT(portConfig->functionMask); if (bitCount > 1) { // shared if (bitCount > 2) { return false; } if ((portConfig->functionMask & FUNCTION_MSP) && (portConfig->functionMask & ALL_FUNCTIONS_SHARABLE_WITH_MSP)) { // MSP & telemetry #ifdef TELEMETRY } else if (telemetryCheckRxPortShared(portConfig)) { // serial RX & telemetry #endif } else { // some other combination return false; } } } if (mspPortCount == 0 || mspPortCount > MAX_MSP_PORT_COUNT) { return false; } return true; } serialPortConfig_t *serialFindPortConfiguration(serialPortIdentifier_e identifier) { for (int index = 0; index < SERIAL_PORT_COUNT; index++) { serialPortConfig_t *candidate = &serialConfigMutable()->portConfigs[index]; if (candidate->identifier == identifier) { return candidate; } } return NULL; } bool doesConfigurationUsePort(serialPortIdentifier_e identifier) { serialPortConfig_t *candidate = serialFindPortConfiguration(identifier); return candidate != NULL && candidate->functionMask; } serialPort_t *openSerialPort( serialPortIdentifier_e identifier, serialPortFunction_e function, serialReceiveCallbackPtr rxCallback, uint32_t baudRate, portMode_t mode, portOptions_t options) { #if !(USE_SERIAL) UNUSED(rxCallback); UNUSED(baudRate); UNUSED(mode); UNUSED(options); #endif serialPortUsage_t *serialPortUsage = findSerialPortUsageByIdentifier(identifier); if (!serialPortUsage || serialPortUsage->function != FUNCTION_NONE) { // not available / already in use return NULL; } serialPort_t *serialPort = NULL; switch(identifier) { #ifdef USE_VCP case SERIAL_PORT_USB_VCP: serialPort = usbVcpOpen(); break; #endif #if defined(USE_UART) #ifdef USE_UART1 case SERIAL_PORT_USART1: #endif #ifdef USE_UART2 case SERIAL_PORT_USART2: #endif #ifdef USE_UART3 case SERIAL_PORT_USART3: #endif #ifdef USE_UART4 case SERIAL_PORT_UART4: #endif #ifdef USE_UART5 case SERIAL_PORT_UART5: #endif #ifdef USE_UART6 case SERIAL_PORT_USART6: #endif #ifdef USE_UART7 case SERIAL_PORT_USART7: #endif #ifdef USE_UART8 case SERIAL_PORT_USART8: #endif #ifdef SITL // SITL emulates serial ports over TCP serialPort = serTcpOpen(SERIAL_PORT_IDENTIFIER_TO_UARTDEV(identifier), rxCallback, baudRate, mode, options); #else serialPort = uartOpen(SERIAL_PORT_IDENTIFIER_TO_UARTDEV(identifier), rxCallback, baudRate, mode, options); #endif break; #endif #ifdef USE_SOFTSERIAL1 case SERIAL_PORT_SOFTSERIAL1: serialPort = openSoftSerial(SOFTSERIAL1, rxCallback, baudRate, mode, options); break; #endif #ifdef USE_SOFTSERIAL2 case SERIAL_PORT_SOFTSERIAL2: serialPort = openSoftSerial(SOFTSERIAL2, rxCallback, baudRate, mode, options); break; #endif default: break; } if (!serialPort) { return NULL; } serialPort->identifier = identifier; serialPortUsage->function = function; serialPortUsage->serialPort = serialPort; return serialPort; } void closeSerialPort(serialPort_t *serialPort) { serialPortUsage_t *serialPortUsage = findSerialPortUsageByPort(serialPort); if (!serialPortUsage) { // already closed return; } // TODO wait until data has been transmitted. serialPort->rxCallback = NULL; serialPortUsage->function = FUNCTION_NONE; serialPortUsage->serialPort = NULL; } void serialInit(bool softserialEnabled, serialPortIdentifier_e serialPortToDisable) { #if !defined(USE_SOFTSERIAL1) && !defined(USE_SOFTSERIAL2) UNUSED(softserialEnabled); #endif serialPortCount = SERIAL_PORT_COUNT; memset(&serialPortUsageList, 0, sizeof(serialPortUsageList)); for (int index = 0; index < SERIAL_PORT_COUNT; index++) { serialPortUsageList[index].identifier = serialPortIdentifiers[index]; if (serialPortToDisable != SERIAL_PORT_NONE) { if (serialPortUsageList[index].identifier == serialPortToDisable) { serialPortUsageList[index].identifier = SERIAL_PORT_NONE; serialPortCount--; } } if ((serialPortUsageList[index].identifier == SERIAL_PORT_SOFTSERIAL1 #ifdef USE_SOFTSERIAL1 && !(softserialEnabled && serialPinConfig()->ioTagTx[RESOURCE_SOFT_OFFSET + SOFTSERIAL1]) #endif ) || (serialPortUsageList[index].identifier == SERIAL_PORT_SOFTSERIAL2 #ifdef USE_SOFTSERIAL2 && !(softserialEnabled && serialPinConfig()->ioTagTx[RESOURCE_SOFT_OFFSET + SOFTSERIAL2]) #endif )) { serialPortUsageList[index].identifier = SERIAL_PORT_NONE; serialPortCount--; } } } void serialRemovePort(serialPortIdentifier_e identifier) { for (uint8_t index = 0; index < SERIAL_PORT_COUNT; index++) { if (serialPortUsageList[index].identifier == identifier) { serialPortUsageList[index].identifier = SERIAL_PORT_NONE; serialPortCount--; } } } uint8_t serialGetAvailablePortCount(void) { return serialPortCount; } bool serialIsPortAvailable(serialPortIdentifier_e identifier) { for (uint8_t index = 0; index < SERIAL_PORT_COUNT; index++) { if (serialPortUsageList[index].identifier == identifier) { return true; } } return false; } void waitForSerialPortToFinishTransmitting(serialPort_t *serialPort) { while (!isSerialTransmitBufferEmpty(serialPort)) { delay(10); }; } void serialEvaluateNonMspData(serialPort_t *serialPort, uint8_t receivedChar) { #ifndef USE_CLI UNUSED(serialPort); #else if (receivedChar == '#') { cliEnter(serialPort); } #endif if (receivedChar == serialConfig()->reboot_character) { systemResetToBootloader(); } } #if defined(GPS) || ! defined(SKIP_SERIAL_PASSTHROUGH) // Default data consumer for serialPassThrough. static void nopConsumer(uint8_t data) { UNUSED(data); } /* A high-level serial passthrough implementation. Used by cli to start an arbitrary serial passthrough "proxy". Optional callbacks can be given to allow for specialized data processing. */ void serialPassthrough(serialPort_t *left, serialPort_t *right, serialConsumer *leftC, serialConsumer *rightC) { waitForSerialPortToFinishTransmitting(left); waitForSerialPortToFinishTransmitting(right); if (!leftC) leftC = &nopConsumer; if (!rightC) rightC = &nopConsumer; LED0_OFF; LED1_OFF; // Either port might be open in a mode other than MODE_RXTX. We rely on // serialRxBytesWaiting() to do the right thing for a TX only port. No // special handling is necessary OR performed. while(1) { // TODO: maintain a timestamp of last data received. Use this to // implement a guard interval and check for `+++` as an escape sequence // to return to CLI command mode. // https://en.wikipedia.org/wiki/Escape_sequence#Modem_control if (serialRxBytesWaiting(left)) { LED0_ON; uint8_t c = serialRead(left); serialWrite(right, c); leftC(c); LED0_OFF; } if (serialRxBytesWaiting(right)) { LED0_ON; uint8_t c = serialRead(right); serialWrite(left, c); rightC(c); LED0_OFF; } } } #endif