Как передать данные между микроконтроллерами на 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, вот и приходится работать с таймерами. Может, кто-нибудь подскажет другой способ?


ссылка на оригинал статьи https://habr.com/ru/post/460805/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *