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

26 posts / 0 new
Last post
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

Pages