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

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

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

久しぶりに format クラスを更新

format クラスの見直し

  • format クラスは、組み込みマイコン用に、危険な printf を置き換える為に、boost::format の縮小版のようなノリでコツコツ実装してきた。
  • 組み込みマイコンで C++ を使う場合に、重宝するものと思う。
  • 組み込みマイコンでは、リソースの問題で、iostream などを使う事が難しい。
  • かと言って、C++ で printf を使うのも気が引ける。
  • printf は可変引数を使っているので、致命的な欠点を持っている。
  • 通常、組み込みマイコンを使った製品では、内部で printf を使う事はない。
  • 自前 format クラスは、printf を使うよりコンパクトになるものと思う。
  • 細かい部分を見直し、常に品質を向上するようにしている。
  • 今まで、%g の対応をしていなかったので、今回は、それに注力した。
  • テストケースも同時にアップデートしてより広範囲に仕様を満足するようにした。

浮動小数点の扱い

  • printf の場合、浮動小数点は、内部的には double 扱いになっている。
  • format クラスは、リソースを節約する為、float 扱いだが、有効桁が8桁くらいなら精度も問題なく運用出来ると思う。
  • float は IEEE-754 32 ビットフォーマットとなっている。

何と言っても簡単に使える

  • format クラスは、「format.hpp」のヘッダーのみで、インクルードするだけで使える。
  • ライブラリのリンクも必要無い。
  • 名前空間として「utils」を使っている。
  • boost::format は、フォームとパラメーターが食い違う場合、例外を投げる、自前 format は、内部でエラーコードで対応している。
  • C++ のオペレーター機能を使い、パラメーターを与えているので、安全で、printf に近いコンビニエンス性がある。
#include "format.hpp"

int main()
{
    float value = 1.234f;
    utils::format("Value: %4.3f\n") % value;
}
Value: 1.234

拡張機能

  • 拡張機能として、二進表示「%b」を追加してある。
  • ポインターのアドレスを表示する場合「%p」を使う。

標準出力

  • format は、標準出力として、stdout を使う。
  • 組み込みマイコンでは、write 関数に対して、stdout のハンドルを使い出力する。
  • RX マイコンでは、syscalls.c にその実装があり、内部で put_char 関数に文字を出力するようになっている。
  • メイン部などで、put_char 関数を "C" 用に extern しておいて、その関数から、SCI などに送れば、シリアル出力できる。

他の使い方

    typedef basic_format<stdout_buffered_chaout<256> > format;
    typedef basic_format<stdout_chaout> nformat;
    typedef basic_format<memory_chaout> sformat;
    typedef basic_format<null_chaout> null_format;
    typedef basic_format<size_chaout> size_format;

上記のような「typedef」があり、文字列出力の他、サイズだけ取得するなど、色々な応用が出来る。

※通常、format は小さなバッファを通して、標準出力しているので、「改行」を送らない場合、明示的に「flush」を呼んで、バッファに残った文字を掃き出す必要がある。

今回のアップデートでは、%g(有効桁自動表示)対応がメイン

長い間、浮動小数点の「%g」(自動)フォームに対応していなかったので、それに対応した。
※「Test20」がその検査にあたる。

まだ、テストケースが不十分な感じがするので、バグがあるものと思うが、とりあえず大丈夫そうなので、Github にプッシュした。

Github: format_class

テストケースは、以下のように20パターンあるけど、まだ足りないので、追々追加して、強固なものにしようと思う。

Test01, output buffer size check: (0)  Ref: '0123456789' <-> Res: '0123456'  Pass.
Test02(0), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(1), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(2), decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test02(3), decimal check. Ref: 'form=    -12345678' <-> Res: 'form=    -12345678'  Pass.
Test02(4), decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test02(5), decimal check. Ref: 'form=-00012345678' <-> Res: 'form=-00012345678'  Pass.
Test02(6), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(7), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(8), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(9), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test03(0), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(1), octal check. Ref: 'form=   1245667' <-> Res: 'form=   1245667'  Pass.
Test03(2), octal check. Ref: 'form=001245667' <-> Res: 'form=001245667'  Pass.
Test03(3), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(4), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test04(0), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(1), binary check. Ref: 'form=    10101110' <-> Res: 'form=    10101110'  Pass.
Test04(2), binary check. Ref: 'form=0000010101110' <-> Res: 'form=0000010101110'  Pass.
Test04(3), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(4), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test05(0), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(1), hex-dedcimal check. Ref: 'form=  12a4bf9c' <-> Res: 'form=  12a4bf9c'  Pass.
Test05(2), hex-dedcimal check. Ref: 'form=012a4bf9c' <-> Res: 'form=012a4bf9c'  Pass.
Test05(3), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(4), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(5), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(6), hex-dedcimal check. Ref: 'form=  12A4BF9C' <-> Res: 'form=  12A4BF9C'  Pass.
Test05(7), hex-dedcimal check. Ref: 'form=012A4BF9C' <-> Res: 'form=012A4BF9C'  Pass.
Test05(8), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(9), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test06(0), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(1), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(2), positive decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test06(3), positive decimal check. Ref: 'form=   4282621618' <-> Res: 'form=   4282621618'  Pass.
Test06(4), positive decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test06(5), positive decimal check. Ref: 'form=004282621618' <-> Res: 'form=004282621618'  Pass.
Test06(6), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(7), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(8), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(9), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test07(0), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(1), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(2), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(3), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(4), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(5), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(6), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(7), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(8), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(9), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(10), floating point check. Ref: 'form=     1' <-> Res: 'form=     1'  Pass.
Test07(11), floating point check. Ref: 'form=    -1' <-> Res: 'form=    -1'  Pass.
Test08(0), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(1), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(2), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(3), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(4), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(5), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(6), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(7), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(8), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(9), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(10), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(11), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(12), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(13), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(14), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(15), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test08(16), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(17), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(18), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(19), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test09(0), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(1), text check. Ref: '   AbcdEFG' <-> Res: '   AbcdEFG'  Pass.
Test09(2), text check. Ref: '  AbcdEFG' <-> Res: '  AbcdEFG'  Pass.
Test09(3), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(4), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test10, format poniter to nullptr, error code: (1)  Pass.
Test11, different type, error code: (3) '%s' (target float)  Pass.
Test11, different type, error code: (3) '%d' (target float)  Pass.
Test11, different type, error code: (3) '%c' (target float)  Pass.
Test11, different type, error code: (3) '%u' (target float)  Pass.
Test11, different type, error code: (3) '%p' (target float)  Pass.
Test12, pointer type check: (0)  Ref: '000000000064fcbc' <-> Res: '000000000064fcbc'  Pass.
Test13, floating point infinity check: (0) Ref: 'inf' <-> Res: 'inf'  Pass.
Test14, different type, error code: (3) '%s' (target integer)  Pass.
Test14, different type, error code: (3) '%f' (target integer)  Pass.
Test14, different type, error code: (3) '%p' (target integer)  Pass.
Test14, different type, error code: (3) '%g' (target integer)  Pass.
Test15(0), fixed point check. Ref: '0.10' <-> Res: '0.10'  Pass.
Test15(1), fixed point check. Ref: '0.49' <-> Res: '0.49'  Pass.
Test15(2), fixed point check. Ref: '0.73' <-> Res: '0.73'  Pass.
Test15(3), fixed point check. Ref: '0.98' <-> Res: '0.98'  Pass.
Test15(4), fixed point check. Ref: '1.00' <-> Res: '1.00'  Pass.
Test16 floating point '-1' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test17 floating point '%-' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test18 report pointer (char*) '%p' check. Ref: '000000000064fcc0' <-> Res: '000000000064fcc0'  Pass.
Test19 report pointer (int*) '%p' check. Ref: '000000000040f7a0' <-> Res: '000000000040f7a0'  Pass.
Test20(0), floating point auto check. Ref: '1e+06' <-> Res: '1e+06'  Pass.
Test20(1), floating point auto check. Ref: '1.41421e+06' <-> Res: '1.41421e+06'  Pass.
Test20(2), floating point auto check. Ref: '100000' <-> Res: '100000'  Pass.
Test20(3), floating point auto check. Ref: '141421' <-> Res: '141421'  Pass.
Test20(4), floating point auto check. Ref: '-100000' <-> Res: '-100000'  Pass.
Test20(5), floating point auto check. Ref: '-141421' <-> Res: '-141421'  Pass.
Test20(6), floating point auto check. Ref: '1000' <-> Res: '1000'  Pass.
Test20(7), floating point auto check. Ref: '1414.21' <-> Res: '1414.21'  Pass.
Test20(8), floating point auto check. Ref: '1' <-> Res: '1'  Pass.
Test20(9), floating point auto check. Ref: '1.41421' <-> Res: '1.41421'  Pass.
Test20(10), floating point auto check. Ref: '0.001' <-> Res: '0.001'  Pass.
Test20(11), floating point auto check. Ref: '0.00141421' <-> Res: '0.00141421'  Pass.
Test20(12), floating point auto check. Ref: '-1e-05' <-> Res: '-1e-05'  Pass.
Test20(13), floating point auto check. Ref: '-1.41421e-05' <-> Res: '-1.41421e-05'  Pass.
Test20(14), floating point auto check. Ref: '1e-05' <-> Res: '1e-05'  Pass.
Test20(15), floating point auto check. Ref: '1.41421e-05' <-> Res: '1.41421e-05'  Pass.
Test20(16), floating point auto check. Ref: '1e-06' <-> Res: '1e-06'  Pass.
Test20(17), floating point auto check. Ref: '1.41421e-06' <-> Res: '1.41421e-06'  Pass.

format class Version: 96
All Pass: 20/20

まとめ

  • C++17 対応のコンパイラなら、RX マイコンに限らず、他の環境でも使えると思う。
  • やった事は無いが、Arduino でも使えるものと思う。(C++17 がコンパイル出来れば・・)
  • CC-RX は C++11 さえ未対応なので使えない。

RXマイコンのクロック設定に「static_assert」を追加

RX マイコンのクロック設定

  • RX マイコンのクロック設定はかなり柔軟ではあるものの、設定が出来ない組み合わせも多々ある。
  • その場合、現状の実装では、微妙なクロックで動作していても、その異変を検出するのが難しくなってしまう。
  • そこで、C++11 から追加された「コンパイル時アサート static_assert」を使い、コンパイル時に、不整合を検出して、エラーで停止するようにした。
  • constexpr を組み合わせて、コンパイル時に複雑な条件を検出できる。
  • static_assert はコンパイル時に評価されるので、実時間に影響を与えない。

※何故、今まで使わなかったのか・・


clock_profile.hpp

  • 以前、Makefile で、各クロック周波数を外部から与えていたが、最近は、clock_profile クラスで定義するようにした。
  • この定義は、外部から簡単に参照出来るようにしてある。

RX72T の場合:

namespace device {

    class clock_profile {
    public:
#ifdef USE_USB
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 192'000'000;        ///< PLL ベースクロック(最大200MHz)

        static constexpr uint32_t   ICLK        = 192'000'000;        ///< ICLK 周波数(最大200MHz)
        static constexpr uint32_t   PCLKA       =  96'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  48'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 192'000'000;        ///< PCLKC 周波数(最大200MHz)
        static constexpr uint32_t   PCLKD       =  48'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  48'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  48'000'000;        ///< BCLK 周波数(最大60MHz)
#else
        static constexpr bool       TURN_USB    = false;            ///< USB を利用しない場合「false」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 200'000'000;        ///< PLL ベースクロック

        static constexpr uint32_t   ICLK        = 200'000'000;        ///< ICLK 周波数
        static constexpr uint32_t   PCLKA       = 100'000'000;        ///< PCLKA 周波数
        static constexpr uint32_t   PCLKB       =  50'000'000;        ///< PCLKB 周波数
        static constexpr uint32_t   PCLKC       = 200'000'000;        ///< PCLKC 周波数
        static constexpr uint32_t   PCLKD       =  50'000'000;        ///< PCLKD 周波数
        static constexpr uint32_t   FCLK        =  50'000'000;        ///< FCLK 周波数
        static constexpr uint32_t   BCLK        =  50'000'000;        ///< BCLK 周波数
#endif
    };

}
  • 上記のように、実際に設定する周波数を、整数で定義してある。
  • RX72T の場合、USB を使う場合と最大周波数で動作させる場合で、異なったプロファイルを使う。
  • 水晶発振子の場合、8MHz~24MHzまでを使う。
  • 細かい周波数の場合は多少注意を要する。
  • この定数は、CMT、MTU、SCI など、タイミングデバイスで、周期を計算する場合に参照される。

system_io::boost_master_clock()

  • 各クロックを切り替える関数では、設定出来ない場合にコンパイルを止める。
  • 分周して余りが出る場合もコンパイルエラーとなる。
  • PLL_BASE は、外部接続クリスタルなどから、PLL で、内部クロックを生成する。
  • PLL_BASE は 0.5 倍単位で、10 倍から 30 倍まで生成出来る。

ベース周波数のチェック

        static constexpr bool check_base_clock_() noexcept
        {
            bool ok = true;
            if(OSC_TYPE_ == OSC_TYPE::XTAL) {
                if(clock_profile::BASE < 8'000'000 || clock_profile::BASE > 24'000'000) ok = false;
            } else if(OSC_TYPE_ == OSC_TYPE::EXT) {
#if defined(SIG_RX72N) || defined(SIG_RX72M)
                if(clock_profile::BASE > 30'000'000) ok = false;
#else
                if(clock_profile::BASE > 24'000'000) ok = false;
#endif
            } else if(OSC_TYPE_ == OSC_TYPE::HOCO) {  // 16MHz, 18MHz, 20MHz
                if(clock_profile::BASE != 16'000'000 && clock_profile::BASE != 18'000'000 && clock_profile::BASE != 20'000'000) ok = false;
            }
            return ok;
        }

...

        static_assert(check_base_clock_(), "BASE out of range.");
  • RX72N、RX72M では、外部入力クロック時に 30MHz までを許容している。
  • それ以外は 24MHz
  • 内臓高速オシレータは、16MHz、18MHz、20MHz を選択出来る。

PLL ベースクロック

  • PLL ベースクロックは、BASE クロックを内部 PLL で0.5倍単位で逓倍する。
  • 10 倍~30 倍の設定が可能
            static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) >= 20, "PLL-base clock divider underflow.");
            static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) <= 60, "PLL-base clock divider overflow.");
            static_assert((clock_profile::PLL_BASE * 2 % clock_profile::BASE) == 0, "PLL-base clock can't divided.");

各モジュール分周器

  • 各モジュール分周器は、1, 1/2,1/4,1/8,1/16,1/32,1/64 を設定出来る。
  • RX72N、RX72M では、バスクロック(BCLK)だけ 1/3 を設定出来る。
        static constexpr uint8_t clock_div_(uint32_t clk) noexcept
        {
            uint8_t div = 0;
            while(clk < clock_profile::PLL_BASE) {
                ++div;
                clk <<= 1;
            }
            if(div > 0b0110) div = 0b111;
            return div;
        }

        static constexpr bool check_clock_div_(uint32_t clk) noexcept
        {
            auto div = clock_div_(clk);
            if(div > 0b0110) {
                return false;  // overflow
            }
            if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
                return false;  // 割り切れない周期
            }
            return true;
        }

        static constexpr uint8_t clock_div_bus_(uint32_t clk) noexcept
        {
#if defined(SIG_RX72N) || defined(SIG_RX72M)
            if((clock_profile::PLL_BASE - (clk * 3)) < 3) {  // 1/3 設定の検出
                return 0b1001;
            }
#endif
            return clock_div_(clk);
        }

        static constexpr bool check_clock_div_bus_(uint32_t clk) noexcept
        {
            auto div = clock_div_bus_(clk);
            if((div & 0b0111) > 0b0110) {
                return false;  // overflow
            }
            if(div == 0b1001) {  // 1/3
                return true;
            } else {
                if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
                    return false;
                } else {
                    return true;
                }
            }
        }

...

            static_assert(check_clock_div_(clock_profile::FCLK), "FCLK can't divided.");
            static_assert(check_clock_div_(clock_profile::ICLK), "ICLK can't divided.");
            static_assert(check_clock_div_bus_(clock_profile::BCLK), "BCLK can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKA), "PCLKA can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKB), "PCLKB can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKC), "PCLKC can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKD), "PCLKD can't divided.");

USB クロック

        static constexpr uint32_t usb_div_() noexcept
        {
            if(clock_profile::TURN_USB) {
                if((clock_profile::PLL_BASE % 48'000'000) != 0) return 0;  // 割り切れない場合
                return (clock_profile::PLL_BASE / 48'000'000);
            } else {  // USB を使わない場合は、常に「2」(リセット時の値)を返す
                return 0b0001 + 1;
            }
        }

...

            {
                static_assert(usb_div_() >= 2 && usb_div_() <= 5, "USB Clock can't divided.");
                // 1/2, 1/3, 1/4, 1/5
                device::SYSTEM::SCKCR2.UCK = usb_div_() - 1;
            }

まとめ

  • C++11 以降に追加された「static_assert」だが、今まで、何故か使っていなかったのか?(もったいない)
  • この少しの改造で、clock_profile で、無効な設定を書いても、コンパイル時に検出して、止める事が可能となった。
  • constexpr を使い、コンパイル時に計算や分岐も可能となるので、複雑な条件であってもコンパイル時に検出が可能となる。

RXマイコン、データフラッシュ関係更新

データフラッシュ関係更新

今まで、RX72Nなどで、データフラッシュに対するアクセスが正しく動作していなかった。

原因が判らず、放置してあったが、思い出したように取り組んでみた。

            device::FLASH::FENTRYR = 0xAA80;
            if(device::FLASH::FENTRYR() == 0x0080) {
                mode_ = mode::PE;
                return true;
            } else {
                debug_format("FACI 'P/E' not ready: 'turn_pe_'\n");
                return false;
            }

上記の部分で、Program/Erase モードに移行しない状況となっていた・・
※ハードウェアーマニュアルでは、0xAA80 を書き込んで、0x0080 が読める事を確認する事になっている。

クロック設定や、シーケンサに設定するクロックなど確認したが問題無かった。
※RX72N には、「データフラッシュメモリアクセス周波数設定レジスタ (EEPFCLK)」があり、注意書きとして、

FCLK の周波数を変更する場合、以下の手順に従い、変更前後で遅い方の周波数で動作している状態で
データフラッシュメモリアクセス周波数設定レジスタ (EEPFCLK) を変更してください。

なので、system_io クラス内の FCLK 設定クラス内に移動した。


又、「フラッシュシーケンサ処理クロック周波数通知レジスタ (FPCKAR)」があり(RX64、RX71、RX72 共通)
起動時に設定している。

色々デバッグする過程で、直ぐに読み出すと正しく読めないような状況だと判った。
そこで、とりあえず、10 マイクロ秒の遅延を間に入れた。

    static constexpr uint32_t MODE_CHANGE_DELAY = 10;   ///< モード変更における遅延

            device::FLASH::FENTRYR = 0xAA80;
            utils::delay::micro_second(MODE_CHANGE_DELAY);
            if(device::FLASH::FENTRYR() == 0x0080) {
                mode_ = mode::PE;
                return true;
            } else {
                debug_format("FACI 'P/E' not ready: 'turn_pe_'\n");
                return false;
            }

これで正しく動作する事が判った。
※ハードウェアーマニュアルを良く調べたが、この「遅延」に関する記述は見つけられなかった・・・


その他:

  • 「イレースチェック」コマンドを追加。
  • バンク指定の不具合修正。
  • メッセージの見直しなど色々追加した。

RX24T のデータフラッシュでは、1バイト単位で書き込めるが、RX6x 系では、4 バイト単位での書き込みとなっており、この辺りも管理を考え直す必要性がある。

また、RX64M にはあるが、RX72N には無いレジスタとか、細かい違い吸収するスマートな方法を考えないとならないと思っている。
※現状では、「#iddef」等で、分岐している。


対話形式のデータフラッシュ操作の様子

Flash drive clock: 60 [MHz]
Data Flash total size: 0x00008000
Data Flash block size: 64 bytes
Data Flash word size: 4 byte
FCLK base: 60 MHz
# ?
Data Flash Size: 32768, Bank: 512, Block: 64, Word: 4
erase [bank] (erase 0 to 512)
check [bank] (erase check 0 to 512)
r[ead] org [end] (read)
write org data... (write)
uid (unique ID list)
# check 5
Erase check: bank 5: 0x0140 to 0x017F NG
# erase 5
Erase OK: bank 5, 0x0140 to 0x017F
# write 140 aaa bbbb ccccc dddddd
# r 140 10
0x0140: 00000AAA 0000BBBB 000CCCCC 00DDDDDD
# check 6
Erase check: bank 6: 0x0180 to 0x01BF NG
# write 180 abcd
FACI 'write32_' write error: 0x0180
Write error: 0x0180: 0x0000ABCD
# erase 6
Erase OK: bank 6, 0x0180 to 0x01BF
# write 180 abcd
# r 180 10
0x0180: 0000ABCD 00000000 00000000 00000000
#

enum class を活用

組み込みのプログラミングでは、内部レジスタにマジックワードを書き込む事が多い。
仕様書には詳しく書いてあるものの、プログラムを観た場合に非常に判り難い。
最近、それを少しででも改善するような試みをしている。

今回、フラッシュの FACI シーケンサコマンドを「enum class」で定義して、専用のコマンドとした。

        /// FACI シーケンサ・コマンド
        enum class FACI : uint8_t {
            WRITE_TOP = 0xE8,       ///< プログラム(データフラッシュメモリ) 4バイトプログラム
            WRITE_FIN = 0xD0,       ///< プログラム(データフラッシュメモリ) 4バイトプログラム 最終コマンド
            ERASE1 = 0x20,          ///< 1st ブロックイレーズ (データフラッシュメモリ 64バイト)
            ERASE2 = 0xD0,          ///< 2nd ブロックイレーズ (データフラッシュメモリ 64バイト)
            CLEAR_STATUS = 0x50,    ///< ステータスクリア
            BREAK = 0xB3,           ///< 強制終了
            CHECK_BLANK1 = 0x71,    ///< 1st ブランクチェック
            CHECK_BLANK2 = 0xD0,    ///< 2nd ブランクチェック
        };

        inline void faci_cmd_(FACI cmd) const noexcept
        {
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd);
        }

        inline void faci_cmd_(FACI cmd1, FACI cmd2) noexcept
        {
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd1);
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd2);
        }

※ FACI コマンドでは、コマンドによっては 2 バイト長もあるので、xxxx1、xxxx2 に別けてあるのが多少痛いのだけど・・・

            faci_cmd_(FACI::WRITE_TOP);
            FLASH::FACI_CMD_AREA = 0x02;  // 書き込み数

...

            faci_cmd_(FACI::WRITE_FIN);
            faci_cmd_(FACI::CHECK_BLANK1, FACI::CHECK_BLANK2);

このような試みで、多少判りやすくなっていると思う。


まとめ

今回は、放置してあったデータフラッシュアクセスを何とか動作するようにしたが、何故「遅延」を入れる必要があるのか不明だ・・・
※RX64M、RX71M などでは、遅延は必要無い。

怪しいのは、動作速度を設定するレジスタ関係などだが、何回確認しても、問題無いと思える。
もしかしたら、順番があるのかもしれないが、それは試していない、現状、遅延を入れて動いているので、「良し」としている。

RX72T ボード関係ソフトを更新

RX72T で SD カードの読み書き

以前に制作したRX72Tボード、SDカードの実験をしていなかったので、必要な部品をハンダ付けして実験した。

イモハンダ・・・

ところが、思ったように動作しない・・・

以前に RX24T 用に、RSPI のソフトを動かしていたので、問題無いと思っていたが、非常に奇妙な動作をする・・

  • SanDisk のカードだと認識する。
  • 他のカードだと認識しない・・・

原因不明で、随分悩んだ。

何故か、カードの挿入信号が正常では無かった・・・

原因は、カードの挿入信号用端子のイモハンダだった・・・

SanDisk のカードだと、形状がほんの少し微妙に違うのだろうと思うが、何とか接触するようだ・・・
※他のカードだと解放状態になる・・・

原因が判り、ハンダ付けをやり直して、動作するようになった。

表面実装部品のハンダ付けは、相当気を使う必要がある・・・


少し前も、RX72T ボードで I2C の実験を行ったが、動作が不安定で、原因を調べたら、RX72T ピンのハンダ不良だった・・・


リードが凄く遅い・・・

色々実験を行う過程で、mmc_io.hpp を色々改修していた。

その中で、デバッグメッセージ表示を行う部分を改修していた。

以前は、「#ifdef」でメッセージの表示を行っていた。

#ifdef MMC_DEBUG
            utils::format("rcvr_datablock_: 0x%08X, %d\n") % (uint32_t)(buff)
                % static_cast<uint32_t>(btr);
            utils::delay::micro_second(100000);
#endif

しかし、デバッグ時に表示するメッセージを色々付けたり外したりするのには適切では無いので、以下のようにした。

//      typedef utils::format debug_format;
        typedef utils::null_format debug_format;

これで、上記の「typedef」をどちらか有効にするだけで、切り替わる。
※「null_format」は中身が無いので最適化されると、メッセージ表示の仕組みがそっくり無くなる。

ところが、以前にデバッグした時に、メッセージが速く流れて良く判らなかったのだろうと思うが、無効な遅延を入れていた。
そんな事はすっかり忘却の彼方になっているのは言うまでもない・・・

            debug_format("rcvr_datablock_: 0x%08X, %d\n") % (uint32_t)(buff)
                % static_cast<uint32_t>(btr);
            utils::delay::micro_second(100000);

それが残り、1ブロックリードする度に大きな遅延が入っていた・・・

オシロで、SPCK を観測して、初めて気がついた・・・

これを消して、正常になった。

Lexar 633X 16GB (SDHC) Class10
Write Open:  200 [ms]
Write: 151 KBytes/Sec
Write Close: 17 [ms]

Read Open:  2 [ms]
Read: 654 KBytes/Sec
Read Close: 0 [ms]

※遅延が残った状態だと、リードは、4 KBytes/Sec だった・・・


I2C_sample

I2C_sample も少し手を入れた。

以前は、各 I2C デバイス毎にプロジェクトを作成していたが、I2C_sample に統合するようにした。

現在サポートしている I2C デバイスは:

I2C デバイス メーカー 機能
AS5600 ams 12bits 磁気エンコーダ
BMP280 Bosch Sensotec 温度、圧力センサ
DS3231 Maxim Integrated リアルタイムクロック
EEPROM[0-7] Micro Chip EEPROM

※これから順次追加する予定。

I2C マスターコマンド

コマンド 機能
list 対応デバイスの表示
scan [start=0] [end=127] 接続I2Cされた I2C デバイスのスキャン
exec I2C-name list 表示されたデバイス操作を起動
adr [X] I2C アドレスを設定
r [num=1] デバイスから num バイトを表示
s data... デバイスにデータを書き込む

※exec で起動した場合、exit で戻る。

BMP280 コマンド

コマンド 機能
list 温度、圧力、標高を表示

DS3231 コマンド

コマンド 機能
date 日付、時間の表示
date yyyy/mm/dd hh:mm[:ss] 日付、時間の設定

EEPROM コマンド

コマンド 機能
read ORG [END=+16] EEPROM からデータを読み込み表示
write ORG DATA... EEPROM へデータ書き込み

AS5600 コマンド

コマンド 機能
list 磁気検出、回転位置表示
run 連続的に回転位置など表示(CTRL-Cで抜ける)

まとめ

やはり、表面実装部品をハンダ付けするのは無理がある・・

かと言って、ステンシルを使ってハンダペーストとかを塗り、全体を焼くのも、1枚とかだと、逆に大変そうだ・・

YouTube のノートPCなどの修理動画を観ていると、ホットブロアーで、QFN などのハンダ付けを行っていた。
それを見て、ああなるほどと思ったー、今後、機器を揃えて、この方法を試してみたいと思った。

RXマイコン、選択型割り込み設定不具合とテンプレート化

DMA転送要因でMTUを指定すると、おかしな挙動をする・・

以前、DACストリームで、DMACの起動要因として、TPUを使っていた。
※分周器の設定が細かい方が良いので。

しかし、RX66T、RX72TにはTPUが無いので、どのデバイスでもあるMTUを使うように修正した。
※TMRを16ビットで使う事も考えたが、TMRは、DTCの起動には使えるが、DMACには使えない事が判り断念・・
※TMRを16ビットで使い、インターバルタイマーとして使う実装は行った。

まず、MTUで、インターバルタイマーだけ機能するような実装を行い、RX72Nで実験した。
ちゃんと動作する事を確認。

次に、PSG音楽再生を行おうと、RX64Mで実験・・、動作しない・・・

かなり時間を使い、原因が「power_mgr クラス」でMTUを定義していない事が原因だった・・・

そこで、power_mgr クラスを全デバイスで全て保守。


TPUを使うと動作する・・・

ここで、MTU関係クラスで、色々マズイ部分を修正。

色々修正したが、正常に動作しない・・・

良く調べると、DACストリームでは、MTUのインターバル設定を初期化時に、二度呼ぶような仕様だった。

RX64MのMTU割り込みは選択型Bとなっている、二度呼ぶと、選択型テーブルにMTUの割り込み要因が二か所設定されてしまうのが原因だった。
※TPUでは、これを避けていた。

しかし、良く精査すると、選択型割り込み設定に問題がある事が判り、修正。

選択型割り込み設定をテンプレート化する。

選択型割り込み設定は、RX24T以外、全てのデバイスに存在する。

同じコードをコピーするのも、保守の点で問題なので、テンプレート化して、共有出来るように実装した。

        //-----------------------------------------------------------------//
        /*!
            @brief  選択型割り込みA設定テンプレート
            @param[in]  ICU         ICU クラス
            @param[in]  VEC_TYPE    割り込み要因型
            @param[in]  TASK_TYPE   タスクタイプ
            @param[in]  sel         割り込み要因
            @param[in]  task        割り込みタスク
            @param[in]  lvl         割り込みレベル
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        template <class ICU, typename VEC_TYPE, typename TASK_TYPE, uint16_t org, uint16_t end>
        static typename ICU::VECTOR set_interruptSELA(VEC_TYPE sel, TASK_TYPE task, uint8_t lvl) noexcept
        {
            // sel 要因があれば消す。
            for(uint16_t i = org; i < (end + 1); ++i) {
                auto idx = static_cast<typename ICU::VECTOR>(i);
                if(ICU::SLIAR[idx] == sel) {
                    ICU::IER.enable(idx, 0);
                    set_interrupt_task(nullptr, i);
                    ICU::IPR[idx] = 0;
                    ICU::SLIAR[idx] = VEC_TYPE::NONE;
                    ICU::IR[idx] = 0;
                }
            }
            if(lvl == 0 || task == nullptr) return ICU::VECTOR::NONE;

            for(uint16_t i = org; i < (end + 1); ++i) {
                auto idx = static_cast<typename ICU::VECTOR>(i);
                if(ICU::SLIAR[idx] == VEC_TYPE::NONE) {
                    ICU::IER.enable(idx, 0);
                    set_interrupt_task(task, i);
                    ICU::IPR[idx] = lvl;
                    ICU::SLIAR[idx] = sel;
                    ICU::IR[idx] = 0;
                    ICU::IER.enable(idx, 1);
                    return idx;
                }
            }

            return ICU::VECTOR::NONE;
        }

呼び出し側:

        //-----------------------------------------------------------------//
        /*!
            @brief  割り込み設定(選択Aベクター)
            @param[in]  sel     割り込み要因
            @param[in]  task    割り込みタスク
            @param[in]  lvl 割り込みレベル(0の場合、割り込み禁止)
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        static ICU::VECTOR set_interrupt(ICU::VECTOR_SELA sel, utils::TASK task, uint8_t lvl) noexcept
        {
            return icu_utils::set_interruptSELA<ICU, ICU::VECTOR_SELA, utils::TASK, 208, 255>(sel, task, lvl);
        }

※、テンプレートの実装は、イマイチの部分があるのだが、自分のスキルにとって何とか理解出来る範疇に収めてある。
それにしても、テンプレートは難しくて面白い、でも、余裕のある時にしか出来ないw


C言語を使い続ける理由はあるのか?

今回の件とは直接関係無いが、とある掲示板で、RXマイコンのUART(SCI)の設定に関しての質問があった・・

IDEの機能で、ソースコードを出力するもので、そのAPIを使う為の事柄のようだー

そこで、思ったのは、シリアル通信を行うだけなのに、多くの手順を行い、ドキュメントを詳細に読む必要があるのだった・・

プログラム生成で思い付く問題点:

  • プログラムで生成する事で、自分が一部のコードを改修したら再度生成した時にマージが難しくなる。
  • 後に保守する場合に備えて、生成したプログラムのバージョンや手順を全て記録しておく必要がある。
  • シリアルのチャネルを変更したら、関数のプロトタイプが変わり、多くの変更を余儀なくされる。
  • 生成プログラムのコードはブラックボックスで公開されていない為、何かの不具合に即座に対応出来ない。
  • プログラム生成が初めての人に対して、簡単とは言えない。

自分の C++ フレームワークでは、シリアル通信の手順は、これ以上簡単に出来ないくらいにはしてある。

※プログラムで生成しないので、自由に変えられ、コンパイルをやり直すだけ。
※このような柔軟性は、C 言語で実装するのは難しいと思える。

  • 利用するシリアルチャネルを決める。
  • 利用するシリアルポートを決める。
  • 受信、送信バッファのサイズを決める
  • ボーレート、シリアル通信フォーマットを決める。
  • 割り込みレベルを決定する。(ポーリングでも機能する)
    typedef utils::fixed_fifo<char, 512> RXB;  // RX (受信) バッファの定義
    typedef utils::fixed_fifo<char, 256> TXB;  // TX (送信) バッファの定義

    typedef device::sci_io<SCI_CH, RXB, TXB> SCI;
// SCI ポートの第二候補を選択する場合
//  typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;
    SCI     sci_;
  • SCI_CH には利用するシリアルチャネルをtypedefする。(直で書いても構わない)
    typedef device::SCI2 SCI_CH;

利用するポートは、port_map クラスにより決定され、候補を選択出来るようになっている。

たとえば、候補2を使う場合:

    typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;

とする。

候補と、シリアルポートの関係は、port_map を観れば、判るようになっている。

あとは、開始するだけ:

    {  // SCI の開始
        uint8_t intr = 2;        // 割り込みレベル(0を指定すると、ポーリング動作になる)
        uint32_t baud = 115200;  // ボーレート(任意の整数値を指定可能)
        sci_.start(baud, intr);  // 標準では、8ビット、1ストップビットを選択
// 通信プロトコルを設定する場合は、通信プロトコルのタイプを指定する事が出来る。
// sci_io.hpp PROTOCOL enum class のタイプを参照
//      sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
    }

これで、文字を、送ったり、受けたりが簡単に出来る。

A を送る:

    sci_.putch('A');

文字を受け取る:

    auto ch = sci_.getch();

printf で使いたければ、C の関数として、文字の入出力を extern 宣言する。
※この場合、「syscalls.c」をコンパイル、リンクする必要がある。
※ C++ では、printf を使う理由は無いので、utils::format を使う。

extern "C" {

    // syscalls.c から呼ばれる、標準出力(stdout, stderr)
    void sci_putch(char ch)
    {
        sci_.putch(ch);
    }

    void sci_puts(const char* str)
    {
        sci_.puts(str);
    }

    // syscalls.c から呼ばれる、標準入力(stdin)
    char sci_getch(void)
    {
        return sci_.getch();
    }

    uint16_t sci_length()
    {
        return sci_.recv_length();
    }
}

シリアルフォーマットを変更したい場合:

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  SCI 通信プロトコル型
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class PROTOCOL {
            B8_N_1S,    ///< 8 ビット、No-Parity、 1 Stop Bit
            B8_E_1S,    ///< 8 ビット、Even(偶数)、1 Stop Bit
            B8_O_1S,    ///< 8 ビット、Odd (奇数)、1 Stop Bit
            B8_N_2S,    ///< 8 ビット、No-Parity、 2 Stop Bits
            B8_E_2S,    ///< 8 ビット、Even(偶数)、2 Stop Bits
            B8_O_2S,    ///< 8 ビット、Odd (奇数)、2 Stop Bits
        };

※現在、以上のフォーマットをサポートしている(7ビットは使わないだろうから、サポートしていない)

8ビット、1ストップビット、偶数パリティの場合:

    {
        uint8_t intr = 2;        // 割り込みレベル(0を指定すると、ポーリング動作になる)
        uint32_t baud = 115200;  // ボーレート(任意の整数値を指定可能)
        sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
    }

設定されたボーレートと、実際に設定されたボーレートの誤差を知りたい場合:

        utils::format("SCI PCLK: %u\n") % SCI_CH::PCLK;
        utils::format("SCI Baud rate (set):  %u\n") % sci_.get_baud_rate();
        float rate = 1.0f - static_cast<float>(sci_.get_baud_rate()) / sci_.get_baud_rate(true);
        rate *= 100.0f;
        utils::format("SCI Baud rate (real): %u (%3.2f [%%])\n") % sci_.get_baud_rate(true) % rate;

同じコードは、RX24T、RX64M、RX65N、RX66T、RX72T、RX72N などでおなじように使える。

途中で、SCI2 では、無く、SCI1 に変更したい場合は・・

    typedef device::SCI1 SCI_CH;

とすれば良く、合わせて、ポートのマッピングを吟味する。

また、SCI1、SCI2、SCI3 などを同時に使う場合であっても、同じように定義とリソースを用意するだけとなっている。

さらに、C++ では、ソースコード(実装)とヘッダー(定義)を別ける必要性が無い為、「common/sci_io.hpp」をインクルードするだけで、
他の設定は必要無い。
※printf を使う為、syscalls.c をリンクする必要はあるが、これは、C に起因している為、省けない・・


質問者は、RX231 だったので、サポートしていないが、要望があれば、RX231 をサポートする準備はある。
※GitHub のスポンサーになる必要があるけど・・
※RX231 が載ったボードを用意して送ってもらう必要がある・・

良く言われる事が、C++ は難しいので、勧められないと言うのがある・・

しかし、多くの初心者が、Arduino を利用していると思う、自分のフレームワークでは、テンプレートを多く使っており、Arduino よりハードルは高いかもしれないが、サンプルも多くあり、真似るだけなら、そんなにハードルは高く無いと思うのだが・・・

RL78 のフレームワークを久々に改善

C++ は組み込みでは積極利用者が少ない事を実感する日々

最近、特に感じるが、何故か、C++ を組み込みマイコンで積極利用している人が少ないように感じる。

やはり、ハードルがあるのだろうと思う(自分は慣れの問題で感じなくなっている)

どう考えても、C で実装するより、間違いが少なく、構造的に作れて、再利用性が大きく、最適化性能が高い。

C++11 以降、痛い部分が改善されて、より良く自然に書けるようにもなった。

勉強しない人には、何かと敷居が高いのかもしれない・・・

ただ、C++ は、C である程度実装出来る人にとっては、「手っ取り早く、楽で魅力的」とは映らない面もある。
C++ のパワーを理解していないものと思う。

自分は、10年以上前に、C++ を「勉強しなくては」と心の底から思った出来事がある。

非常に複雑で、ボリュームが大きいプロジェクト内で、急遽、モニター用のプログラムを作る案件が生じたー

この案件に対して、C++師匠は、STL を駆使して、あっとゆーまに実装を終え、ちゃんと動くアプリを作った。
※自分がこの仕様を観た時は、これは、そこそこ大変だなーと思っていた・・

また、この師匠は、数ギガヘルツで動くようなCPUのアプリを、何でアセンブラに毛が生えたようなC言語でプログラムしないといけないの?
もっと相応しい作り方があるのでは?

と問うて来たー、この一件で、自分の C++ 敬遠傾向は、一蹴されたと思う。

それ以来、C++ を履修しなおし、現在に至る。

  • C++ の勉強会などに参加して、C++ に精通した人から、色々なレクチャーを受けた。
  • C++ は、正しい道を進まないで我流で進むと、とんでもない迷路にはまる事がある事例をいくつも聞かされた。
  • C++ の最適化で、人間が考えもしない独特の手法で、巧妙に行う事例をいくつも観た。
  • C++ になって大きなプログラムでも、メモリーリークは殆どしないようになった。
  • コンパイルが正常終了すると、大抵プログラムも思ったように動く事が多くなった。
  • テンプレートを少しは理解出来て、自分でもある程度作れるようになった。
  • 警告が出ない、綺麗なソースコードを書くようになった。
  • コードレビューをするようになった。
  • C++ のトレンドや、標準化委員会、boost などのレポートを読むようになった。

ただ、先は長い・・、自分の理解を超えるような考え方、概念がまだ沢山ある・・
まぁ、とりあえず、出来る範囲でゆっくり歩こうと思う。

ある人が言ってた、C++ を学ぶ最良の方法:

C++ をある程度出来る人にコードレビューしてもらう事。
間違った道から、正しい道に戻してもらう。


RL78 関係のフレームワークをメンテナンス

長い間、RX マイコンのフレームワークに集中していたので、RL78 のフレームワークには手を付けていなかった・・

しかし、最近、このフレームワークを使っている人が Qiita にコメントしていた。

使っている人がいるなら、RX マイコンのフレームワーク更新で改善されたエッセンスなどを RL78 にも反映したい。

そこで、それらの実装とテストを行った。

何故か、フラッシュが出来ない・・

Windows10 になって、RL78 の開発環境を作り直して、FIRST_sample を書こうと思ったが、エラーが出てフラッシュが出来ない。

CP2102N が悪いのかと思い、FTDI に変えてみたが、駄目で、エラーメッセージを頼りに、プロトコルのコードのパラメータを変えたり、色々したが駄目・・・

ルネサス・フラッシュ・プログラマーの最新版を導入して、それで書こうと思ったが、やはりうまく行かない・・・

※最新の RFP では、RL78 へのフラッシュ書き込みに、COM ポートで行う最小限のハードを使った場合をサポートしている。

ところが、FTDI で、再度やり直したら、今度は書き込みが正常に行われた・・・
※何が理由なのかさっぱり判らない(RFP のインストールが関係しているのかもしれない・・・)

改善した箇所

テンプレートクラスの実態を定義

以前の実装では、最適化しない(-O0)場合、テンプレート内スタティック変数の実態が無い為、リンクに失敗する。

テンプレートクラスでは、スタテック変数の実態を定義する必要があり、特殊な書き方が必要だ。
これは、書籍や C++ テンプレートの解説でも殆ど見ない方法だと思う。

※最適化を行うと、実態は必要無く(最適化すると、実態を経由しなくなる)問題にならない事が多い。

※デバッグが必要な場合、最適化は行わない。

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  インターバル・タイマー・クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class _>
    struct itm_t {

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  12ビット・インターバル・タイマ・コントロール・レジスタ(ITMC)
            @param[in]  T   アクセス・クラス
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        template <class T>
        struct itmc_t : public T {
            using T::operator =;
            using T::operator ();
            using T::operator |=;
            using T::operator &=;

            bit_rw_t<T, bitpos::B15>   RINTE;  ///< 12ビット・インターバル・タイマの動作制御
            bits_rw_t<T, bitpos::B0, 12> ITCMP;  ///< 12ビット・インターバル・タイマのコンペア値設定

        };
        typedef itmc_t< rw16_t<0xFFF90> > ITMC_;
        static ITMC_ ITMC;

...

    };
    // テンプレート内、スタティック定義、実態:
    template<class _> typename itm_t<_>::ITMC_ itm_t<_>::ITMC;

    typedef itm_t<void> itm;

↑のように実装する事でリンクエラーを回避出来る。

「itm_t」クラスは、疑似的にテンプレート化してある。
これは、ヘッダーのみで運用する場合、実態を定義するのに都合が良い。

PORT 定義クラスを更新

シングルポートを定義するテンプレートクラスを最新の物にした。

RX マイコンのフレームワークで得た、知見を組み込んである。

ただ、RL78 のポート関係レジスターは、構造的では無い構成なので、多少異なる構成となっている。

namespace {

    // 吸い込みなので、三番目のパラメーターを「false」とする。
    // LED::P = 1 で、実際のポートは、0になり、LED が点灯する。
    typedef device::PORT<device::port_no::P4, device::bitpos::B3, false> LED;

}

int main(int argc, char* argv[])
{
    utils::port::pullup_all();  ///< 安全の為、全ての入力をプルアップ

    LED::DIR = 1;  // 出力設定

    bool f = true;
    while(1) {
        utils::delay::milli_second(250);  ///< 0.25 秒毎の点滅
        LED::P = f;
        f = !f;
    }
}

PSG_sample を追加

ついでなので、先日実装した PSG エミュレーションで音楽演奏のプロジェクトを追加した。

とりあえず、思ったように鳴っているようだ。

VScode の設定を追加

VScode でソースを編集する場合に、インテリセンスを活用する為、設定ファイルを追加してある。

ただ、理由が不明で、赤線がでる場合がある・・・

これは、今後の課題とする・・・


まとめ

RL78 フレームワークを利用している方には、色々細かく修正したので、最新版を取得すると、コンパイルが通らないかもしれない。

多分、修正にはそんなに多くの苦労は無いものと思うが、新しい版を受け入れてもらいたい。

RXマイコンで PSG 音源をエミュレーション

PSG 音源を生成するテンプレートクラス

最近、R8C 関係の GitHub を整理している。
将来的に、初心者(小学生、中学生)向けの電子工作向けボードを作る予定で、それに向けたものだ。
1000円くらいで、電子工作とプログラミングの初級が学べて、何か作れるような物をリリースしたい。

そこで、R8C 用色々なコンテンツを作成している、その一環で PSG 音源を使った音楽再生を行ってみた。

詳しい記事は、Qiita にある。

とりあえず、R8C でそれなりに鳴ったので、RX マイコンにも移植してみた。

実験したのは、RX72N Envision Kit で、このデバイスには、SSIE 接続の D/A があるので、簡単だ。
サンプリング周波数は 48KHz 固定なので、多少オーバースペックかもしれない。

PSG とは?

PSG は、大昔に、矩形波を基本とする音源で、音楽を演奏するデバイスが売られていたのがルーツと思う。
AY-3-8910 とか、そんなデバイスが発売されて、Apple][ 用のボードで演奏したのを覚えている・・
※多分高校生くらいだったから、電子音楽の衝撃は今でも忘れない。
※今考えると楽譜を入力するオーサリングツールは素晴らしく良く出来ていた。

ファミコンでは、それに似た仕様の音源が内蔵されている。

C++ テンプレートの柔軟性

元は、R8C 用に実装したものだが、ほぼ無改造でそのまま再利用出来た。
※実際は、サンプリング周波数が高く、内部で演算がオーバーフローしたので、多少改修した。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  PSG Manager テンプレート class
        @param[in]  SAMPLE  サンプリング周期
        @param[in]  TICK    演奏tick(通常100Hz)
        @param[in]  BSIZE   バッファサイズ(通常512)
        @param[in]  CNUM    チャネル数(通常4)
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint16_t SAMPLE, uint16_t TICK, uint16_t BSIZE, uint16_t CNUM>
    class psg_mng : public psg_base {

psg_mng テンプレートクラスのプロトタイプは上記のようになっていいて、

  • サンプリング周期
  • 演奏 TICK
  • バッファサイズ
  • チャネル数

を与えるようになっている。
※バッファサイズはこのモジュールとは直接関係無いので、除く方が良さそうだが、とりあえず、R8C 版との互換でそのままにしてある。

TICK を 100Hz、サンプリングを 48KHz とすると、480 バイトのデータ列を生成する必要があるので、バッファは 512 バイト。
※1サンプルづつ波形を引っ張るのなら、バッファサイズは含めなくて良い。

R8C では、メモリを節約する為、psg_mng で波形を生成したデータを、直接、PWM ストリームのバッファにしていたが、RX マイコンでは、構成が異なるので、一旦テンポラリに生成して、それを、サウンド出力クラスに食わすようにしている。

        {
            uint32_t n = SAMPLE / TICK;
            psg_mng_.set_wav_pos(0);
            psg_mng_.render(n);
            typename SOUND_OUT::WAVE t;
            for(uint32_t i = 0; i < n; ++i) {
                uint8_t w = psg_mng_.get_wav(i);
                w -= 0x80;
                t.l_ch = t.r_ch = (static_cast<int16_t>(w) << 8) | ((w & 0x7f) << 1);
                sound_out_.at_fifo().put(t);
            }

            if(delay > 0) {
                delay--;
            } else {
                psg_mng_.service();
            }
        }

SSIE と波形出力テンプレート定義

これは、オーディオを扱うアプリで散々利用してきたテンプレートなので、定義も使い方も、変わる処が無い。
この辺りの柔軟性は C++ で実装した場合の特典みたいなものだと思う。

#ifdef USE_DAC
    typedef sound::dac_stream<device::R12DA, device::TPU0, device::DMAC0, SOUND_OUT> DAC_STREAM;
    DAC_STREAM  dac_stream_(sound_out_);

    void start_audio_()
    {
        uint8_t dmac_intl = 4;
        uint8_t tpu_intl  = 5;
        if(dac_stream_.start(48'000, dmac_intl, tpu_intl)) {
            utils::format("Start D/A Stream\n");
        } else {
            utils::format("D/A Stream Not start...\n");
        }
    }
#endif

#ifdef USE_SSIE
    typedef device::ssie_io<device::SSIE1, device::DMAC1, SOUND_OUT> SSIE_IO;
    SSIE_IO     ssie_io_(sound_out_);

    void start_audio_()
    {
        {  // SSIE 設定 RX72N Envision kit では、I2S, 48KHz, 32/16 ビットフォーマット固定
            uint8_t intr = 5;
            uint32_t aclk = 24'576'000;
            uint32_t lrclk = 48'000;
            auto ret = ssie_io_.start(aclk, lrclk, SSIE_IO::BFORM::I2S_32, intr);
            if(ret) {
                ssie_io_.enable_mute(false);
                ssie_io_.enable_send();  // 送信開始
                utils::format("SSIE Start: AUDIO_CLK: %u Hz, LRCLK: %u\n") % aclk % lrclk;
            } else {
                utils::format("SSIE Not start...\n");
            }
        }
    }
#endif

SSIE インターフェース用と、内蔵 D/A 用の二種類がある。
※RX72T、RX66T、RX24T、など、D/A 内臓のデバイスでも簡単に流用出来ると思う。

他、オーディオ出力コンテキストにサンプリング周波数を設定する関数を出してある。

    void set_sample_rate(uint32_t freq)
    {
#ifdef USE_DAC
        dac_stream_.set_sample_rate(freq);
#endif
#ifdef USE_SSIE
        sound_out_.set_output_rate(freq);
#endif
    }

初期化

初期化では、TICK タイマー、オーディオ出力など初期化する。
また、サンプルで入れた楽曲のスコアテーブルを設定する。

    {
        uint8_t intr = 4;
        cmt_.start(TICK, intr);
    }

    start_audio_();

    {  // サンプリング周期設定
        set_sample_rate(SAMPLE);
    }

    psg_mng_.set_score(0, score0_);
    psg_mng_.set_score(1, score1_);

メインループ

後は、psg_mng テンプレートクラスで波形を生成して、オーディオ出力に渡すだけとなる。
その際、8ビットの波形を16ビットに拡張している。
又、モノラルなので、ステレオにしている。

    uint8_t cnt = 0;
    uint8_t delay = 200;
    while(1) {
        cmt_.sync();
        {
            uint32_t n = SAMPLE / TICK;
            psg_mng_.set_wav_pos(0);
            psg_mng_.render(n);
            typename SOUND_OUT::WAVE t;
            for(uint32_t i = 0; i < n; ++i) {
                uint8_t w = psg_mng_.get_wav(i);
                w -= 0x80;
                t.l_ch = t.r_ch = (static_cast<int16_t>(w) << 8) | ((w & 0x7f) << 1);
                sound_out_.at_fifo().put(t);
            }

            if(delay > 0) {
                delay--;
            } else {
                psg_mng_.service();
            }
        }

        ++cnt;
        if(cnt >= 50) {
            cnt = 0;
        }
        if(cnt < 25) {
            LED::P = 0;
        } else {
            LED::P = 1;
        }
    }

楽曲の定義

最近覚えた、便利な使い方:

C++11 になって、不便で不完全な「enum」から解放された。
「enum class」は、型に厳密で、異なった型を受け付けない、もちろん、整数型から派生しているので、「static_cast」を使って変換可能だ。

厳密になった為、色々な「型」を組み合わせるようなデータ列では不便となる。

そこで「union」を使って、利用出来る型を受け付けるようにしておき、コンストラクターで、異なる型からデータ列を生成するようにした。

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  スコア・コマンド構造 @n
                    ・KEY, len @n
                    ・TR, num @n
                    ・TEMPO, num @n
                    ・FOR, num
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        struct SCORE {
            union {
                KEY     key;
                CTRL    ctrl;
                uint8_t len;
            };
            constexpr SCORE(KEY k) noexcept : key(k) { }
            constexpr SCORE(CTRL c) noexcept : ctrl(c) { }
            constexpr SCORE(uint8_t l) noexcept : len(l) { }
        };

これで、スコアデータ列を構造的に作れるようになった。
※コンパイル時に値を決定して ROM 化するので「constexpr」を付加している。

この簡単な構造体で以下のように楽譜データを記述出来る。

    constexpr PSG::SCORE score0_[] = {
        PSG::CTRL::VOLUME, 128,
        PSG::CTRL::SQ50,
        PSG::CTRL::TEMPO, 80,
        PSG::CTRL::ATTACK, 175,
        // 1
        PSG::KEY::Q,   8,
        PSG::KEY::E_5, 8,
        PSG::KEY::D_5, 8,
        PSG::KEY::E_5, 8,
        PSG::KEY::C_5, 8,
        PSG::KEY::E_5, 8,
        PSG::KEY::B_4, 8,
        PSG::KEY::E_5, 8,

まとめ

やはり、C++ の柔軟性と、再利用性は優れている。

8/16 ビットマイコン用ソースコードが、RX マイコンでも、ほぼそのまま再利用出来た。

当然だが、R8C の PWM 再生に比べると、高品質だ。

YouTube:
https://www.youtube.com/watch?v=4ZHuMYcSQko

Makefile を共通化

Makefile 共通化

RX マイコンの各プロジェクトで、Makefile で共有出来る部分を抜き出して、共通化を行った。

今まで、同じような「手順」を各プロジェクト毎にコピーしていたが、二度手間だし、プロジェクト数が多く、作業が大変だった。
※Makefile を複数に別けたく無かったとゆーこだわりがあったのだが、現在のように手順がそこそこ複雑だと、そうする「こだわり」はあまり意味が無い。

共通部分

# -*- tab-width : 4 -*-
#=======================================================================
#   @file
#   @brief  RX microcontroller share Makefile
#   @author 平松邦仁 (hira@rvf-rc45.net)
#   @copyright  Copyright (C) 2021 Kunihito Hiramatsu @n
#               Released under the MIT license @n
#               https://github.com/hirakuni45/RX/blob/master/LICENSE
#=======================================================================

# System include path for each environment
ifeq ($(OS),Windows_NT)
SYSTEM := WIN
# C++.boost root
LOCAL_PATH  =   /mingw64
else
  UNAME := $(shell uname -s)
  ifeq ($(UNAME),Linux)
    SYSTEM := LINUX
    LOCAL_PATH = /usr/local
  endif
  ifeq ($(UNAME),Darwin)
    SYSTEM := OSX
    OSX_VER := $(shell sw_vers -productVersion | sed 's/^\([0-9]*.[0-9]*\).[0-9]*/\1/')
    LOCAL_PATH = /opt/local
  endif
endif

まず、これは、Windows、Linux、OS-X の環境を判断して、微妙な違いを吸収する。
※一番重要な事は、boost のパスを各環境で同じように扱う事。


LIB_ROOT    =   ../../rxlib/lib

INC_SYS     =   ../../rxlib/include $(LOCAL_PATH)/include

PROG_VERIFY = --verify
  • LIB_ROOT は、RX マイコン専用ライブラリのパスを設定している。
  • INC_SYS は、RX マイコン専用ライブラリのインクルードパスと、ローカルのインクルードパス(boost)などを設定している。
  • PROG_VERIFY は、rx_prog でフラッシュ書き込みする際に、「VERIFY」を行う場合のキーワードとなっている。
  • RX24T は、通常の手順で、VERIFY が行われない為、無効にする必要がある。

ifeq ($(RX_DEF),SIG_RX24T)
  RX_CPU = RX24T
  RX_OPT = v2
  LDSCRIPT  =   ../../RX24T/$(DEVICE).ld
  PROG_VERIFY =
endif

ifeq ($(RX_DEF),SIG_RX64M)
  RX_CPU = RX64M
  RX_OPT = v2
  LDSCRIPT  =   ../../RX64M/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX71M)
  AS_OPT    +=  --defsym MEMWAIT=1
  RX_CPU = RX71M
  RX_OPT = v2
  LDSCRIPT  =   ../../RX71M/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX65N)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes
  USER_LIBS += drw2d
  RX_CPU = RX65N
  RX_OPT = v2
  LDSCRIPT  =   ../../RX65x/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX66T)
  RX_CPU = RX66T
  RX_OPT = v3
  LDSCRIPT  =   ../../RX66T/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72T)
  RX_CPU = RX72T
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72T/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72N)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes 
  USER_LIBS += drw2d
  RX_CPU = RX72N
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72N/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72M)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes
  USER_LIBS += drw2d
  RX_CPU = RX72M
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72M/$(DEVICE).ld
endif

この条件文で、RX マイコン毎に異なる設定を行っている。
現状では、「SIG_xxx」のキーワード毎に行っているが、将来的には、DEVICE で IC の型番を設定しているので、それに従って行うのが妥当だと思う。

※型番で行えば、ピン数の違いによる変更をソースコードへ伝達する事が出来る。

RX71M だけは、ちょっと特殊で、スーパーバイザーモードでしかアクセス出来ないレジスタがある。
そこで、そのレジスタを、start.s アセンブラブログラム内で行っている。「--defsym MEMWAIT=1」

FreeRTOS の場合、ユーザーモードに移行しない事が重要だが、それは、各プロジェクト毎に設定する。

# for FreeRTOS option
AS_OPT      =   --defsym NOT_USER=1

# Renesas GNU-RX gcc compiler version check
TARGET_ISA_TEXT := $(shell rx-elf-gcc --target-help | grep ISA)

# Renesas GNU-RX (8.3.0) compiler 
ifeq ($(TARGET_ISA_TEXT),)
  # for gcc-7.5.0 current gcc source build
  AS_DEFS       =   -mcpu=rx600
  CC_DEFS       =   -mcpu=rx600 -Wa,-mcpu=rxv2
  CP_DEFS       =   -mcpu=rx600
else # Renesas GNU-RX gcc 8.3.0
  AS_DEFS       =   -misa=$(RX_OPT)
  CC_DEFS       =   -misa=$(RX_OPT)
  CP_DEFS       =   -misa=$(RX_OPT)
endif

これは、Renesas GNU-RX gcc コンパイラと、プレーンな gcc コンパイラで、オプションが異なるので、それを自動判別する。


# You should not have to change anything below here.
AS          =   rx-elf-as
CC          =   rx-elf-gcc
CP          =   rx-elf-g++
AR          =   rx-elf-ar
LD          =   rx-elf-ld
OBJCOPY     =   rx-elf-objcopy
OBJDUMP     =   rx-elf-objdump
SIZE        =   rx-elf-size

AFLAGS      =   $(AS_OPT) $(AS_DEFS)
CFLAGS      =   -std=gnu99 $(CC_OPT) $(OPTIMIZE) $(CC_DEFS) $(DEFS)
PFLAGS      =   -std=c++17 $(CP_OPT) $(OPTIMIZE) $(CP_DEFS) $(DEFS)
FLAGS       = $(AS_OPT) $(AS_DEFS) $(CC_OPT) $(CP_OPT) $(OPTIMIZE) $(CC_DEFS) $(CP_DEFS) $(DEFS)

# FLAGS_CMP := $(shell cat $(TARGET).opt)

override LDFLAGS = $(MCU_TARGET) -nostartfiles -Wl,-Map,$(TARGET).map -T $(LDSCRIPT)

OBJCOPY_OPT =   --srec-forceS3 --srec-len 32

OBJECTS =   $(addprefix $(BUILD)/,$(patsubst %.s,%.o,$(ASOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.c,%.o,$(CSOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.cpp,%.o,$(PSOURCES)))

DOBJECTS =  $(addprefix $(BUILD)/,$(patsubst %.c,%.o,$(CSOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.cpp,%.o,$(PSOURCES)))

DEPENDS =   $(patsubst %.o,%.d, $(DOBJECTS))

# all, clean: optional make command
.PHONY: all clean clean_depend run text
.SUFFIXES :
.SUFFIXES : .hpp .s .h .c .cpp .d .o

all: $(BUILD) $(TARGET).elf text

$(TARGET).elf: $(OBJECTS) $(LDSCRIPT) Makefile
    $(CC) $(LDFLAGS) $(LIBINCS) -o $@ $(OBJECTS) $(LIBS)
    $(SIZE) $@

$(BUILD)/%.o: %.s
    mkdir -p $(dir $@); \
    $(AS) -c $(AOPT) $(AFLAGS) $(AINCS) -o $@ $<

$(BUILD)/%.o : %.c
    mkdir -p $(dir $@); \
    $(CC) -c $(COPT) $(CFLAGS) $(CINCS) $(CCWARN) -o $@ $<

$(BUILD)/%.o : %.cpp
    mkdir -p $(dir $@); \
    $(CP) -c $(POPT) $(PFLAGS) $(PINCS) $(CPWARN) -o $@ $<

$(BUILD)/%.d: %.c
    mkdir -p $(dir $@); \
    $(CC) -MM -DDEPEND_ESCAPE $(COPT) $(CFLAGS) $(APPINCS) $< \
    | sed 's/$(notdir $*)\.o:/$(subst /,\/,$(patsubst %.d,%.o,$@) $@):/' > $@ ; \
    [ -s $@ ] || rm -f $@

$(BUILD)/%.d: %.cpp
    mkdir -p $(dir $@); \
    $(CP) -MM -DDEPEND_ESCAPE $(POPT) $(PFLAGS) $(APPINCS) $< \
    | sed 's/$(notdir $*)\.o:/$(subst /,\/,$(patsubst %.d,%.o,$@) $@):/' > $@ ; \
    [ -s $@ ] || rm -f $@

clean:
    rm -rf $(BUILD) $(TARGET).elf $(TARGET).mot $(TARGET).lst $(TARGET).map

clean_depend:
    rm -f $(DEPENDS)

これは、gcc の設定で、従属規則を自動生成する仕組みを内包する。


lst: $(TARGET).lst

%.lst: %.elf
    $(OBJDUMP) -h -S $< > $@

# Rules for building the .text rom images

text: mot lst

lst: $(TARGET).lst
mot: $(TARGET).mot
bin: $(TARGET).bin

%.lst: %.elf
    $(OBJDUMP) -h -S $< > $@

%.mot: %.elf
    $(OBJCOPY) $(OBJCOPY_OPT) -O srec $< $@

%.bin: %.elf
    $(OBJCOPY) -O binary $< $@

これは、リンカーで実行バイナリーを作成後、リストファイル、モトローラーファイル、バイナリーファイルを作成する手順だ。


# Serial Flash write 
run:
    $(MAKE)
    rx_prog -d $(RX_CPU) --progress --erase --write $(PROG_VERIFY) $(TARGET).mot

最後は、シリアル接続で、フラッシュ書き込みを行うツールの設定などになっている。

個別部分

上記のように、共通出来る部分を追い出したので、個別部分はシンプルとなった。

# -*- tab-width : 4 -*-
#=======================================================================
#   @file
#   @brief  RX72N Makefile
#   @author 平松邦仁 (hira@rvf-rc45.net)
#   @copyright  Copyright (C) 2020, 2021 Kunihito Hiramatsu @n
#               Released under the MIT license @n
#               https://github.com/hirakuni45/RX/blob/master/LICENSE
#=======================================================================
TARGET      =   raytracer_sample

DEVICE      =   R5F572NN

RX_DEF      =   SIG_RX72N

BUILD       =   release
# BUILD     =   debug

VPATH       =   ../../

ASOURCES    =   common/start.s

CSOURCES    =   common/init.c \
                common/vect.c \
                common/syscalls.c

PSOURCES    =   RAYTRACER_sample/main.cpp \
                graphics/font8x16.cpp \
                graphics/color.cpp \
                common/stdapi.cpp

USER_LIBS   =

USER_DEFS   =

INC_APP     =   . ../ ../../

AS_OPT      =

CP_OPT      =   -Wall -Werror \
                -Wno-unused-variable \
                -Wno-unused-function \
                -fno-exceptions

CC_OPT      =   -Wall -Werror \
                -Wno-unused-variable \
                -fno-exceptions

ifeq ($(BUILD),debug)
    CC_OPT += -g -DDEBUG
    CP_OPT += -g -DDEBUG
    OPTIMIZE = -O0
endif

ifeq ($(BUILD),release)
    CC_OPT += -DNDEBUG
    CP_OPT += -DNDEBUG
    OPTIMIZE = -O3
endif

-include ../../common/makefile

-include $(DEPENDS)

これは、RX72T の RAYTRACER_sample の Makefile で、基本、リンクするファイルの記述が殆どだ。

リリースビルドと、デバッグビルドの違いで切り替える部分は共通化しなかった。
最適化を変えたり、プロジェクト毎に微妙な違いを許容する事が出来るように配慮した。


まとめ

共通化する過程で、オプションなどを変更した場合に、フルコンパイルが自動で行えるように出来ないか検討したが、make を完全に理解していない為、思ったように作れなかった・・

これは今後の課題にしたいと思う。

RXマイコン、クロックプロファイルクラスの導入

クロック設定を見直す

C++ でRXマイコンフレームワークを作り始めた頃、あまり考えずに、適当に作った部分が、未だに「悪い」見本として残っている。

RXマイコンのクロックジェネレータは、意外と複雑で、面倒な設定を要するデバイスとなっており、柔軟性も必要なのに、かなり適当な創りとなっている。

そこで、これを見直して、もう少し「サッパリ」としたより良い物に変更する。
意外と広範囲な修正になる事から、今まで、見なかった事にしてきたものの、「痛い」部分は速いうちに処置した方が良いので、修正を行った。

以前の設定

以前は、「Makefile」と「main.cpp」の両方で微妙な設定を行っていた・・・

Makefile の設定:

USER_DEFS   =   SIG_RX71M \
                F_ICLK=240000000 \
                F_PCLKA=120000000 F_PCLKB=60000000 F_PCLKC=60000000 F_PCLKD=60000000 \
                F_FCLK=60000000 F_BCLK=120000000

main.cpp:

#if defined(SIG_RX71M)
    typedef device::system_io<12'000'000, 240'000'000> SYSTEM_IO;

...

int main(int argc, char** argv)
{
    SYSTEM_IO::setup_system_clock();

ベースクリスタルの周波数は、ソースコードに埋め込んであるのに、クロックジェネレータの分周器による周期は「Makefile」で環境変数で設定してある・・・

これは、初期の実験コードで色々やっていた時、テスト的に行ったものが、「標準」となってしまい、見直す事が先延ばしになり現在に至ったもの。
第三者を交えて、コードレビューを行えば、かなり早い段階で「ツッコミ」を入れられたと思うが、の機会が無く、そのままになっていた・・

改修後

改修後、クロック設定プロファイルクラスを新規に作り、そこに定数として設定してある。

#pragma once
//=====================================================================//
/*! @file
    @brief  RX71M グループ・クロック。プロファイル @n
            クロックジェネレータで発生させる周波数の定義
    @author 平松邦仁 (hira@rvf-rc45.net)
    @copyright  Copyright (C) 2021 Kunihito Hiramatsu @n
                Released under the MIT license @n
                https://github.com/hirakuni45/RX/blob/master/LICENSE
*/
//=====================================================================//
#include <cstdint>

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  クロック・プロファイル・クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    class clock_profile {
    public:
        static const uint32_t   BASE        =  12'000'000;      ///< 外部接続クリスタル
        static const uint32_t   PLL_BASE    = 240'000'000;      ///< PLL ベースクロック

        static const uint32_t   ICLK        = 240'000'000;      ///< ICLK 周波数
        static const uint32_t   PCLKA       = 120'000'000;      ///< PCLKA 周波数
        static const uint32_t   PCLKB       =  60'000'000;      ///< PCLKB 周波数
        static const uint32_t   PCLKC       =  60'000'000;      ///< PCLKC 周波数
        static const uint32_t   PCLKD       =  60'000'000;      ///< PCLKD 周波数
        static const uint32_t   FCLK        =  60'000'000;      ///< FCLK 周波数
        static const uint32_t   BCLK        = 120'000'000;      ///< BCLK 周波数
    };
}

このソースは、各プラットホーム毎に切り替えて、他ソースから参照するようにしてある。

クリスタルが特殊な場合は、このファイルに追加して、環境変数で切り替えれば良いだろうと思う。

PLL_BASE の周波数は、BASE 周波数の倍率(0.5単位)で割り切れる必要がある。
当然、他の周期も、PLL_BASE からの分周比で割り切れる周波数を設定する必要がある。

また、mainの最初で、クロック周波数を切り替える関数名は、

typedef device::system_io<> SYSTEM_IO;

int main(int argc, char** argv)
{
    SYSTEM_IO::boost_master_clock();

とした。

※RX71M は、クロック設定の特定レジスタが、スーパーバイザモードでアクセスする必要があり、その部分を「start.s」で行っている為、「Makefile」にアセンブラに渡す変数がある。

AS_OPT      =   --defsym MEMWAIT=1

クロックジェネレーターの周波数を参照する場合、以下のように行える。

    auto iclk = device::clock_profile::ICLK / 1'000'000;
    utils::format("Start test for '%s' %d[MHz]\n") % system_str_ % iclk;

内臓高速発信器を利用する場合

typedef device::system_io<device::system_base::OSC_TYPE::HOCO> SYSTEM_IO;

内蔵高速発信器は、通常、16MHz、18MHz、20MHzがあり、「BASE」にどの周波数を使うか指示する。

まとめ

かなり広範囲な修正だったが、それだけの価値はあると思う。

RXマイコン、割り込み関係整理

RX72T で CAN の検証で気がついた・・

RX72T で CAN の動作確認をした際、思ったように動作しない・・
※以前に CAN のサンプルを作成する際、RX64M 1 台で行っており、複数台で互いに通信する確認はしていなかった・・

  • データ送信しても、データが送られない。
  • データの受信もしない。

この感じは、割り込みぽぃなぁーと思い、
割り込み関係を確認したら、単純に CAN 関係割り込みが設定されていないだけだった・・
※本来、割り込みが正しく設定されない場合、初期化時にエラーを返す必要がある・・

  • 標準割り込みは、ちゃんと実装していない事を思い出す。
  • 新規にペリフェラルのマネージャーを実装した時に、追加していた。
  • なので、デバイスによりバラバラで、統一性が無い・・・
  • ここらで、ちゃんと実装しておこうと思う。

RX64M では、CAN 関係は、選択型割り込みBなので、問題無かった。
RX66T/RX72T では、CAN の割り込みは通常ベクターなので、通常ベクターの登録関数に CAN 関係の割り込みを追加する必要がある。
通常割り込みで、厄介なのは、割り込みベクター番号から、IER、IPR などの割り込み設定関係へのアクセスでは規則性が無い場合がある。
その為、対応する通常ベクターを全て実装しておく必要がある・・・
※単純には、割り込みベクター番号から、IER、IPR レジスターを推定出来ない・・

また、IPR は、シェアされて共通になっている場合もある。


割り込み関係の整理

初期の実装では、割り込み設定は、ペリフェラル別に行っていた。
※初期の実装では、ペリフェラルの定義に、割り込みベクターを「定数」として含めていなかったので、個別に対応する必要があった。
現在の実装では、割り込みベクター型や番号は、ペリフェラルの定義を参照する事で得られるようになっている。

↓現在のSCIクラステンプレートの実装:

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  SCI 定義基底クラス
        @param[in]  base    ベース・アドレス
        @param[in]  per     ペリフェラル型
        @param[in]  txv     送信割り込みベクター
        @param[in]  rxv     受信割り込みベクター
        @param[in]  INT     送信終了割り込みベクター型
        @param[in]  tev     送信終了割り込みベクター
        @param[in]  pclk    PCLK 周波数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t base, peripheral per, ICU::VECTOR txv, ICU::VECTOR rxv,
        typename INT, INT tev, uint32_t pclk>
    struct sci_t {

        static const auto PERIPHERAL = per; ///< ペリフェラル型
        static const auto TX_VEC = txv;     ///< 受信割り込みベクター
        static const auto RX_VEC = rxv;     ///< 送信割り込みベクター
        static const auto TE_VEC = tev;     ///< 送信終了割り込みベクター
        static const uint32_t PCLK = pclk;  ///< PCLK 周波数

SCI などでは、割り込みを使う場合、受信と送信、同時に使う仕様なので、それでも良かった。
しかし、割り込みベクター別に設定を行うべきなので、その仕様を改めた。

        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みレベルを設定する
            @param[in]  per 周辺機器タイプ
            @param[in]  lvl 割り込みレベル(0の場合、割り込み禁止)
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        static bool set_level(peripheral per, uint8_t lvl) noexcept
        {
            bool ena = lvl != 0 ? true : false;
            switch(per) {

            case peripheral::SCI1:
                ICU::IPR.RXI1 = lvl;
                ICU::IER.RXI1 = ena;
                ICU::IPR.TXI1 = lvl;
                ICU::IER.TXI1 = ena;
                break;
            case peripheral::SCI5:
                ICU::IPR.RXI5 = lvl;
                ICU::IER.RXI5 = ena;
                ICU::IPR.TXI5 = lvl;
                ICU::IER.TXI5 = ena;
                break;

↑以前の実装では、ペリフェラル毎に割り込みを設定していた・・(今後、廃止する予定)

↓今後、割り込みベクター毎に登録する・・

        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みレベルを設定する
            @param[in]  vec 割り込み要因
            @param[in]  lvl 割り込みレベル(0の場合、割り込み禁止)
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        static bool set_level(ICU::VECTOR vec, uint8_t lvl) noexcept
        {
            bool ena = lvl != 0 ? true : false;
            switch(vec) {

            case ICU::VECTOR::RXI1:
                ICU::IER.RXI1 = 0;
                ICU::IPR.RXI1 = lvl;
                ICU::IER.RXI1 = ena;
                break;
            case ICU::VECTOR::TXI1:
                ICU::IER.TXI1 = 0;
                ICU::IPR.TXI1 = lvl;
                ICU::IER.TXI1 = ena;
                break;

かなり、大掛かりな修正なので、時間がかかりそう・・・


作業実績

RX24T RX64M RX71M RX65N RX72N RX66T RX72T
icu.hpp O O O O O O O
icu_mgr.hpp O O O O O O O

all_compile (Debug)

Pass.

all_compile (Release)

Pass.

追記 (2021-05-11 08:12:55 Tuesday)

  • ハードウェアーマニュアルを良く読むと、IR レジスタ、IER レジスタは、割り込み要因の番号と一致するようだ。
  • IPR レジスタに関しては、割り込み要因番号が32以下の場合と、特定のレジスタ(RX24T)の場合に、例外的なアクセスを行えば良いらしい。

そこで、IPR クラスの [] オペレーターによるアクセスを少々工夫する事で、余分なコードを削減する事が出来た。

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  IPR レジスタ @n
                    全て、下位4ビットが有効
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        template <uint32_t base>
        struct ipr_t {

...

            //-------------------------------------------------------------//
            /*!
                @brief  []オペレータ
                @param[in]  vec     標準割り込みベクター型
                @return IPR レジスターの参照
            */
            //-------------------------------------------------------------//
            volatile uint8_t& operator [] (VECTOR vec) noexcept {
                uint32_t idx = 0;
                switch(vec) {
                case VECTOR::BUSERR: idx = 0; break;
                case VECTOR::RAMERR: idx = 0; break;
                case VECTOR::FIFERR: idx = 1; break;
                case VECTOR::FRDYI:  idx = 2; break;
                case VECTOR::SWINT2: idx = 3; break;
                case VECTOR::SWINT:  idx = 3; break;
                case VECTOR::CMI0:   idx = 4; break;
                case VECTOR::CMI1:   idx = 5; break;
                case VECTOR::CMWI0:  idx = 6; break;
                case VECTOR::CMWI1:  idx = 7; break;

                default: idx = static_cast<uint32_t>(vec); break;
                }
                return *reinterpret_cast<volatile uint8_t*>(base + idx);
            }
        };
        typedef ipr_t<0x00087300> IPR_;
        static IPR_ IPR;

icu_mgr クラスの「set_level()」関数では、以下のようにシンプルとなった。

        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みレベルを設定する
            @param[in]  vec 通常割り込みベクター型
            @param[in]  lvl 割り込みレベル(0の場合、割り込み禁止)
        */
        //-----------------------------------------------------------------//
        static void set_level(ICU::VECTOR vec, uint8_t lvl) noexcept
        {
            bool ena = lvl != 0 ? true : false;
            ICU::IER.enable(vec, 0);
            ICU::IPR[vec] = lvl;
            ICU::IER.enable(vec, ena);
        }