Estoy simulando un proyecto donde dos STM32 se comunican entre sí a través de SPI (controladores HAL), donde el SPI Master es un STM32F4 y el SPI Slave es un STM32F3. Básicamente, el maestro enviará 5 bytes de datos (1 byte de comando seguido de 4 bytes ficticios) y el esclavo responderá con 4 bytes de datos al maestro según el primer byte de comando recibido.
Cómo debe operar el sistema : al presionar el botón de usuario en el maestro, se enviarán datos SPI (en modo sin interrupción) al esclavo (y el esclavo recibirá los datos en modo de interrupción). El esclavo luego envía datos de vuelta al maestro dependiendo del primer byte recibido. Hay un indicador LED azul en el dispositivo esclavo si se han recibido datos SPI y un indicador LED verde en el maestro que indica si recibe la secuencia correcta de datos del esclavo. A continuación se muestra cómo el maestro debe enviar/recibir los datos sincronizados:
Problema del dispositivo esclavo : durante las primeras pulsaciones de botón, el esclavo recibe correctamente los datos del maestro. Después de algunos ciclos, al observar los LED y establecer puntos de interrupción en el modo de depuración, noté que el esclavo comienza a recibir datos después de cada 2 pulsaciones de botón en el maestro. En el dispositivo esclavo, los datos recibidos son correctos. También debo tener en cuenta que la rutina de servicio de interrupción en el maestro se ejecuta cada vez que se presiona un botón, por lo que los datos SPI deben enviarse cada vez que se presiona un botón.
Problema del dispositivo maestro : el maestro nunca recibe la secuencia correcta de datos. Si, por ejemplo, el esclavo envía los datos: 0x01, 0x02, 0x03, 0x04 en ese orden, el maestro recibiría los datos en este orden: 0x02, 0x03, 0x04, 0x01. Además, esto también sucede cada 2 pulsaciones de botón.
Así es como se ve el evento:
Aquí está el código de configuración SPI para el Maestro:
static void MX_SPI1_Init (void) {
/* SPI Configuration for the STM32F4 (Master) */
/* APB2 Clock set to 8MHz to match APB2 clock of Slave*/
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
}
La configuración SPI para el Esclavo:
static void MX_SPI1_Init (void) {
/* SPI Configuration for the STM32F3 (Slave) */
/* APB2 Clock is 8MHz */
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_SLAVE;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
/* Set interrupt priority and enable interrupts for SPI1 */
HAL_NVIC_SetPriority(SPI1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
}
Aquí está el código relevante para el maestro (el segmento de interrupción del botón pulsador):
void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin) {
/* Clear wake-up power flag */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
if (GPIO_Pin == PUSH_BUTTON_PIN) {
/* Transmit one command byte located in pTxBuff array of length cTxLen */
HAL_SPI_Transmit(&hspi1, (uint8_t*)pTxBuff, cTxLen, spi_timeout);
/* Wait for end of SPI transmission */
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
/* Transmit dummy values located in pTempTxBuff and store retrieved relevant data in pRxBuff */
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)pTempTxBuff, (uint8_t*)pRxBuff, cRxLen, spi_timeout);
/* Flash LED if correct sequence stored in pRxBuff was received - cRxLen = 4 */
if((pRxBuff[0] == DATA0) && (pRxBuff[1] == DATA1) && (pRxBuff[2] == DATA2) && (pRxBuff[3] == DATA3)) {
/* Toggle dedicated green LED */
ToggleLED();
}
}
Y el código relevante para el Esclavo:
void HAL_SPI_RxCpltCallback (SPI_HandleTypeDef *hspi) {
/* Toggle blue LED when SPI receives data through interrupt */
ToggleLED();
/* Check the data received through SPI interrupt stored in pRxBuff[0] (array is of size 1) */
if (pRxBuff[0] == COMMAND_DATA) {
/**
* pTxBuff := contains appropriate data to be sent to Master device based on this specific command
* pTempRxBuff := array to store dummy variables, this array is of size 4
*/
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)pTxBuff, (uint8_t*)pTempRxBuff, cTxLen, spi_timeout);
}
/* Set device in SPI Receive interrupt mode again and store received data in pRxBuff of size 1 */
HAL_SPI_Receive_IT(&hspi1, (uint8_t*)pRxBuff, cRxCmdLen);
}
No entiendo dónde está el error aquí. Si solo el maestro envía los datos al dispositivo esclavo, todo funciona bien. También he hecho coincidir la configuración SPI y las velocidades de reloj del bus. También intenté cambiar la línea de dirección SPI_DIRECTION_1LINE
como se sugiere en otro lugar, pero no funcionó. También intenté usar HAL_SPI_Transmit()
y HAL_SPI_Receive()
por separado en lugar de HAL_SPI_TransmitReceive()
, pero siguen apareciendo los mismos problemas. Además, me gustaría usar HAL_SPI_TransmitReceive()
ambos para verificar en el Maestro y el Esclavo que los dispositivos se comunican correctamente. Cualquier ayuda sería muy apreciada.
¡Gracias!
En SPI, el MAESTRO y el Esclavo están realmente conectados en un bucle a los búfer FIFO. Como se muestra en la imagen.
Intente vaciar estos búferes de datos y registros de desplazamiento antes de cada transferencia de tramas de datos. En los controladores maestro y esclavo.
(Fuente de la imagen: Electronics Hub - Conceptos básicos de la interfaz periférica en serie (SPI) )
En general, recomiendo enfáticamente no usar HAL para el código de comunicación. Para SPI, UART e I2C, solo funciona realmente en el escenario de 'camino feliz' y, por lo general, no falla con elegancia (problemas permanentes de alineación de datos, etc.). También es muy inflado y lento, lo que puede crear problemas si intenta hacer cosas que requieren mucha velocidad (como analizar un marco SPI mientras lo recibe activamente, como es el caso aquí). El código generado por STM32Cube configura las funciones del controlador de interrupciones en el archivo stm32xx_it.c; puede comentar los controladores HAL generados y poner los suyos propios. ¡Recuerde borrar los indicadores de interrupción antes de que la función regrese!
Algunas ideas sobre lo que está fallando y algunos consejos a medida que continúa trabajando en este proyecto:
No estás usando la HAL correctamente. En realidad, es imposible hacer lo que quieres hacer correctamente usando HAL. Para que el esclavo responda con nuevos datos en función del primer byte recibido del maestro, debe tener un código para analizar el primer byte desde el propio controlador de interrupciones SPI, no desde la devolución de llamada. Las funciones de devolución de llamada HAL se llaman dentro del controlador de interrupciones SPI, después de que se haya recibido un marco completo (es decir, un marco del tamaño esperado con una transición nSS ascendente). Para implementar el sistema como se describe, deberá escribir su propio controlador de interrupciones SPI.
Como señaló Embedded Guy, los periféricos STM32 SPI tienen un búfer FIFO que se puede llenar con basura debido a errores de software (en el esclavo olado maestro), bordes de reloj espurios causados por ruido, etc. Desafortunadamente, no hay un comando explícito de 'borrar fifo' que se pueda emitir desde dentro del propio periférico. La siguiente idea obvia, usar los registros de datos RX/TX para vaciar el FIFO, tampoco es 100 % efectiva (aunque puede ser lo suficientemente buena en la mayoría de los casos). Por ejemplo, en una situación en la que ha recibido 5/8 bits de una palabra, la lectura del registro de datos RX no eliminará este byte incompleto del FIFO. La única forma consistentemente confiable que he encontrado para borrar los búferes SPI FIFO es usar el periférico RCC (controlador de reloj de reinicio) para restablecer completamente el periférico SPI y luego reiniciarlo rápidamente. Afortunadamente esto no es tan terrible como parece,
Consejo final: puede que no sea un buen diseño de sistema intentar que su esclavo analice el primer byte recibido mientras recibe activamente una trama SPI. Esto se debe simplemente a que SPI puede ejecutarse tan rápido que es posible que no haya terminado de analizar el primer byte y de preparar los bytes de respuesta para cuando el maestro haya terminado de cronometrar el primero (tiene un tiempo muymarco de tiempo limitado para hacerlo, ¡solo el ancho de una transición de reloj!). Si su reloj SPI es lento y tiene un analizador muy simple y rápido que se ejecuta dentro del controlador de interrupciones, probablemente pueda salirse con la suya con esta especificación. Sin embargo, podría ser mejor enviar el byte de "comando" en un marco SPI completo y luego emitir un marco de lectura de varios bytes por separado. En este escenario, el esclavo tendría mucho más tiempo para analizar el comando maestro antes de tener que preparar un mensaje de respuesta.
chris stratton
brahans
HAL_GPIO_EXTI_Callback
. Esa devolución de llamada se llama directamente desde el controlador de interrupciones EXTI, por lo que luego está bloqueando las llamadas SPI desde dentro de la interrupción EXTI. En lugar de eso, simplemente configure unavolatile
bandera desde EXTI y luego, en su ciclo principal, realice las operaciones SPI cuando vea que la bandera se configura.brahans
HAL_SPI_RxCpltCallback
el esclavo. No debería estar haciendo el bloqueoHAL_SPI_TransmitReceive
desde dentro de una devolución de llamada (que en realidad todavía está dentro de la interrupción). O use otravolatile
bandera allí, o hagaHAL_SPI_TransmitReceive_IT
su lugar y haga suHAL_SPI_RxCpltCallback
estado para que recuerde si lo último fueHAL_SPI_TransmitReceive_IT
por 4 bytes oHAL_SPI_Receive_IT
por 1 byte.miguel karas
Cimory
Cimory
Cimory
Cimory
HAL_SPI_TransmitReceive_IT
y agregué estados en laHAL_SPI_RxCpltCallback
función. Sin embargo,HAL_SPI_TransmitReceive_IT
no parece llamarHAL_SPI_RxCpltCallback
cuando solicita los 4 bytes ficticios del maestro (aunque la interrupción funcionó, ya que recibió los 4 bytes ficticios correctos del maestro y los almacenó en una matriz). ¿Alguna razón por la que esto podría ser el caso?Cimory
HAL_SPI_TransmitReceive_IT
conHAL_SPI_TxRxCpltCallback
yHAL_SPI_TxCpltCallback
pero ninguna de las funciones se activó después deHAL_SPI_TransmitReceive_IT
procesarse con éxito.