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->CR1deve 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);
}