Problems with SPI on STM8 - hanging waiting for RXNE that never comes - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 12, 2020, 08:35:34 pm
content: 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:QuoteAs 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:Code: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(); // TESTloop_until_bit_is_set(SPI_SR, SPI_SR_TXE);led_txe_off(); // TESTSPI_DR = *in_data++;// Wait until reception of next data byte, then read it.led_rxne_on(); // TESTloop_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(); // TESTloop_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.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 14, 2020, 06:33:59 pm
content: As an aside, as I was looking at this problem some more this afternoon, I happened to be looking at the generated assembly for my SPI code, and I noticed something odd where I think the compiler, SDCC, is behaving oddly. Doesn't have any bearing on my SPI issues, but odd nonetheless.My debugging macro statements that I'd put in to trace things on the logic analyser took the form of:Code:#define led_txe_on() do { PC_ODR |= _BV(PC_ODR_ODR2); } while(0)Normally, SDCC is smart and converts statements like this to a singlebsetinstruction (andbres,bcplfor&=and^=operations). But, in this particular piece of my test code it's... not.What Iamgetting is this:Code:lda, 0x500a ; <-----ldwx, (0x08, sp) ; start of 'while(count-- > 1)' loopcpwx, #0x0001jrule00228$ldwx, (0x08, sp)decwxldw(0x08, sp), xora, #0x04 ; <-----ld0x500a, a ; <-----It's kept it as a read-modify-store operation, but split the read and the modify-store, with other code inserted in between. Like, WTF is that all about? That seems to be asking for bugs. I know this particular function isn't actually doing anything with that IO register in the time between, but what if I had an ISR that did? It could screw things right up.I don't know what to make of it. Is this a compiler bug?Anyway, I changed my macro to some inline assembly with explicitbsetandbresinstructions:Code:#define led_txe_on() __asm__("bset 0x500A, #2")One really doesn't need potential problem-causing shenanigans like this when already trying to diagnose an extant problem.

author: rhodges
author_rank: Frequent Contributor
timestamp: January 15, 2020, 12:13:24 am
content: What chip are you using? Did you check the errata? I just checked stm8s103 (no SPI errata) and stm8s105 (SPI issue, but does not seem relevant.)

author: HwAoRrDk
author_rank: Super Contributor
timestamp: January 15, 2020, 11:46:23 am
content: I am using an STM8S208RB on a Nucleo board.Good idea about the errata, I hadn't thought of that. But I checked and there doesn't seem to be anything concerning SPI that applies.