Arduino for Pi users
Get used to the Arduino series of boards, with the help of Les Pounder and the Raspberry Pi, to build better projects.
Get used to the Arduino series of boards with the help of Les Pounder and the Raspberry Pi, and build better projects.
The Arduino is a powerful board. It may not have the same processing power as a Raspberry Pi – in fact it cannot even match the original Raspberry Pi – but it has power in its simplicity and connectivity. It has protocols such as I2C, SPI, UART and serial, in addition to digital IO (on/off) and analogue IO.
But why is this important? With an Arduino we can write the code on our computer, in this case a Raspberry Pi, and then flash the code onto the Arduino where it is stored. We can remove the Arduino from the Pi, and as long as we can supply power to the project, it can run quite happily with no screen. With the newer batch of Arduinos we can connect to the internet and networks, send data over said networks and even send data back to the Arduino.
The power of the Arduino is that it can be independent and gather data for you in the field. Sure, the Raspberry Pi can also do this, but why use an entire Linux computer just to gather data when you can run a tiny, power-efficient microcontroller?
In this project we are going to create a simple motorcontrol project that will control the speed of a motor using a rotary encoder, and the status of the motor will be displayed to the user via an LCD screen. As we move through the project, we will also compare the Arduino programming language to Python, the typical language used on the Raspberry Pi.
Hardware setup
The Arduino should be connected to a free USB port on the Raspberry Pi via a known good-quality data cable (not all USB cables carry data lines). Using four male-to-female jumper wires, connect the I2C LCD screen to the Arduino’s I2C pins (5V, GND, SCL and SDA). Then connect the rotary encoder to the Arduino via a breadboard and three male-to-male jumper wires. The centre pin of the three connects to GND. We then need to connect pin 9, our motor pin, to IN1 on the L298N motor controller, and then connect GND from the Arduino to GND on the L298N and to IN2. This will force the motor to spin in one direction only.
Connect a 5V power supply to the L298N, and ensure that the GND connection of the power supply is connected to the GND of the L298N, and the Arduino. This creates a “Common Ground” used as reference for all voltages.
Double-check all of the connections via the circuit diagram – a high-resolution version is available in the download for this project.
Software installation
We start the software aspect of the project by first visiting the Arduino website at www.arduino.cc and clicking on Software > Downloads. For all models of the Raspberry Pi, even the Pi 4, we need to download the 32-bit ARM Linux version of the Arduino IDE. Once downloaded, extract the contents of the file to your
home directory (/home/pi/) and then, using the terminal, navigate to the Arduino-1.8.12/ directory – the version number may change over time so please look for a similar directory name. In this directory we need to run a command to install the Arduino IDE to the Pi. In the terminal type the following command:
$ sudo ./install.sh
Once installed we still need to issue one more command to add our user, pi, to a group called “dialout”, as this will enable us to upload (flash) code to the Arduino via USB. In the terminal type the following:
$ sudo usermod -a -G dialout pi
Once complete, reboot the Pi, and we are ready to open the Arduino IDE.
In the Arduino IDE we need to install a library of code that will enable our Arduino to communicate with the LCD screen over I2C. Go to Tools > Manage Libraries and open the Library Manager. It will take a few seconds to open, even on the powerful Pi 4. Once it has loaded, type in Liquidcrystal i2c in the search box, and the filter should reveal a series of libraries. We are looking for the Liquidcrystal I2C library by Frank de Brabander. Once you’ve located it, click install and the library will be downloaded and set-up, ready for use. Once done, close the Library Manager.
Project code
This project uses a rotary encoder – a dial with incremental steps that we can use to detect and identify the direction of rotation to control the speed of a motor. The speed of the motor is expressed as a percentage, in 20% steps, and the speed data is output to an LCD screen and the serial console.
To start the code, we need to include the libraries that are necessary for working with the I2C LCD screen. If you are familiar with Python you’ll see that this is similar to importing a library. The library has to be installed on the Raspberry Pi in order for us to use it. The address in brackets (0x27,20,4) is a typical I2C address for the LCD screen when used with a PCF8574 Backpack. But if in doubt there is a free I2C scanner.
Connect your screen to the Arduino, load this script,
https://playground.arduino.cc/main/i2cscanner and flash it to the Arduino. In the Serial Monitor it will show all of the I2C devices present.
#include
The pins used to connect our electronics to the Arduino need to be identified in our code, so we use three constant values to name and identify each pin. Creating variables in the Arduino programming language is a little different to in a Raspberry Pi, but nothing too difficult:
#define A 6
#define B 7
#define motor 9
Three more variables are created to store a counter value, used to identify where we are when scrolling around the various motor-power settings. The other two variables store the current and previous state of the rotary encoder. Each of these variables is storing an integer (numerical) value, and we need to tell the code this as we create the variable, unlike in Python where we can just create the variable by storing any data type inside of it:
int counter = 0;
int astate;
int alaststate;
Inside the setup function we now initialise the LCD screen ready for use, then set the two rotary encoder connections (A and B) as inputs before we set our motor pin to be an output. The setup function is similar to defining all of the pins, functions and variables in our code before we start any loops. These are tasks that need to be done only once before we move into the main code.
void setup()
{ lcd.init();
pinmode (A,INPUT);
pinmode (B,INPUT); pinmode(motor, OUTPUT);
The two input pins, A and B, are connected to the rotary encoder, which will detect movement based on connecting pin A or B to the centre pin, connected to GND. When A or B is connected to GND, this will trigger the A/B pin to pull low, effectively dropping the voltage of that pin from 5V to 0V. But we need to set these pins up to start up high, with 5V flowing to the pins. To do this we use digitalwrite to set each of the pins HIGH.
digitalwrite(a, HIGH);
digitalwrite(b, HIGH);
When the Arduino powers up, it would help the user if we gave them a simple instruction. Using the LCD screen, we tell the user to rotate the rotary encoder. First we turn on the backlight so that we can see the text. Then we set the cursor to the top left of the screen and print the first part of the message. We then set the cursor to the second line, and finish the message.
lcd.backlight();
lcd.setcursor(1,0);
lcd.print(“turn dial to “);
lcd.setcursor(1,1);
lcd.print(“start motor”);
To start a serial console, we begin the serial connection with a speed of 9600 baud, fast enough for this low-speed project. Then we read the state of pin A and store the value in the variable alaststate, which will be used for comparison later.
Serial.begin (9600);
alaststate = digitalread(a);
}
We are now finished with the setup function and can move on to the main body of code, which is a loop(),set to repeat as long as the Arduino is powered. Inside the loop we read the current state of pin A and store that to the astate variable. The loop() function is essentially the same as a while True loop in Python. The contents just constantly run when the Arduino is powered up.
void loop() {
astate = digitalread(a);
To check if the rotary encoder has been used, the two variables, astate and alaststate, are compared using a comparison operator, in this case !=, which means that the first variable is not equal to the second. If that is the case then a nested conditional test checks pin B against the current state of A, and if B is different to A then the rotary encoder is being rotated clockwise.
if (astate != alaststate){
if (digitalread(b) != astate) {
What happens if the rotary encoder is turned clockwise? The value stored in the variable counter is incremented by 1. But if the value stored in the counter variable goes above 12, then the counter is reset back to zero.
if (digitalread(b) != astate) {
counter ++;
if (counter > 12){
counter = 0;
}
If the value of the counter variable is less than -1 then the variable is reset to zero.
} else if (counter < -1){
counter = 0;
}
}
The value store in the counter is printed to the serial monitor in order for us to debug any issues.
Serial.print(“value: “);
Serial.println(counter);
How do we detect and react to the counter value? Using another series of if, else if conditional tests we check the value stored in the counter variable, and if the value matches then a section of code is triggered. In the first example if the value of the counter variable is 2, then two lines of text, the Motor On, and percentage of power, are printed to the serial monitor.
if (counter == 2) {
Serial.println(“motor On”);
Serial.println(“20%”);
Then the same data is sent to the LCD screen, with the Motor On text appearing on the top line, and the percentage on the bottom.
lcd.setcursor(1,0);
lcd.print(“motor On”);
lcd.setcursor(1,1);
lcd.print(“20%”);
To finish this conditional test, we use the
analogwrite function to control the motor pin, and set it to 20% power output. The analogwrite function uses pulse width modulation (PWM) to send values between 0 and 255. So 20% of 255 is approximately 51, and this is the value that we pass in order to turn the motor on, albeit slowly.
analogwrite(motor, 51);
}
To test the other conditions, for the values 4, 6, 8 and 10 to be stored in the counter variable we use else if to check one by one. We set the motor speed for 4 to 40%, which is 102.
else if (counter == 4) {
Serial.println(“motor On”);
Serial.println(“40%”);
lcd.setcursor(1,0);
lcd.print(“motor On”);
lcd.setcursor(1,1);
lcd.print(“40%”);
analogwrite(motor, 102);
}
For counter value 6, 60% power is 153.
else if (counter == 6) {
Serial.println(“motor On”);
Serial.println(“60%”);
lcd.setcursor(1,0);
lcd.print(“motor On”);
lcd.setcursor(1,1);
lcd.print(“60%”); analogwrite(motor, 153);
}
For counter value 8 we set the speed to 204.
else if (counter == 8) {
Serial.println(“motor On”);
Serial.println(“80%”);
lcd.setcursor(1,0);
lcd.print(“motor On”);
lcd.setcursor(1,1);
lcd.print(“80%”);
analogwrite(motor, 204);
}
Lastly if the value of the counter variable is 10, we set the power of the motor to 100%, which is 255.
else if (counter == 10) {
Serial.println(“motor On”);
Serial.println(“100%”);
lcd.setcursor(1,0);
lcd.print(“motor On”);
lcd.setcursor(1,1);
lcd.print(“100%”);
analogwrite(motor, 255);
}
The last part of this long series of conditional tests is a simple else condition. This is a catch-all condition that requires no logic for comparison. If no other comparisons are made, the else condition must be true. If this happens the motor is turned off using a value of 0, and the LCD screen is cleared.
else {
analogwrite(motor, 0);
lcd.clear();
}
}
The very last line of code updates the value stored in the alaststate variable so that it matches the value stored in astate. So now the last state and current state of the A pin match.
alaststate = astate; // Updates the previous state of
the outputa with the current state
}
Save the code to your Raspberry Pi with your Arduino connected go to the Tools menu, and ensure that the correct board, and port are selected for the Arduino. When ready click on Sketch > Upload to send the code to the Arduino. In a few moments the board will reset and the LCD screen will show the instructions for use. We can also open the serial monitor, found in the Tools > Serial Monitor menu. In the serial monitor we can see the exact state of the rotary encoder and the power setting for the motor.
Our project is complete, and we can now manually control the speed of a motor using the rotary encoder. Adding extra motors is as simple as defining another pin for the motor, and then adding a few lines to control that pin at the same time as the original motor pin. We have accomplished so much with very basic Arduino code, thanks to a great community of libraries and support.