Как микроконтроллер может читать данные на скорости 1.6 Gbps

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Всем доброго времени суток! никогда такого не было и вот опять. С моей прошлой статьи прошло уже достаточно времени, а оно ставит новые задачи. И если раньше я передавал данные на скорости 100 Mbps, то теперь пришлось замахнутся на 1600 Mbps…

На КПДВ — герой нашего романа — он смог читать данные на такой скорости!





Итак, мой очередной проект потребовал читать поток данных вида 32-бита на скорости 50 МГц (это и будет, кстати, тот самый 1.6 Gbps) в количестве заранее известном — пусть будет 10000. Было бы просто отлично читать сразу с помощью DMA из одного порта — но вот, к сожалению, подходящих процессоров не нашлось (надеюсь, в комментариях это дело кто-нибудь поправит), у всех подходящих по скорости порты почему-то 16-битные.

Но такая мелочь нас не остановит — будем читать из двух портов сразу! Правда, это в общем случае не всегда удастся делать с нужной степенью контроля и синхронности, но в нашем случае все не так печально — есть сигнал, после которого 20 ns данные будут на порту держаться.

А так как процессор у нас — stm32h750 на 400 Мгц, а шина и таймеры на 200 Мгц, то все должно получиться.

Казалось бы простой случай — по сигналу запускать одну пересылку DMA. Но вот только у DMA нет такой возможности — порт может выдать прерывание, но не может управлять DMA. Но зато у нашего процессора есть хорошая штука — DMAMUX, в котором есть генератор событий для канала DMA, а вот в этом генераторе есть две подходящие возможности — или использовать прерывание EXTIT0 или сигнал от таймера TIM12 (такая вот странная фантазия была у разработчиков чипа).

По прерыванию мы не успеем по времени — на даже пустую отработку нужно порядка 47 тактов, а такт у нас — 2.5 ns…

Зато успеем по таймеру. Осталось только затактировать таймер от внешнего сигнала в 100 МГц, причем длину таймера ставим в 1 и по его выходу TRGO будет срабатывать генератор DMAMUX, а уж он-то задаст команду на пересылку DMA и тот прочитает порт и перешлет данные в память.

Но стоп! Порт-то 16-бит, а у нас 32… Что же, можно попробовать читать еще второй порт… Только вот для этого нужен второй канал DMA, и еще это займет ту же шину — то есть прочитать-то мы успеем, но вот можем не успеть записать данные в память. Что-же, теоретически, у этого процессора есть разные типы памяти, и на большом рисунке структуры процессора можно видеть, что и DMA и память RAM_D1 сидят на одной и той-же шине с частотой 200 МГц. Осталось проверить практически.

DMA1->LIFCR |= ~0;
DMA1_Stream0->CR = (0b11 << DMA_SxCR_PL_Pos) | (0b01 << DMA_SxCR_MSIZE_Pos)
    | (0b01 << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC;
DMA1_Stream0->M0AR = (uint32_t) data;
DMA1_Stream0->PAR = (uint32_t) &(GPIOE->IDR);
DMA1_Stream0->NDTR = 10000;
DMA1_Stream1->CR = (0b11 << DMA_SxCR_PL_Pos) | (0b01 << DMA_SxCR_MSIZE_Pos)
    | (0b01 << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC;
DMA1_Stream1->M0AR = (uint32_t) data2;
DMA1_Stream1->PAR = (uint32_t) &(GPIOD->IDR);
DMA1_Stream1->NDTR = 10000;
DMAMUX1_Channel0->CCR = DMAMUX_CxCR_EGE | (1);
DMAMUX1_Channel1->CCR = DMAMUX_CxCR_EGE | (2);
DMAMUX1_RequestGenerator0->RGCR = DMAMUX_RGxCR_GE
    | (0b01 << DMAMUX_RGxCR_GPOL_Pos) | (7);
DMAMUX1_RequestGenerator1->RGCR = DMAMUX_RGxCR_GE
    | (0b01 << DMAMUX_RGxCR_GPOL_Pos) | (7);
DMA1_Stream0->CR |= DMA_SxCR_EN;
DMA1_Stream1->CR |= DMA_SxCR_EN;
TIM12->CNT = 0;
TIM12->CCMR1 |= TIM_CCMR1_CC2S_0;
TIM12->CR2 = (0b010 << TIM_CR2_MMS_Pos);
TIM12->CR1 |= TIM_CR1_CEN;
while (DMA1_Stream0->NDTR)
    i++;
TIM12->CR1 &= ~TIM_CR1_CEN;


Ну и конечно, надо разместить массивы data и data2 в нужном сегменте памяти, это делается так:

__attribute__((section(".dma_buffer"))) uint16_t data[10240],data2[10240];


а в файле для линковщика указываем:

.dma_buffer : {
 *(.dma_buffer)
 } >RAM_D1


Для проверки, ну и как первый вариант, был реализован просто тупое копирование с помощью
центрального процессора (все же 400 МГц):

       uint16_t * ptr = cpudata;
        volatile uint16_t * src = &(GPIOE->IDR);
        volatile uint16_t * src2 = &(GPIOD->IDR);
        for (register int i = 0; i < 10000; i++) {
                *ptr++ = *src;
                *ptr++ = *src2;
        }


Для проверки данные cpudata размещались в разной памяти, быстрее всего подошла (ну она, правда, всего 64К) сверхбыстрая память (тоже 400 МГц) DTCMRAM.

Результаты



В процессе испытаний выяснилось, что с помощью CPU можно читать на скорости 12.5 МГц из двух портов. И 25 МГц из одного. Так что вариант не работает…

С помощью DMA и такой-то матери TIM12 удалось успешно читать на скорости 50 МГц, причем за несколько часов теста ошибок не было ни одной. Читались оба порта, но замерить, насколько отстает чтение по второму DMA, пока не удалось…

Так что в моем (немного вырожденном) случае, удалось достигнуть скорости передачи информации в процессор stm32h750 на скорости 32x50 = 1600 Mbps.
Источник: https://habr.com/ru/post/467459/


Интересные статьи

Интересные статьи

Мы в Яндекс.Такси любим писать логи. Ещё больше мы любим, когда логи помогают нам расследовать проблемы в продакшене. При нагрузке в десятки тысяч RPS просто набора лог-записей мало. Хо...
Бизнес-смыслы появились в Битриксе в начале 2016 года, но мало кто понимает, как их правильно использовать для удобной настройки интернет-магазинов.
В MIT разработали архитектуру нового фотонного процессора. Он повысит эффективность работы оптических нейросетей в тысячу раз, по сравнению с аналогичными устройствами. Чип сократит объемы эл...
Несколько дней назад со мной произошло ровно то, что написано в заголовке. В далеком 2014 году (а именно 28 декабря в 17:00) мы с женой и друзьями играли в перформанс-квест «Коллекционер» от «Кла...
Довольно часто владельцы сайтов просят поставить на свои проекты индикаторы курсов валют и их динамику. Можно воспользоваться готовыми информерами, но они не всегда позволяют должным образом настроить...