よく聞こえる静かなスピーカー
概要
年と共にテレビの音は聞こえるのに内容が聞きづらくなる。テレビの音を大きくすればよいが、周りに喧しく我慢することになる。内容が聞きづらい人に高域をあげたテレビ音声が耳元で聞こえるよう、超指向性スピーカーを正確に自動追尾で人に向ける事により実現した作品である。
仕組みは2Wayスピーカシステムで高域には方向が可変できる超指向性スピーカーを使う。リスナーからリモコンでスキャンコマンドを受けるとリスナーの角度計測しその角度に高域スピーカー向ける。リスナーの所だけ高域が強調された音が聞こえ、周りは静かで聞きやすいスピーカー。
システム構成
- 構成はテレビの音を2Wayスピーカーで鳴らしている。中低域スピーカーは固定で、高域スピーカーは指向性のあるパラメトリックスピーカーを使い、正面から左右にサーボで回転。
- 人のスキャンはリモコン受光部と赤外線アレーセンサを備え、リモコン信号を一定時間(5秒)受けるとセンサが人の位置を検出し、高域スピーカーがリスナーの正面を向くようサーボが回転する。
(リモコン信号はどのようなフォーマット キーの種類でも受け付ける。長押しのみ受け付けるので通常のリモコン操作では動かず通常の使い方に支障はなく、新しいリモコンが増えることもない)
動作
-
リモコン受光ブロックでリモコン信号を受ける。リモコン出力を信号より長い積分定数で積分(1K x10μ)する。これによりどのリモコンフォーマット・コードでもリモコン信号の有無を判別でき5秒以上長押しをカウント。また通常のリモコンの使われ方(5秒以内のキー操作)には反応しない。
-
リモコンを受け付けると、パラメトリックスピーカーに付けられている赤外線アレーセンサ(8x8block)で温度の高い(=人の体温)水平位置を検出。中心Block(=正面)からずれている左右の角度が0度になるようServoを回転させ、中心に来ると回転停止する。
-
高域パラメトリックスピーカーが人の正面を向くことにより、人の居るところだけに高域が強調され音が聞きやすく、周りは静かなスピーカーを実現する。
キーパーツ
部品 | 備考 |
---|---|
NANO | Arduino |
ParametoricSpeaker | 秋月実験KIT |
AMG8833 | 赤外線アレーセンサ スイッチサイエンス |
VS18388 | 赤外リモコン受光ブロック |
MG995 | Servoモーター : 大き目ですが回転が遅ければ小型SG90などでもよさそうです。 |
L7805 | 5V三端子レギュレーター Servo用5V(小型ServoでNANO5V出力が使えれば不要) |
SpeakerBOX | テレビの音を聞くスピーカーBOX |
TPA2006 | 超小型アンプ 秋月 |
100K~200KVR | 中低域SPの音量調整 高域SPとのバランスをとる |
動画
感想
テレビ音声の内容が聞きづらい人に、高域強調することで聞きやすくなる?
パラメトリックスピーカーでの実験では多くの人が肯定的であった。作品はリモコン(種類は問わず5秒押し)が送信されるとスピーカーが人を見つけ追尾する電子工作の楽しさがメインでありコストも高く、もっとシンプルにも作れそうです。(例えば 赤外線アレーセンサは高価@¥5000で、リモコン受光ブロック@¥100を扇に並べリモコン信号の来る方向を検知出来れば安価に出来そうです)
家族の中一人聞きづらく困っている人に、このような製品を提供される参考となれば幸いです。
課題
今回使った指向性パラメトリックスピーカーは40Khz超音波を使用しており,出力音圧 視聴時間によっては耳に違和感を生じることもあるというアドバイスをいただいております。本作品はホビー工作としてまだ検証は十分されておりません。実験される場合注意していただくようお願い致します。今後音圧等の検証を進め他の方式も含め、更に完成度の高い”よく聞こえる静かなスピーカー”を目指したいと思っており、またご教授いただけたら幸いです。
遠くでも障害物があってもよく聞こえるバーチャルスピーカー 参考:パラメトリックスピーカー応用作品
超音波の人体への影響について、いろいろ検証されレポートも出ています。規制する法律はなさそうですが、効果が確認出来る範囲で出力を抑えることが望ましいです。
強力超音波の安全な利用に向けて:人体への影響
超音波が人体に与える影響
パラメトリックスピーカー
よく聞こえる静かなスピーカー
#include <Wire.h>
#define PCTL 0x00
#define RST 0x01
#define FPSC 0x02
#define INTC 0x03
#define STAT 0x04 //SDA
#define SCLR 0x05 //SCL
#define AVE 0x07
#define INTHL 0x08
#define TTHL 0x0E
#define INT0 0x10
#define T01L 0x80
#define AMG88_ADDR 0x68 // IIC68h:default 69h:jumper
//temporaryData to buffer sumtotal IIC ADlow68h ADhigh69h
int TMPDAT1[65], TMPDAT2[65], TMPDAT3[65], TMPDATS[65];
int TMPVS[8]; //TemperatureVerticalsum=TMPVS[0]:RRRSum ~ TMPVS[7]:LLLLSum
int VSmaxH[3] = {4, 4, 4 }; //サーモセンサ正面 verticalSumax HorizontalAngle ([0]:Servo決定 [1]:Buffer [2]:New = 0:RRRR ~4:center~ 8:LLLL)
int TMPVSmax = 0 ;
int VSmin = 3000 ;
int maxmin ;
int SA = 45 ; // ServoAngle Left 0~40度 SP正面40~50度 Right 50度~90度
int RS = 1023 ; //RemoconSignal 0(信号有:送信機正面)~ 1023(信号無)
int RSTIM = 0 ; //RemoconSignal Timer 信号がある時間カウント
int SCANf = 0 ; //ScanFlg Scan終了まで1を維持
#include <Servo.h> // Servoライブラリの読み込み
Servo myservo; // Servoオブジェクトの宣言
const int SV_PIN = 7; // サーボモーターをデジタルピン7に
const int PIN_ANALOG_INPUT = 7 ; //リモコン入力をアナログピン7に
void setup()
{
Serial.begin(115200);
Wire.begin();
//IIC SCL:A5 SDA:A4 AMG8833(Jumperで変更可能)
int fpsc = B00000000;// 1fps
datasend(AMG88_ADDR, FPSC, &fpsc, 1);
int intc = 0x00; // diff interrpt mode, INT output reactive
datasend(AMG88_ADDR, INTC, &intc, 1);
// moving average output mode active
int tmp = 0x50;
datasend(AMG88_ADDR, 0x1F, &tmp, 1);
tmp = 0x45;
datasend(AMG88_ADDR, 0x1F, &tmp, 1);
tmp = 0x57;
datasend(AMG88_ADDR, 0x1F, &tmp, 1);
tmp = 0x20;
datasend(AMG88_ADDR, AVE, &tmp, 1);
tmp = 0x00;
datasend(AMG88_ADDR, 0x1F, &tmp, 1);
int sensorTemp[2];
dataread(AMG88_ADDR, TTHL, sensorTemp, 2);
myservo.attach(SV_PIN, 500, 2400); // サーボの割当(パルス幅500~2400msに指定)
myservo.write(45); //ServoCenter45度にセット
delay(5000); //5秒間で確認
pinMode(2, OUTPUT); //LEDをD2pinに(マイコン内部でLED制限抵抗が付けられるが、命令不明でつかわず)
digitalWrite(2,LOW);
pinMode(3, OUTPUT); //LED GNDをD3pin
digitalWrite(3,LOW);
} //setup END
void loop()
{
if (SCANf == 0) //ScanFlg=0の時リモコン受信無限LOOPにはいる ScanFlg=1の時は終了までリモコン受信無限LOOPに入らない
{remocon() ;}
// Wire library cannnot contain more than 32 bytes in bufffer
// 2byte per one data
// 2 byte * 16 data * 4 times = 2byte*64point
int sensorData[32];
for (int i = 0; i < 4; i++)
{
// read each 32 bytes
dataread(AMG88_ADDR, T01L + i * 0x20, sensorData, 32);
for (int l = 0 ; l < 16 ; l++)
{
int16_t temporaryData = (sensorData[l * 2 + 1] * 256 ) + sensorData[l * 2];
// Vertical_Line 温度ヒストグラム
//*** TMPDATSUM();*** 温度Datasum 3Frame合計で安定化 3面のDATAを更新
//Serial.print(temporaryData);Serial.print("TMPDAT3");Serial.println(TMPDAT3[i * 16 + l]);
TMPDAT3[i * 16 + l] = TMPDAT2[i * 16 + l]; //2面➦3面
TMPDAT2[i * 16 + l] = TMPDAT1[i * 16 + l]; //1面➦2面
TMPDAT1[i * 16 + l] = temporaryData ; // 新➦1面
TMPDATS[i * 16 + l] = TMPDAT1[i * 16 + l] + TMPDAT2[i * 16 + l] + TMPDAT3[i * 16 + l]; //3面SUM
Serial.print(" ");Serial.print(TMPDATS[i * 16 + l]);
} //for:I15 END
Serial.println("");
} //for:i3 END = 1frame X 3面 complete
//*** 温度histgram ∑VerticalLineSum (horizontal angle(0 ~ 7) 毎に縦8ブロック合計を計算)
TMPVS[0] = 0; TMPVS[1] = 0; TMPVS[2] = 0; TMPVS[3] = 0; TMPVS[4] = 0; TMPVS[5] = 0; TMPVS[6] = 0; TMPVS[7] = 0;
for (int m = 0 ; m < 8 ; m++) //m= 0 ~ 7:水平位置毎に縦一列温度(足~頭)合計する
{
TMPVS[0] = TMPVS[0]+TMPDATS[0 + m * 8]; //LLLL:sensor左方向温度
TMPVS[1] = TMPVS[1]+TMPDATS[1 + m * 8]; //LLL
TMPVS[2] = TMPVS[2]+TMPDATS[2 + m * 8]; //LL
TMPVS[3] = TMPVS[3]+TMPDATS[3 + m * 8]; //L
TMPVS[4] = TMPVS[4]+TMPDATS[4 + m * 8]; //:sensor正面方向温度:SPのangleをServoでここがMAXになるように向けてゆく
TMPVS[5] = TMPVS[5]+TMPDATS[5 + m * 8]; //R
TMPVS[6] = TMPVS[6]+TMPDATS[6 + m * 8]; //RR
TMPVS[7] = TMPVS[7]+TMPDATS[7 + m * 8]; //RRR:sensor右方向温度
}
TMPVS[VSmaxH[0]] = TMPVS[VSmaxH[0]] + 20 ; //ヒストグラムでVSmaxH[0]の温度合計max水平位置のTMPVSに+50のヒステリシスを付ける:チャッタ―防止
Serial.println("");
Serial.println(" 温度vs水平角度 サーモヒストグラム");
Serial.println("LLLL 正面 RRR");
Serial.print(TMPVS[0]);Serial.print(" ");Serial.print(TMPVS[1]);Serial.print(" ");Serial.print(TMPVS[2]); Serial.print(" ");Serial.print(TMPVS[3]);Serial.print(" ");
Serial.print(TMPVS[4]);Serial.print(" ");Serial.print(TMPVS[5]);Serial.print(" ");Serial.print(TMPVS[6]); Serial.print(" ");Serial.println(TMPVS[7]);
//********1frame Data complete*********
objectscan() ; //sensor水平範囲(0:左端~7:右端)でVSmax(=人)をさがす 2frame一致H[1]=H[2])(VSmin:maxと差がない時は人がいない)
Serial.print(TMPVSmax);Serial.print("VSmaxH[0]");Serial.print(VSmaxH[0]);Serial.print(" [1]");Serial.print(VSmaxH[1]);Serial.print(" [2]");Serial.print(VSmaxH[2]);
Serial.print(" VSmin");Serial.print(VSmin);Serial.print("差");Serial.print(maxmin);Serial.print(" SA");Serial.println(SA);
//********TMPVSmax が(sensor水平範囲=3:sensor正面)に来るようにservoを回転
servo() ;
delay(20); //delay(50); //delay(100)でチャッタ―(scan発振)防止
} //loop END
//****************************************************************************************************
//*****************************************************************************************************
void remocon() //Remocon信号 (正面 左右) 有無 判別
{
RS = analogRead(PIN_ANALOG_INPUT);
if(RS > 900) RSTIM = 0 ; // リモコン信号停止(停止実測:analonRead()=982)するとTimerReset(RSTIM=0秒)しリモコン受信無限ループ開始
while(RSTIM < 5) // リモコン信号有が5秒以下は無限ループでリモコンを受け継ずける
{
while(RS > 900) //リモコン信号がない(RS>900~1034:実測982)時は無限ループでリモコンを受け続ける
{
RS = analogRead(PIN_ANALOG_INPUT);
Serial.print(RS);Serial.print(" ");Serial.println(RSTIM);
}
RSTIM++ ; //リモコン信号あり 時間カウント(5秒以上あると受信無限ループを抜ける)
delay(1000);
}
} //リモコン信号5秒以上ありで無限ループから抜ける
void datasend(int id, int reg, int *data, int datasize)
{
Wire.beginTransmission(id);
Wire.write(reg);
for (int i = 0; i < datasize; i++)
{
Wire.write(data[i]);
}
Wire.endTransmission();
}
void dataread(int id, int reg, int *data, int datasize)
{
Wire.beginTransmission(id);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(id, datasize, false);
for (int i = 0; i < datasize; i++)
{
data[i] = Wire.read();
}
Wire.endTransmission(true);
}
void objectscan() //Thermosensor縦一列合計のMax Minの値と水平位置(0~7)を特定
{
TMPVSmax = 0; VSmaxH[2]=0; VSmin = 3000; //仮初期値設定
for ( int h = 0; h < 8; h++ )
{
if (TMPVSmax < TMPVS[h]) {TMPVSmax = TMPVS[h] ; VSmaxH[2] = h ;} //VSmax[2]:NewmaxData
if (VSmin > TMPVS[h]) {VSmin = TMPVS[h] ;} //VSmin:NewminData
}
maxmin = TMPVSmax - VSmin ;
if (VSmaxH[1] == VSmaxH[2]) VSmaxH[0] = VSmaxH[1]; //2frame 一致 VSmax[0]に代入
else VSmaxH[1] = VSmaxH[2]; //2frame 一致せず Buffer(=VSmaxH[1])⇐NewData(VSmax[2]) VSmax[0]変更なし
}
void servo() //Therosensor縦一列合計MaxのSensor水平位置が人の正面(VSmaxH[0]=4)になるようServo angleを向けてゆく
{
SCANf = 1 ; digitalWrite(2,HIGH); //Scan中は1を立てScan終了までリモコン無限LOOPに入らない LED点灯
if ((maxmin > 30) && (TMPVSmax > 1600)) //ひとがいるmaxmin差>50 and TMPVS>2700 (実測 朝人/3m:1800 1m:昼2700 日差し窓/3m:2000 2300 )
{ if (VSmaxH[0] < 4) SA = SA-1 ; //Servo: 人はsensor左 to 人の正面(VSmaxH[0]=4 SA=人のangleまで)へServoで左に向けてゆく
else if (VSmaxH[0] >4) SA = SA+1 ; //Servo: 人はsensor右 to 人の正面(VSmaxH[0]=4 SA=人のangleまで)へServoで右に向けてゆく
else {SCANf = 0 ; digitalWrite(2,LOW); } //if (VSmaxH[0] == 4 ) //人はsensor正面 SA=人のangle で停止 Scan終了 ScanLED OFF
}
if (SA <= 0) SA = 0 ; // Servo 0度以下limit :(SP正面-45度左)
if (SA >= 90) SA = 90 ; // Servo 90度以上limit:(SP正面+45度右)
myservo.write(SA) ; //Servo動かす
}
//END
投稿者の人気記事
-
syouwa-taro
さんが
2023/11/06
に
編集
をしました。
(メッセージ: 初版)
-
syouwa-taro
さんが
2023/11/07
に
編集
をしました。
(メッセージ: Wikipedia情報追加)
-
syouwa-taro
さんが
2023/11/07
に
編集
をしました。
-
syouwa-taro
さんが
2023/11/07
に
編集
をしました。
-
syouwa-taro
さんが
2023/11/07
に
編集
をしました。
-
syouwa-taro
さんが
2023/11/07
に
編集
をしました。
(メッセージ: 動作説明追加)
-
syouwa-taro
さんが
2023/11/09
に
編集
をしました。
-
syouwa-taro
さんが
2023/11/09
に
編集
をしました。
(メッセージ: youtube更新 PP説明追加)
Opening
mipsparc
2023/11/09
syouwa-taro
2023/11/12 Opening
syouwa-taro
2023/11/12 -
syouwa-taro
さんが
2023/11/13
に
編集
をしました。
(メッセージ: 読みやすく)
-
syouwa-taro
さんが
2023/11/15
に
編集
をしました。
-
syouwa-taro
さんが
2023/11/18
に
編集
をしました。
(メッセージ: youtubeに音声例追加)
-
syouwa-taro
さんが
2023/11/24
に
編集
をしました。
(メッセージ: youtube 画質/音質 改善)
-
syouwa-taro
さんが
2023/11/26
に
編集
をしました。
(メッセージ: 課題追加)
-
syouwa-taro
さんが
2023/11/28
に
編集
をしました。
(メッセージ: youtube更新)
-
syouwa-taro
さんが
2023/11/30
に
編集
をしました。
(メッセージ: 概要補足)
-
syouwa-taro
さんが
2023/11/30
に
編集
をしました。
ログインしてコメントを投稿するよく年配の方がテレビを見るときに「うるさい!」ってなるの、あるあるですよね。
耳元スピーカといった手段もありますが、これはとても面白いです!
2つのスピーカで音質も配慮している点も良いと思いました。
ご感想ありがとうございます。親戚の家では、おばーちゃんの耳が遠くテレビの音がきこえずらく家族は
テレビが一緒に楽しめないのが、気になっていました。しょうがないと諦めていますがこのようなおうちの助けになる作品が出来ればと思っています。
パラメトリックスピーカーは低域が出ないという課題がありますが、低域スピーカーを加えた2WAYにする事により音質を改善することが出来ました。