Writing a variable value to a variable position in a port register

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

 

Let's say I read a certain bit in PIND and assign it to variable val

uint8_t val = (PIND & (1<<PD7));

Suppose I want to take the bit I just read and write it to PB5 (or to a dynamic bit in PORTB specified by a variable). One way to do it is:

  if (val) PORTB |= 1<<PB5;
  else PORTB &= ~(1<<PB5);

I've tested it, and it works. There seems to be another way to do it:

PORTB = (PORTB & (~(1<<PB5))) | ((val>>PD7)<<PB5);

It works on paper, but something goes wrong when I actually run this code. I suspect I'm missing something trivial, but I just can't figure it out. So my question is twofold: what is wrong with this code, and whether it even makes sense to do it any other way than through the if statement.

 

This topic has a solution.
Last Edited: Tue. Sep 12, 2017 - 11:45 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Both methods should work. The first approach will probably be smaller and quicker. It is more intuitive.
.
Compilers are pretty clever. GCC might see through your obfuscation in method #2.
Method #2 is effectively shifting val two places to the right i.e. 7-5
.
Compare the generated ASM code. Or just step through the disassembled code in the Simulator,
.
David.

Last Edited: Tue. Sep 12, 2017 - 06:55 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

whether it even makes sense to do it any other way than through the if statement.

 No, probably not.  Multibit shifts (val>>PD7) are expensive on an AVR (although the compiler might do something clever...)

 

int main() {
    uint8_t val = (PIND & (1<<PD7));
   0:	80 b3       	in	r24, 0x10	; 16
   2:	80 78       	andi	r24, 0x80	; 128

  if (val) PORTB |= 1<<PB5;
   4:	01 f0       	breq	.+0      	; 0x6 <main+0x6>
   6:	c5 9a       	sbi	0x18, 5	; 24
   8:	00 c0       	rjmp	.+0      	; 0xa <main+0xa>
  else PORTB &= ~(1<<PB5);
   a:	c5 98       	cbi	0x18, 5	; 24


  PORTB = (PORTB & (~(1<<PB5))) | ((val>>PD7)<<PB5);
   c:	98 b3       	in	r25, 0x18	; 24
   e:	88 1f       	adc	r24, r24
  10:	88 27       	eor	r24, r24
  12:	88 1f       	adc	r24, r24
  14:	82 95       	swap	r24
  16:	88 0f       	add	r24, r24
  18:	80 7e       	andi	r24, 0xE0	; 224
  1a:	9f 7d       	andi	r25, 0xDF	; 223
  1c:	89 2b       	or	r24, r25
  1e:	88 bb       	out	0x18, r24	; 24
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
#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 PortB (*((volatile bits_t *)&PORTB))
#define DdrB (*((volatile bits_t *)&DDRB))
#define PinD (*((volatile bits_t *)&PIND))

int main(void) {
    DdrB.b5 = 1;
    while (1) {
        PortB.b5 = PinD.b7;
    }
}

Gives:

    DdrB.b5 = 1;
  6c:   bd 9a           sbi     0x17, 5 ; 23
    while (1) {
        PortB.b5 = PinD.b7;
  6e:   80 b3           in      r24, 0x10       ; 16
  70:   88 0f           add     r24, r24
  72:   88 0b           sbc     r24, r24
  74:   98 b3           in      r25, 0x18       ; 24
  76:   80 fb           bst     r24, 0
  78:   95 f9           bld     r25, 5
  7a:   98 bb           out     0x18, r25       ; 24
  7c:   f8 cf           rjmp    .-16            ; 0x6e <main+0x2>

 

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

Bill's result shows 4/5 cycles execution.   10 byte of Flash.

  if (val) PORTB |= 1<<PB5;
   4:	01 f0       	breq	.+0      	; 0x6 <main+0x6>
   6:	c5 9a       	sbi	0x18, 5	; 24
   8:	00 c0       	rjmp	.+0      	; 0xa <main+0xa>
  else PORTB &= ~(1<<PB5);
   a:	c5 98       	cbi	0x18, 5	; 24

Cliff's method is 7 cycles and uses 14 bytes of Flash.

        PortB.b5 = PinD.b7;
  6e:   80 b3           in      r24, 0x10       ; 16
  70:   88 0f           add     r24, r24
  72:   88 0b           sbc     r24, r24
  74:   98 b3           in      r25, 0x18       ; 24
  76:   80 fb           bst     r24, 0
  78:   95 f9           bld     r25, 5
  7a:   98 bb           out     0x18, r25       ; 24

As far as I can see,  you could:

   in r24, 0x10    //PIND
   bst r24, 7
   in r24, 0x18    //PORTB
   bld r24, 5
   out 0x18, r24   //PORTB

which is 5 cycles and 10 bytes.   So actually,   Bill's method is marginally faster.   In practice you might be happier with the constant execution time.

 

Quite honestly,  you might just as well choose the statement(s) that seems most intuitive.   Let the Compiler do the business.

 

David.

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

Mine wasn't so much about the size/speed but the clarity of the code ;-)

        PortB.b5 = PinD.b7;

That's pretty self explanatory.

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

+1

 

Clarity of code is more important than anything else.   Let the Compiler do the work.   It does a pretty good job.

 

Yes,  I like the "bitfield" syntax.   And GCC produces efficient code.

But the if-else sequence is intuitive too.

 

David.

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

Thank you everyone for the excellent replies!