Atmel SAM D21 DMA завис в состоянии занятости при использовании USB

Я использую SAMD21 xPlained pro с прототипом для получения сигнала, используя внутренний АЦП и DMAC, которые настроены на сбор 5500 выборок. Затем образцы передаются в приложение для ПК через целевой USB.

Прошивка, которую я написал для этого, работает, но я заметил, что время от времени DMA зависает в состоянии занятости. Чтобы отладить это, я отменил передачу части на ПК через USB.

Я заметил, что DMA работает нормально и передает сэмплы в память, но если я подключаю кабель USB к ПК (без передачи данных через USB), время от времени DMA зависает; но как только я отсоединяю (целевой) USB-кабель, DMA работает непрерывно, не застревая в состоянии занятости.

Я подозреваю, что это что-то с прерыванием и приоритетами USB и АЦП, которые используют DMA. Я подумал, что должен установить выборку АЦП с наивысшим приоритетом, чтобы USB не приводил к зависанию DMA в состоянии занятости, но я не смог найти, как это настроить в коде (я использую ASF).

Любая идея, почему подключение USB приводит к тому, что DMA иногда зависает в состоянии занятости? Должна ли эта проблема относиться к приоритетам, как я подозреваю, есть идеи, как уменьшить приоритет прерывания USB?

код:

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);
}

person Modi    schedule 13.03.2017    source источник
comment
Каков порядок приоритета (например, ADC — 0, USB — 1, DMA — 2 и т. д.)? Кроме того, код для обратных вызовов DMA и USB? У меня был опыт работы с DMA и USB для другого чипа Atmel, и я должен сказать... Документация Atmel не самая лучшая. Попробуйте также спросить на www.at91.com   -  person yun    schedule 13.03.2017
comment
Что касается USB, я использую пример ASF для USB. Я не уверен, где они устанавливают уровень приоритета прерывания USB. Обратный вызов DMA является базовым — просто очистите флаг и несколько операций ввода-вывода. Остальное сделает функция конечного автомата, которая вызывается непрерывно. Я добавил исходный код к вопросу   -  person Modi    schedule 13.03.2017
comment
Найдите этот метод в своем проекте: NVIC_SetPriority   -  person yun    schedule 13.03.2017
comment
только core_cm0plus вызывает этот метод: __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { .... NVIC_SetPriority (SysTick_IRQn, (1‹‹__NVIC_PRIO_BITS) - 1); /* установить приоритет для системного прерывания / ... return (0); // Функция выполнена успешно */ }   -  person Modi    schedule 13.03.2017


Ответы (2)


Я столкнулся с очень похожей проблемой. Мой SPI работает нормально, хотя я подключил USB, но начал с этой проблемы, когда запускаю его с помощью определенной команды USB-CDC. Я работаю с asf 3.34 на as 7 и L21 (очень похоже). Мой обходной путь, который не является чистым, но работает: после запуска передачи DMA я непрерывно (в то время как цикл) проверяю бит выполненной передачи (REG_DMAC_CHINTFLAG должен быть 0x2), и когда это так, я устанавливаю статус в ok. В коде:

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;
    }
}

где SPI_done() проверяет регистр, а transfer_tx_is_done будет установлено прерыванием (иногда работает [я сказал, что это грязно])

person 7be    schedule 27.03.2017
comment
Спасибо, но этот обходной путь не работает для меня. Я также устанавливаю флаг transfer_is_done обработчиком прерывания. Однако, когда DMA застревает в состоянии занятости, он не вызывает обработчик прерывания, поэтому флаг не устанавливается. - person Modi; 28.03.2017
comment
когда установлен бит завершения передачи (REG_DMAC_CHINTFLAG&0x02==true), вы можете установить состояние ok вручную dma_resource.job_status=STATUS_OK;, так как прерывание будет заботиться об этом - person 7be; 29.03.2017

Я не эксперт в USB, и это, возможно, не связано с вашим случаем, но у меня также были проблемы с USB-CDC и периодические зависания на чипах UC3A3. Я обнаружил две отдельные причины:

  1. Прерывания USB и задачи должны иметь достаточно свободного времени MCU

    Я устанавливаю тестовые биты при входе и выходе каждого полученного прерывания (включая USB), и если они слишком близки, чтобы перекрываться, начинают происходить странные вещи, такие как зависание, дрожание выходного сигнала (намного больше, чем все < strong>ISR вместе), синхронизировать и подтверждать ошибки и т. д., даже если USB имеет наивысший приоритет. Если я переустанавливаю время всех вещей, которые я использую, чтобы прерывания срабатывали не в одно и то же время, все работает хорошо.

    Остерегайтесь переключения GPIO — это медленная операция, поэтому вы также должны принять это во внимание.

  2. Каждая версия Host OS (Windows) имеет разное время

    Я использую свой MCU для полнодуплексных синхронных массовых передач и получил 3 уровня FIFO (2 на хосте и 1 на MCU), чтобы поддерживать синхронизация (непрерывно 24/7 ~640KByte/s вход и ~640KByte/s выход). С каждой новой версией Windows (w2k -> Xp --> w7) что-то менялось либо в планировании потоков, либо в службах драйверов, и требовалось переназначить время передачи и тайм-ауты, поэтому многопоточность без блокировки для синхронной передачи USB в реальном времени по-прежнему работает. как следует.

    Последнее, что я обнаружил в W7 (они добавили новую «функцию»), это то, что некоторые контроллеры USB (например, Intel) на стороне хоста либо зависают сами по себе, либо имеют разные приоритеты передачи данных (либо на основе конвейера). или направление) и соотношение отправки/получения 1:1 больше не работает на некоторых машинах, вызывая зависание на стороне MCU от 1ms до нескольких секунд из-за заблокированного FIFO с. Обходной путь для этого состоит в том, чтобы полностью заполнить MCU, получая FIFO (или увеличить размер MCU FIFO, что в моем случае невозможно, так как это требует уже почти вся память) что нужно сделать в любом случае, но в моем случае я работаю в RT и у меня не так много пакетов впереди, поэтому я узнаю, что мне нужно отправить как минимум в 3 раза больше пакетов, чем получить, пока не FIFO полностью заполняется с течением времени (и каждый раз, когда отправка хоста зависает, что для некоторых машин происходит постоянно), просто чтобы не потерять синхронизацию и весь механизм FIFO наполовину заполнен. на этих машинах уже не работают.

Таким образом, если ваша USB передача синхронизирована с хостом или наоборот, стоит проверить другую ОС (например, Xp), если проблема тоже есть. Если нет, есть большая вероятность, что у вас возникли проблемы, с которыми я имел дело, поэтому вы можете попробовать обходные пути...

person Spektre    schedule 05.04.2017