I'm experimenting with using ports declared as bitfields for
single bit I/O.

For some of the the other gcc targets I'm currently using (e.g.
H8/300) that's how all of the example code is done, and the gcc
port includs headers that declare the I/O ports and peripheral
registers as both uintN_t and as structs of bitfields.

I find bitfields are particularly nice for single-bit I/O
lines, since there's only a single point in the source code
that defines which port-pin is assigned to a signal.  If you
use the port-name and bitmask method, you always end up with
mutiple places that must be changed when a signal moves to a
different port pin.  Having the same piece of information in
multiple places is a bug just waiting to happen.

Here's my test program:

-------------------------8<-------------------------
#include <io.h>

typedef struct 
{
  unsigned b0: 1;
  unsigned b1: 1;
  unsigned b2: 1;
  unsigned b3: 1;
  unsigned b4: 1;
  unsigned b5: 1;
  unsigned b6: 1;
  unsigned b7: 1;
} tPortBits;

#define sfrbb__(x,x_)  volatile tPortBits x ## bit asm(#x_)
#define sfrbb_(x,x_)   sfrbb__(x,x_)
#define sfrbb(x)  sfrbb_(x, x ## _)

sfrbb(P6OUT);
sfrbb(P6IN);
sfrbb(P1IN);

#define Bit(n) (1<<(n))

void foo(void)
{
  P6OUTbit.b0 = 1;
  P6OUTbit.b0 = 0;
  P6OUTbit.b7 = 1;
  P6OUTbit.b7 = 0;
}

void bar(void)
{
  P6OUT |=  Bit(0);
  P6OUT &= ~Bit(0);
  P6OUT |=  Bit(7);
  P6OUT &= ~Bit(7);
}

extern void zxcv(void);

void asdf(void)
{
  if (P1INbit.b0)
    zxcv();
  if (P1INbit.b7)
    zxcv();
}

void qwer(void)
{
  if (P1IN & Bit(0))
    zxcv();
  if (P1IN & Bit(7))
    zxcv();
}
-------------------------8<-------------------------


Output pins appear to work well:

void foo(void)
{
  P6OUTbit.b0 = 1;
  P6OUTbit.b0 = 0;
  P6OUTbit.b7 = 1;
  P6OUTbit.b7 = 0;
}


foo:
        bis.b        #llo(1), &0x0035
        bic.b        #llo(1),&0x0035
        bis.b        #llo(-128), &0x0035
        and.b        #llo(127), &0x0035
        ret

This is the same as the code generated for the byte/mask
version of the same function as long as optimization is
enabled.  With -O0, the bitfield version is unchanged, but the
byte/mask version ends up with an extra instruction:

void bar(void)
{
  P6OUT |=  Bit(0);
  P6OUT &= ~Bit(0);
  P6OUT |=  Bit(7);
  P6OUT &= ~Bit(7);
}

bar:
        bis.b        #llo(1), &0x0035
        bic.b        #llo(1),&0x0035
        bis.b        #llo(-128), &0x0035
        mov.b        #llo(127), r15
        and.b        r15, &0x0035

It's still atomic, so it's not a big deal.


However, reading input pins generates a lot of useless code:

void asdf(void)
{
  if (P1INbit.b0)
    zxcv();
  if (P1INbit.b7)
    zxcv();
}

asdf:
        mov.b  &0x0020, r15
        and    #llo(1), r15
        jne    .L6
.L4:
        bit.b  #llo(128), &0x0020
        clr.b  r15                    ****
        adc.b  r15                    ****
        and.b  #-1,r15                ****
        cmp    #llo(0), r15           ****
        jne    .L7
.L3:
        ret
.L7:
        call   #zxcv
        jmp    .L3
.L6:
        call   #zxcv
        jmp    .L4


Reading bit 0 wasn't too bad (though a bit.b would have been
better, wouldn't it?).  Reading bit 7 isn't good.  The lines
that appear to be useless are the four marked with asterisks.
AFAICT, you can just delete those four lines and the code is
still correct. 

Compare that to the byte/mask version:
        
void qwer(void)
{
  if (P1IN & Bit(0))
    zxcv();
  if (P1IN & Bit(7))
    zxcv();
}

qwer:
        bit.b  #llo(1),&0x0020
        jeq    .L9
        call   #zxcv
.L9:
        cmp.b  #llo(0), &0x0020
        jl     .L11
.L8:
        ret
.L11:
        call   #zxcv
        jmp    .L8


Much better.        

In either case, I don't see why the calls to zxcv() are pushed
out past the "ret" which results in an extra jump.  Why not
leave the call inline and jump around it like this?

qwer:
        bit.b  #llo(1),&0x0020
        jeq    .L9
        call   #zxcv
.L9:
        cmp.b  #llo(0), &0x0020
        jge    .L8
        call   #zxcv
.L8:
        ret

It only saves one instruction, but that instruction is a
"jmp", so it's a big one if you're counting cycles.

        
-- 
Grant Edwards                   grante             Yow!  My haircut is totally
                                  at               traditional!
                               visi.com            


Reply via email to