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...
#Chip16F1829,32DimRoutine,StkDepth,MaxStackDepthAsByte#DefineStackLocation0#DefineRoutineLocation1#DefineSpareOutPinPortA.1DirSpareOutPinOutDoMyFirstSubMySecondSubLoopSubMyFirstSubLetRoutine=1LetStkDepth=STKPTRIfStkDepth=31ThenLetStkDepth=0'If STKPTR = 31 it has not been used or has overflowedEndIfIfStkDepth>MaxStackDepthThenLetMaxStackDepth=StkDepthEpWrite(StackLocation,MaxStackDepth)EpWrite(RoutineLocation,Routine)EndIfLetSpareOutPin=1Wait10uSLetSpareOutPin=0Wait20uSRepeatRoutineLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSRepeatStkDepthLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uS'Do SubRoutine stuff here...MySecondSubLetRoutine=1LetStkDepth=STKPTRIfStkDepth=31ThenLetStkDepth=0'If STKPTR = 31 it has not been used or has overflowedEndIfIfStkDepth>MaxStackDepthThenLetMaxStackDepth=StkDepthEpWrite(StackLocation,MaxStackDepth)EpWrite(RoutineLocation,Routine)EndIfLetSpareOutPin=1Wait10uSLetSpareOutPin=0Wait20uSRepeatRoutineLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSRepeatStkDepthLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSEndSubSubMySecondSubLetRoutine=2LetStkDepth=STKPTRIfStkDepth=31ThenLetStkDepth=0'If STKPTR = 31 it has not been used or has overflowedEndIfIfStkDepth>MaxStackDepthThenLetMaxStackDepth=StkDepthEpWrite(StackLocation,MaxStackDepth)EpWrite(RoutineLocation,Routine)EndIfLetSpareOutPin=1Wait10uSLetSpareOutPin=0Wait20uSRepeatRoutineLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSRepeatStkDepthLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uS'Do different SubRoutine stuff here...LetRoutine=2LetStkDepth=STKPTRIfStkDepth=31ThenLetStkDepth=0'If STKPTR = 31 it has not been used or has overflowedEndIfIfStkDepth>MaxStackDepthThenLetMaxStackDepth=StkDepthEpWrite(StackLocation,MaxStackDepth)EpWrite(RoutineLocation,Routine)EndIfLetSpareOutPin=1Wait10uSLetSpareOutPin=0Wait20uSRepeatRoutineLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSRepeatStkDepthLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSEndSub
Last edit: mkstevo 2019-06-12
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
#Chip16F1829,32DimMaxStackDepthAsByte#DefineStackLocation0'Location zero in EeProm#DefineRoutineLocation1'Location one in EeProm#DefineSpareOutPinPortA.1DirSpareOutPinOutDoMyFirstSubMySecondSubLoopSubMyFirstSubShowStack(1,STKPTR)'Calling STKPTR here avoids showing the level of the ShowStack routine'Do SubRoutine stuff here...MySecondSubShowStack(1,STKPTR)'Calling STKPTR here avoids showing the level of the ShowStack routineEndSubSubMySecondSubShowStack(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 routineEndSubSubShowStack(InRoutineLevelAsByte,InStackLevelAsByte)IfStackLevel=31ThenLetStackLevel=0'If STKPTR = 31 it has not been used or has overflowedEndIf'Optionally...IfStackLevel<11Then'Only flag when stack usage very highExitSubEndIfIfStackLevel>MaxStackDepthThen'Only write to EeProm the maximum levelLetMaxStackDepth=StackLevelEpWrite(StackLocation,MaxStackDepth)EpWrite(RoutineLocation,RoutineLevel)EndIfDoLetSpareOutPin=1Wait10uSLetSpareOutPin=0Wait20uSRepeatRoutineLevelLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSRepeatStackLevelLetSpareOutPin=1Wait1uSLetSpareOutPin=0Wait2uSEndRepeatWait20uSLoopUntilStackLevel<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!EndSub
Last edit: mkstevo 2019-06-12
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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...
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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...
Last edit: mkstevo 2019-06-12
yes, the PIC16 enhanced Core Devices and the PIC18 have that.
You may also find these bits to be of use:
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
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!
oops wrong thread
Last edit: George Towler 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.
Last edit: 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.
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...
I like that idea! I'll have to give that a go. Thanks for the suggestion.