Pico/mPython – smart car DIY

Time for a new robot approach: building a ‘smart car’ platform. Having some Arduino Uno experiences some years back, it’s time to learn something new:

  • Want to use Pico-W board
  • Learn microPython
  • Using new software IDE: Visual Studio Code
  • Want learn & experience with some robot control, dead reckoning & odometry. Basically driving a robot to a wanted position..
    That would make the robot a bit smarter, doesn’t it?

After reading this inspiring medium article: “Pico Bot” by Doug Blanding, there is no reason to wait any longer.


This post is devided into 2 sections:


The Build

It’s the updated version, with batteries mounted on top of the wheels. (Less slip, better results. Thanks Joep.)

Bits & pieces

There are a lot of robot parts laying around, so let’s use them:

Bottom side


Electronics

Connection diagram used:

Battery Shield V8 & powering the Pico

The battery shield has an “activation button” and with the “mode switch” on “hold” position, the activation button could be used to turn the shield on (1 click) or off (2 clicks).

An additional on/off button, controlling the shield output power.

For powering the RPi Pico: watch this excelent video:ย 
Power for the Raspberry Pi Pico.

So, being able to connect both your battery and upload code via USB, you need some kind of protection. According official Raspberry Pi documentation, it’s best using a P-channel mosfet. A schottky diode (1n5817) is a good alternative, but I don’t have that either:

So I’m using a 1N4007 instead, which seems to work up till now. (This diode has a voltage drop of 0.7V, which is basically burning energy from the battery supply..) ToDo: Order some mosfets…

DRV8835 modes

This DRV8835 motor controller has 2 modes:

  1. MODE pin connected to 5V: Enable/Phase mode.
  2. MODE pin not connected: In/In mode.
    (The pin has an internal pulldown resistor.)

I’m using the enable/phase mode. This requires only 1x PWM signal and 1x direction pin per motor. Having the ability to: Brake, Reverse & Forwards.
So I’m not able to use the ‘coasting’ function. The drive has another optional feature: ‘sleep’ mode, by making VCC=0 Volt.

Pins used on Pico-W

Connection diagram:


Software preperation

Firmware update: Pico-W

Download the latest Pico-W firmware from microptyhon.org and follow procedure with boot-select etc.

Currently using:

  • RPI_PICO_W-20240602-v1.23.0.uf2
  • “RPI_PICO_W-20250911-v1.26.1.uf2”

Install Visual Studio Code

Download and install Visual Studio code.

Watch YouTube: How to Use VSCode with Raspberry Pi Pico W and MicroPython.

Compared to the YouTube, the VSCode extensions seems to be upgraded and renamed: “MicroPico” extension. (Thanks Paul Ober, for making this nice extension available to all of us.)

(The extension also requires some other packages, which is nicely documented. MicroPython is updated to version: 4.3.0.)

Blinky

Now the RPi Pico is powered by the USB only, the LED on the Pico could already be tested, see above youtube movie.

Learning to work with VSC & RPi Pico…

I’m just starting and learning on the way. Some learnings sofar:

  • Starting a new project: “import machine” is not reconized:

For every new VSC/RPi-Pico project, you have to:

  • First of all open a new folder and
  • run > MicroPico > Configure Project command, via Ctrl+Shift+P.

This will install all necessary microPython links to your project.

  • Error: “Module not found”:

Maybe it’s just me, but uploading blinky code to the Pico was quite simple, by selecting the ‘Run current file’ button. But now I like to include a 2nd module, from a seperate file (encoder_portable.py). The VSCode screen does not report an error, but pressing the ‘Run current file’ button, did not upload this additional ‘module’ on the Pico! So when using additonal microPtyhon files/modules, all project files need to be uploaded… (Tnxs Iwan!)  

(ToDo: Understand difference between: ‘running current file’ and uploading files to the Pico..)

  • How to run script remote after booting Pico, w/o USB connected:

When the Pico is connected to VSCode, it’s not running automatically the ‘main.py’ file. But if the USB cable is disconnected and the Pico is re-powered by the batteries, it first tries to run ‘boot.py’ (for setting up internal devices etc) and then continous to execute ‘main.py’ if available.

Example of working ‘main.py’, needs only 2 code lines, for running a different ‘module.py’:

main.py
-------------
import motors
motors.run()
  • Running specific script/function from a module (name == “main”):

While the ‘main.py’ and other code is still copied on the Pico, it’s still possible to run test code inside your module. Say the motor module needs to be tested, but this test code should not automatically start, while running the main module.

With the USB & VSCode connected, the ‘motors.py’ could be activated by ‘Run current file’. Now code following ‘if __name__ == “__main__”:‘ will be exectued.

Optional, the test code could be defined as a function inside the motors.py module:

motors.py
---------

def run_test2()
	print('test case')

#-----------------------------------------------------------------------
if __name__ == "__main__":
	run_test2()
  • Blocked RPi Pico:

After some (other) faulty code, while playing with USB communication, the Pico was completely bricked. ๐Ÿ™ VSCode was not able to connect to the Pico at all. For unblocking your RPi Pico, upload during bootsel the ‘flash-nuke.uf2’ file, see also: How To Un-Brick Your Stuck Raspberry Pi Pico.

  • ToDo: Coding remotely on the Pico:

Wouldn’t it be fantastic programming the Pico robot remotely? 
(Getting Started with REPL on the Raspberry Pi Pico using Rshell
No USB cable needed, unless the Pico is bricked.
This also enables a standard USB connection with PC or Paspberry Pi 4, which might control the Pico someday…


Driving motors & measure power consumption

Now hardware, firmware & software seems to run, it’s time for spinning motors. The duty cycle signals from the Pico will define the speed of the motors in a range of [0..65536] โ†’ 0-5 [Volt] on the pico pin.

The PWM frequency could be set between 0 and 250 kHz, according the DRV8835 datasheet. Re-using the 1000Hz setting from Doug seems to work for this driver too. This frequency setting may influence driver performance.
(ToDo: Some PWM-frequency testing..)

Basic code:

# imports:
from machine import Pin, PWM

# setup motor pin:
pinMtrDir_L = Pin(6, Pin.OUT, value=0) 	#Direction pin
pinMtrPWM_L = PWM(Pin(7))			#Speed pin
pinMtrPWM_L.freq(1000)				#Frequency of the PWM pin
pinMtrPWM_L.duty_u16(0)				#Set duty cycle to zero, we don't want running motors during pin initalisation!

# drive motor opposite direction at ~30% PWM:
pinMtrDir_L.value(1)
pinMtrPWM_L.duty_u16(20_000)		#The '20_000' stands for 20000. But this '_' does not work for negative numbers or floats.

Faulty connector motor/encoder…

Unfortunately one of the provided motor connectors is very loose. Sometimes there is no connection, which will lead to unwanted controll.

Solved: The male header got somewhat loose from the pins, so these pins became very short, creating a poor connection with the female header. So just had to push this to it original position.

Duty cycle versus motor speed

Time to play with the PWM duty cycle. While increasing the duty cycle:

  • At very low PWM value’s, the motor is NOT running. There is a certain ‘cut-in’ speed required. (My case with these N20 motors: roughly 15% when driving on a table.)
  • The motor speed is increasing.
  • Also the motor torque is increasing.

With constant duty cycle value:

  • The relation between speed & torque: If the motor wheel is gently squeezed, ofc. duty cycle value remains the same, but the torque on the motor shaft increases and the speed is decreasing.

Learnings:

  • PWM duty cycle is not linear related to motor speed, due to varing dynamic influences.

Power consumption

Curious about power consumption of both N20 motors and the RPi Pico.

Current measured for different situations & PWM values:

  • Measuring motor driver current, on the 5V line into drv8835:
    • Free spinning motors
    • Robot moving on table
    • Both motors are blocked shortly
  • System current (from battery):
    • Measuring both drive + pico, in blocked mode.

Conclusion:

  • Current through RPi Pico (and both encoders) (no LAN connection) seems to be ~46 mA. (Also tested in unblocked situation.)
  • Current through 2x N20 motors:
    • Free spinning: 40 mA @ 20% duty cycle, moving up to 100 mA @ 60% and getting lower 84 mA @ 100%. (That 60% increse is a bit strange to me, maybe due to current chopping?) ToDo: Play a bit with the PWM-frequency setting…
    • Both motors are blocked: Let’s say a linear behaviour with duty cycle value, up to 560 mA @ 100% duty cycle.
    • Robot moving on table: On a flat table, there seems to be a linear relation between PWM & current consumption. (Robot weight: 250 gram.) ToDo: Meaure current & speed, while moving on table with increased mass, what is the effect?
  • Current and so the speed will depend on real world situations and are not linear to duty cycle.

Moving robot – straight line

So let’s activate both motors with same duty cycle (value: 20000, is roughly 30% power) for exact same duration of 2 seconds forwards and then backwards: Driving 2 motors with identical duty cycles.

Conclusion:

  • What is going on? Surprise: the robot is not moving a straight line at all! Did something went wrong? No, it didn’t. This is common behaviour with these type of DC motors. With the same motor rotating CW and CCW reacts already differently.
  • We need somekind of motor contoller & calibrations, moving the robot in a wanted direction…

The encoders & wheel travel

Using a “N20 500RPM@6V” motor + “standard wheels”, with both A & B encoder signals connected to the Pico, the ’ticks’ from the motorshaft could be displayed.

Encoder+MotorGearboxWheel
Reading A & B signalsDiameter: 44.5 [mm]
28 ticks/ motorshaft_rotationRatio: 1:30Circumference:
139.79 [mm]
~8157 ticks(So ratio is 1:~29)~10 rotations
2926 ticks500 [mm]
5.852 ticks1 [mm]
1 tick0.17 [mm]

Conclusions:

  • Encoder resolution: 0.17 [mm per tick].
  • Some basic parameters could be derived:
    • TICKS_PER_M = 5852 # NrOfTicks traveling 1 meter.

Motor & encoder stability.

Now all motors & encoders seem to work, it’s time to understand some limits.

Variations to test:

  • Encoder reading frequency (default 10 Hz).
  • Duty cycle motors: -100…100 %.

Constants per test:

  • Constant duty cycle & direction for both motors.
  • Motor PWM-frequency: 1000 Hz.
  • Provided motor voltage, is comming from battery: 5 V.

Known noise effects:

  • The motor connector is stil loose (during these measurements), which is ‘sometimes’ effecting these tests.
  • The robot is still connected to the USB. This causes a drag effect on the (light weight) robot.

Updated USB messages from the Pico:

  • Pico current time: ms,
  • Encoder left motor: ticks,
  • Encoder right motor: ticks.

What to expect:

  • Stability of updated messages, is the timer function spot on? In graphs below, the ‘deltaTime’ is shown, per time interval, this should be constant, depending on timer frequency setting.
  • Stability of motor speed, should be constant value inbetween 2 intervals. For both motors the ‘deltaEncoderPosition’ per time interval is calculated. So basically this is the speed of the motor: ticks/sec.

Encoder readings: Frequency setting

What could be ideal update time for encoder readings? As quick as possible, will require a lot of CPU time from the Pico, so other features might hickup. Too low readings, are missing small deviations and end up in less precise measurements. These measurements are done with free spinning wheels: 

Conclusions:

  • All tested update frequencies (10,20,100,200Hz) seems to work fine. The response time delta’s are constant, except for the first graph.
  • While testing the first graph, the motor connector seems to be a bit loose again. This results in lot of noise and also the timer function seems to be influenced!
  • Although tests below were run at 10 Hz, the system seems to report messages nicely at 200 Hz too.
  • With a higher measurement frequency (200 Hz), the motor ‘speeding up’ curve is very nice visible. Seems to take 170 [msec], rotating the motor to maximum speed.
  • In most graphs, the right motor (orange line), is moving a bit quicker then the left motor.

Reading encoders: Free running wheels.

The expectation is after the wheels are up to speed to there corsponding duty cycle setting, the ticks/sec should be constant. The encoder frequency is kept to 10 Hz. Both forwards & backwards speeds are measured.

Conclusions:

  • Response times from the timer is still very stable.
  • At lower speeds (duty cycles: +/-20% to +40%), both motors seem to act simulary. At higher speeds the right motor reacts quicker and so the robot makes a turn.

Reading encoders: Robot (300gr) on table

Same test like before, but now the robot drives on the table, adding some friction & inertia to the wheels. These measurements are not extremly precise, since the USB cable is still connected to the robot and causes a little drag/rotation.

Conclusions:

  • Okay, these measurements are not precise at all… Speeds seem to decrease over time, most likely due to USB cable interferance, the cable is not long enough.

Reading encoders: Heavy robot (600gr) on table

What’s happening with the robot, when adding some mass, maybe a RPi 4 someday… In this case the robot weight is doubled.

Conclusions:

  • Interesting to experience the 20% duty cycle value gives trouble in ‘forward’ direction, while this worked fine with the 300gr robot. So the minimal start PWM should be higher, due to more friction/inertia of the robot.
  • ToDo: Changing motor PWMpin(1000Hz) to different frequencies, how/does this effect motor performance?

Overall conclusions

These tests show some insights, but are far from perfect measurements.

Estimated rough motor/robot speeds, depending on duty cycles:

Duty cycle [%]:-60-40-2020406080100
free spinning:315020008408702000315043005450ticks/sec
robot 300gr2600n/a80050011502150n/an/aticks/sec
robot 600gr2500170065020011502000n/an/aticks/sec
free spinning:535.5340142.8147.9340535.5731926.5mm/sec
robot 300gr442n/a13685195.5365.5n/an/amm/sec
robot 600gr425289110.534195.5340n/an/amm/sec

Creating a reliable speed controller, the update frequency should be:

  • as low as possible, so it needs less CPU time,
  • as low as possible, so measurement resolution is good enough,
  • as high as possible, control loop has a quick response,
  • as high as possible, so imperfections (accelerations/bumps) are better controlled(?).

Let’s see what happens, when changing the controller update frequency:

Update frequency:1 Hz10 Hz20 Hz50 Hz
Period:1000 ms100 ms50 ms20 ms
NrOfTicks (100%):5500550275110
NrOfTicks (20%):11001105522
Encoder resolution:440044022088

Overall conclusions:

  • Motor speeds:
    • Maximum free spinning motor speeds: ~5500 ticks per second.
      Or 5500/TICKS_PER_M = 5500/5852= 0.94 [m/sec]
  • Free spinning versus robot moving on table reduces speeds a lot, around 20 to 40%:
    • The lowest robot speed (PWM ~20%) is around 150 mm/sec.
    • Top speed of robot is estimated between 500-750 mm/sec.
  • Assumption: In general reading the encoder ticks seems to work fine:
    Update frequency between 10 and 20 Hz might be good starting point.
  • Weight of robot definately impacts cut-in speeds!
  • Solved:
    • Solved: Repair that anoying connector, generating noise.
    • New software architecture: Program motor speed/position controller.
    • New software architecture:: Program remote logging, eliminate side effects from cables…

How to program a motor speed/position controller?

Let’s reuse the inspiring work from Doug. The code needs some changes:

  • Using different motor controller, now only 4 pins are used in a slightly different way.
  • VSCode: The ‘encoder_rpi.py’ has some ‘Viper’ coding, which is not working with VSCode/Pico extension. So switching to ‘encoder_portable.py’.
  • Pico firmware update: syntax for ‘asyncio.create_task’ is changed a little, so needs some adjustments.
  • Me missing knowledge: Not able to start that web-interface…
  • Changed the PID error calculation: err = setpoint – actual. (So PID parameters are positive numbers.)

Due to the nicely organized code, changes were straight forward.

  • ToDo: play bit more with: PID settings, minimum running speeds, calibration e-compass.
  • ToDo: do more calibrations, how precise is the robot moving to a target?

Conclusions (so far):

  • With some tweaking the code works ‘out-of-the-box’.
  • Still need to update the code:
    • Missing: accelerating/deaccelerating.
    • Missing: remote logging.
    • Missing: remote sending commands.
    • Optimize algorithm?

An other rich source of information: robotmc.be: RobotLib

Status (Jan. 2025)

The robot is able to follow some waypoints (hard coded).

But somehow the precision moving towards the end point is very low. (Easialy 25 cm off, when moving a 1x1m square.)

Next steps:

  • Try different N20 gearboxes, especially higher gear ratio, which will generate more pulses per wheel revolution. Does that help?
  • Try different motors, including higher resolution encoders.
  • A more heavy robot, will increase friction component and should(?) have less slip.
  • Solved: Re-arranging weight of robot: CoG close above both driving wheels, friction is increased, so less slip, increasing positioning (within ~10cm).
  • ToDo: Have to do a bit more coding. How to make a wireless connection? Using LAN/different ways? Which one has a quick update rate & low CPU usage & ‘simple’ to implement?

Although I’m starting to understanding a bit more about differential drive train, I basically decided to pauze this project.



Mobile robot platform (Oct 2025)

Finaly a workable mobile robot platform!

Hello, I’m back working on this small little robot again. With the help of ChatGPT and reading even more articles on the internet, the complete architecture of the robot is changed into the following:

  • RPI Pico-W: core 0:
    • Is looping a robot class, which is managing:
      • Differential drive train class (which is basically a real time controller):
        • PID on wheel speed.
        • P-control on target pose or position or heading.
      • Onboard LED-driver class:
        • Enabling some basic feed back of the robot.
      • Executes optional commands from a list.
      • Prepaired for staring/stopping missions.
        (Potential missions: line following (need additional sensors); ..)
      • Prepaired for adding more real time controllers (RTC’s), for gripper arm or what ever.
  • RPi Pico-W: core 1:
    • Is looping a communication class over LAN, using UPD messages.
  • PC software:
    • Python GUI (using tkinter):
      • able to communicate with the robot.
      • able to log.

It’s great having somewhat working onboard odometry. It’s still drifting a bit, but good enough for now.

PC-GUI:

Also the pc GUI is very usefull and was a nice first Python-Tkinter encounter:

Auto-execute list:

At robot level, additional “commands” could be added manual to an auto-execute list. The list could also be: cleared, started or stopped. The message added are having the same message protocol, like all other messages.

Using this mechanism, it is very straight forward programming a robot with ‘scalable drive patterns’:

  • Moving a square: distance, ‘CCW’ or ‘CW’, speed.
    Very usefull for calibrations.
  • Roborama challenge ‘Heen & Weer’: distance, speed.
  • Roborama challenge ‘T-tijd’: distance, speed.

First steps…

Some first insights about robot behaviour.

Potential improvements:

  • Drive: Add acceleration/deacceleration, reducing slip. (important)
  • Drive: Improve ‘move_to’ algoritme, find best implementation:
    • 1st orientation, then positioning?
    • mix, best approach?
  • Drive: Current code is not optimized for CPU speed. Even the microPython code could be ‘smarter’, using mainly integers instead of floats…

What next…

Basically my innitial goals are reached, so what is next?

This platform should serve multiple use cases:

  • Followup with improvements?
  • Anticipating with robotica.hcc.nl (Amersfoort, NL) roborama challenges?
  • Since some people want to start simular projects at hahaho-makers.nl, this might be a start for a simple generic robot platform & improve?
  • Or update that waveshare PicoGo v1 robot with encoders?
    It’s having some sensors, display onboard, battery & charger onboard.
    Except for the encoders, all necessary electronics are done already.
  • Then there is also an unfinished mecanum drive laying around..
    (Should be a dropin? Writing driver.py for: Motor 2040: Quad Motor + Encoder Controller?)
  • Beside onboard odometry, it’s still very interesting, increasing robot awareness of its enviroment, using a CAM…

Dreaming further about potential upgrades:

To be continued…


Github

Latest & greatest updates: RPi-Pico-Robot-Platform.