STM32 SPI esclavo: Restablecer el estado DMA en NSS alto

2

Estoy intentando configurar un STM32F303RE SPI2 slave que debe enviar de forma continua y repetida el contenido 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' };

luego quiero que mi placa STM se comporte de la siguiente manera:

  1. si el maestro lo envía 2 bytes , siempre debe devolver 'AB'
  2. si el maestro lo envía 1 byte , siempre debe devolver 'A'
  3. si el maestro lo envía N > 2 bytes , siempre debería enviar 'AB' N / 2 veces + una 'A' al final si N es impar

Ya que soy un principiante con esto, decidí comenzar con una implementación simple y continuar con eso a lo largo del camino. Es por eso que estoy usando DMA sin interrupciones. Así es como se ve el código (relevante) actualmente:

/* 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 @ algún punto (dentro de un solo chip-select), las cosas comienzan a complicarse.

Aquí hay un escenario concreto:

  • Se inicia el tablero STM32
  • El maestro envía 2 bytes en una sola selección de chip y lee los 2 bytes que recibió del extremo del esclavo. Como se esperaba, estos son 'AB' .
  • El Maestro envía 1 byte en una sola selección de chip y lee el byte que recibió del esclavo. Como es de esperar, esto es 'A' .
  • El maestro envía 2 bytes en una sola selección de chip y lee los 2 bytes que recibió del extremo del esclavo. Esta vez esos 2 bytes son 'BA' . De acuerdo con las condiciones indicadas anteriormente (1-3), quiero que sean 'AB' en su lugar.

¿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, así 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-select).

De alguna manera, quiero forzar la finalización de DMA cuando NSS vuelve a ser alto (es decir, cuando el esclavo SPI ya no está seleccionado como chip).

    
pregunta Corneliu Zuzu

2 respuestas

3

Después de leer los capítulos de SPI y DMA a fondo, la conclusión clara es que el periférico SPI (o DMA) no proporciona cambios en la selección de chips (comportamiento NSS) de los indicadores / comportamiento. Pero eso tiene sentido, ya que el pin NSS es también uno de los GPIO y podemos recuperar su estado mediante esa interfaz.

Entonces, logré esto hace algunos días simplemente ...

  1. Configuración de una interrupción en NSS ascendente (PB12): NSS aumenta después de una transacción, es decir, cuando el esclavo ya no está seleccionado en un 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);
    
  2. Restablecimiento de SPI2 (no hay otra forma de borrar TXFIFO ...) y rebobinar el canal DMA al inicio del búfer cuando la interrupción se activa

    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);
    }   
    
  3. Cambiar

    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    

    a

    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    

    para los casos N > 2.

Al principio me preocupaba que deshabilitar y reconfigurar por completo SPI2 tomaría demasiado tiempo en EXTI15_10_IRQHandler, pero logré que se ejecutara en 2.6us (de inicialmente 16us con -O3! ) haciendo un cambio simple en la biblioteca StdPeriph:

hizo todas las funciones llamadas desde el controlador " en línea estática " (como deberían haber sido en primer lugar). Con ese cambio, -O3 se vuelve mucho más útil.

    
respondido por el Corneliu Zuzu
2

No lo entiendo completamente, pero creo que hay una falla en su concepción. Una transferencia DMA se usa para transmitir grandes bloques de datos, y no para esperar un determinado comando y luego responder, esto se hace mediante interrupciones. Una recepción DMA es para escuchar continuamente un flujo de datos desde un dispositivo, seguramente no para esperar un solo byte. Por lo tanto, no puede recibir un paquete de datos de diferente longitud, ya que el búfer DMA tiene que estar lo suficientemente lleno para provocar una interrupción.

EDITAR: Le eché un vistazo a tu código y no lo veo. Esperando en un bucle sin fin, el DMA es un no ir. Cuando el búfer de recepción está lleno, debe desencadenar 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 decía, olvídate de recibir el DMA. El beneficio de DMA es hacer otras cosas cuando el DMA se encarga de enviar / recibir, seguramente no está hecho para esperar en un bucle sin fin para lograrlo.

    
respondido por el Marko Buršič

Lea otras preguntas en las etiquetas