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

Greetings everyone, and welcome to my article 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

Electronic Components Required:
Raspberry Pi Pico
NEO-6M GPS Module
SIM800L GPRS GSM Module
BreadBoard
Red LED
Additional Tools:
Hot Glue
Cutter
Soldering Iron
Software:







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)
3.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...




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!



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.
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.")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 article 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.









