Note to staff: we changed the order
IR sensors work by sending out IR light and measuring the amount reflected back. They take a lot more work to calibrate and are surface dependent, but are far cheaper and faster than the ToF sensors we used to use. Our mouse has 6 of these sensors split into 3 pairs (center/front, left, right). Having 2 sensors per side allows us to statically determine both the distance and angle of the nearest surface.
IR sensors consist of both an IR emitter and detector. IR emitters draw around 50mA each when on, so we have a switch to turn them off when not needed. IR detectors shift their IV curve based on light intensity like in the following graph.
To convert that to a readable voltage signal, we'll use a setup similar to a resistor divider. It's noisier and less tunable compared to a transimpedance amplifier, but uses only one resistor. By placing the IR detector in reverse bias, we use the flatter region of the IV curve. Since the current varies linearly with light intensity, the voltage also varies linearly. MMv3 uses the following circuit (pull-up resistor not shown).
Since we have 6 total analog sensors but only 3 ADC pins on the Pi Pico, we need to multiplex 2 sensors onto each pin. We use one extra pin on each IR detector in open-drain mode to fully disconnect or connect it to ground. To read in one sensor, we perform the following steps in the while loop.
- Enable the IR emitters (set pin to
True
). - Enable the chosen sensor by connecting it to ground (set pin to
False
). - Wait around 1ms for things to settle.
- Take the reading.
- Disable the chosen sensor by setting the pin to open-drain (set pin to
True
). - Disable the IR emitters (set pin to
False
).
Baesd on the above steps and the AnalogIn
documentation, fill out the below TODOs to print out readings from the left IR sensor pair (lir_a
and lir_b
).
import board
import time
import digitalio
from analogio import AnalogIn
# adc
l_adc = """TODO call AnalogIn on GP28"""
# emitter
l_en = """TODO create DigitalInOut output on GP7"""
l_en.direction = digitalio.Direction.OUTPUT
l_en.value = False
# sensors
lir_a = digitalio.DigitalInOut(board.GP5)
lir_a.direction = digitalio.Direction.OUTPUT
lir_a.drive_mode = digitalio.DriveMode.OPEN_DRAIN
lir_a.value = True # high Z mode
lir_b = """TODO create DigitalInOut on GP6 in open-drain mode, use lir_a as direct inspiration"""
while True:
# TODO enable IR emitters using l_en set one of its variables to a (boolean)
# TODO enable chosen sensor lir_a or lir_b by setting one of its variable to false
# TODO wait a bit, 1ms should do time. ...
# TODO take analog reading for future printing print(l_adc. , end="")
# TODO disable chosen sensor lir_a or lir_b
# TODO enable chosen sensor
#
# Repeat above for this sensor
#
# TODO disable IR emitters using l_en
time.sleep(0.05)
- Demonstrate one working sensor.
In order to simplify the whole process of multiplexing and reading from multiple sensors, we wrote a library. Upload, if you havent already irsensor.py
from sanity/, to serve as the library. Read through irsensor.py
to instantiate and use it to understand the parameters of IRSensors.
import board
import time
from irsensor import IRSensors
ir = IRSensors(
board.GP7, board.GP5, board.GP6, board.GP28, # left
board.GP9, board.GP10, board.GP11, board.GP26, # center
board.GP21, board.GP20, board.GP22, board.GP27 # right
)
while True:
ir.scan()
print("lir_a:", ir.lir_a, "\t", "lir_b:", ir.lir_b, "\t",
"cir_a:", ir.cir_a, "\t", "cir_b:", ir.cir_b, "\t",
"rir_a:", ir.rir_a, "\t", "rir_b:", ir.rir_b)
time.sleep(0.05)
- Demonstrate all of your working sensors.
Let's try estimating actual distances from the sensor values. Empirically, the relationship is linear below around 60mm and rises sharply beyond that due to the angled sensors seeing above the wall. Based on maze cell size, we really only need accurate distances around 50mm for wall following and just binary wall detection beyond that.
To save you some time, we're also going to include code for you to get proper distance readings: distance.py
.
Pay attention to this area specifically:
# IR sensors
ir = IRSensors(
board.GP7, board.GP5, board.GP6, board.GP28, # left
board.GP9, board.GP10, board.GP11, board.GP26, # center
board.GP21, board.GP20, board.GP22, board.GP27, # right
avg = 10
)
""" Main """
dist = Distance(ir,
0.0299, -63.6, 0.0195, -39.1,
0.0300, -60.4, 0.0251, -47.9,
0.0258, -50.5, 0.0292, -54.0,
)
"""Make Sure to allign the constants in the right place:
la_a, la_b, lb_a, lb_b,
ca_a, ca_b, cb_a, cb_b,
ra_a, ra_b, rb_a, rb_b,
"""
Within the file we make an instance of the Distance class and passing in the arguments for the a and b constants. The numbers given out are placeholders and you should use the constants you got from the previous section and fill them in appropriately.
- Run the calibration code and save the constants somewhere.
- Using the constants, fill in the distance code and check that it runs correctly.