← Takaisin Arduino-perusteisiin

Arduino-perusteet – Harjoitus

Osa 1: Arduino-simulaatio 📋 Näytä
Tehtävä 1: LED-vilkkuminen (Wokwi) 📋 Näytä

Teoria: Arduino-ohjelman rakenne ja digitaaliset pinnit

Mikä erottaa Arduino-ohjelmoinnin?

Keskeinen ero Arduino-ohjelmoinnin ja tietokoneohjelmoinnin välillä on, että voimme lukea ja hallita prosessorin pinnien tilaa.

  • Voimme lukea, onko pinnillä jännite (digitaalinen päällä/pois)
  • Voimme hallita jännitettä yksittäisillä prosessorin pinneillä (Arduino Unolla: 0V–5V)

On myös erityisiä analog-digitaali pinnejä (ADC-pinnit), joista voimme lukea jännitetasoja. Käsittelemme ADC-pinnit myöhemmin.

Setup- ja loop-funktiot

Kun avaat Arduino-koodieditorin, näet kaksi valmiiksi luotua funktiota:

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}

setup: Suoritetaan kerran, kun Arduino käynnistyy. Tässä määritellään asetukset ja alustetaan komponentit.

loop: Suoritetaan toistuvasti setup-funktion jälkeen. Käytä tätä jatkuviin tehtäviin: reagoimiseen muutoksiin, antureiden lukemiseen ja komponenttien hallintaan.

Kaikki koodi näiden funktioiden sisällä kirjoitetaan aaltosulkeiden { } väliin.

Funktioiden kutsuminen

Jotta koodissa tapahtuu jotain, kutsut funktiota tällä yleisellä muodolla:

functionName(parameter1, parameter2);
  • functionName — kertoo, mikä funktio suoritetaan
  • parameters — funktiolle annettavat arvot
  • ; — puolipiste päättää lauseen

Arduino tarjoaa monia valmiita funktioita yleisiin tehtäviin. Tutustumme oman funktioiden luomiseen myöhemmin.

Digitaalisten pinnien hallinta

Arduinon fyysisiä sähköisiä liitäntöjä kutsutaan pinneiksi. Pinneillä Arduino voi hallita ulkoisia komponentteja ja lukea anturiarvoja. Pinnin numero on yleensä painettu piirilevyn pinnin otsikkoon. Simulaattorissa pinnit ovat Arduinon reunoilla ja niiden numerot on painettu niiden viereen.

Arduino Uno pinnit

Tässä vaiheessa käytämme pinnejä yksinkertaisina päälle/pois-kytkiminä LEDien kaltaisten komponenttien hallintaan.

Kaksi keskeistä funktiota digitaalisille pinneille:

pinMode(0, OUTPUT);   // Aseta pinni '0' OUTPUT-tilaan

digitalWrite(0, HIGH); // Aseta pinnin jännite HIGH-tilaan (5V)
  • pinMode(pin, mode) — määrittää, käytetäänkö pinniä INPUT- vai OUTPUT-tilassa:
    • INPUT — tarkoittaa, että pinnin tila (onko siinä 0V vai 5V jännite) voidaan lukea ohjelmallisesti. Käyttämällä komentoa digitalRead ohjelma voi lukea, onko pinniin kytketty ulkoinen jännite 5V (HIGH) vai 0V (LOW).
    • OUTPUT — tarkoittaa, että pinni voi hallita jännitettä (0V tai 5V). LEDin ohjaamiseksi asetamme pinnin OUTPUT-tilaan.
  • digitalWrite(pin, state) — hallitsee jännitettä OUTPUT-pinnillä:
    • HIGH — asettaa pinnin 5V:een (virta päällä)
    • LOW — asettaa pinnin 0V:een (virta pois)

Tehtävät:

  1. Tehtävä 1: Mitä muutoksia tarvitaan, jotta punainen LED syttyy?
    💡 Lisätietoa: LED-anodi/katodi-dokumentaatio
  2. Tehtävä 2: Miten voimme saada molemmat LEDit syttymään samanaikaisesti?
  3. Etsi virhe: Etsi ja korjaa virhe koodista, jotta LED vilkkuu oikein.
  4. Ratkaisu: Tarkista korjattu koodi ja vertaa omaan ratkaisuusi.

⚠️ Tärkeää: Voit tarkistaa vastusarvoja simulaatiossa klikkaamalla vastusta. Huomaa, että Wokwi ei tarkista, onko vastusarvo oikea. Jos vastus on liian suuri, LED ei sytty. Jos se on liian pieni, LED voi palaa (oikeissa piireissä).

Vastusarvo simulaattorissa

Vastusarvo

LED-anodi ja -katodi

LED-pinnit: Anodi (+) / Katodi (−)

Tehtävä 2: Muuttujat ja funktiot (Wokwi) 📋 Näytä

Teoria: Muuttujat ja funktiot

The delay() function and loop

As mentioned in the basics section, loop runs repeatedly when the Arduino program executes. Sometimes it's necessary to wait a moment before proceeding to the next step. This is where the delay function is used.

delay(1000); // Wait 1000 milliseconds

The delay function takes a parameter in milliseconds and waits until the specified time has elapsed. During this time, no other code is executed, and the Arduino does nothing.

Variables

One essential concept in programming is variables. Variables are a way to store, for example, numeric values for the program to use.

Variables can be local or global. Global variables are visible throughout the entire code, while local variables are only visible within the code block where they are defined (for example, inside a function) and not outside it. A variable can also be static, meaning its value cannot be changed. More variable types will be introduced later, but for now we'll focus on these most important ones.

Defining variables in Arduino programming happens as follows:

int myVariable = 100;
  • int defines the variable type.
  • myVariable is the variable name in this example. The variable name can be freely chosen.
  • = 100 sets the initial value for the variable. Note that, like a function call, this statement ends with a semicolon ;

In Arduino programming, the variable type must always be defined. At this stage, we introduce three different variable types:

  • int variables are integers and can have values between -32768 and 32767
  • bool variables are boolean values and can be true or false
  • float variables represent decimal numbers

Changing variable values

As the name suggests, a variable's value can change during program execution. You can change a variable's value with code:

myVariable = 42;

As you can see, setting a variable's value is similar to creating a new variable, except without defining the variable type.

Variables can also be used in mathematical operations and their values can be stored in other variables:

int myVariable = 100;
int anotherVariable = myVariable + 50; // After this, anotherVariable's value is 150

Variables can also be used to modify themselves:

int myVariable = 100;
myVariable = myVariable - 20; // After this, myVariable's value is 80

The biggest benefit comes from using variables as function parameters:

int pauseTime = 1000;
delay(pauseTime); // Wait 1000 milliseconds

Functions

Functions are reusable blocks of code that perform a specific task. Instead of writing the same code multiple times, you can create a function and call it whenever needed.

Creating your own function:

void myFunction(int parameter) {
  // Code to execute
  digitalWrite(0, HIGH);
  delay(parameter);
  digitalWrite(0, LOW);
  delay(parameter);
}

Calling the function:

void loop() {
  myFunction(500);  // Calls the function with 500ms timing
}
  • void — means the function doesn't return a value
  • myFunction — the function name (you choose this)
  • (int parameter) — the parameter(s) the function accepts
  • Code inside { } is executed when the function is called

We will cover functions in more detail later, including return values and more advanced topics.

Variable naming rules

There are a few special rules for naming variables:

  • Variable name cannot start with a number
  • Variable cannot contain special characters or spaces, e.g., + - * / # . However, underscore _ is allowed.
  • Variable name is case-sensitive (uppercase and lowercase letters matter)
  • Variable name cannot be a reserved keyword in Arduino programming language, e.g., int, bool, float, if, for, etc.

To Do:

  1. Tehtävä 1: Morsea "S" muuttujien avulla (kolme lyhyttä vilkkumista)
  2. Tehtävä 2: Morsea "S" funktioiden ja muuttujien avulla
  3. Etsi virhe: Etsi ja korjaa kaksi loogista virhettä koodista
  4. Ratkaisu: Tarkista korjattu koodi
Tehtävä 3: If-lauseet (Wokwi) 📋 Näytä

Teoria: Ehtolauseet

Introduction to conditionals

So far, the programs we've written have been linear in their execution. For our program to react to changes in its environment, we need conditional statements. Conditional statements, as the name suggests, set a condition based on which the program execution branches into two or more paths.

The if-statement

The simplest conditional statement is the if structure.

if (condition) {
  // If condition is true, execute code inside braces
}

The if structure starts with the reserved keyword if. After this, the condition to be evaluated is placed inside parentheses. Finally, the code to execute when the condition is met is placed inside curly braces.

Boolean conditions

The condition used in an if statement is evaluated as a boolean value (bool type). If the condition evaluates to true, the code inside the braces is executed. If it evaluates to false, the code inside the braces is skipped.

The condition in an if statement can also be a numeric value. In this case, zero (0) is read as false and any non-zero number (including negative numbers) is read as true.

💡 Note: In fact, true is also the number 1 and false is 0. This means you can use boolean values in arithmetic operations (though this is rarely needed).

Usually, you don't directly set the value to true or false in an if statement, but rather use it to monitor the state of a variable:

bool myVariable = true;

if (myVariable) {
  // Execute if myVariable is true
}

Comparison and boolean values

A common use case for if statements is examining and comparing numeric values. This way, we can produce boolean values from numeric values.

int myVariable = 100;

if (myVariable == 90) {
  // This won't execute because the if condition is false
}

In Arduino programming (and other programming languages), comparing numeric values is done using the following operators:

  • == — Equality. Result is true if values are equal, otherwise false. Note the two == signs!
  • > — Greater than. Result is true if the left value is greater than the right value.
  • < — Less than. Result is true if the left value is less than the right value.
  • >= — Greater than or equal to. Result is true if the left value is greater than or equal to the right value.
  • <= — Less than or equal to. Result is true if the left value is less than or equal to the right value.
  • != — Not equal. Result is true if values are different.

If you want to get the inverse of a boolean value, you can use the ! operator:

bool truthValue = false;

if (!truthValue) {
  // Code here executes because the inverse of false is true
}

If-else structure

Sometimes in a conditional statement you want to execute code even when the condition is not met. In this case, you can use the else structure in addition to the if structure.

if (condition) {
  // If condition is true, execute code inside braces
} else {
  // If condition was not true, execute this code
}

If the condition is true, the code inside the first set of braces is executed. If the condition is not true, the code inside the braces after else is executed.

If - else if - else

In addition to simple either-or comparisons, sometimes you need to react to multiple different situations. In this case, you can use else if sections in addition to if and else sections.

if (condition) {
  // Execute if condition is true
} else if (anotherCondition) {
  // Execute if anotherCondition is true
}

You can add as many else if sections as needed without limitations. After the else if sections, you can also add an else section if needed, which executes if none of the conditions are met.

if (condition) {
  // Execute if condition is true
} else if (anotherCondition) {
  // Execute if anotherCondition is true
} else if (thirdCondition) {
  // Execute if thirdCondition is true
} else {
  // Execute if none of the conditions are met
}

Complete example: Traffic light

Here's a practical example using comparison operators to create a traffic light. How should the loop function be modified to create working traffic lights?

// How should the loop function content be modified to create traffic lights?

int myVariable = 100;

void setup() {
  pinMode(0, OUTPUT); // Set pin '0' to OUTPUT mode
  pinMode(1, OUTPUT); // Set pin '1' to OUTPUT mode
  pinMode(2, OUTPUT); // Set pin '2' to OUTPUT mode

  if (myVariable != 100) { // Using comparison '!='
    digitalWrite(0, HIGH); // Turn on green light
  }

  if (myVariable < 30) { // Using comparison '<'
    digitalWrite(1, HIGH); // Turn on yellow light
  }

  if (myVariable >= 101) { // Using comparison '>='
    digitalWrite(2, HIGH); // Turn on red light
  }
}

void loop() {
  // Not needed in this exercise
}

Common errors with if-statements

"Anyone can make mistakes, but for a complete disaster, you need a computer."

⚠️ Typo errors (syntax errors):

  • Using = instead of == in comparisons
  • Forgetting curly braces { } around code blocks
  • Missing semicolon ; after statements inside the if-block
  • Confusing > (greater than) with >> (bit shift operator)
  • Forgetting parentheses ( ) around the condition

❌ Logical impossibilities:

  • Checking if a value is both > 100 and < 50 (impossible!)
  • Using else if with conditions that can never be true based on previous conditions
  • Inverting logic with ! operator incorrectly, creating opposite behavior

To Do:

  1. Tehtävä 1: Käytä if-lausetta ehtojen tarkistamiseen
  2. Tehtävä 2: Käytä if-else-lausetta kahden suorituspolun luomiseen
  3. Etsi virhe: Etsi virhe ja korjaa koodi. Kytkentä on OK.
  4. Ratkaisu: Tarkista korjattu koodi

    What was fixed: Changed pinMode(3, INPUT) to pinMode(3, INPUT_PULLUP)

    INPUT_PULLUP sets the pin to 5V voltage using an internal pull-up resistor. This keeps the pin normally in HIGH state. When the pin is connected to ground (button pressed), its state changes to LOW.

    ⚠️ Simulator limitations: In this example, the simulator doesn't operate correctly - it should not change the LED state without the INPUT_PULLUP setting. Additionally, simulators don't properly account for button bounce (when a button is pressed, it doesn't instantly change to 0V - achieving the final state takes time).

    It's important to note that simulators only provide approximate results. All circuits must naturally be tested in real hardware. For example, the simulator doesn't verify if LED current-limiting resistors are correct - even if the resistor value were zero ohms or missing entirely, the simulator would still show "OK" results. In reality, the LED would burn out and the processor might be damaged.

Tehtävä 4: INPUT ja digitalRead (Wokwi) 📋 Näytä

Teoria: Digitaalisten tulojen lukeminen

INPUT mode and external control

So far in the exercises, the Arduino has operated completely independently. But as mentioned earlier in the course, Arduino can also react to external control. At its simplest, this happens through the digitalRead() function.

pinMode(3, INPUT_PULLUP); // Set pin '3' to INPUT mode with pull-up

The digitalRead() function

For digitalRead to work correctly, the pin being read must first be set to INPUT mode. This means the pin cannot be controlled with digitalWrite(), but its value can be read with digitalRead().

void setup() {
  pinMode(3, INPUT_PULLUP); // Set pin '3' to INPUT mode
}

void loop() {
  int button = digitalRead(3); // Read pin 3 value and store in 'button' variable
}

Function return values

The functions we've used so far - pinMode(), digitalWrite(), and delay() - have only controlled Arduino's behavior. The newly introduced digitalRead() differs in that it's used to get information from the Arduino. In this case, the function "returns" the requested value. The returned value is stored in a variable and can be used later, for example in an if-statement.

int button = digitalRead(3); // Read pin 3 value and store in 'button' variable

In this code, pin 3's voltage is read using the digitalRead() function. digitalRead takes the pin number to read as a parameter and returns HIGH if voltage is connected, and LOW if voltage is not connected.

Using digitalRead with if-statements

The stored variable can be compared in an if-statement:

if (button == HIGH) { // Compare 'button' variable value
  digitalWrite(0, HIGH); // Turn green LED on
} else if (button == LOW) {
  digitalWrite(0, LOW); // Turn green LED off
}

Complete example

void setup() {
  pinMode(0, OUTPUT);       // Set pin '0' to OUTPUT mode
  pinMode(3, INPUT_PULLUP); // Set pin '3' to INPUT mode with pull-up
}

void loop() {
  int button = digitalRead(3); // Read pin 3 value and store in 'button' variable

  if (button == HIGH) { // Compare 'button' variable value
    digitalWrite(0, HIGH); // Turn green LED on
  } else if (button == LOW) { // Compare 'button' variable value
    digitalWrite(0, LOW); // Turn green LED off
  }
}

To Do:

  1. Tehtävä 1: Vihreä nappi ohjaa vihreän LEDin päälle/pois
  2. Tehtävä 2: Muokkaa koodia niin, että LED on normaalisti PÄÄLLÄ, nappi sammuttaa sen
  3. Etsi virhe: Etsi ja korjaa digitalRead-virhe
  4. Ratkaisu: Tarkista korjattu koodi
Tehtävä 5: While-silmukka (Wokwi) 📋 Näytä

Teoria: While-silmukan rakenne

Introduction to while loops

Sometimes in code, you need to wait for a condition to be met. This is where the while loop structure is used.

while (condition) {
  // Do things as long as condition is true
}

The while loop resembles the if structure in function, with the difference that the while loop's condition is checked over and over again until it is no longer true.

Counting iterations

By combining a while loop with a variable, you can achieve a limited number of iterations:

int iterations = 0;           // Initialize 'iterations' variable with value 0

while (iterations < 3) {      // Continue until three iterations are completed
  iterations = iterations + 1; // Increase iteration count by one

  // Code here will be executed three times
}
// Continue here when desired iterations are completed

In this code, the while loop is used to count iterations. When the iterations exceed the desired limit, the while loop's condition is no longer met, and code continues after the curly braces.

Waiting for a condition

Another use case for the while loop is to wait until something happens. This can be combined with, for example, the return value of the digitalRead() function.

while (digitalRead(3) == HIGH) {
  digitalWrite(0, HIGH);
}
digitalWrite(0, LOW);

In this code, the LED is kept on as long as pin 3 is connected to voltage. Note also that the digitalRead() function's return value is not separately stored in a variable, but is compared directly to the HIGH value.

Complete example

This code blinks an LED three times. How would you modify the code to make the LED blink five times?

void setup() {
  pinMode(0, OUTPUT);
}

void loop() {
  int count = 0;
  
  while (count < 3) {        // Blinks three times
    digitalWrite(0, HIGH);
    delay(200);
    digitalWrite(0, LOW);
    delay(200);
    count = count + 1;
  }
  
  delay(1000);              // Wait before repeating
}

Common mistake with while loops

The method used earlier with if-statements - storing the digitalRead() return value in a variable - doesn't work with while loops. Can you explain why?

❌ This doesn't work:

int button = digitalRead(3);
while (button == HIGH) {
  digitalWrite(0, HIGH);
}
digitalWrite(0, LOW);

The problem is that the button variable's value is not updated by calling digitalRead() again, so its value remains unchanged from iteration to iteration.

✅ Correct approach:

int button = digitalRead(3);
while (button == HIGH) {
  digitalWrite(0, HIGH);
  button = digitalRead(3);  // Update 'button' variable value
}
digitalWrite(0, LOW);

Here, the button value is updated at the end of each iteration before the next check of the while loop's condition.

To Do:

  1. Tehtävä 1: Muokkaa koodia toistamaan 5 kertaa 3:n sijaan
  2. Tehtävä 2: Käytä while-silmukkaa ehtomuuttujan kanssa
  3. Etsi virhe: Etsi ja korjaa muuttujan kasvatuspaikan virhe
  4. Ratkaisu: Tarkista korjattu koodi (korjaa laskurin ylivuotovirheen)

    ⚠️ Why overrun errors are hard to detect:

    When count is placed outside the while loop, the variable keeps incrementing every loop cycle. Since int is a 16-bit value (max 32767), after reaching the maximum, it wraps around to -32768 and continues. This means the condition count < 5 will eventually be true again after overflowing, causing unpredictable behavior. The bug might not appear immediately and could work correctly for a while before failing, making it very difficult to debug.

Tehtävä 6: Sarjaliikenne (Wokwi) 📋 Näytä

Theory: Serial port communication

Introduction to Serial port

Sometimes it would be useful to get information about the program's operation stages while the program is running. On Arduino, this is possible using the serial port. The serial port enables communication between Arduino and the computer via the Arduino's USB cable - Arduino can send messages to the computer. This way you can monitor, for example, variable values during program execution.

Serial.begin() - Initialization

For Arduino to be able to send messages, its serial port must be enabled. This is done in the setup() function.

void setup() {
  Serial.begin(115200);
}

The Serial.begin() function initializes Arduino to use the serial port. The value 115200 specifies the serial port speed (baud rate).

⚠️ Important: The PC must have the same settings as Arduino. When using Arduino's Serial Monitor, both communication speeds (baud rate) must match. For other serial communication programs, see instructions on the Arduino website.

Serial.println() - Sending messages

Sending messages is done with the Serial.println() function.

void loop() {
  Serial.println(1);
  delay(1000);
}

This code repeatedly sends the number 1 once per second.

Printing text

The Serial.println() function can also print text.

Serial.println("This will be printed."); // Serial port prints "This will be printed."

An empty Serial.println() function prints a blank line. This can be used, for example, to separate outputs from different loop() function runs.

Printing variable values

The Serial.println() function can also print variable values. This is especially useful when you want to monitor the state of a running program during its execution.

int myVariable = 1;           // Create variable and set its value to 1
Serial.println(myVariable);   // Serial port prints "1"
myVariable = 2;               // Reset variable value to 2
Serial.println(myVariable);   // Serial port prints "2"

You can also print float and boolean type variables with the println() function.

float decimalNumber = 1.1;    // Create float type variable and set value to 1.1
Serial.println(decimalNumber); // Serial port prints "1.1"

bool truthValue = true;       // Create boolean type variable and set value to true
Serial.println(truthValue);   // Serial port prints "1"

💡 Note: As you can see, a boolean variable actually stores its value as a number. Boolean has two values: true (1) and false (0).

Complete example

void setup() {
  Serial.begin(115200);  // Initialize serial communication at 115200 baud
}

void loop() {
  Serial.println(1);     // Send number 1 to serial monitor
  delay(1000);           // Wait 1 second
}

Two-way communication

Serial communication can be bidirectional - Arduino can not only send data to the computer, but also receive data from it. This enables interactive programs where the user can send commands or values to the Arduino.

The String variable type

To handle text data, we use the String variable type. String variables can store text (sequences of characters).

String message = "Hello";  // Create String variable with text

Reading from serial port

To read data sent from the computer, we use Serial.available() and Serial.readString():

  • Serial.available() — returns the number of bytes available to read. Returns > 0 if data is waiting.
  • Serial.readString() — reads incoming text and waits until the user presses Enter (sends newline character).

Echo example (two-way communication)

// Serial communication example: Echo
void setup() {
  Serial.begin(115200);           // Initialize serial communication
  Serial.println("Ready to receive...");
}

void loop() {
  if (Serial.available() > 0) {   // Check if data is available
    String receivedText = Serial.readString();  // Read the text (waits for Enter)
    
    Serial.print("You sent: ");   // Print message
    Serial.println(receivedText); // Echo back what was received
  }
}

💡 Note: The code waits until the user presses Enter or sends a newline character (return). Type your message in the Serial Monitor and press Enter to send it.

Serial Monitor usage

Type your message in the Serial Monitor input field and press Enter to send

To Do:

  1. Tehtävä 1: Lähetä numero sarjamonitoriin toistuvasti
  2. Tehtävä 2: Lähetä tekstiä ja muuttujien arvoja sarjamonitoriin
  3. Find the bug: Fix the output formatting error (print vs println)
  4. Communication: How to send data from PC to Arduino via serial?
Tehtävä 7: Kirjastojen käyttö (Wokwi) 📋 Näytä

Theory: Using Libraries

So far we have written all the code we need ourselves. However, it can sometimes be useful to borrow code written by someone else if it fits your own project. Instead of random searching and copying, it's better to rely on libraries published by others.

What is a library?

A library is program code made for a specific purpose, packaged in an easy-to-use form. Libraries provide functions and new types of variables that allow programs to focus on the program's own logic.

We have actually already been using libraries in this course. However, they are automatically available in the Arduino environment and do not need to be added separately.

void setup() {
  Serial.begin(115200);
}

For example, in this code snippet we use a library called Serial. With it, we don't have to think about how, for example, numbers are converted to a format suitable for printing to the serial port.

In this example, the Serial library provides us with a function called begin(), which starts the serial port at a certain speed. Also Serial.println() is a function provided by the Serial library. Library functions are used by placing a . (dot) between the library name and the function it provides.

Including libraries

The Arduino environment offers some built-in libraries that do not need to be added to the project separately. It is enough to enable them in the code using the #include command.

#include <SoftwareSerial.h>

This enables a library called SoftwareSerial, which allows adding a new serial port to Arduino's other pins. Library names always end with .h and are enclosed between < and > characters.

Creating library objects

SoftwareSerial works slightly differently than the serial port we used before, as it must be initialized by creating a SoftwareSerial-type variable.

SoftwareSerial mySerial(8, 9); // Create new SoftwareSerial object on pins 8 and 9

This creates a new SoftwareSerial-type variable that uses pins 8 and 9 to send and receive messages. Because we're using a library, we don't necessarily need to know how the serial port works internally, as long as we know how to select the required pins correctly. In this case, pin 8 is used for transmitting and pin 9 for receiving.

Using the library object

Otherwise, the SoftwareSerial port is used like a regular serial port:

void setup() {
  mySerial.begin(115200); // Start the serial port
}

void loop() {
  mySerial.println("Hello!"); // Write message to serial port
  delay(1000);
}

Adding libraries in Wokwi

In addition to Arduino's built-in libraries, there are tons of libraries available for different purposes. On the Wokwi platform, adding these is done from the Library Manager tab. From there you can search for libraries for different purposes and also view the libraries used in the exercises. In most of the exercises in this course, the necessary libraries (for example, for reading sensors) have been added in advance, but it's useful to know how to do this for your own projects.

Wokwi Library Manager

Click the "Library Manager" tab in Wokwi to search and add libraries to your project

⚠️ How to add a library: Click "Library Manager" → Search for the library name → Click the "+" button to add it to your project. The library will appear in the libraries.txt file.

To Do:

  1. Tehtävä 1: Käytä SoftwareSerial-kirjastoa viestien lähettämiseen ulkoiselle vastaanottimelle
  2. Tehtävä 2: Lisää Button-kirjasto Library Managerista ja käytä sen funktioita
Tehtävä 8: Servomoottorit (Wokwi) 📋 Näytä

Theory: Servo Motors

Servo motors are motors whose position can be controlled. In code, this means that the servo is given a position, and the servo motor tries to maintain this position. This is especially useful if you want to build, for example, robots that move accurately in a specific way.

The Servo library

For Arduino to be able to control servo motors, it needs to set itself up to send the signal used by servos. This signal is precisely timed, and its implementation is complex for beginners. Fortunately, in programming you don't have to do everything yourself, and the code needed to control servos has been provided ready-made for all Arduino users. Such a ready-made code package is called a library.

Adding the Servo library in Wokwi

In the Wokwi editor, you can add libraries to your project from the Library Manager tab. From the "+" sign in the upper right corner of the view, you can search for and add libraries. Adding the Servo library is done by searching with the word "servo" and selecting the correct library. As you may notice, many libraries have been developed for controlling servos to choose from. However, for the purposes of this course, we will simply use the "Servo" library. In the examples and exercises, the library has already been added in advance, so you don't need to worry about this in the future. However, when doing your own projects, it's good to note the need to add libraries.

Using the Servo library

To use the added Servo library, it must be included in the code:

#include <Servo.h> // Add servo library to use

Here #include means adding a library and Servo.h is the library name.

Creating a Servo object

After this, you can create a new Servo type variable:

Servo myServo; // Create a new servo variable

Here Servo is the variable type, like the previously used int and bool, and myServo is the variable name. Like int type variables, Servo type variables can also be named as you wish. A Servo type variable provides functions that allow us to control how the servo operates.

Attaching the servo to a pin

To control the servo, Arduino must be told in the setup() function which pin the servo is connected to:

void setup() {
  myServo.attach(9); // Control servo using pin number 9
}

A Servo type variable has a function .attach(), which takes the pin number to which the servo is connected as a parameter. By placing a dot between the servo variable name and the function, we can control exactly the servo we want.

Controlling servo rotation

Controlling the servo is done with the Servo variable's .write() function. The .write() function takes the desired position of the servo in degrees between 0-180 degrees as a parameter.

💡 Servo position range: Standard servos accept values from 0 to 180. Where 0 is the minimum position (fully counter-clockwise), 90 is the middle position, and 180 is the maximum position (fully clockwise). You can use any integer value between these limits.

⚠️ Note about 360° servos: There are also continuous rotation servos (360° servos) that work differently. Instead of position, they accept a speed value where 90 is stop, values below 90 rotate in one direction, and values above 90 rotate in the other direction. However, Wokwi does not simulate these servos. We will work with continuous rotation servos in the next section when we start building physical devices.

void loop() {
  myServo.write(180); // Turn servo to maximum position
  delay(1000);        // Wait one second
  myServo.write(0);   // Turn servo to minimum position
  delay(1000);        // Wait one second
}

Complete example

#include <Servo.h>    // Add servo library

Servo myServo;        // Create servo variable

void setup() {
  myServo.attach(9);  // Attach servo to pin 9
}

void loop() {
  myServo.write(180); // Turn servo to 180 degrees
  delay(1000);        // Wait 1 second
  myServo.write(0);   // Turn servo to 0 degrees
  delay(1000);        // Wait 1 second
}

Multiple servo motors

In the basic course traffic light exercises, multiple LED lights were connected to Arduino. You can also connect multiple servos. In this case, a separate variable is created for each servo.

Servo servo1; // Create first servo variable
Servo servo2; // Create second servo variable

These are attached to pins one at a time:

void setup() {
  servo1.attach(9);  // Control servo using pin 9
  servo2.attach(10); // Control servo using pin 10
}

And control happens through each servo's own write() function:

void loop() {
  servo1.write(0);   // Turn first servo to minimum position
  servo2.write(0);   // Turn second servo to minimum position
  delay(1000);
  
  servo1.write(180); // Turn first servo to maximum position
  servo2.write(180); // Turn second servo to maximum position
  delay(1000);
}

💡 Tip: Each servo needs its own variable and pin. You can control servos independently by using their specific variable names (servo1.write(), servo2.write(), etc.).

To Do:

  1. Tehtävä 1: Ohjaa servomoottoria heilumaan edestakaisin
  2. Tehtävä 2: Ohjaa servon asentoa väliasemien avulla
  3. Find the bug: Fix the servo control error
  4. Solution: Check the corrected code
Tehtävä 9: Anturit (Wokwi) 📋 Näytä

Theory: Working with Sensors

Sensors allow Arduino to interact with the physical world by detecting changes in the environment. In this section, we'll explore three common sensors: PIR motion sensor, light sensor, and distance sensor.

1. PIR Motion Sensor

The PIR (Passive Infrared) sensor is a motion sensor based on measuring infrared radiation. It detects changes in the heat radiation from a human body in its environment. When someone steps into the sensor's detection area, the level of heat radiation changes and the sensor recognizes the movement. This makes the PIR sensor an excellent choice for controlling, for example, lighting, alarm systems, or automatic doors.

Reading PIR sensor values

The PIR sensor works by giving a signal whenever it detects motion. When the motion stops, the sensor's signal also stops. In Arduino code, the PIR sensor state can be read with the digitalRead() function.

void setup() {
  Serial.begin(115200);
  pinMode(8, INPUT);     // Set Arduino pin 8 to INPUT mode
}

void loop() {
  int sensorValue = digitalRead(8);  // Read PIR sensor value from pin 8
  Serial.println(sensorValue);       // Print PIR sensor value to serial port (prints 0 or 1)
  delay(500);                        // Wait 500 milliseconds
}

When running this code, you'll notice that the serial port prints 0 when no motion is detected, and 1 when motion is detected.

PIR Motion Sensor in Wokwi

Click the sensor in Wokwi to simulate motion detection (PIR is a motion sensor)

💡 PIR sensor output: The sensor returns a digital signal: 0 = no motion detected, 1 = motion detected. This makes it easy to use with if-statements to trigger actions.

2. Light Sensor (Photoresistor)

A light sensor can be used to detect light and react accordingly. For example, you could control curtains based on sunlight, or turn on a night light in a dark room.

Using the light sensor as an ON/OFF detector

The light sensor used with Arduino is connected to a separate control circuit that can produce an ON/OFF type signal indicating whether the light is on or off. This can be easily read using the digitalRead() function.

void setup() {
  Serial.begin(115200);
  pinMode(8, INPUT);      // Set Arduino pin 8 to INPUT mode
}

void loop() {
  int sensorValue = digitalRead(8);  // Read light sensor value
  Serial.println(sensorValue);       // Print light sensor value to serial port
  delay(500);                        // Wait 500 milliseconds
}

When the light level is changed, you'll notice that in bright light the sensor gives a value of 0, and in dim light or darkness it gives a value of 1.

💡 Light sensor output: The sensor returns a digital signal: 0 = bright light detected, 1 = dim light or darkness. This inverted logic is common in light sensors with built-in comparator circuits.

3. Distance Sensor (Ultrasonic HC-SR04)

The HC-SR04 ultrasonic sensor measures distance using sound waves. It sends an ultrasonic pulse and measures the time it takes for the echo to return. It's commonly used in robotics for obstacle detection and avoidance.

Reading distance with pulseIn()

The HC-SR04 sensor works by sending a trigger pulse and measuring the echo. We can read this directly without a library using the pulseIn() function.

const int trigPin = 10;
const int echoPin = 9;

void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  Serial.println("Distance Sensor Ready");
}

void loop() {
  // Send trigger pulse
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  
  // Read echo pulse
  long duration = pulseIn(echoPin, HIGH);
  
  // Calculate distance in cm
  int distance = duration * 0.034 / 2;
  
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  
  delay(500);
}

This code sends a trigger pulse, reads the echo time, and calculates the distance. The formula duration * 0.034 / 2 converts the time to centimeters (sound travels at 340 m/s, divided by 2 for round-trip).

💡 Distance sensor: The sensor measures distance in centimeters using ultrasonic waves. The pulseIn() function measures how long the echo pin stays HIGH. Useful for detecting obstacles, measuring levels, or creating interactive installations.

4. DHT22 Temperature and Humidity Sensor

The DHT22 sensor measures both temperature and humidity. It's widely used in weather stations, climate control systems, and environmental monitoring projects.

Using the DHT22 sensor library

The DHT22 sensor requires a library to read temperature and humidity values. The library simplifies communication with the sensor.

#include <DHT22.h>  // Include library for DHT22

DHT22 sensor(8);     // Create new sensor object on pin 8
 
void setup() {
  Serial.begin(115200);
}

void loop() {
  float temperature = sensor.getTemperature();  // Get temperature
  Serial.println(temperature);                  // Print to serial
  delay(2000);
}

This code reads the temperature every 2 seconds and prints it to the serial monitor. The DHT22 sensor needs at least 2 seconds between readings.

💡 DHT22 sensor: The sensor provides both temperature (°C) and humidity (%) readings. Use sensor.getTemperature() for temperature and sensor.getHumidity() for humidity. Always wait at least 2 seconds between readings.

Examples:

  1. PIR sensor: Motion detection with serial output
  2. Light sensor: Light-controlled LED
  3. Distance sensor: Ultrasonic distance measurement
  4. DHT22 sensor: Temperature and humidity reading
Tehtävä 10: NeoPixel-perusteet (Wokwi) 📋 Näytä

Theory: NeoPixel LED Strips

Welcome to the colorful part of the Arduino programming course! In this section, we'll learn to program NeoPixel LED strips.

This course assumes you have completed the basic Arduino beginner course. The course is implemented on the Wokwi programming platform.

Goal: Learn to control NeoPixel LED strips, set colors, and create various effects.

What is NeoPixel?

NeoPixel LED strips are colorful LED strips where each LED's color and brightness can be controlled individually. This is made possible by integrated processors in the LED lights that receive and transmit color data from LED to LED along the strip. This way, the strip can be given commands to control a specific LED's color and brightness.

NeoPixel LED strips can be used especially in smart lighting, decorative lighting, and light art.

NeoPixel Programming Basics

The first step in programming NeoPixel strips is to add the Adafruit_NeoPixel library, which provides all the necessary tools for controlling the strips. You can read more about using libraries in the "Using Libraries" section of the course.

#include <Adafruit_NeoPixel.h>  // Add NeoPixel library

Creating a NeoPixel object

Next, we create a new Adafruit_NeoPixel type variable and name it strip.

Adafruit_NeoPixel strip(16, 6, NEO_GRB + NEO_KHZ800);  // Create new NeoPixel object

Creating the new variable happens with a function that takes as parameters:

  • Number of LEDs (16 LEDs)
  • Pin number where the strip is connected (pin 6)
  • Strip-specific parameters (NEO_GRB + NEO_KHZ800) - different manufacturers may require different values, but this is the most commonly working variation

Initializing NeoPixel in setup()

In the setup() function, we initialize the NeoPixel LED strip for use.

void setup() {
  strip.begin();                  // Start using NeoPixel
  strip.setBrightness(255);       // Set NeoPixel brightness to maximum
  uint32_t color = strip.Color(255, 0, 0);  // Create new color (red, green, blue)
  strip.setPixelColor(0, color);  // Set color to first NeoPixel
  strip.show();                   // Send colors to strip
}

Understanding the functions:

  • .begin() — Starts the strip
  • .setBrightness(255) — Sets the brightness (0-255). Here set to maximum.
  • uint32_t — A type for storing large positive integers, used here for RGB color values
  • .Color(red, green, blue) — Combines RGB values into a NeoPixel color. For example, violet would be strip.Color(255, 0, 255)
  • .setPixelColor(index, color) — Sets color to a specific LED. First parameter is LED number (starts from 0), second is the color
  • .show() — Sends the color values from Arduino to the LEDs. Colors won't display until this is called!

⚠️ Important: LED numbering starts from 0! The first LED is 0, the second is 1, etc. For 16 LEDs, valid indices are 0-15.

Complete example

#include <Adafruit_NeoPixel.h>

Adafruit_NeoPixel strip(16, 6, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.setBrightness(255);
  
  // Set first LED to red
  uint32_t red = strip.Color(255, 0, 0);
  strip.setPixelColor(0, red);
  
  strip.show();  // Display the colors
}

void loop() {
  // Leave empty for now
}

For-loop Control

A typical feature of NeoPixels is controlling individual LEDs based on their index number. If the strip has many LEDs, it becomes laborious to write a separate command for each LED.

strip.setPixelColor(0, color);  // Set color to first NeoPixel
strip.setPixelColor(1, color);  // Set color to second NeoPixel
strip.setPixelColor(2, color);  // Set color to third NeoPixel
strip.setPixelColor(3, color);  // Set color to fourth NeoPixel
// etc...

With the for-loop structure, we learned how to easily iterate through number sequences. We can use this with NeoPixel control as well.

for (int i = 0; i <= 7; i++) {  // Variable i gets values 0..7, suitable for controlling 8 LEDs
  strip.setPixelColor(i, color);  // Set color to NeoPixel indicated by i
}

💡 For-loop structure: for (int i = 0; i <= 7; i++) creates a loop where i starts at 0 and increases by 1 each iteration until it reaches 7. This is perfect for controlling multiple LEDs with just a few lines of code!

Examples:

  1. Basic NeoPixel: Light up one LED with a color
  2. For-loop: Control multiple LEDs with a loop
  3. Rainbow effect: Create a rainbow pattern
  4. Animation: Create moving light effects
Tehtävä 11: Itsetesti - Keskeiset käsitteet 📋 Näytä

Itsetesti: Tarkista ymmärryksesi

Tämä ei ole tentti! Tämä on yksinkertainen tarkistuslista Osa 1:n keskeisistä käsitteistä. Käytä sitä tietämyksesi testaamiseen ja aihealueiden tunnistamiseen, joita haluat ehkä kerrata.

💡 Miten käyttää: Lue läpi jokainen käsite. Jos ymmärrät sen, hienoa! Jos et, palaa asiaan liittyvään Tehtävä-osion ja kerrata.

Arduinon peruskäsitteet

  • Pin: Physical leg of the processor whose voltage level can be read or controlled
  • setup(): Function that runs once when Arduino starts
  • loop(): Function that runs repeatedly after setup()
  • pinMode(): Configures a pin as INPUT or OUTPUT
  • digitalWrite(): Sets a digital pin to HIGH (5V) or LOW (0V)
  • digitalRead(): Reads the state of a digital pin (returns HIGH or LOW)
  • delay(): Pauses program execution for specified milliseconds

Variables and Data Types

  • int: 16-bit signed integer on Arduino (-32,768 to 32,767)
  • bool: Boolean variable type (true = 1, false = 0)
  • float: 32-bit floating-point on Arduino (±3.4028235E+38, ~6-7 decimal precision)
  • long: 32-bit signed integer (-2,147,483,648 to 2,147,483,647)
  • String: Variable type for storing text
  • uint32_t: Unsigned 32-bit integer on Arduino (0 to 4,294,967,295, used for NeoPixel colors)
  • Global variable: Declared outside functions, accessible everywhere
  • Local variable: Declared inside a function, only accessible within that function
  • Static variable: Retains its value between function calls

Control Structures

  • if-statement: Executes code block only if condition is true
  • else: Executes code block when if-condition is false
  • else if: Checks additional conditions when previous conditions are false
  • while loop: Repeats code block while condition is true
  • for loop: Repeats code block a specific number of times with a counter variable
  • Comparison operators: ==, !=, <, >, <=, >= for comparing values

Functions

  • Function: Reusable block of code with a name
  • Parameter: Input value passed to a function
  • Return value: Output value that a function gives back
  • void: Function type that doesn't return a value

Serial Communication

  • Serial port: Communication channel between Arduino and computer via USB
  • Serial.begin(): Initializes serial communication with specified baud rate
  • Baud rate: Communication speed (e.g., 115200) - must match on both devices
  • Serial.println(): Sends text or numbers to serial monitor with newline
  • Serial.print(): Sends data without newline
  • Serial.available(): Returns number of bytes available to read
  • Serial.readString(): Reads incoming text (waits for Enter key)

Libraries

  • Library: Pre-written code package for specific purposes
  • #include: Command to add a library to your code
  • Library Manager: Tool in Wokwi for searching and adding libraries
  • Object: Variable created from a library type (e.g., Servo, NeoPixel)

Components and Sensors

  • INPUT mode: Pin configured to read signals from external sources
  • OUTPUT mode: Pin configured to send signals to external components
  • INPUT_PULLUP: INPUT mode with internal pull-up resistor enabled
  • PIR sensor: Detects motion using infrared radiation (returns 0 or 1)
  • Light sensor: Detects light levels (returns 0 for bright, 1 for dark)
  • HC-SR04: Ultrasonic distance sensor (measures in centimeters)
  • pulseIn(): Measures duration of a pulse on a pin
  • DHT22 (or DHT11): Temperature and humidity sensor (requires library)

Servo Motors

  • Servo motor: Motor whose position can be controlled (0-180 degrees)
  • .attach(pin): Connects servo object to a specific pin
  • .write(angle): Sets servo position in degrees (0-180)
  • 360° servo: Continuous rotation servo (speed control, not position; 90 = no movement)

NeoPixel LED Strips

  • NeoPixel: Individually addressable RGB LED strip
  • .begin(): Initializes NeoPixel strip
  • .setBrightness(): Sets overall brightness (0-255)
  • .Color(r, g, b): Creates RGB color from red, green, blue values (0-255 each)
  • .setPixelColor(index, color): Sets color to specific LED (index starts at 0)
  • .show(): Updates the strip to display set colors
  • RGB: Color model using Red, Green, Blue values

Programming Concepts

  • Overflow: When a variable exceeds its maximum value and wraps around (e.g., int at 32,767 + 1 becomes -32,768)
  • Index: Position number in a list or array (starts at 0)
  • Iteration: One execution of a loop
  • Condition: Expression that evaluates to true or false
  • Syntax: Rules for writing code correctly

✅ Congratulations! If you understand most of these concepts, you're ready to move on to Basic arduino exercise!

Osa 2: Perus-Arduino-harjoitus + Edistyneet aiheet (Lisätehtävät) 📋 Näytä
Tehtävä 2.0: Tarvittavien ohjelmien ja työkalujen asennus 📋 Näytä
📥 Arduino IDE -asennus (klikkaa avataksesi)

Jos et ole vielä asentanut Arduino IDE:tä tietokoneellesi, lataa tämä PowerPoint-oppaat ja asenna ohjelma. Asennamme myös tarvittavat kirjastot ja piirilevyt, joita tarvitaan muissa tulevissa työpajoissa. Voit myös ladata Processing-ohjelman, mutta se ei ole pakollinen.

⚠️ Tärkeä huomio: Tällä kurssilla käytämme Arduino-klooneja, jotka tarvitsevat muutaman lisäasetuksen. Varmista, että olet lisännyt kohtaan Arduino IDE -> Settings -> Additional Boards Manager URLs alla olevat rivit.
Boards Manager URL:t

Käytämme harjoituksissa joko aitoja UNO-alustoja tai vaihtoehtoisesti LGT8F328P-kloonia. Jos käytät LGT8F328P-kloonia, valitse se kohdasta Tools -> Board -> LGT8F328P Boards.

Tehtävä 2.1: Napin piiri 📋 Näytä
Button circuit

Napin tilan lukeminen digitalRead():lla

Lukemiseksi, onko nappi painettu, käytämme digitalRead()-funktiota:

pinMode(2, INPUT);  // Aseta pinni 2 tuloksi
int buttonState = digitalRead(2);  // Lue napin tila (HIGH tai LOW)

Esimerkki: LEDin ohjaus napilla

Tämä esimerkki näyttää, miten valmiin LEDin ohjataan napin tilan perusteella:

Napin esimerkkikoodi
const int buttonPin = 2;
const int ledPin = 13;

int buttonState = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  buttonState = digitalRead(buttonPin);

  if (buttonState == HIGH) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }
}
Tehtävä 2.2: Potentiometri 📋 Näytä
Potentiometer circuit

Analogisten arvojen lukeminen analogRead():lla

Potentiometri on muuttuva vastus, jota voidaan käyttää analogisten arvojen lukemiseen (0-1023 Arduinolla):

int potValue = analogRead(A0);  // Lue analoginen arvo pinnistä A0

Esimerkki: LEDin kirkkauden ohjaus potentiometrillä

Tämä esimerkki näyttää, miten LEDin kirkkautta ohjataan potentiometrillä:

Potentiometrin esimerkkikoodi
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  int potValue = analogRead(A0);      // Read potentiometer (0-1023)
  int brightness = map(potValue, 0, 1023, 0, 255);  // Map to LED brightness (0-255)
  
  analogWrite(LED_BUILTIN, brightness);  // Set LED brightness
  delay(10);
}
Tehtävä 2.3: Signaalin vakaannuttaminen (Propeller) 📋 Näytä
Signal stabilization circuit

Napin signaalin vakaannuttaminen

Kun nappia painetaan, se voi luoda useita signaaleja (pomppiminen). Signaalin vakaannuttaminen (propeller) estää tämän:

unsigned long lastPropellerTime = 0;
unsigned long propellerDelay = 50;  // 50ms viive

Esimerkki: Vakaannutettu nappi

Tämä esimerkki näyttää, miten napin signaalit vakaannutetaan oikein:

Signaalin vakaannuttamisen esimerkki
int lastButtonState = HIGH;
unsigned long lastPropellerTime = 0;
unsigned long propellerDelay = 50;
 
void setup() {
  pinMode(2, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}
 
void loop() {
  int reading = digitalRead(2);
  
  if (reading != lastButtonState) {
    lastPropellerTime = millis();
  }
  
  if ((millis() - lastPropellerTime) > propellerDelay) {
    if (reading == HIGH) {
      digitalWrite(LED_BUILTIN, HIGH);
    } else {
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
  
  lastButtonState = reading;
}
Tehtävä 2.4: Servomoottori 📋 Näytä
Servo motor circuit

Servomoottorien ohjaus

Servomoottoreita voidaan ohjata tiettyihin asentoihin (0-180 astetta):

#include <Servo.h>
Servo myServo;

Esimerkki: Servon ohjaus napilla

Tämä esimerkki näyttää, miten servomoottoria ohjataan napilla:

Servon esimerkkikoodi
#include <Servo.h>

Servo myservo;  

int potpin = A0; 
int val;    

void setup() {
  myservo.attach(9); 
}

void loop() {
  val = analogRead(potpin);           
  val = map(val, 0, 1023, 180, 0);     
  myservo.write(val);                  
  delay(15);                           
}
Tehtävä 2.5: RGB-LED 📋 Näytä
RGB LED circuit

RGB-LEDien ohjaus

RGB-LEDeillä on kolme erillistä väriä (Punainen, Vihreä, Sininen), joita voidaan sekoittaa luomaan mikä tahansa väri:

int redPin = 9;
int greenPin = 10;
int bluePin = 11;

Esimerkki: RGB-LEDin värin ohjaus

Tämä esimerkki näyttää, miten RGB-LEDiä ohjataan näyttämään eri värejä:

RGB-LEDin esimerkkikoodi
int redPin = 9;
int greenPin = 10;
int bluePin = 11;

void setup() {
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

void loop() {
  // Red
  analogWrite(redPin, 255);
  analogWrite(greenPin, 0);
  analogWrite(bluePin, 0);
  delay(1000);
  
  // Green
  analogWrite(redPin, 0);
  analogWrite(greenPin, 255);
  analogWrite(bluePin, 0);
  delay(1000);
  
  // Blue
  analogWrite(redPin, 0);
  analogWrite(greenPin, 0);
  analogWrite(bluePin, 255);
  delay(1000);
}
Tehtävä 2.6: Etäisyysanturi 📋 Näytä
Distance sensor circuit

Ultraäänietäisyysanturi (HC-SR04)

HC-SR04 mittaa etäisyyttä ultraääniaaltojen avulla:

const int trigPin = 10;
const int echoPin = 9;

Esimerkki: Etäisyyden mittaus

Tämä esimerkki näyttää, miten etäisyyttä mitataan ultraäänianturilla:

Etäisyysanturin esimerkkikoodi
const int trigPin = 10;
const int echoPin = 9;

void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop() {
  // Send trigger pulse
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  
  // Read echo pulse
  long duration = pulseIn(echoPin, HIGH);
  
  // Calculate distance in cm
  int distance = duration * 0.034 / 2;
  
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  
  delay(500);
}
Tehtävä 2.7: Etäisyysanturi signaalin vakaannuttamisella 📋 Näytä
Distance sensor with signal stabilization

Etäisyysanturi vakailla lukemilla

Yhdistetään etäisyysmittaus signaalin vakaannuttamiseen (propeller) vakaiden lukemien saamiseksi:

const int trigPin = 10;
const int echoPin = 9;
unsigned long lastReadTime = 0;

Esimerkki: Vakaa etäisyysmittaus

Tämä esimerkki näyttää, miten saadaan vakaita etäisyyslukemia:

Etäisyys + vakaannuttamisen koodi
#include <afstandssensor.h>

AfstandsSensor afstandssensor(13, 12);
const int outPin = 9;

int dist;

void setup() {
  pinMode(outPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  dist = afstandssensor.afstandCM();
  Serial.println(dist);
  analogWrite(outPin, map(dist, 0, 100, 0, 255));
  delay(50);
}
Tehtävä 2.8: LED-nauha (NeoPixel) 📋 Näytä
LED strip circuit

NeoPixel LED-nauhat

NeoPixel-nauhat mahdollistavat useiden RGB-LEDien yksittäisen ohjauksen:

#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(16, 6, NEO_GRB + NEO_KHZ800);

Esimerkki: Sateenkaari-efekti LED-nauhalla

Tämä esimerkki näyttää, miten luodaan sateenkaari-efekti NeoPixel-nauhalle:

LED-nauhan esimerkkikoodi
#include <FastLED.h>
FASTLED_USING_NAMESPACE

#define DATA_PIN    6
#define LED_TYPE    WS2811
#define COLOR_ORDER GRB
#define NUM_LEDS    8
CRGB leds[NUM_LEDS];

#define BRIGHTNESS          96
#define FRAMES_PER_SECOND  120

void setup() {
  delay(3000);
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
}

uint8_t gHue = 0;

void loop() {
  bpm();
  FastLED.show();  
  FastLED.delay(1000/FRAMES_PER_SECOND);
  EVERY_N_MILLISECONDS( 20 ) { gHue++; }
}

void bpm() {
  uint8_t BeatsPerMinute = 62;
  CRGBPalette16 palette = PartyColors_p;
  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
  for( int i = 0; i < NUM_LEDS; i++) {
    leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
  }
}
Tehtävä 2.9: DHT11-lämpö- ja kosteusanturi 📋 Näytä
DHT11 sensor circuit

DHT11-lämpö- ja kosteusanturi

DHT11-anturi mittaa sekä lämpötilan että kosteuden:

#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

Esimerkki: Lämpötilan ja kosteuden lukeminen

Tämä esimerkki näyttää, miten luetaan lämpötila ja kosteus DHT11:stä:

DHT11:n esimerkkikoodi
#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  dht.begin();
}

void loop() {
  // Wait 2 seconds between measurements
  delay(2000);
  
  // Read humidity
  float humidity = dht.readHumidity();
  
  // Read temperature in Celsius
  float temperature = dht.readTemperature();
  
  // Check if readings failed
  if (isnan(humidity) || isnan(temperature)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }
  
  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.print(" %  Temperature: ");
  Serial.print(temperature);
  Serial.println(" °C");
}

🚀 Advanced Arduino Topics (Extra Tasks)

Tehtävä 2.10: Keskeytykset ja ajastimet 📋 Näytä
Interrupt circuit

Hardware Interrupts (attachInterrupt)

Hardware interrupts allow your Arduino to respond immediately to external events without constantly checking (polling) in the loop().

  • Use case: Button presses, encoder readings, pulse counting
  • Key function: attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

📚 Task: Measure how long a button is pressed using interrupts. When the button is pressed, record the start time. When released, calculate and display the press duration.

Interrupt Example - Button Press Duration
/*
 * Button Press Duration Measurement using Interrupts
 * 
 * Hardware Setup:
 * - Connect button between pin 2 and GND
 * - Use internal pull-up resistor (button pressed = LOW)
 * - Or use external pull-up resistor
 * 
 * How it Works:
 * - FALLING interrupt: triggered when button is pressed (HIGH to LOW)
 * - RISING interrupt: triggered when button is released (LOW to HIGH)
 * - Calculates duration = release time - press time
 * - Uses debouncing to filter out noise
 */

const int buttonPin = 2;
volatile unsigned long pressStartTime = 0;  // When button was pressed
volatile unsigned long pressDuration = 0;   // Calculated duration
volatile bool buttonPressed = false;        // Button state flag
volatile bool newMeasurement = false;       // Flag for new measurement
volatile int pressCount = 0;                // Number of presses

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);  // Enable internal pull-up resistor
  
  // Use CHANGE mode to detect both press and release
  // Note: Arduino Uno can only have one interrupt per pin
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonISR, CHANGE);
  
  Serial.println("Button Press Duration Measurement");
  Serial.println("Press and hold the button...");
  Serial.println("--------------------------------");
}

void loop() {
  // Display results when button is released
  if (newMeasurement) {
    newMeasurement = false;  // Clear flag
    
    pressCount++;
    Serial.print("Press #");
    Serial.print(pressCount);
    Serial.print(": Duration = ");
    Serial.print(pressDuration);
    Serial.print(" ms (");
    Serial.print(pressDuration / 1000.0);
    Serial.println(" seconds)");
    
    pressDuration = 0;  // Reset for next measurement
  }
  
  delay(10);  // Small delay to prevent excessive CPU usage
}

/*
 * Interrupt Service Routine for button state change
 * Triggered on both press (HIGH to LOW) and release (LOW to HIGH)
 * Uses CHANGE mode to detect both edges
 */
void buttonISR() {
  static unsigned long lastInterruptTime = 0;
  unsigned long currentTime = millis();
  
  // Debouncing: ignore interrupts within 50ms of last event
  if (currentTime - lastInterruptTime < 50) {
    return;  // Ignore bounce
  }
  lastInterruptTime = currentTime;
  
  // Read current button state (LOW = pressed, HIGH = released)
  bool currentState = digitalRead(buttonPin);
  
  if (currentState == LOW) {
    // Button pressed (FALLING edge)
    if (!buttonPressed) {  // Only record if button wasn't already pressed
      pressStartTime = currentTime;
      buttonPressed = true;
      Serial.println("Button PRESSED");
    }
  } else {
    // Button released (RISING edge)
    if (buttonPressed) {  // Only calculate if button was actually pressed
      // Calculate duration
      pressDuration = currentTime - pressStartTime;
      buttonPressed = false;
      newMeasurement = true;  // Signal that new measurement is ready
      Serial.println("Button RELEASED");
    }
  }
}
Tehtävä 2.11: Watchdog-ajastin ja turvallisuusmekanismi 📋 Näytä
Arduino Uno

Watchdog Timer (WDT)

A watchdog timer resets the microcontroller if the software becomes unresponsive or stuck.

  • Your code must "kick the dog" (reset the timer) regularly
  • If the timer reaches zero, Arduino resets automatically

📚 Task: Enable the watchdog timer and create a program that intentionally freezes. Observe automatic reset.

Watchdog Example
// Watchdog library for AVR microcontrollers
#include <avr/wdt.h>

unsigned long lastKick = 0;  // Time of last "kick"

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  delay(500);
  
  Serial.println("=== WATCHDOG TEST ===");
  Serial.println("Press ENTER to kick the dog!");
  Serial.println("If no kick in 4 sec, Arduino resets.");
  
  // Enable watchdog with 4 second timeout
  wdt_enable(WDTO_4S);
  
  lastKick = millis();
}

void loop() {
  // Check if user pressed ENTER
  if (Serial.available()) {
    Serial.read();  // Clear buffer
    Serial.println("✓ Kicked! Timer reset.");
    lastKick = millis();
  }
  
  // Calculate time since last kick
  unsigned long timeSinceKick = millis() - lastKick;
  
  // If time remaining
  if (timeSinceKick < 4000) {
    unsigned long remaining = (4000 - timeSinceKick) / 1000;
    Serial.print("Time left: ");
    Serial.print(remaining);
    Serial.println(" sec");
    
    digitalWrite(LED_BUILTIN, HIGH);  // LED on
    
    // IMPORTANT: Tell watchdog that program is running
    wdt_reset();
  } 
  else {
    // Time's up - LED off and DON'T call wdt_reset()
    digitalWrite(LED_BUILTIN, LOW);
    Serial.println("⚠ TIME'S UP! Watchdog will reset...");
    
    // Freeze program - watchdog will reset Arduino
    while(1) { }
  }
  
  delay(1000);
}
Tehtävä 2.12: I²C-kommunikaatio (kaksijohdin) 📋 Näytä
I2C pins

I²C Communication (Two-Wire)

I²C uses two wires (SDA/SCL) to connect multiple devices on the same bus. Arduino pins: A4 (SDA), A5 (SCL).

📚 Task: Scan the I²C bus for connected devices and display their addresses on Serial Monitor.

I2C Scanner
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("I2C Scanner");
}

void loop() {
  byte error, address;
  int nDevices = 0;
  
  for(address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    
    if (error == 0) {
      Serial.print("Device at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      nDevices++;
    }
  }
  
  if (nDevices == 0) Serial.println("No devices found");
  delay(5000);
}
Example 2: MPU6050 Accelerometer & Gyroscope
/*
 * MPU6050 Accelerometer and Gyroscope via I2C
 * 
 * Hardware Setup:
 * - Connect MPU6050 VCC to 5V (or 3.3V)
 * - Connect MPU6050 GND to GND
 * - Connect MPU6050 SCL to Arduino A5 (SCL)
 * - Connect MPU6050 SDA to Arduino A4 (SDA)
 * - I2C address: 0x68 (default)
 * 
 * MPU6050 Features:
 * - 3-axis accelerometer (measures acceleration)
 * - 3-axis gyroscope (measures rotation rate)
 * - Temperature sensor
 * - I2C interface
 * 
 * How it Works:
 * - Reads raw sensor data from MPU6050 registers
 * - Converts raw values to physical units (g for accelerometer, °/s for gyroscope)
 * - Displays values on Serial Monitor
 * 
 * Library Required:
 * - Wire.h (included with Arduino IDE)
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - Wire Library: https://www.arduino.cc/reference/en/language/functions/communication/wire/
 * - MPU6050 Datasheet: https://www.invensense.com/products/motion-tracking/6-axis/mpu-6050/
 */

#include <Wire.h>

// MPU6050 I2C address
const int MPU6050_ADDR = 0x68;

// MPU6050 register addresses
const int MPU6050_PWR_MGMT_1 = 0x6B;  // Power management register
const int MPU6050_ACCEL_XOUT_H = 0x3B; // Accelerometer data registers
const int MPU6050_GYRO_XOUT_H = 0x43;  // Gyroscope data registers
const int MPU6050_TEMP_OUT_H = 0x41;   // Temperature register

// Sensitivity settings (from datasheet)
const float ACCEL_SENSITIVITY = 16384.0;  // LSB/g for ±2g range
const float GYRO_SENSITIVITY = 131.0;     // LSB/(°/s) for ±250°/s range

void setup() {
  Serial.begin(9600);
  Wire.begin();
  
  // Initialize MPU6050
  initMPU6050();
  
  Serial.println("MPU6050 Accelerometer & Gyroscope");
  Serial.println("====================================");
  delay(1000);
}

void loop() {
  // Read and display sensor data
  readAndDisplayMPU6050();
  
  delay(500);  // Update every 500ms
}

/*
 * Initialize MPU6050 sensor
 */
void initMPU6050() {
  // Wake up MPU6050 (exit sleep mode)
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(MPU6050_PWR_MGMT_1);
  Wire.write(0);  // Set to 0 to wake up
  Wire.endTransmission();
  
  delay(100);  // Wait for sensor to stabilize
  
  Serial.println("MPU6050 initialized");
}

/*
 * Read 16-bit value from two consecutive registers
 */
int16_t read16BitRegister(uint8_t reg) {
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(reg);
  Wire.endTransmission(false);  // Don't release bus
  Wire.requestFrom(MPU6050_ADDR, 2, true);  // Request 2 bytes
  
  // Read high and low bytes
  uint8_t highByte = Wire.read();
  uint8_t lowByte = Wire.read();
  
  // Combine bytes (high byte first, then low byte)
  return (int16_t)((highByte << 8) | lowByte);
}

/*
 * Read and display all MPU6050 sensor data
 */
void readAndDisplayMPU6050() {
  // Read accelerometer data (X, Y, Z)
  int16_t accelX = read16BitRegister(MPU6050_ACCEL_XOUT_H);
  int16_t accelY = read16BitRegister(MPU6050_ACCEL_XOUT_H + 2);
  int16_t accelZ = read16BitRegister(MPU6050_ACCEL_XOUT_H + 4);
  
  // Read gyroscope data (X, Y, Z)
  int16_t gyroX = read16BitRegister(MPU6050_GYRO_XOUT_H);
  int16_t gyroY = read16BitRegister(MPU6050_GYRO_XOUT_H + 2);
  int16_t gyroZ = read16BitRegister(MPU6050_GYRO_XOUT_H + 4);
  
  // Read temperature
  int16_t tempRaw = read16BitRegister(MPU6050_TEMP_OUT_H);
  
  // Convert to physical units
  float accelX_g = accelX / ACCEL_SENSITIVITY;
  float accelY_g = accelY / ACCEL_SENSITIVITY;
  float accelZ_g = accelZ / ACCEL_SENSITIVITY;
  
  float gyroX_dps = gyroX / GYRO_SENSITIVITY;  // degrees per second
  float gyroY_dps = gyroY / GYRO_SENSITIVITY;
  float gyroZ_dps = gyroZ / GYRO_SENSITIVITY;
  
  // Temperature conversion: T = (TEMP_OUT / 340) + 36.53
  float temperature = (tempRaw / 340.0) + 36.53;
  
  // Display results
  Serial.println("--- MPU6050 Data ---");
  
  Serial.print("Accelerometer (g): ");
  Serial.print("X=");
  Serial.print(accelX_g, 3);
  Serial.print(" Y=");
  Serial.print(accelY_g, 3);
  Serial.print(" Z=");
  Serial.println(accelZ_g, 3);
  
  Serial.print("Gyroscope (°/s): ");
  Serial.print("X=");
  Serial.print(gyroX_dps, 2);
  Serial.print(" Y=");
  Serial.print(gyroY_dps, 2);
  Serial.print(" Z=");
  Serial.println(gyroZ_dps, 2);
  
  Serial.print("Temperature: ");
  Serial.print(temperature, 2);
  Serial.println(" °C");
  
  Serial.println();
}

/*
 * Alternative: Read single axis (example for X-axis accelerometer)
 */
float readAccelX() {
  int16_t raw = read16BitRegister(MPU6050_ACCEL_XOUT_H);
  return raw / ACCEL_SENSITIVITY;
}

/*
 * Alternative: Read single axis (example for X-axis gyroscope)
 */
float readGyroX() {
  int16_t raw = read16BitRegister(MPU6050_GYRO_XOUT_H);
  return raw / GYRO_SENSITIVITY;
}
Tehtävä 2.13: 1-Wire-protokolla (DS18B20-lämpöanturi) 📋 Näytä
Arduino

1-Wire Protocol (DS18B20 Temperature Sensor)

1-Wire is a communication protocol that uses a single data line (plus ground). DS18B20 is a digital temperature sensor that uses this protocol. Connect DS18B20 data pin to any digital pin (e.g., pin 2) with a 4.7kΩ pull-up resistor to VCC.

📚 Task: Read temperature from DS18B20 sensor and display it on Serial Monitor. Install libraries: "OneWire" and "DallasTemperature".

DS18B20 Temperature Sensor
/*
 * DS18B20 Temperature Sensor with 1-Wire Protocol
 * 
 * Hardware Setup:
 * - Connect DS18B20 data pin to digital pin 2 (or any digital pin)
 * - Connect DS18B20 VCC to 5V (or 3.3V)
 * - Connect DS18B20 GND to GND
 * 
 * IMPORTANT: Pull-up Resistor Required
 * - A 4.7kΩ pull-up resistor is needed between the data pin and VCC
 * - This resistor ensures proper signal levels for 1-Wire communication
 * - Without the pull-up resistor, the sensor may not work correctly
 * 
 * NOTE: If using an adapter board/module for DS18B20, the pull-up resistor
 * is usually already included on the board, so no external resistor is needed.
 * 
 * IMPORTANT: Compatibility with ESP8266 and Other Peripherals
 * 
 * ESP8266 Wi-Fi Interrupts:
 * - OneWire protocol uses precise timing (delayMicroseconds) for communication
 * - Wi-Fi on ESP8266 uses timer interrupts that can interfere with OneWire timing
 * - If you experience communication errors, try:
 *   1. Disable Wi-Fi during temperature reading: WiFi.mode(WIFI_OFF) before reading
 *   2. Use yield() or delay() between readings to allow Wi-Fi tasks to run
 *   3. Consider using a separate task/thread for Wi-Fi if using RTOS
 * 
 * I2C Compatibility:
 * - I2C and OneWire can be used simultaneously without hardware conflicts
 * - They use different pins: I2C uses SDA/SCL (A4/A5 on Uno, D2/D1 on ESP8266)
 * - OneWire can use any digital pin (avoid using I2C pins for OneWire)
 * - No timer conflicts: I2C uses software or hardware I2C controller, OneWire uses
 *   software timing - they don't share the same hardware timers
 * - However, if both are used in interrupt handlers, there may be timing issues
 * 
 * Libraries Required:
 * - OneWire (by Paul Stoffregen)
 * - DallasTemperature (by Miles Burton)
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - OneWire Library: https://www.arduino.cc/reference/en/libraries/onewire/
 * - DallasTemperature Library: https://www.arduino.cc/reference/en/libraries/dallastemperature/
 */

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 2  // Data pin connected to pin 2

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup() {
  Serial.begin(9600);
  sensors.begin();
  
  Serial.println("DS18B20 Temperature Sensor");
  Serial.println("---------------------------");
}

void loop() {
  sensors.requestTemperatures();
  
  float tempC = sensors.getTempCByIndex(0);
  
  if (tempC != DEVICE_DISCONNECTED_C) {
    Serial.print("Temperature: ");
    Serial.print(tempC);
    Serial.print(" °C / ");
    Serial.print((tempC * 9.0 / 5.0) + 32.0);
    Serial.println(" °F");
  } else {
    Serial.println("Error: Could not read temperature");
  }
  
  delay(1000);
}
Tehtävä 2.14: Analoginen vertailija 📋 Näytä
Arduino

Analog Comparator

Arduino Uno has a built-in analog comparator that compares two voltages without using the ADC. It uses AIN0 (pin D6) and AIN1 (pin D7) as inputs, or can use the internal 1.1V bandgap reference. The comparator output can trigger interrupts when the voltage relationship changes.

📚 Task: Use the built-in analog comparator to compare a potentiometer voltage against the internal 1.1V reference. Connect one potentiometer to AIN0 (D6). The comparator will indicate when the voltage exceeds 1.1V.

Analog Comparator Example
/*
 * Analog Comparator using Arduino Uno's Built-in Comparator
 * 
 * ============================================================
 * HARDWARE SETUP (Simple - Only One Potentiometer Needed):
 * ============================================================
 * - Connect potentiometer: middle pin to AIN0 (pin D6), outer pins to GND and 5V
 * - Optional: Connect LED to pin 13 (built-in LED) to show comparison result
 * - No connection needed to AIN1 (D7) - using internal 1.1V reference
 * 
 * ============================================================
 * HOW IT WORKS:
 * ============================================================
 * - Arduino Uno has a built-in analog comparator (AC) in the ATmega328P chip
 * - AIN0 (D6) is compared against internal 1.1V bandgap reference
 * - When AIN0 > 1.1V, comparator output is HIGH
 * - When AIN0 < 1.1V, comparator output is LOW
 * - The comparator can trigger interrupts on rising/falling edges
 * 
 * ============================================================
 * CHANGING THE REFERENCE VOLTAGE:
 * ============================================================
 * 
 * Option 1: Use External Reference on AIN1 (D7)
 *   - To use a different reference voltage, connect it to AIN1 (pin D7)
 *   - Change ACBG bit to 0: ACSR = (1 << ACIE) | (0 << ACBG) | ...
 *   - Hardware: Connect second potentiometer or voltage divider to AIN1 (D7)
 *   - This allows any reference voltage from 0V to VCC (5V)
 * 
 * Option 2: Use Voltage Divider for Fixed Reference
 *   - Create a voltage divider: R1 and R2 between 5V and GND
 *   - Connect junction to AIN1 (D7)
 *   - Reference voltage = 5V * R2 / (R1 + R2)
 *   - Example: R1 = 10kΩ, R2 = 10kΩ gives 2.5V reference
 *   - Set ACBG = 0 to use external reference
 * 
 * Option 3: Use Second Potentiometer for Adjustable Reference
 *   - Connect second potentiometer: middle pin to AIN1 (D7), outer pins to GND and 5V
 *   - Set ACBG = 0 to use external reference
 *   - Now you can adjust both input and reference voltages
 * 
 * NOTE: The internal 1.1V bandgap reference (ACBG = 1) is fixed and cannot be changed.
 *       It is stable, temperature-compensated, and independent of supply voltage.
 *       To use a different reference, you must use external reference (ACBG = 0).
 * 
 * ============================================================
 * INTERNAL BANDGAP REFERENCE (Current Configuration):
 * ============================================================
 * - ACBG bit = 1: Uses internal 1.1V reference for AIN1
 * - This eliminates the need for a second potentiometer or external reference
 * - The 1.1V reference is stable and temperature-compensated
 * - Cannot be changed - it's a fixed hardware reference
 * 
 * ============================================================
 * ADVANTAGES OF BUILT-IN COMPARATOR:
 * ============================================================
 * - Faster than ADC (no conversion delay)
 * - Lower power consumption
 * - Can trigger interrupts automatically
 * - Useful for threshold detection, zero-crossing detection, etc.
 * 
 * ============================================================
 * CODE MODIFICATION TO USE EXTERNAL REFERENCE:
 * ============================================================
 * To use external reference on AIN1 (D7) instead of internal 1.1V:
 * 
 *   1. Change this line in setup():
 *      ACSR = (1 << ACIE) | (0 << ACBG) | (0 << ACIS1) | (0 << ACIS0);
 *      // Change ACBG from 1 to 0
 * 
 *   2. Connect your reference voltage to AIN1 (pin D7)
 * 
 *   3. Update Serial messages to reflect your reference voltage
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - AVR Analog Comparator: https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
 */

// AVR register definitions for analog comparator
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_PIN 13
#define REFERENCE_VOLTAGE 1.1  // Internal bandgap reference voltage

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  
  // Configure analog comparator
  // 
  // Register bits explanation:
  // - ACIE = 1: Enable analog comparator interrupt
  // - ACBG = 1: Use internal 1.1V bandgap reference for AIN1 (negative input)
  //            = 0: Use external reference on AIN1 (pin D7) - connect pot/voltage divider
  // - ACIS1 = 0, ACIS0 = 0: Trigger interrupt on comparator output toggle (any change)
  //          = 0, ACIS0 = 1: Trigger on falling edge (AIN0 goes below reference)
  //          = 1, ACIS0 = 0: Trigger on rising edge (AIN0 goes above reference)
  //          = 1, ACIS0 = 1: Reserved
  //
  // To change reference voltage:
  // - For internal 1.1V: Keep ACBG = 1 (current setting)
  // - For external reference: Change ACBG to 0 and connect reference to AIN1 (D7)
  //   Example: ACSR = (1 << ACIE) | (0 << ACBG) | (0 << ACIS1) | (0 << ACIS0);
  //
  ACSR = (1 << ACIE) | (1 << ACBG) | (0 << ACIS1) | (0 << ACIS0);
  
  // Enable global interrupts
  sei();
  
  Serial.println("Analog Comparator Example");
  Serial.println("AIN0 (D6) vs Internal 1.1V Reference");
  Serial.println("------------------------------------");
  Serial.print("Reference voltage: ");
  Serial.print(REFERENCE_VOLTAGE);
  Serial.println("V");
}

// Interrupt service routine for analog comparator
ISR(ANALOG_COMP_vect) {
  // Comparator output changed
  if (ACSR & (1 << ACO)) {
    // AIN0 > 1.1V reference
    digitalWrite(LED_PIN, HIGH);
    Serial.println("AIN0 > 1.1V (LED ON)");
  } else {
    // AIN0 < 1.1V reference
    digitalWrite(LED_PIN, LOW);
    Serial.println("AIN0 < 1.1V (LED OFF)");
  }
}

void loop() {
  // Read comparator output status
  bool comparatorOutput = (ACSR & (1 << ACO)) != 0;
  
  // Read AIN0 voltage using ADC for display (optional)
  // Note: AIN0 is pin D6, but we can't read it directly with analogRead
  // Instead, we can read a similar analog pin to show the concept
  // In practice, you'd measure the actual voltage on D6 with a multimeter
  
  Serial.print("Comparator: ");
  Serial.print(comparatorOutput ? "AIN0 > 1.1V" : "AIN0 < 1.1V");
  Serial.print(" | LED: ");
  Serial.println(comparatorOutput ? "ON" : "OFF");
  Serial.println("(Adjust potentiometer to see LED toggle)");
  
  delay(500);
}
Tehtävä 2.15: EEPROM-tallennus (pysyvä data) 📋 Näytä
Arduino

EEPROM Storage

EEPROM stores data that persists when power is removed. Arduino Uno has 1KB of EEPROM memory. This example reads stored password from EEPROM and allows updating it via UART.

📚 Task: Read stored password from EEPROM at startup. Allow writing new password via UART. Display stored password every second.

EEPROM Example - Password Storage
/*
 * EEPROM Storage - Password Storage and Retrieval
 * 
 * Hardware Setup:
 * - Uses built-in UART (Serial) on Arduino Uno
 * - Connect via USB or use Serial Monitor in Arduino IDE
 * 
 * How it Works:
 * - Reads stored password from EEPROM at startup
 * - Allows writing new password via UART (type password and press Enter)
 * - Displays stored password every second
 * - Password persists across power cycles
 * 
 * EEPROM Usage:
 * - Address 0: Password length (1 byte)
 * - Address 1-32: Password string (max 32 characters)
 * - Total: 33 bytes used
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - EEPROM Library: https://www.arduino.cc/reference/en/libraries/eeprom/
 */

#include <EEPROM.h>

#define EEPROM_PASSWORD_LEN_ADDR 0    // Address for password length
#define EEPROM_PASSWORD_ADDR 1        // Address for password string
#define MAX_PASSWORD_LEN 32           // Maximum password length

String storedPassword = "";           // Current password in memory
unsigned long lastReadTime = 0;       // Last time we read from EEPROM
const unsigned long READ_INTERVAL = 1000;  // Read every 1 second

void setup() {
  Serial.begin(9600);
  delay(500);
  
  Serial.println("EEPROM Password Storage");
  Serial.println("========================");
  
  // Read stored password from EEPROM at startup
  readPasswordFromEEPROM();
  
  if (storedPassword.length() > 0) {
    Serial.print("Stored password: ");
    Serial.println(storedPassword);
  } else {
    Serial.println("No password stored in EEPROM");
    Serial.println("Type a password and press Enter to store it");
  }
  
  Serial.println();
  Serial.println("Commands:");
  Serial.println("- Type password and press Enter to store new password");
  Serial.println("- Password is read from EEPROM every second");
  Serial.println();
}

void loop() {
  // Check if user wants to write new password via UART
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    input.trim();
    
    if (input.length() > 0) {
      // Write new password to EEPROM
      writePasswordToEEPROM(input);
      Serial.print("Password saved: ");
      Serial.println(input);
      Serial.println();
    }
  }
  
  // Read password from EEPROM every second
  if (millis() - lastReadTime >= READ_INTERVAL) {
    lastReadTime = millis();
    readPasswordFromEEPROM();
    
    Serial.print("Password from EEPROM: ");
    if (storedPassword.length() > 0) {
      Serial.println(storedPassword);
    } else {
      Serial.println("(empty)");
    }
  }
  
  delay(10);  // Small delay to prevent excessive CPU usage
}

/*
 * Read password from EEPROM
 */
void readPasswordFromEEPROM() {
  // Read password length
  uint8_t passwordLen = EEPROM.read(EEPROM_PASSWORD_LEN_ADDR);
  
  // Validate length
  if (passwordLen == 0 || passwordLen > MAX_PASSWORD_LEN) {
    storedPassword = "";
    return;
  }
  
  // Read password string
  storedPassword = "";
  for (uint8_t i = 0; i < passwordLen; i++) {
    char c = EEPROM.read(EEPROM_PASSWORD_ADDR + i);
    if (c == 0) break;  // Null terminator
    storedPassword += c;
  }
}

/*
 * Write password to EEPROM
 */
void writePasswordToEEPROM(String password) {
  // Limit password length
  if (password.length() > MAX_PASSWORD_LEN) {
    password = password.substring(0, MAX_PASSWORD_LEN);
  }
  
  // Write password length
  EEPROM.update(EEPROM_PASSWORD_LEN_ADDR, password.length());
  
  // Write password string
  for (uint8_t i = 0; i < password.length(); i++) {
    EEPROM.update(EEPROM_PASSWORD_ADDR + i, password.charAt(i));
  }
  
  // Write null terminator
  if (password.length() < MAX_PASSWORD_LEN) {
    EEPROM.update(EEPROM_PASSWORD_ADDR + password.length(), 0);
  }
  
  // Update in-memory copy
  storedPassword = password;
}

/*
 * Alternative: Clear password from EEPROM
 */
void clearPasswordFromEEPROM() {
  EEPROM.update(EEPROM_PASSWORD_LEN_ADDR, 0);
  storedPassword = "";
  Serial.println("Password cleared from EEPROM");
}
Tehtävä 2.16: UART CRC:llä 📋 Näytä
Arduino

UART - CRC Error Checking, String Parsing & Char vs ASCII

CRC is used to detect errors in serial communication. It adds a checksum to the data, allowing the receiver to verify data integrity. This example shows three approaches: CRC error checking, string parsing with value extraction, and understanding char vs ASCII.

📚 Task: Implement UART communication with CRC error checking, demonstrate string parsing that extracts numeric values from commands like "test 100", and understand the difference between char and ASCII.

Example 1: UART - CRC Error Checking
/*
 * UART Communication with CRC-8 Error Checking
 * 
 * Hardware Setup:
 * - Uses built-in UART (Serial) on Arduino Uno
 * - Connect via USB or use Serial Monitor in Arduino IDE
 * 
 * How it Works:
 * - Sender calculates CRC-8 checksum and appends it to data
 * - Receiver recalculates CRC and compares with received checksum
 * - If CRC matches, data is valid; if not, error is detected
 * 
 * Message Format: [DATA][CRC]
 * Example: "Hello" + CRC = "Hello" + 0x5A
 * 
 * CRC-8 Algorithm:
 * - Polynomial: x^8 + x^2 + x + 1 (0x07)
 * - Initial value: 0x00
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - Serial Communication: https://www.arduino.cc/reference/en/language/functions/communication/serial/
 */

#define BUFFER_SIZE 64

// Calculate CRC-8 checksum
uint8_t calculateCRC8(uint8_t *data, uint8_t length) {
  uint8_t crc = 0x00;
  uint8_t polynomial = 0x07;  // CRC-8 polynomial
  
  for (uint8_t i = 0; i < length; i++) {
    crc ^= data[i];
    for (uint8_t j = 0; j < 8; j++) {
      if (crc & 0x80) {
        crc = (crc << 1) ^ polynomial;
      } else {
        crc <<= 1;
      }
    }
  }
  return crc;
}

// Send data with CRC
void sendWithCRC(String message) {
  uint8_t data[BUFFER_SIZE];
  uint8_t len = message.length();
  
  // Copy message to buffer
  for (uint8_t i = 0; i < len; i++) {
    data[i] = message.charAt(i);
  }
  
  // Calculate CRC
  uint8_t crc = calculateCRC8(data, len);
  
  // Send message + CRC
  Serial.print(message);
  Serial.write(crc);
  Serial.println(" [CRC sent]");
}

void setup() {
  Serial.begin(9600);
  delay(1000);
  
  Serial.println("UART with CRC Example");
  Serial.println("---------------------");
  
  // Send test messages
  sendWithCRC("Hello");
  sendWithCRC("Test123");
  sendWithCRC("Arduino");
}

void loop() {
  static uint8_t buffer[BUFFER_SIZE];
  static uint8_t index = 0;
  
  // Receive data
  while (Serial.available() > 0) {
    uint8_t byte = Serial.read();
    
    if (byte == '\n' || byte == '\r') {
      if (index > 1) {  // At least 1 data byte + 1 CRC byte
        // Last byte is CRC, rest is data
        uint8_t dataLen = index - 1;
        uint8_t receivedCRC = buffer[dataLen];
        uint8_t calculatedCRC = calculateCRC8(buffer, dataLen);
        
        // Verify CRC
        if (receivedCRC == calculatedCRC) {
          Serial.print("Valid: ");
          for (uint8_t i = 0; i < dataLen; i++) {
            Serial.write(buffer[i]);
          }
          Serial.println();
        } else {
          Serial.print("CRC Error! Received: 0x");
          Serial.print(receivedCRC, HEX);
          Serial.print(", Calculated: 0x");
          Serial.println(calculatedCRC, HEX);
        }
      }
      index = 0;
    } else if (index < BUFFER_SIZE - 1) {
      buffer[index++] = byte;
    }
  }
}
Example 2: UART - Software Serial String Parsing
/*
 * Software UART String Parsing - Extract Numeric Value from Command
 * 
 * Hardware Setup:
 * - Uses SoftwareSerial library for software UART (no hardware UART)
 * - RX pin: 2 (connect TX from other device here)
 * - TX pin: 3 (connect RX from other device here)
 * - Built-in Serial (USB) used only for debugging output
 * - No error checking (no parity, no CRC, no flow control)
 * 
 * How it Works:
 * - SoftwareSerial emulates UART using software timing
 * - Reads complete string from software UART buffer
 * - Parses commands like "test 100" and extracts the numeric value
 * - Returns the extracted value as an integer
 * 
 * Example Commands (send via software UART on pins 2/3):
 * - "test 100" -> extracts 100
 * - "test 42" -> extracts 42
 * - "test -5" -> extracts -5
 * 
 * Note: SoftwareSerial is slower and less reliable than hardware UART
 *       Maximum reliable baud rate is typically 9600 or lower
 * 
 * Library Required:
 * - SoftwareSerial (included with Arduino IDE)
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - SoftwareSerial Library: https://www.arduino.cc/reference/en/libraries/softwareserial/
 */

#include <SoftwareSerial.h>

// Create software UART instance
// RX pin = 2, TX pin = 3
SoftwareSerial softSerial(2, 3);  // RX, TX

String inputString = "";  // String to hold incoming data

void setup() {
  // Initialize built-in Serial for debugging (USB)
  Serial.begin(9600);
  
  // Initialize software UART
  softSerial.begin(9600);
  
  inputString.reserve(64);  // Reserve 64 bytes for the string
  
  Serial.println("Software UART String Parsing Example");
  Serial.println("Send commands via software UART (pins 2/3)");
  Serial.println("Example: test 100");
  Serial.println("----------------------------------------");
}

void loop() {
  // Check if data is available on software UART
  if (softSerial.available() > 0) {
    // Read the entire string until newline
    inputString = softSerial.readStringUntil('\n');
    inputString.trim();  // Remove whitespace
    
    if (inputString.length() > 0) {
      // Parse the command and extract value
      int value = parseCommand(inputString);
      
      if (value != -9999) {  // -9999 indicates parsing error
        // Echo back via software UART
        softSerial.print("Command: ");
        softSerial.println(inputString);
        softSerial.print("Value: ");
        softSerial.println(value);
        
        // Also print to Serial Monitor for debugging
        Serial.print("Software UART received: ");
        Serial.println(inputString);
        Serial.print("Extracted value: ");
        Serial.println(value);
        Serial.println("---");
      } else {
        // Send error via software UART
        softSerial.print("Error parsing: ");
        softSerial.println(inputString);
        
        // Also print to Serial Monitor
        Serial.print("Error: Could not parse value from: ");
        Serial.println(inputString);
      }
    }
  }
}

/*
 * Parse command string and extract numeric value
 * Format: "test 100" or "test -42" etc.
 * Returns: extracted integer value, or -9999 if error
 */
int parseCommand(String command) {
  // Find the position of the last space
  int lastSpaceIndex = command.lastIndexOf(' ');
  
  if (lastSpaceIndex == -1) {
    // No space found, try to parse entire string as number
    return command.toInt();
  }
  
  // Extract substring after last space
  String valueString = command.substring(lastSpaceIndex + 1);
  
  // Convert to integer
  int value = valueString.toInt();
  
  // Check if conversion was successful
  // toInt() returns 0 if conversion fails, so we need additional check
  if (value == 0 && valueString != "0" && valueString != "-0") {
    // Conversion likely failed
    return -9999;
  }
  
  return value;
}

/*
 * Alternative parsing function for more complex commands
 * Example: "test value=100" or "test:100"
 */
int parseCommandAdvanced(String command) {
  // Try to find '=' separator
  int equalsIndex = command.indexOf('=');
  if (equalsIndex != -1) {
    String valueString = command.substring(equalsIndex + 1);
    return valueString.toInt();
  }
  
  // Try to find ':' separator
  int colonIndex = command.indexOf(':');
  if (colonIndex != -1) {
    String valueString = command.substring(colonIndex + 1);
    return valueString.toInt();
  }
  
  // Try to find last space (original method)
  int lastSpaceIndex = command.lastIndexOf(' ');
  if (lastSpaceIndex != -1) {
    String valueString = command.substring(lastSpaceIndex + 1);
    return valueString.toInt();
  }
  
  return -9999;  // Error
}
Example 3: UART - Char vs ASCII
/*
 * Char vs ASCII - Understanding the Difference
 * 
 * IMPORTANT CONCEPT:
 * - char is NOT the same as ASCII
 * - char is a data type (8-bit integer) that can store ASCII values
 * - ASCII is an encoding standard that maps characters to numbers
 * - char variables store numeric values (0-255), not characters themselves
 * 
 * Key Points:
 * - char c = 'A';  // Stores the ASCII value 65, not the letter 'A'
 * - char is actually a signed 8-bit integer (-128 to 127)
 * - unsigned char is an unsigned 8-bit integer (0 to 255)
 * - When you print a char, it's converted to the corresponding ASCII character
 * - You can do arithmetic with char values
 * 
 * Hardware Setup:
 * - Uses built-in UART (Serial) on Arduino Uno
 * - Connect via USB or use Serial Monitor in Arduino IDE
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - Data Types: https://www.arduino.cc/reference/en/language/variables/data-types/
 */

void setup() {
  Serial.begin(9600);
  delay(1000);
  
  Serial.println("Char vs ASCII Example");
  Serial.println("=====================");
  Serial.println();
  
  demonstrateCharVsASCII();
  demonstrateCharArithmetic();
  demonstrateCharComparison();
}

void loop() {
  // Empty - demonstration runs once in setup()
}

/*
 * Demonstrate that char stores numeric values, not characters
 */
void demonstrateCharVsASCII() {
  Serial.println("1. Char stores numeric ASCII values:");
  Serial.println("-----------------------------------");
  
  char c1 = 'A';        // ASCII value 65
  char c2 = 'B';        // ASCII value 66
  char c3 = 65;         // Same as 'A' - stores number 65
  char c4 = 0x41;       // Hexadecimal 0x41 = 65 = 'A'
  
  Serial.print("char c1 = 'A';  // c1 = ");
  Serial.print((int)c1);  // Cast to int to show numeric value
  Serial.print(" (ASCII code for 'A')");
  Serial.println();
  
  Serial.print("char c2 = 'B';  // c2 = ");
  Serial.print((int)c2);
  Serial.print(" (ASCII code for 'B')");
  Serial.println();
  
  Serial.print("char c3 = 65;   // c3 = ");
  Serial.print((int)c3);
  Serial.print(" (same as 'A')");
  Serial.println();
  
  Serial.print("char c4 = 0x41; // c4 = ");
  Serial.print((int)c4);
  Serial.print(" (hex 0x41 = 65 = 'A')");
  Serial.println();
  Serial.println();
  
  // Print as character vs as number
  Serial.println("Printing as character vs as number:");
  Serial.print("c1 as char: '");
  Serial.print(c1);
  Serial.print("' | c1 as int: ");
  Serial.println((int)c1);
  Serial.println();
}

/*
 * Demonstrate arithmetic operations with char
 */
void demonstrateCharArithmetic() {
  Serial.println("2. Char arithmetic (char is a number):");
  Serial.println("--------------------------------------");
  
  char letter = 'A';
  Serial.print("char letter = 'A';  // letter = ");
  Serial.println((int)letter);
  
  // Add 1 to get next letter
  char nextLetter = letter + 1;
  Serial.print("letter + 1 = ");
  Serial.print((int)nextLetter);
  Serial.print(" which is '");
  Serial.print(nextLetter);
  Serial.println("'");
  
  // Subtract to get previous letter
  char prevLetter = letter - 1;
  Serial.print("letter - 1 = ");
  Serial.print((int)prevLetter);
  Serial.print(" which is '");
  Serial.print(prevLetter);
  Serial.println("'");
  
  // Convert lowercase to uppercase
  char lowercase = 'a';  // ASCII 97
  char uppercase = lowercase - 32;  // 'A' is 65, 'a' is 97, difference is 32
  Serial.print("'");
  Serial.print(lowercase);
  Serial.print("' - 32 = '");
  Serial.print(uppercase);
  Serial.println("'");
  Serial.println();
}

/*
 * Demonstrate comparison and type behavior
 */
void demonstrateCharComparison() {
  Serial.println("3. Char comparison and type behavior:");
  Serial.println("-------------------------------------");
  
  char c = '5';  // ASCII value 53, NOT the number 5!
  int num = 5;   // Actual number 5
  
  Serial.print("char c = '5';  // c = ");
  Serial.print((int)c);
  Serial.print(" (ASCII code, NOT the number 5!)");
  Serial.println();
  
  Serial.print("int num = 5;   // num = ");
  Serial.println(num);
  Serial.println();
  
  Serial.println("Common mistake - comparing char '5' with int 5:");
  Serial.print("c == 5? ");
  Serial.println(c == 5 ? "true" : "false");  // false! 53 != 5
  Serial.print("c == 53? ");
  Serial.println(c == 53 ? "true" : "false");  // true! 53 == 53
  Serial.println();
  
  Serial.println("To convert char '5' to int 5:");
  Serial.print("c - '0' = ");
  Serial.print(c - '0');
  Serial.print(" (subtract ASCII '0' which is 48)");
  Serial.println();
  Serial.print("c - 48 = ");
  Serial.println(c - 48);  // Same thing
  Serial.println();
  
  Serial.println("ASCII table snippet:");
  Serial.println("'0' = 48, '1' = 49, '2' = 50, ..., '9' = 57");
  Serial.println("'A' = 65, 'B' = 66, ..., 'Z' = 90");
  Serial.println("'a' = 97, 'b' = 98, ..., 'z' = 122");
  Serial.println();
}

/*
 * Practical example: Convert char digit to integer
 */
int charToInt(char c) {
  // Check if char is a digit (ASCII '0' to '9' = 48 to 57)
  if (c >= '0' && c <= '9') {
    return c - '0';  // Subtract ASCII '0' (48) to get actual number
  }
  return -1;  // Not a digit
}

/*
 * Practical example: Check if char is uppercase letter
 */
bool isUpperCase(char c) {
  // Uppercase letters: 'A' to 'Z' = ASCII 65 to 90
  return (c >= 'A' && c <= 'Z');
}

/*
 * Practical example: Check if char is lowercase letter
 */
bool isLowerCase(char c) {
  // Lowercase letters: 'a' to 'z' = ASCII 97 to 122
  return (c >= 'a' && c <= 'z');
}
Tehtävä 2.17: Tapahtumapohjainen ohjelmointi 📋 Näytä
Arduino

Event-Driven Programming

Event-driven programming responds to external events (voltage changes, sensor readings, etc.) rather than continuously polling. This approach is more efficient and responsive. Examples include voltage drop detection with filtering, low voltage monitoring, and motion detection.

📚 Task: Implement event-driven systems that respond to voltage changes, monitor battery levels, and detect motion with proper filtering to avoid false triggers.

Example 1: Event - Voltage Drop Detection with Filtering
/*
 * Event-Driven: Voltage Drop Detection with Filtering
 * 
 * Hardware Setup:
 * - Connect voltage source to analog pin A0 (via voltage divider if > 5V)
 * - Connect LED to pin 13 (built-in LED) to indicate voltage drop
 * - Optional: Connect potentiometer to A0 for testing
 * 
 * How it Works:
 * - Monitors voltage on analog pin
 * - Detects when voltage drops below threshold
 * - Uses filtering to avoid false triggers from noise/bouncing
 * - Triggers event handler when voltage drop is confirmed
 * 
 * Filtering Method:
 * - Takes multiple samples and requires consistent low reading
 * - Prevents false triggers from electrical noise
 * - Debouncing delay ensures stable reading
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - analogRead(): https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/
 */

#define VOLTAGE_PIN A0
#define LED_PIN 13
#define THRESHOLD 512        // 2.5V (512 out of 1024 = 2.5V/5V)
#define FILTER_SAMPLES 5    // Number of samples for filtering
#define FILTER_DELAY 10      // Delay between samples (ms)

int voltageDropCount = 0;
bool voltageDropDetected = false;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  pinMode(VOLTAGE_PIN, INPUT);
  
  Serial.println("Voltage Drop Detection with Filtering");
  Serial.println("------------------------------------");
  Serial.print("Threshold: ");
  Serial.print((THRESHOLD * 5.0) / 1024.0);
  Serial.println("V");
}

void loop() {
  // Check for voltage drop event
  if (checkVoltageDrop()) {
    handleVoltageDropEvent();
  }
  
  // Other tasks can run here
  delay(50);
}

/*
 * Check if voltage has dropped below threshold
 * Uses filtering to avoid false triggers
 */
bool checkVoltageDrop() {
  int lowReadings = 0;
  
  // Take multiple samples
  for (int i = 0; i < FILTER_SAMPLES; i++) {
    int reading = analogRead(VOLTAGE_PIN);
    
    if (reading < THRESHOLD) {
      lowReadings++;
    }
    
    delay(FILTER_DELAY);
  }
  
  // Require majority of samples to be low (filtering)
  if (lowReadings >= (FILTER_SAMPLES / 2 + 1)) {
    if (!voltageDropDetected) {
      voltageDropDetected = true;
      return true;  // New event detected
    }
  } else {
    voltageDropDetected = false;  // Reset if voltage recovers
  }
  
  return false;
}

/*
 * Event handler for voltage drop
 */
void handleVoltageDropEvent() {
  voltageDropCount++;
  
  int voltage = analogRead(VOLTAGE_PIN);
  float voltageVolts = (voltage * 5.0) / 1024.0;
  
  // Turn on LED
  digitalWrite(LED_PIN, HIGH);
  
  // Log event
  Serial.print("Event #");
  Serial.print(voltageDropCount);
  Serial.print(": Voltage drop detected! Voltage = ");
  Serial.print(voltageVolts);
  Serial.println("V");
  
  // You can add more actions here:
  // - Save to EEPROM
  // - Send alert via UART
  // - Trigger other devices
  // - etc.
}
Example 2: Event - Low Voltage Indicator
/*
 * Event-Driven: Low Voltage Indicator
 * 
 * Hardware Setup:
 * - Connect battery/voltage source to analog pin A0 (via voltage divider)
 * - Connect LED to pin 13 (built-in LED) for warning
 * - Connect buzzer to pin 9 (optional) for audio alert
 * 
 * How it Works:
 * - Continuously monitors battery voltage
 * - Triggers event when voltage drops below critical level
 * - Uses filtering to prevent false alarms from noise
 * - Provides visual and optional audio warning
 * 
 * Voltage Divider Calculation:
 * - For 12V battery: R1 = 10kΩ, R2 = 10kΩ gives 6V max to A0
 * - For 9V battery: R1 = 4.7kΩ, R2 = 10kΩ gives ~6.5V max to A0
 * - Adjust divider ratio based on your battery voltage
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - analogRead(): https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/
 * - tone(): https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/
 */

#define VOLTAGE_PIN A0
#define LED_PIN 13
#define BUZZER_PIN 9
#define LOW_VOLTAGE_THRESHOLD 3.0   // Critical voltage in volts
#define WARNING_VOLTAGE_THRESHOLD 3.5 // Warning voltage in volts
#define FILTER_SAMPLES 10            // Samples for filtering
#define CHECK_INTERVAL 1000          // Check every 1 second

// Voltage divider ratio (if using divider)
// Actual battery voltage = measured voltage * divider_ratio
// Example: If divider gives 1/2, then ratio = 2.0
#define DIVIDER_RATIO 2.0

bool lowVoltageEvent = false;
bool warningVoltageEvent = false;
unsigned long lastCheck = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(VOLTAGE_PIN, INPUT);
  
  Serial.println("Low Voltage Indicator");
  Serial.println("--------------------");
  Serial.print("Critical threshold: ");
  Serial.print(LOW_VOLTAGE_THRESHOLD);
  Serial.println("V");
  Serial.print("Warning threshold: ");
  Serial.print(WARNING_VOLTAGE_THRESHOLD);
  Serial.println("V");
}

void loop() {
  // Check voltage periodically (event-driven, not continuous polling)
  if (millis() - lastCheck >= CHECK_INTERVAL) {
    lastCheck = millis();
    
    float voltage = readFilteredVoltage();
    
    // Check for low voltage event
    if (voltage < LOW_VOLTAGE_THRESHOLD && !lowVoltageEvent) {
      handleLowVoltageEvent(voltage);
    } else if (voltage >= LOW_VOLTAGE_THRESHOLD) {
      lowVoltageEvent = false;
      digitalWrite(LED_PIN, LOW);
      noTone(BUZZER_PIN);
    }
    
    // Check for warning voltage event
    if (voltage < WARNING_VOLTAGE_THRESHOLD && voltage >= LOW_VOLTAGE_THRESHOLD) {
      if (!warningVoltageEvent) {
        handleWarningVoltageEvent(voltage);
      }
    } else if (voltage >= WARNING_VOLTAGE_THRESHOLD) {
      warningVoltageEvent = false;
    }
    
    // Display current voltage
    Serial.print("Battery voltage: ");
    Serial.print(voltage);
    Serial.println("V");
  }
  
  // Other tasks can run here
}

/*
 * Read voltage with filtering to avoid noise
 */
float readFilteredVoltage() {
  long sum = 0;
  
  // Take multiple samples
  for (int i = 0; i < FILTER_SAMPLES; i++) {
    sum += analogRead(VOLTAGE_PIN);
    delay(5);
  }
  
  // Calculate average
  int average = sum / FILTER_SAMPLES;
  
  // Convert to voltage
  float voltage = (average * 5.0) / 1024.0;
  
  // Apply divider ratio if using voltage divider
  voltage = voltage * DIVIDER_RATIO;
  
  return voltage;
}

/*
 * Event handler for critical low voltage
 */
void handleLowVoltageEvent(float voltage) {
  lowVoltageEvent = true;
  
  Serial.println("!!! CRITICAL: Low Voltage Event !!!");
  Serial.print("Voltage: ");
  Serial.print(voltage);
  Serial.println("V");
  
  // Visual warning - fast blink
  for (int i = 0; i < 5; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(100);
    digitalWrite(LED_PIN, LOW);
    delay(100);
  }
  digitalWrite(LED_PIN, HIGH);  // Keep LED on
  
  // Audio warning (optional)
  tone(BUZZER_PIN, 1000, 500);  // 1kHz tone for 500ms
  
  // Additional actions:
  // - Save critical state to EEPROM
  // - Send alert via UART/WiFi
  // - Enter low-power mode
  // - Shutdown non-essential systems
}

/*
 * Event handler for warning voltage level
 */
void handleWarningVoltageEvent(float voltage) {
  warningVoltageEvent = true;
  
  Serial.println("WARNING: Battery voltage low");
  Serial.print("Voltage: ");
  Serial.print(voltage);
  Serial.println("V");
  
  // Visual warning - slow blink
  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
}
Example 3: Event - Motion Detection with Debouncing
/*
 * Event-Driven: Motion Detection with Debouncing
 * 
 * Hardware Setup:
 * - Connect PIR motion sensor to digital pin 2
 * - PIR sensor: VCC to 5V, GND to GND, OUT to pin 2
 * - Connect LED to pin 13 (built-in LED) for indication
 * - Optional: Connect buzzer to pin 9 for audio alert
 * 
 * How it Works:
 * - Uses interrupt to detect motion (event-driven)
 * - Implements debouncing to filter false triggers
 * - Triggers event handler when motion is detected
 * - Can be used for security, lighting control, etc.
 * 
 * Debouncing:
 * - Prevents multiple triggers from single motion
 * - Uses time-based filtering
 * - Ignores triggers within debounce period
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - attachInterrupt(): https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
 */

#define PIR_PIN 2
#define LED_PIN 13
#define BUZZER_PIN 9
#define DEBOUNCE_TIME 2000  // 2 seconds debounce (ms)

volatile bool motionDetected = false;
volatile unsigned long lastMotionTime = 0;
int motionCount = 0;

void setup() {
  Serial.begin(9600);
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  
  // Attach interrupt for motion detection (event-driven)
  // RISING: triggers when pin goes from LOW to HIGH
  attachInterrupt(digitalPinToInterrupt(PIR_PIN), motionISR, RISING);
  
  Serial.println("Motion Detection with Debouncing");
  Serial.println("--------------------------------");
  Serial.println("Waiting for motion...");
}

void loop() {
  // Check if motion event occurred
  if (motionDetected) {
    handleMotionEvent();
    motionDetected = false;  // Reset flag
  }
  
  // Other tasks can run here
  // The system is event-driven, not polling continuously
  delay(100);
}

/*
 * Interrupt Service Routine (ISR) for motion detection
 * This is called automatically when motion is detected
 */
void motionISR() {
  unsigned long currentTime = millis();
  
  // Debouncing: ignore triggers within debounce period
  if (currentTime - lastMotionTime > DEBOUNCE_TIME) {
    motionDetected = true;
    lastMotionTime = currentTime;
  }
}

/*
 * Event handler for motion detection
 */
void handleMotionEvent() {
  motionCount++;
  
  Serial.print("Motion detected! Event #");
  Serial.println(motionCount);
  Serial.print("Time: ");
  Serial.print(millis() / 1000);
  Serial.println(" seconds");
  
  // Visual indication
  digitalWrite(LED_PIN, HIGH);
  delay(500);
  digitalWrite(LED_PIN, LOW);
  
  // Audio indication (optional)
  tone(BUZZER_PIN, 2000, 200);  // 2kHz tone for 200ms
  
  // Additional actions:
  // - Turn on lights
  // - Send notification via UART/WiFi
  // - Log to EEPROM
  // - Trigger camera
  // - etc.
}

/*
 * Alternative: Non-interrupt version with polling and filtering
 * Use this if interrupts are not available or you prefer polling
 */
bool checkMotionPolling() {
  static unsigned long lastCheck = 0;
  static int consecutiveHigh = 0;
  const int REQUIRED_HIGH = 3;  // Require 3 consecutive HIGH readings
  
  // Check periodically
  if (millis() - lastCheck < 50) {
    return false;  // Too soon to check again
  }
  lastCheck = millis();
  
  // Read sensor
  if (digitalRead(PIR_PIN) == HIGH) {
    consecutiveHigh++;
    
    // Require multiple consecutive readings (filtering)
    if (consecutiveHigh >= REQUIRED_HIGH) {
      consecutiveHigh = 0;
      return true;  // Motion confirmed
    }
  } else {
    consecutiveHigh = 0;  // Reset counter
  }
  
  return false;
}
Tehtävä 2.18: PWM-signaalin suodatus 📋 Näytä
Arduino

PWM Signal Filtering

PWM (Pulse Width Modulation) signals can be filtered using analog low-pass filters (RC circuits) to create smooth analog voltages. This example shows how to control PWM via UART, asking the user for a percentage value (0-100%) and outputting the corresponding PWM signal.

📚 Task: Implement UART-based PWM control where the user enters a percentage (0-100%), and the system outputs the corresponding PWM signal. Different platforms (Arduino Uno vs ESP8266) use different PWM resolution ranges.

For Future: You might need an analog low-pass filter (RC circuit) to smooth the PWM signal into a true analog voltage. Connect a resistor (e.g., 1kΩ) and capacitor (e.g., 10µF) between PWM pin and ground to create a low-pass filter.

Example 1: Arduino Uno - PWM Control via UART
/*
 * PWM Signal Control via UART - Arduino Uno
 * 
 * Hardware Setup:
 * - Connect LED to pin 9 (PWM-capable pin) via 220Ω resistor
 * - Or connect motor/servo to pin 9
 * - Connect via USB for Serial communication
 * 
 * How it Works:
 * - Asks user for PWM percentage (0-100%) via Serial Monitor
 * - Converts percentage to Arduino PWM value (0-255)
 * - Outputs PWM signal on specified pin
 * - Arduino Uno uses 8-bit PWM (0-255 range)
 * 
 * PWM Resolution:
 * - Arduino Uno: 8-bit PWM = 256 levels (0-255)
 * - Frequency: ~490 Hz (pins 5,6) or ~980 Hz (pins 3,9,10,11)
 * 
 * For Future - Analog Low-Pass Filter:
 * - To convert PWM to smooth analog voltage, use RC low-pass filter
 * - Connect: PWM pin → Resistor (1kΩ) → Capacitor (10µF) → GND
 * - Cutoff frequency: f = 1/(2πRC)
 * - Example: R=1kΩ, C=10µF → f ≈ 16 Hz (smooth DC output)
 * - The capacitor charges/discharges, averaging the PWM signal
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - analogWrite(): https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
 */

#define PWM_PIN 9        // PWM-capable pin (3, 5, 6, 9, 10, 11 on Uno)
#define PWM_MAX 255      // Maximum PWM value for Arduino Uno (8-bit)

void setup() {
  Serial.begin(9600);
  pinMode(PWM_PIN, OUTPUT);
  
  // Initialize PWM to 0%
  analogWrite(PWM_PIN, 0);
  
  Serial.println("PWM Control via UART - Arduino Uno");
  Serial.println("===================================");
  Serial.println("Enter PWM percentage (0-100):");
  Serial.println("Example: 50 (for 50%)");
  Serial.println();
}

void loop() {
  // Check if data is available from Serial
  if (Serial.available() > 0) {
    // Read the input string
    String input = Serial.readStringUntil('\n');
    input.trim();
    
    // Parse percentage value
    int percentage = input.toInt();
    
    // Validate input (0-100%)
    if (percentage >= 0 && percentage <= 100) {
      // Convert percentage to PWM value
      // Arduino Uno: 0-100% maps to 0-255
      int pwmValue = map(percentage, 0, 100, 0, PWM_MAX);
      
      // Alternative calculation:
      // int pwmValue = (percentage * PWM_MAX) / 100;
      
      // Set PWM output
      analogWrite(PWM_PIN, pwmValue);
      
      // Calculate actual voltage (assuming 5V system)
      float voltage = (pwmValue * 5.0) / PWM_MAX;
      
      // Display result
      Serial.print("PWM set to: ");
      Serial.print(percentage);
      Serial.print("% (PWM value: ");
      Serial.print(pwmValue);
      Serial.print("/255, Voltage: ");
      Serial.print(voltage);
      Serial.println("V)");
      Serial.println();
      Serial.println("Enter new percentage (0-100):");
    } else {
      Serial.println("Error: Please enter a value between 0 and 100");
      Serial.println("Enter PWM percentage (0-100):");
    }
  }
  
  // Small delay to prevent excessive CPU usage
  delay(10);
}

/*
 * Function to set PWM with percentage (helper function)
 */
void setPWMPercent(int percentage) {
  // Clamp value to valid range
  if (percentage < 0) percentage = 0;
  if (percentage > 100) percentage = 100;
  
  // Convert to PWM value
  int pwmValue = map(percentage, 0, 100, 0, PWM_MAX);
  
  // Set PWM
  analogWrite(PWM_PIN, pwmValue);
}

/*
 * For Future: Analog Low-Pass Filter Design
 * 
 * To smooth PWM into analog voltage:
 * 
 * 1. Calculate cutoff frequency needed:
 *    - For smooth DC: cutoff should be much lower than PWM frequency
 *    - Arduino PWM: ~490-980 Hz
 *    - Recommended cutoff: 10-50 Hz (10x lower than PWM frequency)
 * 
 * 2. Choose resistor and capacitor:
 *    - Cutoff frequency: fc = 1 / (2π × R × C)
 *    - Example: fc = 16 Hz → R = 1kΩ, C = 10µF
 *    - Example: fc = 32 Hz → R = 1kΩ, C = 5µF
 * 
 * 3. Circuit connection:
 *    - PWM pin → Resistor → Output (to load) → Capacitor → GND
 *    - The capacitor acts as a voltage reservoir
 *    - Resistor limits charging current
 * 
 * 4. Output voltage:
 *    - Vout = Vpwm × (duty_cycle / 100)
 *    - Example: 5V PWM at 50% duty → 2.5V output (after filter)
 * 
 * 5. Load considerations:
 *    - Filter output impedance = R (resistor value)
 *    - Load should be much higher than R (10x or more)
 *    - Otherwise voltage will drop under load
 */
Example 2: ESP8266 - PWM Control via UART
/*
 * PWM Signal Control via UART - ESP8266
 * 
 * Hardware Setup:
 * - Connect LED to GPIO pin 4 (D2) via 220Ω resistor
 * - Or connect motor/servo to GPIO pin 4
 * - Connect via USB for Serial communication
 * 
 * How it Works:
 * - Asks user for PWM percentage (0-100%) via Serial Monitor
 * - Converts percentage to ESP8266 PWM value (0-1023)
 * - Outputs PWM signal on specified pin
 * - ESP8266 uses 10-bit PWM (0-1023 range)
 * 
 * PWM Resolution:
 * - ESP8266: 10-bit PWM = 1024 levels (0-1023)
 * - Frequency: Default 1 kHz (can be adjusted)
 * - More resolution than Arduino Uno (1024 vs 256 levels)
 * 
 * Important Differences from Arduino Uno:
 * - analogWrite() range: 0-1023 (not 0-255)
 * - Must call analogWriteRange() to set range (default 1023)
 * - Must call analogWriteFreq() to set frequency (default 1000 Hz)
 * 
 * For Future - Analog Low-Pass Filter:
 * - Same RC filter design as Arduino Uno
 * - ESP8266 PWM frequency: 1 kHz (default)
 * - Recommended cutoff: 50-100 Hz for smooth output
 * - Example: R=1kΩ, C=2.2µF → f ≈ 72 Hz
 */

#define PWM_PIN 4        // GPIO 4 (D2) - PWM-capable pin
#define PWM_MAX 1023     // Maximum PWM value for ESP8266 (10-bit)

void setup() {
  Serial.begin(115200);  // ESP8266 typically uses 115200 baud
  delay(100);
  
  // Configure PWM pin
  pinMode(PWM_PIN, OUTPUT);
  
  // Set PWM range (0-1023 is default, but explicit is good practice)
  analogWriteRange(PWM_MAX);
  
  // Set PWM frequency (default is 1000 Hz, can be adjusted)
  // Range: 100 Hz to 1000 Hz
  analogWriteFreq(1000);  // 1 kHz
  
  // Initialize PWM to 0%
  analogWrite(PWM_PIN, 0);
  
  Serial.println();
  Serial.println("PWM Control via UART - ESP8266");
  Serial.println("=================================");
  Serial.print("PWM Resolution: 10-bit (0-");
  Serial.print(PWM_MAX);
  Serial.println(")");
  Serial.print("PWM Frequency: ");
  Serial.print(1000);
  Serial.println(" Hz");
  Serial.println("Enter PWM percentage (0-100):");
  Serial.println("Example: 50 (for 50%)");
  Serial.println();
}

void loop() {
  // Check if data is available from Serial
  if (Serial.available() > 0) {
    // Read the input string
    String input = Serial.readStringUntil('\n');
    input.trim();
    
    // Parse percentage value
    int percentage = input.toInt();
    
    // Validate input (0-100%)
    if (percentage >= 0 && percentage <= 100) {
      // Convert percentage to PWM value
      // ESP8266: 0-100% maps to 0-1023
      int pwmValue = map(percentage, 0, 100, 0, PWM_MAX);
      
      // Alternative calculation:
      // int pwmValue = (percentage * PWM_MAX) / 100;
      
      // Set PWM output
      analogWrite(PWM_PIN, pwmValue);
      
      // Calculate actual voltage (assuming 3.3V system for ESP8266)
      float voltage = (pwmValue * 3.3) / PWM_MAX;
      
      // Display result
      Serial.print("PWM set to: ");
      Serial.print(percentage);
      Serial.print("% (PWM value: ");
      Serial.print(pwmValue);
      Serial.print("/1023, Voltage: ");
      Serial.print(voltage);
      Serial.println("V)");
      Serial.println();
      Serial.println("Enter new percentage (0-100):");
    } else {
      Serial.println("Error: Please enter a value between 0 and 100");
      Serial.println("Enter PWM percentage (0-100):");
    }
  }
  
  // Small delay to prevent excessive CPU usage
  delay(10);
}

/*
 * Function to set PWM with percentage (helper function)
 */
void setPWMPercent(int percentage) {
  // Clamp value to valid range
  if (percentage < 0) percentage = 0;
  if (percentage > 100) percentage = 100;
  
  // Convert to PWM value
  int pwmValue = map(percentage, 0, 100, 0, PWM_MAX);
  
  // Set PWM
  analogWrite(PWM_PIN, pwmValue);
}

/*
 * Function to change PWM frequency (ESP8266 specific)
 */
void setPWMFrequency(uint32_t frequency) {
  // Frequency range: 100 Hz to 1000 Hz
  if (frequency < 100) frequency = 100;
  if (frequency > 1000) frequency = 1000;
  
  analogWriteFreq(frequency);
  
  Serial.print("PWM frequency set to: ");
  Serial.print(frequency);
  Serial.println(" Hz");
}

/*
 * For Future: Analog Low-Pass Filter Design for ESP8266
 * 
 * To smooth PWM into analog voltage:
 * 
 * 1. ESP8266 PWM characteristics:
 *    - Frequency: 1 kHz (default, can be 100-1000 Hz)
 *    - Resolution: 10-bit (1024 levels)
 *    - Voltage: 3.3V (not 5V like Arduino Uno)
 * 
 * 2. Calculate cutoff frequency:
 *    - PWM frequency: 1000 Hz
 *    - Recommended cutoff: 50-100 Hz (10-20x lower)
 *    - Formula: fc = 1 / (2π × R × C)
 * 
 * 3. Component selection examples:
 *    - R = 1kΩ, C = 2.2µF → fc ≈ 72 Hz (good for 1 kHz PWM)
 *    - R = 1kΩ, C = 1µF → fc ≈ 159 Hz (faster response)
 *    - R = 2.2kΩ, C = 1µF → fc ≈ 72 Hz (higher impedance)
 * 
 * 4. Circuit connection:
 *    - GPIO pin → Resistor → Output (to load) → Capacitor → GND
 *    - Same as Arduino Uno, but note 3.3V system voltage
 * 
 * 5. Output voltage calculation:
 *    - Vout = 3.3V × (duty_cycle / 100)
 *    - Example: 50% duty → 1.65V output (after filter)
 *    - Maximum: 100% duty → 3.3V output
 * 
 * 6. Advantages of 10-bit PWM:
 *    - 1024 levels vs 256 levels (Arduino Uno)
 *    - Finer control resolution
 *    - Smoother transitions
 *    - Better for precision applications
 * 
 * 7. Load considerations:
 *    - Output impedance = R (resistor value)
 *    - Load should be >> R (10x or more recommended)
 *    - Higher R = less current draw, but higher output impedance
 *    - Lower R = more current draw, but lower output impedance
 * 
 * Documentation:
 * - Arduino Reference: https://www.arduino.cc/reference/
 * - ESP8266 Arduino Core: https://arduino-esp8266.readthedocs.io/
 * - analogWrite() for ESP8266: https://arduino-esp8266.readthedocs.io/en/latest/reference.html#analog-output
 */
Osa 3: Esimerkkiprojektit 📋 Näytä
Projekti 3.1: Esteitä välttävä robotti 📋 Näytä
Obstacle-avoiding robot

Autonomisen robotin rakentaminen servomoottoreilla

Tämä projekti luo robotin, joka käyttää servomoottoreita liikkumiseen ja ultraäänietäisyysanturia esteiden havaitsemiseen ja välttämiseen. Robotti valvoo jatkuvasti ympäristöään ja tekee päätöksiä etäisyysmittausten perusteella.

Tarvittavat komponentit:

  • Arduino Uno
  • HC-SR04 ultraäänietäisyysanturi (tai yhteensopiva)
  • 2x jatkuvaa pyörimistä olevaa servomoottoria (360-servo)
  • Robotin runko pyörillä
  • Paristopaketti (6-9V)
  • Hyppylangat
  • afstandssensor-kirjasto (etäisyysanturikirjasto)

Vaihe 1: Sisällytä kirjastot ja luo objektit

Ensin sisällytetään tarvittavat kirjastot ja luodaan instanssit servoille ja etäisyysanturille:

#include <Servo.h>
#include <afstandssensor.h>

AfstandsSensor distanceSensor(13, 12);  // Trigger pin 13, echo pin 12
Servo servoLeft;
Servo servoRight;

Vaihe 2: Määritä muuttujat

Aseta muuttujat servojen kulmille ja ohjausarvoille:

int angleRight = 90;  // 90 = stopped for continuous rotation servo
int angleLeft = 90;   // 90 = stopped for continuous rotation servo
int r = 0;            // Right servo control variable
int l = 0;            // Left servo control variable

Vaihe 3: Setup-funktio

Alusta servot ja sarjaliikenne setup-funktiossa:

void setup() {
  servoLeft.attach(9);     // Attach left servo to pin 9
  servoRight.attach(10);   // Attach right servo to pin 10
  Serial.begin(9600);      // Start serial for debugging
}

Vaihe 4: Pääsilmukka - Etäisyyden valvonta

Pääsilmukka lukee jatkuvasti etäisyyttä ja tulostaa debug-viestejä anturilukemien perusteella:

void loop() {
  float distance = distanceSensor.afstandCM();  // Get distance in cm
  
  if (distance == -1) {
    Serial.println("Fix distance reading");  // Sensor error
  }
  
  if (distance < 20) {
    Serial.println("Reverse and turn");  // Too close, back up
  }
  
  if (distance >= 20) {
    Serial.println("Move forward");  // Clear path
  }
  
  delay(150);  // Delay to prevent sensor overload
}

Valmis robotin koodi

Tässä on valmis ohjelma esteitä välttävälle robotille debug-tulostuksella:

Valmis robotin koodi
/*
 * Obstacle-Avoiding Robot with Servo Motors
 * Uses distance sensor and continuous rotation servos
 * Includes Serial debugging for monitoring behavior
 */

#include <Servo.h>
#include <afstandssensor.h>

AfstandsSensor distanceSensor(13, 12);  // Trigger pin 13, echo pin 12
Servo servoLeft;
Servo servoRight;

int speedRight = 90;  // 90 = stopped for continuous rotation servo
int speedLeft = 90;   // 90 = stopped for continuous rotation servo

void setup() {
  servoLeft.attach(9);    // Attach left servo to pin 9
  servoRight.attach(10);  // Attach right servo to pin 10
  Serial.begin(9600);     // Initialize serial communication
  
  Serial.println("Robot initialized!");
  Serial.println("Starting in 2 seconds...");
  delay(2000);
}

void loop() {
  // Read distance in cm from the sensor
  float distance = distanceSensor.afstandCM();
  
  // If sensor reading failed, set a high dummy value
  if (distance == -1) {
    distance = 1000;
    Serial.println("Sensor error - assuming clear path");
  }
  
  // Debug: print current distance
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.print(" cm - ");
  
  // Take action based on distance
  if (distance < 20) {
    Serial.println("OBSTACLE! Reversing and turning...");
    reverse();         // Too close — reverse and turn
  }
  
  if (distance >= 20) {
    Serial.println("Path clear - moving forward");
    driveForward();    // Path is clear — go forward
  }
  
  delay(150);  // Wait before next sensor reading
}

void reverse() {
  Serial.println("  -> Reversing...");
  setMotorSpeeds(70, 70);  // Move backward
  delay(1000);             // Reverse for 1 second
  
  // Random turn: add variation to direction
  int r = random(-30, 30);
  int l = random(-30, 30);
  speedRight = 70 - r;
  speedLeft = 70 - l;
  
  Serial.print("  -> Turning (L:");
  Serial.print(speedLeft);
  Serial.print(", R:");
  Serial.print(speedRight);
  Serial.println(")");
  
  setMotorSpeeds(speedLeft, speedRight);
  delay(1000);             // Turn for 1 second
}

void driveForward() {
  setMotorSpeeds(100, 100);  // Move forward
}

void setMotorSpeeds(int leftSpeed, int rightSpeed) {
  // Convert values to servo-compatible angles
  servoLeft.write(rightSpeed);       // Left motor
  servoRight.write(180 - leftSpeed); // Right motor reversed
}

Understanding Continuous Rotation Servos (360°):

Unlike regular servos that move to specific angles, continuous rotation servos work like DC motors with speed control:

  • 90 = Stopped (no movement)
  • 0-89 = Rotation in one direction (0 = full speed, 89 = slow)
  • 91-180 = Rotation in opposite direction (91 = slow, 180 = full speed)

Example: servoLeft.write(100) moves the servo forward, while servoLeft.write(70) moves it backward.

How it works:

  1. Distance Measurement: The robot continuously measures the distance to objects in front of it using the ultrasonic sensor.
  2. Decision Making: Based on the distance reading:
    • If distance ≥ 20cm: Move forward at full speed (speed = 100)
    • If distance < 20cm: Reverse and perform random turn to avoid obstacle
    • If sensor error (distance = -1): Assume clear path and continue forward
  3. Obstacle Avoidance: When an obstacle is detected, the robot reverses for 1 second (speed = 70), then performs a random turn with varied speeds to find a clear path.
  4. Motor Control: The setMotorSpeeds() function converts speed values to servo commands. Note that one servo is reversed (180 - speed) because it's mounted facing the opposite direction.