Programming
Imports
There is a strange way that variables are imported.
If you import a variable , then you are simple making a copy.
If you change your copy, the data in the import is not updated,
and functions in the import are referring to their original variable.
Likewise if a function in the import runs, and changes the variable ,
you won't see that value in the main program.
# file main.py
from imp import *
print("local:",AAA) # print local AAA copy
printa() # print value inside imp
#
print("setting imp to 1")
seta(1)
#
print("local:",AAA) # print local AAA copy
printa() # print value inside imp
#
print("setting local to 2")
AAA=2
#
print("local:",AAA) # print local AAA copy
printa() # print value inside imp
# file imp.py
AAA = 0
def printa():
global AAA
print("imported:",AAA)
def seta(value):
global AAA
AAA=value
Results of running main.py:
local: 0
imported: 0
setting imp to 1
local: 0
imported: 1
setting local to 2
local: 2
imported: 1
Interrupts
The input pins on a Pico can be programmed to run a given Python function, when they change state.
They must be digital inputs, and they can cause the function to run either when switching on, or off, or both on and off.
There are problems if interrupts occur while the program is writing a log file
see below for how to solve this
The following commands switch on the wheel sensor interrupts:
See a program to go a certain distance and stop.
And one to turn left 90 degrees.
# the following pins are connected to the encoder inputs (New Piggy):
lwheel = Pin(6,Pin.IN,Pin.PULL_UP)
rwheel = Pin(7,Pin.IN,Pin.PULL_UP)
# the following are the counts. One for each wheel
LDistance=0
RDistance=0
#These 2 functions handle the interrupts:
def Lwheelirq(which):
global LDistance # count of wheel markers
ledL.value(lwheel.value()) # light the sensor bar LED accordingly
LDistance += 1 # increase count
def Rwheelirq(which):
global RDistance # count of wheel markers
ledR.value(rwheel.value()) # light the sensor bar LED accordingly
RDistance += 1 # increase count
# set up interrupt handler for each pin
lwheel.irq(handler=Lwheelirq,trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING)
rwheel.irq(handler=Rwheelirq,trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING)
How to disable interrupts over a section of code: (You will need to import library "machine")
# this disables all interrupts and saves the settings in "irqvals"
irqvals=machine.disable_irq()
# this is the code that must be protected
f = open("log.txt", "a")
f.write(str(L)+","+str(F)+","+str(R)+"\n")
f.close()
# this re-enables the interrupts previously disabled using "irqvals"
machine.enable_irq(irqvals)
Interrupt problems
We had some Problems With the forward() routineIt was exiting before travelling the required distance.
This happened very infrequently.
It occurred less often when there was a pause 0.2 seconds between calls to forward().
It is possibly caused by the fact that forward() resets LDistance and RDistance
to zero at the start of the routine, but it does not stop interrupts while it does so.
If the motors are turning then there is a clash
From ://docs.micropython.org/en/latest/reference/isr_rules.htm See the "Critical sections" paragraph
"Where data is shared between the main program and an ISR(Interrupt Service Routine),
consider disabling interrupts prior to accessing the data in the main program
and re-enabling them immediately afterwards (see Critical Sections)."
It seems the interrupt is clashing with the statement at the start of forward()
preventing it from actually setting the distances to zero.
This would cause forward() to return very quickly without travelling any distance.
Solutions.
A)
Disable interrupts while the distances are being set to zero in each of the 4 movement subroutines.
# this disables all interrupts and saves the settings in "irqvals"
irqvals=machine.disable_irq()
global LDistance, RDistance
LDistance=0
RDistance=0
# re-enable the interrupts previously disabled using "irqvals"
machine.enable_irq(irqvals)
Or
B)
Don't set the distances to zero. - Just let them increase, and calculate a stopping point, from the value on entry.
Serial input
The input pins on a Pico can be used as a Universal Asynchronous Receive Transmit port. (or UART)..
It can communicate with a serial device, such as a Bluetooth module, or a TV Remote receiver.
These plug into the side of the robot.
The following is a program to receive the TV remote signals and toggle the LED when the TV remote "power" button: is pressed.
from machine import UART,Pin
import time
uart = UART(0, 2400) # TV Remote. Speed init with given baudrate. 0 = TX/RX pins
led = Pin(25, Pin.OUT) # inbuilt LED
while True:
time.sleep(.01)
if uart.any(): # if a character is received
bytes = uart.read(1) # read 1 byte from the UART
try:
char = bytes.decode('utf-8') # turn into a string character
except:
char="?"
print(str("'"+char+"'"),ord(char)) # print out what came in
if char=="P":
led.toggle() # toggle the LED if Power button pressed
Going in a straight line
If your robot doesn't go in a straight line, when the motors are set the same "speed" ,
you can program it to use the wheel encoders to find out the necessary settings required
to get the wheels to actually go the same speeds.
This "auto-straightening" program makes small adjustments to the speed settings for the motors,
such that each wheel goes the same distance in a given time.
After a certain distance it stores the final settings in a file called "speeds.py" - This file can be imported into your program to get the right speeds
for straight running.
The auto-steering program is shown below:
# Auto-straightening program
# 4/3/2023 J Fisher
from UKMARS import *
# the following pins are connected to the encoder inputs:
# (Old piggyback)
lwheel = Pin(13,Pin.IN,Pin.PULL_UP)
rwheel = Pin(14,Pin.IN,Pin.PULL_UP)
lmotor = Motor("L")
rmotor = Motor("R")
# the following are the counts. One for each wheel
LDistance=0
RDistance=0
#These 2 functions handle the interrupts:
def Lwheelirq(which):
global LDistance # count of wheel markers
ledL.value(lwheel.value()) # light the sensor bar LED accordingly
LDistance += 1 # increase count
def Rwheelirq(which):
global RDistance # count of wheel markers
ledR.value(rwheel.value()) # light the sensor bar LED accordingly
RDistance += 1 # increase count
# set up interrupt handler for each pin
lwheel.irq(handler=Lwheelirq,trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING)
rwheel.irq(handler=Rwheelirq,trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING)
lmotor.speed(0)
rmotor.speed(0)
while Button.value() == 0:
(left,front,right)= ReadWALLS()
time.sleep(0.1)
LDistance=0
RDistance=0
lspeed = 25
rspeed = 30
lmotor.speed(lspeed)
rmotor.speed(rspeed)
LOld=0
ROld=0
def logspeeds():
pfile = open("speeds.py","w")
pfile.write("lspeed="+str(lspeed)+"\r\n")
pfile.write("rspeed="+str(rspeed)+"\r\n")
pfile.close()
print("\r\nspeeds.py written")
while LDistance <400:
err = LDistance-LOld - RDistance+ROld
if (LDistance-LOld) > (RDistance-ROld):
lspeed= lspeed+(err/5)
elif (RDistance-ROld) > (LDistance-LOld):
lspeed= lspeed-(err/5)
print(LDistance-LOld,RDistance-ROld,lspeed,rspeed)
LOld=LDistance
ROld=RDistance
lmotor.speed(lspeed)
rmotor.speed(rspeed)
time.sleep(0.2)
lmotor.speed(0)
rmotor.speed(0)
logspeeds()
New Motor Attribute
The UKMARS Motor object has acquired a new Attribute : "correction"
it multiplies all future speed() settings.
It can be used to speed up (or slow down) a motor, to make the robot
go in a straight line when the Motors are give equal speed() s
If we want the robot to stop after aguaranteed time, we could use a second thread.
There's lots of example. Here is one that descrives the basics.
If the second thread just does a
time.sleep(2) , then sets a Global flag Danger = 1 then the main thread just needs to read the danger Global and stop what its doing.