Vol.901 23.Aug.2024

コードレス掃除機 GameBar PICミニBB(11)_EEPROMライター

C コードレス掃除機  〜マキタCL181FDW

by fjk

 我が家ではコードレス掃除機としてダイソンなどを使っているが、女房の不満は「稼働時間が短い」(カタログでは40分とあるが、多くのコードレス掃除機を強で使うと10分ももたない)と、結局コード付掃除機を使うことが多い。
 稼働時間がもっと長い機種がないか,ネットで探していると、マキタのCL181FDZW(37/10W)が38%引きの8,145円で売られていた(稼働時間:強で20分/BL1830B使用時)。同機は発売時期が2016年と古いが、安価でパワーがあると人気の機種で、さらにサイクロンアタッチメントを使えば、吸引力が落ちることなく長時間の使用ができそうである。そして、我が家には同機に使えるバッテリー(BL1830B/BL1860B)と充電器が既にある。より強力で稼働時間もほぼ同じCL281FD(60/42/15W)も気になったが、価格(\18,000)が倍以上違うので、CL181FDZWを購入した。

本体の他に、追加したパーツは、(金額は全て税込)
@サイクロンアタッチメント(A-68856、\2,671)・・サイクロンの効率は良いのだが・・
AショートサイクロンSw(A-72475、\2,791)・・・A-68856はチョット大きいので・・
BフロアジュータンノズルS(A-59950、\1,730)・・ジュータン用
C短いストレートパイプ320(459246-9、\351)・・・サイクロン取付時に長さを短くするため
DLANMUフィルターカバー(\849/2個)・・・・サイクロン使用時、ゴミ分離効率向上
E高機能フィルターEX(A-68971、\743)・・・・・フィルター洗濯時などの予備

マキタCL181FDW(+A-72475+A-59950)


G GameBar  〜windows10のキャプチャーツール

by fjk

 GameBarを使っていますか?
 正式にはXbox_Game_Barと呼ばれXboxゲーム用に開発されたものですが、windows10以降に組み込まれ、ゲームやアプリの画面録画やキャプチャーが可能です。
 使い方は、ここ(win11)などが詳しい。
 実際にユーチューブの録画に使ってみると、確かに録画はできるのだが、何故か音声が録音されていない。実はデフォルトのままではゲーム音声のみの録音になっており、以下の設定をすることで、他のアプリの音声も録音できるようなる。

  1. GameBarのメニューバーで、「設定」を選ぶ
  2. 設定画面の左画面メニューで、「ウィジット」を選ぶ
  3. 録音するオーディオで、「全て」のボタンをオン
  4. この状態で、録画ボタンを押すと、音声も記録できる。

※ただし、GameBarではブルーレイ画面はキャプチャーできません。


録音設定


P PICミニBBシリーズ(11)  〜ExcelVBAでPic外付けEEPRomライター

by fjk

 abc889でPICの外付けi2c-EEPROMの読み書き、abc900でExcel-VBAでPICとのシリアル通信、を行ったので、Excel-VBAでPICの外付けi2c-EEPROMの読み書きを行ってみた。
 Excel_VBAはabc900をベースに、複数行データの受信と(各セル)表示ができるように変更した。受信エラーに対応できるようTimeOutも設定した。

  【Excel_VBAプログラム】testEasyComm31.xlsm/bas(zip) (赤字部修正済み、9/26)


VBA開発画面例
(余分なコメントもあるけど・・)
Option Explicit

Sub EasyCommTest()
    Dim cmd As String
    Dim row As Integer
    Dim rcv As String
    Dim S_Time   As Date
'@初期設定
    ec.COMn = Range("B2")
    ec.Setting = "115200,n,8,2"
    ec.AsciiLineTimeOut = 1000
'Aデータの送信
    cmd = Range("B5")
    ec.AsciiLine = cmd
'Bデータの受信
    row = 6
    Range("B6:B18").Value = ""
    S_Time = Now
    Do
        If Now > S_Time + TimeSerial(0, 0, 1) Then Exit Do
        DoEvents
        If ec.InBuffer > 0 Then
            DoEvents
            rcv = ec.AsciiLine
            Cells(row, 2) = rcv
            row = row + 1
        End If
    Loop
    ec.InBufferClear 	'受信バッファをクリア
'Cポートを閉じる
    ec.COMn = -1 	'終了処理
End Sub

 シリアル通信によるROMアクセスコマンドは、基本的にはabc899と同じだが、少し強化した。


"R0,5"コマンド実行例

"U"コマンド実行例

【PICプログラム】(PICハードはabc899と同じで、LCDをバックライト付に)
 abc899をベースに、I2Cメモリ関係を "i2c_EEPROM.c/h" にまとめ、ライブラリとして使うことにした。
 EUSARTおよびi2cLCD_ST7032iについてはabc900を参照。
 テラタームでは "ECHO = ON" としたが、Excelターミナルでは "ECHO = OFF" とした。それでも、Excelでは余計な改行が発生したので、"#define VBA" を定義し、改行が発生しないようにした(Excelターミナルのデリミタを "CrLf" に設定すればOKと思っていたが、Excelで正しく受信できなかったので)。
 また、コマンドの強化として、「S」コマンドの追加と「P」コマンドを以下のように強化した。

P
64バイトのデータバッファーの表示 (Uコマンドはデータを読み込んで表示)
PW[aaaa]
[aaaa]番地を含む64バイトにバッファーの内容を書き込み
PR[aaaa]
[aaaa]番地を含む64バイトをバッファーに読み込み、表示
 
S[aa][.[d0,・・[,]]]
64バイトのデータバッファーに書き込む以外はWコマンドと同じ
(連続書込モードも有効、aaは下位6ビットのみ有効)

※一応、ExcelターミナルでROMの読み書きができたが、時々Excelが異常終了する(以下の処理により解決済み、9/26)。

<原因>
読込Doループ内で、指定時間が経過したならDoループを脱出する設定としたが、脱出できていなかった。
<対策>
初期設定で"ec.AsciiLineTimeOut=1000"を追加、DOループ内の脱出経過時間を1秒に。
無信号から合計2秒後にDoループ脱出。(TimeOut設定値である程度変更可)
<考察>
ec内でタイムアウト処理が混乱していたためか(ExitDoが実行されない)?

【abc901-18325.cファイル】 abc901-18325.c(zip)

/*****************************(abc901-18325.c)***
 *     I2Cメモリ読み書きライブラリーテスト
 *************************************************/

#include "mcc_generated_files/mcc.h"
#include "myProject.h"
#include "i2cEEPROM.h"

char RBuf[BFSIZE];                	// シリアル受信バッファー
uint8_t SFlg;                     	// シリアル受信フラグ

//---- ターミナルにプロンプト文字表示
void prtPrompt(uint8_t m){
    if(m)  printf("# ");           	// 連続書込モード中
    else   printf("$ ");           	// 通常書込モード
}

/*===========================================
 *      Main application
 *==========================================*/
void main(void){
    char cmd;                     	// コマンド文字

    SYSTEM_Initialize();
    LCD_init();
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    puts("ready!\r");
    LCD_str("Ready!");
    #ifndef VBA
        prtPrompt(i2cRom_SqMode());
    #endif
        
    while (1) {
        if(SFlg){
            LCD_clr(); LCD_str(RBuf);   // LCDにコマンド表示
            cmd = RBuf[0];            	// コマンドは第1文字
            #ifndef VBA
                printf("\n\r");
            #endif
            switch(cmd) {             	// 各コマンドの実行
                case 'W': i2cRom_cmd_W(RBuf);  break;
                case 'R': i2cRom_cmd_R(RBuf);  break;
                case 'Q': i2cRom_cmd_Q(RBuf);  break;
                case 'M': i2cRom_cmd_M(RBuf);  break;
                case 'U': i2cRom_cmd_U(RBuf);  break;
                case 'S': i2cRom_cmd_W(RBuf);  break; // バッファーWコマンド
                case 'P': i2cRom_cmd_P(RBuf);  break;
                                                   // 有効なコマンドがなく 
                default:  if(i2cRom_SqMode()){     // 連続書込モード中なら
                            i2cRom_cmd_WSQ(RBuf);  // 書込シーケンスを実行
                          }
                          break; 
            }
            SFlg = 0;
            #ifndef VBA
                prtPrompt(i2cRom_SqMode());
            #endif
        }
        IO_RA4_Toggle();
        __delay_ms(500);
    }
}
/****  End of File  ****/

 
【myProject.hファイル】 myProject.h(zip)

/**************************(myProject.h)***
 *   プロジェクト個別条件指定ファイル     *
 *   (main.cと同じフォルダーに作成)     *
 ******************************************/
#include "mcc_generated_files/mcc.h"
#include "i2cLCDST7032i.h"

#define  ON    1
#define  OFF   0

/***** 使用電源電圧 ***********************/
//--- VCC宣言無しは5V(VCC=50と同じ)
#define VCC   50		// 電源電圧は5V
//#define VCC   33		// 電源電圧は3.3V

/***** EUSARTモジュール *******************/
#define BFSIZE  80
//--- ECHO宣言はどちらかを必ず行うこと
//#define ECHO  ON		// エコー有り
#define ECHO  OFF		// エコー無し

define VBA     			// Excel_VBAを使用

/***** MSSPモジュール *********************/
#define MASTER 1
#define SRAVE  2
#define I2CMODE MASTER    	// マスターとして使用
//#define I2CMODE SRAVE    	// スレーブとして使用
//--- MSSPが一つしか無い場合は I2C宣言無し
#define I2C  1            	// I2C1を使用
//#define I2C  2            	// I2C2を使用

#if I2CMODE == MASTER
    #if I2C == 1
        #include "mcc_generated_files/examples/i2c1_master_example.h"
    #elif I2C == 2
        #include "mcc_generated_files/examples/i2c2_master_example.h"
    #else
        #include "mcc_generated_files/examples/i2c_master_example.h"
    #endif
  //  #define I2CLCD_Adr 0x3c // LCDのI2Cアドレス(i2cLCD_ST7032i.hで設定)
#endif

 
【i2c_EEPROM.hファイル】 i2c_EEPROM.c/h(zip)

/******************************(i2c_EEPROM.h)***************
 *	I2C接続EEPROM(24FC1025)読み書きヘッダーファイル
 ***********************************************************/

#include "i2cLCD_ST7032i.h"
#include "ctype.h"                      // isxdigit()
#include "stdio.h"                      // puts(),..
#include "stdlib.h"                     // atoi()...
#include "string.h"                     // strtok()...
#include "myProject.h"


//----- Wコマンド   W[aaaa][,[d0,・・[,]]]
uint16_t i2cRom_cmd_W(char *str);

//---- Rコマンド  R[aaaa][,n]
uint16_t i2cRom_cmd_R(char *str);

//---- WSQ (連続書込モード)
uint16_t i2cRom_cmd_WSQ(char *str);

//---- MBコマンド  MBn
int i2cRom_cmd_M(char *str);

//---- Qコマンド(連続書込モードの解除)   Q
void i2cRom_cmd_Q(char *str);

//---- U(dump)コマンド    U[aaaa]
//         (64バイトのデータをDBuf[]に読み出す)
int i2cRom_cmd_U(char *str);

//---- P(Page)コマンド    PW[aaaa]
//          (DBuf[]の64バイトをメモリに書き込む)
int i2cRom_cmd_P(char *str);

//----  シーケンスモードの確認
uint8_t i2cRom_SqMode();

 
【i2c_EEPROM.cファイル】

/******************************(i2c_EEPROM.c)************
 *	I2C接続EEPROM(24FC1025)読み書きライブラリ
 **********************************************************/
#define DEVICE	0x50      		// Blk0=0x50,Blk1=0x54
#define MBFSIZE 78  			// メモリ操作用バッファーサイズ

#include "i2cEEPROM.h"


//=== グローバル変数 ============
char     Dlm[] = ",";    		// 文字列区切りデリミタ
char     SBuf[20];     			// 文字列処理用バッファー

//--- I2Cメモリー用
static uint8_t  BlkNum = 0; 		// メモリブロック番号
static uint16_t CrtAdr = 0;  		// カレントアドレス
static uint8_t  DBuf[MBFSIZE];  	// ページメモリ用バッファー
static uint8_t  SqMode = 0;   		// シーケンスモードフラグ
static char     lstCmd;          	// 最終コマンド

/*************************************
 *  I2C EEPROM function
 **************************************/
//-----  アドレスadrから、1バイトを読み出す
uint8_t I2C_Mem_Read(uint16_t adr){
    uint8_t bf[2];

    bf[0] = adr >> 8;
    bf[1] = (uint8_t)adr;
    #if I2C == 1
        I2C1_WriteNBytes(DEVICE + BlkNum,bf,2);
        I2C1_ReadNBytes(DEVICE + BlkNum,bf,1);
    #elif I2C == 2
        I2C2_WriteNBytes(DEVICE + BlkNum,bf,2);
        I2C2_ReadNBytes(DEVICE + BlkNum,bf,1);
    #else
        I2C_WriteNBytes(DEVICE + BlkNum,bf,2);
        I2C_ReadNBytes(DEVICE + BlkNum,bf,1);
    #endif
    return bf[0];
}

//-----  アドレスadrに、1バイトdtを書き込む
void I2C_Mem_Write(uint16_t adr, uint8_t dt){
    uint8_t bf[3];

    bf[0] = adr >> 8;
    bf[1] = (uint8_t)adr;              		// アドレスセット
    bf[2] = dt;                         	// データセット
    #if I2C == 1
        I2C1_WriteNBytes(DEVICE + BlkNum,bf,3);  //書き込む
    #elif I2C == 2
        I2C2_WriteNBytes(DEVICE + BlkNum,bf,3);  //書き込む
    #else
        I2C_WriteNBytes(DEVICE + BlkNum,bf,3);   //書き込む
    #endif    __delay_ms(5);
}

//-----  アドレスadrからnバイトをary[]に読み出す
void I2C_Mem_NRead(uint16_t adr, uint8_t *ary, uint8_t n){
    uint8_t bf[2];

    bf[0] = adr >> 8;
    bf[1] = (uint8_t) adr;
    if(n > MBFSIZE) n = MBFSIZE;           	// 配列要素数以内
    #if I2C == 1
        I2C1_WriteNBytes(DEVICE+BlkNum,bf,2);  // アドレス送信
        I2C1_ReadNBytes(DEVICE+BlkNum,ary,n);  // データ読込
    #elif I2C == 2
        I2C2_WriteNBytes(DEVICE+BlkNum,bf,2);  // アドレス送信
        I2C2_ReadNBytes(DEVICE+BlkNum,ary,n);  // データ読込
    #else
        I2C_WriteNBytes(DEVICE+BlkNum,bf,2);  // アドレス送信
        I2C_ReadNBytes(DEVICE+BlkNum,ary,n);  // データ読込
    #endif
}

//-----  アドレスadrからary[]のnバイトを書き込む
void I2C_Mem_NWrite(uint16_t adr, uint8_t  *ary, uint8_t n){
    uint8_t bf[MBFSIZE + 2];

    bf[0] = adr >> 8;                   	// アドレスセット
    bf[1] = (uint8_t) adr;              	// ページ境界に注意!
    if(n > MBFSIZE) n = MBFSIZE;
    memcpy(bf+2,ary,n);                 	// ary[]をbf[]にコピー
    #if I2C == 1
        I2C1_WriteNBytes(DEVICE+BlkNum,bf,n+2);
    #elif I2C == 2
        I2C2_WriteNBytes(DEVICE+BlkNum,bf,n+2);
    #else
        I2C_WriteNBytes(DEVICE+BlkNum,bf,n+2);
    #endif    __delay_ms(5);
}

//---- ページバッファープリント
void prt_PBf(){
    uint8_t i;

    for(i = 0; i < 64; i++){            	// 16x4文字表示
    #ifdef VBA
        if(i % 16 == 0) printf("\r");
    #else
        if(i % 16 == 0) printf("\n\r");
    #endif
        printf(" %02X",DBuf[i]);
    }
    puts("\r");
}

//---- 16進文字列を10進数に変換
int32_t my_xtoi(char *str){
    uint32_t dec;

    dec = strtoul(str,NULL,16);
    return (int32_t)dec;
}

/************************************
 * 	コマンドアクション
 ************************************/
//----- Wコマンド   W[aaaa][,[d0,・・[,]]]
uint16_t i2cRom_cmd_W(char *str){
    char     *pt;               		// 文字列操作用ポインタ
    uint8_t  last;               		// 文字列の最後の位置
    int32_t  res = 0;           		// Hex文字数値変換結果
    uint8_t  dt = 0;            		// 書き込むバイトデータ
    uint16_t cnt = 0;           		// 実際に書き込んだバイト数

    lstCmd = str[0];
    if(strlen(str)==1){          		// 'W'のみなら
        SqMode = 1;             		// 連続書込モードにセット
        #ifndef VBA
             puts("SqMode ON\n\r");
        #endif
        return 0;               		// データは書き込まない
    }
    last = (uint8_t)strlen(str)-1;		// 文字列の行末位置を取得
    if(str[last] == ','){         		// 行末がコンマなら
        SqMode = 1;             		// 連続書込モードにセット
        #ifndef VBA
            puts("SqMode ON\n\r");
        #endif
        str[last]='\0';          		// 行末のコンマを消す
    }
    if(str[1]!=','){             		// 第2文字がコンマか?
        pt = strtok(str+1,Dlm);    		// コマンドの次文字から
        if(pt != NULL){         		// アドレスデータがある
            res = my_xtoi(pt);
            CrtAdr = (uint16_t)res;
        }
        pt = strtok(NULL,Dlm);     		// 次文字列取込
    }else{
        pt = strtok(str+1,Dlm);   		// 文字列取込
    } 
    do{
        if(pt != NULL){          		// データがあるか?
            res = my_xtoi(pt);
            if(res < 0) return cnt;
            dt = (uint8_t)res;
            if(lstCmd=='W'){
                I2C_Mem_Write(CrtAdr,dt); 	// データを書込む
            }else if(lstCmd == 'S'){
                DBuf[CrtAdr & 0x3F]= dt;  
            }
            sprintf(SBuf,"%d:%04X < %02X",BlkNum,CrtAdr,dt);
            #ifdef VBA
                printf("%s\r",SBuf);
            #else
                printf("%s\n\r",SBuf);
            #endif            
            LCD_cursor(1,1); LCD_str(SBuf);
            CrtAdr++; cnt++;          
            pt = strtok(NULL,Dlm);   		// 次文字へ
        }
    }while(pt != NULL);         		// データがあるだけ繰り返す
    return cnt;                 		// 書き込んだ文字数を返す
}

//---- Rコマンド  R[aaaa][,n]
uint16_t i2cRom_cmd_R(char *str){
    char *pt;                   		// 文字列操作用ポインタ
    uint16_t n = 1;             		// 指定された読込バイト数
    int32_t  res = 0;           		// Hex文字数値変換結果
    uint8_t  dt;                		// 読み込んだバイトデータ
    uint16_t cnt = 0;            		// 実際に読み出されたバイト数
    uint8_t  i;                 		// ループ変数

    lstCmd = str[0];
    if(strlen(str) > 1){
        if(str[1]==','){
            pt = strtok(str+2,Dlm);
            if(pt != NULL){        		// データ無し?
                res = my_xtoi(pt);
                n = (uint16_t)res;
            }
        }else{
            pt = strtok(str+1,Dlm);
            if(pt != NULL){          		// addressがある?
                res = my_xtoi(pt);
                CrtAdr = (uint16_t)res;
            }
            pt = strtok(NULL,Dlm);   		// 次文字へ
            if(pt != NULL){          		// データ無し?
                res = my_xtoi(pt);
                n = (uint16_t)res;
            }
        }
    }
    for(i = 0; i < n;i++ ){
        dt = I2C_Mem_Read(CrtAdr);  		// データを読み出す
        sprintf(SBuf,"%d:%04X > %02X",BlkNum,CrtAdr,dt);
        #ifdef VBA
            printf("%s\r",SBuf);
        #else
            printf("%s\n\r",SBuf);
        #endif
        LCD_cursor(1,1); LCD_str(SBuf);
        CrtAdr++; cnt++;
    }
    SqMode = 0;
    return cnt;                   		// 読み込んだデータ数を返す
}

//---- WSQ (連続書込モード)
uint16_t i2cRom_cmd_WSQ(char *str){
    char     *pt;               		// 文字列操作用ポインタ
    uint8_t  dt;               			// 書き込むバイトデータ
    int32_t  res;                 		// Hex文字数値変換結果
    uint16_t cnt = 0;          			// 実際に読み出されたバイト数

    pt = strtok(str,Dlm);
    while(pt != NULL){       	 		// データがあるだけ繰り返す
        res = my_xtoi(pt);
        dt = (uint8_t)res;
        if(lstCmd == 'W'){
            I2C_Mem_Write(CrtAdr,dt);		// データを書き込む
        }else if(lstCmd=='S'){
            DBuf[CrtAdr & 0x3F]= dt;  
        }    
        #ifdef VBA
            printf("%d:%04X < %02X\r",BlkNum,CrtAdr,dt);
        #else
            printf("%d:%04X < %02X\n\r",BlkNum,CrtAdr,dt);
        #endif
        CrtAdr++; cnt++;
        pt = strtok(NULL,Dlm);     		// 次文字列へ
    }
    return cnt;                   		// 書き込んだデータ数を返す
}

//---- MBコマンド  MBn      (メモリバンクの指定)
int i2cRom_cmd_M(char *str){
    char     *pt;               		// 文字列操作用ポインタ
    uint8_t  b;                   		// メモリバンク番号

    lstCmd = str[0];
    if(str[1] != 'B') return -1;  		// 第2文字がBか?
    pt = strtok(str+2,Dlm);
    if(pt==NULL) return -1;       		// 文字列変換エラー
    b = atoi(pt) & 0x01;
    #ifdef VBA
    	printf("Bank %d selected\r",b);
    #else
    	printf("Bank %d selected\n\r",b);
    #endif
    BlkNum = b * 4;               		// Blk = bit2
    CrtAdr = 0;                  		// 現在アドレスは0に
    SqMode = 0;
//    puts("SqMode OFF\r");
    return b;
}

//---- Qコマンド(連続書込モードの解除)   Q
void i2cRom_cmd_Q(char *str){
    lstCmd = str[0];
    SqMode = 0;
    #ifndef VBA
        puts("SqMode OFF\r");
    #endif
}

//---- U(dump)コマンド    U[aaaa]
//         (64バイトのデータをDBuf[]に読み出す)
int i2cRom_cmd_U(char *str){
    char    *pt;                  		// 文字列操作用ポインタ
    int32_t  res;                  		// Hex文字数値変換結果
    uint16_t ad;                  		// 指定アドレス

    lstCmd = str[0];
    pt = strtok(str+1,Dlm);
    if(pt != NULL){              		// アドレス有り?
        res = my_xtoi(pt);
        CrtAdr = (uint16_t)res;
    }
    ad = CrtAdr & 0xFFC0;        		// 64バイト単位に 
    I2C_Mem_NRead(ad,DBuf,64); 			// 64バイトをDBuf[]に読出
    printf("%d:%04X - %04X",BlkNum,ad,ad+63);
    prt_PBf();
    SqMode = 0;
    return 0;
}

//---- P(Page)コマンド    PW[aaaa]
//          (DBuf[]の64バイトの読み書き)
int i2cRom_cmd_P(char *str){
    char     *pt;                  		// 文字列操作用ポインタ
    int32_t  res;                		// Hex文字数値変換結果
    uint16_t ad;                   		// 指定アドレス

    lstCmd = str[0];
    if(strlen(str)==1){
        printf("PageBuffer");
        prt_PBf();
        return 0;
    }
    if(str[1]=='R'){         			// 第2文字がR?
        i2cRom_cmd_U(str+1);
        return 0;
    }
    if(str[1] != 'W') return -1;    		// 第2文字がW?
    pt = strtok(str+2,Dlm);
    if(pt != NULL){               		// アドレス有り?
        res = my_xtoi(pt);
        CrtAdr = (uint16_t)res;
    }
    ad = CrtAdr & 0xFFC0;         		// 64バイト単位に
    I2C_Mem_NWrite(ad,DBuf,64);   		// DBuf[]の64バイト書込
    #ifdef VBA
    	printf("Page Write %d:%04X - %04X\r",BlkNum,ad,ad+63);
    #else
    	printf("Page Write %d:%04X - %04X\n\r",BlkNum,ad,ad+63);
    #endif
    SqMode = 0;    
    return 0;
}

//----  シーケンスモードの確認
uint8_t i2cRom_SqMode(){
    return SqMode;
}
/******* End of File ********/


※プログラムのリストをハイライト付きのスタイルで見る場合はここをクリック


※ 本レポートの参考・利用は、あくまでも自己責任でお願いします。


コードレス掃除機 GameBar PICミニBB(11)_EEPROMライター