【Arduino Nano】筆圧で音が鳴るボールペンを作ってみた！

#目次
１. Introduction はじめに

２. Methodology 「筆圧で音がなるボールペン」の作製 

- ２.１. 主な使用部品
- ２.２. 構成
  - ２.２.１. 圧力センサ
  - ２.２.２．BMX055使用９軸センサーモジュール
  - ２.２.３. スイッチとスピーカ
  - ２．２．４．回路図
- ２.３.プログラミング
  - ２.３.１.加速度センサを用いた角度測定
  - ２.３.２. 圧力の角度補正
  - ２.３.３. 圧力に応じて音を変化させる
  - ２.３.４. プログラム全体

３. Results 「筆圧で音がなるボールペン」の動作確認
４. Conclusion まとめ
参考文献

#１. Introduction はじめに
こんにちは!「なつめにわ」と申します。

この記事では、以前作製した「筆圧で音がなるボールペン」について紹介したいと思います。
市販のボールペンに、Arduino Nanoや圧力センサ、加速度センサ、スピーカ等を搭載させました。

「圧力センサ」で筆圧を測定し、その圧力に応じて音が鳴る仕組みです。
「加速度センサ」でボールペンの角度を計算し、圧力に補正をかけています。

このボールペンは、この記事を書く６ヶ月以上前に作製しています。そのため、所々うろ覚えです。
分かりにくい箇所があると思いますが、ご了承ください。

#２. Methodology 「筆圧で音がなるボールペン」の作製 

###２.１. 主な使用部品
作製に使用した主な部品について紹介します。※電子部品のリンク先は、秋月電子通商になっています。

| 商品名 | 使用目的 |備考|
|:-----------------|:------------------|:------------------|
|サラサクリップ|本体|
|[Ａｒｄｕｉｎｏ　Ｎａｎｏ](http://akizukidenshi.com/catalog/g/gM-09059/)|制御用|
| [圧力センサーＦＳＲ４００](http://akizukidenshi.com/catalog/g/gP-04003/) |ボールペンの筆圧を測定するために使用|
| [ＢＭＸ０５５使用９軸センサーモジュール](http://akizukidenshi.com/catalog/g/gK-13010/) | 垂直軸からのボールペンの角度を測定するために使用 |JP6をはんだ付け|
| [半固定ボリューム　３３６２Ｐ　５０ｋΩ　［５０３］](http://akizukidenshi.com/catalog/g/gP-03281/)|圧力センサーの電圧調節用 |
|[基板取付用スピーカー　９ｍｍ　ＵＤＸ０１ＣＬＦＰＨ](http://akizukidenshi.com/catalog/g/gP-13324/)|音を出すために使用|
| [基板用押ボタンスイッチ（黒・モーメンタリ）　ＰＳ－７０Ｎ](http://akizukidenshi.com/catalog/g/gP-08781/)| ON/OFF用 |
|[電池ボックス　単５×１本　リード線](http://akizukidenshi.com/catalog/g/gP-12965/)|電源用|
|[単５サイズ形アルカリ電池　１２Ｖ　ゴールデンパワー製　Ａ２３](http://akizukidenshi.com/catalog/g/gB-14529/)|電源用|


###２.２. 構成
本体には、ゼブラ製のサラサクリップを用いています。外装が透明でボールペン内部を確認しやすく、芯の外径が大きくため圧力センサ全体を押しやすいことが選定理由です。PILOT製のJuiceでも良かったです。
以下の画像は、Arduinoやセンサ等を取り付けた状態のボールペンです。
![音が出るボールペン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/ec9d2c9c-4139-58be-1473-5fd23d070fcb.jpeg)!
![音が出るボールペン2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/21aa1dfa-59b7-7acf-219e-4d898aa0ad34.jpeg)

####２.２.１. 圧力センサ
買ったばかりの圧力センサは直径が大きくボールペン内部に入りませんでした。そのため、ボールペン外装の内径以下になるように、センサ外周を少し切っています。
ボールペン外装の一部をヤスリで削り、圧力センサが入るようにしています。芯を出した状態でも、圧力センサが外装に当たらないように、十分な長さで削っています。
カムの底をグルーガンで埋め、山を作ることで筆圧を加えたとき圧力センサ全体が押されるようにしています。
![圧力センサ1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/13b57a34-e68c-2816-eeb3-914cc345d8c6.jpeg)![圧力センサカット_ヤスリで削る.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/aeda911e-ef93-a163-7a8a-49709d6e7568.jpeg)
![カム.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/1d48d377-ddff-48b4-110d-6e1e21450322.jpeg)


####２.２.２．BMX055使用９軸センサーモジュール
クリップの上端に基板と一緒に、グルーガンで取り付けています。取付向きは下図のようになっています。
![ボードペン加速度センサ取付向き.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/cbcb0b40-e6ba-9d51-dc95-fc48a692219a.jpeg)

####２.２.３. スイッチとスピーカ
キャップ上部にスイッチとスピーカをグルーガンで取り付けています。スピーカ上端を押すことで、スイッチがONになると同時にペン先が出るようになっています。

####２.２.４．回路図
下図は回路図です。
![ボードペン回路図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/89c6bbac-86bb-8afa-ec6e-83b48a9d55eb.jpeg)


###２.３.プログラミング
####２.３.１.加速度センサを用いた角度測定
ＢＭＸ０５５を用いた加速度の測定は、[秋月電子通商のサイトにあるArduinoサンプルプログラム](http://akizukidenshi.com/catalog/g/gK-13010/)[1]を参考にしています。

以下は、取得した加速度から鉛直方向からのポールペン角度を計算するプログラムです。計算式は、[AN-1057加速度センサーによる傾きの検出](https://www.analog.com/media/jp/technical-documentation/application-notes/AN-1057_jp.pdf)[2]を参考にしています。
今回の場合、x, y, z軸方向の加速度をそれぞれXaccl, Yaccl, Zacclとすると、鉛直方向からの角度は以下の式で表せます。

```math
θ=\tan^{-1}(\frac{\sqrt{Zaccl^2 + Xaccl^2}}{-Yaccl})
```

```c++

void sensor() {
  //BMX055 加速度の読み取り
  BMX055_Accl();

  xAccl += offxAccl;
  yAccl += offyAccl;
  zAccl += offzAccl;

  theta = atan2(sqrt(zAccl * zAccl + xAccl * xAccl), -yAccl);

}
```
####２.３.２. 圧力の角度補正
下図のように、ボールペンが垂直であれば、鉛直方向の力がすべてボールペン軸方向に加わります。しかし、ボールペンを傾けると、軸方向に加わる力が小さくなってしまいます。加速度センサから計算した角度を用いて補正し、鉛直方向の力を求めるようにしました。
鉛直方向の力(筆圧)をF、 圧力センサ値:P、 鉛直方向からの角度をθとすると以下のような式で表せます。

```math
F= \frac{圧力センサ値P}{\cos θ}
```

![ボードペン筆圧と角度.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/9c4556fb-2333-6f47-481e-ac00ac74f6ab.jpeg)


####２.３.３. 圧力に応じて音を変化させる
人の感覚（知覚）は、刺激強度の対数に比例する[3]ので、圧力の条件を対数で設定しました。

音を出す条件を決めるために、圧力センサーの測定を行いました。
以下のグラフは横軸xが圧力センサの抵抗値x、縦軸がIn(x)です(今回の実験時、圧力センサの初期値は488)。圧力センサの抵抗値は、筆圧を上げると減少します。
![chart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606483/e4145c30-29fa-3f84-cbb0-1b1546aa0aef.png)




普段ボールペンを使用するとき、非常に高い筆圧で書くことは少ないので、普段自分が使用する筆圧の範囲で音階が変化するように、値や半固定抵抗を調整して筆圧の感度を設定することにしました。
In(x)=0付近の変化が大きいことが確認できます。この値付近で音階が変わる条件を複数付けてしまうと、瞬時に音階が変わってしまうため、任意の音を出し続けることが難しいです。なので、今回はIn(x)が2.8以上で音がなるようにしました。

```c++

//音の周波数
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SO 392
#define RA 440
#define SI 494
#define HDO 523

#define FSR A3 //圧力センサーのピン番号
#define BEATTIME 200 //音を出している時間(msec)
#define SPEAKER 12 //スピーカーの出力ピン番号

int freq[] = {HDO, SI, RA, SO, FA, MI, RE, DO};//音配列
float cpres[8];//圧力条件配列
float pres = 0;//圧力センサ値
float offpres = 0;//圧力センサ初期値

void sound() {
  pres = analogRead(FSR);// 圧力センサ値を取得
  pres = log(-(pres-offpres))-2.8;//logの変化率が大きい部分を除外するために-2.8
  pres = pres / (cos(fai));//角度補正
  if(pres<0){//0未満の値を0にする
    pres=0;
  }
 
  for (int i = 0; i < 8; i++) {//条件が厳し方から判定する
    if ( pres >= cpres[i]+0.2) {//ペン先に軽く力を加えた状態から音が鳴るように+0.2
      tone(SPEAKER, freq[i], BEATTIME) ; //音を鳴らす
      break;//音を鳴らしたらforループを抜ける
    }
    else {
      continue;
    }
  }
}
```


####２.３.４. プログラム全体
上記の角度を計算するプログラムと音を出すプログラムを含むプログラム全体を以下に示します。
<details><summary>プログラム全体を表示</summary><div>

```c++
#include<Wire.h>
#include<TimerOne.h>

// BMX055　加速度センサのI2Cアドレス
#define Addr_Accl 0x19 

// センサーの値を保存するグローバル関数
float xAccl = 0.00;
float yAccl = 0.00;
float zAccl = 0.00;

//角度
float theta  = 0.00;
float offxAccl = 0.20;
float offyAccl = -0.50;
float offzAccl = 0.40;


//音の周波数
#define DO 262
#define RE 294
#define MI 330
#define FA 349
#define SO 392
#define RA 440
#define SI 494
#define HDO 523

#define FSR A3 //圧力センサーのピン番号
#define BEATTIME 200 //音を出している時間(msec)
#define SPEAKER 12 //スピーカーの出力ピン番号

//int freq[] = {DO, RE, MI, FA, SO, RA, SI, HDO};//音配列
int freq[] = {HDO, SI, RA, SO, FA, MI, RE, DO};//音配列
float cpres[8];//圧力条件配列
float pres = 0;//圧力センサ値
float offpres = 0;//圧力センサ初期値

void setup() {
  // Wire(Arduino-I2C)の初期化
  Wire.begin();
  Serial.begin(115200);  // シリアル通信速度を設定

  //BMX055 初期化
  BMX055_Init();
  delay(1000);

  //圧力センサの初期値を取得
  for (int i = 0; i < 100; i++) {
    offpres += analogRead(FSR);
  }
  offpres = offpres / 100;

  //条件を設定
  for (int i = 0; i < 8; i++) {
    cpres[7 - i] = 1.8 / 8.0 * (i + 1);
    Serial.print(cpres[i]);
    Serial.print('\t');
  }
}

void loop() {
  sensor();
  sound();
  delay(100);
}


void sound() {
  pres = analogRead(FSR);// 圧力センサ値を取得
  pres = log(-(pres - offpres)) - 2.8; //logの変化率が大きい部分を除外するために-2.8
  pres = pres / (cos(theta));
  if (pres <= 0) {
    pres = 0;
  }

  for (int i = 0; i < 8; i++) {//条件が厳し方から判定する
    if ( pres >= cpres[i]+0.2) {//ペン先に軽く力を加えた状態から音が鳴るように+0.2
      tone(SPEAKER, freq[i], BEATTIME) ; //音を鳴らす
      break;//音を鳴らしたらforループを抜ける
    }
    else {
      continue;
    }
  }
}

void sensor() {

  //  //BMX055 加速度の読み取り
  BMX055_Accl();

  xAccl += offxAccl;
  yAccl += offyAccl;
  zAccl += offzAccl;

  theta = atan2(sqrt(zAccl * zAccl + xAccl * xAccl), -yAccl);

}



//=====================================================================================//
void BMX055_Init()
{
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(100);
}
//=====================================================================================//
void BMX055_Accl()
{
  int data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Accl);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Accl, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}

```

</div></details>

#３. Results 「筆圧で音がなるボールペン」の動作確認
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">筆圧で音が出るボールペン！ <a href="https://t.co/w3imkEoF9T">pic.twitter.com/w3imkEoF9T</a></p>&mdash; なつめにわ (@natsume_garden) <a href="https://twitter.com/natsume_garden/status/1244849973948112896?ref_src=twsrc%5Etfw">March 31, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

筆圧によって音が変わることを確認できました。
音階が「ド」「レ」「ミ」～と連続で変化するため、演奏するのは難易度が高いと感じました。任意の音を上手く出すためには、感度調整も必要ですし、練習も必要そうです。

#４. Conclusion まとめ
最後まで読んでいただきありがとうございました！
この記事では、「筆圧で音が鳴るボールペン」の作製について紹介しました。
筆圧の大きさよって音色が変化する機能をボールペンに持たせることができました。

もし改良するなら、演奏がしやすくしたいです！

##参考文献
[1]秋月電子通商のサイトにあるArduinoサンプルプログラム
http://akizukidenshi.com/catalog/g/gK-13010/
[2]AN-1057加速度センサーによる傾きの検出
https://www.analog.com/media/jp/technical-documentation/application-notes/AN-1057_jp.pdf
[3]竹村 和紘ら，“人間の主観的な力知覚モデルの提案とステアリング操作系への応用”，日本機械学会論文集(C編)78 巻 795 号 （2012-11）
https://www.jstage.jst.go.jp/article/kikaic/78/795/78_3705/_pdf