2020年8月2日日曜日

Raspberry pi 2とFreeBSDで7セグLED時計を作る - プログラミング編


の続きです。

前回まででRaspberry pi 2のFreeBSDの設定は、ハード・ソフトともに終了しました。
最後は、時計のプログラムを書いて終了です。
はじめはSPIを使って制御しようと思ったのですが、1つしかないSPIを使うのは勿体無いので、GPIOを4つ使うことにしました。

GPIOの制御はgpioctlを使ってもできるのですが、シェルスクリプトで時計を書くのはちょっと面倒なので、C言語で書くことにします。

C言語でのGPIOの制御の仕方は、gpio(3)のmanを見ると良いと思います。

サンプルコードはこんな感じです。
コンパイルには、libgpio.hとgpio.cが必要です。
FreeBSDのソースツリーから持ってくるか、以下から入手してください。


clock.c

#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

#include "libgpio.h"

#define CLOCK_PIN_MOSI  (17)
#define CLOCK_PIN_SCLK  (27)
#define CLOCK_PIN_LATCH (23)
#define CLOCK_PIN_BLINK (24)

#define CLOCK_INTERRUPTION (20)

unsigned char iTable[256];

void CLOCK_initTable(void)
{
    for(int tCount1=0;tCount1<256;tCount1++)
    {
        iTable[tCount1]=0x00;
    }

    iTable['0']=0x3f;
    iTable['1']=0x06;
    iTable['2']=0x5b;
    iTable['3']=0x4f;
    iTable['4']=0x66;
    iTable['5']=0x6d;
    iTable['6']=0x7d;
    iTable['7']=0x27;
    iTable['8']=0x7f;
    iTable['9']=0x6f;
    iTable['C']=0x39;
    iTable['E']=0x79;
    iTable['G']=0x3d;
    iTable['H']=0x76;
    iTable['I']=0x06;
    iTable['J']=0x1e;
    iTable['L']=0x38;
    iTable['O']=0x3f;
    iTable['P']=0x73;
    iTable['T']=0x31;
    iTable['U']=0x3e;
    iTable['Y']=0x66;
    iTable['a']=0x77;
    iTable['b']=0x7c;
    iTable['c']=0x58;
    iTable['d']=0x5e;
    iTable['e']=0x7b;
    iTable['f']=0x71;
    iTable['g']=0x6f;
    iTable['h']=0x74;
    iTable['i']=0x05;
    iTable['j']=0x0d;
    iTable['l']=0x18;
    iTable['n']=0x54;
    iTable['o']=0x5c;
    iTable['p']=0x73;
    iTable['q']=0x67;
    iTable['r']=0x50;
    iTable['s']=0x6d;
    iTable['t']=0x78;
    iTable['u']=0x1c;
    iTable['y']=0x6e;
    iTable['=']=0x09;
    iTable['-']=0x40;
}

void CLOCK_sendChar(gpio_handle_t aHandle,unsigned char aChar)
{
    for(int tCount1=0;tCount1<8;tCount1++)
    {
        gpio_pin_low(aHandle,CLOCK_PIN_SCLK);

        if(iTable[aChar]&(0x80>>tCount1))
        {
            gpio_pin_high(aHandle,CLOCK_PIN_MOSI);
        }
        else
        {
            gpio_pin_low(aHandle,CLOCK_PIN_MOSI);
        }

        gpio_pin_high(aHandle,CLOCK_PIN_SCLK);
    }
}

void CLOCK_reflect(gpio_handle_t aHandle)
{
    gpio_pin_high(aHandle,CLOCK_PIN_LATCH);
    gpio_pin_low(aHandle,CLOCK_PIN_LATCH);
}

volatile sig_atomic_t iEFlag=0;

void signal_handler(int aSig, siginfo_t *aInfo, void *aCtx)
{
    iEFlag=1;
}

int main(void)
{
    gpio_handle_t tHandle;
    char tDateString[64];
    char tDateSecond;
    time_t tEpocMinites;
    struct timeval tCurrentTime;
    int tToggleCount;
    long tInterruption;

    struct sigaction tSASigAbrt,tSASigTerm;

    memset(&tSASigAbrt,0,sizeof(tSASigAbrt));
    memset(&tSASigTerm,0,sizeof(tSASigTerm));

    tSASigAbrt.sa_sigaction=signal_handler;
    tSASigAbrt.sa_flags=SA_SIGINFO;

    tSASigTerm.sa_sigaction=signal_handler;
    tSASigTerm.sa_flags=SA_SIGINFO;

    if(sigaction(SIGINT,&tSASigAbrt,NULL)<0)
    {
        exit(1);
    }

    if(sigaction(SIGTERM,&tSASigTerm,NULL)<0)
    {
        exit(1);
    }

    CLOCK_initTable();

    tHandle=gpio_open(0);

    gpio_pin_output(tHandle,CLOCK_PIN_MOSI);
    gpio_pin_output(tHandle,CLOCK_PIN_SCLK);
    gpio_pin_output(tHandle,CLOCK_PIN_LATCH);
    gpio_pin_output(tHandle,CLOCK_PIN_BLINK);

    gpio_pin_low(tHandle,CLOCK_PIN_MOSI);
    gpio_pin_low(tHandle,CLOCK_PIN_SCLK);
    gpio_pin_low(tHandle,CLOCK_PIN_LATCH);
    gpio_pin_low(tHandle,CLOCK_PIN_BLINK);

    tInterruption=(1000000/CLOCK_INTERRUPTION);
    tToggleCount=0;

    tDateSecond=0x00;

    while(1)
    {
        gettimeofday(&tCurrentTime,NULL);

        tEpocMinites=(time_t)tCurrentTime.tv_sec;

        strftime(tDateString,sizeof(tDateString),"%H%M%S",localtime(&tEpocMinites));

        if(tDateSecond!=tDateString[5])
        {
            tDateSecond=tDateString[5];

            if(tDateString[0]=='0')
            {
                tDateString[0]=0x00;
            }

            for(int tCount1=0;tCount1<6;tCount1++)
            {
                CLOCK_sendChar(tHandle,tDateString[tCount1]);
            }

            CLOCK_reflect(tHandle);

            gpio_pin_high(tHandle,CLOCK_PIN_BLINK);
            tToggleCount=0;
        }

        if(tToggleCount==(CLOCK_INTERRUPTION/2))
        {
            gpio_pin_low(tHandle,CLOCK_PIN_BLINK);
        }

        tToggleCount++;

        usleep(tInterruption);

        if(iEFlag)
        {
            for(int tCount1=0;tCount1<6;tCount1++)
            {
                CLOCK_sendChar(tHandle,0x00);
            }

            CLOCK_reflect(tHandle);

            gpio_pin_low(tHandle,CLOCK_PIN_MOSI);
            gpio_pin_low(tHandle,CLOCK_PIN_SCLK);
            gpio_pin_low(tHandle,CLOCK_PIN_LATCH);
            gpio_pin_low(tHandle,CLOCK_PIN_BLINK);

            gpio_close(tHandle);

            exit(1);           
        }

    }

    gpio_close(tHandle);

    return 0;
}

冒頭のdefineで設定しているのは、GPIOのピン番号です。
物理ピン番号ではなく、論理ピン番号(=BCMピン番号)ですので注意してください。

冒頭に制作したLED時計との接続は、以下の通りです。

Vcc       ----  物理ピン 4番
SDI       ----  物理ピン11番(GPIO 17)
CLK      ----  物理ピン13番(GPIO 27)
BLK      ----  物理ピン16番(GPIO 23)
LATCH ----  物理ピン18番(GPIO 24)
GND     ----  物理ピン 6番


このプログラムでは、論理ピン番号17、27のGPIOで、SPIをエミュレートしています。

大したことをしていないプログラムですので、読めばわかると思いますが、比較的わかりにくい部分だけ解説すると、

iTable                           7セグLED用のフォントデータ
CLOCK_sendChar   CLKに8クロックを出力して1文字分のコードを送る関数
CLOCK_reflect          LATCHに立ち上がりクロックを出力して、シフトレジスタに蓄積された文字を7セグLEDに反映させる関数

です。

mainでは、1/CLOCK_INTERRUPTION秒毎に現在の時間を取得し、前回の取得時から1秒進んでいた場合に、時計の再描画を掛けています。

やる気のないビルド用スクリプトもつけておきますので、これを使ってビルドしてください。

build.sh

#!/bin/sh

rm *.o

cc -c gpio.c
cc -c main.c
cc -o clock gpio.o main.o

特に問題なければ、clockという実行ファイルができるので、これを実行すれば7セグLED時計は動作するはずです。

2020年7月31日金曜日

Raspberry pi 2とFreeBSDで7セグLED時計を作る - ソフトウェア設定編



の続きです。

Raspberry pi 2にインストールした後の設定方法は、一般的なi386/AMD64のFreeBSDとほぼ同じです。

rootのパスワードの変更は以下のコマンドで行います。

passwd

Raspberry pi 2版のFreeBSDでは、予めfreebsdというユーザが作られているので、必要に応じてrmuserで削除します。

rmuser freebsd

私の環境では、/etc/rc.confに以下の記述を追記しています。

Raspberry piには標準でRTCが搭載されていない為、再起動するたびに時間がクリアされてしまうため、起動毎にntpdateで時計合わせをするようにしています。
また、標準のkeypmapはUS配列ですので、これを106キー日本語配列に設定しています。

keymap="jp.kbd"
ntpdate_enable="YES"
ntpdate_flags="-b NTPサーバのIPアドレス"

タイムゾーンをJSTに変更するには、以下のようにします。

cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

これぐらいしておけば、あとはほぼ一般的なi386/AMD64版のFreeBSDと同じように使うことができると思います。

2020年7月30日木曜日

Raspberry pi 2とFreeBSDで7セグLED時計を作る - ハードウェア設定編


の続きです。

FreeBSDをインストールしただけでは、Raspberry pi 2が持っている性能や機能をすべて使うことはできません。
いくつかの設定/チューニングが必要です。

まず、RPI2版のFreeBSDでは、デフォルトのCPUクロックが600MHzと低く設定されている為、これを標準の900MHzに設定します。
「/boot/msdos/config.txt」に以下の記述を追加します。

force_turbo=1
arm_freq=900
arm_freq_min=600

また、RPI2版のFreeBSDは、標準の状態ではGPIOは有効化されていますが、I2CやSPIは無効化されている為、必要に応じてこれを有効化します。
I2Cを有効にするには、config.txtに以下の記述を追加します。

dtparam=i2c=on

SPIを有効にするには、/boot/loader.confに以下の記述を追加します。

fdt_overlays="spigen-rpi2.dtbo"

I2CおよびSPIが有効されているかは、再起動後、以下のようにして確認してください。

dmesg | grep iic
dmesg | grep spigen

何らかの表示がされればOKです。

2020年7月27日月曜日

Raspberry pi 2とFreeBSDで7セグLED時計を作る - OSインストール編

Raspberry pi 2とFreeBSDで7セグLED時計を作る - はじめに
Raspberry pi 2とFreeBSDで7セグLED時計を作る - ハードウェア制作

の続きです。

前回までで、ハードウェアの制作が終わりましたので、次にRaspberry pi 2にFreeBSDをインストールしたいと思います。

先に言っておきますが、Raspberry piで手っ取り早く色々やろうとするならば、公式のOSであるRaspbianを使うのが圧倒的に便利です。
インターネット上に情報も多いですし、書籍もたくさん出ています。
どうしてもFreeBSDじゃなきゃ嫌、という人でない限り、Raspbianか他のLinuxディストリビューションを使うことをおすすめします。

Raspberry pi向けのFreeBSDですが、私の知る限りここに情報が集まっています。

https://wiki.freebsd.org/arm/Raspberry%20Pi

2020/07/25現在、FreeBSDでまともに使える/使えそうなRaspberry piは、

Raspberry pi B/B+
Raspberry pi 2
Raspberry pi 3
Raspberry pi Zero(W含む)

です、またRaspberry pi 3およびZero WのWi-Fi機能はFreeBSDでは使えません。
(Wi-Fi機能を提供しているICはSDIO接続で、FreeBSDがSDIOをまともにサポートしていないのが原因です。)

このページでは、私の手元にあるRaspberry pi 2の情報を中心にまとめていきたいと思います。

Raspberry pi 2をFreeBSDで使うには、本体にあったOSイメージをダウンロードしてSDカードに焼く必要があります。

OSイメージですが、FreeBSD 11ではRaspberry pi B/B+および2向けが、FreeBSD 12以降ではB/B+、2、3向けが提供されています。
Raspberry pi 2はarmv7でビルドされていて32bitをターゲットとしています。

Raspberry piに対応したFreeBSDは、以下からダウンロードできます。

https://www.freebsd.org/ja/where.html

RPI-B、RPI2、RPI3がそれぞれRaspberry pi B/B+、2、3に対応したSDイメージになります。

FreeBSDにおいてarm版はTier-2ですので、freebsd-updateが利用できません。
RELEASE版よりも、最新のSTABLE版をダウンロードして利用することをオススメします。
(常に最新の機能を追いかけるのならば、CURRENT版を使って下さい。)

FreeBSD-12.1-STABLE-arm-armv7-RPI2-202XXXXX-rXXXXXX.img.xz

xzアーカイブを解凍してimgファイルにします。
xzコマンドで展開できます。

xz -dv FreeBSD-12.1-STABLE-arm-armv7-RPI2-202XXXXX-rXXXXXX.img.xz

以下のようにしてSD/microSDカードに書き込みます。
(「da0」はご利用の環境によって変わります、dmesg等でSD/microSDカードに割り当てられるデバイス名を確認してから指定してください。)

dd if='PATH TO IMG FILE` of=/dev/da0 bs=16M

作成したSD/microSDカードをRaspberry pi本体にセットして電源を入れれば、FreeBSDがブートするはずです。

尚、rootのデフォルトのパスワードはrootになっています。

2020年7月26日日曜日

ルーターとは別にDHCPサーバーとDNSサーバを立てると吉


家で8年ほど使っていたルータが故障しました。

BHR-4GRVという機種に、DD-WRTをインストールして使っていたのですが、ある日突然リンクしなくなり、HUBとしてすら使えなくなりました。
簡易DNSやヘアピンNATができて重宝していたのですが…でもまあ、長持ちした方かも、今までありがとう。

DD-WRTは高機能なのですが、IPv6パススルーやPPPoEマルチセッションができなかったりします。
正確にいえば、CUIで色々やればできるのですが、GUIで設定できて使えるほどには整理されていません。
この2つの機能のどちらかがないと、フレッツのサービス情報サイトにアクセスできず、回線認証をするフレッツメンバーズクラブのポイント交換ができません。

今後、IPv6化もしたいので、DD-WRTとはここでお別れし、通常の市販ルータに置き換えたいと思います。

それにしても、ルータが壊れるのは悲劇ですね。
インターネットにアクセスできなくなるだけではなく、DHCPでIPアドレスも振られなくなるし、内部の名前も引けなくなるので、一瞬パニックになりました。
今後、ルータが壊れた時に、内部で最低限のことができるように、DHCPと内向きのDNSは別の機器で分けるようにしておきたいと思います。

まず、最初にルータ本体の置き換えから。
最近の大抵の機種では、IPv6パススルーやPPPoEマルチセッションはできるようです。
一方、簡易DNSは高級な機種のみにしかなく、ヘアピンNATはその機能の有無がカタログにすら載っていないことが多いです。
このため、簡易DNSとヘアピンNATをルータ本体で賄わない、と割り切ることにしました。

首都圏の市街地では2.4GHz帯は非常に混雑しており、我が家の周辺でも同様のため、11aに対応したモデルを選び、普通に設定して使いはじめました。
これでとりあえずはインターネットへのアクセスは確保できました。

次に内向きのDNSについて。
イントラ内のクライアントが少ない内向けのDNSの構築には、dnsmasqが向いています。
また、dnsmasqにはDHCP機能もあるので、最終的にDHCPもdnsmasqで賄い、ルータのDHCPを無効にすると良いと思います。

幸いにして、我が家には7セグメントLED時計を表示させるために電源は入りっぱなしのRaspberry pi 2があるので、これを流用することにしました。
このFreeBSDがインストールされたRaspberry pi 2に、portsnapを利用してdnsmasqをインストールします。

portsnap fetch extract
cd /usr/ports/dns/dnsmasq
make install clean

4コアといえども非力なARMに加え、SDカードの読み書きが遅いため、かなり時間がかかるので注意してください。
また、途中でビルドのオプションを問うダイアログがいくつも表示されるので、いちいちOKをしていく必要があります。

インストールが完了したら、「/usr/local/etc/dnsmasq.conf」をこんな感じで編集します。

# global settings

domain-needed
bogus-priv
expand-hosts
domain=local.kishiro.com
listen-address=127.0.0.1,192.168.0.6

# DHCP settings

dhcp-range=192.168.0.128,192.168.0.233,720m
dhcp-leasefile=/tmp/dnsmasq.leases
dhcp-option=option:router,192.168.0.1
dhcp-option=option:netmask,255.255.255.0
dhcp-option=option:dns-server,192.168.0.6
dhcp-option=option:ntp-server,192.168.0.6

# DNS

address=/www.kishiro.com/192.168.0.26

address=/router.local.kishiro.com/192.168.0.1
    :
    :
    :

「192.168.0.6」はRaspberry pi 2に割り当てられているIPアドレスで、「local.kishiro.com」はイントラ内のドメイン名です。

「www.kishiro.com」は外部に公開しているwwwサーバで、外部DNSではルーターのWAN側のIPアドレスが引かれるように設定しています。
また、ルーター側の設定で、WAN側の80/443番ポートへのアクセスをLAN側の「192.168.0.26」にポートフォワードするようにしています。

置き換えた新しいルータは、案の定ヘアピンNATが使えない(内部ネットワークからwww.kishiro.comに接続してもエラーになる)機種でしたので、dnsmasqの設定に以下の一行を加えています。

address=/www.kishiro.com/192.168.0.26

これにより、内部ネットワークから「www.kishiro.com」の名前解決を試みたときに、外のDNSに問い合わせに行く前にdnsmasqがローカルIPアドレスの方を回答します。
結果、パケットがWAN側に出ていかず、折り返しが発生しなくなるわけですね。

長くなりましたが、これでルータが壊れたとしても、Raspberry pi 2さえ動いていれば、dnsmasqによる内部のネットワークのIPアドレス払い出しと、名前解決が担保されるようになりました。
Raspberry pi 2のほうが壊れにくいかは謎ですが。

そして何より、今後、高級なルータを買わなくてもすむようになりました。

皆様もよろしければお試しください。

2020年7月12日日曜日

Linuxで使える軽量のイメージビューア「gPicView」

普段メインで使っているPCには、Xubuntuをインストールしています。
Xubuntuでは、xfceプロジェクトがメンテしているRistrettoって画像ビューアが標準で付属しています。
このRistrettoはよくできているんですが、イメージファイルを開くときに同じフォルダの中に入っている全てのイメージファイルをスキャンするようで、イメージファイルがたくさん入っているディレクトリ、特にネットワークドライブ上のイメージファイルを開くと、やたらと時間がかかります。
色々設定を吟味してみましたが、この動作を抑止するような方法が見つかりませんでした。

ということで、他のビュアーを色々と吟味してみましたが、GPicViewが一番いい感じです。

apt install gpicview

でインストールできますので、同じ現象でお困りの方はお試し下さい。

2020年7月11日土曜日

コマンドラインでmp3のv1、v2ヘッダを除去する

わが家では、FreeBSDをインストールした共有サーバにmp3ファイルを置いて、samba経由でPCやスマホなど色々な機器で再生していますが、mp3ファイルに勝手にヘッダをつける行儀の悪いアプリケーションが多く、難儀しています。
アルバム名や曲名は全てファイル名で管理しているので、v1ヘッダとかv2ヘッダは不要です。

意図せずヘッダがつけられてしまったファイルに対し、一括でヘッダを除去したいと思い、調べてみましたが、mp3のv1ヘッダとv2ヘッダの両方を、コマンドラインで除去できるid3v2というものが提供されているようです。

早速試してみたいと思います。

以下のようにしてインストールします。

pkg install id3v2

ヘッダを除去するには、以下のようにすればよいです。

id3v2 -D filename.mp3

id3v2単体でもワイルドカードが使えるようですが、特定のフォルダ下のmp3のヘッダを全て除去するには、findを組み合わせて以下のようにすればいいです。

find /path/to/mp3 -type f -iname "*.mp3" -exec id3v2 -D {} \;

スッキリしました。