As a further part of my experiments with the STM8, I've been trying to write some code for the SPI peripheral, but I've run into a problem I can't quite fathom.
Initially, I wrote some simple, naive code for a function that takes pointers to two buffers (plus a size) and transmits the 'input' data buffer and writes received data to the 'output' buffer. That all worked fine, but was unfortunately rather slow, with too much delay in between bytes. I am aiming to have a constant uninterrupted stream of bytes traversing the SPI bus, to make transactions as quick as possible.
Then I thought I would try following the exact procedure laid out in the STM8S Reference Manual (section 20.3.5, 'Full Duplex Transmit and receive procedure in master or slave mode', p270), where they advise the following:
1. Enable the SPI by setting the SPE bit
2. Write the first data to be transmitted in the SPI_DR register (this clears the TXE flag).
3. Wait until TXE = 1 and write the second data to be transmitted. Then wait until RXNE = 1 and read the SPI_DR to get the first received data (this clears the RXNE bit). Repeat this operation for each data to be transmitted/received until the n-1 received data.
4. Wait until RXNE = 1 and read the last received data.
5. Wait until TXE = 1 and then wait until BSY = 0 before disabling the SPI.
As the SPI peripheral data writes and reads to SPI_DR register are buffered, it seems they advise this slightly unusual sequence in order to keep the send buffer full throughout the transaction.
I ended up with the following code for my transfer function:
void spi_transfer_buffer(const void *in_buf, void *out_buf, size_t count) {
const uint8_t *in_data = (const uint8_t *)in_buf;
uint8_t *out_data = (uint8_t *)out_buf;
uint8_t dummy = 0x00;
if(count > 0) {
if(in_data != NULL && out_data != NULL) {
// Write first byte of data.
SPI_DR = *in_data++;
while(count-- > 1) {
// Wait until previous data byte has been sent, then write next data byte.
led_txe_on(); // TEST
loop_until_bit_is_set(SPI_SR, SPI_SR_TXE);
led_txe_off(); // TEST
SPI_DR = *in_data++;
// Wait until reception of next data byte, then read it.
led_rxne_on(); // TEST
loop_until_bit_is_set(SPI_SR, SPI_SR_RXNE);
led_rxne_off(); // TEST
*out_data++ = SPI_DR;
}
// Wait until reception of last data byte, and read it.
led_rxne_on(); // TEST
loop_until_bit_is_set(SPI_SR, SPI_SR_RXNE);
led_rxne_off(); // TEST
*out_data++ = SPI_DR;
} else if(in_data != NULL && out_data == NULL) {
// Same as above, but reading into dummy instead of out_data.
} else if(in_data == NULL && out_data != NULL) {
// Same again, but writing from dummy instead of in_data.
}
}
}
The lines commented 'TEST' were to aid my debugging efforts by toggling GPIO lines so I can see (with a logic analyser) how long it is spending waiting for TXE or RXNE. My main loop is just repeatedly formatting a string, printing it (via UART), sending a few fixed bytes plus the contents of the string out via SPI, and a short delay.
The problem I am having though, is that this would work fine for a few iterations of the main loop, but then hang after 35 iterations!
After inspecting things with a logic analyser, it appears that the hang is caused by it never exiting the loop where it is waiting for TXNE to be set from reception of the final byte. TXNE apparently never gets set, so the loop continues forever.
As you can see above on the green trace (positive pulses represent time spent waiting) shows that on the final byte, the RXNE flag never seems to be set, causing it to loop forever.
As a shot in the dark - because it was the only thing I could think of that could potentially interrupt things (no pun intended) - was to globally disable interrupts during my SPI transaction. I have one regular interrupt in my test code, which is a 1ms timer that increments a timestamp (timestamp value gets inserted in the aforementioned string). Doing so prevents the hang, and it has been happily running for a couple of hours now as I type.
Anyone got any ideas what's going on? Obviously, I can't practically use such code that requires no interruption, so I am keen to know if there is some flaw in my code.