Maestro SPI dúplex completo con DMA - STM32F105

Estoy tratando de escribir un controlador SPI para un STM32F105 usando la funcionalidad DMA. Estoy usando la biblioteca de periféricos estándar de ST. No estoy usando interrupciones. Puedo hablar con el dispositivo de destino si utilizo rutinas SPI estándar , pero no he descubierto el DMA... El dispositivo es el IC de memoria flash Spansion S25FL164K .

Como prueba, estoy tratando de leer los identificadores del dispositivo. Debería enviar un solo byte y luego recibir tres bytes en respuesta. Dado que el STM32 es el maestro SPI, debe proporcionar tres bytes de pulsos de reloj para obtener la respuesta. Generalmente, el dispositivo maestro envía tres bytes ficticios para crear este reloj. Con DMA, le dices cuántos bytes recibir y se supone que debe crear las formas de onda por ti.

Con mi código, el primer byte se transmite pero no se crean pulsos de reloj para los valores devueltos. Esto implica que los relojes periféricos DMA y SPI se están configurando correctamente. He buscado en línea y (sorprendentemente) no puedo encontrar ejemplos, tutoriales o notas de aplicaciones que hablen sobre la secuenciación de transmisión y luego recepción.

Supongo que simplemente estoy haciendo mal la secuencia.

Para TX, puedo:

  • Habilitar el dispositivo DMA
  • Deshabilite el dispositivo DMA.
  • Habilite el dispositivo SPI para enviar señales al DMA,
  • Deshabilitar esta señalización
  • Borrar banderas DMA, y
  • Borrar banderas SPI.

Puedo hacer lo mismo, por separado, para RX.

He probado diferentes permutaciones de habilitar/deshabilitar estas diferentes funciones. En lugar de tratar de enumerarlos todos aquí, espero que alguien me diga el método adecuado :) Cualquier ayuda es apreciada; He estado golpeando mi cabeza contra este.

Aquí está mi configuración de DMA, seguida de una de las iteraciones fallidas de la función de lectura de SPI:

void ConfigureDMA(void)
{
    DMA_InitTypeDef     DMA_InitStructure;

    // Enable DMA1 Peripheral Clock (SPI_DECAWAVE and SPI_BUS)
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    // Configure SPI_BUS RX Channel
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // From SPI to memory
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryBaseAddr = 0; // To be set later
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_BufferSize = 1; // To be set later
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);

    // Configure SPI_BUS TX Channel
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // From memory to SPI
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryBaseAddr = 0; // To be set later
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_BufferSize = 1; // To be set later
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5, &DMA_InitStructure);

} // end ConfigureDMA()

En la llamada de función, CommandBuffer y DataBuffer son punteros a matrices. Esta función queda atrapada esperando en la sección "Esperar hasta que se reciban los datos", pero incluso si omito esta verificación, los pulsos del reloj nunca se crean.

void spiFlashRead(uint8_t CommandLength, const uint8_t *CommandBuffer,
        uint16_t DataLength, uint8_t *DataBuffer)
{
    // Prepare the DMA
    DMA1_Channel5->CNDTR = CommandLength;
    DMA1_Channel5->CMAR = (uint32_t)CommandBuffer;
    DMA1_Channel4->CNDTR = DataLength;
    DMA1_Channel4->CMAR = (uint32_t)DataBuffer;

    // Enable the DMAs - They will await signals from the SPI hardware
    DMA_Cmd(DMA1_Channel5, ENABLE); // TX
    DMA_Cmd(DMA1_Channel4, ENABLE); // RX

    // Activate the Flash CS
    GPIO_ResetBits(SPI_MEM_CS_GPIO, SPI_MEM_CS);

    // Enable the SPI communication to the TX DMA, which will send the command
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);

    // Wait until the command is sent to the DR
    while (!DMA_GetFlagStatus(DMA1_FLAG_TC5));

    // Wait until the transmission is completed
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == RESET);

    // Disable the TX DMA and clear DMA flags
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, DISABLE);
    DMA_Cmd(DMA1_Channel5, DISABLE);
    DMA_ClearFlag(DMA1_FLAG_GL4 | DMA1_FLAG_HT4 | DMA1_FLAG_TC4 | DMA1_FLAG_GL5 | DMA1_FLAG_HT5 | DMA1_FLAG_TC5);
    //NOTE: I checked the SPI OVR flag here, and it wasn't set...

    // Enable SPI communication to the RX DMA, which should receive the data
    SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, ENABLE);

    // Wait until the data is received
    while (!DMA_GetFlagStatus(DMA1_FLAG_TC4));

    // Disable the DMAs
    DMA_Cmd(DMA1_Channel4, DISABLE); // RX
    DMA_Cmd(DMA1_Channel5, DISABLE); // TX

    // Release the Flash CS
    GPIO_SetBits(SPI_MEM_CS_GPIO, SPI_MEM_CS);

} // end spiFlashRead()

Respuestas (2)

SPI no creará las formas de onda por usted. Es full duplex y el lado maestro recibe al mismo tiempo que transmite, solamente.

El DMA no solicita al receptor que obtenga datos, es al revés: cuando el receptor ha recibido algo, solicita al DMA que lo transfiera.

Probablemente tenga que configurar las matrices de transmisión y recepción con una longitud de 4 e ignorar la primera recibida.

Lo hice funcionar una vez, siéntete libre de pedir más.

Ah, ya veo. Me pregunto de dónde vino mi suposición incorrecta... Lo intentaré y veré qué sucede :) ¡Gracias!
Gracias venny, pude hacerlo funcionar ahora. ¡Eres un salvavidas!

Enfrenté el mismo problema cuando estaba tratando de hacer la transferencia dúplex completa SPI usando el DMA en ambas direcciones en el STM32F103 (Maple Mini).

El búfer de recepción contenía un byte inesperado al principio. Tuve que ignorarlo y aumentar el tamaño del búfer en un byte para que el canal Rx DMA obtuviera todos los bytes recibidos. Sin embargo, logré encontrar la solución.

En el manual de referencia de RM0008 encontré la sección 25.3.9 Comunicación SPI usando DMA (dirección directa de memoria) que decía:

En recepción, se emite una solicitud DMA cada vez que RXNE se establece en 1. Luego, DMA lee el registro SPI_DR (esto borra el indicador RXNE).

Entonces, traté de limpiar el indicador RXNE en el SPI antes de que comenzara la transferencia DMA:

Búfer Rx no vacío (RXNE)

Cuando se establece, esta bandera indica que hay datos recibidos válidos en el búfer Rx. Se borra cuando se lee SPI_DR.

Afortunadamente, esto ayudó y pude obtener datos limpios sin basura.