Tengo este tipo de problema: necesito transferir datos entre dos microcontroladores STM32F407 al menos a una velocidad de 100 Mbps. Sería posible usar Ethernet (MAC-a-MAC), pero el problema es que está ocupado, es de estos datos que se toman ...
Desde la periferia inactiva, tal vez solo haya SPI, pero solo 42 Mbps.
Por extraño que parezca, no se encontró nada listo en la red. Y decidí implementar un registro de reloj paralelo de 8 bits. Y qué: la frecuencia se puede establecer en 10 MHz (es decir, por supuesto, el reloj en sí es el doble de rápido, pero 20 MHz no es algo complicado), por lo que con una frecuencia tan baja no tendrá que preocuparse por el cableado de la placa. Y la velocidad será de 100 Mbps.
Apenas dicho que hecho. En general, el sistema se ve así. Usamos un temporizador en el lado de transmisión, una de las señales de comparación se envía a un pin; esta será una señal de reloj y la segunda se usará para iniciar una ráfaga para DMA.
Tengo un bus a 82 MHz (debido al consumo de corriente a una frecuencia más alta :), un temporizador a la misma frecuencia: para que con un período de ARR = 8 resulte aproximadamente 10 MHz (entonces será de aproximadamente 80 Mbps, bueno, está bien).
DMA transferirá un byte de la memoria (con incremento automático, por supuesto) directamente al puerto de salida del registro, en mi caso, apareció PORTE, sus primeros 8 bits encajan como la dirección del receptor DMA.
En el lado receptor, usaremos la señal de reloj en ambos bordes para cronometrar el temporizador, con un período de 1, y usaremos la señal de actualización para comenzar a reenviar el DMA, que lee los datos del puerto (el puerto PORTE se acercó nuevamente) y escribe en la memoria con incremento automático.
Ahora queda configurar todo correctamente (código a continuación) y ejecutar. La terminación en ambos lados está determinada por la interrupción del DMA.
Sin embargo, para completar, por supuesto, debe incluir comprobaciones de retrasos en la transmisión y manejo de errores en el código, pero omito esto.
En el siguiente código, el temporizador TIM8 usa el canal CC2 para emitir la señal, para ver qué sucede.
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 las pruebas, se utilizó la misma placa, solo la salida del reloj PE9 se conectó a la entrada PC6. El bucle principal se veía así:
ii_receive(rdata, 256); ii_transmit(tdata, 256); while (!transmit_done); while (!receive_done);
Según los resultados: los datos se enviaron perfectamente durante 30-31 microsegundos sin pérdida. Las señales se parecen a esto:
aquí, el blanco es la salida del temporizador TIM8, el rojo es la señal del reloj (TIM1) y el naranja es el bit de datos menos significativo (0-1-0-1 -...).
Lo que no me gusta de esto es que no puede iniciar DMA por la interrupción de la entrada GPIO, tiene que trabajar con temporizadores. Tal vez alguien te dirá otra manera?
PD Como resultado de nuevos experimentos, resultó que elevar la frecuencia a 168 MHz naturalmente aumentó la velocidad en 2 veces y los datos se transmitieron en 14 microsegundos (es decir, 150 Mbps), pero cuando el temporizador maestro se redujo por debajo de 7, el lado receptor comenzó a fallar: el temporizador no tiene tiempo TIM8. A las 7 todavía funciona, pero a las 6 ya se ha ido, y después de todo sería 200 Mbps ...