PSoCでprintfを使う
   by K.I
   2009/05/16
 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の広告がうるさすぎるので基本は非表示にしました