News:

PROTON pic BASIC Compilers for PIC, PIC24, dsPIC33

Main Menu

Positron Compilers: Static Variables

Started by top204, Nov 29, 2021, 01:00 PM

Previous topic - Next topic

top204

To try and take my mind off the freezing cold over the weekend because of the dreadful weather, I added a very useful directive to the compilers. I've had it in mind for quite a while, so I worked night and day on it, because with the cost of Gas now, here in the UK, we cannot afford to have the heating on too much, so I have to keep my mind pre-occupied. :-(

Variables can now be made as Static types when they are created. This means that a variable that is given an initial constant value, will not use that value in the position where it is within the code. It will load the variable with the constant value before the user's code is started.

I know this sounds a strange concept, but it comes in very handy sometimes, and is in most other good procedural languages. For example, a local variable can be given an initial "default" value when it is created, but that value will only be loaded into it once and it will not be loaded into the variable everytime it comes into the program's flow.

For example, the code below is a good method of detecting a single button press and not blocking the program's flow. I designed it especially to show the practicalities of Static  variables and it works well. It keeps a record of the button pin's condition as a bit within a large variable type, so it can hold 32 button states that must initially have a default value of 0 (no buttons pressed). This allows multiple button operations to be detected, and still detect a single press/release on each of them. In order to make the storage variable a local type, but not have it being loaded with zero everytime the procedure is called, the variable is made a Static  type and loaded with 0:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A simple button press procedure that detects a single button press
' and does not detect it again until the button is released and pressed again
'
' No blocking is performed within the program's flow while waiting for button modes
'
' Written for the Positron compilers by Les Johnson
'
    Device = 18F25K20
    Declare Xtal = 64
'
' Setup USART1
'
    Declare Hserial_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6

$define True 1
$define False 0

'----------------------------------------------------------------
' The main program starts here
' Transmits to a serial terminal when a button is pressed
' But does not continuously transmit while the button is pressed
'
Main:
    Do                                                  ' Create a loop
        If Button_Get(Pin_A1) = True Then               ' Has the button connected to RA1 been activated?
            HRSOutLn "Button on RA1 Pressed"            ' Yes. So transmit a message to the serial terminal

        ElseIf Button_Get(Pin_A2) = True Then           ' Has the button connected to RA2 been activated?
            HRSOutLn "Button on RA2 Pressed"            ' Yes. So transmit a message to the serial terminal

        ElseIf Button_Get(Pin_A3) = True Then           ' Has the button connected to RA3 been activated?
            HRSOutLn "Button on RA3 Pressed"            ' Yes. So transmit a message to the serial terminal

        EndIf
    Loop                                                ' Do it forever

'----------------------------------------------------------------
' Detect a button press
' Input     : pButton holds the pin number that the button is attached too (0 to 31)
' Output    : Returns 1 if a button press is detected
' Notes     : Once the button has been detected and a result returned,
'             it will not detect the button again until it has been released
'           : The bits within the variable "dStateBits" hold the previous states of the pins
'           : For active low button presses with a pull-up resistor
'
Proc Button_Get(pButton As Pin), Bit
    Symbol cPressed = 0                                 ' The value for a button pressed
    Symbol cNotPressed = 1                              ' The value for a button not pressed
Static Dim dStateBits As Dword = 0                      ' Create, and reset, a static variable to hold the states of 32 pins

    PinInput pButton                                    ' Set the button's pin as an input (just in case)
    Result = 0                                          ' Default to a false result (no button pressed)

    If GetBit(dStateBits, pButton) = 0 Then             ' Has the button already been pressed?
        If GetPin(pButton) = cPressed Then              ' No. So is the button pressed?
            Result = 1                                  ' Yes. So return a true result
        EndIf
        LoadBit dStateBits, pButton, Result             ' Store the button's previous state
    Else                                                ' Otherwise... The button has not been previously pressed
        If GetPin(pButton) = cNotPressed Then           ' So... Is the button pressed?
            ClearBit(dStateBits, pButton)               ' No. So reset the previous button state
        EndIf
    EndIf
EndProc

'-------------------------------------------------------------
' Setup the fuses on a PIC18F25K20 device, for the 4xPLL with an external crystal
'
Config_Start
    FOSC = HSPLL        ' HS oscillator, PLL enabled and under software control
    Debug = Off         ' Background debugger disabled' RB6 and RB7 configured as general purpose I/O pins
    XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    STVREN = Off        ' Reset on stack overflow/underflow disabled
    WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
    FCMEN = Off         ' Fail-Safe Clock Monitor disabled
    IESO = Off          ' Two-Speed Start-up disabled
    WDTPS = 128         ' Watchdog is 1:128
    BOREN = Off         ' Brown-out Reset disabled in hardware and software
    BORV = 18           ' VBOR set to 1.8 V nominal
    MCLRE = On          ' MCLR pin enabled, RE3 input pin disabled
    HFOFST = Off        ' The system clock is held Off until the HF-INTOSC is stable.
    LPT1OSC = Off       ' T1 operates in standard power mode
    PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
    CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
    LVP = Off           ' Single-Supply ICSP disabled
    Cp0 = Off           ' Block 0 (000800-001FFFh) not code-protected
    CP1 = Off           ' Block 1 (002000-003FFFh) not code-protected
    CPB = Off           ' Boot block (000000-0007FFh) not code-protected
    CPD = Off           ' Data eeprom not code-protected
    WRT0 = Off          ' Block 0 (000800-001FFFh) not write-protected
    WRT1 = Off          ' Block 1 (002000-003FFFh) not write-protected
    WRTB = Off          ' Boot block (000000-0007FFh) not write-protected
    WRTC = Off          ' Configuration registers (300000-3000FFh) not write-protected
    WRTD = Off          ' Data eeprom not write-protected
    EBTR0 = Off         ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
    EBTR1 = Off         ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
    EBTRB = Off         ' Boot block (000000-0007FFh) not protected from table reads executed in other blocks
Config_End

Notice, within the "Button_Get" procedure, the Static  variable "dStateBits" is loaded with 0? But that zero will not be loaded into the variable at that position, it will be loaded into it before the main program starts, so it is given a default value. This can easily be seen in the assembler code, because I have commented its section for clarity to the users:

;---------------------------------------------
; START OF THE USER'S PROGRAM CODE
;---------------------------------------------
; STATIC VARIABLE ASSIGNMENTS
    clrf Button_GetdStateBitsHHH,0
    clrf Button_GetdStateBitsHH,0
    clrf Button_GetdStateBitsH,0
    clrf Button_GetdStateBits,0

The program above will not keep transmitting while a button is pressed, it will transmit once. Then once the button is released, it will transmit again if a button is pressed, but not block any program flow while waiting for button movements. :-) It may need some small DelayMs commands in the procedure for simple debouncing, but I found it worked well without them.

JonW

I really like how you have called the procedure in an if statement, I didn't think that was possible but I guess it simply resolves to the Boolean value of procedures result.

So at what point in the code is the initial constant value written, is this prior to the main execution loop and the same point as declaring a global variable at the start of the code and writing a constant to it in the setup, prior to the main execution?  Very powerful for code re-use cases and makes the code really easy to read.

top204

#2
The Static  variables are assigned their values before the user's code starts.

Procedure calls can be issued within comparisons and expressions.

Boolean is one of those words that some people use literally as if it is special, when in fact it means "on" or "off", in real hardware terms. i.e. A Bit holding 1 or 0. Some microcprocessors cannot work with individual bit values, so a Boolean to them is 0 or not 0, but it still relates to "on" or "off". That is when a Boolean must relate to the texts: true or false, because false is the alias holding 0 and true is the alias holding whatever the "on" state value is for a 1 with that language.

atomix

Les how to deal with such a code, it will not work correctly.

Device 18F46K22
Declare Xtal 64

clear

wreg = my()     ' return must be 6

proc my(), byte
    Static dim a1 as byte = 5
    a1 = a1 + 1
endproc

RGV250

Hi,
I am having trouble getting my head around this, probably due to lack of beer.
If I want a variable to have anything other than zero at start I do
DIM ThisVariable as Word = 10
how is the static variable any different?

Regards,
Bob

John Lawton

This time of day, i find coffee is more helpful :)

John

joesaliba

@Bob (G8GFA)

Hope I got this one good.

Inside the procedure, variables are local.

Hence with Static, if you want a local variable to be loaded with a certain value at start-up, i.e. in your example Static DIM ThisVariable as Word = 10, will load ThisVariable with 10, but once you re-entered that procedure it will not load again 10.

@atomix Could it be that Clear in your code cleared all variables? Les knows what Clear do exactly with local and global variables.

Regards

Joe 

top204

#7
The Clear command, on its own, is now obsolete, and not required, and was originally created about 18 years ago for the first generation PIC microcontrollers, and to keep compatability with the interpreted BASIC Stamp syntax.

The Clear command, on its own, will reset the contents of all User RAM to 0. i.e. Static  variables, and values assigned to standard Dim variables. That is why it now has a reminder (Hint) issued when it is used in a program. I will be changing this to a Warning message.

To reset variables, it is always better to assign to them when they are first created, or within a Setup procedure. Not all variables need to be initially cleared in a program, so only the ones required should be given a default value. To clear Arrays or Strings, use the Clear command followed by the variable name. For example: Clear MyArray, will clear all of the elements in MyArray.

I would actually like to get rid of the Clear command altogether, on its own. Or set it as ignored if it is not followed by an operand to reset, but I must keep some backward compatability, otherwise it causes grief for me, even when the older commands cause grief in a program because they should not be used. :-)

Please remember, I began writing the compilers when PIC microcontrollers were relatively new, and very limited in what they could do, and that was about 18 years ago. In a language such as C, the Clear command would actually be a library function and would now be an empty function, that would not cause a syntax error, but would not perform any task. That is common with C as the devices mature.

Also, your procedure, in the code snippet, is not loading the return variable with a value, so it will return whatever was in that RAM position at the time the device powered up, or because the Clear command has been used on its own, it will return 0. The Result directive is purely optional, so the compiler does not give a warning or error if it is not used within a procedure that has a return parameter.

top204

#8
That's correct Joe. The variable will not be given the value where it is placed in a program, it will be given the value when the program first starts.

Static type variables are standard practice in most good procedural languages, and are things that, at first, do not look so important. But when you have a procedure that needs an initial value held in a local variable, but not loaded into it every time the procedure is called, they come into a world of their own.

It's, somewhat, the equivalent of creating a Global variable that a procedure will use, and assigning a default value to it when it is created. The procedure will never see that value loaded into it again, because it was created outside the procedure and holds a default value, but it is still a local type, and only seen by the procedure it is within, and only created if the procedure is called from within a program.

top204

#9
With your post Atomix, it triggered something inside my head that was: "Why didn't I think of this before?". :-)

I have made the Clear command into a directive, so it does not clear the RAM from the position it is at within the program, but initialises a piece of code that clears the RAM before the user program starts. So it will not clear pre-assigned variables or Static  variables, but will still clear all RAM to 0 when the device first powers up or resets.

The Clear command was created many, many years ago. Before the variables could be assigned too when they are created, so it was meant to clear RAM before the variables were given values. However, over the years, the compiler has evolved and the extra features added are not always compatible with some of the existing commands, so I will change them if I can, to suit the new compiler features, and not, simply, remove them. Unless there is no alternative.