UMEHOSHI ITA TOP PAGE

アセンブリ言語で作る「 EEPROM上で動作するLチカ プログラム」(パワーオンでスタートさせる
  (これを初期化するためには、SW2の取り付けが必要です。)

このページは、パワーONでないリセット操作で実行するEEPROM上で起動するプログラムの作り方です。
似ていますが、ROM上でプログラムをパワーONでないリセットで起動する方法ページはこちらでで、紹介していますの比較してください。

D1のLEDのチカチカ点滅を、パワーオンにだけで行う例を示します。

D1のLEDは、下記回路図の赤の接続線で示しています。赤マルのRB15に繋がっていますが、 これは制御チップで[PORTB REGISTER]のBit15をを意味しています(資料:DS60001168K TABLE 11-4: PORTB REGISTER MAP)。
このRB15の出力端子がLowで消え、HiにすればLEDに電流が流れて点灯します。

下記は、0x9D020008番地 に『D1のLEDで「 Lチカ 」無限ループするstart_main関数』を 配置することで実現するプログラムです。
これは「umehoshiEdit」ツールで、ビルド実行できます。
右リストは逆アセンブルしたコードで、これを参考に、後述のアセンブリリストを作っています。

#include <xc.h>	// test.c
#include "common.h"
// __attribute__((address( 0x80005000 ))) void start_main (void);
// ROM領領域置は、絶対アドレスを指定する必要があります。(書かないとRAM領域になります。)
__attribute__((address( 0x9D020008 ))) void start_main (void);
void start_main(){
	extern void wait(int n);
	__asm__ ("NOP");
	for(;;){
		PORTBSET = 0x8000;	// D1 LED 点灯(RB15を1):これは、0xbf886128番地への設定です.
		__asm__ ("NOP");	// チョット待つ 
		wait(0x003f0000);
		PORTBCLR = 0x8000;	// D1 LED 消灯(RB15を0):これは、0xbf886124番地への設定です.
		__asm__ ("NOP");	// チョット待つ
		wait(0x003f0000);
	}
}
// ROM領領域置は、可能な絶対アドレスを指定する必要があります。(書かないとRAM領域になります。)
__attribute__((address( 0x9D020060 ))) void wait(int n);
void wait(int n){// nカウント待つ
	while(n--) ;
}
/*
EEPROM領域におけるリセット(パワーオンを伴わない)で起動するプログラムは 0x9D020000番地からに決まっています。
対してパワーオンのリセットで実行させるは関数の位置は任意で、そのアドレスを0x9D03fff0に記憶することになっています。
上記では、このパワーオンのリセットの関数をstart_mainとして、0x9D020008番地に定義しています。
なお、EEPROMの領域(0x9D020000〜0x9D03FFFF)の領域に配置する関数は、
 全て「__attribute__((address( 絶対番地 ))) 〜関数宣言表現;」
 つまり、上記のように2つ関数でも、翻訳されたコード範囲が重ならないように、アドレスを指定する必要がある。
(後述の絶対アドレス指定は余裕のある大きな値にすると良い。適正値は生成されるa.lst.txtからヒント値得られる。)
(リンケージエディタの機能を手動で指定する必要があるが訳だが、mylinkerscript.ldの設定で一部を自動化できる)
*/

9d020008 <start_main>:
9d020008:	27bdffe8 	addiu	sp,sp,-24
9d02000c:	afbf0014 	sw	ra,20(sp)
9d020010:	afbe0010 	sw	s8,16(sp)
9d020014:	03a0f021 	move	s8,sp
9d020018:	00000000 	nop

9d02001c <.L2>:
9d02001c:	3c02bf88 	lui	v0,0xbf88
9d020020:	34038000 	li	v1,0x8000
9d020024:	ac436128 	sw	v1,24872(v0)
9d020028:	00000000 	nop
9d02002c:	0f408018 	jal	9d020060 <wait>
9d020030:	3c04003f 	lui	a0,0x3f
9d020034:	3c02bf88 	lui	v0,0xbf88
9d020038:	34038000 	li	v1,0x8000
9d02003c:	ac436124 	sw	v1,24868(v0)
9d020040:	00000000 	nop
9d020044:	0f408018 	jal	9d020060 <wait>
9d020048:	3c04003f 	lui	a0,0x3f
9d02004c:	0b408007 	j	9d02001c <.L2>
9d020050:	00000000 	nop

9d020060 <wait>:
9d020060:	27bdfff8 	addiu	sp,sp,-8
9d020064:	afbe0004 	sw	s8,4(sp)
9d020068:	03a0f021 	move	s8,sp
9d02006c:	afc40008 	sw	a0,8(s8)
9d020070:	00000000 	nop
9d020074:	8fc20008 	lw	v0,8(s8)
9d020078:	2443ffff 	addiu	v1,v0,-1
9d02007c:	afc30008 	sw	v1,8(s8)
9d020080:	1440fffc 	bnez	v0,9d020074 <wait+0x14>
9d020084:	00000000 	nop
9d020088:	03c0e821 	move	sp,s8
9d02008c:	8fbe0004 	lw	s8,4(sp)
9d020090:	27bd0008 	addiu	sp,sp,8
9d020094:	03e00008 	jr	ra
9d020098:	00000000 	nop

  以下に、上記の実際に動作した、アセンブリリストを示します。上のC言語の逆アセンブルリストをコードにしてみました。

(UMEHOSHI ITA基板で使っている制御チップのPIC32MX270F256B用(MIPS32系)アセンブリに必要な情報の概要です。)


  上記で編集したアセンブリソースを、左のボタンで「アッセンブル」できます。
番地から実行するUME専用Hexコマンドを、 最後に追加埋め込みする場合にチェックする==>

上記で生成した[UME専用Hexコマンド]のテキストを コピー(CTRL+A CTRL+C)してUMEHOSHI ITAへ転送して動かすことができます。
上記プログラムを [UMEHOSHI ITA]基板へを転送して実行させた後、
 【R009D0200080047】コマンド送信などで 0x9D020008 から実行を開始させれば「 Lチカ 」が始まります。
しかし、これだけで電源を供給しただけで実行を始めることができません。

上記コードは、 こちらで示したリセットスイッチでスタートさせるコード0x9D020000番地の関数)を、 0x9D020008番地に配置するように変更しています。 これにより、パワーオンを伴わないリセットで起動しない通常の関数になります。
(関数を0x9D020000番地に配置しなければ、パワーオンを伴わないリセットで起動しません。)

そして改めてこの0x9D020008番地に配置した関数を、 パワーオンにだけで起動できるように、0x9D03fff0番地の記憶に、0x9D020008番地を記憶します。
その仕組みの説明のため、  UMEHOSHI ITAの起動時の初期化の概要を下記に示します。(my_sys.cの内部)
if(  パワーオンリセットか? ){
      マクロ用の関数へのポインタなど各種変数の初期化や_HANDLES配列の初期化を行う。
      「ウメ・エディットプログラム」で使われるマクロ関数が登録される。そして、
        _HANDLES[_IDX_HANDLE_USER_SET_FUNC] = dummy_init_function;  の関数が登録される。
         (上記関数へのポインタは、_handle_user_set_func()のマクロの関数で実行できる)
        _HANDLES[_IDX_INIT_SUB_FUNC] = dummy_init_function;  の関数が登録される。
         (上記関数へのポインタは、_init_sub_func()のマクロの関数で実行できる)
        power_on_flag = 1;
}
_handle_user_set_func(); // 起動時のマクロ関数呼び出し(ここで、_HANDLESの要素の変更などを行う)

SYS_Initialize ( NULL );// ここでUSBや指定IOの初期化が行われる

if(power_on_flag == 1){	// ここがEEPROM領域のパワーONで実行される領域
        power_on_start();
} else {			// ここがEEPROM領域のパワーONを伴わないリセットで実行される領域
        void (* func)(void) = (void *) 0x9D020000;// EEPROM 領域
        if( *((uint32_t *)func) != 0xffffffff ){// EEPROMの記憶内容が0xffffffffでない?
            func();// EEPROMの0x9D020000番地の関数を呼び出す(リセットで自動的に起動)
        }	
}
_init_sub_func(); // 起動時のマクロ関数呼び出し(「ウメ・エディットプログラム」の起動関数)

この後で、『USB受信文字列処理用のループ』に進みます。

注目箇所は、power_on_start();で、これがパワーONの場合に実行されます。
そして、power_on_start();の内容は、次のようになっています。(参考:my_util.cのソース)
void power_on_start(){
        if ((PORTB & 0x80) == 0) return; // SW2が押されてれば、リターン

        // SW2が押されていない場合の起動
        unsigned *flag = (unsigned *) 0x9D03fff0;
        if( *flag == 0xffffffff) return;// EEPROM領域の0x9D03fff0番地内容が0xffffffffならリターン
        void (* func)(void) = (void *) *flag;
        func();// 0x9D03fff0番地の内容が指し示す関数を実行する。
}
上記コードで、0x9D03fff0番地が0xffffffffでなければ、0x9D03fff0番地の内容が指し示す関数が パワーオンの場合だけで実行することになります。
なおプログラム消去や変更する場合、「UME専用Hexコマンド」のUSB通信ができるように作られていないと、EEPROM消去や変更のプログラムが実行できません。 そのため、パワーオン時にSW2が押される時は この関数が起動をしないように先頭で制御しています。
(つまり、SW2のスイッチを押しながらパワーオンすれば、「UME専用Hexコマンド」のUSB通信ができて消去のプログラムなどの転送・実行が可能です。)

『 0x9D020008番地の関数をパワーオン時の場合だけで実行させる設定』

パワーONで、0x9D020008番地の関数を実行するように、0x9D03fff0番地0x9D020008を設定します。
(この実行後、電源供給で、0x9D020008番地の関数が実行するようになります)

// 0x9D03fff0番地を0x9D020008にして、パワーオン実行を可能にする。

#include <xc.h>	// power_exc_on.c
#include "common.h"

__attribute__((address( 0x80005000 ))) void start_main (void);
void start_main(){
	// *((unsigned *)0x9D03fff0) = 0x9D020008;に相当する処理
	_nvm_write_word(0x9D03fff0,(unsigned )0x9D020008);
}

/*

この実行で、0x9D03fff0番地の内容を0x9D020008に設定する。

それにより、0x9D03fff0番地内容が指し示す関数が、パワーオンの場合だけ起動する。

*/

80005000 <start_main>:
80005000:	27bdffe8 	addiu	sp,sp,-24
80005004:	afbf0014 	sw	ra,20(sp)
80005008:	afbe0010 	sw	s8,16(sp)
8000500c:	03a0f021 	move	s8,sp
80005010:	3c02a000 	lui	v0,0xa000
80005014:	344240f4 	ori	v0,v0,0x40f4
80005018:	8c420000 	lw	v0,0(v0)
8000501c:	3c039d03 	lui	v1,0x9d03
80005020:	3464fff0 	ori	a0,v1,0xfff0
80005024:	3c039d02 	lui	v1,0x9d02
80005028:	34650008 	ori	a1,v1,0x8
8000502c:	0040f809 	jalr	v0
80005030:	00000000 	nop
80005034:	03c0e821 	move	sp,s8
80005038:	8fbf0014 	lw	ra,20(sp)
8000503c:	8fbe0010 	lw	s8,16(sp)
80005040:	27bd0018 	addiu	sp,sp,24
80005044:	03e00008 	jr	ra
80005048:	00000000 	nop
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
#include <xc.h>	// 0x9D03fff0番地を0にして、パワーオン実行を可能にする。
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	start_main,address(0x80005000),code
	.ent	start_main
	addiu	sp,sp,-8
	sw	ra,4(sp)
	lui	v0,0xa000
	ori	v0,v0,0x40f4
	lw	v0,0(v0)	# v0 に_nvm_write_word関数のポインタ0xa00040f
	lui	v1,0x9d03	
	ori	a0,v1,0xfff0	# 第1引数 a0をx9D03fff0
	lui	v1,0x9D02	
	ori	a1,v1,0x0008	# 第2引数 a1をx9D020008
	jalr	v0	# _nvm_write_word関数呼び出し
	nop
	lw	ra,4(sp)
	addiu	sp,sp,8
	jr	ra
	nop
左のコードで生成される[UME専用Hexコマンド]は、 x9D03fff0番地のワードをx9D020008に設定するコードで、アセンブルして使います。
実行するためには、「アッセンブル」のボタン操作前で、
[0x80005000]番地から起動するコード埋め込みのチェックをして、生成してください。
そして「command.txt」の名前でファイル化して、pythonで実行させます。
以上を起動した後は、パワーオンの場合だけで、x9D020008に配置された関数が実行されるようになります。
しかし、パワーオンを伴わないリセット操作(SW1を押して放す)では、実行されません。
(パワーオンを伴わないリセット操作で関数を実行させる場合は、x9D020000番地に関数を配置します)

パワーオン操作でも、パワーオンを伴わないリセット操作でも実行させる関数は、_HANDLES[_IDX_INIT_SUB_FUNC] に登録します。
それにより、起動時のマクロ関数呼び出しの_init_sub_func(); 実行で可能となります。

前述のコードの消去

パワーオンのプログラムを消去する場合、プログラム本体のイメージが存在するEEPROMの消去以外に、0x9D03fff0に 0xffffffffi以外の値を設定します。
ですが単にパワーオンした場合は、0x9D020000番地のプログラムが実行してしまうので、 それが[UME専用Hexコマンド]通信が対応していなければ、消去自体ができません。

それで、SW2を押しながら電源供給することで、 特別なパワーオン時の初期化を行って0x9D020000番地のプログラムが起動せずに、
[UME専用Hexコマンド]の通信が可能な起動を行わせます。

この特別な起動行ってから、下記の消去プログラムを実行させるます。
下記プログラムは、 プログラム本体のページ(アドレス0x9D020000)と、 パワーオン起動フラグがあるページ(アドレス0x9D03FC00)のEEPROM領域を消去するプログラムです。

#include <xc.h>	// erase.c
#include "common.h"


__attribute__((address( 0x80005000 ))) void start_main (void);
void start_main(){
	_nvm_erase_page(0x9D020000);//EEPROMの起動ページ
	_nvm_erase_page(0x9D03FC00);//最終ページ消去
}


/*
EEPROMの0x9D020000番地から1024byteの領域を0xFFに消去し、

EEPROMの0x9D03FC00番地から1024byteの領域を0xFFに消去する

nvm_erase_pageの引数のアドレスは
0x9D020000,0x9D020400,0x9D020800,0x9D020c00,0x9D021000,・・
	・・,9D03F800,9D03FC00 の何れかを指定する。

*/


80005000 <start_main>:
80005000:	27bdffe8 	addiu	sp,sp,-24
80005004:	afbf0014 	sw	ra,20(sp)
80005008:	afbe0010 	sw	s8,16(sp)
8000500c:	03a0f021 	move	s8,sp
80005010:	3c02a000 	lui	v0,0xa000
80005014:	344240f0 	ori	v0,v0,0x40f0
80005018:	8c420000 	lw	v0,0(v0)
8000501c:	3c049d02 	lui	a0,0x9d02
80005020:	0040f809 	jalr	v0
80005024:	00000000 	nop
80005028:	3c02a000 	lui	v0,0xa000
8000502c:	344240f0 	ori	v0,v0,0x40f0
80005030:	8c420000 	lw	v0,0(v0)
80005034:	3c039d03 	lui	v1,0x9d03
80005038:	3464fc00 	ori	a0,v1,0xfc00
8000503c:	0040f809 	jalr	v0
80005040:	00000000 	nop
80005044:	03c0e821 	move	sp,s8
80005048:	8fbf0014 	lw	ra,20(sp)
8000504c:	8fbe0010 	lw	s8,16(sp)
80005050:	27bd0018 	addiu	sp,sp,24
80005054:	03e00008 	jr	ra
80005058:	00000000 	nop
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <xc.h>// 0x9d020000,0x9D03FC00のページ消去(0xffに設定)
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	start_main,address(0x80005000),code
	.ent	start_main
	addiu	sp,sp,-8
	sw	ra,4(sp) # 戻り番地記憶
	lui	v0,0xa000
	ori	v0,v0,0x40f0 # hex(0xA0004000+60*4)==>0xa00040f0より算出
	lw	v0,0(v0) # v0 = _nvm_erase_pageのアドレス0xa00040f0
	lui	a0,0x9d02 # 消去対象のアドレスを引数として与える
	jalr	v0	# _nvm_erase_page(a0)
	nop
	lui	v0,0xa000
	ori	v0,v0,0x40f0 # hex(0xA0004000+60*4)==>0xa00040f0より算出
	lw	v0,0(v0) # v0 = _nvm_erase_pageのアドレス0xa00040f0
	lui	a0,0x9d03 # 消去対象のアドレスを引数として与える
	ori	a0,a0,0xFC00 # a0 = 0x9D00000 | 0x3FC00
	jalr	v0	# _nvm_erase_page(a0)
	nop
	lw	ra,4(sp)
	addiu	sp,sp,8
	jr	ra
	nop
左のコードで生成される[UME専用Hexコマンド]は、
0x9d020000番地と0x9D03FC00番地のページを
削除するコードです。

実行するためには、「アッセンブル」のボタン操作前で、
[0x80005000]番地から起動するコード埋め込みのチェックをして、
生成してください。
そして「command.txt」の名前でファイル化して、
 pythonで実行させます。

なお、「command.txt」の最後に、「G109D0200000059」の行を追加することで、
実行前の0x9d020000番地の16byteの内容を表示することができます。
(起動アドレスの消去前のメモリ内容の表示を確認できます。)