From c13c9d5d991422d4351c0373b0cc4541cfbfa04d Mon Sep 17 00:00:00 2001 From: JBKingdon Date: Sun, 26 Feb 2023 07:49:53 -0500 Subject: [PATCH 1/4] AT32 dshot part1 Just enough dshot implementation to do timer pwm dshot output only. Still to do: telemetry, burst mode, bitbang --- src/main/drivers/at32/pwm_output_dshot.c | 657 +++++++++++++++++++++ src/main/drivers/dma.h | 2 + src/main/drivers/dshot.c | 13 +- src/main/drivers/dshot_dpwm.h | 5 +- src/main/drivers/pwm_output_dshot_shared.c | 21 +- src/main/target/AT32F435/target.mk | 4 +- 6 files changed, 693 insertions(+), 9 deletions(-) create mode 100644 src/main/drivers/at32/pwm_output_dshot.c 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..dfd84572e87 --- /dev/null +++ b/src/main/drivers/at32/pwm_output_dshot.c @@ -0,0 +1,657 @@ +/* + * 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(uint8_t bfChannel, 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 + } + + } 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 + } + } + + return result; +} + +#ifdef USE_DSHOT_TELEMETRY + +void dshotEnableChannels(uint8_t motorCount) +{ + for (int i = 0; i < motorCount; i++) { + tmr_channel_select_type atCh = toCHSelectType(dmaMotors[i].timerHardware->channel, dmaMotors[i].output & TIMER_OUTPUT_N_CHANNEL); + + // XXX TODO need to confirm that these AT api calls are equivalent to the commented out ST calls + tmr_primary_mode_select(dmaMotors[i].timerHardware->tim, TMR_PRIMARY_SEL_COMPARE); + + // TODO these polarity set calls might not be needed if they are already the default settings + if (dmaMotors[i].output & TIMER_OUTPUT_N_CHANNEL) { + // TIM_CCxNCmd(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerHardware->channel, TIM_CCxN_Enable); + tmr_output_channel_polarity_set(dmaMotors[i].timerHardware->tim, atCh, TMR_POLARITY_ACTIVE_LOW); + } else { + // TIM_CCxCmd(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerHardware->channel, TIM_CCx_Enable); + tmr_output_channel_polarity_set(dmaMotors[i].timerHardware->tim, atCh, TMR_POLARITY_ACTIVE_HIGH); + } + + tmr_channel_enable(dmaMotors[i].timerHardware->tim, atCh, TRUE); + } +} + +#endif // USE_DSHOT_TELEMETRY + + +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; + 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 + // which seems a bit odd as we don't write to CxDT in this section + // This call has the channel -> AT32 channel selector mapping built in + timerOCPreloadConfig(timer, channel, FALSE); + tmr_channel_enable(timer, toCHSelectType(channel, useCompOut), FALSE); + + timerOCInit(timer, timerHardware->channel, pOcInit); + + tmr_channel_enable(timer, toCHSelectType(timerHardware->channel, useCompOut), TRUE); + timerOCPreloadConfig(timer, timerHardware->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 +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(); + } + // TIM_ARRPreloadConfig(timer, ENABLE); // Need to set the ovfif bit to enable preload + tmr_overflow_event_disable(timer, TRUE); // method is badly named. I think TRUE enables overflow events based on the code + // timer->ARR = 0xffffffff; // arr = auto reload register, may be equivelent to the preload register on at32 + timer->pr = 0xffffffff; + + // TIM_ICInit(timer, &motor->icInitStruct); + + // XXX which input divider setting to use? Looks like it's the equivalent of ST prescaler, and the setting + // would have been in the init struct for ST, but isn't in the AT equivalent. We'll need another way of getting it + // Looks like it may have been hardcoded to div_1 anyway + tmr_input_channel_init(timer, &motor->icInitStruct, TMR_CHANNEL_INPUT_DIV_1); + +#if defined(STM32F4) + motor->dmaInitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; +#endif + + xDMA_Init(dmaRef, pDmaInit); +} +#endif + + +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<3; i++) + { + motorDmaOutput_t * motor = &dmaMotors[i]; + uint32_t *buffer = motor->dmaBuffer; + UNUSED(buffer); + } + + // XXX DEBUG + gpio_bits_set(GPIOC, GPIO_PINS_10); + + for (int i = 0; i < dmaMotorTimerCount; i++) { // dmaMotorTimerCount is a global declared in pwm_output_dshot_shared.c +#ifdef USE_DSHOT_DMAR + 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); + // XXX it's not at all clear that the hardware will be compatible at this level + tmr_dma_control_config(dmaMotorTimers[i].timer, TMR_DMA_TRANSFER_4BYTES, TMR_CTRL1_ADDRESS); + + // XXX TODO - there doesn't seem to be an 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 + { + // TIM_ARRPreloadConfig(dmaMotorTimers[i].timer, DISABLE); + + // Allows setting the period with immediate effect + tmr_period_buffer_enable(dmaMotorTimers[i].timer, FALSE); + + + // dmaMotorTimers[i].timer->ARR = dmaMotorTimers[i].outputPeriod; + + // XXX This isn't set when telemetry is disabled. Do we come through here? Yes, and outputPeriod is 0 + // Will be needed for bidir, check if it needs to be conditional or can be enabled for both cases + // use an api? + // dmaMotorTimers[i].timer->pr = dmaMotorTimers[i].outputPeriod; + + + // TIM_ARRPreloadConfig(dmaMotorTimers[i].timer, ENABLE); + 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; + + // I think the counter is reset here to ensure that the first pulse is the correct width + tmr_counter_value_set(dmaMotorTimers[i].timer, 0); + } + } + // XXX DEBUG + gpio_bits_reset(GPIOC, GPIO_PINS_10); +} + +/** + * 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. +*/ + +// XXX for testing +uint32_t lastDTVal; +bool mapGood = false; +bool dtBAD = false; + +FAST_CODE static void motor_DMA_IRQHandler(dmaChannelDescriptor_t *descriptor) +{ + if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) { + + // XXX DEBUG + gpio_bits_set(GPIOC, GPIO_PINS_11); + + 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 + // disable the dma channel + xDMA_Cmd(motor->dmaRef, FALSE); // Gets re-enabled in pwm_output_dshot_shared.c from pwmWriteDshotInt + + // disable request generation + tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, 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); + // TIM_DMACmd(motor->timerHardware->tim, motor->timerDmaSource, ENABLE); + // XXX check + tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, TRUE); + + dshotDMAHandlerCycleCounters.changeDirectionCompletedAt = getCycleCounter(); + } +#endif + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + + // XXX DEBUG + gpio_bits_reset(GPIOC, GPIO_PINS_11); + + } // 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; + + // XXX testing - find a better place and only enable the mux(s) being used + // merge into dmaMuxEnable? + dmamux_enable(DMA1, TRUE); + dmamux_enable(DMA2, TRUE); + + // If we use pinio config for debug pins then the init will probably be done for us, but not until after this function runs + // grab a pin for debug, C10 will do for now + gpio_init_type init = { + .gpio_pins = GPIO_PINS_10, + .gpio_mode = GPIO_MODE_OUTPUT, + .gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE, + .gpio_out_type = GPIO_OUTPUT_PUSH_PULL, + .gpio_pull = GPIO_PULL_NONE + }; + gpio_init(GPIOC, &init); + + +#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; +#if defined(STM32F4) + // XXX Fixme + dmaChannel = timerHardware->dmaChannel; +#endif +#endif // USE_DMA_SPEC + +#ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + dmaRef = timerHardware->dmaTimUPRef; +#if defined(STM32F4) + // XXX Fixme + dmaChannel = timerHardware->dmaTimUPChannel; +#endif + } +#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 + { + 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_AF, GPIO_Speed_50MHz, GPIO_OType_PP, pupMode); + 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) { // What's the benefit of using either the N or normal channels? + // XXX N channels not yet tested + // OCINIT.TIM_OutputNState = TIM_OutputNState_Enable; + ocConfig->occ_output_state = TRUE; + // OCINIT.TIM_OCNIdleState = TIM_OCNIdleState_Reset; + ocConfig->occ_idle_state = FALSE; // XXX is 'reset' equivalent to TRUE or 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; + } + + // OCINIT.TIM_Pulse = 0; + { // local scope for variable + tmr_channel_select_type atChannel = toCHSelectType(timerHardware->channel, false); // tmr_channel_value_set only supports the normal channels + tmr_channel_value_set(timer, atChannel, 0); + // tmr_channel_value_set(timer, atChannel, (motor->index + 1) * 2); + } + +#ifdef USE_DSHOT_TELEMETRY + + // typedef struct + // { + // tmr_channel_select_type input_channel_select; /*!< tmr input channel select */ + // tmr_input_polarity_type input_polarity_select; /*!< tmr input polarity select */ + // tmr_input_direction_mapped_type input_mapped_select; /*!< tmr channel mapped direct or indirect */ + // uint8_t input_filter_value; /*!< tmr channel filter value */ + // } tmr_input_config_type; + + // TIM_ICStructInit(&motor->icInitStruct); + tmr_input_config_type * icConfig = &motor->icInitStruct; + tmr_input_default_para_init(icConfig); + + // motor->icInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; + icConfig->input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT; + + // motor->icInitStruct.TIM_ICPolarity = TIM_ICPolarity_BothEdge; + icConfig->input_polarity_select = TMR_INPUT_BOTH_EDGE; + + // motor->icInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; // XXX not part of at32 input config + + // motor->icInitStruct.TIM_Channel = timerHardware->channel; + icConfig->input_channel_select = toCHSelectType(timerHardware->channel, false); + + // motor->icInitStruct.TIM_ICFilter = 2; + icConfig->input_filter_value = 2; // What are the units, is this valid? +#endif + + +#ifdef USE_DSHOT_DMAR + if (useBurstDshot) { + motor->timer->dmaBurstRef = dmaRef; + } else +#endif + { + // returns 1 bit set to indicate one of TMR_C1_DMA_REQUEST, C2_, C3_, C4_ + 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, + // although at this stage I'm not sure why we can't just set it to 0 + 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 + { + 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; // XXX shouldn't need to be set here, added for testing + + DMAINIT.direction = DMA_DIR_MEMORY_TO_PERIPHERAL; + + // Nothing about fifo or burst in at32 init struct + // DMAINIT.DMA_FIFOMode = DMA_FIFOMode_Enable; + // DMAINIT.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; // Are we relying on this for something? + // DMAINIT.DMA_MemoryBurst = DMA_MemoryBurst_Single; + // DMAINIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; + + 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 + { + dmaSetHandler(dmaIdentifier, motor_DMA_IRQHandler, NVIC_PRIO_DSHOT_DMA, motor->index); + } + + // toplevel enable for the entire timer - starts the counter running + tmr_counter_enable(timer, TRUE); + + { // local scope + 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); + + // XXX We already did this above, why is it repeated here? + 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..98ea19d88a5 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,14 @@ uint32_t getDshotAverageRpm(void) #endif // 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; +} + + #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..d3641e2d0ad 100644 --- a/src/main/drivers/pwm_output_dshot_shared.c +++ b/src/main/drivers/pwm_output_dshot_shared.c @@ -89,6 +89,10 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) return; } + // XXX DEBUG + gpio_bits_set(GPIOC, GPIO_PINS_12); + + /*If there is a command ready to go overwrite the value and send that instead*/ if (dshotCommandIsProcessing()) { value = dshotCommandGetCurrent(index); @@ -110,15 +114,28 @@ 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 } + + // XXX DEBUG + gpio_bits_reset(GPIOC, GPIO_PINS_12); + } @@ -201,6 +218,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/target/AT32F435/target.mk b/src/main/target/AT32F435/target.mk index df3a20f6dca..33c7675acc7 100644 --- a/src/main/target/AT32F435/target.mk +++ b/src/main/target/AT32F435/target.mk @@ -37,8 +37,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 = From 8ea7f3ad017b243eb19576eb8c54fec4dfc16330 Mon Sep 17 00:00:00 2001 From: JBKingdon Date: Sun, 5 Mar 2023 19:17:42 -0500 Subject: [PATCH 2/4] AT32 Implement dshot telemetry Enables bidir dshot for telemetry on AT32. Only timer-pwm is supported, without burst mode. --- src/main/drivers/at32/pwm_output_dshot.c | 379 +++++++++------------ src/main/drivers/pwm_output_dshot_shared.c | 22 +- src/main/io/serial_4way.c | 2 + src/main/target/AT32F435/target.h | 24 +- 4 files changed, 197 insertions(+), 230 deletions(-) diff --git a/src/main/drivers/at32/pwm_output_dshot.c b/src/main/drivers/at32/pwm_output_dshot.c index dfd84572e87..511defa701e 100644 --- a/src/main/drivers/at32/pwm_output_dshot.c +++ b/src/main/drivers/at32/pwm_output_dshot.c @@ -56,7 +56,7 @@ * * @see TIM_CH_TO_SELCHANNEL macro */ -tmr_channel_select_type toCHSelectType(uint8_t bfChannel, bool useNChannel) +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) @@ -77,6 +77,7 @@ tmr_channel_select_type toCHSelectType(uint8_t bfChannel, bool useNChannel) #ifdef HANG_ON_ERRORS while(true) {} #endif + break; } } else { @@ -98,6 +99,7 @@ tmr_channel_select_type toCHSelectType(uint8_t bfChannel, bool useNChannel) #ifdef HANG_ON_ERRORS while(true) {} #endif + break; } } @@ -106,70 +108,96 @@ tmr_channel_select_type toCHSelectType(uint8_t bfChannel, bool useNChannel) #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_channel_select_type atCh = toCHSelectType(dmaMotors[i].timerHardware->channel, dmaMotors[i].output & TIMER_OUTPUT_N_CHANNEL); - - // XXX TODO need to confirm that these AT api calls are equivalent to the commented out ST calls tmr_primary_mode_select(dmaMotors[i].timerHardware->tim, TMR_PRIMARY_SEL_COMPARE); - // TODO these polarity set calls might not be needed if they are already the default settings - if (dmaMotors[i].output & TIMER_OUTPUT_N_CHANNEL) { - // TIM_CCxNCmd(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerHardware->channel, TIM_CCxN_Enable); - tmr_output_channel_polarity_set(dmaMotors[i].timerHardware->tim, atCh, TMR_POLARITY_ACTIVE_LOW); - } else { - // TIM_CCxCmd(dmaMotors[i].timerHardware->tim, dmaMotors[i].timerHardware->channel, TIM_CCx_Enable); - tmr_output_channel_polarity_set(dmaMotors[i].timerHardware->tim, atCh, TMR_POLARITY_ACTIVE_HIGH); - } - + 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 + #ifndef USE_DSHOT_TELEMETRY ,TIM_OCInitTypeDef *pOcInit, DMA_InitTypeDef* pDmaInit -#endif -) + #endif + ) { -#ifdef USE_DSHOT_TELEMETRY + #ifdef USE_DSHOT_TELEMETRY TIM_OCInitTypeDef* pOcInit = &motor->ocInitStruct; DMA_InitTypeDef* pDmaInit = &motor->dmaInitStruct; -#endif + #endif const timerHardware_t * const timerHardware = motor->timerHardware; TIM_TypeDef *timer = timerHardware->tim; - const uint8_t channel = timerHardware->channel; + 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 defined(USE_DSHOT_DMAR) && !defined(USE_DSHOT_TELEMETRY) if (useBurstDshot) { dmaRef = timerHardware->dmaTimUPRef; } -#endif + #endif xDMA_DeInit(dmaRef); -#ifdef USE_DSHOT_TELEMETRY + #ifdef USE_DSHOT_TELEMETRY motor->isInput = false; -#endif + #endif + // Disable the preload buffer so that we can write to the comparison registers (CxDT) immediately - // which seems a bit odd as we don't write to CxDT in this section // This call has the channel -> AT32 channel selector mapping built in timerOCPreloadConfig(timer, channel, FALSE); + tmr_channel_enable(timer, toCHSelectType(channel, useCompOut), FALSE); - timerOCInit(timer, timerHardware->channel, pOcInit); + // 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; + } - tmr_channel_enable(timer, toCHSelectType(timerHardware->channel, useCompOut), TRUE); - timerOCPreloadConfig(timer, timerHardware->channel, TRUE); + 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); @@ -181,16 +209,15 @@ FAST_CODE void pwmDshotSetDirectionOutput( #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 -) +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); @@ -199,27 +226,26 @@ static void pwmDshotSetDirectionInput( if (!inputStampUs) { inputStampUs = micros(); } - // TIM_ARRPreloadConfig(timer, ENABLE); // Need to set the ovfif bit to enable preload - tmr_overflow_event_disable(timer, TRUE); // method is badly named. I think TRUE enables overflow events based on the code - // timer->ARR = 0xffffffff; // arr = auto reload register, may be equivelent to the preload register on at32 - timer->pr = 0xffffffff; - // TIM_ICInit(timer, &motor->icInitStruct); + tmr_period_buffer_enable(timer, FALSE); + + timer->pr = 0xffffffff; - // XXX which input divider setting to use? Looks like it's the equivalent of ST prescaler, and the setting - // would have been in the init struct for ST, but isn't in the AT equivalent. We'll need another way of getting it - // Looks like it may have been hardcoded to div_1 anyway tmr_input_channel_init(timer, &motor->icInitStruct, TMR_CHANNEL_INPUT_DIV_1); -#if defined(STM32F4) - motor->dmaInitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; -#endif + pDmaInit->direction = DMA_DIR_PERIPHERAL_TO_MEMORY; xDMA_Init(dmaRef, pDmaInit); } -#endif - +#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 @@ -229,47 +255,34 @@ void pwmCompleteDshotMotorUpdate(void) } } - for(int i=0; i<3; i++) - { - motorDmaOutput_t * motor = &dmaMotors[i]; - uint32_t *buffer = motor->dmaBuffer; - UNUSED(buffer); - } - - // XXX DEBUG - gpio_bits_set(GPIOC, GPIO_PINS_10); - for (int i = 0; i < dmaMotorTimerCount; i++) { // dmaMotorTimerCount is a global declared in pwm_output_dshot_shared.c -#ifdef USE_DSHOT_DMAR + #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); - // XXX it's not at all clear that the hardware will be compatible at this level tmr_dma_control_config(dmaMotorTimers[i].timer, TMR_DMA_TRANSFER_4BYTES, TMR_CTRL1_ADDRESS); - // XXX TODO - there doesn't seem to be an equivalent of TIM_DMA_Update in AT32 + // 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 + #endif // USE_DSHOT_DMAR { - // TIM_ARRPreloadConfig(dmaMotorTimers[i].timer, DISABLE); + // 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 - // dmaMotorTimers[i].timer->ARR = dmaMotorTimers[i].outputPeriod; - - // XXX This isn't set when telemetry is disabled. Do we come through here? Yes, and outputPeriod is 0 - // Will be needed for bidir, check if it needs to be conditional or can be enabled for both cases - // use an api? - // dmaMotorTimers[i].timer->pr = dmaMotorTimers[i].outputPeriod; - - - // TIM_ARRPreloadConfig(dmaMotorTimers[i].timer, ENABLE); tmr_period_buffer_enable(dmaMotorTimers[i].timer, TRUE); // Ensure that overflow events are enabled. This event may affect both period and duty cycle @@ -279,13 +292,8 @@ void pwmCompleteDshotMotorUpdate(void) tmr_dma_request_enable(dmaMotorTimers[i].timer, dmaMotorTimers[i].timerDmaSources, TRUE); dmaMotorTimers[i].timerDmaSources = 0; - - // I think the counter is reset here to ensure that the first pulse is the correct width - tmr_counter_value_set(dmaMotorTimers[i].timer, 0); } } - // XXX DEBUG - gpio_bits_reset(GPIOC, GPIO_PINS_10); } /** @@ -294,57 +302,60 @@ void pwmCompleteDshotMotorUpdate(void) * 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. */ - -// XXX for testing -uint32_t lastDTVal; -bool mapGood = false; -bool dtBAD = false; - FAST_CODE static void motor_DMA_IRQHandler(dmaChannelDescriptor_t *descriptor) { - if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) { - - // XXX DEBUG - gpio_bits_set(GPIOC, GPIO_PINS_11); - + if (DMA_GET_FLAG_STATUS(descriptor, DMA_IT_TCIF)) + { motorDmaOutput_t * const motor = &dmaMotors[descriptor->userParam]; -#ifdef USE_DSHOT_TELEMETRY + + #ifdef USE_DSHOT_TELEMETRY dshotDMAHandlerCycleCounters.irqAt = getCycleCounter(); -#endif + #endif // Disable timers and dma -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { xDMA_Cmd(motor->timerHardware->dmaTimUPRef, DISABLE); TIM_DMACmd(motor->timerHardware->tim, TIM_DMA_Update, DISABLE); } else -#endif - // disable the dma channel - xDMA_Cmd(motor->dmaRef, FALSE); // Gets re-enabled in pwm_output_dshot_shared.c from pwmWriteDshotInt - - // disable request generation - tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, FALSE); - + #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 + #ifdef USE_DSHOT_TELEMETRY if (useDshotTelemetry) { pwmDshotSetDirectionInput(motor); xDMA_SetCurrDataCounter(motor->dmaRef, GCR_TELEMETRY_INPUT_LEN); xDMA_Cmd(motor->dmaRef, TRUE); - // TIM_DMACmd(motor->timerHardware->tim, motor->timerDmaSource, ENABLE); - // XXX check tmr_dma_request_enable(motor->timerHardware->tim, motor->timerDmaSource, TRUE); dshotDMAHandlerCycleCounters.changeDirectionCompletedAt = getCycleCounter(); } -#endif - DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); + #endif - // XXX DEBUG - gpio_bits_reset(GPIOC, GPIO_PINS_11); + DMA_CLEAR_FLAG(descriptor, DMA_IT_TCIF); } // if DMA_IT_TCIF } @@ -352,61 +363,35 @@ FAST_CODE static void motor_DMA_IRQHandler(dmaChannelDescriptor_t *descriptor) 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 + #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 + #define OCINIT ocInitStruct + #define DMAINIT dmaInitStruct + #endif dmaResource_t *dmaRef = NULL; uint32_t dmaMuxId = 0; - // XXX testing - find a better place and only enable the mux(s) being used - // merge into dmaMuxEnable? - dmamux_enable(DMA1, TRUE); - dmamux_enable(DMA2, TRUE); - - // If we use pinio config for debug pins then the init will probably be done for us, but not until after this function runs - // grab a pin for debug, C10 will do for now - gpio_init_type init = { - .gpio_pins = GPIO_PINS_10, - .gpio_mode = GPIO_MODE_OUTPUT, - .gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE, - .gpio_out_type = GPIO_OUTPUT_PUSH_PULL, - .gpio_pull = GPIO_PULL_NONE - }; - gpio_init(GPIOC, &init); - - -#if defined(USE_DMA_SPEC) + #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 + #else // not defined USE_DMA_SPEC dmaRef = timerHardware->dmaRef; -#if defined(STM32F4) - // XXX Fixme - dmaChannel = timerHardware->dmaChannel; -#endif -#endif // USE_DMA_SPEC + #endif // USE_DMA_SPEC -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { dmaRef = timerHardware->dmaTimUPRef; -#if defined(STM32F4) - // XXX Fixme - dmaChannel = timerHardware->dmaTimUPChannel; -#endif } -#endif // USE_DSHOT_DMAR + #endif // USE_DSHOT_DMAR if (dmaRef == NULL) { return false; @@ -415,7 +400,7 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m dmaIdentifier_e dmaIdentifier = dmaGetIdentifier(dmaRef); bool dmaIsConfigured = false; -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { const resourceOwner_t *owner = dmaGetOwner(dmaIdentifier); if (owner->owner == OWNER_TIMUP && owner->resourceIndex == timerGetTIMNumber(timerHardware->tim)) { @@ -424,8 +409,8 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m return false; } } else -#endif - { + #endif + { // 'else' block if (!dmaAllocate(dmaIdentifier, OWNER_MOTOR, RESOURCE_INDEX(reorderedMotorIndex))) { return false; } @@ -449,13 +434,13 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m const IO_t motorIO = IOGetByTag(timerHardware->tag); uint8_t pupMode = (output & TIMER_OUTPUT_INVERTED) ? GPIO_PULL_DOWN : GPIO_PULL_UP; -#ifdef USE_DSHOT_TELEMETRY + + #ifdef USE_DSHOT_TELEMETRY if (useDshotTelemetry) { output ^= TIMER_OUTPUT_INVERTED; } -#endif + #endif - // motor->iocfg = IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_PP, pupMode); motor->iocfg = IO_CONFIG(GPIO_MODE_MUX, GPIO_DRIVE_STRENGTH_MODERATE, GPIO_OUTPUT_PUSH_PULL, pupMode); IOConfigGPIOAF(motorIO, motor->iocfg, timerHardware->alternateFunction); @@ -478,12 +463,12 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m tmr_output_default_para_init(ocConfig); ocConfig->oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A; - if (output & TIMER_OUTPUT_N_CHANNEL) { // What's the benefit of using either the N or normal channels? - // XXX N channels not yet tested + 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; // XXX is 'reset' equivalent to TRUE or FALSE??? + 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 { @@ -492,55 +477,26 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m ocConfig->oc_polarity = (output & TIMER_OUTPUT_INVERTED) ? TMR_OUTPUT_ACTIVE_LOW : TMR_OUTPUT_ACTIVE_HIGH; } - // OCINIT.TIM_Pulse = 0; - { // local scope for variable - tmr_channel_select_type atChannel = toCHSelectType(timerHardware->channel, false); // tmr_channel_value_set only supports the normal channels - tmr_channel_value_set(timer, atChannel, 0); - // tmr_channel_value_set(timer, atChannel, (motor->index + 1) * 2); - } - -#ifdef USE_DSHOT_TELEMETRY - - // typedef struct - // { - // tmr_channel_select_type input_channel_select; /*!< tmr input channel select */ - // tmr_input_polarity_type input_polarity_select; /*!< tmr input polarity select */ - // tmr_input_direction_mapped_type input_mapped_select; /*!< tmr channel mapped direct or indirect */ - // uint8_t input_filter_value; /*!< tmr channel filter value */ - // } tmr_input_config_type; - - // TIM_ICStructInit(&motor->icInitStruct); + #ifdef USE_DSHOT_TELEMETRY tmr_input_config_type * icConfig = &motor->icInitStruct; tmr_input_default_para_init(icConfig); - - // motor->icInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; icConfig->input_mapped_select = TMR_CC_CHANNEL_MAPPED_DIRECT; - - // motor->icInitStruct.TIM_ICPolarity = TIM_ICPolarity_BothEdge; 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 - // motor->icInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; // XXX not part of at32 input config - - // motor->icInitStruct.TIM_Channel = timerHardware->channel; - icConfig->input_channel_select = toCHSelectType(timerHardware->channel, false); - - // motor->icInitStruct.TIM_ICFilter = 2; - icConfig->input_filter_value = 2; // What are the units, is this valid? -#endif - - -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { motor->timer->dmaBurstRef = dmaRef; } else -#endif - { - // returns 1 bit set to indicate one of TMR_C1_DMA_REQUEST, C2_, C3_, C4_ + #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, - // although at this stage I'm not sure why we can't just set it to 0 motor->timer->timerDmaSources &= ~motor->timerDmaSource; } @@ -554,7 +510,7 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m dma_default_para_init(&DMAINIT); -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { motor->timer->dmaBurstBuffer = &dshotBurstDmaBuffer[timerIndex][0]; @@ -575,80 +531,63 @@ bool pwmDshotMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t m DMAINIT.DMA_Mode = DMA_Mode_Normal; DMAINIT.DMA_Priority = DMA_Priority_High; } else -#endif - { + #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; // XXX shouldn't need to be set here, added for testing - + DMAINIT.buffer_size = DSHOT_DMA_BUFFER_SIZE; DMAINIT.direction = DMA_DIR_MEMORY_TO_PERIPHERAL; - - // Nothing about fifo or burst in at32 init struct - // DMAINIT.DMA_FIFOMode = DMA_FIFOMode_Enable; - // DMAINIT.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; // Are we relying on this for something? - // DMAINIT.DMA_MemoryBurst = DMA_MemoryBurst_Single; - // DMAINIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; - 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 + #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 + #else pwmDshotSetDirectionOutput(motor, &OCINIT, &DMAINIT); -#endif + #endif -#ifdef USE_DSHOT_DMAR + #ifdef USE_DSHOT_DMAR if (useBurstDshot) { if (!dmaIsConfigured) { dmaSetHandler(dmaIdentifier, motor_DMA_IRQHandler, NVIC_PRIO_DSHOT_DMA, motor->index); } } else -#endif - { + #endif + { // 'else' block dmaSetHandler(dmaIdentifier, motor_DMA_IRQHandler, NVIC_PRIO_DSHOT_DMA, motor->index); } - // toplevel enable for the entire timer - starts the counter running - tmr_counter_enable(timer, TRUE); - { // local scope - tmr_channel_select_type chSel = toCHSelectType(timerHardware->channel, output & TIMER_OUTPUT_N_CHANNEL); + 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); - - // XXX We already did this above, why is it repeated here? tmr_counter_enable(timer, TRUE); - } -#ifdef USE_DSHOT_TELEMETRY + + #ifdef USE_DSHOT_TELEMETRY if (useDshotTelemetry) { // avoid high line during startup to prevent bootloader activation *timerChCCR(timerHardware) = 0xffff; } -#endif + #endif + motor->configured = true; return true; diff --git a/src/main/drivers/pwm_output_dshot_shared.c b/src/main/drivers/pwm_output_dshot_shared.c index d3641e2d0ad..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]; @@ -89,10 +98,6 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) return; } - // XXX DEBUG - gpio_bits_set(GPIOC, GPIO_PINS_12); - - /*If there is a command ready to go overwrite the value and send that instead*/ if (dshotCommandIsProcessing()) { value = dshotCommandGetCurrent(index); @@ -133,9 +138,6 @@ FAST_CODE void pwmWriteDshotInt(uint8_t index, uint16_t value) #endif // USE_FULL_LL_DRIVER } - // XXX DEBUG - gpio_bits_reset(GPIOC, GPIO_PINS_12); - } @@ -193,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) { 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..8f4afb0d470 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 @@ -46,6 +54,10 @@ #define USE_EXTI #define USE_GYRO_EXTI +#define GYRO_1_EXTI_PIN PB12 + +#define USE_GYRO_SPI_ICM42688P +#define USE_ACC_SPI_ICM42688P #define USE_I2C #define USE_I2C_DEVICE_1 @@ -65,14 +77,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 From fc11f7b21dcfc74059787a6271db3c740f119c57 Mon Sep 17 00:00:00 2001 From: JBKingdon Date: Sun, 5 Mar 2023 21:13:33 -0500 Subject: [PATCH 3/4] Updates for target.h and config.h Removed a conflicting definition from target.h and added the PA9/10 uart pins for the at-link serial connection, but these don't seem to be active until added from the cli. --- src/config/ATSTARTF435/config.h | 8 ++++++++ src/main/target/AT32F435/target.h | 3 --- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config/ATSTARTF435/config.h b/src/config/ATSTARTF435/config.h index 7e45077854a..00fbdcbb73c 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/target/AT32F435/target.h b/src/main/target/AT32F435/target.h index 8f4afb0d470..d369ca21ce8 100644 --- a/src/main/target/AT32F435/target.h +++ b/src/main/target/AT32F435/target.h @@ -54,10 +54,7 @@ #define USE_EXTI #define USE_GYRO_EXTI -#define GYRO_1_EXTI_PIN PB12 -#define USE_GYRO_SPI_ICM42688P -#define USE_ACC_SPI_ICM42688P #define USE_I2C #define USE_I2C_DEVICE_1 From a4f0bd3133c3ae740cd50c7fc9f591a566855966 Mon Sep 17 00:00:00 2001 From: JBKingdon Date: Tue, 7 Mar 2023 07:39:54 -0500 Subject: [PATCH 4/4] Wrap erpmToRpm in #def Hopefully fix problems with unit tests by making erpmToRpm conditionally compiled again. --- src/main/drivers/dshot.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/drivers/dshot.c b/src/main/drivers/dshot.c index 98ea19d88a5..2c3105f0af4 100644 --- a/src/main/drivers/dshot.c +++ b/src/main/drivers/dshot.c @@ -325,6 +325,8 @@ 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) { @@ -332,6 +334,7 @@ uint32_t erpmToRpm(uint16_t erpm) return (erpm * 200) / motorConfig()->motorPoleCount; } +#endif // USE_ESC_SENSOR || USE_DSHOT_TELEMETRY #ifdef USE_DSHOT_TELEMETRY_STATS