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

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

R8C フラッシュプログラム

OS-X 用に R8C 用 gcc もビルド出来た事だし、フラュシュライターを実装する事にした。
これが出来れば、MacBook などで R8C の開発が全て完結する〜

R8C/M120AN、R8C/M110AN の場合、mode 端子を Low にしてリセットをかけると、ブートモードになり
シリアル通信で、コマンドとデータを送る事で、内部フラッシュメモリーをプログラミング出来る。

尚、ライターは簡単に自作出来るが、出来合いもあり、これがお勧めかも・・
R8C/M12A ライタ&LEDキット【MR8C-TRM】

通信プロトコルは、以下のリンクから資料を取得した。
R8C/1x、2xシリーズ 標準シリアル入出力モードプロトコル仕様書
R8C/Mxシリーズ、LAxAグループ 標準シリアル入出力モードプロトコル仕様書

最初のハードルは、OS−Xって、どうやって「シリアル通信」すんの?
だった、でも、ちょっと調べたら、どうやらPOSIX準拠のシリアル通信APIをサポートしており、要はLinuxと同じ
コードで、大丈夫のようだったー、これは、MinGWでもサポートしているようなので、簡単にマルチプラットホーム
に出来るおまけ付きだ!
※ただ、デバイスファイル名は、OS依存なのだと思う。

とりあえず、FTDIのシリアルUSBブリッジを接続したら、/dev/tty*にそれらしいデバイスが認識される。

crw-rw-rw-  1 root  wheel   18,   6  5  7 21:18 /dev/tty.usbserial-A600e0xq

これを使って、簡単な通信を行いテストすると、普通に動作する〜
「rs232c_io.hpp」に必要な機能を盛り込んだ〜

それではと、色々ガリガリプログラムを作成して、とりあえず、出来上がった〜
まだ機能追加中で、中途半端な状態ではあるけれども、一応動作しているようだ。
GitHubで公開している。

仕様的には、千秋ゼミで公開されているr8cprogを元にしている。
※このプログラムをダウンロードするには、登録が必要。

ただ、C++ でスクラッチから組んだものなので、動作は、オリジナルとは違うので注意。
俺俺フレームワークとして、「file_io、string_utils、sjis2_utf16」など同梱している。

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

現在は、デバイス名は無視されています。
ポートは、何も指定しないと、上記のデバイスパスが使われるようにしてあります。
また、コンパイルには boost が必要です。

Windows 環境では、テストされていません。
Msys2環境でコンパイル、実行を試しました。
※select の挙動が、OS−Xと違っていて、少し悩みましたが、とりあえず、問題無く動作するようになりました。

R8Cコンパレーターのテスト

R8Cの機能を一通り実装、テストしている。

今回は、コンパレータークラス。

テストプログラムでは、ICOMP3、IVREF3 を電圧比較をして、その結果、LEDの点滅速度が変わると言うもの。
※実験では、REF側に、VCCを1/2に分圧し、COMP側にボリュームを接続、確認した。

R8Cには2チャネルのコンパレーターがある、機能的には、単純な物だが、割り込みを使った時に、実行する
処理をどのような構成にするか、考えてみた。
以前にタイマークラスを実装した時に、タイマー割り込みから、何らかの処理を実行できるように、

void (*task_)(void);

実行ポインターを用意した、ところが、こんな事をすると、コンパイラは、「task_」で何が実行されるか不明
な為、メイン側で利用しているライブラリーのワークを全てスタックに積むようコードを生成する。
※タスク内で、メイン側と同じライブラリーを使った場合に備える為で、強引な方法だが、動作は正しい。
しかし、これでは、スタックが直ぐに限界を超え深くなるのは容易に想像出来る、その為、「実行ポインター」
でのタスク管理は不採用とした。

そこで、コンパイル時に静的な関数が実行できるような仕組みを追加した。
関数オブジェクトを使い。

class task {
    public:
    void operator() () {

    .....

    }
};

()オペレーター内に、実行したい手続きを実装して、それをテンプレートの引数とする。
関数オブジェクト内の手続きが何も無い場合、コンパイラは、その部分一切を削除する。

template <class TASK1, class TASK2>
class comp_io {

    static TASK1 task1_;
    static TASK2 task2_;

    public:
        static INTERRUPT_FUNC void itask1() {
            task1_();
            WCB1INTR.WCB1F = 0;
    }

    .....

};

これなら、コンパイラが、テンプレートクラスを展開する時に、割り込み内手続きを考慮するので、ライブラリー
で使われる変数の退避も最小限となる。

COMP_test ソースコード

-----
そして、R8CのI/O定義は、ほぼ全てを網羅した。

R8C PWM出力(タイマーC)

R8C関係のリソースもかなり色々揃って来ました。

組み込みマイコンでは、外せないPWM機能の実装です。

一般的に、組み込みマイコンのハードウェアータイマーの機能は、多機能で、複雑です。
R8C/M120の、タイマーCでも、PWMは二通りのモードがあり、その他に、インプットキャプチャなど
色々な機能があります。

今回は、3チャネルの出力が可能な、PWMモードの実装を行いました。
※今後必要になったら、PWMモード2や、インプットキャプチャーなど、他の設定も組み込む予定です。

PWMの場合、デューティ可変範囲を切りのよい2のn乗にしたい場合と、あくまでも周波数(周期)で設定
したい場合がありますので、引数の違う2つの設定を用意してあります。

今回、必要無かったので、割り込みを使っていません。

このタイマーCのPWM出力は、設定値とカウンターの一致を利用しているので、設定値を書き換えるタイミングに注意
する必要がありますが、現状の実装では考慮されていません。

    // PWMモード設定
    {
        PML1.P12SEL = pml1_t::P12TYPE::TRCIOB;
        PML1.P13SEL = pml1_t::P13TYPE::TRCIOC;
        PML1.P10SEL = pml1_t::P10TYPE::TRCIOD;
        bool pfl = 0;  // 0->1
        timer_c_.start_pwm(10000, pfl);
        uint16_t n = timer_c_.get_pwm_limit();
        timer_c_.set_pwm_b(n >> 2);  // 25%
        timer_c_.set_pwm_c(n - (n >> 2));  // 75%
    }

↑の例では、PWM周波数を10KHzとしています。
B出力には25%、C出力には75%のデューティで出力します。

    if(adc_.get_state()) {
        uint32_t v = adc_.get_value(1);
        v *= timer_c_.get_pwm_limit();
        timer_c_.set_pwm_d(v >> 10);
	adc_.start();
    }

又、C出力にはLEDが接続してあるので、AN1入力にボリュームを繋げて、0~100%まで
デューティを変化させられますのでLEDの明るさが変化します。

-----
I/Oポートの定義を見直し、ポートのマッピングに「固有の型」を導入しています。
その為、設定が判りやすく、誤設定も減ると思います。

        PML1.P12SEL = pml1_t::P12TYPE::TRCIOB;
        PML1.P13SEL = pml1_t::P13TYPE::TRCIOC;
        PML1.P10SEL = pml1_t::P10TYPE::TRCIOD;

※この型には「enum class」を使っている為、C++11対応コンパイラでコンパイルする必要があります。
現在 gcc-4.7.4 なので、「-std=c++0x」オプションを使っています。

PWM_test

GitHubにソースをプッシュしました。

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

さて、今回はデータフラッシュメモリーを扱います。

R8Cには、プログラムフラッシュとは別に、データの記録用の小規模なフラッシュメモリーが別にあります。

3000番地から始まり、容量2048バイトです。
※1024バイト毎にバンク0、バンク1と分かれています。
フラッシュメモリーは、「1」のビットを「0」にする事は出来ますが、「0」のビットを「1」にする場合は、バンク毎に
イレースコマンドを発行して、全て「1」の状態に戻します。

この辺りのマネージメントが、多少難しいのですが、電源を切っても、記録を残す事が出来るので、非常に有益なメモリーと言えます。

テストプログラムでは、バイト単位での読み出し、書き込み、イレースなどが対話形式で行えるようにしてあります。
※シリアルポートにターミナルを接続します。

Start R8C FLASH monitor
# ?
erase bank[01]
r xxxx
write xxxx yy
# r 3
FF
# write 3 56
# r 3
56

操作説明:
? ---> 簡単な説明
r xxx ---> xxx のデータをリード
write xxx yy ---> xxx へ yy を書き込み
erase bank0 ---> バンク0を消去(0x3000 to 0x33ff)
erase bank1 ---> バンク1を消去(0x3400 to 0x37ff)

※xxx アドレスは、0 から 7ff までの領域です
※値は全て16進数で行います

本来は、プログラム領域の操作関数も含めたいところですが、それは、また次の機会(必要になったら)とします。

-----
R8Cへのプログラム書き込みには、ルネサスのFDTを使っていましたが、コマンドラインから気軽にプログラム出来れば便利です。
又、FDTは Windows 環境にしか対応しておらず、Linuxや、OS-X での開発には不向きでしたが、先日、AVR関係でお世話になっている
YCITのsenshuさん
の掲示板で、コマンドラインで使える、R8C 用プログラムコマンドが公開されました。
元々は、ゆきさん開発の r8cprog を元にしていて、M120AN 32K Flash に対応した
ものですが、使いやすいように、その他の小規模な改造も行っており、安定性も増しています。
私は、コマンドラインから使えるのが便利ですが、GUI 版も用意されており、シンプルで良く精査されたシンプルな設計により、予備知識が無く
ても直ぐに使えるようになっています。
Linux にも対応している事から、OS-X にも対応可能と思います、こちらは、時間が出来たら挑戦してみるつもりです。

R8C割り込みの注意点

ADCを動作検証するサンプルで、UARTとタイマーの割り込みを使うようになった。
他、色々複雑化しているのだが・・

非常に気になる事案が発生するようになった、数時間程度稼動させていると、ハングアップするのだ・・

スタックかと思い調べたが問題無い・・

全く原因が掴めないので、しばらく放置していたが、たまたま確認の為、R8Cのマニュアルを観ていたら
気になる事項が・・

5.7.3 割り込み制御レジスタの変更
(1) 割り込み制御レジスタは、そのレジスタに対応する割り込み要求が発生しない箇所で変更してく
ださい。割り込み要求が発生する可能性がある場合は、割り込みを禁止した後、割り込み制御レジ
スタを変更してください。
(2) 割り込みを禁止して割り込み制御レジスタを変更する場合、使用する命令に注意してください。
IRビット以外のビットの変更
命令の実行中に、そのレジスタに対応する割り込み要求が発生した場合、IR ビットが“1”(割り込み
要求あり)にならず、割り込みが無視されることがあります。このことが問題になる場合は、次の命令
を使用してレジスタを変更してください。
対象となる命令…A N D 、O R、BCLR、BSET
IRビットの変更
IR ビットを“0”(割り込み要求なし)にする場合、使用する命令によってはIR ビットが“0”にならな
いことがあります。IR ビットはM O V 命令を使用して“0”にしてください。

は!?、うーーーん、そうか・・・
これは、要するに、リードモディファイライト命令など、リードからライトへの遅延が短い場合に、
フラグのリセットに失敗するのだろう、しかし、わかり難いのは、常に失敗する訳では無く、何か
の条件が重なると失敗するようだ。
もしかしたらコレかもしれない、そこで、割り込みフラグのリセットを全般的に書き換えた。

※タイマーの割り込みフラグのクリア部分

以前は、
    TRBIR.TRBIF = 0;

だったのを・・

    volatile uint8_t r = TRBIR();
    TRBIR = TRBIR.TRBIF.b(false) | (r & TRBIR.TRBIE.b());

とした。
※同じように、UARTの制御クラスにも修正を加えた。

ここで、TRBIR レジスターを1度読み出しているのだが、「volatile」を指定しないと、最適化されて、
AND 命令とかでI/Oを直接書き換えるようなコードが出てしまう。

この検証には時間がかかる・・・
24時間走らせたが、今度は順調なので、多分解消されたと思う。
割り込みフラグを「消す」場合には注意が必要だ・・

ただ、コンパイルされたアセンブリコードには多少無駄な部分がある、しかしこれはコントロールが難しい
ので、我慢するか、アセンブラで書き直すしかない。
折角、可読性を重視して C++ を活用しているので、まぁ我慢しておく事にする。

元のソース
    uint8_t r = TRBIR();
    TRBIR = TRBIR.TRBIF.b(false) | (r & TRBIR.TRBIE.b());

    8266:	97 80 e7 00 	and.b:s #-128,0xe7
「volatile」修飾子を使った場合
    volatile uint8_t r = TRBIR();
    TRBIR = TRBIR.TRBIF.b(false) | (r & TRBIR.TRBIE.b());

    826b:	75 c4 e7 00 	mov.w:g #231,a0
    826f:	72 60       	mov.b:g [a0],r0l
    8271:	02 ff       	mov.b:s r0l,-1[fb]
    8273:	0a ff       	mov.b:s -1[fb],r0l
    8275:	94 80       	and.b:s #-128,r0l
    8277:	72 06       	mov.b:g r0l,[a0]

R8C A/D変換

R8CのA/D変換も出来ました。

ただ、別の部分で、工夫が必要な事が判りました。

工夫と言うのは、バイナリーサイズが肥大化しているのです・・
問題になってるのは「format」クラスです。
このクラスは、「printf」に代わるものとして実装した物ですが、16キロバイトにもなります。
フラッシュメモリーは32キロまで使えるとは言え、流石に半分も消費するのでは問題です。
RXマイコンで実装した物をほとんどそのまま使っているのですがー、RXマイコンではそこそこのサイズでしたが、R8Cでは巨大になるのです・・

動作は、設計の通りですが、小さくする工夫をする必要があります。

しばしば、小さな組み込みでは、libc の printf が巨大になるので、縮小版を作る人が多いですが、自分もその口です。
そもそも、printf は、引数がスタックベースなので、フォーマットと、引数が食い違う場合に簡単にクラッシュするか、
意図しない微妙な動きになる場合があります。
コンパイラーが整合性をチェックしますが、完全ではありません。
C++では、そんな危険を冒さなくても、安全な方法が色々あります。
「format.hpp」は、オペレーターの機能を使って、boost::format のような実装を行った物です、boost::format 版は、std::cout が必要な為、バイナリーが肥大化して使えませんので、縮小版です。

  int i = 123;
  printf("%d\n", i);

が、

  uint32_t i = 123;
  format("%d\n") % i;

のように書けます。
※R8Cでは「int」が16ビットなので、in32_t、uint32_t を使います。
※「%」オペレーターをオーバーロードしています。

調べると、現在の実装では、文字の出力や、定型サイズなどをテンプレートの引数として受け取るのですが、これが肥大化の要因のようです。
そこで、テンプレートをやめ、通常のクラスとして定義しなおし、エラーレポートもオフにしてみました、その結果、半分以下になりました。
これなら、許容範囲と思いますと言いたいのですが、もう少し何とかしないと駄目な感じです・・
R8C の gcc はメンテされていないのと、基本16ビットのCPUなので、コンパイラのコード生成に問題があるのかもしれません・・
※R8Cでは、フラッシュは64キロ以内なので、64キロを超えてジャンプする機構は必要無いのですが、コンパイル
されたマシンコードには、そのキックコードが含まれています、コンパイラのスイッチで排除出来ると思いますが、少し調べた限りでは判りませんでした・・

さて、本題はA/D変換です、R8Cに内臓されているA/Dコンバーターは10ビットの分解能で、2チャネル、アナログ入力は6チャネルあります。
変換速度は高速で、最大で2.3uSです。
アナログ用電源はピン数の制限から、デジタル電源と共通ですが、ルネサス系のCPUは電源ノイズも少なく、A/D変換の精度もAVR系より優れている感覚があります。
※AVR系ではサンプルホールドが無い為、変換結果に誤差が乗り易く、正確な変換を行うのが難しいと思えます。

format クラスには、A/D変換などの表示に適した、固定小数点表示「%y」を実装してあります。
A/D変換結果は大抵、電圧や電流などですが、表示する場合、小数点付きで10進表記で表示した方が判りやすい
ですが、通常だと実数と少数部を分けて、別々に計算をする必要があり、多少面倒です。

固定少数点表記機能を使うと、例えば、1023を5Vとすると、実数1桁、小数2桁、小数点以下8ビットの精度としてー

  uint16_t v = adc_.get_value();
  format("%1.2:8y") % static_cast(((v + 1) * 10) >> 3);

とすれば、v が1023の場合に「5.00」と表示できます。

A/D変換のサンプルコード

浮動小数点の計算は、リソースを食うし、FPUが無いとスピードも出ませんので、固定小数点の計算を簡単に行えると便利だと思います。
※何故10倍して8で割っているのか、良く考えれば判ります。

現在の「format.hpp」は「float、double」の実装がまだで、中途です、IEEE754 の表示を浮動少数点を使わずに整数計算だけで実装したいのですが、苦労しています、道まだ半ばです・・・

型指定子にどんなものが使えるか、「format.hpp」を観れば直ぐに解ると思います。
※8進数は、通常あまり使わないので、オプションとしています。

R8C RB2を使ったタイマー

R8C もようやくタイマー関係の定義が出来ました。

R8C には3つのタイマーモジュールが内臓されています。
それぞれ、RC、RJ、RB です。

AVRに比べると、複雑で、機能を使うには、マニュアルの意図を理解するのに時間がかかりますが、
高機能で、AVRと両方使って思うのは、ルネサス系のタイマーの方が、細かい部分が良くできている点です。
インプットキャプチャー機能などは、ダブルバッファになっていて、正確な値を取得できますし、PWM機能は、
多くの場面を想定していて、十分な使い方が出来ます。

この中で、比較的単純な構造なのはRBです。
単純と言っても、色々な仕組みがあるのですが、とりあえず、16ビットのタイマーとして使い、周期的に割り込みをかけるのに使う事にします。

タイマークラスでは、登録した関数を割り込み内から呼び出す仕組みを設けてありますが、通常「禁止」としています。
これを「有効」にすると、ライブラリー関数の静的変数を全てスタックへ退避するコードが追加される為、注意が必要です。
※そうしないと、メイン側で、あるライブラリー関数を実行していた場合、それを、割り込み側でも呼ぶと、問題が起こります。

周期的な割り込みを使うと、プログラム全体の時間管理が単純に行えるので便利です。
これは、同期式とも言います、ゲームなどに向いた方法で、画像の表示、書き換えを同期させ、常に一定の速度で全体を動作させます。
又、シングルタスクでありながら、マルチタスク的な並列動作を行うのに便利な方式です。
※厳密な意味での「並列」ではありませんが。

組み込みでも、「同期式」は、色々な面で、都合が良い場合が多くあります。

簡易的なマルチタスク的な動作が出来ます。

一つの例として、スイッチのチャタリング除去などが上げられます。
周期的な割り込みを行い、その周期で I/O ポートの値を読み込む事で、デジタルフィルター的な効果が付加され、結果的にチャタリングを除去
します。
この周期はスイッチの機械的特性によりますが、60Hz~100Hzくらいが適当です。
※スクリーンのリフレッシュレートが60Hzなので、その値が使われます。

当然ながら、1秒に60回以上のOn/Offを検出出来なくなりますが、通常は問題ありません。

また、個々の処理を時分割で動作するように実装して、それを周期的に呼び出す事で、並列動作が実現出来ます。

通常は、カーネルがタイムスライスを行い各スレッドを管理しますが、小さなシステムでは、マルチスレッドは複雑になりすぎる場合があり、
「同期式」でも十分な場合があります。

ゲームを作っている人には、常識的な実装ですが、初めて触れる人には新鮮で、判り難いかもしれませんが、馴れておくと良いと思います。
今まで、複雑で、難解な実装が、シンプルに出来る場合があり、モジュール的に他の処理と分離する事ができます。

ここで、今までに実装したR8C関係クラスなどをまとめておきます。

M120AN/
  m120an.ld     ---> リンカースクリプト
  system.hpp    ---> システム制御
  clock.hpp     ---> クロック発生回路
  port.hpp      ---> I/O ポート
  intr.hpp      ---> 割り込み
  uart.hpp      ---> シリアルインターフェース
  timer_rb.hpp  ---> タイマRB
  timer_rc.hpp  ---> タイマRC
  timer_rj.hpp  ---> タイマRJ

I/O制御関係では、以下のファイルがあります。

common/
  start.s        ---> R8C起動アセンブラソース
  vect.[hc]      ---> 割り込み、割り込みベクター関連
  init.c         ---> 初期化関連
  io_utils.hpp   ---> レジスター定義ユーティリティーテンプレート
  uart_io.hpp    ---> シリアルインターフェース制御クラス
  trb_io.[hc]pp  ---> タイマRB制御クラス

各デバイス毎にテストしたプロジェクトがあります。

R8C/
L_chika     ---> 基本の基本LED点滅(最小限の実装)
UART_test   ---> シリアルインターフェースのテスト(ポーリング、割り込み)
TIMER_test  ---> タイマーRBのテスト(ポーリング、割り込み)

これからも、随時追加していく予定です。

R8C UART の制御

凄い長い間、間が空いたけど、再びR8Cです。
※WR250Xを買ったせいで、休みは、出かける事も多く、体調を崩した事もあり、進んでませんでした・・

UARTですが、M120、M110には1チャネルのUARTが備わっています。
R8CではSCIと言わないのが、何となく系統の違いを感じますが、内部の機構や構成は、RXマイコンなどと殆ど同じです。
※良く調べたら、送信割り込みの機構はかなり違っていて、RXマイコンやSH用SCIのプログラムはそのままの構成では流用できませんでした。
※もしかしたら、これが「UART」と呼ぶ事なのかもしれません・・

クロックジェネレーターの構成により、あまり高いボーレートを使う場合は苦しいかもしれません。
※内部発振器は通常20MHzで発振しますが、調整機構があり、18.432MHzで発振させる事が出来るようで、ボーレートクロックに適しています。
※19.968MHzならもっと良かったのに・・

追加したファイルは、以下です。

R8C/M120AN/uart.hpp
R8C/common/uart_io.hpp
R8C/UART_test/*

現在のところ、ポーリングによる動作しか実装していませんが、そのうち割り込み動作も追加するつもりです。
R8Cは、RAMのリソースに余裕が無い為、割り込みベクターをROM側に置いておく必要があり、その為のしくみを考え中です。

M120以外のR8Cでも流用が出来るように、UART0だけに特化していません。
※今のところ、M120以外のR8Cを使う予定はありませんが・・

以前にRXマイコン用に作った、sci_io.hpp をR8C/UART 用に焼きなおしただけで、基本的な構成は似ています。

・テンプレート関数内 static 宣言
テンプレートクラスで static クラスを宣言すると、実態が必要なので、uart_io.hpp の最後の方で実体を宣言してあります。
※以前、この宣言方法が判らなくて、少し苦労しました・・

template<class UART, uint16_t recv_size, uint16_t send_size>
	utils::fifo<recv_size> uart_io<UART, recv_size, send_size>::recv_;
template<class UART, uint16_t recv_size, uint16_t send_size>
	utils::fifo<send_size> uart_io<UART, recv_size, send_size>::send_;

何故、static にする必要があるかと言えば、割り込みが関係しています。
割り込みが発生した場合、テンプレートクラスのリソースにアクセスする為には、テンプレートクラスの実体のアドレスが必要ですが、
通常の方法では、簡単に得られません(クロックを消費します)、そこで、static 変数にしておく事で、コンパイル時にアドレスが決定される為、
割り込み関数から、容易にアクセスできるのです。
static にしてありますが、テンプレート引数に依存しているので、UART チャネルが違う場合は、別のリソースとして割り当てられます。

・最適化の功名
main 関数内で、UART にボーレートを設定している関数がありますが、内部でボーレートジェネレーターに設定する値などを計算しています、
しかしこれは定数を指定している為、コンパイル時に最適化によって計算部分が最適化され、ダイレクトに定数がレジスターに設定されるような
アセンブリコードが出力されます。
※機能としてボーレートを変更できるような仕様だと、省く事が出来ない為、計算するコードが含まれる事になります。

・基本的な使い方
UART0を使うには、モジュールを有効にして、I/Oのマッピングを設定する必要があります。

    MSTCR.MSTUART = 0;  // モジュールスタンバイ制御
    PMH1E.P14SEL2 = 0;
    PMH1.P14SEL = 1;
    PMH1E.P15SEL2 = 0;
    PMH1.P15SEL = 1;

※M120の場合、I/Oポートの方向レジスターの設定は不要のようです。

M120のシリアルフラッシュライターでは、TXDのポートは、本来のTXD0とは違うポートを使っている為、フラッシュライターのハード
を使って、シリアル入力のテストを行うには、P1:B6とP1:B5をブリッジする必要があります、今回は適当な抵抗でブリッジしています。

※使い方は、main.cpp を参考にして下さい。

いつものように GitHub にプッシュしてあります。

追記:
割り込みにも対応するべく、色々やっていました。
色々なバグなども見つけて、安定度なども向上しつつあります。

リンカースクリプトに記述してあるスタックのアドレスを間違ってました・・

それを修正して、割り込みベクターの設定ハード用のテンプレートを実装したら、受信割り込みは簡単に動いたのだけど、
送信割り込みがーー、R8Cでは、送信割り込みハードの実装が微妙で、スマートな方法が使えないのです。

普通、送信割り込みはバッファが「空」になったら発生するので、ソフトとの連携も楽なのですが、R8Cではそうなっておらず、
少し苦労しましたが、何とか方法を考えて実装しました、以外と簡単な対処ですが、ロジックに誤りがあれば、普段は良くても、何か違う状況
の場合などに微妙に正しく動作しない可能性があります。
※バッファに送信データが残るとか、送られないデータが出るとか・・
割り込みを含んだロジックは、隠れて見えない部分が多いものなので、しばらく注意して観ておく必要があります。

送信割り込みで運用したロジックは以下のような物です。
・リングバッファが空で、UARTのレジスタも空なら、送る物が何も無い状態なので、直接送信レジスターにデータを書く。
・リングバッファが空では無いか、UARTのレジスタが埋まっていれば、送信中なので、リングバッファにデータをいれる。
・送信割り込みでは、リングバッファが空なら、送る物が無いので、何もしないで終了とする。
・送信割り込みでは、リングバッファに何かデータがあれば、それをUARTのレジスターに書いて終了する。

R8Cスタートアップの手順と、Lチカ

さて、R8C用 gcc が出来たのでー、早速、最初の一歩、スタートアップルーチンの実装です~

RX マイコン用 gcc で色々学習したので、今回はかなり楽です。
ただ、R8C のアセンブラは初めてなので、少し苦労しそう・・・

全体の構成としてー

・start.s ---> CPU リセット時に開始するルーチン
・init.c ---> 初期化関係
・vect.c ---> ベクターアドレス関係
・xxx.ld ---> リンカースクリプト

この4つの構成になります。


「start.s」

	.text
	.global _reset_start
_reset_start:
	.global	_start
_start:
	/* 割り込みスタック設定 */
	.extern _isp_init
	ldc #_isp_init,isp

	/* ユーザースタック設定 */
	.extern _usp_init
	fset u
	ldc #_usp_init,sp

	/* 割り込み許可 */
	fset i

	.extern _init
	jmp.w _init

	.global _exit
_exit:
	jmp.w	_exit

・「_reset_strat」、「_start」は同一アドレスに向けておきます。
※リンク時、「-nostartfiles」を指定しても、「_start」のシンボルは必要なようです。
・「_isp_init」、「_usp_init」はリンカースクリプトで指定してあります。
※それぞれ、割り込み用スタック、ユーザースタックです、プログラムの性質に合わせて、少ないメモリーのどこにどれくらい配置するか考える必要があります。


「init.c」

int main(int argc, char**argv);

extern short _datainternal;
extern short _datastart;
extern short _dataend;

extern short _bssstart;
extern short _bssend;

extern short _preinit_array_start;
extern short _preinit_array_end;
extern short _init_array_start;
extern short _init_array_end;

int init(void)
{
	// R/W-data セクションのコピー
	{
		short *src = &_datainternal;
		short *dst = &_datastart;
		while(dst < &_dataend) {
			*dst++ = *src++;
		}
	}

	// bss セクションのクリア
	{
		short *dst = &_bssstart;
		while(dst < &_bssend) {
			*dst++ = 0;
		}
	}

	// 静的コンストラクターの実行(C++ )
	{
		short *p = &_preinit_array_start;
		while(p < &_preinit_array_end) {
			void (*prog)(void) = (void *)*p++;
			(*prog)();
		}
	}
	{
		short *p = &_init_array_start;
		while(p < &_init_array_end) {
			void (*prog)(void) = (void *)*p++;
			(*prog)();
		}
	}

	// main の起動
	static int argc = 0;
	static char **argv = 0;
	int ret = main(argc, argv);

	return ret;
}

void __dso_handle(void)
{
}

初期化ルーチンでは、一連の準備をする必要があります。
・メモリーのクリア(通常、bss セクション)
・初期値が設定してあるメモリーにROMエリアからコピー
・静的コンストラクターの実行(C++ の場合のみ必要)
・最後に「main」関数の起動


「vect.c」

#include <stdlib.h>

#define INTERRUPT_FUNC __attribute__ ((interrupt))

extern void reset_start(void);

// 未定義命令
INTERRUPT_FUNC void undef_inst_(void)
{
}

// null interrupt TASK
INTERRUPT_FUNC void null_task_(void)
{
}

// R8C M110AN, M120AN の場合、ポインターは2バイト、ベクターテーブルは4バイト単位のアドレスを想定
const void* fixed_vectors_[] __attribute__ ((section (".vec"))) = {
// 0xFFD8  予約領域 (with OSF2)
	(const void*)0xffff, (const void*)0xffff,
// 0xFFDC  未定義命令 (with ID1)
    undef_inst_, (const void*)0xff00,
// 0xFFE0  オーバーフロー (with ID2)
	null_task_,  (const void*)0xff00,
// 0xFFE4  BRK 命令
	null_task_,  (const void*)0xff00,
// 0xFFE8  アドレス一致 (with ID3)
	null_task_,  (const void*)0xff00,
// 0xFFEC  シングルステップ (with ID4)
	null_task_,  (const void*)0xff00,
// 0xFFF0  ウオッチドッグタイマ、発振停止検出、電圧監視1 (with ID5)  
	null_task_,  (const void*)0xff00,
// 0xFFF4  予約 (with ID6)
	(const void*)0xffff,  (const void*)0xffff,
// 0xFFF8  予約 (with ID7)
	(const void*)0xffff,  (const void*)0xffff,
// 0xFFFC  リセット (with OFS)
	reset_start, (const void*)0xff00,
};

※R8C/M110AN、R8C/M120AN では、ポインターは2バイトです、ですが、ハードウェアーベクターは4バイト構成になっています。
※また、R8Cのアーキテクチャー的には、最大1Mバイトの空間までアクセス出来るので、ベクターは3バイトあれば十分です、最上位バイトは、別の機能(「IDコードチェック機能」、「オプション機能選択」などのレジスター)として使われています、通常0xFFにしておけば良いようです。
※間違って書き換えてしまうと、フラッシュの書き換えが出来なくなる場合があるようなので、注意が必要です


「リンカースクリプト」(抜粋)


MEMORY {
	RAM (w) : ORIGIN = 0x0300, LENGTH = 0x0480
	ROM (r) : ORIGIN = 0x8000, LENGTH = 0x7FD8
	VEC (r) : ORIGIN = 0xFFD8, LENGTH = 40
}
SECTIONS
{
        _usp_init = 0x07E0 - 2;
        _isp_init = 0x0800 - 2;

...

  .rodata : {
    . = ALIGN(2);
    *(.plt)
    KEEP (*(.init))
    KEEP (*(.fini))
    *(.rodata .rodata.* .gnu.linkonce.r.*)
    *(.rodata1)
    *(.eh_frame_hdr)
    KEEP (*(.eh_frame))
    KEEP (*(.gcc_except_table)) *(.gcc_except_table.*)
    . = ALIGN(2);
    PROVIDE(__romdatastart = .); /* IF_ROROM */
  } > ROM

  PROVIDE( __datainternal = ABSOLUTE(LOADADDR(.data)));

...

  .vec : {
    *(.vec)
  } > VEC

...

・リンカースクリプトは、通常 gcc のバージョンによって構造が異なります、今回 gcc-4.7.4 ベースの M32C-elf をビルドしましたが、
m32c-elf/m32c-elf/lib/r8c.ld に基本になるリンカースクリプトがありますので、これを修正して使っています。
※正しい書き方や、意味を完全に理解している訳では無いので、カットアンドトライ的ですが、とりあえず、現状のスクリプトで大丈夫と思います。


「main.cpp」Lチカのプログラムです。

#include "port.hpp"

int main(int argc, char *ragv[])
{
	using namespace device;

	PD1.B0 = 1;

	while(1) {
		for(int i = 0; i < 1000; ++i) P1.B0 = 0;
		for(int i = 0; i < 1000; ++i) P1.B0 = 1;
	}
}

・初期状態の内臓発振器を想定したソフトループとなっています。
・port.hpp がポートのテンプレートクラス定義です。
・Makefile により、自動で、ファイルの従属規則を生成しています。

IMG_0712
IMG_0713

とりあえず、ソースコードは、GitHub にプッシュしてありますので参考にして下さい。
※定義は I/O ポートの部分しか無いので、ちょくちょく更新、及び追加すると思います。

R8Cマイコン用 gcc (m32c-elf) の構築

さてー、M110AN、M120ANが、もの凄く使えるやつだって判ったのでー

まず、お決まりの gcc ビルドです。
色々調べると、R8Cは、M32Cマイコンのサブセットのようです、そこで、M32Cマイコン用 gcc をビルドします。
M110AN、M120AN は R8C の中でも(Tiny)と呼ばれる縮小セット版のようです。
※コンパイルオプションとして「-mcpu=r8c」などとします。

毎回、gcc のビルドは苦労します。

Windows7 MSYS(MinGW) の場合:
※ MSYS に、gcc ビルドに必要な、「gmp、mpfr、mpc」がインストールしてある事を前提とします。

・gmp-5.1.3
・mpfr-3.1.2
・mpc-1.0.1

OS-X の場合:
・mac brew を使ってするのが楽です。
※他に、「MacPorts」が同じようなシステムですが、こちらは、メインツリー以外のバージョンをインストールする
場合など、全く融通が利かないので、mac brew を推します。
※mac brew のインストールについては、詳しいサイトが沢山あるので、そちらを参照下さい。
OS-X では、標準コンパイラは clang で、gcc コマンドがオーバーライドされています、gcc をインストールしておき、シンボリックリンクを貼り直しておきます。

brew install gcc
sudo ln -sf /usr/local/bin/gcc-4.9 /usr/local/bin/gcc
sudo ln -sf /usr/local/bin/g++-4.9 /usr/local/bin/g++
brew install gmp
brew install mpfr
brew install mpc

※確認

gcc --version
gcc (Homebrew gcc 4.9.2_1) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

まず、binutils-2.24 のビルド

binutils-2.24 のソースを展開したら、
※gcc-4.9 では途中でビルドを失敗するので、binutils-2.25 を使います。

cd binutils-2.24
# 直でビルドしないで、専用のディレクトリーで作業するのが普通です、失敗したらディレクトリーを削除してやり直せる。
mkdir m32c_build
cd m32c_build
../configure --target=m32c-elf --prefix=/usr/local/m32c-elf --disable-nls
make
make install

アセンブラのパスを通して、パスを有効にする為コンソールを開きなおします、そして、最初のCコンパイラをビルドします。
※この段階では、C コンパイラのみビルドします、C++ は、後でビルドします。
gcc-4.7.4 のソースを展開したら、(とりあえず、C++11 完全対応の gcc4.8 系を使わずに枯れたバージョンで試します。)
※ gcc は 4.8 系でかなり大きく構造が変わったようで、メンテナンスがされていない CPU の場合、4.8 系ソースでは、コンパイルに失敗する事が多い為です。
※C++11 完全対応ではありませんが、C++0x オプションがありますから、ほぼ遜色無く C++11 の機能を使えます。

cd gcc-4.7.4
mkdir m32c_build
cd m32c_build
../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c --disable-libssp --with-newlib --disable-nls
make

windows:
make install

OS-X:
sudo make install

次に newlib をビルドします。

newlib-2.1.0 のソースを展開したら

cd newlib-2.1.0
mkdir m32c_build
cd m32c_build
../configure --target=m32c-elf --prefix=/usr/local/m32c-elf
make

windows:
make install

OS-X:
sudo make install

最後に C++ をビルドします。

cd gcc-4.7.4
cd m32c_build
../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c,c++ --disable-libssp --with-newlib --disable-nls --disable-libstdcxx-pch
make

windows:
make install

OS-X:
sudo make install

とりあえず、C++ のビルド中、怪しい動作がありましたが、最後まで到達しました。(Windows)
簡単なテストは、とりあえずOKで、詳細なテストはこれからです。

スタートアップルーチンは、デバイスに合わせて書く事になる為、しばらくは、ハードウェアーマニュアル、ソフトウェアーマニュアルと「にらめっこ」です・・
また、R8C のアセンブラもある程度理解する必要があります・・

-cpu=r8c でR8C を選択した場合、「int」のサイズは16ビットのようです。
※この CPU によって int のサイズを16にしたり32にしたりする慣わしって誰が始めたのか知らないけど、ホントクズ仕様だよなぁー・・・
なので、int32_t、とかがあるんだけど、short があるんだから、int は32ビットで良くない!?

gcc のビルドは、手間もそうですが、時間もそれなりにかかるので、「めんどぃ」って人は、
今回ビルドしたバイナリーを以下のリンクに置いておきますので活用下さい~
MSYS(MinGW)版 M32C-gcc-elf
※通常は、「/usr/local/」で、アーカイブを解凍すれば、
「/usr/local/m32c-elf」が出来ますので、「/usr/local/m32c-elf/bin」にパスを通します。
これで、

m32c-elf-gcc
m32c-elf-g++

などが利用可能です。