Difference between revisions of "Arduino Programming Fundamentals"
Line 315: | Line 315: | ||
− | [[Category:Arduino]] | + | [[Category:Arduino]][[Category:Programming]] |
Revision as of 13:23, 12 January 2023
Introduction to Wiring
In the About Arduino page you could read that Arduino is in principle an attempt to make physical computing easier accessible for the average user. It did this by creating a hardware platform (development board) based on the Atmel AVR 8- and 32-bit family of μControllers. You also got introduced to the Wiring[1] language framework Arduino implemented for their hardware platforms and the Integrated Development Environment (IDE) Arduino adapted from the Processing project.
In this section we will go a bit further and see what all these elements are hiding from us.
Have a cappuccino
When we advance in our explorations at some point it is important to realise that making things easier usually means making compromises on other levels. In case of the Arduino and Wiring language framework this specifically means we compromise memory space and execution speed for ease of programming. In many cases this is perfectly fine but there are certain situations where it is important to understand these compromises and their ways around it (on the compromise of ease of use).
In principle Arduino adds a lot of milk and sugar to the μController it is based on. This makes us a nice cappuccino, but what if what we need is an espresso?
Cappuccino vs. espresso
The ingredient to our cappucino is quite close and is revealed by first thing you'll see when starting a new sketch in the Arduino IDE. Each new sketch will start with the standard template:
1void setup() {
2 // put your setup code here, to run once:
3
4}
5
6void loop() {
7 // put your main code here, to run repeatedly:
8}
Now this looks easy enough and we all know how what to do here. However, of course there is more to this and to understand what, it is important to know the language we are actually writing our Arduino (Wiring) code in. The language used by the Wiring language (and with that the Arduino language) is C / C++ with an additional sugar topping. Actually this is a combination of the languages C and C++ which can be intermingled. From here on, for brevity, everywhere C is mentioned it can be read as C / C++ unless noted otherwise.
Every program written in C should contain at least a routine called main
. This is the main entry point for code execution[2]. The simplest C program that does nothing at all looks like this:
1int main(void) {
2 // put your code here
3}
If Arduino code is just C code, where is the main
routine in the above example? It turns out the main
routine does exist but is hidden below the surface. When we press the Verify or Upload button in the IDE a lot of things are set in motion to get your code translated into a format the μController on the Arduino board does understand. Later we will see what exactly does happen but for now it is enough to understand that our setup()
and loop()
routines are added to the main
routine.
In the arduino core[3] there exists a file main.cpp[4]. This is the the file where the main
entry point resides. If you open this file in your text editor you will see something along these lines[5]
1#include <WProgram.h>
2
3int main(void)
4{
5 init();
6
7 setup();
8
9 for (;;)
10 loop();
11
12 return 0;
13}
The names on the highlighted lines 7 & 10 should look familiar. These are the function calls to the setup()
and loop()
routines in your arduino program. So you can see that, when the μController starts executing its program, it is actually the init()
routine that is executed first. This routine takes care of low-level housekeeping, e.g. setting up the timers for keeping track of elapsed timed. Only after all low level initialisations have been done the user setup()
routine is called. Once setup()
is done the loop()
routine is called from within the infinite loop construct for (;;)
. The last statement on line 12, return 0;
, is never reached and is only there to keep the compiler from complaining[6].
Notes & References
- ↑ http://wiring.org.co
- ↑ The linker will search for this function within the source files. If it can't find it it will fail with an error message like (.text+0x20): undefined reference to `main.
- ↑ https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/
- ↑ https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/main.cpp
- ↑ Shown code is from an earlier version. The current version is a bit more complicated. However the overall structure remains the same
- ↑ The main() routine is declared as returning an integer. If the compiler finds no value being returned on reaching the end of the function definition the compiler will complain with a warning message: control reaches end of non-void function.
Functions
In computer science, a subroutine or subprogram (also called procedure, method, function, or routine) is a portion of code within a larger program, which performs a specific task and is relatively independent of the remaining code.
A function is a way for programmers to reuse code without having to rewrite it, this is time saving, and often makes code more readable.
what we need to have a function:
1) each function must have a unique name
2) the function name is followed by parentheses()
3) functions have a return type, e.g. void
4) the body of a function is enclosed in opening and closing braces {}
Lets do a very simple example to see how it works
void setup() {
Serial.begin(9600);
DashedLine(); //here we are calling the function
Serial.println("| BONJOUR |");
DashedLine(); //here we call it again
}
void loop() {
}
void DashedLine() //here is where we create the function
{
Serial.println("----------------");
}
Passing a value to a function
and a bit of for loop
void setup() {
Serial.begin(9600);
// draw the menu box
DashedLine(24);
Serial.println("| Program Options Menu |");
DashedLine(24);
}
void loop() {
}
//a function needs to be able to accept an integer value that is passed to it,
//the variable type and the name of the variable
//are inserted between the opening an closing parentheses after the function name
void DashedLine(int len)
{
int i;
// draw the line
//The body of the sketch uses the len variable in a for loop
//to print out the correct number of dashes that make up the dashed line of the menu box
for (i = 0; i < len; i++) {
Serial.print("-");
}
// move the cursor to the next line
Serial.println("");
}
let's try it with our usual Hello World blink
pinMode(13, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
blinkLed();
}
void blinkLed() {
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
now we can reuse the function multiple times
In this example, we’ll add a parameter to the function to allow the function to vary the speed of the blink
void setup() {
// initialize digital pin 13 as an output.
pinMode(13, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
blinkLed(200);
}
void blinkLed(int delayTime) {
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
delay(delayTime); // wait for a second
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
delay(delayTime); // wait for a second
}
Arrays and the for loop
int timer = 100; // The higher the number, the slower the timing.
int ledPins[] = { 2, 3, 4, 5, 6, 7 }; // an array of pin numbers to which LEDs are attached
int pinCount = 6; // the number of pins (i.e. the length of the array)
void setup() {
// the array elements are numbered from 0 to (pinCount - 1).
// use a for loop to initialize each pin as an output:
//We can see that thisPin is initialized at 0 and pinCount is equal to 6
//pinCount is one of the variables declared at the top
//Every time through the for loop, thisPin is incremented by adding 1
for (int thisPin = 0; thisPin < pinCount; thisPin++) {
pinMode(ledPins[thisPin], OUTPUT);
}
}
void loop() {
// loop from the lowest pin to the highest:
for (int thisPin = 0; thisPin < pinCount; thisPin++) {
// turn the pin on:
digitalWrite(ledPins[thisPin], HIGH);
delay(timer);
// turn the pin off:
digitalWrite(ledPins[thisPin], LOW);
}
// loop from the highest pin to the lowest:
for (int thisPin = pinCount - 1; thisPin >= 0; thisPin--) {
// turn the pin on:
digitalWrite(ledPins[thisPin], HIGH);
delay(timer);
// turn the pin off:
digitalWrite(ledPins[thisPin], LOW);
}
}
Beyond the delay()
Blink without delay
const int ledPin = 13; // the number of the LED pin
// Variables will change:
int ledState = LOW; // ledState used to set the LED
long previousMillis = 0; // will store last time LED was updated
// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000; // interval at which to blink (milliseconds)
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop()
{
// here is where you'd put code that needs to be running all the time.
// check to see if it's time to blink the LED; that is, if the
// difference between the current time and last time you blinked
// the LED is bigger than the interval at which you want to
// blink the LED.
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW)
ledState = HIGH;
else
ledState = LOW;
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
}
As overly complicate as it seems, Blink without delay illustrates a very important concept known as a State Machine
A State Machine
A ‘state’ is the condition of a thing at a specific time. Something that can accomplish tasks and that utilizes states at its core is a state machine. They are also known as Finite State Machines (FSM), meaning that we know all possible states of the thing. The key to the state machine is the concept of time and history. The state of the machine is evaluated periodically. Each time it is evaluated, a new state is chosen (which could be the same state again), and the output is presented.
int ledPin = 13; // the number of the LED pin
int ledState = LOW; // ledState used to set the LED
unsigned long previousMillis = 0; // will store last time LED was updated
long OnTime = 500; // milliseconds of on-time
long OffTime = 500; // milliseconds of off-time
void setup()
{
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop()
{
// check to see if it's time to change the state of the LED
unsigned long currentMillis = millis();
unsigned long elapsed = currentMillis - previousMillis; // elapsed is the time since the end of the last cycle
if((ledState == HIGH) && (elapsed >= OnTime))
{
ledState = LOW; // Turn it off
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
else if ((ledState == LOW) && (elapsed >= OffTime))
{
ledState = HIGH; // turn it on
previousMillis = currentMillis; // Remember the time
digitalWrite(ledPin, ledState); // Update the actual LED
}
}