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++

などが利用可能です。

R8C/M11A、M12Aの秘密

M11A、M12Aについて~

低価格なマイコンと言えば、AVRですが、ルネサスにも、CPの高いマイコンがあります。
それは、R8Cマイコンで、R8C/M12A/M11Aです。
「@100円、確かに安いけど、AVRもあるし、PICもあるしで、そんなにインパクト無いけどーー」と言いたくなるかもしれませんが・・
このマイコン、裏事情があって、表向きは
・プログラムメモリ(フラッシュ):2KB
・RAM:256バイト
となっていますが、実際には、
・プログラムメモリ(フラッシュ):32KB
・RAM:1280バイト
時代に逆行するアセンブラ屋さんの調査による。

これが、現在出回っているデバイスでも有効に出来るのなら、今までの常識が一遍しそうです。

AVRのATTiny2313は、安くて使いやすいマイコンですが、A/Dコンバーターが無いのが痛いとこです。
最近秋月の価格を観ると@150になっていますね・・

M12Aの場合は:
・最大20MHz動作(2.7V以上)
・1.8V~5.5V動作
・入出力ポート:17本 (LED駆動用ポート含む)
・外部割り込み入力:8本
・16ビット多機能タイマ(タイマRJ2):1
・8ビットプリスケーラ付8ビット多機能タイマ(タイマRB2):1
・16ビットインプットキャプチャ/アウトプットコンペアタイマ(タイマRC):1
・UART/クロック同期形シリアルインタフェース:1チャネル
・10ビットA/Dコンバータ:6チャネル
・コンパレータ:2回路
・ウォッチドッグタイマ
・クロック発生回路:XINクロック発振回路、オンチップオシレータ(高速/低速)
この充実ぶりです・・

この機能と、プログラムエリア32Kバイト、RAMエリア1280バイトとなると、かなりの用途にマッチします。


フラッシュの書き込みボードの作成

・配線は、ハードウェアーマニュアルに詳しく書かれていますので参考にして下さい。
・フラッシュ書き込みモードで電源投入後、FDTとの接続が時間内に無いと、接続出来なくなるので、「リセットスイッチ」は必要です。
・フラッシュの書き込みは、3.3Vでも行えるようです。
このDIPタイプは、シリアルインターフェースで接続出来る為、非常に手軽に書き込みが行えます。
IMG_0705

購入したデバイス
デバイスの表記では(下に書かれているのはロット番号と思います)


 M120AN
0432N01

 M110AN
0432N01

となっていました。


書き込みソフトのインストールと設定ファイルの修正

書き込みソフトは、FDT4.09 を使います。
インストール後、以下の設定ファイルを書き換えます。(Windows7 64 ビットの環境です)

/c/Program Files (x86)/Renesas/FDT4.09/kernels/ProtD/R5F2M110A/Renesas/1_0_00/R5F2M110A.fcf
/c/Program Files (x86)/Renesas/FDT4.09/kernels/ProtD/R5F2M120A/Renesas/1_0_00/R5F2M120A.fcf

「R5F2M110A.fcf」の修正箇所

[Header]
Description = Flash Development Toolkit Configuration File
File Type = 2.1

[Device Info]
EVB Name = R5F2M110A
EVB Kernel Suffix = R5F2M110A

...

FLASH ROM Size = 32768

...

Secret Flash Memory = 
Erase Block Count = 10
Memory Area Count = 2
Reserved Area Count = 0

...

[Erase Block 2]
Block Name = EB8
Start Address = 32768
Start (Hex) = 0x00008000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 3]
Block Name = EB7
Start Address = 36864
Start (Hex) = 0x00009000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 4]
Block Name = EB6
Start Address = 40960
Start (Hex) = 0x0000A000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 5]
Block Name = EB5
Start Address = 45056
Start (Hex) = 0x0000B000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 6]
Block Name = EB4
Start Address = 49152
Start (Hex) = 0x0000C000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 7]
Block Name = EB3
Start Address = 53248
Start (Hex) = 0x0000D000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 8]
Block Name = EB2
Start Address = 57344
Start (Hex) = 0x0000E000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Erase Block 9]
Block Name = EB1
Start Address = 61440
Start (Hex) = 0x0000F000
Block Size = 4096
Size (Hex) = 0x00001000
EBR0 = 0
EBR1 = 0
EBR2 = 0
Overlay = 0
Lockable = False

[Memory Area 0]
Block Name = UA0
Start Address = 32768
Start (Hex) = 0x00008000
Block Size = 32768
Size (Hex) = 0x00008000
Area Type = User Flash
Overlay = 0

...

「R5F2M120A.fcf」の修正箇所

[Header]
Description = Flash Development Toolkit Configuration File
File Type = 2.1

[Device Info]
EVB Name = R5F2M120A
EVB Kernel Suffix = R5F2M120A

...
 
FLASH ROM Size = 32768

...

Secret Flash Memory = 
Erase Block Count = 10
Memory Area Count = 2
Reserved Area Count = 0

...

[Erase Block 2]
 Block Name = EB8
 Start Address = 32768
 Start (Hex) = 0x00008000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 3]
 Block Name = EB7
 Start Address = 36864
 Start (Hex) = 0x00009000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
Overlay = 0
Lockable = False

 [Erase Block 4]
 Block Name = EB6
 Start Address = 40960
 Start (Hex) = 0x0000A000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 5]
 Block Name = EB5
 Start Address = 45056
 Start (Hex) = 0x0000B000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 6]
 Block Name = EB4
 Start Address = 49152
 Start (Hex) = 0x0000C000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 7]
 Block Name = EB3
 Start Address = 53248
 Start (Hex) = 0x0000D000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 8]
 Block Name = EB2
 Start Address = 57344
 Start (Hex) = 0x0000E000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

 [Erase Block 9]
 Block Name = EB1
 Start Address = 61440
 Start (Hex) = 0x0000F000
 Block Size = 4096
 Size (Hex) = 0x00001000
 EBR0 = 0
 EBR1 = 0
 EBR2 = 0
 Overlay = 0
 Lockable = False

[Memory Area 0]
 Block Name = UA0
 Start Address = 32768
 Start (Hex) = 0x00008000
 Block Size = 32768
 Size (Hex) = 0x00008000
 Area Type = User Flash
 Overlay = 0

書き込みプロジェクトの設定

・デバイスを指定する際、チェックサムが合わない為、警告が出ますが、そのまま進みます。
・とりあえずこの修正で、32Kまでプログラムメモリーが使えると思います。
※MSYS のコンソールから、emacs でファイルを直接編集したのですが、パーミッションの関係で、実際には更新されない事が判りました。
エクスプローラーなどで、編集後のファイルを上書き保存する必要があります。(その際、警告のダイアログが出ます)


謝辞

・「時代に逆行するアセンブラ屋」さんの「toida」さんには、設定内容などお世話になりました。

・M110AN 設定ファイル
・M120AN 設定ファイル

WR250Xカスタム(その1)

最初はノーマルで乗ろうと思ったけど、やっぱし改造してしまう・・・

先日オークションを観てたら、ヨシムラのスリップオンマフラーがそこそこの値段で出ていたー
見た目、大きな傷も無いし、即決価格がそんなに高くなかったので、ポチってしまった・・・

WR250用のサイレンサーは色々出ているけど、各社物色したり、色々な情報を総括して、「RS-4J」はベストな選択だと思う。
チタンカバーで、カーボンエンド、これがやっぱり良さそうで、創りは流石に「ヨシムラ」隅々まで非常に良く出来ている。

中古なので、まぁ色々あるのが普通だけど、「写真の部品が全て」と書いてあったがあまり気にせず、落札したが、「物」が届いてみると、エキパイのクランプバンドが無い・・。
出品者に連絡したけど、「無い」との事、「何で???」多少怪しさを感じながらも、ヨシムラジャパンに電話した。
そして、部品をオーダーしようとしたのだが、衝撃的な事実が・・
「部品は今欠品中で最短でも来年の2月くらい」との返事・・・、えーーー、そしたら、「43-47」のクランプバンドなら、付きますよ!
との事で、探してみると・・あった!、これだ!「キジマ(Kijima) マフラーバンド 43-47mm」早速アマゾンで注文した。
※毎度の事だけど、アマゾンさん、何で、コレだけ送るのにあんな大きなボックスで送るかなーーwww(一番小さいらしいけど・・)

マフラーのガスケットも基本新品が良いので、近くのYSPで注文した、特殊な形状のガスケットで、1166円だった(高い!)・・・

そして、早速組んでみたー、多少、組み付けで悩んだが、何とか取り付けできた~
アイドリング中は、音は爆音でもなく(ノーマルより若干大きい程度)、回すとけっこう気持ちよい音がするー。
※12月に入って急に寒くなった、でも早速テスト走行と思ったけど、雨が降ってきたので、今日は試せなかった、残念・・・

で、次の日、ちょっと奥多摩駐車場まで・・前日は、イベントやってたらしい、この時期寒くて「5度」・・どんだけー

IMG_0701

排気デバイスが無いので、低速トルクが無くなったけど、アクセル開けると凄く気持ちよく回るー、これはタコが欲しい~
※流石に回すとそれなりの音がするー

—–
モタードやモトクロスの倒立では、インナーパイプを保護する為カバーが付いている、なので、インナーパイプを掃除出来ない。
なので、外して掃除。
フロントフォークのカバーを止めているボルトはスチールの安っすいボルト・・
多分錆びると思い、ステンレスのキャップボルトを買ってきた、このまま付けるのもイマイチなので、旋盤でテーパー加工してみた。

まず、ボルトをチャックに銜える為に、アルミカラーを作り三等分、そしてひたすら加工。

IMG_0694

IMG_0695

WR250Xは、峠楽しい!

組み込みでもC++

この記事はC++ Advent Calender 2014の参加記事です。

感覚的には、組み込み関係の業界では、C++の取り組みはかなりお寒いものがあります。

現代の組み込みでは、32ビットCPU、FPU内蔵、100MHz動作、そんなデバイスが539円、gcc で開発でき、もちろんC++を使えます、STLやboostは限定的に使えます。
※メモリーが少ないので、メモリーを多く必要なライブラリーに起因したクラスは使えません(たとえば、iostream 関係は、非常に多くメモリーを消費する為、デバイスによっては使えません)

去年は「WinAVR C++ の実力とは!?」を書きました。
AVRマイコンは8ビットのRISCマイコンで、扱えるリソースが少ない為もあり、STLが使えないなど制限がありました。
今回は、ルネサス エレクトロニクスの32ビットマイコンであるRX600シリーズで、C++の開発環境や、C++で実装するI/O定義クラスの紹介をしたいと思います。

RX621マイコンボード
IMG_0448s
・搭載マイコン:R5F56218BDFP(32ビットRXコア)
・プログラム用フラッシュメモリ:512Kバイト
・データ用フラッシュメモリ:32Kバイト
・RAM:96Kバイト(ノーウェイト動作)
・動作周波数:96MHz(12MHzをPLLにより8逓倍動作)

RXマイコンは、CISC型の32ビットマイコンで、低消費電力、安価、高機能、高速など、色々なメリットがある国産のマイコンです。
組み込み系マイコンと言えば、ARMの人気がもの凄く高く、色々なメーカーが採用している為、バリエーションも多く価格も安く、開発環境も整っていると言えるでしょう。
しかしながら、RXマイコンは、自分が評価した価格ゾーンでは、ARMより多くのメリットがあると感じます。
日本のメーカーにエールを送りたいとゆーのもありますが、コストが同じ程度なら、性能、入手性、日本語マニュアルなど、より扱いやすい物を使う方が効率が良いと感じます。
それでも、本当はARMの方が優位だとは思いますが、天邪鬼なんでRX押しです!www

開発環境:

ルネサスでは、開発ツールとして、自社開発の統合開発環境を用意しており、無料版(64キロバイト以上のバイナリーを作れない)もあります。
又、GNUを使った無料の統合開発環境「KPIT」も使う事が出来ますが、ルネサス提供のサンプルプログラムをコンパイル出来ないなど、冷遇されており、自分のようにコマンドラインで、コンパイラ、リンカー、Makefile、emacs で開発を進めるスタイルにも合いません。
・統合開発環境では多くの場合、裏で、目に見えない「何か」をやっており、「つぶし」が利かない。
・サターンの時代(SH2)に、自社開発のコンパイラを使った経験から、あまり良い印象が無いので、最初から使う気にもならず評価もしていません。
・新しい統合開発環境の操作を学ぶのに時間がかかる。
・少し大きなプログラムを作ったら、簡単に64キロバイトを超えてしまい、有料版ツールが必須になるが、数十万円の開発ツールは買う気にはなりません。
・ gcc ならソースコードを取ってきて、自分でコンパイルすれば、RX用の gcc を使う事が出来ます。
・自分で開発環境を用意する場合、デバッグ環境も自分で整えなければなりませんが、そこは、経験とアイディアで乗り切りますwww
・マイコンのフラッシュメモリーにプログラムを書き込むツールは、無料版があり(Windows環境のみ)こちらは、大きな制限はありません。
・メーカー専用のコンパイラは、gcc より、進んだ最適化が出来るとか言いますが、非常に限られた場合の評価しか宣伝していない為、懐疑的です。
・メーカー製IDEの良い部分としては、J−TAGプローブなどで行うリアルタイムのデバッグ環境などです。

マイコン用クロス開発用 gcc のビルドは、以下の3つのバージョンがマッチしないと、コンパイルに失敗する場合があります。
RXマイコンの場合、最新版は、以下の3つの組み合わせになるようです。
GCC 4.8.3
Binutils 2.24
Newlib 2.1.0

※まだ、自分は4.7.3を使っているので、試していません。

Windows の cygwin 環境では、コンパイルを通す為には、色々なオプションを付ける必要があるようで、大抵標準のオプションだけではコンパイルに失敗します。
又、コンパイルに必要な、ツールのバージョンにも影響するようで、再現性に乏しく困難なので、最近は MSYS(MinGW) でコンパイルしています、こちらは、比較的普通にコンパイルに成功します。
MacOSやLinuxでは、何の苦労も無くコンパイル出来ます、ただし、デバイス内臓のフラッシュROMに書き込むツールが対応していないので、現実的には Windows を使う事になります、現在、コマンドラインから、フラッシュへ書き込むプログラムを独自開発している最中です。

組み込みマイコン用の gcc が、PCの gcc と違う点は、スタートアップルーチンをデバイス毎に用意する必要があり(デバイスによって、持っているRAM、ROMのサイズが違い、初期化を行う方法が異なっているのが普通です)、多少複雑です、マイコンの場合、コールドスタートした状態では、割り込みは禁止され、最低限の設定状態で起動しています。
なので、最初に行うべきは、デバイスの必要な部分を初期化して、スタックや、記憶割り当てが使うメモリーの領域設定、C++の場合は、静的なクラスのコンストラクターを呼び出して初期化するなど、行う必要があります。

rx-elf/lib/rx.ld
RX用 gcc をビルドしたら、上記パスに、スタートアップに必要なファイルが用意されています。
「rx.ld」は、リンカースクリプトで、データセクション、プログラムセクション、など、初期化が必要な領域、初期化を必要とするクラスのコンストラクターやデストラクターのエントリーテーブルなど必要なリンク情報が入っています。
また、リンカースクリプトで、デバイスに適合するRAMやROM領域のアドレスを指定します。

以下は、リンカースクリプトの一部です。

OUTPUT_ARCH(rx)
ENTRY(_start)
MEMORY {
RAM(rwx) : org = 0x00000000, len = 0x00002000
ROM(rx) : org = 0xFFFF0000, len = 0x00010000
}
SECTIONS {
    _usp_init = 0x00001f00;
    _isp_init = 0x00002000;
    .fvectors 0xFFFFFFD0 : {
        vect.o(.fvectors)
    }

...

※ハードウェアーベクターテーブルの指定もあります。

マイコンは電源が入るとリセット信号がアサートされ、ハードウェアーベクターにあるリセットベクター先に飛び、プログラムを開始します。
以下はスタートルーチンで、マイコン独自の命令を実行する為、アセンブラで記述してあります。
※この部分だけはアセンブラを使う必要があります。

	.global _power_on_reset
_power_on_reset:
.global _start
_start:
# スタックの設定
    .extern _usp_init
    mvtc #_usp_init, usp
    .extern _isp_init
    mvtc #_isp_init, isp
# 割り込みベクタの設定
    .extern _interrupt_vectors
    mov.l #_interrupt_vectors, r5
    mvtc r5,intb
    mov.l #0x100, r5
    mvtc r5,fpsw
# Iレジスタを設定し、割り込みを許可する
    mov.l #0x00010000, r5
    mvtc r5,psw
# PMレジスタを設定し、ユーザモードに移行する
    mvfc psw,r1
    or	#0x100000, r1
    push.l r1
# UレジスタをセットするためにRTE命令を実行する
    mvfc pc,r1
    add	#0x0a,r1
    push.l r1
    rte
    nop
    nop
# init() 関数から開始
    .extern _init
    bsr	_init

    .global _set_intr_level
_set_intr_level:
    and	#15,r1
    shll	#24,r1
    or	#0x100000,r1
    mvtc r1,psw
    rts
    nop

    .global _exit
_exit:
    wait
    bra _exit

これは、「init.c」で、main に飛ぶ前に行う初期化を含んだものです。
※RXマイコンでは割り込みベクターのベースアドレスを変更できるので、自分のシステムでは、利便性を考慮して、RAM上に置いてあります。

int main(int argc, char**argv);
extern int _datainternal;
extern int _datastart;
extern int _dataend;
extern int _bssstart;
extern int _bssend;
extern int _preinit_array_start;
extern int _preinit_array_end;
extern int _init_array_start;
extern int _init_array_end;
extern void (*interrupt_vectors[256])(void);
extern void null_task_(void);
int init(void)
{
    // 割り込みベクターテーブルの初期化
    for(int i = 0; i < 256; ++i) {
        interrupt_vectors[i] = null_task_;
    }

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

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

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

    {
        int *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;
}

これで、main から実行でき、後は、通常のアプリケーションとほぼ同じとなります、最低限これだけあれば、後は、工夫次第で何とでもなります。
※リンカーで、独自のリンカースクリプトを使い、標準のスタートアップを行わないので、「-nostartfiles」を指定します。

最低限のデバッグとして、シリアルインターフェースを繋いで、ターミナルで文字の入出力が必要な場合、stdin、stdout に相当する部分を実装する必要があります。
RX63Tでは無理ですが、RX621では、それらの実装で、iostreamクラスを使え、通常のPCアプリとほぼ遜色無く動作します。
※PCでは、メモリーの消費はあまり気にしませんが、組み込みでは、注意する必要があります。

I/O定義:

組み込みマイコンでのデバイスドライバーに相当する部分は、通常は、Linux のようなシステムとは違い、カーネルによるしばりは無く、自由に直接プログラム出来ます。
であるにも係わらず、未だに、「C」でプログラムする事が当たり前のような状況になっています。
デバイスメーカーが提供するサンプルプログラムも、C++で作られたのは見た記憶がありません。(ARMではあるようです)
※それ以前に、サンプルプログラムの品質が低いと感じます。
趣味で、組み込みマイコンのプログラムをする場合でも、C++は敬遠されている感じがします。

さて、組み込みマイコンでは、I/O(シリアルコミュニケーション、A/D 変換、ポートなど)の操作を直接行いますが、最近のI/Oは高機能で、非常に多くの機能があります。
定義は複雑で肥大化しており、マイコンのバリエーションにより、機能が微妙に異なったりする場合もあります、もはや、一つのヘッダーでは、管理できないと思われます。

たとえば、ルネサスは、マイコン内のI/Oの機能を定義したヘッダーを提供していますが、これは、処理系依存で実装されている為、gcc でそのまま使う事は出来ません。
また、基本的に「C」言語を想定した定義なので、C++でプログラミングしたい場合にあまりメリットがありません。

これは、RX62Nの定義ヘッダーの一部です(A/D変換の部分です)

struct st_ad {
	unsigned short ADDRA;
	unsigned short ADDRB;
	unsigned short ADDRC;
	unsigned short ADDRD;
	char           wk0[8];
	union {
		unsigned char BYTE;
		struct {
			unsigned char :1;
			unsigned char ADIE:1;
			unsigned char ADST:1;
			unsigned char :1;
			unsigned char CH:4;
		} BIT;
	} ADCSR;
	union {
		unsigned char BYTE;
		struct {
			unsigned char TRGS:3;
			unsigned char :1;
			unsigned char CKS:2;
			unsigned char MODE:2;
		} BIT;
	} ADCR;
	union {
		unsigned char BYTE;
		struct {
			unsigned char DPSEL:1;
		} BIT;
	} ADDPR;
	unsigned char  ADSSTR;
	char           wk1[11];
	union {
		unsigned char BYTE;
		struct {
			unsigned char :6;
			unsigned char DIAG:2;
		} BIT;
	} ADDIAGR;
};

ビットフィールドが使われています、これは、処理系依存で、gcc では、LSB、MSBが逆になります。
また、最適化した場合に、適切なビット幅のアクセス(32ビットのI/Oに8ビットのアクセスなど)が行われない場合があり、RO(リードのみ)、WO(ライトのみ)などの表現も無く、読みにくく、使いやすいとは言えないと思います。

そこで、C++をもっと積極的に使って定義ファイルを作ったらどうかと考えます。
下の実装は、上のビットフィールドから、テンプレートを使った実装です。(少し冗長な部分があります)

//=====================================================================//
/*!	@file
	@brief	RX62N, RX621 グループ・AD 定義 @n
			Copyright 2013 Kunihito Hiramatsu
	@author	平松邦仁 (hira@rvf-rc45.net)
*/
//=====================================================================//
#include "io_utils.hpp"

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  AD 定義
        @param[in]  base  ベース・アドレス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t base>
    struct ad_t {

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDRA レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        io16<base + 0x00> ADDRA;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDRB レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        io16<base + 0x02> ADDRB;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDRC レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        io16<base + 0x04>	ADDRC;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDRD レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        io16<base + 0x06>	ADDRD;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADCSR レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        typedef io8<base + 0x10> adcsr_io;
        struct adcsr_t : public adcsr_io {
            using adcsr_io::operator =;
            using adcsr_io::operator ();
            using adcsr_io::operator |=;
            using adcsr_io::operator &=;

            bits_t<adcsr_io, 0, 4>	CH;
            bit_t<adcsr_io, 5>	ADST;
            bit_t<adcsr_io, 6>	ADIE;
        };
        static adcsr_t	ADCSR;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADCR レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        typedef io8<base + 0x11> adcr_io;
        struct adcr_t : public adcr_io {
            using adcr_io::operator =;
            using adcr_io::operator ();
            using adcr_io::operator |=;
            using adcr_io::operator &=;

            bits_t<adcr_io, 0, 2>	MODE;
           bits_t<adcr_io, 2, 2>	CKS;
            bits_t<adcr_io, 5, 3>	TRGS;
        };
        static adcr_t	ADCR;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDPRA レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        typedef io8<base + 0x12> addpra_io;
        struct addpra_t : public addpra_io {
            using addpra_io::operator =;
            using addpra_io::operator ();
            using addpra_io::operator |=;
            using addpra_io::operator &=;

            bit_t<addpra_io, 7>	DPSEL;
        };
        static addpra_t	ADDPRA;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADSSTR レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        static io8<base + 0x13> ADSSTR;

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ADDIAGR レジスタ
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        typedef io8<base + 0x15> addiagr_io;
        struct addiagr_t : public addiagr_io {
            using addiagr_io::operator =;
            using addiagr_io::operator ();
            using addiagr_io::operator |=;
            using addiagr_io::operator &=;

            bits_t<addiagr_io, 0, 2>	DIAG;
        };
        static addiagr_t	ADDIAGR;

    };
    typedef ad_t<0x00088040>	AD0;
    typedef ad_t<0x00088060>	AD1;
}

io_utils.hppは、git にありますので参照の事。
今までにこのような取り組みは多少あり、同じような考えによる実装はいくつか参考にした事がありますが、不満な部分などがあり、結局自分で考えて実装したものです。
・ビットと機能の対比がわかりやすい。
・8、16、32ビットアクセス、RO、WO、RWを表現できる。
・最適化レベルに関係無く、厳密なアクセスを行える。
・エンディアンに依存しない。
・処理系に依存しない。(実際は gcc 以外で試していませんが、x86 の clang でシュミレーターで実験しています)
・最適化されたアセンブラソースを確認して、これなら満足できると思う。
※但し、レビューもされていませんし、もっと良い方法、適切な実装があるかもしれません。
※参照RX、I/O関係
※RO、WO、RWを分ける必要があるので、読み出しの場合は、()オペレーターを使っています。

クラス化した事で色々な恩恵を受けられます。
たとえば、シリアルコミュニケーションは、通常複数チャネルがありますが、I/O部分のアクセスをクラス化した事で、かなりスッキリ書けるようになり、異なったチャネルの操作もテンプレートで簡単に記述できるようになりました。

I/O操作をC++で書けると何がありがたいかと言うと、テンプレートが使える事で、微妙な違いを、隠蔽して、判りやすくて使いやすい入出力クラスをシンプルに書けます。

組み込みでC++を使うべき理由の一つです。

最後に:

組み込みの世界では、何故か、C++が敬遠される傾向にあると感じがしますが、C++こそ、組み込みに適した言語と思えてなりません。

これらRXマイコン関連の成果は、以下のリンクで公開していますので、興味ある人はどうぞ。

RX関係ソースコード

ウィンカーの LED 化(その1)

前から改造してみたかった、ウインカーのLED化を行っている。

まず、電球をLED化する、球切れの心配が無くなり、消費電力が減る。
それとLEDは、点灯、消灯がハッキリしていて視認性も良くなるのではと思う。

改造したバイクはヤマハのWR250Xモタードで、標準的には10Wの電球が付いていた。
IMG_0679s
ポジションは無く、1灯式バルブ。
※LED化する事でウィンカーリレーも自作するので、ポジションランプ機能も加えようと思う。

口金は「BA15s」と言うタイプで、ピンが水平に出ている。
※千石電商で購入

自作する場合に考えなくてはならない事は、以下のような点・・
・頑丈で、振動などで、点灯不良にならない構造。(接着剤での接着などを極力避ける)
・最低でも4個は必要なので、作りやすさ、同じ物が作れる(再現性の確保)事。
・売っているLEDバルブには無い機能と性能など。

以上のような点だろうか・・

最初に問題となるのは、口金にLEDをどのように固定するか?
そこで、以前に自動車の車内を照らすシーリングライトをLED化した時の手法を応用した。
まず、口金にユニバーサル基板をピッタリサイズで切り出し差し込む、この時、スルホールが、口金の金属と近くなるようにする。
基板には、垂直に基板を固定する為のボスを作っておく。
IMG_0676s

基板を中に入れて、スルホールにハンダを流し、口金の金属部分にもハンダを流して固定する。
※それなりにハンダを流し込めば、ある程度の強度は確保出来ると思う。
IMG_0678s

蓋をするように基板を切り出す、大体円になるように基板を削る、中心も、先ほどの基板のボスが入るようにリューターで削る。
※10Wの電球は外形19mmくらいなので、それ以下の大きさにする。
IMG_0680s

IMG_0681s

さて、LEDはどのような物を選ぶのが良いか?、何個くらいを並列にするか?

LEDの性能は、日進月歩で、日増しに効率が高くなっている為、「使いたい時にまとめて買う!」のが良いと思う。
今回選択したのは、このLED「OSWX4EZ4E1P」リンクは秋月のものだが、適当なLED屋さんでまとめて買った方が安いと思う(自分は千石の手前のLED専門ショップで50個1000円で買った)

このLEDは、3個の白色LEDが直列になっていて、90mAまで電流が流せる、その場合30ルーメンと言う明るさ。
LEDは、 全ての範囲で電流と明るさが比例するわけではなく、効率の良い電流値があるものだ、その電流なら、発熱も少ない。
今回は実験して、10Wの電球との明るさの違いなどを調べ、1個辺り、約20mA~30mA程度にし、全部で9個を使う(大体全体で200ルーメン程度だと思う)のが丁度良いと思った。
※LEDを並列にして使うので、特性が揃っている必要がある為、同じロットの物を選ぶ必要がある。
※それでも10Wの電球よりかなり明るい。
※ある程度数を多くして分散させた方が、光が集中しないで電球と同じように拡散させる事が出来る。

IMG_0683s
実験用として作ったLEDアレイ

LEDに流れる電流の制限抵抗は、以下のように計算した。

・電源の電圧を13Vとする。
※LED保護と汎用性を考えて、ショットキーダイオードによるブリッジを組む、こうすると、極性が事実上無くなるので、扱いが楽になる。
・ショットキーダイオードの順方向電圧降下を0.4Vとする。
・LEDのVFを3.1Vとする。

25mAの電流を流すには?

13-0.4×2-3.1×3 / (25mA×9) = 13オーム

※適当な抵抗が無かったので47オームを4本並列とした。

まず、下の列、5個を直線で繋ぎ~
IMG_0684s

適当に曲げて五角形にする。
IMG_0685s

それを、口金のベースに並べて端子をハンダ付け。
IMG_0686s

最後に正面用アレイ:
まず、2個のLED、サイドを斜めに削って、斜めに密着させる。
IMG_0687s

それを2組作り、やはりサイドを斜めに削り、密着させて4個のアレイにする。

それを、上に載せて隙間からハンダを流して接続、出来上がり~
IMG_0689s
※順番を考えながら、ショートに注意して組み立てる、失敗すると修正が大変なので注意。
※4隅の角を少し削って19mmの直径に収まるようにする。

やっと1個出来た・・・、あと3個、とりあえず今日はここまで。