SPI interface freezes within the DMA ISR – [SOLVED] –

stevestrong
Wed Dec 28, 2016 11:08 pm
I am posting here as a side-issue of this post.

The setup:
SPI is driven by the DMA. DMA provides 16 bit data stream from a memory array to SPI which just outputs serial data on the MOSI pin.
The DMA is configured to generate an interrupt after it finished to send all the data to SPI.

The flow:
The DMA IRQ is correctly generated after data transmission finished, but before all 16 bits are pulsed out by SPI.
That’s why it is needed to check the SPI status before sending new data.

The problem:
In the DMA ISR, checking the TXE and BSY flags of the SPI interface using this code doesn’t wok:
while (spi_is_tx_empty(_currentSetting->spi_d) == 0); // "5. Wait until TXE=1 ..."
/* and */
while (spi_is_busy(_currentSetting->spi_d) != 0); // "... and then wait until BSY=0 before disabling the SPI."


ahull
Thu Dec 29, 2016 12:47 am
Bear with me as I am simply looking at the documentation, and have no idea (as usual) what I am talking about… but…
I can’t see anything obviously wrong with your code (so you may have encountered a bug)… but is true=1 is false=0 what exactly does spi_is_busy return?! Are we being fooled by comparing a boolean with an int or something daft?

uint8 spi_is_busy(spi_dev * dev)
Determine whether the device’s peripheral busy (SPI_SR_BSY) flag is set.
Parameters:
dev –
SPI device
Return:
true, iff dev’s BSY flag is set.

… appears to be a valid way to see if you are ready to fire off the next transmission. At least that is how I am reading http://docs.leaflabs.com/static.leaflab … i/spi.html
I am probably barking up the wrong tree (or perhaps not even barking in the right forest). :D


stevestrong
Thu Dec 29, 2016 7:37 am
Thanks, Andy, but this was copied from a working part of code.
I also tried your suggestion, but as expected, same “bug”, it is something else there.

Btw, same code snippet works flawlessly if is executed on the main level, not within the ISR.


ahull
Thu Dec 29, 2016 9:17 am
stevestrong wrote:Thanks, Andy, but this was copied from a working part of code.
I also tried your suggestion, but as expected, same “bug”, it is something else there.

Btw, same code snippet works flawlessly if is executed on the main level, not within the ISR.


stevestrong
Thu Dec 29, 2016 9:44 am
The code accesses the SPI registers within DMA ISR. The ISR is entered when the DMA finished to transmit all bytes.

This is getting weird…Even disabling the SPI peripheral breaks the whole system…
Calling
spi_peripheral_disable(_currentSetting->spi_d);


stevestrong
Thu Dec 29, 2016 9:54 am
Just for reference, I attach here my code (dirty, full with debug lines, but good for testing)
/*****************************************************************************/
void SPIClass::dmaSendIrq(void)
{
digitalWrite(DEB, LOW);
//chan_regs->CCR &= (DMA_CCR_EN_BIT|DMA_CCR_TCIE)^0xFFFFFFFF;
bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 0); //dma_disable(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel);//
bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_TCIE_BIT, 0);//dma_disable_irq(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel);//
//spi_tx_dma_disable(_currentSetting->spi_d);
dmaIrqCount++;

if ( dmaIrqCount>1 ) {
digitalWrite(DEB, HIGH);
digitalWrite(DEB, LOW);
spi_peripheral_disable(_currentSetting->spi_d); // here we have the freeze, the CPU will not execute the following lines...
digitalWrite(DEB, HIGH);
digitalWrite(DEB, LOW);
}
digitalWrite(DEB, HIGH);
while ( !spi_is_tx_empty(_currentSetting->spi_d) ); // "5. Wait until TXE=1 ..."
digitalWrite(DEB, LOW);
while ( spi_is_busy(_currentSetting->spi_d) ); // "... and then wait until BSY=0 before disabling the SPI."
digitalWrite(DEB, HIGH);
//uint16 x = spi_rx_reg(_currentSetting->spi_d); // dummy read, needed, don't remove!
uint8 tail = bufTail;
// check if there is buffer available to send
if ( tail!=bufHead ) { // data to be written available?
// write window address
writeWinAddr( dmaBuffer[tail].winAddr );
// update length and memory to write from
chan_regs->CMAR = (uint32)(&dmaBuffer[tail].color);//dma_set_mem_addr(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, &dmaBuffer[bufTail].color);//
chan_regs->CNDTR = dmaBuffer[tail].length;//dma_set_num_transfers(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, dmaBuffer[bufTail].length);//
bufTail = (tail+1) & BUFF_SIZE_MASK;
dmaSending = 1;
writeCSActive(); // activate CS
}
else
{ //nothing to send
dmaSending = 0;
writeCSIdle(); // release chip select
}
digitalWrite(DEB, LOW);
/**/
dma_clear_isr_bits(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel);
if (dmaSending) {
spi_tx_dma_enable(_currentSetting->spi_d);
bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_TCIE_BIT, 1); // enable irq//dma_enable_irq(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel);//
bb_peri_set_bit(&chan_regs->CCR, DMA_CCR_EN_BIT, 1);//dma_enable(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel); // enable transmit
}
digitalWrite(DEB, HIGH);
}


RogerClark
Thu Dec 29, 2016 10:20 am
@stevestrong

Do you have an Blue Pill you could turn into a BlackMagic Probe, as If the code has had an exception, its fairly easy to find out the what has caused the exception (which line of code) using GDB.


stevestrong
Thu Dec 29, 2016 10:26 am
Roger, if I manage to debug with STLink, how exactly can I detect the exception cause?
The code line causing the trouble is actually known. In fact, any SPI register access seems to cause this effect.
It must have to do with accessing within ISR, but I really cannot figure out what exactly can disturb so heavily…

Pito
Thu Dec 29, 2016 11:51 am
For some configurations, disabling the SPI and entering the Halt mode while a transfer is ongoing can cause the current transfer to be corrupted and/or the BSY flag might become unreliable.

To avoid any of those effects, it is recommended to respect the following procedure when disabling the SPI:
http://stm32info.com/en/disabling-the-spi/

http://electronics.stackexchange.com/qu … y-flag-set


zmemw16
Thu Dec 29, 2016 11:53 am
is it possible that it’s 2 interrupts ?

set up a timer (with a silly limit) to force it out, just for debug only
hangs are so indeterminate :D

srp


stevestrong
Thu Dec 29, 2016 12:59 pm
@pito
Thanks, I am well aware of those information, but none of them discusses the system hangup by SPI register accesses within DMA ISR! And exactly this is what happens in my case. I cannot get the result of checking BSY flag, the system suddenly hangs…

@zmemw16
I cannot image why should the DMA trigger 2 interrupts at the same time.
But, anyway, even if it would so, why freezes the system when I access any SPI register within the ISR?

What kind of severe exception could occur?
If I manage to debug with STLink, how exactly can I detect the exception cause?


ahull
Thu Dec 29, 2016 1:29 pm
Not sure if this helps, but you might like to look at -> https://github.com/leaflabs/libmaple/bl … rrupts.txt

3.3: Interrupts not handled by libmaple (isrs.S)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Though libmaple does provide some IRQ handlers, it doesn’t define one
for every available interrupt. This is true for various reasons: maybe
the peripheral or interrupt isn’t supported yet, maybe there’s no
useful default behavior, etc.

In this case, it would be wasteful to have a separate function for
each unhandled interrupt. To handle this, there’s a single file that
deals with all unhandled interrupts. Its name is isrs.S, and it lives
in the same directory as the corresponding vector_table.S. For
example, for Maple, the file is libmaple/stm32f1/performance/isrs.S.

These aren’t complicated; read the source to see how they work.

4. Adding your own interrupt handlers
————————————-

When adding an interrupt handler (or overriding a default one), you
need to decide whether you want it for a particular program, or if
what you’re writing is general-purpose enough that it should live in
libmaple itself.


Rick Kimball
Thu Dec 29, 2016 4:56 pm
stevestrong wrote:
What kind of severe exception could occur?

stevestrong
Thu Dec 29, 2016 8:57 pm
I think I solved the problem.

In the error case, I used the following line to attach the interrupt for DMA:
dma_attach_interrupt(_currentSetting->spiDmaDev, _currentSetting->spiTxDmaChannel, (void(*)())(&SPIClass::dmaSendIrq));


RogerClark
Thu Dec 29, 2016 9:33 pm
@stevstrong

I came across the same problem myself ( but I can remember what code I was tryng to write)

From what I remember, its not possible to assign a pointer to a function to call a function inside a class.

It may be possible to use a static ( global ) callback function inside the SPI class, but it would be common to all instances of the class. ( but you would need to double check this)

There are probably other work-arounds to this, as its a well known problem. But I dont think there is an ideal solution.


Leave a Reply

Your email address will not be published. Required fields are marked *