Setting multiple BITS more efficiently

mortifyu

New Member
Hi Guru's

Here I have a scenario in which I need to read specific BYTE's from an external LED&Keyscan matrix chip (Holtek - HT16K33), make alterations to individual BITS of the read BYTE and then write the BYTE back to the HT16K33. For understanding, there are parts of two different BYTES making up a row of 6 LED's.


The following CODE does work flawlessly, however each SETBIT line burns 3 BYTES of program space and CLEARBIT lines burn 7 BYTES. Is there a more program space efficient way I can do this?

It'd be nice if I could essentially say:

setbit Temp_Byte,{1st BIT},{2nd BIT},{3rd BIT},etc..

or better yet,
setbit Temp_Byte,(%10101010) in the hope of combining SETBIT and CLEARBIT in one command line, but obviously this does not work.

Any ideas spring to mind?


Code:
HI2cSetup I2CMASTER, $E0, I2CFAST, I2CBYTE    'I2C Communication for HT16K33 - I2C Address $E0

'Initialize and setup HT16K33
hi2cout [$E0],($21)    'Turn on Oscillator
pause 2
hi2cout [$E0],($EF)    'Set LED's Brightness ($E0 = min), ($EF = max)
pause 2
hi2cout [$E0],($81)    'Turn on Display with no blinking

Symbol Temp_Byte = b0
Symbol ADC_Read = b1
Symbol ADC_In = C.2


readadc ADC_In, ADC_Read

if ADC_Read>=206 then
    HI2CIN [$E0],$01,(Temp_Byte)
    setbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    setbit Temp_Byte,3
    setbit Temp_Byte,2
    setbit Temp_Byte,1
    setbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
elseif ADC_Read<206 and b9>=192 then
    HI2CIN [$E0],$01,(Temp_Byte)
    setbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    clearbit Temp_Byte,3
    setbit Temp_Byte,2
    setbit Temp_Byte,1
    setbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
elseif ADC_Read<192 and b9>=177 then
    HI2CIN [$E0],$01,(Temp_Byte)
    setbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    clearbit Temp_Byte,3
    clearbit Temp_Byte,2
    setbit Temp_Byte,1
    setbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
elseif ADC_Read<177 and b9>=163 then
    HI2CIN [$E0],$01,(Temp_Byte)
    setbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    clearbit Temp_Byte,3
    clearbit Temp_Byte,2
    clearbit Temp_Byte,1
    setbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
elseif ADC_Read<163 and b9>=156 then
    HI2CIN [$E0],$01,(Temp_Byte)
    setbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    clearbit Temp_Byte,3
    clearbit Temp_Byte,2
    clearbit Temp_Byte,1
    clearbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
elseif ADC_Read<156 then
    HI2CIN [$E0],$01,(Temp_Byte)
    clearbit Temp_Byte,6
    setbit Temp_Byte,5
    HI2COUT [$E0],$01,(Temp_Byte)
    pause 2
    HI2CIN [$E0],$08,(Temp_Byte)
    clearbit Temp_Byte,3
    clearbit Temp_Byte,2
    clearbit Temp_Byte,1
    clearbit Temp_Byte,0
    HI2COUT [$E0],$08,(Temp_Byte)
endif


Thanks in advance.


Regards,
Mort.
 

AllyCat

Senior Member
Hi,

Instructions like SETBIT (which I have never ued) are basically "System Macro" commands which the PE breaks down into simpler instructions that you could write yourself - often more efficiently. The "best" (or maybe "worst") example is BINTOASCII (and similar) which uses over 40 bytes !

For your application you need to use the "Logic" operators such as AND , OR and XOR, etc. which can be found in the User Manual (2) "Variables Mathematics", that can work with (up to) 16 bits in parallel (on Word variables). Basically you use a "mask", putting a "1" in every bit-position that you want to Set and use an OR, for Example: Temp_Byte = Temp_Byte OR %01010101 will set all the "even" bits (6, 4, 2, and 0).

To Clear bits you make the bits that you want to clear a "0" in the mask (the others a "1") and use the AND operator, e.g. Temp_Byte = Temp_Byte AND %10101010 will clear all the even bits (sometimes it is easier to use ANDNOT which is not the same as NAND) . There is also the XOR which "toggles" all the bits that are set to a "1" in the mask. For example: Temp_Byte = Temp_Byte XOR %11111111 will "flip" ALL the bits in the byte (and so will Temp_Byte = NOT Temp_Byte ).

Most of these operators have single character "shortcuts" e.g. AND == & , but personally I prefer the "words", because symbols like | and ^ are not "obvious". Note that also the "mask" can itself be a variable so something like w1 = w1 XOR w2 is possible (and sometimes quite useful).

Cheers, Alan.
 

lbenson

Senior Member
Since Temp_Byte is b0, you can directly set its bits:
HI2CIN [$E0],$01,(Temp_Byte): bit6=1: bit5=1
 

mortifyu

New Member
Since Temp_Byte is b0, you can directly set its bits:
HI2CIN [$E0],$01,(Temp_Byte): bit6=1: bit5=1

And there it is...
Code:
HI2CIN [$E0],$01,(Temp_Byte): bit6=1: bit5=1
Thank you Ibenson. That is exactly what I was looking/hoping for.


Also thanks to the other Guru's that popped in suggestions. Always a pleasure to score some further education from AlleyCat and others ;)



Kind regards,
Mort.
 

mortifyu

New Member
So now I find using b0 works fine, but a change to b1 (or any other byte) does not. Why would this be?

Code:
HI2CIN [$E0],$00,(b0): bit7=1: pause 8: HI2COUT [$E0],$00,(b0)   'Works fine

HI2CIN [$E0],$00,(b1): bit7=1: pause 8: HI2COUT [$E0],$00,(b1)   'Does not work.

Regards,
Mort.
 

premelec

Senior Member
Hi - what are the numbers of bits in b1?? ;-0 Clue... B0 is bit0 to bit7 ,,, b1 is bit8 to...

From manual 2
In addition there are up to 32 individual bit variables (bit0, bit1 etc..). These bit
variables can be used where you just require a single bit (0 or 1) storage
capability. Bit variables are part of the lower value byte variables e.g.
b0 = bit7: bit6: bit5: bit4: bit3: bit2: bit1: bit0
b1 = bit15: bit14: bit13: bit12: bit11: bit10: bit9: bit8
etc...
 
Last edited:

mortifyu

New Member
Hi - what are the numbers of bits in b1?? ;-0 Clue... B0 is bit0 to bit7 ,,, b1 is bit8 to...

From manual 2
In addition there are up to 32 individual bit variables (bit0, bit1 etc..). These bit
variables can be used where you just require a single bit (0 or 1) storage
capability. Bit variables are part of the lower value byte variables e.g.
b0 = bit7: bit6: bit5: bit4: bit3: bit2: bit1: bit0
b1 = bit15: bit14: bit13: bit12: bit11: bit10: bit9: bit8
etc...

Yes, I am a BIT 🤪 of a goose. I did read that, guess I am getting a BIT old and slow ☺. I made alterations using BIT15 to BIT8 and that resolved the problem when utilizing b1.

Further, I have also now come to the realization that...

b0 = bit7 to bit 0
b1 = bit15 to bit 8
b2 = bit23 to bit 16
b3 = bit31 to bit 24


So now my question becomes if I am to use b4, one would presume we are back to bit7 to bit0. However using b4 with bit7 to bit0 does not work.


As a recap...
Code:
HI2CIN [$E0],$00,(b3): bit31=1: pause 8: HI2COUT [$E0],$00,(b3)   'Works fine

HI2CIN [$E0],$00,(b4): bit7=1: pause 8: HI2COUT [$E0],$00,(b4)   'Does not work. Additionally doing a SERTXD(#b4,cr) here returns a 0.


Regards,
Mort.
 

Aries

New Member
So now my question becomes if I am to use b4, one would presume we are back to bit7 to bit0. However using b4 with bit7 to bit0 does not work.
NO - the ONLY variables with bit availability are b0-b3 (w0-w1) which are bit0 - bit31. In any case - think about it: if you say bit7=1, how is anybody supposed to know you meant bit 7 of b4 rather than bit 7 of b0?
 

mortifyu

New Member
NO - the ONLY variables with bit availability are b0-b3 (w0-w1) which are bit0 - bit31. In any case - think about it: if you say bit7=1, how is anybody supposed to know you meant bit 7 of b4 rather than bit 7 of b0?
Thanks for the clarification of having bit availability of ONLY b0 to b3.

how is anybody supposed to know...

Code:
HI2CIN [$E0],$00,(b4): bit7=1: pause 8: HI2COUT [$E0],$00,(b4)
Sorry, I thought this line of code clarified what I was talking about.

Regards,
Mort.
 

AllyCat

Senior Member
Hi,
Code:
HI2CIN [$E0],$00,(b4): bit7=1
It isn't "one line of code" , the colon ( : ) marks a "Newline" so the two parts are completely independent, i.e the same as :
Code:
HI2CIN [$E0],$00,(b4)
bit7 = 1
The compiler "knows" that bit7 is part of b0, it has no way of "knowing" that you want it to apply to b4 (or any other variable that might have been previously referenced).

Note that what I told you in #3 (and Janne more briefly in #2) will work for ANY variable, e.g. Temp_Byte = Temp_Byte OR %00110000 and happens to uses exactly the same number of program bytes (actually 3) as Bit4 = 1 : Bit5 =1 , which applies ONLY when Temp_Byte is b0 . Bit manipulation is not really one of PICaxe's strengths, so I don't know if the following "demonstration" that you can run in the Simulator (preferably for an M2) will help:
Rich (BB code):
#picaxe 08m2
#terminal 4800
#no_data
symbol mybit0 = %00000001    ;  1
symbol mybit1 = %00000010    ;  2
symbol mybit2 = %00000100    ;  4
symbol mybit3 = %00001000    ;  8
symbol mybit4 = %00010000    ; 16
symbol mybit5 = %00100000    ; 32
symbol mybit6 = %01000000    ; 64
symbol mybit7 = %10000000    ;128
symbol mymask = s_w1

symbol Temp_Byte = b20
mymask = mybit4 + mybit5            ; Include for any bit to be set (OR) or cleared (ANDNOT)
Temp_Byte = 0
Temp_Byte = Temp_Byte OR mymask    ; Change OR to ANDNOT to Clear the bits

showbinary:
sertxd(#Temp_Byte," = %")
s_w1 = 128
do
  s_w2 = Temp_Byte AND s_w1 max 1
  sertxd(#s_w2)
  s_w1 = s_w1 / 2
loop until s_w1 = 0
You can set the Temp_Byte SYMBOL to ANY variable, you just need to set it to any initial value and construct mymask to include all the bits you want to change. Then use OR or ANDNOT to Set or clear the bits respectively. Of course normally the programmer creates "mymask" himself, because doing it inside the program takes lots of bytes!

Cheers, Alan.
 

Aries

New Member
And EVEN IF the compiler could read your mind, what would it (or you) make of:
Code:
bit7 = 1 : b2 = b0 * b4
 

Mark.R

Member
nstructions like SETBIT (which I have never ued) are basically "System Macro" commands which the PE breaks down into simpler instructions that you could write yourself - often more efficiently. The "best" (or maybe "worst") example is BINTOASCII (and similar) which uses over 40 bytes !
Out of interest Alan what would be a more byte friendly way of doing the BINTOASCII command and why does the compiler use 40 bytes?
 

AllyCat

Senior Member
Hi,
...what would be a more byte friendly way of doing the BINTOASCII command and why does the compiler use 40 bytes?
"Formatting" data to a neat appearance is often very greedy of program bytes, but there are two potential "issues" with BINTOASCII. First, the maximum value that can be represented by a Word variable is 65535, so as a "General Purpose Macro", it must assume that 5 digits may be generated (interestingly it generates an error message if you only assign 4 output bytes, but not for 3). But the program-writer may know that he only needs to calculate four or even 3 digits (e.g. if he had to choose a word variable because he knew the value might sometimes slightly exceed 255). Secondly, the "ASCII" part of the function is to convert the digits to their ASCII symbols, which is particularly inefficient if the program-writer actually wants the original digit values, so he has to convert back from ASCII to digit (Binary/Decimal) values (by subtracting 48 or "0").

The basic conversion relies on numerical Division, which can produce one of two results, the "Integer" part ( / operator) and the "Remainder" ( // operator). The first digit is easy to calculate, we just divide the Word by 10,000 to give a single digit Integer result (which quite possibly might be zero). Similarly the last digit is easy because it's the Remainder when you divide by 10. The other three digits are more complex because to select a single digit needs both a Divide and a Remainder operation. Then the conversion of each digit to ASCII basically requires the addition of 48, because "0" has a value of 48 in the ASCII code table and the other digits follow on (i.e. "1" = ASCII 49, etc.). Therefore, the "under the hood" (as hippy often says) calculation macro that BINTOASCII has to (always) create is as follows (which might explain why it takes over 40 bytes) :

Code:
BINTOASCII w0 , b2 , b3 , b4 , b5 , b6    ; (Which uses 42 bytes of Program Code) is  Expanded to:

b2 = w0 / 10000 + 48
b3 = w0 / 1000 // 10 + 48
b4 = w0 / 100 // 10 + 48
b5 = w0 / 10 // 10 + 48
b6 = w0 // 10  + 48          ;  Which just happens to use 42 bytes of Program Code    ;-)
If you actually NEED all 5 digits converted to ASCII, then using BINTOASCII is probably not very wasteful. However, if the number of variables used is an issue (and you don't need to keep w0 for later), then you could calculate the equivalent of BINTOASCII w0 , b5 , b4 , b3 , b2 , b1 but the Editor/Compiler won't allow that. However, if you know that you only need 4 , 3 , 2 or even 1 digit, then then "DIY" code version will save some program bytes.

Cheers, Alan.
 

mortifyu

New Member
Hi,

It isn't "one line of code" , the colon ( : ) marks a "Newline" so the two parts are completely independent, i.e the same as :
Code:
HI2CIN [$E0],$00,(b4)
bit7 = 1
The compiler "knows" that bit7 is part of b0, it has no way of "knowing" that you want it to apply to b4 (or any other variable that might have been previously referenced).
Hi, yes I understood : made for a new line of code, I just thought (for some silly reason) a BIT change like that was going to be referenced to the preceeding addressed BYTE. Instead in future I will use your example in POST #11.

Thanks Alan, you're a gem.



Regards,
Mort.
 

AllyCat

Senior Member
Hi,
.... thought .. that was going to be referenced to the preceeding addressed BYTE. ......
.... Instead in future I will use your example in POST #11.
I don't want to "do this to death" but there are some quite important (and useful) concepts here. What if the first instruction was: b4 = b5 + b6 , which was the preceding "addressed" byte? It's often said that PICaxe Basic works "From Left to Right" but the last variable addressed is the b4 , because that is where the result of the calculation will be written. Fairly obvious here, but it becomes very important with the @bptrinc variable. That's quite an advanced feature (because it is a combination of both a "number" and a "command") but it can be very useful. The INC is a "Post Increment" (i.e. an INCrement After the instruction is executed) , but exactly when? For example: @bptrINC = @bptrDEC + 1 is a perfectly valid instruction but even I would probably look at the simulator (or a real PICaxe chip) to be sure what it does. ;)

Also, bit7 is a (constant) number, it is not an instruction. PICaxe Basic uses a lot of numbers that "look" more like instructions, for example the time variable, pinc.2 , b2400_4 (in SEROUT), m16 (in SETFREQ), etc.. You can check which really are numbers by putting them in a SERTXD(#keyword)
instruction which will be flagged as a Syntax Error if the keyword is not a number, and the Simulator will even tell you its numerical value if it is (provided you remember the # ). The "Reserved Words" in Appendix 2 of Manual 2 names a lot of these keywords, but not all are numbers, nor are all listed. There are a few that are (surprisingly) NOT numbers such as PWMDIV4 and IT_5V0 (in READINTERNATEMP) ; it would be "nice" if ALL the Reserved Words were categorised as: Instructions, Pseudeo-Instructions, Constants (numbers) and other Qualifiers (like WORD), but there would be a lot of work there. ;)

Particularly as the OP was concerned about the Program Code "Size", there's quite a lot "implied" in my code of #11. It's hopefully well-known that the SYMBOL instruction does NOT use any Program Memory itself (and neither do "labels:"). However, the line mymask = mybit4 + mybit5 DOES use Program space because the Editor/Compiler converts it to: s_w1 = 16 + 32 which must be executed by the program every time it runs. To minimise the size of the program, mymask needs to be a "constant" which can be defined with an instruction such as: SYMBOL MYMASK = 48 (or %0011 0000 or $30) and the Editor does allow you to write SYMBOL MYMASK = 32 + 16 , but only with one operator, so SYMBOL MYMASK = 32 + 16 + 8 is not accepted. You could write: SYMBOL MYMASK1 = 32 + 16 : SYMBOL MYMASK= MYMASK1 + 8 , but IMHO that is "A Step (or Symbol) too far". :) Note that I have capitalised the "mymask" to show (me) that it is a "constant", not a "variable" (so I couldn't then write MYMASK = 48 in a program), but the Editor/Compiler is NOT "Case Sensitive".

In post #11 , I said that the sample program can use ANY variable but obviously mymask and Temp_Byte cannot both be the same base variable (it's not a problem if MYMASK is a constant). Therefore, I introduced one of the "unfamiliar" System Words so that it didn't conflict with any variable that a novice might use. The System Words can be used in any program (better for M2s), but cannot be referenced as separate Bytes (Let alone Bits). However, their bits (and Bytes) can be Written or Read by using the methods in this thread.

Finally, if you can't remember that mybit6 has a value of 64 (or %0100 0000 , or $40) and don't want to keep looking it up, then you can add the following #defines to your program. But you are still limited that the symbol command can't be expanded past symbol MYMASK = mybit4 + mybit5 . Actually, to keep to my own "rule" , maybe they also should be capitalised as MYBIT0 , etc.. ;)
Code:
#define mybit0 = 1
#define mybit1 =  2
#define mybit2 =  4
#define mybit3 =  8
#define mybit4 = 16
#define mybit5 =  32
#define mybit6 =  64
#define mybit7 =  128
But note that you must NOT put any comment ( ; or ' marker) in the #define lines.

Cheers, Alan.
 
Top