SPISlave and interrupt on STM32 MCU

27 Aug 2016

Maybe it will be interesting for some MBED developers.

I explain how to extend the SPISlave class for use interrupts on STM32 MCU. With this solution we do not need to change anything in the basic SPISlave class.

First step is common and strighforward. We create instance of SPISlave class and do some configuration (format, frequency, ...). Next we setup SPI CR2 register to enable interrupts. Especially:

- TXEIE bit to enable 'Tx buffer empty' interrupt - RXNEIE bit to enable 'RX buffer not empty' interrupt

and at last we install own ISR routine for SPI interrupts.

SPI interrupt snippet

extern uint32_t Default_Handler;
SPI_TypeDef *cspi = SPI2;
bool blockReader, blockWriter;

void setIRQvector(IRQn_Type it, uint32_t fa)
{
	uint32_t dh;
	if(it >= 0)
	{
		if(!fa)
		{
			NVIC_DisableIRQ(it);
			dh = (uint32_t) Default_Handler;
			NVIC_SetVector(it, dh);
		}
		else
		{
			NVIC_SetVector(it, fa);
			NVIC_EnableIRQ(it);
		}
	}
}

#define TXEIE_MASK	0x80
#define RXNEIE_MASK	0x40

void enableSPIinterrupt(bool enable)
{
	uint32_t isr = 0;
	if(enable)
	{
		isr = (uint32_t) rwSPIservice;
		cspi->CR2 |= (TXEIE_MASK | RXNEIE_MASK);
	}
	else
		cspi->CR2 &= ~(TXEIE_MASK | RXNEIE_MASK);
	setIRQvector(SPI2_IRQn, isr);
}

#define RXNE_MASK	1
#define TXE_MASK		2

void rwSPIservice(void)
{
	static char intBuf[16];
	static uint16_t cInBuf;
	int tc;
	
	if(cspi->SR & RXNE_MASK && cInBuf < sizeof(intBuf))
	{
		tc = cspi->DR;
		if(tc)
			intBuf[cInBuf++] = tc;
	}
	if(cInBuf > 0 && !blockReader)
	{
		short saved = saveInBuf(intBuf, cInBuf);
		if(saved > 0)
		{
			cInBuf -= saved;
			if(cInBuf > 0)
				memmove(intBuf, intBuf + saved, cInBuf);
		}
	}
	if(cspi->SR & TXE_MASK)
	{
		tc = 0;
		if(!blockWriter)
		{
			tc = char2send();
			if(tc < 0)
				tc = 0;
		}
		cspi->DR = tc;
	}
}

Some explanaition.

Because SPI work in full duplex mode we must have 'filler' (empty) character. I use null byte as filler. Send or receive null byte means that there is nothing to send (or receive). As a consequence this protocol cant be used for binary data. Null bytes must be encoded/decoded on both side (escape sequence processing).

My ISR routine cooperate with two buffers and simple API: - saveInBuf save received characters in external buffer - char2send get next byte to send from transmit buffer

Local (static) buffer 'intBuf' is used in the case when main receive buffer is (temporarily) in use and received character can't be saved.

I run test program on Nucleo-STM32F103RB (64 MHz HSI) connected to Orange Pi Lite (Armbian Linux). On Orange Pi side I use two transmission buffers and Timer interrupt to check for pending transmission (once per 100 us). I configure SPI on both side in 8 bit mode and 16 Mhz frequency.