STM8S SPI
Wednesday, May 16th, 2012In this latest in the series I am taking a look at communication between two systems (namely a Netduino Mini and a STM8S103) using SPI.
As with previous articles the system will use the IAR compiler and the STD Peripheral Library. The Netduino Mini will use Visual Studio and .NET to act as the master for the SPI bus.
I ran into some problems along the way and had to ask for help in the Netduino Forums. Special mention to Chris Walker and CW2 for their help.
Netduino Mini Application
SPI is one of the protocols supported by the NETMF framework on the Netduino platform. As such there is little work to do. The Mini will have to act as the SPI master as this is the only role which is supported on the Netduino for this bus. We simply need to set up the SPI bus with the correct parameters and then start writing data to the bus.
To remove as many possible problems as possible we will perform a simple task, have the Netduino Mini write some data to the bus on a regular basis. In this case we will write three bytes with the values 1, 2 and 3. The code on the Mini is as simple as the following:
public static void Main()
{
SPI.Configuration config = new SPI.Configuration(SPI_mod: SPI.SPI_module.SPI1,
ChipSelect_Port: Pins.GPIO_PIN_5, // Enable pin is D5.
ChipSelect_ActiveState: false, // Active Low
ChipSelect_SetupTime: 0, // 0 uS setup time.
ChipSelect_HoldTime: 0, // 0 uS hold after data transmission has completed.
Clock_IdleState: false, // Clock line is low when the device is inactive.
Clock_Edge: false, // Data is sampled on the clock falling edge.
Clock_RateKHz: 500); // 500 KHz clock speed.
SPI spi = new SPI(config);
byte[] buffer = new byte[] { 1, 2, 3 };
while (true)
{
spi.Write(buffer);
Thread.Sleep(1000);
}
}
This code configures the bus as follows:
- Chip select will be D5 on the Netduino Mini
- Chip select will be active low
- Clock is low when the device is inactive
- Data is sampled on the falling edge of the clock signal
- Clock rate is 500 KHz
The selection of these values is deliberate as they match the values used on the GO! bus.
STM8S Code
This is where it got tricky and where I need some help. We have a simple application on the Netduino Mini which simply sends out three bytes, waits a second and then repeats. In order to know that we have received this data OK, the application on the STM8S will read these bytes from the SPI bus, add 100 and then send the result back to the Mini.
The first thing we need to do is to set up a project similar to the first article in this series. The main STD Peripheral Library module we will be using here is the SPI library which can be found in stm8s_spi.c. Add this to the project and then reference the stm8s_spi.h in the main program file.
Now we have the SPI library referenced and the library file added to the application we need to start using it. So the first step we need to take is to configure the library ready for use. The following code should achieve this matching the parameters we have set up in Netduino Mini project above.
SPI_DeInit();
SPI_Init(SPI_FIRSTBIT_MSB, // MSB first.
SPI_BAUDRATEPRESCALER_32, // For slave comms this does not matter as the clock is controlled by the master.
SPI_MODE_SLAVE, // SPI Slave
SPI_CLOCKPOLARITY_LOW, // Clock is high when the bus is idle.
SPI_CLOCKPHASE_2EDGE, // Falling clock edge clocks the first data bit.
SPI_DATADIRECTION_2LINES_FULLDUPLEX, // Full duplex transmission.
SPI_NSS_HARD, // Disable slave management by software.
0x07); // CRC style.
SPI_ITConfig(SPI_IT_RXNE, ENABLE); // Interrupt when the Rx buffer is not empty.
SPI_ITConfig(SPI_IT_TXE, ENABLE); // Interrupt when the Tx buffer is empty.
The first this we do is call SPI_DeInit to reset any SPI bus configuration.
Next we call SPI_Init to configure the system, as a SPI slave using the same parameters as the SPI master (i.e. the Netduino Mini). You should not that although the baud rate prescalar parameter is set here it will be ignored. The master controls the clock on the SPI bus.
The final two statements (SPI_ITConfig statements) determine which actions will generate an interrupt. In this case we are instructing the system to generate an interrupt in the following cases:
- When the Rx buffer is not empty (i.e. data has been received)
- When the Tx buffer is empty (i.e. the system is ready to send more data)
Interrupt Handler
The data will be handled by an interrupt handler. This handler will be called when we need to send data or when we have data ready for processing. As before, the handlers can be found in the file stm8s_it.c. The handler we will be looking for is the SPI_IRQHandler. Locate this handler and replace the code with the following:
INTERRUPT_HANDLER(SPI_IRQHandler, 10)
{
if (SPI->SR & SPI_SR_RXNE)
{
// u8 ch = SPI_ReceiveData();
u8 ch = SPI->DR;
// SPI_SendData(ch + 100);
SPI->DR = ch + 100;
}
SPI->SR = (u8) 0;
}
The two lines commented out are the equivalent calls into the SPI library. Remember that ISR’s should be as fast as possible. Replacing the calls with the appropriate expansions will decrease the amount of time required to execute the ISR.
So, here we check to see if the ISR is being called because the Rx buffer is not empty. If the buffer is not empty then we extract the value from the buffer, add 100 to it and then put this in the Tx buffer.
The final thing we do is to clear the interrupt from the status register.
Main Program Loop
The code in the main program queues up a byte ready for transmission, enables the interrupts and then repeatedly waits for more interrupts. The code looks like this:
int main(void)
{
Initialise();
SPI_SendData('*'); // Ready to send before master can initiate transfer.
enableInterrupts(); // Make sure interrupts are enabled.
while (1)
{
wfi();
}
}
So What Happened?
After spending a fair amount of time with the logic analyser and the debugger I hit a brick wall. The interrupts we not getting fired as I expected. This is where Chris Walker and CW2’s help was required. The point I was missing regarded the SPI_NSS signal. On the STM8S103 chip I was using this function is an alternative function for the labelled pin. In order to allow this pin to act as the chip select for the SPI bus it was necessary to set the alternative function register on the chip. Once this was set correctly the system started to generate interrupts as expected.