Image

Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Extern keyword in C and proper way to use extern variables in embedded systems

+3
−0

I have an 8-bit microcontroller with 100 pins. Each pin belongs to a port with a unique address and each pin has a number from 0 to 7. I want to create a scheme where in the code each pin is a struct that carries the pin's port address and number with it. I have defined a pin_t struct in gpio.h.

/*gpio.h */
typedef enum {INPUT = 0, INPUT_PULLUP = 1, OUTPUT = 2} gpio_mode_t;
typedef enum {LOW = 0, HIGH = 1} gpio_value_t;

typedef struct
{
    volatile uint8_t *const port_addr; //const pointer that points to volatile register
    const uint8_t pin_number;
} pin_t;

void pin_mode(const pin_t* pin, gpio_mode_t mode);
void digital_write(const pin_t* pin, gpio_value_t value);

Then, I have pins.c in which all the pin definitions are made. The port address and pin number should not be changed anywhere else in the program.

/* pins.c */
#include "gpio.h"
pin_t D1  = {.port_addr = &PORTG, .pin_number = 5};
pin_t D2  = {.port_addr = &PORTE, .pin_number = 0};
/* more definitions below ...*/

Then I have pins.h that declares each pin extern.

/*pins.h */
extern pin_t D1;
extern pin_t D2;
/* more below ...*/

Such that I in main.c can do things like this

/* main.c */
#include "pins.h"
int main(void)
{
    pin_mode(&D1, OUTPUT);
    for(;;) {}
}

Question 1: As far as I understand it, declaring variables as extern in pins.h makes them visible to source files that include pins.h, and I need not define them again. But to be honest, I'm not sure why I need extern in the first place. I get a compiler error if I remove it, saying there are multiple definitions of the pins in main.o. And with the same logic, shouldn't the function prototypes in gpio.h also be extern for my source files to see them?

Question 2: Is this the appropriate way to share variables across multiple files for an embedded system?

History

1 comment thread

Vintage tutorial about extern in C (1 comment)

2 answers

+4
−0

And with the same logic, shouldn't the function prototypes in gpio.h also be extern for my source files to see them?

Function declarations default to being extern. If you don't specify any storage-class specifiers, a function prototype is extern.

This is specified in C23 in 6.2.2p5:

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#subsection.6.2.2

If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern.

Function definitions are only those that have a function body, so the storage-class specifier is independent.

In the case of variables, it's different.

As far as I understand it, declaring variables as extern in pins.h makes them visible to source files that include pins.h, and I need not define them again.

You need not declare them again. There's a difference between declarations and definitions.

With functions, it's more obvious: a function prototype declares a function, but only a function definition (those that have a function body) defines a function.

With variables, it's different: automatic variables and static variables are not declared; they're directly defined. With extern variables, you can declare them without defining them.

The way to define a variable with external linkage is to declare it in file scope without any storage class specifiers.

The way to declare a variable with external linkage is to declare it in file scope with the extern storage-class specifier.

But to be honest, I'm not sure why I need extern in the first place. I get a compiler error if I remove it, saying there are multiple definitions of the pins in main.o.

To disambiguate between a declaration and a definition. The definition is what assigns some storage to the name, and also the initial value.

I get a compiler error if I remove it, saying there are multiple definitions of the pins in main.o.

Because by removing it, you're defining the variable in more than one translation unit, and thus assigning a different storage to the same variable name in different TUs. There must be unambiguous storage for any given variable.

History

0 comment threads

+2
−0

Regarding the part "when to use extern"/external linkage for objects (not functions), the general rule of thumb for all C programs embedded or otherwise is: never. Check out Why is global evil?

Also note that declaring variables at file scope without neither extern nor static in C makes them subject to a fuzzy rule called "tentative definitions" and there's a lot that can be said about that dubious feature, but it is better to simply never use tentative definitions either.

Now of course, there exist nearly no programming rule you should follow as strictly as never, even if this one comes close...


For embedded systems there is one acceptable exception: specifically when you have a register map directly corresponding to memory-mapped register hardware and that register map may be used by any library in your project. Then it would be acceptable to provide all such registers as extern in a header, to be defined by a a corresponding .c file. That is, if you want to be able to view the registers in a common debugger as if they were any other variable.

There are three different flavours of debuggers here:

  • Excellent MCU-aware debuggers for embedded systems that know about MCU-specific hardware peripherals without you providing any particular info.
  • So-so debuggers that will let you view hardware peripheral registers but only if given info about them in an ELF file. And for the registers to end up in the ELF file after linking, a C file needs to define them and the linker script needs to tell the linker "this memory area is hands-off for you".
  • Awful debuggers (Eclipse...) that simply don't have a clue about microcontrollers or about "scary memory thingies". They will let you view registers if provided in an ELF file too but don't treat them any different than any other RAM cell and what they do with the memory area despite the linker script is anyone's guess. They might go ahead and corrupt the register memory map in case of hardware peripherals that clear flags by reading them etc.

Depending on if you picked an excellent, so-so or awful tool chain, you may need to adapt the source code accordingly.

More info here: How to access a hardware register from firmware?


Now in your specific case, pin_t is actually not a hardware peripheral register, but an abstraction layer on top of it, so it should not be shared around through global spaghetti. Rather, it should be local and private to the file using it. In general: writing generic HAL layers on top of GPIO rarely ends up well and is not something I recommend - it is very easy to create needless bloat that way since GPIO at least historically was very simple and straight-forward. But this is a whole story of its own.

History

0 comment threads

Sign up to answer this question »