Menu

Very simple way of monitoring the Stack on a PIC.

mkstevo
2019-06-11
2020-09-01
  • mkstevo

    mkstevo - 2019-06-11

    Having been looking at using a simulator to trace a programs flow and the state of RAM without success, I started to wonder if there was another way of doing things...

    This may only apply to the 16F1825/1829, they are the chips I use most of, and they are the only ones I've looked at for the time being.

    I looked at the datasheet for the 16F1825/1829 and there is a section dedicated to the stack. It turns out that there is an internal memory location, referenced by STKPTR that maintains the current stack depth. By reading this value, it is easy to find out how many stack calls are currently on the stack, and how near to stack overflow the program is.

    This value could tested to see how close it is to the limit and then either be written to a location in Eeprom to allow reading by an external programmer, perhaps with some reference to the current routine, or made available to the 'outside world' by pulsing one of the pins of the device, assuming there is either a spare pin available to you, or an output pin that could tolerate being pulsed very quickly with out destabilising any external circuitry. These pulses could then be interpreted by an oscilloscope, or a logic analyser.

    I've knocked up some very quick pseudo code to demonstrate my thoughts: I don't promise it will work, or compile, as I'm not at work at the minute. I had some similar code running just before leaving work which did seem to be working correctly, but I have not had time to crack the 'scope out yet and see if I can read those pulses in any way. I may have program execution pause if the stack starts to get above 10 or so to allow me to monitor it better...

    #Chip 16F1829, 32
    Dim Routine, StkDepth, MaxStackDepth As Byte
    #Define StackLocation 0
    #Define RoutineLocation 1
    #Define SpareOutPin PortA.1
    Dir SpareOutPin Out
    
    Do
        MyFirstSub
        MySecondSub
    Loop
    
    Sub MyFirstSub
    Let Routine = 1
    Let StkDepth = STKPTR
    If StkDepth = 31 Then
        Let StkDepth = 0 'If STKPTR = 31 it has not been used or has overflowed
    End If
    If StkDepth > MaxStackDepth Then
        Let MaxStackDepth = StkDepth
        EpWrite(StackLocation, MaxStackDepth)
        EpWrite(RoutineLocation, Routine)
    End If
    Let SpareOutPin = 1
    Wait 10 uS
    Let SpareOutPin = 0
    Wait 20 uS
    Repeat Routine
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    Repeat StkDepth
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    'Do SubRoutine stuff here...
    MySecondSub
    
    Let Routine = 1
    Let StkDepth = STKPTR
    If StkDepth = 31 Then
        Let StkDepth = 0 'If STKPTR = 31 it has not been used or has overflowed
    End If
    If StkDepth > MaxStackDepth Then
        Let MaxStackDepth = StkDepth
        EpWrite(StackLocation, MaxStackDepth)
        EpWrite(RoutineLocation, Routine)
    End If
    Let SpareOutPin = 1
    Wait 10 uS
    Let SpareOutPin = 0
    Wait 20 uS
    Repeat Routine
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    Repeat StkDepth
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    End Sub
    
    Sub MySecondSub
    Let Routine = 2
    Let StkDepth = STKPTR
    If StkDepth = 31 Then
        Let StkDepth = 0 'If STKPTR = 31 it has not been used or has overflowed
    End If
    If StkDepth > MaxStackDepth Then
        Let MaxStackDepth = StkDepth
        EpWrite(StackLocation, MaxStackDepth)
        EpWrite(RoutineLocation, Routine)
    End If
    Let SpareOutPin = 1
    Wait 10 uS
    Let SpareOutPin = 0
    Wait 20 uS
    Repeat Routine
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    Repeat StkDepth
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    'Do different SubRoutine stuff here...
    
    Let Routine = 2
    Let StkDepth = STKPTR
    If StkDepth = 31 Then
        Let StkDepth = 0 'If STKPTR = 31 it has not been used or has overflowed
    End If
    If StkDepth > MaxStackDepth Then
        Let MaxStackDepth = StkDepth
        EpWrite(StackLocation, MaxStackDepth)
        EpWrite(RoutineLocation, Routine)
    End If
    Let SpareOutPin = 1
    Wait 10 uS
    Let SpareOutPin = 0
    Wait 20 uS
    Repeat Routine
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    Repeat StkDepth
        Let SpareOutPin = 1
        Wait 1 uS
        Let SpareOutPin = 0
        Wait 2 uS
    End Repeat
    
    Wait 20 uS
    
    End Sub
    
     

    Last edit: mkstevo 2019-06-12
  • Chris Roper

    Chris Roper - 2019-06-11

    yes, the PIC16 enhanced Core Devices and the PIC18 have that.
    You may also find these bits to be of use:

        PCON.6    STKUNF - Stack Underflow
        PCON.7    STKOVF - Stack Overflow
    

    They are set to indicte if and witch Stack error forced a device reset.

    I am not sure how much use they are in GCBASIC though as the compiler preamble may well clear them befor control is passed to a GCBASIC program.

     

    Last edit: Chris Roper 2019-06-11
    • mkstevo

      mkstevo - 2019-06-12

      They are set to indicate if and which Stack error forced a device reset.
      I am not sure how much use they are in GCBASIC though as the compiler preamble may well clear them before control is passed to a GCBASIC program.

      I had assumed they would be 'lost' on a reset, so thought they were of limited use. My main desire really, is to come up with some way of monitoring the stack, and perhaps pause execution should there be a danger of creating an overflow. If I knew which routine was being called at the time, I could then look at changing my program and it's flow of execution.

      I have very limited time for coding and this is always interrupted part way through critical sections which leads to very poorly managed code, riddled with errors and silliness. Tracking those down, sometimes days after having to leave what I was trying to do, leads to further silliness. Once written and adjusted so that it (as a minimum) compiles and (hopefully) runs, often leaves me with no coherent understanding of what is happening! This is when I'd like to be able to monitor the stack, to make sure I have not overdosed on SubRoutines. Ideally, I'd take the 'working' code, print it out and then re-write the whole thing, in one go, with no interruptions, no days away from the program, no design change requests, just time to myself so that I could get a proper handle on how it was actually working. Some hope!

       
  • George Towler

    George Towler - 2019-06-12

    oops wrong thread

     

    Last edit: George Towler 2019-06-12
  • mkstevo

    mkstevo - 2019-06-12

    This could be wrapped into a subroutine in itself...

    Which would be tidier and reduce the program size, though at the risk of adding yet more subroutines onto the stack.

    #Chip 16F1829, 32
    Dim MaxStackDepth As Byte
    #Define StackLocation   0 'Location zero in EeProm
    #Define RoutineLocation 1 'Location one  in EeProm
    #Define SpareOutPin PortA.1
    Dir SpareOutPin Out
    
    Do
        MyFirstSub
        MySecondSub
    Loop
    
    Sub MyFirstSub
    
        ShowStack(1, STKPTR) 'Calling STKPTR here avoids showing the level of the ShowStack routine
    
    'Do SubRoutine stuff here...
        MySecondSub
    
        ShowStack(1, STKPTR) 'Calling STKPTR here avoids showing the level of the ShowStack routine
    
    End Sub
    
    Sub MySecondSub
    
        ShowStack(2, STKPTR) 'Calling STKPTR here avoids showing the level of the ShowStack routine
    
        'Do different SubRoutine stuff here...
    
        ShowStack(2, STKPTR) 'Calling STKPTR here avoids showing the level of the ShowStack routine
    
    End Sub
    
    Sub ShowStack(In RoutineLevel As Byte, In StackLevel As Byte)
    
    If StackLevel = 31 Then
        Let StackLevel = 0 'If STKPTR = 31 it has not been used or has overflowed
    End If
    
    'Optionally...
    If StackLevel < 11 Then 'Only flag when stack usage very high
        Exit Sub
    End If
    
    If StackLevel > MaxStackDepth Then 'Only write to EeProm the maximum level
        Let MaxStackDepth = StackLevel
        EpWrite(StackLocation, MaxStackDepth)
        EpWrite(RoutineLocation, RoutineLevel)
    End If
    
    Do
        Let SpareOutPin = 1
        Wait 10 uS
        Let SpareOutPin = 0
        Wait 20 uS
        Repeat RoutineLevel
            Let SpareOutPin = 1
            Wait 1 uS
            Let SpareOutPin = 0
            Wait 2 uS
        End Repeat
    
        Wait 20 uS
    
        Repeat StackLevel
            Let SpareOutPin = 1
            Wait 1 uS
            Let SpareOutPin = 0
            Wait 2 uS
        End Repeat
    
        Wait 20 uS
    
     Loop Until StackLevel < 14 
     'The stack level is tested before this subroutine.
     'This sub will add another level to the stack so if
     'it was 14 on calling this routine, it will be 15 now.
     'And if the stack is 15 (for the 16F1829), we've run out!
    
    End Sub
    
     

    Last edit: mkstevo 2019-06-12
  • mkstevo

    mkstevo - 2019-06-12

    I put the original code into a program I am currently working on at work today, and was pleased to see that I could count the pulses reasonably well. If the second code was used and set to monitor for very high stack levels only, the scope could be set to capture a single event then the 'information' could more easily be read and decoded.

    I personally will be able to put this to good use.

     
  • SPostma

    SPostma - 2020-08-31

    Another suggestion could be to fill the TOSH value for all stack entries to 0xFF upon startup.
    Later on, you can check the highest stack entry that has TOSH <> 0xFF to determine how many stack places have been used as a maximum since the processor was started.
    (remember to disablethe global interrupts when manipulating the hardware STKPTR register).

    This approach is like the fences around allocated memory areas in C to find memory leaks...

     
  • mkstevo

    mkstevo - 2020-09-01

    I like that idea! I'll have to give that a go. Thanks for the suggestion.

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.