Build a GPS Vehicle Tracker Using Raspberry Pi Pico | MicroPython & Thonny IDE Tutorial

by Shahbaz Hashmi Ansari in Circuits > Raspberry Pi

23 Views, 0 Favorites, 0 Comments

Build a GPS Vehicle Tracker Using Raspberry Pi Pico | MicroPython & Thonny IDE Tutorial

How to build a GPS Vehicle Tracker using Raspberry Pi Pico | MicroPython & Thonny IDE Tutorial
Screenshot (1154).png

Greetings everyone, and welcome to my Instructables tutorial. Today, I'll guide you through the process of creating a GPS Vehicle Tracker using Raspberry Pi Pico.

Project Overview:

Build a professional GPS-based vehicle tracking system using Raspberry Pi Pico, the SIM800L GSM module, and the Neo-6M GPS receiver. This project tracks real-time vehicle location via SMS and Google Maps, offering reliable performance even with poor connectivity. Perfect for IoT, vehicle monitoring, and asset tracking applications, all programmed in MicroPython using Thonny IDE.

Before beginning, a huge shoutout to JLCMC for sponsoring.

Now, let's get started with our project!

Supplies

IMG_20251021_190849.jpg

Electronic Components Required:

  1. Raspberry Pi Pico
  2. NEO-6M GPS Module
  3. SIM800L GPRS GSM Module
  4. BreadBoard
  5. Red LED

Additional Tools:

  1. Hot Glue
  2. Cutter
  3. Soldering Iron

Software:

  1. Thonny IDE

Components Assembly & Circuit Design

Circuit Diagram GPS Tracker.png
Screenshot (1156).png
Screenshot (1157).png
Screenshot (1158).png
Screenshot (1159).png
Screenshot (1160).png
Screenshot (1161).png
Screenshot (1162).png

Follow the steps:

  1. Mount the Raspberry Pi Pico, NEO-6M GPS Module, & SIM800L GPRS GSM Module into the BreadBoard.
  2. Using the jumper wire, connect the SIM800L GPRS GSM Module:
GSM Module → Raspberry Pi Pico
TX → GP1 (UART0 RX - Pin 2)
RX → GP0 (UART0 TX - Pin 1)
  1. Using the jumper wire, connect the NEO-6M GPS Module:
GPS Module → Raspberry Pi Pico
TXD → GP5 (UART1 RX - Pin 7)
RXD → GP4 (UART1 TX - Pin 6)

See you in the next step...

Elevate Your Electronic Projects - JLCMC

Screenshot (1164).png
Screenshot (1163).png
Screenshot (1165).png
Screenshot (1167).png
Screenshot (1166).png

JLCMC is your one-stop shop for all electronic manufacturing needs, offering an extensive catalog of nearly 600,000 SKUs that cover hardware, mechanical, electronic, and automation components. Their commitment to guaranteeing genuine products, rapid shipping (with most in-stock items dispatched within 24 hours), and competitive pricing truly sets them apart. In addition, their exceptional customer service ensures you always get exactly what you need to bring your projects to life.

They have everything you need for your next project:

  1. Custom Linear Guide Shafts: Precision-engineered for applications like 3D printing, CNC machines, and industrial automation.
  2. Aluminum Profiles: Versatile, durable framing solutions—perfect for machine enclosures, workstations, and custom assemblies.

To show their support for our community, JLCMC is offering an exclusive $70 discount coupon. This is the perfect opportunity to save on high-quality components for your next project. Don’t miss out—visit https://jlcmc.com/?from=RLO to explore their amazing range of products and grab your discount coupon today!

Power Supply Circuit

Screenshot (1168).png
Copy of Copy of Copy of Heading (1).png
Copy of Copy of Copy of Heading.png

Follow the steps:

  1. Now connect the NEO-6M GPS Module and the SIM800L GPRS GSM Module:
VCC --> VBUS (Raspberry Pi Pico)
GND --> GND (Raspberry Pi Pico)

Power Supply:

  1. USB Power Supply: Simply connect the Raspberry Pi Pico to your computer or car USB charger using a USB cable. This provides both power and programming access.
  2. External Power Supply (Recommended for Field Use): If you’re deploying the system in a vehicle or remote location, you can use a regulated 5V external power source. Ensure that the SIM800L module receives stable voltage (3.7V–4.2V range), using an external power source or a Li-ion battery with proper regulation is ideal for reliable GSM performance.


Understanding MicroPython & Uploading Code

Micropython programming and Raspberry Pi Pico programming using Thonny IDE

The above video will provide you with a clear explanation of the code and how to upload it.

Full Code: https://github.com/ShahbazCoder1/GPS-Vehicle-Tracker-using-Raspberry-Pi-Pico-MicroPython-Thonny-IDE


In this project we are using the MicropyGPS library by Michael Calvin McCoy


Main.py:

# GPS Vehicle Tracker using Raspberry Pi Pico | MicroPython & Thonny IDE
# Written by Shahbaz Hashmi Ansari

import machine
import time
import sys
from micropygps import MicropyGPS

# --- Pin Definitions ---
POWER_LED_PIN = 25
GSM_LED_PIN = 15
GPS_LED_PIN = 14

# --- Constants and Configuration ---
ADMIN_NUMBER = "+9186********"
LOCATION_INTERVAL_MS = 1 * 60 * 1000 # 1 minute for testing
GSM_CHECK_INTERVAL_MS = 10000 # Check GSM network status every 10 seconds

# --- Hardware Configuration ---
# UART for GSM Module (SIM800L, etc.)
gsm_uart = machine.UART(0, baudrate=9600, tx=machine.Pin(0), rx=machine.Pin(1), timeout=1000)

# UART for GPS Module (NEO-6M, etc.)
gps_uart = machine.UART(1, baudrate=9600, tx=machine.Pin(4), rx=machine.Pin(5))

# --- System State Variables ---
gsm_connected = False
gps_fix_acquired = False
gsm_initialized = False

# --- GPS Parser Setup ---
# India is UTC+5:30, so local_offset is 5.5
gps_parser = MicropyGPS(location_formatting='dd', local_offset=5.5)

# --- Timing Variables for Non-Blocking Delays ---
last_location_sent_ms = 0
last_gsm_check_ms = 0

# --- LED Setup ---
power_led = machine.Pin(POWER_LED_PIN, machine.Pin.OUT)
gsm_led = machine.Pin(GSM_LED_PIN, machine.Pin.OUT)
gps_led = machine.Pin(GPS_LED_PIN, machine.Pin.OUT)
power_led.on()

#=================================================
# ===== HELPER FUNCTIONS =========================
#=================================================

def send_at_command(command, wait_time_ms=1000, max_wait_ms=3000):
"""Sends an AT command to the GSM module and returns the response."""
print(f"Sending: {command}")
try:
# Clear any pending data first
if gsm_uart.any():
gsm_uart.read()
gsm_uart.write((command + '\r\n').encode())
# Wait for response with timeout
response = ""
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < max_wait_ms:
if gsm_uart.any():
chunk = gsm_uart.read()
if chunk:
response += chunk.decode('utf-8', 'ignore')
# Small delay to allow complete response
time.sleep_ms(wait_time_ms)
# Read any remaining data
if gsm_uart.any():
chunk = gsm_uart.read()
if chunk:
response += chunk.decode('utf-8', 'ignore')
break
time.sleep_ms(100)
if response:
print(f"Response: {response.strip()}")
else:
print("No response received")
return response
except Exception as e:
print(f"Error in send_at_command: {e}")
return ""

def check_gsm_module():
"""Check if GSM module is responding."""
print("\nChecking GSM module connection...")
# Try basic AT command
response = send_at_command('AT', 1000, 2000)
if 'OK' in response:
print("✓ GSM module is responding")
return True
print("✗ GSM module not responding")
print(" Check: 1) Power supply (4V) 2) TX/RX connections 3) Ground connection")
return False

def send_sms(number, message):
"""Sends an SMS message with improved timeout handling."""
if not gsm_connected:
print("Cannot send SMS. GSM not connected.")
return False
print(f"\nSending SMS to {number}")
print(f"Message: {message}")
try:
# Set SMS text mode
send_at_command('AT+CMGF=1', 500)
# Send the SMS command
gsm_uart.write(f'AT+CMGS="{number}"\r\n'.encode())
# Wait for '>' prompt with timeout
prompt_received = False
start_time = time.ticks_ms()
response = ""
while time.ticks_diff(time.ticks_ms(), start_time) < 5000:
if gsm_uart.any():
chunk = gsm_uart.read()
if chunk:
response += chunk.decode('utf-8', 'ignore')
if '>' in response:
prompt_received = True
break
time.sleep_ms(50)
if not prompt_received:
print(f"Failed to get SMS prompt '>'")
# Send ESC to cancel
gsm_uart.write(b'\x1B')
return False
print("Got prompt, sending message...")
# Send message content
gsm_uart.write(message.encode('utf-8'))
time.sleep_ms(100)
# Send Ctrl+Z to finish
gsm_uart.write(b'\x1A')
# Wait for confirmation
confirmation = ""
start_time = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), start_time) < 10000:
if gsm_uart.any():
chunk = gsm_uart.read()
if chunk:
confirmation += chunk.decode('utf-8', 'ignore')
if '+CMGS:' in confirmation or 'OK' in confirmation:
print("✓ SMS sent successfully!")
return True
if 'ERROR' in confirmation:
print(f"✗ SMS error: {confirmation}")
return False
time.sleep_ms(100)
print("SMS timeout - message may not have been sent")
return False
except Exception as e:
print(f"Error sending SMS: {e}")
return False

def has_valid_gps_data():
"""Check if GPS has valid position data."""
# Check if latitude and longitude tuples have valid data
if (gps_parser.latitude[0] is not None and
gps_parser.longitude[0] is not None and
gps_parser.latitude[0] != 0 and
gps_parser.longitude[0] != 0):
return True
return False

def get_current_location_string():
"""Formats the current location into a readable string."""
if has_valid_gps_data():
# Formatted (human-readable)
lat_str = gps_parser.latitude_string()
lon_str = gps_parser.longitude_string()

# Decimal (for Google Maps link)
lat = gps_parser.latitude[0]
lon = gps_parser.longitude[0]

# Check if timestamp and date are valid before using them
ts = gps_parser.timestamp
dt = gps_parser.date
timestamp_str = ""
if dt[0] and ts[0] is not None:
try:
timestamp_str = f"\nTime: {dt[0]:02d}/{dt[1]:02d}/20{dt[2]:02d} {ts[0]:02d}:{ts[1]:02d}:{int(ts[2]):02d}"
except:
timestamp_str = "\nTime: N/A"
else:
timestamp_str = "\nTime: N/A"

# Use decimal format for maps link (no ° N/E)
maps_link = f"http://maps.google.com/maps?q={lat},{lon}"
return f"Location: {lat_str}, {lon_str}{timestamp_str}\nMap: {maps_link}"
else:
return "GPS signal not available. Please wait."


def print_debug_report():
"""Prints a live status report to the console."""
print("\n")
print("=" * 50)
print(" SYSTEM DEBUG REPORT")
print("=" * 50)
print("\n[ System Status ]")
print("-" * 50)
print(f"GSM Module Initialized: {'Yes' if gsm_initialized else 'No'}")
print(f"GSM Network Connected: {'Yes' if gsm_connected else 'No'}")
print(f"GPS Fix Acquired: {'Yes' if gps_fix_acquired else 'No'}")
print("\n[ GPS Information ]")
print("-" * 50)
if gps_fix_acquired and has_valid_gps_data():
print(f"Latitude: {gps_parser.latitude_string()}")
print(f"Longitude: {gps_parser.longitude_string()}")
print(f"Satellites in use: {gps_parser.satellites_in_use}")
ts = gps_parser.timestamp
if ts[0] is not None:
print(f"Time: {ts[0]:02d}:{ts[1]:02d}:{int(ts[2]):02d}")
else:
print("No GPS fix - waiting for satellites...")
print("\n[ Location Tracking ]")
print("-" * 50)
time_since_last = time.ticks_diff(time.ticks_ms(), last_location_sent_ms)
next_send = (LOCATION_INTERVAL_MS - time_since_last) // 1000
print(f"Interval: {LOCATION_INTERVAL_MS // 60000} minute(s)")
print(f"Last sent: {time_since_last // 1000} seconds ago")
print(f"Next send: {max(0, next_send)} seconds")
print("\n[ Hardware Info ]")
print("-" * 50)
print(f"GSM UART: TX=Pin{0}, RX=Pin{1}")
print(f"GPS UART: TX=Pin{4}, RX=Pin{5}")
print(f"Admin Number: {ADMIN_NUMBER}")
print("\n" + "=" * 50 + "\n")

def check_serial_input():
"""Check for serial input in a MicroPython compatible way."""
# Try to check if stdin has data using polling
try:
# Use sys.stdin with non-blocking read
if hasattr(sys.stdin, 'read'):
# This method works better on MicroPython
return True
return False
except:
return False

#=================================================
# ===== INITIALIZATION ===========================
#=================================================

print("\n" + "=" * 50)
print(" Vehicle Tracking System V5 for RPi Pico")
print("=" * 50)
print("\nInitializing...")

# Check if GSM module is responding
gsm_initialized = check_gsm_module()

if gsm_initialized:
print("\nConfiguring GSM module for SMS...")
send_at_command('ATE0', 500) # Disable command echo
send_at_command('AT+CMGF=1', 500) # Set SMS to text mode
send_at_command('AT+CNMI=2,1,0,0,0', 500) # Configure SMS delivery
print("GSM configuration complete.")
else:
print("\n⚠ WARNING: GSM module not detected!")
print("The system will continue, but SMS features won't work.")
print("Please check your wiring and power supply.\n")

print("\nSystem is running...")
print("Commands:")
print(" - Type 'x' for debug report")
print(" - Type 't' to test SMS")
print(" - Type 'l' to get location")
print("-" * 50)

#=================================================
# ===== MAIN LOOP ================================
#=================================================

input_buffer = ""

while True:
try:
current_time_ms = time.ticks_ms()

# --- 1. Process GPS Data ---
if gps_uart.any():
data = gps_uart.read().decode('utf-8', 'ignore')
for char in data:
sentence = gps_parser.update(char)
# Optional: Print when complete sentence is parsed
# if sentence:
# print(f"Parsed: {sentence}")
# Check for GPS fix using correct attribute (fix_type instead of fix_stat)
# fix_type: 1 = no fix, 2 = 2D fix, 3 = 3D fix
if not gps_fix_acquired and gps_parser.fix_type >= 2 and has_valid_gps_data():
gps_fix_acquired = True
gps_led.on()
print(f"\n✓ GPS Fix Acquired! (Fix type: {gps_parser.fix_type}D)")
if gsm_connected:
send_sms(ADMIN_NUMBER, "GPS fix acquired. Vehicle tracking active.")
# --- 2. Check GSM Network Connection ---
if gsm_initialized and not gsm_connected:
if time.ticks_diff(current_time_ms, last_gsm_check_ms) >= GSM_CHECK_INTERVAL_MS:
response = send_at_command('AT+CREG?', 500, 2000)
if '+CREG: 0,1' in response or '+CREG: 0,5' in response:
gsm_connected = True
gsm_led.on()
print("\n✓ GSM Network Connected!")
send_sms(ADMIN_NUMBER, "Vehicle Tracking System is online.")
last_location_sent_ms = current_time_ms
elif '+CREG:' in response:
print("GSM not registered yet, will retry...")
last_gsm_check_ms = current_time_ms

# --- 3. Interval Location Reporting ---
if gsm_connected and gps_fix_acquired and has_valid_gps_data():
if time.ticks_diff(current_time_ms, last_location_sent_ms) >= LOCATION_INTERVAL_MS:
location_message = get_current_location_string()
send_sms(ADMIN_NUMBER, location_message)
last_location_sent_ms = current_time_ms

# --- 4. Check for Incoming SMS ---
if gsm_uart.any():
response = gsm_uart.read().decode('utf-8', 'ignore')
if '+CMT:' in response:
print(f"\nIncoming SMS: {response}")
if 'location' in response.lower():
print("Location request via SMS")
location_message = get_current_location_string()
send_sms(ADMIN_NUMBER, location_message)

# --- 5. Check for Console Commands (Simplified for MicroPython) ---
# Note: Interactive input via REPL may be limited during runtime
# Consider using a button or external trigger for commands in production
try:
# Simple polling approach for MicroPython
# This may not work in all environments - use hardware buttons as alternative
import select
if select.select([sys.stdin], [], [], 0)[0]:
char = sys.stdin.read(1)
if char == '\n' or char == '\r':
# Process the command
cmd = input_buffer.strip().lower()
input_buffer = ""
if cmd == 'x':
print_debug_report()
elif cmd == 't':
print("\nTesting SMS...")
send_sms(ADMIN_NUMBER, "Test message from Vehicle Tracker")
elif cmd == 'l':
print("\nCurrent Location:")
print(get_current_location_string())
elif cmd != '':
print(f"\nUnknown command: '{cmd}'")
print("Valid commands: x (debug), t (test SMS), l (location)")
else:
input_buffer += char
except:
# If select is not available or fails, skip input checking
pass
# Small delay
time.sleep_ms(50)
except KeyboardInterrupt:
print("\n\nShutting down...")
break
except Exception as e:
print(f"\nError in main loop: {e}")
time.sleep_ms(500)

print("System stopped.")

Troubleshooting

GPS Not Getting Fix

  1. Ensure GPS antenna has clear view of the sky.
  2. Wait 2-5 minutes for initial satellite lock (cold start)
  3. Check UART connections (TX/RX not reversed)
  4. Verify 3.3V power supply to GPS module

GSM Module Not Responding

  1. Check power supply (needs 3.7-4.2V with sufficient current)
  2. Verify SIM card is active and has credit
  3. Check antenna connection
  4. Ensure correct APN settings for your carrier
  5. Try AT commands manually to test the module.

No SMS Received

  1. Verify phone number format (include country code)
  2. Check if the SIM card has SMS capability
  3. Ensure GSM module is registered on the network.
  4. Check signal strength in your area

Code Upload Fails

  1. Ensure MicroPython firmware is properly installed
  2. Check USB cable connection
  3. Try different USB port
  4. Restart Thonny IDE

Working Video and Tutorial

How to build a GPS Vehicle Tracker using Raspberry Pi Pico | MicroPython &amp; Thonny IDE Tutorial

Congratulations! You’ve successfully built your GPS Vehicle Tracker using Raspberry Pi Pico. A demonstration video of this project can be viewed here: Watch Now

Thank you for your interest in this project. If you have any questions or suggestions for future projects, please leave a comment, and I will do my best to assist you.

For business or promotional inquiries, please contact me via email at Email.

I will continue to update this instructable with new information. Don’t forget to follow me for updates on new projects and subscribe to my YouTube channel (YouTube: roboattic Lab) for more content. Thank you for your support.