[En-Nut-Discussion] SPI in IRQ

22 views
Skip to first unread message

Klaus Kloos

unread,
Feb 16, 2012, 2:22:36 AM2/16/12
to Ethernut User Chat (English)
Hello

I have to work with an 24bit ADC AD7767 with 32kSPS.
The values can be read between two finished conversions from the device using SPI. There are ca. 30us left for this when using the 32kSPS.

My first though was to get the 'value ready' signal by IRQ, inform a thread by NutEventPostFromIrq() and get the value by SPI in a separate thread. But that is much to slow. Im not able to guaranty a context switch in this short time.

The second approach was to get the SPI-value in the IRQ by bitbanging. This is a little to slow and very ugly. I will have to code this part in assembler.....

So I tried to use the NutOS functions. (*spiBus1At91.bus_alloc) and (*spiBus1At91.bus_release) are called outside the IRQ, (*spiBus1At91.bus_transfer) is called at IRQ time.
The transfer of the 3 bytes last ca. 12us using 10MHz SPI. It is working, but unreliable. Sometimes my device freezes.
Im almost sure that the SPI-functions are not designed to be used like this.

What are my possibilities? Is there a recommended way to solve such time-critical SPI-requests?
Can I get the Nut/OS functions IRQ save somehow?

Thanks in advance for any hint.

Greetings Klaus

Ingenieurbüro Kloos
Soft+Hardware Entwicklung
Haus Heyden Strasse 111
52134 Herzogenrath
Tel.: 02407/918376
Handy.: 0176/20925123


_______________________________________________
http://lists.egnite.de/mailman/listinfo/en-nut-discussion

Harald Kipp

unread,
Feb 16, 2012, 4:29:34 AM2/16/12
to Ethernut User Chat (English)
Hi Klaus,

On 16.02.2012 08:22, Klaus Kloos wrote:
> So I tried to use the NutOS functions. (*spiBus1At91.bus_alloc) and (*spiBus1At91.bus_release) are called outside the IRQ, (*spiBus1At91.bus_transfer) is called at IRQ time.
> The transfer of the 3 bytes last ca. 12us using 10MHz SPI. It is working, but unreliable. Sometimes my device freezes.

Probably. In general, it is not allowed to call API functions in
interrupt context, except NutEventPostFromIrq(). As a special exception
it is possible to use stdio functions on a polling device, usually devDebug.

> What are my possibilities? Is there a recommended way to solve such time-critical SPI-requests?
> Can I get the Nut/OS functions IRQ save somehow?

Nut/OS allows to use native interrupt handlers. For the AT91 you can use

static void MySpiIrqHandler(void) __attribute__ ((naked));
void MySpiIrqHandler(void)
{
IRQ_ENTRY();
...your code here...
IRQ_EXIT();
}

In your main code you need to write the address of the handler into the
related AIC service routine register.

outr(AIC_SVR(SPI0_ID), (unsigned int) MySpiIrqHandler);

And...not to forget...it is always a good idea to check the datasheet. ;-)

Regards,

Harald
_______________________________________________
http://lists.egnite.de/mailman/listinfo/en-nut-discussion

Klaus Kloos

unread,
Feb 16, 2012, 6:04:28 AM2/16/12
to Ethernut User Chat (English)
Hello Harald

Thanks for your fast answer.

> On 16.02.2012 08:22, Klaus Kloos wrote:
>> So I tried to use the NutOS functions. (*spiBus1At91.bus_alloc) and (*spiBus1At91.bus_release) are called outside the IRQ, (*spiBus1At91.bus_transfer) is called at IRQ time.
>> The transfer of the 3 bytes last ca. 12us using 10MHz SPI. It is working, but unreliable. Sometimes my device freezes.
>
> Probably. In general, it is not allowed to call API functions in
> interrupt context, except NutEventPostFromIrq(). As a special exception
> it is possible to use stdio functions on a polling device, usually devDebug.
>

ok, i was aware that this is not the right way. It was only a test and it gave me a 'near to working' result :-) My hope was to get bus-transfer() IRQ save somehow......
What is necessary to make a function IRQ save?

>> What are my possibilities? Is there a recommended way to solve such time-critical SPI-requests?
>> Can I get the Nut/OS functions IRQ save somehow?
>
> Nut/OS allows to use native interrupt handlers. For the AT91 you can use
>
> static void MySpiIrqHandler(void) __attribute__ ((naked));
> void MySpiIrqHandler(void)
> {
> IRQ_ENTRY();
> ...your code here...
> IRQ_EXIT();
> }
>
> In your main code you need to write the address of the handler into the
> related AIC service routine register.
>
> outr(AIC_SVR(SPI0_ID), (unsigned int) MySpiIrqHandler);
>

Interesting, this might give me back the 1us which I loose between an IRQ and the calling of my IRQ-routine using the NutOS way.

But that's not the main problem. How do I get the SPI-Value in my 30us slot? I see these ways:
Is there a way to generate a fast context-switch from IRQ to the thread which is allowed to call bus_transfer()? Can this be done reliable in ca. 10us?
Then I can use the NutOS SPI routines.

An other way is the read the data in the IRQ. But then I have to code a function like bus_transfer() by myself (?). Not a nice chance...

A simple solution would be working without an IRQ and do everything in a thread without allowing a context-switch. So the sam7 will freeze for up to 50ms (i have to read 1000 values).

> And...not to forget...it is always a good idea to check the datasheet. ;-)
>

Not that I have completely memorized it, but Ive read it several times. The problem seems clear. Im too slow.
There is also a 128kSPS variant of the chip available. Then the time slot is less than 8us. Im happy that Im not forced to solve this problem....

Greetings Klaus
_______________________________________________
http://lists.egnite.de/mailman/listinfo/en-nut-discussion

Ulrich Prinz

unread,
Feb 16, 2012, 9:25:58 AM2/16/12
to Ethernut User Chat (English)
Hi Klaus,

If I understand the datasheet of the ADC correctly it serves you with
/DRDY signal every time a sample is complete.
So if you write a GPIO IRQ handler that reads the SPI at this time,
you are done with the first part.

For reading SPI you need to throw out dummy bytes that just trigger
SCK so the ADC can write via MISO to your AT91.
To get more free CPU time, you should think about using DMA transfer for SPI.
So you set up a dummy region of RAM that hold a fixed pattern of
control words and dummy bytes.
Then you set up another region to store the read values into.
Then you setup and start DMA with completion interrupt set.

The DMA completion interrupt then shows your application that there is
a new value available.

If you need several values at a time, you can expand the regions of
dummy and reception values and setup the DMA to transfer this bigger
regions. The DMA Interrupt then only counts down the number of samples
per block and calls your application after counter has reached 0.

To be more flexible and gather some additional time:
You can set the AT91 DMA to work with separate pages. So if a DMA is
completed you can flip to a nother page where it continues to work
right away. So after you sampled one page, you have time to decode and
move the date from there, while the DMA continues to write into the
second page. After the second page is completed the DMA flips back to
the first page or even a third page.

If I remeber my last AT91 devel over a year ago, it should be possible
to implement you ADC routines with just DMA and a single DMA interrupt
handler.

In general:
You should keep a state variable in you code, that shows if your
sampling is active. If not active, you can use normal nut/os SPI
functions to setup / program your ADC. After activating your sampling,
the variable should state that no normal nut/os function should
trigger SPI.

You did not state the reason for this construction but in general I
would suppose to use the I2S interface of an AT91 or STM32 and use the
special features of this interface that automates constant time
sampling and delivery of data with very low software overhead
including multi buffer handling ( work in one while the other is
filled, then switch)

I2S is just a derivate of SPI...

Ulrich

_______________________________________________
http://lists.egnite.de/mailman/listinfo/en-nut-discussion

Klaus Kloos

unread,
Mar 2, 2012, 3:02:30 AM3/2/12
to Ethernut User Chat (English)

Am 16.02.2012 um 15:25 schrieb Ulrich Prinz:

Hello Ulrich

Your mail gave me some areas to think about. Now it is working.
I was thinking a IRQ/NutOS problem was driving me crazy, but the main problem was a simple memory fault.
Writing one byte further than the allocated space is always interesting.
In my application bus_transfer can be called in IRQ without problems. Now ive extracted the code of bus_transfer to get a bit faster.

Is there a way to find such memory problems more easily?
Im aware of the NUTDEBUG_CHECK_STACK which is very helpful in other situations.

Greetings Klaus

Interesting, I2S is new for me. I will look at this and at the DMA stuff if I will get some spare time.....

Thanks for your help.

Reply all
Reply to author
Forward
0 new messages