Making a Rpi Pico Based Smart Vehicle

I am going start a project on a Pico based smart vehicle. The project will be in many parts. The first part is summarized below.

Pico Smart Vehicle, Part 1 - Controlling a DC motor.

Introduction

This project is on how to use the Rpi Pico with a DC motor controller, such as L298N, to drive a toy DC motor with a speed encoder, such as the N20.

This first part of the project is focused on Pico, Motor Controller, and DC motor.

Rpi4B or Pi-Top 4 would be used as the master controller to communicate with this motor controller, and also controllers of other actuators and senors.

Update 2021jun30hkt2044

When I said above that I would use Rpi4B or Pi-Top 4 as the master controller, I had little confidence I was saying something sensible, because I never saw a Pi-Top 4 before. So I watched a recent video ([Ref 4])((https://www.youtube.com/watch?v=wFWbuj9JdiQ)) to make sure I know what is going on recently.

I was glad to see the video title “Rpi 4 on the Go” and that I can use Rpi OS instead of Pi-Top OS. The touch screen and BlueTooth keyboard also look impressive.

However, I am a very slow guy, so I will be going very slowly, with this Part 1 of controlling a DC motor. :slight_smile:

One other thing is that I am not too sure that I am doing the right thing in this forum. I used to write sort of blogs in rpi.org forum, and also short answers in StackOverflow and Stack Exchange Q&A sites such as for Rpi… So this kind of project discussion topic looks very new to me. I guess I need to search to find out how should I start a project discussion topic.

So I searched and found this post about projects category (Ref 5). I am glad to know that this is a new thing, so I arrived in good time.


References

  1. Compatible motors/cabling - @Ltrain, 2021may25

  2. N20 Gear Motor (50:1 Ratio, 460 rpm, with Encoder) - Servo City

  3. How to Use Your Raspberry Pi Pico With DC Motors (Using DRV8833, L298 or L9110S) - By Les Pounder, Tom’s Hardware 2021jan30

  4. Raspberry Pi 4 On The Go! Pi-Top 4 FHD Touch Display & Bluetooth Keyboard Review - 2021jan25, 74,468 views

  5. About the Projects category post - pi-topTEAM, 2021mar19

  6. Dr. William Rankin Lecture (On Design etc) 1:18 hrs - Arkansas State 1.2K subscribers, 579 views, 2014sep05

1 Like

Pico Smart Vehicle, Part 1 - Controlling a DC motor.

(1) How do I describe my project?

Just now I browsed the projects category here and found not too many examples for me to learn how to properly present my project work in progress.

I jumped to the quick conclusion that there are no strict and fast rules on how to describe your work in progress projects. For one project I saw a brief introduction post, followed by short replies of comments and discussions. For another project I saw the project proposer posts some videos in his Instagram.

My feeling is that the general rule is to post short messages on my work in progress, so other interested readers might comment and make suggestions, so I can modify my project specification and requirements for along the way.

For my previous Micky Mouse toy Rpi projects, I used to post messages in forums, such as in rpi.org, and my favourite section is “Automation, sensing and robotics”.

An example post is this DC Motor Post

The rpi.org forum accepts imgur.com images, but does not entertain videos. So I usually upload YouTube videos about my work in progress. such as this:

tlfong01’s Step Motor Test Youtube Video 2021may1501


(2) Now I am reading the following tutorial to learn how to use Pico to control a DC motor.

How to Use Your Raspberry Pi Pico With DC Motors (Using DRV8833, L298 or L9110S) - By Les Pounder, Tom’s Hardware 2021jan30

I usually summarize what I have learned by a sequence of pictures which also are useful for refreshing memory later, or for newbie readers to get a rough picture of what I am doing.

And I usually modify slightly or sometimes heavily the tutorial’s hardware setup and software configurations etc. By not just blindly following everything in the tutorial but trying new things make me learn things deeper and remember ideas harder.




I also study the tutorial’s software code twice, the first time separate code segments and explanations, and the second time, the complete code. I make sure I understand every statement of the complete code, before I modify it for my version of project. Now the complete code listing below.

import utime
from machine import Pin

motor1a = Pin(14, Pin.OUT)
motor1b = Pin(15, Pin.OUT)

def forward():
   motor1a.high()
   motor1b.low()

def backward():
   motor1a.low()
   motor1b.high()

def stop():
   motor1a.low()
   motor1b.low()

def test():
   forward()
   utime.sleep(2)
   backward()
   utime.sleep(2)
   stop()

for i in range(5):
	test()

(3) Now I following the above Tom’s Harware tutorial to use the Pico and a DC motor driver to move a motor.



(3.1) I am using the Chinese Windows 10 Thonny python IDE. This way everything is simple: (a) No external power supply is needed for now. Of course an external power supply for the motor is required later.

(3.2) I am using a simple “toggle the system LED” program to make sure the Pico hardware is setup more or less OK. The test program listing and a run program sample output is shown below.

# Program Name
#   toggle_pico_system_led_v03.py - tlfong01 2021jul01hkt1531
# Configuration
#   Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #3
# Intepreter
#   Micropython (Rapsberry Pi Pico)
# Program Function
#   Toggle Pico system LED at GPIO pin 15
# User Guide
#   Run code to toggle LED from On to Off or Off to On

from machine import Pin

systemLed = Pin(25, Pin.OUT)
systemLed.toggle()

# END

# Sample Output - tlfong01 2021jul01hkt1537
# MicroPython v1.14 on 2021-02-02; Raspberry Pi Pico with RP2040

# Type "help()" for more information.
# >>> %Run -c $EDITOR_CONTENT
# >>> 

(4) Now I using a modified version of Tom’s Hardware tutorial demo program to move a DC motor. To make things as simple, as cheap, and as newbie safe/proof as possible, for this prototyping stage, I am not using the very popular L298N motor driver, but MX1509, which is more efficient. And for the motor, not that expensive N20 gear motor with encoder, but the cheapest TT130 yellow toy motor.

The basic test of driving only one motor is OK. The program listing is shown below. More documentation will come later.


# Program Name
#   move_dc_motor_v01.py - tlfong01 2021jul01hkt1629
# Configuration
#   Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #3
# Intepreter
#   Micropython (Rapsberry Pi Pico)
# DC Motor
#   TT130 DC3~6V DC Gear Motor - AliEXpress US$1
#   https://www.aliexpress.com/item/32855311589.html
# DC Motor Driver
#   MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1
#   https://www.aliexpress.com/item/32688083107.html

# Program Function
#   Move DC motor forward, backward, and stop
# User Guide
#   Run code to move motor

import utime
from machine import Pin

motor1a = Pin(10, Pin.OUT)
motor1b = Pin(11, Pin.OUT)

def moveMotorForward():
    motor1a.high()
    motor1b.low()

def moveMotorBackward():
    motor1a.low()
    motor1b.high()

def stopMotor():
    motor1a.low()
    motor1b.low()

def test():
    print('  Begin move motor test()')
    moveMotorForward()
    utime.sleep(1)
    moveMotorBackward()
    utime.sleep(1)
    stopMotor()
    print('  End   test()')

for i in range(2):
    print('Test ', i)
    test()

# *** End of program ***

# *** Sample Output - tlfong01  2021jul01hkt1707
'''
>>> %Run -c $EDITOR_CONTENT
Test  0
  Begin move motor test()
  End   test()
Test  1
  Begin move motor test()
  End   test()
>>> 
'''


Pico, MX1508 motor driver, TT130 motor test video


References

(1) MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1

/ to continue, …

Pico Smart Vehicle, Part 2 - Controlling Two DC Motors.

  1. For Part 2 Controlling Two Motors, I will be using Piromoni Tiny2040 and N20 motors.


Testing Plan of Pico Smart Vehicle Part 2, Moving Two Motors

My testing/development plan is something like below.

(1) Modify the move one motor program to move two motors.

(2) Mount two TT130 motors on a TWD (TWo Wheel Driver) body, and write a small program to turn the robot car, first turn left, then turn right, then stop.

(3) Repeat Test (2) above, replacing TT130 motors by N2 motors.

/ to continue, …


References

(1) N20 DC3V/6V/12V RPM 7.5 to 6000 Micro Gear Motor - Amazon

It appears that I can no more edit/append my own post if it is two or three days old . So my workaround is “quote something and reply”. This makes my incremental short project progress report like a long blog, where other readers can comment and make suggestion in time. I am thinking of later using a blog site to report my progress, so the late readers can read a coherent article.

Update 2021jul04hkt1307

Errata: I just found out that this pi-top forum is using the Discourse software: Discourse (Software 2013 by Jeff Atwoodet al) - Wikipedia, which I never heard of, and therefore have misunderstood the working and restriction of newbie/low level users. Eg, I wrongly thought that I could not edit my own posts after a couple of days. Actually if I have gone up high levels, I can edit my own post within 30 days. Also Discourse seems to encourage “long form” input, and “infinite scrolling”, which I don’t have any idea about. I am used to StackExchange’s short form Q&A (Question and Answers), not discussion forums, so I need to change my mindset. Anyway, I need to know more about Discourse, before I can make up my mind how to present my project.


Anyway, now I am reading pi-top 4 DIY/Robotic Kit’s documents to get a rough idea of how their rovers look like, so my Smart Pico Vehicle (SPV v1.0) can follow their ideas somewhat. Here are the pi-top 4 references I am reading:

(1)

(2) pi-top 4 DIY

It appears that the pi-top 4 DIY edition is still a work in progress. So I think I can for now DIY my project Smart Pico Vehicle flexibly, perhaps also let other readers give brainstorming ideas.


So I am now reading more N20 motor documentation, and perhaps rapid prototyping a 4WD, ie Four Wheel Drive, with four N20 motors.


This gloomy lock down Sunday morning I was browsing casually around the forum and I was glad to see other users also making 4WDs, using pi-top Robotic kits. I found them using “Mecanum Wheels” which I read long time ago, but never have a chance playing with the real thing. I think these strange looking wheels need advanced, scary maths. So for now, humble me will stick to simple, plain wheels.


SPV (Smart Pico Vehicle) v0.1 Assembly Notes


/ to continue, …


References

(1) How to use motor drivers with H-bridge and PWM input, to control direction and speed of DC motors? - EE SE Q&A, Asked 2020jul16, Viewed 1k times

(2) 10 Software Packages You Can Add to Your Pi-Top [4] - Elijah Phillips, 2020jun15

(3) Further - Easy to Use STEM Software for Schools - pi-top.com

(4) Raspberry Pi Pico by Raspberry Pi - ClassRoomEshop Hongkong/Taiwan HK$40


I can no longer edit/append to my old post, so I continue by a reply.

I found Pi-top [4] Rover with ultrasonic sensor and camera too complicated. So I am thinking of using the simpler MMK (Motors and Motion Kit) as a reference.


In other words, I am starting off with a 2WD, and only later scale it up to a 4WD.


So I am assembling my Pico 2WD with 47mm diameter simple plain wheels. But I am jealous seeing other guys building 4WD’s with Mecannum wheels of 60mm diameter, I think. So I have decided to also use 60mm plain wheels, to make it easier to convert to Mecanum later.



Which Motor Driver for SPV (Smart Pico Vehicle) v0.1 2WD

Last time I used the simple and newbie friendly MX1508 motor driver for basic testing and found everything OK. MX1508 does not have easy PWM speed control features. So this time I am using a more sophisticated motor driver, the
Toshiba TB6612FNG Dual DC Motor Driver.


References

(1) Moebius 60mm Mecanum Wheel Omni Tire Compatible with TT Motor LEGOs for Arduino DIY Robot STEM Toy Parts - AliExpress US$25

(2) Pi-top [4] 4WD Mecannum - forum.pi-top.com 2021may25

(3) TB6612FNG Dual Motor Driver Carrier - Pololu Robotic and Electronics

(4) The Review of DC Motor Drivers: L298N, TB6612FNG and LV8406T - MakeBlock, 2014jan

(5) TB6612FNG Dual DC Motor Driver IC Datasheet, Toshiba, 2007jun

(6) TB6612FNG Dual DC Motor Driver Module - AliExpress, US$1.5

1 Like

Now I am testing the N20 motor with encoder. I found the tutorials below on encoders and kit very useful

I used 6V to power both N20 motors, and used a scope to display the quadrature motor encoder outputs A, B of both motors, as shown below.


This is the hardware setup of SPV 2WD v0.1, with two N20 motors and 60mm simple plain wheels.


Next step is to setup and test the Toshiba TB6612FNG Dual DC Motor Driver, to control the direction and speed (using PWM) of the SPV 2WD’s two N20 DC motors.


Appendices

Appendix A - TB6612FNG Programming Cheat Sheet



/ to continue, …


References

(1) Motor Encoders with Arduino - Andrew Kramer’s Research, 2016jan

(2) Magnetic Encoder Pair Kit for Micro Metal Gearmotors, 12 CPR, 2.7-18V (old version) - Pololu


Now I am assembling the TB6612FNG test rig board



Design note:

I have selected TB6612FNG which has separate PWM speed control for each motor, and not RDV8833 which does not have separate motor speed control.


TB6612FNG Programming Cheat Sheet

Next step is doing the wring, and try PWM speed control.


References

(1) pi-top/pi-top-Python-SDK - pi-top

(2) pi-top Python SDK (Preview)

(3) pi-top Python SDK - 6.3 Encoder Motor

Note from SDK 6.3: pi-top motor encoders use a built-in closed-loop control system, that feeds the readings from an encoder sensor to an PID controller. This controller will actively modify the motor’s current to move at the desired speed or position, even if a load is applied to the shaft.


Appendices

Appendix A - pi-top Python SDK (Preview) - DIY Rover API

  1. A simple, modular interface for interacting with a pi-top and its related accessories and components.

  2. This SDK aims to provide an easy-to-use framework for managing a pi-top. It includes a Python 3 package (pitop), with several modules for interfacing with a range of pi-top devices and peripherals It also contains CLI utilities, to interact with your pi-top using the terminal.

  3. Status: Active Development - This SDK is currently in active development.

  4. Supports all pi-top devices:

  5. Supports pi-top Maker Architecture (PMA)

  6. Supports all pi-top peripherals

  7. Backwards Compatibility

  8. The SDK is included out-of-the-box with pi-topOS.

  9. This library is installed as a Python 3 module called pitop. It includes several submodules that allow you to easily interact with most of the hardware inside a pi-top.

  10. Getting started docs

  11. 4.2. Robotics Kit: DIY Rover API

$ 4.2.1 Import modules
from pitop import
(
EncoderMotor,
ForwardDirection,
BrakingType
)

$ 4.2.2 Setup the motors for the rover configuration
motor_left = EncoderMotor(“M3”, ForwardDirection.CLOCKWISE)
motor_right = EncoderMotor(“M0”, ForwardDirection.COUNTER_CLOCKWISE)
motor_left.braking_type = BrakingType.COAST
motor_right.braking_type = BrakingType.COAST

$ 4.2.3 Define some functions for easily controlling the rover

  def drive(target_rpm: float):
      print("Start driving at target", target_rpm, "rpm...")
      motor_left.set_target_rpm(target_rpm)
      motor_right.set_target_rpm(target_rpm)

  def stop_rover():
      print("Stopping rover...")
      motor_left.stop()
      motor_right.stop()

 def turn_left(rotation_speed: float):
     print("Turning left...")
     motor_left.stop()
     motor_right.set_target_rpm(rotation_speed)

 def turn_right(rotation_speed: float):
     print("Turning right...")
     motor_right.stop()
     motor_left.set_target_rpm(rotation_speed)

 $ 4.2.4 Start a thread to monitor the rover

  def monitor_rover():
      while True:
      print("> Rover motor RPM's (L,R):", round(motor_left.current_rpm, 2), round(motor_right.current_rpm, 2))
      sleep(1)

  monitor_thread = Thread(target=monitor_rover, daemon=True)
  monitor_thread.start()
  # Go!

  rpm_speed = 100
  for _ in range(4):
      drive(rpm_speed)
      sleep(5)

  turn_left(rpm_speed)
  sleep(5)

  stop_rover()

2[quote=“tlfong01, post:7, topic:924”]
TB6612FNG Programming Cheat Sheet

Next step is doing the wring, and try PWM speed control.
[/quote]


Now that I have skimmed the Pi-Top Python 3 SDK’s DIY Rover APIs doc, the time has come for me to write little test programs and APIs for the TB6612FNG based SPV (Smart Pico Vehicle) 0.1 2WD.

I will be using pico and Windows 10 Thonny Python 3.9.3 IDE. In other words, no Rpi4B or Pi-Top [4] will be used in this prototyping stage.



Now I am testing the TB6612FNG Dual DC Motor driving two N20 gear motors and finding everything OK.

The TB6612FNG power and control signals are summarized below.

1. Motor power = 6V, connected to pins VM and Ground

2. Logic powr = 5V, connected to pins Vcc and Ground

3. Standby logical control = H (no stand by, normal operation)

4. PWM signal = High (100% duty cycle, full speed)

5. IN1 = Low, IN2 = High (CW)

6. AO1, AO2 connected to N20 gear motor 1

7. BO1, BO2 connected to N20 gear motor 2 

Testing Results

Both N20 motor turn CW, as expected.



YouTube video of TB6612FNG Dual DC Motor Driver Driving Two N20 Motors


Next step

Next step is to write a little python program for Pico to control the TB6612FNG motor driver control the two N20 gear motors to move the 2WD forward, backward, turn left, turn right etc.

Desgin note: pi-top [4] python SDK has APIs of similar vehicle moving functions, but I think they are using the servo motor to steer the vehicle to turn left or right. My SPV 2WD APIs to write next are using the two motors’ differential speeds to turn left or right.


/ to continue, …


Now I have stacked all three modules to start python programming:

(1) Motor (N20 x 2), 

(2) Motor Driver (TB66112FNG x 1)

(3) MCU (Pico x 1)


/ to continue, …

I am reading my old post to refresh memory on how to drive a DC motor. Last time I followed Tom’s Hardware tutorial to use Pico and MX1508 motor driver to drive only one motor TT130. Everything goes well.

This time I am modifying Tom’s Hardware’s python program to control my 2WD’s two N20 motors. I am also using the TB6612FNG motor controller. As I understand, for my simple applications, any common driver (L298N, DRV8833), and common motors TT130, N20) can be used.


Now I am testing moving only one N20 motor, and using Pico to control only IN1, IN2. The Standby and PWM signals are manually hardwired.



And the full program listing.

# Program Name
#   move_dc_motor_v02.py - tlfong01 2021jul10hkt1132
# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
# Configuration
#   Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
# Intepreter
#   Micropython (Rapsberry Pi Pico)
# DC Motor
#   (a) TT130 DC3~6V DC Gear Motor - AliEXpress US$1
#       https://www.aliexpress.com/item/32855311589.html
#   (b) N20 Gear Motor
#   
# DC Motor Driver
#   (a) MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1
#         https://www.aliexpress.com/item/32688083107.html
#   (b) TB6612FNG Dual DC Motor Driver

# Program Function
#   Move DC motor forward, backward, and stop

# User Guide
#   (a) Connect PWMA to High (Vcc) 
#   (b) Connect Standby to High
#   (c) Run code to move motor forward, backward, and stop

import utime
from machine import Pin

motor1a = Pin(10, Pin.OUT)
motor1b = Pin(11, Pin.OUT)

def moveMotorForward():
    motor1a.low()
    motor1b.high()

def moveMotorBackward():
    motor1a.high()
    motor1b.low()

def stopMotor():
    motor1a.low()
    motor1b.low()

def test():
    print('  Begin move motor test()')
    moveMotorForward()
    utime.sleep(1)
    moveMotorBackward()
    utime.sleep(1)
    stopMotor()
    print('  End   test()')

for i in range(2):
    print('Test ', i)
    test()

# *** End of program ***

# *** Sample Output - tlfong01  2021jul01hkt1707
'''
>>> %Run -c $EDITOR_CONTENT
Test  0
  Begin move motor test()
  End   test()
Test  1
  Begin move motor test()
  End   test()
>>> 
'''

Now I have written motor driver dictionaries and tested them OK, to make it easy to scale up 1WD to 2WD for two motors, and later 4WD for four motors. The program is fully listed below:

$ Program Name
$ move_dc_motor_v04.py - tlfong01 2021jul10hkt1558
$ Reference
$ Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
$ Making a Rpi Pico Based Smart Vehicle
$ Configuration
$ Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
$ Intepreter
$ Micropython (Rapsberry Pi Pico)
$ DC Motor
$ (a) TT130 DC3~6V DC Gear Motor - AliEXpress US$1
$ https://www.aliexpress.com/item/32855311589.html
$ (b) N20 Gear Motor x 2
$
$ DC Motor Driver
$ (a) MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1
$ https://www.aliexpress.com/item/32688083107.html
$ (b) TB6612FNG Dual DC Motor Driver

$ Program Function
$ Move two N20 DC motors forward, backward, and stop

$ User Guide
$ (a) Configuration
$ GP10 - Motor1 IN1
$ GP11 - Motor1 IN2
$ GP12 - Motor1 PWMA
$ GP13 - Motor2 IN1
$ GP14 - Motor2 IN2
$ GP15 - Motor2 PWMB
$ GP9 - Motor1, 2, Standby
$ (b) Run program to move in sequence, two motors forward, backward, and stop

import utime
from machine import Pin

$ Configuration

multiMotorDriverDict01 = {
‘Title’ : ‘Motor Driver Dict For 4WD’,
‘STBY’ : 9,
‘1’ : {‘IN1’ : 10,
‘IN2’ : 11,
‘PWM’ : 12,
},
‘2’ : {‘IN1’ : 13,
‘IN2’ : 14,
‘PWM’ : 15,
},
‘3’ : {‘IN1’ : 0,
‘IN2’ : 0,
‘PWM’ : 0,
},
‘4’ : {‘IN1’ : 0,
‘IN2’ : 0,
‘PWM’ : 0,
},
}

multiMotorDriverDictDict = {
‘1’: multiMotorDriverDict01,
‘1’: multiMotorDriverDict01,
}

def setupMotor(multiMotorDriverDictDictNum, motorDriverNum):
motorDriverDict = multiMotorDriverDictDict[str(multiMotorDriverDictDictNum)]

in1PinNum = motorDriverDict[str(motorDriverNum)]['IN1']
in2PinNum = motorDriverDict[str(motorDriverNum)]['IN2']
pwmPinNum = motorDriverDict[str(motorDriverNum)]['PWM']

print('In1PinNum =', in1PinNum)
print('In2PinNum =', in2PinNum)
print('pwmPinNum =', pwmPinNum)

in1Pin = Pin(in1PinNum, Pin.OUT)
in2Pin = Pin(in2PinNum, Pin.OUT)
pwmPin = Pin(pwmPinNum, Pin.OUT)

picoMotorDriverControlPinDict = {'IN1': in1Pin, 'IN2': in2Pin, 'PWM': pwmPin}

return picoMotorDriverControlPinDict

def moveMotorForward(motorDriverDict):
in1Pin = motorDriverDict[‘IN1’]
in2Pin = motorDriverDict[‘IN2’]
pwmPin = motorDriverDict[‘PWM’]

in1Pin.low()
in2Pin.high()
pwmPin.high()    
return

def moveMotorBackward(motorDriverDict):
in1Pin = motorDriverDict[‘IN1’]
in2Pin = motorDriverDict[‘IN2’]
pwmPin = motorDriverDict[‘PWM’]

in1Pin.high()
in2Pin.low()
pwmPin.high()    
return

def stopMotor(motorDriverDict):
in1Pin = motorDriverDict[‘IN1’]
in2Pin = motorDriverDict[‘IN2’]
pwmPin = motorDriverDict[‘PWM’]

in1Pin.low()
in2Pin.low()
pwmPin.high()    
return

$ Main $

$ Test Motors #1, #2

motorDictDictNum = 1
motorNumList = [1, 2]

for motorNum in motorNumList:
motorDriverDict = setupMotor(motorDictDictNum, motorNum)
moveMotorForward(motorDriverDict)
utime.sleep(1)
moveMotorBackward(motorDriverDict)
utime.sleep(1)
stopMotor(motorDriverDict)

$ End of program


Next step is writing functions to turn vehicle right and left.

/ to continue, …

The turn right/left functions are easy to write and test. The ‘#’ symbol crashes with the forum editor. So I replaced the symbol to ‘$’

$ Program Name
$   move_dc_motor_v08.py - tlfong01 2021jul10hkt1625
$ Reference
$   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
$   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
$ Configuration
$   Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port $4
$ Intepreter
$   Micropython (Rapsberry Pi Pico)
$ DC Motor
$   (a) TT130 DC3~6V DC Gear Motor - AliEXpress US$1$       
$   (b) N20 Gear Motor x 2
$   
$ DC Motor Driver
$   (a) MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1
$         https://www.aliexpress.com/item/32688083107.html
$   (b) TB6612FNG Dual DC Motor Driver

$ Program Functions
$   (a) Move two N20 DC motors forward, backward, and stop.
$   (b) Move left motor forward, right motor backward, so to turn vehicle right.
$   (c) Similarly, left motor backward, right motor forward, so to turn vehicle left

$ User Guide
$   (a) Configuration
$       GP10 - Motor1 IN1
$       GP11 - Motor1 IN2
$       GP12 - Motor1 PWMA
$       GP13 - Motor2 IN1
$       GP14 - Motor2 IN2
$       GP15 - Motor2 PWMB
$       GP9  - Motor1, 2, Standby
$   (b) Run program to move in sequence, two motors forward, backward, and stop

import utime
from machine import Pin

$ Configuration

multiMotorDriverDict01 = {
    'Title'   : 'Motor Driver Dict For 4WD',
    'STBY'    : 9,
    '1' : {'IN1'    : 10,
           'IN2'    : 11,
           'PWM'    : 12,
          },
    '2' : {'IN1'    : 13,
           'IN2'    : 14,
           'PWM'    : 15,
          },
    '3' : {'IN1'    : 0,
           'IN2'    : 0,
           'PWM'    : 0,
          },
    '4' : {'IN1'    : 0,
           'IN2'    : 0,
           'PWM'    : 0,
          },
    }

multiMotorDriverDictDict = {
    '1': multiMotorDriverDict01,
    '1': multiMotorDriverDict01,
    }
    
$ *** 
 
def setupMotor(multiMotorDriverDictDictNum, motorDriverNum):
    motorDriverDict = multiMotorDriverDictDict[str(multiMotorDriverDictDictNum)]
    
    in1PinNum = motorDriverDict[str(motorDriverNum)]['IN1']
    in2PinNum = motorDriverDict[str(motorDriverNum)]['IN2']
    pwmPinNum = motorDriverDict[str(motorDriverNum)]['PWM']
    
    print('In1PinNum =', in1PinNum)
    print('In2PinNum =', in2PinNum)
    print('pwmPinNum =', pwmPinNum)
    
    in1Pin = Pin(in1PinNum, Pin.OUT)
    in2Pin = Pin(in2PinNum, Pin.OUT)
    pwmPin = Pin(pwmPinNum, Pin.OUT)
    
    picoMotorDriverControlPinDict = {'IN1': in1Pin, 'IN2': in2Pin, 'PWM': pwmPin}
    
    return picoMotorDriverControlPinDict

def moveMotorForward(motorDriverDict):
    in1Pin = motorDriverDict['IN1']
    in2Pin = motorDriverDict['IN2']    
    pwmPin = motorDriverDict['PWM']
    
    in1Pin.low()
    in2Pin.high()
    pwmPin.high()    
    return

def moveMotorBackward(motorDriverDict):
    in1Pin = motorDriverDict['IN1']
    in2Pin = motorDriverDict['IN2']    
    pwmPin = motorDriverDict['PWM']
    
    in1Pin.high()
    in2Pin.low()
    pwmPin.high()    
    return

def stopMotor(motorDriverDict):
    in1Pin = motorDriverDict['IN1']
    in2Pin = motorDriverDict['IN2']    
    pwmPin = motorDriverDict['PWM']
    
    in1Pin.low()
    in2Pin.low()
    pwmPin.high()    
    return

def turnVehicleRight(motorDriverDictLeft, motorDriverDictRight):
    moveMotorForward(motorDriverDictLeft)
    moveMotorBackward(motorDriverDictRight)
    return    
    
def turnVehicleLeft(motorDriverDictLeft, motorDriverDictRight):
    moveMotorForward(motorDriverDictRight)
    moveMotorBackward(motorDriverDictLeft)
    return        
    
$ Main test functions

def testSequentiallyMoveTwoMotorsForwardBackwardAndStop():
    motorDictDictNum = 1
    motorNumList = [1, 2]

    for motorNum in motorNumList:
        motorDriverDict = setupMotor(motorDictDictNum, motorNum)
        moveMotorForward(motorDriverDict)
        utime.sleep(1)
        moveMotorBackward(motorDriverDict)
        utime.sleep(1)
        stopMotor(motorDriverDict)    
    return

def testTurnVehicleRightOneSecondThenLeftOneSecond():
    motorDictDictNum = 1       
    leftMotorDriverDict = setupMotor(motorDictDictNum, 1)    
    rightMotorDriverDict = setupMotor(motorDictDictNum, 2)
    
    turnVehicleRight(leftMotorDriverDict, rightMotorDriverDict)    
    utime.sleep(2)
    turnVehicleLeft(leftMotorDriverDict, rightMotorDriverDict)    
    utime.sleep(2)
    stopMotor(leftMotorDriverDict)
    stopMotor(rightMotorDriverDict)
    

$ Main $

$ testSequentiallyMoveTwoMotorsForwardBackwardAndStop()

testTurnVehicleRightOneSecondThenLeftOneSecond()

$ End of program

Now that I have completed the point to point wiring of Pico GB6~9 pins to the two N20 encoder outputs. Next step is to write a Pico microPython to do all motor control in software.


I forgot I need first to do some calibration of the N20 motor encoder signal vs speed (rpm) off line (ie no Pico software). So I disconnect the Pico GP signal and use manual jumper wires to give the control signals, and use my 50MHz scope to check the motor speed. as shown below.


Now I am analysing the N20 Motor 1 Encoder signal Output 1, and see if we can calculate the motor speed from this signal. Avery rough formula is 600uS ~= 30 rpm. Next step is is see how to do the time stamping use micro python.



MicroPython utime – time related functions
https://docs.micropython.org/en/latest/library/utime.html

Description - The utime module provides functions for getting the current time and date, measuring time intervals, and for delays.


I read the docs for utime and found it similar to Thonny python, except a little bet smaller. I found user friendly examples on how to use the utime (now I know the “u” of utime means “micro”! :grinning:). One good such example has limit of time calculation less than 500us, but N20 motor’s timing is of the order of 600uS, in other words, just NOT make. I am too lazy to modify my demo programs to it my N20 applications. So lazy me go to Tom’s Hardware for help. I remember Tom;s Hardware has developed a big number of tutorials for newbies, as listed below.


Tom’s Hardware Tutorials on Rpi Pico

In the relatively short time that the Pico has been on the market, the Raspberry Pi community has already developed a ton of resources. At Tom’s Hardware, we’ve been publishing our fair share of Pico how-tos, which you can find below.

Tutorial #7 on DC motors is useful for my N20 motor 2WD project here. There is also a useful demo program on the use of buttons. The critical statements are highlighted in pink.


Resuming Long Stalled SPV (Smart Pico Vehicle) Project

Part 1

This project has be stalled for over a month, and I have forgotten what I did last time. So I need now to refresh my memory. The first thing is to make sure the basic Pico hardware still running OK. My first test procedure is to run the “toggle system led program”. Luckily all looks well. Below is the updated toggle led program, with sample output.

# Program Name
#   toggle_pico_system_led_v04.py - tlfong01 2021aug16hkt1606
# Configuration
#   Thonny 3.3.3, Acer Intel CORE i5 Chinese Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
# Intepreter
#   Micropython (Rapsberry Pi Pico)
# Program Function
#   Toggle Pico system LED at GPIO pin 15 (run program to switch on LED, run again to switch off)
# User Guide
#   Run code to toggle LED from On to Off or Off to On

from machine import Pin

systemLed = Pin(25, Pin.OUT)

print('Testing Toggling Pcio System LED, v0.4  tlfong01  2021aug16hkt1613')

systemLed.toggle()

# END

# Sample Output - tlfong01 2021aug16hkt1614
'''
>>> %Run -c $EDITOR_CONTENT
Testing Toggling Pcio System LED, v0.4  tlfong01  2021aug16hkt1613
>>> %Run -c $EDITOR_CONTENT
Testing Toggling Pcio System LED, v0.4  tlfong01  2021aug16hkt1613
>>> 
'''

Part 2 - Checking the 2WD Wiring

I vaguely remember that I first tested one TT130 toy motor with the very simple MX1508 driver, as described by the Tom’s Hardware tutorial. Then I moved on to used TB6616 motr driver, with two N20 motors with speed encoders. I vaguely remember that I used Pico GPIO to read the one of the two encoder output signals, and got a rough idea of the motor speed vs encoder signal periods. So I need to resume from this point. But before that, I need to make sure the motor driver and motor can still move. First step is check out the hardware setup and wiring, and run the basic test program.

Actually I forgot the details of the hardware. So I read my old post to refresh my memory: Making a Rpi Pico Based Smart Vehicle.

The following image is a good memory refresher:

image


Part 3 - Manual Jumper Wire Motor Moving Testing

Now I am using the following cheat sheet to refresh my memory on how to use jumper wires to off line testing moving motors

To move Motor 1 CW, Motor 2 CCW, I am using the following conifg/wiring

  1. IN1, IN2 (Yellow, Blue) to Low High or High Low

  2. PWM (Purple) = High

  3. StdBy (Brown) = High


Part 4 - Using MicroPython to test moving motors

Now I am using the following Pico MicroPython program is move one motor forward, backward, and stop, and found everything OK.

# Program Name
#   move_one_motor_v01.py - tlfong01 2021aug16hkt2218
# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
# Configuration
#   Thonny 3.3.3, Windows 10 (64-bit), Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
# Intepreter
#   Micropython (Rapsberry Pi Pico)
# DC Motor
#   (a) TT130 DC3~6V DC Gear Motor - AliEXpress US$1
#       https://www.aliexpress.com/item/32855311589.html
#   (b) N20 Gear Motor
#   
# DC Motor Driver
#   (a) MX1508 2~10V, 1.5A, Dual H-Bridge DC Motor Driver - AliExpress US$1
#         https://www.aliexpress.com/item/32688083107.html
#   (b) TB6612FNG Dual DC Motor Driver

# Program Function
#   Move DC motor forward, backward, and stop

# User Guide
#   (a) Connect PWMA (purple), PWMB (purple) to High (Vcc)
#   (b) Connect Standby (brown) to High
#   (c) Run program to move motor forward, backward, and stop

import utime
from machine import Pin

motor1a = Pin(10, Pin.OUT)
motor1b = Pin(11, Pin.OUT)

def moveMotorForward():
    motor1a.low()
    motor1b.high()

def moveMotorBackward():
    motor1a.high()
    motor1b.low()

def stopMotor():
    motor1a.low()
    motor1b.low()

def test():
    print('  Begin move motor test()')
    moveMotorForward()
    utime.sleep(1)
    moveMotorBackward()
    utime.sleep(1)
    stopMotor()
    print('  End   test()')

for i in range(2):
    print('Test ', i)
    test()

# *** End of program ***

# *** Sample Output - tlfong01  2021jul01hkt1707
'''
>>> %Run -c $EDITOR_CONTENT
Test  0
  Begin move motor test()
  End   test()
Test  1
  Begin move motor test()
  End   test()
>>> 
'''

Part 5 - Moving two motors

Now that my little demo microPython program moving one N20 motor goes well, I am now upgrading it to move two motors. Now I have a problem of mixing up the getting complicated jumper wires, and therefore find it difficult to do troubleshooting. So I need to do some proper documentation, starting with the photo below.

Now the time has come to use Pico MicroPython to move two motors. The following program is tested OK.

# Program Name
#   move_two_motors_v05.py - tlfong01 2021aug17hkt1607
# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Intel COIRE i5 PC Chinese Windows 10 (64-bit), Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
# DC Motor
#   N20 1:48 6V DC Gear Motor with quadrature encoder signals A, B
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Program Function
#   Move Two DC motors forward, backward, and stop

# User Guide
#   (a) Connect PWMA (purple), PWMB (purple) to High (Vcc)
#   (b) Connect Standby (brown) to High (Vcc)
#   (c) Pico GP 10, 11 (Blk, Bwn) to Motor Driver AIN1, AIN2 (Yel, Blu)
#   (d) Pico GP 12, 13 (Red, Orn) to Motor Driver BIN1, BIN2 (Yel, Blu)
#   (c) Run program to move motor forward, backward, and stop

import utime
from machine import Pin

motorDict01 = {
              '1': {'IN1': 10, 'IN2': 11},
              '2': {'IN1': 12, 'IN2': 13},
              '3': {'IN1':  0, 'IN2':  0},
              '4': {'IN1':  0, 'IN2':  0},
              }

motorDictDict = {
                '1': motorDict01,
                '2': motorDict01,                
                }

def setupMotor(motorDictNum, motorNum):
    motorDict = motorDictDict[str(motorDictNum)] 
    motorIn1 = Pin(motorDict[str(motorNum)]['IN1'], Pin.OUT)
    motorIn2 = Pin(motorDict[str(motorNum)]['IN2'], Pin.OUT)    
    motorInList = [motorIn1, motorIn2]   
    return motorInList

def moveMotorForward(motorDictNum, motorNum):
    motorInList = setupMotor(motorDictNum, motorNum)
    motorInList[0].high()
    motorInList[1].low()  
    return motorInList

def moveMotorBackward(motorDictNum, motorNum):
    motorInList = setupMotor(motorDictNum, motorNum)
    motorInList[0].low()
    motorInList[1].high()   
    return motorInList

def moveMotorStop(motorDictNum, motorNum):
    motorInList = setupMotor(motorDictNum, motorNum)
    motorInList[0].low()
    motorInList[1].low()      
    return motorInList

# *** Tests ***

moveMotorForward(motorDictNum = 1, motorNum = 1)
utime.sleep(1)
moveMotorBackward(motorDictNum = 1, motorNum = 1)
utime.sleep(1)
moveMotorStop(motorDictNum = 1, motorNum = 1)

moveMotorForward(motorDictNum = 1, motorNum = 2)
utime.sleep(2)
moveMotorBackward(motorDictNum = 1, motorNum = 2)
utime.sleep(2)
moveMotorStop(motorDictNum = 1, motorNum = 2)

# *** End of program ***

# *** Sample Output - tlfong01  2021jul01hkt1707
'''
'''

Part 6 - Moving two motors with program controlling StandBy and PWM

The following program moving two motor and program control StandBy and PWM is tested OK.

# Program Name
#   move_two_motors_v09.py - tlfong01 2021aug17hkt2014
# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Intel COIRE i5 PC Chinese Windows 10 (64-bit), Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
# DC Motor
#   N20 1:48 6V DC Gear Motor with quadrature encoder signals A, B
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Program Function
#   Move Two DC motors forward, backward, and stop

# User Guide
#   (a) Connect PWMA (purple), PWMB (purple) to High (Vcc)
#   (b) Connect Standby (brown) to High (Vcc)
#   (c) Pico GP 10, 11 (Blk, Bwn) to Motor Driver AIN1, AIN2 (Yel, Blu)
#   (d) Pico GP 12, 13 (Red, Orn) to Motor Driver BIN1, BIN2 (Yel, Blu)
#   (c) Run program to move motor forward, backward, and stop

import utime
from machine import Pin

driverDict01 = {
              'Title' : 'TB6612FNG Dual DC Motor Driver',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0},
              }

driverDictDict = {
                '1': driverDict01,
                '2': driverDict01,                
                }

def setupDriver(driverDictNum):
    driverDict = driverDictDict[str(driverDictNum)]
    standBy = Pin(driverDict['STANDBY'], Pin.OUT)
    standBy.high()    
    return
    
def setupMotor(driverDictNum, motorNum):
    driverDict = driverDictDict[str(driverDictNum)]
    motorIn1 = Pin(driverDict[str(motorNum)]['IN1'], Pin.OUT)
    motorIn2 = Pin(driverDict[str(motorNum)]['IN2'], Pin.OUT)
    motorPwm = Pin(driverDict[str(motorNum)]['PWM'], Pin.OUT)
    motorControlPinList = [motorIn1, motorIn2, motorPwm]
    return motorControlPinList

def moveMotorForward(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].low()
    motorControlPinList[1].high()
    motorControlPinList[2].high()        
    return

def moveMotorBackward(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].high()
    motorControlPinList[1].low()
    motorControlPinList[2].high()     
    return

def moveMotorStop(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].low()
    motorControlPinList[1].low()
    motorControlPinList[2].high()     
    return

# *** Motor Test Functions ***

def motorTest01(driverDictNum, motorNum):
    # *** Setup Driver ***
    setupDriver(driverDictNum)
    # *** Move motor forward ***
    moveMotorForward(driverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor backward ***
    moveMotorBackward(driverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor stop ***
    moveMotorStop(driverDictNum, motorNum)    
    return    

# *** Main Tests ***

# Setup motor driver, Move Motor 1 forward, backward, stop
motorTest01(driverDictNum = 1, motorNum = 1)

# Setup motor driver, Move Motor 2 forward, backward, stop
motorTest01(driverDictNum = 1, motorNum = 2)

# *** End of program ***

# *** Sample Output - tlfong01  2021jul01hkt1707
'''
>>> %Run -c $EDITOR_CONTENT
'''

Part 6 - Reading Encoder Signals

The following program give N20 1:100 gear speed of around 400rpm

# Program Name
#   measure_motor_speed_v09.py - tlfong01 2021aug18hkt1120
# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Intel COIRE i5 PC Chinese Windows 10 (64-bit), Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# DC Motor
#   N20 1:100 6V DC Gear Motor with quadrature encoder signals A, B
#   N20 Gear Motor Spec (6V gear 1:100 no load speed = 300 rpm (https://www.pololu.com/search/compare/173)
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Program Function
#   Measure speed of DC motor N20 with quadrature encoder

import utime
from machine import Pin

driverDict01 = {
              'Title' : 'TB6612FNG Dual DC Motor Driver',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0},
              }

driverDictDict = {
                '1': driverDict01,
                '2': driverDict01,                
                }

def setupDriver(driverDictNum):
    driverDict = driverDictDict[str(driverDictNum)]
    standBy = Pin(driverDict['STANDBY'], Pin.OUT)
    standBy.high()    
    return
    
def setupMotor(driverDictNum, motorNum):
    driverDict  = driverDictDict[str(driverDictNum)]
    motorIn1    = Pin(driverDict[str(motorNum)]['IN1'], Pin.OUT)
    motorIn2    = Pin(driverDict[str(motorNum)]['IN2'], Pin.OUT)
    motorPwm    = Pin(driverDict[str(motorNum)]['PWM'], Pin.OUT)
    motorEncode = Pin(driverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) 
    motorControlPinList = [motorIn1, motorIn2, motorPwm, motorEncode]
    return motorControlPinList

def moveMotorForward(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].low()
    motorControlPinList[1].high()
    motorControlPinList[2].high()        
    return

def moveMotorBackward(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].high()
    motorControlPinList[1].low()
    motorControlPinList[2].high()     
    return

def moveMotorStop(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)    
    motorControlPinList[0].low()
    motorControlPinList[1].low()
    motorControlPinList[2].high()     
    return

def readMotorEncodeValue(driverDictNum, motorNum):
    motorControlPinList = setupMotor(driverDictNum, motorNum)       
    encodeValue = motorControlPinList[3].value()
    return encodeValue

# *** Motor Test Functions ***

def testMoveMotor01(driverDictNum, motorNum):
    # *** Setup Driver ***
    setupDriver(driverDictNum)
    # *** Move motor forward ***
    moveMotorForward(driverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor backward ***
    moveMotorBackward(driverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor stop ***
    moveMotorStop(driverDictNum, motorNum)    
    return

def testReadMotorEncodeValue01(driverDictNum, motorNum):
    # *** Setup Driver ***
    setupDriver(driverDictNum)
    # *** Read motor encode value ***
    print('Move motor by hand and read encode value every second, ...')
    for secondCount in range(100):
        encodeValue = readMotorEncodeValue(driverDictNum, motorNum)
        print('  motor encode value =', encodeValue)
        utime.sleep(1)
    return  

def testReadMotorEncodeValue02(driverDictNum, motorNum):
    # *** Setup Driver ***
    setupDriver(driverDictNum)
    
    # *** Move motor forward ***
    moveMotorForward(driverDictNum, motorNum)       
    
    # *** Read motor encode value ***
    print('Move motor by hand and read encode value every second, ...')
    for secondCount in range(100):
        encodeValue = readMotorEncodeValue(driverDictNum, motorNum)
        print('  motor encode value =', encodeValue)
        utime.sleep(1)
    return  

def testMeasureMotorSpeed01(driverDictNum, motorNum):
    
    # *** Setup Driver ***
    setupDriver(driverDictNum)
    
    # *** Move motor forward ***
    moveMotorForward(driverDictNum, motorNum)       

    # *** Measure Motor Speed ***
    
    # *** Start counting 10 revolutions ***
    usTicks1 = utime.ticks_us()
    
    # *** Find lapse time of 100 revolutions ***
    revCount = 0    
    while revCount < 10:
        if readMotorEncodeValue(driverDictNum, motorNum) == 0:
            revCount = revCount + 1
        utime.sleep(0.000001)            
    
    usTicks2  = utime.ticks_us()
    
    rev10Us    = utime.ticks_diff(usTicks2, usTicks1)
    revUs      = int(rev10Us / 10)
    rps        = int(1000000 / revUs)
    rpmRaw     = int(rps * 60)
    rpmGear100 = int(rpmRaw / 100)
  
    print('revUs             =', revUs)
    print('rps               =', rps)          
    print('rpm raw           =', rpmRaw)
    print('rpm 100           =', rpmGear100)    
    
    moveMotorStop(driverDictNum, motorNum)    
    
    return

# *** Main Tests ***

# *** Test move motor forward, backward, stop ***
# testMoveMotor01(driverDictNum = 1, motorNum = 1)
# testMoveMotor01(driverDictNum = 1, motorNum = 2)

# *** Test measure motor speed ***
testMeasureMotorSpeed01(driverDictNum = 1, motorNum = 1)

# ***
#moveMotorStop(driverDictNum = 1, motorNum = 1) 

# *** End of program ***

# *** Sample Output - tlfong01  2021aug18hkt1120
'''
>>> %Run -c $EDITOR_CONTENT
revUs               = 1250
rps                   = 800
rpm raw           = 48000
rpm 100           = 480
>>> 
'''

Part 7 - Using PWM to control speed of N20 Motor

Now I have written the following program to test basic PWM functions. I need to use my scope to display the 1kHz, 50% duty cycle waveform, before trying to use this PWM signal to control the speed of the N20 motor.

# *** PWM Functions ***

def setupPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
motorDriverDict = motorDriverDictDict[str(motorDriverDictNum)]
pwmPinNum = motorDriverDict[str(motorNum)]['PWM']
motorPwm = PWM(Pin(pwmPinNum))    
motorPwm.freq(pwmFreq)
motorPwm.duty_u16(int(65536 / 100) * dutyCycle)
print('PWM freq  (Hz) =', motorPwm.freq())
print('PWM duty  (%)  =', int((motorPwm.duty_u16() / 65536) * 100))
print('PWM width (ns) =', motorPwm.duty_ns())    
return          

# *** Motor Test Functions ***

def testMoveMotor01(motorDriverDictNum, motorNum):
# *** Setup Driver ***
setupMotorDriver(motorDriverDictNum)
# *** Move motor forward ***
moveMotorForward(motorDriverDictNum, motorNum)
utime.sleep(1)
# *** Move motor backward ***
moveMotorBackward(motorDriverDictNum, motorNum)
utime.sleep(1)
# *** Move motor stop ***
moveMotorStop(motorDriverDictNum, motorNum)    
return

def testMeasureMotorSpeed01(motorDriverDictNum, motorNum):
measureMotorSpeed(motorDriverDictNum, motorNum)    
return

def testSetupMotorPwm01(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
setupPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)    
return

# *** Main Tests ***

# *** Test move motor forward, backward, stop ***
# print('Test Motor #1 Move Forward, Backward, Stop') 
# testMoveMotor01(motorDriverDictNum = 1, motorNum = 1)

# print('Test Motor #2 Move Forward, Backward, Stop') 
# testMoveMotor01(motorDriverDictNum = 1, motorNum = 2)

# *** Test measure motor speed ***
# print('\nTest Measure Motor #1 Speed') 
#testMeasureMotorSpeed01(motorDriverDictNum = 1, motorNum = 1)

# *** Test PWM Funcions ***
print('\nTest PWM Funcions')
testSetupMotorPwm01(motorDriverDictNum = 1, motorNum =1, pwmFreq = 1000, dutyCycle = 50)


# *** End of program ***

# *** Sample Output - tlfong01  2021aug18hkt1120
'''
>>> %Run -c $EDITOR_CONTENT
revUs             = 1250
rps               = 800
rpm raw           = 48000
rpm 100           = 480
>>> 
'''

'''
>>> %Run -c $EDITOR_CONTENT
Test PWM Funcions
PWM freq  (Hz) = 1000
PWM duty  (%)  = 49
PWM width (ns) = 499728
>>>
'''

I have modified my old program to use PWM pin to control the speed of the motor (earlier I only used PWM pin as high/low logical signal to turn motor fully on or off. Now I am using PWM 1kHz and duty cycle 0~100% to control speed of motor. I found, as expected, motor moves slowly with duty cycle small and quickly with high duty cycle. However, I found the speed measurement function not reliable, mainly because I use counting 100 logical low level signals/revolutions from encoder, and I did not set the between low levels appropriately. Now I am thinking of using interrupt to detect number of high to low falling edges in a specified time period to calculate the motor speed. This way the measurement of speed should be more accurate.

Anyway, I am listing my old speed measurement program to do interrupt driven method to compare and contrast.

Note - I found that this long post exceeds the forum’s 32k words limit. So I am deleting some functions to fit the bill.

# Program Name
#   measure_motor_speed_v16.py - tlfong01 2021aug19hkt1132
#   docs and functions deleted

import utime
from machine import Pin, PWM

# *** Configuration ***

motorDriverDict01 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0},
              }

motorDriverDictDict = {
                '1': motorDriverDict01,
                '2': motorDriverDict01,                
                }

# *** Read Motor Encoder, Measure Motor Speed ***

def readMotorEncodeValue(motorDriverDictNum, motorNum):
    motorControlPinList = setupMotor(motorDriverDictNum, motorNum)       
    encodeValue = motorControlPinList[3].value()
    return encodeValue

def measureMotorSpeed(motorDriverDictNum, motorNum):
    # *** Setup Motor Driver ***
    setupMotorDriver(motorDriverDictNum)
    
    # *** Move motor forward ***
    moveMotorForward(motorDriverDictNum, motorNum)       

    # *** Measure Motor Speed ***
    
    # *** Start counting 10 revolutions ***
    usTicks1 = utime.ticks_us()
    
    # *** Find lapse time of 100 revolutions ***
    revCount = 0    
    while revCount < 10:
        if readMotorEncodeValue(motorDriverDictNum, motorNum) == 0:
            revCount = revCount + 1
        utime.sleep(0.000001)            
    
    usTicks2  = utime.ticks_us()
    
    rev10Us    = utime.ticks_diff(usTicks2, usTicks1)
    revUs      = int(rev10Us / 10)
    rps        = int(1000000 / revUs)
    rpmRaw     = int(rps * 60)
    rpmGear100 = int(rpmRaw / 100)
  
    print('  uS per revolution =', revUs)
    print('  rps raw           =', rps)          
    print('  rpm raw           =', rpmRaw)
    print('  rpm gear 1:100    =', rpmGear100)          
    
    return rpmGear100

# *** PWM Functions ***

def setupMotorDriverPwm(motorDriverDictNum):
    motorDriverDict = motorDriverDictDict[str(motorDriverDictNum)]
    # print('  motDriverDictNum =', motorDriverDictNum)
    standBy = Pin(motorDriverDict['STANDBY'], Pin.OUT)
    standBy.high()    
    return

def setupMotorPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
    motorDriverDict = motorDriverDictDict[str(motorDriverDictNum)]
    motorIn1Pin = motorDriverDict[str(motorNum)]['IN1']
    motorIn2Pin = motorDriverDict[str(motorNum)]['IN2']
    motorPwmPin = motorDriverDict[str(motorNum)]['PWM']    
    
    # print('  IN1 pinNum =', motorIn1Pin)
    # print('  IN2 pinNum =', motorIn2Pin)    
    # print('  PWM pinNum =', motorPwmPin)
    
    motorIn1    = Pin(motorIn1Pin, Pin.OUT)
    motorIn2    = Pin(motorIn2Pin, Pin.OUT)
    motorPwm    = PWM(Pin(motorDriverDict[str(motorNum)]['PWM']))
    
    motorPwm.freq(pwmFreq)
    motorPwm.duty_u16(int(65536 / 100) * dutyCycle)    
    pwmPinNum = motorDriverDict[str(motorNum)]['PWM']
    # print('    Setup PWM')
    # print('      PWM freq  (Hz) =', motorPwm.freq())
    # print('      PWM duty  (%)  =', int((motorPwm.duty_u16() / 65536) * 100))
    # print('      PWM width (ns) =', motorPwm.duty_ns())
    motorEncode = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN)      
    motorControlPinList = [motorIn1, motorIn2, motorPwm, motorEncode]    
    return motorControlPinList

def moveMotorForwardPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
    motorControlPinList = setupMotorPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)    
    motorControlPinList[0].low()
    motorControlPinList[1].high()
    
    motorControlPinList[2].freq(pwmFreq)    
    motorControlPinList[2].duty_u16(int(65536 / 100) * dutyCycle)        
    return

def measureMotorSpeedPwm(motorDriverDictNum, motorNum):
    # *** Setup Motor Driver ***
    # setupMotorDriver(motorDriverDictNum)
    
    # *** Move motor forward ***
    # moveMotorForwardPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)       

    # *** Measure Motor Speed ***
    
    # *** Start counting 10 revolutions ***
    usTicks1 = utime.ticks_us()
    
    # *** Find lapse time of 100 revolutions ***
    revCount = 0    
    while revCount < 10:
        if readMotorEncodeValue(motorDriverDictNum, motorNum) == 0:
            revCount = revCount + 1
        utime.sleep(0.0001) # <<<<<<<<<<<<            
    
    usTicks2  = utime.ticks_us()
    
    rev10Us    = utime.ticks_diff(usTicks2, usTicks1)
    revUs      = int(rev10Us / 10)
    rps        = int(1000000 / revUs)
    rpmRaw     = int(rps * 60)
    rpmGear100 = int(rpmRaw / 100)
  
    # print('  uS per revolution =', revUs)
    # print('  rps raw           =', rps)          
    # print('  rpm raw           =', rpmRaw)
    # print('  rpm gear 1:100    =', rpmGear100)          
    
    return rpmGear100

# *** Motor Test Functions ***

def testMoveMotor01(motorDriverDictNum, motorNum):
    # *** Setup Driver ***
    setupMotorDriver(motorDriverDictNum)
    # *** Move motor forward ***
    moveMotorForward(motorDriverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor backward ***
    moveMotorBackward(motorDriverDictNum, motorNum)
    utime.sleep(1)
    # *** Move motor stop ***
    moveMotorStop(motorDriverDictNum, motorNum)    
    return

def testMeasureMotorSpeed01(motorDriverDictNum, motorNum):
    measureMotorSpeed(motorDriverDictNum, motorNum)    
    return

#def testSetupMotorPwm01(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
#    setupPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)    
#    return

def testMoveMotorForwardPwm01(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
    setupMotorDriverPwm(motorDriverDictNum)
    moveMotorForwardPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)
    utime.sleep(4)
    moveMotorStop(motorDriverDictNum, motorNum)    
    return

def testMeasureMotorSpeedPwm01(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
    setupMotorDriverPwm(motorDriverDictNum)
    moveMotorForwardPwm(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)
    utime.sleep(2)    
    speedRpmGear = measureMotorSpeedPwm(motorDriverDictNum, motorNum)
    print('dutyCycle   =', dutyCycle)
    print('speed (rpmGear) =', speedRpmGear)

    # moveMotorStop(motorDriverDictNum, motorNum)    
    return

# *** Main Tests ***

# *** Test move motor forward, backward, stop ***
# print('Test Motor #1 Move Forward, Backward, Stop') 
# testMoveMotor01(motorDriverDictNum = 1, motorNum = 1)

# print('\nTest Motor #2 Move Forward, Backward, Stop') 
# testMoveMotor01(motorDriverDictNum = 1, motorNum = 2)

# *** Test measure motor speed ***
# print('\nTest Measure Motor #1 Speed') 
# testMeasureMotorSpeed01(motorDriverDictNum = 1, motorNum = 1)

# *** Test Move Motor / Measure Speed PWM Funcions ***

print('Test Move Motor Forward')
# testMoveMotorForwardPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 100)
# testMoveMotorForwardPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 50)
# testMoveMotorForwardPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 10)

print('Test Measure Motor Speed')
testMeasureMotorSpeedPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 80)

print('Test Measure Motor Speed')
testMeasureMotorSpeedPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 50)

print('Test Measure Motor Speed')
testMeasureMotorSpeedPwm01(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 40)

print('Stop Motor')
moveMotorStop(motorDriverDictNum = 1, motorNum = 1) 

# *** End of program ***

# *** Sample Output - tlfong01  2021aug18hkt1120
'''
MicroPython v1.16 on 2021-06-18; Raspberry Pi Pico with RP2040

Type "help()" for more information.
>>> %Run -c $EDITOR_CONTENT
Test Move Motor Forward
Test Measure Motor Speed
dutyCycle   = 80
speed (rpmGear) = 479
Test Measure Motor Speed
dutyCycle   = 50
speed (rpmGear) = 479
Test Measure Motor Speed
dutyCycle   = 40
speed (rpmGear) = 524
Stop Motor
>>>
'''

'''
>>> %Run -c $EDITOR_CONTENT
Test PWM Funcions
PWM freq  (Hz) = 1000
PWM duty  (%)  = 49
PWM width (ns) = 499728
>>>
'''

Note - This reply post is getting too long, exceeding the forum’s 32k word limit. So I am breaking up to another reply.

/ to continue, …

This reply follows from the early long reply. I am starting to study how to use microPython interrupt, to write a new function to measure the speed of the N20 geared motor with encoders.

Interrupt Learning Notes

I know interrupt handlers (Interrupt Service Routines, ISR’s) are very difficult to debug. So my goal is try to write an interrupt handler as simple as possible, but not simpler.

Below are the tutorials I will start reading, before writing the very simple interrupt handler for measuring the speed of the N20 motor.

(1) Raspberry Pi GPIO Interrupts Tutorial

(2) MicroPython Writing interrupt handlers

(3) Dual Cores & Interrupts on Pi Pico (Using Thonny) - By tonygo2


MicroPython Writing interrupt handlers - Reading Notes

Tips and recommended practices

Where an ISR returns multiple bytes use a pre-allocated bytearray. If multiple integers are to be shared between an ISR and the main program consider an array (array.array).

Where data is shared between the main program and an ISR, consider disabling interrupts prior to accessing the data in the main program and re-enabling them immediately afterwards (see Critical Sections).

Simplicity

For a variety of reasons it is important to keep ISR code as short and simple as possible. It should do only what has to be done immediately after the event which caused it: operations which can be deferred should be delegated to the main program loop. Typically an ISR will deal with the hardware device which caused the interrupt, making it ready for the next interrupt to occur. It will communicate with the main loop by updating shared data to indicate that the interrupt has occurred, and it will return. An ISR should return control to the main loop as quickly as possible.

Communication between an ISR and the main program

Normally an ISR needs to communicate with the main program. The simplest means of doing this is via one or more shared data objects, either declared as global or shared via a class (see below). There are various restrictions and hazards around doing this, which are covered in more detail below. Integers, bytes and bytearray objects are commonly used for this purpose along with arrays (from the array module) which can store various data types.

Creation of Python objects

One way to avoid this issue is for the ISR to use pre-allocated buffers. For example a class constructor creates a bytearray instance and a boolean flag. The ISR method assigns data to locations in the buffer and sets the flag. The memory allocation occurs in the main program code when the object is instantiated rather than in the ISR.


N20 Motor Speed Measurement Function Using 2 Core Pico and Interrupts - Part 1

I skimmed all three tutorials above and found Ref 1 good as a general introduction to interrupts. But it is based on Rpi4, not on Pico, so not too useful else. Ref 2 is base on MicroPython, but a bit too advanced for newbies. Ref 3 by TonyGo2 is very good, very newbie friend and can be used as a template to adapt to my motor speed measurement function. It uses 2 core Pico and also Thonny MicroPython. So I am thinking a learning 2 core programming using this example.


Last reply exceeded the forum’s 32k words limit. So this time I am using the Penzu stuff to make this reply’s program listing shorter, as show below.

Motor Speed Measure v0.1 Program Listing


I found the TonyGo2 tutorial on interrupt handler good, but not easy to adapt to my simple applications. So I searched for more newbie friendly tutorials and found the first two of the following three tutorials good.

(1) Raspberry Pi Pico using MicroPython - Tutorial #2: Button Inputs (Digital Inputs and Interrupts at 9:04) - RoboCraze, 2,056 views Feb 26, 2021

(2) How to use the two Cores of the Pi Pico? And how fast are Interrupts? - Andreas Spiess, 71,479 views, 2021feb21 (Two thread/core at 5:03, Interrupt at 8:04, frequency ounter at 8:54, PIO fast counter at 11:04)

(3) Rpi Pico hardware_irq Hardware APIs - SDK Documentation

The first tutorial from RoboCraze is simple, so I will be using it to adapt to my N20 motor speed measurement. Andreas Spiess’s tutorial is very comprehensive. So I might use it to improve my first trial of speed measurement.


Now I am looking closely at RoboCraze’s interrupt program example, which I think is excellent for newbies, because it is what I used to say “Make it as simple as possible, but not simpler” or often called the Ockham’s Razor. RoboCraze’s example is good in that it does not even use any global variable/flag/counter. The main part is shown below:


spv312_2021aug2005.py


Now I have debugged a preliminary version of the N20 speed measurement function spv30310.py.

spv31310_2021aug2101.py


This version does the following:

1. Setup and move motor forward
2. Setup and move motor backward
3. Setup and measure motor speed


# Program Name
#   spv31313_2021aug2101.py - tlfong01 2021aug21hkt2058

# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Aspire Intel COIRE i5 PC Chinese Windows 10 (64-bit), Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# DC Motor
#   N20 1:100 6V DC Gear Motor with quadrature encoder signals A, B
#   N20 Gear Motor Spec (6V gear 1:100 no load speed = 300 rpm (https://www.pololu.com/search/compare/173)
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Brief Description of Program Function
#   1. Move DC motor N20 forward, backware, stop.
#   2. Use PWM to control motor speed
#   3. Use N20 motor encoder to measure speed

# ========= ========= ========= ========= ========= ========= ========= ========= ========= ========= 
# ========= ========= ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= ========= ========= =========

import utime
from machine import Pin, PWM

# *** Configuration ***

motorDriverDict01 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary v0.1  tlfong01 2021aug21hkt0949',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0},
              }

motorDriverDictDict = {
                '1': motorDriverDict01,
                '2': motorDriverDict01,                
                }

# *** Motor Functions ***

def setupMotor(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):
    motorDriverDict = motorDriverDictDict[str(motorDriverDictNum)] # Get driver dict
    driverStandBy = Pin(motorDriverDict['STANDBY'], Pin.OUT) # create driverStandBy pin object    
    motorIn1      = Pin(motorDriverDict[str(motorNum)]['IN1'], Pin.OUT) # Create Motor # motorNum In1 pin object
    motorIn2      = Pin(motorDriverDict[str(motorNum)]['IN2'], Pin.OUT) # Create Motor # motorNum In2 pin object    
    motorPwm      = PWM(Pin(motorDriverDict[str(motorNum)]['PWM'])) # Create Motor # motorNum Pwm pin object    
    motorEncode   = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) # Create Motor # motorNum Encode pin object

    motorConfigDict = {'StdBy': driverStandBy, 'In1': motorIn1, 'In2': motorIn2, 'Pwm': motorPwm, 'Encode': motorEncode, \
                       'PwmFreq': pwmFreq, 'DutyCycle': dutyCycle}
    
    motorConfigDict['StdBy'].high() # enable motor driver normal operation, (low = disable)    
    motorConfigDict['Pwm'].freq(pwmFreq)                          # setup frequency
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * dutyCycle) #   and duty cycle
    stopMotor(motorConfigDict)
    return motorConfigDict

def moveMotorForwardNonStop(motorConfigDict):
    motorConfigDict['In1'].low()  # move motor
    motorConfigDict['In2'].high() #   backward
    return

def stopMotor(motorConfigDict):
    motorConfigDict['In1'].low() 
    motorConfigDict['In2'].low() 
    return

def moveMotorForward(motorConfigDict, moveSeconds):
    motorConfigDict['In1'].low()  
    motorConfigDict['In2'].high()
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

def moveMotorBackward(motorConfigDict, moveSeconds):
    motorConfigDict['In1'].high()  
    motorConfigDict['In2'].low()
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

# *** Test Functions *** 

def testMoveMotorForward(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    print('\ntestMoveMotorForward()')
    motorConfigDict = setupMotor(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)
    moveMotorForward(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

def testMoveMotorBackward(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    print('\ntestMoveMotorBackward()')    
    motorConfigDict = setupMotor(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)  
    moveMotorBackward(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)        
    return

def testMeasureMotorSpeed(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):    
    print('\ntestMeasureMotorSpeed(), ...')     

    # *** Setup motor and move motor forward ***
    motorConfigDict = setupMotor(motorDriverDictNum, motorNum, pwmFreq, dutyCycle)
    moveMotorForwardNonStop(motorConfigDict)  

    # *** Define callback funtion ***
    def encodeIntCallBack(pin):
        global encodeIntEventCount
        encodeIntEventCount = encodeIntEventCount + 1    
        return
    
    # *** Setup callback function ***
    motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    
    # *** Count Encode Interrupt Events Every Second ***
    global encodeIntEventCount
    encodeIntEventCount = 0
    
    totalSecondCount = 4
    #print('totalSecondCount =', totalSecondCount)
    
    for secondCount in range(totalSecondCount):
        #print('TotalEncodeIntEventCount =', encodeIntEventCount)
        utime.sleep(1)
        
    avgEncodeIntEventCount = int(encodeIntEventCount / totalSecondCount)
    #print('pwmFreq                                =', motorConfigDict['PwmFreq'])
    print('dutyCycle                              =', motorConfigDict['DutyCycle'])
    #print('average encodeIntEventCount Per Second =', avgEncodeIntEventCount)
    print('N20 rpm                                =', int((avgEncodeIntEventCount * 60)/100))    
        
    return

# *** Main ***

testMoveMotorForward(motorDriverDictNum  = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 90, moveSeconds = 1)
testMoveMotorBackward(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 90, moveSeconds = 1)
testMeasureMotorSpeed(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle = 100)
testMeasureMotorSpeed(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle =  50)
testMeasureMotorSpeed(motorDriverDictNum = 1, motorNum = 1, pwmFreq = 1000, dutyCycle =  10)

# End

'''
Sample output - tlfong01  2021aug21
>>> %Run -c $EDITOR_CONTENT

testMoveMotorForward()

testMoveMotorBackward()

testMeasureMotorSpeed(), ...
dutyCycle                              = 100
N20 rpm                                = 960

testMeasureMotorSpeed(), ...
dutyCycle                              = 50
N20 rpm                                = 454

testMeasureMotorSpeed(), ...
dutyCycle                              = 10
N20 rpm                                = 52
>>> 
'''

Now I am plotting N20 motor rpm speed vs dutyCycle using the following speed measurement results

testMeasureMotorSpeed(), …
dutyCycle = 100
N20 rpm = 964

testMeasureMotorSpeed(), …
dutyCycle = 955
N20 rpm = 67

testMeasureMotorSpeed(), …
dutyCycle = 90
N20 rpm = 868

testMeasureMotorSpeed(), …
dutyCycle = 80
N20 rpm = 769

testMeasureMotorSpeed(), …
dutyCycle = 70
N20 rpm = 661

testMeasureMotorSpeed(), …
dutyCycle = 60
N20 rpm = 553

testMeasureMotorSpeed(), …
dutyCycle = 50
N20 rpm = 448

testMeasureMotorSpeed(), …
dutyCycle = 40
N20 rpm = 351

testMeasureMotorSpeed(), …
dutyCycle = 30
N20 rpm = 247

testMeasureMotorSpeed(), …
dutyCycle = 20
N20 rpm = 150

testMeasureMotorSpeed(), …
dutyCycle = 10
N20 rpm = 50

testMeasureMotorSpeed(), …
dutyCycle = 5
N20 rpm = 0

I am glad to see that the N20 motor rpm speed vs pwm duty cycle is very linear.


Motor to motor speed difference

I tested two motors and found the following difference.

motorNum = 1  dutyCycle = 50  N20 rpm = 445
motorNum = 2  dutyCycle = 50  N20 rpm = 473

The speed difference is about 473 / 445 ~=6%

This means that if both motors are moved with duty cycle 50%, the wheel speed is about 6%, or in other words the vehicle will not move in a straight line.

So if I want the vehicle move in a straight line, I need to adjust the duty cycle of each motor.


Distance traveled vs motor moving time

Now I am modifying the program to find the distance of a 6.3cm diameter wheel traveled in 4 seconds.

spv3135.py

Sample Output (my dodgy calculation not verified! :))

>>> %Run -c $EDITOR_CONTENT
motorNum = 1  dutyCycle = 50  seconds moved                       = 4
  revolution made                     = 2883
  1/100 geared wheel revolutions made = 28
  distance traveled (cm)             = 570
>>> 

Errata - I forgot that the quadrature encoder might have 6, 12 or more pulses/counts per revolution. So the actual distance traveled in the above calculation should be 570 / 12 ~= 50cm.

Note - But I am not too sure. I am using only the falling edge of only Signal A of the quadrature encoder. Some specs says Count Per Revolution (CPR) is 4 times of Pulses Per Revolution (PPR), because CPR takes care of both Signals A, and B, and both falling and rising edges.


How to move a specified distance

So I moved on to write a function to move to a specified distance, say 1 metre. I timed the time required to move one revolution and calculate the specified distance by the distance of one revolution, and get the number of revolution required distance. However, I found this trick does not work, because the time for the first or second revolution is longer that the later revolutions (because it take time to start moving motor and more time to settle down to a steady speed). So I think a better way is to calculate the number of encode interrupts for one revolution, instead of the time required for one revolution. This distance vs encode interrupt counts is also good, because it is independent of speed, PWM frequency, and duty cycle magnitude.

Anyway, the program for using the wrong trick is listed below.

Bad program for moving to a specified distance


Number of Encode Interrupt Counts Per Revolution

I found that for pwmFreq = 1000, dutyCycle = 50, time required to move motor for one revolution is about 1.38 seconds, and

the more precise number of interrupt counts for one revolution is 983, which is a very important number for my later programs.


Refactoring

spv31321.py

spv31325.py can now move a number of encode interrupts/revolutions/distance


Considering trying two core interrupts

Now that my motor moving program using interrupts is working properly, I am thinking of try out two core interrupt as described in the TonyGo2 tutorial. I found the trick is simple, but makes the processing more complicated. So I am not try it for now. Anyway, I summarized TonyGo2’s programming below, for later reference.

TonyGo2' Tutorial (https://www.instructables.com/Dual-Cores-Interrupts-on-Pi-Pico/)

1. Break down into 3 parts.

a. Setting up and activating the cores
b. Shutting down the cores in sequence
c. Using interrupts

2. Two Core/Process/Thread operation

a. Core 0 process/thread counts (button pushing) interrupts and does something when counts up (to 30)
b. Core 1 process/thread blinks LEDs

3. Programming

a. Imports _thread module
b. Imports gc (garbage module)
c. Two Core 0 process/tasks
   Two interrupt pins associated with two interrupt events (and Interrupt Service Routines (IsRS): 
   (i)  "LedDirectionTask(pin)" triggered by "Led direction pin signal rising edge" 
   (ii) "HaltTask(pin), triggered by "Halt pin signal rising edge" 
d. One Core 1 process/task 
   (i)  main process running LEDs, thread exits if not "running".
   (ii) garbage collect if interrupt counts to 100

Testing two motors

spv31327.py


How to drive the 2WD (1) in a straight line, (b) in a circle, © in a square

Now that I know how to count Encoder signals in a time period, it is time to do more fancy things, like driving the 2WD in a straight line. Before that, I need to ‘sync’ the two motor’s encoder interrupt count, distance traveled etc. A sample output of the calibration is shown below:

'''
Sample output - tlfong01  2021aug25hkt1145
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 100  MoveSeconds =, 1  encodeIntCount = 1563
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 100  MoveSeconds =, 1  encodeIntCount = 1941
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 50  MoveSeconds =, 1  encodeIntCount = 1077
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 50  MoveSeconds =, 1  encodeIntCount = 830
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 10  MoveSeconds =, 1  encodeIntCount = 193
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 10  MoveSeconds =, 1  encodeIntCount = 88
>>>
'''

And the program listed below:

Rpi Pico 2WD spv232329.py


Realtime Sync Design Notes

I found the sync by interrupt count not very reliable on order of seconds, as summarized below.

>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 1  encodeIntCount = 809
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 1  encodeIntCount = 808
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 1  encodeIntCount = 810
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 1  encodeIntCount = 824
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 1  encodeIntCount = 808
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 1  encodeIntCount = 814
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 1  encodeIntCount = 809
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 1  encodeIntCount = 820
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 10  encodeIntCount = 8390
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 10  encodeIntCount = 7388
>>> %Run -c $EDITOR_CONTENT
MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 55  MoveSeconds =, 100  encodeIntCount = 84405
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 48  MoveSeconds =, 100  encodeIntCount = 72700
>>> 

The root problem is that the differential speed error accumulates, with the result that it is not practical to use this trick the drive the vehicle move in a straight line, not to mention in a circle or a square. So I now need to do realtime sync, not in seconds resolution, but in milliseconds. The idea is to watch speed/distance millisecond by millisecond and adjust one or both duty cycles to minimize the difference.


This reply is getting too long and confusing. So I am making a new reply with a selfie walk through of what I have bee doing, and finally reached this point as a milestone. The following version of program is working version of all the testing functions at the motor level. Next reply will move up to the 2WD vehicle’s wheel level. In Object Oriented Programming sense, the new classes at wheel level is higher than the motor level. In Functional Programming sense, the MicroPython dictionaries at moving from motor to wheels/vehicle level.

SPV v4.0 Program


/ to continue, …

SPV(Smart Pico Vehicle)v4.0 - Introduction

This is reply to the last 13 or so replies of this smart vehicle project. I know that using the term “reply” is a bit confusing. Actually I am, in effect, writing a “blog” of the project. I think the term “blog” better reflects the situation, that it is like a diary, with dates of my project progress, especially the mistakes I have been making, which is revealed when doing the tests to verify my project design and direction is more or less OK. I need to remind myself that I am doing TDD (Test Driven Development), or PBL (Problem Based Learning), or Agile/Prototyping, or a mix of all the three approaches. The other thing I need to remind myself is that I am doing sort of structured programming, distributed processing, multiprocessing/multithreading, functional programming (but not object oriented programming).

Now I will do a brief selfie walk through, or an intermittent progress report, in the form of casual, random notes.

  1. I started with Rpi Pico MCU, and N20 geared motor with quadrature encoder, because I read other users proposed these two things.

  2. I got my Pico’s some months ago, but have been a bit lazy to try it out. I did read about Tom’s Hardware tutorials, and other interesting YouTube videos (especially those by the funny Swiss guy) which I think is good for newbies to start off.

  3. I have been using popular DC motor drivers such as L298N to drive two motor vehicles, but only at the start off stage, controlling by PWM by Rpi3/4. I remember one motor driver tutorial challenged to do more advanced things such as moving the two wheeled car/robot in a square. I noticed that no newbies reported that they did it. So I think there is a steep learning curve on how to teach the 2WD to do smart things, like move in a straight line, in a circle, in a square etc.

    It was only when I tried out different things, like motor encoder signal interrupts, especially with 2 core Pico, that the stuff is very tricky, especially for newbies. That is why that I need to look back the over ambitious goals I hope to reach, and mistakes I made, before I move on my smart vehicle project.

  4. Now let me report what I am getting stuck at this point. I found moving the N20 motor with a simple spec or requirement such as (1) number of revolutions, (2) number of motor encoder signals/pulses, (3) distance, eg, 1 or 2 meters, is difficult, if precision is needed (eg 5% accuracy/resolution), not to mention synchronizing two or four motors.

  5. So now I am going back a bit and redo some of the tests and calibrations, as a preparation of doing this seemingly easy thing:

    Moving a 2WD in a straight line

  6. First thing I need to do is to calibrate the N20 motor encoder interrupt signal timing. I am using the following scope to display the waveforms.

Digital Oscilloscope ATTEN ADS1102CAL+

ATTEN ADS100 User Manual


Now I am using my 100MHz DSO to display the N20 motor encoders pulse signal waveform. I found that for PWM frequency 1kHz, Duty cycle 50%, both motor’s encoders give about 700 pulses per second. I will be using this number 770Hz as calibration/bench mark for my future tests.


And the time has come to try to move my 2WD in a straight line, before trying out the more advanced tricks of Pi Top [4] DIY 2WD, such as autonomous driving in a straight line.


Discrepancy between python program measurement and scope display

I lookded back what I measured earlier using python program, with the following results:

MotorDriverDictNum = 2  MotorNum = 1  PWM Freq = 1000  DutyCycle = 50  MoveSeconds =, 1  encodeIntCount = 1077
MotorDriverDictNum = 2  MotorNum = 2  PWM Freq = 1000  DutyCycle = 50  MoveSeconds =, 1  encodeIntCount = 830

It is weird that software results 1077 and 830 interrupt counts per second, but scope measure around 760~770 interrupt counts per second. I need to find out why software counts more interrupts hardware scope. If software results are not reliable or inaccurate, then it would be difficult o sync two motors/wheels to move vehicle in a straight. Perhaps I need to use more motor samples to find out the cause of discrepancy.


PiTop [4] and PiPico Gesture Control

I remember PiTop [4] is using MPU6050 to do smart things like gesture control. So I am hoping to use the same or similar gyro and accelero for fun. This morning I read a newbie friendly tutorial on using MPU6050. So I will try it later.

Using Gyroscope and accelerometer with MPU6050, Rpi Pico and MicroPython - Peppr80, 2021aug85



Pi-Top Encoder Motor API

Now I am read the Pi-Top Encoder API, to see what classes, functions, and parameters are used in their library. There are parameters I understand, such as wheel diameter and circumference. But are some I don’t understand exactly what they are, for example, two types of stopping the motor.

Pi-Top Encoder Motor API - Pi-Top

6. API - pi-top Maker Architecture (PMA) Components

6.3. Encoder Motor

Note

This is a Motor Component which connects to a MotorEncoder Port [M0-M3].

from pitop import (
    EncoderMotor,
    ForwardDirection,
    BrakingType
)

from time import sleep

# Setup the motor

motor = EncoderMotor("M0", ForwardDirection.COUNTER_CLOCKWISE)

motor.braking_type = BrakingType.COAST

# Move in both directions

rpm_speed = 100
for _ in range(4):
    motor.set_target_rpm(rpm_speed)
    sleep(2)
    motor.set_target_rpm(-rpm_speed)
    sleep(2)

motor.stop()
classpitop.pma.EncoderMotor(port_name, forward_direction, braking_type=<BrakingType.COAST: 0>, wheel_diameter=0.075, name=None)[source]
Represents a pi-top motor encoder component.

Note that pi-top motor encoders use a built-in closed-loop control system, that feeds the readings from an encoder sensor to an PID controller. This controller will actively modify the motor’s current to move at the desired speed or position, even if a load is applied to the shaft.

This internal controller is used when moving the motor through set_target_rpm or set_target_speed methods, while using the set_power method will make the motor work in open-loop, not using the controller.

Note

Note that some methods allow to use distance and speed settings in meters and meters per second. These will only make sense when using a wheel attached to the shaft of the motor.

The conversions between angle, rotations and RPM used by the motor to meters and meters/second are performed considering the wheel_diameter parameter. This parameter defaults to the diameter of the wheel included with MMK. If a wheel of different dimmensions is attached to the motor, you’ll need to measure it’s diameter, in order for these methods to work properly.

Parameters:	

port_name (str) – The ID for the port to which this component is connected.

forward_direction (ForwardDirection) – The type of rotation of the motor shaft that corresponds to forward motion.

braking_type (BrakingType) – The braking type of the motor. Defaults to coast.

wheel_diameter (int or float) – The diameter of the wheel attached to the motor.

backward(target_speed, distance=0.0)[source]

Run the wheel backwards at the desired speed in meters per second.

This method is a simple interface to move the wheel that wraps a call to set_target_speed, specifying the back direction.

If desired, a distance to travel can also be specified in meters, after which the motor will stop. Setting distance to 0 will set the motor to run indefinitely until stopped.

Note

Note that for this method to move the wheel the expected distance, the correct wheel_circumference value needs to be used.

Parameters:	

target_speed (int or float) – Desired speed in m/s

distance (int or float) – Total distance to travel in m. Set to 0 to run indefinitely.

braking_type

Returns the type of braking used by the motor when it’s stopping after a movement.

Setting this property will change the way the motor stops a movement:

BrakingType.COAST will make the motor coast to a halt when stopped.

BrakingType.BRAKE will cause the motor to actively brake when stopped.

Parameters:	braking_type (BrakingType) – The braking type of the motor.

current_rpm
Returns the actual RPM currently being achieved at the output shaft, measured by the encoder sensor.

This value might differ from the target RPM set through set_target_rpm.

current_speed

Returns the speed currently being achieved by the motor in meters per second.

This value may differ from the target speed set through set_target_speed.

distance

Returns the distance the wheel has travelled in meters.

This value depends on the correct wheel_circumference value being set.

forward(target_speed, distance=0.0)[source]
Run the wheel forward at the desired speed in meters per second.

This method is a simple interface to move the motor that wraps a call to set_target_speed, specifying the forward direction.

If desired, a distance to travel can also be specified in meters, after which the motor will stop. Setting distance to 0 will set the motor to run indefinitely until stopped.

Note

Note that for this method to move the wheel the expected distance, the correct wheel_circumference value needs to be used.

Parameters:	

target_speed (int or float) – Desired speed in m/s

distance (int or float) – Total distance to travel in m. Set to 0 to run indefinitely.

forward_direction

Represents the forward direction setting used by the motor.

Setting this property will determine on which direction the motor will turn whenever a movement in a particular direction is requested.

Parameters:	forward_direction (ForwardDirection) – The direction that corresponds to forward motion.

max_rpm

Returns the approximate maximum RPM capable given the motor and gear ratio.

max_speed

The approximate maximum speed possible for the wheel attached to the motor shaft, given the motor specs, gear ratio and wheel circumference.

This value depends on the correct wheel_circumference value being set.

own_state

Representation of an object state that will be used to determine the current state of an object.

power()[source]

Get the current power of the motor.

Returns a value from -1.0 to +1.0, assuming the user is controlling the motor using the set_power method (motor is in control mode 0). If this is not the case, returns None.

rotation_counter

Returns the total or partial number of rotations performed by the motor shaft.

Rotations will increment when moving forward, and decrement when moving backward. This value is a float with many decimal points of accuracy, so can be used to monitor even very small turns of the output shaft.

set_power(power, direction=<Direction.FORWARD: 1>)[source]

Turn the motor on at the power level provided, in the range -1.0 to +1.0, where:

1.0: motor will turn with full power in the direction provided as argument.

0.0: motor will not move.

-1.0: motor will turn with full power in the direction contrary to direction.

Warning

Setting a power value out of range will cause the method to raise an exception.

Parameters:	

power (int or float) – Motor power, in the range -1.0 to +1.0

direction (Direction) – Direction to rotate the motor

set_target_rpm(target_rpm, direction=<Direction.FORWARD: 1>, total_rotations=0.0)[source]

Run the motor at the specified target_rpm RPM.

If desired, a number of full or partial rotations can also be set through the total_rotations parameter. Once reached, the motor will stop. Setting total_rotations to 0 will set the motor to run indefinitely until stopped.

If the desired RPM setting cannot be achieved, torque_limited will be set to True and the motor will run at the maximum possible RPM it is capable of for the instantaneous torque. This means that if the torque lowers, then the RPM will continue to rise until it meets the desired level.

Care needs to be taken here if you want to drive a vehicle forward in a straight line, as the motors are not guaranteed to spin at the same rate if they are torque-limited.

Warning

Setting a target_rpm higher than the maximum allowed will cause the method to throw an exception. To determine what the maximum possible target RPM for the motor is, use the max_rpm method.

Parameters:	

target_rpm (int or float) – Desired RPM of output shaft

direction (Direction) – Direction to rotate the motor. Defaults to forward.

total_rotations (int or float) – Total number of rotations to be execute. Set to 0 to run indefinitely.

set_target_speed(target_speed, direction=<Direction.FORWARD: 1>, distance=0.0)[source]

Run the wheel at the specified target speed in meters per second.

If desired, a distance to travel can also be specified in meters, after which the motor will stop. Setting distance to 0 will set the motor to run indefinitely until stopped.

Warning

Setting a target_speed higher than the maximum allowed will cause the method to throw an exception. To determine what the maximum possible target speed for the motor is, use the max_speed method.

Note

Note that for this method to move the wheel the expected distance, the correct wheel_diameter value needs to be used.

Parameters:	

target_speed (int or float) – Desired speed in m/s

direction (Direction) – Direction to rotate the motor. Defaults to forward.

distance (int or float) – Total distance to travel in m. Set to 0 to run indefinitely.

stop()[source]

Stop the motor in all circumstances.

target_rpm()[source]

Get the desired RPM of the motor output shaft, assuming the user is controlling the motor using set_target_rpm (motor is in control mode 1).

If this is not the case, returns None.

torque_limited

Check if the actual motor speed or RPM does not match the target speed or RPM.

Returns a boolean value, True if the motor is torque- limited and False if it is not.

wheel_circumference

wheel_diameter

Represents the diameter of the wheel attached to the motor in meters.

This parameter is important if using library functions to measure speed or distance, as these rely on knowing the diameter of the wheel in order to function correctly. Use one of the predefined pi-top wheel and tyre types, or define your own wheel size.

Note

Note the following diameters:

pi-top MMK Standard Wheel: 0.060.0m

pi-top MMK Standard Wheel with Rubber Tyre: 0.065m

pi-top MMK Standard Wheel with tank track: 0.070m

Parameters:	wheel_diameter (int or float) – Wheel diameter in meters.

6.3.1. Parameters

classpitop.pma.parameters.BrakingType[source]
Braking types.

BRAKE= 1

COAST= 0

classpitop.pma.parameters.ForwardDirection[source]

Forward directions.

CLOCKWISE= 1

COUNTER_CLOCKWISE= -1

classpitop.pma.parameters.Direction[source]

Directions.

BACK= -1

FORWARD= 1

.END

Using I2C and SPI for LCD Display, MPU6050 etc

I found MagPi 109 has 20 interesting Pico projects, some of which is useful for my projects here. So I am summarizing here. PiTop 4’s Line Follower is based ofn Rpi4B. I need to modify it for Pico, and the first project listed here is very useful for my reference.

Line Follower Robot using Pico - Raspberry Pi Pico Projects

Raspberry Pi Pico Line Follower Robot | Raspberry Pi Pico Projects 1,572 views Apr 14, 2021

Raspberry Pi Pico: digital communication protocols I2C and SPI to control LCD Display - Page 54 MagPi 109


MagPi109 on Pico Controlled Automated Model Railroad

MagPi Issue 109 (2021sept) also has an interesting project on Raspberry Pi Pico Controlled Simple Model Railway

Raspberry Pi Pico Controlled Simple Automated Model Railroad | Model Railroad Automation - By KushagraK7 2,62316Featured

Raspberry Pi Pico Controlled Simple Model Railway | Model Railroad Automation

TB6612FNG H-Bridge Motor Controller - Better than L298N? - 81,466 views 2019dec16

Note - This tutorial explains why I am using TB6612FNG and not the very popular L298N.


Note - There is one other exclusive feature of TB6612 which make it to do 2WD or 4WD superior than L298N, …


Pi-Top Encoder Motor vs TT130 Motor

Pi-Top Servo Motor is sort of a black box. So I am checking out MiaowLab’s TT encode motor to compare and contrast.


PiTop [4] DIY with Rpi4B / To consider later, …

Installing the Raspberry Pi 4 into your pi-top [4] DIY Edition 8,310 viewsSep 23, 2020

Pi-Top [4] DIY Edition


Swapping N20 encoder motor with TT130 encoder motor


Now I have swapped N20 motor with TT130 motor


TT130 Motor TB6612FNG Driver Test 2021aug2901


Pico GPIO Libraries

This couple of weeks I have been mainly using Tom’s Hardware tutorials as a newbie’s guide on how to write python app code. So far so good, except that when I hope to dig deeper to expand my code, eg. from one motor to two motors or 4 motors, I need to write my own code. I tried to good pico GPIO libraries without luck. I only found Rpi3/4, but no Pico ones, as described below.

GPIO Programming on the Raspberry Pi: Python Libraries - Sebastian 2021apr30


TT130 Motors replacing N20 motors

One reason that I am trying TT130 motor is that its stall torques is stronger, 2.5kg-cm, compared with N20’s 0.4kg-cm.


The test program

# Program Name
#   spv4002_2021aug2801.py - tlfong01 2021aug28hkt2114

# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Aspire Intel COIRE i5 PC Chinese Windows 10 (64-bit), Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# DC Motor
#   N20 1:100 6V DC Gear Motor with quadrature encoder signals A, B
#   N20 Gear Motor Spec (6V gear 1:100 no load speed = 300 rpm (https://www.pololu.com/search/compare/173)
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Brief Description of Program Function
#   1. Move DC motor N20 forward, backware, stop.
#   2. Use PWM to control motor speed
#   3. Use N20 motor encoder to measure speed
#   4. Use encoder signals to help driving a 2WD to move in a straight line

# Digital Oscilloscope ATTEN ADS1102CAL+
# https://toolboom.com/en/digital-oscilloscope-atten-ads1102calplus/

# ATTEN ADS100 User Manual
# https://micromir.ucoz.ru/Oscil/Atten/ADS1000_User_Manual.pdf

import utime
from machine import Pin, PWM

# *** Configuration ***

# *** Vehicle/Wheel Config ***

wheelDict01 = {
                'FRONT_LEFT' : {'MOTOR_DRIVER_DICT_NUM': 2, 'MOTOR_NUM': 1},
                'FRONT_RIGHT': {'MOTOR_DRIVER_DICT_NUM': 2, 'MOTOR_NUM': 2},
                'BACK_LEFT'  : {'MOTOR_DRIVER_DICT_NUM': 2, 'MOTOR_NUM': 3},
                'BACK_RIGHT' : {'MOTOR_DRIVER_DICT_NUM': 2, 'MOTOR_NUM': 4},
              }

wheelDictDict = {
                '1': wheelDict01,
                '2': wheelDict01,                
                }

FrontLeftWheel = {'WheelDictNum': 1,
                  'DefaultPulsesPerSecond': 980,
                  'DefaultPwmFreqDutycycle': {'Freq': 1000, 'DutyCycle': 80},                  
                  'TestSeconds': 1,
                 }

# *** Motor Dicts ***

motorDriverDict02 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary v0.2  tlfong01 2021aug23hkt1009',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              }

motorDriverDictDict = {
                '1': motorDriverDict02,
                '2': motorDriverDict02,                
                }

# *** Motor Functions ***

def setupMotor(motorDriverDictNum, motorNum):
    motorDriverDict   = motorDriverDictDict[str(motorDriverDictNum)] # Get driver dict
    driverStandBy     = Pin(motorDriverDict['STANDBY'], Pin.OUT) # create driverStandBy pin object    
    motorIn1          = Pin(motorDriverDict[str(motorNum)]['IN1'], Pin.OUT) # Create Motor # motorNum In1 pin object
    motorIn2          = Pin(motorDriverDict[str(motorNum)]['IN2'], Pin.OUT) # Create Motor # motorNum In2 pin object    
    motorPwm          = PWM(Pin(motorDriverDict[str(motorNum)]['PWM'])) # Create Motor # motorNum Pwm pin object    
    motorEncode       = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) # Create Motor # motorNum Encode pin object
  
    motorPwmFreq      = motorDriverDict[str(motorNum)]['DEFAULT_PWM_FREQ']
    motorDutyCycle    = motorDriverDict[str(motorNum)]['DEFAULT_DUTY_CYCLE']
    motorEncIntCntRev = motorDriverDict[str(motorNum)]['DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION']
    
    motorConfigDict = {'StdBy': driverStandBy, 'In1': motorIn1, 'In2': motorIn2, 'Pwm': motorPwm, 'Encode': motorEncode}    
    
    motorStateDict = {'PwmFreq': motorPwmFreq, 'DutyCycle': motorDutyCycle, 'EncodeIntCntRev': motorEncIntCntRev} 
    
    motorDict = {'MOTOR_CONFIG_DICT': motorConfigDict, 'MOTOR_STATE_DICT': motorStateDict}
    
    motorConfigDict['StdBy'].high() # enable motor driver normal operation, (low = disable)
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          # setup frequency
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle']) #   and duty cycle
    stopMotor(motorConfigDict)
    return motorDict

def changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle):
    motorStateDict = motorDict['MOTOR_STATE_DICT']
    motorStateDict['PwmFreq'] = pwmFreq
    motorStateDict['DutyCycle'] = dutyCycle
    
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle'])
    return

def stopMotor(motorConfigDict):
    motorConfigDict['In1'].low() 
    motorConfigDict['In2'].low() 
    return

def moveMotorForwardNonStop(motorConfigDict):
    motorConfigDict['In1'].low()  # move motor
    motorConfigDict['In2'].high() #   backward
    return

def moveMotorForwardSeconds(motorConfigDict, moveSeconds):
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

def moveMotorBackwardNonStop(motorConfigDict):
    motorConfigDict['In1'].low()  # move motor
    motorConfigDict['In2'].high() #   backward
    return

def moveMotorBackwardSeconds(motorConfigDict, moveSeconds):
    moveMotorBackwardNonStop(motorConfigDict)    
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

# *** Interrupt funtion ***

def encodeIntCallBack(pin):
    global encodeIntCount
    encodeIntCount = encodeIntCount + 1    
    return

def readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    motorDict = setupMotor(motorDriverDictNum, motorNum)   
    motorStateDict  = motorDict['MOTOR_STATE_DICT']
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)    

    motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    
    global encodeIntCount
    encodeIntCount = 0    
    
    moveMotorForwardNonStop(motorConfigDict)  
    utime.sleep(moveSeconds)        
    stopMotor(motorConfigDict)              
    
    totalEncodeIntCount = encodeIntCount
    return totalEncodeIntCount

# *** Move revoloutions and distance ***

def moveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    #intCountPerSecond = 983
    intCountPerSecond = 750     
  
    print('  encodeIntCount    =', encodeIntCount)
    print('  intCountPerSecond =', intCountPerSecond)
 
    moveSeconds = encodeIntCount / intCountPerSecond  
    print('  moveSeconds       =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

def moveMotorRevolutions(motorDriverDictNum, motorNum, revolutions):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    intCountPerRevolution = 983
    intCountPerSecond = 750
    moveSeconds = (revolutions * intCountPerRevolution) / intCountPerSecond     
  
    print('  intCountPerRevolution    =', intCountPerRevolution)
    print('  intCountPerSecond        =', intCountPerSecond)
    print('  moveSeconds              =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

# *** Test Functions *** 

snipped, because 32k words limit reached

# *** Main ***

#testMoveMotorEncodeIntCount(motorDriverDictNum = 2, motorNum = 1, encodeIntCount = 70000)

#testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 50, moveSeconds = 60)
#testStopMotor(motorDriverDictNum = 2, motorNum = 1)

#testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 2, pwmFreq = 1000, dutyCycle = 50, moveSeconds = 60)
testStopMotor(motorDriverDictNum = 2, motorNum = 2)

# End

/ to continue, …

SPV v50.01 Development Notes

Last reply was hitting this forum’s 30k words limit. So I am making this new reply. Let me make a briefly progress report.

(1) Progress so far

I started with a copy cat of Tom’sHardware tutorial of using a motor driver to drive a single motor (MX1508 driving TT130). Everything went OK.

I then used the TB6612FNG motor driver to drive two N20 motor. Everything also went well.

I then used the TB6612FNG motor driver to drive two TT130 motors, and I had a big problem of intermittent failures when trying to sequentially test one motor and then the other, and found that one or other motor could not start, or only could start if I used my hand to help start moving. One starts moving, the motor can move happily. It took me some 4 or 5 hours to conclude that the non start problem might be a software problem, and a software reset might solve the problem. Anyway, I tidied up the program, version 50.01 which can almost always start one motor then another motor, and the other way round. I am saving this almost very good program for later reference.

SPV50.01 Program Listing


(2) Troubleshooting by swapping

I have been using the swapping/pairing trick which I found very effective to locate the trouble making place. Very briefly, I test two motors, with two sets of hardware and wiring. So I can swap motor, connector, wring in seconds, as show in the photo below. Actually I have 4 or 5 identical sets of motors and duPont connectors for fast swapping.



Rpi Pico TB6612FNG TT130 Motor Schematic V5.0



Sticky Motor 2 Cannot Always Self Start Problem

I use the following test to sequentially run Motor 1 and Motor 2 four times.

for count in range(4):
    testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 100, moveSeconds = 2)
    testStopMotor(motorDriverDictNum = 2, motorNum = 1)    
    utime.sleep(1)
    testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 2, pwmFreq = 1000, dutyCycle = 100, moveSeconds = 2)
    testStopMotor(motorDriverDictNum = 2, motorNum = 2)    
    utime.sleep(1)

I still find Motor has a problem to start, and I need to push it a little bit to help it to start. I am not sure if it is the motor’s stick gear box problem, or the motor driver’s second channel sticky problem. I need to do some swapping troubleshooting tomorrow.


TT130 Encoder Motor Calibration Notes

I am going to measure the speeds of a range of motor power, 9V, 7.2V, 6V, 4.5V, and 3V. The picture below shows 9V motor power gives 70rpm.


Now below is the complete list of Vm (V motor) vs speed (rpm)

9.0V = 350mA = 240uS = 70rpm
7.2V = 270mA = 300us = 55rpm
6.0V = 250mA = 340us = 49rpm
4.5V = 200mA = 400us = 42rpm
3.0V = 150mA = 660us = 25rpm

Now what I am curious to know or to calibrate is if I use Vm motor power = 9V, what is the corresponding (PWM freq = 1000Hz) Duty Cycle of 7.2V/55rpm, 6.0V 49rpm 4.5V/42rpm, 3.0V/25rpm?


TT130 Motor Vm vs speed (rpm)

I found speed vs Vm (yellow line) is rather linear. I also need to plot speed vs duty cycle, as I did for N20 motor earlier. So far so good. So I will take a longer break this time.


Vehicle Dictionary Config

So far we have been assigning each of the two motors by two numbers: motorDictNum, motorNum. So far so good. We have tested OK all the motor dependant functions by two numbers motorDictNum, motorNum

Now we are going the motor dictionary one level up, to vehicle dictionary vechicleDict. Each vehicleDict configs on vehicle which has two motors.

Earlier to setup two motors, we need to set two motors one by one. Now we can set up the two motors of one vehicle in one go, by just saying setup vehicle,

All the low functions stays the same, it is only the higher vehicle dictionaries and functions are new. In other words if the vehicle dicts and function are working, all the lower calling functions should work without any problem (this is the beauty of “Functional Programming”.

The new vehicle dictionaries and functions are listed below:

# *** Vehicle Config ***

vehicleDict01 = {
                  'MOTOR_DRIVER_DICT_NUM': 2,
                }

vehicleDictDict = {
                     '1': vehicleDict01,
                     '2': vehicleDict01,
                  }

# *** Vehicle Functions ***

def setupVehicle(vehicleNum):
    motorDriverDictNum = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    setupMotor(motorDriverDictNum, 1)
    setupMotor(motorDriverDictNum, 2)
    return

Vehicle Abstract Level Functions

Now I am starting off to write the first coouple of vehicle level functions. Below is an example.

# *** Old Motor Abstract Level Function ***
# Motor Level ADT (Abstract Data Type) on moving motor forward
def testMoveMotorForwardSeconds(motorDriverDictNum, motorNum, moveSeconds):
    print('\ntestmoveMotorForwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    moveMotorForwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

# Example call
testMoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1, moveSeconds = 2)

# *** Proposed New Vehicle Abstract Level Functions ***

def testVehicleMoveOneMotor(vehicleNum, wheelNum, actionNum, speedNum, moveSeconds):
    ...
    return

Progress report of writing vehicle level functions

Writing the vehicle functions has been as smooth as I expected. I first wrote a function to move a vehicle’s two front motors, then another function to stop the front two motors. The test sample below (1) run the two motors non stop, (2) hold 4 seconds, (3) stop two motors.

# *** Vehicle Config ***

vehicleDict01 = {
                  'MOTOR_DRIVER_DICT_NUM': 2,
                }

vehicleDictDict = {
                     '1': vehicleDict01,
                     '2': vehicleDict01,
                  }

# *** Vehicle and Wheel Functions ***

def setupVehicle(vehicleNum):
    motorDriverDictNum  = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    frontLeftMotorDict  = setupMotor(motorDriverDictNum, 1)
    frontRightMotorDict = setupMotor(motorDriverDictNum, 2)
    motorDictDict = {'FRONT_LEFT_MOTOR': frontLeftMotorDict, 'FRONT_RIGHT_MOTOR': frontRightMotorDict}
    return motorDictDict

def moveVehicleTwoFrontMotorsForwardNonStop(vehicleNum):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    moveMotorForwardNonStop(frontLeftMotorConfigDict)
    moveMotorForwardNonStop(frontRightMotorConfigDict)    
    return

def stopVehicleTwoFrontMotors(vehicleNum):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']   
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

def testMoveVehicleTwoFrontMotorsForwardNonStop():
    moveVehicleTwoFrontMotorsForwardNonStop(vehicleNum = 1)
    return

def testStopVehicleTwoFrontMotors():
    stopVehicleTwoFrontMotors(vehicleNum = 1)
    return

# *** Sample Test ***

# testMoveVehicleTwoFrontMotorsForwardNonStop()
# utime.sleep(4)
# testStopVehicleTwoFrontMotors()

Move vehicle forward and backward, turn vehicle left and right tested OK

Only four statements are need to move vehicle forward and backward, turn left and right, as listed below:

# test move vehicle forward and backward, turn vehicle left and right
testMoveVehicleMotorListForwardHoldTime()   # move forward  4 seconds
testMoveVehicleMotorListBackwardHoldTime()  # move backward 4 seconds
testTurnVehicleLeftHoldTime()               # turn left     4 seconds
testTurnVehicleRightHoldTime()              # turn right    4 seconds

Full listing below:

# Program Name
#   spv5011_2021sep0504.py - tlfong01 2021sep05hkt1927

# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Aspire XC-780 Intel CORE i5-6400 2.7GHz, 8GB, Chinese Windows 10 Home (64-bit, 2020nov15),
#   Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# DC Motor
#   TT130 1:48 gear motor 
#     9.0V = 350mA = 240uS = 70rpm
#     7.2V = 270mA = 300us = 55rpm
#     6.0V = 250mA = 340us = 49rpm
#     4.5V = 200mA = 400us = 42rpm
#     3.0V = 150mA = 660us = 25rpm
#   N20 1:100 6V DC Gear Motor with quadrature encoder signals A, B
#   N20 Gear Motor Spec (6V gear 1:100 no load speed = 300 rpm (https://www.pololu.com/search/compare/173)
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Brief Description of Program Function
#   1. Move DC motor forward, backware, stop.
#   2. Use PWM to control motor speed
#   3. Use motor encoder to measure speed
#   4. Use encoder signals to help driving a 2WD to move in a straight line

# Digital Oscilloscope ATTEN ADS1102CAL+
# https://toolboom.com/en/digital-oscilloscope-atten-ads1102calplus/

# ATTEN ADS100 User Manual
# https://micromir.ucoz.ru/Oscil/Atten/ADS1000_User_Manual.pdf

import utime
from machine import Pin, PWM

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Motor Level Config and Functions ***

# *** Motor Dicts ***

motorDriverDict02 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary v0.2  tlfong01 2021aug23hkt1009',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},                 
              }

motorDriverDictDict = {
                '1': motorDriverDict02,
                '2': motorDriverDict02,                
                }

# *** Motor Functions ***

def setupMotor(motorDriverDictNum, motorNum):
    motorDriverDict   = motorDriverDictDict[str(motorDriverDictNum)] # Get driver dict
    driverStandBy     = Pin(motorDriverDict['STANDBY'], Pin.OUT) # create driverStandBy pin object    
    motorIn1          = Pin(motorDriverDict[str(motorNum)]['IN1'], Pin.OUT) # Create Motor # motorNum In1 pin object
    motorIn2          = Pin(motorDriverDict[str(motorNum)]['IN2'], Pin.OUT) # Create Motor # motorNum In2 pin object    
    motorPwm          = PWM(Pin(motorDriverDict[str(motorNum)]['PWM'])) # Create Motor # motorNum Pwm pin object    
    motorEncode       = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) # Create Motor # motorNum Encode pin object
  
    motorPwmFreq      = motorDriverDict[str(motorNum)]['DEFAULT_PWM_FREQ']
    motorDutyCycle    = motorDriverDict[str(motorNum)]['DEFAULT_DUTY_CYCLE']
    motorEncIntCntRev = motorDriverDict[str(motorNum)]['DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION']
    
    motorConfigDict = {'StdBy': driverStandBy, 'In1': motorIn1, 'In2': motorIn2, 'Pwm': motorPwm, 'Encode': motorEncode}    
    
    motorStateDict = {'PwmFreq': motorPwmFreq, 'DutyCycle': motorDutyCycle, 'EncodeIntCntRev': motorEncIntCntRev} 
    
    motorDict = {'MOTOR_CONFIG_DICT': motorConfigDict, 'MOTOR_STATE_DICT': motorStateDict}
    
    motorConfigDict['StdBy'].high() # enable motor driver normal operation, (low = disable)
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          # setup frequency
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle']) #   and duty cycle
    stopMotor(motorConfigDict)
    return motorDict

def changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle):
    motorStateDict = motorDict['MOTOR_STATE_DICT']
    motorStateDict['PwmFreq'] = pwmFreq
    motorStateDict['DutyCycle'] = dutyCycle
    
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle'])
    return

def stopMotor(motorConfigDict):
    motorConfigDict['In1'].low() 
    motorConfigDict['In2'].low() 
    return

def moveMotorForwardNonStop(motorConfigDict):
    motorConfigDict['In1'].low()  # move motor
    motorConfigDict['In2'].high() #   backward
    return

def moveMotorForwardSeconds(motorConfigDict, moveSeconds):
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

def moveMotorForwardHoldTime(motorConfigDict, holdTime):
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(holdTime)    
    stopMotor(motorConfigDict)
    return

def moveMotorBackwardNonStop(motorConfigDict):
    motorConfigDict['In1'].high()  # move motor
    motorConfigDict['In2'].low() #   backward
    return

def moveMotorBackwardSeconds(motorConfigDict, moveSeconds):
    moveMotorBackwardNonStop(motorConfigDict)    
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Interrupt Related Functions ***

def encodeIntCallBack(pin):
    global encodeIntCount
    encodeIntCount = encodeIntCount + 1    
    return

def readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    motorDict = setupMotor(motorDriverDictNum, motorNum)   
    motorStateDict  = motorDict['MOTOR_STATE_DICT']
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)    

    motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    
    global encodeIntCount
    encodeIntCount = 0    
    
    moveMotorForwardNonStop(motorConfigDict)  
    utime.sleep(moveSeconds)        
    stopMotor(motorConfigDict)              
    
    totalEncodeIntCount = encodeIntCount
    return totalEncodeIntCount

# *** Move revoloutions and distance ***

def moveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    #intCountPerSecond = 983
    intCountPerSecond = 750     
  
    print('  encodeIntCount    =', encodeIntCount)
    print('  intCountPerSecond =', intCountPerSecond)
 
    moveSeconds = encodeIntCount / intCountPerSecond  
    print('  moveSeconds       =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

def moveMotorRevolutions(motorDriverDictNum, motorNum, revolutions):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    intCountPerRevolution = 983
    intCountPerSecond = 750
    moveSeconds = (revolutions * intCountPerRevolution) / intCountPerSecond     
  
    print('  intCountPerRevolution    =', intCountPerRevolution)
    print('  intCountPerSecond        =', intCountPerSecond)
    print('  moveSeconds              =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

# *** Test Functions *** 

def testMoveMotorForwardSeconds(motorDriverDictNum, motorNum, moveSeconds):
    print('\ntestmoveMotorForwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    moveMotorForwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

def testMoveMotorBackwardSeconds(motorDriverDictNum, motorNum, moveSeconds):
    print('\ntestMoveMotorBackwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    moveMotorBackwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

def testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    print('\ntestChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)
    
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    print('moveSeconds =', moveSeconds)
    moveMotorForwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return 

def testMeasureMotorSpeed(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):    
    totalEncodeIntCount = readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds = 1)        
    print('motorNum =', motorNum, end = '')
    print('  pwmFreq   =', pwmFreq, end = '')
    print('  dutyCycle =', dutyCycle, end = '')
    print('  totalEncodeIntCount Per Second =', totalEncodeIntCount, end = '')
    print('  motor speed rpm =', int(int(totalEncodeIntCount * 60)/100))              
    return

def testMoveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount):
    print('testMoveMotorEncodeIntCount(), ...')
    moveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount)
    return

def testMoveMotorRevolutions(motorDriverDictNum, motorNum, revolutions):
    print('testMoveMotorRevolutions(), ...')
    moveMotorRevolutions(motorDriverDictNum, motorNum, revolutions)
    return

def testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    encodeIntCount = readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds)
    print('MotorDriverDictNum =', motorDriverDictNum, ' ', end = '')
    print('MotorNum =', motorNum, ' ', end = '')
    print('PWM Freq =', pwmFreq, ' ', end = '')
    print('DutyCycle =', dutyCycle, ' ', end = '')
    print('MoveSeconds =,', moveSeconds, ' ', end = '')            
    print('encodeIntCount =', encodeIntCount)
    return encodeIntCount

def testStopMotor(motorDriverDictNum, motorNum):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    stopMotor(motorConfigDict)     
    return

# *** Old Main Tests ***

'''
testMoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1, moveSeconds = 2)
testMoveMotorBackwardSeconds(motorDriverDictNum = 2, motorNum = 2, moveSeconds = 4)
testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1 , \                                              pwmFreq = 1000, dutyCycle = 10, moveSeconds = 2)                               
testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1 , \ 
                                                     pwmFreq = 1000, dutyCycle = 90, moveSeconds = 1)
testMeasureMotorSpeed(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 80)
testMeasureMotorSpeed(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 10)
testMoveMotorEncodeIntCount(motorDriverDictNum = 2, motorNum = 1, encodeIntCount = 1020)
testMoveMotorEncodeIntCount(motorDriverDictNum = 2, motorNum = 2, encodeIntCount = 1920)
testMoveMotorRevolutions(motorDriverDictNum = 2, motorNum = 1, revolutions = 1)
testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 100, moveSeconds = 4)
testStopMotor(motorDriverDictNum = 2, motorNum = 1)
'''

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

#
# *** Vehicle Level Config and Functions ***

vehicleDict01 = {
                  'MOTOR_DRIVER_DICT_NUM': 2,
                }

vehicleDictDict = {
                     '1': vehicleDict01,
                     '2': vehicleDict01,
                  }

# *** Vehicle and Wheel Functions ***

def setupVehicle(vehicleNum):
    motorDriverDictNum  = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    frontLeftMotorDict  = setupMotor(motorDriverDictNum, 1)
    frontRightMotorDict = setupMotor(motorDriverDictNum, 2)
    motorDictDict = {'FRONT_LEFT_MOTOR': frontLeftMotorDict, 'FRONT_RIGHT_MOTOR': frontRightMotorDict}
    return motorDictDict

def moveVehicleMotorListForwardHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    motorConfigDictList = [frontLeftMotorConfigDict,  frontRightMotorConfigDict] 
    for motorConfigDict in motorConfigDictList:
        moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(holdTime)
    for motorConfigDict in motorConfigDictList:
       stopMotor(motorConfigDict) 
    return

def moveVehicleMotorListBackwardHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    motorConfigDictList = [frontLeftMotorConfigDict,  frontRightMotorConfigDict] 
    for motorConfigDict in motorConfigDictList:
        moveMotorBackwardNonStop(motorConfigDict)
    utime.sleep(holdTime)
    for motorConfigDict in motorConfigDictList:
       stopMotor(motorConfigDict) 
    return

def turnTwoMotorsLeftHoldTime(frontLeftMotorConfigDict, frontReghtConfigDict, holdTime):
    moveMotorForwardNonStop(frontLeftMotorConfigDict)
    moveMotorBackward(frontRightMotorConfigDict)    
    utime.sleep(holdTime)    
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

def turnVehicleLeftHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    moveMotorForwardNonStop(frontLeftMotorConfigDict)
    moveMotorBackwardNonStop(frontRightMotorConfigDict)    
    utime.sleep(holdTime)
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

def turnVehicleRightHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    moveMotorBackwardNonStop(frontLeftMotorConfigDict)
    moveMotorForwardNonStop(frontRightMotorConfigDict)    
    utime.sleep(holdTime)
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

# *** Test Functions ***

def testMoveVehicleMotorListForwardHoldTime():
    moveVehicleMotorListForwardHoldTime(vehicleNum = 1, holdTime = 4)
    return

def testMoveVehicleMotorListBackwardHoldTime():
    moveVehicleMotorListBackwardHoldTime(vehicleNum = 1, holdTime = 4)
    return

def testTurnVehicleLeftHoldTime():
    turnVehicleLeftHoldTime(vehicleNum = 1, holdTime = 4)
    return

def testTurnVehicleRightHoldTime():
    turnVehicleRightHoldTime(vehicleNum = 1, holdTime = 4)
    return

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Main Tests ***

# * test move vehicle forward and backward, turn vehicle left and right
testMoveVehicleMotorListForwardHoldTime()   # move forward  4 seconds
testMoveVehicleMotorListBackwardHoldTime()  # move backward 4 seconds
testTurnVehicleLeftHoldTime()               # turn left     4 secopnds
testTurnVehicleRightHoldTime()              # turn right    4 seconds
    
# End

Now the youtube.


How to sync the two motors

We can see that the two motors do not sync, in the sense that the two wheels, each with the white stripe (cable tie), should move at the same speed.

In other words, if they start being adjacent to each other, they should always stay adjacent after any number of revolutions, forward or backward. And for turning left and right, they should go opposite direction, but stay adjacent again (for a split second) after one revolution.

I have not yet any solid idea of how to do the sync. It would be nice if any interested readers can reply with suggestions.


My first version of interrupt function works OK, but only for one motor. Now I am trying to modify it for use in 2WD and 4WD. The first draft is listed below.

# *** Interrupt Functions - Draft v0.1 2021sep06hkt1632 ***

# Design Notes

# 1. I am drafting the interrupt functions for 2WD, scalable to 4WD
# 2. / to continue, ...

def setupMotorIntCallBacks(VehicleNum = 1):
    # draft only!!! 
    #motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    #motorStateDict  = motorDict['MOTOR_STATE_DICT']
    #motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    return 

def motorEncodeIntCallBack1(pin):
    global motorEncodeIntCount1
    motorEncodeIntCount1 = motorEncodeIntCount1 + 1
    return

def motorEncodeIntCallBack2(pin):
    global motorEncodeIntCount2
    motorEncodeIntCount2 = motorEncodeIntCount2 + 1
    return

def motorEncodeIntCallBack3(pin):
    global motorEncodeIntCount3
    motorEncodeIntCount3 = motorEncodeIntCount3 + 1
    return

def motorEncodeIntCallBack4(pin):
    global motorEncodeInt4
    motorEncodeIntCount4 = motorEncodeIntCount4 + 1
    return

def readMotorEncodeIntEventCount1(motorDict, countTime):    
    global motorEncodeInt1
    return motorEncodeInt1

def readMotorEncodeIntEventCount2(motorDict, countTime):    
    global motorEncodeInt2
    return motorEncodeInt2

def readMotorEncodeIntEventCount3(motorDict, countTime):    
    global motorEncodeInt3
    return motorEncodeInt3

def readMotorEncodeIntEventCount4(motorDict, countTime):    
    global motorEncodeInt4
    return motorEncodeInt4

I am hitting the forum’s 32k limit. So I am making another reply to move on, …

/ to continue, …

I am drafting a test function to proving the concept of:

  1. Using a list of two interrupt event counts of this 2WD (later 4 counts for 4WD).

  2. Using only one callback function for 2WD’s two interrupt pins.


# *** Interrupt Functions ***

# *** Interrupt Event Variables and Service Routines *** 

# *** Setup Motor and Vehicle ***

def setupMotorV2(motorDriverDictNum, motorNum):
    motorDriverDict         = motorDriverDictDict[str(motorDriverDictNum)] # Get driver dict
    driverStandBy           = Pin(motorDriverDict['STANDBY'], Pin.OUT) # create driverStandBy pin object    
    motorIn1                = Pin(motorDriverDict[str(motorNum)]['IN1'], Pin.OUT) # Create Motor # motorNum In1 pin object
    motorIn2                = Pin(motorDriverDict[str(motorNum)]['IN2'], Pin.OUT) # Create Motor # motorNum In2 pin object    
    motorPwm                = PWM(Pin(motorDriverDict[str(motorNum)]['PWM'])) # Create Motor # motorNum Pwm pin object    
    motorEncode             = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) # Create Motor # motorNum Encode pin object
    motorPwmFreq            = motorDriverDict[str(motorNum)]['DEFAULT_PWM_FREQ']
    motorDutyCycle          = motorDriverDict[str(motorNum)]['DEFAULT_DUTY_CYCLE']
    motorEncodeIntCount     = 0   
    motorConfigDict = {'StdBy': driverStandBy, 'In1': motorIn1, 'In2': motorIn2, 'Pwm': motorPwm, 'Encode': motorEncode}
    motorConfigDict['StdBy'].high() # enable motor driver normal operation, (low = disable)
    motorConfigDict['Pwm'].freq(motorPwmFreq)                          # setup frequency
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorDutyCycle) #   and duty cycle    
    motorStateDict = {'PwmFreq': motorPwmFreq, 'DutyCycle': motorDutyCycle, 'MotorEncodeIntCount': motorEncodeIntCount}     
    motorDict = {'MOTOR_CONFIG_DICT': motorConfigDict, 'MOTOR_STATE_DICT': motorStateDict}    
    stopMotor(motorConfigDict)
    return motorDict

def setupVehicleV2(vehicleNum):
    print('  setupVehicle(), ...')
    motorDriverDictNum  = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    frontLeftMotorDict  = setupMotorV2(motorDriverDictNum, 1)
    frontRightMotorDict = setupMotorV2(motorDriverDictNum, 2)
    frontLeftMotorInterruptPin  = frontLeftMotorDict['MOTOR_CONFIG_DICT']['Encode']
    frontRightMotorInterruptPin = frontRightMotorDict['MOTOR_CONFIG_DICT']['Encode']
    motorInterruptPinList = [frontLeftMotorInterruptPin, frontRightMotorInterruptPin]    
    vehicleDict = {'FRONT_LEFT_MOTOR': frontLeftMotorDict, 'FRONT_RIGHT_MOTOR': frontRightMotorDict, 'INTERRUPT_PIN_LIST': motorInterruptPinList}  
    return vehicleDict

def testSyncTwoVehicleWheels():
    print('testSyncTwoVehicleWheels(), ...')   
  
    # *** This function reads one of the interrupt count list *** 
    def readMotorEncodeIntCount(countNum):    
        return motorEncodeIntCount[countNum]

    # *** motorEncodeCallBack ***
    # Notes:
    #   This callback function is a bit too clever to understand.  All motor interrupt pins will trigger this callback.
    #   The callback will find which interrupt pin interrupts, then increment the corresponding element of the int count list.    
    def motorEncodeIntCallBack(pin):
        index = interruptPinList.index(pin)
        motorEncodeIntCountList[index] += 1    
        return        
    
    vehicleDict = setupVehicleV2(vehicleNum = 1)
    
    vehicleDict['INTERRUPT_PIN_LIST'][0].irq(motorEncodeIntCallBack, Pin.IRQ_FALLING)    
    vehicleDict['INTERRUPT_PIN_LIST'][1].irq(motorEncodeIntCallBack, Pin.IRQ_FALLING)     
    
    #print('  motorEncodeIntServiceRoutine[2] =', motorEncodeIntCountList[2])    
    
    return

# *** Main Tests ***

#testMoveVehicleForwardBackwardTurnLeftTurnRight()

testSyncTwoVehicleWheels()
    
# End

I was jealous that PiTop4 can use mpu6050 gesture to control a robot. This morning I read how to do it. So I might try later to use my smart phone to control my smart robot, to say, first walk in a straight line.

Raspberry Pi Pico Controls Robot with Smartphone Accelerometer - Ash Hill, 2021sep07

Raspberry Pi: Python Libraries for I2C, SPI, UART - Sebastian Günther, 2021sep06


I feel jealous that some smart guy has a smart 4WD, while mine is only a stupid looking 2WD, which should damage my reputation. So I am stalling my 2WD project and go build a smart 4WD. Stay tuned. See you later.


Scaling up 2WD to 4WD

So there you are, 2WD becomes 4WD!


# Program Name
#   spv5013_2021sep0603.py - tlfong01 2021sep06hkt1802

# Reference
#   Pi-Top Forum - making-a-rpi-pico-based-smart-vehicle/924
#   https://forum.pi-top.com/t/making-a-rpi-pico-based-smart-vehicle/924
#
# Configuration
#   Acer Aspire XC-780 Intel CORE i5-6400 2.7GHz, 8GB, Chinese Windows 10 Home (64-bit, 2020nov15),
#   Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# DC Motor
#   TT130 1:48 gear motor 
#     9.0V = 350mA = 240uS = 70rpm
#     7.2V = 270mA = 300us = 55rpm
#     6.0V = 250mA = 340us = 49rpm
#     4.5V = 200mA = 400us = 42rpm
#     3.0V = 150mA = 660us = 25rpm
#   N20 1:100 6V DC Gear Motor with quadrature encoder signals A, B
#   N20 Gear Motor Spec (6V gear 1:100 no load speed = 300 rpm (https://www.pololu.com/search/compare/173)
#   
# DC Motor Driver
#   TB6612FNG Dual DC Motor Driver

# Brief Description of Program Function
#   1. Move DC motor forward, backware, stop.
#   2. Use PWM to control motor speed
#   3. Use motor encoder to measure speed
#   4. Use encoder signals to help driving a 2WD to move in a straight line

# Digital Oscilloscope ATTEN ADS1102CAL+
# https://toolboom.com/en/digital-oscilloscope-atten-ads1102calplus/

# ATTEN ADS100 User Manual
# https://micromir.ucoz.ru/Oscil/Atten/ADS1000_User_Manual.pdf

import utime
from machine import Pin, PWM

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Motor Level Config and Functions ***

# *** Motor Dicts ***

motorDriverDict02 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary v0.2  tlfong01 2021aug23hkt1009',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},                 
              }

motorDriverDictDict = {
                '1': motorDriverDict02,
                '2': motorDriverDict02,                
                }

# *** Motor Functions ***

def setupMotor(motorDriverDictNum, motorNum):
    motorDriverDict   = motorDriverDictDict[str(motorDriverDictNum)] # Get driver dict
    driverStandBy     = Pin(motorDriverDict['STANDBY'], Pin.OUT) # create driverStandBy pin object    
    motorIn1          = Pin(motorDriverDict[str(motorNum)]['IN1'], Pin.OUT) # Create Motor # motorNum In1 pin object
    motorIn2          = Pin(motorDriverDict[str(motorNum)]['IN2'], Pin.OUT) # Create Motor # motorNum In2 pin object    
    motorPwm          = PWM(Pin(motorDriverDict[str(motorNum)]['PWM'])) # Create Motor # motorNum Pwm pin object    
    motorEncode       = Pin(motorDriverDict[str(motorNum)]['ENCODE'], Pin.IN, Pin.PULL_DOWN) # Create Motor # motorNum Encode pin object
  
    motorPwmFreq      = motorDriverDict[str(motorNum)]['DEFAULT_PWM_FREQ']
    motorDutyCycle    = motorDriverDict[str(motorNum)]['DEFAULT_DUTY_CYCLE']
    motorEncIntCntRev = motorDriverDict[str(motorNum)]['DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION']
    
    motorConfigDict = {'StdBy': driverStandBy, 'In1': motorIn1, 'In2': motorIn2, 'Pwm': motorPwm, 'Encode': motorEncode}    
    
    motorStateDict = {'PwmFreq': motorPwmFreq, 'DutyCycle': motorDutyCycle, 'EncodeIntCntRev': motorEncIntCntRev} 
    
    motorDict = {'MOTOR_CONFIG_DICT': motorConfigDict, 'MOTOR_STATE_DICT': motorStateDict}
    
    motorConfigDict['StdBy'].high() # enable motor driver normal operation, (low = disable)
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          # setup frequency
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle']) #   and duty cycle
    stopMotor(motorConfigDict)
    return motorDict

def changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle):
    motorStateDict = motorDict['MOTOR_STATE_DICT']
    motorStateDict['PwmFreq'] = pwmFreq
    motorStateDict['DutyCycle'] = dutyCycle
    
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    motorConfigDict['Pwm'].freq(motorStateDict['PwmFreq'])                          
    motorConfigDict['Pwm'].duty_u16(int(65536 / 100) * motorStateDict['DutyCycle'])
    return

def stopMotor(motorConfigDict):
    motorConfigDict['In1'].low() 
    motorConfigDict['In2'].low() 
    return

def moveMotorForwardNonStop(motorConfigDict):
    motorConfigDict['In1'].low()  # move motor
    motorConfigDict['In2'].high() #   backward
    return

def moveMotorForwardSeconds(motorConfigDict, moveSeconds):
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

def moveMotorForwardHoldTime(motorConfigDict, holdTime):
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(holdTime)    
    stopMotor(motorConfigDict)
    return

def moveMotorBackwardNonStop(motorConfigDict):
    motorConfigDict['In1'].high()  # move motor
    motorConfigDict['In2'].low() #   backward
    return

def moveMotorBackwardSeconds(motorConfigDict, moveSeconds):
    moveMotorBackwardNonStop(motorConfigDict)    
    utime.sleep(moveSeconds)    
    stopMotor(motorConfigDict)
    return

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Interrupt Related Functions ***

def encodeIntCallBack(pin):
    global encodeIntCount
    encodeIntCount = encodeIntCount + 1    
    return

def readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    motorDict = setupMotor(motorDriverDictNum, motorNum)   
    motorStateDict  = motorDict['MOTOR_STATE_DICT']
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)    

    motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    
    global encodeIntCount
    encodeIntCount = 0    
    
    moveMotorForwardNonStop(motorConfigDict)  
    utime.sleep(moveSeconds)        
    stopMotor(motorConfigDict)              
    
    totalEncodeIntCount = encodeIntCount
    return totalEncodeIntCount

# *** Move revoloutions and distance ***

def moveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    #intCountPerSecond = 983
    intCountPerSecond = 750     
  
    print('  encodeIntCount    =', encodeIntCount)
    print('  intCountPerSecond =', intCountPerSecond)
 
    moveSeconds = encodeIntCount / intCountPerSecond  
    print('  moveSeconds       =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

def moveMotorRevolutions(motorDriverDictNum, motorNum, revolutions):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']    
        
    intCountPerRevolution = 983
    intCountPerSecond = 750
    moveSeconds = (revolutions * intCountPerRevolution) / intCountPerSecond     
  
    print('  intCountPerRevolution    =', intCountPerRevolution)
    print('  intCountPerSecond        =', intCountPerSecond)
    print('  moveSeconds              =', moveSeconds)      
       
    moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(moveSeconds)
    stopMotor(motorConfigDict)
    return

# *** Test Functions *** 

def testMoveMotorForwardSeconds(motorDriverDictNum, motorNum, moveSeconds):
    print('\ntestmoveMotorForwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    moveMotorForwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

def testMoveMotorBackwardSeconds(motorDriverDictNum, motorNum, moveSeconds):
    print('\ntestMoveMotorBackwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    moveMotorBackwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return

def testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    print('\ntestChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds()')
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)
    
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    print('moveSeconds =', moveSeconds)
    moveMotorForwardSeconds(motorConfigDict, moveSeconds)
    stopMotor(motorConfigDict)     
    return 

def testMeasureMotorSpeed(motorDriverDictNum, motorNum, pwmFreq, dutyCycle):    
    totalEncodeIntCount = readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds = 1)        
    print('motorNum =', motorNum, end = '')
    print('  pwmFreq   =', pwmFreq, end = '')
    print('  dutyCycle =', dutyCycle, end = '')
    print('  totalEncodeIntCount Per Second =', totalEncodeIntCount, end = '')
    print('  motor speed rpm =', int(int(totalEncodeIntCount * 60)/100))              
    return

def testMoveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount):
    print('testMoveMotorEncodeIntCount(), ...')
    moveMotorEncodeIntCount(motorDriverDictNum, motorNum, encodeIntCount)
    return

def testMoveMotorRevolutions(motorDriverDictNum, motorNum, revolutions):
    print('testMoveMotorRevolutions(), ...')
    moveMotorRevolutions(motorDriverDictNum, motorNum, revolutions)
    return

def testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    encodeIntCount = readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds)
    print('MotorDriverDictNum =', motorDriverDictNum, ' ', end = '')
    print('MotorNum =', motorNum, ' ', end = '')
    print('PWM Freq =', pwmFreq, ' ', end = '')
    print('DutyCycle =', dutyCycle, ' ', end = '')
    print('MoveSeconds =,', moveSeconds, ' ', end = '')            
    print('encodeIntCount =', encodeIntCount)
    return encodeIntCount

def testStopMotor(motorDriverDictNum, motorNum):
    motorDict = setupMotor(motorDriverDictNum, motorNum)
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    stopMotor(motorConfigDict)     
    return

# *** Old Main Tests ***

'''
testMoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1, moveSeconds = 2)
testMoveMotorBackwardSeconds(motorDriverDictNum = 2, motorNum = 2, moveSeconds = 4)
testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1 , \                                              pwmFreq = 1000, dutyCycle = 10, moveSeconds = 2)                               
testChangeMotorPwmFreqAndDutyCycleAndmoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1 , \ 
                                                     pwmFreq = 1000, dutyCycle = 90, moveSeconds = 1)
testMeasureMotorSpeed(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 80)
testMeasureMotorSpeed(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 10)
testMoveMotorEncodeIntCount(motorDriverDictNum = 2, motorNum = 1, encodeIntCount = 1020)
testMoveMotorEncodeIntCount(motorDriverDictNum = 2, motorNum = 2, encodeIntCount = 1920)
testMoveMotorRevolutions(motorDriverDictNum = 2, motorNum = 1, revolutions = 1)
testMeasureEncodeIntCountMoveSeconds(motorDriverDictNum = 2, motorNum = 1, pwmFreq = 1000, dutyCycle = 100, moveSeconds = 4)
testStopMotor(motorDriverDictNum = 2, motorNum = 1)
'''

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

#
# *** Vehicle Level Config and Functions ***

vehicleDict01 = {
                  'MOTOR_DRIVER_DICT_NUM': 2,
                }

vehicleDictDict = {
                     '1': vehicleDict01,
                     '2': vehicleDict01,
                  }

# *** Vehicle and Wheel Functions ***


'''
def encodeIntCallBack(pin):
    global encodeIntCount
    encodeIntCount = encodeIntCount + 1    
    return

def readEncodeIntCount(motorDriverDictNum, motorNum, pwmFreq, dutyCycle, moveSeconds):
    motorDict = setupMotor(motorDriverDictNum, motorNum)   
    motorStateDict  = motorDict['MOTOR_STATE_DICT']
    motorConfigDict = motorDict['MOTOR_CONFIG_DICT']
    changeMotorPwmFreqAndDutyCycle(motorDict, pwmFreq, dutyCycle)    

    motorConfigDict['Encode'].irq(encodeIntCallBack, Pin.IRQ_FALLING)
    
    global encodeIntCount
    encodeIntCount = 0    
    
    moveMotorForwardNonStop(motorConfigDict)  
    utime.sleep(moveSeconds)        
    stopMotor(motorConfigDict)              
    
    totalEncodeIntCount = encodeIntCount
    return totalEncodeIntCount
    
motorDriverDict02 = {
              'TITLE' : 'TB6612FNG Dual DC Motor Driver Dictionary v0.2  tlfong01 2021aug23hkt1009',
              'STANDBY' : 5, 
              '1': {'IN1': 10, 'IN2': 11, 'PWM' : 3, 'ENCODE': 14, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '2': {'IN1': 12, 'IN2': 13, 'PWM' : 4, 'ENCODE': 15, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '3': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},
              '4': {'IN1':  0, 'IN2':  0, 'PWM' : 0, 'ENCODE':  0, \
                    'DEFAULT_PWM_FREQ': 1000, 'DEFAULT_DUTY_CYCLE': 50, 'DEFAULT_ENCODE_INT_PULSE_PER_REVOLUTION': 980},                 
              }    
'''

def setupVehicle(vehicleNum):
    motorDriverDictNum  = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    frontLeftMotorDict  = setupMotor(motorDriverDictNum, 1)
    frontRightMotorDict = setupMotor(motorDriverDictNum, 2)
    motorDictDict = {'FRONT_LEFT_MOTOR': frontLeftMotorDict, 'FRONT_RIGHT_MOTOR': frontRightMotorDict}
    return motorDictDict

def moveVehicleMotorListForwardHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    motorConfigDictList = [frontLeftMotorConfigDict,  frontRightMotorConfigDict] 
    for motorConfigDict in motorConfigDictList:
        moveMotorForwardNonStop(motorConfigDict)
    utime.sleep(holdTime)
    for motorConfigDict in motorConfigDictList:
       stopMotor(motorConfigDict) 
    return

def moveVehicleMotorListBackwardHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    motorConfigDictList = [frontLeftMotorConfigDict,  frontRightMotorConfigDict] 
    for motorConfigDict in motorConfigDictList:
        moveMotorBackwardNonStop(motorConfigDict)
    utime.sleep(holdTime)
    for motorConfigDict in motorConfigDictList:
       stopMotor(motorConfigDict) 
    return

def turnTwoMotorsLeftHoldTime(frontLeftMotorConfigDict, frontReghtConfigDict, holdTime):
    moveMotorForwardNonStop(frontLeftMotorConfigDict)
    moveMotorBackward(frontRightMotorConfigDict)    
    utime.sleep(holdTime)    
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

def turnVehicleLeftHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    moveMotorForwardNonStop(frontLeftMotorConfigDict)
    moveMotorBackwardNonStop(frontRightMotorConfigDict)    
    utime.sleep(holdTime)
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

def turnVehicleRightHoldTime(vehicleNum, holdTime):
    motorDictDict = setupVehicle(vehicleNum)    
    frontLeftMotorConfigDict  = motorDictDict['FRONT_LEFT_MOTOR']['MOTOR_CONFIG_DICT']
    frontRightMotorConfigDict = motorDictDict['FRONT_RIGHT_MOTOR']['MOTOR_CONFIG_DICT']       
    moveMotorBackwardNonStop(frontLeftMotorConfigDict)
    moveMotorForwardNonStop(frontRightMotorConfigDict)    
    utime.sleep(holdTime)
    stopMotor(frontLeftMotorConfigDict)
    stopMotor(frontRightMotorConfigDict)    
    return

snipped … to avoid 32k word limit

def setupVehicleV2(vehicleNum):
    print('  setupVehicle(), ...')
    motorDriverDictNum  = vehicleDictDict[str(vehicleNum)]['MOTOR_DRIVER_DICT_NUM']
    frontLeftMotorDict  = setupMotorV2(motorDriverDictNum, 1)
    frontRightMotorDict = setupMotorV2(motorDriverDictNum, 2)
    frontLeftMotorInterruptPin  = frontLeftMotorDict['MOTOR_CONFIG_DICT']['Encode']
    frontRightMotorInterruptPin = frontRightMotorDict['MOTOR_CONFIG_DICT']['Encode']
    motorInterruptPinList = [frontLeftMotorInterruptPin, frontRightMotorInterruptPin]    
    vehicleDict = {'FRONT_LEFT_MOTOR': frontLeftMotorDict, 'FRONT_RIGHT_MOTOR': frontRightMotorDict, 'INTERRUPT_PIN_LIST': motorInterruptPinList}  
    return vehicleDict

def testSyncTwoVehicleWheels():
    print('testSyncTwoVehicleWheels(), ...')   
  
    # *** This function reads one of the interrupt count list *** 
    def readMotorEncodeIntCount(countNum):    
        return motorEncodeIntCount[countNum]

    # *** motorEncodeCallBack ***
    # Notes:
    #   This callback function is a bit too clever to understand.  All motor interrupt pins will trigger this callback.
    #   The callback will find which interrupt pin interrupts, then increment the corresponding element of the int count list.    
    def motorEncodeIntCallBack(pin):
        index = interruptPinList.index(pin)
        motorEncodeIntCountList[index] += 1    
        return        
    
    vehicleDict = setupVehicleV2(vehicleNum = 1)
    
    vehicleDict['INTERRUPT_PIN_LIST'][0].irq(motorEncodeIntCallBack, Pin.IRQ_FALLING)    
    vehicleDict['INTERRUPT_PIN_LIST'][1].irq(motorEncodeIntCallBack, Pin.IRQ_FALLING)     
    
    #print('  motorEncodeIntServiceRoutine[2] =', motorEncodeIntCountList[2])    
    
    return

# *** Main Tests ***

#testMoveVehicleForwardBackwardTurnLeftTurnRight()
#testSyncTwoVehicleWheels()
#testTurnVehicleLeftHoldTime()

testMoveMotorForwardSeconds(motorDriverDictNum = 2, motorNum = 1, moveSeconds = 10)

TM310 DC Motor Calibration

TT DC Gear Reduced Motor with Encoder for Smart Car DIY - AliExpress US$5

Spec:
Rated voltage: DC 9V
No-load current: 100mA
Stall current: 1.2A
No-load speed: 8000 rpm
Suitable voltage: 6-12V
Reduction ratio: 1:48/1:90 



TM310 Motor #1 and #2 Speed Discrepancy

So I calibrated the second motor. I found the speeds of the second motor is very close to the first one; both encoder signals measure almost “exactly” 300uS.

This is very good news, because it means that it is practical and easy to sync two motors (bought by same order, therefore very likely from the same production lot from the manufacturer), by adjusting PWM duty cycle.

The second motor’s encoder AB signals at 12V power are displayed below.



Adding WS2812b NeoPixels LEDs to my 4WD

Now I am thinking of adding NeoPixels to my smart 4WD.


This pico relay module looks good. I hope to get on and mount it on my smart pico.


MT310 Encoder Motor #3 Calibration Notes

So I checked out MT310 #3. It seems too good to be true that this third motor’s encoder A signal is also almost exactly 300uS. Now I need to display all 4 motor’s encoder A signals together, at the same time, and and see if there is any tiny difference of the A signal periods. I still think now it is too good to be true, or there are some critical mistake I have made in the calibrations.


MT310 Motor #4 12V power Speed Measurement

So I checked out Motor 4. I was glad to see the that that encoder Signal is now 290us, not 300us as other three. If all 4 motors were axactly 300us, than is was too good to be true. Now (300 - 290) / 300 ~= 0.3% speed difference. I guess it should be easy to adjust the duty cycle of either motor to get same speed. (Apology: it should not be called “in sync” which is about time, but should be called speed matching, which is about speed.



Note - I am hitting the forum’s 32k word limit again. So I need to make a new reply.

/ to continue, …

Speed measurement of 4 MT310 motors

The encoder A signal of Motor 4 has little glitches. I need to display all 4 A signals together, at the same time, to see if this switching noise/glitches is common to all 4 motors, or just Motor 4.

Now I am doing the following speed measurements.

  1. All 4 MT310 motors powered by the same 12V DC.

  2. All 4 MT310 motor encoder powered by the same 5VDC.

  3. Use 30MHz 4 channel scope to display the 4 encoder A signals.

  4. Check if the A signals have almost identical, say, 300uS period.


MT310 Encoder Motor x 4 powered by 12VDC moving at the same time


4WD TM310 1:90 Encoder 12ppr powered by 12VDC

I am happy to see my 4 channel scope displaying the 4 motors’ encoder A signals all showing roughly 300us period pulses. I am not going to take pulse period measurements by hand and then calculate the corresponding speeds, because my next step is to use Thonny micropython to detect the encoder A signals as interrupt events and count them to calculate very precise speed values.


WaveShare DC Motor x 4 Module

Now I am looking at the WaveShare Pico DC Motor x 4 Module, to see how the professionals are playing with Pico and 4WD. I am glad to see that WaveShare is using the TB6612FNG motor driver. But I am also jealous to find that they are using PCA9615 16 PWM channel to drive the PCA9615. In order not to damage */my reputation, I think I need to catch up and show off that I can also use PCA9615. :slight_smile:

DC Motor Driver Module for Raspberry Pi Pico, Driving up to 4x DC Motors SKU: 19764 Part Number: Pico-Motor-Driver Brand: Waveshare $11.99

Waveshare Display Module for Raspberry Pi Pico 0.96 Inch Display Nenhuma Avaliação Ainda 0 Vendido R$52,79


TM310 Encoder Motor Current Measurements
*
Now I am measuring the motors’ no load free running currents. This is a summary:

M1                            = 0.114A
M2                            = 0.165A
M3                            = 0.134A
M4                            = 0.130A

M1                            = 0.118A
M1 + M2                       = 0.274A
M1 + M2 + M3                  = 0.399A
M1 + M2 + M3 + M4             = 0.515A

So very roughly each motor takes 100mA to 170mA, all 4 motors together takes about 500mA in total.

Perhaps I should also check out the stall currents.


TM310 Stall Current

I check the stall current of only Motor #1, and found it around a scary 2.2A. I only held the motor wheel for 1 second or so. The motor uses metal gear, so I guess the metal gear box won’t crash, but the motor coil should heat up and burn out. Perhaps I should do a destructive motor burnt out test later.


Using Pico Thonny MicroPython to calculate TM310 motor speed by counting Encoder A pulses per second


Now let me do my dodgy calculations.

  1. The Motor encoder A pulse period as displayed by the scope is 300us.

  2. The TM310 spec says pulses per revolution is 12ppr.

  3. So one revolution takes 12 pulses or 12 x 300us = 3600us.

  4. So rps (revolutions per second) = 1 / 3600us = 1000000/3600 = 278rps

  5. So shaft rotation speed without 1:90 gear ratio = 278 x 60 = 16680 rpm

  6. So geared motor speed = 16680 / 90 = 185 rpm

This 185 rpm is what I am going to match or verify with pico micropython now.

Note1

TM310 spec say no load speed at 9V is 8000rpm. But so far I have been using 12V to drive to motor. So I need to test again at 9VDC.


TM310 motor speed at 9VDC

So I tested the same motor again, this time using 9V. I found the pulse period goes down from 300us to 400us.

So rpm should be 16680 x 9/12 = 12,510rpm, still much higher than the specified 8000rpm.

So I still need to use pico micropython to help finding out who is correct.


Using PCA9615 16 Channel PWM Driver to control TB6612FNG Motor Controller

Now I found a problem when scaling up from 2WD to 4WD: that I seem to have not enough GP pins to go round for controlling the motor, using TB6612FNG, sooner or later. One workaround is to use I2C PCA9615 16 channel controllers.
WaveShare’s module is looking good. So I am studying their documentation to see if there is anything I can borrow. Below is the references and demo code.



'''
Pico-Motor-Driver Demo Code - WaveShare
https://www.waveshare.com/w/upload/5/5c/Pico-Motor-Driver-code.7z

Pico-Motor-Driver Module Wiki - WasveShare
https://www.waveshare.com/wiki/Pico-Motor-Driver

Pico-Motor-Driver Schematic - WaveShare
https://www.waveshare.com/w/upload/b/b8/Pico_Motor_Driver_SCH.pdf

'''

import time
from machine import Pin, I2C
import math

class PCA9685:
    # Registers/etc.
    __SUBADR1            = 0x02
    __SUBADR2            = 0x03
    __SUBADR3            = 0x04
    __MODE1              = 0x00
    __PRESCALE           = 0xFE
    __LED0_ON_L          = 0x06
    __LED0_ON_H          = 0x07
    __LED0_OFF_L         = 0x08
    __LED0_OFF_H         = 0x09
    __ALLLED_ON_L        = 0xFA
    __ALLLED_ON_H        = 0xFB
    __ALLLED_OFF_L       = 0xFC
    __ALLLED_OFF_H       = 0xFD

    def __init__(self, address=0x40, debug=False):
        self.i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=100000)
        self.address = address
        self.debug = debug
        if (self.debug):
            print("Reseting PCA9685") 
        self.write(self.__MODE1, 0x00)
	
    def write(self, cmd, value):
        "Writes an 8-bit value to the specified register/address"
        self.i2c.writeto_mem(int(self.address), int(cmd), bytes([int(value)]))
        if (self.debug):
            print("I2C: Write 0x%02X to register 0x%02X" % (value, cmd))
	  
    def read(self, reg):
        "Read an unsigned byte from the I2C device"
        rdate = self.i2c.readfrom_mem(int(self.address), int(reg), 1)
        if (self.debug):
            print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, int(reg), rdate[0]))
        return rdate[0]
	
    def setPWMFreq(self, freq):
        "Sets the PWM frequency"
        prescaleval = 25000000.0    # 25MHz
        prescaleval /= 4096.0       # 12-bit
        prescaleval /= float(freq)
        prescaleval -= 1.0
        if (self.debug):
            print("Setting PWM frequency to %d Hz" % freq)
            print("Estimated pre-scale: %d" % prescaleval)
        prescale = math.floor(prescaleval + 0.5)
        if (self.debug):
            print("Final pre-scale: %d" % prescale)

        oldmode = self.read(self.__MODE1)
        #print("oldmode = 0x%02X" %oldmode)
        newmode = (oldmode & 0x7F) | 0x10        # sleep
        self.write(self.__MODE1, newmode)        # go to sleep
        self.write(self.__PRESCALE, int(math.floor(prescale)))
        self.write(self.__MODE1, oldmode)
        time.sleep(0.005)
        self.write(self.__MODE1, oldmode | 0x80)

    def setPWM(self, channel, on, off):
        "Sets a single PWM channel"
        self.write(self.__LED0_ON_L+4*channel, on & 0xFF)
        self.write(self.__LED0_ON_H+4*channel, on >> 8)
        self.write(self.__LED0_OFF_L+4*channel, off & 0xFF)
        self.write(self.__LED0_OFF_H+4*channel, off >> 8)
        if (self.debug):
            print("channel: %d  LED_ON: %d LED_OFF: %d" % (channel,on,off))
	  
    def setServoPulse(self, channel, pulse):
        pulse = pulse * (4095 / 100)
        self.setPWM(channel, 0, int(pulse))
    
    def setLevel(self, channel, value):
        if (value == 1):
              self.setPWM(channel, 0, 4095)
        else:
              self.setPWM(channel, 0, 0)

class MotorDriver():
    def __init__(self, debug=False):
        self.debug = debug
        self.pwm = PCA9685()
        self.pwm.setPWMFreq(50)       
        self.MotorPin = ['MA', 0,1,2, 'MB',3,4,5, 'MC',6,7,8, 'MD',9,10,11]
        self.MotorDir = ['forward', 0,1, 'backward',1,0]

    def MotorRun(self, motor, mdir, speed, runtime):
        if speed > 100:
            return
        
        mPin = self.MotorPin.index(motor)
        mDir = self.MotorDir.index(mdir)
        
        if (self.debug):
            print("set PWM PIN %d, speed %d" %(self.MotorPin[mPin+1], speed))
            print("set pin A %d , dir %d" %(self.MotorPin[mPin+2], self.MotorDir[mDir+1]))
            print("set pin b %d , dir %d" %(self.MotorPin[mPin+3], self.MotorDir[mDir+2]))

        self.pwm.setServoPulse(self.MotorPin[mPin+1], speed)        
        self.pwm.setLevel(self.MotorPin[mPin+2], self.MotorDir[mDir+1])
        self.pwm.setLevel(self.MotorPin[mPin+3], self.MotorDir[mDir+2])
        
        time.sleep(runtime)
        self.pwm.setServoPulse(self.MotorPin[mPin+1], 0)
        self.pwm.setLevel(self.MotorPin[mPin+2], 0)
        self.pwm.setLevel(self.MotorPin[mPin+3], 0)

    def MotorStop(self, motor):
        mPin = self.MotorPin.index(motor)
        self.pwm.setServoPulse(self.MotorPin[mPin+1], 0)
        

if __name__ == '__main__':
    m = MotorDriver()
    time.sleep(1)
    print("ttest code")
    try:
        print("this is a motor driver test code")
        
        #Parameter 1: motor select:'MA', 'MB', 'MC', 'MD'
        #Parameter 2: turn dir:'forward', 'backward'
        #Parameter 3: motor speed: 0-100
        #Parameter 4: Running time: >0
        print("motor A forward, speed 100%, Run for 2S, then stop")
        m.MotorRun('MA', 'forward', 100, 2)
        print("motor A backward, speed 100%, Run for 2S, then stop")
        m.MotorRun('MA', 'backward', 100, 2)
        print("motor B forward, speed 50%, Run for 4S, then stop")
        m.MotorRun('MB', 'forward', 50, 4)
        print("motor B forward, speed 50%, Run for 4S, then stop")
        m.MotorRun('MB', 'backward', 50, 4)
        time.sleep(2)
        
    except KeyboardInterrupt:
        exit()

# .END


Writing a python program to measure the TM310 encoder DC motor speed by counting the encoder A signal interrupts per second


TB6612FNG troubleshooting notes - Channel B bad

I found TB6612FNG Channel A driving Motor 1 working smoothly, but Channel B driving Motor 2 a bit “sticky” often I need to give the motor a “push” to start it moving. I swapped the AO1, 2 with the BO1,2 and found that Motor 1 now becomes sticky and Motor 2 becomes smooth. So I can safely conclude that the TB6612FNG’s Channel B is not working properly, perhaps damaged.


Grouping working functions as modules - blinkLedv076

The 4WD code is getting messy and complicated. So I am thinking of grouping some of the working functions as modules. The first candidate is blink_led_v076. But I don’t know how to load the python program into MicroPython’s /lib or /tlfong01_lib. I found the utility “rshell” not working in Windows Power Shell, or pip to out of date and could not be updated. So I am thinking of using Rpi4B instead of Win10 PC to host the Thonny IDE, to see if problem can be solved.


blink_led_v076 module

# Program Name
#   blink_led_v77.py - tlfong01 2021sep13
#
# Reference
# Adding External Modules To MicroPythn With Raspberry Pi Pico - peppe80, 2021may31
#    https://peppe8o.com/adding-external-modules-to-micropython-with-raspberry-pi-pico/
#    sys.path.append('c:/pico_micropython_library')
#    print('sys.path =', sys.path)
#    sys.path = ['', '/lib', 'c:/pico_micropython_library']
#
# The Raspberry Pi Pico File Management System - (Ep. 0.1) 6,615 viewsMar 15, 2021
#    https://www.youtube.com/watch?v=G06tPDjZ3zM
# Using MicroPython and uploading libraries on Raspberry Pi Pico Using rshell to upload custom code - Martin Fitzpatrick
#   https://www.mfitzp.com/tutorials/using-micropython-raspberry-pico/
#
# Configuration
#   Acer Aspire XC-780 Intel CORE i5-6400 2.7GHz, 8GB, Chinese Windows 10 Home (64-bit, 2020nov15),
#   Thonny 3.3.3, Python 3.7.9 (32-bit), Tk 8.6.9, USB COM Port #4
#
# Thonny MicroPython Intepreter
#   Micropython (Rapsberry Pi Pico)
#
# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

import utime
from machine import Pin, PWM, Timer
import sys

# ========= ========= ========= ========= ========= ========= ========= =========
# ========= ========= ========= ========= ========= ========= ========= =========

# *** Print Hello World - last update 2021sep13 ***

def printHelloWorld():
    print('  Hello World')
    return

def testPrintHelloWorld():
    print('\ntestPrintHelloWorld(), ...')
    printHelloWorld()
    return

# *** Sample Test ***
#testPrintHelloWorld()

'''
Sample output - tlfong01  2021sep12hkt2032
>>> %Run -c $EDITOR_CONTENT
testPrintHelloWorldV060(), ...
  Hello World
'''

# *** Blink Led Using toggle pin function - Last update 2021sep13 ***

def loopBlinkLed(ledPinNum, onTime, offTime, blinkCount):
    ledPin = Pin(ledPinNum, Pin.OUT)      
    for count in range(blinkCount):
        ledPin.high()
        utime.sleep(onTime)
        ledPin.low()
        utime.sleep(offTime)        
    return

def testLoopBlinkLed():
    print('\nBegin testLoopBlinkLed(), ...')
    print('  System LED at pin25 should blink now, ...')    
    loopBlinkLed(ledPinNum = 25, onTime = 0.1, offTime = 1, blinkCount = 4)
    print('End   testLoopBlinkLed(), ...')    
    return

# *** Sample Test ***
#testLoopBlinkLed()

'''
Sample output 
>>> %Run -c $EDITOR_CONTENT
Begin testBlinkLed(), ...
  System LED at pin25 should blink now, ...
End   testBlinkLed(), ...
>>> 
'''

# *** Blink System LED - Last update 2021spep13 ***
# Notes
#   1. Thie blink LED function uses the callback function blinkLed() with input parameter blinkLedTimer
#   2. To blink led, initialize timer blinkLedTimer which repeatedly at set frequency triggers callback function blinkLed()
#   3. A more newbie friendly blink Led version uses a loop of function toggleLed()

def callBackBlinkLed(blinkLedTimer):
    global systemLedPin
    systemLedPin.toggle()
    return
  
def testCallBackBlinkLed():
    global systemLedPin
    systemLedPinNum = 25
    systemLedPin    = Pin(systemLedPinNum, Pin.OUT)
    blinkLedTimer   = Timer()
    blinkLedTimer.init(freq = 2.0, mode = Timer.PERIODIC, callback = callBackBlinkLed) 
    print('\ntestBlinkLed(), ...')
    print('  System LED at pin25 should blink now, ...')
    return

# *** Sample test ***

#testCallBackBlinkLed()

''' Sample output ***
>>> %Run -c $EDITOR_CONTENT
testBlinkLed(), ...
  System LED at pin25 should blink now, ...
''' 

# *** Main ***

print('Begin blink_led_v77, ...')
testPrintHelloWorld()
testLoopBlinkLed()
testCallBackBlinkLed()
print('End blink_led_v77, ...')

# End   

TB6612FNG Module Capacitor Explosion

So I tested a new TB6612, hoping that it would replace the bad, one channel sticky guy.

But as soon as powered it up, there was “loud” explosion, following by “heavy” smoke that last about three minutes. I let the smoke settle down, and found that the pretty yellow capacitor was burnt out to become half yellow, half charcoal black!

I vaguely remember long time ago I read about old versions of TB6612 had the faulty capacitor problem. But I never thought that new versions still have problems. I guess the poor guy’s cap is for max 9V, but I powered it with 12V!

Anyway, I think I would replace the burned cap with a 30V 10uF or higher and try again, and see if there is any more explosion. :slight_smile:


I am confused about the problematic cap. So I go to Pololu to find the schematic.


TB6612FNG Replacement Cap 25V 470u

I don’t understand why Pololu uses 10uF cereamic cap. I guess it is for easy manufacturing. Anyway, I think it is safe to use a big electrolyte cap.

But I dare not to try it to night, in case there is another explosion and tripping off my flat’s MCB (Mini Circuit Breaker) and I could not find any electrician to come to rescue in time.


Rpi Pico TB6612FNG TM310 Testing Rig Notes


BLDC Motor 4WD (Aslong GM25-325S BLDC) Testing Notes

The motors I have been playing with, N20, TT130, MT310, are a bit too weak. So I now try to test stronger motors. My plan is to use the same Pico to drive all of them, with little or not modification of the motor driver hardware and software. The motors being tested is Aslong GM25-325S 1:260 gear motor.


TB6612FNG x 3 = 6WD

Now I am scaling up my 4WD to 6WD, by TB6612FNG x 3. I might not wire 6 motors for now, but 3 motor drivers with 6 channels for 4WD is helpful for swapping testing and troubleshooting. (For the modules with marking TB6612FNG, I have no confidence of the ceramic cap, because one of them had a big explosion. So I am replacing the original ceramic ones with electrolyte 25V 470uF/47uF. So far so good, no explosions, :slight_smile:


Standard Pico Module A Bit Bulky

I am finding the standard Pico board a bit bulky. So I am taking the opportunity of upgrading my 2WD to 4WD/6WD project to try out another Pico module.

Eleven RP2040 alternatives to the Raspberry Pi Pico #PiDay #RaspberryPiPico @MUO_official - 2021sep17


TM310 4WD Mark II

Now I am assembling TM310 Mark II for field testing. Also I found that when upscaling feom2Wd to 4WD, the wiring and singal routing complexity increases not just two fold but four or more folds, and the troubleshooting chore is becoming unmanageable. So with two almost identical cars make swap troubleshooting much easier than with only one car to tackle.



TB6612FNG 4WD wheels turning off center

It was difficult to fit the cheapy yellow wheel to the TM310 motor, with the result that the wheels are moving uglily off centre.


Testing 4WD TM310 Motor Encoder Signals

Now I am going to test the 4WD’s encoder signals. The setup is briefly described below.

  1. All four motors are powered by the same 12V DC source Riden RD6006 DC
    PSU

  2. All four motors encoders are power by the same 5V DC.

  3. All encoder’ encoder A signals are displayed by the Rigol DS1054 4 channel scope.



Scope display of the four encoder’s A signals

/ to continue, …