日記とか、工作記録とか

自分に書けることを何でも書いてゆきます。作った物、買ったもの、コンピュータ系の話題が多くなるかもしれません。

M5Stack + ローテーションサーボ FS90R

ローテーションサーボという部品があるのを知ったので秋月電子で買ってみました。

f:id:WindVoice:20190922090422j:plain

普通のサーボモーターは可動範囲が+90度~-90度などと決まっていますが、ローテーションサーボは可動域に制限がなくどちらの方向にも回り続けることができます。ただ、通常のサーボモーターと違って角度を決めて止まることはできません。動作としては、回る方向(時計回り、反時計回り)と速度をPWMを使って制御することになります。

データシートをみると、700~2300マイクロ秒のPulse Widthで操作するとあります。700~1500マイクロ秒で時計回り(1500で停止)、1500~2300マイクロ秒で反時計回りとなります。

ただこれ、何秒のうち、この時間をONにするのか、周期が良くわからないですね…… この資料はどう読んだらいいのでしょうか。0~700マイクロ秒を制御に使っていないことを考えると2300~3000も使っていない、つまり1周期は3000マイクロ秒と考えるのが自然ですか?

良くわからないので、実物で実験しました。プロの人からは怒られるかもしれませんがしかたないので。

ソースコード

Arduino IDEで書いています。結論から言うと、16bit、50Hzでポートを設定してある場合に、3000 - 5000、5000 - 7000のあたりで制御ができました。また、5000付近で停止ができるはずですが、実際にはどちらかにゆっくり回ってしまう状態になりがち(止まることもある)ので、停止をしたい場合にはledcWrite()で0を指定するほうが確実でした。

#include <M5Stack.h>

int FS90R = 16;

//PWMの設定
const double PWM_Hz = 50;     //PWM周波数
const uint8_t PWM_level = 16; //PWM 16bit(0~65535)

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Lcd.setBrightness(100);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE, BLACK);

  pinMode(FS90R, OUTPUT);
  
  //モータのPWMのチャンネル、周波数の設定
  ledcSetup((uint8_t)1, PWM_Hz, PWM_level);

  //モータのピンとチャンネルの設定
  ledcAttachPin(FS90R, 1);
}
void loop() {
  //[FS90R]
  //1500~ 700usecで時計回り(CW)
  //1500~2300usecで反時計回り(CCW)
  //1500(±45)usecのときに停止。とはいうものの良くわからない(Hz?)ので実機で確認。

  // 4950 - 5050 を停止として、3000 - 7000 で稼働するようだ。
  // 3000 - 5000 で 時計回り、
  // 5000 - 7000 で反時計回り。
  
  for (int i = 3000; i <= 7000; i+=50) {
    ledcWrite(1, i);
    delay(100);
    M5.Lcd.setCursor(0, 20);
    M5.Lcd.printf("PWM: %4d\n", i);
  }
  for (int i = 7000; i >= 3000; i-=50) {
    ledcWrite(1, i);
    delay(100);
    M5.Lcd.setCursor(0, 20);
    M5.Lcd.printf("PWM: %4d\n", i);
  }
}

動作の様子

いつものとおり、動画で動作の様子を残しておきます。ローテーションサーボがひとつだけであれば、外部電源はいりません。USBケーブルを外した状態でも動作可能でした。しかし、あまりスピードは出ませんね。4個つけて車にする計画でしたが、のんびり走るものになりそうです。

youtu.be

サーボモーター SG90を分解

サーボモーターSG90で以前作ったものから外そうとしたら外殻が壊れてしまったので分解してみようと思い立ちました。それだけですけど、分解って楽しいですよね。元に戻せないとしても!

f:id:WindVoice:20190919204123j:plain

底面の四角に小さいネジ頭があります。抜いてみたら長かった…… これを抜くと頭のカバーが外れます。

f:id:WindVoice:20190919204343j:plain

シールをはがすと底の部分が取れます。小さなモーターと基板が現れます。小さいモーター魅力的ですね。これだけ買えないかなぁ。

f:id:WindVoice:20190919204443j:plain

小さな基板にはICが両面実装されています。型番は読めない…… というか削り取られている?

f:id:WindVoice:20190919204631j:plain

基板の奥にはなにやら円盤状の部品が。私には役目がわからず。

f:id:WindVoice:20190919204641j:plain

小さい歯車も魅力的です。モーターの回転よりはだいぶ遅めに回るように見えます。

デジタル・マイクロサーボ SG90 (5個)

デジタル・マイクロサーボ SG90 (5個)

M5Stack + 光センサー(型番不明の硫化カドミウムセル)

なんだかちょっと調子に乗ってきたので、部屋に転がっていた、壊れた保安灯から取った硫化カドミウムセルの光センサー(たぶん)を使って明るさ検知をしてみました。身の回りのもので何ができるのか、ここしばらくそんなことばかり考えています。

光センサー

こんなやつです。見たことがある人も多いのではないでしょうか。表面のうねうねした回路に光が当たると、電気抵抗が変わるので電流を測れば明るさが間接的にわかるというものです。明るくなると抵抗が下がり、暗くなると上がります。安いのを買ったのですがすぐ壊れてしまい保安灯としては使えなくなりました。センサー部分を根元からニッパーで切って再利用する、というよくあるパターンですね。限りある資源を大切に。ちなみに購入しても100円未満のようです。

f:id:WindVoice:20190915202025j:plain

型番不明なのですが、調べてみたところ、硫化カドミウムを使ったものではないかと思われます。可視光線によく反応するので、人間が感じる明暗の感覚と近いデータがとれます。

回路

こんな感じで接続しました。5VとGNDの間を際限なく電流が流れないよう、抵抗を追加します。1kΩのものを使っています。その間に分岐を作って、アナログポート(ADC:Analog Digital Converter)につないで、入力モードで使います。M BUSの表をみると、ADCは35と36ですね。今回は35につないでいます。

f:id:WindVoice:20190915214138j:plain

できあがると、こんな感じになります。

f:id:WindVoice:20190915215009j:plain

コーディング

今回もArduino IDEで書いています。MicroPythonはまだ仕様が落ち着いていないのか、どうもドキュメント通りに動かないのが気になります。Cで書いたほうが仕様が調べやすいですね。アナログポートを読むときはanalogRead()を使います。整数値が取れるのであとは良いように使います。明るいほど大きい値が取れますが、実験したところ4095が上限のようです。

#include <M5Stack.h>

// 光センサーのポート番号。アナログ入力ポートは35が使えるそうだ
int CdS_Port = 35;

void setup() {
  Serial.begin(115200);
  m5.begin();

  // 雑音は決しておく。なんとなく。
  m5.Speaker.write(0);

  // 明るさ入力ポートの準備
  pinMode(CdS_Port, INPUT);
}

void loop() {

  int d;

  // 採取される値は 0 - 4095 のようだ
  d = analogRead(CdS_Port);

  // シリアルポートに報告
  Serial.printf("Read data -> %4d\n", d);

  // LCDに表示
  m5.Lcd.fillScreen(BLACK);
  m5.Lcd.setTextColor(WHITE);
  m5.Lcd.setTextSize(5);
  m5.Lcd.setCursor(0,0);
  m5.Lcd.printf("Read data -> %4d\n", d);
  
  delay(300);
}

結果

動作は簡単。結果も簡単。いくつかセンサーやモーターを使ってきたけれど、組み合わせで何をするかが問題ですね。基礎力は大事だけれど応用はどうしたらいいのやら……

youtu.be

M5Stack + サーボモーターSG90

以前Raspberry piで遊んだ時のサーボモーターSG90を、M5Stackとつないで動かしてみました。
実は全然うまくいかなくて試行錯誤して場当たり的に解決したのですが、まぁそのへんはそれはそれということで……

SG90

サーボモーターというのは、モーターの一種で、回転しつつ角度を決めて止まることができるものです。首振りのような動作ができるので、車のハンドル、ロボットの関節などで使われます。PWM(pulse width modulation)という信号を使って回転する位置を指定します。データシートにはこんな感じで書いてあります。

f:id:WindVoice:20190914233011p:plain

つまり、20ミリ秒(1/50秒)のうち、0.5ミリ秒だけGPIOをHIGHにしておけばサーボモーターは-90度の位置に固定され、2.4ミリ秒だけHIGHにすれば90度の位置に固定されるので、その間でほしい角度の分だけHIGHになるように調整してね、という意味です(たぶんあっているはず)。SG90はおよそ180度の範囲で動かすことができます。

Raspberry piのときにはモータードライバーのICを使って間接的に操作したのですが、M5Stackの場合はGPIOのポートを直接使えます。

今回はArduino IDEを使ってみていますので、ledcWrite()という関数を使えばいいようです。

配線

今回は、信号線としてGPIO 16を使ってみました。このへん、いろいろ試したのですが結局どれが適切なのかわからず…… あちこちつないでみたのですが画面が乱れたり音がなったりわけがわかりません。あとの2つはGroundと5Vにつなぎます。

f:id:WindVoice:20190914233758j:plain

USBケーブルでPCと接続しながら開発していたのですが、サーボを動かしているうちにM5Stackが再起動してしまい、GPIOの使い方が良くないのかとずいぶん悩んでしまいました。しかしよく考えるとUSBケーブルの電源品質が悪かったようで、電源不安定のため再起動していたようです。Arduino IDEからツール⇒シリアルポートモニタを開いてみていると、Brownoutエラーが記録されていました。

コーディング

試行錯誤の結果、SG90の仕様に合わせてPWMを50Hzに指定、指定する値の範囲は16bit(0~65535)にしてみました。この場合-90度(0.5ms)は1638、90度(2.4ms)は7864…… に相当する計算だと思うのですが、実機で試行錯誤したところ2300~9000くらいで首振りすることになりました。どうしてデータシートの計算と合わないのかなぁ。不思議ですが、こんなものですかね?

#include <M5Stack.h>

int led1 = 16;

//PWMの設定
const double PWM_Hz = 50;   //PWM周波数
const uint8_t PWM_level = 16; //PWM 16bit(0~65535)

void setup() {
  Serial.begin(115200);
  m5.begin();
  pinMode(led1, OUTPUT);
  //モータのPWMのチャンネル、周波数の設定
  ledcSetup((uint8_t)1, PWM_Hz, PWM_level);

  //モータのピンとチャンネルの設定
  ledcAttachPin(led1, 1);
}
void loop() {
  for (int i = 2300; i <= 9000; i=i+100) {
    ledcWrite(1, i);
    delay(30);
    Serial.printf("%d\n", i);
  }
  for (int i = 9000; i > 2300; i=i-100) {
    ledcWrite(1, i);
    delay(30);
    Serial.printf("%d\n", i);
  }
}

結果

動作結果は下のようになりました。片道2秒で右、左に首を振ります。それ以外には特段面白いこともなく申し訳ない。首振りで面白くなる何かを考えなければ。


M5stack + サーボモーターSG90

追記

MicroPythonでも書いてみたのですが、MicroPythonのPWMは1%刻みでしかコントロールできない様子。PWMのマニュアルではdutyは0~1023の間で指定することになっているのだけれど、100以上の値を指定するとエラーになるみたい。実際のdutyの定義域は0~100なのかな。サーボがカクついてしまいます。

from m5stack import *
import machine
import utime

p16   = machine.Pin(16)
pwm16 = machine.PWM(p16)

pwm16.freq(50)

while True:
    # dutyは 0.5/20=0.025 ~ 2.4/20=0.12 なので、これを100倍して3~12の間。
    # ……なのだけど、実機では4から15くらいで動作している。
    # MicroPythonではPWMは0~100%の1%刻みでしか操作できない?
    # サーボモーターの動作としては結構ぎくしゃくしてしまう感じだ。
    for d in range(4, 15):
        pwm16.duty(d) # dutyは0から100の間
        utime.sleep(1)
    for d in reversed(range(4, 15)):
        pwm16.duty(d) # dutyは0から100の間
        utime.sleep(1)

デジタル・マイクロサーボ SG90 (5個)

デジタル・マイクロサーボ SG90 (5個)

M5Stack + 距離センサーHC-SR04

さて、M5Stackで何か遊ぼうということで、以前買ったまま放置していた距離センサーHC-SR04を取り出してきました。

f:id:WindVoice:20190908175557j:plain
秋月電子の資料のHC-SR04

正面に40kHzの超音波を発射、その跳ね返ってくるまでの時間を計測できるセンサーです。計測できるのは時間なので、距離は自分で計算することになります。私の部屋は25℃くらいなので、音速は346m/秒くらいです。2cm~400cmまで、分解能0.3cmって書いてあるけれど、これは利用する基板側も精一杯センサーの能力を引き出す努力をしないといけないのではないかと思います。

さて、ではさっそくこれを使った工作をしていきましょう。

配線

配線は、秋月電子で手に入るデータシートに従ってつないでいきます。

写真の上からVcc(5Vの電源)、Trig(距離を測りたいときにHighを10マイクロ秒送ってセンサーに合図する)、Echo(跳ね返ってきた音波が届くまでの時間がここから取れる)、GND(0Vにつなぐ)となっています。ここではM5Stack付属のケーブルで、順に紫、灰、白、黒をつなぎました。

f:id:WindVoice:20190907231031j:plain

M5Stack側はこんなです。青で2、5と書いてあるところがGPIOの2番、5番ポートです。5VがVcc、GはGNDのことですね。配線は刺すだけなので簡単です。

f:id:WindVoice:20190908120756j:plain

プログラミング

プログラミング環境は、Visual Studio Code + MicroPythonです。環境の作り方は前回書いたので省略します。アプリの内容についてはいくつかのサイトを参考にさせてもらっていますが、距離を測って数字で表示するだけ、ではなくもう少し面白くしたかったので、距離が緑色のバーの伸び縮みで表現されるようにしたり、音による感覚的なフィードバック(よくゲームなどでみられるようなアレですね)も実装しました。それぞれ何をしているのかはソースコード内のコメントにしてあります。

ところで、M5StackでMicroPythonを使う時のm5stackモジュールの詳細が書かれたドキュメントはないのでしょうか? 今回はインターネット上にばらばらの情報を寄せ集めて書いたのですが、開発元発信の情報がどこかにあるんじゃないかと思っているのですが…… ご存じの方は教えてください。

下の内容を、M5StackのappsフォルダにHC-SR04.pyのような名前で保存しておけば、M5Stack再起動時にBボタンを押したときのAPP.Listに表示されるようになります。アプリが起動後はCボタンで音のON / OFFが切り替えられます。

from m5stack import lcd
from m5stack import *
import sys

import utime, time
import machine
from uiflow import *

############################################################
# GPIO pin設定(距離センサーHC-SR04を使用する準備)
############################################################

# GPIO2を入力ポートとしてECHOにセット
ECHO = machine.Pin(2, machine.Pin.IN)

# GPIO5を出力ポートとしてTRIGGERと接続してLowにセット
TRIGGER = machine.Pin(5, machine.Pin.OUT)
TRIGGER.value(0)

############################################################
# 画面の準備
############################################################

lcd.clear()

left  =  10
top   =  50
right = 310
height = 30

# ゲージを描画
onemeter = int((right - left)/4)
lcd.line(left               , top + height + 1,    right, top + height + 1, lcd.WHITE)
lcd.rect(left               , top + height + 1, onemeter, 6, lcd.WHITE, lcd.WHITE)
lcd.rect(left + 1 * onemeter, top + height + 1, onemeter, 6, lcd.WHITE, lcd.BLACK)
lcd.rect(left + 2 * onemeter, top + height + 1, onemeter, 6, lcd.WHITE, lcd.WHITE)
lcd.rect(left + 3 * onemeter, top + height + 1, onemeter, 6, lcd.WHITE, lcd.BLACK)
lcd.print('0cm',  10, 90, lcd.WHITE)
lcd.print('400', 285, 90, lcd.WHITE)

# ミュートはCボタン
lcd.print('[C:SPEAKER ON/OFF]', 170, 220, 0xFFFFFF)

############################################################
# 音を出す準備
############################################################

vol = 0
speaker.setVolume(vol)
interval = 10
interval_count = interval

############################################################
# Main Loop
############################################################

while True:

    ########################################################
    # 距離を測る
    ########################################################
    # Triggerポートを最低10マイクロ秒Highにすると、計測が始まる。
    TRIGGER.value(0) # Low
    time.sleep_us(5)
    TRIGGER.value(1) # High
    time.sleep_us(10)
    TRIGGER.value(0) # Low

    # ECHOピンがHighになっている時間が返る。単位はマイクロ秒。
    pulse_time_us = machine.time_pulse_us(ECHO, 1, 30000)

    # 計算で前方の障害物までの距離を求める。
    # HC-SR04は2cm~400cmの距離を測ることができる。
    # 音速は25℃で346m/sec.
    # 1マイクロ秒で0.34600mm進むことを逆に考えると、
    # 1mm : x usec = 0.34600 : 1 usec
    # 0.346 x = 1
    # x = 2.890
    # 音波は障害物との間を往復するから、実際にはこの倍で578で割ればよい。
    mm = pulse_time_us * 100 // 578

    ########################################################
    # 計算結果を表示
    ########################################################

    # [1] 普通に文字で表示
    lcd.setCursor(30, 30)
    lcd.print('Distance:  {:>4.0f} mm\n'.format(mm))

    # [2] 四角いバーで表示
    # 0 - 4000mmを 10px - 310px で表現するとして、13.3で割れば良い
    rectwidth = int(mm / 13.3)
    # 緑の部分を描画
    lcd.rect(left                , top,       rectwidth, height, lcd.GREEN, lcd.GREEN)
    # 黒の部分を描画
    lcd.rect(left + rectwidth + 1, top, 320 - rectwidth, height, lcd.BLACK, lcd.BLACK)

    # [3] 音でお知らせ(400Hz - 700Hz、近いほうが高い音、遠いときは頻度を減らす)
    freq = 1400 - rectwidth * 2
    interval = int(mm / 400) + 3
    interval_count = interval_count + 1
    if interval_count > interval:
        speaker.tone(freq, 3)
        interval_count = 0

    #Cボタンで音のON / OFF
    if btnC.wasPressed():
        vol = 1 - vol
        speaker.setVolume(vol)

    utime.sleep(0.1)

試してみた感想など

動作はこんな感じです。Cボタンを押してスピーカーをONにしたあと、センサーに物を近づけたり遠ざけたりすると音が変わります。ちょっと見づらいですが、画面表示も変わっています。距離が正確に測れているかというと…… 正直なところだいたいですね。センサーの正面から15度の範囲で計測できることになっていますが、これを持っていたら見えない場所でも歩けるかというと、正直厳しいと思います。センサーの問題ではなくて実装側の課題だとは思うのですが、改善の方法があるのかはよくわかりません。

動画では音がでるように感じられるかもしれませんが、これはアプリがM5Stackのスピーカーを鳴らしているのであって、センサーの発する音は耳には聞こえません。

www.youtube.com

HC-SR04 超音波距離センサーモジュール For Arduino

HC-SR04 超音波距離センサーモジュール For Arduino

Visual Studio CodeでM5stack開発

最近は、M5stackで遊んだりなどしています。

f:id:WindVoice:20190827195949j:plain
M5stack Gray

M5Stackは、Raspberry piよりさらに小さい本体に機能が詰まった小さなコンピュータです。
ラズパイのようにOSはありませんが、その分反応速度は速いです。

しかも小さなモニター、3つのボタン、スピーカー、WiFiBluetoothまでついていてPCとUSB接続もできます。小さなバッテリーもついているので、少しの間はケーブルレスで動作することもできます。

ラズパイのようにI/Oポートが付いているので、自分でハードウェアを拡張することもできますし、モジュールを購入して積み重ねる(Stack)することで手軽な機能拡張も可能です。

Visual Studio CodeでM5stack開発

世の中には、M5stackでいろいろ遊んでいる人がすでにたくさんいます。いまさら私が書くことはなさそうだ、とも思ったのですが、Visual Studio Codeからの開発の人はまだ少ない様子です。準備も難しくないのでメモをまとめておきます。Visual Studio CodeからUSB経由接続で開発する方法は、これまでの開発とまったく違和感がなくおすすめです。

M5Burnerを使ってUIFlow-v1.3.2をM5Stackに書き込む

こちらは、通常のUIFlowの導入手順と変わりません。ほかの方の詳しい説明にお任せすることにします。PCへのデバイスドライバ(CP210x)のインストールと、M5Burnerを使ってファームウェアを書き込むところまで実行します。

raspberrypi.mongonta.com

M5StackをUSBモードにする

M5stackを再起動後、素早くCボタンを押してSETUPモードに入り、Switch to USB modeを選択します。これによって、PCとUSBケーブルで接続してPCから操作ができるようになります。再起動後はWindowsのデバイスマネージャで、COMポートに接続されているように認識されます。私の環境では、COM3ですね。

f:id:WindVoice:20190907140336j:plain

Visual Studio CodeにExtensionをインストール

Extensionsから、vscode-m5stack-mpyを検索してインストールします。

Visual Studio CodeにM5stackを認識させる

VSCodeの下にAdd M5stackという表示がでますので、これをクリックします。

f:id:WindVoice:20190907140741j:plain

するとウインドウの上にどのCOMポートと接続するか選択する表示がでますので、M5stackを選択します。私の場合はCOM3です。

f:id:WindVoice:20190907140823j:plain

開発を始める

ここまでくると、VSCodeExplorerでM5STACK DEVICEという表示がでるようになります。リロードアイコンを押すとM5stackの内容が参照できます。コードを新しいファイルで書き始めるときは+ボタン、何かリソースをM5Stackに転送するときはアップロードボタンです。ここでappsフォルダに *.pyファイルを置いておけば、起動時にアプリケーションとして選択できます。

f:id:WindVoice:20190907140948j:plain

できたコードを実行するときは、コードを右クリックしてRun In M5Stackです。

f:id:WindVoice:20190907141314j:plain

ここまでくれば簡単。いつもの感覚でお手軽開発ができて便利です。これで作業も進むかなぁ。

Raspberry pi 3 Model B + Juliusで音声認識

まえおき

ここしばらく、AIアシスタント付きのPCやスマホやスピーカーが汎用品になってきました。

私もiPhoneのSiri、Surface ProのCortana、Echo dotのAlexaとちょっとずつ使ってみましたけど、まだ少しだけ新しいことができるという段階で、生活が便利になるにはまだまだ発展が必要かなぁという感じですね。ただ、短期間のうちにSiriもAlexaもできることが増えたり、こちらの言うことに正しく応答してくれるようになってきました。この先が楽しみです。

で、私もマネをしてみたくなったので、多くの人の後追いではありますが、音声認識を試してみることにしました。おいおいやっていこうとしている遠大な(?)計画はあるのですが、ひとまずは私の言うことを聞き取るところを目指そうと思います。今回は日本語音声を認識して文字にしてくれるJuliusを試してみます。

準備

いつものRaspberry pi 3 Model Bです。今回、32GBのmicroSDカードを使ってRaspbian OSのインストールからやり直しました。

f:id:WindVoice:20180826151438j:plain

ところで、スイッチサイエンスさんの調査で、pi 3は快適に使うには5V / 2.5Aの電源が必要とあるのですが、これがなかなか難しい条件で、私の部屋で見つけられるのは、iPad用の充電アダプタ(2.4A)が精一杯でした。確かに、言われてみれば/var/log/messagesにVoltage関係のメッセージがでていたりするので、これまでは電源不足から制限がかかった状態で動いていたのだなぁと認識した次第です。iPadの電源でもまだVoltageエラーは出るのですが、以前よりは明らかに快速になったので、しばらくはこれで行こうかと思います。

Juliusのダウンロード

Juliusは、最新版がGitHubに置かれていますので、git cloneで取り寄せます。現在の最新版は4.4.1です。

pi@raspberrypi:~ $ git clone https://github.com/julius-speech/julius.git
Cloning into 'julius'...
remote: Counting objects: 2341, done.
remote: Total 2341 (delta 0), reused 0 (delta 0), pack-reused 2341
Receiving objects: 100% (2341/2341), 8.54 MiB | 3.21 MiB/s, done.
Resolving deltas: 100% (1059/1059), done.
pi@raspberrypi:~ $

実はうまく動作させることができず、いろいろ試行錯誤したのですが、結果的には下の要領でconfigureしてうまくいきました。--with-mictypeというのは、音声入力をOSのどの仕組みから受け取るか、を選択するものですが、最初OSS(Open Sound System)経由で受け取ることになっていました。しかし、これは動作させることはできませんでした。

ALSA(Advanced Linux Sound Architecture)のほうが新しい仕組みなのだそうで、こちらを使うと動作しました。パッケージも試行錯誤の途中でいろいろ入れたので、どれが重要だったかわからないのですが、ALSA関係は必要だったはずです。

他に使えるconfigureオプションは、ここにリストがあります。

pi@raspberrypi:~ $ sudo apt-get install alsa-utils
pi@raspberrypi:~ $ sudo apt-get install sox
pi@raspberrypi:~ $ sudo apt-get install libsox-fmt-all
pi@raspberrypi:~ $ sudo apt-get install osspd-alsa
pi@raspberrypi:~ $ sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev
:
:
:
pi@raspberrypi:~/julius $ ./configure --enable-words-int --with-mictype=alsa

ちょっとでも速く動いてほしいので、コンパイル最適化オプションをつけてmakeします。

pi@raspberrypi:~/julius $ export CFLAGS="-O3"
pi@raspberrypi:~/julius $ make
pi@raspberrypi:~/julius $ make install
:
:
:
pi@raspberrypi:~/julius $ julius -setting
JuliusLib rev.4.4.2.1 (fast)

Engine specification:
-  Base setup   : fast
-  Supported LM : DFA, N-gram, Word
-  Extension    : WordsInt LibSndFile
-  Compiled by  : gcc -O3

Library configuration: version 4.4.2.1
- Audio input
    primary A/D-in driver   : alsa (Advanced Linux Sound Architecture)
    available drivers       : alsa
    wavefile formats        : various formats by libsndfile ver.1
    max. length of an input : 320000 samples, 150 words
- Language Model
    class N-gram support    : yes
    MBR weight support      : yes
    word id unit            : integer (4 bytes)
- Acoustic Model
    multi-path treatment    : autodetect
- External library
    file decompression by   : zlib library
- Process hangling
    fork on adinnet input   : no
- built-in SIMD instruction set for DNN

    NONE AVAILABLE, DNN computation may be too slow!

Try `-help' for more information.
pi@raspberrypi:~/julius $

USBマイクの優先順位を上げる

Raspberry pi 3にはマイク端子は付属していないので、USBマイクを使います。こちらも手元にあるものを使うということで、こちらを引っ張り出してきました(ゲーム用ですが)。マイクとスピーカーが一体になっているので、USBポートが一個で間に合うのはちょっと便利かな。

接続したら、lsusbで認識していることを確認します。1行目ですね。

pi@raspberrypi:~ $ lsusb
Bus 001 Device 007: ID 046d:0a6d Logitech, Inc.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
pi@raspberrypi:~ $

音声関係のデバイスが複数ある場合、どれに優先してデータを流すかを決める必要があります。下のコマンドでデバイスの優先度がわかるのですが、このままだとUSBポートに接続したヘッドフォンの優先度が低いので、これを最上位(ゼロ)に持ってきます。alsa-base.confファイルを作成して、下の通り記載して保存したらRaspberry piを再起動です。options ... index=0が最上位のデバイスになります。

pi@raspberrypi:~ $ sudo cat /proc/asound/modules
 0 snd_bcm2835
 1 snd_usb_audio
pi@raspberrypi:~ $ sudo vi /etc/modprobe.d/alsa-base.conf
options snd slots=snd_usb_audio,snd_bcm2835
options snd_usb_audio index=0
options snd_bcm2835 index=1
pi@raspberrypi:~ $
:
(再起動)
:
pi@raspberrypi:~ $ sudo cat /proc/asound/modules
 0 snd_usb_audio
 1 snd_bcm2835
pi@raspberrypi:~ $

録音テスト

他の方のJuliusの利用例では、arecordコマンドを使っていることが多いのですが、Juliusには同梱の録音ツールadinrecがあります。トラブルシューティング的には、こちらを使ったほうが良いかと思います。arecord -lで録音可能なデバイス(=マイク)の一覧を取得すると、カード0、デバイス0でヘッドフォンが認識されていることがわかります。環境変数ALSADEVに、"plughw:0,0"を設定してからadinrecを起動すれば、ファイルに録音することができます。

pi@raspberrypi:~/julius $ arecord -l
**** ハードウェアデバイス CAPTURE のリスト ****
カード 0: Headset [G433 Gaming Headset], デバイス 0: USB Audio [USB Audio]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0
pi@raspberrypi:~/julius $
pi@raspberrypi:~ $ export ALSADEV="plughw:0,0"
pi@raspberrypi:~ $ adinrec -nostrip /tmp/test.wav
STAT: ###### initialize input device
Stat: adin_alsa: device name from ALSADEV: "plughw:0,0"
Stat: capture audio at 16000Hz
Stat: adin_alsa: latency set to 32 msec (chunk = 512 bytes)
Stat: "plughw:0,0": Headset [G433 Gaming Headset] device USB Audio [USB Audio] subdevice #0
STAT: AD-in thread created
.............Error: adin_alsa: error in snd_pcm_wait() (Input/output error)
Error: adin thread exit with error

14528 samples (29056 bytes, 0.91 sec.) recorded
pi@raspberrypi:~ $
pi@raspberrypi:~/julius $ aplay -D plughw:0,0 /tmp/test.wav
再生中 WAVE '/tmp/test.wav' : Signed 16 bit Little Endian, レート 16000 Hz, モノラル
pi@raspberrypi:~/julius $

実は、動作しているようなのに録音されない…… という問題にかなり悩みました。マイクに近づいて、ハキハキ喋ると発生を認識してくれます。ある程度音圧がないと、認識が始まらないようです。できあがる録音ファイルは、無音部分がなくなって「発声」として認識したところだけが保存されています。aplayコマンドを上のように使うと、再生することができます。

ディクテーションキットの入手

日本語音声をテキストデータに変換するには、日本語の音の特徴がどういうものか、というデータが必要になります。例えば「あ」という声が、どのような音でできているのか、あらかじめ知っている必要があるわけです。Juliusの場合、日本語用のキットが用意されているので、これを活用させてもらいます(便利!)。

pi@raspberrypi:~/julius-kit $ wget --trust-server-names 'https://osdn.net/frs/redir.php?m=iij&f=julius%2F66544%2Fdictation-kit-v4.4.zip'
--2018-08-24 22:16:28--  https://osdn.net/frs/redir.php?m=iij&f=julius%2F66544%2Fdictation-kit-v4.4.zip
osdn.net (osdn.net) をDNSに問いあわせています... 202.221.179.17
osdn.net (osdn.net)|202.221.179.17|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Found
場所: http://iij.dl.osdn.jp/julius/66544/dictation-kit-v4.4.zip [続く]
--2018-08-24 22:16:28--  http://iij.dl.osdn.jp/julius/66544/dictation-kit-v4.4.zip
iij.dl.osdn.jp (iij.dl.osdn.jp) をDNSに問いあわせています... 202.232.140.70, 2001:240:bb8f::1:70
iij.dl.osdn.jp (iij.dl.osdn.jp)|202.232.140.70|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 426837341 (407M) [application/zip]
`dictation-kit-v4.4.zip' に保存中

dictation-kit-v4.4.zip        100%[==============================================>] 407.06M  7.56MB/s    in 44s     

2018-08-24 22:17:12 (9.26 MB/s) - `dictation-kit-v4.4.zip' へ保存完了 [426837341/426837341]

pi@raspberrypi:~/julius-kit $

これをunzipコマンドで展開して、ディレクトリ内にあるmain.jconfと、am-gmm.jconfファイルに一般的な設定が作られていますので、これをJuliusコマンドで読み込み、あとは適宜オプションを追加して起動する、ということになります。ちなみに、うまくいかないときのトラブルシューティング用資料がこのディレクトリのTROUBLE.txtにあるので、参考にしました(SJISで書かれたテキストファイルなので、読むときはiconv -f sjis -t utf8 TROUBLE.txtで読みます)。

起動後は、マイクに向かってハキハキ喋ります。-recordオプションをつけておくと、認識した音声を細切れのファイルに保存しておいてくれます。発音に所定の無音時間があると別の発言と認識されるので、一続きに読み上げるように話す必要があります。

pi@raspberrypi:~ $ unzip dictation-kit-v4.4.zip
pi@raspberrypi:~ $ cd dictation-kit-v4.4
pi@raspberrypi:~/dictation-kit-v4.4 $ julius -C main.jconf -C am-gmm.jconf -input mic -nostrip -record /tmp
:
:
:
(だーっと長い出力があって……)
:
:
:
------
### read waveform input
Stat: adin_alsa: device name from ALSADEV: "plughw:0,0"
Stat: capture audio at 16000Hz
Stat: adin_alsa: latency set to 32 msec (chunk = 512 bytes)
Stat: "plughw:0,0": Headset [G433 Gaming Headset] device USB Audio [USB Audio] subdevice #0

recorded to "/tmp/2018.0826.112825.wav" (74112 bytes, 2.32 sec.)
pass1_best:  こんにちは 今日 は い て です 。
pass1_best_wordseq: <s> こんにちは+感動詞 今日+名詞 は+助詞 い+動詞 て+助詞 です+助動詞 </s>
pass1_best_phonemeseq: silB | k o N n i ch i w a | ky o: | w a | i | t e | d e s u | silE
pass1_best_score: -6249.713867
### Recognition: 2nd pass (RL heuristic best-first)
STAT: 00 _default: 42918 generated, 2459 pushed, 535 nodes popped in 230
sentence1:  こんにちは 今日 は いい 天気 です 。
wseq1: <s> こんにちは+感動詞 今日+名詞 は+助詞 いい+形容詞 天気+名詞 です+助動詞 </s>
phseq1: silB | k o N n i ch i w a | ky o: | w a | i: | t e N k i | d e s u | silE
cmscore1: 1.000 0.819 0.412 0.255 0.294 0.277 0.764 1.000
score1: -6235.160645

<<< please speak >>>^C
pi@raspberrypi:~/dictation-kit-v4.4 $

トラブルシューティングとか

GitHubからダウンロードされるファイルは、ところどころ日本語で書かれたものがあるのですが、日本語部分のキャラクタコードはSJISなので、Raspberry pi(UTF-8)では文字化けして読めません。iconvコマンドは、覚えておく必要があると思います。こんな感じですね。

pi@raspberrypi:~/julius $ iconv -f sjis -t utf8 Release-ja.txt | less
4.4.2.1 (2016.12.20)
====================
- AndroidとiOS用の細かい修正
- msvcディレクトリの整理

4.4.2 (2016.09.12)
===================
- dnnconf 内の相対パスをそのdnnconf自身からの相対パスとして扱うよう修正
- DNN使用時、認識処理の第2パスが異常に遅いことがある不具合を修正
- AVX非対応のCPUでDNN計算が動作しない不具合を修正
- ARMとVisualStudioでのビルドを改善

4.4.1 (2016.09.07)
===================
- DNNのSIMD計算の高速化と安定化:SSE命令、FMA命令、ARMのNEON命令を実装
- DNN使用時にCPUを自動チェックして最適なSIMDコードを選択使用するよう拡張
- VisualStudio2013でのコンパイル用に PortAudio と zlib のソースを同梱
- mkbinhmmlistで作ったバイナリHMMListが正しく読めないことがあるバグを修正
- adintool-gui コンパイル時のSDL周りが動かないのを修正
- "INSTALL.txt" に各OSでのビルドのセットアップと実行方法を記述
- pkg-config に対応
- その他バグの修正

juliusコマンドの出力の日本語は、何もしなくてもUTF-8で出力されます。これはおそらくLANG環境変数が設定されているからだと思います。

今後

Juliusの日本語の認識能力は「特定の発言がある」と分かっている場合は、文法ファイルをあらかじめ作っておくことで精度をあげることができるようです。「アレクサ」と言うことがあるとわかっていれば、準備をしておくことで認識しやすくなるわけですね。命令でいろいろRaspberry piを動かすのが目標ですので、文法ファイルの作り方も、おいおい試してみようと思います。