関数電卓をでデザインする
何か、実用的なアプリを作りたくて、今回「関数電卓」を選んでみました。
グラフ表示付きの関数電卓が、それなりの値段で売られているので、それなら、自分で作ればいいのでは?
と言う、不憫な動機も少しはありますw
実際、作ってみると、けっこう奥が深く、C++ の学習にも良い教材と思います。
- 実際のコードは、それなりに巨大で、サンプルと言うには無理があるかもしれないです。
- GUI 関係の挙動をプログラムすると、物理的にコード量が多くなります。
- 通常、演算には double を使えば、それなりの精度なので実用上十分かもしれませんが面白味に欠けると思います。
- 240MHz で動く RX72N で動かすのならと思い、「多倍長」のライブラリを利用してみました。
- メモリが許す限り大きな桁が可能なのですが、とりあえず250桁にしています。(簡単に増やせますが、表示方法を工夫する必要があります)
- 将来的には、WiFi を有効にして、ブラウザ経由で操作する事も行う予定です。
- また、プログラム電卓としての機能やグラフ表示、数式処理も付けたいと思っています。
- まだ及第点と言う訳ではなく、とりあえず、最低限の機能を使える状態です。
API レベルシュミレータの導入
組み込みマイコンで開発を行う場合、フラッシュメモリへの書き込みを行い、ターゲットを起動して動作確認をする流れになります。
しかし、実行バイナリーサイズが大きくなると、フラッシュへの書き込み時間が増え、「修正」→「確認」のサイクルが長くなります。
GUI 系プログラムでは、「良い見た目と」、「良い操作性」を実現するには、細かい調整や修正がどうしても必要になるので、「試す」サイクルが長いのは、かなりの問題で、開発効率が悪いです。
- 漢字のビットマップフォントだけでも260Kあります。
- 実機では、UTF-8 を OEM(S-JIS)のコードに変換する為、FatFs を利用しています。
前もって、PC 上で「見た目」や「操作の挙動」を確認できるだけでも、開発効率が格段に上がります。
今回、PC で動作するシュミレータを作り、その助けを借りて、このアプリを作成しました。
電卓の機能もシュミレータ上で確認出来るよう、RX マイコンのソースコードを共有しています。
RX マイコンの GUI フレームワークでは、描画は全てソフトで行っており、DRW2D エンジンをまだ有効に利用していません。
これは、DRW2D エンジンが内蔵されていない環境でも動作させる事と、現在、ソフトで描画している部分を DRW2D で置き換える事が可能な構造になるようにする意味もあります。
※ DRW2D エンジンによる描画クラスは現在研究、開発中です。
シュミレータは、以前から開発を続けている「glfw3_app」フレームワークを利用しています。
このフレームワークは、OpenGL、GLFW3、OpenAL などオープンソースを組み合わせた、リアルタイム描画に適したものです。
glfw3_app は、マルチプラットホームで、OS-X でも同じように動作します。
自分で開発しているので、Unity や VS の C# 環境より扱い易く、思った事が短時間で実現出来ます。
シュミレータの構成
RX マイコンの GUI 描画では、表示ハードウェアーとして GLCDC を利用します。
ソフトレンダラーは、GLCDC のフレームバッファに対して描画を行う構造なので、これを真似たクラスを作ります。
シュミレータは、このクラスのフレームバッファを PC のフレームにコピーしています。
コピーは毎フレーム行っているので、リアルタイムな動作が出来ます。
template <uint32_t LCDX, uint32_t LCDY>
class glcdc_emu {
public:
static const uint32_t width = LCDX;
static const uint32_t height = LCDY;
private:
uint16_t fb_[LCDX * LCDY];
public:
void sync_vpos() { }
void* get_fbp() { return fb_; }
};
typedef glcdc_emu<LCD_X, LCD_Y> GLCDC;
タッチパネルインターフェースとして I2C 接続された、FT5206 デバイスの代わりに、マウスの移動と、クリックをタッチパネル操作に模倣するようにします。
class touch_emu {
public:
struct touch_t {
vtx::spos pos;
};
private:
touch_t touch_[4];
uint32_t num_;
public:
touch_emu() : num_(0) { }
uint32_t get_touch_num() const { return num_; }
const auto& get_touch_pos(uint32_t idx) const {
if(idx >= 4) idx = 0;
return touch_[idx];
}
void update() { }
void set_pos(const vtx::spos& pos)
{
touch_[0].pos = pos;
num_ = 1;
}
void reset() { num_ = 0; }
};
typedef touch_emu TOUCH;
RX マイコンフレームワークで利用しているクラスを、シュミレータ側に持ってきて、利用します。
RX マイコンのフレームワークは、別のプラットホームでもコンパイル可能なように、依存関係をなるべく排除した実装なので、無改造で利用出来ます。
#ifndef EMU
#include "common/renesas.hpp"
#include "common/fixed_fifo.hpp"
#include "common/sci_i2c_io.hpp"
#include "chip/FT5206.hpp"
#endif
#include "graphics/font8x16.hpp"
#include "graphics/kfont.hpp"
#include "graphics/graphics.hpp"
#include "graphics/simple_dialog.hpp"
#include "graphics/widget_director.hpp"
#include "graphics/scaling.hpp"
詳しくは、プロジェクトのコードを参照して下さい。
このシュミレータのおかげて、細かい調整が効率よく出来ました、今後他のプロジェクトでも利用したいと思います。
多倍長ライブラリの選択
最初、boost の 多倍長浮動小数点ライブラリ Boost Multiprecision Library を利用していましたが、RX マイコンの環境では、コンパイルが出来ない事が判りました。
これは主に、マルチスレッド時における、オブジェクトのロックに起因する API が、RX マイコン側の環境で見つからない為のようでした。
本来、boost のソースコードは、RX マイコン用に対応させる必要がありますが、多くのクラスでは、それを行わなくても動作するので、mingw64 環境用の boost を利用していました。
boost を RX マイコンに対応させるのは、今後の課題です。
RX マイコンでは、スレッド関係のモデルは、FreeRTOS を利用する事になるので、対応する部分を修正する必要があると思います。
これが、どのくらいのハードルなのかも未定だったので別の選択枠を考えました。
boost はライセンスが緩いので、なるべく使いたいのですが仕方ありません。
そこで、見つけたのは、GNU gmp、mpfr ライブラリです。
これらは、古いプロジェクトで、C 言語ベースです、多分 RX マイコン用にビルド出来るものと思いました。
gmp、mpfr の RX マイコンへのポート(コンパイル)
最近、RX マイコンの開発環境として、Renesas GNU RX gcc-8.3.0 を利用しています。
これらのツールチェインは、mingw 環境で独自にコンパイルされたパッケージで提供されています。
パスを通せば、MSYS2 のシェルから動かせるのですが、「./configure」を動かす場合、パス中にスペースが含まれていたり、マルチバイト文字があると、configure が正しく動作しません。
そこで、MSYS2 上の「/usr/local」に、ツールチェインをコピーしました。
cp -r /c/Users/hira/AppData/Roaming/GCC\ for\ Renesas\ RX\ 8.3.0.202004-GNURX-ELF/rx-elf /usr/local/.
※現在、アップデートされたパッケージをインストールしている為、上記パスとなっています。
gmp のソースコードを取得して展開、コンパイル
.lz 形式のファイルを展開する為、lzip を扱えるようにインストールする必要があります。
% pacman -S lzip
% tar --lzip -xvf gmp-6.2.1.tar.lz
% cd gmp-6.2.1
% ./configure --host=rx-elf --prefix=/usr/local/rxlib --disable-shared
% make
% make install
mpfr のソースコードを取得して展開、コンパイル
% ./configure --host=rx-elf --prefix=/usr/local/rxlib --with-gmp=/usr/local/rxlib
% make
% make install
mpfr のラッパー
mpfr を C++ から扱うラッパーは色々な選択肢があります。
しかし、組み込みマイコンで使うとなると色々制約があります。
組み込みマイコンでは、iostream は巨大になるので利用する事が出来ない。
殆ど全てのラッパーは、結果出力として、iostream を使ったものが主流です。(C++ なので当然そうなります・・)
RX マイコンでも、iostream を利用する事は出来るのですが、巨大で、RAM も沢山消費する為、できれば利用したくありません。
結局、自分のシステムにマッチする mpfr ラッパーを実装しました。
basic_arith テンプレートクラスが利用する範囲に限られるので、シンプルなものです。
common/mpfr.hpp にあります。
mpfr では、数値を扱うのは「mpfr_t」構造体です。
ラッパーでは、この構造体をクラスで包んで、クラスオブジェクトとして扱い、このオブジェクトに対する「操作」として、オペレータをオーバーロードするだけです。
名前空間を mpfr として、value クラスとしています。
#include <cmath>
#include <mpfr.h>
namespace mpfr {
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief mpfr オブジェクト
@param[in] num 有効桁数
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t num>
class value {
mpfr_t t_;
mpfr_rnd_t rnd_;
オペレータのオーバーロード関係:
bool operator == (int v) const noexcept { return mpfr_cmp_si(t_, v) == 0; }
bool operator != (int v) const noexcept { return mpfr_cmp_si(t_, v) != 0; }
bool operator == (long v) const noexcept { return mpfr_cmp_si(t_, v) == 0; }
bool operator != (long v) const noexcept { return mpfr_cmp_si(t_, v) != 0; }
bool operator == (double v) const noexcept { return mpfr_cmp_d(t_, v) == 0; }
bool operator != (double v) const noexcept { return mpfr_cmp_d(t_, v) != 0; }
value& operator = (const value& th) noexcept {
mpfr_set(t_, th.t_, rnd_);
return *this;
}
value& operator = (long v) noexcept {
mpfr_set_si(t_, v, rnd_);
return *this;
}
value& operator = (double v) noexcept {
mpfr_set_d(t_, v, rnd_);
return *this;
}
const value operator - () noexcept
{
value tmp(*this);
mpfr_neg(tmp.t_, tmp.t_, rnd_);
return tmp;
}
value& operator += (const value& th) noexcept
{
mpfr_add(t_, t_, th.t_, rnd_);
return *this;
}
value& operator -= (const value& th) noexcept
{
mpfr_sub(t_, t_, th.t_, rnd_);
return *this;
}
value& operator *= (const value& th) noexcept
{
mpfr_mul(t_, t_, th.t_, rnd_);
return *this;
}
value& operator /= (const value& th) noexcept
{
mpfr_div(t_, t_, th.t_, rnd_);
return *this;
}
value operator + (const value& th) const noexcept { return value(*this) += th; }
value operator - (const value& th) const noexcept { return value(*this) -= th; }
value operator * (const value& th) const noexcept { return value(*this) *= th; }
value operator / (const value& th) const noexcept { return value(*this) /= th; }
basic_arith クラス
四則演算などの記号や括弧、シンボル名、関数名を含んだ文字列をパースして、実際の演算を行うクラスです。
このクラスは、数値オブジェクト、シンボル名、関数名、クラスをパラメータとするテンプレートです。
static const uint32_t CALC_NUM = 250; ///< 250 桁
typedef mpfr::value<CALC_NUM> NVAL;
typedef utils::calc_symbol<NVAL> SYMBOL;
SYMBOL symbol_;
typedef utils::calc_func<NVAL> FUNC;
FUNC func_;
typedef utils::basic_arith<NVAL, SYMBOL, FUNC> ARITH;
ARITH arith_;
まとめ
今回、シュミレータの導入で、GUI を含んだアプリを効率良く開発する事が出来ました。
また、gmp、mpfr などのライブラリを扱う事で、これらの学習もある程度出来ました。
アプリは、タッチパネル付き LCD のガジェットにも適当なものと思います。
ライセンス
基本は、MIT ライセンスですが、gmp、mpfr ライブラリは注意が必要です。
libgmp: GNU LGPL v3 and GNU GPL v2
libfrmp: GNU LGPL v3