LR1121 FSK (#2789)
[ExpressLRS.git] / src / lib / ServoOutput / devServoOutput.cpp
blob8d869ed364276bddd9165a7e30c6b2064cc2fa85
1 #if defined(GPIO_PIN_PWM_OUTPUTS)
3 #include "devServoOutput.h"
4 #include "PWM.h"
5 #include "CRSF.h"
6 #include "config.h"
7 #include "logging.h"
8 #include "rxtx_intf.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;
17 #endif
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)
31 switch (mode)
33 case som50Hz:
34 return 50U;
35 case som60Hz:
36 return 60U;
37 case som100Hz:
38 return 100U;
39 case som160Hz:
40 return 160U;
41 case som333Hz:
42 return 333U;
43 case som400Hz:
44 return 400U;
45 case som10KHzDuty:
46 return 10000U;
47 default:
48 return 0;
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
64 else
65 #endif
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);
77 else
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
95 servoWrite(ch, us);
97 else if (chConfig->val.failsafeMode == PWMFAILSAFE_NO_PULSES) {
98 servoWrite(ch, 0);
100 else if (chConfig->val.failsafeMode == PWMFAILSAFE_LAST_POSITION) {
101 // do nothing
106 static void servosUpdate(unsigned long now)
108 static uint32_t lastUpdate;
109 if (newChannelsAvailable)
111 newChannelsAvailable = false;
112 lastUpdate = now;
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
119 if (crsfVal == 0)
121 continue;
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)
129 us = 3000U - us;
131 servoWrite(ch, us);
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
137 // go to failsafe
138 else if (lastUpdate && ((getLq() == 0) || (now - lastUpdate > FAILSAFE_ABS_TIMEOUT_MS)))
140 servosFailsafe();
141 lastUpdate = 0;
145 static void initialize()
147 if (!OPT_HAS_SERVO_OUTPUT)
149 return;
152 #if defined(PLATFORM_ESP32)
153 uint8_t rmtCH = 0;
154 #endif
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)
164 pin = UNDEF_PIN;
166 #endif
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)
171 pin = UNDEF_PIN;
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
183 rmtCH++;
185 pin = UNDEF_PIN;
187 #endif
188 servoPins[ch] = pin;
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);
196 else
198 DBGLN("Initializing PWM output: ch: %d, pin: %d", ch, pin);
201 pinMode(pin, OUTPUT);
202 digitalWrite(pin, LOW);
207 static int start()
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
223 #endif
225 return DURATION_NEVER;
228 static int event()
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;
251 #endif
252 servoPins[ch] = UNDEF_PIN;
254 return DURATION_NEVER;
256 return DURATION_IMMEDIATELY;
259 static int timeout()
261 servosUpdate(millis());
262 return DURATION_IMMEDIATELY;
265 device_t ServoOut_device = {
266 .initialize = initialize,
267 .start = start,
268 .event = event,
269 .timeout = timeout,
272 #endif