FSR阵列入门教程

https://www.sensitronics.com/tutorials/fsr-matrix-array/index.php

教程:读取矩阵数组
Sensitronics 的 MatrixArray 产品由多个以网格形式排列的力传感电阻元件组成。MatrixArray

提供 N 输入多点触控 + 力传感功能,是乐器、PC 输入设备和其他创新设计的基础。

为了帮助您入门,这里提供了一个基本示例,演示如何使用 Arduino 和一些常用部件连接和读取我们的一个 16x10 MatrixArray。
广告段

 

所需零件/技能

要启动并运行此演示,您需要以下列出的部件。我们假设您具备一些基本的 Arduino 编程经验,但实际上,我们已经提供了完整的代码示例 - 只要您知道如何上传 Arduino 代码并打开串行终端窗口,您就可以根据需要将代码复制粘贴到可运行的演示中。

    • 16x10 ThruMode 矩阵阵列
    • Arduino(最好是Uno,但任何都可以)
    • 74HC595移位寄存器(x2)
    • 74HC4051 模拟多路复用器 (x2)
    • 20k电阻(x1)
    • 无焊面包板
    • 跳线

可以用更少的硬件来实现这一点吗?

如果您查看零件清单,认为 Arduino 的 IO 引脚足够驱动 16 列并读取 10 行,且仅需一个外部多路复用器,那么您是对的。我们选择使用 2 个移位寄存器和 2 个多路复用器有两个原因,可以避免混合板载和外部 IO:

1. 它使设计和代码保持简洁。本项目仅供参考,因此我们希望最大限度地提高清晰度。2

. 它具有高度可扩展性!按照相同的连接模式,只需更改两行代码,即可扩展此示例以扫描 96 x 96(9216 个单元)矩阵。

那么,让我们来看看电路是如何连接的......

第 2 部分:硬件连接

示意图

现在您已经收集了上一节列出的所有必需零件,是时候开始动手制作了。

所有零件均采用 DIP/通孔封装,因此我们可以直接用面包板搭建整个电路,无需焊接。开门见山,以下是原理图:

 

它是如何工作的?

如果您曾经通过查看分压器两端的电压来测量单元件FSR,那么我们这里也采用同样的方法。
但在本例中,我们通过为单个驱动列供电来选择测量点,启用一个多路复用通道,以便ADC检测到单行数据。在下一节中,Arduino代码演示了一种扫描模式,可以按顺序读取所有160个单元。

它会是Ghost吗?

“重影”是指传感器或按键矩阵中常见的问题。我们不再赘述,因为网络上已经有其他文章详细讨论过了。

这里有一篇很好的文章,解释了重影(与电脑键盘电路相关)。

简而言之,问题在于,当多个单元被按下时,行和列之间可能会创建意外的路径,导致未按下单元的读数错误。这是我们设计的问题吗?嗯,部分原因。如果我们使用带有三态输出的移位寄存器来驱动列,并将未驱动的列设置为高阻态,我们预计会看到像键盘矩阵一样的“鬼影”点。但是,我们打算将未驱动的列接地。这些意外的路径实际上与传感电阻 (R1) 并联,当同一列或同一行中的其他单元被按下时,这会降低某些单元的视在灵敏度。因此,我们可以说这个例子演示了“反向鬼影”。

最终结果是,这种设计在多点触摸时无法提供准确的绝对读数。然而,它能够提供每个单元相对力的良好测量结果,相对于传感器片上的其他力。

观察电路,另一个问题是“移位寄存器的高/低输出会相互短路吗?”。在这种情况下,答案是“不会”,因为在这个特定的ThruMode矩阵中,每个单元的最小电阻(满载时)在2.5k-5.0k之间,这将任何移位寄存器引脚的最大拉/吸电流限制在1-2 mA。如果我们使用的传感器矩阵中,单元的电阻可能会被强制接近零欧姆,那么是的,我们很可能会熔化传感器膜并炸毁移位寄存器。

有解决这一限制的办法吗?

是的,可以设计放大器级来取代简单的分压器输出,以提高单元独立性并滤除不需要的信号成分。然而,事情会变得有点复杂,而且具体应用也有所不同。因此,为了本教程的基础,这方面的研究留给读者自己去完成。

第 3 部分:Arduino 代码

现在硬件已经连接好了,是时候编写一些代码了。
要读取 MatrixArray 的单个交点(或“单元”),我们需要执行以下步骤:

  • 使用 +5V 驱动电池行
  • 启用单元的列(多路复用器通道)
  • 在引脚 A0 上读取 ADC 读数
  • 通过串口输出读数

很简单,所以从这里开始,我们将编写一个扫描模式来高效地驱动和读取矩阵的每个单元。回想一下,在本例中,我们将移位寄存器的驱动引脚称为“行”,将复用的读取引脚称为“列”。我们将按顺序启用每一列,当每一列处于活动状态时,我们将每一行驱动为高电平并进行读取。

下面的代码示例实现了一个扫描模式,并将输出连续地转储到串行终端。

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

* Project: MatrixArray.ino

* By: Chris Wittmier @ Sensitronics LLC

* LastRev: 09/09/2015

* Description: FSR MatrixArray Demonstration of Sensitronics' 16x10 element resistive ThruMode. Scanning

* electronics consist of 2 HCT595 shift registers, and 2 4051 multiplexers connected to Arduino Uno.

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





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

* MACROS / PIN DEFS

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

#define BAUD_RATE                 115200

#define ROW_COUNT                 10

#define COLUMN_COUNT              16



#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, etc must be wired to consecutive Arduino pins

#define PIN_MUX_CHANNEL_1         5

#define PIN_MUX_CHANNEL_2         6

#define PIN_MUX_INHIBIT_0         7  //inhibit = active low enable. All mux IC enables must be wired to consecutive Arduino pins

#define PIN_MUX_INHIBIT_1         8



#define ROWS_PER_MUX              8

#define MUX_COUNT                 2

#define CHANNEL_PINS_PER_MUX      3





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

* GLOBALS

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

int current_enabled_mux = MUX_COUNT - 1;  //init to number of last mux so enabled mux increments to first mux on first scan.





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

* 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);

  pinMode(PIN_MUX_INHIBIT_0, OUTPUT);

  pinMode(PIN_MUX_INHIBIT_1, OUTPUT);

}





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

* loop()

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

void loop()

{

  for(int i = 0; i < ROW_COUNT; i ++)

  {

    setRow(i);

    shiftColumn(true);

    shiftColumn(false);                         //with SR clks tied, latched outputs are one clock behind

    for(int j = 0; j < COLUMN_COUNT; j ++)

    {

      int raw_reading = analogRead(PIN_ADC_INPUT);

      byte send_reading = (byte) (lowByte(raw_reading >> 2));

      shiftColumn(false);

      printFixed(send_reading);

      Serial.print(" ");

    }

    Serial.println();

  }

  Serial.println();

  delay(200);

}





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

* setRow() - Enable single mux IC and channel to read specified matrix row.

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

void setRow(int row_number)

{

  if((row_number % ROWS_PER_MUX) == 0)  //We've reached channel 0 of a mux IC, so disable the previous mux IC, and enable the next mux IC

  {

    digitalWrite(PIN_MUX_INHIBIT_0 + current_enabled_mux, HIGH);  //Muxes are enabled using offset from MUX_INHIBIT_0. This is why mux inhibits MUST be wired to consecutive Arduino pins!

    current_enabled_mux ++;

    if(current_enabled_mux >= MUX_COUNT)

    {

      current_enabled_mux = 0;

    }

    digitalWrite(PIN_MUX_INHIBIT_0 + current_enabled_mux, LOW);  //enable the next mux, active low

  }

  for(int i = 0; i < CHANNEL_PINS_PER_MUX; i ++)

  {

    if(bitRead(row_number, i))

    {

      digitalWrite(PIN_MUX_CHANNEL_0 + i, HIGH);

    }

    else

    {

      digitalWrite(PIN_MUX_CHANNEL_0 + i, LOW);

    }

  }

}





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

* 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.

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

void shiftColumn(boolean is_first)

{

  if(is_first)

  {

    digitalWrite(PIN_SHIFT_REGISTER_DATA, HIGH);

  }

  digitalWrite(PIN_SHIFT_REGISTER_CLOCK, HIGH);

  digitalWrite(PIN_SHIFT_REGISTER_CLOCK, LOW);

  if(is_first)

  {

    digitalWrite(PIN_SHIFT_REGISTER_DATA, LOW);

  }

}





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

* printFixed() - print a value padded with leading spaces such that the value always occupies a fixed

* number of characters / space in the output terminal.

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

void printFixed(byte value)

{

  if(value < 10)

  {

    Serial.print("  ");

  }

  else if(value < 100)

  {

    Serial.print(" ");

  }

  Serial.print(value);

}

第四部分:初步结果

串行终端输出

 

使用第三部分的代码对 Arduino 进行编程后,我们打开一个串行终端窗口,看到一系列以网格形式排列的数据转储。当 MatrixArray 为空/未加载时,我们应该看到全零或非常低的读数。

如果所有连接都正确,用食指按下 MatrixArray 的中心,我们应该会看到一些读数……

成功!在右侧的屏幕截图中,我们看到 Matrix 中心附近的读数,这些读数与施加的力成比例变化。

 

范围视图

现在我们已经验证了项目基本可以正常工作,我们将拿出探针,在数字示波器上查看系统的运行情况。这将清晰地展示系统运行/时序,或许还能提供一些关于哪些方面可以进行优化的线索。

将水平窗口设置为显示完整的 MatrixArray 扫描周期,并根据 Arduino 引脚名称标记信号,我们观察到的结果如下:

从图像中,我们可以看到在多行/列上检测到了一次手指按压(信号 A0)。我们还注意到,单个扫描周期大约需要 57 毫秒才能完成。如果这对于我们的设计来说足够快,那么我们可以就此打住。但对于许多设计来说,这太慢了——你可能不会把 17Hz 刷新率的触摸板称为“响应迅速”。

所以,让我们开始优化吧。

第 5 部分:优化性能

在第四部分中,我们观察到 MatrixArray 上的力度输入在正确的位置被检测到。读数与力度成正比,并且相当接近 ADC 的满量程范围。模拟性能看起来令人满意,所以让我们关注速度。

我们之前观察到,一个完整的扫描周期大约需要 57 毫秒才能完成(17Hz 更新率),这对于大多数项目来说太慢了,尤其是在测量用户触摸输入时。

您可能已经发现了初始 Arduino 代码示例中的一些(明显的)效率低下问题。再次参考右侧的示波器视图,让我们看看一些可能大幅提升速度的关键领域。



1. 串行通信

查看TX引脚,我们可以看到该系统的主要瓶颈。即使硬件扫描速度更快,我们仍然需要等待串行传输完成才能进行下一次扫描。

在最初的Arduino代码示例中,我们每次读取都发送3个字节,加上换行符,以保持终端窗口中文本间距的一致性/可读性。

如果我们将每次读取的数据减少到单字节并删除换行符,就可以立即使串行通信速度提高3倍以上。当然,这会弄乱终端窗口,因此我们将在第8部分中创建自己的接收应用程序。

 


我们还注意到长串的零,因此我们将应用简单的压缩,将连续的零值作为单个零 + 计数发送。例如,假设有一串连续 100 个零,我们将发送“0”,然后发送“100”——2 个字节而不是 100 个字节。

 

2. ADC 转换速率:

使用默认 ADC 配置,每次 ADC 转换大约需要 100 微秒。我们每个扫描周期读取 160 个读数,因此总共需要 16 毫秒。为了缩短转换时间,我们将 ADC 预分频器降低到能够提供良好读数的最小值。

3. IO 速度 / digitalWrite()

使用外部 IO(移位寄存器和多路复用器)意味着需要翻转大量位来扫描 MatrixArray——每个扫描周期我们调用近 400 次 digitalWrite()。

我们将用直接端口操作替换 digitalWrite。 直接端口操作已在其他网站上进行了广泛讨论,因此我们在此省略完整解释,但我们应该能够将每个扫描周期缩短一到两毫秒。

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

* Project: FastMatrixArray.ino

* By: Chris Wittmier @ Sensitronics LLC

* LastRev: 04/22/2014

* Description: Demonstration of Sensitronics' 16x10 element resistive ThruMode MatrixArray. Scanning

* electronics consist of 2 HCT595 shift registers, and 2 4051 multiplexers connected to an Arduino Uno. 

* This sketch is Uno-specific and optimized for faster rate.

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





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

* MACROS / PIN DEFS

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

#define BAUD_RATE                 115200

#define ROW_COUNT                 10

#define COLUMN_COUNT              16



#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, etc must be wired to consecutive Arduino pins

#define PIN_MUX_CHANNEL_1         5

#define PIN_MUX_CHANNEL_2         6

#define PIN_MUX_INHIBIT_0         7  //inhibit = active low enable. All mux IC enables must be wired to consecutive Arduino pins

#define PIN_MUX_INHIBIT_1         8



#define SET_SR_DATA_HIGH()        PORTD|=B00000100

#define SET_SR_DATA_LOW()         PORTD&=~B00000100

#define SET_SR_CLK_HIGH()         PORTD|=B00001000

#define SET_SR_CLK_LOW()          PORTD&=~B00001000



#define ROWS_PER_MUX              8

#define MUX_COUNT                 2

#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 current_enabled_mux = MUX_COUNT - 1;  //init to number of last mux so enabled mux increments to first mux on first scan.

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);

  pinMode(PIN_MUX_INHIBIT_0, OUTPUT);

  pinMode(PIN_MUX_INHIBIT_1, 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 = (byte) (lowByte(raw_reading >> 2));

      shiftColumn(false);

      sendCompressed(send_reading);

    }

  }

  if(compressed_zero_count > 0)

  {

    Serial.write((byte) 0);

    Serial.write((byte) compressed_zero_count);

  }

  Serial.write((byte) PACKET_END_BYTE);

}





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

* setRow() - Enable single mux IC and channel to read specified matrix row. digitalWrite() have not been

* replaced in this function, as mux changes are relatively infrequent.

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

void setRow(int row_number)

{

  if((row_number % ROWS_PER_MUX) == 0)  //We've reached channel 0 of a mux IC, so disable the previous mux IC, and enable the next mux IC

  {

    digitalWrite(PIN_MUX_INHIBIT_0 + current_enabled_mux, HIGH);  //This offset is why mux inhibits must be wired to consecutive Arduino pins

    current_enabled_mux ++;

    if(current_enabled_mux >= MUX_COUNT)

    {

      current_enabled_mux = 0;

    }

    digitalWrite(PIN_MUX_INHIBIT_0 + current_enabled_mux, LOW);  //enable the next mux, active low

  }

  for(int i = 0; i < CHANNEL_PINS_PER_MUX; i ++)

  {

    if(bitRead(row_number, i))

    {

      digitalWrite(PIN_MUX_CHANNEL_0 + i, HIGH);

    }

    else

    {

      digitalWrite(PIN_MUX_CHANNEL_0 + i, LOW);

    }

  }

}





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

* 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 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);

    }

  }

}

第七部分:优化结果

关键时刻到了……我们能提高那平淡无奇的 17Hz 扫描率吗?

让我们再看看示波器,使用与上次相同的时间/格设置:

成功!

使用光标绘制单个扫描周期,我们观察到一次典型的扫描现在只需 3.5 毫秒即可完成——更新率为 285Hz。还不错!

现在我们已经达到了更实用的速度,让我们在 PC 端添加一些图形反馈来结束这次扫描。

 

posted @ 2025-05-12 13:41  mcwhirr  阅读(47)  评论(0)    收藏  举报