「ソフトウェアー・エンジニアリング」カテゴリーアーカイブ

ソフトウェアー関係の話題など・・

最近は、github にプッシュしてます~

R8Cを使ってRCサーボを動かす

ホビーでよく使うアイテムとして、ラジコン用のサーボモーターがある。

R8C/M120AN で動かしてみたので、簡単に、解説する。

まず、テスト用にアマゾンで、一番安いサーボモーター買ってみた。
IMG_0768s

HKSCM9-6 RCサーボ(ホビーキング製)
コネクタタイプ:JR
GND:茶
+V: 赤
信号: 橙
最大定格: 6V

※このサーボは意外と小さくて、そんなに大きいトルクを出せないが、テスト用に丁度良い。
※もちろん、他のサーボでもかまわない。
※フタバタイプでは、微妙に信号の仕様が異なるので、調整が必要。
※R8Cを5Vで駆動

ラジコン用サーボを動かす信号は、メーカーにより微妙に異なるが、ほぼ同一で、PWM で、
パルス幅と角度が比例関係にあり、JR系では、

周期: 50Hz(20ms)
ニュートラル: 1500uS(マイクロ秒)
※フタバタイプでは、1520uS(マイクロ秒)となっている。
可動範囲: +-600uS、900uS~2100uS(マイクロ秒)

となっているようだが、周期については、ある程度許容範囲があるようだ、当然ながら、
周期を短くした方が、応答は良くなるものと思われる。

・R8CのCチャネルで、20MHzを8分周すると、50000カウントで、20mSで、
50Hzとなり、16ビットの範囲に収まる。

    bool pfl = 0;  // 0->1
    uint8_t ir_level = 2;
    timer_c_.start_pwm(50000, trc_type::divide::f8, pfl, ir_level);

・ニュートラルは、1500uSなので、3750となる。
・可動範囲は+-600uSなので、+-1500となる。
・PWM出力をP1_2(18):TRCIOB、P1_3(17)TRCIOCにして、
2個のRCサーボを別々に動かす。
※TRCIODを使えば、3個まで駆動できる。

サンプルプログラムでは、AN0、AN1でA/D変換した結果(0~1023)を、使って
サーボの可動範囲±1500(±600uS)になるようにしている。

IMG_0769s

プロジェクトのソースコード

FFmpeg ライブラリーを使った動画のデコード(C++ソースコード)

最近、色々忙しくなってきて、週末は時間が何かと足りなくなってます・・

では、早速本題!
FFmpeg はオープンソースで、動画や音声のエンコード、デコードなどを行うツール
です、非常に多くの人が改善してきた事で、非常に高い品質と、機能が実装されて
います。

また、このプロジェクトはマルチプラットホームなので、多くの環境で同じように
使う事が出来ます。
「FFmpeg」コマンドを使って動画をエンコードしたりする話は、既に沢山の方が書
かれています、今回の話は、「FFmpeg」が利用しているライブラリー郡を使って、
C++ のプログラムから、動画をデコードしてみようという内容です。

(1)準備
・まず、FFmpeg コマンドをインストールします。
※MSYS2 MinGW-w64 で、話を進めますが、OS-X の BREW などでも同じように出来ま
す。

pacman -S mingw-w64-x86_64-ffmpeg

この操作だけで、ffmpeg 他必要なライブラリーなど全てインストールされます。

(2)実装
・動画の中の、特定の1フレームをデコードするサンプル

・まず、必要そうなヘッダーをインクルード

#include <iostream>
#include <string>
extern "C" {
	#include <libavcodec/avcodec.h>
	#include <libavfilter/avfilter.h>
	#include <libavformat/avformat.h>
	#include <libswscale/swscale.h>
};

※これら、API のプロトタイプでは、全てC言語のみが想定されている為、C++
から使う場合は「extern "C"」が必要です。

・初期化

    avcodec_register_all();
    av_register_all();
    avfilter_register_all();

※FFmpeg 関係お約束の呼び出しですかねぇ・・

・ビデオファイルを開く


    std::string file_name = argv[1];
    AVFormatContext* pFormatCtx = NULL;
    if(avformat_open_input(&pFormatCtx, file_name.c_str(), NULL, NULL) != 0) {
        std::cerr << "ERROR: avformat_open_input(): '" << file_name << '\'' << std::endl;
        return -1;
    }

・ストリーム情報の取得と表示

    if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        std::cerr << "ERROR: avformat_find_stream_info(): '" << file_name << '\'' << std::endl;
        return -1;
    }

    // ストリーム情報の表示
    av_dump_format(pFormatCtx, 0, file_name.c_str(), 0);
    fflush(stderr);

・コーデックの検索とコーデックのオープン

    AVCodecContext* pCodecCtx = pFormatCtx->streams[0]->codec;
    AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    // コーデックを開く
    if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        std::cerr << "ERROR: avcodec_open2(): '" << file_name << '\'' << std::endl;
        return -1;
    }

・ログ・レベルの設定

    av_log_set_level(1);

※デコードの過程で、非常に細かく、内部の軽微な問題を報告してくれるので、それをカット!

・バッファー関係の確保と必要な初期化

    // フレームの確保
    AVFrame* pFrame = avcodec_alloc_frame();
    // イメージの確保
    AVFrame* pImage = avcodec_alloc_frame();

    // イメージ用バッファの確保
    unsigned char* buffer = (unsigned char *)av_malloc(avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height));
    // バッファとフレームを関連付ける
    avpicture_fill((AVPicture*)pImage, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);

・スケーリング用コンテキストの取得

    struct SwsContext* pSWSCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height
        , pCodecCtx->pix_fmt
        , pCodecCtx->width, pCodecCtx->height
        , PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);

・メイン

    int count = 0;
    AVPacket packet;

    // フレーム読み込み
    while(av_read_frame(pFormatCtx, &packet) >= 0) {

        // デコード
        int state;
        avcodec_decode_video2(pCodecCtx, pFrame, &state, &packet);
 
        // 各フレーム、デコード完了
        if(state) {
            // 特定のフレームを切り出し
            if(count == 400) {
                int bsize = pCodecCtx->width * pCodecCtx->height * 3;
                sws_scale(pSWSCtx, (const uint8_t **)pFrame->data
                    , pFrame->linesize, 0 , pCodecCtx->height
                    , pImage->data, pImage->linesize);

                 FILE *fp = fopen("rgb24.raw", "wb");
                 if(fp) {
                     std::cout << "FrameSize: " << (int)pCodecCtx->width << ", " << (int)pCodecCtx->height << std::endl;
                     fwrite(buffer, bsize, 1, fp);
                     fclose(fp);
                 }
             }
             ++count;
        }
        // パケット・メモリ解放
        av_free_packet(&packet);
    }
    std::cout << "Total frame: " << count << std::endl;

※この場合、400フレーム目を「RGB24」で「RAW」ファイルとして書き出します。

・メモリ解放

    sws_freeContext(pSWSCtx);
    av_free(buffer);
    av_free(pImage);
    av_free(pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

・コンパイルと実行

g++ -c -O2 -std=c++14 -DHAVE_STDINT_H -DWIN32 -DNDEBUG -isystem /mingw64/include -Wall -Werror -Wno-deprecated-declarations -o release/decode.o decode.cpp
g++ -L/mingw64/lib release/decode.o -lpthread -lavdevice -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil -lz -o ffdecode_test.exe

ffdecode_test test.mp4

※適当な動画を食わせれば、400フレーム目をデコードして、RGB24ビットで、ファイル「rgb24.raw」を書き出します。

decode.cpp

R8C SDカードを使ったWAVファイルの再生

以前にSDカードをSPI接続で読み書きするテストを行ったが、パフォーマンスの評価
を行うのに、WAV形式の音楽を再生するのが丁度良いと思い実験してみた。

事前の懸念事項として:
・SDカードを読み書きするSPIポートは、完全なソフトウェアー仕様なので、あまり
速度が出ない。
※UARTと共有になっている同期式シリアル変換を使えば、速度は改善するものと思う
が、シリアル通信が使えなくなる為、今回は見送った。
・パラレル⇔シリアル変換では、C++ コンパイラの最適化度合いが非常に大きなファクタ
ーを占める。

以上の点を考慮して、コンパイルされたアセンブリコードを眺めながら、ソースコードに
修正を加えたりしながら、色々実験した。

8ビット、ステレオで、11.025KHz(44.1KHzの 1/4)くらいならギリギリ再生できる事が
判った。
これには、11.025KHz でサンプリングループを回す割り込みや、PWM で変換する為の処理
も含まれる。

再生させてみると、多少ノイズが乗る、PWM レジスターの書き換えは割り込みを使って行
っているものの、何かノイズが乗る要因があるものと思われるが、原因を究明できなかっ
たので、とりあえず、この状態で公開する。

タイマーCのPWMはB、Cチャネルを使っている。
それぞれ、ポートP12、P13から出力される。
これを、単純なRCフィルター(C:0.1uF、R:2200)に通している。
ライン出力のGNDは、VCCとGNDの中点(470オームで分圧)にしている。

IMG_0758

現在の実装では、「OHAYODEL.WAV」ファイルを再生するようになっている。
※再生するWAVファイルは、8ビット、ステレオ、11.025KHzにしておく必要がある。
※フォーマットが違う場合、エラーを出力する。
※再生開始時や終了時などにポップノイズが発生する場合があるので、ボリュームに注意する事。

SD_WAV_play

R8CタイマーJを使った周波数測定

前回からだいぶ間が空きましたが、パルス入力を使った、周波数測定を行ってみました。

タイマーJは、パルス出力と入力の機能があり、パルス幅測定、周期測定、パルスカウント
など沢山の機能があります。
今回は、周期測定機能を使って、周波数測定をしてみました。

サンプルなので、プログラムをあまり複雑にしない為、「単機能」で実装してありますが、
実用的な物にするには、カウンターがオーバーフローした場合にマスタークロックを切り
替えるとか、周波数が高い場合に、単位時間辺りのパルス数計測に切り替えるなどの工夫
を行う必要があると思います。

サンプルでは、シリアル接続したターミナルに、計測した結果を1秒毎に表示します。

ルネサス系では、タイマーJの構成で、良く出来ていると思うのは、割り込みを使わなく
ても、レジスターの状態が保持される為、簡単に厳密な計測が行えます。
このような、細かい点に配慮したハードウェアーの構成は、日本メーカーのマイコンな
らではと思います。

IMG_0757s

基本的な機能実装は、「common/trj_io.hpp」に集約させていますが、設定が複雑なので、
場合によっては、「trj_io」クラスの機能だけでは不足している場合もあると思います。

又、割り込みを使う場合は、パルス出力と入力で、登録する割り込み関数が違うので、
注意が必要です。

//-----------------------------------------------------------------//
/*!
    @brief  パルス計測の開始(TRJIO 端子から、パルスを入力)@n
            ※VCOUT1 端子から入力する場合は、TRJIOSEL を設定する。
    @param[in]  measur    パルス計測のモード
    @param[in]  s         クロック選択(measur::countの場合は無効)
    @param[in]  ir_lvl    割り込みレベル(0の場合割り込みを使用しない)
*/
//-----------------------------------------------------------------//
void pluse_inp(measurement measur, source s, uint8_t ir_lvl = 0) const;

「measurement」型として、

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  パルス計測モード
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class measurement : uint8_t {
    low_width,     ///< Low レベル幅測定
    high_width,    ///< High レベル幅測定
    count,         ///< パルス数測定
    freq,          ///< 周期測定
};

4つの測定モードがあります。

クロック選択では、

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  カウンターソース
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class source : uint8_t {
    f1 = 0,     ///< F_CLK
    f2 = 3,     ///< F_CLK / 2
    f8 = 1,     ///< F_CLK / 8
    fHOCO = 2   ///< fHOCO(高速オンチップオシレーター)
};

4つの中から選択が出来ます。

詳しい使い方は、「PLUSE_INP_test」を参照して下さい。

R8Cを使ったLCD表示、パルス出力

以前に作成した、パルス出力テスト、エンコーダー入力、LCD表示を組み合わせて
単独で動作するパルス出力を作成しました。

以前に作ったものを組み合わせるだけなので、簡単です。

LCD表示はビットマップ表示なので、適当なフォントを用意しました。
ビットマップ表示は、とても柔軟な表示が行えるのですが、自分で「絵」をデザイン
する必要があります。
そこで、以前にビットマップ表示に適した画像エディターを探しました、フリーで、
非常に使いやすいエディターがありましたので紹介しておきます。
※Windows版です、Mac版は、あまり良いものを探せませんでした。
EDGE
※ソフトは2009年が最終版ですが、機能は揃っており、不具合も無いようです。

「ドット絵エディタ」は256色まで対応ですが、モノクロの絵や、アイコンのよう
なグラフッックにはむしろ最適です。
グリッドを設定する機能もあり、任意サイズのグラフィックを描くのに便利です。

font32
上のように、1つのファイルにまとめて配置して、切り出して使います。

切り出して、ソースコードに埋め込むのに、bmc も作成してあります。
※このソフトは、かなり昔に実装して、少しづつ改変しています。
※コンパイルは大変なので、実行バイナリーを用意してあります。(build/bmc.exe)
※必要な DLL を用意してあります。
※コンパイルの方法は、「MSYS2 で俺俺フレームワーク、アプリをコンパイルする」を参照して下さい。
※Mac版は、brew を入れれば、Windowsと同じようにコンパイルできます。

bmc では、以下のように、切り出す位置とサイズを指定して、テキスト形式に変換
しています。
※bitmap/Makefile

    bmc -offset 0*20,0*32 -size 18,32 -header 8 -text -c_style nmb_0 font32.png font32.h
    bmc -offset 1*20,0*32 -size 18,32 -header 8 -text -c_style nmb_1 font32.png -append font32.h
    bmc -offset 2*20,0*32 -size 18,32 -header 8 -text -c_style nmb_2 font32.png -append font32.h
    bmc -offset 3*20,0*32 -size 18,32 -header 8 -text -c_style nmb_3 font32.png -append font32.h
    bmc -offset 4*20,0*32 -size 18,32 -header 8 -text -c_style nmb_4 font32.png -append font32.h
    bmc -offset 5*20,0*32 -size 18,32 -header 8 -text -c_style nmb_5 font32.png -append font32.h
    bmc -offset 6*20,0*32 -size 18,32 -header 8 -text -c_style nmb_6 font32.png -append font32.h
    bmc -offset 7*20,0*32 -size 18,32 -header 8 -text -c_style nmb_7 font32.png -append font32.h
    bmc -offset 8*20,0*32 -size 18,32 -header 8 -text -c_style nmb_8 font32.png -append font32.h
    bmc -offset 9*20,0*32 -size 18,32 -header 8 -text -c_style nmb_9 font32.png -append font32.h
    bmc -offset 3*20,1*32 -size 19,32 -header 8 -text -c_style txt_hz font32.png -append font32.h
    bmc -offset 2*20+9,1*32 -size 9,32 -header 8 -text -c_style txt_k font32.png -append font32.h


変換された形式は、ビット単位で、状態を表現したシンプルな構造で、common/monograph.hpp、
draw_mobj で描画する事が出来ます。

このソフトでは、色々なオプションを用意してあります。
・BDF 形式のフォントの読み出し。
・ディザリング機能
・GUI で確認する機能
など

BitMap Converter
Copyright (C) 2013/2015, Hiramatsu Kunihito
Version 0.75
usage:
    bmc.exe [options] in-file [out-file]
    -preview,-pre     preview image (OpenGL)
    -header size      output header
    -text             text base output
    -c_style symbol   C style table output
    -offset x,y       offset location
    -size x,y         clipping size
    -bdf              BDF file input
    -append           append file
    -inverse          inverse mono color
    -dither           ditherring
    -verbose          verbose

IMG_0756

さて、肝心な、パルス出力ですが、出力する周波数範囲は、かなり広いので、エンコーダーだけ
では可変範囲が広すぎます、そこで単位毎に自動でレンジを切り替えて、増減する量を動的に変
えています。
20Hz~99Hz 1単位
100Hz~999Hz 10単位
1000Hz~9999Hz 100単位
10000Hz~99999Hz 1000単位
100KHz~999KHz 10KHz単位
1000KHz~10000KHz 100KHz単位

PLUSE_OUT_LCD

R8C インターバルタイマーを使ったエンコーダー入力(スイッチ入力)

ロータリーエンコーダーを使った入力装置は、設定範囲が広く、厳密な値と、
広い範囲(レンジ)を網羅できる入力デバイスとして、組み込み機器では重宝
する入力デバイスです。
しかし、その入力を正しくエンコードして、読み取るには、多少の工夫が必要
です。

エンコーダー入力の前に、シンプルなスイッチ入力を考えてみます。
機械式接点では、チャタリングがあるので、それを除去する必要があります。
一つの方法として、サンプリング理論に沿ったデジタルフィルターを使って行
う方法があります。
これは、もっとも一般的な方法です。

サンプリング方式では、周期的に割り込みなどをかけて、入力信号を間欠的に
取り込みます。
サンプリング理論では、周期的に、状態を取り込む事で、ローパスフィルター
を通した信号とほぼ同じような状態となり、チャタリングの高い周波数を除去
したのと同じ効果となります。
サンプリング周期より速い(短い)信号は除去されます。
これにより、サンプリングするだけで、チャタリングを除去できます。

また、チャタリングが発生している期間は、機械的な構造による特性があり、
それにより周期を選ぶ必要があります。
通常、押しボタンスイッチのようなもので、4ms(ミリ秒)程度ですが、
接点の状態が悪いと、それより長くなったりするので、ある程度マージンを取
る必要があります。
通常のスイッチでは、一般的に、60Hz程度が良く選ばれます。

状態変化による、トリガーの生成:
サンプリングでは、別の要件を網羅できます、それは、「トリガー」入力の判断
です。
サンプリングした状態を1つ保持する事で、信号がどう変化したかを確認できま
す。
・前がOFF、今回がONなら、「立ち上がりエッジ」(押した瞬間)。
・間がON、今回がOFFなら、「立ち下がりエッジ」(離した瞬間)。

    uint8_t lvl = ~device::P1();  /// 状態を取得
    inp_pos_ = ~inp_lvl_ &  lvl;  /// 立ち上がりエッジ検出 
    inp_neg_ =  inp_lvl_ & ~lvl;  /// 立ち下がりエッジ検出
    inp_lvl_ = lvl;  ///< 状態を退避

・スイッチの状態表示

    if(inp_pos_ & device::P1.B0.b()) {
        sci_puts("SW0 - positive\n");
    }
    if(inp_pos_ & device::P1.B1.b()) {
        sci_puts("SW1 - positive\n");
    }

    if(inp_neg_ & device::P1.B0.b()) {
        sci_puts("SW0 - negative\n");
    }
    if(inp_neg_ & device::P1.B1.b()) {
        sci_puts("SW1 - negative\n");
    }

    if(inp_lvl_ & device::P1.B0.b()) {
        if((cnt % 10) == 0) {
	    sci_puts("SW0 - ON\n");
        }
    }
    if(inp_lvl_ & device::P1.B1.b()) {
        if((cnt % 10) == 0) {
            sci_puts("SW1 - ON\n");
        }
    }

SWITCH_test

では、次はエンコーダーのデコードです。
基本的に、エンコーダーのデコードはスイッチの入力と基本的に変わらない。
エンコードする方法は、デバイスの分解能などの物理的要因により、いくつ
かの方法があるのですが、今回1回転辺り24ステップ(クリック付き)
エンコーダーを使い、サンプリングによる方法を行ってみました。

機械式接点による、ロータリーエンコーダーの場合は、かなり短い周期でパルス
が発生する為、製品のスペックに合わせたサンプリング周期を設定する必要があ
ります。
エンコーダーの回転軸を速く回した場合にも追従出来るようにするには、ギリ
ギリまでサンプリング周波数を高くする必要があります。
このエンコーダーのチャタリングは3.5ms以下のようなので、240Hzを採用
しました。

IMG_0755

光学式や磁気式のエンコーダーで、より高分解の場合は、チャタリングが無いの
と、周波数が高いので、サンプリングとは別の方法が用いられますが、それは、
又、別の機会とします。

static uint8_t enc_lvl_ = 0;
static uint8_t enc_pos_ = 0;
static uint8_t enc_neg_ = 0;
static uint16_t enc_count_ = 0;

static void encoder_service_()
{
    uint8_t lvl = ~device::P1();  /// 状態の取得
    enc_pos_ = ~enc_lvl_ &  lvl;  /// 立ち上がりエッジ検出
    enc_neg_ =  enc_lvl_ & ~lvl;  /// 立ち下がりエッジ検出
    enc_lvl_ = lvl;  /// 状態のセーブ

    if(enc_pos_ & device::P1.B0.b()) {
        if(enc_lvl_ & device::P1.B1.b()) {
            --enc_count_;
        } else {
            ++enc_count_;
        }
    }
    if(enc_neg_ & device::P1.B0.b()) {
        if(enc_lvl_ & device::P1.B1.b()) {
            ++enc_count_;
        } else {
            --enc_count_;
        }
    }
    if(enc_pos_ & device::P1.B1.b()) {
        if(enc_lvl_ & device::P1.B0.b()) {
            ++enc_count_;
        } else {
            --enc_count_;
        }
    }
    if(enc_neg_ & device::P1.B1.b()) {
        if(enc_lvl_ & device::P1.B0.b()) {
            --enc_count_;
        } else {
            ++enc_count_;
        }
    }
}

A相、B相、の立ち上がり、立ち下がりを検出して、対になる「相」の
レベルにより、時計周り(CW)、反時計周り(CCW)を検出します。

ENCODER_test

R8C TRJ を使った、パルス出力

R8Cもかなり、色々なクラスが充実してきた、内臓デバイスの規模
が大きいと、デバイスドライバー的なクラスを十分用意するのは、大
変だけど、M1x系だと、適度な周辺デバイスなので、見通しも良く、
シンプルに作れるのが良いところだと感じている。
※RXマイコンとかだと、周辺機器が充実しすぎていて、しかも高機能
なので、実装するのもかなりボリュームが大きくなってしまう。


タイマーJは、主に、パルス出力と、周期計測、パルス幅の計測に、
適した構成のタイマーで、比較的良く使われる機能だと思う。

ルネサスの場合、この手のタイマーは、非常に構成が細かく、広い応用
範囲を網羅している。
ハードウェアマニュアルには、全体の構成が記されており、全体の構成
や設定要領を掴むのに便利です。
TRJ_Block

R8C 20MHz の場合、出力可能な周波数は、おおよそ、20Hz~10MHzくらい
になる、タイマーのダウンカウントで発生する「アンダーフロー」を
トリガーにして、フリップフロップでトグルする仕様なので、出力は、必
ずデューティ50%となり、最大クロックの1/2の周波数が最大となる。
設定レジスターは、周波数の逆数なので、高い周波数ほど、荒い設定しか
出来なくなる。
※実際には、低い周波数領域は、小数点以下の設定が可能なのだけど、
その仕様は割愛してある。

IMG_0753s
1KHzを出力した場合

R8C/PLUSE_OUT_test

MSYS2 で俺俺フレームワーク、アプリをコンパイルする

自前で作っている、glfw3 を使った OpenGL のフレームワーク、開発環境が、MacOS に
移って、Windows 環境は取り残されていたー。
しかしながら、最近(でもないけど)になって、MSYS2 環境が非常に良くなっているの
で、試しに、コンパイルしてみたので、インストール方法も含めて紹介する。

※基本的に、MSYS2 の clang であっても、std::thread に関連する「未実装」な部分
は残っているようで(これは gcc でも同じだと思う)、他プラットホームで実現出
来る機能が全て揃っている訳では無い事に注意する必要がある。
スレッドのモデルは、POSIX pthread で問題無いのだけど、C++ の「std::async、
std::future」系などは、リンクで失敗する。(何か別のライブラリーをリンクする必
要があるのかもしれないが・・)

まぁ、でも、これらは、かなり便利ではあるけれども、自分が色々なプラットフォーム
で調べた限りでは、とても使えないような実装になっていて、やはり他の方法で実現
する方が良いだろう、「未来」には、改善するかもしれない。
※これで、別スレッドが起動して、裏で動いてくれると思っている人が多いが、実際
の実装は、そうはなっていない。

まず、MSYS2 をインストールする。
※今回インストールに使ったのは「msys2-x86_64-20150512.exe」

一応起動を確認する。

MSYS2 には、3つの起動バッチがある。

mingw32_shell.bat
mingw64_shell.bat
msys_shell.bat

で、今回メインに使うのは「mingw64_shell.bat」で、他と何が違うかと言うと、64
ビット対応の実行ファイルに対するパスが標準となっているもので、基本コマンド以外
は、基本的に64ビット対応のパッケージをインストールして使う。

標準の状態で、既に「mintty」が動作しているようなので、ホームディレクトリーに
「.minttyrc」をコピーする事で、shell 窓が見やすくなる。

BoldAsFont=no
Font=Inconsolata
Locale=ja_JP
Charset=UTF-8
FontHeight=12
Columns=100
Rows=34
Transparency=medium
Term=xterm-256color
RightClickAction=paste
OpaqueWhenFocused=no
PgUpDnScroll=yes

※自分は、基本、UTF-8を使う。
「Inconsolata」は、OTF タイプのフォントで、別途インストールしておく。
このフォントは英字だけなので、日本語は、内部で自動で選択される、この厳密なやり
方は不明であるけれでも、多分、内部は、FreeType2 を使っているので、そのルールに
従っているものと思われる。
※このフォントは「俺俺フレームワーク」のWidgets マネージャーでも使っている。
自分の使っているのは以下。

msys2_con

自分は、テキストエディターはemacsを使っているが、パッケージ管理でemacsを入れる
事も出来るのだけど、パッケージ管理で取得できるバージョンは、日本語 IME のパッチ
が当たっていない版がインストールされるようなので、別途 ime パッチ済みの emacs
を入れて、パスを通しておく。
※パスは「.bash_profile」に追加した、またプロムプト表示は以下のようにした。

# emacs path 
export PATH=$PATH:/c/emacs/bin

# prompt
PS1='\h.\w % '

「.bashrc」にコメントアウトしてある箇所があるので、自分の嗜好に合わせて適宜コメント
を取り除く。

alias ls='ls -hF --color=tty'

さて、パッケージ管理コマンドを使って、最新の状態にアップデートしておく。
※このやり方は、みんなが同じようにやっている「まね」である。

$ pacman --needed -Sy bash pacman pacman-mirrors msys2-runtime

bash を起動しなおして、さらにアップデート。

$ pacman -Syu

一応 bash をもう一度起動しなおして。

まず、clang をインストール

$ pacman -S mingw-w64-x86_64-clang
$ clang --version
clang version 3.6.1 (tags/RELEASE_361/final)
Target: x86_64-w64-windows-gnu
Thread model: posix

※「x86_64」が64ビット対応版

unzip、make をインストール

$ pacman -S unzip
$ pacman -S make

続いて、俺俺フレームワークが使っている各種ライブラリーのインストール

$ pacman -S mingw-w64-x86_64-glfw
$ pacman -S mingw-w64-x86_64-glew
$ pacman -S mingw-w64-x86_64-freetype
$ pacman -S mingw-w64-x86_64-libjpeg-turbo
$ pacman -S mingw-w64-x86_64-openjpeg2
$ pacman -S mingw-w64-x86_64-libpng
$ pacmas -S mingw-w64-x86_64-libtiff
$ pacman -S mingw-w64-x86_64-openal
$ pacman -S mingw-w64-x86_64-libmad
$ pacman -S mingw-w64-x86_64-taglib
$ pacman -S mingw-w64-x86_64-faad2
$ pacman -S mingw-w64-x86_64-boost

※今までは、自分でソースを取ってきて、ビルドしていたので、凄く便利、この手順は
MacOS の brew とあまり変らない。

ここで、boost が「中る」・・・
紆余曲折色々回り道したが、Mingw64 環境では、コンパイル時に「-DBOOST_USE_WINDOWS_H」
を追加しないと駄目な事が判った・・・
※これは、windows.h (win32-api)と同居する場合に必要なオプションだと思う。

---------

これで、環境は整った、 GitHub/glfw3_app GitHub から、クローンするなり、アーカイブを取ってきて、
make すればOK!

一応、動作が怪しい部分があるけど、一応、ビルドも通って、動作するようになった!

現在、コンパイルして、動作を確認したアプリ:

spinv     ---> Space Invader エミュレーター(動作には ROM/SOUNDS ファイルが必要)
bmc       ---> ビットマップコンバーター
player    ---> 音楽プレイヤー
gui_test  ---> GUI テストサンプル
image     ---> 画像ビューアー
pmdv      ---> MMD ファイルビューアー
pn        ---> パーリンノイズ実験アプリ
effv      ---> Effekseer のビューアー

※ファイル選択などで、速い操作で誤動作して、実際にはパスが存在しない場所に、
行ってしまった場合は、「xxx.pre」ファイルを編集するか、消せば、やり直せる。

※「build」フォルダがあるアプリは、中に実行ファイルを置いてある。
また、動作に必要な DLL を libraries/dll に置いておいたので、コンパイル等
面倒で、アプリだけを動かしたい人は、各種 dll をアプリと同じ場所に置けば動く
と思う。

R8CでSDカード

さて、LCDも何とか動くようになったので、避けては通れないSDカードのアクセス
をやってみたいと思います。

先日 Flash が64Kある事が判ったので、RAM容量は心細いですが、
SDカードアクセスに不安はありません。
組み込みでSDカードと言ったら ChaN さんでしょうー、えるむさんが実装して、ソース
を公開しているSDカードのソリューションは非常に柔軟性があり、細部まで良く考えら
れており、組み込みマイコンにおけるSDカードスタンダードだと思います。
少ないリソースでも動作するように、プチ fatfs もあり、今回のR8Cに最適な
ソリューションです。(感激!感謝です!)

SDカードですが、FlashAir を使えるように、標準SDカードソケットを使います。
標準SDカードソケットなら、直接ユニバーサル基板にハンダ付けできますが、スイッチ
関係のパッドが狭く、その部分は、絶縁テープで保護して、直接配線します。
SDカードソケットは、ヒロセ製(DM1B-DSF-PEJ)を使いました。
このソケットは、価格は高いですが、機能と品質が非常に優れています。

事前に考えておかなければならない点として、ポートにどのように、各信号を割り当てる
かです。
まず・・
・「/RESET(3)」は、ポートにも割り当てが出来るけど、一応キープ。
・「MODE(8)」端子は当然のようにキープ。
・「TXD(16)、RXD(15)」もシリアル通信で必須なのでキープ
・「AN0(20)、AN1(19)、AN2(18)、AN3(17)」は、とりあえずキープ
・「XIN(6)」は、高精度なクロックを入れて使う場合があるのでキープ
・「TRJIO(13)」は、周期測定用としてキープ

// P4_2(1):   LCD_SCK  ,SD_CLK(5)
// P3_7(2):   LCD_/CS
// /RES(3):  (System reset)
// P4_7(4):            ,SD_DO/DAT0(7)
// VSS:(5)   (Power GND)
// P4_6(6):   XIN (高精度なクロック用)
// VCC(7):   (Power +V)
// MODE(8):  (System mode)
// P3_5(9):   I2C_SDA
// P3_4(10):           ,SD_/CS(1)
// P1_0(20):  AN0 (keep)
// P1_1(19):  AN1 (keep)
// P1_2(18):  AN2 (keep)
// P1_3(17):  AN3 (keep)
// P1_4(16):  TXD0
// P1_5(15):  RXD0
// P1_6(14):  LCD_A0 (share)
// P1_7(13):  TRJIO (keep)
// P4_5(12):  LCD_SDA  ,SD_DI/CMD(2)
// P3_3(11):  I2C_SCL

こんな感じになりました、これで、ほぼ全てのポートが埋まりました。
「P1_6(16)」は、Flash 書き込み時に使うので注意が必要です。
※無難な、LCD_A0(コマンド/データ切り替え信号)にしています。
※AN0、AN1、AN2、AN3 はアプリによって自由に使えるポートとしています。
IMG_0751s
※電源は3.3Vで動かします!
R8Cは3.3Vで動かしても、20MHzで動作します、フラッシュプログラムも問題なくできます。

ぷち FatFsはソースコードを展開して、pffconf.h で基本的な設定を行います。

/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/

#define	_USE_READ	1	/* Enable pf_read() function */
#define	_USE_DIR	1	/* Enable pf_opendir() and pf_readdir() function */
#define	_USE_LSEEK	1	/* Enable pf_lseek() function */
#define	_USE_WRITE	1	/* Enable pf_write() function */

#define _FS_FAT12	1	/* Enable FAT12 */
#define _FS_FAT16	1	/* Enable FAT16 */
#define _FS_FAT32	1	/* Enable FAT32 */

とりあえず、全て「1」で良いでしょう。
FAT12、FAT16 は「0」でも問題は少ないですが、古いSDカードは「FAT12」や「FAT16」
でフォーマットされており、それらを使う必要があるなら「1」にする必要があります、
但し、実行バイナリーは多少大きくなります。(おおよそ1Kバイト増加)
※FAT32でフォーマットをやり直せば、使える物もあるかと思います。

/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/

#define _USE_LCC	1	/* Allow lower case characters for path name */

#define	_CODE_PAGE	932

「_USE_LCC」を「1」にして
コードページは932にしておきます。

Sample プロジェクトの generic を参考に、「diskio.c」シリアル/パラレル変換の部分を書き換えます。
自分のシステムでは、I/O ポートの制御部分は C++ のクラスで構成されているので、
「diskio.cpp」として、メインのポート定義「port_def.hpp」を使って、実装します。
※port_def で宣言しているポートクラスの実体は、「diskio.cpp」で宣言していますが、
このクラスは、最適化されインラインで組み込まれるものと思います。
他に、「マイクロ秒」単位の時間待ちルーチンが必要で、「common/delay.hpp」に該当する
関数があるので、それを使います。
※「utils::delay::micro_second(n);」

// マイクロ秒単位の、「時間待ち」
static void dly_us(unsigned int n)

/*-----------------------------------------------------------------------*/
/* Transmit a byte to the MMC (bitbanging)                               */
/*-----------------------------------------------------------------------*/
static void xmit_mmc(BYTE d)

/*-----------------------------------------------------------------------*/
/* Receive a byte from the MMC (bitbanging)                              */
/*-----------------------------------------------------------------------*/
static BYTE rcvr_mmc(void)

/*-----------------------------------------------------------------------*/
/* Receive a byte from the MMC (bitbanging)                              */
/*-----------------------------------------------------------------------*/
static BYTE rcvr_mmc()

基本的に以上の4つの関数を実装すれば良く、他に、元のソースでは、コマンド
の定義を、「#define」で行っていたので、「static const BYTE」に換えました、C++
では、この定数は、コンパイル時にのみ使われ、メモリー上に置かれる事はありません。

これで完了です。
簡単なテストとして、カードをマウントして、ディレクトリーをリストしてみます。

    FATFS fatfs;
    bool mount = false;
    // pfatfs を開始
    {
        if(pf_mount(&fatfs) != FR_OK) {
            sci_puts("SD mount error\n");
        } else {
            sci_puts("SD mount OK!\n");
            mount = true;
        }
    }

    if(mount) {
        DIR dir;
        if(pf_opendir(&dir, "") != FR_OK) {
            sci_puts("Can't open dir\n");
        } else {
            for(;;) {
                FILINFO fno;
                // Read a directory item
                if(pf_readdir(&dir, &fno) != FR_OK) {
                    sci_puts("Can't read dir\n");
                    break;
                }
                if(!fno.fname[0]) break;

                if(fno.fattrib & AM_DIR) {
                    utils::format("          /%s\n") % fno.fname;
                } else {
                    utils::format("%8d  %s\n") % static_cast<uint32_t>(fno.fsize) % fno.fname;
                }
            }
        }

SD_test_0

GitHub R8C

R8C I2C で EEPROM アクセス

先日、I2C接続のRTCが動いたのだが、対象のデバイスがローカルすぎて、
誰も追試できるような状況ではなかったです。

それで、I2Cとしては、もっと汎用的なデバイスでも試してみたかったので、
EEPROM用の入出力を実装してみたが、どうも、動作しない。
読み込みは出来てるようだけど、書き込みが出来ない・・・

どうも、I2C の STOP コンディションが、正しく機能していないようで、
調査したら、ACK を受け取った後に、SDA を「0」レベルに保つ必要があっ
たのに、抜けてたようだった。
※SDA を「0」に戻さないと、STOP コンディションが正しく機能しない。
それで、これを修正したら、無事書き込みが出来るようになった。

EEPROM は、STOP コンディションをトリガーにして、実際に書き込む為
動作しなかったようだー。

i2c_io クラスでは、なるべくシンプルにした方が良いだろうと思って、単純に
データを転送する関数と、受け取る関数のみ用意していたけど・・

//-----------------------------------------------------------------//
/*!
    @brief  受信(リード)
    @param[in] address スレーブアドレス(7ビット)
    @param[out]	dst    先
    @param[in]  num    数
    @return 失敗なら「false」が返る
*/
//-----------------------------------------------------------------//
bool recv(uint8_t address, uint8_t* dst, uint8_t num) const;

//-----------------------------------------------------------------//
/*!
    @brief  送信(ライト)
    @param[in] address スレーブアドレス(7ビット)
    @param[in] src      元
    @param[in] num      数
    @return 失敗なら「false」が返る
*/
//-----------------------------------------------------------------//
bool send(uint8_t address, const uint8_t* src, uint8_t num) const;

内部動作では、EEPROMにデータを書き込む場合は、データアクセスのアドレス
を、指定する必要がある。
通常の実装だと、テンポラリーを用意して、アドレスとデータをコピーしてから、
送らないとならない。
しかし、それでは、効率が悪いので、冗長ではあるけど、関数を追加する事にした。
アドレス指定は、1バイトと2バイト用に二種類がある。

//-----------------------------------------------------------------//
/*!
    @brief  送信(ライト)
    @param[in] address スレーブアドレス(7ビット)
    @param[in] first    ファーストデータ
    @param[in] src      元
    @param[in] num      数
    @return 失敗なら「false」が返る
*/
//-----------------------------------------------------------------//
bool send(uint8_t address, uint8_t first, const uint8_t* src, uint8_t num) const;

//-----------------------------------------------------------------//
/*!
    @brief  送信(ライト)
    @param[in] address スレーブアドレス(7ビット)
    @param[in] first   ファースト・データ
    @param[in] second  セカンド・データ
    @param[in] src     元
    @param[in] num     数
    @return 失敗なら「false」が返る
*/
//-----------------------------------------------------------------//
bool send(uint8_t address, uint8_t first, uint8_t second, const uint8_t* src, uint8_t num) const;

内部で行っている事は単純だけど、EEPROMへの書き込みはシンプルとなった。
又、EEPROMの書き込みでは、最後のSTOPコンディションがトリガーとなり、
ページバッファにあるデータを実際に書き込む、書き込み時間は比較的長く、5ミリ
秒とかかかる、その為、書き込みが終了するまで、待機する必要があり、今回はポー
リングで待つ事にした。
ポーリングは、比較的簡単で、書き込んだアドレスを読み出しに行けば、書き込み中
なら、正規のACKが返らないので、読み出しが失敗する、それを利用している。
もし、規定時間以上、ACKが返らない場合は、「書き込みが失敗した」として、
ストールさせる。
ポーリングで待つ場合だと、EEPROMの持っている性能を最大限生かせて、
スループットは最大となる。

あと、重要なのは、ページ境界をまたがった書き込みの場合で、start 関数で指定した
ページサイズを超えた書き込みが発生した場合は、2回に分けて書き込むように配慮
した。

さらに、もう一つの仕様として、ページサイズ以内で、ページを超えない書き込みの
場合は、書き込み終了を待たずに、関数は、直ぐに終了する。

アプリ側で、ページサイズ以内で、書き込み待ちが起こらないような間隔で書き込むよ
うに配慮してあれば、「待ち」の為にポーリングしなくて済むので、CPU時間を有効
に利用できる。
この場合、書き込みの発行を分けて、複数回行うようなコードを書くと、二回目の
書き込みで「失敗」してストールする。
※これは、回避する事も出来るけど、余分なワークを消費するので、仕様とした。
一応、書き込み中か、判定する関数も用意してある。

//-----------------------------------------------------------------//
/*!
    @brief    書き込み状態の検査
    @param[in]  adr    検査アドレス
    @return 「false」なら、書き込み中
 */
//-----------------------------------------------------------------//
bool get_write_state(uint32_t adr) const;

IMG_0751s
先日、実験したLCD基板で、実験中~

I2C_EEPROM_test
実際のテストでは、ターミナルとの対話形式で、書き込み、読み出しのテストを行えるようにした。
ボーレートは19200、8ビット、1ストップビット

Start R8C EEPROM monitor
# help
speed KBPS (KBPS: 10 to 1000 [Kbps])
read ADRESS [LENGTH]
write ADRESS DATA ...
fill ADDRESS LENGTH DATA ...

EX:

# speed 1000

1000Mbps (1M bps) の指定

# read 1000

1000 番地から16バイト表示

# read 1000 10ff

1000 番地から、10ff 番地までダンプ

# write 127e fc ba 95 67 8e

127E 番地より、「FC BA 95 67 8E」を書き込み
※最大8バイトまで

# fill 0 1ff 1 23 45 6 78 9a b

00000 ~ 001FF 番地までを、「01 23 45 06 78 9A 0B」で埋める。
※最大8バイトまで

R8C GitHub