Port/bit manipulation vs Arduino DigitalWrite()

Go To Last Post
32 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This seems like such a simple thing to do, yet it has me stumped.

Coming from Arduino, I would write the following:

bool x;

//code

//make PD2 equal to x
digitalWrite(2, x);

How is this done using the PORTB register and bit manipulation (without writing an if/else statement)?

 

thadwald

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Several equivalent expressions are possible. One is

 

if (x)
    PORTD |= (1<<PD2);
else
    PORTD &= ~(1<<PD2);

 

The challenge is an expression that takes either logic state. Most of us know, before hand what state is wanted and will just use one or the other of the expressions. C simply has no statement that assigns a value to a bit in a variable according to the logic state of a passed argument. THus, the need for the if...else. That is really what goes on inside digitalWrite().

 

By the way, you cannot do that to PD2 in PORTB because PD2 is in PORTD.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

Last Edited: Thu. Jun 29, 2017 - 07:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:
By the way, you cannot do that to PD2 in PORTB because PD2 is in PORTD.

For clarity, I agree.  In practice, PD2 and PB2 are both defined as 2.

 

Re OP's question:  I'll bet if you dig into the digitalWrite() innards you'll find a conditional statement, as outlined in the response.  Along with a bunch of overhead to translate "pin 2" to a port and mask.

 

In practice, inline code usually "knows" whether it wants to set or clear.  So the point becomes moot.

 

Recently in https://www.avrfreaks.net/comment... I posted the brute-force "output driver" that I use when I have a bank of outputs with various states to apply.  Yes, it is a conditional statement.  Let's poke at that a bit, using the source code in the given link:

 

                 ;0000 04A0 // Active-high outputs
                 ;0000 04A1 //
                 ;0000 04A2 	if (output_mask & OUTPUT_1)
000133 fe40      	SBRS R4,0
000134 c002      	RJMP _0x29
                 ;0000 04A3 		{
                 ;0000 04A4 		OUT_1 = 1;
000135 9a5d      	SBI  0xB,5
                 ;0000 04A5 		}
                 ;0000 04A6 	else
000136 c001      	RJMP _0x2C
                 _0x29:
                 ;0000 04A7 		{
                 ;0000 04A8 		OUT_1 = 0;
000137 985d      	CBI  0xB,5
                 ;0000 04A9 		}
                 _0x2C:

which is 5/6 cycles if I counted correctly.  About a microsecond per bit for the "driver".  Just setting up and calling a function, e.g. digitalWrite(), will have more overhead than that and you haven't done anything yet.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree with the responses given by theusch and ka7ehk; however, if I must do it "without writing an if/else statement", you could:

    PORTD &= ~(1 << PD2);   // Always clear the bit

    PORTD |= (x << PD2);    // Set the bit if x is true

 

realizing that this is (probably) an inefficient method of achieving the operation...

 

David

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

frog_jr wrote:
realizing that this is (probably) an inefficient method of achieving the operation...

It is indeed tempting.  However, if you do that you will have to consider EACH output, and any possible effects a couple cycle low pulse might have.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The digitalWrite() is defined in hardware/arduino/cores/arduino/wiring_digital.c as below.

void digitalWrite(uint8_t pin, uint8_t val)
{
        uint8_t timer = digitalPinToTimer(pin);
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        volatile uint8_t *out;

        if (port == NOT_A_PIN) return;

        // If the pin that support PWM output, we need to turn it off
        // before doing a digital write.
        if (timer != NOT_ON_TIMER) turnOffPWM(timer);

        out = portOutputRegister(port);

        uint8_t oldSREG = SREG;
        cli();

        if (val == LOW) {
                *out &= ~bit;
        } else {
                *out |= bit;
        }

        SREG = oldSREG;
}

if/else doesn't look so bad after that. ;)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    PORTD |= (x << PD2);    // Set the bit if x is true

Careful now.

 

E.g the value 123 is 'true'.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It won't hang but it won't do what the code writer might expect :)

 

The Arduino code just tests for zero (LOW) or not zero and will do the same thing.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

frog_jr wrote:

I agree with the responses given by theusch and ka7ehk; however, if I must do it "without writing an if/else statement", you could:   

PORTD &= ~(1 << PD2);   // Always clear the bit
PORTD |= (x << PD2);    // Set the bit if x is true

 

realizing that this is (probably) an inefficient method of achieving the operation...

 

 

How about a combination of those:

PORTD &= ~(1 << PD2) | (x << PD2);

Should this work?

 

thadwald

Last Edited: Fri. Jun 30, 2017 - 01:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

thadwald wrote:

PORTD &= ~(1 << PD2) | (x << PD2);

Should this work?

NO.  It can only clear a bit.

PORTD= (PORTD & ~(1<<2)) | (x<<2);

 

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:
C simply has no statement that assigns a value to a bit in a variable according to the logic state of a passed argument.
Well that is not entirely true...

#include <stdbool.h>
#include <avr/io.h>

typedef struct {
    int b0:1;
    int b1:1;
    int b2:1;
    int b3:1;
    int b4:1;
    int b5:1;
    int b6:1;
    int b7:1;
} bits_t;

#define PortD (*((volatile bits_t *)&PORTD))

#define MyPD2 PortD.b2

int main(void) {
    bool x;
	while(1) {
		x = true;
		MyPD2 = x;
		x = false;
		MyPD2 = x;
	}
}

which yields:

        while(1) {
                x = true;
                MyPD2 = x;
  6c:   92 9a           sbi     0x12, 2 ; 18
                x = false;
                MyPD2 = x;
  6e:   92 98           cbi     0x12, 2 ; 18
  70:   fd cf           rjmp    .-6             ; 0x6c <main>

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Clawson's code optimizes well, because the compiler notices that x was assigned a constant.
If you do something that make x actually variable, you get nastier code (although... it doesn't actually contain any jumps, which is interesting.)
 

	x = getbool();
   0:	00 d0       	rcall	.+0      	; get a boolean in r24.
	MyPD2 = x;
   2:	87 95       	ror	r24             ; not sure what it's doing!
   4:	88 27       	eor	r24, r24        ;  probably making sure the bool
   6:	87 95       	ror	r24             ;   is actually 1 or 0.
   8:	88 0f       	add	r24, r24        ;    in a rather convoluted way
   a:	88 0b       	sbc	r24, r24
   c:	92 b3       	in	r25, 0x12	; get portD in r25
   e:	80 fb       	bst	r24, 0          ; transfer boolean to T bit.
  10:	92 f9       	bld	r25, 2          ; transfer T to r25
  12:	92 bb       	out	0x12, r25	; set PORTD back out.

 

(the bst/bld is probably the closest the AVR has to "move bit from Ra to different bit of Rb."

It probably would have been more efficient to use an if, mostly because of the ambiguity of "bool")

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino's bit manipulation by functions ( a function call, computation, a function return : I do not think they are underlined) is sometimes considered as a slow solution (but very comfortable) any other solution is faster; other (SPI, I2C, blocking analog read ) are reasonably fast -and remain comfortable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Well that is not entirely true...
clawson wrote:
which yields: while(1) { x = true; MyPD2 = x; 6c: 92 9a sbi 0x12, 2 ; 18 x = false; MyPD2 = x; 6e: 92 98 cbi 0x12, 2 ; 18 70: fd cf rjmp .-6 ; 0x6c

C'mon, Cliff, you cherry-picked.  The compiler peeked at what you had set "x" to and turned it into SBI/CBI.

 

Lessee, GCC "bool" has a 1/0 value?  Let's force the compiler to do some real work; adjusted source program:

#include <stdbool.h>
#include <avr/io.h>

typedef struct {
	int b0:1;
	int b1:1;
	int b2:1;
	int b3:1;
	int b4:1;
	int b5:1;
	int b6:1;
	int b7:1;
} bits_t;

#define PortD (*((volatile bits_t *)&PORTD))

#define MyPD2 PortD.b2

int main(void) {
	bool x;
	while(1) {
		x = PINB & 1;
		MyPD2 = x;
		x = PINB & 1;
		MyPD2 = x;
	}
}

WHICH YIELDS:

		x = PINB & 1;
  46:	83 b1       	in	r24, 0x03	; 3
		MyPD2 = x;
  48:	80 fd       	sbrc	r24, 0
  4a:	5a 9a       	sbi	0x0b, 2	; 11
  4c:	80 ff       	sbrs	r24, 0
  4e:	5a 98       	cbi	0x0b, 2	; 11
		x = PINB & 1;
  50:	83 b1       	in	r24, 0x03	; 3
		MyPD2 = x;
  52:	80 fd       	sbrc	r24, 0
  54:	5a 9a       	sbi	0x0b, 2	; 11
  56:	80 ff       	sbrs	r24, 0
  58:	5a 98       	cbi	0x0b, 2	; 11
  5a:	f5 cf       	rjmp	.-22     	; 0x46 <main>

So OP does indeed not have conditional in the source.  But the result would be the same as having two if() in some type of wrapper -- macro or inline function.

 

The twin SBRC/SBRS will indeed have constant time, unlike the snippet I posted with RJMP.  However, note that both solutions will have some jitter, setting the bit a few cycles before clearing the bit.

 

[edit] LOL -- see below what happens when PINB&1; is changed to PINB;  Now the whole port value needs to be checked for 1/0:

		x = PINB;
  46:	93 b1       	in	r25, 0x03	; 3
  48:	81 e0       	ldi	r24, 0x01	; 1
  4a:	91 11       	cpse	r25, r1
  4c:	01 c0       	rjmp	.+2      	; 0x50 <main+0xa>
  4e:	80 e0       	ldi	r24, 0x00	; 0

[editorial comment] An ongoing thread in another forum about C and determinism and not knowing what is produced.  The above, IMO, is a perfect example of a) knowing your toolchain and b) give the compiler a chance.  The &1 costs nothing but a couple characters of typing

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Fri. Jun 30, 2017 - 07:19 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not sure what point you are trying to make? Jim said you can't do bit assignment from a bool in C. I showed him that you can. To be honest I don't really care how efficient the generated code is. Just that it's possible.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Not sure what point you are trying to make? Jim said you can't do bit assignment from a bool in C. I showed him that you can. To be honest I don't really care how efficient the generated code is. Just that it's possible.

I guess technically (pedantically?) I must agree.  Does it also satisfy OP's "no conditional" requirement?  At the source code level, yes.  Is your construction of MyPD2 as a wrapper any different than a macro wrapper or inline function with the conditional, once you get to main()?  The source line for your MyPD2 = x; is now, say, MyPD2(x);

 

To paraphrase a biblical passage:

And if thine if/else offend thee, pluck it out, and cast it from the source: it is better for thee to enter into main() with one line and no conditional, rather than having two clauses, if and else, to be cast into hell fire.
 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Fri. Jun 30, 2017 - 07:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

From context, OP is apparently asking about a similarly convenient replacement for digitalWrite.

sbi and cbi macros work when one already knows the value of x.

For variable x, there are no similarly convenient standard functions or macros.

Writing one's own is not hard.

The hard part is deciding what one wants.

If clarity is the overriding criterion,

put a wrapper on an if/else.

Speed and jitter as criteria will produce different solutions.
 

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the comments. I just thought I missed something, that's all. I'm glad I asked.

I ended up with the following, as it seems to be the most concise (jitter free) option offered here, both in C and machine code.
Not that I can actually read the latter, but I can count the nunber of cycles.

 

if (x)
{
   PIND |= (1 << n);
}
else
{
   PIND &= ~(1 << n);

 

 

Edit:  This should read PORTB, not PIND.

 

thadwald

Last Edited: Mon. Jul 3, 2017 - 08:56 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just for completeness I'll just point out that if one is willing to accept a separate conditional then my solution above is as "tight" as anything else for the actual output:

int main(void) {
	while(1) {
		if (PIND) {
			MyPD2 = 1;
		}
		else {
			MyPD2 = 0;
		}
	}
}

yields:

        while(1) {
                if (PIND) {
  6c:   80 b3           in      r24, 0x10       ; 16
  6e:   88 23           and     r24, r24
  70:   11 f0           breq    .+4             ; 0x76 <main+0xa>
                        MyPD2 = 1;
  72:   92 9a           sbi     0x12, 2 ; 18
  74:   fb cf           rjmp    .-10            ; 0x6c <main>
                }
                else {
                        MyPD2 = 0;
  76:   92 98           cbi     0x12, 2 ; 18
  78:   f9 cf           rjmp    .-14            ; 0x6c <main>

One might event have:

#define GREEN_LED    PortD.b2
#define ALARM_BELL PortD.b5

...

		if (PIND) {
			ALARM_BELL = 1;
			GREEN_LED = 0;
		}
		else {
			ALARM_BELL = 0;
			GREEN_LED = 1;
		}

which some might consider fairly "pretty" code ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

thadwald wrote:
Thanks for the comments. I just thought I missed something, that's all. I'm glad I asked. I ended up with the following, as it seems to be the most concise (jitter free) option offered here, both in C and machine code.

OK, I'll bite--what happened to the no-conditional requirement in this snipe hunt?

 

thadwald wrote:
(without writing an if/else statement)

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The whole point of my initial post was for me to find out if I am missing something. The no-conditional was there only because I already knew that

I've been a hobbyist programmer since I was a kid, in Visual Basic, SQL, C# and more recently, on the Arduino, which I currently use in my middle school STEM classes. 

This is the first time I have not been able to assign a bit directly, so I figured I would ask.

So what happened to the no-conditional requirement is that I ditched it because none of the solutions are easier to read or shorter.

 

I apologize if you feel I wasted your time but I did learn a lot from the ensuing discussion and I found it entertaining to boot.

 

Now your response hints that maybe there is a valid reason for wanting a no-conditional bit assignment ... 

thadwald

Last Edited: Mon. Jul 3, 2017 - 06:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you're skilled at using Visual BASIC, SQL, and C# then you might want to ask your teachers if you can move out of middle school STEM classes and go on directly to high school.  I think that you're ready for high school at this point.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You could use the ternary operator:

PORTB = x ? PORTB | (1 << 2) : PORTB & ~(1 << 2);

Or more generally:

#define CONCATENATE(a, b) CONCATENATE_(a, b)
#define CONCATENATE_(a, b) a ## b

#define CAT CONCATENATE

#define PORT(n) CAT(PORT, n)

#define WRITE_PORT(port, bit, val)                      \
  do { if (val) { PORT(port) |=  (1<<bit); }            \
       else     { PORT(port) &= ~(1<<bit); } } while(0)
#define LED_PORT B
#define LED_PIN  2
.
.
.
int main(void) {
.
.
.
  WRITE_PORT(LED_PORT, LED_PIN, x);
.
.
.
}

@skeeve has a slightly different approach, where he combines the port and pin into a single define.  Don't know where that thread is...

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Mon. Jul 3, 2017 - 08:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heh

 

I'm the STEM middle school teacher.

 

thadwald

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My initial reaction to Post #1 was that a student had been given an assignment by a teacher...laugh

 

Edit: By the way, welcome to AVRFreaks!
 

David

Last Edited: Mon. Jul 3, 2017 - 09:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OT:

 

Simonetta wrote:
If you're skilled at using Visual BASIC, SQL, and C# then you might want to ask your teachers if you can move out of middle school STEM classes and go on directly to high school.

 

thadwald wrote:
I'm the STEM middle school teacher.

 

LOL! Literally! Hilarious!

 

As you where, gentlemen..

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Obviously, I am NOT teaching AVR.

 

I agree, it is quite funny.

 

thadwald

Last Edited: Mon. Jul 3, 2017 - 11:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:
@skeeve has a slightly different approach, where he combines the port and pin into a single define.  Don't know where that thread is....
https://www.avrfreaks.net/forum/p...

Note that there is no copy bit function or macro.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:

@skeeve has a slightly different approach, where he combines the port and pin into a single define.  Don't know where that thread is....

https://www.avrfreaks.net/forum/p...

 

Note that there is no copy bit function or macro.

This is the one I was thinking about:

https://www.avrfreaks.net/comment/1950576#comment-1950576

... same macros it seems.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm the STEM middle school teacher.

 

Heh.  One of the things I think it's important to learn, as you go through different languages, is that while some languages make certain concepts particularly easy to express (compared to other languages, anyway), that's an illusion created by the compiler or interpreter.   By the time you get down to the work that the processor actually needs to do, it's about the same effort (cycles) regardless of language.

 

It makes me sad every time I see a Python/Java/C++ programmer throw something like "dictionaries" (for example) at a trivial problem.

(It also makes me sad that I shy away from such solutions, even when performance is not an issue.  Sigh.)

 

Does anyone know of a processor/cpu with native support for "boolean" data types?  Lots of chip have some sort of bitfield support, but I can't think of one offhand that has a "put 1 in the destination for any non-zero source" instruction.   (MANY can do it in two or three instructions, so it would probably have to date back to the wide-word CISC days...)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

thadwald wrote:
So what happened to the no-conditional requirement is that I ditched it because none of the solutions are easier to read or shorter.
Except one ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One situation in which PINx is pretty much THE solution is when a possibly

asynchronous task/ISR/whatever only "owns" some of the PORTx bits.

In that case, figure out which PORTx bits need toggling and assign to PINx.

 

correction PINx --> PORTx

Moderation in all things. -- ancient proverb

Last Edited: Sat. Jul 15, 2017 - 05:19 PM