Como transferir dados entre microcontroladores a 100 Mbps

Tive esse tipo de problema: preciso transferir dados entre dois microcontroladores STM32F407 pelo menos a uma velocidade de 100 Mbps. Seria possível usar Ethernet (MAC para MAC), mas o problema é que ele está ocupado, é a partir desses dados que eles são capturados ...
Na periferia ociosa, talvez exista apenas SPI - mas são apenas 42 Mbps.

Curiosamente, nada pronto foi encontrado na rede. E eu decidi implementar um registro de relógio paralelo de 8 bits. E o que - a frequência pode ser configurada para 10 MHz (ou seja, o relógio em si é duas vezes mais rápido, mas 20 MHz não é algo complicado) -, portanto, com uma frequência tão baixa que você não precisa se preocupar com a fiação da placa. E a velocidade será de 100 Mbps.

Mal disse o que fez. Em geral, o sistema se parece com isso. Usamos um temporizador no lado de transmissão, um dos sinais de comparação é emitido para um pino - este será um sinal de relógio e o segundo será usado para iniciar uma rajada para DMA.

Eu tenho um barramento em 82 MHz (devido ao consumo atual em uma frequência mais alta :), um temporizador na mesma frequência: para que, com um período de ARR = 8, ocorra cerca de 10 MHz (portanto, serão cerca de 80 Mbps, tudo bem).

O DMA transferirá um byte da memória (com incremento automático, é claro) diretamente para a porta de saída do registro - no meu caso, o PORTE surgiu - seus primeiros 8 bits se encaixam como o endereço do receptor DMA.

No lado do recebimento, usaremos um sinal de relógio nas duas extremidades para registrar o relógio, com um período de 1, e usaremos o sinal de atualização para iniciar o encaminhamento para o DMA, que lê dados da porta (a porta PORTE se aproximou novamente) e grava na memória com auto incremento.

Agora resta configurar tudo corretamente (código abaixo) e executar. A rescisão dos dois lados é determinada pela interrupção do DMA.

No entanto, para ser completo, é claro que você precisa incluir verificações de atrasos na transmissão e tratamento de erros no código, mas eu o omito.

No código abaixo, o timer TIM8 usa o canal CC2 para emitir o sinal - para ver o que acontece.

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

Para os testes, a mesma placa foi usada, apenas a saída do relógio PE9 foi conectada à entrada PC6. O loop principal ficou assim:

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

De acordo com os resultados: os dados foram enviados perfeitamente por 30 a 31 microssegundos sem perda. Os sinais são mais ou menos assim:


aqui, branco é a saída do timer TIM8, vermelho é o sinal do relógio (TIM1) e laranja é o bit de dados menos significativo (0-1-0-1 -...).

O que eu não gosto sobre isso é que você não pode iniciar o DMA de interromper a entrada GPIO de forma alguma, então você precisa trabalhar com temporizadores. Talvez alguém lhe diga outra maneira?

PS Como resultado de novas experiências, descobriu-se que aumentar a frequência para 168 MHz naturalmente aumentou a velocidade em 2 vezes e os dados foram transmitidos em 14 microssegundos (ou seja, 150 Mbps), mas quando o timer principal foi reduzido para menos de 7, o lado receptor começou a apresentar falhas - o timer não tem tempo TIM8. Aos 7 ainda funciona, mas aos 6 já se foi, e afinal seria 200 Mbps ...

Source: https://habr.com/ru/post/pt460805/


All Articles