I'm using some ISRs for timers and for reconfiguring the DMAC after copying a block of ADC data to external memory.
While reading about another topic on this forum I noticed various mentions of
SF_CONTEXT_SAVE
R_BSP_IrqStatusClear(R_SSP_CurrentIrqGet());
SF_CONTEXT_RESTORE
However, I also saw various Synergy examples which don't use these. My current understanding is as follows:
SF_CONTEXT_SAVE and SF_CONTEXT_RESTORE are mainly used to enable ISR event tracing with TraceX. It shouldn't really matter if these are missing with the current implementation when this feature is not needed.
R_BSP_IrqStatusClear(R_SSP_CurrentIrqGet()); is used to clear the ISR flag of the current ISR to prevent calling it twice when it doesn't finish fast enough.
I also saw a mention that some peripherals might need some flags cleared in their ISRs (e.g. here: renesasrulz.com/.../interrupts-under-threadx https://renesasrulz.com/synergy/f/synergy---forum/7254/making-own-isr-in-s7g2 ). How can I find out what I need to clear? I don't clear anything right now.
Hi Jeremy,
yes I'm talking about ISRs, not callbacks from Synergy.
I'm using a timer and the DMAC transfer driver callback, which I think is an IRQ as well, by looking at r_dmac.c, R_DMAC_Open():
if (NULL != p_cfg->p_callback) { ssp_vector_info_t * p_vector_info; fmi_event_info_t event_info = {(IRQn_Type) 0U}; err = g_fmi_on_fmi.eventInfoGet(&feature, SSP_SIGNAL_DMAC_INT, &event_info); p_ctrl->irq = event_info.irq; DMAC_ERROR_RETURN(SSP_INVALID_VECTOR != p_ctrl->irq, SSP_ERR_IRQ_BSP_DISABLED); R_SSP_VectorInfoGet(p_ctrl->irq, &p_vector_info); NVIC_SetPriority(p_ctrl->irq, p_cfg->irq_ipl); *(p_vector_info->pp_ctrl) = p_ctrl; }
if (NULL != p_cfg->p_callback)
{
ssp_vector_info_t * p_vector_info;
fmi_event_info_t event_info = {(IRQn_Type) 0U};
err = g_fmi_on_fmi.eventInfoGet(&feature, SSP_SIGNAL_DMAC_INT, &event_info);
p_ctrl->irq = event_info.irq;
DMAC_ERROR_RETURN(SSP_INVALID_VECTOR != p_ctrl->irq, SSP_ERR_IRQ_BSP_DISABLED);
R_SSP_VectorInfoGet(p_ctrl->irq, &p_vector_info);
NVIC_SetPriority(p_ctrl->irq, p_cfg->irq_ipl);
*(p_vector_info->pp_ctrl) = p_ctrl;
}
For the timer I don't set any register flags in the Interrupt handler. For the DMAC, my code currently looks like this:
void dmac_adc_measurement_callback(transfer_callback_args_t * p_args){ SF_CONTEXT_SAVE; SSP_PARAMETER_NOT_USED(p_args); g_gpt0.p_api->stop(g_gpt0.p_ctrl); /* Count blocks and store adc_buffer.buffer index of the next block */ adc_device.interrupt_count = (adc_device.interrupt_count + 1) % DMAC_NUM_BLOCKS; adc_device.current_block_index = adc_device.interrupt_count * DMAC_NUM_TRANSFERS; /* Check if measurement is completed (= buffer is filled once since trigger_index)*/ if(adc_device.state == ADCDevice::State::MEASURING && adc_device.current_block_index == adc_device.trigger_index) adc_device.set_state(ADCDevice::State::MEASUREMENT_END); else { /* Set the number of transfers for the next block */ R_DMAC0->DMCRB = DMAC_NUM_TRANSFERS; /* If we start again at the beginning */ if(adc_device.interrupt_count == 0) { /* Reset the DMAC for the next capture */ /* Reset the source, destination address */ g_transfer0.p_cfg->p_info->p_dest = static_cast<void*>(adc_device.buffer.data()); /* Reset the DMAC. * Source address will be the same. * New Destination address. * Refresh the block count */ g_transfer0.p_api->reset(g_transfer0.p_ctrl, g_transfer0.p_cfg->p_info->p_src, g_transfer0.p_cfg->p_info->p_dest, DMAC_NUM_TRANSFERS ); } g_transfer0.p_api->enable(g_transfer0.p_ctrl); g_gpt0.p_api->start(g_gpt0.p_ctrl); } /* Calibrate the sensor channels while idle */ //double FM_idle_time = static_cast<double>(tx_time_get() - FM::instance().idle_since) / static_cast<double>(TX_TIMER_TICKS_PER_SECOND); if(adc_device.state == ADCDevice::State::IDLE && (tx_time_get() - FM::instance().idle_since) > DMAC_NUM_TRANSFERS * ADC_SPEED * TX_TIMER_TICKS_PER_SECOND) calibration_step(); SF_CONTEXT_RESTORE;}
void dmac_adc_measurement_callback(transfer_callback_args_t * p_args)
SF_CONTEXT_SAVE;
SSP_PARAMETER_NOT_USED(p_args);
g_gpt0.p_api->stop(g_gpt0.p_ctrl);
/* Count blocks and store adc_buffer.buffer index of the next block */
adc_device.interrupt_count = (adc_device.interrupt_count + 1) % DMAC_NUM_BLOCKS;
adc_device.current_block_index = adc_device.interrupt_count * DMAC_NUM_TRANSFERS;
/* Check if measurement is completed (= buffer is filled once since trigger_index)*/
if(adc_device.state == ADCDevice::State::MEASURING
&& adc_device.current_block_index == adc_device.trigger_index)
adc_device.set_state(ADCDevice::State::MEASUREMENT_END);
else
/* Set the number of transfers for the next block */
R_DMAC0->DMCRB = DMAC_NUM_TRANSFERS;
/* If we start again at the beginning */
if(adc_device.interrupt_count == 0)
/* Reset the DMAC for the next capture */
/* Reset the source, destination address */
g_transfer0.p_cfg->p_info->p_dest = static_cast<void*>(adc_device.buffer.data());
/* Reset the DMAC.
* Source address will be the same.
* New Destination address.
* Refresh the block count
*/
g_transfer0.p_api->reset(g_transfer0.p_ctrl,
g_transfer0.p_cfg->p_info->p_src,
g_transfer0.p_cfg->p_info->p_dest,
DMAC_NUM_TRANSFERS );
g_transfer0.p_api->enable(g_transfer0.p_ctrl);
g_gpt0.p_api->start(g_gpt0.p_ctrl);
/* Calibrate the sensor channels while idle */
//double FM_idle_time = static_cast<double>(tx_time_get() - FM::instance().idle_since) / static_cast<double>(TX_TIMER_TICKS_PER_SECOND);
if(adc_device.state == ADCDevice::State::IDLE && (tx_time_get() - FM::instance().idle_since) > DMAC_NUM_TRANSFERS * ADC_SPEED * TX_TIMER_TICKS_PER_SECOND)
calibration_step();
SF_CONTEXT_RESTORE;
The code is used to implement a multi-DMAC block ring buffer with a pre/post-triggering system for short time measurements, somewhat similar to an oscilloscope. The code is (mostly) working as expected. so I believe that I don't need to clear any additional registers.
However, I sometimes see an erroneous data point at the block boundary, which I assume comes from timing problems in this fucntion. I've written some different variants which will be tested shortly:
I hope that one of these will help with this problem, otherwise I'll have to do some signal processing to filter out invalid data points.
If may be worth while considering using a custom DMAC ISR as the SSP ISR handlers can be rather "code heavy" and could slow the response time.
Do you mean replacing g_transfer0.p_api->enable() with a custom implementation?
We cannot (or probably shouldn't) start it directly at the ISR start since we may have to stop the measurement at the post triggering time. It may be possible to use a slightly larger buffer to keep it running before doing the check at the cost of some additional complexity.
I think there isn't too much time critical other processes going on. We have some ethernet communication and a much slower ADC measurement on ADC1. There's also some controlling done in the ISR I showed above, but that can happen after reconfiguration. There are however some IRQ based triggers for the measurement with higher or equal priority as the DMAC ISR.
I've started some optimizations for the ISR:
void dmac_int_isr (void){ /* Stop the timer that triggers the ADC */ g_gpt0.p_api->stop(g_gpt0.p_ctrl); /* Count blocks and store adc_buffer.buffer index of the next block */ adc_device.interrupt_count = (adc_device.interrupt_count + 1) % DMAC_NUM_BLOCKS; /* Check if measurement is completed (= buffer is filled once since trigger_index)*/ if(adc_device.state != ADCDevice::State::MEASURING || adc_device.interrupt_count != adc_device.trigger_interrupt_count) { /* Set the number of transfers for the next block */ R_DMAC0->DMCRB = DMAC_NUM_TRANSFERS; /* If we start again at the beginning */ if(adc_device.interrupt_count == 0) { /* Reset the DMAC for the next capture */ /* Reset the source, destination address */ g_transfer0.p_cfg->p_info->p_dest = static_cast<void*>(adc_device.buffer.data()); /* Reset the DMAC. * Source address will be the same. * New Destination address. * Refresh the block count */ g_transfer0.p_api->reset(g_transfer0.p_ctrl, g_transfer0.p_cfg->p_info->p_src, g_transfer0.p_cfg->p_info->p_dest, DMAC_NUM_TRANSFERS ); } /* Transfer is disabled during the interrupt if an interrupt is requested after each block. If not all transfers * are complete, reenable transfer here. */ auto p_ctrl = static_cast<dmac_instance_ctrl_t*>(g_transfer0.p_ctrl); static_cast<R_DMAC0_Type*>(p_ctrl->p_reg)->DMCNT_b.DTE = true; extern R_ICU_Type * gp_icu_regs; gp_icu_regs->DELSRn[p_ctrl->channel].DELSRn_b.DELS = p_ctrl->trigger; /* Start the ADC timer again to resume measurement */ g_gpt0.p_api->start(g_gpt0.p_ctrl); } else adc_device.set_state(ADCDevice::State::MEASUREMENT_END); adc_device.current_block_index = adc_device.interrupt_count * DMAC_NUM_TRANSFERS; /* Calibrate the sensor channels while idle */ //double FM_idle_time = static_cast<double>(tx_time_get() - FM::instance().idle_since) / static_cast<double>(TX_TIMER_TICKS_PER_SECOND); if(adc_device.state == ADCDevice::State::IDLE && (tx_time_get() - FM::instance().idle_since) > DMAC_NUM_TRANSFERS * ADC_SPEED * TX_TIMER_TICKS_PER_SECOND) calibration_step(); /** Clear pending IRQ to make sure it doesn't fire again after exiting */ R_BSP_IrqStatusClear(R_SSP_CurrentIrqGet());} /* End of function dmac_int_isr */
void dmac_int_isr (void)
/* Stop the timer that triggers the ADC */
if(adc_device.state != ADCDevice::State::MEASURING
|| adc_device.interrupt_count != adc_device.trigger_interrupt_count)
/* Transfer is disabled during the interrupt if an interrupt is requested after each block. If not all transfers
* are complete, reenable transfer here. */
auto p_ctrl = static_cast<dmac_instance_ctrl_t*>(g_transfer0.p_ctrl);
static_cast<R_DMAC0_Type*>(p_ctrl->p_reg)->DMCNT_b.DTE = true;
extern R_ICU_Type * gp_icu_regs;
gp_icu_regs->DELSRn[p_ctrl->channel].DELSRn_b.DELS = p_ctrl->trigger;
/* Start the ADC timer again to resume measurement */
/** Clear pending IRQ to make sure it doesn't fire again after exiting */
} /* End of function dmac_int_isr */
It might be possible to optimize away the stopping of the ADC timer (if it's fast enough) and the check for ADCDevice::State::MEASURING. I'm not sure if the gp_icu_regs->DELSRn[p_ctrl->channel].DELSRn_b.DELS = p_ctrl->trigger; line is needed.
In the next step I'll try to use a second DMA channel. If that is not enough I might add one more block to the rin buffer to directly resume the measurement without checking for the stop condition. If that is still not sufficient the DTC approach might be needed. Do you think this is a valid approach?
Hi Richard, thanks for the example! I actually went ahead and wrote an implementation myself in parallel. I will check yours out and compare it against mine. I'll report back with test results tomorrow.
One question: Is it necessary to move the resetting of the DMAC channel that recently finished into the main loop / a thread or can it remain in the ISR after starting the new one?
I more or less have the same implementation, except that I haven't migrated to my own ISR yet and still rely on the callback, in which I also setup the DMAC channel that will be needed next time. I've also used pointers to the transfer structures and registers which I swap on each invocation of the callback.
Best Regards, Chris