Bluetooth-Controlled Mini Lift Using Stepper Motor with Android Mobile App
01 Overview
This Mini Lift project is achieved with the use of a stepper motor. The motor will rotate either clockwise or counterclockwise depending on whether you want it to go up or down, controlled by a mobile app over Bluetooth. Additionally, a TFT LCD display shows the user which floor the lift is currently on, as well as when it is moving.
Project Use Case
This project is a good introduction to combining precise stepper-motor control, wireless (Bluetooth) input from a custom mobile app, and a visual status display — concepts that extend to small-scale automation, hoists, and other position-controlled mechanisms.
02 Hardware and Software Components
Gather everything below before you start.
Hardware Components
| Component | Description |
|---|---|
| Arduino Due | Main microcontroller. Runs at 3.3V logic, which the TFT display requires. |
| Stepper Motor NEMA 17 (17HS4401) | Drives the lift mechanism up and down. |
| 5mm x 8mm Motor Shaft Coupling | Connects the stepper motor shaft to the lift's drive mechanism. |
| Stepper Motor Driver DRV8825 | Drives the NEMA 17 stepper motor with adjustable current limiting and microstepping. |
| HC-05 Bluetooth Module | Receives floor commands wirelessly from the Android app. |
| 2.2" TFT LCD Display ILI9341 (240x320) | Shows the current floor and lift movement status. |
| 47Ω Resistor | Placed in series with the TFT display's LED backlight pin. |
| 100μF Capacitor | Placed in series on the stepper driver's motor supply line. |
| Breadboard | Platform for building and testing the circuit. |
| 9V Power Supply | Powers the stepper motor driver. |
| Jumper Wires | Connects all modules to the Arduino and breadboard. |
| Rubber Belt / String | Transfers the stepper motor's rotation into vertical lift movement. |
| Container / Platform | The lift platform/car itself. |
Software Tools
| Software | Description |
|---|---|
| Arduino IDE | Used to write, compile, and upload the sketch to the Arduino Due. |
| MIT App Inventor | Cloud-based, block-coding tool used to build the Android control app. |
| App Inventor Companion App | Lets you preview the app live on your Android device while building it. |
Libraries Used
| Library | Purpose |
|---|---|
| SPI (Serial Peripheral Interface) | Required by the Adafruit_ILI9341 library to communicate with the TFT display. |
| Adafruit_ILI9341 | Driver library for the ILI9341-based TFT LCD display. |
03 Application Discussion
Stepper Motor NEMA 17
A stepper motor, also known as a step motor or stepping motor, is a type of brushless DC electric motor. Very precise positioning and speed control can be achieved with computer-controlled stepping, which is why stepper motors are the motor of choice for many precision motion control applications.
How Does It Work?
Stepper motors divide a full rotation into a number of equal steps. They have multiple coils organized into groups called phases. By using electromagnets to energize each phase in sequence, the motor rotates one step at a time.
Specifications
| Spec | Value |
|---|---|
| Phase | 2 |
| Step Angle | 1.8° |
| Step Angle Accuracy | ± 5% (full step, no load) |
| Rated Current | 1.3 A |
| Phase Resistance | 2.4 Ω |
| Resistance Accuracy | ± 10% |
| Phase Inductance | 2.8 mH |
| Inductance Accuracy | ± 20% |
| Holding Torque | 28 N·cm |
| Detent Torque | 1.6 N·cm |
| Rotor Inertia | 34 g·cm² |
Stepper Motor Driver DRV8825
The DRV8825 stepper motor driver features adjustable current limiting, over-current protection, over-temperature protection, and six microstep resolutions (down to 1/32-step). It operates from 8.2V to 45V and can deliver up to approximately 1.5A per phase without a heat sink or forced air flow (rated for up to 2.2A per coil with sufficient additional cooling).
How Does It Work?
The stepper motor driver is the circuit that controls how the stepper motor operates. It works by sending current through the motor's various phases in pulses. The DRV8825, a microstepping driver, allows higher resolutions by enabling intermediate step locations, achieved by energizing the coils with intermediate current levels.
Specifications
| Spec | Value |
|---|---|
| Operating Voltage | 8.2–45 V |
| Continuous Current per Phase | 1.5 A |
| Maximum Current per Phase | 2.2 A |
| Logic Voltage | 2.5–5.25 V |
| Microstep Resolutions | Full, 1/2, 1/4, 1/8, 1/16, 1/32 |
HC-05 Bluetooth Module
The HC-05 Bluetooth Module is an easy-to-use Bluetooth SPP (Serial Port Protocol) module, designed for transparent wireless serial connection setup. It's one of the most widely used wireless communication protocols in embedded projects due to its low cost and wide range of applications.
How Does It Work?
The serial port Bluetooth module is a fully qualified Bluetooth V2.0+EDR (Enhanced Data Rate) 3Mbps modulation module with a complete 2.4GHz radio transceiver and baseband. It uses a CSR BlueCore 04-External single-chip Bluetooth system with CMOS technology and Adaptive Frequency Hopping. In simple terms, the Bluetooth module manages the wireless communication channel — it can transmit and receive data wirelessly, communicating via serial communication, which makes it easy to interface with a microcontroller.
Specifications
| Spec | Value |
|---|---|
| Input Voltage | 5 V |
| Communication Method | Serial |
| Frequency | 2.4 GHz |
| Default PIN/Password | 1234 |
| Mode | Master and slave mode can be switched |
2.2" TFT LCD Display ILI9341 (240x320)
TFT LCD is short for thin-film-transistor liquid-crystal display. It's a variant of LCD that uses thin-film transistor technology to improve image qualities such as addressability and contrast. These are commonly used in appliances including computer monitors, mobile phones, handheld devices, video game systems, navigation systems, projectors, and televisions.
How Does It Work?
All the pixels on a TFT LCD screen are arranged in a row-and-column format, and each pixel is attached to a very tiny silicon transistor that rests directly on the glass panel. This setup allows each pixel to be given a charge, and for that charge to be maintained even when the screen refreshes to produce a new image.
04 Hardware Setup
2.2" TFT LCD Display Connections
| Display Pin | Connect To |
|---|---|
| SDO / MISO | Arduino Due SPI MISO pin (SPI header pin 1) |
| LED | Arduino Due 5V, with a 47Ω resistor in series |
| SCK | Arduino Due SPI SCK pin |
| SDI / MOSI | Arduino Due SPI MOSI pin (SPI header pin 4) |
| DC / RS | Arduino Due pin 26 |
| RESET | Arduino Due pin 22 |
| CS | Arduino Due pin 24 |
| Vcc / Gnd | Arduino Due 5V / Ground respectively |
HC-05 Bluetooth Module Connections
| Module Pin | Connect To |
|---|---|
| RXD | Arduino Due pin TX2 (pin 16) |
| TXD | Arduino Due pin D4 |
| Vcc / Gnd | Arduino Due 5V / Ground respectively |
Stepper Motor Driver DRV8825 Connections
| Driver Pin | Connect To |
|---|---|
| DIR | Arduino Due pin 2 |
| STEP | Arduino Due pin 3 |
| RESET / SLEEP | Arduino Due 5V |
| GND (logic side) | Arduino Due Ground |
| VMT / GND (motor supply side) | 9V power supply and its Ground respectively, with a 100μF capacitor in series |
Stepper Motor NEMA 17 Connections
One way to set the current limit is to adjust and measure the voltage on the driver's REF pin to calculate the resulting current limit. The REF pin is the small circle on the circuit board and can be adjusted with a small screwdriver. The current limit is twice the reference voltage, so to set a 1.3A current limit, set the reference voltage to 0.65V.
| Motor Wire | Connect To |
|---|---|
| 1st wire (usually red) | Stepper Motor Driver pin 1B |
| 2nd wire (usually black) | Stepper Motor Driver pin 2B |
| 3rd wire (usually blue) | Stepper Motor Driver pin 1A |
| 4th wire (usually green) | Stepper Motor Driver pin 2A |
05 Android App Setup
For the Android mobile app, this project uses the cloud-based MIT App Inventor. In just a few steps, you can set it up and start building apps.
Step 1 — Install the Companion App
Install the App Inventor Companion App on your Android device so you can preview your app live as you build it. If you don't have an Android device, you'll need to install software on your computer to use the on-screen Android emulator instead. Learn more about the initial setup on the official MIT App Inventor setup guide (https://appinventor.mit.edu/explore/ai2/setup-device-wifi.html).
Step 2 — Create a New Project
Click on the Project tab, start a new project, and give it a proper name. In the main interface, you'll see a component palette on the left, a viewer in the middle, and components/properties tabs on the right.
Step 3 — Add and Arrange Components
Drag and drop the required components from the palette onto the viewer screen in order. To make components easier to identify, rename them using the rename button below the components list. Grouping components into a consistent arrangement makes it easier to show or hide a group of components at a time later on.
Step 4 — Customize the Interface
Customize the labels and buttons to your liking — for example, custom button images plus a grayscale version to use when a button is disabled, and a background image on Screen1. Then uncheck the visibility of the arrangement containing the floor controls so the initial screen looks like a "not yet connected" state.
Step 5 — Test on Your Device
To see how the app looks on your phone, connect it to the App Inventor Companion by clicking the Connect button on the top menu and selecting "AI Companion".
.aia project file and a ready-to-install .apk for this app are provided alongside the Arduino code in the Code section.
06 Code
Read the Code Breakdown section to understand what each part does.
Mini_Lift.ino
#include "SPI.h"
#include "Adafruit_ILI9341.h"
#define dirPin 2
#define stepPin 3
#define stepsPerRevolution 200
#define TFT_DC 26
#define TFT_CS 24
#define TFT_RST 22
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
int input; //a string to hold incoming data
int lastInput;
int floorDiff;
int compensation;
bool stringComplete = false;
void setup() {
Serial2.begin(9600);
tft.begin();
displayFloor();
//Set pins as output
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
Serial.println("Mini Lift program has started.");
}
void moveUp() {
digitalWrite(dirPin, HIGH);
for (int i = 0; i < (1.8*stepsPerRevolution*floorDiff)+compensation ; i++) {
digitalWrite(stepPin, HIGH); //These four lines result in 1 step
delayMicroseconds(1000);
digitalWrite(stepPin, LOW);
delayMicroseconds(1000);
}
}
void moveDown() {
digitalWrite(dirPin, LOW);
for (int i = 0; i < (1.8*stepsPerRevolution*floorDiff)-compensation; i++) {
digitalWrite(stepPin, HIGH); //These four lines result in 1 step
delayMicroseconds(1000);
digitalWrite(stepPin, LOW);
delayMicroseconds(1000);
}
}
unsigned long displayFloor() {
tft.fillScreen(ILI9341_BLACK);
tft.setRotation(1);
tft.setCursor(0, 70);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
tft.setTextColor(ILI9341_WHITE); tft.setTextSize(5);
tft.println("Mini Lift");
tft.setTextColor(ILI9341_MAGENTA); tft.setTextSize(3);
tft.print("Current Floor:");
tft.setTextColor(ILI9341_MAGENTA); tft.setTextSize(3);
tft.println(input);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
}
unsigned long displayMoving() {
tft.fillScreen(ILI9341_BLACK);
tft.setRotation(1);
tft.setCursor(0, 80);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
tft.setTextColor(ILI9341_CYAN); tft.setTextSize(3);
tft.print("Lift is moving ");
if (lastInput < input) {
tft.println((char)24);
}
else if (lastInput > input) {
tft.println((char)25);
}
tft.print("from floor ");
tft.print(lastInput);
tft.print(" - ");
tft.println(input);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
}
void serialEvent() {
while (Serial2.available()) {
input = Serial2.read();
displayMoving();
stringComplete = true;
}
}
void loop() {
serialEvent();
if (stringComplete) {
Serial2.print(lastInput);
if (lastInput < input) {
floorDiff = input - lastInput;
moveUp();
}
else if (lastInput > input) {
floorDiff = lastInput - input;
moveDown();
}
displayFloor();
}
delay(100);
lastInput = input;
stringComplete = false;
}
MIT App Inventor Project
The Android app is built entirely with MIT App Inventor's block-based editor (no text code to copy) — see the Code Breakdown section below for how the connection and floor-button blocks work.
07 Code Breakdown
Arduino Code
Pre-Initialization
#include "SPI.h"
#include "Adafruit_ILI9341.h"
These are the libraries needed for the project. The Adafruit_ILI9341 library controls the TFT LCD display, and since it depends on the Serial Peripheral Interface, the SPI library is also included.
#define dirPin 2
#define stepPin 3
#define stepsPerRevolution 200
#define TFT_DC 26
#define TFT_CS 24
#define TFT_RST 22
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
This defines the direction and step pins for the stepper motor, as well as steps per revolution. Typical stepper motors move 1.8° per step, which works out to 200 steps per revolution. It also defines the DC, CS, and reset pins for the TFT LCD display and creates the TFT object with those pins assigned.
int input; //a string to hold incoming data
int lastInput;
int floorDiff;
int compensation;
bool stringComplete = false;
This initializes the variables used later. input stores the incoming data, and lastInput stores the previous input.
setup()
Serial2.begin(9600);
tft.begin();
displayFloor();
//Set pins as output
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
This sets the Serial2 baud rate to 9600 bps, the default for serial communication. The Arduino Due has 4 serial ports, and Serial2 is on pins 16 and 17. It also initializes the TFT display and immediately calls displayFloor(), then sets the stepper motor pins as outputs.
Stepper Motor Control Functions
void moveUp() {
digitalWrite(dirPin, HIGH);
for (int i = 0; i < (1.8*stepsPerRevolution*floorDiff)+compensation ; i++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(1000);
digitalWrite(stepPin, LOW);
delayMicroseconds(1000);
}
}
void moveDown() {
digitalWrite(dirPin, LOW);
for (int i = 0; i < (1.8*stepsPerRevolution*floorDiff)-compensation; i++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(1000);
digitalWrite(stepPin, LOW);
delayMicroseconds(1000);
}
}
moveUp() and moveDown() are nearly identical, differing only in the direction pin — HIGH rotates the motor clockwise, LOW rotates it counter-clockwise. Each is a for-loop that steps the motor a set number of times, with delayMicroseconds() controlling speed. Steps per revolution is multiplied by floorDiff to determine how many rotations are needed, then adjusted by a compensation value. This compensation matters when using a one-sided rolling belt: as the belt rolls up, it accumulates radius and needs fewer rotations to reach the same height.
TFT Display Functions
unsigned long displayFloor() {
tft.fillScreen(ILI9341_BLACK);
tft.setRotation(1);
tft.setCursor(0, 70);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
tft.setTextColor(ILI9341_WHITE); tft.setTextSize(5);
tft.println("Mini Lift");
tft.setTextColor(ILI9341_MAGENTA); tft.setTextSize(3);
tft.print("Current Floor:");
tft.setTextColor(ILI9341_MAGENTA); tft.setTextSize(3);
tft.println(input);
tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
tft.println("--===---===----===---===--");
}
displayFloor() prints text on the TFT display. First the screen is filled black and rotated 90° for landscape view (the 1 in setRotation() means 90°, and can be set from 0–3). The cursor is then positioned so the text prints roughly centered on the screen — cursor coordinates of 0,0 would print at the top instead. Printing works like printing to the Serial Monitor, with the added ability to set text color and size.
displayMoving() is called while the stepper motor is moving, and shows which floor the lift is moving from and to. (char)24 and (char)25 render as up and down arrows, respectively.
serialEvent()
void serialEvent() {
while (Serial2.available()) {
input = Serial2.read();
displayMoving();
stringComplete = true;
}
}
This checks for incoming data on the Bluetooth serial connection. While data is available, it's stored in input, and the stringComplete flag is set to true to indicate the data was received successfully.
loop()
serialEvent();
if (stringComplete) {
Serial2.print(lastInput);
if (lastInput < input) {
floorDiff = input - lastInput;
moveUp();
}
else if (lastInput > input) {
floorDiff = lastInput - input;
moveDown();
}
displayFloor();
}
delay(100);
lastInput = input;
stringComplete = false;
The loop calls serialEvent() to check for new data. If data was received, it sends the last known floor back over Serial2, then compares lastInput and input to decide whether to call moveUp() or moveDown(), calculating floorDiff in the process. After moving, it calls displayFloor() to update the screen. The short delay gives communication time to process, and finally lastInput is updated and stringComplete is reset to false.
MIT App Inventor Code
Connection Buttons
Since MIT App Inventor uses code blocks, the logic is fairly readable directly from the blocks editor. When the Bluetooth list-picker button is clicked, its "BeforePicking" event populates the list with nearby Bluetooth devices the user can connect to. Its "AfterPicking" event runs after a device is chosen — if the connection to the HC-05 module succeeds, it hides the "not connected" arrangement and shows the arrangement containing the floor controls, then checks the lift's last known floor so the app resumes from where it left off (e.g., disabling the button for the floor the lift is already on).
The Disconnect button does the opposite — it disconnects from the Bluetooth device and restores the "not connected" arrangement.
Floor Buttons
Each floor button, when clicked, checks that the app is still connected to the Bluetooth device, then sends that floor's number over Bluetooth for the HC-05 module to receive. This value is stored in the input variable in the Arduino code, which — combined with the calculations described above — tells the stepper motor how far to rotate. The rest of the block logic disables the pressed button (switching its image to a grayscale version) and re-enables any previously disabled buttons.
08 Conclusion
Stepper motors are really strong. A motor's position can be moved and held at any of its steps as long as it's carefully sized to the application in terms of torque and speed. In this build, it was able to lift a small platform of office supplies, and can lift heavier loads still, according to experiments conducted by other sources. Stepper motors like this one are used in a wide variety of applications, including industrial machines, medical scanners, blood analysis machinery, 3D printing equipment, and more.
09 References
-
DRV8825 Stepper Motor Driver Carrier, High Current — Pololu
(https://www.pololu.com/product/2133) -
All About Stepper Motors: What is a Stepper Motor? — Adafruit Learning System
(https://learn.adafruit.com/all-about-stepper-motors/what-is-a-stepper-motor) -
Arduino TFT Display and Font Library — Instructables
(https://www.instructables.com/id/Arduino-TFT-display-and-font-library/)
10 Project Authors
- Eddyson Canoy
- Lukie-Mar Lonzaga
- Miles Darren Bagnol
- Elijah Joseph Orias
