Header.h not recognizing a #define in sketch.ino

In my RA02.h below, the preprocessor does not find MCU_TYPE defined in the original sketch. I have tried both #define MCU_TYPE 68 and #define MCU_TYPE DUE_R3

My goals here are two:
(1) Make hardware changes only in OriginalSketch.ino
(2) Instantiate those changes in various Hardware.h and Hardware.cpp files, but require no manual changes each time.

How I know I have a problem:
(1) My #if MCU_TYPE == MKR_WiFi_1010 guard below always evaluates #else

(2) The IDE editor does not identify MCU_TYPE in RA02.h as a macro when I hover over it.

It occurs to me that this may be a matter of the order in which the translation units are compiled. Other similar comparisons work, for example #if MCU_MODE == RX_MODE. At execution time though, MCU_TYPE reliably evaluates as 68. What path to follow?

/*
* RA02.h
* Based on RadioLib Example PhysicalLayer_interface
*
* Communication is via SPI, the only documented method.
*/

#include <RadioLib.h>
#include "RR_LoRa.h"

#define RADIO_TYPE SX1278
#define MKR_WiFi_1010 77
#define Portenta_H7 80 
#define DUE_R3 68 
#define Heltec_ESP32 72 
#define RX_MODE 82
#define TX_MODE 84

// set the pinout depending on the wiring and module type
// SPI NSS pin:               10
// interrupt pin:             2
// reset pin:                 9 (unused on some modules)
// extra GPIO/interrupt pin:  3 (unused on some modules)
#if MCU_TYPE == MKR_WiFi_1010
  #define S_NSS 6
  #define S_INT 2
  #define S_RST 7
  #define S_GPI 3

#elif MCU_TYPE == DUE_R3
  #define S_NSS 10
  #define S_INT 2
  #define S_RST 9
  #define S_GPI 3

#else             // the indeterminate condition
  #define S_NSS 60
  #define S_INT 61
  #define S_RST 62
  #define S_GPI 63
#endif // RADIO_TYPE radio = new Module(S_NSS, S_INT, S_RST, S_GPI);

//Function Prototypes
void RA02_setup(void);          // Function that includes LoRa configuration
void RA02_tx_loop(void);        // Function that includes LoRa tx operation
void RA02_rx_loop(void);        // Function that includes LoRa rx operation
//void getCommand(void);     // Function to receive the command and parse it
//void execCommand(char, int);  // Function to execute relevant functions depending on the command

#ifndef DPACKET
#define DPACKET
 struct DataPacket {
  char station[5]; // 4 bytes plus NUL
  uint32_t time;   // 4 bytes
  int16_t lat;     // 2 bytes
  int16_t lon;     // 2 bytes
  uint16_t temp;   // 2 bytes
  int16_t alti;    // 2 bytes
  int16_t pres;    // 2 bytes
};
extern DataPacket txpacket;
#endif //DPACKET

Each translation unit is compiled independently from others. But translation depends on the #included header files. A header file can be translated differently when #included in various other files.

Each translation unit is compiled independently from others.

So then Hardware.cpp compiles after transcluding all the other required .h files including Hardware.h. At this point, whatever #defines are in the sketch.ino are invisible to the compiler. This is the challenge.

I don't understand your point :frowning:

The target hardware etc. is submitted to the compiler by the IDE from the Tools menu.

Yes, the processor is selected from the drop-down in the IDE. Hopefully, that's confirmed by the USB handshake.

"Hardware" in my parlance is other stuff: sensors, FRAM, radio module, MOSFET switches, and the like. To maintain modularity I write and test a hardware.cpp + hardware.h pair for each new bit and bob I add.

It seems to me that the sketch.ino should describe the configuration and operation of the whole system at the highest level. All of the other files are details. As I type this, I'm starting to think that I need one more high level header.h to define the configuration of the systems as a whole. Then, every other .h can point to it. Those who follow me can look at the sketch to see how the whole thing works, but edit the HighLevel.h to change the configuration.

Consider to switch from C to Pascal.

In former times a config.h and/or similar general headers have been added as abstract headers to all (library...) compilation units. Then each project added its version of all these headers.

Your topic is not specific to IDE 2.x but more a general programming question and hence has been moved.


Instead of having your own #define for the board, you can consider to base your code on predefined constants. E.g.

-DARDUINO_SAMD_MKRWIFI1010
-DARDUINO_SAM_DUE
-DARDUINO_AVR_LEONARDO
-DARDUINO_AVR_MICRO
-DARDUINO_AVR_UNO

You can find those constants by enabling verbose output during compilation, compiling e.g. blink and checking the output for -D (capital D).

Your modified RA02.h would look like this.
Notes:

  • I added a warning so you can see for which board you're compiling; you can remove that.
  • I disabled the other includes because I do not have the libraries
/*
* RA02.h
* Based on RadioLib Example PhysicalLayer_interface
*
* Communication is via SPI, the only documented method.
*/

//#include <RadioLib.h>
//#include "RR_LoRa.h"

#define RADIO_TYPE SX1278
#define MKR_WiFi_1010 77
#define Portenta_H7 80 
#define DUE_R3 68 
#define Heltec_ESP32 72 
#define RX_MODE 82
#define TX_MODE 84

// set the pinout depending on the wiring and module type
// SPI NSS pin:               10
// interrupt pin:             2
// reset pin:                 9 (unused on some modules)
// extra GPIO/interrupt pin:  3 (unused on some modules)
#if defined(ARDUINO_SAMD_MKRWIFI1010)
  #define S_NSS 6
  #define S_INT 2
  #define S_RST 7
  #define S_GPI 3
  #warning ARDUINO_SAMD_MKRWIFI1010
#elif defined (ARDUINO_SAM_DUE)
  #define S_NSS 10
  #define S_INT 2
  #define S_RST 9
  #define S_GPI 3
  #warning ARDUINO_SAM_DUE
#else
  #define S_NSS 60
  #define S_INT 61
  #define S_RST 62
  #define S_GPI 63
  #warning OTHER_BOARD
#endif // RADIO_TYPE radio = new Module(S_NSS, S_INT, S_RST, S_GPI);

//Function Prototypes
void RA02_setup(void);          // Function that includes LoRa configuration
void RA02_tx_loop(void);        // Function that includes LoRa tx operation
void RA02_rx_loop(void);        // Function that includes LoRa rx operation
//void getCommand(void);     // Function to receive the command and parse it
//void execCommand(char, int);  // Function to execute relevant functions depending on the command

#ifndef DPACKET
#define DPACKET
 struct DataPacket {
  char station[5]; // 4 bytes plus NUL
  uint32_t time;   // 4 bytes
  int16_t lat;     // 2 bytes
  int16_t lon;     // 2 bytes
  uint16_t temp;   // 2 bytes
  int16_t alti;    // 2 bytes
  int16_t pres;    // 2 bytes
};
extern DataPacket txpacket;
#endif //DPACKET

You will need to include this file everywhere where you need the #defines like S_NSS 6.

If you don't want to compile for any other boards (your indeterminate condition), you can change #warning OTHER_BOARD to #error OTHER_BOARD.

Advice: use an include guard so multiple includes of the same file will not result in redefinition errors.

2 Likes

That's what I was going to suggest at first based on this:

But now it seems @roberthadow has moved the goal post on what his definition of "hardware configuration" is:

and ...

Where he's gone off the beam is that can't be done at compile time using just a #define statement in the ".ino" file. That #define must be in a ".h" file that's is subsequently #include(d) in ALL ".cpp" files that need to change their compile time behavior based on the setting.

Thank you for your time. ...and everyone else who looked.

(1) I have adopted now (and gotten to work quite well) assignment of a decimal to each #defined Arduino board. Then I can assign #define MCU_TYPE board_name. That way, my MCU_TYPE is unique. I can never make the mistake of having two different boards defined when I compile. At any later time, I can test MCU_TYPE == board_name or MCU_MODE == TX.

(2) I will adopt the Arduino board naming standard suggested. That's great.

(3) #warning is a great help.

(4) Between the lines, I read that the simple Arduino .ino structure is fine for a simple sketch. When one has many (say four or more) .cpp and .h pairs in addition to the .ino sketch, shared stuff needs to go into its own .h file, from which all others can #include. While one might want to include the .ino file, that's not part of the game ("going off the beam").

I refactored my code to include a header (.h) with appropriate guards and the important global definitions and it works great. Many thanks.

(4) The decimal method requires that one assign a unique set of magic numbers once, then forget them and use explantory labels from then on. That way, I am only compiling for one target board, one of tx or rx, or one brand of accelerometer at a time.

(5) Thanks for reclassifying the post.

(6) I thank the good doctor for the suggestion that I switch from C to Pascal, but that would make my head explode.

(7) For those who read this thread and want to write a multi-file sketch without "not defined" and "previously declared errors," here are the high points.

(a) Write your sketch.ino with the minimum amount of code in setup() and loop(). Let setup() and loop() call functions you have defined elsewhere.

(b) "Elsewhere" includes pairs of .cpp and .h files you have organized according to function or hardware as suits your needs. Each includes an #include that points to a top level header file (.h) into which goes whatever you need globally.

(c) Your top level header file (.h) should include all of the #includes you need for external libraries and references to your own header files. Into this file goes prototype functions, struct, class, and variable definitions you need globally. Each of those is removed, one-by-one from your sketch.ino, until your sketch.ino is quite lean. Then make sure all of your .h files have an #include naming this top-level header file. Include guards so that as many times as this file is #included, the content is processed once and once only at compile time.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.