Pressure Sensor Matrix Shift Registers(74HC595) Multiplexors(74HC4051) Velostat

Hello Everyone I am creating a project based off a video that uses Multiplexors and Shift registers to scan and display areas when pressure is being applied. The idea goes like this

"Every pressure-dependent voltage divider in our matrix consists of a variable resistor (sensor) and a fixed resistor. The sensors are arranged in columns and rows of 8. The high sides of all sensors in a column are connected to one output of a shift register. The low sides of all sensors in a row are connected to a fixed ground-resistor and an analog switch input. A MCU controls the shift register and the analog switch in a common scanning strategy: Rows are connected one at a time to a MCU analog input pin by the analog switch and their voltage is measured while columns are energized one at a time by the shift register. That way we are able to measure all 64 sensor voltages with only one ADC and a few digital pins." - Pressure Sensor Matrix Mat Project – reps.cc
Hi-Res Pressure Sensor Matrix with the LattePanda - YouTube

Though I want to change this to fit my goal. I want to use this scanning strategy to see exactly where someone is stepping. I plan to make a Mat that is 2.5m by 1m, 4x10 totaling 40 squares. I will give a user a pattern and they must follow the pattern stepping in the correct square and sequence. So my question is, is it possible to use this strategy of scanning the mat and also portion it into specific squares with id ( if possible) to code different patterns? And see if the user stepped into the correct square?

As of now I'm working on the prototype, I'm trying to get a small scale square to sense that I am putting pressure on it. Im using 1 mux and 1 shift registers. I am not the most experiment user of the Arduino but I have an understating of the basics ( pin assignment, arrays, loops), Right now I'm having trouble having the ICs run and have the data from the mux stored in a array that updates and is shown in a way that the user can read it (Where pressure is being applied) . When ever I try to read it using the Serial monitor it comes up as boxes and random symbols. Using the analog read command and so forth.

I will gladly give out any other information you may want to know!

Thank you! Any sort of help is appreciated, Just trying to learn and understand more!

I attached a design of the Schematic of how I plan to lay the full size version out, however it may be subjected to change I might just use 4 Multiplexors ( 1 for each column) and 10 shift registers (1 for each row)

Sorry I don’t think that will work. You have a resistor with one end to ground and you are connecting 5V and the input of an A/D converter to the other end. I can’t see how this A/D is going to see any more than 5V no matter what the resistor is.

Maybe if you drew just say three columns and rows as a matrix it would be more obvious whether I am wrong or right.

I'm using this material called velostat which changes resistance when pressure is applied. The last picture is what my prototype looks like at the moment. 1 shift register and 1 multiplexor.

Thanks, but I was thinking about a schematic that actually shows the connection between the row and columns.

Have you got it working for just one shift register and one multiplexer?

I don't think you should have individual resistors pulling down the "columns". That is going to warp the measurements. Put one resistor on the multiplexer common.

Seems to me you are overcomplicating things.
With just 40 inputs, why not feed them direct into 5 analog muxes going to 5 analog inputs?
Make each pad it's own device, separate from any other.
Set the address lines to 000, read from all 5 muxes with 5 analogRead() from A0,A1,A2,A3,A4.
Set the address lines, to 001, read from all 5.
:
:
Set the address lines to 111, read from all 5.

No need for the output shift registers at all.

If the 40 analogRead() turns out to be too slow, change to 5 of MCP3008 and read the 40 inputs via SPI.

Grumpy_Mike:
Sorry I don’t think that will work. You have a resistor with one end to ground and you are connecting 5V and the input of an A/D converter to the other end. I can’t see how this A/D is going to see any more than 5V no matter what the resistor is.

Maybe if you drew just say three columns and rows as a matrix it would be more obvious whether I am wrong or right.

#define BAUD_RATE                 9600

#define ROW_COUNT                8
#define COLUMN_COUNT              8

#define PIN_ADC_INPUT            A0
#define PIN_SHIFT_REGISTER_DATA  2
#define PIN_SHIFT_REGISTER_CLOCK  3
#define PIN_MUX_CHANNEL_0        4  //channel pins 0, 1, 2,
#define PIN_MUX_CHANNEL_1        5
#define PIN_MUX_CHANNEL_2        6

#define SET_SR_DATA_HIGH()        PORTD|=B00000100 // D2  high, rest unchanged, PORTD on the Uno.
#define SET_SR_DATA_LOW()        PORTD&=~B00000100 // D2 low, rest unchanged
#define SET_SR_CLK_HIGH()        PORTD|=B00001000 // D1  high, rest unchanged, PORTD on the Uno.
#define SET_SR_CLK_LOW()          PORTD&=~B00001000 //D2 low, rest unchanged

#define ROWS_PER_MUX              8
#define CHANNEL_PINS_PER_MUX      3

#define PACKET_END_BYTE          0xFF
#define MAX_SEND_VALUE            254  //reserve 255 (0xFF) to mark end of packet
#define COMPRESSED_ZERO_LIMIT    254
#define MIN_SEND_VALUE            1    //values below this threshold will be treated and sent as zeros

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

/**********************************************************************************************************

  • GLOBALS
    **********************************************************************************************************/

int compressed_zero_count = 0;

/**********************************************************************************************************

  • setup()
    **********************************************************************************************************/
    void setup()
    {
      Serial.begin(BAUD_RATE);
      pinMode(PIN_ADC_INPUT, INPUT);
      pinMode(PIN_SHIFT_REGISTER_DATA, OUTPUT);
      pinMode(PIN_SHIFT_REGISTER_CLOCK, OUTPUT);
      pinMode(PIN_MUX_CHANNEL_0, OUTPUT);
      pinMode(PIN_MUX_CHANNEL_1, OUTPUT);
      pinMode(PIN_MUX_CHANNEL_2, OUTPUT);

sbi(ADCSRA,ADPS2);  //set ADC prescaler to CLK/16
  cbi(ADCSRA,ADPS1);
  cbi(ADCSRA,ADPS0);
}

/**********************************************************************************************************

  • loop()
    **********************************************************************************************************/
    void loop()
    {
      compressed_zero_count = 0;
      for(int i = 0; i < ROW_COUNT; i ++)
      {
        setRow(i);
        shiftColumn(true);
        for(int j = 0; j < COLUMN_COUNT; j ++)
        {
          int raw_reading = analogRead(PIN_ADC_INPUT);
          byte send_reading_in_Pounds = map(raw_reading, 0, 1023, 1, 135);
          shiftColumn(false);
          sendCompressed(send_reading_in_Pounds);
        }
      }
      if(compressed_zero_count > 0)
      {
        Serial.write((byte) 0);
        Serial.write((byte) compressed_zero_count);
      }
      Serial.write((byte) PACKET_END_BYTE);
    }

/**********************************************************************************************************

  • shiftColumn() - Shift out a high bit to drive first column, or increment column by shifting out a low
  • bit to roll high bit through cascaded shift register outputs. digitalWrite() has been replaced with direct
  • port manipulation macros, as this function performs the vast majority of pin writes
    **********************************************************************************************************/
    void setRow(int row_number)
    {
    }

void shiftColumn(boolean is_first)
{
  if(is_first)
  {
    SET_SR_DATA_HIGH();
  }
  SET_SR_CLK_HIGH();
  SET_SR_CLK_LOW();
  if(is_first)
  {
    SET_SR_DATA_LOW();
  }
}

/**********************************************************************************************************

  • sendCompressed() - If value is nonzero, send it via serial terminal as a single byte. If value is zero,
  • increment zero count. The current zero count is sent and cleared before the next nonzero value
    **********************************************************************************************************/
    void sendCompressed(byte value)
    {
      if(value < MIN_SEND_VALUE)
      {
        if(compressed_zero_count < (COMPRESSED_ZERO_LIMIT - 1))
        {
          compressed_zero_count ++;
        }
        else
        {
          Serial.write((byte) 0);
          Serial.write((byte) COMPRESSED_ZERO_LIMIT);
          compressed_zero_count = 0;
        }
      }
      else
      {
        if(compressed_zero_count > 0)
        {
          Serial.write((byte) 0);
          Serial.write((byte) compressed_zero_count);
          compressed_zero_count = 0;
        }
        if(value > MAX_SEND_VALUE)
        {
          Serial.write((byte) MAX_SEND_VALUE);
        }
        else
        {
          Serial.write((byte) value);
        }
      }
    }
  • I have not got it working yet still trying to figure out the code for it, I did find someone who had a similar idea using this sort of scanning strategy, I’ve been messing with it to see if I could some parts of the code.

Paul__B:
I don’t think you should have individual resistors pulling down the “columns”. That is going to warp the measurements. Put one resistor on the multiplexer common.

Are you saying have all the mux col. have only 1 resistor to ground? Wouldnt that make the readings all the same? What do you mean by it will wrap measurements?

CrossRoads:
Seems to me you are overcomplicating things.
With just 40 inputs, why not feed them direct into 5 analog muxes going to 5 analog inputs?
Make each pad it’s own device, separate from any other.
Set the address lines to 000, read from all 5 muxes with 5 analogRead() from A0,A1,A2,A3,A4.
Set the address lines, to 001, read from all 5.
:
:
Set the address lines to 111, read from all 5.

No need for the output shift registers at all.

If the 40 analogRead() turns out to be too slow, change to 5 of MCP3008 and read the 40 inputs via SPI.
https://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf

Well for what I understand the shift registers(Horizontal copper strips) are what send the initial high signal and the mux(vertical copper strips) is what intakes that signal and reads the value. So i would have 1 row turn on 1 at a time and the mux would scan each input reading its voltage values to see if it has changed. Then the same process will happen to the next row. How would I know if someone is stepping in the square if their is no initial high signal? Unless the mux can send the high signal and read it a the same time?

@hgt97 please learn how to use quote tags correctly. What they say goes inside the tags. What you say goes outside the tags, otherwise it looks like you are putting words into their mouths.

You may have missed this important part of Crossroad’s suggestion:

CrossRoads:
Make each pad it’s own device, separate from any other.

This would mean you would not need to scan the strips.

Oh my apologies, I will fix them.

I see, but How would I implement this with the Velostat method and not use individual pressure sensors (Force sensors)?

I chose to go with this Velostat method since it will be significantly cheaper.

The problem with the single sheet, is that when you attempt to measure the resistance at one crossing, there are also alternative paths for the current through all the surrounding crossings (and not just those adjacent). If only one area is pressed, this will not be too much of a problem and indeed, neither will be only two points of pressure, but more points of pressure and lack of distinction between the pressed versus non-pressed resistance values will likely be an insurmountable problem since the more total points you have, the more are the alternative paths in a quadratic order.

My earlier point was that connecting resistors to the "columns" that you are not attempting to measure at a given moment will further foul up your measurements. The single bias resistor needs to to connect only to the multiplexer common.

+1 karma for fixing the quote tags.

hgt97:
I chose to go with this Velostat method since it will be significantly cheaper.

I don't think Crossroad's suggestion would be much more expensive than what you are proposing. What other circuits did you consider?

Paul__B:
The problem with the single sheet, is that when you attempt to measure the resistance at one crossing, there are also alternative paths for the current through all the surrounding crossings (and not just those adjacent). If only one area is pressed, this will not be too much of a problem and indeed, neither will be only two points of pressure, but more points of pressure and lack of distinction between the pressed versus non-pressed resistance values will likely be an insurmountable problem since the more total points you have, the more are the alternative paths in a quadratic order.

My earlier point was that connecting resistors to the "columns" that you are not attempting to measure at a given moment will further foul up your measurements. The single bias resistor needs to to connect only to the multiplexer common.

Oh I see what you are saying, yeah I agree this might cause some issues in the future. However at this point I just want it to sense someone is stepping into a square.

Well as of now I do plan to use these components, I'm gonna have the 4 Multiplexors at the top (vertical columns) and the 10 shift registers along the sides (Horizontal rows). I plan to have one shift register turn on, then have the multiplexors scan the area to see if any value is there. If it does sense anything in a certain square then it would say this square is being stepped in. Then it would continue to the next row and the process will repeat.

I like this method also so if I want to update it in the future the components will be already there, just the code will need to be updated.

The code is where I'm having trouble at the moment, I posted some snippet of someone with the same general idea but still kind of lost on how to turn the shift register on, and have the mux scan and send information back to the computer. I have set it in a array, and for the multiplexor to increase each value to scan each port, but when I use the analog read command it just comes back as some weird symbols.

it just comes back as some weird symbols.

Have you done any basic Arduino tutorials before leaping into this project? That sounds like one of the most basic errors that complete beginners learn about while doing their first tutorials: incorrect baud rate setting.

I have done a couple projects with Arduinos though this would be my first at this complexity.

Got it, ill mess with that and we'll see what happens

Hello, So I have been working on this project but I’m coming into an issue again with the mux. I’m using some libraries to help with the signals however I cant get the mux to read any values. It says it is at a constant number. I also have the information being read from a python script that visualizes it.
This is my code at the moment, any help is appreciated. Thank you!

#include <FastMap.h>
#include <FastGPIO.h>

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // Macro to clear a bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Macro to set a bit

FastMap mapper; // A faster map() function


const int analogPin = A1; // Analog pin

const int latchPin = 5; // ST_CP Latch
const int clockPin = 6; // SH_CP
const int dataPin = 7;  // DS
const int selectPins[3] = {2, 3, 4}; // Multiplexer controls
const int rowMap[8] = {6,7,5,4,0,3,2,1}; // Translates row number to multiplexer pin
int inputVal = 1;

void setup() {


  pinMode(analogPin, INPUT);
  pinMode(latchPin, OUTPUT);
  digitalWrite(latchPin, HIGH);
  pinMode(dataPin, OUTPUT);
  digitalWrite(dataPin, LOW);
  pinMode(clockPin, OUTPUT);
  digitalWrite(clockPin, HIGH);
  
  for (int i=0; i<3; i++)
  {
    pinMode(selectPins[i], OUTPUT);
    digitalWrite(selectPins[i], HIGH);
  }
  
  Serial.begin(500000);
  delay(100);
  Serial.write(0); // 0 indicates start of new frame

  mapper.init(0, 1023, 1, 255);
}

void loop() {
  for (int row=0; row<8; row++) // Changing rows is slow, so it's the outer loop
  {
      int pin = rowMap[row%8];
      FastGPIO::Pin<2>::setOutput(bitRead(pin,0)); // Select multiplexer pin
      FastGPIO::Pin<3>::setOutput(bitRead(pin,1)); // Select multiplexer pin
      FastGPIO::Pin<4>::setOutput(bitRead(pin,2)); // Select multiplexer pin

      FastGPIO::Pin<latchPin>::setOutput(LOW);  // Send 1 high bit to the shift register chain
      FastGPIO::Pin<dataPin>::setOutput(HIGH);  // Send 1 high bit to the shift register chain
      FastGPIO::Pin<clockPin>::setOutput(LOW);  // Send 1 high bit to the shift register chain
      delayMicroseconds(2);
      FastGPIO::Pin<clockPin>::setOutput(HIGH); // Send 1 high bit to the shift register chain
      delayMicroseconds(2);
      FastGPIO::Pin<clockPin>::setOutput(LOW);  // Send 1 high bit to the shift register chain
      FastGPIO::Pin<dataPin>::setOutput(LOW);   // Send 1 high bit to the shift register chain
      FastGPIO::Pin<latchPin>::setOutput(HIGH); // Send 1 high bit to the shift register chain
      delayMicroseconds(2);
      
      for (int col=0; col<8; col++) // Changing cols is fast, so it's the inner loop
      {
        inputVal = readMux(row);
        Serial.write((byte)mapper.map(inputVal)); // Actually write 1 byte to serial
        FastGPIO::Pin<latchPin>::setOutput(LOW);  // Shift shift register data 1 step
        FastGPIO::Pin<clockPin>::setOutput(HIGH); // Shift shift register data 1 step
        delayMicroseconds(2);
        FastGPIO::Pin<latchPin>::setOutput(HIGH); // Shift shift register data 1 step
        FastGPIO::Pin<clockPin>::setOutput(LOW);  // Shift shift register data 1 step
        delayMicroseconds(2);
      }
  }
  Serial.write(0); // Mark the beginning of the next frame
}

int readMux(int row)
{
  if (row <8)  //Prototype scale 
    return analogRead(A1);

}

Remove those 2 libraries and use only standard Arduino functions, ie. those listed on the language reference page. Once the code is working, you can re-introduce those libraries if you still feel they are needed.

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // Macro to clear a bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // Macro to set a bit




#define analogPin A0 // Analog pin

const int latchPin = 5; // ST_CP Latch
const int clockPin = 6; // SH_CP
const int dataPin = 7;  // DS

#define muxPin0 2  // Digital Pin 2
#define muxPin1 3  // Digital Pin 3
#define muxPin2 4  // Digital Pin 4

const int rowMap[8] = {0, 1, 2, 3, 4, 5, 6, 7}; // Translates row number to multiplexer pin

int inputVal = 1;

void setup() {


  pinMode(latchPin, OUTPUT);
  digitalWrite(latchPin, HIGH);
  pinMode(dataPin, OUTPUT);
  digitalWrite(dataPin, LOW);
  pinMode(clockPin, OUTPUT);
  digitalWrite(clockPin, HIGH);



  Serial.begin(9600);
  delay(100);
  Serial.write(0); // 0 indicates start of new frame



}

void loop() {
  for (int row = 0; row < 8; row++) // Changing rows is slow, so it's the outer loop changed
  {

  int pin = rowMap[row];
  digitalWrite(muxPin0, bitRead(row, 0));
  digitalWrite(muxPin1, bitRead(row, 1));
  digitalWrite(muxPin2, bitRead(row, 2));

  
    digitalWrite(latchPin, LOW);
    digitalWrite(dataPin, HIGH);
    digitalWrite(clockPin, LOW);

    // delayMicroseconds(2);
    digitalWrite(clockPin, HIGH);
    // delayMicroseconds(2);
    digitalWrite(clockPin, LOW);
    digitalWrite(dataPin, LOW);
    digitalWrite(latchPin, HIGH);
    // delayMicroseconds(2);



    for (int col = 0; col < 8; col++) // Changing cols is fast, so it's the inner loop
    {
      inputVal = readMux(row);
      digitalWrite(latchPin, LOW);
      digitalWrite(clockPin, HIGH);
      // delayMicroseconds(2);

        map(inputVal, 0, 1023, 1, 255);
      
      digitalWrite(latchPin, HIGH);
      digitalWrite(clockPin, LOW);
      // delayMicroseconds(2);
    }
  }
  Serial.print(inputVal);
  Serial.write(inputVal);
  Serial.println();      // NEW LINE
  Serial.write(0); // Mark the beginning of the next frame
  Serial.println("*********************************");
  delay(1000);
}

int readMux(int channel)
{

 
  // read from the selected  channel (A0)
  int muxValue = analogRead(analogPin);
  // return the  analog value
  return muxValue;
}

I have got it working sorta, when pressure is applied the values goes down to around 60-130 range and when no pressure is applied if fluctuates around 270-400.

Would their be a way to make it so the values don’t fluctuate as much? I have it outputting only one value, I’m choosing to go with this method as of now just to be able to sense pressure is being applied somewhere on the square.

I’m also using the serial monitor to see these values

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.