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

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

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

RX マイコン用リンカースクリプトとスタートアップ

RX マイコン用 gcc を更新したので、リンカースクリプトも更新してみた。

gcc のリンカースクリプトの記述仕様は「不明」の部分が多く、それでも、C 言語
なら、そう問題にならないが、C++ の場合、事前準備が必要なので、そう簡単では
無いと思える。
また、gcc の新しい機能として、新規のセクションが必要な場合もあるかもしれず、
gcc のバージョンを変更したら、その gcc がインストールされたディレクトリーに
ある基準となるリンカースクリプトをベースにしておく必要がある。

組み込みマイコン用に、自前で gcc をビルドして使っている人の中には、かなり
昔に作られた、乖離したリンカースクリプトを使っている場合もあるようだけど、
これは、少し問題がある、何か新しい機能が新設された場合、ライブラリーが正しく
動作する保障は無いと言える。
※ C 言語のみの場合は、あまり問題にならない場合が多いかもしれないけど・・

自分の環境では:(/usr/local/rx-elf)
gcc 付属のリンカースクリプトは、「/usr/local/rx-elf/rx-elf/lib/rx.ld」にあり、
それを、基準にしている。

・「rx.ld」を観ていこう~
(1) これは、RAM、ROM領域、及びスタックを定義している、自分が使うマイコンの
メモリーマップに従って、領域を設定する。
※スタックは、下位アドレスに向かって消費するので、「LENGTH」に大きな意味は無いものと
思う。

/* This memory layout corresponds to the smallest predicted RX600 chip.  */
MEMORY {
	RAM (w)   : ORIGIN = 0x00000000, LENGTH = 0x00020000 /* 128k */
	STACK (w) : ORIGIN = 0x00020000, LENGTH = 16 /* top of RAM */

	ROM (w)   : ORIGIN = 0xfff40000, LENGTH = 0x000bffd0 /* 768k */
/* This is the largest RX6000: */
/*	ROM (w)   : ORIGIN = 0xffe00000, LENGTH = 0x001fffd0 */ /* 2Mb */
}

(2) このセクションは、プログラムコードが収められている。

  .text :
  {
    PROVIDE (_start = .);
    *(.text P .stub .text.* .gnu.linkonce.t.*)
    KEEP (*(.text.*personality*))
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
    *(.interp .hash .dynsym .dynstr .gnu.version*)
    PROVIDE (__etext = .);
    PROVIDE (_etext = .);
    PROVIDE (etext = .);
    . = ALIGN(4);
    KEEP (*(.init))
    KEEP (*(.fini))
  } > ROM

(3) このセクションは、読み出し専用データが収められている。

  .rodata : {
    . = ALIGN(4);
    *(.plt)
    *(.rodata C C_2 C_1 W W_2 W_1 .rodata.* .gnu.linkonce.r.*)
    *(.rodata1)
    *(.eh_frame_hdr)
    KEEP (*(.eh_frame))
    KEEP (*(.gcc_except_table)) *(.gcc_except_table.*)
    . = ALIGN(4);
    PROVIDE(__romdatastart = .);
  } > ROM

(4) このセクションは、起動時、RAM にコピーしておく必要のあるデータなどで、ROM 領域
に配置する。
※「__romdatacopysize」が定義されている。
この中で、「.ctors」、「.dtors」と二つの重要な部分がある、これは、アプリケーション
を起動する前に準備しておく必要のあるコンストラクター、及び、アプリケーション終了時
に呼び出す、デストラクター関係である。
※通常組み込みでは、アプリケーションは終了しないので、デストラクターは呼び出す必要
が無い、もちろん、複数のアプリケーションを動的に呼び出すような機構を備えたシステム
では、デストラクターを呼び出す事は、必須であるけど。

  .data : {
    . = ALIGN(4);
    PROVIDE (__datastart = .); /* IF_ROROM */
    PROVIDE (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE (__preinit_array_end = .);
    PROVIDE (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array))
    PROVIDE (__init_array_end = .);
    PROVIDE (__fini_array_start = .);
    KEEP (*(.fini_array))
    KEEP (*(SORT(.fini_array.*)))
    PROVIDE (__fini_array_end = .);
    LONG(0); /* Sentinel.  */

    /* gcc uses crtbegin.o to find the start of the constructors, so
       we make sure it is first.  Because this is a wildcard, it
       doesn't matter if the user does not actually link against
       crtbegin.o; the linker won't look for a file to match a
       wildcard.  The wildcard also means that it doesn't matter which
       directory crtbegin.o is in.  */
    KEEP (*crtbegin*.o(.ctors))

    /* We don't want to include the .ctor section from from the
       crtend.o file until after the sorted ctors.  The .ctor section
       from the crtend file contains the end of ctors marker and it
       must be last */
    KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))

    KEEP (*crtbegin*.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
    KEEP (*(.jcr))
    *(.data.rel.ro.local) *(.data.rel.ro*)
    *(.dynamic)

    *(.data D .data.* .gnu.linkonce.d.*)
    KEEP (*(.gnu.linkonce.d.*personality*))
    SORT(CONSTRUCTORS)
    *(.data1)
    *(.got.plt) *(.got)

    /* We want the small data sections together, so single-instruction offsets
       can access them all, and initialized data all before uninitialized, so
       we can shorten the on-disk segment size.  */
    . = ALIGN(4);
    *(.sdata .sdata.* .gnu.linkonce.s.* D_2 D_1)

    . = ALIGN(4);
    _edata = .;
    PROVIDE (edata = .);
    PROVIDE (__dataend = .);
  } > RAM AT> ROM

  /* Note that __romdatacopysize may be ZERO for the simulator, which
     knows how to intialize RAM directly.  It should ONLY be used for
     copying data from ROM to RAM; if you need to know the size of the
     data section, subtract the end symbol from the start symbol.  */
  /* Note that crt0 assumes this is a multiple of four; all the
     start/stop symbols are also assumed long-aligned.  */
  PROVIDE (__romdatacopysize = SIZEOF(.data));

(5) このセクションは、変数などの領域、通常、起動時に「0」クリアしておく。

  .bss : {
    . = ALIGN(4);
    PROVIDE (__bssstart = .);
    *(.dynbss)
    *(.sbss .sbss.*)
    *(.bss B B_2 B_1 .bss.* .gnu.linkonce.b.*)
    . = ALIGN(4);
    *(COMMON)
    . = ALIGN(4);
    PROVIDE (__bssend = .);
    _end = .;
    PROVIDE (end = .);
  } > RAM
  PROVIDE (__bsssize = SIZEOF(.bss) / 4);

(6) このセクションは、スタック領域
RX マイコンの場合、スタックは2つあり、ハードウェアー依存のスタック(主に、割り込み
発生の場合などで、ステータスやレジスターを退避する)と、ユーザー領域依存のスタックが
ある。

  .stack (ORIGIN (STACK)) :
  {
    PROVIDE (__stack = .);
    *(.stack)
  }

(7) このセクションは、ハードウェアー依存のベクターテーブル専用領域。
※RX マイコンでは、「リセット」信号がアサートされると、「_start」から起動する。
※他に、ハードウェアー依存の例外が発生した場合に起動するベクターを記述する。

  /* Providing one of these symbols in your code is sufficient to have
     it linked in to the fixed vector table.  */

  PROVIDE (__rx_priviledged_exception_handler = 0x00000000);
  PROVIDE (__rx_access_exception_handler = 0x00000000);
  PROVIDE (__rx_undefined_exception_handler = 0x00000000);
  PROVIDE (__rx_floating_exception_handler = 0x00000000);
  PROVIDE (__rx_nonmaskable_exception_handler = 0x00000000);

  .vectors (0xFFFFFFD0) :
  {
    PROVIDE (__vectors = .);
    LONG (__rx_priviledged_exception_handler);
    LONG (__rx_access_exception_handler);
    LONG (0);
    LONG (__rx_undefined_exception_handler);
    LONG (0);
    LONG (__rx_floating_exception_handler);
    LONG (0);
    LONG (0);
    LONG (0);
    LONG (0);
    LONG (__rx_nonmaskable_exception_handler);
    LONG (_start);
  }

・それでは、「_start」関数から、「main」までにどのような手順を踏んだら良いのか?
同じディレクトリーに、「crt0.o」がありこのコードが基準となる。
このオブジェクトは、既にバイナリーになっているので、objdump などで、逆にアセン
ブリコードに戻す事で、内容を確認出来る。

  rx-elf-objdump -S crt0.o

crt0.o:     file format elf32-rx-le

Disassembly of section P:

00000000 <_start>:
	.text

	.global _start
_start:
.LFB2:
	mvtc	#0, psw
   0:	fd 77 00 00                   	mvtc	#0, psw
	/* Enable the DN bit - this should have been done for us by
           the CPU reset, but it is best to make sure for ourselves.  */	
	mvtc    #0x100, fpsw
   4:	fd 7b 03 00 01                	mvtc	#256, fpsw
   9:	fb 02 00 00 00 00             	mov.l	#0, r0
	mov	#__stack, r0
   f:	fd 73 0c 00 00 00 00          	mvtc	#0, intb
	mvtc	#__vectors, intb
  16:	fb 12 00 00 00 00             	mov.l	#0, r1

	mov	#__datastart, r1
  1c:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__romdatastart, r2
  22:	fb 32 00 00 00 00             	mov.l	#0, r3
	mov	#__romdatacopysize, r3
	smovf
  28:	7f 8f                         	smovf
  2a:	fb 12 00 00 00 00             	mov.l	#0, r1

	mov	#__bssstart, r1
	mov	#0, r2
  30:	66 02                         	mov.l	#0, r2
  32:	fb 32 00 00 00 00             	mov.l	#0, r3
	mov	#__bsssize, r3
	sstr.l
  38:	7f 8a                         	sstr.l
  3a:	fb d2 00 00 00 00             	mov.l	#0, r13
	/* Initialise the constant data pointer and small data pointers.  */
	mov	#__pid_base, r13
	mov	#__gp, r12
#else
	/* Initialise the small data area pointer.  */
	mov	#__gp, r13
  40:	05 00 00 00                   	bsr.a	40 <_start+0x40>
	mov	# _start, r1
	mov	# _etext, r2
	bsr.a	__monstartup
#endif

	mov	#0, r1 /* argc */
  44:	66 01                         	mov.l	#0, r1
	mov	#0, r2 /* argv */
  46:	66 02                         	mov.l	#0, r2
	mov	#0, r3 /* envv */
  48:	66 03                         	mov.l	#0, r3
  4a:	05 00 00 00                   	bsr.a	4a <_start+0x4a>
	bsr.a	_main
  4e:	05 00 00 00                   	bsr.a	4e <_start+0x4e>

00000052 <_rx_run_preinit_array>:
        mov      r1, r13       ; Save return code.
	bsr.a	__mcleanup
        mov     r13, r1
#endif

	bsr.a	_exit
  52:	fb 12 00 00 00 00             	mov.l	#0, r1

	.global	_rx_run_preinit_array
	.type	_rx_run_preinit_array,@function
_rx_run_preinit_array:
	mov	#__preinit_array_start,r1
  58:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__preinit_array_end,r2
  5e:	04 25 00 00                   	bra.a	83 <_rx_run_inilist>

00000062 <_rx_run_init_array>:
	bra.a	_rx_run_inilist
  62:	fb 12 00 00 00 00             	mov.l	#0, r1

	.global	_rx_run_init_array
	.type	_rx_run_init_array,@function
_rx_run_init_array:
	mov	#__init_array_start,r1
  68:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__init_array_end,r2
	mov	#4, r3
  6e:	66 43                         	mov.l	#4, r3
  70:	04 13 00 00                   	bra.a	83 <_rx_run_inilist>

00000074 <_rx_run_fini_array>:
	bra.a	_rx_run_inilist
  74:	fb 22 00 00 00 00             	mov.l	#0, r2

	.global	_rx_run_fini_array
	.type	_rx_run_fini_array,@function
_rx_run_fini_array:
	mov	#__fini_array_start,r2
  7a:	fb 12 00 00 00 00             	mov.l	#0, r1
	mov	#__fini_array_end,r1
	mov	#-4, r3
  80:	fb 36 fc                      	mov.l	#-4, r3

00000083 <_rx_run_inilist>:
	/* fall through */

_rx_run_inilist:
next_inilist:
	cmp	r1,r2
  83:	47 12                         	cmp	r1, r2
  85:	20 17                         	beq.b	9c 
	beq.b	done_inilist
	mov.l	[r1],r4
  87:	ec 14                         	mov.l	[r1], r4
	cmp	#-1, r4
  89:	75 04 ff                      	cmp	#-1, r4
  8c:	20 0c                         	beq.b	98 
	beq.b	skip_inilist
	cmp	#0, r4
  8e:	61 04                         	cmp	#0, r4
  90:	20 08                         	beq.b	98 
	beq.b	skip_inilist
	pushm	r1-r3
  92:	6e 13                         	pushm	r1-r3
	jsr	r4
  94:	7f 14                         	jsr	r4
	popm	r1-r3
  96:	6f 13                         	popm	r1-r3

00000098 :
skip_inilist:
	add	r3,r1
  98:	4b 31                         	add	r3, r1
  9a:	2e e9                         	bra.b	83 <_rx_run_inilist>

0000009c :
	bra.b	next_inilist
done_inilist:
	rts
  9c:	02                            	rts

Disassembly of section .fini:

00000000 <__rx_fini>:
	.text

	.global _start
_start:
.LFB2:
	mvtc	#0, psw
   0:	05 00 00 00                   	bsr.a	0 <__rx_fini>

00000003 :
	...

ここから、必要な部分をコピーして、また、自分のシステムに必要な部分を追加して、スタートアップ
ルーチンを構築するのが良いと思う。

で、RX63T(RAM: 8KB, ROM: 64KB) 用のリンカースクリプトと、スタートアップルーチンを作成してみた。

RX63T 用リンカースクリプト
スタートアップルーチン「start.s」
初期化関数「init.c」
※自分の実装では、「スタートアップ」は、「start.s」と「init.c」に分けている。

RXマイコン用、フラッシュ書き込みプログラム(RX63T)

先日、gcc-4.9.3 を使って、R8C の開発環境を整えた。

RXマイコンにおいても、gcc-4.9.3 で、構築でき、そのコンパイラでビルド
したプロジェクトも問題無く動作する事を確認した。

以前に R8C 用のフラッシュ書き込みプログラムを作成して、もはや、ルネサス
のツールを一切利用する事なく、オープンソースだけで開発出来るようになった。
※Mac-Book でも、R8C マイコンの開発が可能となった。

RXマイコン用のフラッシュ書き込みツールも、ブート時のプロトコルが公開さ
れている為、実装は可能だと思っていた。
そこで、RX63T のハードウェアーマニュアルに記載されたプロトコルを理解しな
がら、少しづつ、機能を実装して、書き込みまで確認出来たので、とりあえず公開
する。
同じルネサスでも、R8C とは、構成が違うので、基本的な部分以外は書き直しとな
った。
※RX63T の仕様を元にしているが、他のRXシリーズにも、そのまま、又は少しの
修正で使えると思う。

いつものように GitHub に公開している。
※R8C のソースと重複するソースコードが含まれているが、多少更新している。
そのうち、重複する部分は、別にまとめておきたいと思う。

-----
まず参考にした資料は、「RX63Tグループ ユーザーズマニュアル ハードウェア編」
で、ルネサスからダウンロードできる。

細かい仕様は、マニュアル「40.8 ブートモード」に詳細に書かれているので、それを
理解すれば、誰でも実装する事が出来る。
16ビットや32ビットの値を送る場合や、読み込む場合は、ビッグ・エンディアン
になっているようだ。
※これは、マニュアルには書かれていなかった。

現在使っている、デバイスは、RX63Tの64ピン版で、USBを持たないので、
シリアル通信に限定している。

また、コードプロテクト機能を使わない場合でも、フラッシュ書き込みモード(P/E)
に移行すると、内部フラッシュは自動で消去されてしまう為、内部フラッシュの状態を
読み出す必要がある場合は、必ず、プロテクトIDを設定する必要があるようだ。

ソースコードをマルチプラットホームにしたいので、RS232C の入出力は、「termios.h」
を使って実現しているので、MSYS2 環境でコンパイルする必要があり、また、boost を
使っている為、「/usr/local/」以下に boost のソースを配置しておく必要がある。
※ライブラリーを使っていないのでヘッダーだけあれば良い。
MacOS や Linux でもコンパイルする事が出来ると思う。
※現状では「termios.h」は、MSYS2 環境にのみ存在する。
※ MSYS2 環境では、boost はインストーラーで管理していないみたい。
※ボーレートの設定範囲は、環境に依存する様なので、どの環境でもサポートされている
ボーレートのみを使っている為、上限は、115200 となっている。

IMG_0782

フラッシュへの書き込みは、必ず256バイトのブロック毎に行う仕様で、ベリファイの
コマンドは無いので、比較を行う場合は、P/E モード中に読み出して、比較する必要がある。

コードプロテクト行った場合は、フラッシュの消去は自動では無いので、書き込む前に消去
を行う必要がある。

まだ、未完成ではあるが、とりあえず、RX63T に、書き込めるようになったので、とりあえず
公開しておく。

R8C 用コンパイラの構築 gcc-4.9.3

Windows の開発環境で、MSYS2 がメインになってから、
長い間、最新の gcc で m32c(R8C)のコンパイラの構築に失敗していた。
コンパイラの構築には時間がかかる為、オプションの影響などもあまり正確に認識していな
かった。
休みに入ったので、コンパイラと格闘してみた結果、
gcc-5.2.0 で、m32c 用 gcc の構築に成功したので、「要点」を書いておこうと思う。
gcc-5.2.0 の構築は出来たものの、バイナリーを作成してデバイスに書き込んでも、正常に動作しなかったので、
とりあえず、動作が確認されているバージョン(gcc-4.8.3、gcc-4.9.3)を使う。

※「gcc-4.7.4」は現状では、MSYS2 のカレントのコンパイラが 4.9 系では、正常にコンパイル
できない~
いつもながら、クロス gcc の構築には、苦労する・・・

Windows、OS-X で試したので、多分再現性は高いと思う。

Windows では、MSYS2 環境で行い、OS-X では、macport で行う。
基本的に、MSYS2 環境の方が、多少ハードルが高いけど、MSYS2 は、ツールのインストール
が pacman で出来るので、以前より簡単となっている。

まず、MSYS2をインストールする。
※詳しいインストールの方法は、ネットで探すと沢山出てくるので、参考にされたい。
※MSYS2には、3つの実行環境があり、今回は、「msys2_shell.bat」環境で行う。
・まず、システムをアップデート

% update-core

完了したら、ターミナルをクローズして、再度ターミナルを開き、残りを更新。

% pacman -Su

次に、ビルドに必要なツール類をインストールする。(MSYS2)

% pacman -S gcc
% pacman -S texinfo
% pacman -S mpc-devel
% pacman -S diffutils
% pacman -S automake

※他にもあるかもしれないので、適宜インストールする。
※もし、何かツールが無くてコンパイルに失敗したら、ビルドディレクトリーを消して、
「configure」からやり直すのが「確実」だと思う。

・ソースコードのアーカイブを取っておく、今回は以下の組み合わせで行った。
※「gcc-4.8.3」を使う場合は、「4.9.3」を「4.8.3」に読み替えて行う。

    binutils-2.25.1.tar.gz
    gcc-4.9.3.tar.gz
    newlib-2.2.0.tar.gz

・binutils-2.25.1 の構築

% cd
% tar xfvz binutils-2.25.1.tar.gz
% cd binutils-2.25.1
% mkdir m32c_build
% cd m32c_build
% ../configure --target=m32c-elf --prefix=/usr/local/m32c-elf --disable-nls
% make
% make install

・「/usr/local/m32c-elf/bin」へPATHを通して、シェルを起動しなおす。

・C コンパイラの構築
※少し古い gcc のソースコードでは、MSYS2 環境を認識する定義が無い為、「automake-1.15」の
設定をコピーする。(その時の最新をコピーすれば良いと思われる、今回は 1.15 を使った)

% cd
% tar xfvz gcc-4.9.3.tar.gz
% cd gcc-4.9.3
% cp /usr/share/automake-1.15/config.guess .
% mkdir m32c_build
% cd m32c_build
% ../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c --disable-libssp --with-newlib --disable-nls --disable-threads --disable-libgomp --disable-libmudflap --disable-libstdcxx-pch --disable-multilib --disable-bootstrap
% make
% make install

・newlib-2.2.0 の構築

% cd
% tar xfvz newlib-2.2.0.tar.gz
% cd newlib-2.2.0
% mkdir m32c_build
% cd m32c_build
% ../configure --target=m32c-elf --prefix=/usr/local/m32c-elf
% make
% make install

・C++ コンパイラの構築

% cd
% cd gcc-4.9.3
% cd m32c_build
% ../configure --prefix=/usr/local/m32c-elf --target=m32c-elf --enable-languages=c,c++ --disable-libssp --with-newlib --disable-nls --disable-threads --disable-libgomp --disable-libmudflap --disable-libstdcxx-pch --disable-multilib --disable-bootstrap
% make
% make install

エラー無く終了すれば、完了!

r8cprog の構築も MSYS2 で出来るので、これでやっと R8C の開発環境が全て MSYS2 で完結する~

一応、I2C_RTC_test をビルドして、M120 に書き込んでテストしてみた。
※問題なく動作しているようである。

※「configure」で、「m32c」を「rx」に変更すれば、RX マイコン用 gcc が構築出来る。
※ rx マイコン用 gcc の構築では、4.9.3 を使う。(4.8.3 は C コンパイラの構築中エラーで止まる)

参考リンク:
最低限のクロスコンパイラの作り方
MSYS2における正しいパッケージの更新方法

R8C RTC に DS3231 を追加

最近、アマゾンで、中華の Arduino 用モジュールが色々入手出来る。
※かなり前からかな・・

これらのモジュール基板、多くの種類があり、値段がもの凄く安い!
※品質はそれなりかもしれないが、この値段なら十分だと思える。
※部品単体で買っても、こんなに安くは買えない。

で、気になった「基板」をいくつか買ってみた~
※RTC、ジャイロ・加速度センサー、リチウムイオン充電モジュールなど

・DS3231(RTC)
このモジュールは、マキシム社のRTCで、I2Cインターフェースを備えている。
温度補償水晶発振器(TCXO)および水晶を内蔵していて、超高精度。

このモジュールには、32KのEEPROM、リチウムイオン電池(CR2032)による
バックアップ付きで、電池まで付いている。

これで、たったの、200円
※degi-key で調べると、デバイス単体で890円

ありえない値段でビックリ!
※2個買ったけど、どちらも機能するようだ。

とりあえず、以前にDS1371でRTCのテストプロジェクトを作成したので、DS3231用
クラスを実装してみた。
※しょうもないバグで、数時間悩んだけど・・

内臓レジスターは、BCDになっており、非常に面倒な仕様となっている。
クラスでは、レジスターを読み出してから、time_t に変換している。
逆にレジスターに書き込むには、time_t から、tm 形式に変換してから行う。
※そうしないと、多くの場合、不便極まりないので・・・
本当は、こんな「クソ」仕様のRTCは使いたくないのだが、安いので仕方無い・・・
※この仕様のRTCは「うるう年」を厳密には正しく扱えない、まぁ今は既に2015年なので、
2099年までの範囲なら、まぁ問題無いか・・

common/time.c は、タイムゾーンを含めた、時間関係で最低限必要な関数を実装してある。
通常は、libc に含まれるが、R8C で、time 関係のライブラリーをリンクすると、非常に大きな
容量となるので、独自に実装した関数郡を用意してある。
標準の「time.h」をインクルードすると、「当たる」ので注意が必要。

// 対象の RTC を有効にする
// #define DS1371
#define DS3231

※「main.cpp」で、どちらかを有効にする事で、RTCのタイプを切り替えできる。

IMG_0781s

I2C_RTC_test

100円で買える R8C の C++ と開発環境

この記事は C++ Advent Calendar 2015 の 15日目の記事です.
前の日は amedama41 さんの「io_service の使い方」でした.

R8C/M120AN,R8C/M110AN の紹介

このマイコンは、秋月電子などで、1個100円で売られているマイコンで、それぞれ、DIP20
DIP14パッケージになっています。

※DIP20、DIP14とは、以下のような物で、電子工作では、ユニバーサル基板に組んだり、
ブレッドボードに組んだりできるので、重宝するパッケージです。
(昔ながらのパッケージです)
IMG_0777s

このマイコンに注目するのは、大きな理由があります。
カタログスペックでは、ProgramFlash:2キロバイト(メインプログラムを格納する領域)、
RAM:256バイト(スタックやワーク領域)、でも実際には・・

ProgramFlash:64キロバイト、RAM:1280バイト
の容量を持っていて、制限なく使う事ができます。

このくらいあると、プログラムの幅が広がり、C++ でプログラミングして動作させるのにも
問題ありません。
※RAM が1280バイトでは、記憶割り当てが使えない為、大きな制限がある事はあるので
すが・・・
通常マイコンには、様々なバリエーションのデバイスがあり、基本的には、Flash、RAM、内部
機能の違いで、付加価値を変えており販売価格が違います。
しかしながら、内部のシリコンを個別に設計するのは莫大なコストがかかる為、多くは共通に
なっているものと思います。
通常では、デバイスの許す領域以外はアクセスしても、無効なエリアとなるような仕組みと専
用ヒューズが用意されている
ものと思いますが、このデバイスは安価なバリエーションに属するデバイスで、ツール側の対
応で、行う方針だったように推測します。
Flash へプログラムを書き込む場合、専用の書き込みツールを使う為、通常は、デバイスの範
囲を超えて書き込む事はできませんが、書き込みソフトも実装したので、全ての容量を使う事
が出来ます。
※書き込みツールは Mac や Linux(試していませんが・・)でも使えるはずです。

始め、この話を聞いた時、そんな「旨い」話があるはず無いとも思っていましたが、実際に試
したら、使えたので、小躍りした思い出がありますww
※最初は32Kまで(0x8000 ~ 0xffff)確認できましたが、その後の調査で、もう32Kあ
る事が発覚しました。(0x10000 ~ 0x17fff)
※R8C は基本、8ビットのマイコンで、基本的に64Kバイトを超える領域にアクセスするに
は、特別な方法が必要ですが割と簡潔な方法で、拡張されたエリアを使う事ができます。

開発環境の準備


ルネサスエレクトロニクスが、提供する、IDE(無料版には制限があります)を使う事も出来ますが、
Windows だけなので、自分で開発環境を用意します。
と、言っても、gcc などのソースコードを取得してコンパイルするだけです。
R8C マイコンは、M32C マイコンのサブセットで、M32C のコンパイラを構築して、デバイスオプ
ションで切り替えます。
gcc の構築については、以下のリンクを参照下さい。
・m32c gcc の構築
※このバージョンで構築した gcc は、4.7.4 ですが、「-std=c++0x」でほぼ C++11 でプログラ
ム出来ます。

※ m32c のクロスコンパイラは、メンテナンスされていない為、最新の gcc ではコンパイルに失
敗するようです。

※ 時間とエンジニアリングがあれば、最新の gcc や、clang でコンパイル可能な物も作ってみた
いです。

※gcc-4.9.3 を使えます。

プログラムの書き込み

組み込みマイコンが、「組み込み」たる所以は、「プログラムを書き込んで、単独で動作する事」
です。
このマイコンには、モード端子があり、それを切り替える事で、内臓フラッシュへの書き込みモー
ドと、単独でブートするモードがあります。
内臓フラッシュの書き込みでは、データを入出力する為の簡単なプログラムが内臓されており、シ
リアル接続を使って、簡単なプロトコルを使い内臓フラッシュへデータを書き込めるようになって
います。

以下は、代表的な USB シリアル変換回路の回路図です。
SerialModule
※USB シリアル変換は、安価に入手出来るので、自分で作る必要はありませんが、上記の回路なら、500円くらいで作れます。

R8C のフラッシュライター(参考回路)
R8C_Writer

USB シリアル変換と、ゼロプレッシャーソケットを組み合わせた書き込み機
IMG_0744

デバイスのプログラムは、通常、開発基板のソケットからデバイスを外して、ライターの
ソケットにセットして、プログラムを書き込み、ライターから、開発基板にデバイスを戻
す。
などの、サイクルで行うのですが、プログラムを変更する度に、ソケットから外して、戻
してだと非常に面倒です、そこで、開発基板にシリアル入出力用のコネクター(写真では
6ピン)を付けておき、スイッチ(白い小さなスライドスイッチ)で切り替えて、基板に
デバイスを乗せた状態でプログラム出来るように工夫してあります。
IMG_0779s

書き込みプログラムは、一応、Windows、Mac でコンパイルできるように実装してあります。
r8cprog
※Windows でコンパイルする為には、termios.h が必要です。(特定の環境に付属します)

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

この例では、「led_test.mot」ファイルを「R5F2M120」へ書き込んでいます。
その際、「-e」でイレース、「-w」で書き込み、「-v」でベリファイ、のオプションを
追加しています。
マイコンのフラッシュプログラムは、他にみ色々な機能がありますので、コマンドのヘルプ
などを参考にして下さい。
Windows 版の GUI 付き書き込みプログラムが、千秋ゼミで公開されています。
※登録が必要です。

I/O定義の構築

メーカーがサンプルで提供している、I/O定義ファイルは、非常にチープで、解りにくい代物
です。
そこで、R8C/M11Aグループ、R8C/M12Aグループ データシートに記述されているレジスター名に一致して、扱い易いようI/O定義
を C++ のテンプレートで作成しました。
このテンプレートは、以前にRXマイコンで養ったテクニックを盛り込んだ物ですが、まぁ60
点くらいの出来だと思います。
R8C I/O 定義ヘッダー集
GitHub に上げてあります。
組み込みマイコンで C++ ぽぃ書き方が出来ますww

メーカーがサンプルなどで提供するI/O定義は、C言語向けのもので、特定のコンパイラでしか
正しく機能しない物が多く、判りにくく、エラーに対する許容度が低く使う気になりませんでした。

ネットには、同じような組み込みマイコン向けのI/O定義をテンプレート化するコードはありま
したが、色々調べると、機能的に満足出来ない部分もあり、結局、学習も含めて自分で実装する事
にしました。
開発過程で、コンパイルされたアセンブリコードを眺めて、ある程度許容されたコードが出るよう
に工夫しました。
また、最適化無しでも、最適化しても、動作するコードが出るように工夫してあります。

たとえば、P1ポート・レジスターのB2を「1」にする場合は

    P1.B2 = 1;

と書けます。

読み出す場合は

    bool value = P1.B2();

とします。
「()」オペレーターを使って、「読み出し動作」を明確に分けています。
こうしないと、最適化した場合と、しない場合で、問題のあるコードがでてしまう場合があります。

他に、「enum class」でポートの機能切り替えを定義しておき、機能を切り替えるようにしてみま
した。

#include "common/port_map.hpp"

    utils::PORT_MAP(utils::port_map::P10::AN0);
    utils::PORT_MAP(utils::port_map::P11::AN1);
↑ P10、P11 をアナログ入力 AN0、AN1 に指定

    utils::PORT_MAP(utils::port_map::P12::TRCIOB);
    utils::PORT_MAP(utils::port_map::P13::TRCIOC);
↑ P12 を TRCIOB、P13 を TRCIOC に設定

デバイス操作クラス

組み込みマイコンには、色々なハードウェアーリソースがあり、これらを簡潔に使う為に、小さなク
ラスライブラリーを提供しています。
※C++ のテンプレートは、このような用途に最適で、非常に柔軟性のある判り易い実装ができ、最適
化にも有利です。
R8C/M120/M110 では以下のような機能があります。

(1) 入出力ポート:17本 (LED 駆動用ポート含む)
(2) 外部割り込み入力:8本
(3) 16ビット多機能タイマ(タイマ RJ2) ---> trj_io
(4) 8ビットプリスケーラ付8ビット多機能タイマ(タイマRB2 ):1 ---> trj_io
(5) 16ビットインプットキャプチャ/アウトプットコンペアタイマ(タイマRC ):1 ---> trc_io
(6) UART/クロック同期形シリアルインタフェース:1チャネル ---> uart_io
(7) 10ビットA/Dコンバータ:6チャネル ---> adc_io
(8) コンパレータ:2回路 ---> comp_io
(9) ウォッチドッグタイマ
(10) クロック発生回路:XINクロック発振回路、オンチップオシレータ(高速/低速)
(11) データフラッシュ:2KB ---> eeprom_io

他に、I2C インターフェースをソフトでエミュレーションするクラス。
特定のデバイス向けクラスなど色々実装してあります。
※GitHub 参照

拡張領域の利用法

リンクローダーに与えるローダースクリプトに、0x10000 ~ 0x17fff までのエリア
(セクション)を宣言しておき、プログラムソースで、

__attribute__ ((section (".exttext")))

※この場合、拡張エリアは「exttext」としてセクションを定義してあります。
セクションを切り替え、コードが格納される場所を分散すれば、後はコンパイラが
64K を超える領域への呼び出しを、R8C が持つ、20 ビットアドレスが可能な命令
に置き換えてくれます。

M120AN/m120an.ld  --->  リンカースクリプト
ADC_test/main.cpp  --->  main 関数が、exttext エリアに指定

アセンブリコード:
    80a2:	fd 00 00 01 	jsr.a 10000 <_main>

Disassembly of section .exttext:

00010000 <_main>:
   10000:	7c f2 d1    	enter #0xd1
   10003:	7e 9f 98 00 	bset:g 0,0x13

サンプルプログラム

組み込みマイコンで、良く使われる機能に絞って、色々サンプルプロジェクトを開発しました。

ADC_test/          A/D コンバーターのテスト
COMP_test/         コンパレーター入力のテスト
ENCODER_test/      エンコーダースイッチを使うテスト
FLASH_test/        内臓データ EEPROM を操作するテスト
I2C_EEPROM_test/   I2C 接続の EEPROM を操作するテスト
I2C_RTC_test/      I2C 接続の RTC を読み書きするテスト
LCD_test/          LCD(Bitmap Graphics)を描画するテスト
LED_test/          LED の点滅テスト(よく言う「Lチカ」)
PLUSE_INP_test/    パルス入力周期を測定するテスト
PLUSE_OUT_LCD/     任意のパルス出力を行うテスト(エンコーダー入力、LCD 表示)
PLUSE_OUT_test/    任意のパルス出力を行うテスト
PSG_test/          ※開発中
PWM_test/          PWM 出力を行うテスト
RC_SERVO_test/     ラジコン用サーボを動作させるテスト
RGB_LED_test/      ※開発中
SD_monitor/        SDカードの読み書きモニター
SD_test/           簡単なSDカードの読み書きテスト
SD_WAV_play/       SDカードにあるWAVファイルを再生するテスト(PWM信号としてアナログ的に出力)
SWITCH_test/       スイッチ入力テスト
TIMER_test/        タイマー機能のテスト
UART_test/         UART を使った、シリアル入出力テスト

各プロジェクトには、Makefile があり、

make

とすれば、コンパイルが行われます、その際、従属規則も同時に生成するようにし
ています。
プログラムを書き込み際には

make run

とします。
※「Makefile」参照

まとめ

小さくて、機能豊富なマイコンなら、アイデア次第で、色々な「オリジナル電子小物」を作る事が
でき楽しいものです~
上記のクラスライブラリーを使えば、最低限の手間と理解で、簡潔にやりたい事が出来ると思いま
す。
興味があったら、何か作ってみてはいかがですか?
質問等あったら気軽にメールでも下さい。

IMG_0775s

R8Cを使ってRCサーボを動かす

ホビーでよく使うアイテムとして、ラジコン用のサーボモーターがある。

R8C/M120AN で動かしてみたので、簡単に、解説する。

まず、テスト用にアマゾンで、一番安いサーボモーター買ってみた。
IMG_0768s

HKSCM9-6 RCサーボ(ホビーキング製)
コネクタタイプ:JR
GND:茶
+V: 赤
信号: 橙
最大定格: 6V

※このサーボは意外と小さくて、そんなに大きいトルクを出せないが、テスト用に丁度良い。
※もちろん、他のサーボでもかまわない。
※フタバタイプでは、微妙に信号の仕様が異なるので、調整が必要。
※R8Cを5Vで駆動

ラジコン用サーボを動かす信号は、メーカーにより微妙に異なるが、ほぼ同一で、PWM で、
パルス幅と角度が比例関係にあり、JR系では、

周期: 50Hz(20ms)
ニュートラル: 1500uS(マイクロ秒)
※フタバタイプでは、1520uS(マイクロ秒)となっている。
可動範囲: +-600uS、900uS~2100uS(マイクロ秒)

となっているようだが、周期については、ある程度許容範囲があるようだ、当然ながら、
周期を短くした方が、応答は良くなるものと思われる。

・R8CのCチャネルで、20MHzを8分周すると、50000カウントで、20mSで、
50Hzとなり、16ビットの範囲に収まる。

    bool pfl = 0;  // 0->1
    uint8_t ir_level = 2;
    timer_c_.start_pwm(50000, trc_type::divide::f8, pfl, ir_level);

・ニュートラルは、1500uSなので、3750となる。
・可動範囲は+-600uSなので、+-1500となる。
・PWM出力をP1_2(18):TRCIOB、P1_3(17)TRCIOCにして、
2個のRCサーボを別々に動かす。
※TRCIODを使えば、3個まで駆動できる。

サンプルプログラムでは、AN0、AN1でA/D変換した結果(0~1023)を、使って
サーボの可動範囲±1500(±600uS)になるようにしている。

IMG_0769s

プロジェクトのソースコード

FFmpeg ライブラリーを使った動画のデコード(C++ソースコード)

最近、色々忙しくなってきて、週末は時間が何かと足りなくなってます・・

では、早速本題!
FFmpeg はオープンソースで、動画や音声のエンコード、デコードなどを行うツール
です、非常に多くの人が改善してきた事で、非常に高い品質と、機能が実装されて
います。

また、このプロジェクトはマルチプラットホームなので、多くの環境で同じように
使う事が出来ます。
「FFmpeg」コマンドを使って動画をエンコードしたりする話は、既に沢山の方が書
かれています、今回の話は、「FFmpeg」が利用しているライブラリー郡を使って、
C++ のプログラムから、動画をデコードしてみようという内容です。

(1)準備
・まず、FFmpeg コマンドをインストールします。
※MSYS2 MinGW-w64 で、話を進めますが、OS-X の BREW などでも同じように出来ま
す。

pacman -S mingw-w64-x86_64-ffmpeg

この操作だけで、ffmpeg 他必要なライブラリーなど全てインストールされます。

(2)実装
・動画の中の、特定の1フレームをデコードするサンプル

・まず、必要そうなヘッダーをインクルード

#include <iostream>
#include <string>
extern "C" {
	#include <libavcodec/avcodec.h>
	#include <libavfilter/avfilter.h>
	#include <libavformat/avformat.h>
	#include <libswscale/swscale.h>
};

※これら、API のプロトタイプでは、全てC言語のみが想定されている為、C++
から使う場合は「extern "C"」が必要です。

・初期化

    avcodec_register_all();
    av_register_all();
    avfilter_register_all();

※FFmpeg 関係お約束の呼び出しですかねぇ・・

・ビデオファイルを開く


    std::string file_name = argv[1];
    AVFormatContext* pFormatCtx = NULL;
    if(avformat_open_input(&pFormatCtx, file_name.c_str(), NULL, NULL) != 0) {
        std::cerr << "ERROR: avformat_open_input(): '" << file_name << '\'' << std::endl;
        return -1;
    }

・ストリーム情報の取得と表示

    if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        std::cerr << "ERROR: avformat_find_stream_info(): '" << file_name << '\'' << std::endl;
        return -1;
    }

    // ストリーム情報の表示
    av_dump_format(pFormatCtx, 0, file_name.c_str(), 0);
    fflush(stderr);

・コーデックの検索とコーデックのオープン

    AVCodecContext* pCodecCtx = pFormatCtx->streams[0]->codec;
    AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    // コーデックを開く
    if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        std::cerr << "ERROR: avcodec_open2(): '" << file_name << '\'' << std::endl;
        return -1;
    }

・ログ・レベルの設定

    av_log_set_level(1);

※デコードの過程で、非常に細かく、内部の軽微な問題を報告してくれるので、それをカット!

・バッファー関係の確保と必要な初期化

    // フレームの確保
    AVFrame* pFrame = avcodec_alloc_frame();
    // イメージの確保
    AVFrame* pImage = avcodec_alloc_frame();

    // イメージ用バッファの確保
    unsigned char* buffer = (unsigned char *)av_malloc(avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height));
    // バッファとフレームを関連付ける
    avpicture_fill((AVPicture*)pImage, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);

・スケーリング用コンテキストの取得

    struct SwsContext* pSWSCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height
        , pCodecCtx->pix_fmt
        , pCodecCtx->width, pCodecCtx->height
        , PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);

・メイン

    int count = 0;
    AVPacket packet;

    // フレーム読み込み
    while(av_read_frame(pFormatCtx, &packet) >= 0) {

        // デコード
        int state;
        avcodec_decode_video2(pCodecCtx, pFrame, &state, &packet);
 
        // 各フレーム、デコード完了
        if(state) {
            // 特定のフレームを切り出し
            if(count == 400) {
                int bsize = pCodecCtx->width * pCodecCtx->height * 3;
                sws_scale(pSWSCtx, (const uint8_t **)pFrame->data
                    , pFrame->linesize, 0 , pCodecCtx->height
                    , pImage->data, pImage->linesize);

                 FILE *fp = fopen("rgb24.raw", "wb");
                 if(fp) {
                     std::cout << "FrameSize: " << (int)pCodecCtx->width << ", " << (int)pCodecCtx->height << std::endl;
                     fwrite(buffer, bsize, 1, fp);
                     fclose(fp);
                 }
             }
             ++count;
        }
        // パケット・メモリ解放
        av_free_packet(&packet);
    }
    std::cout << "Total frame: " << count << std::endl;

※この場合、400フレーム目を「RGB24」で「RAW」ファイルとして書き出します。

・メモリ解放

    sws_freeContext(pSWSCtx);
    av_free(buffer);
    av_free(pImage);
    av_free(pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

・コンパイルと実行

g++ -c -O2 -std=c++14 -DHAVE_STDINT_H -DWIN32 -DNDEBUG -isystem /mingw64/include -Wall -Werror -Wno-deprecated-declarations -o release/decode.o decode.cpp
g++ -L/mingw64/lib release/decode.o -lpthread -lavdevice -lavformat -lavfilter -lavcodec -lswresample -lswscale -lavutil -lz -o ffdecode_test.exe

ffdecode_test test.mp4

※適当な動画を食わせれば、400フレーム目をデコードして、RGB24ビットで、ファイル「rgb24.raw」を書き出します。

decode.cpp

R8C SDカードを使ったWAVファイルの再生

以前にSDカードをSPI接続で読み書きするテストを行ったが、パフォーマンスの評価
を行うのに、WAV形式の音楽を再生するのが丁度良いと思い実験してみた。

事前の懸念事項として:
・SDカードを読み書きするSPIポートは、完全なソフトウェアー仕様なので、あまり
速度が出ない。
※UARTと共有になっている同期式シリアル変換を使えば、速度は改善するものと思う
が、シリアル通信が使えなくなる為、今回は見送った。
・パラレル⇔シリアル変換では、C++ コンパイラの最適化度合いが非常に大きなファクタ
ーを占める。

以上の点を考慮して、コンパイルされたアセンブリコードを眺めながら、ソースコードに
修正を加えたりしながら、色々実験した。

8ビット、ステレオで、11.025KHz(44.1KHzの 1/4)くらいならギリギリ再生できる事が
判った。
これには、11.025KHz でサンプリングループを回す割り込みや、PWM で変換する為の処理
も含まれる。

再生させてみると、多少ノイズが乗る、PWM レジスターの書き換えは割り込みを使って行
っているものの、何かノイズが乗る要因があるものと思われるが、原因を究明できなかっ
たので、とりあえず、この状態で公開する。

タイマーCのPWMはB、Cチャネルを使っている。
それぞれ、ポートP12、P13から出力される。
これを、単純なRCフィルター(C:0.1uF、R:2200)に通している。
ライン出力のGNDは、VCCとGNDの中点(470オームで分圧)にしている。

IMG_0758

現在の実装では、「OHAYODEL.WAV」ファイルを再生するようになっている。
※再生するWAVファイルは、8ビット、ステレオ、11.025KHzにしておく必要がある。
※フォーマットが違う場合、エラーを出力する。
※再生開始時や終了時などにポップノイズが発生する場合があるので、ボリュームに注意する事。

SD_WAV_play

R8CタイマーJを使った周波数測定

前回からだいぶ間が空きましたが、パルス入力を使った、周波数測定を行ってみました。

タイマーJは、パルス出力と入力の機能があり、パルス幅測定、周期測定、パルスカウント
など沢山の機能があります。
今回は、周期測定機能を使って、周波数測定をしてみました。

サンプルなので、プログラムをあまり複雑にしない為、「単機能」で実装してありますが、
実用的な物にするには、カウンターがオーバーフローした場合にマスタークロックを切り
替えるとか、周波数が高い場合に、単位時間辺りのパルス数計測に切り替えるなどの工夫
を行う必要があると思います。

サンプルでは、シリアル接続したターミナルに、計測した結果を1秒毎に表示します。

ルネサス系では、タイマーJの構成で、良く出来ていると思うのは、割り込みを使わなく
ても、レジスターの状態が保持される為、簡単に厳密な計測が行えます。
このような、細かい点に配慮したハードウェアーの構成は、日本メーカーのマイコンな
らではと思います。

IMG_0757s

基本的な機能実装は、「common/trj_io.hpp」に集約させていますが、設定が複雑なので、
場合によっては、「trj_io」クラスの機能だけでは不足している場合もあると思います。

又、割り込みを使う場合は、パルス出力と入力で、登録する割り込み関数が違うので、
注意が必要です。

//-----------------------------------------------------------------//
/*!
    @brief  パルス計測の開始(TRJIO 端子から、パルスを入力)@n
            ※VCOUT1 端子から入力する場合は、TRJIOSEL を設定する。
    @param[in]  measur    パルス計測のモード
    @param[in]  s         クロック選択(measur::countの場合は無効)
    @param[in]  ir_lvl    割り込みレベル(0の場合割り込みを使用しない)
*/
//-----------------------------------------------------------------//
void pluse_inp(measurement measur, source s, uint8_t ir_lvl = 0) const;

「measurement」型として、

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  パルス計測モード
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class measurement : uint8_t {
    low_width,     ///< Low レベル幅測定
    high_width,    ///< High レベル幅測定
    count,         ///< パルス数測定
    freq,          ///< 周期測定
};

4つの測定モードがあります。

クロック選択では、

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  カウンターソース
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class source : uint8_t {
    f1 = 0,     ///< F_CLK
    f2 = 3,     ///< F_CLK / 2
    f8 = 1,     ///< F_CLK / 8
    fHOCO = 2   ///< fHOCO(高速オンチップオシレーター)
};

4つの中から選択が出来ます。

詳しい使い方は、「PLUSE_INP_test」を参照して下さい。

R8Cを使ったLCD表示、パルス出力

以前に作成した、パルス出力テスト、エンコーダー入力、LCD表示を組み合わせて
単独で動作するパルス出力を作成しました。

以前に作ったものを組み合わせるだけなので、簡単です。

LCD表示はビットマップ表示なので、適当なフォントを用意しました。
ビットマップ表示は、とても柔軟な表示が行えるのですが、自分で「絵」をデザイン
する必要があります。
そこで、以前にビットマップ表示に適した画像エディターを探しました、フリーで、
非常に使いやすいエディターがありましたので紹介しておきます。
※Windows版です、Mac版は、あまり良いものを探せませんでした。
EDGE
※ソフトは2009年が最終版ですが、機能は揃っており、不具合も無いようです。

「ドット絵エディタ」は256色まで対応ですが、モノクロの絵や、アイコンのよう
なグラフッックにはむしろ最適です。
グリッドを設定する機能もあり、任意サイズのグラフィックを描くのに便利です。

font32
上のように、1つのファイルにまとめて配置して、切り出して使います。

切り出して、ソースコードに埋め込むのに、bmc も作成してあります。
※このソフトは、かなり昔に実装して、少しづつ改変しています。
※コンパイルは大変なので、実行バイナリーを用意してあります。(build/bmc.exe)
※必要な DLL を用意してあります。
※コンパイルの方法は、「MSYS2 で俺俺フレームワーク、アプリをコンパイルする」を参照して下さい。
※Mac版は、brew を入れれば、Windowsと同じようにコンパイルできます。

bmc では、以下のように、切り出す位置とサイズを指定して、テキスト形式に変換
しています。
※bitmap/Makefile

    bmc -offset 0*20,0*32 -size 18,32 -header 8 -text -c_style nmb_0 font32.png font32.h
    bmc -offset 1*20,0*32 -size 18,32 -header 8 -text -c_style nmb_1 font32.png -append font32.h
    bmc -offset 2*20,0*32 -size 18,32 -header 8 -text -c_style nmb_2 font32.png -append font32.h
    bmc -offset 3*20,0*32 -size 18,32 -header 8 -text -c_style nmb_3 font32.png -append font32.h
    bmc -offset 4*20,0*32 -size 18,32 -header 8 -text -c_style nmb_4 font32.png -append font32.h
    bmc -offset 5*20,0*32 -size 18,32 -header 8 -text -c_style nmb_5 font32.png -append font32.h
    bmc -offset 6*20,0*32 -size 18,32 -header 8 -text -c_style nmb_6 font32.png -append font32.h
    bmc -offset 7*20,0*32 -size 18,32 -header 8 -text -c_style nmb_7 font32.png -append font32.h
    bmc -offset 8*20,0*32 -size 18,32 -header 8 -text -c_style nmb_8 font32.png -append font32.h
    bmc -offset 9*20,0*32 -size 18,32 -header 8 -text -c_style nmb_9 font32.png -append font32.h
    bmc -offset 3*20,1*32 -size 19,32 -header 8 -text -c_style txt_hz font32.png -append font32.h
    bmc -offset 2*20+9,1*32 -size 9,32 -header 8 -text -c_style txt_k font32.png -append font32.h


変換された形式は、ビット単位で、状態を表現したシンプルな構造で、common/monograph.hpp、
draw_mobj で描画する事が出来ます。

このソフトでは、色々なオプションを用意してあります。
・BDF 形式のフォントの読み出し。
・ディザリング機能
・GUI で確認する機能
など

BitMap Converter
Copyright (C) 2013/2015, Hiramatsu Kunihito
Version 0.75
usage:
    bmc.exe [options] in-file [out-file]
    -preview,-pre     preview image (OpenGL)
    -header size      output header
    -text             text base output
    -c_style symbol   C style table output
    -offset x,y       offset location
    -size x,y         clipping size
    -bdf              BDF file input
    -append           append file
    -inverse          inverse mono color
    -dither           ditherring
    -verbose          verbose

IMG_0756

さて、肝心な、パルス出力ですが、出力する周波数範囲は、かなり広いので、エンコーダーだけ
では可変範囲が広すぎます、そこで単位毎に自動でレンジを切り替えて、増減する量を動的に変
えています。
20Hz~99Hz 1単位
100Hz~999Hz 10単位
1000Hz~9999Hz 100単位
10000Hz~99999Hz 1000単位
100KHz~999KHz 10KHz単位
1000KHz~10000KHz 100KHz単位

PLUSE_OUT_LCD