====== 小さい printf、sprintf ====== ===== sprintfだけのプログラム ===== 以下のような短いプログラムを書きます。 #include void main(void) { char buff[8] ; sprintf(buff,"%c",'a') ; printf("abc %s\n",buff) ; } マップファイルを見てみます。 Segment start end size ROM atr main $200000 $200003 $4 $200000 CODE(R) TEXT $200004 $203487 $3484 $200004 CODE(R) DATA_CONST $203488 $2034A8 $21 $203488 IDATA(R) DATA $2034AA $2035D9 $130 $2034AA IDATA(RW) STACK $FFEF10 $FFF34F $440 $2035DA DATA(RW) Symbol Address(hex) (ObjctFile,SegmentName) __smr_adr $203488 (..\lib3052\startup.asm,DATA_CONST) _STACK_BTM $FFEF50 (..\lib3052\startup.asm,STACK) START $200004 (..\lib3052\startup.asm,TEXT) __dbg_flag $2034A8 (..\lib3052\startup.asm,DATA_CONST) __exit $20000E (..\lib3052\startup.asm,TEXT) _main $20001A (test.ASM,TEXT) __l_mul $200072 (CLIB.ASM,TEXT) __l_divs $2000E0 (CLIB.ASM,TEXT) __l_divu $2000B8 (CLIB.ASM,TEXT) __l_mods $200158 (CLIB.ASM,TEXT) __l_modu $200130 (CLIB.ASM,TEXT) __d_dtol_set $200372 (FCLIB.ASM,TEXT) __d_sub_set $2001F8 (FCLIB.ASM,TEXT) __d_sltod_set $200352 (FCLIB.ASM,TEXT) __d_neg_set $2001D4 (FCLIB.ASM,TEXT) __d_cmp $200310 (FCLIB.ASM,TEXT) __d_div $2002B4 (FCLIB.ASM,TEXT) __d_mul $200258 (FCLIB.ASM,TEXT) _printf $202EBE (PRINTF.ASM,TEXT) _sprintf $202F1A (PRINTF.ASM,TEXT) __Dnorm $202F86 (MATH.ASM,TEXT) _fputc $203182 (STDIO.ASM,TEXT) _fputs $20333E (STDIO.ASM,TEXT) __iob $203510 (STDIO.ASM,DATA) _strcat $2033EC (STRING.ASM,TEXT) _strcpy $203398 (STRING.ASM,TEXT) _strlen $203446 (STRING.ASM,TEXT) これだけのプログラムでも内蔵RAMの容量を越えてしまいます。13KByteもあります。\\ 内蔵RAMでは既にエミュレーションデバッグができません。\\ printfはYellow Scopeへのログ出力、sprintf はシリアルへのログ出力やLCDへの出力など\\ 使う頻度は多いので、これでは困ります。 ===== 秋月で売っているH8のROMとRAM ===== ^ 型番 ^ ROM ^ RAM ^ | 3048F| 128KByte| 4KByte| | 3052F| 512KByte| 8KByte| | 3067| 128KByte| 4KByte| | 3069F| 512KByte| 16KByte| | 3664F| 32KByte| 2KByte| | 3664N| 32KByte| 2KByte| ===== putc,putsと機能限定printf,sprintfの作成 ===== 使う頻度が少ない(変わりの手段のある)%f、%eを省いて軽量化します。\\ 使用できるのは%c,%s,%d,%u,%x,%X,と%-05d等の、左寄せ、桁数指定、0で埋める。\\ \\ まずは、Yellow Scopeへの出力を作ります。ライブラリのソースを見ながら作成。\\ ライブラリの処理の仕方に疑問が残りますが…まぁいいでしょう。 extern unsigned char *_smr_adr[] ; // 標準入出力関数の入出力先の割り当て //***************************************************************************** // Yellow Scope へ1文字出力 // // _putc_ys // 2004/12/23 : 初期バージョン完成 // // [IN] char code = 出力コード // // [OUT] なし //***************************************************************************** static void _putc_ys(char c) { Uint8 *tdr = _smr_adr[1] + 3 ; Uint8 *ssr = _smr_adr[1] + 4 ; while((*ssr & 0x80) == 0) ; // 送信完了待ち *tdr = c ; // 送信 *ssr &= ~0x80 ; // フラグクリア } 次にputcを作ってしまいます。putsはputcを文字数分呼ぶだけですし。\\ 簡単なプロトコルがありますので、これもライブラリのソースを見ながら作ります。\\ //***************************************************************************** // 標準出力に1文字出力 // // _putc // 2004/12/23 : 初期バージョン完成 // // [IN] char code = 出力コード // // [OUT] なし //***************************************************************************** void _putc(char code) { Uint8 *scr = _smr_adr[1] + 2 ; *scr &= ~0x40 ; _putc_ys(0x48) ; _putc_ys(0x01) ; _putc_ys(code) ; _putc_ys(-(0x48 + 0x01 + code)) ; *scr |= 0x40 ; } putsは単純ですね。 //***************************************************************************** // 標準出力に文字列出力 // // _puts // 2004/12/23 : 初期バージョン完成 // // [IN] char *str = 出力文字列 // // [OUT] なし //***************************************************************************** void _puts(const char *str) { while(*str) { _putc(*str) ; *str ++ ; } } printfとsprintf。printfは256Byteのバッファに、一旦sprintfしてputsを呼び出します。\\ バッファオーバーとかは気にしない方向で。書式文字列の展開は同じ処理を呼び出してます。 //***************************************************************************** // 軽量版 printf // // _printf // 2004/12/22 : 初期バージョン完成 // // [IN] char *format = 書式文字列 // // [OUT] なし //***************************************************************************** void _printf(const char *format,...) { char buff[256] ; va_list argptr ; va_start(argptr,format) ; _vsprintf(buff,format,argptr) ; va_end(argptr) ; _puts(buff) ; } //***************************************************************************** // 軽量版 sprintf // // _sprintf // 2004/12/22 : 初期バージョン完成 // // [IN] char *buff = 展開バッファのポインタ // char *format = 書式文字列 // // [OUT] なし //***************************************************************************** void _sprintf(char *buff,const char *format,...) { va_list argptr ; va_start(argptr,format) ; _vsprintf(buff,format,argptr) ; va_end(argptr) ; } 最後に書式文字列の解釈ルーチン。思いつくまま記述したので、もう少し小さくできる気がします。 //***************************************************************************** // 軽量版 _vsprintf // // _vsprintf // 2004/12/23 : 初期バージョン完成 // // [IN] char *buff = 展開バッファのポインタ // char *format = 書式文字列 // va_list argptr = 引数リストのポインタ // // [OUT] なし // // 備考:%c %s %d %u %x %X と %-05d 等の、左寄せ、桁数指定、 // 0 で埋めるは使用可能。%f %e 等の小数点を含むものは使用できない。 //***************************************************************************** void _vsprintf(char *buff,const char *format,va_list argptr) { Uint8 lflag ; int ecx,len ; while(*format) { switch(*format) { case '%' : format ++ ; lflag = 0 ; if(*format == '-') { // 左詰め lflag |= (1 << 0) ; format ++ ; } if(*format == '0') { // 0 で埋める lflag |= (1 << 1) ; format ++ ; } len = 0 ; // 桁数指定 if((*format >= '1') && (*format <= '9')) { while((*format >= '0') && (*format <= '9')) { len = len * 10 + *format - '0' ; format ++ ; } } switch(*format) { case 'c' : if(len) { memset(buff,' ',len) ; if(lflag & (1 << 0)) { *buff = va_arg(argptr,char) ; } else { buff[len - 1] = va_arg(argptr,char) ; } buff += len ; } else { *buff = va_arg(argptr,char) ; buff ++ ; if(*buff) buff ++ ; // パッチ 2005/01/14 } format ++ ; break ; case 's' : { char *str = va_arg(argptr,char *) ; int slen = strlen(str) ; if(len) { if(slen > len) len = slen ; memset(buff,' ',len) ; if(lflag & (1 << 0)) { memcpy(buff,str,slen) ; } else { memcpy(&buff[len - slen],str,slen) ; } buff += len ; } else { memcpy(buff,str,slen) ; buff += slen ; } format ++ ; break ; } case 'd' : case 'u' : case 'x' : case 'X' : { char str[6] ; int slen = 0 ; BOOL act = FALSE ; switch(*format) { case 'd' : { int num = va_arg(argptr,int) ; if(num < 0) { *buff = '-' ; buff ++ ; if(len) len -- ; num = ~num + 1 ; } for(ecx = 10000 ; ecx != 0 ; ecx /= 10) { str[slen] = num / ecx + '0' ; num %= ecx ; if((str[slen] != '0') || (act)) { act = TRUE ; slen ++ ; } } break ; } case 'u' : { Uint16 num = va_arg(argptr,Uint16) ; for(ecx = 10000 ; ecx != 0 ; ecx /= 10) { str[slen] = num / ecx + '0' ; num %= ecx ; if((str[slen] != '0') || (act)) { act = TRUE ; slen ++ ; } } break ; } case 'x' : case 'X' : { Uint16 num = (Uint16)va_arg(argptr,int) ; for(ecx = 0 ; ecx < 16 / 4 ; ecx ++) { str[slen] = (num >> (12 - (ecx << 2))) & 0x0f ; if(str[slen] <= 9) str[slen] += '0' ; else if(*format == 'x') str[slen] += 'a' - 10 ; else str[slen] += 'A' - 10 ; if(str[0] == '0') continue ; slen ++ ; } break ; } } if(!slen) slen ++ ; if(len) { if(slen > len) len = slen ; if(lflag & (1 << 1)) memset(buff,'0',len) ; else memset(buff,' ',len) ; if(lflag & (1 << 0)) { memcpy(buff,str,slen) ; } else { memcpy(&buff[len - slen],str,slen) ; } buff += len ; } else { memcpy(buff,str,slen) ; buff += slen ; } format ++ ; break ; } } break ; default : *buff = *format ++ ; buff ++ ; break ; } } *buff = 0 ; } まとめるとこんな感じ。 //***************************************************************************** // 軽量版 putc,puts,printf,sprintf,vsprintf 関数 // (C)YdlProg //***************************************************************************** #include #include #include #include "../lib3052/typedef.h" //============================================================================= // プロトタイプ宣言 //============================================================================= //------------------------------------------------------------------------- // sprintf.c //------------------------------------------------------------------------- extern unsigned char *_smr_adr[] ; // 標準入出力関数の入出力先の割り当て //***************************************************************************** // Yellow Scope へ1文字出力 // // _putc_ys // 2004/12/23 : 初期バージョン完成 // // [IN] char code = 出力コード // // [OUT] なし //***************************************************************************** static void _putc_ys(char c) { Uint8 *tdr = _smr_adr[1] + 3 ; Uint8 *ssr = _smr_adr[1] + 4 ; while((*ssr & 0x80) == 0) ; // 送信完了待ち *tdr = c ; // 送信 *ssr &= ~0x80 ; // フラグクリア } //***************************************************************************** // 標準出力に1文字出力 // // _putc // 2004/12/23 : 初期バージョン完成 // // [IN] char code = 出力コード // // [OUT] なし //***************************************************************************** void _putc(char code) { Uint8 *scr = _smr_adr[1] + 2 ; *scr &= ~0x40 ; _putc_ys(0x48) ; _putc_ys(0x01) ; _putc_ys(code) ; _putc_ys(-(0x48 + 0x01 + code)) ; *scr |= 0x40 ; } //***************************************************************************** // 標準出力に文字列出力 // // _puts // 2004/12/23 : 初期バージョン完成 // // [IN] char *str = 出力文字列 // // [OUT] なし //***************************************************************************** void _puts(const char *str) { while(*str) { _putc(*str) ; *str ++ ; } } //***************************************************************************** // 軽量版 printf // // _printf // 2004/12/22 : 初期バージョン完成 // // [IN] char *format = 書式文字列 // // [OUT] なし //***************************************************************************** void _printf(const char *format,...) { char buff[256] ; char *str = buff ; va_list argptr ; va_start(argptr,format) ; _vsprintf(buff,format,argptr) ; va_end(argptr) ; _puts(str) ; } //***************************************************************************** // 軽量版 sprintf // // _sprintf // 2004/12/22 : 初期バージョン完成 // // [IN] char *buff = 展開バッファのポインタ // char *format = 書式文字列 // // [OUT] なし //***************************************************************************** void _sprintf(char *buff,const char *format,...) { va_list argptr ; va_start(argptr,format) ; _vsprintf(buff,format,argptr) ; va_end(argptr) ; } //***************************************************************************** // printf,sprintf 展開処理 // // _vsprintf // 2004/12/23 : 初期バージョン完成 // // [IN] char *buff = 展開バッファのポインタ // char *format = 書式文字列 // va_list argptr = 引数リストのポインタ // // [OUT] なし // // 備考:%c %s %d %u %x %X と %-05d 等の、左寄せ、桁数指定、 // 0 で埋めるは使用可能。%f %e 等の小数点を含むものは使用できない。 //***************************************************************************** void _vsprintf(char *buff,const char *format,va_list argptr) { Uint8 lflag ; int ecx,len ; while(*format) { switch(*format) { case '%' : format ++ ; lflag = 0 ; if(*format == '-') { // 左詰め lflag |= (1 << 0) ; format ++ ; } if(*format == '0') { // 0 で埋める lflag |= (1 << 1) ; format ++ ; } len = 0 ; // 桁数指定 if((*format >= '1') && (*format <= '9')) { while((*format >= '0') && (*format <= '9')) { len = len * 10 + *format - '0' ; format ++ ; } } switch(*format) { case 'c' : if(len) { memset(buff,' ',len) ; if(lflag & (1 << 0)) { *buff = va_arg(argptr,char) ; } else { buff[len - 1] = va_arg(argptr,char) ; } buff += len ; } else { *buff = va_arg(argptr,char) ; buff ++ ; if(*buff) buff ++ ; // パッチ 2005/01/14 } format ++ ; break ; case 's' : { char *str = va_arg(argptr,char *) ; int slen = strlen(str) ; if(len) { if(slen > len) len = slen ; memset(buff,' ',len) ; if(lflag & (1 << 0)) { memcpy(buff,str,slen) ; } else { memcpy(&buff[len - slen],str,slen) ; } buff += len ; } else { memcpy(buff,str,slen) ; buff += slen ; } format ++ ; break ; } case 'd' : case 'u' : case 'x' : case 'X' : { char str[6] ; int slen = 0 ; BOOL act = FALSE ; switch(*format) { case 'd' : { int num = va_arg(argptr,int) ; if(num < 0) { *buff = '-' ; buff ++ ; if(len) len -- ; num = ~num + 1 ; } for(ecx = 10000 ; ecx != 0 ; ecx /= 10) { str[slen] = num / ecx + '0' ; num %= ecx ; if((str[slen] != '0') || (act)) { act = TRUE ; slen ++ ; } } break ; } case 'u' : { Uint16 num = va_arg(argptr,Uint16) ; for(ecx = 10000 ; ecx != 0 ; ecx /= 10) { str[slen] = num / ecx + '0' ; num %= ecx ; if((str[slen] != '0') || (act)) { act = TRUE ; slen ++ ; } } break ; } case 'x' : case 'X' : { Uint16 num = (Uint16)va_arg(argptr,int) ; for(ecx = 0 ; ecx < 16 / 4 ; ecx ++) { str[slen] = (num >> (12 - (ecx << 2))) & 0x0f ; if(str[slen] <= 9) str[slen] += '0' ; else if(*format == 'x') str[slen] += 'a' - 10 ; else str[slen] += 'A' - 10 ; if(str[0] == '0') continue ; slen ++ ; } break ; } } if(!slen) slen ++ ; if(len) { if(slen > len) len = slen ; if(lflag & (1 << 1)) memset(buff,'0',len) ; else memset(buff,' ',len) ; if(lflag & (1 << 0)) { memcpy(buff,str,slen) ; } else { memcpy(&buff[len - slen],str,slen) ; } buff += len ; } else { memcpy(buff,str,slen) ; buff += slen ; } format ++ ; break ; } } break ; default : *buff = *format ++ ; buff ++ ; break ; } } *buff = 0 ; } ===== 結果と使い方 ===== まずは小さくなったか、マップファイルを見てみます。 Segment start end size ROM atr main $200000 $200003 $4 $200000 CODE(R) TEXT $200004 $200B3F $B3C $200004 CODE(R) DATA_CONST $200B40 $200B5F $20 $200B40 IDATA(R) DATA $200B60 $200B6C $D $200B60 IDATA(RW) STACK $FFEF10 $FFF34F $440 $200B6E DATA(RW) Symbol Address(hex) (ObjctFile,SegmentName) __smr_adr $200B40 (..\lib3052\startup.asm,DATA_CONST) _STACK_BTM $FFEF50 (..\lib3052\startup.asm,STACK) START $200004 (..\lib3052\startup.asm,TEXT) __exit $20000E (..\lib3052\startup.asm,TEXT) _main $20001A (test.ASM,TEXT) __putc $2000DA (..\lib3052\printf.ASM,TEXT) __puts $200148 (..\lib3052\printf.ASM,TEXT) __sprintf $2001DA (..\lib3052\printf.ASM,TEXT) __printf $200182 (..\lib3052\printf.ASM,TEXT) __vsprintf $20021A (..\lib3052\printf.ASM,TEXT) __w_shift_ur $200A28 (CLIB.ASM,TEXT) _memcpy $200A8C (STRING.ASM,TEXT) _memset $200AEE (STRING.ASM,TEXT) _strlen $200A4A (STRING.ASM,TEXT) 2.8Kぐらいになりました。アセンブラ化すればもっと減るかもしれません。\\ 内蔵8KByteの3052Fでも、小さいプログラムならエミュレーションデバッグできます。\\ \\ 軽量版関数を使用する場合は、以下のように定義すると便利です。 #undef putc // YC で定義済みのものを消す #define putc _putc #define puts _puts #define printf _printf #define sprintf _sprintf #define vsprintf _vsprintf