h_nari @ 熊本市のブログ。電子工作、プログラミング、ゲーム、TV、 政治、インターネットなどに日々の思い付きを、 うだうだ~と書いていきたい。
このブログにはコメント欄を設けておりません。 記事への御意見、ご質問はtwitter @h_nari宛に お願い致します。


アーカイブ

メタ情報
RSS
Login

タグ:『プログラミング』の付いた記事

XIAOMI湿度計その後

フィラメント・ケースの湿度を監視している XIAOMIの湿度計だがトラブルが発生した。 Linuxサーバーが遅くなる。 調べると bluepy-helperというプロセスが 幾つか全速で走っている。 どうも pythonのbluepyを使用しているプログラムで Peripheralとのconnectionを切断した時に 時々bluepy-helperのプロセスが走りっぱなしに なるようだ。

LYWSD03MMC.pyには bluepy-helperをkillする処理が何箇所か入っているが それでも充分では無いようだ。

いろいろプログラムを試して Threadをセンサー毎に走らせ connectionを繋ぎっぱなしで 切断しないプログラムにしたら bluepy-helper問題は発生しなくなった。 システムのload averageも監視しているが 特に重くなることも無いようだ。

ただし、 pythonでbluepyを使用し scanするプログラムが停止するようになった。 何かエラーが発生しているのだろう。 こちらのプログラムは停止していても さほど問題は無いので、このまま運用することにする。

プログラムを以下に示す。

#!/usr/local/bin/python3 -u
from bluepy import btle
import time
import sys
import os
import requests
import json
import threading
class XiaomiDelegate(btle.DefaultDelegate):
    def __init__(self, addr, sensor_id):
        btle.DefaultDelegate.__init__(self)
        self.addr = addr
        self.sensor_id = sensor_id
        self.tSend = None
    def handleNotification(self, chandle, data):
        if not self.tSend or time.time() - self.tSend > 60*10:
            self.sendData(data)
            self.tSend = time.time()
    def sendData(self, data):
        t = int.from_bytes(data[0:2],byteorder='little',signed=True)/100
        h = int.from_bytes(data[2:3],byteorder='little')
        v = int.from_bytes(data[3:5],byteorder='little')/1000
        s = self.sensor_id
        json_str = json.dumps([{'sensor_id' : s, 'value': t},
                               {'sensor_id' : s+1, 'value' : h},
                               {'sensor_id' : s+2, 'value' : v}])
        r = requests.post('http://my_server_addr/fluentd_port',
                      data=json_str)
def thread_getData(**kwargs):
    addr = kwargs['addr']
    sensor_id = kwargs['sensor_id']
    while True:
        try:
            p = btle.Peripheral(addr)
            p.writeCharacteristic(0x0038, b'\x01\x00', True);
            p.writeCharacteristic(0x0046, b'\xf4\0x01\0x00', True);
            p.withDelegate(XiaomiDelegate(addr, sensor_id))
            while True:
                p.waitForNotifications(10)
        except btle.BTLEException as e:
            print('Error:',e)
if __name__ == '__main__':
    sensors = {
        "A4:C1:38:91:79:3F": 138,
        "A4:C1:38:BC:11:D9": 132,
        "A4:C1:38:1F:EB:AB": 135,
        "A4:C1:38:79:5E:67": 141
    }
    threads = {}
    for addr in sensors:
        print('thread start for',addr)
        threads[addr] = t = threading.Thread(target=thread_getData,
                                             kwargs={'addr': addr,
                                                     'sensor_id':
                                                     sensors[addr]})
        t.start()
        time.sleep(20)

しかし、防湿フィラメント・ケースが完成し 湿度の監視ができるようになったら すっかり秋になり湿度が下がってしまった。 湿度50%ぐらいでも防湿は必要なのだろうか?



XIAOMI 湿度計到着

AliExpressに注文していた XIAOMIの湿度計がやっと届いた。 8月16日に注文していたので5.5週ぐらいで 届いたことになる。 価格は4個で1,229円。1個あたり306円と激安。

コンパクトで可愛いデザイン。 電池の絶縁テープを抜くと すぐに温度と湿度が表示された。 裏蓋を外し内部を確認。 電池はCR2032。 MOYASHI氏のフィラメントケースの ホルダーにバッチリはまって気持ちが良い。

データを取得するプログラム

アプリでアクセスできるらしいのだが、 どうせアプリでは使わないので MOYASHI氏のブログで紹介されている JsBergbau/MiTemperature2: Read the values of the Xiaomi Mi Bluetooth Temperature sensor 2を試す。

Python3.7以上が必要ということで、 自分のLinux機はPython3.5.3だったので ソースをダウンロードし 3.7.9をインストール。 LYWSD03MMC.pyをダウンロードし 動かすと usageが表示された。 これを読んで以下のことがわかった。

  • デバイスのMACアドレスを指定する必要がある
  • -cで回数を指定しないと永遠にデータを表示し続ける
  • -callでデータ取得時に外部プログラムを起動可能
  • データ補正機能が充実。湿度データがあまり信用されていない?

MACアドレスの取得

bluetoothについて詳しくないのだが、 湿度計のMACアドレスは以下のbluetoothctlコマンドを実行し、 scan onと入力することで取得できた。 LYWSD03MMC がXIAOMIの湿度計らしい。

$ bluetoothctl
[NEW] Controller 00:1B:DC:03:9F:EC hslpc24 [default]
[NEW] Device B0:99:28:A4:55:D2 F-PLUG_001BDC039FEC
[bluetooth]# scan on
Discovery started
[NEW] Device 51:64:82:65:03:9D 51-64-82-65-03-9D
[NEW] Device 57:FC:16:D4:63:DB 57-FC-16-D4-63-DB
[NEW] Device A4:C1:38:91:79:3F LYWSD03MMC
[NEW] Device A4:C1:38:79:5E:67 LYWSD03MMC
[NEW] Device A4:C1:38:1F:EB:AB LYWSD03MMC
[NEW] Device A4:C1:38:BC:11:D9 LYWSD03MMC
[CHG] Device 51:64:82:65:03:9D RSSI: -59
[CHG] Device A4:C1:38:79:5E:67 RSSI: -62
[CHG] Device 51:64:82:65:03:9D RSSI: -76
[CHG] Device A4:C1:38:1F:EB:AB RSSI: -71
[bluetooth]# quit
[DEL] Controller 00:1B:DC:03:9F:EC hslpc24 [default]
$

callbackプログラムの引数

-callで与えるプログラムに 渡される引数を確認するため、引数を 表示するだけのプログラムで動かしてみる。

$ cat callback.py
#!/usr/local/bin/python3
import sys
print(sys.argv)
$ python3 ./LYWSD03MMC.py -d A4:C1:38:91:79:3F -c 1 -call callback.py
Trying to connect to A4:C1:38:91:79:3F
Temperature: 28.78
Humidity: 65
Battery voltage: 2.726
1 measurements collected. Exiting in a moment.
/somewhere/callback.py sensorname,temperature,humidity,voltage,timestamp A4:C1:38:91:79:3F 28.78 65 2.726 1601098469
['/somewhere/callback.py', 'sensorname,temperature,humidity,voltage,timestamp', 'A4:C1:38:91:79:3F', '28.78', '65', '2.726', '1601098469']
$

sys.argv[1]にパラメータ名のリストが渡され sys.argv[2]以降に値が渡されている。 パラメータはLYWSD03MMC.pyのオプションで変化する場合が あるらしい。

データ記録プログラム

必要な情報が揃ったので、 湿度データを記録するプログラムを作成した。 データの記録はwebサーバのapiにアクセスすることで センサーIDと値が時刻とともにMysqlに保存している。

まず、callback用のプログラム。

$ cat xiaomi_callback.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, os, urllib.request

sensors = {
    "A4:C1:38:BC:11:D9": 132,    # MAC addr: sensor_id
    "A4:C1:38:1F:EB:AB": 135,
    "A4:C1:38:91:79:3F": 138,
    "A4:C1:38:79:5E:67": 141
}

if len(sys.argv) > 1:
    params = sys.argv[1]
    i = 2
    data = {}
    for param in params.split(','):
        data[param] = sys.argv[i]
        i += 1
    id = data['sensorname']
    sid = sensors[id]
    keys = ['temperature','humidity','voltage'];

    for i in range(3):
        key = keys[i]
        if key in data:
            v = data[key]
            url  = 'http://my_server_addr/dms/api.put?'
            url += 'sensor=%d&value=%s' % (sid+i, v)
            urllib.request.urlopen(url)
else:
    for k in sensors.keys():
        print(k)
$

cronから呼び出すプログラム

nari@hslpc24$ cat xiaomi.sh
#!/bin/bash
DIR="/home/my_program_dir"
for id in  `$DIR/xiaomi_callback.py`; do
    /usr/local/bin/python3 -u $DIR/LYWSD03MMC.py -d $id -c 1 -call xiaomi_callback.py
done
$

このプログラムをcronで10分毎に起動している。

$ crontab -l
...
*/10 *    *   *   *   /home/my_program_dir/xiaomi.sh > /dev/null
...
$

実装

調べたMACアドレスと割り当てたセンサーID(番号)を印刷し、 両面テープでXIAOMI湿度計に貼り付けた。

フィラメント・ケース-3Dプリンタ間は PTFEチューブにいれてフィラメントを送っている。 このチューブには湿気の侵入を防ぐことと 3Dプリンタがフィラメントを引いても ケースが動かないという利点がある。 X-Smartのヘッドはチューブを受け止める 形状になっているが、ENDER3V2はそうなっていないので、 自作の部品を追加し、チューブを受け止めれるようにした。

計測結果

前にも書いたが Mysqlに書き込んだデータは grafanaでグラフ化し ブラウザで見る。 昨晩、湿度計を設置したあとのグラフを以下に示す。

湿度計は今回設置したXIAOMIの4個以外に2個ある。 1つは netatomの室内モジュールについているもので、 もう一つは, sht31モジュールで自作したもの。 XIAOMIの湿度計が、なかなか来ないので 待ちきれず作ってしまった。 sht31は湿度の精度は±2%で高精度ということになっている。 これと比べるとXIAOMIの奴は10%程度高い湿度を示すようである。

XIAOMIの湿度計の設置状況は以下の通り。

  • X-Smart用フィラメントBox(1F設置)
  • Ender3V2用フィラメントBox
  • フィラメント保管Box
  • Ender3V2近く

上3つは密閉ケースで乾燥機が入っている。 ケースに入れた時点で湿度が下がり始め、 蓋を開けたタイミングで湿度が戻る様子が 観測できる。

今後

乾燥剤がどれくらいの期間持つかとか、 本当に湿度絡みのトラブルがなくなるのかとか、 様子を見ていきたい。

余ったXIAOMI湿度計1個は フィラメントBOXをもう1個作って入れる予定。

2020/10/09(金)追記

このプログラムでは問題が発生してしまった。 詳しくは XIAOMI湿度計その後参照。


Arduino で XIAOプログラミング

HDDサンダーで 回転数をXIAOで読み取るまでできた。 その際に取得できた ArduinoでXIAOのプログラム、 ライブラリでは解決できず、 自分でチップのレジスタを操作するような プログラムを書くために 必要な知識をいくつか紹介する。


ソースの在り処

seeeduino XIAOのarduionoのソースは web上では https://github.com/Seeed-Studio/ArduinoCore-samd にある。割と見やすいので、よく利用する。 ただし tools以下のファイルは無い。 PC上では、arduinoのboardマネージャで導入した場合, C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino ディレクトリ以下に配置される。

Exploreで開けるのも面倒な場所なので C:\Users\ユーザ名\AppData\Local\Arduino15\packages フォルダを exploreで「クイックアクセスにピン留め」しておくと 各種CPUのarduinoのソースに簡単にアクセスでき 便利になる。

ついでに cygwinのchereコマンドで context menuに「Cygwin here」を追加しておくと、 cygwinを開き

$ find . -type f | xargs grep SOME_TEXT
$ find . -name '*.h' | xargs grep SOME_TEXT

などとして、任意の文字列の出現箇所を調べられるので便利。

プログラミングの雰囲気を掴む

チップのレジスタを操作するようなプログラムを いきなり書くのは敷居が高いので、 まずは簡単な機能ソースを見て 雰囲気を掴もう。

GPIOの操作の仕方は digitalWrite()のソースを見る。 wiring_digital.c にある。 PWMの使い方は wiring_analog.c のanalogWrite()を見る。

プログラムを詳しく読む

雰囲気だけではプログラムは書けないので、 真面目に XIAOで扱われているマイコン、 ATSAMD21G18に取り組もう。 データシートにざっと目を通す。

マイコンの各ブロックの制御レジスタは 型定義が

C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1\CMSIS-Atmel\CMSIS\Device\ATMEL\samd21\include\component
にブロックごとに有る。

実態の定義は以下に有る。

C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1\CMSIS-Atmel\CMSIS\Device\ATMEL\samd21\include\samd21g18a.h

これらを参照しながら読むとプログラムの詳細な動きが 理解できる。

PWMの周期を変更

前回の報告で述べた通り、 HDDモータを制御するPWM信号の周波数は DRV11873の仕様で 7~100kHzの範囲内である 必要がある。 Seeeduino XIAOの analogWrite()のPWMの周波数は 実測で730Hzぐらいなので 仕様に合わない。

これを修正すべく wiring_analog.c を読んだところ以下のことが判明した。

  • カウンタは16bitモードで動作
  • 48MHzのクロックを216=65536で分周し、 48,000,000Hz ÷ 65536 = 732.421875 Hzが出力されている。
  • カウントの最大値 0xffff を設定している箇所がある。

カウントの設定値を4800に変更することで、 PWMの周波数を 48MHz ÷ 4800 = 10KHz にすることができた。

信号の周期を計測

BLDCモータドライバDRV11873は モータ駆動の1サイクルに同期した パルスがFG端子から出力される。 この何サイクルかがモータの1回転に 対応するので、この信号の周期を計測することで モータの回転数を取得することができる。 ATSAMD21G18のカウンタでFG信号の周期を 計測したので、その方法を説明する。

カウンタで信号の周期を計測するための 構成を図に示す。 外部ポートPA4から入力された信号を EIC(外部割込)に接続しイベントを発生させ、 EVSYS(イベントシステム)でタイマーに接続し 周期を計測する。タイマーはTC4とTC5を合わせて 32bitタイマーとして計測する。 クロックはシステムクロックの48Mhzを使用するので 最大 232[count]÷ 48M[count/秒] = 89.47[秒]周期まで計測可能 となる。

特徴的なのがEVSYS(イベントシステム)モジュール。 これは各周辺ブロック間で信号をやり取りするバスで

  • 12チャンネル
  • イベント・ジェネレータ74個
  • イベント・ユーザ29個
となっている。 他のマイコン、例えばSTM32だと タイマーやADやDAなどの制御レジスタに これを1にすると入力がTimer-nのX信号が 接続されます、というようなbitがいろいろある。 謎のブロック間ネットワークである。 EVSYSはそれを一般化しているわけで 好意がもてる。 しかし、残念なことに制御レジスタのアドレス空間を ケチっているので使いにくい。

EICの使い方は WInterrupt.cにある attachInterrupt()のソースを見ると 大体わかる。

EVSYSは使い方が分かりづらい。 arduinoでは全く使われていない。 ハマったのは以下の2点。

  • クロックを供給する必要がある
  • レジスタを部分アクセスすると失敗する

クロックの供給というのは、コードで言うと以下の部分。

PM->APBCMASK.bit.EVSYS_ = 1;

STM32とかでプログラムしていると当たり前の部分だが、 Arduinoのソースを読んでいてもあまり出てこないので 気が付かなかった。 調べると wiring.cで結構初期化されている。

Datasheetの 11章 Peripherals Configuration Summaryが参考になった。

レジスタを部分アクセスすると失敗する というのは次の部分。 まず、正常に動かないコード。

EVSYS->USER.bit.CHANNEL = 2;
EVSYS->USER.bit.USER  = 0x13;

正常に動くコード。

EVSYS->USER.reg = EVSYS_USER_USER(0x13) | EVSYS_USER_CHANNEL(2);

このコードはイベント・チャンネル1 (CHANNEL-1の値)の出力を TC(USER 0x13)に接続しているが、 上側のコードではDMAC CH0(USER 0x00)にも接続されてしまい、 DMAC側でイベントを受け入れる設定になっていないので エラーとなり、イベント・チャンネル1が動かなる模様。 正常に動くコードにたどり着くまで、かなり時間がかかった。

割込処理

これでタイマーに信号の周期が記録されるようになるが、 プログラムの処理上、周期計測後に割込を発生させる。 割込の処理も WInterrupt.cが参考になる。

タイマーのレジスタで割込を有効化し、 以下のコードでNVIC(割り込みコントローラを設定する)

IRQn_Type v = TC4_IRQn;
NVIC_DisableIRQ(v);
NVIC_ClearPendingIRQ(v);
NVIC_SetPriority(v, 0);
NVIC_EnableIRQ(v);

cortex_handlers.c にデフォルトの割込ハンドラが定義されている。 TC4関連だけ抜き出すと以下のようになる。

/* Default empty handler */
void Dummy_Handler(void)
{
#if defined DEBUG
  __BKPT(3);
#endif
  for (;;) { }
}
...
void TC4_Handler ( void ) __attribute__ ((weak, alias "Dummy_Handler")));
...

デフォルトのTC4割込ハンドラは Dummpy_Handlerという無限ループのハンドラが設定されている。 weak属性が付いているので、 TC4_Handlerという 関数を定義すれば呼び出される。

void TC4_Handler(void){
  uint8_t flags = TCx->COUNT32.INTFLAG.reg;
  if (flags & TC_INTFLAG_MC0) {
    ... 割込処理 ...
  }
  // 割込フラグクリア
  TCx->COUNT32.INTFLAG.reg = 0xff;
}

VSCodeのエラーを消す

VSCodeでArduinoのソースを編集していると intellisenseで大量のエラーが表示される。 今までは無視してきたのだが、 今回 エラーを消すべく設定を調べてみた。

intellisenseの設定は プロジェクトのフォルダーの .vscode/c_cpp_properties.json を用意すれば良いのだが、 自分で最初から書くのは大変なので VSCodeにArduino拡張を利用する。 拡張機能タブでArduinoを検索すると 3つぐらい見つかるので、その中の Microsoft製のものをインストールする。

インストール後、コマンドパレットで Arduino:initializeを実行。 Boardで Seeduino XIAOを選択すると .vscode/c_cpp_properties.json が生成されるが、 まだ膨大にエラーが表示される。

修正が必要なのは defines と includePath。 definesの参考になるのが Seeeduino Arduinoの boards.txtファイル。 これに

seeed_XIAO_m0.build.extra_flags= -DARDUINO_SAMD_ZERO -D__SAMD21__ -D__SAMD21G18A__ -DARM_MATH_CM0PLUS -DSEEED_XIAO_M0 {build.usb_flags}
という行がある。 あと、足りないincludePathを追加し 以下の設定でエラーが無くなった。

{
    "env": {
        "users": "C:/Users/ユーザ名",
        "ulib": "${users}/Documents/Arduino/libraries",
        "seeeduinoDir": "${users}/AppData/Local/Arduino15/packages/Seeeduino",
        "samd": "${seeeduinoDir}/hardware/samd/1.7.6"
    },
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${ulib}/Adafruit_SSD1306",
                "${ulib}/Adafruit_GFX_Library",
                "${samd}/cores/arduino",
                "${samd}/libraries/SPI",
                "${samd}/libraries/WIRE",
                "${samd}/libraries/Adafruit_ZeroDMA",
                "${samd}/libraries/TimerTCC0",
                "${samd}/variants/XIAO_m0",
                "${samd}/**",
                "${seeeduinoDir}/tools/**"
            ],
            "forcedInclude": [
                "${samd}/cores/arduino/Arduino.h"
            ],
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "${seeeduinoDir}/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gcc.exe",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "defines": [
                "ARDUINO=10813",
                "ARDUINO_SAMD_ZERO",
                "__SAMD21__",
                "__SAMD21G18A__",
                "ARM_MATH_CM0_PLUS",
                "SEEED_XIAO_M0"
            ]
        }
    ],
    "version": 4
}

includePathで、 ** でサブディレクトリとして 指定してあるのに個別のディレクトリを指定している 部分がある。これは個別指定の方を削除すると エラーになってしまうので仕方なく入れている。

intelliSenseModeとcompilerpathも デフォルトから変えている。 これはデフォルトのままだと pinMode()が未定義というようなエラーが 出るためで、原因はわからない。 intellisenseは謎が多い。

次は

HDDの出力をPWMで制御し、 回転数も検出できるようになった。 次は、フィードバックをかけて 定速制御を行えばいいのだが、 フィードバック・ゲインをどう決めるかが問題。

いつもなら、実験しながら適当に決めてしまうのだが、 ちゃんと特性を計測し、極配置とかで設計してやるのも 面白いかもとか思っている。 どうなることか。



「ESP8266のntpの設定は1行で」更新

「ESP8266のntpの設定は1行で」 というのは 3年前にQiitaに投稿した記事なのだが、 地味にLGTM(Look Good to Me,旧like)を 貰い続けている記事の1つ。

その記事に引数の値がおかしい、と コメントがついた。 サンプルプログラム通りだと 時間が18時間遅れるらしい。

調べると esp8266/arduinoの versionのせいらしい。 2.6.3までは正常に動くが 2.7.0以降はおかしくなる。 configTime()関数の 第1引数の符号の解釈が 逆になってしまったらしい。

これが一時的なバグなのか、 恒久的な仕様変更なのかわからないと configTime()関数は使用しづらい。 幸い 2.7.0以降 configTzTime()という 関数が追加されており、TimeZoneを 文字列("JST-9"とか)で指定する。 これなら、将来 仕様が変わることも ないだろうということで、 Qiitaの記事を更新した。

Arudioでプログラムしていると ソースが全部 githubで見れるのは いいのだが、みんな容赦なく 更新してくるので、公開したプログラム でコンパイルエラーが出る、などという目に 割とよく合う。 プログラム公開時には、動作確認した ライブラリのversionを明示しておいた方が良い。 問題発生時、ライブラリのversionを指定のものに 戻して下さいと言える。



USB-I2C基板 (ソフトウェア編)

USB-I2C基板はpythonから使いたい。 pythonでI2C経由で機器を操作する プログラムを作りたい。 MCP2221 Pythonで検索すると いろいろ情報が見つかる。


PyMCP2221Aを試す

PyMCP2221Aというライブラリを試す。 MCP2221Aの機能(ADC,DAC,GPIO,CLK,I2C)を全て サポートしているようだ。

I2C接続のセンサーを試すと、簡単に値の読み取りなど できるようになった。もう少し派手なデモも欲しいので、 I2C接続のOLEDディスプレイ(128x64モノクロ)も試すが、 うまくいかない。 調べると PyMCP2221Aは 60byteまでの書き込みにしか対応していないことが判明。 そこで 60byte超のデータにも対応できるよう改造してみるが うまくいかない。

Circuit Python Libraryを試す

Adafruitも MCP2221AのUSB-I2Cボードを出しているので、 それ用のPythonライブラリを試してみる。 すると描画はできるが、繰り返すとエラーになる。 試したプログラムを以下に示す。

import os
os.environ['BLINKA_MCP2221'] = '1'

from board import SCL, SDA
import busio
import adafruit_ssd1306
from random import randint

i2c = busio.I2C(SCL, SDA)
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)
oled.fill(0)
for i in range(1000):
    print(i)
    x0 = randint(0,128)
    y0 = randint(0,64)
    x1 = randint(0,128)
    y1 = randint(0,64)
    oled.line(x0,y0,x1,y1,1)
    oled.show()

実行結果も下に示す。

$ C:/Users/nari/AppData/Local/Programs/Python/Python36-32/python.exe c:/01proj/v385_USB_I2C/python/blinka_test.py
0
1
 ... 中略 ...
86
87
Traceback (most recent call last):
  File "c:/01proj/v385_USB_I2C/python/blinka_test.py", line 19, in 
    oled.show()
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_ssd1306.py", line 185, in show
    self.write_framebuf()
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_ssd1306.py", line 232, in write_framebuf
    self.i2c_device.write(self.buffer)
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_bus_device\i2c_device.py", line 104, in write
    self.i2c.writeto(self.device_address, buf, start=start, end=end, stop=stop)
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\busio.py", line 94, in writeto
    address, memoryview(buffer)[start:end], stop=stop
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_blinka\microcontroller\mcp2221\i2c.py", line 19, in writeto
    self._mcp2221.i2c_writeto(address, buffer, start=start, end=end)
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_blinka\microcontroller\mcp2221\mcp2221.py", line 298, in i2c_writeto
    self._i2c_write(0x90, address, buffer, start, end)
  File "C:\Users\nari\AppData\Local\Programs\Python\Python36-32\lib\site-packages\adafruit_blinka\microcontroller\mcp2221\mcp2221.py", line 202, in _i2c_write
    raise RuntimeError("Unrecoverable I2C state failure")
RuntimeError: Unrecoverable I2C state failure

$

その他の試み

MicroChip社提供の MCP2111A用DLLを Pythonから使ってみようとするが、 うまくいかず、 Linux用DriverをRaspberry Piで試すが OLEDへの表示はできない。

現象としては、MCP2221Aへ書き込み(USBパケットの送出)を 繰り返していると、コマンドが受け入れられませんでした という状態になり、リトライを繰り返すうち、 復旧不可能な状態になってしまう感じ。

結論

MCP2221のI2Cは、短いパケットであれば 使えているようであるが、 (現状のソフトウェアでは?) 長いデータの書き込み などを安定して行えないようである。

自分の用途では、短いデータのやりとりで 充分な気もするが、動作のわけのわからない 感じが気持ち悪いし USB-I2Cの転送速度の遅さも気に入らないので MCP2221Aの使用は諦めることにする。

次は FT232Hを試そうかと思う。 これだと I2Cに加えてSPIも使用可能。 Adafruitの奴はカッコいいのだが、 カッコ良すぎて実験用にピンヘッダをハンダ付け するのもためらわれてしまう。 秋月の奴だと気軽に実験できそう。



LED版RssDispも更新

昨日更新した OLED版のRSS表示プログラムの調子は良いが、 LED版の方は調子が悪い。 調子が悪くて、しばらく消していたのだが、 RSS表示はともかく、時計が見易く慣れてしまっていたので LED表示も復活させたい。 というわけでLED版も更新することにした。 ソースを調べるとLED版はOLED版のソースを流用したものだったので、 変更箇所が同じで、簡単に更新できた。

これでhttpsのRSSが読み込めるようになったので、 読み込み先も更新するのだが、 なんかYahoo!ニュース - RSSにあるものから適当に選べば十分ではないか、 という気がしてきた。そうすれば、このサイトのfingerprintだけ あれば十分なので更新も年1回すれば済む。

ということで、 このページから、ネットやTVであまり見かけなさそうな ニュースソースを意識しながら、海外ニュース(BBC,CNN)とか、 ローカルニュース(熊本日日新聞)とか、お笑いナタリーとかを選ぶ。

しかし このページは、何のために存在しているのだろうか? RSSでニュースを読む人のために存在しているの? よくわからない。

更新したプログラムを githubにアップロードした。 これは LCDマトリックスコントローラ用の Arduino Library, Humblesoft_LedMatの サンプルプログラムの一つで esp8266(ESP-WROOM-02)上で動作する。 この コントローラ LEDマトリックス・モジュール 5V1Aの電源 があれば動作させることができる。



RssDispを更新

OLEDの寿命で取り上げた RSSを表示するプログラム: RssDispの表示が 調子悪くなってしまった。 原因の1つは、httpでRSSを提供するサイトが減ってしまったことで、 もう一つは Make:の RSSの反応が遅くなってしまったことらしい。 リクエストを出して10秒ぐらい返事が帰ってこない。

OLEDに表示されたRSSを真剣に読んでいるわけではないが、 正常に表示されないと気持ち悪いので プログラムを修正することにした。

改良点は、以下の通り。

  • httpsサイトへの対応
  • サイトごとのRSS最大表示数の設定
  • その他、表示が不自然なところを減らす

esp8266 Arduinoだと、 httpsサイトへのアクセスも可能だが、 サイトの指紋/fingerprintも指定してやらなければならない。 このfingerprintはchrome等でアクセスし証明書を表示させれば わかるのだが、証明書が変わるたび更新しないといけない。 ということで、これまでhttpsへの対応は行っていなかった。

以前は、httpで提供されているRSSも多かったのだが、 殆どのサイトでhttpsでしかRSSが提供されなくなってきた。 Yahoo ニュースのRSS も httpsでしか提供されない。

ということで、とりあえず fingerprintで httpsへの 接続を行うことにした。

サイトごとの最大表示記事数は、 Make:のRSSが50記事あり、 かなり古い記事まで含んでいるので、その対策。

その他の表示の改良は、RSSの取得に時間がかかる場合、 表示が止まっていたのを、それなりの表示が継続するするよう 改造した。

今回の改良で、以前のように 多くのサイトのRSSを表示できるようになり満足している。 次は LEDに表示している奴 にも同様の改良を施したい。 その次に、fingerprintを使わない httpsへの接続の実験か。



2GTプーリー形状のベジェ曲線化

タイミングベルト2GT用のプーリーを3Dプリンタで 出力できるようになったが、 頂点多すぎる問題が気になって 他の作業が進まない。 仕方が無いので、 プーリー形状のベジェ曲線化に取り組むことにする。

点列をベジェ曲線に変換する方法を ネットで検索するが、簡単には見つからない。 TensorFlowを使う方法 最小二乗法を使う方法 (媒介変数tの値が解れば最小2乗法で解けるらしい) などに翻弄されつつ、 stack overflow 経由で 1990年に出版された本 Graphic Gems のプログラム FitCurves.c に辿り着く。 ソースも github で公開されている。 コンパイルすると、ちゃんと動いた。

そのままでは使いづらいので、自分でpython化した。 できあがったのが こちら。 numpyを使ったので、割とコンパクトになったのでは なかろうか。 点列をベジェ曲線の制御点列に変換する。 なかなか便利な気がする。他の用途にも使えそうだ。

出来上がったプログラムは githubに公開している。 今回のプログラムでは、 ベジェ曲線化するため、 なるべく単純な曲線を扱いたかったので、 プーリー形状が歯ごとにおなじ形の繰り返しであり、 その形も左右対称であるので、歯の半分の形状を生成、 それをベジェ曲線化、さらに倍にしたのち歯数分コピー という手順で作成した。 このため曲線の数は歯数の2倍の数の倍数となる。 例えば20歯のプーリーの曲線数は40の倍数になる。

以下にベジェ曲線化した歯型と元の歯型を比較した図を示す。 ベジェ曲線化の程度は、許容誤差(-eオプションで指定)で変化する。 許容誤差を大きくしても曲線の数は40より小さくはならない。 見た感じでは、許容誤差0.05mm, 曲線数80ぐらいあれば充分のような 気がする。 今回のプログラムでは、処理が複雑になるので、オフセットの設定は できなくなっている。

生成したsvgファイルをfusion360に挿入すると、 以前のものより処理が軽い。ほとんど問題にならない感じである。 20歯、40歯、60歯のプーリーを試作し、タイミングベルトとの噛み合わせ を確認したが、問題は無かった。

プログラムの残りの課題は、前述のオフセット処理と高速化である。 そのうち、気が向いたらやってみたい。



タイミング・ベルト用プーリーの製作

前回シンクロベルトを購入したと書いたが、 タイミング・ベルトというのが正しいのだろうか? ミスミの分類ではタイミングペルトで、 シンクロベルトはバンドー化学の商品名か?という気もする。 今後はタイミングペルトと書くことにする。

タイミングペルトがあれば、ギアというか歯付きプーリーも必要になる。 とりあえず手持ちで20歯のものが4個あるが、3Dプリンタで自由に作れると大変嬉しい。

ギアのように、生成プラグインや歯型生成の方法があれば いいのだが見つからない。ベルトの形状はわかっているので、 多角形で近似して、多角形同士の論理演算のライブラリがあれば、 多角形でプーリーの形状が得られるのではないか?と フリーの多角形論理演算ライブラリを探すとあった。

複数見つかった中 Angus jhonson's Clipper library には pythonバインディングがある。 サンプルプログラムを試したら動いたので、 これを使う。

まず、タイミングベルト2GTの形状を作成する。 2GT 図面で検索すると いろいろみつかるが、 ミスミのページの図面がわかりやすかったので、 これを元に製図し、必要な座標、角度等を求め、 形状生成のプログラムを作成した。 次にピッチ円状に沿ってベルトが回転しながら、 ピッチ円からベルト形状を切り取ることで プーリーの歯型を生成するプログラムを 作成した。 SVGファイルが出力される。 プログラムは github で公開している。

生成したSVGファイルをFusion360で読み込み、 赤い棒が10mmであることを確認。 直径5mmの穴を追加し、押出でプーリーを試作。 歯型を生成する際、オフセットを指定し、 指定分だけ内側に縮めて作成できる。 オフセットなし、0.1mm, 0.2mm, 0.3mmの4種類で 20歯のプーリーを印刷してみた。 ベルトと噛み合せた結果 オフセットなしが最も良好。 0.1mmも噛み合うが、0.2mm, 0.3mmは隙間が大きくて 噛み合わないことがわかった。

次に、どれくらい小さいプーリーも使えるか 調べるため、オフセット無しで 16歯、14歯、12歯、10歯 のプーリーを印刷。 実際に使えるかどうかは、わからないが 10歯までベルトと噛み合わせることができた。

ここまでの試作品は、形状や噛み合わせをみるために ツバが無いものだが、実用上はベルトが外れないよう ツバが必要だ。ツバ有りのものも試作した。 サポート有りで1体で印刷できた。サポート材が 歯面を乱すこともない。

次はベルトとプーリーを使って動くものを作りたい。 あと、現状のプーリー形状はなかなか重い。 20歯で1000頂点を超える。Fusion360での押出処理も重い。 歯数が多くなると、もっと重くなるので、 頂点を減らす処理も検討したい。



srcPrint

テキストファイルをpdfに変換するプログラムを 作成した。名前はsrcPrint。プログラムのソースファイルを 印刷する時に使うやつ。 昔あった a2psみたいなやつ。 GitHubで公開中

プログラムをしていると、時々欲しくなる。 a2psやa2pdfなどや フリーソフトを探してみたりして、 なんとかしてきたが、 今回 electron と typescript の練習を兼ねて、 作ってみた。

electronを使っているので、テキストファイルは htmlに変換され、 cssでフォーマットを指定、 electronの機能でpdfに変換される。

現状、最低限の機能のみだが、 自分用としては、十分使える。 気が向いたら、機能拡張したい。



Doxygen再び

今、マイコンで動くテキスト・エディタを作っている。 まだ、Linux上で開発している段階だが、 ある程度できたので、 ここらでプログラムの整理をすることにする。

関数をグループごとに整理し、必要ならば名前も付け替えたい。 ここで Doxygenを使ってみたら便利だった。

まず、ファイルごとに関数の一覧を出してくれる。 これだけで便利だ。


次に、Doxygenの生成するhtmlを見ていると、 これを、もっと良いものにしてやろうという気持ちが起こる。 しかし、無駄に説明を追加しがちにもなるので、注意も必要だ。

あと、 graphvizも使うと、関数の呼び出し関係図を表示してくれて 興味深い。


マイコン用のテキスト・エディタは RAMの使用量が数Kbyte程度で、 sdカード上のファイルを編集するもの。 画面は ANSIエスケープ・コードで制御する。

現在プログラムは、1800行程度で、大体の機能は実装できた。 キーバインドはemacs風。C-w (delete-region)とC-y(yank)も 実装した。 undoは未実装。編集可能なファイルのサイズに 制限は無い(はず、 2Gまで?、処理速度は知らない)

使用目的は、マイコン・システムの開発支援。 sdメモリカードは便利で、開発するマイコンシステムで 頻繁に使用するが、メモリーカードの内容を書き換えるのに PCに差し替えるのが面倒。 開発中にデバッグ用のコンソールから書き換えられたら便利だろう、 というのが開発の動機。

マイコン(STM32F2XX)への移植は、これからだが、 うまくいくと良いなぁ。



Raspberry Pi用OLED ライブラリを作った

最終的に作ったライブラリは こちら

ラズパイ・ファン基板で OLEDを取り付け易くしたのだけれど、 i2c接続なので表示速度はどうなのだろうというのが気になっていた。

というのは、i2cは転送速度が速くない。 ラズパイのデフォルトでは100kbps。 OLEDは 128x64 dot なので、128x64 = 8kbit。 これを100kbpsで転送すると、転送時間は 8kbit ÷ 100kbps = 0.08sec 連続表示させると 毎秒 1 ÷ 0.08 = 12.5 フレーム表示できることになる。 スムーズな動画表示には少し遅い。転送以外の処理の時間も必要。

実際のところはどうなのだろうと測ってみることにした。

テストプログラム

使用したライブラリは Adafruit_Python_SSD1306。 グラフィック・ライブラリ Pillowのimageのデータを、そのまま表示できる 使い易いライブラリだ。 以下のプログラムを動かしてみた。

import Adafruit_SSD1306,time
from PIL import Image,ImageDraw,ImageFont
disp = Adafruit_SSD1306.SSD1306_128_64(rst=None,i2c_address=0x3C)
disp.begin()
image = Image.new('1',(disp.width,disp.height))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(
    font='/usr/share/fonts/truetype/freefont/FreeMono.ttf',
    size=50)
t0 = time.time()
draw.text((0, 0), 'Test', font=font, fill=1)
t1 = time.time()
disp.image(image)
t2 = time.time()
disp.display()
t3 = time.time()
print('draw text : %f' % (t1-t0))
print("disp image: %f" % (t2-t1))
print("display   : %f" % (t3-t2))

実行結果は次のようになった。

pi@raspberrypi$ python speed_test.py
draw text : 0.001905
disp image: 0.033468
display   : 0.116709
pi@raspberrypi$

データ転送に 0.116秒かかっているが、計算の 0.08秒とは さほど違わない。次に /boot/config.txtに以下の行を追加後 rebootし、 i2cを400kbpsにして試してみる。

dtparam=i2c_baudrate=400000

実行結果

pi@raspberrypi$ python speed_test.py
draw text : 0.002004
disp image: 0.033668
display   : 0.036361
pi@raspberrypi$

データ転送は期待通り約4倍高速化されたが、 disp imageの 0.033秒が大きい。 これはPillowのImageのデータをOLEDのバッファの フォーマットに変換する処理で、ソースを見ると Pythonで1bitづつ処理している。 この処理に時間が掛るとデータ転送を 早くしても、表示はあまり速くならない。

ライブラリ作成

ということで、 ラズパイ i2c接続OLED用に Pythonのライブラリを作ってしまった。 githubで公開している。

i2cの操作は直接 /dev/i2c-1とかをオープンして行っている。 ここの資料を見ながらプログラムしたら、 特に問題もなく出来た。

テストプログラムで確認

新しいテストプログラムで処理時間を調る。 テストプログラムを新しいライブラリ用に若干修正した。

import time
from PIL import Image,ImageDraw,ImageFont
from RaspiOled import oled
oled.begin()
image = Image.new('1',oled.size)
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(
    font='/usr/share/fonts/truetype/freefont/FreeMono.ttf',
    size=50)
t0 = time.time()
draw.text((0, 0), 'Test', font=font, fill=1)
t1 = time.time()
oled.image(image)
t2 = time.time()
oled.vsync()
t3 = time.time()
print('draw text : %f' % (t1-t0))
print("disp image: %f" % (t2-t1))
print("sync      : %f" % (t3-t2))

実行結果は次のようになった。 ちなみにi2sの速度は400kbps

pi@raspberrypi$ python3 speed_test2.py
draw text : 0.001897
disp image: 0.000538
sync      : 0.023852
pi@raspberrypi$

OLEDバッファへの書き込み(disp image)は0.0005秒になり、 データ転送も 0.023秒と高速化された。

で、やっぱり Bad Apple

ここまでやると、当然やるのはBad Apple。 Bad Appleの動画をffmpegでフレームごとに bmpのファイルに変換し、 画像表示プログラム( image.py)で 再生している。今回は音もwavファイルに変換したものを ラズパイのaplayコマンドで同時に再生している。 ちなみに 画像表示プログラムとaplayを同時に走らせると、 画像が遅れるので、sleepコマンドで1.6秒後にaplayを起動している。

RssDisp

動画 BadAppleを再生できるのは良いのだが、まだなんか不満。 Oledでグリグリヌメヌメ表示できることを示したい。 HSES-NODE-OLEDで動かした RssDispを動かしたい。

で、実装したのだが、予想外に手間がかかった。 文字のスクロール表示は、当初Pillow側でやろうかと思って いたのだが、あまり効率よくできそうな気がせず、 ライブラリに表示イメージをずらす機能を追加。 更にテキストをスクロール表示させるクラスも ライブラリに追加。 これでやっと RSSの表示プログラム rssDisp.pyが完成した。

HSES-NODE-OLED版とほぼ同じ動作が実現できた。 こちらの方が https サイトにも対応しているので、 表示できるRSSサイトが多い。 ESP8266/Arduinoでも httpsサイトへのアクセスはできるのだが、fingerprintを 入力する必要があり、面倒なので使っていない。

でもまぁ、HSES-NODE-OLEDの方が表示しっぱなしでも 惜しくないので良いかもしれない。 HSES-NODE-OLEDは スイッチサイエンスで発売中



VS Codeでstm32のプログラミング

windows上のemacsでIMEを上手くコントロールできないため、 VisualStudio Code(以下VS Codeと記す)の環境整備を頑張った所、 かなり使えるようになってきた。 これで移行できるかもしれない。

Windows上の上のemacsといえば、 昔はmeadowとか使って不満は無かったのだが、 更新されなくなり、 gnupack の emacs だとIMEパッチが当たっていて 良いのだが、 makeコマンドが無い? 導入方法がわからないため、 導入が楽なcygwinのemasを使用し、 日本語はだましだまし使っていた。

今回、stm32のプログラムを開発するに当たって、 日本語のメモをたくさん書いていきたいと考えたため emacsに我慢できなくなり、vscodeの 環境構築にトライしてみた。 electronのプログラムで VS Codeに慣れてきたというのもある。

makeの実行

まず、vscodeからmakeコマンドの起動。 タスクの構成で、 tasks.jsonファイルを 以下のように設定することで何とかなった。

    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "make",
            "type": "shell",
            "command": "/c/MinGW/msys/1.0/bin/make.exe write ",
            "options": {
                "cwd": "${workspaceFolder}/src"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "new",
                "showReuseMessage": true
            },
            "problemMatcher": {
                "owner": "gcc",
                "fileLocation": [
                    "relative",
                    "${workspaceRoot}/src"
                ],
                "pattern": {
                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(.*):\\s+(.*)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "endLine": 2,
                    "endColumn": 3,
                    "severity": 4,
                    "message": 5
                }
            }
        }
    ]
}

problemMatcherも定義しているので、 コンパイル時のエラーも「問題」パネルを 開けば、クリック一発で問題箇所を開くことができる。

emacsのnext-errorコマンドみたいに、 1コマンドで「問題」パネルを開き、最初の問題を選択してくれると 嬉しいのだが、そのようなコマンドを未だ発見できていない。

プログラムの自動フォーマット

keybinds.jsonで ctrl+I を押した時 editor.action.formatが呼び出され インデントの修正等、自動で行われるようにしているのだが、 Cのプログラムを編集しているときctrl+Iを押しても、 Cのフォーマッタが無いというようなエラーが表示されるだけで、 フォーマットされない。

そこで、 拡張機能 Microsoft C/C++ for VS Codeを導入。 無事、フォーマットされるようになった。

includePathの設定

しかし、使っていると includePathの設定がされていない、 というメッセージが出るようになる。 面倒で無視していたのだが、 C/C++拡張機能のお蔭で、高度な機能(intelliSenseとか 定義に移動とか)使えるようなので、 真面目に設定してみた所、 設定ファイルc_cpp_properties.jsonは 次のようになった。

{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "C:\\cygwin64\\usr\\include",
                "C:\\cygwin64\\usr\\include\\w32api",
                "C:\\gnu_tools_arm_embedded\\6-2017-q2-update\\arm-none-eabi\\include",
                "C:\\gnu_tools_arm_embedded\\6-2017-q2-update\\lib\\gcc\\arm-none-eabi\\6.3.1\\include",
                "${workspaceFolder}\\src"
            ],
            "defines": ["__CYGWIN__"],
            "intelliSenseMode": "clang-x64",
            "compilerPath": "/c/gnu_tools_arm_embedded/6-2017-q2-update/bin/arm-none-eabi-gcc.exe",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

これで、だいたい、使えるようになったのだが、まだ問題がある。 なんか includePathに定義していないPath C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\includeが含まれているようなのである。 stdint.hで「定義へ移動」とすると、このフォルダーのstdint.hが 表示されてしまう。これはどうしたら良いのだろうか。



Electronの本 読了

Electronの本を読了した。

掲載されているコードを律儀に入力し、 動作させながら読んでいったので、 時間はかかったが、 ある程度、知識が身についた気がする。

コードを本の通りに入力しても、 1文字でも間違えれば、動作しない。 コンソールを開ければ、例外のログとかが見れて、 大体理由がわかるのだが、 devToolを開けるのも 大変な場面が多々あり、なかなか大変だった。

あと main process側のメッセージは devToolのコンソールには 表示されないのかな? この辺の話は、まだ十分理解できていない。 こういうデバッグの話も詳しく説明してくれると、 もっといい本になるのではないかと思う。

今後は、独自プログラムの開発を行いつつ、 いろんなライブラリを試し、 Electronの技術を身に着けて行きたい。



Electronの本

Electronを覚えたいと、 正月にも書いているのだが、 一向に使えるようにならない。

ネットでElectron入門的な記事を探し、 試しているのだが、周辺の知識が 足りなすぎて、勉強が進まない。

本でも買うかとAmazonで検索すると、 Electronとタイトルにある本は2冊しかない。

1冊は あなたにとってElectronを学ぶ価値があるかどうか、この本を読んで判断してみてくださいで、kindle本。 41ページで99円。

とりあえず、これを読んでみて 2冊めのElectronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろうのレビューを見ると、 入門書ではなさそう。なんか良さそうに思えたので中古を購入。

で、届いて読んでいるのだが、結構良さそうだ。 この本に従って、アプリ開発を試してみよう。



電光掲示板プログラムを公開

HSES-LMC1の秋月電子での発売に合わせて、 電光掲示板プログラムを GitHubで公開した。

これは、HSES-LMC1を一般(?)の電光掲示板として動作 させるためのプログラム。

表示内容をSDカードに記憶し、読み出しながら表示する。 Web経由で表示内容の編集ができる。 (使用可能なブラウザは chrome のみ)

インストール方法・使い方は GitHubのWikiに書いたので 一度、見てみて欲しい。

表示できるメッセージは、

  • スタティック・テキスト: 文字列を内蔵Fontで描画、動きなし
  • スクロール・テキスト:文字列をスクロール表示
  • 画像(静止、スクロールとも)
  • 動画(未リリース)

などがあるが、使い方を説明しているのは、上2つまで。 画像はリリースしているが、説明が未だ。 動画は、完成しているが、変換プログラム(FFmpeg)の 説明を検討中で未リリース。

あと、メッセージをzipファイルでアップロード/ダウンロード する機能もある。

今はブラウザ・Javscriptの各種ライブラリが凄いので、 Webインターフェースをつけると、高機能なUIを 簡単に作れてしまう。



esp8266/Arduinoを 2.3.0に戻す

以前いじっていた esp8266/Arduinoのプログラムを make ota で書き込もうとするがエラーになる。 ArduinoOTAのexampleの BasicOTA で試すと書き込めるので、サイズの問題か? OTAが使えないと、とてもプログラムを開発する気にならない。 困った。

2.3.0のころは書けていたので、2.4.1から戻してみることにする。 ボードマネージャで戻す方法はよくわからなかったので、 git で esp8266/Arduinoをインストールし直す。 ちなみにgit clone では -b オプションで バージョン(tag)を 指定できる。

Arduino-IDEからは、すぐに使用できるようになったが makefileからだとエラーになる。 いろいろ調べた結果、git でインストールした際、 テキストファイルの改行コードが変換されていたことが 原因だと判明。 boards.txt platform.txtの改行が CRLFになってしまうと、 makefileでの処理中にCRコードが残り、 エラーになってしまうようだ。

これで makefileから OTAで書き込めるようになった。 2.4.1でうまくいかない理由は調べていない。 ArduinoOTAについても、いつか、調べないとなぁ。



Raspberry Piで動画をLEDに音付きで再生

hzeller/rpi-rgb-led-matrix付属の 動画再生プログラム video-viewerを改造し、 動画ファイルを音つきで再生できるようになった。 前にも書いたが フレームの遅れが蓄積されない ように改造した。 変更点は、pull-requestを投げたいが、 まだ、ちょっと汚いのが悩みどころ。

音の再生は別プログラム(aplay)で 行っている。これも将来的には組込みたい。

いつもの MMD Bad Apple!! Now in 3D with more Color を再生したのが下の動画。

プログラムの改造は簡単だったが、 動作の確認のためRaspberry-Piから 音を出すのに手間取った。

まず hzeller/rpi-rgb-led-matrixを動かすには /boot/config.txtで dtparam=audio=off しなければいけないので、 Raspberry Piのaudio出力は使用できなし、 HDMIへの音声出力も使えなくなる。

USB-speakerやbluetoothへの出力は可能なので、 まず手持ちのbluetoothスピーカで試すと、 音は出たが、厳しい条件の動画(60fpsのやつとか)を 再生させると音が出なくなり、復旧できなくて あきらめた。

USBスピーカを購入し試すと、音は出るが 最大音量で、音量調節が効かない。スピーカ側に ボリュームやミュートのスイッチはあるのだが、 どうもソフトウェアでコントロールするタイプのようで、 Windowsでは機能したがRaspberry Piでは動かない。 amixerコマンドで音量調節を試みるが、 あまり音量が変化しなかったり、無音になったりと 極端でうまく音量調節できない。 無音に鳴る寸前の数値で、ある程度音量調整できたので、 そこで動画を撮影した。 アナログのボリュームつまみがついたUSBスピーカが欲しい。



Raspberry Pi Zero WH 動作テスト

miniHDMI変換アダプタとUSB-microB OTGケーブル が届いたので、 Raspberry Pi Zero WHを動作させてみる。

Zeroは基板は極小だが、ケーブルを刺すと そうでもなくなる。 ケーブルを ケーブル・オーガナイザで固定してやると良い感じである。

Raspbianをダウンロード、 microSDカードに書込み 立ち上げたら、特に問題もなく起動。 キーボード、WiFiの設定を行い、 普通に使えるようになった。

その後、 hzeller/rpi-rgb-led-matrixをインストールし、 LEDマトリックスモジュールへの表示を試す。

表示はできるが、負荷が重い感じ。 動画の再生を行うと、はっきりと遅い。 やはり コア4個のRasberry Pi 3と比べると、 コア1個のZeroだと、余裕が無い感じだ。 表示も、多少、ちらつきが感じられる。



ESP32_bt_speaker 更新

ESP32をBluetoothスピーカーにするプログラム ESP32_bt_speakerを更新した。

作成後、esp-idfが、かなり更新されたので、 いろいろと問題があるだろうなとは思っていたのだが、 手を付けずにいたのだが、 githubにissueついたので、 対応することにした。

最新の esp-idf環境でESP32_bt_speakerをコンパイルしてみるとエラーが沢山発生。 ひとつづつ対応するのも大変なんで、元となったサンプルプログラム bluetooth/a2dp_sinkから再び作り直すつもりで、a2dp_sinkを コンパイルしてみると、I2Sと内蔵DACへの出力機能が 追加されていることが判明した。

これが動くのであれば私のESP32_bt_speakerは、お役御免。 今後、私が面倒を見なくても良くなる、と思って プログラムをESP32開発ボードに書き込むも 音が出る様子は無い。

わたしのプログラムと見比べながら、修正と試行を繰り返し、 音が出るようになった。I2S出力側は、実験できないので わからないが、内蔵DAC出力側は、テストされていないような 感じがする。

修正は pull requestを出して esp-idf側に反映してもらいたい ところだが、やり方がよくわからないので、 とりあえず ESP32_bt_speaker側を更新しておいた。

変更点を以下に示すので、これを見て a2dp_sinkの ソースを変更してもらっても良いと思う。

diff --git a/examples/bluetooth/a2dp_sink/main/bt_app_av.c b/examples/bluetooth/a2dp_sink/main/bt_app_av.c
index 289c1f16..4958e3d9 100644
--- a/examples/bluetooth/a2dp_sink/main/bt_app_av.c
+++ b/examples/bluetooth/a2dp_sink/main/bt_app_av.c
@@ -53,7 +53,27 @@ void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
 
 void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
 {
-    i2s_write_bytes(0, (const char *)data, len, portMAX_DELAY);
+    TickType_t delay = 50 / portTICK_PERIOD_MS;
+    if(len % 8){
+      ESP_LOGE(BT_AV_TAG,"unexpected data len:%u",len);
+      return;
+    }
+
+    for(int i=0; i<len; i+= 4){
+      uint16_t d[2];
+
+      d[0] = data[i+0]|(data[i+1] << 8);
+      d[0] ^= (1 << 11);
+      d[0] <<= 4;
+      d[1] = data[i+2]|(data[i+3] << 8);
+      d[1] ^= (1 << 11);
+      d[1] <<= 4;
+    
+      int n = i2s_push_sample(0, (const char *)d, delay);
+      if(n < 0)
+	ESP_LOGE(BT_AV_TAG, "i2s_write_bytes error:%d",n);
+    }
+    
     if (++m_pkt_cnt % 100 == 0) {
         ESP_LOGI(BT_AV_TAG, "Audio packet count %u", m_pkt_cnt);
     }
diff --git a/examples/bluetooth/a2dp_sink/main/main.c b/examples/bluetooth/a2dp_sink/main/main.c
index a6093386..30ee28a2 100644
--- a/examples/bluetooth/a2dp_sink/main/main.c
+++ b/examples/bluetooth/a2dp_sink/main/main.c
@@ -54,14 +54,14 @@ void app_main()
 
     i2s_config_t i2s_config = {
 #ifdef CONFIG_A2DP_SINK_OUTPUT_INTERNAL_DAC
-        .mode = I2S_MODE_DAC_BUILT_IN,
+        .mode = I2S_MODE_DAC_BUILT_IN | I2S_MODE_MASTER | I2S_MODE_TX,
 #else
         .mode = I2S_MODE_MASTER | I2S_MODE_TX,                                  // Only TX
 #endif
         .sample_rate = 44100,
         .bits_per_sample = 16,                                              
         .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                           //2-channels
-        .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
+        .communication_format = I2S_COMM_FORMAT_I2S_MSB,
         .dma_buf_count = 6,
         .dma_buf_len = 60,                                                      //
         .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1                                //Interrupt level 1

このプログラムには、まだ問題が残されている。 1つは音量を上げた時、音割れする問題で、 もう一つは 通信を中断させた場合DMAバッファに 音データが残り、鳴り続けてしまう問題である。

音割れの方は、DACが8bit, Bluetooth上のデータも12bitなので、 ボリュームを上げていけば音割れするのは仕方ないような 気もするが、 iPhone単体や、他のbluetoothスピーカーで試すと ボリュームを上げても音割れしない。

データ操作のバグも疑い、いろいろ試すがわからない。 仕様なのではないかという気もするのだが、 どうなのだろう。

切断後、音が鳴る問題は、音の出力先を切り替えるなどすると、 発生させることができる。通信の切断の検出はできるので、 ここでDMAバッファをクリアする処理を書いてみたのだが うまくいかなかった。やりかたがまずかったのかもしれない。

Bluetoothについての知識が乏しいので、 ちゃんと対策しようとすると、 なかなか苦しい。