こんにちは、ナナです。
前章のA/D変換において光センサの値によりLEDを制御しました。ここで問題なのが光センサは0~1023の値が取れているはずですが、具体的にどれくらいの数値が取れているのかがわからないことです。
そこで本章ではマイコン側の情報をパソコンに送ることで情報を可視化する手段を手に入れましょう。コンピュータ間でデータ通信を行うためのシリアル通信の制御方法について学びます。
本記事では次の疑問点を解消する内容となっています。
では、シリアル通信の仕組みを学んでいきましょう。
シリアル通信の概要
通信ってそもそも何のために必要なのでしょうか?まずはそこから知っていきましょう。
コンピュータ間の接続
世の中にはたくさんのコンピュータが存在します。そうするとコンピュータ同士でお互いのデータを交換して仕事がしたいという場面が出てきます。その際にはデータを受け渡す方法が必要になりますね
通信の方法はEthernet、I2C、CAN、Bluetooth・・・などたくさんの規格がありますが、 その中でも比較的メジャーな通信規格がシリアル通信です。制御が簡単であり、短距離間通信ではよく使われます。
シリアル通信の用途
パソコンにはプログラム結果を表示するためのディスプレイがあるため、プログラムの挙動をprintf関数を使って表示することが可能です。しかし、多くの組み込み機器にはディスプレイというハードウェアがなく、プログラムの動きを捉えることが難しい場面が出てきます。
例えば前章で行った光センサのA/D変換ですが、白や黒を検知したときに具体的に0~1023のどのあたりの数値が取得できているのかわからないですよね。
このような時に使えるのがシリアル通信を使った動作確認方法です。多くのマイコンにはシリアル通信用の機能が搭載されており、パソコンと通信ができるようにハード設計することも珍しくありません。
ビュートローバーもシリアル通信ができるように設計されています。シリアル通信機能を実装しプログラムの動きを目で見て追えるようにしてみましょう。
シリアル通信機能の使い方
シリアル通信は、データシート「15章 シリアルコミュニケーションインタフェース3(SCI3)」に記載されています。30ページ程あるため、読み応えのある機能になっています。
特徴
「15.1章 特徴」にはシリアル通信の特徴が記載してあります。
- シリアルデータ通信フォーマットを調歩同期式またはクロック同期式に設定可能
- 全二重通信が可能
- 内蔵ボーレートジェネレータで任意のビットレートを選択可能
- 送受信クロックソースとして内蔵ボーレートジェネレータまたは外部クロックを選択可能
- 6種類の割り込み要因
特殊な用語が出てきますので解説しておきましょう。
通信フォーマット
シリアル通信の方法として調歩同期式モードとクロック同期式モードというものがあります。どちらを選ぶかによってプログラムの仕方は変わるのですが、私の経験では調歩同期式モードを使う方が一般的です。
本章においても扱うのは調歩同期式モードとなります。
全二重通信
通信とは送信側と受信側の双方が電気的な信号にて情報をやり取りします。シリアル通信は送信と受信を分けて通信しますので、送信中に受信するといった同時並行の送受信が可能です。
ビットレート
ビットレートとは情報を転送する際の転送速度を表します。bps(Bit Per Second)という単位で表現され、1秒間に何ビットを転送できるかを示します。もちろん数字が大きいほど転送量を多くすることが可能です。
割り込み要因
送信終了/送信データエンプティ/受信データフル/オーバランエラー/フレーミングエラー/パリティエラーの6種類の割り込みイベントが用意されています。
これまでの他の機能と比べると非常に多くの種類があります。今回は割り込みは利用しませんが、本格的なシリアル通信機能を利用するとなるとこれらのイベントを上手に制御する必要がでてきます。
調歩同期式モードの設定情報
調歩同期式モードでは通信する際のルールとして、いくつかの情報を送信側・受信側であらかじめ決めておく必要があります。通信とはコンピュータ同士の会話ですから会話するためのルールを双方で決めておくということです。
データ長
1度に転送するデータの長さです。選択肢として7ビットと8ビットがあります。
ビットレート(スピード)
転送速度です。非常に多くの種類があります。代表的なビットレートは、9600bps/19200bps/38400bps/115200bpsなどがあります。
パリティ
転送時のエラー検出用の情報です。選択肢として、使用しない/偶数パリティ/奇数パリティがあります。
ストップビット長
データ長分のデータを送った後に付与するデータ転送の終わりを示すデータの長さです。選択肢として1ビットと2ビットがあります。
今回の通信設定内容
今回のシリアル通信を扱う際のこれらの情報は下記として対応します。後程これらの情報はレジスタを利用して適切に選択する必要があります。
調歩同期式モードの通信設定
データ長:8bit
ビットレート:19200bps
パリティ:使用しない
ストップビット長:1bit
レジスタ構成
データシート「15.3章 レジスタの説明」にシリアル通信のレジスタ一覧があります。
- レシーブシフトレジスタ(RSR)
- レシーブデータレジスタ(RDR)
- トランスミットシフトレジスタ(TSR)
- トランスミットデータレジスタ(TDR)
- シリアルモードレジスタ(SMR)
- シリアルコントロールレジスタ3(SCR3)
- シリアルステータスレジスタ(SSR)
- ビットレートレジスタ(BRR)
非常に多くのレジスタがあります。今回の課題ではビュートローバーから送信する機能のみを作成します。そのため太字のレジスタを解説します。
シリアルモードレジスタ(SMR)/ビットレートレジスタ(BRR)とは
この2つのレジスタはシリアル通信の通信設定を管理するレジスタです。
ほとんどのビットがシリアル通信の設定と1対1で対応しているのがわかります。通信クロックの設定はBRRレジスタとも連動しています。
ビットレートの設定方法はデータシートの読み方を知る必要がありますので解説しましょう。データシート「15.3.8章 ビットレートレジスタ(BRR)」を見てください。次の表があります。
まずは縦軸と横軸で着目する欄を決定します。ビュートローバーはシステムクロック12MHzであるのと今回目標のビットレートは19200bpsです。そこからnとNの値を見つけ、対象のレジスタ設定値とします。これがビットレートのレジスタ値の決定方法です。
シリアルコントロールレジスタ3(SCR3)とは
SCR3レジスタはシリアル通信の送受信や割り込み制御用のレジスタです。
このレジスタを利用して通信の開始/停止を主に制御します。
トランスミットデータレジスタ(TDR)とは
シリアル通信で送信するデータを書き込む1Byteのレジスタです。
ビュートローバーへのシリアル通信切り替え設定を行おう
ビュートローバーとパソコンの間でシリアル通信を可能にするためには、あらかじめシリアル通信ができる状態に設定を変更しておく必要があります。その方法を解説します。
シリアル通信への切り替え手順
手順1
パソコンとビュートローバーをUSBケーブルで接続してください。
手順2
第02章 環境構築 にてインストールした「VS-WRC003LV シリアルコンバータ」を使用します。このツールを起動してください。「シリアルポートへ変更」を押下します。
次の画面で書き込みが完了するまではUSBケーブルを決して抜かないようにしてください。プログラムの更新ができなくなります。
次の画面が表示されればUSBケーブルを抜いて大丈夫です。切り替え完了です。
この作業は一度だけ行えばよく、以降はビュートローバーの電源を落としてもシリアル通信の状態が継続されます。
シリアル通信切り替え後のプログラム書き込み手順の変更について
ビュートローバーをシリアル通信に切り替えると、これまで行ってきたプログラムの書き込み手順に少し変更が必要となります。まずは、いつも通りH8 Writer Vstoneを起動します。
これまでは「USB HID」が選択されていたはずですが、「シリアルポート」を選択するようにしてください。下記図ではCOM 3とされていますが、皆さんのパソコンでは番号が異なる可能性があります。
選択後に「書き込み」を行うことでいつも通りプログラムをビュートローバーに書き込むことができます。
以前のようにUSB HIDで書き込みしたい場合は、VS-WRC003LV シリアルコンバータを再度起動し「HID(標準)へ変更」を実施すれば元に戻すこともできる!
シリアル通信を行うTeraTermとの接続と設定
シリアル通信機能を実装する前に、第02章 環境構築でインストールしたTeraTermの設定をしておきましょう。
ビュートローバーをパソコンがUSBケーブルで接続された状態でTeraTermを起動し次の設定を行います。
接続設定
TeraTermをシリアル接続として動作させます。ポートにはビュートローバーに接続するCOMが選択されているはずです。
受信改行コード設定
ビュートローバーから受信した文字の改行コードの扱いを設定します。
この設定をしておくと改行文字(\n)を受信した際に綺麗に改行されるようになります。
通信パラメータの設定
調歩同期式モードの通信パラメータの設定を行います。ビュートローバーの設定と同一にする必要があります。
設定の保存
変更した設定を保存します。これをしておかないとTeraTermを閉じた際に再度設定を行わないといけなくなります。毎回設定するのは手間なので保存しておきましょう。
TERATERM.INIの保存ダイアログが表示されますので上書き保存してください。
これらの設定が完了したら一旦、TeraTermを閉じて終了しておいてください。
シリアル通信でデータ通信を行うSciモジュールの作成
シリアル通信の制御方法がわかりましたのでプログラムの作成を行います。いつもの手順でsci.cとsci.hをプロジェクトに登録してください。sciとはシリアル通信:Serial Communication Interfaceの略です。
Sciモジュールの役割検討
シリアル通信モジュールでは次の機能を実装したいと思います。
- ビュートローバーからパソコンに対する送信機能のみを対応
- 通信モードは調歩同期式モードを利用
- 19200bps/8bit長/1stopビット/パリティなしとして通信設定を行う。
- 初期化処理では通信に必要な設定を行う
- 他モジュールからの送信要求に従い、1文字送信と文字列送信の2つをインターフェースとして対応する。
- 割り込みイベントは利用しない
では、これらを満たすインターフェース仕様を検討します。
Sciモジュールのインターフェース仕様書
1.提供ヘッダファイル名
#include “sci.h”
2.定数定義
なし
3.インターフェース定義
3.1 初期化
3.2 文字の送信
3.3 文字列の送信
課題:Sciモジュールを使ってデータ通信をしてみよう
課題1
課題内容
インターフェース仕様に従いシリアル通信モジュールを作成せよ。sci.cとsci.hは次のプログラムをベースにして修正を加えよ。
sci.h
#ifndef SCI_H
#define SCI_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
//------------------------------------------------
#endif // SCI_H
sci.hに追記すべきこと
- インターフェースのプロトタイプ宣言を追加せよ
sci.c
#include <stdio.h>
#include "iodefine.h"
#include "sci.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Sci_init(void)
{
}
//------------------------------------------------
// 概 要:文字の送信
// 引 数:moji 送信したい文字
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Sci_putChar(char moji)
{
}
//------------------------------------------------
// 概 要:文字列の送信
// 引 数:str 送信したい文字列
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Sci_putString(char * str)
{
}
sci.cに追記すべきこと
- Sci_init関数ではデータシート「15.4.2章 SCI3の初期化」に従いレジスタの初期化設定をせよ。
- Sci_putChar関数ではデータシート「15.4.3章 データ送信」 に従い、送信処理を作成せよ。
- Sci_putString関数では文字列の終端までループ処理にて1文字ずつSci_putChar関数を利用し送信処理を作成せよ。
sci.h
#ifndef SCI_H
#define SCI_H
//------------------------------------------------
//------------------------------------------------
// マクロ定義(Macro definition)
//------------------------------------------------
//------------------------------------------------
// 型定義(Type definition)
//------------------------------------------------
//------------------------------------------------
// プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
void Sci_init(void);
int Sci_putChar(char moji);
int Sci_putString(char * str);
//------------------------------------------------
#endif // SCI_H
- インターフェース仕様書に従いプロトタイプ宣言を追加する。
sci.c
#include <stdio.h>
#include "iodefine.h"
#include "sci.h"
//------------------------------------------------
// 概 要:初期化
// 引 数:なし
// 戻り値:なし
//------------------------------------------------
void Sci_init(void)
{
long i;
SCI3.SCR3.BIT.TE = 0; // 送信禁止
SCI3.SCR3.BIT.RE = 0; // 受信禁止
SCI3.SMR.BIT.CKS = 0; // n = 0(ボーレート:19200bps)
SCI3.SMR.BIT.COM = 0; // 調歩同期式
SCI3.SMR.BIT.CHR = 0; // データ長8bit
SCI3.SMR.BIT.PE = 0; // パリティなし
SCI3.SMR.BIT.PM = 0; // 偶数パリティ
SCI3.SMR.BIT.STOP = 0; // 1stop bit
SCI3.SMR.BIT.MP = 0; // マルチディスエーブル
SCI3.BRR = 19; // N = 19(ボーレート:19200bps)
for (i=0 ; i<1000 ; i++); // 1bit転送時間待ち
SCI3.SCR3.BIT.TE = 1; // 送信許可
IO.PMR1.BIT.TXD = 1; // P22のポートをシリアル送信端子へ変更
}
//------------------------------------------------
// 概 要:文字の送信
// 引 数:moji 送信したい文字
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Sci_putChar(char moji)
{
// 転送可能待ち
while (SCI3.SSR.BIT.TDRE == 0);
// 対象文字のデータ転送要求
SCI3.TDR = moji;
// 転送完了待ち
while (SCI3.SSR.BIT.TEND == 0);
return 0;
}
//------------------------------------------------
// 概 要:文字列の送信
// 引 数:str 送信したい文字列
// 戻り値:0 正常
// -1 異常
//------------------------------------------------
int Sci_putString(char * str)
{
if (str == NULL)
{
return -1;
}
// 順番に文字列終端まで1文字ずつ送信
while (*str != '\0')
{
Sci_putChar(*str);
str++;
}
return 0;
}
- Sci_init関数では「15.4.2章 SCI3の初期化」に記載されている内容をプログラム化する。
- Sci_putChar関数ではTDREビットを監視し送信可能になるまで待ち、可能であれば送信データレジスタに送信文字を書き込んで転送する。転送の完了をTENDビットで監視する。
課題2
課題内容
MAINモジュールでシリアル通信モジュールを使えるようにせよ。
また、main関数を変更し1秒間隔で光センサ1と光センサ2を文字列に変換し、シリアル通信でパソコン側に送信せよ。
main.c
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
unsigned short sen1;
unsigned short sen2;
// 光センサー値取得
Lsen_getSensor(&sen1, &sen2);
// 送信用の文字列の作成
// シリアル通信で文字列を送信
Timer_waitTime(1000);
}
}
送信用の文字列作成方法
文字列の作成はsprintf関数を利用して実施する。stdio.hをインクルードすることで利用することができる。
sprintfはprintf関数のメモリ出力版の関数である。第1引数に書き込み先の文字配列を指定することで、出力書式に応じた内容を書き込むことができる。次のように使う。
char string[32];
int tmp = 100;
// string[] <-- "Hello:100\n\0"が書き込まれる
sprintf(string, "Hello:%d\n", tmp);
送信時の文字列フォーマット
光センサの値は次のフォーマットで出力する。日本語部分は適宜変数に変更すること。
sprintf(出力先配列, "Sen1:%4d Sen2:%4d\n", センサ1, センサ2);
課題が完成したらビルドを行いビュートローバー上で動作させ、期待動作通りに動くことを確認せよ。
期待動作
光センサ1と光センサ2が1秒間隔で次のようにTeraTerm上で表示されることを確認する。
main.c
#include <stdio.h>
#include "iodefine.h"
#include "led.h"
#include "sw.h"
#include "timer.h"
#include "lsen.h"
// シリアル通信モジュールを使用するためインクルード
#include "sci.h"
//------------------------------------------------
// グローバル変数
//------------------------------------------------
// シリアル通信送信用の配列
static char gMainSciString[128];
//------------------------------------------------
// 内部プロトタイプ宣言
//------------------------------------------------
void main_init(void);
//------------------------------------------------
// 概 要:エントリーポイント
//------------------------------------------------
void main(void)
{
// システム初期化処理
main_init();
while(1)
{
unsigned short sen1;
unsigned short sen2;
// 光センサー値取得
Lsen_getSensor(&sen1, &sen2);
// 送信用の文字列の作成
sprintf(gMainSciString, "Sen1:%4d Sen2:%4d\n", sen1, sen2);
// シリアル通信で文字列を送信
Sci_putString(gMainSciString);
Timer_waitTime(1000);
}
}
//------------------------------------------------
// 概 要:システム初期化
//------------------------------------------------
void main_init(void)
{
//---------------------------------------------------
// ウォッチドッグタイマの停止(消さないこと)
//---------------------------------------------------
WDT.TCSRWD.BYTE = 0x92;
WDT.TCSRWD.BYTE = 0x92;
//---------------------------------------------------
// PCRの設定(PCRは書き込み専用レジスタ)
//---------------------------------------------------
IO.PCR6 = 0x11; // P60,P64を出力
IO.PCR7 = 0x00; // P74を入力
//---------------------------------------------------
// モジュールの初期化(各モジュールの初期化を実施)
//---------------------------------------------------
Led_init();
Sw_init();
Timer_init();
Lsen_init();
Sci_init();
}
- stdio.hとsci.hをインクルードする。
- 送信文字列を格納するためのグローバル変数gMainSciStringを定義する。
- main関数ではsprint関数で送信文字列を作成し、Sci_putString関数で送信を行う。
- main_init関数ではSci_init関数の呼び出しを追加する。
Q&A:シリアル通信に関するよくある質問
このエラーはTeraTermが起動している状態でプログラムの書き込みを行おうとした時に表示されるエラーです。手間ではありますがライターでプログラムを書き込む際にはTeraTermの画面は閉じておきましょう。
ライターがシリアル通信で書き込む際にCOMというシリアル通信ポートを開く必要があるのですが、TeraTermがCOMを開いているためライターがCOMを開くことができないことが原因です。
使わないというわけではありません。
クロック同期式の場合はハードウェア上で通信するための信号線を1つ増やす必要があります。そういった点からも少し選択されづらい経緯はあると思います。
まずは、シリアル通信の基本は調歩同期式なのでこちらをマスターするとよいでしょう。