mirror of
https://github.com/ArduPilot/ardupilot.git
synced 2026-10-06 19:00:27 +08:00
The STM32 JTAG pins JTDI (PA15) and NJTRST (PB4) come out of every reset, and out of power-on, with an internal pull-up enabled, and 84 hwdef directories in the tree route a PWM output through one of them: 61 through PA15, 38 through PB4, 15 through both. A servo on such an output sees the line pulled high from then until firmware reconfigures the pin. On the H743 that measured 692us to the bootloader's board init on a warm reset, and 496us and 520us on two cold power-ons. Each is a pulse inside the range servos accept, so the servo drives to an endpoint and holds it through the rest of the boot. Nothing can shorten that window, since no code runs during it. Give hwdef a HOLD_HIGH keyword for PWM pins. The generator emits such a pin as a pulled-up input with its timer alternate function preset, and a HAL_PWM_HOLD_HIGH_MASK of the channels concerned. It rejects the keyword on any pin RCOutput will never hand back to a timer: anything other than a PWM(n) timer output in the main configuration, so also an RC input, the alarm and an ALT(n) pin. One predicate decides both that and which pins get a mask bit, so the two cannot disagree. The check is made when the pin is parsed, which covers bootloader builds too. STM32F1 is rejected outright, since its pin setup does not honour the keyword at all. RCOutput keeps each channel an input until the first non-zero value is pushed to it, in the PWM and DShot output paths, and only then switches the pin to the timer. With the bootloader also keeping the pull-up, the line is high continuously from the reset until the first real frame, which for a channel outside BRD_SAFETY_MASK means until safety is disarmed, so the servo sees a high far longer than any valid pulse rather than a plausible short one followed by seconds of silence. On the bench the test servo did not move at all across ten reboots. The mode switch is an unlocked read-modify-write of the port registers, and other threads change the modes of other pins on the same port, so a single write can be lost. The channel is therefore not marked handed over until a later output finds the pin already in alternate mode; until then each output writes the mode again, so a lost write is repaired one output later rather than leaving the pin an input until reboot. Several other owners of a pad have to take it explicitly, because none of them goes through the normal output path: - the alarm driver configures a timer and never the pad, and disables the group's channels first, so the hold on the alarm's own channel is released before that. Only that channel: no real frame will ever arrive for the rest of the group, so a HOLD_HIGH pin among them is correctly left held; - soft serial saves and restores the pad's mode, so BLHeli passthrough hands the pin over when it selects it, or it would save "input" and transmit nothing; - neopixel and ProfiLED output is chosen at runtime by SERVOx_FUNCTION on an ordinary PWM(n) pin and is sent without push_local(), so the serial LED path releases its group's pads before driving them; - DShot commands such as beeps are sent before arming, when nothing has released the pad yet, so the command path releases every channel it transmits on; - bidirectional DShot, when enabled at runtime, takes the pads of a whole group during init, so it clears the pending bit there. A pin merely declared BIDIR keeps its hold; one whose group has bidirectional DShot enabled gets no protection. The hwdef allows HOLD_HIGH and BIDIR together deliberately. Verified under Renode on YJUAV_A6SE_H743 with ArduPlane: the application's board init leaves PA15 an input, it stays one through 24.8s of init, and the mode write that puts it on TIM2 lands at the same microsecond as the first non-zero CCR1 write. With the first mode write dropped on purpose, the next output found PA15 still an input and wrote it; unmodified, the second output sees the mode has held and later outputs no longer read the port. MatekF405-TE and the IOMCU firmware still build, and a no-mask board's firmware is byte-identical.
197 lines
6.2 KiB
C++
197 lines
6.2 KiB
C++
/*
|
|
* This file 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.
|
|
*
|
|
* This file 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <hal.h>
|
|
#include "RCOutput.h"
|
|
#include <AP_Math/AP_Math.h>
|
|
#include <AP_BoardConfig/AP_BoardConfig.h>
|
|
#include "hwdef/common/stm32_util.h"
|
|
#include <AP_InternalError/AP_InternalError.h>
|
|
#include <AP_Vehicle/AP_Vehicle_Type.h>
|
|
|
|
#if HAL_USE_PWM == TRUE
|
|
#if HAL_DSHOT_ENABLED
|
|
|
|
#if HAL_WITH_IO_MCU
|
|
#include <AP_IOMCU/AP_IOMCU.h>
|
|
extern AP_IOMCU iomcu;
|
|
#endif
|
|
|
|
using namespace ChibiOS;
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
// send a dshot command to group
|
|
// chan is the FMU channel to send the command to
|
|
bool RCOutput::dshot_send_command(pwm_group& group, uint8_t command, uint8_t chan)
|
|
{
|
|
if (!group.can_send_dshot_pulse()) {
|
|
return false;
|
|
}
|
|
|
|
if (soft_serial_waiting() || !is_dshot_send_allowed(group.dshot_state)) {
|
|
// doing serial output or DMAR input, don't send DShot pulses
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAL_GPIO_LINE_GPIO81
|
|
TOGGLE_PIN_DEBUG(81);
|
|
#endif
|
|
// first make sure we have the DMA channel before anything else
|
|
#if AP_HAL_SHARED_DMA_ENABLED
|
|
osalDbgAssert(!group.dma_handle->is_locked(), "DMA handle is already locked");
|
|
group.dma_handle->lock();
|
|
#endif
|
|
|
|
// only the timer thread releases the locks
|
|
group.dshot_waiter = rcout_thread_ctx;
|
|
bool bdshot_telem = false;
|
|
#ifdef HAL_WITH_BIDIR_DSHOT
|
|
bdshot_prepare_for_next_pulse(group);
|
|
bdshot_telem = group.bdshot.enabled;
|
|
#endif
|
|
|
|
memset((uint8_t *)group.dma_buffer, 0, DSHOT_BUFFER_LENGTH);
|
|
|
|
// keep the other ESCs armed rather than sending nothing
|
|
const uint16_t zero_packet = create_dshot_packet(0, false, bdshot_telem);
|
|
const uint16_t packet = create_dshot_packet(command, true, bdshot_telem);
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
|
if (!group.is_chan_enabled(i)) {
|
|
continue;
|
|
}
|
|
|
|
#if HAL_USE_PWM_HOLD_HIGH_MASK_ENABLED
|
|
// a command, or the zero packet keeping the others armed, goes out
|
|
// on every enabled channel. Commands such as beeps are sent before
|
|
// arming, when no ordinary output has released the pad yet.
|
|
release_hold_high(group, i);
|
|
#endif
|
|
|
|
if (group.chan[i] == chan || chan == RCOutput::ALL_CHANNELS) {
|
|
fill_DMA_buffer_dshot(group.dma_buffer + i, 4, packet, group.bit_width_mul);
|
|
} else {
|
|
fill_DMA_buffer_dshot(group.dma_buffer + i, 4, zero_packet, group.bit_width_mul);
|
|
}
|
|
}
|
|
|
|
chEvtGetAndClearEvents(group.dshot_event_mask);
|
|
// start sending the pulses out
|
|
send_pulses_DMAR(group, DSHOT_BUFFER_LENGTH);
|
|
#ifdef HAL_GPIO_LINE_GPIO81
|
|
TOGGLE_PIN_DEBUG(81);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
// Send a dshot command, if command timout is 0 then 10 commands are sent
|
|
// chan is the servo channel to send the command to
|
|
void RCOutput::send_dshot_command(uint8_t command, uint8_t chan, uint32_t command_timeout_ms, uint16_t repeat_count, bool priority)
|
|
{
|
|
// once armed only priority commands will be accepted
|
|
if (hal.util->get_soft_armed() && !priority) {
|
|
return;
|
|
}
|
|
// not an FMU channel
|
|
if (chan < chan_offset || chan == ALL_CHANNELS) {
|
|
#if HAL_WITH_IO_MCU
|
|
if (iomcu_dshot) {
|
|
iomcu.send_dshot_command(command, chan, command_timeout_ms, repeat_count, priority);
|
|
}
|
|
#endif
|
|
if (chan != ALL_CHANNELS) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
DshotCommandPacket pkt;
|
|
pkt.command = command;
|
|
if (chan != ALL_CHANNELS) {
|
|
pkt.chan = chan - chan_offset; // normalize to FMU channel
|
|
} else {
|
|
pkt.chan = ALL_CHANNELS;
|
|
}
|
|
|
|
if (command_timeout_ms == 0) {
|
|
pkt.cycle = MAX(10, repeat_count);
|
|
} else {
|
|
pkt.cycle = MAX(command_timeout_ms * 1000 / _dshot_period_us, repeat_count);
|
|
}
|
|
|
|
// prioritize anything that is not an LED or BEEP command
|
|
if (!_dshot_command_queue.push(pkt) && priority) {
|
|
_dshot_command_queue.push_force(pkt);
|
|
}
|
|
}
|
|
|
|
// Set the dshot outputs that should be reversed (as opposed to 3D)
|
|
// The chanmask passed is added (ORed) into any existing mask.
|
|
// The mask uses servo channel numbering
|
|
void RCOutput::set_reversed_mask(uint32_t chanmask) {
|
|
_reversed_mask |= chanmask;
|
|
}
|
|
|
|
// Set the dshot outputs that should be reversible/3D
|
|
// The chanmask passed is added (ORed) into any existing mask.
|
|
// The mask uses servo channel numbering
|
|
void RCOutput::set_reversible_mask(uint32_t chanmask) {
|
|
_reversible_mask |= chanmask;
|
|
#if HAL_WITH_IO_MCU
|
|
const uint32_t iomcu_mask = ((1U<<chan_offset)-1);
|
|
if (iomcu_dshot && (chanmask & iomcu_mask)) {
|
|
iomcu.set_reversible_mask(chanmask & iomcu_mask);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Update the dshot outputs that should be reversible/3D at 1Hz
|
|
void RCOutput::update_channel_masks() {
|
|
|
|
// post arming dshot commands will not be accepted
|
|
if (hal.util->get_soft_armed() || _disable_channel_mask_updates) {
|
|
return;
|
|
}
|
|
|
|
// The masks use servo channel numbering
|
|
#if NUM_SERVO_CHANNELS > 0
|
|
for (uint8_t i=0; i<NUM_SERVO_CHANNELS; i++) {
|
|
switch (_dshot_esc_type) {
|
|
case DSHOT_ESC_BLHELI:
|
|
case DSHOT_ESC_BLHELI_S:
|
|
case DSHOT_ESC_BLHELI_EDT:
|
|
case DSHOT_ESC_BLHELI_EDT_S:
|
|
if (_reversible_mask & (1U<<i)) {
|
|
send_dshot_command(DSHOT_3D_ON, i, 0, 10, true);
|
|
}
|
|
if (_reversed_mask & (1U<<i)) {
|
|
send_dshot_command(DSHOT_REVERSE, i, 0, 10, true);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_dshot_esc_type == DSHOT_ESC_BLHELI_EDT || _dshot_esc_type == DSHOT_ESC_BLHELI_EDT_S) {
|
|
send_dshot_command(DSHOT_EXTENDED_TELEMETRY_ENABLE, ALL_CHANNELS, 0, 10, true);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif // HAL_DSHOT_ENABLED
|
|
#endif // HAL_USE_PWM
|