あなたの天然記念物
ホーム更新雑談Perl鉄ゲタランドナーコースガイド自転車Linuxリンク経歴連絡先

FreeRTOSでprintf (2015.05.15)

背景

これまでRedSuite(NXP版)を使いタスクテーブルを利用していましたが、LPCXpresso(開発環境の方)に移行すると FreeRTOS未サポートでタスク関連の表示機能がありません。 これは面白くないのでLPCXpressoのコンソールに実行時のタスク起動/終了メッセージを表示させようと思いました。

sprintfもどき

ライブラリのsprintfは、sbrk呼び出し問題があるので使えません。 そこで、もどき版を作ります。 機能は です。 桁数指定不可、ゼロサプレス指定不可、16進英字の大文字指定不可です。 ソースはこちら。 他のヘッダファイルも要るけど省略しています。 マクロKSRK_ASSERTは実行時にカッコ内が0でブレーク(ブレークポイントで停止)します。
#include <stdarg.h>

void ksrk_sprintf(char *pString, const char *pFormat, ...) {
	va_list varg;
	va_start(varg, pFormat);

	const char *pSearch;
	for (pSearch = pFormat; *pSearch; pSearch++) {
		if ('%' != *pSearch) {
			*(pString++) = *pSearch;
		} else if ('s' == pSearch[1]) {
			pSearch++;
			const char *pStrArg = va_arg(varg, const char *);
			while (*pStrArg) {
				*(pString++) = *(pStrArg++);
			}
		} else if ('d' == pSearch[1]) {
			pSearch++;
			int IntArg = va_arg(varg, int);
			if (IntArg < 0) {
				*(pString++) = '-';
				IntArg = -IntArg;
			}
			int iBase;
			for (iBase = 1000000000; iBase && IntArg < iBase; iBase /= 10) {
			}
			for (; iBase; iBase /= 10) {
				int iMod = IntArg % iBase;
				int iDigit = (IntArg - iMod) / iBase;
				*(pString++) = "0123456789"[iDigit];
				IntArg = iMod;
			}
		} else if ('x' == pSearch[1]) {
			pSearch++;
			uint32_t UIntArg = va_arg(varg, uint32_t);
			uint32_t uBase;
			for (uBase = 0x10000000; uBase && UIntArg < uBase; uBase /= 0x10) {
			}
			for (; uBase; uBase /= 0x10) {
				uint32_t uMod = UIntArg % uBase;
				uint32_t uDigit = (UIntArg - uMod) / uBase;
				*(pString++) = "0123456789abcdef"[uDigit];
				UIntArg = uMod;
			}
		} else {
			// ここに来た場合はフォーマット文字列の指定間違い
			KSRK_ASSERT(0);
			*(pString++) = *pSearch;
		}
	}
	*(pString++) = '\0';

	va_end(varg);
}

セミホスト

LPCXpressoのコンソール出力をセミホスト(semihost)と呼びます。
以前の説明→FreeRTOSでセミホスト表示
シングルタスクのプロジェクトであればライブラリをリンクしてprintfでコンソールに表示されます。 デバッガでprintfの中を命令ステップ実行で逆アセンブラ表示しながら追跡して表示方法を解析しました。 単に文字列を送信するソースがこちらです。 マクロKSRK_VALIDATE_STACKはスタック不足の際にブレークします。 FreeRTOSのスタック検証はタスクスイッチ発生時なので不足のタイミングが分かりませんから別の機構を使っています。 関数ksrk_in_debuggerはデバッガ環境下で稼働しているか返します。 アセンブラ表記内で使うレジスタを指定する方法やc側の変数を渡す方法が分かりましたのでその辺りを修正しています。
// デバッグ用コンソールへ文字列(終端\0)を表示
void ksrk_trace_s(const char *msg)
{
	KSRK_VALIDATE_STACK();

	if (ksrk_in_debugger()) {
		__asm volatile (
			// バイト数取得
			"		mov		r1, #0\n"
			"loop:	ldrb	r2, [%0, r1]\n"
			"		teq		r2, #0\n"
			"		itt		ne\n"
			"		addne	r1, #1\n"
			"		bne		loop\n"
			// パラメタ設定
			"		push	{r1}\n"
			"		mov		r0,	%0\n"
			"		push	{r0}\n"
			"		mov		r0, #1\n"
			"		push	{r0}\n"
			"		mov		r0, #5\n"
			"		mov		r1, sp\n"
			// r0 = 5
			// r1 = &{文字列バイト数, 文字列の先頭アドレス, 1}
			"		bkpt	0x00ab\n"
			"		add		sp, #12\n"
			: // 戻り値なし
			: "r" (msg)
			: "r0", "r1", "r2"
		);
	}
}

使用例

こんな感じ(注・今書いたので未検証)。 マクロKSRK_TRACE_Sはksrk_trace_sを呼びます。
char LineBuff[256];
ksrk_sprintf(LineBuff, "Hello, world.(%s - %d)\n", __FILE__, __LINE__);
KSRK_TRACE_S(LineBuff);

本日の鉄ゲタ

LPCXpressoでFreeRTOS未サポート、なんと言ってもタスクテーブルが表示されない事に尽きます。 で、これを書いて気付いたのが%のエスケープを処理できないから%を表示したくても%%でASSERT発行するぞ、コレ(汗)。