I'm trying to setup a Raspberry Pi to measure the position and speed of 8 DC motors that all have incremental quadrature encoders. At full speed (unloaded), each encoder ticks at 3.3 kHz. I assume that this means that I have to sample the encoders at >13.2 kHz to measure the position without missing any ticks. (Note: I only drive one or two motors at a time.)
The current board that I have has a MCP23008 I/O expander on it to sample the encoders, but I think that the I2C communication is too slow. With a simple Python script, I can sample a single encoder at ~1kHz:
from adafruit_mcp230xx.mcp23008 import MCP23008
encoder_address=0x20
# Initialize I2C
i2c = board.I2C()
# Setup encoder reader (I/O expander)
mcp = MCP23008(i2c, address=encoder_address)
encoder1a = mcp.get_pin(6)
encoder1b = mcp.get_pin(7)
encoder1a.direction = digitalio.Direction.INPUT
encoder1b.direction = digitalio.Direction.INPUT
encoder1a.pull = digitalio.Pull.UP
encoder1b.pull = digitalio.Pull.UP
# Read encoders
A = opt.encoder1a.value
B = opt.encoder1b.value
I also tried calling i2c-tools
from my Python script, but this was insanely slow (~75Hz):
import subprocess as sb
output = sb.Popen("i2cget -y 1 0x20 0x09", shell=True, stdout=sb.PIPE).stdout.read()
I then tried a simple C++ program, but this only got me up to 1.5 kHz (reading the entire GPIO register):
/* encoder.h */
class Encoder {
private:
unsigned int device; // device address
int file; // I2C file
public:
// Constructor
Encoder(int device_address);
// Read motor encoders
unsigned char read_encoders();
};
/* encoder.cpp */
#include "encoder.h"
#include<iostream>
#include<sstream>
#include<fcntl.h>
#include<iomanip>
#include<stdio.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
Encoder::Encoder(int device_address) {
std::string name = "/dev/i2c-1";
this->device = device_address;
this->file = open(name.c_str(), O_RDWR);
if (this->file < 0) {throw;}
if (ioctl(file, I2C_SLAVE, device) < 0) {throw;}
}
unsigned char Encoder::read_encoders() {
// Write to GPIO address
unsigned char buffer[1] = {0x09};
if (::write(file, buffer, 1) < 0) {throw;};
// Read GPIO register
unsigned char output[1];
if (::read(file, output, 1)<0) {throw;};
return output[0];
}
My questions are:
Is there any way to use my current board to sample up to 13.5 kHz? I.e., is there any way to make my Python/C++ programs run faster?
Would it be better to connect the encoders directly to the GPIO pins? (I didn't want to do this initially because I have so many motors + other peripherals.)
Or, is it necessary to use a dedicated microcontroller? I.e., a microcontroller that keeps track of the relative position, which I can then periodically send to the raspberry pi.
I'm hoping to have a simple PID loop to control the speed of the motors + detect if they hit their limits and stop moving.