Interrupts

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Interrupts

Post by Gunplumber » Sun Jan 01, 2023 12:49 pm

Hi Guys

Is there any more info on setting and using device hardware Interrupts? I am trying to work out how to make my own interrupts handler routines for various hardware (timers, USART, ETC)..

My current project involves reading 4 bytes of serial data (USART) and based on those 4 bytes, play a sound. I have the sound play from an SD card working, with a timer1 interrupt setting the Audio output data rate. Since this application cant miss any serial commands i need to implement a high priority ISR to capture the USART serial data, even when a sound is being played. The serial data is always 4 bytes long and is generated at random times..
I see there is already an USART ISRRX module but i'm not sure its entirely suitable, and besides i'd really like to learn more about how SF handles Interrupts.
My plan is to store the 4 bytes of serial data in an long word array, setup as a circular buffer, probably 32 long words long. The ISR should read the 4 bytes into the array position (0) and then increment the array element pointer, ready for the next 4 bytes. I could then buffer up to 32 commands to play sounds.
In order to prevent data loss from the main program reading the long word elements at the same time as the ISR may be writing it, i plan to have an equal length Boolean flag array which is set to true only after the 4 bytes serial data are written into the buffer array.
The main program loop then checks each boolean flag array and if true (data in the buffer at that position) then branch off to process that serial command and play the sound required. Once complete that boolean flag is set back to zero and the data will be simply overwritten the next time the ISR gets to that element.

Something like this..

Code: Select all

Dim My_Array(32) as LongWord=0
Dim My_flag(32) as Boolean=false
Dim My_pointer as Byte=0
Dim Counter as byte


Interrupt onRX()
	//Context save here
	//Clear Int flag
	My_array(My_pointer)=USART.ReadLongWord 
	My_Flag(My_pointer)=true
	Inc(My_pointer)
	IF My_Pointer=31 then 
		My_Pointer=0
	end if
//context restore.

End Interrupt


Start:

Do
	For counter = 0 to 31
		If My_flag(Counter)=true then
			//Call subroutine to process serial command 
			//My_Flag(Counter)=False
		end if 
	Next

Until forever.



I just need to work out how to setup and configure the Interrupt handler etc but i am quite lost on how to achieve this. Is there a guide?

Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: Interrupts

Post by Jerry Messina » Sun Jan 01, 2023 3:53 pm

Other than the manual, there are various user module examples that might help for some ideas.
See https://www.sfcompiler.co.uk/wiki/pmwik ... er.Modules
and https://www.sfcompiler.co.uk/wiki/pmwik ... Interrupts

One comment on what you have so far...
it's a little more work, but it would probably be better to use USART.ReadByte() in your ISR instead of ReadLongWord(), and just let your main module track the bytes. Let it assemble the four bytes into a longword if that's what you want.

ReadLongWord will leave you stuck in the ISR waiting for all four bytes of a message (and that could take a long time), during which you can't do anything else. It's almost always advisable to do the absolute minimum in the ISR itself unless you have a very simple system.

Have the ISR put the bytes into a circular buffer and leave the rest to code outside the interrupt. You don't need the ISR to try and track "I got 4 bytes" or "I got 31 messages"... that usually will only get you in trouble later on. The more complex you make the ISR the more that can go wrong and you end up fighting yourself trying to keep the main code and the ISR in sync.

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: Interrupts

Post by Gunplumber » Tue Jan 03, 2023 8:14 am

Hi Jerry

Thanks for the tips. I've written some code below but when i enable the second interrupt, everything gets sketchy.
With out the "Enable (ON_RX)" statement the code runs perfectly and i get good clear consistent audio play back.. However as soon as i include that statement in the code, even without actually tuning on RX Interrupts and also having the ON_RX ISR empty, the playback is effected and mostly crashes after a few seconds. I am also sure the second interrupt isn't firing as i have a logic analyzer connected to LED4 to monitor for changes and it's never fired.
I am 100 % sure its related to context saving because i can simulate the same symptoms by removing the Save/Restore statements from my single Timer ISR code.
The Ser_Data_out() sub is a bit bash serial routine which takes the passed Array byte element and sends it out to the A/D converter.
The ISR doesn't modify or write any data variable used the main program, only two Boolean variables that it uses to check various conditions are ture or false to determine which buffer stack to transfer data from.

Am i missing anything in terms of correct context saved in the ISR?

Cheers
Lee

Code: Select all


Dim Timer As TMR1L.AsWord               // alias to Timer1
Dim TimerOn As T1CON.Booleans(0)        // start and stop
Dim TimerEnabled As PIE4.Booleans(0)    // enable timer 1 interrupts
Dim TimerInterrupt As PIR4.Booleans(0)  // interrupt flag
Dim Timer_priority As IPR4.Booleans(0)  //Priority
Const TimerReload = 65513               // 65491 for 11025 hz @500Khz timer 1 oscilator (90us interval) sample rate 

Dim RX1_Enable As PIE3.booleans(5)     // enable/disable usart 1 Interupts
Dim RX1_Flag As PIR3.booleans(5)   //usart 1 rx flag
Dim RX1_priority As IPR3.Booleans(5)  //Priority


Interrupt OnTimer(1)                     // Timer ISR    
    Dim Data_A As Word
    Timer = TimerReload
    Save(0,Ser_data_out)    // context save 
    TimerInterrupt = False  // clear interrupt flag     
                                                          
    Low(AD_CS)                      //set CS line low to start TX to A/D                   
    If Buffer_status Then					//check which buffer is being filled and which is ready for playback. 
        Data_A=BufferB(Read_pointer) 
    Else 
       Data_A=BufferA(Read_pointer)
    End If

    Ser_data_out(Data_A.byte1)         //send main data to A/D converter
    Ser_data_out(Data_A.byte0)
    High (AD_CS)
    
    Low(AD_OUT)         //Turn on output on A/D
    High(AD_OUT)             
    If Read_pointer=255 Then        //check for end of buffer        
        If Buffer_status=false Then    //just read from a
            If Last_bufferA=true Then
                TimerOn = False           //turn off timer1 End playback
            End If
            Buffer_status=true              //switch to buffer b
        ElseIf Buffer_status=true Then      //just read from b
            If Last_bufferB=true Then
                TimerOn = False             //turn off timer1 End playback
            End If
            Buffer_status=false             //switch to buffer A
        End If       
    End If
    Inc (Read_pointer)                   //increment pointer,  pointer rolls over to 0 on buffer change     
                
   Restore                               //context restore. 
End Interrupt   
        
        
        
Interrupt ON_RX(2)
    	Save(0) 
      	RX1_Flag=false   //clear interupt flag
     	Toggle (LED4)
             
     	Restore       
End Interrupt


USART.clearoverrun()        //clear any data in RX buffer before enable interrupts
RX1_Flag=false              //clear interrupt flag
RX1_priority=true           //set RX1 as high priority
RX1_Enable=false        //set no interrupts initially

//Enable (ON_RX)                //set the interrupt handler
//RX1_Enable=true        //turn on RX Interrupts



Timer_priority=false                 //set low priority    IPR4.0
Timer = TimerReload                  //load Timer
TimerOn = False       //T1CON.0      // stop timer initially
T1CON.1= 1                               //setup timer 1
T1CON.2=1                               //not sync with ext clock
T1CON.4=0                               //Bits 4&5 no prescaler
T1CON.5=0                               //Bits 4&5 no prescaler
TMR1CLK=5                               //set timer clock scource to 500 Khz
T1GCON=0                                //set timer gate postions to 0
Enable(OnTimer)                         // assign the interrupt handler
TimerEnabled = True                     //turn on timer 1 Interrupts




Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: Interrupts

Post by Jerry Messina » Tue Jan 03, 2023 3:17 pm

The K40 high-priority ISR will save the WREG, STATUS, and BSR registers for you automatically, but that's it.
For a low-priority interrupt, SF will add code to the ISR to save these same registers for you too.

In terms of the low-priority Timer ISR, the 'Save(0,Ser_data_out)' context save/restore is required.
The 'SAVE(0)' will add code to save the FSR0, FSR1, and PRODL/PRODH registers. These are used for array access (which you have, ie 'BufferB(Read_pointer) ') and PROD regs are used for any multiply operations (which may be used unknown to you, so it's good to save them).

The 'SAVE(Ser_data_out)' portion should save any temp stack frame variables that might be used by the Ser_data_out sub, so that's good too.

For the high-priority UART RX interrupt, the UART IF flag is read-only, and the only way to clear it is to read the RCREG. The 'RX1_Flag=false' statement will be ignored, and with what you have there if RXIF ever gets set you'll go into an infinite loop. With what you have showing there, the 'save/restore' shouldn't really be required.

Now, for the confusing part...
The PIC18F interrupt controller has two modes of operation... Mid-Range Compatibility mode and Interrupt Priority mode.
These are controlled by the IPEN bit, which SF manages for you based on what interrupt code you have. IPEN is usually found in the INTCON or RCON registers.

If you define both low and high priority interrupt handlers and have an 'enable()' statement for BOTH of them then you get IPEN=1 (Interrupt Priority mode). If you don't have an 'enable()' statement for both then things get a little more murky depending on exactly what you DO have. If you only have 'enable(high)' then you still get IPEN=1, but if you only have 'enable(low)' then you get Mid-Range Compatibility mode (IPEN=0) and GIE=1/PEIE=1.
In that mode you only get a single vector, the interrupt priority bits are ignored, and everything must be handled in the "low-priority" ISR handler.

Bottom-line is this: if you only have one ISR it's usually better to just define a single interrupt routine and use it that way.

janni
Posts: 51
Joined: Wed Aug 24, 2022 2:30 pm

Re: Interrupts

Post by janni » Wed Jan 04, 2023 1:49 am

Gunplumber wrote:
Sun Jan 01, 2023 12:49 pm
Since this application cant miss any serial commands i need to implement a high priority ISR to capture the USART serial data, even when a sound is being played.
Before you fix the context saving following Jerry's instructions, you cold rethink the algorithm over. Unless you use very high communication speed, it's not necessary to give high priority to USART interrupt to ascertain sure reception. Even at 115 kBod Rx servicing can wait for the time that the timer interrupt could take (surely less than the 90us period), without the receive buffer overflowing (it's two bytes deep). So it makes more sense to give the timer interrupt high priority, thus ensuring uninterrupted sound generation, and service the serial communication in low priority interrupt.

Gunplumber
Posts: 38
Joined: Wed Nov 02, 2022 10:31 am

Re: Interrupts

Post by Gunplumber » Wed Jan 04, 2023 12:47 pm

Hi Guys

I have done a little more digging and there seems to be something odd going on with the context saving, although looking that the ASM code i cannot see what..
As mentioned with one Interrupt enabled i get good clean sound and files play to the end as expected. I checked how the data throughput from the card to the buffer progresses in between each Interrupt and the poor little 26K40 is going flat chat to keep up.. I estimate i am only getting around 1.5 bytes on average from the card for every byte sent to the A/D converter so i have very little room to move during playback.

I have found that if add the line

Code: Select all

#option ISR_SHADOW = false
I only get around 1/2 a second of playback before it crashes.. I suspected that the extra context saving instructions that are added to the code were pushing out the time it takes to complete the ISR just enough to reduce the time available to read data from the card, causing the data rate to drop below what is required. From what i could gather from the Assembler code software context saving only adds three extra copy instructions for the context registers at the start and end of the ISR. So i set about trying to optimize the ISR code to make up for the lost time. I have changed the bit bash sub to 16 bits and removed the intermediate variable thereby saving copying two extra bytes and reducing the subroutine calls from two to one.
The ISR now completes in a shorter time (actually shorter than before) but when software shadowing is enforced the code sill fails to play properly and often just crashes as soon as the ISR fires.

New ISR code as follows..

Code: Select all

Interrupt OnTimer(1)                     // Timer ISR    
    Timer = TimerReload
    Save(0,Ser_data_out)    // context save 
    TimerInterrupt = False  // clear interrupt flag     
                                                          
    Low(AD_CS)                      //set CS line low to start TX to A/D                   
    If Buffer_status Then
        Ser_data_out(Bufferb(read_pointer))         //send word data    
    Else 
        Ser_data_out(BufferA(read_pointer))         //send word data
    End If    
    High (AD_CS)
    
    Low(AD_OUT)         //Turn on output on A/D
    High(AD_OUT)             
    If Read_pointer=255 Then        //check for end of buffer        
        If Buffer_status=false Then    //just read from a
            If Last_bufferA=true Then
                TimerOn = False           //turn off timer1
            End If
            Buffer_status=true              //switch to buffer b
        ElseIf Buffer_status=true Then      //just read from b
            If Last_bufferB=true Then
                TimerOn = False             //turn off timer1
            End If
            Buffer_status=false             //switch to buffer A
        End If       
    End If
    Inc (Read_pointer)                   //incremend pointer,  pointer rolls over to 0 on buffer change     
                
   Restore                               //context restore. 
End Interrupt 
Cheers
Lee

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: Interrupts

Post by Jerry Messina » Wed Jan 04, 2023 1:49 pm

By default, high priority interrupts use the hardware shadow registers for
context saving the WREG, STATUS, and BSR registers, and low priority interrupts
save them using software.
You can force the compiler to perform software context saving in software for both
high and low interrupts rather than using hardware shadow registers by using the
ISR_SHADOW option:

Code: Select all

#option ISR_SHADOW = false
This disables all hardware shadow register context saving.
It will add an additional 12 instruction cycles to the ISR.

Normally, with IPEN = 1 this would only effect the high-priority intr handler.
If you end up in Mid-Range Compatibility mode (IPEN=0) by declaring both a high-priority 'interrupt(2)'
and low-priority 'interrupt(1)' routines but only ever 'enable(low)', then the ISR_SHADOW option will apply to the single isr, and you'll get the add'tl 12 cycles added.

In this case, that would be the Timer ISR.
If the code can't handle the timing for the extra 12 cycles then you'll never be able to have a high-priority UART ISR added to the mix.

As janni says, it looks like you'll have to assign the timer to the HP intr and UART to the LP intr.
The execution time for your timer routine can't exceed 2 bytes times or the UART will fail.
For sustained throughput it would need to be one byte time.

What's the Ser_data_out() routine look like? Is the routine called anywhere else, or just the timer ISR?
There may be a way to optimize things such that the Save/Restore can be eliminated/reduced, and that could potentially save a bunch of time.

Post Reply