PSoCでprintfを使う
by K.I
2009/05/16
Index
- C言語で文字出力するといえば、やっぱりprintf関数だ。
- でも、ImageCraftのコンパイラには、printfが無い。
- LCDやUARTには、文字列出力用の関数があるけど、やっぱりprintfが使いたいな〜。
- ということで、主に PastelMagicさんとこの、printfをいろいろ弄ったメモです。
- てきとうに直してるところもあるので、間違っていたらご指摘下さい。。
- ImageCraftのコンパイラにも、printfが追加されたとのことで、それも 追記しました(120418)
[top]
- まず、前述のJunk-Boxさんのページから、 lcdtest.lzhをダウンロード。
- 解凍して、cstdef.h,ctype.c,ctype.h,pr.c等を、自分のプロジェクトへコピー。
- ProjectメニューからAdd Fileで、.cや.hのファイルをインクルードする。
- あとは、print_chrという名前で、1文字だけ出力する関数を用意する。
- あとは、以下のようにprintfが使えるようになる。
extern void printf(const unsigned char *string, ...);
:
printf("\nDATA:%d",data);
- つまり、UARTでもLCDでも1文字出力ルーチンだけ用意すれば、printfが使えるというワケだ。
- やっぱりprintfが使えるのは有難い。JUNK-BOXさんに感謝です。
- PICのCCSCのprintfは、例えばLCDやUART用の一文字出力を切替えて、使えるようになってる。
- で、printfの定義を以下のように、出力ルーチンを指定出来るように変更。
- 出力先を変更できるようにしたので、名前をxprintfに変更しました。
//void printf(const unsigned char *string, unsigned int arg) // 出力ルーチンを指定出来るように変更
void xprintf( void(*outfunc)(unsigned char c), const unsigned char *string, unsigned int arg)
{
print_chr = outfunc; // 関数ポインタをコピー
prtout(string, &arg);
}
- これで、printfの1文字出力ルーチンを変更出来るようになったので、
- 例えば以下のように、UART用とLCD用の一文字出力ルーチンを用意しておく。
void txout(unsigned char c)
{
int i;
switch (c) {
case '\f': UART_1_PutCRLF(); break; // carriage return
case '\n': UART_1_PutCRLF(); break; // carriage return
case '\b': break; // dummy
case '\r': break; // dummy
default: UART_1_PutChar(c); break; // normal character
}
}
void lcdout(unsigned char c)
{
int i;
switch (c) {
case '\f': LCD_1_Control(0x01); // clear screen (1st line home)
LCD_1_Delay50uTimes(40); break; // (Execution delay 2ms)
case '\n': LCD_1_Position(1,0); break; // carriage return (2nd line home)
case '\b': LCD_1_Control(0x10); break; // backward
case '\r': break; // dummy
default: LCD_1_WriteData(c); break; // normal character
}
}
extern void xprintf(void(*outfunc)(unsigned char c), const unsigned char *string, ...);
xprintf(lcdout,">Data:%d",xxx); // LCDへ出力
xprintf(txout,">Data:%d",xxx); // UARTへ出力
やっぱり、何にでもprintf出来るのは便利だな〜。あらためてJUNK-BOXさんに感謝!
- ちょっと、DWORD(unsigned 32bit)の数値を扱わなきゃいけなくなって、printf使えるかな〜と思って調べたら、
- ちゃんとDWORDにも対応しているのが分かったので、早速使っていた。
- ところが、DWORDの値を表示する時に、時々表示がおかしくなることに気が付いた。
- どうも 0xFE000000のような数値はダメで、0x12345678はOK。何故?
DWORD data;
data=0xFE000000;
printf("DATA:%08LX",data);
data=0x12345678;
printf("DATA:%08LX",data);
- このようにすると、以下のように表示される。
DATA:0000FE00
DATA:12345678
- pr.cのソースを見てみると、print_valルーチンで、以下のように上位ワードを見て切替えて表示しているが、
- prtoutルーチンで、Lフラグの処理をしているが一見以下のようにしてるように見える
- 16bitの時は、udat.word[1]=0、udat.word[0]=data
- 32bitの時は、udat.word[1]=dataの上位WORD、udat.word[0]=dataの下位WORD
- でも、デバッガで追ってみると、実際は逆に、
- 32bitの時に、udat.word[0]=dataの上位WORD、udat.word[1]=dataの下位WORDになっているようだ。
- PSoCのendianってどっちだっけ?Big-Endianだと思うんだけど。。それなら良いんじゃないのかなぁ。。。
- あれ?*param--になってる、下位から処理してるのか。なんでだろう?でもそうすると、
- 16bitの時は、udat.word[1]=0、udat.word[0]=data
- 32bitの時は、udat.word[0]=dataの上位WORD、udat.word[1]=dataの下位WORDということなので、
- 16bitの時と、32bitの時で、上位Wordが入っている変数が違うということか。
- data.word[1]をフラグにすると、ちょっとマズイみたいだ。
- うーん。でも下手に書き直すとヘンになりそうなので、Lフラグ処理内のlongflagという変数をprint_valに渡すようにして、
- print_valでは、longflagを使って32bitデータの表示を行うようにしてみた。
static void print_val(unsigned int width,
unsigned int padchar,
unsigned int base,
unsigned int lflag, // longflagを渡すように
union DATAS data)
{
:
:
// if (data.word[1]) { // word[1]で判断するのは止めて
if (lflag) { // longflagで判断するように
: // 32bitデータの表示
- 後は、例えば以下のように、prtout内のprint_valでlongflagを渡すように変更(5箇所)
print_val(width,padchar,16,longflag,udat); // これはXの場合
- あまりスマートじゃないが、こういう修正なら、おそらく副作用は無いと思うけど。。
- とりあえず、これで正しく表示されるようになったみたいだ。
- 以下のpr.cは、DWORDに関する修正は同じですが、1文字出力ルーチンを指定出来るように改造したxprintf版のソースです。
[top]
- DWARD関連で、PastelMagicさんの掲示板を見ていたら、Hi-Tech C用に書き直したprintfもあるようだ。
- Hi-Tech Cは、lite版が自由に使えるみたいなので、これから主流になりそうだし。。
- でも、Hi-Tech Cには、printfがあるみたいなんだけど、どうも使い方が分からない。
- 多分、同様に1文字出力関数を用意しておく方式だと思うんだけど。。
- 使用例があれば、分かるんだけどなぁ。。どうもHi-Tech Cのマニュアルは一般的なことしか書いてないようにみえる。
- で、やっぱり PastelMagicさんのprintfを使ってみることにしました。
- といっても、PSoC Designer 5.0を使ったことがなかったので、ハマリました。。
- PastelMagicさんとこの、 Hi-TECH C用簡易printfをダウンロード
- またDWORD出力がおかしい。以下のように出力される。
DATA:1F001234
前半に、全く無関係な数値が表示され、上位WORDが下位に表示されている!?
- デバッガでみると、paramが最初に、0x12345678の先頭を指しているが、*param--なので、この変数の前にある全然関係ない値を表示している。
- *param++にすれば良いわけだが、そもそも何でポインタが違ってるんだろう?
- で、デバッガでRAM上のパラメータを見てみると、
- ImageCraftCは、para3,para2,para1と並んでいるが、
- HiTechCは、para1,para2,para3と並んでいるように見える。
- パラメータの並び順が違ってるのかな?うーん。勘違いかな?
- それならということで、以下のように変更してみる。
- param--になっている部分を全てparam++に変更
- float出力部のfp--というのも、次のパラメータのアドレスを求めるためのようなので、fp++に変更
- そうするとDWORDも上から素直に取ればいいので、以下のように1スッキリした。
- 16bitの時は、udat.word[0]=0、udat.word[1]=data
- 32bitの時は、udat.word[0]=dataの上位WORD、udat.word[1]=dataの下位WORD
- JuncBoxさんのImageCraft版は、本来はこうするはずが、パラメータ順が逆だったので逆にしたのかな?
- PSoC Designer 5は、あまり使っていないので、確認が不十分かもしれません。
1こうすればword[0]で判定できるのでlongflagは不要になる。
[top]
- PSoCでは、プログラムはフラッシュROM上にあり、変数はRAM上にある。
- でもRAMはあまり大きくないので、文字列をRAM上に置くとRAMが不足してしまう。
- フラッシュROMはそれなりに大きいので、文字列はROM上に置いた方がメモリを有効利用できる場合が多い。
- 文字を変化させる必要がある場合は、多くの場合はテーブルを使えば済む。
const char name[4][6] = {"Alpha","Beta ","Gamma","Delta"};
xprintf(lcdout,"\f>%s:%d",name[n],data); // nを0〜3に変えれば、Alpha〜Deltaと表示が変わる
- PastelMagicさんのprintfも、フラッシュROM上の文字列を扱うようになっている。
- 普通は、文字列はフラッシュROM上に置くので、RAM上の文字列を表示する必要は無い。
- でもICEが無い場合のデバッグ等で、RAM上の文字列を表示したい場合もあるので、またちょっと弄る2。
- const unsigned char*を表示する、print_str関数をコピーしてRAM用に変更。
- あとは、case 's':をコピーして、大文字の'S'でRAM出力する。
const char cstring_data[] = "ABCDE"; // フラッシュROM上に文字列をセット
char string_data[] = "ABCDE"; // RAM上に文字列をセット
xprintf(lcdout,"\fcstring:%s",cstring_data): // 通常はフラッシュ上の文字列表示
xprintf(lcdout,"\n string:%S",string_data); // RAM上の文字列を表示する場合
2通常は使わない方が良い蛇足の機能だが。
[top]
- ImageCraftのコンパイラにも、printfが追加されたらしいとは聞いていたんだけど、
- JuncBoxさんのprintfに慣れていたので、手を付けずに居た。
- でも新規のプログラムを書くことになって、思い切って使ってみました。
- stdio.hをインクルードして、cprintfを使えば良いらしい。
- 出力先を切替えるようにしてみた。
- 単純に、putchar_outという関数ポインタを用意して、
- putcharは、それを実行するようにしただけです。
#include <stdio.h>
static void (*putchar_out)(unsigned char c);
void txout(unsigned char c)
{
int i;
switch (c) {
case '\f': break; // dummy
case '\n': UART_1_PutCRLF(); break; // CRLF
case '\b': break; // dummy
case '\r': /*UART_1_PutChar(13);*/ break; // CR
default: UART_1_PutChar(c); break; // normal character
}
}
void lcdout(unsigned char c)
{
int i;
switch (c) {
case '\f': LCD_1_Control(0x01); // clear screen (1st line home)
LCD_1_Delay50uTimes(40); break; // (Execution delay 2ms)
case '\1': LCD_1_Position(0,0); break; // carriage return (1nd line home)
case '\2': LCD_1_Position(1,0); break; // carriage return (2nd line home)
case '\n': break; // dummy
case '\b': LCD_1_Control(0x10); break; // backward
case '\r': break; // dummy
default: LCD_1_WriteData(c); break; // normal character
}
}
int putchar(char c)
{
putchar_out(c);
return c;
}
- 面倒なので、今まで使っていた UART用、LCD用の1文字出力ルーチンをそのまま流用している。
- あとは以下のように、putchar_outに1文字出力ルーチンを設定してやれば
- まぁ、あまりスマートじゃないけど、
- とりあえず、これで使えるようになったので、いろいろと試してみたい。
- 新しいCコンパイラは、マクロ定義で簡単に可変引数を使えるらしい。
xprintf(lcdout,"Hello World!");
- 可変引数マクロって便利だなぁ。
- でもImageCraftのC って、ちゃんとしたコンパイラなんだなぁ。
cprintf("%04X",data);
- 前言撤回。。ダメじゃん3。。。いや、多分自分のやり方が悪いんだろうけど。。
int putchar(char c)
{
char str[] = " ";
strncpy(str,&c,1);
LCD_1_PrString(str);
return c;
}
うーん。何が間違っているのかな? ちゃんと出来ている方も居るみたいなんだけどなぁ。
- とりあえず急いでいたので、今まで通りJUNKBOXさんのprintfを使うことにした。
3まぁ、コンパイラの所為ってわけじゃないけど。
[top]
- PSoC5LPで、printfを使うのはどうすれば良いのか調べてみる。
- どうやら、PSoC3はKeilのコンパイラで、putcharルーチンをoverrideすればよかったが、
- PSoC5はgccなので、writeルーチンを書かないといけないらしい
- スレッドには、以下のような記述があったので試してみたが、今のところうまくいっていない。
- 確かに、_writeルーチンを記述すれば、ゴミみたいなデータが出力されるのだけれど。。
Conclusion how to use "printf()"
For all compillers add include to stdio library:
#include
For PSoC 3(Keil) revise putchar() function with communication component which has to send data:
char putchar( char c)
{
UART_1_PutChar(c);
return c;
}
For PSoC 5(GCC) revise _write() function:
int _write(int file, char *ptr, int len)
{
int i;
for (i = 0; i < len; i++)
{
UART_1_PutChar(*ptr++);
}
return len;
}
Add an explicit reference to the floating point printf library to allow the usage of floating point conversion specifier:
#if defined (__GNUC__)
asm (".global _printf_float");
#endif
Now you can use printf() which will send formatted data through "UART_1".
void main()
{
uint32 i = 444444444;
float f = 55.55555f;
CyGlobalIntEnable; /* Enable interrupts */
UART_1_Start(); /* Start communication component */
printf("Test printf function.long:%ld,float:%f \r\n",i,f);
}
The log from terminal software:
Test printf function.long:444444444,float:55.555553
Hope it will help.
- 表示が抜けるみたいなので、"fflush(stdout);"を追加したら、少しマシになったが、
- 入出力のストリームが邪魔をしているような気がするんだけど、
- "setbuf(stdout,NULL);"とか、いろいろやってみたがダメ。
- writeルーチンを、overrideすれば何とかなる様だけど、どうすれば良いんだろう?
- それだけじゃなくて、何か他にもやらないとダメなんだろうか。。
- 結局、標準のprintfを使うのは、自分の知識では無理そうなので早々に諦めた
- sprintf4で、文字列を作ってから、それをUARTやLCD等に送信する
- USBUARTに出力するためのprintfの例
void USBUART_printf(const char *form, ...)
{
va_list argp;
char line[256];
char *pline;
va_start(argp,form);
vsnprintf(line,256,form,argp);
va_end(argp);
pline = line;
while(*pline) {
while(!USBUART_1_CDCIsReady());
USBUART_1_PutChar(*pline++);
}
}
- UARTは、改行コードをそのまま送信すれば良いが、LCD等では少し工夫が必要かも
- 標準のvsnprintfは、コードサイズが大きいという問題があるので、
- 汎用のprintfで、何か良いのがないかな〜と探していたら、
- まず、printfモジュールをダウンロードして、
- プロジェクトに、xprintf.cとxprintf.hを追加する
- 1文字出力ルーチンを用意するだけなので、UARTだけじゃなく、LCD等でもやり易いんじゃないかと思う
- 出力だけじゃなく、1文字入力ルーチンを用意すれば、getsも使える
unsigned char USBUART_getc(void)
{
while(!USBUART_1_DataIsReady());
return USBUART_1_GetChar();
}
- PSoCのUSBUARTルーチンは、初めて使ったので、使い方がこれで良いかどうか分からないが、
- データシートにサンプルが見当たらないので、いろいろ試行錯誤して、この方法に落ち着いた。
- あとは、1文字入出力ルーチンのポインタを設定用のマクロで、以下のように指定するだけ。
- これで、xprintfや、xgetsが使えるようになる
- おまけに、数値変換用のxatoiという便利な関数も付いている
#include "xprintf.h"
char line[16];
char *pline;
long data;
xgets(line,16);
xprintf(">>>>Program Start!\n\r");
for(;;) {
xprintf("> parameter? ");
xgets(line,16);
pline = line;
xatoi(&pline,&data);
:
:
:
}
- ソースコードもすっきりしているし、使いやすい。移植性も考えられているみたい
- 浮動小数点出力には対応していないが、自分の用途には全く問題なし
- 試した限りは、とても良さそうなので、これからは是非使わせてもらおうと思う。
4実際はオーバーフローする危険があるので、snprintfやvsnprintfを使ったほうが良い。
[top]
[電子工作関連に戻る]
⇒ Disqusの広告がうるさすぎるので基本は非表示にしました