Nios® V/II Embedded Design Suite (EDS)
Support for Embedded Development Tools, Processors (SoCs and Nios® V/II processor), Embedded Development Suites (EDSs), Boot and Configuration, Operating Systems, C and C++
12589 Discussions

Nios SPI Master and interrupt from slave

Altera_Forum
Honored Contributor II
2,690 Views

Hello forum !! 

 

I have another issue i can't solve.. 

 

I have a nios2 processor running and I use a SPI protocol (in which I am the master) to communicate with a slave hardware. The slave, under several conditions, pull up a pin to report an interrupt event.  

 

What I want to accomplish is that when an interrupt is generated (and the pin is put on high state) by the SPI slave, the SPI master go to read some variable on the slave with the SPI protocol. 

 

Do you have any suggestions ??? 

 

Thank you all for the help! Have a nice day.
0 Kudos
10 Replies
Altera_Forum
Honored Contributor II
603 Views

You should connect that report pin to a PIO component in your Nios system. That PIO component can be configured to generate an interrupt when an edge is detected, and in your interrupt routine you trigger an SPI read.

0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Thank you Daixiwen !! 

 

So, I have added the PIO in my .qsys and re-genereted the system. Then I've updated the .bdf schematic and set the pin used as PIO. 

 

Now, I've added this code to my .c file before the main() function: 

 

#ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT static void handle_my_interrupt(void * context) # else static void handle_my_interrupt(void * context, uint32_t id) # endif { volatile int * edge_capture_ptr = (volatile int*) context; *edge_capture_ptr=IORD_ALTERA_AVALON_PIO_EDGE_CAP(MY_PIO_BASE); /* * * some code here */ } volatile int edge_capture; 

 

Then I have created the routine that initialize the PIO and register the interrupt and I call it in my main function: 

 

static void init_pio(){ void * edge_capture_ptr=(void *) &edge_capture; IOWR_ALTERA_AVALON_PIOIRQ_MASK(MY_PIO_BASE, 0x01); IOWR_ALTERA_AVALON_PIO_EDGE_CAP(MY_PIO_BASE,0x0); # ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT alt_ic_isr_register(MY_PIO_IRQ_INTERRUPT_CONTROLLER_ID, MY_PIO_IRQ, handle_my_interrupt, edge_capture_ptr, 0x0); # else alt_irq_register(MY_PIO_IRQ, edge_capture_ptr, handle_my_interrupt); } 

 

 

Now, I can't test it directly beacause I need some extra hardware. But from what I've understood I have to put the code i want to be executed after an interrupt event in the handle_my_interrupt routine. It's right ? 

 

Do I have to put any 'reset' conditions of the interrupt in handle_my_interrupt

 

 

Thank you !!!
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

I suggest that you read the documentation for the PIO. It is here (http://www.altera.com/literature/ug/ug_embedded_ip.pdf), chapter 10. On the hardware level you will need to be sure that the component is configured to generate an interrupt on edge capture. I think that by default it will generate one on level. 

You are right that you put your code in your handle_my_interrupt() routine. Before returning you will need to clear the edge cap register, as explained on page 10-8, or else the interrupt will fire right away again. 

It is best practise (especially if you are working with a real time OS) to have your interrupt routine as small and fast as possible, to avoid disturbing the rest of the software. That means that if you do the SPI reading from the interrupt routine, you shouldn't poll the SPI master to wait for the answer, but use the SPI's interrupt system instead.
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Thank you Daixiwen !! 

 

Now I'm not so confused. 

 

One more question, in handle_my_interrupt(void * context, uint32_t id) I have to work with some variables that are defined in main(). 

So, I have added some pointers to the handle_my_interrup function:  

handle_my_interrup(void * context, uint32_t id, uint8_t * var_ptr1 , uint8_t var_ptr1). 

 

After doing so and rebuilding the preject I get this warning in corrispondence of alt_irq_register(my_pio_irq_interrupt_controller_id, my_pio_irq, handle_my_interrupt, edge_capture_ptr, 0x0) fuction: 

 

warning: passing argument 3 of alt_ic_isr_register from incompatible pointer type 

 

The warning disappear if I rewrite the handle_my_interrupt() routine with the suggested two input arguments. How can I modify the main vars with my interrupt routine ?? 

 

Moreover, the interrupt has to be launched when the pin of my slave harware (set as input PIO) go HIGH. This is why I have configured the PIO as rising edge. Should I still change it to level ??  

 

 

Thank you for the help. I really appreciate. Have a nice day !
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Before using interrupts you should look at the interrupt entry/exit code paths lengths and compare those to the time the task itself will take. 

Also look at what the ISR itself does. 

If the ISR only sets a flag, can you read the hardware register at the point where you would look at the flag? Doing so saves you an interrupt entry/exit. 

 

Not using interrupts also means that you don't have to disable them when accessing the data areas the ISR might change. 

 

I will use interrupts on embedded systems, but only where it is necessary to process something before the current 'task' returns to the idle loop. In which case it is necessary to do all the relevant hardware accesses in the interrupt routine itself. 

 

I'm not sure how long your SPI read takes, a 32bit exchange at 20MHz is 160 instructions at 100MHz - not many really. The ISR paths could easily be longer than that, especially if they go through any OS interrupt dispatch code and a later call to schedule a thread (or whatever the RTOS uses). So polling for completion (even in an ISR) may actually make sense, especially if there some other actions you can do while the SPI transfer is progressing.
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Thank you dsl !! 

 

 

--- Quote Start ---  

 

I will use interrupts on embedded systems, but only where it is necessary to process something before the current 'task' returns to the idle loop. In which case it is necessary to do all the relevant hardware accesses in the interrupt routine itself. 

 

--- Quote End ---  

 

 

Could you explain me a little more ?? 

 

 

 

This is my scenario: 

I am a SPI master and I have a SPI slave in my system. The slave keeps doing some operation and if it encounters an error it pulls up a pin to signal an error event. What I want to do is to launch an isr when this condition comes true. Then in the isr routine I read the register slave with the SPI protocol to see what error the slave had encountered and subsequently update some local variable. 

 

In alternative I must keep polling via SPI the status register of the slave but the interrupt solution seemed to me much more efficient. 

 

Any suggestion !? 

 

Thank you for help !
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Attach the error pin to a pio register instead of an interrupt. 

Actually, do both and add logic to disable the interrupt request. 

Then the software can read the 'error' flag and then request the SPI read when an error is indicated. 

 

I have seen spi-like hardware where the default state of the master was to poll-read one slave register.
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

 

--- Quote Start ---  

Attach the error pin to a pio register instead of an interrupt. 

 

--- Quote End ---  

 

 

I'm confused.. But I'm new to fpga and nios too so I think it's not so strange.. :cool: 

 

 

By the way.. I have attached the interrupt pin to the PIO core and set the PIO as: 

 

width : 1bit 

direction : input only 

Edge capture reg 

synchronously capture : yes 

rising edge :yes 

generate irq : yes 

edge : yes 

 

Now I have created a global variable: my_interrupt_event_variable. Its value is set to 1 only one the handle_my_interrupt(...) is executed 

 

So in my main function I keep checking the value of my_interrupt_event_variable. If my_interrupt_event_variable==1 then i run the code I need to when an interrupt event happens, and after I set my_interrupt_event_variable=0. 

I would prefer not to use a global variable but use a local main() variable passed by reference to handle_my_interrupt. In this case I've noticed that the compiler return me this warning (as mantioned before): 

 

warning: passing argument 3 of alt_ic_isr_register from incompatible pointer type 

 

Any suggestion? 

 

Thank you so much !
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

 

--- Quote Start ---  

One more question, in handle_my_interrupt(void * context, uint32_t id) I have to work with some variables that are defined in main(). 

So, I have added some pointers to the handle_my_interrup function:  

handle_my_interrup(void * context, uint32_t id, uint8_t * var_ptr1 , uint8_t var_ptr1). 

--- Quote End ---  

Yes, as the compiler warning suggests, you can't do it that way. The interrupt routines in the HAL are supposed to follow a strict prototype, with only two arguments. If you need to pass more parameters to your ISR, then the trick is to place them in a structure, and use the address of that structure as context. Something like this (I didn't try to compile it, there could be some syntax errors):typedef struct my_isr_context_t { int capture; uint8_t *var_ptr1; uint8_t var_2; } my_isr_context_t; volatile my_isr_context_t my_isr_context; # ifdef ALT_ENHANCED_INTERRUPT_API_PRESENT alt_ic_isr_register(MY_PIO_IRQ_INTERRUPT_CONTROLLER_ID, MY_PIO_IRQ, handle_my_interrupt, &my_isr_context, 0x0); # else alt_irq_register(MY_PIO_IRQ, edge_capture_ptr, &my_isr_context);  

Then in your ISR, you cast the void* context to a my_isr_context_t* and access the different structure members.
0 Kudos
Altera_Forum
Honored Contributor II
603 Views

Flawless victory !! 

 

Thank you !
0 Kudos
Reply