hira のすべての投稿

RL78の開発は中断しています

RL78/G13系は、低価格で、高性能なマイコンで、R8CとRXの
中間を埋めるものとして期待して、始めたものの、以下の理由で、中断し
ている。

(1) 内臓データフラッシュのハードウェアー仕様などが公開されない。
(2) 内臓データフラッシュ操作ライブラリーは、gcc 環境では提供されな
い。

以上が、「中止」の主要因で、他にもある。
(3) 16ビットマイコン特有の、特殊な制限
(4) RX24Tの存在

(3) については、色々あるが、16ビットマイコンとは言っても、内部の
構成は8ビットマイコンと変わらず、リニアにアクセスできる領域は64
Kバイトとなっており、特別な命令により、広範囲にアクセスできる。
また、領域には特別な意味を持たせ、小さい空間を効率よくアクセスでき
るようにしているが、デバイス特有なもので、専用のコードになってしま
う。

(4) は、コストに関するものだ、RX24Tは、10個購入で@550円
なので、それでもRL78/G13に比べるとコスト高だが、32ビット
マイコンで、80MHz動作、256Kのフラッシュ、16KのRAMと、
かなりCPが高いマイコンではある。
また、あまり多くのデバイスに手を広げるのはリソースの集中の観点から
も良くないと感じる。
当面、R8C、RXマイコンに集中する。

それでも、RL78のリソースは、一通りあり、基本的にハードウェアー
特有の部分は隠蔽してあるので、特別な事をしない限り困らないと思う。
また、R8CやRXのテンプレートライブラリーと共通性がある為、コー
ドの再利用は可能と思う。

GitHub のリポジトリーはそのまま残す。

-----
RX24TはCPが高いが、RTCが無い、CAN、イーサーネットが無
いなど、ハードウェアーを制限している(元々インバーター向けの製品な
ので・・)為、後付すると、RX62系や、RX63系など、より上位の
マイコンとコストは変わらなくなるので、使い方は多少微妙だが・・・

R8C の割り込みベクターを共有する

先日、R8C関係のブログにコメントが入り、「誰かの役に立ってるんだなぁ」
と実感、RXで得た知見を元に、R8Cにも少しばかり反映を行った。

AVRの「ATTINY2313-20PU」は、以前は、ちょっとした物を創
る場合の救世主だったが、最近は、単価が上がり、気軽に使えなくなった。
※それでも、USBの直接接続など、需要は少なく無い。
それに代わって、現れた救世主が、「ルネサスのR8C/M120AN」で、値
段も100円で、隠し機能のおかげで重宝している。

R8C関係のC++テンプレートも、それなりに充実してきて、何か実験やテス
トを行うのに困らなくなってきている。

-----
RXのテンプレートクラスライブラリーを実装する課程で、gcc 独自の「拡張」
機能を学んだ。

__attribute__((weak));

これは、シンボル名に対する拡張で、二重定義の場合に「後から宣言されたシン
ボル」を有効にするもので、割り込みベクターの定義に有用だと判った。

つまり、

void UART0_TX_intr(void) __attribute__((weak));
void UART0_TX_intr(void) { }

上記のように「仮」の関数を定義しておき、割り込みベクターを宣言しておく事
ができる。

const void* variable_vectors_[] __attribute__ ((section (".vvec"))) = {
...
    UART0_TX_intr,   NULL,    // (17) UART0 送信
...

アプリケーション側で、割り込みが必要なクラスを使いたい場合、同じ名前で、
再定義すると、そちらが優先される。


#include "common/uart_io.hpp"
#include "common/fifo.hpp"

namespace {
    typedef utils::fifo<uint8_t, 16> buffer;
    typedef device::uart_io<device::UART0, buffer, buffer> uart;
    uart uart_;
}

extern "C" {

    void UART0_TX_intr(void) {
        uart_.isend();
    }

...

};

この改修で、今まで、アプリケーション側に定義していた、割り込みベクター
テーブルを追い出す事ができ、冗長なコードが改善できる。

追記:
しかし・・・、問題が全く無い訳では無い。
アプリ側のコードで、割り込みエントリーのシンボル名を「タイポ」した場合、
「vect.c」で定義されたシンボル名が優先されるので、割り込みエントリーが
空のままとなる、そして、コンパイル、リンクはエラー無く終了するので、こ
の手のミスが明るみになる可能性はかなり低い・・・
これは、課題とする。

R8C(M120AN)を使った、MAX6675のテスト

最近は、RXマイコンが多くて、R8Cはご無沙汰だった。

R8Cは、ちょっとしたデバイスの試験なら、気軽にできる。

以前に、オーブントースターを温度制御したくて、買っておいた、
k熱電対センサーと、モジュールを使って、温度を表示してみた。
MAX6675/Kタイプ 熱電対

MAX6675は、アンプとA/Dコンバーターが一体になった
ICで、出力はSPI、電源も3V~5Vまで使え、0.25℃
の分解能、0℃~1024℃まで計測でき、便利なICだ。

プログラムは、ICからデータを読み出して、表示するだけの物
だが、チップ・テンプレート・クラスに新たに、「MAX6675.hpp」
を追加した。

MAX6675 は、初期化の必要も無く、単にデータを読み出すだけの
ものだ。

サンプルでは、以下のように定義した。
※MAX6675 の SPI は、出力(MOSI)は使わないので「NULL_PORT」と
している。

// P1_0(20):
typedef device::PORT<device::PORT1, device::bitpos::B0> SPI_SCK;
// P1_1(19):
typedef device::PORT<device::PORT1, device::bitpos::B1> MAX_CS;
// P1_2(18):
typedef device::PORT<device::PORT1, device::bitpos::B2> SPI_SDI;

typedef device::spi_io<SPI_SCK, device::NULL_PORT, SPI_SDI> SPI;
SPI     spi_;

typedef chip::MAX6675<SPI, MAX_CS> MAX6675;
MAX6675	max6675_(spi_);

初期化は、以下のようにすれば良い。

    // SPI 開始
    spi_.start();

    // MAX6675 開始
    max6675_.start();

温度表示:

    auto v = max6675_.get_temp();
    utils::format("%6.3f\n") % v;

※当然ながら、RXマイコンでも同じように使える。

GitHub にサンプルプロジェクトを追加した。
MAX6675サンプル

RXマイコン、C++、割り込み対応の文字出力

ネットスタック作りは、未だ実装中、もうしばらくかかる・・・
意外と奥が深い、色々考えると、タイミングや応答の順番、イレギュラーな場合
など、色々考慮する必要があり、実験しながらなので、中々決まらない。
それでも、少しづつは進んでいる。

その過程で、イーサーネットの割り込みタスク(EDMAC)内から、文字出力
を行う事が多くなってきたが、文字出力は、排他制御されていないので、メイン
ループの出力と衝突すると、厄介な事になる(文字出力がハングアップする)そ
こで、どうにかできないか考えてみた。

void sci_putch(char ch)
{
    static volatile bool lock = false;
    static utils::fifo<uint8_t, 1024> tmp;
    if(lock) {
        if((tmp.size() - tmp.length()) >= 2) {
            tmp.put(ch);
        }
        return;
    }
    lock = true;
    while(tmp.length() > 0) {
        sci_.putch(tmp.get());
    }
    sci_.putch(ch);
    lock = false;
}

とりあえず、ロック機構を使ってリソースを包んで、ロック状態の場合は、一旦
バッファ(FIFOテンプレートクラス)に貯める。
ロックが外れたら、バッファに残っていた文字を取り出し、表示する。
※この取り出すタイミングは、多少問題で、文字出力が無いと、バッファに残っ
た文字が永遠に出力されない。
バッファに残った文字は、メイン部分で、タイマーの同期ループなどで出力する
ようにすればOKと思う。

RXマイコン、C++、新たなネットスタックの実装(UDP通信)

やっと、UDP通信が出来た。

まだ、不完全ではあると思うが、簡単な送受信が成功した。

作ってみて判るのは、やはり、ネットスタックの実装は、C++に良くマッチ
すると思う事。

・構造体やクラスは、処理系にとって都合の良いように、アライメントされる
が、パケットの中身は、処理系のアラインサイズとは無関係な場合もある為、
随所に「__attribute__((__packed__))」を使って、「余り」が出ないように
してある、これは、gcc 特有のキーワードなので、コンパイラーが違うと、エ
ラーになると思う。
・内部で扱うバッファは、固定サイズにしてあり、アロケーターを必要としない
ようにした。(テンプレートのパラメーターとしてある)
※これが、良いか悪いか、判断の分かれる部分ではあるけど、組み込みでは、
少メモリーに対応する必要と、システムのアロケーターが使えない場合も考慮
する必要性もあるので、そのような実装にしてある。
・UDP/TCP の経路を開始した時に必要なバッファを動的に確保しないと、スタ
テックなメモリーは設定した経路数の最大値によって、多く消費してしまうが、
この辺りの管理は、ユーザーの設定(テンプレートパラメーター)にまかせる
事としている。
・また、メモリー管理を独自に実装する事も考えられるが、現実として、独自
のアロケーターを実装しても、安定して動作するまでは、かなり時間のかかる
困難な作業ではあるし、「DL malloc」(通称)を超える事は、ほとんど不可能
に近い事を考えると、これは後回しにすべき問題と思える。
※殆どのシステムには、高性能な「DL malloc」か、又は、修正版が実装されて
いるにも拘らず、独自に実装している「例」を多く見かけるが、一度、システム
のアロケーターとの違い(勝負)を厳密に行うべきで、容易には、様々な面で、
超える事は出来ないと思われる。(記憶割り当ての効率、速度、断片化割合)

割り込み内での処理と、通常の処理を、分離してあり、コンテキストのオーバー
ラップを起こさないように工夫してある。

また、マルチタスクでは無く、シングルタスクをポリシーとして実装してあり、
処理が集中しないように工夫してある。
※大きなバッファの処理は、ある程度は仕方無いが・・・
※各クラスには、サービスルーチンが用意してあり、10ms単位で呼ぶよう
にするが、必ず、割り込み外から呼ぶようにデザインしてある。

-----
現状では、RX64Mや、GR-KAEDEで動作する。
※イーサーネットの物理層(PHY)ハードウェアーとの接続は、RMIIで
リンクアップはポーリングで行っている。

ソースコードは、GitHub net2で公開している。

UDP通信の簡易テストUDP

※MITライセンス

RXマイコン、C++、新たなネットスタックの実装

先日、ルネサスのネットスタック「r_t4_rx、r_socket_rx」などを使い、
実際にパケットの送受信を行ったのだが、どうも、思ったように動作しない。
※前回の「http サーバー」の実装

そして、その原因を追うと、内部のステータスが、何かの都合で変化しない
とか、とにかく、不安定であるようだ。
※使い方に問題があるのかもしれない。
そもそも、ソースコードは、コメントも少ないし、凄まじいスパゲッティー
コードで、読みにくく、冗長だったりと、内部の動作を追いにくい、散々苦
労しても、実りが少なく、時間だけ消費する。
そんなこんなで、もう「ギブ」って感じになった。

そこで、今一度、イーサーネット関係のプロトコルや運用など、ネットの情
報を元に再学習してみた。
そこで感じたのは、「再利用」(既にある物を利用する)を意識しすぎて、
当初の目的(良好で、安定なネット環境)を見失っていると言う事実・・・

プロトコルを理解すると、ブラックボックス的思考が開けてきて、もう、
いっそうのこと、全部創ればいいんじゃねぇか?
とゆー結論に至り、全部ゼロから実装してみる事にした。
※何故、最初からそうしなかったのか、プロトコルや運用をちゃんと理解す
れば、ゼロから創っても、そんなに時間はかからないと思える。

C++は、この手の実装に向いている、順調に進み、ARPプロトコル、
ICMPプロトコルのサービスができる状態になり、外部から「ping」が通
るようになった。
※ネットエンディアンが、「ビック」なので、多少、強引な部分はあるが、
それなりにスマートになっていると思う、プロトコル別にモジュール化して
あり(ソースを分ける)拡張や改修もやりやすいように配慮した。
※「dhcp_client.hpp」は、真似コードなので全面的に書き直す必要がある。

・ARPプロトコル
IPアドレスに対応するMACアドレスを返信する。
・ICMPプロトコル
ping で使われる、送信、受信を確認するプロトコル

とりあえず、以前のルネサスベースのスタック関係は、そのまま残し、新た
に「net2」として始めた。

common/net_tools.hpp
common/dhcp_client.hpp
net2/net_main.hpp
net2/net_st.hpp
net2/ethernet.hpp
net2/arp.hpp
net2/icmp.hpp
net2/ipv4.hpp

※TCP や UDP はこれから実装するのだが、ルネサスのコードに比べると1/4
くらいになると思われる、しかも、関数のプロトタイプの説明も既に入っている。
何故、低機能なのにあんなに巨大になるのか、不思議・・・

-----
以下のサイトを主に参考にした、実装には非常に助かった。
TCP/IP通信プログラミング Ver.2

RXマイコン、C++、HTTPサーバーの実装(ソケット版)

先日、ping が通るようになったネット・スタックだが、その先が中々進ま
ない。

苦労して、デバッグをした結果、ようやく、動作するようになった。
・ルネサスの、「r_socket_rx」にバグがあった。
※アドレス構造体にポートを設定する部分は、ソケットの仕様では、ネット
エンディアン(ビッグエンディアン)で行うのだが、それが逆だった。
・「r_t4_rx」のチェックサム計算ソースに問題があった。
※このアセンブラソースは、ルネサスの統合環境向けのソースコードで、
「gcc」の場合、アセンブルは通るが、そのままでは正しく動作しないよ
うだ。(これが判るまで、非常に苦労した)

 .file	"cksum_rx_little.s"
    .section    .text.ck
    .global     __cksum
__cksum:    ; function: _cksum
;uint16 _cksum(uchar *data, uint16 nbytes, uint16 sum0);
    and     #0ffffh, r2      ;arg2 uint16
    and	    #0ffffh, r3      ;arg3 uint16

上記のように、少し修正した。

次に、ソケット関連だが、ソースコードを見ると、ノンブロックモードが
用意されているようだが、実際はこのモードはまともに動作しない事が判
った。(とりあえず、ブロックモードで行っている)
ソケットのモジュールでは、BSDソケットとの互換を意識して、関数名
や、構造体を同じにしているが、これが、オリジナル(POSIX)のソケット
関係と当るので、それに準じた、名称に変更した。

これらを修正し、以前に実装した、Arduino 系のプログラムをソケット系に
修正を加え、http_server クラスを新規にコミットした。

また、元のネットスタック関係をデバッグ中、色々問題がある部分が多く、
それらに修正を加えた、なので、オリジナルソースとは違うものになってい
る。
・ソースの色々な部分で、「extern」を使い、変数の型や、関数の型を宣言
しているが、一つのソースで共有すべきなので、新たに、「global.h、
global.c、config_tcpudp.h」を作成した。
※まだ、みるに絶えない部分は沢山あるが、とりあえず・・・

ネットスタック全体は以下のような構成になっている。

ip_adrs.hpp
dhcp_client.hpp
net_tools.hpp
net_core.hpp
socket.hpp
http_server.hpp
r_tcpip_private.[hc]
r_config/r_t4_rx_config.h
r_config/r_socket_rx_config.h
r_t4_rx/src/config_tcpudp.[hc]
r_t4_rx/T4_src/dhcp.[hc]
r_t4_rx/T4_src/ether.[hc]
r_t4_rx/T4_src/global.[hc]
r_t4_rx/T4_src/igmp.[hc]
r_t4_rx/T4_src/ip.[hc]
r_t4_rx/T4_src/r_t4_itcpip.h
r_t4_rx/T4_src/t4define.h
r_t4_rx/T4_src/tcp.[hc]
r_t4_rx/T4_src/tcp_api.c
r_t4_rx/T4_src/type.h
r_t4_rx/T4_src/udp.[hc]
r_t4_rx/T4_src/checksum/rx/cksum_rx_little.s

インサーネットのハードウェアーの定義は以下のようにする。


    typedef device::ETHERC0 ETHERC;      // Ethernet Controller
    typedef device::EDMAC0 EDMAC;        // Ethernet DMA Controller
    typedef chip::phy_base<ETHERC> PHY;  // Ethernet PHY
    typedef device::ether_io<ETHERC, EDMAC, PHY> ETHER_IO;
    ETHER_IO    ether_;

    typedef net::net_core<ETHER_IO> NET_CORE;
    NET_CORE    net_(ether_);

    typedef net::http_server<ETHER_IO, SDC> HTTP;
    HTTP        http_(ether_, sdc_);

※PHY 層デバイスは、GR-KAEDE に使っている、Microchip LAN8720 用だが、
PHY 層デバイスは共通部分が多いので、そのまま使える、他のデバイス用に
カスタムする場合、「phy_base.hpp」を参考にして別のデバイス用を作成す
る。
※「SDC」は、SDカードの操作を行うクラス。(「main.cpp」参照)
※他に、ルネサスのネットスタックとの仕様を合わせるCの関数を定義する。
(ソースコード「main.cpp」を参照)

メインプログラムからHTTPサーバーを使う場合、以下のようにすれば良
い。
※「/」ルートページのレンダリング定義は、サンプルでは動的なプログラム
で行う。(C++ではラムダ式が使えるので簡潔に書ける)

    {  // Ethernet の開始
        ether_.set_intr_task(lan_inthdr);
        uint8_t intr_level = 4;
        ether_.start(intr_level);

        static uint8_t mac[6] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
        net_.start(mac);
    }

    {  // HTTP サーバーの設定
        http_.start("GR-KAEDE_net HTTP Server");

        // ルート・ページ設定
        http_.set_link("/", "", [=](void) {
            time_t t = get_time();
            struct tm *m = localtime(&t);
            HTTP::http_format("%s %s %d %02d:%02d:%02d  %4d
\n") % get_wday(m->tm_wday) % get_mon(m->tm_mon) % static_cast(m->tm_mday) % static_cast(m->tm_hour) % static_cast(m->tm_min) % static_cast(m->tm_sec) % static_cast(m->tm_year + 1900); http_.tag_hr(500, 3); } ); } uint32_t cnt = 0; while(1) { // 100Hz (10ms interval) cmt_.sync(); sdc_.service(); net_.service(); bool l = net_.check_link(); if(l) { http_.service(); } device::PORTC::PDR.B0 = 1; // output LED port device::PORTC::PODR.B0 = (cnt < 10) ? 0 : 1; ++cnt; if(cnt >= 50) cnt = 0; // 0.5 sec }

GR-KAEDE_net プロジェクト

ルネサス製、ネットワーク・ソフトウェアーをgccで利用する

ネット関係をチマチマ実装している・・・

色々調べたが、やはり「ルネサス」が提供するネット関連のソフトウェアーが
一番ハードルが低いだろうと考えた。
※自分でスクラッチから実装するには、時間がかかり過ぎるので、再利用させ
てもらう。

他のオープンソース(GPL)に、フル機能の物もあるが、内容を理解して、
使うにはそれなりの時間と労力がかかるだろうし、大抵、リアルタイムOSの
下で動作するように設計されている、それはそれで、良いのだが、自分として
は、OSに依存しない事がメリットになると考えており、パフォーマンスを引
き出すのも作り方しだいと思うので、今回はそのような方針で行った。
※多分、RXマイコンで、このような方向性で行っているプロジェクトは少な
いと思う。

-----

まず、ルネサスのソフトウェアー郡から、

an-r01an3467jj0111-rx-fit

をダウンロードして精査した。
※アーカイブはモジュール別になっており、必要なモジュールを展開した。

r_ether_rx_v1.11    ※参考にして C++ に移行
r_t4_dhcp_client_rx_v1.04    ※参考にして C++ に移行
r_t4_driver_rx_v1.05    ※参考にして C++ に移行
r_t4_rx_v2.05
r_socket_rx_v1.31

※ドキュメントはしっかりしており、利用法やカスタマイズの方法も詳しく書か
れているが、肝心のソースコードの品質はあまり良くない。
※基本、ルネサスのIDEでコンパイルする事を前提にしている為、そのままで
は、gcc でコンパイルする事は出来ない。

以下はごく一部:
・規約違反
・意味が無く、不必要で危険なキャスト
・「const」が使われていない
・適切では無い戻り値の管理
・不必要で混乱するtypedefによる「型」の再定義
・関数や変数の一貫しないプロトタイプ宣言や、方法
・gcc ではコンパイルできない、又は警告となるような実装依存
・POSIX 等、標準ライブラリーとの親和性が悪い

また、ハードウェアー定義の「iodefine.h」は使いたく無いので、ハードに依存し
た部分はC++で書き直した。
※既に、インサーネット以外のハードウェアーリソースは十分あるので、元のシス
テムに依存する部分を書き換えている(そんなに多くは無い)
※「r_ether_rx、r_t4_driver_rx」など
他、タイマーの設定や、割り込み関係の制御など、ハードに依存する部分を分離し
て追い出し、書き換えた。

このネットスタックは、インサーネットと、インサーネットDMAC、タイマーで
動作するように実装されており、割とシンプルな構造となっている。
※タイマーは10ms(100Hz)で動作させる前提になっている(変更可能)

TCP/UDPのインターフェースは、まだ実装中なのだが、「r_socket_rx」が
あるので、これを利用しようと思う。(BSDソケットの縮小版らしい)
※HTTPサーバーや、FTPサーバーも専用のモジュールがあるがソケットを使
うコードで統一するので利用しない。

とりあえず、それなりに時間がかかったが、ようやく、DHCPが動いて、ping が
通るようになったので、全体プロジェクトをコミットした。
※以前に使った、GR-KAEDE、WEBプログラミング用コード(Arduino互換)
は、順次廃止して、新しいスタックに移行する予定。

GR-KAEDEネットワーク関係

RXマイコン、C++、std::function クラスの謎

FTPサーバーの次は、HTTPサーバーも実装しているが、まだ機能が不足
している、順次機能追加していく。

登録タスクの実行としくみ:
組み込みマイコンで動かすサーバーでは、固定されたページを単に送るだけで
は無く、状況により動的に生成した構文を送る事ができる。
その際、リンクパスと、関連する実行関数を登録出来ると、非常に利便性が高
い。

C++11 から導入された、「std::function」テンプレートは、関数ポインタでも、
ラムダ式でも、クラスのメンバーでも何でも登録できる、便利な物だが、何らか
の要因により、コンパイル、リンク後の使用メモリーが巨大になる現象が発生し
ている・・・

#include <functional>

typedef std::function< void () > http_task_type;

http_task_type	task_;

void aaa()
{
    if(task_) {
        task_();
    }
}

※関数呼び出しが1箇所の場合

   text    data     bss     dec     hex
 115676   71804   79388  266868   41274
void aaa()
{
    if(task_) {
        task_();
    }
}

void bbb()
{
    if(task_) {
        task_();
    }
}

※関数呼び出しが2箇所になった場合

   text    data     bss     dec     hex
 515860  106844   84148  706852   ac924

実行バイナリーは500K増え、RAMも40K増えている・・
RX64M のメモリーは潤沢なので、それでも問題は無いが、納得は出来ない。

組み込みマイコンでC++を使う場合の難点は、巨大なクラスを使えない(使わ
ない)事で、あらかじめ判っている事として、「iostream」関係がある。
(1)「iostream」を使わない場合

   text    data     bss     dec     hex
  86708      48   20988  107744   1a4e0

(2)「iostream」を使った場合

   text    data     bss     dec     hex
 528228  108620   27936  664784   a24d0

※なので、それに代わる「format」、「input」クラスなどを実装している。

どうやら、「std::function」は、何らかの場合に、「iostream」の機能を使うら
しい・・・
※「例外」と「rtti」は無効にしている。
※ソースコードを詳細に追っていないので、明確には判らないが・・・
※コードは巨大で複雑なので、時間が出来た時にでも調査する事にするが、とりあ
えず、巨大にならないように工夫して実装するしか無い。

RXマイコン(組み込み向け)formatクラスの改修

前回、「fixed_string」テンプレートを紹介したが、本題の「format」に移る。

「format」クラスは、「boost::format」の組み込みマイコン向けに仕様を変更した俺俺クラスだ。
それでも、sprintf などの他ライブラリに依存していない為、「format.hpp」のみで簡潔するようになっている。
※インクルード部

#include <type_traits>
#include <unistd.h>

最終的な出力は、「iostream」では無く、ターミナルへの文字出力で、「syscalls」の「write」を使っている。(ディスクリプタは stdout)

また、出力クラスは、テンプレートの引数で与えており、出力ファンクタを実装する事で、色々な出力形態に対応できる。
以前は、固定文字列クラスを実装していなかった為、簡易固定文字列を作り、利用していたが、柔軟性が無く拡張しずらかった為文字列クラスを使える仕様に修正した。
※出来てみると、よりシンプルな構造になり、使いやすく、判りやすくなった。
クラスの型は、こんな感じだ。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  簡易 format クラス
    @param[in]  CHAOUT  文字出力ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class CHAOUT>
class basic_format {

※「CHAOUT」へは、基本1文字づつ追加する。

標準の文字出力クラスは以下のようになっている(stdout)

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  標準出力ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
class stdout_chaout {

    uint32_t     size_;

public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクター
    */
    //-----------------------------------------------------------------//
    stdout_chaout() : size_(0) { } 

    void operator() (char ch) {
        char tmp = ch;
        write(1, &tmp, 1);  // FD by stdout
        ++size_;
    }

    void clear() { size_ = 0; };

    uint32_t size() const { return size_; }
};

「format」クラスの一番大きな変更は、「CHAOUT」をスタティックとした点で。

    static CHAOUT  chaout_;
・・・
};
// テンプレートクラス定義の外で、static の実態を宣言する。
template <class CHAOUT> CHAOUT basic_format<CHAOUT>::chaout_;

文字出力が、都度、リセットされないので、追加などの制御がやりやすくなった。
CHAOUT の暗黙的な仕様として、「operator(char)」、「size()」があれば良い。
※「clear()」は利便性の為用意してある。

標準的な文字列クラスを使った場合は以下のように、文字の終端を制御するクラスも与える事で、内部的制御を一般化できる。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  文字列クラス、出力ファンクタ
    @param[in]  STR     文字列クラス
    @param[in]  TERM    ターミネーター・ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class STR, class TERM>
class string_chaout {

・・・

    void operator () (char ch) {
        str_ += ch;
        if(str_.size() >= str_.capacity()) {
            term_(str_.c_str(), str_.size());
            str_.clear();
        }           
    }

・・・

};

「TERM」クラスは、「STR」クラスへの文字追加が出来なくなる前に、挙動を制御する。

    void flush() {
        if(str_.size() > 0) {
            term_(str_.c_str(), str_.size());
            str_.clear();
        }
    }

    STR& at_str() { return str_; }

    TERM& at_term() { return term_; }

「flush()」関数と、「STR」クラスへの参照、「TERM」クラスへの参照を追加してある。
※「format」クラスは、これらの関数を必要としない。

CHAOUT クラスをスタティックにすると、一時的に確保した領域(スタック)などで使いたい場合に、使いにくくなってしまう。
そこで、バッファのポインターとサイズを与えて処理を行えるように、コンストラクターを追加した。(これは以前に作ったものを部分的に作り直した。)
リソースはスタティックなので、利便性を考えて関数をリセットする為の仕組みも追加した。

//-----------------------------------------------------------------//
/*!
    @brief  コンストラクター
    @param[in]  form    フォーマット式
    @param[in]  buff    文字バッファ
    @param[in]  size    文字バッファサイズ
    @param[in]  append  文字バッファに追加する場合「true」
*/
//-----------------------------------------------------------------//
basic_format(const char* form, char* buff, uint32_t size,  bool append = false) noexcept :

また、この場合に合わせて、「memory_chaout」クラスを用意した。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  メモリー文字列クラス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
class memory_chaout {
    char*       dst_;
    uint32_t    size_;
    uint32_t    pos_;
public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクター
        @param[in]  dst     出力先
        @param[in]  size    最大サイズ
    */
    //-----------------------------------------------------------------//
    memory_chaout(char* dst = nullptr, uint32_t size = 0) : dst_(dst), size_(size), pos_(0) { }

    void set(char* dst, uint32_t size)
    {
        if(dst_ != dst || size_ != size) {  // ポインター、サイズ、どちらか異なる場合は常にリセット
            pos_ = 0;
        }
        dst_ = dst;
        size_ = size;
    }

    void operator () (char ch) {
        if(pos_ < (size_ - 1)) {
            dst_[pos_] = ch;
            ++pos_;
            dst_[pos_] = 0;
        }
    }

    void clear() { pos_ = 0; }

    uint32_t size() const { return size_; }
};

これで、以下のように使える。

typedef utils::basic_format<utils::memory_chaout> sformat;
    char tmp[256];
    sformat(xxxxxxx, tmp, sizeof(tmp));
    sformat(xxxxxxx, tmp, sizeof(tmp), true);  // 追加する場合

format クラス、完全なソースコード