MPLABXでPIC開発: シミュレータでprintf出力する方法

PC上で動作するプログラムのデバッグ出力は簡単にできます。C言語ならprintf()、javascriptならconsole.log()を使えば、出力ウィンドウやコンソール上にログ出力されます。

PIC開発の場合も、MPLABX上でC言語を使えばprintf()で同様の出力ができますが、ちょっと一癖あります。その手順の備忘録を残しておきます。

UARTでシリアル通信を使って標準出力を行う

ほとんどのPICには、EUSART(Enhanched Universal Synchronous Asynchronous Receiver Transmitter ここではUARTと呼ぶ)というハードウェアが内蔵されています。UART送信できるようにしておけば、TXREGレジスタに書き込んだ文字がTXピンから送信されて、相手のRXピンを経由して伝達します。これがシリアル通信の一種です。

実機の場合、PCにUSBシリアル変換機を接続して、そのRXピンとPICのTXピンをワイヤ接続します。USBシリアル変換機のポートに対してTeraTermなどのコンソールアプリを起動すると、転送された文字が表示されるようになるわけです。

uart_printf7

Simulatorを使ったコンソール出力は、このUARTを利用して送信した文字をSimulatorが受信して、文字列をウィンドウに表示させる仕組みになっている。プログラムの実装は、上記の実機のシリアル通信で必要なものと同じで、出力先だけを変えるようなイメージである。

uart_printf8

printf()が使えるようにする準備

デフォルトの状態だと、プログラムでprintf("hello world\n")と記述しても何も出力されないです。まずprintf関数を実行すると、上記のUARTの送信用バッファのレジスタTXREGに書き込むようなプログラムを実装する必要があります。

コンパイラによって、実装が違うので注意。XC8の例は、Microchipのデベロッパー向けサイトにある。

Printing to the UART Console in MPLAB® X IDE Simulator

XC8 (8bit)コンパイラの場合

putch()という名称で、以下のように関数を実装する。内部処理はTXIFビットがONになったら、TXREGに1文字書き込む内容になっている。この関数は、printf()内部で何度もコールされます。

#include <stdio.h>

void putch(unsigned char data){
    while(PIR1bits.TXIF != 1){
        continue;
    }
    TXREG = data;
}

UARTを有効にするために以下の2つのビットをONにする必要があります。

TXSTAbits.TXEN = 1;
RCSTAbits.SPEN = 1;

XC8を使うPICの場合はこれでOK。

XC16 (16bit)コンパイラの場合

XC16の場合のputch()相当のものが以下になります。これを実装します。注意点としては以下のサンプルでは、U1STAU1TXREGレジスタを使っています。PIC24FJ64GB002の場合、UARTが2機あるようなので、どちらを使うか(U1STAかU2STA)を決めておきます。

int __attribute__((__section__(".libc.write"))) write(
    int handle, void *buffer, unsigned int len) {
    int i;
    while(U1STAbits.TRMT == 0);  
    for (i = len; i; --i){
        while(U1STAbits.TRMT == 0);
        U1TXREG = *(char*)buffer++;
    }
    return(len);
}

UARTを有効にするビットをONします。こちらもU1とU2使うレジスタを合わせておきます。

U1MODEbits.UARTEN = 1;
U1STAbits.UTXEN = 1;

MCCを使う場合

MCC(Microchip Code Configuratior)を使ってUARTを追加するだけで、上記のコードは自動で追加されます。ただし、下図のオプションをONしてからGenerateする必要があります。

uart_printf2

uart_printf

Generateすれば、すでに利用可能になっています。

サンプルプログラム

XC8コンパイラ(PIC16F1455を使用)のサンプル

// XC8, PIC16F1455 Sample Code
:
#include <stdio.h>
:
void putch(unsigned char data){
    while(PIR1bits.TXIF != 1){
        continue;
    }
    TXREG = data;
}

void main(void)
{
    TXSTAbits.TXEN = 1;
    RCSTAbits.SPEN = 1;
    
    printf("Hello World!\n");

    while(1){
        // Add your application code
    }
    return;
}

XC16コンパイラ(PIC24FJ64GB002を使用)のサンプル

// XC16, PIC24FJ64GB002 Sample Code
:
#include <stdio.h>
:
int __attribute__((__section__(".libc.write"))) write(
    int handle, void *buffer, unsigned int len) {
    int i;
    while(U1STAbits.TRMT == 0);  
    for (i = len; i; --i)
    {
        while(U1STAbits.TRMT == 0);
        U1TXREG = *(char*)buffer++;        
    }
    return(len);
}

int main(void)
{
    U1MODEbits.UARTEN = 1;
    U1STAbits.UTXEN = 1;
    
    printf("Hello World!\n");

    while(1){
        // Add your application code
    }
    return -1;
}

コンソール出力するためのシミュレータの準備

XC16のプロジェクト使った手順になりますが、XC8も同じです。まずは、プロジェクト設定で、Hardware ToolとしてSimulatorを選択します。

uart_printf6

続いて、Simulatorの設定を選択して、プルダウンからUart1 IO Optionsを選択して切り替えます。Enable Uart1 IOをONを選択します。次にOutputとしてWindowを選択します。(XC8の場合など使用するPICによって名称は変わりそうですが、UARTの出力云々だと思います)

uart_printf3

デバッグ実行を行います。

uart_printf4

デバッグ実行されると、MPLABXの下段のOutputウィンドウに、UARTの出力タブが自動的に追加されて、printf()の内容がコンソールに出力されるようになる。

uart_printf5

1点気になることとしては、XC8の場合、最後の1文字がTXREGに残ったまま転送されないです。(Flushされてないようなイメージで、上記の場合だと最後の改行文字\nがコンソールには出力されてないです。次の出力時に押し出されるように出力されます。)自動Flushのようなオプションがあれば出力されそうです。

PC上のソフトウェア開発と違って、お約束のHelloWorldのコンソール出力が結構メンドクサイですが、デバッガの使い勝手がちょっと悪いので、上記のようにコンソール出力できるようになっておくと便利です。今回はシミュレータで出力していますが、実機の場合でもprintfを使う場合は同様の実装が必要になります。PIC開発では必須の小技だと思います。

シミュレータも使いこなせるようになると、便利そうですが使い勝手にやはり一癖あるので、ちょっとずつ覚えていく予定。