# How to Build a Delivery Robot Car from Scratch Using Raspberry Pi 4B
## Overview
This document explains how to create a delivery robot car using a Raspberry Pi 4B. The project integrates motor control, ultrasonic sensors for collision avoidance, and a weight sensor for object detection. By following this guide, you will learn how to configure hardware and software to build a functional robot capable of movement and obstalcle detection. The robot car is controlled via a Flask-based web application, providing a simple interface for testing and operations.
## Required Components
### Software
- **Raspberry Pi OS (Raspbian Buster 10)**
- **Python 3.x**
- Libraries:
- Flask
- RPi.GPIO
- statistics
- hx711 (for weight sensor integration)
- battery monitoring software (for displaying remaining power)
### Hardware
- **Raspberry Pi 4B**
- **UPS HAT** (for uninterrupted power supply)
- **US-100 Ultrasonic Sensor** (for distance measurement)
- **HX711 Weight Sensor Module** (for weight detection)
- **DC Motors** (2 sets for left and right wheels)
- **L298N Motor Driver Module** (to control motor directions)
- **Four-wheel drive chassis kit**
- **12V battery holders** ×2
- **AA batteries** ×8
- **Breadboard and Dupont Line**
## Pre-installation
1. **Setup Raspberry Pi**:
- You can refer to this website for detailed instructions on setting up a Raspberry Pi.
https://www.raspberrypi.com/documentation/computers/getting-started.html
> **Note**: I am using the 64-bit Buster operating system.
- Enable **SSH** by navigating to the Raspberry Pi Configuration menu under the Interfaces tab and selecting **"Enabled"** for SSH.

> SSH is required to remotely manage and control your Raspberry Pi without needing a physical display and keyboard.
- Enable **I2C** for the UPS HAT by navigating to the Raspberry Pi Configuration menu under Interfaces and selecting **"Enabled"** for I2C.

>I2C is required to communicate with the UPS HAT for monitoring battery status and remaining charge.
2. **Install Required Libraries**:
- Update the package list and install **Python**:
```bash
sudo apt update
sudo apt install python3 python3-pip
```
- Install **Flask** and other dependencies:
```bash
sudo apt install python3-flask
```
If you want to build a website from scratch by yourself, you can refer to this website:
https://projects.raspberrypi.org/en/projects/python-web-server-with-flask/0
- Clone or install the **hx711** Python library for weight sensing.
```python
git clone https://github.com/tatobari/hx711py
```
- Install **battery monitoring software** for displaying remaining power.
- Install the 7-Zip utility:
```python
sudo apt-get install p7zip
```
- Download the battery software:
```python
wget http://www.waveshare.net/w/upload/d/d9/UPS_HAT.7z
```
- Extract the software package:
```python
7z x UPS_HAT.7z -r -o./
```
This extracts all the contents of the `UPS_HAT.7z` archive into the current directory, preserving the folder structure.
## Prepare the Hardware
- Install the UPS HAT and ensure it is securely connected to the Raspberry Pi.
- Attach motors, ultrasonic sensor, weight sensor, and the L298N motor driver modules to the Raspberry Pi GPIO pins.
## Implement
### Motor Control
The robot's movement is controlled by two DC motors, one for each end of the robot.
DC motors work by converting electrical energy into mechanical energy using electromagnetic principles, by setting GPIO pins high or low. Since Raspberry Pi does not have a built-in motor driver module, we utilize L298N motor driver module to control the motors.
> For more details on DC motor operation, refer to this guide: https://projects.raspberrypi.org/en/projects/physical-computing/14
#### Assemble the Car
To learn how to assemble your four-wheel drive car, you can refer to this video:
{%youtube Ze4c_a3luqg%}
#### Hardware Connections

1. GPIO Pins:
- **GND pin**: Connected to the **GND** pin of the L298N to ensure a common ground.
- **GPIO.BOARD 11**: Connected to the L298N's **IN1** input.
- **GPIO.BOARD 12**: Connected to the L298N's **IN2** input.
- **GPIO.BOARD 15**: Connected to the L298N's **IN3** input.
- **GPIO.BOARD 16**: Connected to the L298N's **IN4** input.
2. L298N Motor Driver:
- Outputs:
- **OUT1** and **OUT2**: Connected to the terminals of the left DC motor.
- **OUT3** and **OUT4**: Connected to the terminals of the right DC motor.
3. Battery Holder:
- **Positive** terminal: Connected to the **12V input** of the L298N motor driver.
- **Negative** terminal: Connected to the **GND** of the L298N motor driver and Raspberry Pi for a shared ground.
- Assign GPIO pins for each motor.
#### Code Implementation
1. **GPIO Pin Setup**:
```python
GPIO.setmode(GPIO.BOARD)
```
- The **GPIO.BOARD** mode is used in this project, which refers to the physical pin numbering on the Raspberry Pi.
> For a comparison between BOARD and BCM modes, refer to: [Pinout.xyz.](https://pinout.xyz/pinout/)
```python
IN1, IN2, IN3, IN4 = 11, 12, 15, 16 # Front wheels
IN1_2, IN2_2, IN3_2, IN4_2 = 29, 31, 36, 37 # Rear wheels
```
> **Note**: The GPIO pin configuration does not have to match the one shown here.
```python
GPIO.setup([IN1, IN2, IN3, IN4, IN1_2, IN2_2, IN3_2, IN4_2], GPIO.OUT)
```
2. **Motion Control Functions**:
- **Forward**:
```python
def forward():
GPIO.output(IN1, False)
GPIO.output(IN2, True)
GPIO.output(IN3, False)
GPIO.output(IN4, True)
GPIO.output(IN1_2, False)
GPIO.output(IN2_2, True)
GPIO.output(IN3_2, False)
GPIO.output(IN4_2, True)
```
Both motors rotate forward, making the robot move straight.
- **Backward**:
```python
def backward():
GPIO.output(IN1, True)
GPIO.output(IN2, False)
GPIO.output(IN3, True)
GPIO.output(IN4, False)
GPIO.output(IN1_2, True)
GPIO.output(IN2_2, False)
GPIO.output(IN3_2, True)
GPIO.output(IN4_2, False)
```
Both motors rotate backward, making the robot reverse.
- **Left Turn**:
```python
def turn_left():
GPIO.output(IN1, False)
GPIO.output(IN2, True)
GPIO.output(IN3, True)
GPIO.output(IN4, False)
GPIO.output(IN1_2, False)
GPIO.output(IN2_2, True)
GPIO.output(IN3_2, True)
GPIO.output(IN4_2, False)
```
Right motor rotates forward while the left motor reverses, causing a left turn.
- **Right Turn**:
```python
def turn_right():
GPIO.output(IN1, True)
GPIO.output(IN2, False)
GPIO.output(IN3, False)
GPIO.output(IN4, True)
GPIO.output(IN1_2, True)
GPIO.output(IN2_2, False)
GPIO.output(IN3_2, False)
GPIO.output(IN4_2, True)
```
Left motor rotates forward while the right motor reverses, causing a right turn.
- **Stop**:
```python
def stop():
GPIO.output([IN1, IN2, IN3, IN4, IN1_2, IN2_2, IN3_2, IN4_2], GPIO.LOW)
```
All motors stop, bringing the robot to a halt.
### Ultrasonic Sensor
The ultrasonic sensor works by emitting a high-frequency sound wave and listening for its echo. By measuring the time taken for the echo to return, the distance to the nearest object can be calculated using the following formula:
Distance = (Time taken × Speed of Sound) / 2
- **Sound emission**: The sensor sends a high-frequency ultrasonic pulse.
- **Echo reception**: The pulse hits an object and reflects back to the sensor.
- **Distance calculation**:
- The speed of sound in air is approximately 343 m/s, or 0.0343 cm/µs.
- Time taken is divided by 2 because the sound travels to the object and back.
> For a detailed understanding of how ultrasonic sensors work, you can refer to this link:
> https://toonrobotics.com/en/isr_ultrasonicsensoren20231228/
#### Hardware Connections

- **VCC**: Connected to the **5V pin** on the Raspberry Pi to power the sensor.
- **Trig**: Connected to **GPIO.BOARD 24** to receive trigger pulses.
- **Echo**: Connected to **GPIO.BOARD 26** to send echo pulses back to the Raspberry Pi.
- **GND**: Connected to the **GND pin** on the Raspberry Pi.
#### Code Implementation
The ultrasonic sensor measures the distance to the nearest object by timing the delay between sending a sound pulse and receiving the echo.
1. **Set Up GPIO Pins:**
```python
trigger_pin = 24
echo_pin = 26
```
> **Note**: The GPIO pin configuration does not have to match the one shown here.
- The **GPIO.BOARD** mode is used in this project, which refers to the physical pin numbering on the Raspberry Pi.
> For a comparison between BOARD and BCM modes, refer to: [Pinout.xyz.](https://pinout.xyz/pinout/)
Configures the `trigger_pin` as an output and `echo_pin` as an input.
```python
GPIO.setup(trigger_pin, GPIO.OUT)
GPIO.setup(echo_pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
```
2. **Define Program Constants:**
The constants control the operation and data processing of the sensor.
```python
number_of_samples = 5
sample_sleep = 0.01
calibration1 = 30
calibration2 = 1750
time_out = 0.05
samples_list = []
stack = []
```
- `number_of_samples`: Number of distance measurements for averaging
- `sample_sleep`: Time in seconds between sensor readings
- `calibration1`: The distance the sensor was calibrated at
- `calibration2`: The median value reported back from the sensor at 30 cm
- `time_out`: Timeout duration in seconds for waiting for the echo
- `samples_list = []`: Stores distance samples for averaging
- `stack = []`: Records start and end times for each distance measurement
3. **Timing Callback Function:**
This function records the time of rising and falling edges of the echo pin.
```python
def timer_call(channel):
now = time.monotonic()
stack.append(now)
```
4. **Trigger Sound Pulse**:
Sends a 10-microsecond pulse to initiate the ultrasonic wave.
```python
def trigger():
GPIO.output(trigger_pin, GPIO.HIGH)
time.sleep(0.00001) # 10-microsecond pulse
GPIO.output(trigger_pin, GPIO.LOW)
```
5. **Measure Distance**:
Calculate the distance using the time difference between sending and receiving the pulse.
```python
def check_distance():
samples_list.clear()
while len(samples_list) < number_of_samples:
trigger()
while len(stack) < 2: # waiting for the stack to fill with a start and end time
start = time.monotonic()
while time.monotonic() < start + time_out:
pass
trigger()
if len(stack) == 2:
samples_list.append(stack.pop() - stack.pop())
elif len(stack) > 2:
stack.clear()
time.sleep(sample_sleep)
return (statistics.median(samples_list) * 1000000 * calibration1 / calibration2)
```
6. **Add Event Detection:**
Detect both rising and falling edges on the `echo_pin`, which represent the start and end of the ultrasonic pulse's travel time. When such an event occurs, the `timer_call` function is triggered to record the corresponding time:
```python
GPIO.add_event_detect(echo_pin, GPIO.BOTH, callback=timer_call)
```
### Weight Sensor
The HX711 weight sensor detects objects placed on the robot by measuring their weight.
#### Principle of Operation
The HX711 module is a 24-bit ADC (Analog-to-Digital Converter) designed to read signals from strain gauge-based sensors, such as load cells used in electronic scales. Its operation involves:
1. **Signal Amplification**:
- The sensor amplifies small voltage changes caused by the strain gauge as weight is applied.
2. **Analog-to-Digital Conversion**:
- Converts the amplified signal into digital values for processing by the Raspberry Pi.
3. **Data Readout**:
- The Raspberry Pi reads these digital values to determine the weight.
#### Hardware Connections

1. YZC-131 Load Cell Connections:
- **Red (E+)**: Connected to HX711 **E+**
- **Black (E-)**: Connected to HX711 **E-**
- **White (A-)**: Connected to HX711 **A-**
- **Green (A+)**: Connected to HX711 **A+**
2. HX711 Connections:
- **GND**: Connected to Raspberry Pi **GND**
- **DT**: Connected to Raspberry Pi GPIO.BOARD **21**
- **SCK**: Connected to Raspberry Pi GPIO.BOARD **23**
- **VCC**: Connected to Raspberry Pi **5v power**
#### Code Implementation
1. **Import the HX711 Module:**
To use the HX711 weight sensor, import the module as follows:
```python
from hx711 import HX711
```
This library provides functions to interact with the HX711 ADC module, which converts the load cell's analog signal into digital data for the Raspberry Pi.
2. **Initialize Sensor**:
```python
hx = HX711(21, 23)
hx.set_reading_format("MSB", "MSB")
hx.reset()
hx.tare()
```
- `HX711(21, 23)`: Configure the data and clock pins for the HX711 module.
- `set_reading_format("MSB", "MSB")`: Set the data reading format to use the Most Significant Bit (MSB).
- `reset()`: Reset the HX711 module.
- `tare()`: Zero the scale to ensure subsequent weight measurements are accurate and based on the initial state.
3. **Define Threshold and Stability Parameters:**
```python
THRESHOLD = 300
object_present = False
last_check_time = 0
DEBOUNCE_TIME = 0.5
stable_start_time = None
STABLE_TIME = 0.5
```
- `THRESHOLD = 300`: The minimum weight required to detect an object on the sensor.
- `object_present = False`: Tracks whether an object is currently detected.
- `last_check_time = 0`: Records the last time the check_weight() function was called, used for debouncing.
- `DEBOUNCE_TIME = 0.5`: Ensures a 0.5-second interval between consecutive weight checks to avoid rapid, unstable readings.
- `stable_start_time = None`: Tracks the time when a stable weight change begins.
- `STABLE_TIME = 0.5`: The weight must remain stable for 0.5 seconds before confirming a state change (object placed or removed).
4. **Initialize Sensor State:**
```python
initial_weight = hx.get_weight(1)
object_present = initial_weight > THRESHOLD
```
Reads the initial weight and determines whether an object is already present.
6. **Check Weight Logic**:
```python
def check_weight():
global object_present, last_check_time, stable_start_time
raw_val = hx.get_weight(1)
current_time = time.time()
if current_time - last_check_time < DEBOUNCE_TIME:
return None
last_check_time = current_time
if raw_val > THRESHOLD:
if not object_present:
if stable_start_time is None:
stable_start_time = current_time
elif current_time - stable_start_time >= STABLE_TIME:
object_present = True
stable_start_time = None
return "Object Placed"
else:
stable_start_time = None
else:
if object_present:
if stable_start_time is None:
stable_start_time = current_time
elif current_time - stable_start_time >= STABLE_TIME:
object_present = False
stable_start_time = None
return "Object Removed"
else:
stable_start_time = None
return None
```
- **Logic Flow**:
- Compare the current time with `last_check_time` to ensure at least `DEBOUNCE_TIME` seconds have passed since the last check.
- If the weight exceeds the threshold and remains stable, the system registers an object as placed.
- If the weight drops below the threshold and remains stable, the system registers the object as removed.
- Returns "Object Placed" or "Object Removed" when a state change is detected, or None if no change occurs.
3. **Status Integration**:
```python
def check_weight():
status = check_weight()
if status == "Object Placed":
return jsonify({'status': "Object Detected", 'alert': "An object has been placed."})
elif status == "Object Removed":
return jsonify({'status': "No Object", 'alert': "The object has been removed."})
current_status = "Object Detected" if object_present else "No Object"
return jsonify({'status': current_status, 'alert': None})
```
- Returns the current state through the API, indicating whether an object is present or not.
## Demo Video
{%youtube G1HLN7NK7WI %}
## Source Code
For the complete source code of this project, please visit the GitHub repository at:
https://github.com/ichenx2/Delivery-Robot-Car
## Acknowledgments
Lasty, I would like to express my deepest gratitude to:
- **Professor George** and **Teaching Assistant Gordon** for their invaluable guidance and support throughout this project. Their insights and advice have been crucial to the development of this delivery robot car.
- All of my classmates from the Practical Internet of Things course, who were always willing to help each other during class and beyond class hours. And my friends, who constantly encouraged me after each failure. Special thanks to **Oliver**, who provided unwavering support during moments when I felt like giving up.
- Also, I want to thank myself for the dedication and perseverance throughout the project. Despite its imperfections, this experience has been a precious learning journey, and I aspire to improving myself further in the future.
## References
- Raspberry Pi Official Documentation: https://www.raspberrypi.com/documentation/computers/getting-started.html
- Python Web Server with Flask: https://projects.raspberrypi.org/en/projects/python-web-server-with-flask
- CSDN Blog on Ultrasonic Sensors: https://blog.csdn.net/weixin_43073852/article/details/83085306
- Controlling DC Motors with L298N and Raspberry Pi: https://maker.pro/raspberry-pi/tutorial/how-to-control-a-dc-motor-with-an-l298-controller-and-raspberry-pi
- Think Robotics on Ultrasonic Sensors: https://thinkrobotics.com/blogs/learn/ultrasonic-distance-sensor?srsltid=AfmBOoogTW_85jsPQ3NHMsSIPhQghckRl1VSwvhSMyB3m2riLUY-XJuh
- Ultrasonic Distance Sensor Lesson: https://erich.forler.ca/component/content/article/33-blog-hands-on-tech/146-raspberry-pi-ultrasonic-distance-sensor-hr-srf05-full-lesson
- DIY AI POS Machine: https://chtseng.wordpress.com/2020/02/14/diy-%E4%B8%80%E5%8F%B0%E8%90%AC%E8%83%BD%E7%9A%84ai-pos%E7%B5%90%E5%B8%B3%E6%A9%9F/
- Digital Raspberry Pi Scale with HX711: https://tutorials-raspberrypi.com/digital-raspberry-pi-scale-weight-sensor-hx711/
- Waveshare UPS HAT: https://www.waveshare.net/wiki/UPS_HAT