2020年3月30日月曜日

ラズベリーパイでGNU Radio: その1 Raspberry Pi 4で動作させる


今更ながらUSBドングルでSDR受信を試すことにしました。手元にあったワンセグ受信用ドングルは、アンテナコネクタがMCXで使い勝手が悪いなーと調べていたら、e-bayでSMAコネクタがついたRTL-SDR Blogブランドのものが2000円台で売られているのを見つけ購入してみました。現物はワンセグ用ドングルより大きく、サイズ感が想像と違いました。


Raspberry Pi 4のセットアップ

Linuxで使いたいのでラズパイ4を新たに購入しました。ラズパイ4は消費電力が大きくCPUの冷却が必要とされているので、FLIRCのファンレスタイプのケースを選びました。このケースのアルミ筐体がCPUと熱的に結合され、ケース全体がCPUの放熱板として働くのでファン要らずになるという仕掛けです。アイドル動作中には、ケースが人肌程度に暖まります。注意点としては、ラズパイのヘッダピンがケースに覆われてしまうので使えなくなることでしょうか。このケース、国内では入手しづらいようで、amazon.comで購入しました。



ラズパイ4では、電源コネクタがUSB Type-Cに、ビデオ出力がmicroHDMIにそれぞれ変更されています。ACアダプタ(5.1V 3A以上)、HDMIケーブルは秋月電子で購入できます。ちなみにRTL-SDRドングルを含めた消費電流はアイドル時に約0.6A。SDR使用中に0.9Aを超える程度まで変動します。なお、ラズパイには電源スイッチがありません。なので電源スイッチ付きUSBケーブルがローテクながら便利です。

ラズパイのOSインストールにはこれまでと特段変わりないので割愛します。今回はRaspbian Buster with desktop and recommended softwareを入れてみました。設定でSSHログインとVNC Serverを有効にしておきます。インストール時にはラズパイにHDMIディスプレを接続すると思いますが、画面解像度はスタートメニューから 設定 → Screen Configuration にて1920×1080に変更できます。

ラズパイのヘッドレス運用

ディスプレイやキーボードなどを接続せずに使うことをヘッドレス運用とよぶそうです。WindowsからVNCでリモートログインするには、追加設定が必要でした。
一つ目はVNCのパスワード設定です。まず、ラズパイGUI画面で右上タスクバーのVNCアイコンをクリックしVNC Serverアプリを起動します。アプリ右上の三本線メニューでOptions... → Security → Authenticationを"VNC password"に変更します。
これで、WindowsにVNC Viewerアプリをインストールすればリモートログインできます。ところが、ここでラズパイからHDMIディスプレイを取り外すとCannot currently show the desktopとエラーが出て表示できません。そこで、ラズパイのLXTerminalまたはSSHログインにてraspi-configを実行し、画面解像度を設定します。
$ sudo raspi-config
7 Advanced Options → A5 Resolution → DMT Mode 82 1920x1080 60Hz 16:9 としました。

rtl-sdrをインストール

SDR受信を行うには、Osmocom projectのrtl-sdrライブラリを使います。導入には一癖あるようです。よくわからないこともありWeb記事に沿って作業を行いました。まずは、rtl-sdrに必要なlibusbライブラリとビルドツールをインストールします。
$ sudo apt-get update
$ apt-cache search libusb
 ・・・ インストール可能なパッケージが表示されますが、libusb-1.0-0ではだめなのでlibusb-1.0-0-devを選びます
$ sudo apt-get install libusb-1.0-0-dev
$ sudo apt-get install autoconf
$ sudo apt-get install libtool
次いでrtl-sdrをインストールします
$ git clone git://git.osmocom.org/rtl-sdr.git
$ cd rtl-sdr
$ aclocal
$ autoreconf --force --install
$ ./configure
$ make
$ sudo make install
$ sudo make install-udev-rules
$ sudo ldconfig
ここでrtl-sdrで採用されているRTL28xxチップに対応するテレビ受信用標準ドライバをblacklist登録してカーネルに読み込まれないようにします。
$ sudo nano /etc/modprobe.d/rtl_blacklist.conf
ファイルの内容は次の2行です
blacklist dvb_usb_rtl28xxu
blacklist rtl2832
ラズパイを再起動して、rtl_testコマンドで情報が取得できればOK。
$ rtl_test

rtl_fmコマンドで受信テスト

rtl_sdrライブラリ付属のコマンドラインツールrtl_fmで受信してみました。まずは音声再生のためsoxをインストールします。
$ sudo apt-get install -y alsa-utils sox libsox-fmt-all
そしてFMラジオを受信します。82.5MHzのFMラジオを受信しようとしたら、やや低い周波数を周波数を指定する必要がありました。いちおう音は出るんですが、ノイズ混じりだしひずみっぽいし、さらには金属的な付帯音が聞こえます。RFゲイン問題でも無さそうですし、サンプルレートの変換がうまく行っていないのか、aplayで再生しても同じで、原因はわかりませんでした。
rtl_fm -f 82.184M -M wbfm -s 200k| play -r 32k -t raw -e s -b 16 -c 1 -V1 -
rtl_fm -f 82.184M -M wbfm -s 200k -r 32000-| aplay -r 32 -f S16_LE


GNU Radioのインストール

まずはGNU Radio
$ sudo apt-get install gnuradio

次いでGNU radioからrtl-sdrを使えるようgr-osmosdrをインストール
$ sudo apt-get install gr-osmosdr

動作確認として osmocom_fft コマンドでスペクトラムを表示させてみました。NHK FMラジオが無音になったタイミングで、中心キャリアのほかにステレオパイロット信号とVICS信号がぼんやりと盛り上がって見えます。FFTサンプルレートは任意の値を指定できるようにみえて制約がありました。デフォルトは250kHzで1MHzはOKでしたが、500kHzはNGでした。
$ osmocom_fft -f 82.5M -s 250k


次に本命のGNU Radioを起動します。紹介記事で見かける画面が現れました。
$ gnuradio-companion

ネット上にあるFMラジオ受信のサンプルファイルを実行しようとすると、The xterm executable " is missingなるエラーが出ます。xtermが要るようなのでインストールします。
$ sudo apt-get install xterm
それでもエラーが出ますが、使い方がわからず手も足も出ません。そこで、RFワールドNo.44 GRCで広がるSDRの世界を片手に試すことにしました。ところがいちばん最初の、1kHz信号源をオーディオ出力するところでいきなりつまずいてしまいます。Audio Sinkのプロパティで(サウンド)Device Nameがブランクになっているのが原因のようです。試行錯誤してここにhw:0,1またはplughw:0,1とセットするとラズパイのイヤホンジャックを経由して音が出ることを確認できました。また、オーディオのサンプルレートは32kHz/48kHz/192kHzはOKですが、44.1kHzは使えないようです。


ラズパイを受信サーバとしてWindowsでリモート受信

ラズパイで受信サーバコマンドrtl_tcpを起動します。IPアドレスはラズパイのものに適宜読み替えてください。1234はデフォルトポート番号です。
$ sudo /usr/local/bin/rtl_tcp -a 192.168.0.xx -p 1234
Windows側ではWindows SDR Software Packageをダウンロードして、SDR#を起動します。次の操作で受信できるはずです。
・SourceとしてRTL-SDR(TCP)を選びます
・Configure Source(歯車マーク)をクリックし、受信サーバのIPアドレスをセット
 ※rtl-sdrライブラリのドライバではTCP接続ではDirect samplingモードを選べませんでしたが、RTL-SDR Blogのドライバでは改善されているようです(未確認)
・Start(再生マーク)をクリック

RTL-SDR Blog V3

DS-DT305

DT305では、局発リークとスプリアスが見えます。また、ウチの環境ではSDR#をTCP受信で使用すると、受信停止やアプリの終了のタイミングで高確率でハングアップします。

2020年3月28日土曜日

有料化した2D CAD「DraftSight」の代替は その2

前回の記事でQCADを使い始めていましたが、その後、ロシアNanosoftからnanoCAD(フリー版)がリリースされていることに気づきました。よりAutoCADライクなメニュー構成で、DWG形式をネイティブサポート、しかも動作も軽快。日本語フォントも表示されます。
QCADで作成したDXFファイルで一部読み込みできないものがありましたが、これまでの作図数が数枚なので躊躇なく乗り換えることにしました。


インストールは、ダウンロード画面でメール登録すると、折り返しダウンロードリンク、アクティベーションに必要なシリアルナンバ、パスワードがメールで届くという段取りで進めます。

2020年1月1日水曜日

ラズベリーパイでradikoラジオをつくる: その9 AirPlay楽曲情報取得

AirPlayのメタデータを取得

この記事は、Show Artist & Song Metadata Using Airplay on Raspberry Piを参考にしました。AirPlayのメタデータを取得するには、shairport-sync の作者Mike Brady氏が作成された shairport-sync-metadata-reader を使います。(感謝!)
事前準備として、前記事の通りshairport-syncを --with-metadata --with-dbus-interface --with-mpris-interfaceオプション付きでビルドしておく必要があります。そしてshairport-sync.conf のmetadataに関する設定を有効にしてメタデータを出力させます。
$ sudo nano /etc/shairport-sync.conf
metadata =
{
enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be  stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems
pipe_name = "/tmp/shairport-sync-metadata";
pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
};
shairport-syncサービスを再起動します。
$ sudo systemctl restart shairport-sync
メタデータを表示してみます。次のコマンドを実行し、iTunesから何か音楽を再生すると、どばっとXMLデータが表示されます。でも、何が何だかわからんですね。
$ cat /tmp/shairport-sync-metadata

shairport-sync-metadata-reader

XMLデータの解析ツールshairport-sync-metadata-readerをインストールします
$ cd ~
$ git clone https://github.com/mikebrady/shairport-sync-metadata-reader.git
$ cd shairport-sync-metadata-reader
$ autoreconf -i -f
$ ./configure
$ make
$ sudo make install
AirPlayが接続済みであれば再接続します。そして、次のコマンドを実行しiTunesから音楽を再生してみます。
$ shairport-sync-metadata-reader < /tmp/shairport-sync-metadata

今度は、少しわかりやすく表示されました。
冒頭に音源の情報が羅列されています。snuaは"user agent"、acreはActive-Remoteトークン、daidはDACP-IDです。スピーカ側からiTunesを制御?するときに使うようです。スピーカのボリューム変更がiTuneにも反映されるようにするとかですかね、よくわかりません。pendは再生ストリームの終了、pbegは再生ストリームの開始、pvolは音量、dapoはソース側のポート番号でiTunesは3689です。
次いでAlbum Name, Artist, Titleの情報が取得できています。
そして、mdstはメタデータのシーケンス開始、mdenは終了、数値はRTPタイムスタンプを表します。Shairport Syncの説明によると、32ビットの符号なし整数で1秒あたり44,100フレームのオーディオフレームのインデックス番号であるとのこと。
pcstは画像の送信開始、pcenは送信終了です。Picture received, length 240192 bytes.とあるとおりディスク・ジャケット画像が受信されshairport-sync.confで指定したフォルダに保存されます。この画像は、shairport-syncが受信しており、画像が変わる度に新たな名前で更新され、前の画像は削除されるようです。なお、ジャケット画像を含まない音楽ファイルでは更新されず、前の画像が残ったままになるようです。


prgr(progress)は、再生シーケンス開始、現在の再生ポイント、再生シーケンス終了のRTPタイムスタンプを表しています。
再生を一時停止すると次のようになります。flsrは、停止位置のRTSPタイムスタンプのようです。pflsは再生ストリームが無くなったことを示しています。


そのまま待っていると、10秒ほどで"ssnc" "pend"が2回繰り返しそのままとなります。pendは再生ストリームの終了を示しています。また、再生を再開するときは、再生開始と同じメッセージが出ました。


スライダーで再生位置を変更。


曲の再生が終わり、次の曲に移るとき。


iTunesには音量調整のスライダーがあります。音量最大、中間、最小に変更すると、1回ずつpvolが出ます。音量値の単位はdBで、-144.00はミュートを表すそうです。
shairport-sync.confでignore_volume_control = "yes"としていなければ、音量が変わるはずです。


メタデータの利用

曲名等とアルバム・ジャケット画像の表示のために、次のメタデータ情報を使います。prgrは再生開始、pflsは一時停止、pendは再生停止と解釈してみることにします。
Album Name: "Opus".
Artist: "山下達郎".
Title: "Down Town".
Picture received, length 240192 bytes.
"ssnc" "prgr": "3268798776/3268811064/3283215360".
"ssnc" "pfls": "".
"ssnc" "pend": "".
次に、shairport-sync-metadata-readerの出力を受け取り、楽曲情報・再生状態・アルバム・ジャケット画像ファイル名を表示するスクリプトを作成してみました。


Pythonスクリプトを以下に示します。標準出力の受け取り方がわからず困惑しましたが、Webに投稿された例を参考にしました。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import subprocess
import os

class airplay_metadata:
    def __init__(self):
        self.artist = ""
        self.album = ""
        self.title = ""
        self.status = ""
        self.coverart_path = '/tmp/shairport-sync/.cache/coverart'
        self.coverart_filename = ""

    def read(self):
        cmd = "shairport-sync-metadata-reader < /tmp/shairport-sync-metadata"
        proc = subprocess.Popen(cmd,stdout=subprocess.PIPE,shell=True)

2019年12月18日水曜日

ラズベリーパイでradikoラジオをつくる: その8 AirPlayストリーミング

shairport-syncをインストール

タイトルとは矛盾しますが、外部入力くらい付けても良いよね、ということでAppleのAirPlay対応を試してみることにしました。これは、iPhoneやitunesからWi-Fi接続のスピーカに見えるというものです。shairport-syncが有名で、様々なサイトで紹介されています。何をやっているのかよくわかりませんので、手順のみ列挙します。

必要なパッケージをインストール
$ sudo apt-get update
$ sudo apt-get install -y build-essential git xmltoman autoconf automake libtool libdaemon-dev libasound2-dev libpopt-dev libconfig-dev avahi-daemon libavahi-client-dev libssl-dev libsoxr-dev
gitからshairport-syncをクローンしてビルド。
$ git clone https://github.com/mikebrady/shairport-sync.git
$ cd shairport-sync
$ autoreconf -i -f   ・・・数分かかります
$ ./configure --sysconfdir=/etc --with-alsa --with-avahi --with-ssl=openssl --with-metadata --with-soxr --with-systemd
$ make
$ sudo make install
shairport-syncの自動起動を設定
$ sudo systemctl enable shairport-sync

shairport-sync.conf を編集します。設定項目がコメントアウトされているので、適宜修正して有効にします。
$ sudo nano /etc/shairport-sync.conf
general = {
name = "IP Radio Receiver";
//interpolation = "soxr";
ignore_volume_control = "yes";
nameは、iPhone等に表示されるデバイス名です。interpolationは、サンプルレート変換に使用するソフトウエアで soxr は処理が重い代わりに音質が良いとされています。私は有効にせず、デフォルトの basic で行くことにしました。ignore_volume_control = "yes"で iPhone等でのボリューム設定を無視します。

shairport-syncサービスを起動
$ sudo systemctl start shairport-sync
systemctl のステータスを表示
$ sudo systemctl status shairport-sync

iTunesから鳴らしてみる

アプリの上部のアイコンをクリックすると、IP Radio Receiverが表示されました。レ点を入れ、再生するとRadiko音声とミックスされて音楽が聞こえてきます。iPadからもあっさりと動作してくれて拍子抜けでしたね。
iTunesの再生ボタンを押してから音が出るまで2秒ほど遅延があります。Wi-Fiの品質にシビアのようで、弱電界では途切れたり再生が停止してしまします。ラズパイのWi-Fiは2.4GHzなので混信の影響も大きいのだろうと考えています。



2019年12月14日土曜日

ラズベリーパイでradikoラジオをつくる: その7 radikoの再生停止問題

スピーカを接続

Auratoneに接続してみました。スピーカのエッジはぼろぼろなのに、まともな音で鳴ってくれます。エッジ交換したら良くなるんですかね。初めて使ったD級パワーアンプ、好印象です。


radiko再生停止問題

radikoを起動後1分ほどで再生が停止、手動でもういちど再生するとその後は安定するという現象がありましたが。この開発を進めるうち、いつの間にやら再生開始から1分ほどで停止するのを繰り返すようになってしまいました。苦し紛れで、自動的に再接続する仕組みを実装しましたけど、1分毎に10秒以上無音になるようでは、最早使い物になりません。調べてみたところ、mplayer のキャッシュを設定すれば改善できるという情報があり早速試したところ解決することができました。

mplayer の設定は、/etc/mplayer/mplayer.conf にあります。こんな内容です。
# Use 8MB input cache by default.
#cache = 8192
#
# Prefill 20% of the cache before starting playback.
#cache-min = 20.0
mplayer.conf のデフォルトはキャッシュ無しの設定でした。そこでキャッシュを設定したところ256kBで安定して再生できることを確認しました。RadikoのPC向け配信ビットレートはHE-AAC 48kbpsということからキャッシュ256kBは約40秒に相当します。家庭内の光ファイバ環境ならこれで十分ではないでしょうか。とはいえキャッシュを増やせば、再生開始までの時間が増えてしまうはず。
  • 設定なし: 再生まで14秒、1分ほどで再生が停止
  • cache = 256 :再生まで14秒、その後も途切れない
  • cache = 512 :再生まで16秒、その後も途切れない
以上の結果から、256kBが妥当と考えました。cache-minは、0から20まで変えてみましたが、再生開始までの時間に大きな変化はありません。光回線の速度が十分に速いためでしょう。ところで再生まで14秒とは、時間かかりすぎです。以前は10秒程度だったはず。ラジコプレミアム対応のスクリプトをコマンドで手動実行して時間を測ってみました。

  • 初回: 13秒
  • 2回目以降: 8秒
初回よりも2回目以降はより早く再生開始します。これは認証情報を使い回しているからと考えています。この初回時の時間と、操作ボタンでの再生開始までの時間は、ほぼ一致します。ということは、操作ボタンでは初回扱いとして動作しているに違いありません。Pythonのsubprocess.Popen()でスクリプトを起動しているせいかなあ。

ところで、開発を進めるうちに再生停止が顕在化する原因がわかりました。なんと、操作スクリプトがCPU使用率を圧迫していたのです。python /home/pi/operation_panel/panel.pyというのがそれで63.8%もあります。radikoの再生をしている mplayer と合わせてCPU使用率 90%弱とほぼ目一杯使い切っています。


操作スクリプトがそんなに重いのは不自然です。とりあえず、対症療法でwaitを入れると、10%程に下がりました。それでもSSHログインやpsコマンドを打ち込むだけでも一瞬音が途切れます。操作スクリプトの見直しが必要ですね。


2019年12月10日火曜日

ラズベリーパイでradikoラジオをつくる: その6 Wi-Fiを自動切替・信号強度表示

登録済みのWi-Fiを自動的に切り替え

自宅や外出先で使用することを想定して、Wi-Fiの自動切り替えを設定してみます。
まずは、wpa_supplicant.confにアクセスポイントを設定を追加して行きます。
$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
 次のように、3つのアクセスポイントを設定してみました。関連する設定を抜粋して示します。ssidはSSID名、pskはパスフレーズ、priorityは接続優先度で、値が大きいssidに優先的に接続します。なお、パスフレーズの暗号化やSSIDのステルス機能を使うには更に設定が必要です。
network={
ssid="aterm-00000"
psk="abcd0000"
priority=2
}
network={
ssid="MOBILErouter"
psk="abcd1111"
priority=3
}
network={
ssid="Android1234"
psk="abcd2222"
priority=1
}
さらにIPアドレスを固定する場合は dhcpcd.conf を設定します。
$ sudo nano /etc/dhcpcd.conf
IPアドレスを固定したいアクセスポイントのみ次のように記載します。モバイルルータやスマホのテザリングでは自動割り当てにしたいので何も記載しません。
interface wlan0
ssid aterm-00000
static ip_address=192.168.0.100/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1

Wi-Fi IPアドレス表示

Linuxの「ip」コマンドでwlan0の情報を取得しました。
$ ip addr show dev wlan0
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.100/24 brd 192.168.0.255 scope global noprefixroute wlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::6f54:0000:0000:0000/64 scope link
       valid_lft forever preferred_lft forever
さらにawkでIPアドレスを抽出します
$ ip addr show dev wlan0 | awk '/inet /{printf substr($2, 1, index($2, \"/\")-1)}'
192.168.0.100
ところで、ipコマンドで送受信バイト数が表示出来ますから、この数値を使ってリアルタイムのデータ転送速度を表示出来そうです。
$ ip -s link show dev wlan0
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DORMANT group default qlen 1000
    link/ether b8:27:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    RX: bytes  packets  errors  dropped overrun mcast 
    1935131    3444     0       0       0       1527 
    TX: bytes  packets  errors  dropped carrier collsns
    177144     1401     0       0       0       0   
   

Wi-Fi 信号強度とSSID表示

Linuxの「iwconfig」コマンドを利用します。
$ iwconfig wlan0
wlan0     IEEE 802.11  ESSID:"aterm-00000" 
          Mode:Managed  Frequency:2.442 GHz  Access Point: 00:3A:9D:91:D3:94 
          Bit Rate=65 Mb/s   Tx-Power=31 dBm 
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
          Link Quality=70/70  Signal level=-38 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:1  Invalid misc:0   Missed beacon:0
信号強度、ESSIDもawkで抽出します。ESSIDは二重引用符で囲まれたものを抽出したかったのですが、苦し紛れで文字数数えています。
$ iwconfig wlan0 | awk '/Signal level/{print substr($4,7)}'
$ iwconfig wlan0 | awk '/ESSID/{printf substr($NF, 8, length($NF)-8)}'

Wi-Fi 信号強度インジケータの実装

スマホの真似をしようとしたんですが、表示の閾値をいくつにするか悩みました。通信品質はWi-Fiの変調方式はもちろん、機器の差や近隣の干渉波によっても変わるはず。Webで調べるとバッファローは「-30dBm~-40dBmで無線接続が安定します」、IBS JapanのページにはVoIPの最小信号強度-67dBmとありました。これらを参考に、-60dBm/-50dBm/-40dBmでインジケータを振らすことにしました。


2019年11月28日木曜日

ラズベリーパイでradikoラジオをつくる: その5 ロータリエンコーダで音量調整

ロータリエンコーダを使ってみる

今回使うのは、BOURNSのPEC16という1回転で24パルスのロータリエンコーダです。デテント付きと言って、軸を廻すとクリック感のあるタイプを選びました。データシートにはPRO AUDIOとあって、何のことかと思いましたが廻して納得。フィーリングが心地よく、ひょっとしたらデジタルミキサなどのプロ用音響機器に採用されているのかもと思わせるものでした。



ところでロータリエンコーダの軸は、断面がD形(半月形)になっていて、つまみが入手しにくい印象です。今回はRSで購入しましました。記事を書きながら「D形シャフト用つまみ」を改めて調べてみたら、マーベル社の製品が千石で扱いがあるようです。
ロータリエンコーダの中にはスイッチが2個入っていて、回転に合わせてスイッチが断続します。波形の変化から回転方向がわかります。下図でDはデテントの固定位置、CWは時計回り、CCWは反時計回りを示しています。


ロータリエンコーダの接続はこんな感じです。ENC-A, ENC-BをラズパイのGPIOで読み取ります。ラズパイ側のプルアップ抵抗は無しです。


ロータリエンコーダの接点を表示するスクリプト

まずはPythonでスクリプトを作成しました。ENC-A, ENC-Bのどちらかのレベルが変動したら、接点を読み取り標準出力に表示します。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import RPi.GPIO as GPIO_RPi
# GPIO pin assign
SW_ENCA= 22
SW_ENCB= 23

def callback(callback):
    re = (GPIO_RPi.input(SW_ENCA) &1)
    re += (GPIO_RPi.input(SW_ENCB) &1)<<1
    print(re)

def main():
    GPIO_RPi.setwarnings(False)
    GPIO_RPi.setmode(GPIO_RPi.BCM)
    # GPIO initialize
    GPIO_RPi.setup(SW_ENCA, GPIO_RPi.IN)
    GPIO_RPi.setup(SW_ENCB, GPIO_RPi.IN)
    GPIO_RPi.add_event_detect(SW_ENCA, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    GPIO_RPi.add_event_detect(SW_ENCB, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    try:
        while(True):
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("break")
        GPIO_RPi.cleanup()

if __name__ == "__main__":
    main()
早速スクリプトを実行させて、右に2ステップ、左に3ステップ廻してみました。右に廻すと0→1→3→2、左は1→0→2→3の順番で変化します。見た感じ、チャタリングは無いように見えます。


ロータリエンコーダで音量を調整

読み取ったデータからスクリプトでロータリエンコーダの回転方向を判定し、音量調整に利用します。回転方向判定は、ChaN氏のページのテーブル変換を参考にしました。ただ、そのままでは軸を1クリック廻すごとに4回動いたと判定されてしまうため、1回になるようテーブルに手を入れています。
ロータリエンコーダの回転を検知する毎にamixerコマンドを発行すれば、音量が変わります。このコマンドで音量は0から255までの数値で指定できますが、減衰量は1/256ずつ直線的に変化すると思っていましたが、実際にやってみるとそうではないようです。この変化のルールがどうなっているのか調べたところ、同じamixerコマンドでその答えが表示されることがわかりました。この表示によると0dBから-51dBまで0.2dBステップで調整できることになっています。


ラジオのボリュームとして使いやすいよう16段階で音量調整できるようにします。減衰量は配列に入れておき、ロータリエンコーダが動く都度読み出します。減衰量のデータは適当に作ったので音量変化が妥当になるよう見直しが必要ですね。参考までスクリプトを示します。今回の製作で初めてPythonに触れたので不適切なところがあるかも、と言い訳しておきます。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import subprocess
import RPi.GPIO as GPIO_RPi
# GPIO pin assign
SW_ENCA= 22
SW_ENCB= 23
re = 0
position = 13

def callback(callback):
    global re
    global position
    #dir = [0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0]
    dir = [0,1,0,0,-1,0,0,0,0,0,0,0,0,0,0,0]
    re = (re &3)<<2
    re += (GPIO_RPi.input(SW_ENCA) &1)
    re += (GPIO_RPi.input(SW_ENCB) &1)<<1
    n = dir[re & 15]
    if n==1:
        print('CW')
        if position < 15:
            position += n
        print(position)
        vol_cont(position)
    elif n==-1:
        print('CCW')
        if position > 0:
            position += n
        print(position)
        vol_cont(position)

def vol_cont(position):
    vol = [0,115,125,135,145,155,165,175,185,195,205,215,225,235,245,255]
    cmd = ["amixer", "-c0", "sset", "PCM", str(vol[position]), "unmute"]
    ret = subprocess.Popen(cmd)

def main():
    GPIO_RPi.setwarnings(False)
    GPIO_RPi.setmode(GPIO_RPi.BCM)
    # GPIO initialize
    GPIO_RPi.setup(SW_ENCA, GPIO_RPi.IN)
    GPIO_RPi.setup(SW_ENCB, GPIO_RPi.IN)
    GPIO_RPi.add_event_detect(SW_ENCA, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    GPIO_RPi.add_event_detect(SW_ENCB, GPIO_RPi.BOTH, callback=callback, bouncetime=5)
    try:
        while(True):
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("break")
        GPIO_RPi.cleanup()

if __name__ == "__main__":
    main()