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