PIC/Hitech C - conversion of char to boolean (bit?) - Page 1
Forum section: electronics / general

author: icon
author_rank: Regular Contributor
timestamp: April 28, 2012, 06:56:18 pm
content: HiI'm trying to convert my meagre ASM skills into C. I had wrongly assumed that a char (or the result of a char expression) would be understood as 'false' if it equalled 0 (ie 0b00000000) or 'true' if greater than 0 (eg 0b10000000). After an entire afternoon struggling with the logic of a few lines of code, I discover this is not so. Specifically, I'm interested in whether any bits between RB4 and RB7 are low. RB0 to RB3 could be anything. So I do this:PORTB | 0b00001111Then I 1's complement the result:~(PORTB | 0b00001111)If RB4 to RB7 had been all ones, I would now have an expression that evaluated to 0b00000000; or if (say) RB7 had been 0, then I would have 0b10000000. My intention is to use it in a statement:if (~PORTB | 0b00001111) {stuff...In ASM, I could just use comf whatever, btfsc STATUS,Z. Am I missing an obvious construction that would work in C?CheersJohn

author: Mechatrommer
author_rank: Super Contributor
timestamp: April 28, 2012, 07:08:46 pm
content: Quoteyeah! hacking for the one goddamn bit. now you know the fun thing when somebody else encapsulated a funny standard into their library. but i cannot see why "greater or other than zero byte" = false.

author: Rufus
author_rank: Super Contributor
timestamp: April 28, 2012, 07:17:18 pm
content: Quote from: icon on April 28, 2012, 06:56:18 pmYou are probably missing integer promotion rules. Values in expressions are promoted to a minimum size of int before evaluation. For some expressions this makes no difference and the compiler can optimize away the inefficiency, but, not in your case.(PORTB & 0xF0)Will be true if any of bits 4 to 7 are set in PORTB.If you understand assembler looking at the code the compiler generates will help you understand the language. If you are using the free version of Hitech PICC the generated code will horribly inefficient.

author: IanB
author_rank: Super Contributor
timestamp: April 28, 2012, 07:58:25 pm
content: I think you are doing it backwards.First mask out the bits you are interested in:PORTB & 0xF0Now compare with the all ones value:(PORTB & 0xF0) == 0xF0If this expression is true you have all ones. If this expression is false you have one or more zero bits.

author: IanB
author_rank: Super Contributor
timestamp: April 28, 2012, 08:01:50 pm
content: Quote from: icon on April 28, 2012, 06:56:18 pmIt is indeed so, by the way. That is not what is going wrong. Checking intermediate values with print statements or a debugger would help you see why your code is not working. Spending an afternoon struggling is not good when you could simply debug your code line by line and immediately find where it is not doing what you expect.

author: icon
author_rank: Regular Contributor
timestamp: April 28, 2012, 10:59:39 pm
content: Quote from: IanB on April 28, 2012, 07:58:25 pmReluctantly, and with ill grace, I have to concede that you're right. This:RC0 = ~((PORTB & 0xF0) == 0xF0)works (RC0 lights and goes out as expected), whereasRC0 = (~(PORTB | 0x0F)) == 0doesn't. It also takes 9 more bytes not to work, which seems poor value. Can you point out my blunder? I can't see for looking. All I can say is that the logic worked and made sense in the ASM version - testing for zero is built in. See below:movfw PORTBmovwf latchmovlw b'00001111'iorwf latch ; make non-input bits 1comf latch,1 ; and 'not'btfsc STATUS,Z ; ie skip next instruction if any switch closedgoto s_o ; all switches openI can feel myself falling back into the warm, welcoming bosom of ASM... must... resist...John

author: Unknown
author_rank: Unknown
timestamp: April 28, 2012, 11:17:25 pm
content: I agree with Rufus's analysis, the result is probably promoted to a 16-bit integer. Casting the result to uint8_t might help. foo & MASK == MASK is the common pattern to check if certain bits are set. I would write PORTB & 0xF0 != 0xF0, however, or at least !(PORTB & 0xF0 == 0xF0). ~ is a binary inversion operator, so ~1 = 0xFFFE for 16-bit ints, while !1 is 0.

author: amspire
author_rank: Super Contributor
timestamp: April 28, 2012, 11:23:53 pm
content: Quote from: icon on April 28, 2012, 10:59:39 pmHave you tried:RC0 = (~(PORTB | 0x0F) == 0) ?I am not sure of the order of precedence of "=" and "==", but it might be doing RC0 = (~(PORTB | 0x0F)) before (~(PORTB | 0x0F)) == 0. I always surround a comparison in brackets just to make sure.

author: IanB
author_rank: Super Contributor
timestamp: April 28, 2012, 11:39:41 pm
content: Quote from: icon on April 28, 2012, 10:59:39 pmNot immediately. It is probably something like Rufus said, to do with integer promotion rules.Your logic does seem to work, although it is not the way I would approach the problem. Here is my test program:Code:#include <stdio.h>int main(){unsigned char PORTB;PORTB = 0xA0;if ( ~ (PORTB | 0x0F) ) printf("%2X: zeroes are present\n", PORTB);PORTB = 0xF0;if ( ~ (PORTB | 0x0F) ) printf("%2X: no zeroes are present\n", PORTB);return 0;}And here is the output:Code:A0: zeroes are presentF0: no zeroes are presentTo find out why it is not working for you, I think you will need to break it down into intermediate steps line by line, and then check the value at each step.

author: IanB
author_rank: Super Contributor
timestamp: April 28, 2012, 11:45:39 pm
content: Quote from: icon on April 28, 2012, 10:59:39 pmIt was already mentioned above, but I will mention it also, that this should better be written as:RC0 = ! ((PORTB & 0xF0) == 0xF0);or even betterRC0 = (PORTB & 0xF0) != 0xF0;"~" is the bitwise NOT operator, whereas "!" is the logical NOT operator.

author: IanB
author_rank: Super Contributor
timestamp: April 29, 2012, 12:04:40 am
content: For example, here is how you could set out the code line by line to match the assembly version:Code:unsigned char PORTB;unsigned char latch;int RC0;PORTB = 0xA0; // emulate input valuelatch = PORTB; // read input bytelatch |= 0x0F; // set low order bitslatch = ~latch; // one's complementRC0 = latch ? 1 : 0; // set RC0 if any switches are closedWith the code in this form you can then step through it with the debugger and check the result of each operation. You should quickly see where something is going astray. Once it works, you can condense it back into a single expression.

author: IanB
author_rank: Super Contributor
timestamp: April 29, 2012, 12:12:46 am
content: Quote from: icon on April 28, 2012, 10:59:39 pmSorry about the afternoon struggling comment, by the way. I have been there myself, so I speak from experience. In such situations the debugger is your friend. When programs do mysterious things you just have to get in there with the debugger and see what is happening.

author: Rufus
author_rank: Super Contributor
timestamp: April 29, 2012, 12:21:06 am
content: Quote from: alm on April 28, 2012, 11:17:25 pmIn this~(PORTB | 0b00001111)it is PORTB that gets promoted to int or unsigned int (16 bits) with the high order 8 bits getting sign extension or 0s depending on PORTB being signed or unsigned. Assuming PORTB is unsigned the high 8 0 bits get complemented and the result is always non-zero or true. The complier should optimize away the whole expression, except PORTB is probably volatile so the compiler is required to read it. That is my understanding without reading up on promotion rules again.~(PORTB | 0xFF0F) would do what I think the OP intended.Not considering the effect of promotions is an easy mistake to make but it doesn't often make a difference.

author: IanB
author_rank: Super Contributor
timestamp: April 29, 2012, 12:29:49 am
content: Incidentally, when you create your constant 0b00001111 you are not doing it the best way. It is far better to write ~0b11110000 instead as then you will guarantee to put 1's everywhere they might be needed, and not just in the lower four bits.If you do this you will then have PORTB | ~0b11110000Next you will 1's complement the result:~ (PORTB | ~0b11110000)Now if you apply De Morgan's laws to this expression you get the equivalent form~PORTB & 0b11110000Now you can writeif (~PORTB & 0b11110000) { /* a switch is closed */ }Since "0b" is non-standard, good style would suggestif (~PORTB & 0xF0) { ... }or perhapsRC0 = (~PORTB & 0xF0) ? 1 : 0;

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 05:08:09 pm
content: HiWell thanks for all the replies! Having compounded my spending an afternoon on this, by adding a further evening and afternoon, I can confirm that this((PORTB & 0xF0) != 0xF0)works as expected, as doesRC0 = (~PORTB & 0xF0) ? 1 : 0There's enough other material in this thread to spend another day playing with with other ways of expressing the logic, and exploring how the compiler treats different objects, but I gotta get on. Besides, how many programming cycles can one PIC take before dying?I'm going to start another thread about debugging, since there's clearly lots I'm missing out on.Thanks again.John

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 06:29:57 pm
content: OK, I know what I said, but look at this.RC0 = (PORTB | 0x0F) ? 1 : 0;Produces this code:BCF 0x3, 0x5BCF 0x3, 0x6MOVF 0x6, WIORLW 0xfBTFSC 0x3, 0x2GOTO 0x7fcBCF 0x3, 0x5BCF 0x3, 0x6BSF 0x7, 0GOTO 0x7deBCF 0x3, 0x5BCF 0x3, 0x6BCF 0x7, 0Basically what you'd expect - it doesn't do what I need, of course, because I need a 'COMF' somehow after the IORLW. So lets stick in a bitwise operator to do that:RC0 = ~(PORTB | 0x0F) ? 1 : 0;Produces this:BCF 0x3, 0x5BCF 0x3, 0x6BSF 0x7, 0That's it! Just complete bollocks. No testing, no nothing, just sets RC0 to 0, thank you very much. No wonder the sodding code doesn't work.(~PORTB & 0xF0) also (obviously) produces a working version of code.Thanks for listening.John

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: April 29, 2012, 06:33:55 pm
content: QuoteThis should always just set RC0 regardless of PORTB, as anything | 0x0f is going to be nonzero

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 07:11:14 pm
content: Quote from: mikeselectricstuff on April 29, 2012, 06:33:55 pmYes that's right - but the code is correctly generated to *test* whether it's true - I suppose if anything the compiler could in theory optimise it out. My point is that when you stick a ~ in front of it (so now FALSE if PORTB | 0x0F had been all ones, or TRUE if there had been any zeros - which is what I'm trying to test) the compiler optimisation *does* suddenly decide to creak into action - only wrongly.John

author: IanB
author_rank: Super Contributor
timestamp: April 29, 2012, 07:25:48 pm
content: OK, try this:RC0 = ~(PORTB | ~0xF0) ? 1 : 0;Remember my note previously, you should always tell the compiler what bits youareinterested in, not what bits you arenotinterested in. You are interested in bits 4-7, so tell the compiler that explicitly, don't leave it to assume your intent.

author: Rufus
author_rank: Super Contributor
timestamp: April 29, 2012, 07:50:14 pm
content: Quote from: icon on April 29, 2012, 06:29:57 pmI already posted about integer promotions. Both values are promoted to 16 bit unsigned ints. If PORTB contains 0xF0 for example what you are saying isRC0 = ~(0x00F0 | 0x000F) ? 1 : 0;Which unconditionally assigns 1 to RC0.As an aside the Hi-Tech compiler is bugged, the compiler should generate code to read PORTB regardless because PORTB is qualified volatile. Sometimes it optimizes away these required accesses.

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 07:54:59 pm
content: Quote from: IanB on April 29, 2012, 07:25:48 pmAgain, gritted teeth, you're quite right; it works. That was one of the options Ididn'ttry. Interestingly, it does go all 16 bit, and uses incf to see whether the register rolls over to zero, rather than comf. The code is longer and (because it's 16bit) uses more registers than your other version, so it's of academic interest only!ThanksJon

author: IanB
author_rank: Super Contributor
timestamp: April 29, 2012, 07:55:36 pm
content: Quote from: Rufus on April 29, 2012, 07:50:14 pmWhich is the technical reason why you should write ~0xF0 rather than 0x0F. The compiler should convert ~0xF0 to 0xFF0F in 16 bit cases, or 0xFFFFFF0F in 32 bit cases (see my debugger window in the other thread).

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 08:01:06 pm
content: Quote from: Rufus on April 29, 2012, 07:50:14 pmYou're quite right, you did. I didn't follow through the implication that the compiler might do all that 'internally' without any sign of it making its way into the code.CheersJohn

author: Rufus
author_rank: Super Contributor
timestamp: April 29, 2012, 08:07:54 pm
content: Quote from: icon on April 29, 2012, 08:01:06 pmThat is what an optimizing compiler does. You appear to be using the free version of the Hi-Tech compiler which has severely crippled optimization. I was a little surprised to see it optimize away the whole expression.

author: icon
author_rank: Regular Contributor
timestamp: April 29, 2012, 08:34:54 pm
content: Quote from: Rufus on April 29, 2012, 08:07:54 pmI can't speak for the optimisation, but I can say that the code appears to be stuffed full of redundant 'GOTO's. The algorithm seems to be - wherever there's a real GOTO needed, stuff a fake one after it that's never called, but pads out the code. In Pro mode (I've turned on the trial) these aren't there. "Omniscient Code Generation" seems to = "Don't pad the code out with redundant GOTOs". Why, it's almost like making a 100MHz scope and then crippling some of them and selling them as 50MHz. Call me suspicious.John