diff --git a/src/config/ATSTARTF435/config.h b/src/config/ATSTARTF435/config.h index 607d304438e..08bcfa63489 100644 --- a/src/config/ATSTARTF435/config.h +++ b/src/config/ATSTARTF435/config.h @@ -47,3 +47,11 @@ #define USE_ACC #define USE_ACCGYRO_BMI160 +// These would be useful for connecting configurator via the at-link, +// but don't seem to be active when defined here +#define UART1_RX_PIN PA10 +#define UART1_TX_PIN PA9 +#define USE_MSP_UART SERIAL_PORT_USART1 + + + diff --git a/src/main/drivers/at32/pwm_output_dshot.c b/src/main/drivers/at32/pwm_output_dshot.c new file mode 100644 index 00000000000..511defa701e --- /dev/null +++ b/src/main/drivers/at32/pwm_output_dshot.c @@ -0,0 +1,596 @@ +/* + * This file is part of Cleanflight and Betaflight. + * + * Cleanflight and Betaflight are free software. You can redistribute + * this software and/or modify this software 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. + * + * Cleanflight and Betaflight are distributed in the hope that they + * 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 software. + * + * If not, see . + */ + +#include +#include +#include + +#include "platform.h" + +#ifdef USE_DSHOT + +#include "build/debug.h" + +#include "drivers/dma.h" +#include "drivers/dma_reqmap.h" +#include "drivers/io.h" +#include "drivers/nvic.h" +#include "drivers/rcc.h" +#include "drivers/time.h" +#include "drivers/timer.h" +#include "drivers/system.h" + +#include "drivers/pwm_output.h" +#include "drivers/dshot.h" +#include "drivers/dshot_dpwm.h" +#include "drivers/dshot_command.h" +#include "drivers/pwm_output_dshot_shared.h" + +/** + * Convert from BF channel to AT32 constants for timer output channels + * + * The AT and ST apis take a different approach to naming channels, so just passing the bf + * channel number to the AT calls doesn't work. This function maps between them. + * + * @param bfChannel a channel number as used in timerHardware->channel (1 based) + * @param useNChannel indicates that the desired channel should be the complementary output (only available for 1 through 3) + * @return an AT32 tmr_channel_select_type constant + * XXX what to return for invalid inputs? The tmr_channel_select_type enum doesn't have a suitable value + * + * @see TIM_CH_TO_SELCHANNEL macro +*/ +tmr_channel_select_type toCHSelectType(const uint8_t bfChannel, const bool useNChannel) +{ + tmr_channel_select_type result = TMR_SELECT_CHANNEL_1; // XXX I don't like using ch 1 as a default result, but what to do? + if (useNChannel) + { + // Complementary channels only available for 1 through 3 + switch (bfChannel) { + case 1: + result = TMR_SELECT_CHANNEL_1C; + break; + case 2: + result = TMR_SELECT_CHANNEL_2C; + break; + case 3: + result = TMR_SELECT_CHANNEL_3C; + break; + default: + // bad input + #ifdef HANG_ON_ERRORS + while(true) {} + #endif + break; + } + + } else { + switch (bfChannel) { + case 1: + result = TMR_SELECT_CHANNEL_1; + break; + case 2: + result = TMR_SELECT_CHANNEL_2; + break; + case 3: + result = TMR_SELECT_CHANNEL_3; + break; + case 4: + result = TMR_SELECT_CHANNEL_4; + break; + default: + // bad input + #ifdef HANG_ON_ERRORS + while(true) {} + #endif + break; + } + } + + return result; +} + +#ifdef USE_DSHOT_TELEMETRY + +/** + * Enable the timer channels for all motors + * + * Called once for every dshot update if telemetry is being used (not just enabled by #def) + * Called from pwm_output_dshot_shared.c pwmStartDshotMotorUpdate +*/ +void dshotEnableChannels(uint8_t motorCount) +{ + for (int i = 0; i < motorCount; i++) { + tmr_primary_mode_select(dmaMotors[i].timerHardware->tim, TMR_PRIMARY_SEL_COMPARE); + + tmr_channel_select_type atCh = toCHSelectType(dmaMotors[i].timerHardware->channel, dmaMotors[i].output & TIMER_OUTPUT_N_CHANNEL); + tmr_channel_enable(dmaMotors[i].timerHardware->tim, atCh, TRUE); + } +} + +#endif // USE_DSHOT_TELEMETRY + +/** + * Set the timer and dma of the specified motor for use as an output + * + * Called from pwmDshotMotorHardwareConfig in this file and also from + * pwmStartDshotMotorUpdate in src/main/drivers/pwm_output_dshot_shared.c +*/ +FAST_CODE void pwmDshotSetDirectionOutput( + motorDmaOutput_t * const motor + #ifndef USE_DSHOT_TELEMETRY + ,TIM_OCInitTypeDef *pOcInit, DMA_InitTypeDef* pDmaInit + #endif + ) +{ + #ifdef USE_DSHOT_TELEMETRY + TIM_OCInitTypeDef* pOcInit = &motor->ocInitStruct; + DMA_InitTypeDef* pDmaInit = &motor->dmaInitStruct; + #endif + + const timerHardware_t * const timerHardware = motor->timerHardware; + TIM_TypeDef *timer = timerHardware->tim; + const uint8_t channel = timerHardware->channel; // BF channel index (1 based) + const bool useCompOut = (timerHardware->output & TIMER_OUTPUT_N_CHANNEL) != 0; + + dmaResource_t *dmaRef = motor->dmaRef; + + #if defined(USE_DSHOT_DMAR) && !defined(USE_DSHOT_TELEMETRY) + if (useBurstDshot) { + dmaRef = timerHardware->dmaTimUPRef; + } + #endif + + xDMA_DeInit(dmaRef); + + #ifdef USE_DSHOT_TELEMETRY + motor->isInput = false; + #endif + + // Disable the preload buffer so that we can write to the comparison registers (CxDT) immediately + // This call has the channel -> AT32 channel selector mapping built in + timerOCPreloadConfig(timer, channel, FALSE); + + tmr_channel_enable(timer, toCHSelectType(channel, useCompOut), FALSE); + + // The at32 apis do everything except actually put the channel in output mode, so we have to do that ourselves + // This is probably a bug in the at32 sdk, so the need for this code may go away in future releases + const uint8_t CH_OUTPUT = 0; + switch (channel) { + case 1: + timer->cm1_output_bit.c1c = CH_OUTPUT; + break; + case 2: + timer->cm1_output_bit.c2c = CH_OUTPUT; + break; + case 3: + timer->cm2_output_bit.c3c = CH_OUTPUT; + break; + case 4: + timer->cm2_output_bit.c4c = CH_OUTPUT; + break; + } + + timerOCInit(timer, channel, pOcInit); + + // On ST mcu this would be part of the ocInit struct, but on AT we have to do it seperately + { // local scope for variables + const bool useNChannel = false; // tmr_channel_value_set only supports the normal channels + const tmr_channel_select_type atChannel = toCHSelectType(timerHardware->channel, useNChannel); + tmr_channel_value_set(timer, atChannel, 0); + } + + tmr_channel_enable(timer, toCHSelectType(channel, useCompOut), TRUE); + timerOCPreloadConfig(timer, channel, TRUE); + + pDmaInit->direction = DMA_DIR_MEMORY_TO_PERIPHERAL; + xDMA_Init(dmaRef, pDmaInit); + + // Generate an interrupt when the transfer is complete + xDMA_ITConfig(dmaRef, DMA_FDT_INT, TRUE); + +} + + +#ifdef USE_DSHOT_TELEMETRY +/** + * Set the timer and dma of the specified motor for use as an input +*/ +FAST_CODE +static void pwmDshotSetDirectionInput(motorDmaOutput_t * const motor) +{ + DMA_InitTypeDef* pDmaInit = &motor->dmaInitStruct; + const timerHardware_t * const timerHardware = motor->timerHardware; + TIM_TypeDef *timer = timerHardware->tim; + dmaResource_t *dmaRef = motor->dmaRef; + + xDMA_DeInit(dmaRef); + + motor->isInput = true; + if (!inputStampUs) { + inputStampUs = micros(); + } + + tmr_period_buffer_enable(timer, FALSE); + + timer->pr = 0xffffffff; + + tmr_input_channel_init(timer, &motor->icInitStruct, TMR_CHANNEL_INPUT_DIV_1); + + pDmaInit->direction = DMA_DIR_PERIPHERAL_TO_MEMORY; + + xDMA_Init(dmaRef, pDmaInit); +} +#endif // USE_DSHOT_TELEMETRY + +/** + * Start the timers and dma requests to send dshot data to all motors + * + * Called after pwm_output_dshot_shared.c has finished setting up the buffers that represent the dshot packets. + * Iterates over all the timers needed (note that there may be less timers than motors since a single timer can run + * multiple motors) and enables each one. +*/ +void pwmCompleteDshotMotorUpdate(void) +{ + // If there is a dshot command loaded up, time it correctly with motor update + if (!dshotCommandQueueEmpty()) { + if (!dshotCommandOutputIsEnabled(dshotPwmDevice.count)) { + return; + } + } + + for (int i = 0; i < dmaMotorTimerCount; i++) { // dmaMotorTimerCount is a global declared in pwm_output_dshot_shared.c + #ifdef USE_DSHOT_DMAR + // NB burst mode not tested + if (useBurstDshot) { + xDMA_SetCurrDataCounter(dmaMotorTimers[i].dmaBurstRef, dmaMotorTimers[i].dmaBurstLength); + xDMA_Cmd(dmaMotorTimers[i].dmaBurstRef, TRUE); + + // TIM_DMAConfig(dmaMotorTimers[i].timer, TIM_DMABase_CCR1, TIM_DMABurstLength_4Transfers); + tmr_dma_control_config(dmaMotorTimers[i].timer, TMR_DMA_TRANSFER_4BYTES, TMR_CTRL1_ADDRESS); + + // XXX TODO - what is the equivalent of TIM_DMA_Update in AT32? + // TIM_DMACmd(dmaMotorTimers[i].timer, TIM_DMA_Update, ENABLE); + // tmr_dma_request_enable(dmaMotorTimers[i].timer, ) + } else + #endif // USE_DSHOT_DMAR + { + // I think the counter is reset here to ensure that the first pulse is the correct width, + // and maybe also to reduce the chance of an interrupt firing prematurely + tmr_counter_value_set(dmaMotorTimers[i].timer, 0); + + // Allows setting the period with immediate effect + tmr_period_buffer_enable(dmaMotorTimers[i].timer, FALSE); + + #ifdef USE_DSHOT_TELEMETRY + // NB outputPeriod isn't set when telemetry is not #def'd + tmr_period_value_set(dmaMotorTimers[i].timer, dmaMotorTimers[i].outputPeriod); + #endif + + tmr_period_buffer_enable(dmaMotorTimers[i].timer, TRUE); + + // Ensure that overflow events are enabled. This event may affect both period and duty cycle + tmr_overflow_event_disable(dmaMotorTimers[i].timer, FALSE); + + // Generate requests from the timer to one or more DMA channels + tmr_dma_request_enable(dmaMotorTimers[i].timer, dmaMotorTimers[i].timerDmaSources, TRUE); + + dmaMotorTimers[i].timerDmaSources = 0; + } + } +} + +/** + * Interrupt handler called at the end of each packet + * + * Responsible for switching the dshot direction after sending a dshot command so that we + * can receive dshot telemetry. If telemetry is not enabled, disables the dma and request generation. +*/ +FAST_CODE static void motor_DMA_IRQHandler(dmaChannelDescriptor_t *descriptor) +{ + if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) + { + motorDmaOutput_t * const motor = &dmaMotors[descriptor->userParam]; + + #ifdef USE_DSHOT_TELEMETRY + dshotDMAHandlerCycleCounters.irqAt = getCycleCounter(); + #endif + + // Disable timers and dma + + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + xDMA_Cmd(motor->timerHardware->dmaTimUPRef, DISABLE); + TIM_DMACmd(motor->timerHardware->tim, TIM_DMA_Update, DISABLE); + } else + #endif + { // block for the 'else' in the #ifdef above + + // Important - disable requests before disabling the dma channel, otherwise it's possible to + // have a pending request that will fire the moment the dma channel is re-enabled, which + // causes fake pulses to be sent at the start of the next packet. + // This may be the problem described in the errata 1.10.1. The full work-around sounds a bit + // heavyweight, but we should keep it in mind in case it's needed. + // ADVTM + // How to clear TMR-triggered DAM requests + // Description: + // TMR-induced DMA request cannot be cleared by resetting/setting the corresponding DMA + // request enable bit in the TMRx_IDEN register. + // Workaround: + // Before enabling DMA channel, reset TMR (reset CRM clock of TMR) and initialize TMR to + // clear pending DMA requests. + + // disable request generation + tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, FALSE); + + // disable the dma channel, (gets re-enabled in pwm_output_dshot_shared.c from pwmWriteDshotInt) + xDMA_Cmd(motor->dmaRef, FALSE); + } + + // If we're expecting telem, flip direction and re-enable + #ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + pwmDshotSetDirectionInput(motor); + xDMA_SetCurrDataCounter(motor->dmaRef, GCR_TELEMETRY_INPUT_LEN); + xDMA_Cmd(motor->dmaRef, TRUE); + tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, TRUE); + + dshotDMAHandlerCycleCounters.changeDirectionCompletedAt = getCycleCounter(); + } + #endif + + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + + } // if DMA_IT_TCIF +} + +bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t motorIndex, uint8_t reorderedMotorIndex, + motorPwmProtocolTypes_e pwmProtocolType, uint8_t output) +{ + #ifdef USE_DSHOT_TELEMETRY + #define OCINIT motor->ocInitStruct + #define DMAINIT motor->dmaInitStruct + #else + TIM_OCInitTypeDef ocInitStruct; + DMA_InitTypeDef dmaInitStruct; + #define OCINIT ocInitStruct + #define DMAINIT dmaInitStruct + #endif + + dmaResource_t *dmaRef = NULL; + uint32_t dmaMuxId = 0; + + #if defined(USE_DMA_SPEC) + const dmaChannelSpec_t *dmaSpec = dmaGetChannelSpecByTimer(timerHardware); + + if (dmaSpec != NULL) { + dmaRef = dmaSpec->ref; + dmaMuxId = dmaSpec->dmaMuxId; + } + #else // not defined USE_DMA_SPEC + dmaRef = timerHardware->dmaRef; + #endif // USE_DMA_SPEC + + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + dmaRef = timerHardware->dmaTimUPRef; + } + #endif // USE_DSHOT_DMAR + + if (dmaRef == NULL) { + return false; + } + + dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(dmaRef); + + bool dmaIsConfigured = false; + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + const resourceOwner_t *owner = dmaGetOwner(dmaIdentifier); + if (owner->owner == OWNER_TIMUP && owner->resourceIndex == timerGetTIMNumber(timerHardware->tim)) { + dmaIsConfigured = true; + } else if (!dmaAllocate(dmaIdentifier, OWNER_TIMUP, timerGetTIMNumber(timerHardware->tim))) { + return false; + } + } else + #endif + { // 'else' block + if (!dmaAllocate(dmaIdentifier, OWNER_MOTOR, RESOURCE_INDEX(reorderedMotorIndex))) { + return false; + } + } + + motorDmaOutput_t * const motor = &dmaMotors[motorIndex]; + TIM_TypeDef *timer = timerHardware->tim; + + // Boolean configureTimer is always true when different channels of the same timer are processed in sequence, + // causing the timer and the associated DMA initialized more than once. + // To fix this, getTimerIndex must be expanded to return if a new timer has been requested. + // However, since the initialization is idempotent (can be applied multiple times without changing the outcome), + // it is left as is in a favor of flash space (for now). + const uint8_t timerIndex = getTimerIndex(timer); + const bool configureTimer = (timerIndex == dmaMotorTimerCount-1); + + motor->timer = &dmaMotorTimers[timerIndex]; + motor->index = motorIndex; + motor->timerHardware = timerHardware; + + const IO_t motorIO = IOGetByTag(timerHardware->tag); + + uint8_t pupMode = (output & TIMER_OUTPUT_INVERTED) ? GPIO_PULL_DOWN : GPIO_PULL_UP; + + #ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + output ^= TIMER_OUTPUT_INVERTED; + } + #endif + + motor->iocfg = IO_CONFIG(GPIO_MODE_MUX, GPIO_DRIVE_STRENGTH_MODERATE, GPIO_OUTPUT_PUSH_PULL, pupMode); + + IOConfigGPIOAF(motorIO, motor->iocfg, timerHardware->alternateFunction); + + if (configureTimer) { + RCC_ClockCmd(timerRCC(timer), ENABLE); + + tmr_counter_enable(timer, FALSE); + + uint32_t prescaler = (uint16_t)(lrintf((float) timerClock(timer) / getDshotHz(pwmProtocolType) + 0.01f) - 1); + uint32_t period = (pwmProtocolType == PWM_TYPE_PROSHOT1000 ? (MOTOR_NIBBLE_LENGTH_PROSHOT) : MOTOR_BITLENGTH) - 1; + + tmr_clock_source_div_set(timer, TMR_CLOCK_DIV1); + tmr_repetition_counter_set(timer, 0); + tmr_cnt_dir_set(timer, TMR_COUNT_UP); + tmr_base_init(timer, period, prescaler); + } + + tmr_output_config_type * ocConfig = &OCINIT; + tmr_output_default_para_init(ocConfig); + + ocConfig->oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A; + if (output & TIMER_OUTPUT_N_CHANNEL) { + // XXX N channels not yet tested, comments are the stm32 code + // OCINIT.TIM_OutputNState = TIM_OutputNState_Enable; + ocConfig->occ_output_state = TRUE; + // OCINIT.TIM_OCNIdleState = TIM_OCNIdleState_Reset; + ocConfig->occ_idle_state = FALSE; + // OCINIT.TIM_OCNPolarity = (output & TIMER_OUTPUT_INVERTED) ? TIM_OCNPolarity_Low : TIM_OCNPolarity_High; + ocConfig->occ_polarity = (output & TIMER_OUTPUT_INVERTED) ? TMR_OUTPUT_ACTIVE_LOW : TMR_OUTPUT_ACTIVE_HIGH; + } else { + ocConfig->oc_output_state = TRUE; + ocConfig->oc_idle_state = FALSE; + ocConfig->oc_polarity = (output & TIMER_OUTPUT_INVERTED) ? TMR_OUTPUT_ACTIVE_LOW : TMR_OUTPUT_ACTIVE_HIGH; + } + + #ifdef USE_DSHOT_TELEMETRY + tmr_input_config_type * icConfig = &motor->icInitStruct; + tmr_input_default_para_init(icConfig); + icConfig->input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT; + icConfig->input_polarity_select = TMR_INPUT_BOTH_EDGE; + const bool useNChannel = output & TIMER_OUTPUT_N_CHANNEL; + icConfig->input_channel_select = toCHSelectType(timerHardware->channel, useNChannel); + icConfig->input_filter_value = 2; + #endif // USE_DSHOT_TELEMETRY + + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + motor->timer->dmaBurstRef = dmaRef; + } else + #endif + { // 'else' block + motor->timerDmaSource = timerDmaSource(timerHardware->channel); + + // clear that bit from timerDmaSources + // timerDmaSources can have more than one source set in it if multiple motors share a common timer, + motor->timer->timerDmaSources &= ~motor->timerDmaSource; + } + + xDMA_Cmd(dmaRef, FALSE); + xDMA_DeInit(dmaRef); + + if (!dmaIsConfigured) { + dmaEnable(dmaIdentifier); + dmaMuxEnable(dmaIdentifier, dmaMuxId); + } + + dma_default_para_init(&DMAINIT); + + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + motor->timer->dmaBurstBuffer = &dshotBurstDmaBuffer[timerIndex][0]; + + DMAINIT.DMA_Channel = timerHardware->dmaTimUPChannel; + DMAINIT.DMA_Memory0BaseAddr = (uint32_t)motor->timer->dmaBurstBuffer; + DMAINIT.DMA_DIR = DMA_DIR_MemoryToPeripheral; + DMAINIT.DMA_FIFOMode = DMA_FIFOMode_Enable; + DMAINIT.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; + DMAINIT.DMA_MemoryBurst = DMA_MemoryBurst_Single; + DMAINIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; + + DMAINIT.DMA_PeripheralBaseAddr = (uint32_t)&timerHardware->tim->DMAR; + DMAINIT.DMA_BufferSize = (pwmProtocolType == PWM_TYPE_PROSHOT1000) ? PROSHOT_DMA_BUFFER_SIZE : DSHOT_DMA_BUFFER_SIZE; // XXX + DMAINIT.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + DMAINIT.DMA_MemoryInc = DMA_MemoryInc_Enable; + DMAINIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; + DMAINIT.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; + DMAINIT.DMA_Mode = DMA_Mode_Normal; + DMAINIT.DMA_Priority = DMA_Priority_High; + } else + #endif // USE_DSHOT_DMAR + { // 'else' block + motor->dmaBuffer = &dshotDmaBuffer[motorIndex][0]; + DMAINIT.memory_base_addr = (uint32_t)motor->dmaBuffer; + DMAINIT.memory_inc_enable = TRUE; + DMAINIT.memory_data_width = DMA_MEMORY_DATA_WIDTH_WORD; + DMAINIT.loop_mode_enable = FALSE; + DMAINIT.buffer_size = DSHOT_DMA_BUFFER_SIZE; + DMAINIT.direction = DMA_DIR_MEMORY_TO_PERIPHERAL; + DMAINIT.peripheral_base_addr = (uint32_t)timerChCCR(timerHardware); // returns the address of CxDT for timerHardware->channel + DMAINIT.peripheral_inc_enable = FALSE; + DMAINIT.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_WORD; + DMAINIT.priority = DMA_PRIORITY_HIGH; + } + + motor->dmaRef = dmaRef; + + + #ifdef USE_DSHOT_TELEMETRY + motor->dshotTelemetryDeadtimeUs = DSHOT_TELEMETRY_DEADTIME_US + 1000000 * + (16 * MOTOR_BITLENGTH) / getDshotHz(pwmProtocolType); + motor->timer->outputPeriod = (pwmProtocolType == PWM_TYPE_PROSHOT1000 ? (MOTOR_NIBBLE_LENGTH_PROSHOT) : MOTOR_BITLENGTH) - 1; + pwmDshotSetDirectionOutput(motor); + #else + pwmDshotSetDirectionOutput(motor, &OCINIT, &DMAINIT); + #endif + + #ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + if (!dmaIsConfigured) { + dmaSetHandler(dmaIdentifier, motor_DMA_IRQHandler, NVIC_PRIO_DSHOT_DMA, motor->index); + } + } else + #endif + { // 'else' block + dmaSetHandler(dmaIdentifier, motor_DMA_IRQHandler, NVIC_PRIO_DSHOT_DMA, motor->index); + } + + { // local scope + const tmr_channel_select_type chSel = toCHSelectType(timerHardware->channel, output & TIMER_OUTPUT_N_CHANNEL); + tmr_channel_enable(timer, chSel, TRUE); + } + + if (configureTimer) { + // Changes to the period register are deferred until the next counter overflow + tmr_period_buffer_enable(timer, TRUE); + tmr_output_enable(timer, TRUE); + tmr_counter_enable(timer, TRUE); + } + + #ifdef USE_DSHOT_TELEMETRY + if (useDshotTelemetry) { + // avoid high line during startup to prevent bootloader activation + *timerChCCR(timerHardware) = 0xffff; + } + #endif + + motor->configured = true; + + return true; +} + +#endif diff --git a/src/main/drivers/dma.h b/src/main/drivers/dma.h index 85febf3c5ac..c1d9e607502 100644 --- a/src/main/drivers/dma.h +++ b/src/main/drivers/dma.h @@ -43,6 +43,8 @@ typedef struct dmaResource_s dmaResource_t; #elif defined(STM32H7) // H7 has stream based DMA and channel based BDMA, but we ignore BDMA (for now). #define DMA_ARCH_TYPE DMA_Stream_TypeDef +#elif defined(AT32F435) +#define DMA_ARCH_TYPE dma_channel_type #else #define DMA_ARCH_TYPE DMA_Channel_TypeDef #endif diff --git a/src/main/drivers/dshot.c b/src/main/drivers/dshot.c index 1121f1e84a1..2c3105f0af4 100644 --- a/src/main/drivers/dshot.c +++ b/src/main/drivers/dshot.c @@ -317,11 +317,6 @@ void dshotCleanTelemetryData(void) memset(&dshotTelemetryState, 0, sizeof(dshotTelemetryState)); } -uint32_t erpmToRpm(uint16_t erpm) -{ - // rpm = (erpm * 100) / (motorConfig()->motorPoleCount / 2) - return (erpm * 200) / motorConfig()->motorPoleCount; -} uint32_t getDshotAverageRpm(void) { @@ -330,6 +325,17 @@ uint32_t getDshotAverageRpm(void) #endif // USE_DSHOT_TELEMETRY +#if defined(USE_ESC_SENSOR) || defined(USE_DSHOT_TELEMETRY) + +// Used with serial esc telem as well as dshot telem +uint32_t erpmToRpm(uint16_t erpm) +{ + // rpm = (erpm * 100) / (motorConfig()->motorPoleCount / 2) + return (erpm * 200) / motorConfig()->motorPoleCount; +} + +#endif // USE_ESC_SENSOR || USE_DSHOT_TELEMETRY + #ifdef USE_DSHOT_TELEMETRY_STATS FAST_DATA_ZERO_INIT dshotTelemetryQuality_t dshotTelemetryQuality[MAX_SUPPORTED_MOTORS]; diff --git a/src/main/drivers/dshot_dpwm.h b/src/main/drivers/dshot_dpwm.h index dc1b1900721..bbb59613dec 100644 --- a/src/main/drivers/dshot_dpwm.h +++ b/src/main/drivers/dshot_dpwm.h @@ -25,10 +25,12 @@ #include "drivers/dshot.h" #include "drivers/motor.h" +// Timer clock frequency for the dshot speeds #define MOTOR_DSHOT600_HZ MHZ_TO_HZ(12) #define MOTOR_DSHOT300_HZ MHZ_TO_HZ(6) #define MOTOR_DSHOT150_HZ MHZ_TO_HZ(3) +// These three constants are times in timer clock ticks, e.g. with a 6 MHz clock 20 ticks for bitlength = 300kHz bit rate #define MOTOR_BIT_0 7 #define MOTOR_BIT_1 14 #define MOTOR_BITLENGTH 20 @@ -61,6 +63,7 @@ motorDevice_t *dshotPwmDevInit(const struct motorDevConfig_s *motorConfig, uint1 #define GCR_TELEMETRY_INPUT_LEN MAX_GCR_EDGES // For H7, DMA buffer is placed in a dedicated segment for coherency management +// XXX Do we need any special qualifier for AT32? #if defined(STM32H7) #define DSHOT_DMA_BUFFER_ATTRIBUTE DMA_RAM #elif defined(STM32G4) @@ -71,7 +74,7 @@ motorDevice_t *dshotPwmDevInit(const struct motorDevConfig_s *motorConfig, uint1 #define DSHOT_DMA_BUFFER_ATTRIBUTE // None #endif -#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) +#if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) || defined(STM32G4) || defined(AT32F435) #define DSHOT_DMA_BUFFER_UNIT uint32_t #else #define DSHOT_DMA_BUFFER_UNIT uint8_t diff --git a/src/main/drivers/pwm_output_dshot_shared.c b/src/main/drivers/pwm_output_dshot_shared.c index 6d92deb338c..15a4845aaeb 100644 --- a/src/main/drivers/pwm_output_dshot_shared.c +++ b/src/main/drivers/pwm_output_dshot_shared.c @@ -80,7 +80,16 @@ uint8_t getTimerIndex(TIM_TypeDef *timer) return dmaMotorTimerCount - 1; } - +/** + * Prepare to send dshot data for one motor + * + * Formats the value into the appropriate dma buffer and enables the dma channel. + * The packet won't start transmitting until later since the dma requests from the timer + * are disabled when this function is called. + * + * @param index index of the motor that the data is to be sent to + * @param value the dshot value to be sent +*/ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) { motorDmaOutput_t *const motor = &dmaMotors[index]; @@ -110,15 +119,25 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) #endif { bufferSize = loadDmaBuffer(motor->dmaBuffer, 1, packet); + motor->timer->timerDmaSources |= motor->timerDmaSource; + #ifdef USE_FULL_LL_DRIVER xLL_EX_DMA_SetDataLength(motor->dmaRef, bufferSize); xLL_EX_DMA_EnableResource(motor->dmaRef); #else xDMA_SetCurrDataCounter(motor->dmaRef, bufferSize); + + // XXX we can remove this ifdef if we add a new macro for the TRUE/ENABLE constants + #ifdef AT32F435 + xDMA_Cmd(motor->dmaRef, TRUE); + #else xDMA_Cmd(motor->dmaRef, ENABLE); -#endif + #endif + +#endif // USE_FULL_LL_DRIVER } + } @@ -176,6 +195,10 @@ static uint32_t decodeTelemetryPacket(uint32_t buffer[], uint32_t count) #endif #ifdef USE_DSHOT_TELEMETRY +/** + * Process dshot telemetry packets before switching the channels back to outputs + * +*/ FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) { if (!useDshotTelemetry) { @@ -201,6 +224,8 @@ FAST_CODE_NOINLINE bool pwmStartDshotMotorUpdate(void) #ifdef USE_FULL_LL_DRIVER LL_EX_TIM_DisableIT(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerDmaSource); +#elif defined(AT32F435) + tmr_dma_request_enable(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerDmaSource, FALSE); #else TIM_DMACmd(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerDmaSource, DISABLE); #endif diff --git a/src/main/io/serial_4way.c b/src/main/io/serial_4way.c index 7d395db1f10..091c7e0ffff 100644 --- a/src/main/io/serial_4way.c +++ b/src/main/io/serial_4way.c @@ -51,6 +51,8 @@ #if defined(USE_HAL_DRIVER) #define Bit_RESET GPIO_PIN_RESET +#elif defined(AT32F435) +#define Bit_RESET 0 #endif #define USE_TXRX_LED diff --git a/src/main/target/AT32F435/target.h b/src/main/target/AT32F435/target.h index 304a04d5e1f..d369ca21ce8 100644 --- a/src/main/target/AT32F435/target.h +++ b/src/main/target/AT32F435/target.h @@ -28,6 +28,14 @@ #define AT32F435 #endif +#ifdef DEBUG +// Development aid - invalid inputs or other failures are tested and will sit in a while(true) loop +// so that you can go straight to the problem with the debugger +#define HANG_ON_ERRORS +#endif + +#define USE_FAKE_GYRO + #define USE_UART1 #define USE_UART2 #define USE_UART3 @@ -47,6 +55,7 @@ #define USE_EXTI #define USE_GYRO_EXTI + #define USE_I2C #define USE_I2C_DEVICE_1 #define USE_I2C_DEVICE_2 @@ -65,14 +74,22 @@ //#undef USE_BEEPER #undef USE_LED_STRIP #undef USE_TRANSPONDER -#undef USE_DSHOT + +// #undef USE_DSHOT +// #undef USE_DSHOT_TELEMETRY +// bitbang not implemented yet +#undef USE_DSHOT_BITBANG +// burst mode not implemented yet +#undef USE_DSHOT_DMAR + + #undef USE_CAMERA_CONTROL #undef USE_RX_PPM #undef USE_RX_PWM #undef USE_RX_SPI #undef USE_RX_CC2500 #undef USE_RX_EXPRESSLRS -#undef USE_SERIAL_4WAY_BLHELI_BOOTLOADER +// #undef USE_SERIAL_4WAY_BLHELI_BOOTLOADER #undef USE_SERIAL_4WAY_SK_BOOTLOADER #define FLASH_PAGE_SIZE ((uint32_t)0x1000) // 4K sectors diff --git a/src/main/target/AT32F435/target.mk b/src/main/target/AT32F435/target.mk index 54ff0a356f7..1936c67c0f1 100644 --- a/src/main/target/AT32F435/target.mk +++ b/src/main/target/AT32F435/target.mk @@ -41,8 +41,8 @@ DEVICE_FLAGS += -DUSE_ATBSP_DRIVER -DAT32F43x -DHSE_VALUE=$(HSE_VALUE) -DAT32 MCU_COMMON_SRC = \ $(addprefix startup/at32/,$(notdir $(wildcard $(SRC_DIR)/startup/at32/*.c))) \ $(addprefix drivers/at32/,$(notdir $(wildcard $(SRC_DIR)/drivers/at32/*.c))) \ - drivers/bus_i2c_timing.c - + drivers/bus_i2c_timing.c \ + drivers/pwm_output_dshot_shared.c MCU_EXCLUDES =