本教程对于电子制造商来说将非常令人兴奋,因为我们将使用 Arduino Nano 设计我们自己的触控电容式钢琴。我们将在我们的钢琴上加入录音和回放功能。到目前为止,我们已经使用 Arduino 制作了一些钢琴项目,但这个项目完全不同,因为我们将使用电容式触摸键作为我们的钢琴键。因此,在学习如何打造有趣的钢琴演奏的同时,我们还将探索如何在 PCB 上设计电容式触摸键,因为您可以尝试让我们的按键看起来像真正的钢琴键。由于其制造商PCBWay ,PCB 看起来和工作起来都像钢琴,我们还将探索我们如何设计和制造此板,但在此之前,让我们探索电容式触摸传感器及其工作原理。
电容式触摸传感器如何工作?
我们知道,为了形成一个具有一定电容值的电容器,我们需要两个平行的导电板,由介电材料隔开。但是我们如何仅仅通过用手指触摸导电板来判断电容是否发生了变化呢?我们的答案是基于我们对电容器的基本理解。众所周知,改变导电板的面积或两个平行导电板之间的距离可以改变电容值。在导电板和手指之间,我们有空气作为电介质。结果,当我们用手指触摸板时,电容的增加不确定,因为我们的手指充当导电物体,两个导电物体之间的距离减小了。我们知道平行板电容器的电容的基本公式是,
C = εA/d
其中“A”代表导电板的面积,“d”代表两个导电板之间的距离,“ε”代表 AIR 的介电常数。结果,增加面积并减小两个平行导电板之间的距离会增加电容值。在我们的例子中,触摸导电板会减少距离,同时增加电容值。我们可以通过将导电材料连接到电阻器和微控制器的 GPIO 引脚来检测这种变化的电容吗?答案是,我们不能。是的,将电压源连接到它会导致模拟电压发生微小变化,但这不是一个非常可靠的解决方案。
如何检测电容式触摸传感器中的电容变化?
那么,我们如何判断电容值是否发生了变化呢?但是,有更好的方法来解决它。让我们看一下下面的框图。将其视为由微控制器(在本例中为 Arduino Nano)、1 兆欧电阻和导电板组成的基本电路。Arduino Nano 的两条数字线连接到带有 1 兆欧电阻的电阻回路。该电阻器也有一点与导电板相连。虽然这块板充当电容器的单点,但它仍然可以引入电容,当我们触摸它时会发生变化。然而,这不能简单地通过检测电压变化来检测。这条线上的电容变化并不像感应 GPIO 引脚上的值的切换那样容易。
电容式传感器库如何工作?
这就是 Arduino 库派上用场的地方。非常感谢“ CapacitiveSensor ”库的作者Paul Bagder和Paul Stoffregen 。当我们触摸那个导电板时,我们可以使用这个库来检测电容的变化。在这个库中,一个数字引脚用作发送引脚(作为OUTPUT),而另一个用作接收引脚(作为INPUT)。发送引脚变为高电平和接收引脚变为高电平之间的持续时间是检测电容变化的唯一方法 。 当您将发送引脚设置为高电平(或5 伏)时,电阻-电容对 会 在发送引脚变为高电平和接收引脚从发送引脚读取高值之间产生延迟。CapacitiveSensor 库提供了一个将发送引脚设置为HIGH的函数,然后等待并计数,直到接收引脚被读取为 HIGH。此函数返回可用于检测电容变化的时间值。当时间值增加或减少时, 表示电容值发生了变化。当电容较大时,接收引脚达到高电平所需的时间较长 ,而当电容较小时,接收引脚达到高电平所需的时间较短。因此,我们可以确定正常状态是什么,然后在每次发送引脚切换时检查更改。
我们将使用“ CapacitveSensor ”库来检测电容的变化。但在进入编程部分之前,让我们为我们的项目创建电路和 PCB。在这里,我们使用EasyEDA平台为我们的项目创建原理图和 PCB。为了检测电容的变化,我们将使用“ CapacitveSensor ”库。在开始编程之前,让我们从项目的电路和 PCB 开始。我们项目的原理图和 PCB 是使用EasyEDA平台创建的。在EasyEDA平台上,我们创建了大量的PCB项目。这些项目可用于获得如何在 EasyEDA 上设计 PCB 的概念。
使用 Arduino Nano 构建 PCB 钢琴所需的组件
使用 Arduino Nano 构建 PCB 钢琴需要以下组件。
Arduino纳米
电阻器 (1Mega Ohm) X 8
压电蜂鸣器
18650 电池芯
18650 电池座
18650电池充电模块
直流到直流电压升压器。
使用 Arduino Nano 的 PCB 钢琴电路图
在以下电路图中,八个 1Mega Ohm 电阻器连接到 Arduino Nano 的数字引脚 2 。数字引脚 3 到 10进一步连接到每个电阻的其他连接点。在下图中,我们有一个标有“RECODINGSWITCH”的滑动开关。Arduino Nano 的数字引脚 12连接到滑动开关的“EN”引脚。滑动开关的“Vs”引脚连接到 Arduino Nano 的“ 5V”引脚。滑动开关的“GND”引脚连接到 Arduino Nano 的“连接到 Arduino Nano 的“A4” 引脚。蜂鸣器的负极连接到 Arduino Nano 的接地引脚。
我们已将八个 10uF 电容器连接 到每个电阻器。每个电容器的负极引脚连接到Arduino Nano 的接地引脚。然后我们有一个电源部分,为Arduino Nano 的“Vin”引脚提供适当的 6.6V。18650 电池单元连接到18650 电池充电器模块,充电器模块的输出连接到DC 到 DC 升压器。升压器的正输出引脚( BOUT + ) 连接到 Arduino Nano 的“Vin” 引脚,升压器的负输出引脚(BOUT-)连接到Arduino Nano 的接地引脚。
注:如有需要,我们可以加电容。强烈建议使用小电容器 (20pF - 400pF) 来稳定检测到的数据。但是,请确保电容器接地,因为这会降低并联体电阻。但是,就我而言,我没有使用电容器,因为没有它们对我来说效果很好。我在上面的示意图中提到了电容器,因此您可以在实际实施过程中轻松添加它们。按照“ CapacitveSensor ”库文档中的规定,以下电容器的值必须介于20pF 和 400pF之间。
PCB概述
上述原理图的 PCB 视图如下图所示。您可以从我们的 GitHub 存储库下载项目的 Gerber 文件。或者,您可以访问EasyEDA平台上的项目了解更多详情。黄色用于顶层丝绸层。而绿色代表底部丝绸层。红色代表顶层,蓝色代表底层。
PCB的顶层:
现在,让我们逐层查看PCB的每一层。顶层如下图所示。如您所见,顶层是红色的。我设计了每个导电板,使其看起来像钢琴。钢琴的每个键都分别连接到每个 1 兆欧电阻上。
我使用了可以在 EasyEDA 的 PCB 工具部分找到的矩形形状,在下图中以红色圈出。确保按键的宽度足够大,以便您可以用手指触摸每个按键。就我而言,我设法绘制了宽度为 10 毫米或大于 10 毫米的每个键。
PCB的底层:
在底层,我们有一个完整的铜层,用于连接所有接地。您可以在 EasyEDA的“PCB Tools”中使用“Solid Region”选项。这被称为 “铜浇注”方法。此步骤会将底层转换为公共接地层。我们在这一层还有一些其他的铜连接。
PCB的顶层丝绸层:
下图表示 PCB 的 Top-Silk-Layer。我们可以通过添加一些丝层或非铜层来设计我们的 PCB。我将滑动开关标记为“ RECORD”和“ PLAY”。这样我们就可以了解我们使用的是哪种模式。我们在顶层丝绸层有 BUZZER 的足迹。“ XL6009E1”是由方形区域包围的 DC 到 DC 升压模块的封装。我们可以使用EasyEDA 上提供的相应PCB 工具添加文本和图像。
PCB的底层丝绸层:
在 PCB 的底部丝层,我们有 Arduino Nano、8 个 1Mega ohm 电阻器、8 个电容器、一个 18650 电池座或单节电池和充电模块的封装。
使用 Arduino Nano 对钢琴 PCB 进行编程
“ CapacitiveSensor ”库非常易于使用,并且他们提供了有关如何使用该库的很好的文档。在进入程序之前,让我们在 Arduino IDE 上安装“ CapacitiveSensor”库。您需要下载库的 zip 文件。然后转到Arduino IDE 工具栏下的“ Sketch -》 Include Library ”部分。使用“添加 .Zip 库。。.”选项添加 zip 文件,如下图所示。然后重新启动 Arduino IDE。
现在,您可以从我们的 github 存储库中下载该项目的代码,然后打开“ codes ”文件夹中的“ piano_pcb.ino ”文件。我们有一个名为“ piano_tones.h ”的自定义头文件。此头文件包含在主文件中以获取一些预定义的自定义钢琴音色。就像钢琴一样,每个音调都指的是一个音符。让我们看看“piano_pcb.ino”文件里面有什么。
#include
#include "piano_tones.h"
#define common_pin 2 // 所有键的通用“发送”引脚
#define Buzzer A4 //压电蜂鸣器的输出引脚
#define recordbtn 12 // 录音按钮
#define CPin(pin) CapacitiveSensor (common_pin, pin)
在“ piano_pcb.ino ”文件的开头,我们有“ CapacitiveSensor.h ”和“ piano_tones.h ”头文件。然后我为各自的 GPIO 引脚定义了三个宏。“ common_pin”用于将“digital pin 2”设置为“Send Pin”, “ buzzer”用于定义模拟pin 4(A4),“recordbtn”用于从“将按钮”滑入“数字引脚 12”。然后我创建了“CPin (pin)”宏,这样我们就不需要传递发送 pin(即common_pin)和CapacitiveSensor(common_pin,pin)中的接收引脚(即各个电阻器的引脚)多次。
整数注释[]={NOTE_C7,NOTE_D7,NOTE_E7,NOTE_F7,NOTE_G7,NOTE_A7,NOTE_B7,NOTE_C8};
// 启动时的声音
int soundOnStartUp[] = {
注意_E7, 注意_E7, 0, 注意_E7,
0, 注意_C7, 注意_E7, 0,
NOTE_G7, 0, 0, 0,
NOTE_G6, 0, 0, 0 };
电容式传感器键[] = {CPin(3), CPin(4), CPin(5), CPin(6), CPin(7), CPin(8), CPin(9), CPin(10)};
调用库并定义某些宏后,我们需要创建 3 个如上所述的数组。即notes[]、soundOnStartUp[]和keys[]。“ notes[] ”数组存储的是钢琴在一定音阶中对应的音符(例如 NOTE_C7、NOTE_D7等)。您可以通过取消注释我在代码中提供的其他“ notes[] ”来更改比例。soundOnStartUp []数组用于存储我在钢琴启动期间使用的一些曲调。该数组已在setup()函数中调用。电容式传感器键[ ]array 用于存储相应的接收引脚,使用我们之前定义的CPin()函数。
无效记录按钮(){
// 设置传感器的灵敏度。
long touch1 = keys[0].capacitiveSensor(灵敏度);
long touch2 = keys[1].capacitiveSensor(灵敏度);
long touch3 = keys[2].capacitiveSensor(灵敏度);
long touch4 = keys[3].capacitiveSensor(灵敏度);
long touch5 = keys[4].capacitiveSensor(灵敏度);
long touch6 = keys[5].capacitiveSensor(灵敏度);
long touch7 = keys[6].capacitiveSensor(灵敏度);
long touch8 = keys[7].capacitiveSensor(灵敏度);
pev_button = 按钮;
// 当我们触摸到传感器时,按钮会记录相应的数字。
如果(触摸1 > 灵敏度)
按钮 = 1;
if (touch2 > 灵敏度)
按钮 = 2;
if (touch3 > 灵敏度)
按钮 = 3;
if (touch4 > 灵敏度)
按钮 = 4;
if (touch5 > 灵敏度)
按钮 = 5;
if (touch6 > 灵敏度)
按钮 = 6;
if (touch7 > 灵敏度)
按钮 = 7;
if (touch8 > 灵敏度)
按钮 = 8;
// 当我们没有触摸它时,不会产生音调。
if (touch1<=敏感度 & touch2<=敏感度 & touch3<=敏感度 & touch4<=敏感度 & touch5<=敏感度 & touch6<=敏感度 & touch7<=敏感度 & touch8<=敏感度)
按钮 = 0;
/****将按下的按钮记录在一个数组中***/
如果(按钮!= pev_button && pev_button != 0)
{
记录按钮[button_index] = pev_button;
按钮索引++;
记录按钮[按钮索引] = 0;
按钮索引++;
}
/**录制程序结束**/
}
我们使用“keys[0].capacitiveSensor(sensitivity)”函数读取每个键的电容值,并将其与一些指定值进行比较,以确定在上述recordButtons()函数中按下了哪个键。我们还跟踪在此函数中按下按钮的顺序。记录的值保存在记录的button[]数组中。我们首先查看是否按下了新键,如果是,我们再次检查它不是按钮 0。但是没有按下按钮,因此按钮 0 什么都没有。我们将值保存在 if 循环内的变量button_index指定的索引位置,然后我们增加该索引值以避免覆盖相同的位置。
无效playTone(){
/****Rcord 数组中每个按钮按下之间的时间延迟***/
如果(按钮!= pev_button)
{
note_time = (millis() - start_time) / 10;
如果(注意时间!= 0){
记录时间[time_index] = note_time;
时间索引++;
开始时间 = 毫秒();
}
Serial.println(time_index);
}
/**录制程序结束**/
如果(按钮 == 0)
{
noTone(蜂鸣器);
}
如果(按钮 == 1)
{
音调(蜂鸣器,注释[0]);
}
如果(按钮 == 2)
{
音调(蜂鸣器,注释[1]);
}
如果(按钮 == 3)
{
音调(蜂鸣器,注释[2]);
}
如果(按钮 == 4)
{
音调(蜂鸣器,注释[3]);
}
如果(按钮 == 5)
{
音调(蜂鸣器,注释[4]);
}
如果(按钮 == 6)
{
音调(蜂鸣器,注释[5]);
}
如果(按钮 == 7)
{
音调(蜂鸣器,注释[6]);
}
如果(按钮 == 8)
{
音调(蜂鸣器,注释[7]);
}
}
使用各种if条件,我们将为 playTone() 函数中的按键播放适当的音调。该函数的完整代码显示在上面。我们还将使用一个名为recorded time[] 的数组来保存按下按钮的时间长度。该过程类似于记录按钮序列,因为我们使用 millis() 函数来计算每个按钮被按下的时间,然后将该值除以 10 以减小变量的大小。我们在按钮 0 的同一时间段内不播放任何音调,这表示用户没有按下任何东西。
无效设置(){
序列号.开始(9600);
// 关闭所有通道的自动校准:
for(int i=0; i<8; ++i) {
键[i].set_CS_AutocaL_Millis(0xFFFFFFFF);
}
// 将蜂鸣器设置为输出:
pinMode(蜂鸣器,输出);
pinMode(recordbtn,输入);
noTone(蜂鸣器);
延迟(10);
int sizeed = sizeof(soundOnStartUp) / sizeof(int);
for (int thisNote = sizeed; thisNote > 0 ; thisNote--) {
音(蜂鸣器,soundOnStartUp[thisNote]);
延迟(100);
}
noTone(蜂鸣器);
延迟(10);
}
在setup () 函数中有两个 for 循环。在第一个循环中,我使用“keys[i].set_CS_AutocaL_Millis(0xFFFFFFFF) ”设置了键。第二个for 循环用于通过使用音调(蜂鸣器,soundOnStartUp[thisNote])播放soundOnStartUp[]音符。
无效循环(){
Serial.println(digitalRead(recordbtn));
while (digitalRead(recordbtn) == 1) //如果拨动开关设置为录制模式
{
记录按钮();
播放音();
}
while (digitalRead(recordbtn) == 0) //如果拨动开关设置为播放模式
{
for (int i = 0; i < sizeof(recorded_button) / 2; i++)
{
延迟((记录时间[i])* 10);//等待支付下一曲
if (recorded_button[i] == 0)
noTone(蜂鸣器);//用户没有触摸任何按钮
别的
音(蜂鸣器,注释[(recorded_button[i] - 1)]);//播放用户触摸的按钮对应的声音
}
}
}
然后,要播放录制的音调,用户必须在录制后将滑动开关推到另一方向。完成此操作后,程序退出前一个 while 循环并进入第二个 while 循环,在该循环中,我们按照键被击中的顺序播放先前记录的长度的音符。上面提供了完成此操作的代码。您可以观看下面附加的视频以获取更多说明。
代码
#include
#include “piano_tones.h”
#define common_pin 2 // 所有电阻的公共“发送”引脚
#define buzzer A4 // 压电蜂鸣器的输出引脚
#define recordbtn 12 // 录音button
//这个宏为每个电阻引脚创建一个电容传感器对象
#define CPin(pin) CapacitiveSensor(common_pin, pin)
char button = 0;
整数模拟值;
字符 REC = 0;
int 记录按钮[200];
诠释 pev_button;
int 灵敏度 = 2000;
int 记录时间[200];
字符时间索引;
字符按钮索引 = 0;
无符号长开始时间;
int note_time;
// 每个键对应一个音符,这里定义。取消注释您要使用的比例:
//int notes[]={NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_B4,NOTE_C5}; // C 大调音阶
//int notes[]={NOTE_A4,NOTE_B4,NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5}; // A-小调音阶
//int notes[]={NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_C5,NOTE_D5}; // C 蓝调音阶
//int notes[] = {1300, 1500, 1700, 1900, 2000, 2300, 2600, 2700};
整数注释[]={NOTE_C7,NOTE_D7,NOTE_E7,NOTE_F7,NOTE_G7,NOTE_A7,NOTE_B7,NOTE_C8};
//int notes[] = {1915, 1700, 1519, 1432, 1275, 1136, 1014, 956};
// 启动声音
int soundOnStartUp[] = {
NOTE_E7, NOTE_E7, 0, NOTE_E7,
0, NOTE_C7, NOTE_E7, 0,
注意_G7, 0, 0, 0,
注意_G6, 0, 0, 0
};
// 定义寄存器连接的引脚:
CapacitiveSensor keys[] = {CPin(3), CPin(4), CPin(5), CPin(6), CPin(7), CPin(8), CPin( 9), CPin(10)};