Estoy tratando de configurar un esclavo STM32F303RE SPI2 que debe enviar contenido de forma continua y repetida de un búfer de 2 bytes mediante DMA.
Más específicamente, si mi búfer es:
#define ALIGN(x) __attribute__((aligned(x)))
ALIGN(4) uint8_t TxBuffer[2] = { 'A', 'B' };
entonces quiero que mi placa STM se comporte de la siguiente manera:
Como soy un principiante en esto, decidí comenzar con una implementación simple y desarrollarla en el camino. Por eso estoy usando DMA sin interrupciones. Así es como se ve actualmente el código (relevante):
/* TX & RX buffers for SPI. */
ALIGN(4) uint8_t TxBuffer[2];
ALIGN(4) uint8_t RxBuffer[2]; /* Dummy, not actually used. */
int main(void)
{
SPI_Config();
SysTickConfig();
RxBuffer[0] = (RxBuffer[1] = 0);
TxBuffer[0] = 'A';
TxBuffer[1] = 'B';
while (1)
{
/* Clear DMA1 global flags */
DMA_ClearFlag(DMA1_FLAG_GL4);
DMA_ClearFlag(DMA1_FLAG_GL5);
/* Disable the DMA channels */
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
/* Disable the SPI peripheral */
SPI_Cmd(SPI2, DISABLE);
/* Disable the SPI Rx and Tx DMA requests */
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, DISABLE);
DMA1_Channel4->CNDTR = (DMA1_Channel5->CNDTR = 2);
DMA1_Channel4->CPAR = (uint32_t) &SPI2->DR;
DMA1_Channel5->CPAR = (uint32_t) &SPI2->DR;
DMA1_Channel4->CMAR = (uint32_t) &RxBuffer[0];
DMA1_Channel5->CMAR = (uint32_t) &TxBuffer[0];
/* Enable the SPI Rx and Tx DMA requests */
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
/* Enable the SPI peripheral */
SPI_Cmd(SPI2, ENABLE);
DMA_Cmd(DMA1_Channel4, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);
/* Wait the SPI DMA transfers complete */
while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET) {}
while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET) {}
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) {}
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == SET) {}
// Here RxBuffer data can be inspected
}
}
static void SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable SCK, MOSI, MISO and NSS GPIO clocks */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB , ENABLE);
/* SPI pin mappings */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_5); // SPI2_NSS
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_5); // SPI2_SCK
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_5); // SPI2_MISO
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_5); // SPI2_MOSI
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* SPI SCK pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* SPI MOSI pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* SPI MISO pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* SPI NSS pin configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Enable the SPI peripheral */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
/* SPI configuration -------------------------------------------------------*/
SPI_I2S_DeInit(SPI2);
SPI_StructInit(&SPI_InitStructure);
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_CalculateCRC(SPI2, DISABLE);
SPI_TIModeCmd(SPI2, DISABLE);
SPI_NSSPulseModeCmd(SPI2, DISABLE);
/*
* SPI_I2S_FLAG_RXNE flag should be set as soon as 1 byte (quarter buffer)
* is shifted into receiving FIFO.
*/
SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);
/* Enable the DMA peripheral */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* DMA Configuration -------------------------------------------------------*/
DMA_DeInit(DMA1_Channel4);
DMA_DeInit(DMA1_Channel5);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &SPI2->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_BufferSize = 0;
DMA_InitStructure.DMA_MemoryBaseAddr = 0;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
}
Esto funciona bien si el maestro siempre envía un número par de bytes por selección de chip (pin NSS). Si el maestro solo envía un byte @ en algún punto (dentro de una sola selección de chip), las cosas comienzan a complicarse.
He aquí un escenario concreto:
¿Qué debo hacer para lograr esto? Noté que " while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
" nunca termina cuando el maestro envía solo un byte, por lo que supongo que detrás de escena, el DMA simplemente siempre espera 2 bytes por transferencia (es decir, antes de configurar TC), independientemente del estado de NSS (chip -seleccionar).
De alguna manera, quiero forzar la finalización de DMA cuando NSS esté alto nuevamente (es decir, cuando el esclavo SPI ya no esté seleccionado por chip).
Después de leer detenidamente los capítulos de SPI y DMA, la conclusión clara es que el periférico SPI (o el DMA) no proporciona indicadores/cambios de orientación de comportamiento de selección de chip (pin NSS). Pero eso tiene sentido ya que el pin NSS también es uno de los GPIO y podemos recuperar su estado mediante esa interfaz.
Entonces, lo logré hace unos días simplemente...
Configuración de una interrupción al aumentar NSS (PB12): NSS aumenta después de una transacción, es decir, cuando el esclavo ya no está seleccionado por chip
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource12);
EXTI_InitStruct.EXTI_Line = EXTI_Line12;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStruct);
/* 4 bits Preemptive priority, 4 bits Sub-priority. */
NVIC_SetPriorityGrouping(3);
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 15;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 15;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
Restablecer SPI2 (no hay otra forma de borrar TXFIFO...) y rebobinar el canal DMA al inicio del búfer cuando se activa la interrupción
void EXTI15_10_IRQHandler(void)
{
EXTI_ClearITPendingBit(EXTI_Line12);
/* Clear DMA1 global flags */
DMA_ClearFlag(DMA1_FLAG_GL4);
DMA_ClearFlag(DMA1_FLAG_GL5);
/* Disable the DMA channels */
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
/*
* Bring back SPI2 DMAs to start of Rx & Tx buffers -
* CPAR/CMAR stay the same after disable, no need to
* `restore` those.
*/
DMA1_Channel4->CNDTR = (DMA1_Channel5->CNDTR = 2);
/* Reset SPI2 (clears TXFIFO). */
RCC->APB1RSTR |= RCC_APB1RSTR_SPI2RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_SPI2RST;
/* Reconfigure SPI2. */
SPI_Init(SPI2, &SPI_InitStructure);
SPI_CalculateCRC(SPI2, DISABLE);
SPI_TIModeCmd(SPI2, DISABLE);
SPI_NSSPulseModeCmd(SPI2, DISABLE);
/* Re-enable SPI2 and DMA channels. */
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, ENABLE);
DMA_Cmd(DMA1_Channel4, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
SPI_Cmd(SPI2, ENABLE);
}
Cambiar
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
a
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
para los N>2 casos.
Al principio, me preocupaba que deshabilitar y reconfigurar por completo SPI2 llevaría demasiado tiempo en EXTI15_10_IRQHandler, pero logré que se ejecutara en 2.6us (desde inicialmente 16us con -O3 ) haciendo un simple cambio en la biblioteca StdPeriph:
hizo todas las funciones llamadas desde el controlador " static inline " (como deberían haber sido en primer lugar). Con ese cambio -O3 se vuelve mucho más útil.
No entiendo completamente, pero creo que hay un defecto en su concepción. Una transferencia DMA se usa para transmitir un gran bloque de datos, y no para esperar un comando determinado y luego responder; esto se hace mediante interrupciones. Una recepción DMA está diseñada para escuchar continuamente un flujo de datos desde un dispositivo, seguramente sin esperar un solo byte. Por lo tanto, no puede recibir un paquete de datos con una longitud diferente, ya que el búfer DMA debe estar lo suficientemente lleno para activar una interrupción.
EDITAR: Eché un vistazo a tu código y no funciona. Esperar en bucle sin fin el DMA es imposible. Cuando el búfer de recepción está lleno, debe activar una interrupción: una función de devolución de llamada, este es el lugar donde debe evaluar el búfer de recepción. Pero como se dijo, olvídese de recibir DMA. El beneficio de DMA es hacer otras cosas cuando DMA se encarga de enviar/recibir, seguramente no está hecho para esperar en un ciclo sin fin para lograrlo.
Supongo que estaba usando una operación sin CS/NSS para poder usar el pin NSS como interrupción (EXTI).
En mi caso necesitaba un hardware NSS. Envié la señal NSS tanto al pin NSS como a un EXTI GPIO. Luego usé esta interrupción para reiniciar el DMA SPI de esta manera:
HAL_SPI_Abort(psHandleSlave->psPeripheral);
__HAL_RCC_SPI2_FORCE_RESET();
__HAL_RCC_SPI2_RELEASE_RESET();
Las líneas 2 y 3 hacen exactamente lo mismo que:
RCC->APB1RSTR |= RCC_APB1RSTR_SPI2RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_SPI2RST;
... pero son proporcionados por HAL.
zuzu cornelio
zuzu cornelio
Ber