Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/Cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<id> <baud> <mode> <options>`, 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 |
Expand Down
18 changes: 18 additions & 0 deletions docs/Serial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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> <tx pad> <rx pad>
```

`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

Expand Down
8 changes: 8 additions & 0 deletions docs/Spektrum Smart ESC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions docs/development/msp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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.

## <a id="msp2_inav_serial_pads"></a>`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.

## <a id="msp2_inav_set_serial_pad"></a>`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.

## <a id="msp2_betaflight_bind"></a>`MSP2_BETAFLIGHT_BIND (12288 / 0x3000)`
**Description:** Initiates the receiver binding procedure for supported serial protocols (CRSF, SRXL2).

Expand Down
90 changes: 89 additions & 1 deletion docs/development/msp/msp_messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": {
"major": 2,
"minor": 1,
"patch": 2
"patch": 3
},
"messages": {
"MSP_API_VERSION": {
Expand Down Expand Up @@ -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."
}
}
}
2 changes: 2 additions & 0 deletions src/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/main/config/parameter_group_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 70 additions & 9 deletions src/main/drivers/pwm_mapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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[] = {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down
Loading
Loading