RZ/A1で楽しい組込み開発(4)2017/05/22
初期化ルーチンを組み込もう
前回はシリアルフラッシュメモリにプログラムを書き込み、パワーオンでLEDをチカチカさせることに成功しましたが、組込みプログラムとしてはまだまだ「お遊び」の域を出ていません。しかし、今回からは、本格的な(?)CPUの初期化ルーチンを使用して、キャッシュや割り込み処理なども駆使し、レベルアップを図っていきます。
初期化プログラムの準備
現時点での初期化ルーチンはC言語のスタートアップルーチンとしてはいいのですが、ちゃんとした組込みプログラムとするためには、もう少し初期化が必要です。割り込みやモジュールの初期化、キャッシュなどです。そして、たぶんですがCPUが最高クロックで動いていないはずです。
今回は、CPUを最高速で動作させることと、タイマ割り込みを使えるようにすることを目標に初期化を行います。
では、さっそくルネサスのページより初期化プログラムをダウンロードしましょう。
「ん?作るんじゃないの?」と思われる方もおられるでしょうが、今どきの多機能なCPUの初期化プログラムを一から作るのはとても難しく、バグの原因となることは間違いありません。
あるものは使えと言いますが、逆に初期化プログラムが用意されていないCPUは使うな、ということさえ言えると思います。
前置きが長くなってしまいましたが、ルネサスのホームページからRZ/A1Hのサンプルコードをダウンロードします。現在ターゲットにしているのはRZ/A1Lですが、RZ/A1L用のサンプルコードがないので、RZ/A1Hのサンプルコードで代用します。この2つCPUの違いは微妙ですので、少し気をつければ動作させることは可能です。
初期化プログラムのダウンロード
検索エンジンから「RZA1H」を検索するとルネサスのRZ/A1Hのページがヒットしますので、そこから[サンプルコード]をクリックし、ダウンロードへ進みます。ダウンロードするサンプルコードは以下の「RZ/A1H グループ 周辺機能の設定例および使用例」でシリアルコミュニケーションインターフェースと書かれたものです。(2017年4月28日現在)
RZ/A1H グループ 周辺機能の設定例および使用例 (for GENMAI)
シリアルコミュニケーションインタフェース
Oct.16.15 Rev.1.00
シリアルコミュニケーションインターフェースとは書かれていますが、必要な初期化プログラムがパッケージされています。
では、さっそくダウンロードして展開してみましょう。展開すると「an_r01an2176jj0105_rza1h_other」というフォルダが作成されます。今回、目的とするプログラムはこの中にある「workspace\RZ_A1H_other_sample\kpitgcc」です。さらにこの中から必要なソースファイルをピックアップして、e2 studioのプロジェクトに登録していきます。
ファイルを眺めてみよう
ざっと、ファイルを眺めてみましょう。まずkpitgcc\srcフォルダですが、以下のようになっています。
src │ sections.c │ syscalls.c │ ├─asm │ initsect.s │ irq.s │ irqfiq_handler.s │ l1_cache_init.s │ l1_cache_operation.s │ reset_handler.s │ ttb_init.s │ vbar_init.s │ vector_mirrortable.s │ vector_table.s │ vfp_init_asm.s │ └─inc irq.h stdbool.h stdint.h
asmフォルダにはアセンブラソースが並んでいます。ファイル名から推測すると、割り込み関連、1次キャッシュ、リセットハンドラ、ベクタ関連などなど、初期化に必要そうなファイルがそろっています。
次は、kpitgcc\commonを見てみましょう。
common ├─inc │ └─src │ main.c │ ├─board_settings │ led.c │ peripheral_init_basic.c │ port_init.c │ siochar.c │ stb_init.c │ ├─common_settings │ cache.c │ l2_cache_init.c │ resetprg.c │ rza_io_regrw.c │ siorw.c │ ├─drivers │ :(省略) └─samples :(省略)
そもそも、これらのプログラムは、ルネサスのGENMAIというRZ/A1H搭載ボードで動作するものです。よって、board_settings(ボードの設定)もGENMAI用になっていますが、微調整すればRZ/A1LのCEVボードでも動くはずです。
common_settingsやdriversフォルダはCEVボードでもそのまま使えそうです。samplesについてはボードの依存が大きいので今回は使いません。(まあ、LEDを光らせるだけですので・・・)
もう一つ忘れてはならないのが、リンカスクリプトファイルです。これはlinker_script_fileフォルダにある「RZ_A1H_other_kpitgcc.ld」というファイルです。前回まではe2 studioのリンカオプション設定画面で、セクションを変更してきましたが、今回からはリンカスクリプトファイルを使います。
リンカスクリプトファイルとは、メモリマップやセクションの配置などが細かく指示されたテキストファイルです。
リンカスクリプトファイルの設定は奥深いです。正直なところGUIで設定したいのですが、すでにできあがっているリンカスクリプトファイルを地道にGUIに移行させるのも逆に手間ですし、内容をちゃんと理解しておいた方が後々役立ちそうです。ということで、リンカスクリプトファイルを採用することにします。
ソースファイルの削除と追加
では、e2 studioを起動して、プロジェクトに登録されているソースファイルを変更してみましょう。時短(?)のため、ソースファイルの「いる」、「いらない」をあらかじめ調査しましたので、そのとおりに進めて行きます。また、新たなソースファイルを登録することで、現在登録しているソースファイルと機能がかぶるものがありますので、それらは潔く削除します。
まず最初に、機能がかぶっているソースファイルを削除します。削除対象のファイルは次のとおりです。
hardware_setup.c interrupt_handlers.c interrupt_handlers.h reset_program.asm vector_table.c typedefine.h
お気づきかと思いますが、ほとんどのソースファイルです。プロジェクトエクスプローラーでソースファイルを選択して、DELキーで削除します。削除後のファイルは、これだけになります。
なお、プロジェクトエクスプローラーでファイルを削除すると、ファイルの本体も削除されてしまうため、最初は驚かされます。プロジェクトエクスプローラーはWindowsのフォルダと同じなんですね。
ついでですが、プロジェクトエクスプローラーのフォルダ上で右クリックして、[ローカル履歴から復元]を選択すると、今まで削除したファイルを元に戻すことができます。
それでは引き続き、新しいソースファイルを追加します。追加するソースファイルは次のとおりです。
kpitgcc\src\asm\*.s kpitgcc\src\sections.c kpitgcc\src\inc\*.h kpitgcc\common\src\drivers\intc\inc\*.h kpitgcc\common\src\drivers\intc\intc_driver\*.c kpitgcc\common\src\drivers\intc\userdef\intc_userdef.c kpitgcc\common\src\board_settings\peripheral_init_basic.c kpitgcc\common\src\board_settings\stb_init.c kpitgcc\common\src\common_settings\resetprg.c kpitgcc\common\src\common_settings\l2_cache_init.c kpitgcc\common\src\common_settings\cache.c kpitgcc\common\inc\r_typedefs.h kpitgcc\common\inc\dev_drv.h kpitgcc\common\inc\peripheral_init_basic.h kpitgcc\common\inc\resetprg.h kpitgcc\common\inc\cache.h kpitgcc\common\inc\stb_init.h
これらのソースファイルには、初期化用のアセンブラルーチン、ベクタの設定、キャッシュの設定、割り込みコントローラの初期化などが含まれています。
ちょっとめんどくさい量ですが、チマチマと追加することにしましょう。Windowsのフォルダを開いて、ソースファイルを選択し、プロジェクトエクスプローラーのsrcフォルダにドラッグ&ドロップします。
すると、以下のような確認ダイアログが表示されますので、「ファイルをコピー」を選択してOKボタンを押します。
ソースファイルを追加した結果、最終的に以下のようになりました。もう少しフォルダ分けすれば見やすいのでしょうけど・・・。
実際にはフォルダ間で、ファイルをコピーすればいいだけですので、以下のようなバッチファイルを作ってもいいかもしれません。
set COPYDEST="c:\e2studio\workspace" copy kpitgcc\src\asm\*.s %COPYDEST% copy kpitgcc\src\sections.c %COPYDEST% copy kpitgcc\src\inc\*.h %COPYDEST% copy kpitgcc\common\src\drivers\intc\inc\*.h %COPYDEST% copy kpitgcc\common\src\drivers\intc\intc_driver\*.c %COPYDEST% copy kpitgcc\common\src\drivers\intc\userdef\intc_userdef.c %COPYDEST% copy kpitgcc\common\src\board_settings\peripheral_init_basic.c %COPYDEST% copy kpitgcc\common\src\board_settings\stb_init.c %COPYDEST% copy kpitgcc\common\src\common_settings\resetprg.c %COPYDEST% copy kpitgcc\common\src\common_settings\l2_cache_init.c %COPYDEST% copy kpitgcc\common\src\common_settings\cache.c %COPYDEST% copy kpitgcc\common\inc\r_typedefs.h %COPYDEST% copy kpitgcc\common\inc\dev_drv.h %COPYDEST% copy kpitgcc\common\inc\peripheral_init_basic.h %COPYDEST% copy kpitgcc\common\inc\resetprg.h %COPYDEST% copy kpitgcc\common\inc\cache.h %COPYDEST% copy kpitgcc\common\inc\stb_init.h %COPYDEST%
現段階では、まだまだコンパイルは通りません。あと、やらなければならないのは、コンパイルオプションでインクルードパスを設定することと、リンカスクリプトファイルをコピーして、セクションの設定を行うことです。
インクルードパスの設定
srcフォルダ内のヘッダファイルをインクルードするため、srcフォルダにはインクルードパスを設定する必要があります。設定の方法は、プロジェクトエクスプローラーのルート「test [HardwareDebug]」を右クリックし、[Renesas Tool Settings]を選択します。
表示されたウィンドウで[Compiler]の[ソース]を選択し、[インクルード・ファイル・ディレクトリー]でインクルードパスの設定を追加します。
右上の追加ボタンを押すと、下の画面のように「ディレクトリー・パスの追加」が表示されますので、srcフォルダを指定します。
ここで、絶対パスを指定してしまうと、違うフォルダでコンパイルしたときに「パスが見つからない」と怒られますので、e2 studioの内部に保持している環境変数を使って、フォルダ依存しないようにします。環境変数はいちいち調べなくても、e2 studioが設定してくれます。[ワークスペース...]ボタンを押して、フォルダを指定します。
すると、以下のように環境変数付きのパスが設定されますので、OKを押せばインクルードパスの設定は完了です。
リンカスクリプトファイルの登録
次はリンカスクリプトファイルをビルド環境に登録します。まず、サンプルコードのフォルダからリンカスクリプトファイルをコピーします。linker_script_fileフォルダにある「RZ_A1H_other_kpitgcc.ld」をプロジェクトエクスプローラーにドラッグ&ドロップします。名前にちょっと違和感があるので、「test.ld」にリネームしておきます。
プロジェクトエクスプローラーに登録しただけでは、リンカスクリプトファイルは有効になりませんので、設定を行います。再び、[Renesas Tool Settings]から設定画面を開き、[Linker]の[その他]を選択します。リンカスクリプトファイルの設定だから[セクション]にあると思ったのですが、実際は[その他]にあったのでハマりました。
[コマンドファイルの上書き]の設定で[External Linker script(-T)]を選択し、その下にリンカスクリプトファイル名を入力すればOKです。参照ボタンを押してファイルを指定すると、絶対パスで登録されてしまいますので、インクルードパスの設定で使った環境変数を使って、
"${workspace_loc:/${ProjName}/src}/test.ld"
のように指定します。設定画面は以下のようになります。
リンカスクリプトファイルの変更
では、メモリマップに合わせてこのファイルを変更していきます。プロジェクトエクスプローラーからtest.ldをダブルクリックすると、エディタに内容が表示されます。
このファイルの変更点ですが、ROMのアドレスが0番地となっていますので、シリアルフラッシュメモリの0x18000000番地に変更します。また、この設定はRZ/A1H用ですので、内蔵RAMサイズが10MBになっています。これをRZ/A1L用に3MBにします。SDRAMも今回は使いませんので設定を削除します。
まず、MEMORYセクションを変更します。変更前の状態は以下のようになっています。
MEMORY { ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128M SYSTEM_RAM (rwx): ORIGIN = 0x20020000, LENGTH = 0x04000 STACK (rw) : ORIGIN = 0x20024000, LENGTH = 0x14000 CACHED_RAM (rw) : ORIGIN = 0x20039000, LENGTH = 0x004C7000 SDRAM (rw) : ORIGIN = 0x08000000, LENGTH = 128M UNCACHED_RAM (rw) : ORIGIN = 0x60500000, LENGTH = 0x00500000 }
変更した結果は下のようになります。赤色で示しているのが変更点です。SDRAMの行は削除しています。
MEMORY { ROM (rx) : ORIGIN = 0x18000000, LENGTH = 128M SYSTEM_RAM (rwx): ORIGIN = 0x20020000, LENGTH = 0x04000 STACK (rw) : ORIGIN = 0x20024000, LENGTH = 0x14000 CACHED_RAM (rw) : ORIGIN = 0x20039000, LENGTH = 0x100000 UNCACHED_RAM (rw) : ORIGIN = 0x60139000, LENGTH = 0x100000 }
ここで面白いのは、CACHED_RAMとUNCACHED_RAMの設定です。この2つは実体が同じ内蔵RAMですが、0x20000000番地の内蔵RAMのイメージが0x60000000番地に現れています。違うアドレス空間になっていることで0x20000000番地には2次キャッシュを割り付けて、0x60000000番地には割り付けないようにしているんですね。(たぶん・・・)
あとは、以下のSECTIONSのSDRAMのセクションが不要ですので削除します。
.SDRAM (NOLOAD) : { * (BSS_DMAC_SAMPLE_SDRAM) /* DMAC sample transfer destination work area */ } > SDRAM
他にも不要な定義がいろいろありますが、とりあえず置いとくことにします。
ひたすらエラーを取り除く
設定は以上で完了しました。あとはエラーとなるコードをひたすら修正していく作業になるのですが、やっぱり時短(?)のために結論を用意しています。以下のとおりにソースファイルを変更していきます。
- iodefine.hの「#include "typedefine.h"」を以下のヘッダファイル名に変更
#include "r_typedefs.h" - peripheral_init_basic.cの以下の行を削除
#include "devdrv_bsc.h"
#include "rza_io_regrw.h" - peripheral_init_basic.cのPeripheral_BasicInit関数の以下2行を削除
CS0_PORTInit();
R_BSC_Init((uint8_t)(BSC_AREA_CS0 | BSC_AREA_CS1)); - peripheral_init_basic.cのCS0_PORTInit関数本体を削除
- resetprg.cの以下の行を削除
#include "devdrv_bsc.h" /* Common Driver Header */
#include "sio_char.h"
#include "port_init.h" - resetprg.cのSystemInit関数の以下4行を削除
PORT_Init();
R_BSC_Init((uint8_t)BSC_AREA_CS3);
R_BSC_Init((uint8_t)BSC_AREA_CS2);
IoInitScif2();
SDRAM、SCIF、PORTなど、今回使わない周辺I/Oの初期化を削除しています。削除と書きましたが、コメントアウトでもかまいません。
ここまでの変更で、コンパイルエラーがなくなりますのでやってみましょう。
無事コンパイルが通りました。ただし、初期化ルーチンを呼び出していないので、これではまだCPUの初期化は行われません。初期化関数はSystemInitです。この関数をmainの先頭で以下のように呼び出します。
extern void SystemInit(void); // 追加 int main(void) { SystemInit(); // 追加 GPIO.PMC7 &= ~0x0300; GPIO.PM7 &= ~0x0300; :(以下省略)
ちなみにSystemInitという関数が、何をやっているのか確認しておきましょう。この関数から呼び出されている関数は以下のとおりです。
STB_Init(); R_INTC_Init(); L2CacheInit(); L1CacheInit(); VbarInit(); __enable_irq(); __enable_fiq();
上から順番に、周辺モジュールへのクロック供給、割り込みコントローラの初期化、2次キャッシュ、1次キャッシュの初期化、ベクタアドレスの設定、割り込み許可、高速割り込みも許可、となっています。これでキャッシュや割り込みが有効になるようです。
では再度コンパイルして、エラーが出なければ今回の変更目標はクリアです。コンパイル結果をICEでダウンロードして試してみましょう。
動作確認とコードの検証
前回と同様、シリアルフラッシュメモリに書き込んで実行します。PALMiCE3を使う方法については前回の記事「パワーオン起動に挑戦」の「シリアルフラッシュメモリへの書き込み」を参考にしてください。
実際に実行すると、LEDはこんな感じになります。
ご覧のように2つのLEDが点灯状態になって、チカチカしません。これはCPUのクロックが速くなり、かつキャッシュがしっかり効いていて、点滅周期が速くなりすぎたからだと考えられます。
「いやいや、本当にそうなの?何の検証もなくそういうことを言われても。」と、つっこまれそうですので、検証してみましょう。
まずはCPUのクロックからです。CPUクロックはreset_handlerからPeripheral_BasicInit関数を経て、CPG_Init関数で初期化されます。CPG_Init関数の本体部分を抜粋すると、以下のように初期化が行われています。
L2C.REG15_POWER_CTRL = 0x00000001uL; dummy_buf_32b = L2C.REG15_POWER_CTRL; CPG.FRQCR = 0x1035u; CPG.FRQCR2 = 0x0001u; CPG.SYSCR3 = 0x0Fu; dummy_buf_8b = CPG.SYSCR3;
クロック周波数を決めているのは3行目の「CPG.FRQCR = 0x1035u;」です。これは、周波数制御レジスタFRQCRの設定を行っています。RZ/A1Lのマニュアルによると、このレジスタのIFCの2ビットでクロック周波数の分周率を決めています。
15
14
13 - 12
11
10
9 - 8
7
6
5
4
3
2
1
0
設定値が0x1035ですから8~9ビット目のIFCは00となり、PLL回路からのクロックをx1/1倍(分周なし)でCPUクロックとしていることが分かります。
CEVボードはUSB_X1端子から48MHzのクロックを入力しており、これが分周器1で1/4に分周され、さらにPLL回路で32逓倍されたものがCPUクロックになります。つまり、以下のようになります。
48 ÷ 4 × 32 = 384MHz
おおっ、400MHzより微妙に足りないスペックだったんですね。ちなみにXTAL端子に13.33MHzを入れると400MHzとなります。
次の行の「CPG.FRQCR2 = 0x0001u;」ですが、実はRZ/A1LにFRQCR2レジスタはありません。これはRZ/A1Hのレジスタですので、この行は削除しておく必要があります。(あっても影響はないみたいですが)
次は1次キャッシュの検証を行います。キャッシュの設定は、ソースファイルresetprg.cのSystemInit関数で行われています。CP15のキャッシュビットを確認するために、SystemInit関数を抜けたところで止めてみます。
ご覧のようにCP15の「C1.I」と「C1.C」が1になっています(赤枠のところ)。このビットが1ならばコードキャッシュとデータキャッシュが有効になっていることを表しています。
2次キャッシュについてはttb_init.sで設定しているのですが、1次キャッシュほど簡単ではありません。MMUの設定と密接に関係していて、とっても面倒なアドレス変換テーブルを解析する必要があります。
ということで、2次キャッシュの検証はまたの機会とさせていただきまして、次回は、いよいよタイマ割り込みを使ったLチカを実現します。m(_ _)m