Arduino - Functions
Arduino - Writing Modular Code with Functions
Section titled “Arduino - Writing Modular Code with Functions”As your programs grow, keeping all your code inside setup() and loop() becomes messy and hard to manage. Functions are the primary tool for structuring programs into logical, reusable blocks of code, each designed to perform a specific task.
Why Use Functions?
Section titled “Why Use Functions?”- Organization: Functions break down complex problems into smaller, manageable pieces (e.g.,
readSensors(),updateDisplay(),sendData()). - Reusability (DRY Principle): Stands for “Don’t Repeat Yourself”. If you need to perform the same action multiple times, you write the code once in a function and call it whenever needed.
- Readability: Well-named functions make your code self-documenting.
blinkLed(3, 500);is much clearer than a block offor,digitalWrite, anddelaycalls. - Debugging: Isolating code into functions makes it easier to test and debug individual parts of your program without affecting the rest.
- Modularity: Functions allow you to create libraries of code that can be easily shared and used in other projects.
Every Arduino sketch already uses two special, required functions: setup(), which runs once at the beginning, and loop(), which runs continuously.
Anatomy of a Function
Section titled “Anatomy of a Function”A function definition in C++ has four main parts:
returnType functionName(parameterType parameterName1, ...){ // The function's body: code to be executed return returnValue; // (only if returnType is not 'void')}- Return Type: The data type of the value the function sends back after it’s done (e.g.,
int,float,bool). If the function doesn’t return anything, you usevoid. - Function Name: A descriptive name that follows the same rules as variable names.
- Parameters (or Arguments): A list of variables that act as inputs to the function. This is how you pass data into the function. If a function doesn’t take any inputs, the parentheses are empty:
functionName(). - Function Body: The block of code inside the curly braces
{}that gets executed when the function is called.
Declaring and Defining Functions
Section titled “Declaring and Defining Functions”The C++ compiler reads your code from top to bottom. It must know about a function before you try to call it. You have two ways to achieve this:
- Definition Before Use: Simply define the entire function above any code that calls it (e.g., above
setup()andloop()). This is simple but can make the top of your file cluttered. - Prototype then Definition (Recommended): Declare a prototype at the top of your sketch. A prototype is like a function’s signature without the body. This tells the compiler, “A function with this name, return type, and parameters exists somewhere.” You can then place the full function definition anywhere else, typically below
loop()for better organization.
Example: Using a Function Prototype
Section titled “Example: Using a Function Prototype”This complete sketch demonstrates a void function and a function that returns a value, using the recommended prototype method.
// ============ Function Prototypes ============
// A function that takes two parameters and performs an action (blinking).// It does not return a value, so its return type is 'void'.void blinkLed(int pin, int blinkCount);
// A function that performs a calculation and returns a result.// It returns an integer, so its return type is 'int'.int celsiusToFahrenheit(int celsius);
// ============ Global Variables ============const int BUILTIN_LED_PIN = LED_BUILTIN;
// ================== Setup ===================void setup() { Serial.begin(9600); pinMode(BUILTIN_LED_PIN, OUTPUT);
Serial.println("--- Function Demo ---");
// Calling the 'blinkLed' function to signal setup is complete. Serial.println("Blinking LED 3 times..."); blinkLed(BUILTIN_LED_PIN, 3);
// Calling the 'celsiusToFahrenheit' function and storing the result. int tempC = 25; int tempF = celsiusToFahrenheit(tempC); Serial.print(tempC); Serial.print(" degrees Celsius is "); Serial.print(tempF); Serial.println(" degrees Fahrenheit.");}
// ================== Loop ====================void loop() { // The main loop is kept clean. All logic is in functions. // This makes the program's intent clear.}
// ========== Function Definitions ===========
/** * @brief Blinks a specified LED a certain number of times. * @param pin The pin number of the LED to blink. * @param blinkCount The number of times to blink the LED. */void blinkLed(int pin, int blinkCount) { for (int i = 0; i < blinkCount; i++) { digitalWrite(pin, HIGH); delay(250); digitalWrite(pin, LOW); delay(250); }}
/** * @brief Converts a temperature from Celsius to Fahrenheit. * @param celsius The temperature in degrees Celsius. * @return The temperature in degrees Fahrenheit as an integer. */int celsiusToFahrenheit(int celsius) { // Using floating point for the calculation for accuracy before converting back to int return (celsius * 9.0 / 5.0) + 32;}Best Practices for Writing Functions
Section titled “Best Practices for Writing Functions”- Single Responsibility Principle: A function should do one thing and do it well. If your function is named
readSensorAndPrintValue, consider splitting it intoint readSensor()andvoid printValue(int value). - Descriptive Naming: Name functions based on the action they perform (
calculateAverage,checkForButtonPress) or the value they return (isButtonPressed). - Keep Functions Short: Shorter functions are easier to understand, test, and debug.
- Pass by Value vs. Pass by Reference: By default, arguments are passed by value, meaning the function gets a copy of the variable. Modifying it inside the function does not affect the original. To modify the original variable, you can pass by reference by adding an ampersand (
&) to the parameter type (e.g.,void myFunction(int& myVar)). Use this with caution.