PicoCat Electronics
Learn about the parts that make up the PicoCat
This detailed guide describes the all the steps required to wire-up your own PicoCat.
SG90 Servos
PicoCat uses 11 x SG90 servos. An alternative design made for MG90s Servos will also be created to enable a more powerful, stronger version of the cat.
You can grab the SG90 servo’s from our shop here.
PCA9685
The Servos connect to a PCA9685 board, which is powered by the main battery. The PCA9685 board also connects to the Raspberry Pi Pico.
You can grab the PCA9685 board from our shop here.
Raspberry Pi Pico
The brain of the PicoCat is the Raspberry Pi Pico. It connects to the PCA9685 Board, and uses a MicroPython library to enable easy communication with the board. The source code for the MicroPython PCA9685 library can be found here: https://github.com/kevinmcaleer/pca9685_for_pico.
Servo.py
Below is the servo.py code. This is used with the PCA9685.py library, which is below the servo code listing.
# servo.py
# Kevin McAleer
# March 2021
from pca import PCA9685
import math
class Servos:
def __init__(self, i2c, address=0x40, freq=50, min_us=600, max_us=2400,
degrees=180):
self.period = 1000000 / freq
self.min_duty = self._us2duty(min_us)
self.max_duty = self._us2duty(max_us)
self.degrees = degrees
self.freq = freq
self.pca9685 = PCA9685(i2c, address)
self.pca9685.freq(freq)
def _us2duty(self, value):
return int(4095 * value / self.period)
def position(self, index, degrees=None, radians=None, us=None, duty=None):
span = self.max_duty - self.min_duty
if degrees is not None:
duty = self.min_duty + span * degrees / self.degrees
elif radians is not None:
duty = self.min_duty + span * radians / math.radians(self.degrees)
elif us is not None:
duty = self._us2duty(us)
elif duty is not None:
pass
else:
return self.pca9685.duty(index)
duty = min(self.max_duty, max(self.min_duty, int(duty)))
self.pca9685.duty(index, duty)
def release(self, index):
self.pca9685.duty(index, 0)
PCA9685.py
# pca9685.py
# Kevin McAleer
# March 2021
'''
@author Kevin McAleer
'''
import ustruct
import time
class PCA9685:
"""
This class models the PCA9685 board, used to control up to 16
servos, using just 2 wires for control over the I2C interface
"""
def __init__(self, i2c, address=0x40):
"""
class constructor
Args:
i2c ([I2C Class, from the build in machine library]): This is used to
bring in the i2c object, which can be created by
> i2c = I2C(id=0, sda=Pin(0), scl=Pin(1))
address (hexadecimal, optional): [description]. Defaults to 0x40.
"""
self.i2c = i2c
self.address = address
self.reset()
def _write(self, address, value):
self.i2c.writeto_mem(self.address, address, bytearray([value]))
def _read(self, address):
return self.i2c.readfrom_mem(self.address, address, 1)[0]
def reset(self):
self._write(0x00, 0x00) # Mode1
def freq(self, freq=None):
if freq is None:
return int(25000000.0 / 4096 / (self._read(0xfe) - 0.5))
prescale = int(25000000.0 / 4096.0 / freq + 0.5)
old_mode = self._read(0x00) # Mode 1
self._write(0x00, (old_mode & 0x7F) | 0x10) # Mode 1, sleep
self._write(0xfe, prescale) # Prescale
self._write(0x00, old_mode) # Mode 1
time.sleep_us(5)
self._write(0x00, old_mode | 0xa1) # Mode 1, autoincrement on
def pwm(self, index, on=None, off=None):
if on is None or off is None:
data = self.i2c.readfrom_mem(self.address, 0x06 + 4 * index, 4)
return ustruct.unpack('<HH', data)
data = ustruct.pack('<HH', on, off)
self.i2c.writeto_mem(self.address, 0x06 + 4 * index, data)
def duty(self, index, value=None, invert=False):
if value is None:
pwm = self.pwm(index)
if pwm == (0, 4096):
value = 0
elif pwm == (4096, 0):
value = 4095
value = pwm[1]
if invert:
value = 4095 - value
return value
if not 0 <= value <= 4095:
raise ValueError("Out of range")
if invert:
value = 4095 - value
if value == 0:
self.pwm(index, 0, 4096)
elif value == 4095:
self.pwm(index, 4096, 0)
else:
self.pwm(index, 0, value)
Demo.py
This is a demo program that uses both the servo.py and pca9685.py library to bring it all together.
from pca9685 import PCA9685
from machine import I2C, Pin
from servo import Servos
sda = Pin(0)
scl = Pin(1)
id = 0
i2c = I2C(id=id, sda=sda, scl=scl)
pca = PCA9685(i2c=i2c)
servo = Servos(i2c=i2c)
servo.position(index=0, degrees=0)
Range Finder
The PicoCat uses the HC-SR04 Range Finder to detect distances. There is a 3.3v version of the Range Finder - HC-SR04P, which works without the need for a voltage divider.
For more information about how to setup the 5v version of the range finder for use with the 3.3v Pico, watch this section of the video.
You can grab the 5v version of the range finder here.