...

воскресенье, 21 июля 2019 г.

Как передать данные между микроконтроллерами на 100 Mbps

Встала передо мной такая вот проблема — надо передавать данные между двумя микроконтроллерами STM32F407 хотя бы на скорости 100 Mbps. Можно было бы использовать Ethernet (MAC-to-MAC), но вот беда — он занят, именно из него и берутся эти данные…
Из незадействованной периферии есть разве что SPI — но он только 42 Mbps.

Как ни странно, ничего готового в сети не нашлось. И я решил реализовать параллельный тактируемый регистр на 8 бит. А что — частоту можно задать в 10 Мгц (то есть, конечно, собственно такты вдвое быстрее, но и 20 Мгц не есть что-то сложное) — так что с такой невысокой частотой не придется мучиться с разводкой платы. А скорость как раз и будет 100 Mbps.

Сказано — сделано. В общем виде система выглядит так. На передающей стороне используем таймер, один из сигналов сравнения выводим на пин — это будет тактовый сигнал, а второй будем использовать для запуска одной пересылки (burst) для DMA.
Шина у меня на частоте 82 МГц (из-за потребления тока на большей частоте :), таймер на той же частоте: так что при периоде ARR = 8 получается 10 Мгц примерно (стало быть будет около 80 Mbps, ну да и ладно).

DMA будет по такту пересылать один байт из памяти (с автоинкрементом, конечно) прямо в порт вывода регистра — в моем случае подошел PORTE — его первые 8 бит как раз и подходят как адрес приемника DMA.

На приемной стороне тактовый сигнал будем по обоим перепадам использовать для тактирования таймера, с периодом 1, а сигнал update будем использовать для запуска пересылки для DMA, который читает данные из порта (опять подошел порт PORTE) и записывает в память с автоинкрементом.

Теперь осталось правильно настроить все (код ниже) и запустить. Завершение на обеих сторонах определяется по прерыванию от DMA.

Однако, для полноты картины нужно конечно включить в код проверки на задержки передачи и обработку ошибок, но это я опускаю.

В коде ниже у таймера TIM8 использован канал CC2 для вывода сигнала — чтобы посмотреть, что получается.

volatile int transmit_done;
volatile int receive_done;

void DMA2_Stream1_IRQHandler(void) {
    TIM8->CR1 &= ~TIM_CR1_CEN;
    DMA2->LIFCR |= 0b1111 << 8;
    receive_done = 1;
}

void DMA2_Stream4_IRQHandler(void) {
    TIM1->CR1 &= ~TIM_CR1_CEN;
    TIM1->EGR |= TIM_EGR_BG;
    DMA2->HIFCR |= 0b1111101;
    transmit_done = 1;
}

void ii_receive(uint8_t *data, int len) {
    GPIOE->MODER = (GPIOE->MODER & 0xFFFF0000) | 0x0000;
    DMA2_Stream1->PAR = (uint32_t) &(GPIOE->IDR);
    DMA2_Stream1->M0AR = (uint32_t) data;
    DMA2_Stream1->NDTR = len;
    TIM8->CNT = 0;
    TIM8->BDTR |= TIM_BDTR_MOE;
    receive_done = 0;
    DMA2_Stream1->CR |= DMA_SxCR_EN;
    TIM8->CR1 |= TIM_CR1_CEN;
}

void ii_transmit(uint8_t *data, int len) {
    GPIOE->MODER = (GPIOE->MODER & 0xFFFF0000) | 0x5555;
    DMA2_Stream4->PAR = (uint32_t) &(GPIOE->ODR);
    DMA2_Stream4->M0AR = (uint32_t) data;
    DMA2_Stream4->NDTR = len;
    TIM1->CNT = 6;
    transmit_done = 0;
    DMA2_Stream4->CR |= DMA_SxCR_EN;
    TIM1->SR |= TIM_SR_BIF;
    TIM1->BDTR |= TIM_BDTR_MOE;
    TIM1->CR1 |= TIM_CR1_CEN;
}

// tx: TIM1 CH4 on DMA2/stream4/channel6, CH1 on output clock in PE9
// rx: TIM8 CH2 on DMA2/stream3/channel7, CH1 on input clock in PC6
void ii_init() {
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_TIM1_CLK_ENABLE();
    __HAL_RCC_TIM8_CLK_ENABLE();
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();
    GPIOC->MODER |= (0b10 << GPIO_MODER_MODE6_Pos)
            | (0b10 << GPIO_MODER_MODE7_Pos);
    GPIOC->PUPDR |= (0b10 << GPIO_PUPDR_PUPD7_Pos);
    GPIOC->AFR[0] |= (GPIO_AF3_TIM8 << 24) | (GPIO_AF3_TIM8 << 28);
    GPIOE->MODER |= (0b10 << GPIO_MODER_MODE9_Pos);
    GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9 | 0xFFFF;
    GPIOE->AFR[1] |= GPIO_AF1_TIM1 << 4;
    GPIOE->PUPDR |= (0b10 << GPIO_PUPDR_PUPD9_Pos);
    TIM1->ARR = 8;
    TIM1->CCR1 = 5;
    TIM1->CCR4 = 1;
    TIM1->EGR |= TIM_EGR_CC4G;
    TIM1->DIER |= TIM_DIER_CC4DE;
    TIM1->CCMR1 |= (0b110 << TIM_CCMR1_OC1M_Pos);
    TIM1->CCER |= TIM_CCER_CC1E;
    TIM1->EGR |= TIM_EGR_BG;
    TIM8->ARR = 1;
    TIM8->CCR2 = 1;
    TIM8->EGR |= TIM_EGR_UG;
    TIM8->DIER |= TIM_DIER_UDE;
    TIM8->SMCR |= (0b100 << TIM_SMCR_TS_Pos) | (0b111 << TIM_SMCR_SMS_Pos);
    TIM8->CCMR1 = (0b01 << TIM_CCMR1_CC1S_Pos) | (0b110 << TIM_CCMR1_OC2M_Pos);
    TIM8->CCER |= (0b11 << TIM_CCER_CC1P_Pos) | TIM_CCER_CC2E;
    DMA2_Stream1->CR = DMA_CHANNEL_7 | DMA_PRIORITY_VERY_HIGH | DMA_MINC_ENABLE
            | (0b00 << DMA_SxCR_DIR_Pos) | DMA_SxCR_TCIE | DMA_SxCR_TEIE
            | DMA_SxCR_DMEIE;
    DMA2_Stream1->FCR |= DMA_FIFOMODE_ENABLE;
    DMA2_Stream4->CR = DMA_CHANNEL_6 | DMA_PRIORITY_VERY_HIGH | DMA_MINC_ENABLE
            | (0b01 << DMA_SxCR_DIR_Pos) | DMA_SxCR_TCIE | DMA_SxCR_TEIE
            | DMA_SxCR_DMEIE;
    DMA2_Stream4->FCR |= DMA_FIFOMODE_ENABLE;
    HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
    HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}

Для тестов была использована одна и та же плата, просто тактовый выход PE9 был соединен с входом PC6. Главный цикл выглядел так:

 ii_receive(rdata, 256);
 ii_transmit(tdata, 256);
 while (!transmit_done);
 while (!receive_done);

По результатам: данные отлично пересылались за 30-31 микросекунду без потерь. Сигналы выглядят как-то так:


здесь белый — выход таймера TIM8, красный — тактовый сигнал (TIM1), ну а оранжевый — это младший бит данных (0-1-0-1-...).

Что не нравится при этом — ну нельзя никак запускать DMA от прерывания от входа GPIO, вот и приходится работать с таймерами. Может, кто-нибудь подскажет другой способ?

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий