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.
In reply to Jeremy:
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.
In reply to ChrisS:
In reply to Richard:
Hi Richard,
I think we actually talked about this before and you gave me an example.
We're measuring a signal with the ADC with a very high sampling rate of 500 kHz, so very close to the limit of the S5D5. The measuring time is 1 s and the data is saved continously in a ring buffer into an external SDRAM. The DMAC is triggered by the ADC Scan End event to copy the measured data points (4 channels every 2 µs). However, as the DMAC can only transfer up to 65536 times it needs to be reconfigured repeatedly. This is done in the previous code by resetting the number of transfers (R_DMAC0->DMCRB = DMAC_NUM_TRANSFERS;) and by setting the destination address to the start of the new block (g_transfer0.p_cfg->p_info->p_dest = static_cast<void*>(adc_device.buffer.data());). Afterwards the DMAC is reset and enabled and the timer which triggers the ADC measurements is started again.
Below is the code which I used to initialize the peripherals and to reset it after an event has been measured:
void ADCDevice::initialize(){ ssp_err_t ssp_err; /* Open the ADC Unit 0 - Channels AN003, AN004, AN005, AN006 are enabled */ ssp_err = g_adc0.p_api->open(g_adc0.p_ctrl, g_adc0.p_cfg); ASSERT_STATUS(ssp_err); /* Configure the ADC Unit 0 - Channels AN003, AN004, AN005, AN006 are enabled */ ssp_err = g_adc0.p_api->scanCfg(g_adc0.p_ctrl, g_adc0.p_channel_cfg); ASSERT_STATUS(ssp_err); /* Open the GPT that will trigger the ADC every 2us */ ssp_err = g_gpt0.p_api->open(g_gpt0.p_ctrl, g_gpt0.p_cfg); ASSERT_STATUS(ssp_err); /* Link the GPT5 Overflow event to trigger the ADC UNIT 0 */ ssp_err = g_elc.p_api->linkSet(this->elc_peripheral, this->elc_event); ASSERT_STATUS(ssp_err); /* Set the DMAC source and destination addresses * All other parameters have been set in the configuration */ g_transfer0.p_cfg->p_info->p_src = (void*)&R_S12ADC0->ADDRn[this->sensor_channels[0]]; g_transfer0.p_cfg->p_info->p_dest = (void*)&buffer[0][0]; /* Open the DMAC */ ssp_err = g_transfer0.p_api->open(g_transfer0.p_ctrl, g_transfer0.p_cfg); ASSERT_STATUS(ssp_err); /* Direct register access of DMAC0 to enable the correct interrupt */ /* By default, if DMAC interrupts are enabled then DTIE, ESIE and RPTIE are enabled * We only want DTIE, so rewrite DMIT from 0x1C to 0x10 * Refer to section 17.2.6 of the Hardware User's Manual for more information */ R_DMAC0->DMINT = 0x10; /* Set the ADC ready for triggering */ ssp_err = g_adc0.p_api->scanStart(g_adc0.p_ctrl); ASSERT_STATUS(ssp_err); //Open the IRQs to enable triggering uint32_t i = 0; for(auto irq : this->surge_irqs) { const_cast<external_irq_cfg_t *>(irq->p_cfg)->p_context = &surge_trigger_irq_counts[i]; ssp_err = irq->p_api->open(irq->p_ctrl, irq->p_cfg); ASSERT_STATUS(ssp_err); i++; } return;}void ADCDevice::reset(){ uint32_t status = SSP_SUCCESS; /* Reset the DMAC for the next capture */ /* Reset the source, destination address */ g_transfer0.p_cfg->p_info->p_dest = (void*)&buffer[0][0]; /*Also reset the current index in the buffer */ current_block_index = 0; interrupt_count = 0; /* Reset the DMAC. * Source address will be the same. * New Destination address. * Refresh the block count */ status = 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 ); ASSERT_STATUS(status); /* Enable the DMAC, ready for trigger */ status = g_transfer0.p_api->enable(g_transfer0.p_ctrl); ASSERT_STATUS(status); /* Start continuous measurement by starting GPT0 that will start the "FAST" ADC, DMAC process */ status = g_gpt0.p_api->start(g_gpt0.p_ctrl); ASSERT_STATUS(status); //Enable interrupts to enable the measurement triggers for(auto irq : surge_irqs) { status = irq->p_api->enable(irq->p_ctrl); ASSERT_STATUS(status); }}
void ADCDevice::initialize()
ssp_err_t ssp_err;
/* Open the ADC Unit 0 - Channels AN003, AN004, AN005, AN006 are enabled */
ssp_err = g_adc0.p_api->open(g_adc0.p_ctrl, g_adc0.p_cfg);
ASSERT_STATUS(ssp_err);
/* Configure the ADC Unit 0 - Channels AN003, AN004, AN005, AN006 are enabled */
ssp_err = g_adc0.p_api->scanCfg(g_adc0.p_ctrl, g_adc0.p_channel_cfg);
/* Open the GPT that will trigger the ADC every 2us */
ssp_err = g_gpt0.p_api->open(g_gpt0.p_ctrl, g_gpt0.p_cfg);
/* Link the GPT5 Overflow event to trigger the ADC UNIT 0 */
ssp_err = g_elc.p_api->linkSet(this->elc_peripheral, this->elc_event);
/* Set the DMAC source and destination addresses
* All other parameters have been set in the configuration
g_transfer0.p_cfg->p_info->p_src = (void*)&R_S12ADC0->ADDRn[this->sensor_channels[0]];
g_transfer0.p_cfg->p_info->p_dest = (void*)&buffer[0][0];
/* Open the DMAC */
ssp_err = g_transfer0.p_api->open(g_transfer0.p_ctrl, g_transfer0.p_cfg);
/* Direct register access of DMAC0 to enable the correct interrupt */
/* By default, if DMAC interrupts are enabled then DTIE, ESIE and RPTIE are enabled
* We only want DTIE, so rewrite DMIT from 0x1C to 0x10
* Refer to section 17.2.6 of the Hardware User's Manual for more information
R_DMAC0->DMINT = 0x10;
/* Set the ADC ready for triggering */
ssp_err = g_adc0.p_api->scanStart(g_adc0.p_ctrl);
//Open the IRQs to enable triggering
uint32_t i = 0;
for(auto irq : this->surge_irqs)
const_cast<external_irq_cfg_t *>(irq->p_cfg)->p_context = &surge_trigger_irq_counts[i];
ssp_err = irq->p_api->open(irq->p_ctrl, irq->p_cfg);
i++;
return;
void ADCDevice::reset()
uint32_t status = SSP_SUCCESS;
/*Also reset the current index in the buffer */
current_block_index = 0;
interrupt_count = 0;
status = g_transfer0.p_api->reset(g_transfer0.p_ctrl,
ASSERT_STATUS(status);
/* Enable the DMAC, ready for trigger */
status = g_transfer0.p_api->enable(g_transfer0.p_ctrl);
/* Start continuous measurement by starting GPT0 that will start the "FAST" ADC, DMAC process */
status = g_gpt0.p_api->start(g_gpt0.p_ctrl);
//Enable interrupts to enable the measurement triggers
for(auto irq : surge_irqs)
status = irq->p_api->enable(irq->p_ctrl);
The constants are defined as follows:
const uint16_t DMAC_TRANSFER_SIZE = 4;const uint16_t DMAC_NUM_TRANSFERS = 2500;const uint16_t DMAC_BLOCK_SIZE = DMAC_TRANSFER_SIZE * DMAC_NUM_TRANSFERS;
This means that the DMAC reconfiguration will happen every 2 µs * 2500 = 5 ms. At this block boundary I'm getting nonsensical values sometimes. They can be larger than the 12 bit range of the ADC.
Hi Richard, I think that's a great idea, I will try to implement and test this tomorrow. Is it possible to setup an ELC link to have it start automatically? My knowledge about the ELC system is very limited unfortunately. Otherwise the start of the second DMA channel needs to be fast enough to continue the measurement here. If necessary, I could also tolerate a somewhat longer time between the data points at this boundary, since the point should be deterministic and could be considered in later signal processing. Best Regards, Chris
Edit: I found a diagram in the µC manual that seems to indicate that only the DTC can be started by the ELC, so this does not seem to be an option.
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