[TUT] [C] [GCC] How to define a pin as a variable

Last post
26 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The following is based on posts from Kevin Rosenberg and Johan Ekdahl. Thanks!

In some compilers, the compiler allows the programmer to directly refer to a single bit of an AVR port. For example, if we want to set the third bit of PORTB, the following works just fine:

PORTB.3 = 1;

Unfortunately, GCC rejects this. The reasons for this are obscure to a newbie, but is actually simple: The above syntax violates the rules of standard C. Since GCC itself supports more than just the AVR processor (and since the other processor users like the standard just fine, thank you :D), we AVR users are stuck with the way the C standard (and GCC) as they are.

The most common suggestion to solve this problem is to learn how to use the bit manipulation operations in C. For a tutorial on this, check out [TUT] [C] Bit manipulation (AKA "Programming 101 For Embedded Code"). Whether you use the following method or not, we strongly suggest you read the "Programming 101" tutorial. You will end up using it, no matter what.

Okay, if you are stubborn and insist on defining a pin as a variable in GCC, there is a way. Suppose we set up a structure that defines the bits of a single byte:

typedef struct
{
  unsigned int bit0:1;
  unsigned int bit1:1;
  unsigned int bit2:1;
  unsigned int bit3:1;
  unsigned int bit4:1;
  unsigned int bit5:1;
  unsigned int bit6:1;
  unsigned int bit7:1;
} _io_reg;

Now we can define a macro that will allow us to define our single bit variable:

#define REGISTER_BIT(rg,bt) ((volatile _io_reg*)&rg)->bit##bt

The above macro works like this:

#define is the start of a macro definition

REGISTER_BIT is the name of the macro

(tg,bt) are the parameters to the macro

The C keyword volatile is accurately described in the AVR-libc Manual Frequently Asked Questions, in the FAQ item #1. It is also described in the article Introduction to the "Volatile" Keyword.

_io_reg is the struct type that is typedef'ed right above the macro, and thus
_io_reg* is a pointer to such a struct, and so
(volatile _io_reg*) is a typecast to "a volatile pointer to a _io_reg struct"

& is the "address of" operator, and thus
&rg is the address of rg, ie the address of the first parameter to the macro

The -> is the "member by pointer operator", in other words it "refers" to one of the fields in the struct that the pointer points to.

## is the token pasting operator (of the C preprocessor). The preprocessor concatenates the two operands together so that the compiler sees one token, in this case "bit" and whatever value the bt parameter to the macro has.

So, if you do

REGISTER_BIT(PORTB,4)

this is expanded by the C preprocessor to

((volatile _io_reg*)&PORTB)->bit4 

By the way, PORTB is a macro, too. It is defined in a header file, based on your processor type. For example, in an ATmega128, PORTB is defined as

#define PORTB     _SFR_IO8(0x18)

You would be right if you guessed that _SFR_IO8 is also a macro, but that takes us a little far from the current discussion.

Let's use this in some example code:

#include 

typedef struct
{
  unsigned int bit0:1;
  unsigned int bit1:1;
  unsigned int bit2:1;
  unsigned int bit3:1;
  unsigned int bit4:1;
  unsigned int bit5:1;
  unsigned int bit6:1;
  unsigned int bit7:1;
} _io_reg;

#define REGISTER_BIT(rg,bt) ((volatile _io_reg*)&rg)->bit##bt


#define BUTTON_PIN  REGISTER_BIT(PINB,3)
#define LED_PORT    REGISTER_BIT(PORTB,4)

#define BUTTON_DIR  REGISTER_BIT(DDRB,3)
#define LED_DIR     REGISTER_BIT(DDRB,4)
 
main() {
  uint8_t is_button = BUTTON_PIN;
  
  LED_DIR = 1;
  BUTTON_DIR = 0;

  while (1) {
    LED_PORT = BUTTON_PIN;
  }
} 

First off, we include the header file . This defines the PORT and PIN ports available on the selected AVR.

We then included our structure definition and the REGISTER_BIT macro.

We then define two register bits:

#define BUTTON_PIN  REGISTER_BIT(PINB,3)
#define LED_PORT    REGISTER_BIT(PORTB,4)

which in turn calls on the REGISTER_BIT macro, so that when we do

  uint8_t is_button = BUTTON_PIN;

this actually is expanded by the C preprocessor to

uint8_t is_button = ((volatile _io_reg*)&PINB)->bit3; 

What does this mean? This: Take the address of the PINB register. Treat that address as a pointer to a _io_reg struct (where the different bits are conveniently declared). Dereference that pointer, and get the member with the name "bit3".

We have defined the "direction" bits for our register bits:

#define BUTTON_DIR  REGISTER_BIT(DDRB,3)
#define LED_DIR     REGISTER_BIT(DDRB,4)

This allows us to tell the processor what direction (input or output) the pin will be set.

In the main routine, set up the LED to be an output:

  LED_DIR = 1;

which after the preprocessor looks like:

  ((volatile _io_reg*)&DDRB)->bit4 = 1;

Similar to the description of is_button before, this translates to: Take the address of the DDRB register. Treat that address as a pointer to a _io_reg struct. Dereference that pointer, and get the member with the name "bit4". Set that bit to a 1.

We also set up the BUTTON to be an input:

  BUTTON_DIR = 0;

By now, the translation from the macro to pre-processed code should be apparent.

Inside the loop, we can actually set the output LED_PORT directly from the input BUTTON_PIN. How? Again, through the dereferencing of the structure, the compiler will set bit 4 of PORTB to the current value of bit 3 in PINB.

If you are confused now, then that is quite OK. We have dealt both with the C preprocessor and the compiler per se. In the preprocessor we have not only been involving straight-forward macros, but have used the token pasting operator (that is known to be confusing, and the source of more than one bug). (For more on macros, check out Macro tutorial and Macro Functions and the Wikipedia C Preprocessor entry.)

We have forced the compiler to look at a register as a struct, and when referencing the members we are using the token pasting operator to produce a correct member name.

You have at least three options now:

1) Not really understand why all this works, but accept that it does work and just use it.

2) Take a deep breath and start learning the ins and outs of the C Preprocesor, structs and typecasting to really understand what is going on here. This has very little to do with AVR-specific things and almost everything to do with standard C so any good book or other source of C knowledge will do.

3) Use the bit manipulation alternative presented when we started, using the bit manipulation operators to do the job.

You have this choice, but if you want my advice go with 3) for now until you gain proficiency in C, and then start on 2).

No matter what, be sure to learn more about C. Practically everything here is standard C. You can find online C tutorials at:

Programming Tutorial: C Made Easy

How C Programming Works

GNU C Programming Tutorial

And the following books are pretty good - in fact, the first should be required on every C programmer's desk!

The C Programming Language is almost a necessity.

Absolute Beginners Guide To C

Writing Solid Code I personally recommend this. Lots of good tips.

Book and Dev Kits: Smiley Micros - Smiley frequently posts on this forum!

Have fun!!

Stu

Edit 1: Fixed other compiler referenced. Did various typo and missing word fixes.

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

stu_san wrote:

In compilers such as IAR, the compiler allows the programmer to directly refer to a single bit of an AVR port. For example, if we want to set the third bit of PORTB, the following works just fine:
PORTB.3 = 1;

Not quite. "PORTB.3" is not valid syntax... only Codevision supports this non-standard format (AFAIK). IAR does offer bitwise access, but adheres to the C language requirements, like GCC, of member names starting with an underscore, or alpha character.

For IAR it would be "PORTB.PORTB_Bit3"

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

FYI: Original discussion was here: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=67291&highlight= . Ive made some comments there on my contribution to the above tutorial.

"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]

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

glitch wrote:
stu_san wrote:

In compilers such as IAR, the compiler allows the programmer to directly refer to a single bit of an AVR port. For example, if we want to set the third bit of PORTB, the following works just fine:
PORTB.3 = 1;

Not quite. "PORTB.3" is not valid syntax... only Codevision supports this non-standard format (AFAIK).

Thanks for the heads-up. I've change the phrase to "some compilers", not naming them. Perhaps that's better?

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

stu_san wrote:
Thanks for the heads-up. I've change the phrase to "some compilers", not naming them. Perhaps that's better?

Works for me :)

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

Fantastic tutorial, very interesting and useful.

i'm fairly new to AVRs and this post looks like it could help me on the way to understanding it all a bit better. So i guess i'll have to take your advice and

Quote:
Take a deep breath and start learning the ins and outs of the C Preprocesor

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

Quote:
unsigned int bit0:1

i'm sorry for asking this question a little bit late.

i'm not getting the meaning of the colon and 1

Quote:
bit0:1

"An hour with a book would have brought to your mind the secret that took the whole year to find;
the facts that you learned at enormous expense,were all on a library shelf to commence"

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

takashi_85 wrote:
i'm not getting the meaning of the colon and 1
It's standard C defining a bitfield one bit long.

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

thanks a lot the bitfield word was my key to understand.
i started using google to get the full picture

thank u again.

"An hour with a book would have brought to your mind the secret that took the whole year to find;
the facts that you learned at enormous expense,were all on a library shelf to commence"

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

You're welcome. Yes, knowing the correct name for a construct can make all the difference in finding information about it.

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

Hi Stu,
thanks for this excellent tutorial. Unfortunately I am one of them who use it but don't understand in full depth how it works. Will you please introduce how can I check a particular bit of a read/write register by usink your tricky way?
Thanks,
Istvan

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

dircon wrote:
Will you please introduce how can I check a particular bit of a read/write register by usink your tricky way?
Say that you defined an input pin B3 using
#define BUTTON_PIN  REGISTER_BIT(PINB,3)

, then'd you read the value of that bit by using something like

if (BUTTON_PIN) {
  do_whatever();
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

using portb.3 = 1 without the semicolon works fine for me i am using oshons avr sim but please remember to declear portb.3 as a bit

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

Hi,
its okay in case of an explicitly _input_ pin, but let me illustrate my problem:

#define LET_OC0 REGISTER_BIT(TCCR0,COM00)
#define GET_OC0 REGISTER_BIT(TCCR0,COM00)

To set as an output pin is ok:
LET_OC0=1;

But how can read if I want e.g. toggle it?

if ( GET_OC0==true)
{ do_something; }

Now GCC says:

../myproject.c:133: error: structure has no member named `bitCOM00'

Thanks,
Istvan

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

Hi folks, I got it. I changed symbolic bit references to numeric values and this way is ok.
Istvan

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

dircon wrote:
#define LET_OC0 REGISTER_BIT(TCCR0,COM00)
#define GET_OC0 REGISTER_BIT(TCCR0,COM00)
...
./myproject.c:133: error: structure has no member named `bitCOM00'
Your two definitions expand to the exact same thing. Note that REGISTER_BIT requires a number for the second argument. Look again at the definition of REGISTER_BIT
#define REGISTER_BIT(rg,bt) ((volatile _io_reg*)&rg)->bit##bt 

The ## concatenate the strings. So, your REGISTER_BIT expands to ((volatile _io_reg*)&TCCR0)->bitCOM00, but bitCOM00 is not a structure member, only bit0 to bit7.

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

Hi everyone, here goes my first post to AVRFREAKS:
I loved the tutorial, i´ve been looking for this answer for quite a few hours now. And I definitely love STU´s signature!

Oh, sorry, here goes:
Handling bits directly is a great way to go IMO, it makes program writing and mantaining quite easier. In fact, I´m also programming for 8051 on Keil C51 and they have a "sbit" definition which works just like that:
sbit LED = P0^1; // or better yet
sbit LED = LEDpORT^LEDpIN;
and there you go, no matter what changes on your board, you are covered with a few defines.
I came here trying to imitate this with winavr, but got no luck yet, I´ll try it your way for now. Have you looked for any code size or run time overhead of this? It may be even better than Keil C51 in terms of portability since it´s standard C, but bit-fields i think are implementation dependant so you could find yourself with your bits packed head-over-heels if you change compilers.
Anyway, that´s just me thinking after a few hours of googling, trying things and grumbling.

GREAT TUTORIAL STU!!!!
Regards,
Alex

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

Alex, the code presented uses single opcodes (assuming the register location is low enough) with gcc and IAR. Imagecraft doesn't not optimize this and CodeVision has bit variables that seem similar to the sbit behavior you described.

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

alex79 wrote:
Have you looked for any code size or run time overhead of this?
It really depends on the efficiency of the compiler's "back end" (the code generator and the optimizer) as to how well these constructs will be used. For registers in the low address range (0x00 - 0x1F), the GCC compiler actually generates single bit set and clear instructions. Other compilers may vary.

alex79 wrote:
It may be even better than Keil C51 in terms of portability since it´s standard C, but bit-fields i think are implementation dependent so you could find yourself with your bits packed head-over-heels if you change compilers.
Since the bit-field definition is a C standard, I would not expect that the ordering of the bits would change between compilers, but hey, what do I know? :wink: There are always crufty corners of C where one compiler decides to "make things easier" by not following the standard, or where the standard is intentionally ambiguous. I guess, "Your Mileage May Vary" is the best response I can give to this.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

Stu,

I'm guessing that for eight :1 fields in a uchar that almost all compilers will do the same even if it is "implementation dependent" (does any CPU put bit0 at the left and bit7 at the right??) but consider something like:

typedef struct { 
	UINT16 segment_leds_intensity		         : 	4,	  // intensity = 0 ..15:  0=min.  15=max intensity
UINT16 segment_leds_refresh_cycle_frequency      : 	4,	  // 70..310 Hz with 16Hz units: 0=70  15=310
UINT16 segment_leds_automation_select      	         : 	3,	  //  0=spin leds – all with the same intensity
  //  1=spin leds – diminishing intensities from leed led
  //  2=segment direct mode: disables spin leds. In
  //       this mode the host may emulate spinning 
  //       by writing to the field ‘segment_state_mask’
  //       in register OPERATE_SEGMENT_LEDS
   //   7=DEMO1 selected (???) 
	keep_ 
  UINT16 led_positions_when_switching_direction      : 1      	  //  =1 to keep the lit spin leds in the same 
  //  positions when the spin direction is 
  //  changed. What is seen is the position of
  //  the leed led shifts in the group. If =0
  //  then the leds trail clockwise or anti-clock
  // wise from the spin leds so different leds are 
  // lit when the spin led changes direction.
UINT16 segment_leds_param_1 		         :  3  	  //  = 0..7: for spin automations defines the 
                                                                                                          //      number of spin leds  which are grouped 
  //     behind the lead led for spin_leds 
  //     automations
UINT16 enable_spin_leds_intensities_profile	         :  1  	  //  = 1 to enable the use of four I2C bytes (ie 8 
  //       nibbles which  define an  intensities profile 
  //      weighting (4 bits) from the lead spin LED

} REG_CONFIGURE_SEGMENT_LEDS

care to predict exactly where those bits will be located?

 

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

clawson wrote:
care to predict exactly where those bits will be located?
I wouldn't mind predicting, but since the answer is not specified by the C language specification, I'd rely instead on observation.

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

Cliff wrote:
care to predict exactly where those bits will be located?
Exactly my point. Does the compiler take endian-ness into account? As you said, the bitfields in a uchar are likely to be pretty settled (as the bitfields in the original article are), but extending that to larger types is problematic.

Having written code that had to work on both a PowerPC and x86 processor, I understand the problems with endian-ness and bitfield ordering. I've come to the conclusion that, without testing, there is no such thing as "compiler independent" code, only "compiler compliant" code. A bold statement, but for non-trivial code an apparently true one.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

stu_san wrote:
I've come to the conclusion that, without testing, there is no such thing as "compiler independent" code, only "compiler compliant" code. A bold statement, but for non-trivial code an apparently true one.
There is "compiler independent" code. It is code whose function is guaranteed by the language specification, so that any conforming compiler will produce the specified results. As we all know, the C language specifications leaves many details unspecified and those areas are thus implementation-specific.

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

Hello, All.
"sbit.h"

(((*(volatile struct bits*)&port).b##pin))

works well on I/O register, but what about a GPR (r0 - r31)?
Is there some way to map the bitfield structure to particular MPU register, not an I/O location?

I'm using mega8. There is no GPIOR in IO space. Store flags in RAM is too slow. So, I'm trying to do something like that:

//Global flags
register volatile unsigned char flags asm("r10"); 
#define flags_TimerFired_bp         0
#define flags_ProcessingStarted_bp  1

#define FL_TIMERFIRED  SBIT(flags,0) /* does not work */
//error: address of global register variable 'flags' requested :-(

#define FL_PROCESSINGSTARTED  SBIT(flags,1) /* another flag, just to illustrate */

looking to avr/sfr_defs.h I found no suitable definition of register. There are memory or I/O locations only.

Final code using flags supposed to be like this

if (FL_TIMERFIRED) {FL_PROCESSINGSTARTED = 1}

is there a way to use register as such a mapping target? or maybe another similar solution?
Ideally it must generate sbr, cbr, sbrc, sbrs opcodes.

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

Quote:

Is there some way to map the bitfield structure to particular MPU register, not an I/O location?


Well there is

#define REG_3_BIT_5 ((*(volatile struct bits*)0x0003).b5)

but note a couple of things:

1) This stuff is for C. On the whole you cannot mess with R0..R31 as the C compiler is going to allocate and use them itself. There is however the -ffixed-N compiler flag that can tell it to avoid a register but this is costly and library code may still have been built to use it.

2) The registers appear at RAM address 0..31 so they can be accessed like any other memory location but they are in neither SBI/CBI nor IN/OUT range so any bit manipulation done to them will be some kind of LD/modify/ST (possibly LDS/STS) so if you are looking for "fast bit variables" this is not the way to achieve it. Use GPIOR0 on modern AVR or TWAR on your old mega8 instead which is in range of SBi/CBI and is an 8 bit read/write register (assuming you aren't using TWI that is!):

//==>   REG_3_BIT_5 = 1;
        ldi r30,lo8(3)
        ldi r31,0
        ld r24,Z
        ori r24,lo8(1<<5)
        st Z,r24

 

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

Thank You, clawson!

clawson wrote:
Well there is

#define REG_3_BIT_5 ((*(volatile struct bits*)0x0003).b5)

It works! Probably they've told to compiler that those are memory locations, but they've forgot to tell it that those are also "super fast memory locations", and we have no _SFR_REGFILE, no mov, cbr, etc.. :-/

clawson wrote:

There is however the -ffixed-N compiler flag that can tell it to avoid a register but this is costly and library code may still have been built to use it.

This is the case, I already use

register volatile unsigned char flags asm("r10");

assuming the reg is strictly reserved (not for libs, of course, but there are no libs in use).

clawson wrote:
2) The registers appear at RAM address 0..31 so they can be accessed like any other memory location but they are in neither SBI/CBI nor IN/OUT range so any bit manipulation done to them will be some kind of LD/modify/ST (possibly LDS/STS) so if you are looking for "fast bit variables" this is not the way to achieve it.

You're absolutely right, it's LDS/STS:

		REG_3_BIT_5 = ~REG_3_BIT_5;

	LDS       R24,0x0003
	SWAP      R24            
	LSR       R24
	ANDI      R24,0x07
	LAT       R24
	ANDI      R24,0x01
	SWAP      R24
	LSL       R24
	ANDI      R24,0xE0
	LDS       R25,0x0003
	ANDI      R25,0xDF
	OR        R25,R24
	STS       0x0003,R25

(huge and terrible :-/ )

clawson wrote:
Use GPIOR0 on modern AVR or TWAR on your old mega8 instead which is in range of SBi/CBI and is an 8 bit read/write register (assuming you aren't using TWI that is!)

Thanks also for TWAR hack! It seems they intended it to be used this way placing it in such a pretty nice location =)
But I do use TWI. =(
(Perhaps it's time to get some rest to see such hacks by myself =)

So, I decided to use weird "compiler-proof" format like this:

		//loop_until_bit_is_clear(flags, flags_TimerFired_bp);

		while ((flags & (1<<flags_TimerFired_bp)) == 0) {}

	SBRS      R10,0
	RJMP      PC-0x0001

       		flags &= ~(1<<flags_TimerFired_bp);

	LDI       R24,0xFE
	AND       R10,R24

It seems to result in a quite compact code and is readable enough.
And that is not my own lack of style sense, so not my personal shame, it is just how it is. =))

Thanks.