QTR-MD-16RC & MCP23017 (or any port expander)

Hi!
I’m completely new to electronics, and I wanted to build a line follower from scratch. I decided to use a MCP23017 breakout board with my Arduino Nano and wire a QTR-MD-16RC to the MCP23017. To get it to run with the Adafruit MCP23017 library, I ended up modifying the qtr sensor library code a bit (just the readPrivate function) but it keeps giving me 0 readings across the board with both the min and max for all sensors being 2500 when calibration finishes. However, when just one sensor is hooked up to the MCP23017, it does work, but kind of weirdly.

Here are some things I’ve tried:

  1. Increasing the number of calibrations up to 1600 from the default didn’t seem to do much - I couldn’t get even 2 sensors to work on the expansion board.
  2. Hooking up the sensors to the remaining pins I have on my nano seems to work completely fine - I get normal readings!

It’s very likely my modifications of the library broke something. I’ve also noticed that when using my modified version with the calls to the Adafruit MCP23017 library, the sensor calibrates a lot slower. I’m also modifying SensorCount and the array passed into setSensorPins() correctly, so I don’t think that’s related to the problem.

void QTRSensors::readPrivate(uint16_t * sensorValues, uint8_t start, uint8_t step)
{
  if (_sensorPins == nullptr) { return; }

  switch (_type)
  {
    case QTRType::RC:
      for (uint8_t i = start; i < _sensorCount; i += step)
      {
        sensorValues[i] = _maxValue;
        // make sensor line an output (drives low briefly, but doesn't matter)
        mcp.pinMode(_sensorPins[i], OUTPUT);
        // pinMode(_sensorPins[i], OUTPUT);
        // drive sensor line high
        mcp.digitalWrite(_sensorPins[i], HIGH);
        // digitalWrite(_sensorPins[i], HIGH);
      }

      delayMicroseconds(10); // charge lines for 10 us

      {
        // disable interrupts so we can switch all the pins as close to the same
        // time as possible
        // noInterrupts();

        // record start time before the first sensor is switched to input
        // (similarly, time is checked before the first sensor is read in the
        // loop below)
        uint32_t startTime = micros();
        uint16_t time = 0;

        for (uint8_t i = start; i < _sensorCount; i += step)
        {
          // make sensor line an input (should also ensure pull-up is disabled)
          mcp.pinMode(_sensorPins[i], INPUT);
          // pinMode(_sensorPins[i], INPUT);
        }

        // interrupts();

        while (time < _maxValue)
        {
          // disable interrupts so we can read all the pins as close to the same
          // time as possible
          // noInterrupts();

          time = micros() - startTime;
          for (uint8_t i = start; i < _sensorCount; i += step)
          {
            if ((mcp.digitalRead(_sensorPins[i]) == LOW) && (time < sensorValues[i]))
            // if ((digitalRead(_sensorPins[i]) == LOW) && (time < sensorValues[i]))
            {
              // record the first time the line reads low
              sensorValues[i] = time;
            }
          }

          // interrupts(); // re-enable
        }
      }
      return;

    case QTRType::Analog:
      // reset the values
      for (uint8_t i = start; i < _sensorCount; i += step)
      {
        sensorValues[i] = 0;
      }

      for (uint8_t j = 0; j < _samplesPerSensor; j++)
      {
        for (uint8_t i = start; i < _sensorCount; i += step)
        {
          // add the conversion result
          sensorValues[i] += analogRead(_sensorPins[i]);
        }
      }

      // get the rounded average of the readings for each sensor
      for (uint8_t i = start; i < _sensorCount; i += step)
      {
        sensorValues[i] = (sensorValues[i] + (_samplesPerSensor >> 1)) /
          _samplesPerSensor;
      }
      return;

    default: // QTRType::Undefined or invalid - do nothing
      return;
  }
}

^ my modifications of readPrivate(), just replacing every digitalRead or pinMode with the Adafruit library equivalent

^ seemingly completely normal readings when the sensor is wired to pins on the nano - D11 and D12

^ the readings I get when just one of the sensors is connected to pin PA0 of the MCP23017, where I’m just passing my finger directly underneath the sensor and it seems to be reacting

^ the readings when I get two of the sensors connected to the MCP23017 (pins PA0 & PA1), and basically any number of sensors above that

Again, I’m basically a complete beginner so I’m sorry if this post comes off as silly. Thank you!

edit: looked it up and it seems the max current the MCP23017 can handle is 150mA while the QTR-MD-16RC gets up to 250mA…is this why it’s not working?

Hello.

We do not make any products with the MCP23017, so the support we can offer for this might be limited.

I am concerned that using our QTR sensor array this way might be impractical from it taking too long to read the pins on your port expander (at least, unless you do more to optimize that, which would not be trivial). Can you try characterizing long it takes to read the state of a pin on your MCP23017? The main loop of the program will probably look something like this (though I do not have an MCP23017, so I have not tested it out myself):

uint32_t  startTime = micros();
mcp.digitalRead(YOUR_PIN);
uint16_t  time = micros() - startTime;
Serial.println(time);

I do not think a power issue would produce the kind of behavior you are seeing, but if you want to rule out that possibility, you can try powering your QTR sensor array from a separate source that you know will be adequate. Just make sure you keep a common ground connection between the new power supply, your QTR sensor array, and your other electronics.

- Patrick