Best practices and general questions

Hello,

I'm new to the software development with the Arduino IDE.

I currently have a project where I use an Arduino Nano ESP32 and the Arduino IDE 2.3.2.

What are some general best practices and code structuring for Arduino?
I'm doing PLC development since 10 years now.

And general question:
I like the code snippets (sketches) of the libaries I use, but my main program gets really "messy" I'd say with all the used snippets so I'd like to rather restructure my .ino file, or split the different functions up into seperate files which I then just call in my main .ino file. Is that possible? Can I call setup and loop in other files too?

What are your go-to things you do for medium projects?

Because I'd also like to implement some QoL features, like WiFi scan & whats printed to the serial port, I'd like to display on a web server, so it's possible to see, what is happening without needing to plug into the COM port.. etc, also some diagnosis prints etc.

Thank you in advance!

Sure it is! The compiler is a C++ one, so you can either use "#include" to incorporate external file (located in the same .ino directory) or even create custom classes (.cpp) to be used in your code.

You can, but you shouldn't. If you do you will have endlessly recursive code... Until it crashes.

While you can have multiple .ino files and regular C++ source files in the root sketch directory, I'd recommend having just the one single .ino, which has the same name as the directory (that's what makes it a valid sketch); and then regular C++ source and headers under a tree in the src subdirectory. The compiler will recursively compile everything there and link them together. So

  • mysketch/
    • mysketch.ino
    • src/
      • some_stuff.cpp
      • some_stuff.h
      • one/
        • one.cpp
        • one.h
        • other_one.cpp
        • other_one.h
        • third_one.hpp
      • two/
        • two.cpp
        • two.h

Use relative directories to include, e.g. in mysketch.ino

#include "src/one/other_one.h"

normally i push often used piece of code in a function, or if this piece do something similar like LCD output, or time renew. so loop() contain only names of functions and it is mostly clear what sketch do. and when function ends, every not static variable will disappear and free memory.
extremely big chunk of code is better to place in external .ino file. good example is 3D printer or CNC firmware.

I want to do like a little sequence:

Scan wifi networks > connect to the network thats implemented into the project > connect to the mqtt broker thats implemented in the project

And after that sequence if everything is fine I just want to subscribe to a topic, deserialize json .. do some input/output stuff and also check if I lost connection to the wifi and then start the setup sequence again until everything is connected. A will message is also implemented.

But I'm currently already at 280 lines of code in the .ino file, which is a lot to scroll through.. thats why I wanted to just call those functions in my .ino file after another.

Also I want to setup a very simple web server which just prints the message i currently print to serial port.

Have you tried collapsing the functions that you don't currently want to see the details of ?

I feel like it's not visibly enough collapsed.. or spaced.

Currently trying to print my mac address without a wifi connection

As already mentioned, use of proper .h / .cpp files provides useful modularity. See My Port #5 in this Thread.

Take your pick:

#include "Arduino.h"
#include "WiFi.h"

void setup() {
	Serial.begin(115200);
	delay(2000);

	// Method 1
	uint8_t mac[6];
	WiFi.macAddress(mac);
	Serial.printf("MAC Address: %0.2X:%0.2X:%0.2X:%0.2X:%0.2X:%0.2X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	// Method 2
	Serial.printf("MAC Address: %s\n", WiFi.macAddress().c_str());
}

void loop() {
}

I can't get what you're saying here, please provide an example.
If you "fold" a function (Using Ctrl Shift ') it shows only the definition and collapses the content. E.g. the function:

void showDateKWH() {
  lcd.setCursor(0, 0);
  lcd.print("--/--");
  lcd.setCursor(0, 1);
  lcd.print("--.-k");
  lcd.setCursor(0, 0);

  sprintf(sTemp2, "%02d/%02d", Day, Month);
  lcd.print(sTemp2);
  lcd.setCursor(0, 1);
  dtostrf(KWH_MULT*totWh/1000, 5, 2, sTemp2);
  sTemp2[4] = 0;
  lcd.print(sTemp2);      
}

is being folded as:
image

By clicking on the ">" you can switch the fold on or off.
You can see all the folding keyboard shortcuts pressing F1 then search "Fold"

There are a few options.

  1. Create additional ino files in the sketch directory and give them a proper name.
    • mysketch/
      • mysketch.ino
      • WiFi.ino
      • Webserver.ino
  2. Add .h and .cpp files in the sketch directory
    • mysketch/
      • mysketch.ino
      • WiFi.h
      • WiFi.cpp
      • Webserver.h
      • Webserver.cpp
  3. The approach given in post #4.

Pros and cons for each.

  1. Multiple inos. All ino files will be preprocessed and concatenated to one big cpp file and that will be compiled.
    • Pros:
      • Easy for beginners.
    • Cons:
      • The creation of the big .cpp file might not always result in a correct .cpp file and the compiler will throw errors.
      • Making a mistake in e.g. WiFi.ino might result in an error reported for Webserver.ino.
  2. .h/.cpp files in the sketch directory
    • Pros:
      • None of the cons of multiple inos.
    • Cons:
      • If you have a lot of files navigating might become a little tricky once all tab pages no longer fit horizontally. I haven't touched IDE 2.x for a while so can't say for sure if you can access them from the left "file explorer" pane (if open).
  3. .h/.cpp in src subdirectories
    • Pros:
      • None of the cons of multiple inos.
    • Cons:
      • To my knowledge, you can not edit those files directly in the IDE; again, I haven't touched IDE 2.x for a while, maybe @kenb4 can advise.

You do not call setup() and loop(). As you might know, every C/C++ program has a function called main(); the Arduino environment hides it for you although you can find it in the core directory of the core that you're using. This main() function is responsible for calling setup() and loop() (and for configuring the hardware).

You can place loop() and setup() in their own ino files, e.g. loop.ino and setup.ino and leave the sketch's ino file itself empty. If you however break up your program in functions and place those functions in their own files (grouped by functionality as shown above), your setup() and loop() will stay relatively small so there is hardly a need for it.

I've used this code snippets now:

#include "esp_mac.h"
#include "Arduino.h"
#include "WiFi.h"
uint8_t mac[8];
uint8_t cam[6];

void setup() {
  Serial.begin(115200);
  esp_efuse_mac_get_default(mac);
	WiFi.macAddress(cam);
}

void loop() {
  // Method esp_efuse_mac_get_default
  Serial.printf("esp_efuse_mac_get_default MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  // Method 1
  Serial.printf("Method 1 MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n", cam[0], cam[1], cam[2], cam[3], cam[4], cam[5]);
  // Method 2
	Serial.printf("Method 2 MAC Address: %s\n", WiFi.macAddress().c_str());
  delay(20000);
}

But thank you anyway!

Yes, you can access them in the sketchbook tree. This applies to files in the root of the sketch, and any other subdirectories, including src (the only subdirectory the compiler back-end will check). The IDE just has no (obvious?) option to create files, other than in the root.

In the meatball menu at the right end of the tabs, there is a menu option to create a New Tab, which is a file in the root. Those tabs are opened automatically for existing files when the sketch is opened and cannot be closed; there are menu options to Delete or Rename the file. Tabs for other files elsewhere have a close icon. In that same menu, all those root files are also listed at the bottom, and may be easier to navigate vertically than with the tabs.

So creating a file in src is a minor one-time inconvenience for each file. I usually touch from a shell that is always open anyway. And the tab/menu features for sketch files in the root apply only to supported extensions

  export namespace Extensions {
    export const DEFAULT = '.ino';
    export const MAIN = [DEFAULT, '.pde'];
    export const SOURCE = ['.c', '.cpp', '.S', '.cxx', '.cc'];
    export const CODE_FILES = [
      ...MAIN,
      ...SOURCE,
      '.h',
      '.hh',
      '.hpp',
      '.tpp',
      '.ipp',
    ];
    export const ADDITIONAL = [...CODE_FILES, '.json', '.md', '.adoc'];
    export const ALL = Array.from(new Set([...MAIN, ...SOURCE, ...ADDITIONAL]));
  }

Con: Without the Arduino IDE pre-processing, you may have to #include a bunch of files that can be difficult to pin down, and your .cpp files will have to be CORRECT C++ (ie with prototypes for forward and external references.)

Arguably a pro, not a con :slight_smile:

And if in doubt, can always #include <Arduino.h> to get the kitchen sink

Yup, take off the training wheels and learn to do it correctly!!!

Yup, easy peasy.

  • split down the "large" code into functions
  • use OOP with classes/objects
  • use several tabs (=files) in the Arduino IDE to split your project into logic parts

see loop() just as a "controller" which calls the different things, it can be quite short, for example the call of a finite state machine, and the .update()/.run()/.tick() calls of your objects