News:

Let's find out together what makes a PIC Tick!

Main Menu

Mixing Bit Variables and Port Bits

Started by JohnB, Mar 31, 2024, 02:19 PM

Previous topic - Next topic

JohnB

Dim HeatDemand As Bit
Dim PumpDemand As Bit
Dim HeatingRelay as PortB.5

This always return 0....
HeatingRelay = HeatDemand & PumpDemand

whereas this reflects the anded states of Heat and pump Demands

Dim TmpBit As Bit
TmpBit = HeatDemand & PumpDemand
HeatingRelay = TmpBit

Is this an known issue or is it just my lack of knowledge?

JohnB

trastikata

#1
Hello JohnB,

Quote"HeatingRelay = HeatDemand & PumpDemand"

I think this is an anomaly, because Les is using the STATUS regitser Z-bit to check if the addition to the working register resulted in 0. However the btfss instruction (Skip Next Instruction if Bit is Set) will skip addition to the WREG register and the initial loading 0 to the WREG "movlw 0" instruction doesn't set the STATUS.2 (Z:Zero-bit) since movlw does not affect the STATUS Register .

So STATUS.2 (The Zero-bit) never get set and PORTB.5 bit is always cleared ... actually the end result will depend on the previous state of the STATUS.2 bit, and if it had been set before the current operation, you will have the correct result, although for the wrong reason.

Whereas in the second code example you gave, the Assembler code uses exactly the same principle, but instead of evaluating the STATUS.2 bit, the TempBit is initially loaded with "1" and further evaluated by the exactly same mechanism. Thus the initial bit set will give the correct result.


; BIT HOLDER VARIABLES
_B__VR1 equ 0x00
; ALIAS VARIABLES
#define HeatDemand _B__VR1,0
#define PumpDemand _B__VR1,1
#define HeatingRelay PORTB,5

F1_000009 equ $ ; in [TEST.BAS] HeatingRelay = HeatDemand & PumpDemand
    movlw 0              --> Move 0 to WREG
    btfss _B__VR1,0,0    --> Skip next instruction if bit 0 (HeatDemand) is set
    addlw 1              --> Skip this instruction because bit 0 (HeatDemand) is set
    btfss _B__VR1,1,0    --> Skip next instruction if bit 1 (PumpDemand) is set
    addlw 1              --> Skip this instruction because bit 1 (PumpDemand) is set
    btfsc STATUS,2,0      --> Skip next instruction if STATUS.2 (STATUS register Z:Zero bit) is clear
                          --> STATUS.2 (STATUS register Z:Zero bit) is [b]NOT Set[/b] because although WREG is 0, no addition has been carried out
    bsf PORTB,5,0        --> Set PORTB.5 is [b]NOT executed[/b] because of previous instruction
    btfss STATUS,2,0      --> Skip next instruction if STATUS.2 (STATUS register Z:Zero bit) is set
                          --> STATUS.2 (STATUS register Z:Zero bit) is [b]NOT Set[/b] because although WREG is 0, no addition has been carried out
    bcf PORTB,5,0        --> [b]Clear PORTB.5 is executed[/b] because of previous instruction




kcsl

Off topic and I don't want to hijack this thread, but looking at John's code has raised a question for me.
What's the difference between:

Dim HeatingRelay as PortB.5

and

SYMBOL HeatingRelay = PortB.5


I personally never use DIM like this so I'm wondering what I'm missing out on?

Regards,
Joe


There's no room for optimism in software or hardware engineering.

trastikata

Hello kcsl,

there's no difference, both are producing the same code.

kcsl

There's no room for optimism in software or hardware engineering.

top204

#5
Well spotted John, and nicely analysed trastikata.

The assembler code produced by the compiler is different if the bits do not share a RAM bank, or a Port is the assignment, otherwise, the Port's bit would be set one way, then set the other when the anding result was known.

I have re-written the assembler code for a Port or a group of bit variables not sharing a RAM bank, and the compiler now produces the assembler code:

F1_000050 equ $ ; in [TEST_18F25K20.BAS] PinOut = MyBit1 & MyBit2
    movlw 1
    btfss _B__VR1,0,0
    clrf WREG,0
    btfss _B__VR1,1,0
    clrf WREG,0
    btfsc WREG,0,0
    bsf PORTB,2,0
    btfss WREG,0,0
    bcf PORTB,2,0

For bits not in the same RAM bank, or a PORT or LAT is used as the assignment. Running tests, it works nicely.

If all the bits used in the expression are all in the same RAM bank, and the assignment is not a Port or Lat, the compiler optimises the code and produces:

F1_000049 equ $ ; in [TEST_18F25K20.BAS] BitOut = MyBit1 & MyBit2
    bsf _B__VR1,2,0
    btfss _B__VR1,0,0
    bcf _B__VR1,2,0
    btfss _B__VR1,1,0
    bcf _B__VR1,2,0

See how, if the assignment was a Port and using the assembler code above, it would set the Port (_B__VR1,2), then clear it after it has tested the bits, thus causing it to give a "blip", thus the different assembler codes.

Within the compiler, the source code below is used for Anding two bit variables with the assignment also being a bit:

if((AddressOfVarin1 == AddressOfVarout) && (pBitin1 == pBitout) && (CompareBanks(pVarin1, pVarin2) == true))
    {
        btfss(pVarin2, pBitin2);
        bcf(pVarin1, pBitin1);
    }
else if((Is_Port_Or_Lat(pVarout) == true) || ((CompareBanks(pVarin1, pVarin2) == false) && (CompareBanks(pVarin1, pVarout) == false)))
    {
        num_wreg(1);
        Mbtfss(pVarin1, pBitin1);
        clrw();
        Mbtfss(pVarin2, pBitin2);
        clrw();
        wreg_bit(pVarout, pBitout);
    }
else
    {
        Mbsf(pVarout, pBitout);
        Mbtfss(pVarin1, pBitin1);
        Mbcf(pVarout, pBitout);
        Mbtfss(pVarin2, pBitin2);
        Mbcf(pVarout, pBitout);
    }

I have a corrections update coming soon, so the changes will be in that.

JohnB

On a slightly different vein
TmpBit, HeatDemand, PumpDemand and HeatonSched are all bit variables:

PumpDemand = 1
HeatDemand = 1
HeatOnSched = 0

TmpBit = HeatDemand & PumpDemand & HeadOnSched ' Results in 1 - Incorrect
 
TmpBit = HeatDemand & PumpDemand
TmpBit = TmpBit & HeatOnSched  ' Results in 0 - Correct.

Is it not possible and more than one bit at a time.
Would it be better if I was working in bytes and doing my own masking
JohnB

top204

#7
In a way, it is sometimes better to use a Byte variable instead of a Bit variable. Especially when using boolean operators, because there are no assembler mnemonics for individual bit booleans, but there are for byte booleans, so the compiler does not have to workaround. However, that is not always the case, and sometimes the compiler will use the WREG to load a value into, then use the boolean mnemonics on it. Especially with enhanced 14-bit core devices and 18F devices, because they have WREG as an SFR.

For example, replacing the Bit variables with Byte variables, the assembler code becomes:

F1_000038 equ $ ; in [TEST_18F25K20.BAS] BitOut = ByteIn1 & ByteIn2
    movf ByteIn1,W,0
    andwf ByteIn2,W,0
    bsf _B__VR1,0,0
    btfss WREG,0,0
    bcf _B__VR1,0,0
F1_000039 equ $ ; in [TEST_18F25K20.BAS] PinOut = ByteIn1 & ByteIn2
    movf ByteIn1,W,0
    andwf ByteIn2,W,0
    btfsc WREG,0,0
    bsf PORTB,2,0
    btfss WREG,0,0
    bcf PORTB,2,0

For the BASIC code layout:
    Symbol PinOut = PORTB.2
   
    Dim ByteIn1 As Byte
    Dim ByteIn2 As Byte
    Dim BitOut As Bit
                        
    BitOut = ByteIn1 & ByteIn2
    PinOut = ByteIn1 & ByteIn2

You can see, now the compiler can use the Andwf mnemonic, instead of having to skip bits to simulate And. And instead of loading the bytes with 1 or 0, you can load them with 255 and 0, where 255 is a boolean value for a set bit. Then the compiler will use the Setf and Clrf mnemonics for a bit set and a bit clear, again speeding things up and making smaller code. But the expression with the Bit as the assignment still takes 5 mnemonics, so it that case, Bit variables are better because they use less RAM and are always held in Access RAM on 18F devices.

However, it still has to create the code so the Port is not prematurely altered before the And is complete when it is the assignment, and this will always take a few more mnemonics.

My personal choice is to use a temporary Bit variable, or an If-Else-Endif comparison for Port . Pin manipulations wherever possible, because it gives more control.

trastikata

#8
Hello Les,

I have an unrelated question - when I was looking through the resulting Assembler code, I noticed that although the port pin is initialized as an output, the corresponding TRIS bit is cleared every time the port pin is being set or cleared using High or Low directive resulting in some overhead, is this clearing the TRIS bit intentional to make sure High and Low directives are always working?

F1_000008 equ $ ; in [TEST.BAS] Output PORTB.5
    bcf TRISB,5,0
F1_000010 equ $ ; in [TEST.BAS] A = 5
    movlw 5
    movwf _A,0
F1_000011 equ $ ; in [TEST.BAS] high Port
    bcf TRISB,5,0
    bsf LATB,5,0
F1_000012 equ $ ; in [TEST.BAS] B = A - 2
    movlw 2
    subwf _A,W,0
    movwf _B,0
F1_000013 equ $ ; in [TEST.BAS] low Port
    bcf TRISB,5,0
    bcf LATB,5,0
F1_000014 equ $ ; in [TEST.BAS] A = B + 7
    movlw 7
    addwf _B,W,0
    movwf _A,0
F1_000015 equ $ ; in [TEST.BAS] high Port
    bcf TRISB,5,0
    bsf LATB,5,0
F1_000017 equ $ ; in [TEST.BAS] end

JohnB

Thanks Les, that's very informative
JohnB

top204

#10
The compiler has no way of knowing and remembering that a Port or Pin has been set as an Output or Input or Low or High, So the PinLow and PinHigh commands always manipulate the TRIS SFR for the pin.

Once a user knows that the pin is an output, they can then use the PinSet or PinClear commands, because these do not touch the TRIS SFR and only the PORT or LAT pin. Within programs I write, I always setup the pins as High or Low or Input or Output at the beginning of a program, or within an initialisation subroutine or procedure, then use the PinSet or PinClear commands whenever possible. But sometimes a pin needs to be set as an Input within the code, and this is where the mandatory manipulation of the TRIS comes into play when it needs to be set High or Low as well elsewhere in the code.

top204

#11
John... You could alias your bits to a variable and use a mask to isolate them, but this would take a bit more code memory space.

Something like:

    Symbol PinOut = PORTB.2
    Symbol cBits0and1_Mask = %00000011

    Dim MyByte As Byte
    Dim BitIn0 As MyByte.0
    Dim BitIn1 As MyByte.1

    PinLow PinOut
    If (MyByte & cBits0and1_Mask) = 3 Then
        PinSet PinOut
    Else
        PinClear PinOut
    EndIf

This is the way C and C++ have to do it, and it is rather inefficient. However, sometimes it is a necessary method if more than 1 or 2 bits are needed to be checked in a single instance. But for 1 or 2 bit checks, using the bits themselves, or the Bit mechanisms of the compiler is better, when possible (In My Opinion).