Otimização de SPI Half-Duplex no STM32: Comunicação de 3 Fios em Alta Velocidade

Contexto e Desafios da Comunicação SPI a 3 Fios

Ao trabalhar com transceptores como o A7105, é comum encontrar a necessidade de utilizar o modo SPI de 3 fios (bidirecional). Embora a emulação via software (bit-banging) seja funcional, ela impõe limites severos de performance — em um MCU de 48MHz, a velocidade costuma estagnar em torno de 1.6 Mbps. A transição para o periférico de hardware do STM32 permite alcançar velocidades consideravelmente supeirores, chegando a 12 Mbps, mas exige um controle rigoroso sobre os estados do barramento.

Para operar em modo half-duplex utilizando apenas o pino MOSI para transmissão e recepção, o periférico SPI do STM32 deve ser configurado seguindo estas premissas fundamentais:

  • O bit BIDIMODE no registrador SPIx->CR1 deve ser definido como 1 para ativar o modo bidirceional.
  • O bit BIDIOE controla a direção do fluxo: 1 para escrita (Master Out) e 0 para leitura (Master In).
  • A inversão de direção exige a desativação momentânea do periférico através do bit SPE.

Implementação da Inicialização e Escrita

O principal obstáculo na implementação de hardware é o gerenciamento do clock (SCLK). No modo de leitura, assim que o periférico é habilitado com BIDIOE = 0, o sinal de clock começa a ser gerado imediatamente. Se o processador não for rápido o suficiente para desabilitar o SPI após a recepção do byte, pulsos de clock adicionais serão enviados, dessincronizando a comunicação com o Slave.

void configurar_spi_3fios(void)
{
    GPIO_InitTypeDef estrutura_gpio;
    SPI_InitTypeDef init_spi;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    // Configuração dos pinos: SCK e MOSI
    estrutura_gpio.GPIO_Mode = GPIO_Mode_AF;
    estrutura_gpio.GPIO_Speed = GPIO_Speed_50MHz;
    estrutura_gpio.GPIO_OType = GPIO_OType_PP;
    estrutura_gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
    estrutura_gpio.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    GPIO_Init(GPIOB, &estrutura_gpio);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_0);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_0);

    SPI_I2S_DeInit(SPI1);
    init_spi.SPI_Direction = SPI_Direction_1Line_Tx;
    init_spi.SPI_Mode = SPI_Mode_Master;
    init_spi.SPI_DataSize = SPI_DataSize_8b;
    init_spi.SPI_CPOL = SPI_CPOL_Low;
    init_spi.SPI_CPHA = SPI_CPHA_1Edge;
    init_spi.SPI_NSS = SPI_NSS_Soft;
    init_spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; 
    init_spi.SPI_FirstBit = SPI_FirstBit_MSB;
    init_spi.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &init_spi);

    // Pino NSS controlado manualmente
    estrutura_gpio.GPIO_Mode = GPIO_Mode_OUT;
    estrutura_gpio.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOA, &estrutura_gpio);

    SPI_Cmd(SPI1, ENABLE);
}

void transmitir_byte_spi(uint8_t dado)
{
    if (!esta_em_modo_tx)
    {
        SPI1->CR1 &= ~SPI_CR1_SPE; // Desabilita para trocar direção
        SPI1->CR1 |= SPI_Direction_Tx;
        esta_em_modo_tx = 1;
    }

    *(uint8_t*)&SPI1->DR = dado;
    SPI1->CR1 |= SPI_CR1_SPE;

    while (!(SPI1->SR & SPI_I2S_FLAG_TXE));
    while (SPI1->SR & SPI_I2S_FLAG_BSY);
    
    SPI1->CR1 &= ~SPI_CR1_SPE;
}

O Problema Crítico da Leitura e a Solução de Temporização

Em altas frequências (prescalers baixos), a verificação padrão do flag RXNE seguida pela desativação do SPI é frequentemente lenta demais. O tempo que a CPU leva para executar essas instruções permite que o hardware gere ciclos de clock extras, corrompendo os dados subsequentes. A solução para estabilizar a leitura em 12 Mbps (DIV4) consiste em inverter a lógica de controle: habiliatr o SPI e desabilitá-lo imediatamente antes de aguardar o dado no buffer.

uint8_t receber_byte_spi(void)
{
    uint8_t valor_recebido = 0;

    if (esta_em_modo_tx)
    {
        SPI1->CR1 &= ~SPI_CR1_SPE;
        SPI1->CR1 &= SPI_Direction_Rx;
        
        // Ajuste do limiar do FIFO para 8 bits
        SPI1->CR2 &= ~SPI_CR2_FRXTH;
        SPI1->CR2 |= SPI_RxFIFOThreshold_QF;
    }
    
    esta_em_modo_tx = 0;

    // Técnica de frenagem de clock: habilita e desabilita imediatamente
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->CR1 &= ~SPI_CR1_SPE;

    while (!(SPI1->SR & SPI_I2S_FLAG_RXNE));
    valor_recebido = *(uint8_t*)&SPI1->DR;

    return valor_recebido;
}

Considerações sobre o uso de DMA

Embora o DMA seja ideal para transferências em lote, no caso específico do SPI 3-fios em modo Master Read, ele apresenta dificuldades de estabilidade em altas velocidades. O overhead de gerenciamento dos canais de DMA impede a interrupção instantânea do clock SPI. Em testes práticos, para garantir a integridade dos dados via DMA, foi necessário reduzir o BaudRate significativamente (prescaler de 128), o que torna o método manual otimizado superior para aplicações que exigem baixa latência e alta taxa de transferência.

void leitura_multipla_dma(uint8_t *buffer, uint32_t comprimento)
{
    if (esta_em_modo_tx)
    {
        SPI1->CR1 &= ~SPI_CR1_SPE;
        SPI1->CR1 &= SPI_Direction_Rx;
        SPI1->CR2 |= SPI_RxFIFOThreshold_QF;
    }
    esta_em_modo_tx = 0;

    DMA1_Channel2->CCR &= ~DMA_CCR_EN;
    DMA1_Channel2->CMAR = (uint32_t)buffer;
    DMA1_Channel2->CNDTR = comprimento;
    
    SPI1->CR2 |= SPI_I2S_DMAReq_Rx;
    SPI1->CR1 |= SPI_CR1_SPE;
    DMA1_Channel2->CCR |= DMA_CCR_EN;

    while(!(DMA1->ISR & DMA1_FLAG_TC2));

    DMA1_Channel2->CCR &= ~DMA_CCR_EN;
    SPI1->CR1 &= ~SPI_CR1_SPE;
    SPI1->CR2 &= ~SPI_I2S_DMAReq_Rx;
    DMA_ClearFlag(DMA1_FLAG_TC2);
}

Tags: STM32 SPI Half-Duplex Embedded-C Microcontrollers

Publicado em 7-21 04:05