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

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

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

R8C 用コンパイラの構築 gcc-4.9.3

Windows の開発環境で、MSYS2 がメインになってから、
長い間、最新の gcc で m32c(R8C)のコンパイラの構築に失敗していた。
コンパイラの構築には時間がかかる為、オプションの影響などもあまり正確に認識していな
かった。
休みに入ったので、コンパイラと格闘してみた結果、
gcc-5.2.0 で、m32c 用 gcc の構築に成功したので、「要点」を書いておこうと思う。
gcc-5.2.0 の構築は出来たものの、バイナリーを作成してデバイスに書き込んでも、正常に動作しなかったので、
とりあえず、動作が確認されているバージョン(gcc-4.8.3、gcc-4.9.3)を使う。

※「gcc-4.7.4」は現状では、MSYS2 のカレントのコンパイラが 4.9 系では、正常にコンパイル
できない~
いつもながら、クロス gcc の構築には、苦労する・・・

Windows、OS-X で試したので、多分再現性は高いと思う。

Windows では、MSYS2 環境で行い、OS-X では、macport で行う。
基本的に、MSYS2 環境の方が、多少ハードルが高いけど、MSYS2 は、ツールのインストール
が pacman で出来るので、以前より簡単となっている。

まず、MSYS2をインストールする。
※詳しいインストールの方法は、ネットで探すと沢山出てくるので、参考にされたい。
※MSYS2には、3つの実行環境があり、今回は、「msys2_shell.bat」環境で行う。
・まず、システムをアップデート

% update-core

完了したら、ターミナルをクローズして、再度ターミナルを開き、残りを更新。

% pacman -Su

次に、ビルドに必要なツール類をインストールする。(MSYS2)

% pacman -S gcc
% pacman -S texinfo
% pacman -S mpc-devel
% pacman -S diffutils
% pacman -S automake

※他にもあるかもしれないので、適宜インストールする。
※もし、何かツールが無くてコンパイルに失敗したら、ビルドディレクトリーを消して、
「configure」からやり直すのが「確実」だと思う。

・ソースコードのアーカイブを取っておく、今回は以下の組み合わせで行った。
※「gcc-4.8.3」を使う場合は、「4.9.3」を「4.8.3」に読み替えて行う。

    binutils-2.25.1.tar.gz
    gcc-4.9.3.tar.gz
    newlib-2.2.0.tar.gz

・binutils-2.25.1 の構築

% cd
% tar xfvz binutils-2.25.1.tar.gz
% cd binutils-2.25.1
% mkdir m32c_build
% cd m32c_build
% ../configure --target=m32c-elf --prefix=/usr/local/m32c-elf --disable-nls
% make
% make install

・「/usr/local/m32c-elf/bin」へPATHを通して、シェルを起動しなおす。

・C コンパイラの構築
※少し古い gcc のソースコードでは、MSYS2 環境を認識する定義が無い為、「automake-1.15」の
設定をコピーする。(その時の最新をコピーすれば良いと思われる、今回は 1.15 を使った)

% cd
% tar xfvz gcc-4.9.3.tar.gz
% cd gcc-4.9.3
% cp /usr/share/automake-1.15/config.guess .
% mkdir m32c_build
% cd m32c_build
% ../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c --disable-libssp --with-newlib --disable-nls --disable-threads --disable-libgomp --disable-libmudflap --disable-libstdcxx-pch --disable-multilib --disable-bootstrap
% make
% make install

・newlib-2.2.0 の構築

% cd
% tar xfvz newlib-2.2.0.tar.gz
% cd newlib-2.2.0
% mkdir m32c_build
% cd m32c_build
% ../configure --target=m32c-elf --prefix=/usr/local/m32c-elf
% make
% make install

・C++ コンパイラの構築

% cd
% cd gcc-4.9.3
% cd m32c_build
% ../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c,c++ --disable-libssp --with-newlib --disable-nls --disable-threads --disable-libgomp --disable-libmudflap --disable-libstdcxx-pch --disable-multilib --disable-bootstrap
% make
% make install

エラー無く終了すれば、完了!

r8cprog の構築も MSYS2 で出来るので、これでやっと R8C の開発環境が全て MSYS2 で完結する~

一応、I2C_RTC_test をビルドして、M120 に書き込んでテストしてみた。
※問題なく動作しているようである。

※「configure」で、「m32c」を「rx」に変更すれば、RX マイコン用 gcc が構築出来る。
※ rx マイコン用 gcc の構築では、4.9.3 を使う。(4.8.3 は C コンパイラの構築中エラーで止まる)

参考リンク:
最低限のクロスコンパイラの作り方
MSYS2における正しいパッケージの更新方法

R8C RTC に DS3231 を追加

最近、アマゾンで、中華の Arduino 用モジュールが色々入手出来る。
※かなり前からかな・・

これらのモジュール基板、多くの種類があり、値段がもの凄く安い!
※品質はそれなりかもしれないが、この値段なら十分だと思える。
※部品単体で買っても、こんなに安くは買えない。

で、気になった「基板」をいくつか買ってみた~
※RTC、ジャイロ・加速度センサー、リチウムイオン充電モジュールなど

・DS3231(RTC)
このモジュールは、マキシム社のRTCで、I2Cインターフェースを備えている。
温度補償水晶発振器(TCXO)および水晶を内蔵していて、超高精度。

このモジュールには、32KのEEPROM、リチウムイオン電池(CR2032)による
バックアップ付きで、電池まで付いている。

これで、たったの、200円
※degi-key で調べると、デバイス単体で890円

ありえない値段でビックリ!
※2個買ったけど、どちらも機能するようだ。

とりあえず、以前にDS1371でRTCのテストプロジェクトを作成したので、DS3231用
クラスを実装してみた。
※しょうもないバグで、数時間悩んだけど・・

内臓レジスターは、BCDになっており、非常に面倒な仕様となっている。
クラスでは、レジスターを読み出してから、time_t に変換している。
逆にレジスターに書き込むには、time_t から、tm 形式に変換してから行う。
※そうしないと、多くの場合、不便極まりないので・・・
本当は、こんな「クソ」仕様のRTCは使いたくないのだが、安いので仕方無い・・・
※この仕様のRTCは「うるう年」を厳密には正しく扱えない、まぁ今は既に2015年なので、
2099年までの範囲なら、まぁ問題無いか・・

common/time.c は、タイムゾーンを含めた、時間関係で最低限必要な関数を実装してある。
通常は、libc に含まれるが、R8C で、time 関係のライブラリーをリンクすると、非常に大きな
容量となるので、独自に実装した関数郡を用意してある。
標準の「time.h」をインクルードすると、「当たる」ので注意が必要。

// 対象の RTC を有効にする
// #define DS1371
#define DS3231

※「main.cpp」で、どちらかを有効にする事で、RTCのタイプを切り替えできる。

IMG_0781s

I2C_RTC_test

100円で買える R8C の C++ と開発環境

この記事は C++ Advent Calendar 2015 の 15日目の記事です.
前の日は amedama41 さんの「io_service の使い方」でした.

R8C/M120AN,R8C/M110AN の紹介

このマイコンは、秋月電子などで、1個100円で売られているマイコンで、それぞれ、DIP20
DIP14パッケージになっています。

※DIP20、DIP14とは、以下のような物で、電子工作では、ユニバーサル基板に組んだり、
ブレッドボードに組んだりできるので、重宝するパッケージです。
(昔ながらのパッケージです)
IMG_0777s

このマイコンに注目するのは、大きな理由があります。
カタログスペックでは、ProgramFlash:2キロバイト(メインプログラムを格納する領域)、
RAM:256バイト(スタックやワーク領域)、でも実際には・・

ProgramFlash:64キロバイト、RAM:1280バイト
の容量を持っていて、制限なく使う事ができます。

このくらいあると、プログラムの幅が広がり、C++ でプログラミングして動作させるのにも
問題ありません。
※RAM が1280バイトでは、記憶割り当てが使えない為、大きな制限がある事はあるので
すが・・・
通常マイコンには、様々なバリエーションのデバイスがあり、基本的には、Flash、RAM、内部
機能の違いで、付加価値を変えており販売価格が違います。
しかしながら、内部のシリコンを個別に設計するのは莫大なコストがかかる為、多くは共通に
なっているものと思います。
通常では、デバイスの許す領域以外はアクセスしても、無効なエリアとなるような仕組みと専
用ヒューズが用意されている
ものと思いますが、このデバイスは安価なバリエーションに属するデバイスで、ツール側の対
応で、行う方針だったように推測します。
Flash へプログラムを書き込む場合、専用の書き込みツールを使う為、通常は、デバイスの範
囲を超えて書き込む事はできませんが、書き込みソフトも実装したので、全ての容量を使う事
が出来ます。
※書き込みツールは Mac や Linux(試していませんが・・)でも使えるはずです。

始め、この話を聞いた時、そんな「旨い」話があるはず無いとも思っていましたが、実際に試
したら、使えたので、小躍りした思い出がありますww
※最初は32Kまで(0x8000 ~ 0xffff)確認できましたが、その後の調査で、もう32Kあ
る事が発覚しました。(0x10000 ~ 0x17fff)
※R8C は基本、8ビットのマイコンで、基本的に64Kバイトを超える領域にアクセスするに
は、特別な方法が必要ですが割と簡潔な方法で、拡張されたエリアを使う事ができます。

開発環境の準備


ルネサスエレクトロニクスが、提供する、IDE(無料版には制限があります)を使う事も出来ますが、
Windows だけなので、自分で開発環境を用意します。
と、言っても、gcc などのソースコードを取得してコンパイルするだけです。
R8C マイコンは、M32C マイコンのサブセットで、M32C のコンパイラを構築して、デバイスオプ
ションで切り替えます。
gcc の構築については、以下のリンクを参照下さい。
・m32c gcc の構築
※このバージョンで構築した gcc は、4.7.4 ですが、「-std=c++0x」でほぼ C++11 でプログラ
ム出来ます。

※ m32c のクロスコンパイラは、メンテナンスされていない為、最新の gcc ではコンパイルに失
敗するようです。

※ 時間とエンジニアリングがあれば、最新の gcc や、clang でコンパイル可能な物も作ってみた
いです。

※gcc-4.9.3 を使えます。

プログラムの書き込み

組み込みマイコンが、「組み込み」たる所以は、「プログラムを書き込んで、単独で動作する事」
です。
このマイコンには、モード端子があり、それを切り替える事で、内臓フラッシュへの書き込みモー
ドと、単独でブートするモードがあります。
内臓フラッシュの書き込みでは、データを入出力する為の簡単なプログラムが内臓されており、シ
リアル接続を使って、簡単なプロトコルを使い内臓フラッシュへデータを書き込めるようになって
います。

以下は、代表的な USB シリアル変換回路の回路図です。
SerialModule
※USB シリアル変換は、安価に入手出来るので、自分で作る必要はありませんが、上記の回路なら、500円くらいで作れます。

R8C のフラッシュライター(参考回路)
R8C_Writer

USB シリアル変換と、ゼロプレッシャーソケットを組み合わせた書き込み機
IMG_0744

デバイスのプログラムは、通常、開発基板のソケットからデバイスを外して、ライターの
ソケットにセットして、プログラムを書き込み、ライターから、開発基板にデバイスを戻
す。
などの、サイクルで行うのですが、プログラムを変更する度に、ソケットから外して、戻
してだと非常に面倒です、そこで、開発基板にシリアル入出力用のコネクター(写真では
6ピン)を付けておき、スイッチ(白い小さなスライドスイッチ)で切り替えて、基板に
デバイスを乗せた状態でプログラム出来るように工夫してあります。
IMG_0779s

書き込みプログラムは、一応、Windows、Mac でコンパイルできるように実装してあります。
r8cprog
※Windows でコンパイルする為には、termios.h が必要です。(特定の環境に付属します)

r8c_prog -d R5F2M120 -s 57600 -e -w -v led_test.mot

この例では、「led_test.mot」ファイルを「R5F2M120」へ書き込んでいます。
その際、「-e」でイレース、「-w」で書き込み、「-v」でベリファイ、のオプションを
追加しています。
マイコンのフラッシュプログラムは、他にみ色々な機能がありますので、コマンドのヘルプ
などを参考にして下さい。
Windows 版の GUI 付き書き込みプログラムが、千秋ゼミで公開されています。
※登録が必要です。

I/O定義の構築

メーカーがサンプルで提供している、I/O定義ファイルは、非常にチープで、解りにくい代物
です。
そこで、R8C/M11Aグループ、R8C/M12Aグループ データシートに記述されているレジスター名に一致して、扱い易いようI/O定義
を C++ のテンプレートで作成しました。
このテンプレートは、以前にRXマイコンで養ったテクニックを盛り込んだ物ですが、まぁ60
点くらいの出来だと思います。
R8C I/O 定義ヘッダー集
GitHub に上げてあります。
組み込みマイコンで C++ ぽぃ書き方が出来ますww

メーカーがサンプルなどで提供するI/O定義は、C言語向けのもので、特定のコンパイラでしか
正しく機能しない物が多く、判りにくく、エラーに対する許容度が低く使う気になりませんでした。

ネットには、同じような組み込みマイコン向けのI/O定義をテンプレート化するコードはありま
したが、色々調べると、機能的に満足出来ない部分もあり、結局、学習も含めて自分で実装する事
にしました。
開発過程で、コンパイルされたアセンブリコードを眺めて、ある程度許容されたコードが出るよう
に工夫しました。
また、最適化無しでも、最適化しても、動作するコードが出るように工夫してあります。

たとえば、P1ポート・レジスターのB2を「1」にする場合は

    P1.B2 = 1;

と書けます。

読み出す場合は

    bool value = P1.B2();

とします。
「()」オペレーターを使って、「読み出し動作」を明確に分けています。
こうしないと、最適化した場合と、しない場合で、問題のあるコードがでてしまう場合があります。

他に、「enum class」でポートの機能切り替えを定義しておき、機能を切り替えるようにしてみま
した。

#include "common/port_map.hpp"

    utils::PORT_MAP(utils::port_map::P10::AN0);
    utils::PORT_MAP(utils::port_map::P11::AN1);
↑ P10、P11 をアナログ入力 AN0、AN1 に指定

    utils::PORT_MAP(utils::port_map::P12::TRCIOB);
    utils::PORT_MAP(utils::port_map::P13::TRCIOC);
↑ P12 を TRCIOB、P13 を TRCIOC に設定

デバイス操作クラス

組み込みマイコンには、色々なハードウェアーリソースがあり、これらを簡潔に使う為に、小さなク
ラスライブラリーを提供しています。
※C++ のテンプレートは、このような用途に最適で、非常に柔軟性のある判り易い実装ができ、最適
化にも有利です。
R8C/M120/M110 では以下のような機能があります。

(1) 入出力ポート:17本 (LED 駆動用ポート含む)
(2) 外部割り込み入力:8本
(3) 16ビット多機能タイマ(タイマ RJ2) ---> trj_io
(4) 8ビットプリスケーラ付8ビット多機能タイマ(タイマRB2 ):1 ---> trj_io
(5) 16ビットインプットキャプチャ/アウトプットコンペアタイマ(タイマRC ):1 ---> trc_io
(6) UART/クロック同期形シリアルインタフェース:1チャネル ---> uart_io
(7) 10ビットA/Dコンバータ:6チャネル ---> adc_io
(8) コンパレータ:2回路 ---> comp_io
(9) ウォッチドッグタイマ
(10) クロック発生回路:XINクロック発振回路、オンチップオシレータ(高速/低速)
(11) データフラッシュ:2KB ---> eeprom_io

他に、I2C インターフェースをソフトでエミュレーションするクラス。
特定のデバイス向けクラスなど色々実装してあります。
※GitHub 参照

拡張領域の利用法

リンクローダーに与えるローダースクリプトに、0x10000 ~ 0x17fff までのエリア
(セクション)を宣言しておき、プログラムソースで、

__attribute__ ((section (".exttext")))

※この場合、拡張エリアは「exttext」としてセクションを定義してあります。
セクションを切り替え、コードが格納される場所を分散すれば、後はコンパイラが
64K を超える領域への呼び出しを、R8C が持つ、20 ビットアドレスが可能な命令
に置き換えてくれます。

M120AN/m120an.ld  --->  リンカースクリプト
ADC_test/main.cpp  --->  main 関数が、exttext エリアに指定

アセンブリコード:
    80a2:	fd 00 00 01 	jsr.a 10000 <_main>

Disassembly of section .exttext:

00010000 <_main>:
   10000:	7c f2 d1    	enter #0xd1
   10003:	7e 9f 98 00 	bset:g 0,0x13

サンプルプログラム

組み込みマイコンで、良く使われる機能に絞って、色々サンプルプロジェクトを開発しました。

ADC_test/          A/D コンバーターのテスト
COMP_test/         コンパレーター入力のテスト
ENCODER_test/      エンコーダースイッチを使うテスト
FLASH_test/        内臓データ EEPROM を操作するテスト
I2C_EEPROM_test/   I2C 接続の EEPROM を操作するテスト
I2C_RTC_test/      I2C 接続の RTC を読み書きするテスト
LCD_test/          LCD(Bitmap Graphics)を描画するテスト
LED_test/          LED の点滅テスト(よく言う「Lチカ」)
PLUSE_INP_test/    パルス入力周期を測定するテスト
PLUSE_OUT_LCD/     任意のパルス出力を行うテスト(エンコーダー入力、LCD 表示)
PLUSE_OUT_test/    任意のパルス出力を行うテスト
PSG_test/          ※開発中
PWM_test/          PWM 出力を行うテスト
RC_SERVO_test/     ラジコン用サーボを動作させるテスト
RGB_LED_test/      ※開発中
SD_monitor/        SDカードの読み書きモニター
SD_test/           簡単なSDカードの読み書きテスト
SD_WAV_play/       SDカードにあるWAVファイルを再生するテスト(PWM信号としてアナログ的に出力)
SWITCH_test/       スイッチ入力テスト
TIMER_test/        タイマー機能のテスト
UART_test/         UART を使った、シリアル入出力テスト

各プロジェクトには、Makefile があり、

make

とすれば、コンパイルが行われます、その際、従属規則も同時に生成するようにし
ています。
プログラムを書き込み際には

make run

とします。
※「Makefile」参照

まとめ

小さくて、機能豊富なマイコンなら、アイデア次第で、色々な「オリジナル電子小物」を作る事が
でき楽しいものです~
上記のクラスライブラリーを使えば、最低限の手間と理解で、簡潔にやりたい事が出来ると思いま
す。
興味があったら、何か作ってみてはいかがですか?
質問等あったら気軽にメールでも下さい。

IMG_0775s

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