Thanks for your quick response @steved. At the moment I am doing what you have proposed. I have a callback configured for when the DMA finishes. When it does the interrupt configures a timer to wait for 25us (or less). This in turn calls another interrupt that starts the next transfer. However, the SPI DMA interrupt keeps lining up with the system tick clock instead of whatever lower value I set for the timer.
In a previous version I had a spare timer running at a faster rate that would launch a SPI transfer independent of the DMA transfer status, however this didn't work as the first SPI transfer would not finish before the timer set up a next transfer, leading to SPI DMA errors and the system entering the halt state.
Below the pieces of code that are related to this question. Hopefully that gives more insight into my setup.
The piece of code below sets up CC0 (the burst timer) and CC1 (main frequency 1kHz) of TIM5.
Code: Select all
void ads8167_configure(void) {
utils_sys_lock_cnt();
state = INIT;
// ------------- Timer5 for external ADC sampling ------------- //
TIM_DeInit(TIM5);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM5->CNT = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_Period = TIM5_CLOCK / ADC_SAMPLE_RATE; // auto reload register value
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // count up till value of auto reload register
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); // sets counter, auto reload and prescaler registers
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;
TIM_OCInitStructure.TIM_Pulse = TIM5_CLOCK / ADC_BURST_FREQUENCY;
TIM_OC1Init(TIM5, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Disable);
TIM_OCInitStructure.TIM_Pulse = TIM5_CLOCK / ADC_SAMPLE_RATE;
TIM_OC2Init(TIM5, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM5, ENABLE);
TIM_CCPreloadControl(TIM5, ENABLE);
// PWM outputs have to be enabled in order to trigger ADC on CCx
TIM_CtrlPWMOutputs(TIM5, ENABLE);
TIM_SelectInputTrigger(TIM5, TIM_TS_ITR1);
TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_Reset);
// Enable TIM5
TIM_Cmd(TIM5, ENABLE);
// Enable CC1 and CC2 interrupt
TIM_ITConfig(TIM5, TIM_IT_CC1, ENABLE);
TIM_ITConfig(TIM5, TIM_IT_CC2, ENABLE);
utils_sys_unlock_cnt();
state = BURST;
nvicEnableVector(TIM5_IRQn, 6);
}
The ads_isr is called from the TIM5_IRQHandler. Both CC0 and CC1 interrupts trigger this function.
Code: Select all
void ads_isr(void) {
// Default TIM channel configuration mode values
static TIM_OCInitTypeDef TIM_OCInitStructure = {
.TIM_OCMode = TIM_OCMode_PWM1,
.TIM_OutputState = TIM_OutputState_Enable,
.TIM_OCPolarity = TIM_OCPolarity_High,
.TIM_OCNPolarity = TIM_OCNPolarity_High,
.TIM_OCIdleState = TIM_OCIdleState_Set,
.TIM_OCNIdleState = TIM_OCNIdleState_Set
};
static uint8_t ain_counter = 0;
switch(state) {
case INIT:
state = BURST;
break;
case BURST:
// Determine next state
if (ain_counter < HALL_SENSORS) {
palClearPad(GPIOC, 6);
state = BURST;
ain_counter++;
// Collect data
chSysLockFromISR();
spiSelectI(driver);
spiStartReceiveI(driver, 4, (uint8_t *) &ADS_Value);
// Ensure burst only continues after DMA receive has finished
palTogglePad(GPIOC, 7);
TIM_ITConfig(TIM5, TIM_IT_CC1, DISABLE);
chSysUnlockFromISR();
} else {
state = ADS_RESET;
palTogglePad(GPIOC, 6);
// Wait for end of ADC Period
TIM_ITConfig(TIM5, TIM_IT_CC1, DISABLE);
TIM_ITConfig(TIM5, TIM_IT_CC2, ENABLE);
}
break;
case ADS_RESET:
ain_counter = 0;
// TODO find a better way of integrating the position estimator
// Run this before enabling the compare registers
palClearPad(GPIOC, 8);
chSysLockFromISR();
calculate_position();
chSysUnlockFromISR();
palSetPad(GPIOC, 8);
TIM_OCInitStructure.TIM_Pulse = TIM5_CLOCK / ADC_BURST_FREQUENCY;
TIM_OC1Init(TIM5, &TIM_OCInitStructure);
// Launch burst sequence
TIM_ITConfig(TIM5, TIM_IT_CC1, ENABLE);
TIM_ITConfig(TIM5, TIM_IT_CC2, DISABLE);
state = BURST;
break;
case STOP:
break;
}
}
The SPI DMA callback. This configures CC0 of TIM5 for the next burst acquisition.
Code: Select all
void ads_spi_isr(void) {
chSysLockFromISR();
palSetPad(GPIOC, 6);
spiUnselectI(driver);
// Reset counter and continue
TIM5->CNT = 0;
ADC_Value[HW_ADC_CHANNELS + (ADS_Value >> 20)] = (uint16_t) ((ADS_Value & 0xff) << 8) | ((ADS_Value & 0xff00) >> 8);
// Continue burst sequence
TIM_ITConfig(TIM5, TIM_IT_CC1, ENABLE);
palTogglePad(GPIOC, 7);
chSysUnlockFromISR();
}
Thanks again! Cheers.