News:

;) This forum is the property of Proton software developers

Main Menu

Manipulate stack of PIC12F1822

Started by charliecoutas, Dec 08, 2023, 11:09 AM

Previous topic - Next topic

charliecoutas

Hi all

I am trying to use a 12F1822 and am struggling: When an interrupt occurs, no matter what else is happening, I want to go to a certain label and effectively start again.

Something like:

On_interrupt int

Here_next: blah blah blah
   other code

int:   Context Save
         Fiddle around with input from PORTA, set a few things
         Clear stack pointer
         Push address of "Here_next" on the stack
       Context Restore

Any help gratefully received.
Charlie

tumbleweed

Instead of bothering with the context and trying to manipulate the stack, you could just branch to 'Here_next', something like:

Device = 12F1822
Declare Xtal = 4

On_Hardware_Interrupt GoTo ISR_Handler

' initial main loop
main:
Low PORTA.0
Low PORTA.1
INTCONbits_GIE = 1  ' Enable all interrupts
Do
    Toggle PORTA.0
    DelayMS 100
Loop


'ISR
ISR_Handler:
    ' when you get here interrupts are disabled (GIE = 0)
    ' clear the intr flag for whatever got you here...
    INTCONbits_INTF = 0         ' as an example

    'Fiddle around with input from PORTA, set a few things

    ' instead of returning go to 'Here_next'
    STKPTR = 0x1F            ' clear the stack
    GoTo Here_next
    ' or, another way instead of GOTO would be to set the PC directly...
    PCLATH = AddressOf(Here_next) >> 8
    PCL  = AddressOf(Here_next)
    ' never get here...
    Nop

' destination after interrupt... new main loop
' when you get here the stack is empty and interrupts are disabled
Here_next:
    INTCONbits_GIE = 1  ' re-enable all interrupts (assuming you really want to do this...)

Do
    Toggle PORTA.1
    DelayMS 50
Loop

Since you never return from the ISR_Handler you don't need to save the context, or to return via RETFIE.
It gets a little more complicated if you actually want to use other interrupts too, but not much... just wrap the above with a test for the IF and IE flags to be true.

charliecoutas

Many thanks Tumbleweed, a straight "goto new_address" works a treat.

Charlie

kcsl

I'm curious, Won't the original return address stay on the stack and eventually cause a stack overflow since it's never popped ?

Looking at the datasheet for this PIC is says:

8.5 Automatic Context Saving
Upon entering an interrupt, the return PC address is
saved on the stack. Additionally, the following registers
are automatically saved in the shadow registers:

and

2.2 16-Level Stack with Overflow and Underflow
These devices have an external stack memory 15 bits
wide and 16 words deep. A Stack Overflow or Underflow will set the appropriate bit (STKOVF or STKUNF)
in the PCON register, and if enabled will cause a software Reset.
There's no room for optimism in software or hardware engineering.

Dompie

In this kind of situation I set a Flag in the interrupt routine and in my Main_Loop I test the flag and jump to my start label as I know there are no more subroutines running at this label test point.

Johan

tumbleweed

#5
QuoteI'm curious, Won't the original return address stay on the stack and eventually cause a stack overflow since it's never popped ?
Before doing the GOTO, the stack is reset to its PON setting by the statement 'STKPTR = 0x1F'.
This has the effect of "popping" everything off the stack, so the stack is seen as empty.
The hardware context info is just left there to be overwritten since there was never a RETFIE instruction.

From datasheet Fig 3-4:
QuoteInitial Stack Configuration: After Reset, the stack is empty. The empty stack is initialized so the Stack Pointer is pointing at 0x1F

One thing you may have to worry about once you do the "goto new_address" is the setting of the BSR bank select register.
Doing this is likely to confuse the compiler's bank select tracking, so you may have to do something to get it back on track.


QuoteIn this kind of situation I set a Flag in the interrupt routine and in my Main_Loop I test the flag and jump to my start label
The OP stated "When an interrupt occurs, no matter what else is happening, I want to go to a certain label", and just setting a flag for the main loop to handle won't accomplish that.

I should note that by doing this your hardware may be left in some unknown state if you get interrupted right in the middle of doing something with a peripheral, so it's up to you to re-init anything that it might need to. I never said doing this was a good idea...
 
 


charliecoutas

After playing around for a while I tried a Reset inside the interrupt routine. It worked and of course the stack pointer, bank addresses etc are all reset to known values. You can tell if it's a software triggered reset by looking at the PCON register bit 2. If this is clear then it's a software Reset.

This also resets interrupt enables, interrupt-on-change, tris etc so everything needs setting up again. But it does the job, not very elegant perhaps.

Charlie

tumbleweed

Quote...not very elegant perhaps.
That's actually probably your best option. That way you know that all the internals of the pic are reset, so the only thing you might have to deal with is the state of any external hdw (LCD's, I2C, etc).

I didn't realize that series supported a RESET instruction. Good to know!

trastikata

Quote from: tumbleweed on Dec 10, 2023, 05:17 PMThat's actually probably your best option. That way you know that all the internals of the pic are reset....

I remember reading about different "reset" options, the MC guide says that software reset won't reset RAM and some SFR's and will not reinitialize hardware modules. Is it certain that the stack will reset at software reset?

tumbleweed

Quote from: trastikata on Dec 10, 2023, 10:58 PMI remember reading about different "reset" options, the MC guide says that software reset won't reset RAM and some SFR's and will not reinitialize hardware modules. Is it certain that the stack will reset at software reset?
Nothing resets RAM. It's up to the code to do that if that's important.

All of the datasheets include a SPECIAL FUNCTION REGISTER SUMMARY that outlines how registers are initialized. For the 12F1822 it's in TABLE 3-8.
That table will show you the reset state of all peripheral/SFR registers. The peripherals will be reset to the register settings from that table, and it's up to your code to set them up again.

There are a few registers that are left unchanged by certain resets, but they are normally ones that would be reinitialized by the software anyway or are don't care.
In this case, the STKPTR is reset to 0x1F for all reset sources (bottom of pg 37).

The main thing you may have to watch out for is the state of external hardware since that's untouched and will left in whatever state it was in when the interrupt occurred and the RESET instruction was executed.





Stephen Moss

I am probably missing something here as I can never quite get my head fully around how the Stack works and this solution seems too simple, but...
If you don't include context save/restore in your ISR would not the last item on the stack then be the return address that code execution would normally returns to when the ISP exits?. If so, then within the ISR could you not put a POP command to remove it and then a PUSH command to place the desired return address on to the stack so that code execution resumes at the desired point when ISR terminates instead.

Surely identifying the address location within the stack of the return address pushed onto it by an interrupt and putting the "new" return address into that location would be better than trashing the entire stack.   

kcsl

Quote from: Stephen Moss on Dec 11, 2023, 09:10 AMI am probably missing something here as I can never quite get my head fully around how the Stack works and this solution seems too simple, but...
If you don't include context save/restore in your ISR would not the last item on the stack then be the return address that code execution would normally returns to when the ISP exits?. If so, then within the ISR could you not put a POP command to remove it and then a PUSH command to place the desired return address on to the stack so that code execution resumes at the desired point when ISR terminates instead.

Surely identifying the address location within the stack of the return address pushed onto it by an interrupt and putting the "new" return address into that location would be better than trashing the entire stack. 

I thought exactly that, but then I looked at the CPU instruction set in the datasheet. There doesn't appear to be any instructions to manipulate the stack.
There's no room for optimism in software or hardware engineering.

tumbleweed

#12
Normally, when you get an interrupt the return address is automatically pushed onto the stack for you. 'Context save' is just to save some variable states... with a pic these don't go on the stack, they get saved in normal ram. 'Context restore' would restore these variables/registers and then execute a RETFIE instruction, which pops the return address off the stack and re-enables interrupts.

In the case we're talking about here, the ISR never sees a RETFIE ('context restore'), so it never returns. The return address that was pushed onto the stack remains there but since there's no return it will just be overwritten by future pushes.

There are no direct "push" and "pop" instructions, but the stack can be accessed through the TOSH, TOSL and STKPTR registers. For the 12F1822, datasheet section 3.4 describes how it works.

If you were to actually look at the contents of the stack itself you'd see all the old addresses that were pushed prior to the interrupt since the stack is never really cleared, it's just the pointer register that gets set.

The RESET instruction sets the STKPTR to 0x1F (same as what the hardware sets it to at PON) which indicates the stack is empty, which is what you want since all the return addresses on the stack aren't used anymore... you really DO want to "trash the entire stack".