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

в 15:40, , рубрики: diy или сделай сам, микроконтроллер, программирование микроконтроллеров

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

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

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

Итак, мой очередной проект потребовал читать поток данных вида 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.

Автор: oam2oam

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js