GitHub サンプルプロジェクト:
プロジェクトのビルド
R8C/UART_Sample に移動後、ビルドして、R8C にプログラムを書き込みます。
Uranus-W10./d/Git/R8C/UART_sample % make
...
m32c-elf-size uart_sample.elf
text data bss dec hex filename
14026 298 156 14480 3890 uart_sample.elf
m32c-elf-objcopy --srec-forceS3 --srec-len 32 -O srec uart_sample.elf uart_sample.mot
m32c-elf-objdump -h -S uart_sample.elf > uart_sample.lst
Uranus-W10./d/Git/R8C/UART_sample % make run
r8c_prog -d R5F2M120 --progress -e -w -v uart_sample.mot
Erase: #################################################
Write: #################################################
Verify: #################################################
- マイコンの P1_4(TXD) と USB シリアルの RXD を接続
- マイコンの P1_5(RXD) と USB シリアルの TXD を接続
- シリアルポートと USB シリアルなどを接続して、TeraTerm などを利用します。
TeraTerm の設定例
- スピード:57600
- データ:8ビット
- パリティ:無し
- ストップビット:1ビット
- フロー制御:無し
シリアル通信を使う方法(UART の使い方)
R8C/C++ フレームワークを使えば、シリアル通信も簡単に行えます。
最低限必要な設定だけ行えば使えるように工夫してあります。
- 基本的に C++ テンプレートを活用しているので、それに沿った定義や使い方が必要です。
- C++ テンプレートは、初心者には難しいと感じる場合もありますが、「例」があれば、それを真似るだけです。
- 通常とは異なる定義を行いたい場合には、多少の知識が必要になる場合もあります。
- R8C/Mxx の UART は1チャネルです、以下のサンプルでは、P1_4:TXD(Output)、P1_5:RXD(Input)を利用します。
では、プログラムを解説します。
インクルードファイル
# include "common/vect.h"
# include "system.hpp"
# include "clock.hpp"
# include "port.hpp"
# include "intr.hpp"
# include "common/delay.hpp"
# include "common/port_map.hpp"
# include "common/uart_io.hpp"
# include "common/fifo.hpp"
# include "common/command.hpp"
# include "common/format.hpp"
# include "common/input.hpp"
- 今回のプロジェクトに必要なインクルードファイルです。
- 多少冗長ですが、どのファイルが必要なのかを明確にする意図があり、現在のような手順になっています。
シリアル通信を行うデバイスの定義
typedef utils::fifo<uint8_t, 16> TX_BUFF; // 送信バッファ
typedef utils::fifo<uint8_t, 16> RX_BUFF; // 受信バッファ
typedef device::uart_io<device::UART0, TX_BUFF, RX_BUFF> UART;
UART uart_;
- uart_io テンプレートクラスは、UART デバイス、送信バッファ、受信バッファの型が必要です。
- R8C/Mxx は、UART デバイスは、UART0 しかありませんので、UART0 を指定します。
- 送信バッファ、受信バッファは、fifo テンプレートクラスを利用します。(First In First Out バッファ)
- ぞれぞれ16バイトを指定しています。(アプリケーションによりサイズを調整する事も出来る)
※詳しくは、各ソースコードを参照して下さい。
シリアル通信 API の準備と、シリアル通信割り込みの定義
extern "C" {
void sci_putch(char ch) {
uart_.putch(ch);
}
char sci_getch(void) {
return uart_.getch();
}
uint16_t sci_length() {
return uart_.length();
}
void sci_puts(const char* str) {
uart_.puts(str);
}
void UART0_TX_intr(void) {
uart_.isend();
}
void UART0_RX_intr(void) {
uart_.irecv();
}
};
シリアル通信を行う際、標準出力、標準入力を準備しておくと便利です。(printf などが使える)
そこで、上記のように、C 言語ソースから参照出来るようにして、1文字出力、1文字入力を定義しておきます。
この関数は、syscalls.c から呼ばれます。
※printf などを使うと、syscalls を経由して呼ばれます。
※ただ、printf は C 言語の API なので、C++ では使いません、代わりに、utils::format クラスを用意してあります。
UART0 の割り込み関数が呼ばれるように、「UART0_TX_intr」、「UART0_RX_intr」を定義してあります。
※これらの関数は、「vect.h」で定義してあり、「vect.c」から呼ばれます。
※割り込み関数は、gcc で扱う場合、「attribute ((interrupt))」を付加する必要があります。
これは、割り込み関数は、アセンブラでは、「reti」命令で終了する事が必要な為です。
この定義は、「vect.h」で行われており、割り込み関数を使う場合、必ずこのヘッダーをインクルードする必要があります。
このインクルードは、「renesas.hpp」で行われています。
これらの前提があり、Makefile には、main.cpp の他、以下のように C 言語ソースをリンクします。
ASOURCES = common/start.s
CSOURCES = common/vect.c \
common/init.c \
common/syscalls.c
PSOURCES = main.cpp
※「start.s, init.c」は標準的に必要
シリアル通信の開始
// UART の設定 (P1_4: TXD0[out], P1_5: RXD0[in])
// ※シリアルライターでは、RXD 端子は、P1_6 となっているので注意!
{
utils::PORT_MAP(utils::port_map::P14::TXD0);
utils::PORT_MAP(utils::port_map::P15::RXD0);
// ※「0」を設定するとポーリングとなる。
uint8_t intr_level = 1;
uart_.start(57600, intr_level);
}
- UART0 のポートを「PORT_MAP」APIを利用して設定します。
- 割り込みレベルを設定します。(0~2)(0だとポーリングになり割り込みを使いません)
- ボーレートを整数値で設定します。(最大57600くらいまで)
- ボーレートが高すぎたり、低すぎたりで設定が出来ない場合は、「false」を返して失敗します。
- ボーレートは内部で自動で計算されますが、ボーレートにより誤差があります。
- UART0 のハードウェアーの能力を超えた設定は出来ません。
- 上記の設定では、シリアルフォーマットは8ビットデータ、1ストップビットです。
※UART 通信では、文字入力は非同期で予想出来ないので、割り込みは標準的に利用する方が適切な運用が出来ます。
シリアル入出力の操作
uart_.puts("Start R8C UART sample\n");
utils::format("Real baud rate: %u\n") % uart_.get_real_baud_rate();
command_.set_prompt("# ");
uint8_t cnt = 0;
while(1) {
if(command_.service()) {
auto cmdn = command_.get_words();
if(cmdn >= 1) {
char tmp[32];
if(command_.get_word(0, sizeof(tmp), tmp)) {
int32_t a = 0;
auto n = (utils::input("%d", tmp) % a).num();
if(n == 1) {
utils::format("Value: %d, 0x%X\n") % a % a;
} else {
utils::format("Input only decimal: '%s'\n") % tmp;
}
}
}
}
// 10ms ソフトタイマー
utils::delay::milli_second(10);
// LED の点滅
++cnt;
if(cnt < 25) {
LED0::P = 0;
LED1::P = 1;
} else {
LED0::P = 1;
LED1::P = 0;
}
if(cnt >= 50) cnt = 0;
}
- 「開始」すれば、後は、文字を出力したり、入力したりするだけです。
- サンプルでは、utils::command クラスを使い、入力された文字を数値として変換し、10進、16進で表示しています。
- 10ms 毎にサンプリングを行っています。
- 文字列から、数値に変換するには、「utils::input」クラスを利用しています。
- 「utils::format」クラスは、printf と同じように使えて、安全な C++ 向けのテンプレートクラスです。
- printf は、スタック経由でパラメーターを受け渡すので安全ではありません、C++ では利用しませんし必要もありません。
※10ms でサンプリングしているので、10ms 以内に16文字を超える文字が入力されると、文字バッファが足りずにロストします。
※送信時は、16文字を超えて文字出力を行うと、バッファが空くまでプログラムが遅延(待ちが発生)します。
TeraTerm での表示例:
Start R8C UART sample
Real baud rate: 59523
# 123456
Value: 123456, 0x1E240
# asdfg
Input only decimal: 'asdfg'
# 98765321
Value: 98765321, 0x5E30A09
# -29847
Value: -29847, 0xFFFF8B69
# -1
Value: -1, 0xFFFFFFFF
#
まとめ
どうでしょうか?
R8C/C++ フレームワークを使う事で、簡単にシリアル通信を行う事が出来ると思います。
別プログラムでコードを生成したり、多くの設定が必要な API を操作する事なく最低限の設定で使えるように工夫してあります。
難解で面倒な UART デバイスの直接的操作を隠蔽して、なるべく簡易な方法で使いやすく利用出来るように工夫してあります。
また、最低限必要な自由度もあり、間違った設定をなるべく行わせないように工夫してあります。
自由度が大きいと、余分なコードが含まれて、プログラムが肥大化したり、余分な手続きで、速度が低下する場合もありますが、
C++ テンプレートクラスの場合、非常に優れた最適化が行われる為、適切な実行結果が得られます。
参考リンク
次の記事:
開発環境構築など: