diff --git a/docs/Cli.md b/docs/Cli.md index be9def8c302..8c502d54a38 100644 --- a/docs/Cli.md +++ b/docs/Cli.md @@ -117,6 +117,7 @@ While connected to the CLI, all Logical Switches are temporarily disabled (5.1.0 | `save` | Save and reboot | | `sd_info` | Sdcard info | | `serial` | Configure serial ports. [Usage](Serial.md) | +| `serialpad` | Move a UART's TX and RX to output pads. [Usage](Serial.md#uart-pins-on-output-pads) | | `serialpassthrough` | Passthrough serial data to port, with ` `, where `id` is the zero based port index, `baud` is a standard baud rate, mode is `rx`, `tx`, or both (`rxtx`), and options is a short string like `8N1` or `8E2` | | `servo` | Configure servos | | `set` | Change setting with name=value or blank or * for list | diff --git a/docs/Serial.md b/docs/Serial.md index ef10d4d0a3e..a3de19389af 100644 --- a/docs/Serial.md +++ b/docs/Serial.md @@ -65,6 +65,24 @@ The `serial` CLI command takes 6 arguments. 5. Telemetry baud rate (auto baud allowed) 6. Blackbox baud rate +### UART pins on output pads + +On many boards the processor can also connect a UART's TX or RX to one of the motor or servo output pads. That gives a port to a board whose UART pads are all taken, or puts a half-duplex device (SmartAudio, Tramp, a Smart ESC) on the servo connector it is already plugged into. + +The Ports tab shows a Pins column for the UARTs that can move; choose the S pad for TX or RX there and save. In the CLI: + +``` +serialpad +``` + +`port` is the identifier `serial` uses, the pads are numbered as in the Outputs tab (S1, S2, ...) and 0 keeps the UART's own pin. `serialpad` alone lists the current choices, and marks any the UART is not using now: the port has no function, the pad is not an output on this board, soft serial, an ADC input or another port uses that pin, or the change is waiting for a save. The change takes effect after `save`. + +* The pad stops being a motor, servo, LED strip, beeper or PINIO output, and the other outputs keep their numbers. If the mixer drives a motor or servo on it, arming is blocked with a PWM output error (`status` shows "Motor or servo output used by a UART"): change the mixer or choose another pad. +* A pad carries one UART pin at a time, and the move happens only while the port has a function. +* F4 and AT32 boards invert SBUS and SmartPort with an inverter on the UART's own pin, which an output pad does not have. F7 and H7 invert inside the UART. +* A UART the board swaps (TX and RX exchanged) does not move. +* Available on boards with more than 512 KB of flash (F405, F745, F765, H7 and AT32), and on the NEXUS, NEXUS X and Vantac RF007, whose ESC connector UART1 can reach. + ### Baud Rates diff --git a/docs/Spektrum Smart ESC.md b/docs/Spektrum Smart ESC.md index edf2d10c53d..06c501eafa6 100644 --- a/docs/Spektrum Smart ESC.md +++ b/docs/Spektrum Smart ESC.md @@ -43,6 +43,14 @@ lead. The motor pad that would normally have driven this ESC is simply left unused. +On many boards the processor can also connect a UART's TX to a motor or servo pad, so +the ESC can stay on the pad it is plugged into: choose that pad as the port's TX in the +Pins column of the Ports tab, or with `serialpad` in the CLI (see +[Serial](Serial.md#uart-pins-on-output-pads)). The NEXUS, NEXUS X and Vantac RF007 +helicopter boards have a connector labelled ESC that UART1's TX reaches, so a Smart ESC +there needs UART1 assigned to it and `serialpad 0 5 0` (the connector is S5 in the +Outputs tab). + ## Setting it up 1. **Ports tab**: assign `Spektrum Smart ESC (SRXL2)` to a spare UART, one per diff --git a/docs/development/msp/README.md b/docs/development/msp/README.md index ec6054fb51b..e9ca64f7bf4 100644 --- a/docs/development/msp/README.md +++ b/docs/development/msp/README.md @@ -474,6 +474,8 @@ When the MSP JSON specification changes, bump `msp_messages.json` version: [8754 - MSP2_INAV_MAG_UNALIGNED](#msp2_inav_mag_unaligned) [8755 - MSP2_INAV_ESC_SRXL2_STATUS](#msp2_inav_esc_srxl2_status) [8756 - MSP2_INAV_ESC_SRXL2_CALIBRATE](#msp2_inav_esc_srxl2_calibrate) +[8758 - MSP2_INAV_SERIAL_PADS](#msp2_inav_serial_pads) +[8759 - MSP2_INAV_SET_SERIAL_PAD](#msp2_inav_set_serial_pad) [12288 - MSP2_BETAFLIGHT_BIND](#msp2_betaflight_bind) [12289 - MSP2_RX_BIND](#msp2_rx_bind) @@ -5045,6 +5047,38 @@ When the MSP JSON specification changes, bump `msp_messages.json` version: **Notes:** Requires USE_MOTOR_SRXL2 and at least one request byte. Accepted commands: 0 abort, 1 automatic start, 4 manual high, 5 manual low. Driver safety checks can reject start requests; read MSP2_INAV_ESC_SRXL2_STATUS for the reason. Other command values return an MSP error. Remove propellers before calibration. +## `MSP2_INAV_SERIAL_PADS (8758 / 0x2236)` +**Description:** Lists the output pads each UART's TX and RX can move to. + +**Request Payload:** **None** + +**Reply Payload:** +|Field|C Type|Repeats|Size (Bytes)|Description| +|---|---|---|---|---| +| `entryCount` | `uint8_t` | - | 1 | Number of entries that follow. | +| `identifier` | `uint8_t` | entryCount | 1 | Serial port identifier (`serialPortIdentifier_e`) of the UART. | +| `direction` | `uint8_t` | entryCount | 1 | 0 TX, 1 RX. | +| `pad` | `uint8_t` | entryCount | 1 | Output pad the pin can move to, numbered from 1 in the order of `MSP2_INAV_OUTPUT_MAPPING_EXT2` (S1, S2, ...). | +| `chosen` | `uint8_t` | entryCount | 1 | 1 when this is the pad configured for this pin, else 0. | +| `usage` | `uint8_t` | entryCount | 1 | What the pad drives now: 0 nothing, 1 motor, 2 servo, 3 LED strip. | +| `usageNumber` | `uint8_t` | entryCount | 1 | The motor or servo number from 1, as in `MSP2_INAV_OUTPUT_ASSIGNMENT`; 0 otherwise. | + +**Notes:** Requires USE_SERIAL_PADS. Lists, for every UART of the board, each output pad its TX or RX can move to; a UART with no entry stays on its own pins. A pad listed for several pins carries one at a time. Changes made with `MSP2_INAV_SET_SERIAL_PAD` are reported at once but take effect after a save and reboot. + +## `MSP2_INAV_SET_SERIAL_PAD (8759 / 0x2237)` +**Description:** Moves a UART's TX or RX to an output pad, or back to its own pin. + +**Request Payload:** +|Field|C Type|Size (Bytes)|Description| +|---|---|---|---| +| `identifier` | `uint8_t` | 1 | Serial port identifier (`serialPortIdentifier_e`) of the UART. | +| `direction` | `uint8_t` | 1 | 0 TX, 1 RX. | +| `pad` | `uint8_t` | 1 | Output pad as numbered by `MSP2_INAV_SERIAL_PADS`, or 0 for the UART's own pin. | + +**Reply Payload:** **None** + +**Notes:** Requires USE_SERIAL_PADS. Returns an error for a pad `MSP2_INAV_SERIAL_PADS` does not list for that pin. A pad already chosen for another pin is taken from it. Takes effect after a save and reboot; a motor or servo the mixer drives on the chosen pad blocks arming with a PWM output error. + ## `MSP2_BETAFLIGHT_BIND (12288 / 0x3000)` **Description:** Initiates the receiver binding procedure for supported serial protocols (CRSF, SRXL2). diff --git a/docs/development/msp/msp_messages.json b/docs/development/msp/msp_messages.json index 50d6d0b27d0..aefdec79c7c 100644 --- a/docs/development/msp/msp_messages.json +++ b/docs/development/msp/msp_messages.json @@ -2,7 +2,7 @@ "version": { "major": 2, "minor": 1, - "patch": 2 + "patch": 3 }, "messages": { "MSP_API_VERSION": { @@ -12023,6 +12023,94 @@ "reply": null, "notes": "Requires USE_MOTOR_SRXL2 and at least one request byte. Accepted commands: 0 abort, 1 automatic start, 4 manual high, 5 manual low. Driver safety checks can reject start requests; read MSP2_INAV_ESC_SRXL2_STATUS for the reason. Other command values return an MSP error. Remove propellers before calibration.", "description": "Controls SRXL2 ESC throttle-range calibration." + }, + "MSP2_INAV_SERIAL_PADS": { + "code": 8758, + "mspv": 2, + "request": null, + "reply": { + "payload": [ + { + "name": "entryCount", + "ctype": "uint8_t", + "desc": "Number of entries that follow.", + "units": "" + }, + { + "repeating": "entryCount", + "payload": [ + { + "name": "identifier", + "ctype": "uint8_t", + "desc": "Serial port identifier (`serialPortIdentifier_e`) of the UART.", + "units": "" + }, + { + "name": "direction", + "ctype": "uint8_t", + "desc": "0 TX, 1 RX.", + "units": "" + }, + { + "name": "pad", + "ctype": "uint8_t", + "desc": "Output pad the pin can move to, numbered from 1 in the order of `MSP2_INAV_OUTPUT_MAPPING_EXT2` (S1, S2, ...).", + "units": "" + }, + { + "name": "chosen", + "ctype": "uint8_t", + "desc": "1 when this is the pad configured for this pin, else 0.", + "units": "" + }, + { + "name": "usage", + "ctype": "uint8_t", + "desc": "What the pad drives now: 0 nothing, 1 motor, 2 servo, 3 LED strip.", + "units": "" + }, + { + "name": "usageNumber", + "ctype": "uint8_t", + "desc": "The motor or servo number from 1, as in `MSP2_INAV_OUTPUT_ASSIGNMENT`; 0 otherwise.", + "units": "" + } + ] + } + ] + }, + "variable_len": true, + "notes": "Requires USE_SERIAL_PADS. Lists, for every UART of the board, each output pad its TX or RX can move to; a UART with no entry stays on its own pins. A pad listed for several pins carries one at a time. Changes made with `MSP2_INAV_SET_SERIAL_PAD` are reported at once but take effect after a save and reboot.", + "description": "Lists the output pads each UART's TX and RX can move to." + }, + "MSP2_INAV_SET_SERIAL_PAD": { + "code": 8759, + "mspv": 2, + "request": { + "payload": [ + { + "name": "identifier", + "ctype": "uint8_t", + "desc": "Serial port identifier (`serialPortIdentifier_e`) of the UART.", + "units": "" + }, + { + "name": "direction", + "ctype": "uint8_t", + "desc": "0 TX, 1 RX.", + "units": "" + }, + { + "name": "pad", + "ctype": "uint8_t", + "desc": "Output pad as numbered by `MSP2_INAV_SERIAL_PADS`, or 0 for the UART's own pin.", + "units": "" + } + ] + }, + "reply": null, + "notes": "Requires USE_SERIAL_PADS. Returns an error for a pad `MSP2_INAV_SERIAL_PADS` does not list for that pin. A pad already chosen for another pin is taken from it. Takes effect after a save and reboot; a motor or servo the mixer drives on the chosen pad blocks arming with a PWM output error.", + "description": "Moves a UART's TX or RX to an output pad, or back to its own pin." } } } diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 1cfd028c3f9..fb08059711e 100755 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -407,6 +407,8 @@ main_sources(COMMON_SRC io/piniobox.h io/serial.c io/serial.h + io/serial_pads.c + io/serial_pads.h io/serial_4way.c io/serial_4way.h io/serial_4way_avrootloader.c diff --git a/src/main/config/parameter_group_ids.h b/src/main/config/parameter_group_ids.h index fe52a22e63f..741b08f9fd9 100644 --- a/src/main/config/parameter_group_ids.h +++ b/src/main/config/parameter_group_ids.h @@ -136,7 +136,8 @@ #define PG_DRONECAN_CONFIG 1045 #define PG_TERRAIN_NAV_CONFIG 1046 #define PG_DRONECAN_DNA_SERVER 1047 // Separate PG so we don't wipe user settings if the allocation table changes -#define PG_INAV_END PG_DRONECAN_DNA_SERVER +#define PG_SERIAL_PAD_CONFIG 1050 +#define PG_INAV_END PG_SERIAL_PAD_CONFIG // OSD configuration (subject to change) //#define PG_OSD_FONT_CONFIG 2047 diff --git a/src/main/drivers/pwm_mapping.c b/src/main/drivers/pwm_mapping.c index 06cdf5ea279..7799ca950e1 100644 --- a/src/main/drivers/pwm_mapping.c +++ b/src/main/drivers/pwm_mapping.c @@ -42,6 +42,7 @@ #include "sensors/rangefinder.h" #include "io/serial.h" +#include "io/serial_pads.h" #include "io/motor_srxl2.h" #include "io/servo_sbus.h" @@ -60,7 +61,10 @@ static const char * pwmInitErrorMsg[] = { /* PWM_INIT_ERROR_TOO_MANY_SERVOS */ "Mixer defines too many servos", /* PWM_INIT_ERROR_NOT_ENOUGH_MOTOR_OUTPUTS */ "Not enough motor outputs/timers", /* PWM_INIT_ERROR_NOT_ENOUGH_SERVO_OUTPUTS */ "Not enough servo outputs/timers", - /* PWM_INIT_ERROR_TIMER_INIT_FAILED */ "Output timer init failed" + /* PWM_INIT_ERROR_TIMER_INIT_FAILED */ "Output timer init failed", +#ifdef USE_SERIAL_PADS + /* PWM_INIT_ERROR_OUTPUT_ROUTED_TO_UART */ "Motor or servo output used by a UART", +#endif }; static const motorProtocolProperties_t motorProtocolProperties[] = { @@ -93,54 +97,74 @@ const motorProtocolProperties_t * getMotorProtocolProperties(motorPwmProtocolTyp return &motorProtocolProperties[proto]; } +static void getUartPins(UARTDevice_e device, serialPortPins_t *pins) +{ +#ifdef USE_SERIAL_PADS + // a UART moved to a pad leaves the outputs numbered as on its own pins + serialPadGetOwnPins(device, pins); +#else + uartGetPortPins(device, pins); +#endif +} + +static bool isRoutedToUart(const timerHardware_t *timHw) +{ +#ifdef USE_SERIAL_PADS + return serialPadIsRouted(timHw->tag); +#else + UNUSED(timHw); + return false; +#endif +} + static bool checkPwmTimerConflicts(const timerHardware_t *timHw) { serialPortPins_t uartPins; #if defined(USE_UART2) - uartGetPortPins(UARTDEV_2, &uartPins); + getUartPins(UARTDEV_2, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART2) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART3) - uartGetPortPins(UARTDEV_3, &uartPins); + getUartPins(UARTDEV_3, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART3) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART4) - uartGetPortPins(UARTDEV_4, &uartPins); + getUartPins(UARTDEV_4, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART4) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART5) - uartGetPortPins(UARTDEV_5, &uartPins); + getUartPins(UARTDEV_5, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART5) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART6) - uartGetPortPins(UARTDEV_6, &uartPins); + getUartPins(UARTDEV_6, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART6) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART7) - uartGetPortPins(UARTDEV_7, &uartPins); + getUartPins(UARTDEV_7, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART7) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } #endif #if defined(USE_UART8) - uartGetPortPins(UARTDEV_8, &uartPins); + getUartPins(UARTDEV_8, &uartPins); if (doesConfigurationUsePort(SERIAL_PORT_USART8) && (timHw->tag == uartPins.txPin || timHw->tag == uartPins.rxPin)) { return true; } @@ -401,13 +425,20 @@ void pwmBuildTimerOutputList(timMotorServoHardware_t *timOutputs) } // LEDs: only on the auto pass, and only if timer is uncontested - if (!isDedicated && TIM_IS_LED(timHw->usageFlags) + if (!isDedicated && TIM_IS_LED(timHw->usageFlags) && !isRoutedToUart(timHw) && !pwmHasMotorOnTimer(timOutputs, timHw->tim) && !pwmHasServoOnTimer(timOutputs, timHw->tim)) { pwmAssignOutput(timOutputs, timHw, MAP_TO_LED_OUTPUT); } } } + + // the beeper, LED strip and PINIO find their pads by these flags + for (int idx = 0; idx < timerHardwareCount; idx++) { + if (isRoutedToUart(&timerHardware[idx])) { + timerHardware[idx].usageFlags &= ~(TIM_USE_LED | TIM_USE_BEEPER | TIM_USE_PINIO); + } + } } static bool motorsUseHardwareTimers(void) @@ -465,6 +496,11 @@ static void pwmInitMotors(timMotorServoHardware_t * timOutputs) // Finally initialize individual motor outputs for (int idx = 0; idx < motorCount; idx++) { const timerHardware_t *timHw = timOutputs->timMotors[idx]; + if (isRoutedToUart(timHw)) { + pwmInitError = PWM_INIT_ERROR_OUTPUT_ROUTED_TO_UART; + LOG_ERROR(PWM, "Motor %d output is used by a UART", idx); + return; + } if (!pwmMotorConfig(timHw, idx, feature(FEATURE_PWM_OUTPUT_ENABLE))) { pwmInitError = PWM_INIT_ERROR_TIMER_INIT_FAILED; LOG_ERROR(PWM, "Timer allocation failed for motor %d", idx); @@ -515,6 +551,11 @@ static void pwmInitServos(timMotorServoHardware_t * timOutputs) // Configure individual servo outputs for (int idx = 0; idx < MIN(servoCount, timOutputs->maxTimServoCount); idx++) { const timerHardware_t *timHw = timOutputs->timServos[idx]; + if (isRoutedToUart(timHw)) { + pwmInitError = PWM_INIT_ERROR_OUTPUT_ROUTED_TO_UART; + LOG_ERROR(PWM, "Servo %d output is used by a UART", idx); + return; + } if (!pwmServoConfig(timHw, idx, servoConfig()->servoPwmRate, servoConfig()->servoCenterPulse, feature(FEATURE_PWM_OUTPUT_ENABLE))) { pwmInitError = PWM_INIT_ERROR_TIMER_INIT_FAILED; @@ -540,6 +581,26 @@ const timMotorServoHardware_t *pwmGetOutputAssignment(void) return &timOutputsStatic; } +#ifdef USE_SERIAL_PADS +uint32_t pwmGetPadFunction(const timerHardware_t *timHw, uint8_t *number) +{ + for (int i = 0; motorsUseHardwareTimers() && i < timOutputsStatic.maxTimMotorCount; i++) { + if (timOutputsStatic.timMotors[i] == timHw) { + *number = i + 1; + return TIM_USE_MOTOR; + } + } + for (int i = 0; servosUseHardwareTimers() && i < timOutputsStatic.maxTimServoCount; i++) { + if (timOutputsStatic.timServos[i] == timHw) { + *number = i + 1; + return TIM_USE_SERVO; + } + } + *number = 0; + return feature(FEATURE_LED_STRIP) && TIM_IS_LED(timHw->usageFlags) ? TIM_USE_LED : 0; +} +#endif + // Upper bound for timerHardware[] size across all supported targets. // timerHardwareCount is a runtime value; this constant prevents a VLA on the MSP // task stack. Targets with 22+ entries (e.g. OMNIBUSF4) need more than MAX_PWM_OUTPUTS. diff --git a/src/main/drivers/pwm_mapping.h b/src/main/drivers/pwm_mapping.h index 158ab8ce6b5..4c9ced3a043 100644 --- a/src/main/drivers/pwm_mapping.h +++ b/src/main/drivers/pwm_mapping.h @@ -72,6 +72,7 @@ typedef enum { PWM_INIT_ERROR_NOT_ENOUGH_MOTOR_OUTPUTS, PWM_INIT_ERROR_NOT_ENOUGH_SERVO_OUTPUTS, PWM_INIT_ERROR_TIMER_INIT_FAILED, + PWM_INIT_ERROR_OUTPUT_ROUTED_TO_UART, } pwmInitError_e; typedef struct rangefinderIOConfig_s { @@ -105,3 +106,7 @@ const char * getPwmInitErrorMessage(void); const timMotorServoHardware_t *pwmGetOutputAssignment(void); void pwmCalculateAssignment(timMotorServoHardware_t *out, const uint8_t *proposedModes); #endif // SITL_BUILD +#ifdef USE_SERIAL_PADS +// What the pad drives, as TIM_USE_MOTOR, TIM_USE_SERVO, TIM_USE_LED or 0, with the motor or servo numbered from 1 +uint32_t pwmGetPadFunction(const timerHardware_t *timHw, uint8_t *number); +#endif diff --git a/src/main/drivers/serial_uart.h b/src/main/drivers/serial_uart.h index 4c1e6d57508..7f24366f8d4 100644 --- a/src/main/drivers/serial_uart.h +++ b/src/main/drivers/serial_uart.h @@ -69,6 +69,10 @@ typedef struct { } uartPort_t; void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins); +#ifdef USE_SERIAL_PADS +// Whether TX (or RX) can be on this pin instead; with apply, the port uses it from its next open +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply); +#endif void uartClearIdleFlag(uartPort_t *s); void uartConfigurePinSwap(uartPort_t *uartPort); #if defined(AT32F43x) diff --git a/src/main/drivers/serial_uart_at32f43x.c b/src/main/drivers/serial_uart_at32f43x.c index 438af01c665..705c1af3c21 100644 --- a/src/main/drivers/serial_uart_at32f43x.c +++ b/src/main/drivers/serial_uart_at32f43x.c @@ -20,6 +20,8 @@ #include "platform.h" +#include "common/utils.h" + #include "drivers/time.h" #include "drivers/io.h" #include "rcc.h" @@ -397,6 +399,109 @@ void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins) } } +#ifdef USE_SERIAL_PADS +static const uartAltPin_t uartAltPins[] = { +#ifdef USE_UART1 + UART_ALT_RX(1, PA10, GPIO_MUX_7), + UART_ALT_RX(1, PB7, GPIO_MUX_7), + UART_ALT_RX(1, PB3, GPIO_MUX_7), + UART_ALT_TX(1, PA9, GPIO_MUX_7), + UART_ALT_TX(1, PA15, GPIO_MUX_7), + UART_ALT_TX(1, PB6, GPIO_MUX_7), +#endif +#ifdef USE_UART2 + UART_ALT_RX(2, PA3, GPIO_MUX_7), + UART_ALT_RX(2, PA15, GPIO_MUX_8), + UART_ALT_RX(2, PB0, GPIO_MUX_6), + UART_ALT_TX(2, PA2, GPIO_MUX_7), + UART_ALT_TX(2, PA8, GPIO_MUX_8), + UART_ALT_TX(2, PA14, GPIO_MUX_8), +#endif +#ifdef USE_UART3 + UART_ALT_RX(3, PB11, GPIO_MUX_7), + UART_ALT_RX(3, PC5, GPIO_MUX_7), + UART_ALT_RX(3, PC11, GPIO_MUX_7), + UART_ALT_TX(3, PB10, GPIO_MUX_7), + UART_ALT_TX(3, PC4, GPIO_MUX_7), + UART_ALT_TX(3, PC10, GPIO_MUX_7), +#endif +#ifdef USE_UART4 + UART_ALT_RX(4, PA1, GPIO_MUX_8), + UART_ALT_RX(4, PC11, GPIO_MUX_8), + UART_ALT_RX(4, PH2, GPIO_MUX_8), + UART_ALT_TX(4, PA0, GPIO_MUX_8), + UART_ALT_TX(4, PC10, GPIO_MUX_8), + UART_ALT_TX(4, PH3, GPIO_MUX_8), +#endif +#ifdef USE_UART5 + UART_ALT_RX(5, PB5, GPIO_MUX_8), + UART_ALT_RX(5, PB8, GPIO_MUX_8), + UART_ALT_RX(5, PD2, GPIO_MUX_8), + UART_ALT_RX(5, PE11, GPIO_MUX_8), + UART_ALT_TX(5, PB6, GPIO_MUX_8), + UART_ALT_TX(5, PB9, GPIO_MUX_8), + UART_ALT_TX(5, PC12, GPIO_MUX_8), + UART_ALT_TX(5, PE10, GPIO_MUX_8), +#endif +#ifdef USE_UART6 + UART_ALT_RX(6, PA5, GPIO_MUX_8), + UART_ALT_RX(6, PA12, GPIO_MUX_8), + UART_ALT_RX(6, PC7, GPIO_MUX_8), + UART_ALT_RX(6, PG9, GPIO_MUX_8), + UART_ALT_TX(6, PA4, GPIO_MUX_8), + UART_ALT_TX(6, PA11, GPIO_MUX_8), + UART_ALT_TX(6, PC6, GPIO_MUX_8), + UART_ALT_TX(6, PG4, GPIO_MUX_8), +#endif +#ifdef USE_UART7 + UART_ALT_RX(7, PB3, GPIO_MUX_8), + UART_ALT_RX(7, PC1, GPIO_MUX_8), + UART_ALT_RX(7, PE7, GPIO_MUX_8), + UART_ALT_RX(7, PF6, GPIO_MUX_8), + UART_ALT_TX(7, PB4, GPIO_MUX_8), + UART_ALT_TX(7, PC0, GPIO_MUX_8), + UART_ALT_TX(7, PE8, GPIO_MUX_8), + UART_ALT_TX(7, PF7, GPIO_MUX_8), +#endif +#ifdef USE_UART8 + UART_ALT_RX(8, PC3, GPIO_MUX_8), + UART_ALT_RX(8, PC9, GPIO_MUX_7), + UART_ALT_RX(8, PD14, GPIO_MUX_8), + UART_ALT_RX(8, PE0, GPIO_MUX_8), + UART_ALT_TX(8, PC2, GPIO_MUX_8), + UART_ALT_TX(8, PC8, GPIO_MUX_7), + UART_ALT_TX(8, PD13, GPIO_MUX_8), + UART_ALT_TX(8, PE1, GPIO_MUX_8), +#endif +}; + +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply) +{ + uartDevice_t *uart = uartHardwareMap[device]; + + // a swapped port has its TX on the RX pin, which the table cannot express + if (!uart || uart->pinSwap || !pin) { + return false; + } + + for (unsigned i = 0; i < ARRAYLEN(uartAltPins); i++) { + const uartAltPin_t *alt = &uartAltPins[i]; + if (alt->device == device && alt->tx == tx && alt->pin == pin) { + if (apply && tx) { + uart->tx = pin; + uart->tx_af = alt->af; + } else if (apply) { + uart->rx = pin; + uart->rx_af = alt->af; + } + return true; + } + } + + return false; +} +#endif + void uartClearIdleFlag(uartPort_t *s) { (void) s->USARTx->sts; diff --git a/src/main/drivers/serial_uart_impl.h b/src/main/drivers/serial_uart_impl.h index d983570285b..2afcf8fa2a9 100644 --- a/src/main/drivers/serial_uart_impl.h +++ b/src/main/drivers/serial_uart_impl.h @@ -23,6 +23,19 @@ extern const struct serialPortVTable uartVTable[]; void uartStartTxDMA(uartPort_t *s); +#ifdef USE_SERIAL_PADS +// A pin the UART's TX or RX can move to, with the alternate function that connects it +typedef struct uartAltPin_s { + uint8_t device; + bool tx; + ioTag_t pin; + uint8_t af; +} uartAltPin_t; + +#define UART_ALT_RX(device, pin, af) { UARTDEV_ ## device, false, DEFIO_TAG_E(pin), af } +#define UART_ALT_TX(device, pin, af) { UARTDEV_ ## device, true, DEFIO_TAG_E(pin), af } +#endif + uartPort_t *serialUART1(uint32_t baudRate, portMode_t mode, portOptions_t options); uartPort_t *serialUART2(uint32_t baudRate, portMode_t mode, portOptions_t options); uartPort_t *serialUART3(uint32_t baudRate, portMode_t mode, portOptions_t options); diff --git a/src/main/drivers/serial_uart_stm32f4xx.c b/src/main/drivers/serial_uart_stm32f4xx.c index 0f54fb69142..5e45b6fe93a 100644 --- a/src/main/drivers/serial_uart_stm32f4xx.c +++ b/src/main/drivers/serial_uart_stm32f4xx.c @@ -20,6 +20,8 @@ #include "platform.h" +#include "common/utils.h" + #include "drivers/time.h" #include "drivers/io.h" #include "rcc.h" @@ -253,6 +255,79 @@ void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins) } } +#ifdef USE_SERIAL_PADS +static const uartAltPin_t uartAltPins[] = { +#ifdef USE_UART1 + UART_ALT_RX(1, PA10, GPIO_AF_USART1), + UART_ALT_RX(1, PB7, GPIO_AF_USART1), + UART_ALT_TX(1, PA9, GPIO_AF_USART1), + UART_ALT_TX(1, PB6, GPIO_AF_USART1), +#ifdef STM32F411xE + UART_ALT_RX(1, PB3, GPIO_AF_USART1), + UART_ALT_TX(1, PA15, GPIO_AF_USART1), +#endif +#endif +#ifdef USE_UART2 + UART_ALT_RX(2, PA3, GPIO_AF_USART2), + UART_ALT_RX(2, PD6, GPIO_AF_USART2), + UART_ALT_TX(2, PA2, GPIO_AF_USART2), + UART_ALT_TX(2, PD5, GPIO_AF_USART2), +#endif +#ifdef USE_UART3 + UART_ALT_RX(3, PB11, GPIO_AF_USART3), + UART_ALT_RX(3, PC11, GPIO_AF_USART3), + UART_ALT_RX(3, PD9, GPIO_AF_USART3), + UART_ALT_TX(3, PB10, GPIO_AF_USART3), + UART_ALT_TX(3, PC10, GPIO_AF_USART3), + UART_ALT_TX(3, PD8, GPIO_AF_USART3), +#endif +#ifdef USE_UART4 + UART_ALT_RX(4, PA1, GPIO_AF_UART4), + UART_ALT_RX(4, PC11, GPIO_AF_UART4), + UART_ALT_TX(4, PA0, GPIO_AF_UART4), + UART_ALT_TX(4, PC10, GPIO_AF_UART4), +#endif +#ifdef USE_UART5 + UART_ALT_RX(5, PD2, GPIO_AF_UART5), + UART_ALT_TX(5, PC12, GPIO_AF_UART5), +#endif +#ifdef USE_UART6 + UART_ALT_RX(6, PC7, GPIO_AF_USART6), + UART_ALT_TX(6, PC6, GPIO_AF_USART6), +#ifdef STM32F411xE + UART_ALT_RX(6, PA12, GPIO_AF_USART6), + UART_ALT_TX(6, PA11, GPIO_AF_USART6), +#else + UART_ALT_RX(6, PG9, GPIO_AF_USART6), + UART_ALT_TX(6, PG14, GPIO_AF_USART6), +#endif +#endif +}; + +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply) +{ + uartDevice_t *uart = uartHardwareMap[device]; + + if (!uart || !pin) { + return false; + } + + for (unsigned i = 0; i < ARRAYLEN(uartAltPins); i++) { + const uartAltPin_t *alt = &uartAltPins[i]; + if (alt->device == device && alt->tx == tx && alt->pin == pin) { + if (apply && tx) { + uart->tx = pin; + } else if (apply) { + uart->rx = pin; + } + return true; + } + } + + return false; +} +#endif + void uartClearIdleFlag(uartPort_t *s) { (void) s->USARTx->SR; diff --git a/src/main/drivers/serial_uart_stm32f7xx.c b/src/main/drivers/serial_uart_stm32f7xx.c index 944634c31ea..6f458f8de39 100644 --- a/src/main/drivers/serial_uart_stm32f7xx.c +++ b/src/main/drivers/serial_uart_stm32f7xx.c @@ -20,6 +20,8 @@ #include "platform.h" +#include "common/utils.h" + #include "drivers/time.h" #include "drivers/io.h" #include "rcc.h" @@ -338,6 +340,112 @@ void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins) } } +#ifdef USE_SERIAL_PADS +static const uartAltPin_t uartAltPins[] = { +#ifdef USE_UART1 + UART_ALT_RX(1, PA10, GPIO_AF7_USART1), + UART_ALT_RX(1, PB7, GPIO_AF7_USART1), + UART_ALT_TX(1, PA9, GPIO_AF7_USART1), + UART_ALT_TX(1, PB6, GPIO_AF7_USART1), +#ifdef STM32F765xx + UART_ALT_RX(1, PB15, GPIO_AF4_USART1), + UART_ALT_TX(1, PB14, GPIO_AF4_USART1), +#endif +#endif +#ifdef USE_UART2 + UART_ALT_RX(2, PA3, GPIO_AF7_USART2), + UART_ALT_RX(2, PD6, GPIO_AF7_USART2), + UART_ALT_TX(2, PA2, GPIO_AF7_USART2), + UART_ALT_TX(2, PD5, GPIO_AF7_USART2), +#endif +#ifdef USE_UART3 + UART_ALT_RX(3, PB11, GPIO_AF7_USART3), + UART_ALT_RX(3, PC11, GPIO_AF7_USART3), + UART_ALT_RX(3, PD9, GPIO_AF7_USART3), + UART_ALT_TX(3, PB10, GPIO_AF7_USART3), + UART_ALT_TX(3, PC10, GPIO_AF7_USART3), + UART_ALT_TX(3, PD8, GPIO_AF7_USART3), +#endif +#ifdef USE_UART4 + UART_ALT_RX(4, PA1, GPIO_AF8_UART4), + UART_ALT_RX(4, PC11, GPIO_AF8_UART4), + UART_ALT_TX(4, PA0, GPIO_AF8_UART4), + UART_ALT_TX(4, PC10, GPIO_AF8_UART4), +#ifdef STM32F765xx + UART_ALT_RX(4, PA11, GPIO_AF6_UART4), + UART_ALT_RX(4, PD0, GPIO_AF8_UART4), + UART_ALT_TX(4, PA12, GPIO_AF6_UART4), + UART_ALT_TX(4, PD1, GPIO_AF8_UART4), +#endif +#endif +#ifdef USE_UART5 + UART_ALT_RX(5, PD2, GPIO_AF8_UART5), + UART_ALT_TX(5, PC12, GPIO_AF8_UART5), +#ifdef STM32F765xx + UART_ALT_RX(5, PB5, GPIO_AF1_UART5), + UART_ALT_RX(5, PB8, GPIO_AF7_UART5), + UART_ALT_RX(5, PB12, GPIO_AF8_UART5), + UART_ALT_TX(5, PB6, GPIO_AF1_UART5), + UART_ALT_TX(5, PB9, GPIO_AF7_UART5), + UART_ALT_TX(5, PB13, GPIO_AF8_UART5), +#endif +#endif +#ifdef USE_UART6 + UART_ALT_RX(6, PC7, GPIO_AF8_USART6), + UART_ALT_RX(6, PG9, GPIO_AF8_USART6), + UART_ALT_TX(6, PC6, GPIO_AF8_USART6), + UART_ALT_TX(6, PG14, GPIO_AF8_USART6), +#endif +#ifdef USE_UART7 + UART_ALT_RX(7, PE7, GPIO_AF8_UART7), + UART_ALT_RX(7, PF6, GPIO_AF8_UART7), + UART_ALT_TX(7, PE8, GPIO_AF8_UART7), + UART_ALT_TX(7, PF7, GPIO_AF8_UART7), +#ifdef STM32F765xx + UART_ALT_RX(7, PA8, GPIO_AF12_UART7), + UART_ALT_RX(7, PB3, GPIO_AF12_UART7), + UART_ALT_TX(7, PA15, GPIO_AF12_UART7), + UART_ALT_TX(7, PB4, GPIO_AF12_UART7), +#endif +#endif +#ifdef USE_UART8 + UART_ALT_RX(8, PE0, GPIO_AF8_UART8), + UART_ALT_TX(8, PE1, GPIO_AF8_UART8), +#endif +}; + +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply) +{ + uartDevice_t *uart = uartHardwareMap[device]; + + if (!uart || !pin) { + return false; + } + +#ifdef USE_UART4_SWAP + // a swapped port has its TX on the RX pin, which the table cannot express + if (device == UARTDEV_4) { + return false; + } +#endif + + for (unsigned i = 0; i < ARRAYLEN(uartAltPins); i++) { + const uartAltPin_t *alt = &uartAltPins[i]; + // one alternate function serves both pins of a port here + if (alt->device == device && alt->tx == tx && alt->pin == pin && alt->af == uart->af) { + if (apply && tx) { + uart->tx = pin; + } else if (apply) { + uart->rx = pin; + } + return true; + } + } + + return false; +} +#endif + uartPort_t *serialUART(UARTDevice_e device, uint32_t baudRate, portMode_t mode, portOptions_t options) { uartPort_t *s; diff --git a/src/main/drivers/serial_uart_stm32h7xx.c b/src/main/drivers/serial_uart_stm32h7xx.c index 233aa7057a1..7d8b62037bc 100644 --- a/src/main/drivers/serial_uart_stm32h7xx.c +++ b/src/main/drivers/serial_uart_stm32h7xx.c @@ -20,6 +20,8 @@ #include "platform.h" +#include "common/utils.h" + #include "drivers/time.h" #include "drivers/io.h" #include "rcc.h" @@ -297,6 +299,101 @@ void uartGetPortPins(UARTDevice_e device, serialPortPins_t * pins) } } +#ifdef USE_SERIAL_PADS +static const uartAltPin_t uartAltPins[] = { +#ifdef USE_UART1 + UART_ALT_RX(1, PA10, GPIO_AF7_USART1), + UART_ALT_RX(1, PB7, GPIO_AF7_USART1), + UART_ALT_RX(1, PB15, GPIO_AF4_USART1), + UART_ALT_TX(1, PA9, GPIO_AF7_USART1), + UART_ALT_TX(1, PB6, GPIO_AF7_USART1), + UART_ALT_TX(1, PB14, GPIO_AF4_USART1), +#endif +#ifdef USE_UART2 + UART_ALT_RX(2, PA3, GPIO_AF7_USART2), + UART_ALT_RX(2, PD6, GPIO_AF7_USART2), + UART_ALT_TX(2, PA2, GPIO_AF7_USART2), + UART_ALT_TX(2, PD5, GPIO_AF7_USART2), +#endif +#ifdef USE_UART3 + UART_ALT_RX(3, PB11, GPIO_AF7_USART3), + UART_ALT_RX(3, PC11, GPIO_AF7_USART3), + UART_ALT_RX(3, PD9, GPIO_AF7_USART3), + UART_ALT_TX(3, PB10, GPIO_AF7_USART3), + UART_ALT_TX(3, PC10, GPIO_AF7_USART3), + UART_ALT_TX(3, PD8, GPIO_AF7_USART3), +#endif +#ifdef USE_UART4 + UART_ALT_RX(4, PA1, GPIO_AF8_UART4), + UART_ALT_RX(4, PA11, GPIO_AF6_UART4), + UART_ALT_RX(4, PB8, GPIO_AF8_UART4), + UART_ALT_RX(4, PC11, GPIO_AF8_UART4), + UART_ALT_RX(4, PD0, GPIO_AF8_UART4), + UART_ALT_RX(4, PH14, GPIO_AF8_UART4), + UART_ALT_TX(4, PA0, GPIO_AF8_UART4), + UART_ALT_TX(4, PA12, GPIO_AF6_UART4), + UART_ALT_TX(4, PB9, GPIO_AF8_UART4), + UART_ALT_TX(4, PC10, GPIO_AF8_UART4), + UART_ALT_TX(4, PD1, GPIO_AF8_UART4), + UART_ALT_TX(4, PH13, GPIO_AF8_UART4), +#endif +#ifdef USE_UART5 + UART_ALT_RX(5, PB5, GPIO_AF14_UART5), + UART_ALT_RX(5, PB12, GPIO_AF14_UART5), + UART_ALT_RX(5, PD2, GPIO_AF8_UART5), + UART_ALT_TX(5, PB6, GPIO_AF14_UART5), + UART_ALT_TX(5, PB13, GPIO_AF14_UART5), + UART_ALT_TX(5, PC12, GPIO_AF8_UART5), +#endif +#ifdef USE_UART6 + UART_ALT_RX(6, PC7, GPIO_AF7_USART6), + UART_ALT_RX(6, PG9, GPIO_AF7_USART6), + UART_ALT_TX(6, PC6, GPIO_AF7_USART6), + UART_ALT_TX(6, PG14, GPIO_AF7_USART6), +#endif +#ifdef USE_UART7 + UART_ALT_RX(7, PA8, GPIO_AF11_UART7), + UART_ALT_RX(7, PB3, GPIO_AF11_UART7), + UART_ALT_RX(7, PE7, GPIO_AF7_UART7), + UART_ALT_RX(7, PF6, GPIO_AF7_UART7), + UART_ALT_TX(7, PA15, GPIO_AF11_UART7), + UART_ALT_TX(7, PB4, GPIO_AF11_UART7), + UART_ALT_TX(7, PE8, GPIO_AF7_UART7), + UART_ALT_TX(7, PF7, GPIO_AF7_UART7), +#endif +#ifdef USE_UART8 + UART_ALT_RX(8, PE0, GPIO_AF8_UART8), + UART_ALT_TX(8, PE1, GPIO_AF8_UART8), +#endif +}; + +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply) +{ + uartDevice_t *uart = uartHardwareMap[device]; + + // a swapped port has its TX on the RX pin, which the table cannot express + if (!uart || uart->pinSwap || !pin) { + return false; + } + + for (unsigned i = 0; i < ARRAYLEN(uartAltPins); i++) { + const uartAltPin_t *alt = &uartAltPins[i]; + if (alt->device == device && alt->tx == tx && alt->pin == pin) { + if (apply && tx) { + uart->tx = pin; + uart->af_tx = alt->af; + } else if (apply) { + uart->rx = pin; + uart->af_rx = alt->af; + } + return true; + } + } + + return false; +} +#endif + void uartIrqHandler(uartPort_t *s) { UART_HandleTypeDef *huart = &s->Handle; diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c index 15339c4f39e..ead367bc24b 100644 --- a/src/main/fc/cli.c +++ b/src/main/fc/cli.c @@ -97,6 +97,7 @@ bool cliMode = false; #include "io/osd/custom_elements.h" #include "io/motor_srxl2.h" #include "io/serial.h" +#include "io/serial_pads.h" #include "fc/fc_msp_box.h" @@ -947,6 +948,63 @@ static void cliSerial(char *cmdline) memcpy(currentConfig, &portConfig, sizeof(portConfig)); } +#ifdef USE_SERIAL_PADS +static void printSerialPads(uint8_t dumpMask, const serialPadConfig_t *config, const serialPadConfig_t *defaultConfig) +{ + const char *format = "serialpad %d %d %d"; + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + if (!serialIsPortAvailable(i)) { + continue; + } + const ioTag_t *pins = config->pin[i]; + bool equalsDefault = false; + if (defaultConfig) { + const ioTag_t *defaultPins = defaultConfig->pin[i]; + equalsDefault = pins[SERIAL_PAD_TX] == defaultPins[SERIAL_PAD_TX] && pins[SERIAL_PAD_RX] == defaultPins[SERIAL_PAD_RX]; + cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, serialPadFind(defaultPins[SERIAL_PAD_TX]), serialPadFind(defaultPins[SERIAL_PAD_RX])); + } + cliDumpPrintLinef(dumpMask, equalsDefault, format, i, serialPadFind(pins[SERIAL_PAD_TX]), serialPadFind(pins[SERIAL_PAD_RX])); + } +} + +static void cliSerialPad(char *cmdline) +{ + if (isEmpty(cmdline)) { + printSerialPads(DUMP_MASTER, serialPadConfig(), NULL); + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT && serialIsPortAvailable(i); dir++) { + if (!serialPadInEffect(i, dir)) { + cliPrintLinef("# port %d %s: not on its pad now", i, dir == SERIAL_PAD_TX ? "TX" : "RX"); + } + } + } + return; + } + + int args[3]; + const char *ptr = cmdline; + for (int i = 0; i < 3; i++) { + if (!ptr) { + cliShowParseError(); + return; + } + args[i] = fastA2I(ptr); + ptr = nextArg(ptr); + } + + const int identifier = args[0]; + const int txPad = args[1]; + const int rxPad = args[2]; + if (txPad < 0 || txPad > UINT8_MAX || rxPad < 0 || rxPad > UINT8_MAX || (txPad && txPad == rxPad) + || !serialPadIsValid(identifier, SERIAL_PAD_TX, txPad) || !serialPadIsValid(identifier, SERIAL_PAD_RX, rxPad)) { + cliPrintErrorLinef("No such pad for this port"); + return; + } + serialPadSet(identifier, SERIAL_PAD_TX, txPad); + serialPadSet(identifier, SERIAL_PAD_RX, rxPad); +} +#endif + #ifdef USE_SERIAL_PASSTHROUGH portOptions_t constructPortOptions(char *options) { @@ -4607,6 +4665,9 @@ static void printConfig(const char *cmdline, bool doDiff) cliPrintHashLine("Ports"); printSerial(dumpMask, &serialConfig_Copy, serialConfig()); +#ifdef USE_SERIAL_PADS + printSerialPads(dumpMask, &serialPadConfig_Copy, serialPadConfig()); +#endif #ifdef USE_LED_STRIP cliPrintHashLine("LEDs"); @@ -5142,6 +5203,9 @@ const clicmd_t cmdTable[] = { #endif CLI_COMMAND_DEF("save", "save and reboot", NULL, cliSave), CLI_COMMAND_DEF("serial", "configure serial ports", NULL, cliSerial), +#ifdef USE_SERIAL_PADS + CLI_COMMAND_DEF("serialpad", "move a UART's TX and RX to output pads", " : pads as the Outputs tab numbers them, 0 for the port's own pin", cliSerialPad), +#endif #ifdef USE_SERIAL_PASSTHROUGH CLI_COMMAND_DEF("serialpassthrough", "passthrough serial data to port", " [baud] [mode] [options]: passthrough to serial", cliSerialPassthrough), #endif diff --git a/src/main/fc/fc_init.c b/src/main/fc/fc_init.c index 360c9bc098e..e12a50d1ee0 100644 --- a/src/main/fc/fc_init.c +++ b/src/main/fc/fc_init.c @@ -119,6 +119,7 @@ #include "io/rcdevice_cam.h" #include "io/motor_srxl2.h" #include "io/serial.h" +#include "io/serial_pads.h" #include "io/displayport_msp.h" #include "io/smartport_master.h" #include "io/crsf_sensor.h" @@ -297,6 +298,10 @@ void init(void) timerInit(); // timer must be initialized before any channel is allocated +#ifdef USE_SERIAL_PADS + // before any port opens and before the outputs are assigned + serialPadsInit(); +#endif serialInit(feature(FEATURE_SOFTSERIAL)); #if defined(USE_MOTOR_SRXL2) && !defined(SITL_BUILD) && !defined(BRUSHED_MOTORS) diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 3bf520b977a..610d6b05e05 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -100,6 +100,7 @@ #include "io/osd.h" #include "io/motor_srxl2.h" #include "io/serial.h" +#include "io/serial_pads.h" #include "io/serial_4way.h" #include "io/vtx.h" #include "io/vtx_string.h" @@ -1731,6 +1732,12 @@ static bool mspFcProcessOutCommand(uint16_t cmdMSP, sbuf_t *dst, mspPostProcessF break; #endif +#ifdef USE_SERIAL_PADS + case MSP2_INAV_SERIAL_PADS: + serialPadsWriteList(dst); + break; +#endif + case MSP2_INAV_WIND: #ifdef USE_WIND_ESTIMATOR { @@ -3874,6 +3881,22 @@ static mspResult_e mspFcProcessInCommand(uint16_t cmdMSP, sbuf_t *src) break; #endif +#ifdef USE_SERIAL_PADS + case MSP2_INAV_SET_SERIAL_PAD: + if (dataSize >= 3) { + const uint8_t identifier = sbufReadU8(src); + const uint8_t direction = sbufReadU8(src); + const uint8_t pad = sbufReadU8(src); + if (!serialPadIsValid(identifier, direction, pad)) { + return MSP_RESULT_ERROR; + } + serialPadSet(identifier, direction, pad); + } else { + return MSP_RESULT_ERROR; + } + break; +#endif + case MSP2_INAV_SELECT_MIXER_PROFILE: if (!ARMING_FLAG(ARMED) && sbufReadU8Safe(&tmp_u8, src)) { setConfigMixerProfileAndWriteEEPROM(tmp_u8); diff --git a/src/main/io/serial_pads.c b/src/main/io/serial_pads.c new file mode 100644 index 00000000000..0211d0ff4cc --- /dev/null +++ b/src/main/io/serial_pads.c @@ -0,0 +1,265 @@ +/* + * This file is part of INAV. + * + * INAV 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. + * + * INAV 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 INAV. If not, see . + */ + +#include +#include + +#include "platform.h" + +#ifdef USE_SERIAL_PADS + +#include "config/feature.h" +#include "config/parameter_group_ids.h" + +#include "drivers/io.h" +#include "drivers/pwm_mapping.h" +#include "drivers/serial.h" +#include "drivers/serial_uart.h" +#include "drivers/timer.h" + +#include "fc/config.h" + +#include "io/serial_pads.h" + +// usage byte of MSP2_INAV_SERIAL_PADS +typedef enum { + PAD_USAGE_NONE = 0, + PAD_USAGE_MOTOR, + PAD_USAGE_SERVO, + PAD_USAGE_LED, +} padUsage_e; + +PG_REGISTER(serialPadConfig_t, serialPadConfig, PG_SERIAL_PAD_CONFIG, 0); + +static ioTag_t ownPins[SERIAL_PAD_UART_COUNT][SERIAL_PAD_DIRECTION_COUNT]; + +// the outputs MSP2_INAV_OUTPUT_MAPPING_EXT2 lists, in its order +static bool isPad(const timerHardware_t *timHw) +{ + return !(timHw->usageFlags & (TIM_USE_PPM | TIM_USE_PWM)); +} + +static const timerHardware_t *padHardware(uint8_t pad) +{ + for (int i = 0; pad && i < timerHardwareCount; i++) { + if (isPad(&timerHardware[i]) && --pad == 0) { + return &timerHardware[i]; + } + } + return NULL; +} + +uint8_t serialPadFind(ioTag_t tag) +{ + uint8_t pad = 0; + for (int i = 0; tag && i < timerHardwareCount; i++) { + if (isPad(&timerHardware[i])) { + pad++; + if (timerHardware[i].tag == tag) { + return pad; + } + } + } + return 0; +} + +static ioTag_t currentPin(int uart, int direction) +{ + serialPortPins_t pins; + uartGetPortPins(uart, &pins); + return direction == SERIAL_PAD_TX ? pins.txPin : pins.rxPin; +} + +// what checkPwmTimerConflicts() keeps off the outputs, and a UART would break the same way +static bool isReserved(ioTag_t tag) +{ +#ifdef USE_SOFTSERIAL1 + if (feature(FEATURE_SOFTSERIAL) && (tag == IO_TAG(SOFTSERIAL_1_RX_PIN) || tag == IO_TAG(SOFTSERIAL_1_TX_PIN))) { + return true; + } +#endif +#ifdef USE_SOFTSERIAL2 + if (feature(FEATURE_SOFTSERIAL) && (tag == IO_TAG(SOFTSERIAL_2_RX_PIN) || tag == IO_TAG(SOFTSERIAL_2_TX_PIN))) { + return true; + } +#endif +#ifdef USE_ADC + const ioTag_t adcPins[] = { +#ifdef ADC_CHANNEL_1_PIN + IO_TAG(ADC_CHANNEL_1_PIN), +#endif +#ifdef ADC_CHANNEL_2_PIN + IO_TAG(ADC_CHANNEL_2_PIN), +#endif +#ifdef ADC_CHANNEL_3_PIN + IO_TAG(ADC_CHANNEL_3_PIN), +#endif +#ifdef ADC_CHANNEL_4_PIN + IO_TAG(ADC_CHANNEL_4_PIN), +#endif +#ifdef ADC_CHANNEL_5_PIN + IO_TAG(ADC_CHANNEL_5_PIN), +#endif +#ifdef ADC_CHANNEL_6_PIN + IO_TAG(ADC_CHANNEL_6_PIN), +#endif + IO_TAG(NONE) + }; + for (unsigned i = 0; i < ARRAYLEN(adcPins); i++) { + if (tag == adcPins[i]) { + return true; + } + } +#endif + return false; +} + +static bool isHeldByUart(ioTag_t tag) +{ + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + if (doesConfigurationUsePort(i) && (currentPin(i, SERIAL_PAD_TX) == tag || currentPin(i, SERIAL_PAD_RX) == tag)) { + return true; + } + } + return false; +} + +void serialPadsInit(void) +{ + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT; dir++) { + ownPins[i][dir] = currentPin(i, dir); + } + } + + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT; dir++) { + const ioTag_t tag = serialPadConfig()->pin[i][dir]; + // a pin this board has no output on, or one already in use, leaves the UART where it was + if (doesConfigurationUsePort(i) && timerGetByTag(tag, TIM_USE_ANY) && !isReserved(tag) && !isHeldByUart(tag)) { + uartRoutePin(i, dir == SERIAL_PAD_TX, tag, true); + } + } + } +} + +bool serialPadInEffect(int uart, serialPadDirection_e direction) +{ + const ioTag_t tag = serialPadConfig()->pin[uart][direction]; + return !tag || currentPin(uart, direction) == tag; +} + +bool serialPadIsRouted(ioTag_t tag) +{ + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT; dir++) { + if (tag && currentPin(i, dir) == tag && ownPins[i][dir] != tag) { + return true; + } + } + } + return false; +} + +void serialPadGetOwnPins(int uart, serialPortPins_t *pins) +{ + pins->txPin = ownPins[uart][SERIAL_PAD_TX]; + pins->rxPin = ownPins[uart][SERIAL_PAD_RX]; +} + +static bool isOffered(int uart, int direction, ioTag_t tag) +{ + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + // the own pins of a port in use; pads moved to one stay on offer, see serialPadSet() + if ((i == uart || doesConfigurationUsePort(i)) && (ownPins[i][SERIAL_PAD_TX] == tag || ownPins[i][SERIAL_PAD_RX] == tag)) { + return false; + } + } + return !isReserved(tag) && uartRoutePin(uart, direction == SERIAL_PAD_TX, tag, false); +} + +bool serialPadIsValid(serialPortIdentifier_e identifier, serialPadDirection_e direction, uint8_t pad) +{ + if (identifier < 0 || identifier >= SERIAL_PAD_UART_COUNT || direction >= SERIAL_PAD_DIRECTION_COUNT + || !serialIsPortAvailable(identifier)) { + return false; + } + const timerHardware_t *timHw = padHardware(pad); + return pad == 0 || (timHw && isOffered(identifier, direction, timHw->tag)); +} + +void serialPadSet(serialPortIdentifier_e identifier, serialPadDirection_e direction, uint8_t pad) +{ + const timerHardware_t *timHw = padHardware(pad); + const ioTag_t tag = timHw ? timHw->tag : IO_TAG(NONE); + + for (int i = 0; tag && i < SERIAL_PAD_UART_COUNT; i++) { + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT; dir++) { + if (serialPadConfig()->pin[i][dir] == tag) { + serialPadConfigMutable()->pin[i][dir] = IO_TAG(NONE); + } + } + } + serialPadConfigMutable()->pin[identifier][direction] = tag; +} + +static padUsage_e padUsage(const timerHardware_t *timHw, uint8_t *number) +{ + switch (pwmGetPadFunction(timHw, number)) { + case TIM_USE_MOTOR: return PAD_USAGE_MOTOR; + case TIM_USE_SERVO: return PAD_USAGE_SERVO; + case TIM_USE_LED: return PAD_USAGE_LED; + default: return PAD_USAGE_NONE; + } +} + +void serialPadsWriteList(sbuf_t *dst) +{ + uint8_t *count = sbufPtr(dst); + sbufWriteU8(dst, 0); + + for (int i = 0; i < SERIAL_PAD_UART_COUNT; i++) { + if (!serialIsPortAvailable(i)) { + continue; + } + for (int dir = 0; dir < SERIAL_PAD_DIRECTION_COUNT; dir++) { + uint8_t pad = 0; + for (int t = 0; t < timerHardwareCount; t++) { + const timerHardware_t *timHw = &timerHardware[t]; + if (!isPad(timHw)) { + continue; + } + pad++; + // a pin listed twice in timerHardware[] is offered once + if (serialPadFind(timHw->tag) != pad || !isOffered(i, dir, timHw->tag) || sbufBytesRemaining(dst) < 6) { + continue; + } + uint8_t number; + const padUsage_e usage = padUsage(timHw, &number); + sbufWriteU8(dst, i); + sbufWriteU8(dst, dir); + sbufWriteU8(dst, pad); + sbufWriteU8(dst, serialPadConfig()->pin[i][dir] == timHw->tag); + sbufWriteU8(dst, usage); + sbufWriteU8(dst, number); + (*count)++; + } + } + } +} + +#endif diff --git a/src/main/io/serial_pads.h b/src/main/io/serial_pads.h new file mode 100644 index 00000000000..4d2433503a1 --- /dev/null +++ b/src/main/io/serial_pads.h @@ -0,0 +1,53 @@ +/* + * This file is part of INAV. + * + * INAV 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. + * + * INAV 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 INAV. If not, see . + */ + +#pragma once + +#include +#include + +#include "common/streambuf.h" +#include "config/parameter_group.h" +#include "drivers/io_types.h" +#include "drivers/serial.h" +#include "io/serial.h" + +#define SERIAL_PAD_UART_COUNT (SERIAL_PORT_USART8 + 1) + +typedef enum { + SERIAL_PAD_TX = 0, + SERIAL_PAD_RX, + SERIAL_PAD_DIRECTION_COUNT +} serialPadDirection_e; + +typedef struct serialPadConfig_s { + // the pad's pin rather than its number, so a target that renumbers its outputs keeps the wiring + ioTag_t pin[SERIAL_PAD_UART_COUNT][SERIAL_PAD_DIRECTION_COUNT]; +} serialPadConfig_t; + +PG_DECLARE(serialPadConfig_t, serialPadConfig); + +// Pads are numbered from 1 in the order of the Outputs tab (S1, S2, ...); 0 is the UART's own pin +void serialPadsInit(void); +bool serialPadIsRouted(ioTag_t tag); +// Whether the UART uses the stored pad now (true too when none is stored) +bool serialPadInEffect(int uart, serialPadDirection_e direction); +void serialPadGetOwnPins(int uart, serialPortPins_t *pins); +uint8_t serialPadFind(ioTag_t tag); +bool serialPadIsValid(serialPortIdentifier_e identifier, serialPadDirection_e direction, uint8_t pad); +void serialPadSet(serialPortIdentifier_e identifier, serialPadDirection_e direction, uint8_t pad); +void serialPadsWriteList(sbuf_t *dst); diff --git a/src/main/msp/msp_protocol_v2_inav.h b/src/main/msp/msp_protocol_v2_inav.h index eadd294a0ab..d004e87bda1 100755 --- a/src/main/msp/msp_protocol_v2_inav.h +++ b/src/main/msp/msp_protocol_v2_inav.h @@ -165,3 +165,6 @@ // command keeps its number, an unmerged one has no claim on it. #define MSP2_INAV_ESC_SRXL2_STATUS 0x2233 #define MSP2_INAV_ESC_SRXL2_CALIBRATE 0x2234 + +#define MSP2_INAV_SERIAL_PADS 0x2236 +#define MSP2_INAV_SET_SERIAL_PAD 0x2237 diff --git a/src/main/target/NEXUS/target.h b/src/main/target/NEXUS/target.h index 63c08049931..69d8a9b8bca 100644 --- a/src/main/target/NEXUS/target.h +++ b/src/main/target/NEXUS/target.h @@ -152,7 +152,7 @@ // S2: PB5 (TIM3_CH2) - Servo header // S3: PB0 (TIM3_CH3) - Servo header // S4: PB3 (TIM2_CH2) - Servo header (Tail) -// M1: PB6 (TIM4_CH1) - ESC header (motor only, NOT UART1) +// M1: PB6 (TIM4_CH1) - ESC header (motor, or UART1 TX with serialpad) // // Pin multiplexing when UARTs freed: // PA2 (TIM5_CH3) - shared with UART2 TX / FREQ input @@ -171,3 +171,7 @@ #define TARGET_IO_PORTC 0xffff #define DEFAULT_FEATURES (FEATURE_TX_PROF_SEL | FEATURE_BLACKBOX) + +// UART1's TX also reaches the ESC header (PB6), for a Smart ESC there: see docs/Spektrum Smart ESC.md +#define USE_SERIAL_PADS +#define USE_MOTOR_SRXL2 diff --git a/src/main/target/NEXUSX/target.h b/src/main/target/NEXUSX/target.h index abe7bbaef28..769d3b4ee97 100644 --- a/src/main/target/NEXUSX/target.h +++ b/src/main/target/NEXUSX/target.h @@ -154,6 +154,10 @@ #define USE_DSHOT #define USE_SERIALSHOT #define USE_ESC_SENSOR + +// UART1's TX also reaches the connector labelled ESC (PA9), for a Smart ESC there: see docs/Spektrum Smart ESC.md +#define USE_SERIAL_PADS +#define USE_MOTOR_SRXL2 #define USE_SMARTPORT_MASTER // no internal current sensor, enable SMARTPORT_MASTER so external ones can be used #define USE_DSHOT_DMAR diff --git a/src/main/target/VANTAC_RF007/target.h b/src/main/target/VANTAC_RF007/target.h index 25bd1373171..c0c18ea6d7d 100644 --- a/src/main/target/VANTAC_RF007/target.h +++ b/src/main/target/VANTAC_RF007/target.h @@ -145,6 +145,10 @@ #define USE_DSHOT #define USE_SERIALSHOT #define USE_ESC_SENSOR + +// UART1's TX also reaches the connector labelled ESC (PA9), for a Smart ESC there: see docs/Spektrum Smart ESC.md +#define USE_SERIAL_PADS +#define USE_MOTOR_SRXL2 #define USE_SMARTPORT_MASTER // no internal current sensor, enable SMARTPORT_MASTER so external ones can be used #define USE_DSHOT_DMAR diff --git a/src/main/target/common.h b/src/main/target/common.h index 71848c3134c..40732af8fb2 100644 --- a/src/main/target/common.h +++ b/src/main/target/common.h @@ -83,6 +83,11 @@ #define USE_MOTOR_SRXL2 #endif +// UART TX and RX on an output pad; the 512 KB boards are left out for flash +#if !defined(USE_SERIAL_PADS) && (MCU_FLASH_SIZE > 512) && (defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(AT32F43x)) +#define USE_SERIAL_PADS +#endif + #ifndef USE_ADC_AVERAGING #define USE_ADC_AVERAGING #endif diff --git a/src/test/unit/CMakeLists.txt b/src/test/unit/CMakeLists.txt index de97812ddcc..7303d30d784 100644 --- a/src/test/unit/CMakeLists.txt +++ b/src/test/unit/CMakeLists.txt @@ -166,6 +166,10 @@ set_property(SOURCE gps_heartbeat_unittest.cc PROPERTY definitions GPS_NULL_PORT set_property(SOURCE gimbal_serial_unittest.cc PROPERTY depends "io/gimbal_serial.c" "drivers/gimbal_common.c" "common/maths.c" "drivers/headtracker_common.c") set_property(SOURCE gimbal_serial_unittest.cc PROPERTY definitions USE_SERIAL_GIMBAL GIMBAL_UNIT_TEST USE_HEADTRACKER) + +set_property(SOURCE serial_pads_unittest.cc PROPERTY depends "io/serial_pads.c" "common/streambuf.c") +set_property(SOURCE serial_pads_unittest.cc PROPERTY definitions USE_SERIAL_PADS + SOFTSERIAL_1_TX_PIN=PA2 SOFTSERIAL_1_RX_PIN=NONE SOFTSERIAL_2_TX_PIN=NONE SOFTSERIAL_2_RX_PIN=NONE) set_property(SOURCE mixer_battery_profile_throttle_rate_limit_unittest.cc PROPERTY depends "flight/mixer.c" "sensors/battery.c" "common/maths.c" "common/filter.c" "common/lulu.c") set_property(SOURCE mixer_battery_profile_throttle_rate_limit_unittest.cc PROPERTY definitions USE_ADC) diff --git a/src/test/unit/platform.h b/src/test/unit/platform.h index 12b2671167b..ea830b80262 100644 --- a/src/test/unit/platform.h +++ b/src/test/unit/platform.h @@ -44,6 +44,10 @@ typedef struct { void * test; } TIM_TypeDef; +typedef struct { + void * test; +} USART_TypeDef; + typedef enum { EXTI_Trigger_Rising = 0x08, EXTI_Trigger_Falling = 0x0C, diff --git a/src/test/unit/serial_pads_unittest.cc b/src/test/unit/serial_pads_unittest.cc new file mode 100644 index 00000000000..cead50063be --- /dev/null +++ b/src/test/unit/serial_pads_unittest.cc @@ -0,0 +1,287 @@ +/* + * This file is part of INAV. + * + * INAV 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. + * + * INAV 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 INAV. If not, see . + */ + +#include +#include + +extern "C" { +#include "platform.h" +#include "common/streambuf.h" +#include "config/feature.h" +#include "drivers/io.h" +#include "drivers/pwm_mapping.h" +#include "drivers/serial.h" +#include "drivers/serial_uart.h" +#include "drivers/timer.h" +#include "fc/config.h" +#include "io/serial.h" +#include "io/serial_pads.h" +} + +#include "gtest/gtest.h" +#include "unittest_macros.h" + +// A board with an input first, so pads and timerHardware[] indexes differ: PB0 is S1 and carries motor 1 +static const serialPortIdentifier_e UART2 = SERIAL_PORT_USART2; +static const serialPortIdentifier_e UART3 = SERIAL_PORT_USART3; +static const serialPortIdentifier_e UART4 = SERIAL_PORT_USART4; + +struct altPin { + int uart; + bool tx; + ioTag_t pin; +}; + +static const altPin altPins[] = { + { UART2, true, IO_TAG(PA2) }, + { UART2, false, IO_TAG(PA0) }, + { UART3, true, IO_TAG(PB0) }, + { UART4, true, IO_TAG(PA0) }, + { UART4, true, IO_TAG(PC10) }, + { UART4, false, IO_TAG(PA1) }, +}; + +static ioTag_t uartPins[SERIAL_PAD_UART_COUNT][SERIAL_PAD_DIRECTION_COUNT]; +static bool portUsed[SERIAL_PAD_UART_COUNT]; +static uint32_t features; + +extern "C" { +timerHardware_t timerHardware[6]; +const int timerHardwareCount = 6; + +void uartGetPortPins(UARTDevice_e device, serialPortPins_t *pins) +{ + pins->txPin = uartPins[device][SERIAL_PAD_TX]; + pins->rxPin = uartPins[device][SERIAL_PAD_RX]; +} + +bool uartRoutePin(UARTDevice_e device, bool tx, ioTag_t pin, bool apply) +{ + for (const altPin &alt : altPins) { + if (alt.uart == device && alt.tx == tx && alt.pin == pin) { + if (apply) { + uartPins[device][tx ? SERIAL_PAD_TX : SERIAL_PAD_RX] = pin; + } + return true; + } + } + return false; +} + +bool doesConfigurationUsePort(serialPortIdentifier_e identifier) +{ + return identifier >= 0 && identifier < SERIAL_PAD_UART_COUNT && portUsed[identifier]; +} + +const timerHardware_t *timerGetByTag(ioTag_t tag, timerUsageFlag_e flag) +{ + for (int i = 0; tag && i < timerHardwareCount; i++) { + if (timerHardware[i].tag == tag && (flag == 0 || (timerHardware[i].usageFlags & flag))) { + return &timerHardware[i]; + } + } + return NULL; +} + +bool feature(uint32_t mask) +{ + return features & mask; +} + +bool serialIsPortAvailable(serialPortIdentifier_e identifier) +{ + return identifier == UART2 || identifier == UART3 || identifier == UART4; +} + +uint32_t pwmGetPadFunction(const timerHardware_t *timHw, uint8_t *number) +{ + if (timHw == &timerHardware[1]) { + *number = 1; + return TIM_USE_MOTOR; + } + *number = 0; + return 0; +} +} + +class SerialPadsTest : public ::testing::Test { +protected: + void SetUp() override + { + const ioTag_t tags[] = { IO_TAG(PB3), IO_TAG(PB0), IO_TAG(PA0), IO_TAG(PA1), IO_TAG(PA2), IO_TAG(PC10) }; + memset(timerHardware, 0, sizeof(timerHardware)); + for (int i = 0; i < timerHardwareCount; i++) { + timerHardware[i].tag = tags[i]; + timerHardware[i].usageFlags = TIM_USE_OUTPUT_AUTO; + } + timerHardware[0].usageFlags = TIM_USE_PPM; + + memset(uartPins, 0, sizeof(uartPins)); + uartPins[UART2][SERIAL_PAD_TX] = IO_TAG(PC6); + uartPins[UART2][SERIAL_PAD_RX] = IO_TAG(PC7); + uartPins[UART3][SERIAL_PAD_TX] = IO_TAG(PC10); + uartPins[UART3][SERIAL_PAD_RX] = IO_TAG(PC11); + uartPins[UART4][SERIAL_PAD_TX] = IO_TAG(PB9); + uartPins[UART4][SERIAL_PAD_RX] = IO_TAG(PB8); + + memset(portUsed, 0, sizeof(portUsed)); + features = 0; + memset(serialPadConfigMutable(), 0, sizeof(serialPadConfig_t)); + } +}; + +TEST_F(SerialPadsTest, PadsAreNumberedWithoutInputs) +{ + EXPECT_EQ(1, serialPadFind(IO_TAG(PB0))); + EXPECT_EQ(2, serialPadFind(IO_TAG(PA0))); + EXPECT_EQ(5, serialPadFind(IO_TAG(PC10))); + EXPECT_EQ(0, serialPadFind(IO_TAG(PB3))); + EXPECT_EQ(0, serialPadFind(IO_TAG(NONE))); +} + +TEST_F(SerialPadsTest, OnlyAPortInUseMoves) +{ + serialPadConfigMutable()->pin[UART4][SERIAL_PAD_TX] = IO_TAG(PA0); + + serialPadsInit(); + EXPECT_EQ(IO_TAG(PB9), uartPins[UART4][SERIAL_PAD_TX]); + EXPECT_FALSE(serialPadIsRouted(IO_TAG(PA0))); + + portUsed[UART4] = true; + serialPadsInit(); + EXPECT_EQ(IO_TAG(PA0), uartPins[UART4][SERIAL_PAD_TX]); + EXPECT_EQ(IO_TAG(PB8), uartPins[UART4][SERIAL_PAD_RX]); + EXPECT_TRUE(serialPadIsRouted(IO_TAG(PA0))); + EXPECT_FALSE(serialPadIsRouted(IO_TAG(PA1))); +} + +TEST_F(SerialPadsTest, APinAnotherPortHoldsStays) +{ + serialPadConfigMutable()->pin[UART4][SERIAL_PAD_TX] = IO_TAG(PC10); + portUsed[UART3] = true; + portUsed[UART4] = true; + + serialPadsInit(); + EXPECT_EQ(IO_TAG(PB9), uartPins[UART4][SERIAL_PAD_TX]); + EXPECT_FALSE(serialPadIsRouted(IO_TAG(PC10))); +} + +TEST_F(SerialPadsTest, OutputsKeepTheirNumbering) +{ + serialPadConfigMutable()->pin[UART4][SERIAL_PAD_TX] = IO_TAG(PA0); + portUsed[UART4] = true; + serialPadsInit(); + + serialPortPins_t pins; + serialPadGetOwnPins(UART4, &pins); + EXPECT_EQ(IO_TAG(PB9), pins.txPin); + EXPECT_EQ(IO_TAG(PB8), pins.rxPin); +} + +TEST_F(SerialPadsTest, OnlyPadsTheUartReachesAreValid) +{ + portUsed[UART3] = true; + serialPadsInit(); + + EXPECT_TRUE(serialPadIsValid(UART4, SERIAL_PAD_TX, 0)); + EXPECT_TRUE(serialPadIsValid(UART4, SERIAL_PAD_TX, 2)); + EXPECT_FALSE(serialPadIsValid(UART4, SERIAL_PAD_RX, 2)); + EXPECT_FALSE(serialPadIsValid(UART4, SERIAL_PAD_TX, 6)); + EXPECT_FALSE(serialPadIsValid((serialPortIdentifier_e)SERIAL_PAD_UART_COUNT, SERIAL_PAD_TX, 0)); + // in range but not on this board + EXPECT_FALSE(serialPadIsValid(SERIAL_PORT_USART1, SERIAL_PAD_TX, 0)); + // S5 is UART3's own TX, free only while UART3 has no function + EXPECT_FALSE(serialPadIsValid(UART4, SERIAL_PAD_TX, 5)); + portUsed[UART3] = false; + EXPECT_TRUE(serialPadIsValid(UART4, SERIAL_PAD_TX, 5)); +} + +TEST_F(SerialPadsTest, SoftSerialKeepsItsPin) +{ + // SOFTSERIAL_1_TX_PIN is PA2 (S4) in this test's build + serialPadConfigMutable()->pin[UART2][SERIAL_PAD_TX] = IO_TAG(PA2); + portUsed[UART2] = true; + features = FEATURE_SOFTSERIAL; + + serialPadsInit(); + EXPECT_EQ(IO_TAG(PC6), uartPins[UART2][SERIAL_PAD_TX]); + EXPECT_FALSE(serialPadIsValid(UART2, SERIAL_PAD_TX, 4)); + + features = 0; + serialPadsInit(); + EXPECT_EQ(IO_TAG(PA2), uartPins[UART2][SERIAL_PAD_TX]); +} + +TEST_F(SerialPadsTest, AStoredPadNotInUseIsMarked) +{ + serialPadConfigMutable()->pin[UART4][SERIAL_PAD_TX] = IO_TAG(PA0); + serialPadConfigMutable()->pin[UART2][SERIAL_PAD_TX] = IO_TAG(PA2); + serialPadConfigMutable()->pin[UART2][SERIAL_PAD_RX] = IO_TAG(PC12); + portUsed[UART2] = true; + features = FEATURE_SOFTSERIAL; + serialPadsInit(); + + // no function, soft serial's pin, not an output: all left on their own pins + EXPECT_FALSE(serialPadInEffect(UART4, SERIAL_PAD_TX)); + EXPECT_FALSE(serialPadInEffect(UART2, SERIAL_PAD_TX)); + EXPECT_FALSE(serialPadInEffect(UART2, SERIAL_PAD_RX)); + EXPECT_TRUE(serialPadInEffect(UART3, SERIAL_PAD_TX)); + + portUsed[UART4] = true; + serialPadsInit(); + EXPECT_TRUE(serialPadInEffect(UART4, SERIAL_PAD_TX)); + serialPadSet(UART4, SERIAL_PAD_RX, 3); + EXPECT_FALSE(serialPadInEffect(UART4, SERIAL_PAD_RX)); +} + +TEST_F(SerialPadsTest, ChoosingAPadTakesItFromAnotherPort) +{ + serialPadsInit(); + serialPadSet(UART4, SERIAL_PAD_TX, 2); + EXPECT_EQ(IO_TAG(PA0), serialPadConfig()->pin[UART4][SERIAL_PAD_TX]); + + serialPadSet(UART2, SERIAL_PAD_RX, 2); + EXPECT_EQ(IO_TAG(PA0), serialPadConfig()->pin[UART2][SERIAL_PAD_RX]); + EXPECT_EQ(IO_TAG(NONE), serialPadConfig()->pin[UART4][SERIAL_PAD_TX]); + + serialPadSet(UART2, SERIAL_PAD_RX, 0); + EXPECT_EQ(IO_TAG(NONE), serialPadConfig()->pin[UART2][SERIAL_PAD_RX]); +} + +TEST_F(SerialPadsTest, ListOffersEachReachablePad) +{ + serialPadConfigMutable()->pin[UART4][SERIAL_PAD_TX] = IO_TAG(PA0); + portUsed[UART3] = true; + serialPadsInit(); + + uint8_t buffer[64]; + sbuf_t dst; + sbufInit(&dst, buffer, buffer + sizeof(buffer)); + serialPadsWriteList(&dst); + + // identifier, direction, pad, chosen, usage (1 motor), motor or servo number; S5 is UART3's own TX + const uint8_t expected[] = { + 5, + UART2, SERIAL_PAD_TX, 4, 0, 0, 0, + UART2, SERIAL_PAD_RX, 2, 0, 0, 0, + UART3, SERIAL_PAD_TX, 1, 0, 1, 1, + UART4, SERIAL_PAD_TX, 2, 1, 0, 0, + UART4, SERIAL_PAD_RX, 3, 0, 0, 0, + }; + ASSERT_EQ(sizeof(expected), (size_t)(dst.ptr - buffer)); + EXPECT_EQ(0, memcmp(expected, buffer, sizeof(expected))); +}