かみやんの技術者ブログ

主にプログラムの話です

SH7125でsprintf

前回までにSH7125でシリアル通信、ロータリーエンコーダドライバ、モータドライバドライバ、システムタイマを書いた(ソースはこちら)ので、今回は、去年作ったマイコン側メインプログラムを移植。
その前に、sprintfを書いた。SH7125ではstdio.hをインクルードして、libをリンクすればsprintfもあるようだが、よりコンパクトなsprintfを書いた。ついでにシリアル通信ライブラリにもSerialPrintf()という関数も書いた。
メインプログラムの移植は、あっさり。で、オドメトリ精度を計測しようかなと、思って手でロボットを押してみたところ、左のロータリーエンコーダに引っ掛かりがあり、スリップする。接触していそうなところを一生懸命ヤスリで削るがいまいち。結局、右と左のロータリーエンコーダを交換して、少しネジを締めたらうまく回った。さて、計測。というところでミスってしまった。組立分解手順を間違えて、メインスイッチを壊してしまった。というところで時間切れ。次回はなんとかオドメトリ精度を計測したい。

ソースについて

sprintfだが、一応バッファオーバーランチェック用にsnprintf()という関数の型にしているが、実際にはチェックしていない。気が向いたらチェック入れるかも。あと前回、Int32ToStrin()が冗長なので書きなおしたいと書いたが、ちょっと短くした。
しかし可変長引数の関数を書くのは久しぶり。
va_list ap;
va_start(ap,format);
va_arg(ap,int)
va_end(ap);
基本的には、va_start()マクロ、va_arg()マクロ、va_end()マクロの3つを使うと実装できる。va_listの実態はvoid*型、va_start()は、スタックポインタの位置を取ってきて、apに代入する処理。第二引数のformatは、「...」のひとつ前の引数を指定。va_arg()は取得したい型を指定するとその値が取得できて、apポインタを1つ進める(次の引数へ)。va_end()は、apの解放処理。SH7125では特に何もしていないようだ。

SerialPrintf()は、テンポラリバッファを呼び出し元で用意することにした。PC用のプログラムであればSerialPrintf()の中でスタックに用意するところだが、RAM8kbyte(スタックメモリ領域はデフォルト512byte)のため、スタックメモリの消費も危険なのでこうした。

sprintf.h

//sprintf.h
//ibis inc. Eiji Kamiya 2009/05/09
//Licence : New BSD

#ifndef __SPRINTF_H__
#define __SPRINTF_H__

#include "typedefine.h"

//フォーマット
//対応しているフォーマットは、%s %nのみ。
//%3dのような桁指定、非対応
//%03dのような左パディング指定も非対応
//%-03dのような位置決めも非対応
//@param size 出力バッファサイズ(現在、内部でチェックしていない)
void snprintf(char* out,int32 size,const char* format,...);

//フォーマット(va_list版)
void snprintf_valist(char* out,int32 size,const char* format,va_list ap);

//int32から文字列へ変換
//@param out 出力先バッファ(12バイト以上用意すること)
//@return 出力桁数(0終端子含まず)
int Int32ToString(int32 n,char* out);

#endif//__SPRINTF_H__

sprintf.c

//sprintf.c
//ibis inc. Eiji Kamiya 2009/06/07
//Licence : New BSD

#include <stdarg.h>
#include "sprintf.h"

//プロトタイプ宣言

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//フォーマット
//対応しているフォーマットは、%s %nのみ。
//%3dのような桁指定、非対応
//%03dのような左パディング指定も非対応
//%-03dのような位置決めも非対応
//@param size 出力バッファサイズ(現在、内部でチェックしていない)
void snprintf_valist(char* out,int32 size,const char* format,va_list ap)
{
  const char* p;
  int n;
  char buf[12];
  char* str;

  for(p=format;*p!=0;p++){
	  if(*p=='%'){//%のとき
		  p++;
		  if(*p=='%'){//%%のとき
			  *(out++)='%';
		  } else if(*p=='d'){//%dのとき
			n=va_arg(ap,int);
			n=Int32ToString(n,buf);
			strncpy(out,buf,n);
			out+=n;
		  } else if(*p=='s'){//%sのとき
			  str=va_arg(ap,char*);
			  strcpy(out,str);
			  out+=strlen(str);
		  } else {//それ以外のとき
			  *(out++)='%';
			  *(out++)=*p;
		  }
	  } else {//普通のとき
		  *(out++)=*p;
	  }
  }
  *out='\0';
}

//フォーマット
//対応しているフォーマットは、%s %nのみ。
//%3dのような桁指定、非対応
//%03dのような左パディング指定も非対応
//%-03dのような位置決めも非対応
//@param size 出力バッファサイズ(現在、内部でチェックしていない)
void snprintf(char* out,int32 size,const char* format,...)
{
  va_list ap;

  va_start(ap,format);
  snprintf_valist(out,size,format,ap);
  va_end(ap);
}

//int32から文字列へ変換
//@param out 出力先バッファ(12バイト以上用意すること)
//@return 出力桁数(0終端子含まず)
int Int32ToString(int32 n,char* out)
{
	char* p=out;
	int32 div;
	int32 d;
	bool isOut=false;

	if(n==0){//0のとき
		*(p++)='0';
		*p='\0';
		return 1;
	} else if(n<0){//負のとき
		if(n==-2147483647-1){
			strcpy(out,"-2147483648");
			return 11;
		}
		*(p++)='-';
		n=-n;
	}
	for(div=1000000000;div>0;div/=10){
		d=n/div;
		if(d!=0){
			*(p++)='0'+d;
			isOut=true;
		} else {
			if(isOut==true){
				*(p++)='0'+d;
			}
		}
		n-=d*div;
	}
	*p='\0';
	return p-out;
}

Serial.h抜粋

//フォーマット付き送信
void SerialPrintf(char* buf,int32 size,const char* format,...);

Serial.c抜粋

//フォーマット付き送信
void SerialPrintf(char* buf,int32 size,const char* format,...)
{
	va_list ap;
	va_start(ap,format);
	snprintf_valist(buf,size,format,ap);
	va_end(ap);
	SerialPuts(buf);
}