By: Jan Joe S. Carcillar
Memory - Store data the processor ouputs or receives.
Syntax highlighting
When microcontrollers first came out, they were programmed using the C language. Now, there are a few programming language than you can work with namely:
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
delay(10000);
bool ledState = LOW;
unsigned long prevMillis = 0;
unsigned long currentMillis = 0;
const unsigned long blinkInterval = 1000;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
currentMillis = millis(); //get the number of milliseconds the microcontroller has run since boot up
if (currentMillis - prevMillis >= blinkInterval){ //if current time exceeds the previous time by the set interval
ledState = !ledState; //toggle the state of the LED to its opposite using "!"
prevMillis = currentMillis; //set the current time as the previous time to be used in the next iteration
}
digitalWrite(LED_BUILTIN, ledState); //turn on or turn off the LED depending on the condition above
}
if (currentMillis - prevMillis > = blinkInterval) {
ledState = !ledState;
prevMillis = currentMillis;
}
An array is a collection of variables that are accessed with an index number.
//initializing arrays
int myInts[6]; //values default to 0
int myPins[] = {2, 4, 8, 3, 6};
int mySensVals[5] = {2, 4, -8, 3, 2};
char message[] = "level"; //enclose in double-quotes
char *messages[] = {"message1", "message2", "message3"}; //* indicates pointers
//assigning value to arrays
//indexing starts at 0
//which corresponds to the first element
myInts[0] = 100;
myInts[5] = 99;
//myInts -> {100, 0, 0, 0, 0, 99}
//retrieving values from arrays
message[4] //-> 'l' (last character)
message[3] //-> 'e'
message[2] //-> 'v'
message[1] //-> 'e'
message[0] //-> 'l' (first character)
//initializing the string object
String myString = "Hello String"; // using a constant String
String myString = String('a'); // converting a constant char into a String
String newString = String("This is a string"); // converting a constant string into a String object
String myString = String(newString + " with more"); // concatenating two strings
String myString = String(13); // using a constant integer
String myString = String(45, HEX); // using an int and a base (hexadecimal)
String myString = String(255, BIN); // using an int and a base (binary)
String myString = String(millis(), DEC); // using a long and a base
String myString = String(5.698, 3); // using a float and the decimal places
//operations on string
myString.indexOf() //returns index of a character in a string
myString.length() //returns length of a string
myString.remove() //remove character at specified location
myString.replace() //replaces parts of a string
myString.toDouble() //convert string to double
myString.toInt() //convert string to integer
myString.toFloat() //convert string to float
myString.toLowerCase() //convert string to lower case
myString.toUpperCase() //convert string to upper case
myString.trim() //remove leading and trailing spaces
Identify which type is the most suitable for following data description.
The use of IF statements is very common in the any field of programming. This allows for actions to happen when a certain condition is met.
float waterLevel = 3.4;
float alarmThreshold = 5;
if (waterLevel >= alarmThreshold){
activateAlarm(); //📢
}
Alternative conditions can also be addressed using the ELSE statement.
float waterLevel = 3.4;
float alarmThreshold = 5;
if (waterLevel >= alarmThreshold){
//activates when the condition is true
activateAlarm(); //📢
} else {
//activates when the above condition is false 🪅
playPartyMusic();
}
Sometimes there could be many alternative conditions.
float waterLevel = 3.4;
float alarmThreshold = 5;
if (waterLevel >= 0.9*alarmThreshold){
activateLoudestAlarm();
} else if (waterLevel >= 0.75*alarmThreshold){
activateModerateAlarm();
} else if (waterLevel >= 0.5*alarmThreshold){
activateSoftAlarm();
} else {
//activates when all of the previous conditions are false 🪅
playPartyMusic();
}
If the IF-ELSE-IF statements only contain checks for equality, the switch case control can be used to provide a more concise syntax.
//if-else-if statement
int fanSpeed = 0;
if (fanSpeed == 0) {
turnOffFan();
} else if (fanSpeed == 1) { //double equals checks for equality
setLowFanSpeed();
} else if (fanSpeed == 2) {
setMediumFanSpeed();
} else if (fanSpeed == 3) {
setHighFanSpeed();
} else {
activateBrokenKnobAlarm(); //oh no 😧
}
//switch case implementation
int fanSpeed = 0;
switch (fanSpeed) {
case 0:
turnOffFan(); break;
case 1:
setLowFanSpeed(); break;
case 2:
setHighFanSpeed(); break;
case 3:
setHighFanSpeed(); break;
default: //optional
activateBrokenKnobAlarm(); break; //oh no 😧
}
FOR loops are control structures that repeats tasks by keeping track of an incrementing variable.
int NUM_REPEAT = 5;
String firstPart = "Went over the loop ";
String message = "";
for (int i = 0; i < NUM_REPEAT; i++) {
message = String(firstPart + String(i+1) + " times");
print(message);
}
The way the loop variable changes can also be changed.
//prints number starting from 1 increasing by 2
//while the number is less than 10
for (int i = 1; i < 10; i += 2) {
print(i); //output -> 1, 3, 5 , 7, 9
}
//prints number starting from 15 decreasing by 1
//while the number is greater than 10
for (int j = 15; j > 10; j--){
print(j); //output -> 15, 14, 13, 12, 11
}
//prints powers of 2
//while the value is less than 100
for (int k = 1; k < 100 ; k = 2*k){
print(k); //output -> 1, 2, 4, 8, 16, 32, 64
}
Sometimes, you want to repeat something while a condition is true. For this, the WHILE loop can be used.
int myNumber = 0;
while (myNumber < 10) {
print(myNumber); //output -> 0, 2, 4, 6, 8
myNumber += 2;
}
continue;
break;
int myNumber = 0;
while (myNumber < 10) {
if (myNumber == 2){
myNumber += 2;
continue; //skip current iteration
}
if (myNumber == 6){
break; //end loop here
}
print(myNumber); //output -> 0, 4, 6
myNumber += 2;
}
A WHILE loop where the condition is evaluated after the action. Ensures that at least one iteration happens.
int myNumber = 0;
do {
print(myNumber); //output -> 0, 2, 4, 6, 8
myNumber += 2;
} while (myNumber < 10);
int i = 0;
i -= 1;
if (i == 0){
doAction1();
} else if (i == 1) {
doAction2();
} else if (i < 2) {
doAction3();
} else {
doAction4();
}
int NUM_REPEAT = 5;
String firstPart = "Went over the loop ";
String message = "";
for (int i = 0; i < NUM_REPEAT; i++) {
message = String(firstPart + String(i+1) + " times.");
print(message);
}
int i = 0;
while (i < 10) {
print(i);
}
continue;
break;
Using boolean algebra there is way to combine simple conditions, such as equality and inequality checks into a single statement. Arduino has three boolean algebra operators:
int numLegs = 2;
String movementMethod = "crawling";
String conclusion = "";
if (numLegs == 2) && (movementMethod == String("crawling")) {
conclusion = String("This could be a human baby. 👶");
}
else if (numLegs == 2) && (movementMethod == String("flying")) {
conclusion = String("This could be a bird. 🦜");
}
else if (numLegs = 3) && (movementMethod == String("flying")){
conclusion = String("I have no idea what this is. 👽");
}
else if (numLegs > 100) && (movementMethod == String("crawling")) {
conclusion = String("This could be a millipede. 🪱");
}
else {
conclusion = String("I need further research. 🔬")
}
bool isRainingHard = false;
bool isHoliday = false;
bool isClasses = true;
if (isRainingHard || isHoliday) {
isClasses = false; ⛈️
}
bool isRaining = false;
bool isHotDay = false;
bool bringUmbrella = true; //initially true
if (!isRaining && !isHotDay) {
bringUmbrella = false;
}
bool isRaining = true;
//first satement
if (isRaining == true) {
//do stuffs here
}
//second satement
if (isRaining) {
//do stuffs here
}
A switch will be toggled from OFF📴 state to an ON🔦 state repeatedly. In each switch cycle, an LED is illuminated moving from left to right.
Before the main thing, let's talk about states.
In digital circuits, there commonly are two logic states which are HIGH and LOW. But is this actually the case? Try the simple code below.
const int inputPin = 12;
bool inputState;
void setup() {
pinMode(inputPin,INPUT);
pinMode(LED_BUILTIN,OUTPUT);
}
void loop() {
//read the state of the input pin
//the input pin will not be connected to anything
inputState = digitalRead(inputPin);
//copy whatever state the input pin is
//into the LED pin
digitalWrite(LED_BUILTIN,inputState);
}
Strange 😲. It seems like the output LED is in the HIGH state. Is it really? Let's look deeper through our Serial tools. Add the following lines.
const int inputPin = 12;
bool inputState;
void setup() {
pinMode(inputPin,INPUT);
pinMode(LED_BUILTIN,OUTPUT);
//initialize serial communication
//specify 9600 as rate of data transfer
Serial.begin(9600);
}
void loop() {
//read the state of the input pin
//the input pin will not be connected to anything
inputState = digitalRead(inputPin);
//print stuff to the serial monitor
Serial.print("inputState:"); //print horizontally
Serial.println(inputState); //print vertically
//copy whatever state the input pin is
//into the LED pin
digitalWrite(LED_BUILTIN,inputState);
}
The input state seems random. This indicates that the input is floating or has no definite state. To fix this, we just need to make the state definite. How so? Add a 1k$\Omega$ resistor between GND and the input pin.
Here's the plan of attack.
int toggleCount = 0;
const int numLEDs = 5;
const int ledPins[] = {2,3,4,5,6};
const int togglePin = 12;
void setup () {
//1. Set the pin modes to output using a loop
}
void loop () {
/*1. Count how many times the switch was toggled.
2. Activate the LED corresponding to the current toggle count.
3. If the count exceeds the number of LEDs, stat over.*/
}
Here's the code implementation.
int toggleCount = 0;
const int numLEDs = 5;
const int ledPins[] = {2,3,4,5,6};
const int togglePin = 12;
bool currentState;
bool prevState;
char messageBuffer[50];
void setup() {
pinMode(togglePin,INPUT);
for (int i = 0; i < numLEDs; i++) {
pinMode(ledPins[i],OUTPUT);
}
Serial.begin(9600);
prevState = digitalRead(togglePin);
}
void loop() {
sprintf(messageBuffer,"toggleCount = %d, prevState = %d, currentState = %d",toggleCount,prevState,currentState);
Serial.println(messageBuffer);
if (toggleCount == 0) {
//turn of last LED
digitalWrite(ledPins[numLEDs - 1],LOW);
}
else if (toggleCount == 1) {
//turn off first LED
digitalWrite(ledPins[0],HIGH);
} else if (toggleCount > 1) {
//turn on LED corresponding to count
//turn off previous LED
digitalWrite(ledPins[toggleCount - 1],HIGH);
digitalWrite(ledPins[toggleCount - 2],LOW);
}
currentState = digitalRead(togglePin);
if (currentState != prevState) {
if (currentState == HIGH) {
toggleCount ++;
}
if (toggleCount > numLEDs){
toggleCount = 0;
}
}
prevState = currentState;
}
It's your turn. Modify the existing code and try to replicate this output. Tip: Use a for-loop to turn on and turn off multiple LEDs.
A potentiometer will create a voltage divider. The micrcontroller detects the voltage ⚡. The higher the voltage, the higher number of LEDs will be illuminated 🚨. An indicator LED increases and decreases in brightness at the same time.
All electronics are analog down to the physics of how things interact. A layer above that is the digital side of things like the logic gates that allows processing of instuctions handed to the micrcontroller. To switch between analog and digital signals, the following are present in a micrcontroller.
The value we would get from the ADC are not the actual units of voltage but the digital representation whose scale varies with the number bits in the ADC. The Arduino Uno and Nano have 10-bit ADCs which means the ADC has: $$2^{10} = 1024$$ values to represent 0 to 5V. Hence the output range of analogRead is 0 to 1023.
The Uno and Nano DACs on the other hand have lower resolution at 8 bits. Hence we could write the voltage from 0V to 5V using: $$2^8 = 256$$ unique symbols. Hence the ouput range is 0 to 255.
Here's the plan of attack.
const int numLEDs = 5;
const int ledPins[] = {2,3,4,5,6};
//should be a DAC pin - one with "~"
const int indicatorLED = 10;
//any of the analog ADC pins
const int knobPin = A0;
int inputReading;
void setup () {
//1. Set the pin modes to output using a loop
}
void loop () {
/*
1. Measure voltage at the potentio meters wiper.
2. Map the voltage to the indices of the LEDs
3. Map the voltage to the brightness of the indicator LED.*/
}
Here's the code implementation.
const int numLEDs = 5;
const int ledPins[] = {2,3,4,5,6};
const int indicatorLED = 10; //must be a DAC pin (look for "~")
const int knobPin = A0; //must be an ADC pin
int inputReading;
int mappedValue;
int indicatorBrightness;
char messageBuffer[80];
void setup() {
pinMode(indicatorLED,OUTPUT);
for (int i = 0; i < numLEDs; i++) {
pinMode(ledPins[i],OUTPUT);
}
Serial.begin(9600);
}
void loop() {
inputReading = analogRead(knobPin);
mappedValue = map(inputReading,0,1023,0,6);
indicatorBrightness = map(inputReading,0,1023,0,255);
//print values
sprintf(messageBuffer,"inputReading:%d; mappedValue:%d; indicatorBrightness:%d;",inputReading,mappedValue,indicatorBrightness);
Serial.println(messageBuffer);
//adjust brightness of indicator LED
analogWrite(indicatorLED,indicatorBrightness);
//turn on leds
for (int i = 0; i <= numLEDs; i++) {
if ( i> 0 && i <= mappedValue) {
digitalWrite(ledPins[i-1],HIGH);
} else {
digitalWrite(ledPins[i],LOW);
}
}
}
It's your turn. Modify a single line in the code so that the number of active LEDs decreases as the potentiometer is turned.
Two boards will communicate via serial 💬🗨️. Each will control 🎮 the other board's servo motor position via an analog input through a potentiometer.
//used for libraries where the compiler
//knows the location of the library file
#include <filename.h>
//used for custom libraries in the working directory
#include "filename.h"
Servo motors are DC motor with a built-in control system for keeping track of its angular position. Show below are the inner workings of a servo motor.
The control of servo motors are carried out via Pulse Width Modulation (PWM). This works the same way as as the analogWrite function albeit with more inctricacies with the timing of pulses.
The Servo library can be imported as show below. This library comes by default with the Arduino IDE.
//import the Servo.h libary
#include <Servo.h>
/*
Any pins can be used to control servo motors but
pins 9 and 10 for Arduino Uno and Nano will not be
//able to use analogWrite even if there are no
servos connected when Servo.h is imported.
*/
The SoftwareSerial libary allows two devices to communicate via the serial protocol. The said libary simulates the hardware serial and thus making way for many serial communication channels.
//import the SoftwareSerial libary
#include <SoftwareSerial.h>
Here's the plan of attack.
#include <SoftwareSerial.h>
#include <SServo.h>>
const int potPin = A5; // Potentiometer pin
const int servoPin = 9; // Servo motor pin
// SoftwareSerial object for communication
// Receive Pin (Rx) - 4
// Transmit Pin (Tx) - 5
SoftwareSerial mySerial(4, 5);
// Servo motor object
Servo servo;
void setup() {
//Initialize serial and servo objects
}
void loop() {
/*
1. Create separate function that reads incoming data
The said function will also move the servo to the
location specified by the other board.
2. Create a separate function that reads the analog
level of the potentiometer. This function would
map the value to the appropriate range and send the
data to the other board.
3. Run each function in succession and display the
data sent and received.
*/
}
Here's the code implementation.
#include <SoftwareSerial.h>
#include <SServo.h>>
// Pins
const int potPin = A5; // Potentiometer pin
const int servoPin = 9; // Servo motor pin
// SoftwareSerial object for communication
SoftwareSerial mySerial(4, 5);
// Servo motor object
Servo servo;
void setup() {
// Initialize serial communication
Serial.begin(9600);
mySerial.begin(9600);
servo.attach(servoPin);
}
//function for receiving data
int readIncoming() {
int receivedValue = mySerial.read();
servo.write(receivedValue);
//this is the ouput of this function
return receivedValue;
}
//function for sending data
int sendData() {
int potValue = analogRead(potPin);
int mappedValue = map(potValue, 0, 1023, 0, 180);
mySerial.write(mappedValue);
//this is the ouput of this function
return mappedValue;
}
void loop() {
if (mySerial.available()) {
int received = readIncoming();
Serial.print("Received:");
Serial.print(received);
Serial.print("; ");
}
int sent = sendData();
Serial.print("Sent:");
Serial.print(sent);
Serial.println(";");
//let the servo catch up
delay(10);
}
The micrcontroller would read an analog value and show the value and its corresponding voltage level in an OLED display 🖥️ through text and a bar graph 📶.
Like the serial communication, I2C is one of the commonly used communication protocol out there. Many devices can communicate over two wires namely SCL (serial clock) and SDA (serial data) lines.
Here's the plan of attack.
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define BAR_THICKNESS 15
//oled display object
Adafruit_SSD1306 myDisplay = Adafruit_SSD1306(128, 64, &Wire);
void setup() {
//Initialize OLED display
}
void loop() {
/*
1. Create a separate function that will
draw a retangle give two opposite corners.
2. Read analog signal and map it to the screen
resolution you want.
3. Change bar graph and text values.
*/
}
Here's the code implementation.
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define BAR_THICKNESS 15
//oled display object
Adafruit_SSD1306 myDisplay = Adafruit_SSD1306(128, 64, &Wire);
void setup() {
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!myDisplay.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
myDisplay.display();
delay(500);
myDisplay.setTextSize(1);
myDisplay.setTextColor(WHITE);
myDisplay.setRotation(0);
myDisplay.clearDisplay();
}
//function for drawing rectangles
void drawRectangle(int x1, int y1, int x2, int y2) {
int max_x = max(x1,x2);
int min_x = min(x1,x2);
int max_y = max(y1,y2);
int min_y = min(y1,y2);
for (int i = min_x; i <= max_x; i++){
for (int j = min_y; j <= max_y; j++) {
myDisplay.drawPixel(i,j,WHITE);
}
}
}
void loop() {
int potValue = analogRead(A0);
int barLength = map(potValue,0,1023,0,SCREEN_WIDTH-1);
float voltageValue = (potValue/1023.0)*5;
myDisplay.clearDisplay();
//draw bar graph
drawRectangle(0,0,barLength,BAR_THICKNESS-1);
//show analog pin reading
myDisplay.setCursor(0,20);
myDisplay.println("Reading:");
myDisplay.setCursor(0,50);
myDisplay.setTextSize(2);
myDisplay.println(potValue);
myDisplay.setTextSize(1);
//show corresponding voltage
char messageBuffer[4];
dtostrf(voltageValue,1,2,messageBuffer);
myDisplay.setCursor(70,20);
myDisplay.println("Voltage:");
myDisplay.setCursor(70,50);
myDisplay.setTextSize(2);
myDisplay.print(messageBuffer);
myDisplay.println(" V");
myDisplay.setTextSize(1);
//update screen
myDisplay.display();
}
for (int i = min_x; i <= max_x; i++){
for (int j = min_y; j <= max_y; j++) {
myDisplay.drawPixel(i,j,WHITE);
}
}