組み込み向けのコンパクトなsprintfサブセットを作ろう

AVRマイコンやH8マイコンのファームを開発する時に、デバッグ用としてUARTからメッセージを吐き出して、TeraTermなどのターミナルソフトで確認したり、ディスプレイデバイスを接続して表示する際に、コンパクトなsprintf相当の関数があれば便利だと思い、作成してみました。

通常コンパイラのライブラリ等で供給されるフルフィーチャーのsprintf関数は、その機能が豊富であるがためにかなりのROM空間を占有します。しかし、その全ての機能を通常の用途で必要としているとは限りません。
例えば、浮動小数点型のfloatの表示処理などです。

そこで、機能を限定したsprintfを作成し、Tiny sprintfという事で、tsprintfと名づけました。

可変長引数を扱い、そして直接UART等に出力もできるprintf関数を作るために

まず、可変長引数(任意個数の引数)を扱う為には、stdarg.hをインクルードして、以下のような関数のプロトタイプ宣言をして引数にアクセスできるようにします。

int tsprintf(char* ,char* , ...);

...」という指定は、C言語で可変長引数を使用するための宣言に使用します。
そして、具体的な可変長引数の使用方法についての説明は割愛しますが、va_start()マクロや、va_arg()マクロを使用してアクセスします。
但し、tsprintf関数で直接引数にアクセスする構造の関数を使用した場合、例えばこの関数を内部で使用する別な可変長引数で書式指定子を渡す関数(例:UARTに直接出力するprintf関数など)を作成する時、うまく値を可変長で渡せなくなってしまうので、まずva_list型で値を受けて出力するvsprintf関数に相当するvtsprintf関数を作成します。そして、この関数にアクセスする以下のようなtsprintf関数を作成します。

int tsprintf(char* buff,char* fmt, ...){
    va_list arg;
    int len;

    va_start(arg, fmt);

    len = vtsprintf(buff,fmt,arg);
    
    va_end(arg);

    return(len);
}

あとは、vtsprintfの実装です。以下のような関数を作成しました。

int vtsprintf(char* buff,char* fmt,va_list arg){
    int len;
    int size;
    int zeroflag,width;

    size = 0;
    len = 0;

    while(*fmt){
        if(*fmt=='%'){        /* % に関する処理 */
            zeroflag = width = 0;
            fmt++;

            if (*fmt == '0'){
                fmt++;
                zeroflag = 1;
            }
            if ((*fmt >= '0') && (*fmt <= '9')){
                width = *(fmt++) - '0';
            }

            /* printf ("zerof = %d,width = %d\n",zeroflag,width); */

            switch(*fmt){
            case 'd':        /* 10進数 */
                size = tsprintf_decimal(va_arg(arg,signed long),buff,zeroflag,width);
                break;
            case 'x':        /* 16進数 0-f */
                size = tsprintf_hexadecimal(va_arg(arg,unsigned long),buff,0,zeroflag,width);
                break;
            case 'X':        /* 16進数 0-F */
                size = tsprintf_hexadecimal(va_arg(arg,unsigned long),buff,1,zeroflag,width);
                break;
            case 'c':        /* キャラクター */
                size = tsprintf_char(va_arg(arg,int),buff);
                break;
            case 's':        /* ASCIIZ文字列 */
                size = tsprintf_string(va_arg(arg,char*),buff);
                break;
            default:        /* コントロールコード以外の文字 */
                /* %%(%に対応)はここで対応される */
                len++;
                *(buff++) = *fmt;
                break;
            }
            len += size;
            buff += size;
            fmt++;
        } else {
            *(buff++) = *(fmt++);
            len++;
        }
    }

    *buff = '\0';        /* 終端を入れる */

    va_end(arg);
    return (len);
}

この関数では、以下の書式指定子に対応しています。

書式指定子表示内容使用例
%d10進%d、%04d、%6d、正負数にも対応
%x16進(小文字)%x、%04x、%6x
%X16進(大文字)%X、%04X、%6X
%cキャラクター%c
%s文字列%s
%%%(パーセント)文字そのものを指定%%

以下のようにして使用する事ができます。


    char buff[80];
    int i=32;

    tsprintf(buff,"debug : i = %04d\n",i);
    uart_txs(buff);/*UARTで文字列出力する関数*/

このようにすれば、「debug : i = 0032」と表示されるはずです。

ソースコード一式

以下からダウンロードして下さい。

tsprintf_0100.lzh

もしかすると、intが32ビットでない処理系では手直しが必要かも。


Author: projectc3@gmail.com
Last modified: Sun Jul 31 06:57:24 JST 2005