「電子工作な日々」カテゴリーアーカイブ

電子工作に関連するお話など・・

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 クラス、完全なソースコード

RXマイコン(組み込み向け)固定文字列クラス

RX マイコン向け http サーバーを実装する過程で、マイクロソフトのIEだ
け極めて遅い応答しかできない事が判った。

Firefox では、1秒以下で表示できるページなのに、IE(最新版)だと、
30秒、またはそれ以上の時間がかかる。

調べてみると、IEのパケット受け取りに、大きな制限があり、異常にインタ
ーバルが大きくなっているようだった、小さいパケットで、分割して送ると、
IEで許容しているサイズに満たない場合、タイムアウトするまで、html の
デコードが遅延され、結果次のパケットを受け付けない。
※何という腐った仕様だろうか・・・
※他の要因として、html のヘッダーにある「Content-Length」が無い、又は
正しくない場合に起こる挙動なのかもしれない。

当初、RXマイコン側での html 文の生成は、行単位で動的に行い、内部の行
生成のタイミングでパケットを送信していた。
※全体の長さは、最終文が来るまで判らないので、「Content-Length」は作成
していないかった。
この方法はメモリーの利用は最低限になるのだが、IEのように大きなパケット
を前提にした、腐ったブラウザでは表示速度に問題が起こる。

そこで、html 文を一旦メモリーに展開して貯めて、一気に転送する方法に変更
する事にした。
※完全に固定されたページでは無く、動的に生成している為
ここで問題なのは、「utils::format」クラスの挙動で、基本、行単位の動作を
前提にしている為、かなり大きく、柔軟に変更する必要がある事がわかり、仕様
を検討した。

そこで、固定サイズの配列で動作する文字列クラスを実装した。
メモリーが潤沢に無い場合には、「std::string」は事実上利用出来ないからで、
メモリーアロケーターが密接に関連したクラスは使う事が出来ない。
※今まで何故作らなかったのか不思議・・・
とりあえず、最低限の機能のみ実装してあり、必要になった機能を追加する。
※当然、std::string の仕様に準拠する。

以下に、ソースの一部を示す。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  固定サイズ文字列クラス
    @param[in]  SIZE    文字列サイズ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t SIZE>
class fixed_string {
    char        text_[SIZE];
    uint32_t    pos_;

public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクタ
        @param[in]    str     初期設定文字列
    */
    //-----------------------------------------------------------------//
    fixed_string(const char* str = nullptr) : pos_(0) {
        if(str != nullptr) {
            std::strcpy(text_, str);
            pos_ = std::strlen(text_);
        } else {
            text_[pos_] = 0;
        }
    }

    //-----------------------------------------------------------------//
    /*!
        @brief  格納可能な最大サイズを返す(終端の数を除外)
        @return 格納可能な最大サイズ
    */
    //-----------------------------------------------------------------//
    uint32_t capacity() const noexcept { return SIZE - 1; }
...

完全なソースコード

RXマイコン、C++、FTPサーバーの実装

RX マイコン C++ で、FTPサーバーを実装した。
※動作検査は、GR-KAEDE で行っている。

車輪の再発明的要素は高いが、学習的要素もあるしで、取り組んでみた。
※Arduino の FtpServer プログラムとネットの情報を参考とした。
Arduino 系の C++ ソースは、自分的には気に入らない部分が多い、なので応答メ
ッセージや、全体の流れなど参考とした。

・Arduino の構成は、最新の C++ からは乖離しており、あまり使う気にならない。
・必要の無い継承関係(継承は便利な機能ではあるが、結びつきが強すぎるので、
最近はあまり使わない、どうしても使う必要がある場合だけ使う)
・テンプレートを使わないスタイル(テンプレートを使う事で、柔軟性と拡張性
が導入できると思うので、最近は良く使うようになっている)
・ヘッダーとソースに分かれている。(C++ では、定義と実装を分離する必要が
無く、1つのソース(ヘッダー)のみで管理できる)
・他、細かい部分で色々・・

-----
ある程度実装して、クライアントを接続してみた。

最初、何とか、FFFTP のみ使える状態になった。
※データ転送は PASV モードで行った。

次に FileZilla を試したが、正常に動作しない・・・

MSYS2 上のコンソールから、ftp コマンドで接続してみたが、やはり駄目・・・

原因を調査すると、「SYST」コマンドのタイミングのようだった。
・FFFTP では、SYST は使っていないようだったのでサポートしていなかった。
・コンソールの ftp コマンドでは、SYST のリクエストが来るタイミングが、USER
認証の前だった。
FileZilla では、PASS 認証の後だった。
これを改修したら、接続は出来るようになった。

次に問題になったのは、データ転送のやり方だった。
・FFFTP では、PASV モードで転送を行っていたので、PORT コマンドに対応してい
なかった、そこで、PORT コマンドの対応も行った。
※PORT コマンドでは、FTP サーバーのデータ転送は、クライアント接続となる。
そこで、関連するデータ転送部分で、PASV モードと、PORT モードで、サーバー
動作か、クライアント動作を分けた。

これを行ってから、PORT モードでの転送も行えるようになり、ftp コマンド、
FileZilla での接続等、出来るようになった。

各種クライアントによる接続状況:

FFFTP:
・PASV 有効 ---> OK
・PASV 無効 ---> NG
※PASV 無効 では PORT モードで接続するが、ネットワークが複数(WiFiや
セカンダリーネットワーク)ある機種では、利用している IP アドレスを正しく
取得しない為、正常に接続できないようだ、これは、FFFTP のバグ(仕様)と
思える。

FileZilla:
・既定値(PORT モード)---> OK
・アクティブ(PASV モード) ---> NG
・パッシブ(PASV モード)---> OK
※「アクティブ」の仕様が不明

ftp:(MSYS2 上のコマンド、Windows の ftp コマンドでは無い)
たぶん PORT モード ---> OK

-----
まだ、現在、ネットスタックが調整中であり、中途な状態ではあるが・・

ftp_server.hpp

に、ftp_server のソースコードがある。

サポートコマンド一覧:(※一部、実装中)

    // RFC 959
    ABOR,   ///< ファイルの転送を中止する。
    ACCT,   ///< アカウント情報。引数はユーザアカウントを示す文字列。
    ALLO,   ///< ファイルを受け取るために十分なディスクスペースを割り当てる。引数は予約するサイズ。
    APPE,   ///< 引数に示したファイルに対して追記する。
    CDUP,   ///< 親ディレクトリに移動する。
    CWD,    ///< 作業ディレクトリの変更。引数は移動するディレクトリ。
    DELE,   ///< ファイルを削除する。引数は削除するファイル。
    HELP,   ///< コマンドの一覧。引数を指定するとより詳しいコマンド情報を返す。
    LIST,   ///< 引数に指定したファイルの情報やディレクトリの一覧。
            //   指定しない場合、現在のディレクトリの情報を一覧。
    MKD,    ///< 引数に指定した名前のディレクトリを作成する。
    NLST,   ///< 引数に指定したディレクトリのファイル一覧を返す。
    NOOP,   ///< 何もしない。接続維持のためダミーパケットとして使われることがほとんど。
    MODE,   ///< 転送モードの設定(ストリーム、ブロック、圧縮)。
    PASS,   ///< 認証パスワード。
    PASV,   ///< パッシブモードに移行する。
    PORT,   ///< サーバが接続すべきポートとアドレスを指定する。
    PWD,    ///< 作業ディレクトリを取得する。
    XPWD,   ///< 作業ディレクトリを取得する。(拡張)
    QUIT,   ///< 接続を終了する。
    REIN,   ///< 接続を再初期化する。
    REST,   ///< ファイルの転送を指定した箇所から再開する。
    RETR,   ///< リモートファイルをダウンロード(Retrieve)する。
    RMD,    ///< 引数に指定したディレクトリを削除する。
    RNFR,   ///< 引数に指定した名前のファイル(ディレクトリ)をリネームする。
    RNTO,   ///< 引数に指定した名前のファイル(ディレクトリ)にリネームする。
    SITE,   ///< RFCで定義されていないようなリモートサーバ特有のコマンドを送信する。
    SMNT,   ///< ファイル構造をマウントする
    STAT,   ///< 現在の状態を取得する。
    STOR,   ///< ファイルをアップロード(Stor)する。
    STOU,   ///< ファイル名が重複しないようにファイルをアップロードする。
    STRU,   ///< 転送するファイルの構造を設定する。
    SYST,   ///< システムの種別を返す。
    TYPE,   ///< 転送モードを設定する(アスキーモード、バイナリモード)。
    USER,   ///< 認証するユーザー名

    // RFC 2389
    FEAT,   ///< サーバに実装されている拡張コマンドのリストを取得する。 
    OPTS,   ///< 拡張機能の設定。 

    // RFC 3659
    MDTM,   ///< 引数に指定したファイルの最終更新時間の詳細を返す。
    MLSD,   ///< 引数に指定したディレクトリのファイル一覧を詳細な最終更新時間をつけて返す。
    MLST,   ///< 引数に指定したディレクトリの詳細な情報を返す。
    SIZE,   ///< ファイルサイズを返す

ftp_server クラスは、ファイルアクセスクラスを参照する為、以下のように
SDC(ファイルアクセスクラス「utils::sdc_io」)を typedef しておく。

typedef net::ftp_server<SDC> FTP;
    FTP     ftp_;

コンストラクターで、SDC の実態、及び、Ethernet クラス(net スタッククラス)
を渡す。

    ftp_(ethernet_, sdc_io_)

開始時、ユーザー名、パスワードを引数とする。

    ftp_.start("user", "pass");

実行時、1/100秒間隔でサービスを呼び出す。

    ftp_.service();

-----
ネットスタックは、GR-KAEDE の WEB コンパイラ用ソースを元にしているが、
かろうじて動いている状態なので、回収中。
※あまりにも、酷いソースコードなので、前面的に修正中・・・

サーミスタの温度計算テンプレート

サーミスタの温度計算テンプレートを実装してみた。

このような簡単なテンプレートクラスを書くのは、程よい難しさもあり、パズル的
要素があって意外に楽しい。

クラスのプロトタイプは以下のようになっている。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
  @brief  NTCTH テンプレートクラス
  @param[in]  ADNUM A/D 変換値の量子化最大値
  @param[in]  THM   サーミスタの型
  @param[in]  REFR  分圧抵抗値
  @param[in]  thup  サーミスタが VCC 側の場合「true」、GND 側の場合「false」
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t ADNUM, thermistor THM, uint32_t REFR, bool thup>
class NTCTH {

使い方:

#include "chip/NTCTH.hpp"

・・・

// A/D: 12 bits, NT103, 分圧抵抗: 10K オーム、サーミスタ: VCC側
typedef chip::NTCTH<4095, chip::thermistor::HX103_3380, 10000, true> THERMISTOR;
THERMISTOR thermistor_;

・「NTCTH.hpp」をインクルードする。
・テンプレートパラメーターを typedef しておく。
※A/D 変換の分解能(この場合は12ビット)
※NT103 型サーミスタ(B定数などの定義は、「NTCTH.hpp」にある)
※分圧抵抗の値(10000オーム)
※サーミスタが、VCC側か、GND側にあるのか定義

auto v = get_adc(6);  // CH6
utils::format("温度: %5.2f [度]") % thermistor_(v);

クラスに、A/D変換値を入れて、呼ぶと温度が返ってくる。
※サーミスタの抵抗値が「0」になる場合は抵抗計算が成立しなくなるが、実際には、
そのようねケースは起こらないので、「良し」としとく。

テンプレート化している事で、不必要な比較的要素(サーミスタがVCC側かGND側)
かで別れる計算過程などがコンパイル時に決定されるので、条件分岐は無くなり、最適化
も十分行われる。

※対数計算があるので、数学ライブラリ「libm」をインクルードする必要がある。

※サーミスタは種類が多く特性が違うので、新たなサーミスタを使う場合は、テンプレー
トに定義を追加する必要がある。
・「thermistor」型の定義

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  サーミスタ型
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class thermistor {
    NT103_34G,  ///< THB:3435, TR25:10K
    NT103_41G,  ///< THB:4126, TR25:10K
    HX103_3380, ///< THB:3380, TR25:10K (25C to 50C)
};

・「パラメーターの取得関数」

static void get_para_(float& THB, float& TR25)
{
    switch(THM) {
    case thermistor::NT103_34G:
        THB  = 3435.0f;  ///< サーミスタB定数
        TR25 = 10e3;     ///< R25 サーミスタ25℃基準抵抗値
        break;
    case thermistor::NT103_41G:
        THB  = 4126.0f;  ///< サーミスタB定数
        TR25 = 10e3;     ///< R25 サーミスタ25℃基準抵抗値
        break;
    case thermistor::HX103_3380:
        THB  = 3380.0f;  ///< サーミスタB定数
        TR25 = 10e3;     ///< R25 サーミスタ25℃基準抵抗値
        break;
    default:
        break;
}

R8C/M120AN サーミスター・サンプル

-----
100円マイコン、R8Cでも、問題なく動作した。
・NTCサーミスタ(NCP18XH103F03RB)

※将来的にR8Cで温度制御に使う予定。

サーミスタの温度表示サンプル、R8Cでのバイナリーサイズ:
※log 計算や float の計算が含まれるので、確認

m32c-elf-size thm_sample.elf
   text    data     bss     dec     hex filename
  24580     296      76   24952    6178 thm_sample.elf

R8C/M120AN でのバイナリーサイズ

RX64M データフラッシュの操作

以前にRX24T用に、実装した事があるので、まぁ、同じようなものだろうと思った
が、全く違う仕様で、マニュアルを見ながら、チクチクと実装したので、それなりに時
間がかかった。
※「RX64Mグループ、RX71Mグループフラッシュメモリユーザーズマニュアル ハードウェア インタフェース編」
※リンクを開くには、ルネサスのアカウントが必要

RX64M、RX71Mは共通のようだが、基本的に、RX64Mは、フラッシュメモ
リーはEEPROM系では無い感じだ。

通常、EEPROMでは、イレースすると、データが「FF」になるが、RX64Mは
以前に書かれた値が維持されるようだ、しかしながら、イレースを発行したバンクには
新規に新しい値に変更ができるようになる。

なので、以前とは少し違った管理方法を考える必要がある。

それでも、R8C、RX24T、などと共通の仕様にしておく事は有益なので、そのよ
うな見た目を提供できるように実装を行った。
※単純にデータを保持する目的なら、I2Cで外部に接続したEEPROMの方が簡単
で汎用性が高いと思われる。
前から疑問なのだが、なぜ、こんなにも、面倒な操作が必要なのか理解に苦しむ。
※単純にメモリーが「主」のEEPROMをウェアハ上に作る場合と、一般的なロジッ
クが「主」のマイコンでは、製造過程におけるプロセスが随分違う為、マイコン内臓の
EEPROMの場合は、そう単純ではないのかもしれないが、単にデータフラッシュに
読み書きするだけなのに、毎回、仕様を見ながらシーケンスを作る手間は勘弁してほし
い、かと言って、「ル◎サ◎」が提供するソースコードの品質は極めて低いので使う気
にならないしで・・・

RX64Mのフラッシュが特殊な点は、フラッシュメモリーの操作は、内臓されたシー
ケンサーが行い、このシーケンサーにコマンドを送る事で二次的な操作で行う点だ。

初期化では、このシーケンサーにファームを転送して、起動する必要がある。
ファームは、RX64M内に置かれているので、単にデータの転送を行うだけなのだが、
特殊だと感じる。
※確かに、シーケンサーを動かすファームを書き換える事で、色々な仕様を網羅できる
のは後々便利なのだが・・

RX64Mのフラッシュで、とりあえず、駄目なところは、書き込みが4バイト単位で
しか行えない点だろう。
※当然書き込み先アドレスも4の倍数となる。
一度、データを書いたブロックは、イレースするまで更新ができない訳だから、バイト
単位でオーバラップするような操作は行えない事になる・・・
フラッシュの書き込みは、必ず4バイト(32ビット)単位で行うようにしなければな
らない。

とりあえず、「できた」レベルで、あまり詳細に検討していないがソースコードを、
Git に上げた。

flash_io.hpp