Reputation: 23
I'm using the SAMD21 xPlained pro with a prototype to acquire a signal, using the internal ADC and DMAC which configured to collect 5500 samples. The samples then transferred to a PC application via the target USB.
The firmware that I wrote for that is working, but I noticed that from time to time the DMA get stuck in busy state. To debug that I canceled the transfer part to the PC via the USB.
I noticed that DMA works fine and transfers the samples to the memory, but if I connect the USB cable to the PC (without transferring data through the USB), the DMA gets stuck from time to time; but once I disconnect the (target) USB cable, the DMA works continuously without getting stuck in busy state.
I suspect that it has something with the interrupt and priorities of the USB and the ADC, which are both using the DMA. I thought I should set the ADC sampling at top priority so the USB won't cause the DMA stuck in busy state, but I couldn't find how to configure that in the code (I'm using ASF).
Any idea why plugging the USB causes the DMA occasionally stuck in busy state? Should this problem relates to priorities as I suspect, any idea how to reduce the USB interrupt priority?
code:
void GSWatchMainProcess(void)
{
static int i=0;
GSPacket PacketToStream;
switch (KnockKnockStateMachine)
{
case InitKnockKnock:
KnockKnockStateMachine = KnockKnockStandby;
break;
case KnockKnockStandby:
if (StreamADC && !RequestForAcknowledge) KnockKnockStateMachine = WakeupAphrodite;
KnockKnockStateMachine = WakeupAphrodite; //this line was added to skip waiting for a command from the PC
break;
case WakeupAphrodite:
if (dma_is_busy(&example_resource))
{
KnockKnockStateMachine = AbortKnockKnock;
}
else
{
port_pin_set_output_level(PIN_PB09, true);
port_pin_set_output_level(LED_0_PIN, false);
transfer_is_done = false;
if(dma_start_transfer_job(&example_resource))
{
KnockKnockStateMachine = AbortKnockKnock;
}
}
KnockKnockStateMachine = WaitForBurstToEnd;
i=200000; //this counter is used as work-around to reset the DMA when it get stuck after timeout
break;
case WaitForBurstToEnd:
if (!dma_is_busy(&example_resource))
{
KnockKnockStateMachine = ProcessBurst;
}
if(!--i) // work-around to reset DMA when it get stuck
{
KnockKnockStateMachine = AbortKnockKnock;
}
break;
case ProcessBurst:
PacketToStream.Type=Stream;
PacketToStream.ContentLength=0;
for (i = 0; i<(ADC_SAMPLES); i++)
{
PacketToStream.Content[PacketToStream.ContentLength++] = adc_result_buffer[i] / 256;
PacketToStream.Content[PacketToStream.ContentLength++] = adc_result_buffer[i] % 256;
if(PacketToStream.ContentLength>=PACKET_MAX_SIZE)
{
//SendViaGSWatchLink(PacketToStream);
PacketToStream.ContentLength=0;
}
}
//if(PacketToStream.ContentLength>0) SendViaGSWatchLink(PacketToStream);
RequestForAcknowledge = true;
KnockKnockStateMachine = KnockKnockStandby;
break;
case AbortKnockKnock:
dma_abort_job(&example_resource);
dma_free(&example_resource);
port_pin_set_output_level(PIN_PB09, false);
port_pin_set_output_level(LED_0_PIN, true);
transfer_is_done = true;
configure_dma_resource(&example_resource);
setup_transfer_descriptor(&DMA_ADC_descriptor);
dma_add_descriptor(&example_resource, &DMA_ADC_descriptor);
dma_register_callback(&example_resource, transfer_done, DMA_CALLBACK_TRANSFER_DONE);
dma_enable_callback(&example_resource, DMA_CALLBACK_TRANSFER_DONE);
system_interrupt_enable_global();
KnockKnockStateMachine = WakeupAphrodite;
break;
}
}
void transfer_done(struct dma_resource* const resource )
{
transfer_is_done = true;
port_pin_set_output_level(PIN_PB09, false);
port_pin_set_output_level(LED_0_PIN, true);
}
void setup_transfer_descriptor(DmacDescriptor *descriptor)
{
struct dma_descriptor_config descriptor_config;
dma_descriptor_get_config_defaults(&descriptor_config);
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
descriptor_config.block_transfer_count = ADC_SAMPLES;
descriptor_config.dst_increment_enable = true;
descriptor_config.src_increment_enable = false;
descriptor_config.source_address = (uint32_t)(&adc_instance.hw->RESULT.reg);
//descriptor_config.destination_address = (uint32_t)adc_result_buffer + 2 * ADC_SAMPLES;
descriptor_config.destination_address = (uint32_t)adc_result_buffer + sizeof(adc_result_buffer);
dma_descriptor_create(descriptor, &descriptor_config);
}
void configure_dma_resource(struct dma_resource *resource)
{
struct dma_resource_config config_dma;
dma_get_config_defaults(&config_dma);
config_dma.peripheral_trigger = ADC_DMAC_ID_RESRDY;
config_dma.trigger_action = DMA_TRIGGER_ACTON_BEAT;
config_dma.priority = DMA_PRIORITY_LEVEL_3;
dma_allocate(resource, &config_dma);
}
void configure_adc(void)
{
struct adc_config config_adc;
adc_get_config_defaults(&config_adc);
// config_adc.gain_factor = ADC_GAIN_FACTOR_DIV2; //TODO: check if we need this feature
config_adc.clock_prescaler = ADC_CLOCK_PRESCALER_DIV32; //TODO: check whether it possible to work with 8
config_adc.reference = ADC_REFERENCE_INTVCC0; //ADC_REFERENCE_INT1V; //ADC_REFERENCE_INTVCC1;
config_adc.positive_input = ADC_POSITIVE_INPUT_PIN8;
config_adc.negative_input = ADC_NEGATIVE_INPUT_GND;
config_adc.resolution = ADC_RESOLUTION_12BIT;
config_adc.sample_length = 0; //4
config_adc.differential_mode = false;
config_adc.freerunning = true;
adc_init(&adc_instance, ADC, &config_adc);
adc_enable(&adc_instance);
}
Upvotes: 0
Views: 1837
Reputation: 51835
I am no expert in USB and this is possibly unrelated to you case but I got also issues with USB-CDC and occasional freezing on UC3A3 chips I found out 2 separate reasons:
USB interrupt and tasks must have enough free MCU time
I am setting test bits on entering and exiting of each interrupt I got (USB included) and if they are too close to overlap weird things start to happen like freezing, output signal jitter (way bigger then all ISRs together), sync and acknowledge errors etc even if USB has the highest priority. If I re-time all the stuff I use so the interrupts are firing not at the same times all works good.
beware GPIO toggling is slow operation so you need to take that into account too.
Each version of Host OS (windows) has different timing
I use my MCU for full duplex synchronous bulk transfers and got 3 layers of FIFO (2 on Host and 1 on MCU) to keep up the sync (contnuous 24/7 ~640KByte/s
in and ~640KByte/s
out). With each new version of Windows (w2k -> Xp --> w7
) something changed in either scheduling of threads or driver services and re-timing was necessary changing transfer times and timeouts so the lock less multi threads for RT synchronous USB transfers still work as should.
Latest thing I discover in W7 (they added new "feature") is that some USB controllers (like Intel) on Host side have either freezes on their own or different priorities of data transfers (either based on pipe or direction) and sending/receiving ratio 1:1 no more works on some machines causing freezes on the MCU side from 1ms
up to few seconds due to blocked FIFOs. The workaround for that is to fully fill MCU receiving FIFO's (or increase MCU FIFO size which is not possible in my case as it takes almost all memory already) which should be done anyway but in my case I work in RT and do not have many packets ahead so I find out I need to send at least 3 times more packets then receive until the FIFOs full-fill over time (and each time the Host sending freezes which is for some machines all the time) just not to lost sync and all the half full FIFO mechanism on those machines not work anymore.
So in case your USB transfer is synchronized with host or the other way around it is worth to check different OS (like Xp) if the problem is also there. If not there is a high chance you got similar problems I was dealing with so you can try the workarounds ...
Upvotes: 0
Reputation: 1
I experienced a very similar issue. My SPI workes fine although I plugged in the USB but started with this Issue when I start it from a specific USB-CDC command.
I am working with asf 3.34 on as 7 and a L21 (very similar).
My workaround which is not clean but works:
After starting the DMA transfer I continiusly(while-loop) check for the transfer done bit(REG_DMAC_CHINTFLAG
should be 0x2) and when it is I set the status to ok.
In Code:
transfer_tx_is_done=0;
dma_start_transfer_job(&res_2_Byte);
while (!transfer_tx_is_done) {
/* Wait for transfer done */
if (SPI_done())
{
res_2_Byte.job_status=STATUS_OK;
break;
}
}
where SPI_done()
checks the register and transfer_tx_is_done
would be set by the interrupt (works sometimes [I said it is dirty])
Upvotes: 0