1 #if defined(GPIO_PIN_PWM_OUTPUTS)
3 #include "devServoOutput.h"
10 static int8_t servoPins
[PWM_MAX_CHANNELS
];
11 static pwm_channel_t pwmChannels
[PWM_MAX_CHANNELS
];
12 static uint16_t pwmChannelValues
[PWM_MAX_CHANNELS
];
14 #if (defined(PLATFORM_ESP32))
15 static DShotRMT
*dshotInstances
[PWM_MAX_CHANNELS
] = {nullptr};
16 const uint8_t RMT_MAX_CHANNELS
= 8;
19 // true when the RX has a new channels packet
20 static bool newChannelsAvailable
;
21 // Absolute max failsafe time if no update is received, regardless of LQ
22 static constexpr uint32_t FAILSAFE_ABS_TIMEOUT_MS
= 1000U;
24 void ICACHE_RAM_ATTR
servoNewChannelsAvailable()
26 newChannelsAvailable
= true;
29 uint16_t servoOutputModeToFrequency(eServoOutputMode mode
)
52 static void servoWrite(uint8_t ch
, uint16_t us
)
54 const rx_config_pwm_t
*chConfig
= config
.GetPwmChannel(ch
);
55 #if defined(PLATFORM_ESP32)
56 if ((eServoOutputMode
)chConfig
->val
.mode
== somDShot
)
58 // DBGLN("Writing DShot output: us: %u, ch: %d", us, ch);
59 if (dshotInstances
[ch
])
61 dshotInstances
[ch
]->send_dshot_value(((us
- 1000) * 2) + 47); // Convert PWM signal in us to DShot value
66 if (servoPins
[ch
] != UNDEF_PIN
&& pwmChannelValues
[ch
] != us
)
68 pwmChannelValues
[ch
] = us
;
69 if ((eServoOutputMode
)chConfig
->val
.mode
== somOnOff
)
71 digitalWrite(servoPins
[ch
], us
> 1500);
73 else if ((eServoOutputMode
)chConfig
->val
.mode
== som10KHzDuty
)
75 PWM
.setDuty(pwmChannels
[ch
], constrain(us
, 1000, 2000) - 1000);
79 PWM
.setMicroseconds(pwmChannels
[ch
], us
/ (chConfig
->val
.narrow
+ 1));
84 static void servosFailsafe()
86 constexpr unsigned SERVO_FAILSAFE_MIN
= 988U;
87 for (int ch
= 0 ; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
89 const rx_config_pwm_t
*chConfig
= config
.GetPwmChannel(ch
);
90 if (chConfig
->val
.failsafeMode
== PWMFAILSAFE_SET_POSITION
) {
91 // Note: Failsafe values do not respect the inverted flag, failsafe values are absolute
92 uint16_t us
= chConfig
->val
.failsafe
+ SERVO_FAILSAFE_MIN
;
93 // Always write the failsafe position even if the servo has never been started,
94 // so all the servos go to their expected position
97 else if (chConfig
->val
.failsafeMode
== PWMFAILSAFE_NO_PULSES
) {
100 else if (chConfig
->val
.failsafeMode
== PWMFAILSAFE_LAST_POSITION
) {
106 static void servosUpdate(unsigned long now
)
108 static uint32_t lastUpdate
;
109 if (newChannelsAvailable
)
111 newChannelsAvailable
= false;
113 for (int ch
= 0 ; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
115 const rx_config_pwm_t
*chConfig
= config
.GetPwmChannel(ch
);
116 const unsigned crsfVal
= ChannelData
[chConfig
->val
.inputChannel
];
117 // crsfVal might 0 if this is a switch channel, and it has not been
118 // received yet. Delay initializing the servo until the channel is valid
124 uint16_t us
= CRSF_to_US(crsfVal
);
125 // Flip the output around the mid-value if inverted
126 // (1500 - usOutput) + 1500
127 if (chConfig
->val
.inverted
)
132 } /* for each servo */
133 } /* if newChannelsAvailable */
135 // LQ goes to 0 (100 packets missed in a row)
136 // OR last update older than FAILSAFE_ABS_TIMEOUT_MS
138 else if (lastUpdate
&& ((getLq() == 0) || (now
- lastUpdate
> FAILSAFE_ABS_TIMEOUT_MS
)))
145 static void initialize()
147 if (!OPT_HAS_SERVO_OUTPUT
)
152 #if defined(PLATFORM_ESP32)
155 for (int ch
= 0; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
157 pwmChannelValues
[ch
] = UINT16_MAX
;
158 pwmChannels
[ch
] = -1;
159 int8_t pin
= GPIO_PIN_PWM_OUTPUTS
[ch
];
160 #if (defined(DEBUG_LOG) || defined(DEBUG_RCVR_LINKSTATS)) && (defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32))
161 // Disconnect the debug UART pins if DEBUG_LOG
162 if (pin
== U0RXD_GPIO_NUM
|| pin
== U0TXD_GPIO_NUM
)
167 // Mark servo pins that are being used for serial (or other purposes) as disconnected
168 auto mode
= (eServoOutputMode
)config
.GetPwmChannel(ch
)->val
.mode
;
169 if (mode
>= somSerial
)
173 #if defined(PLATFORM_ESP32)
174 else if (mode
== somDShot
)
176 if (rmtCH
< RMT_MAX_CHANNELS
)
178 auto gpio
= (gpio_num_t
)pin
;
179 auto rmtChannel
= (rmt_channel_t
)rmtCH
;
180 DBGLN("Initializing DShot: gpio: %u, ch: %d, rmtChannel: %u", gpio
, ch
, rmtChannel
);
181 pinMode(pin
, OUTPUT
);
182 dshotInstances
[ch
] = new DShotRMT(gpio
, rmtChannel
); // Initialize the DShotRMT instance
189 // Initialize all servos to low ASAP
190 if (pin
!= UNDEF_PIN
)
192 if (mode
== somOnOff
)
194 DBGLN("Initializing digital output: ch: %d, pin: %d", ch
, pin
);
198 DBGLN("Initializing PWM output: ch: %d, pin: %d", ch
, pin
);
201 pinMode(pin
, OUTPUT
);
202 digitalWrite(pin
, LOW
);
209 for (int ch
= 0; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
211 const rx_config_pwm_t
*chConfig
= config
.GetPwmChannel(ch
);
212 auto frequency
= servoOutputModeToFrequency((eServoOutputMode
)chConfig
->val
.mode
);
213 if (frequency
&& servoPins
[ch
] != UNDEF_PIN
)
215 pwmChannels
[ch
] = PWM
.allocate(servoPins
[ch
], frequency
);
217 #if defined(PLATFORM_ESP32)
218 else if (((eServoOutputMode
)chConfig
->val
.mode
) == somDShot
)
220 dshotInstances
[ch
]->begin(DSHOT300
, false); // Set DShot protocol and bidirectional dshot bool
221 dshotInstances
[ch
]->send_dshot_value(0); // Set throttle low so the ESC can continue initialsation
225 return DURATION_NEVER
;
230 if (!OPT_HAS_SERVO_OUTPUT
|| connectionState
== disconnected
)
232 // Disconnected should come after failsafe on the RX,
233 // so it is safe to shut down when disconnected
234 return DURATION_NEVER
;
236 else if (connectionState
== wifiUpdate
)
238 for (int ch
= 0; ch
< GPIO_PIN_PWM_OUTPUTS_COUNT
; ++ch
)
240 if (pwmChannels
[ch
] != -1)
242 PWM
.release(pwmChannels
[ch
]);
243 pwmChannels
[ch
] = -1;
245 #if defined(PLATFORM_ESP32)
246 if (dshotInstances
[ch
] != nullptr)
248 delete dshotInstances
[ch
];
249 dshotInstances
[ch
] = nullptr;
252 servoPins
[ch
] = UNDEF_PIN
;
254 return DURATION_NEVER
;
256 return DURATION_IMMEDIATELY
;
261 servosUpdate(millis());
262 return DURATION_IMMEDIATELY
;
265 device_t ServoOut_device
= {
266 .initialize
= initialize
,