2008年4月23日水曜日

VC++2005/2008 .NET SerialPort を使用する方法

VC++2005/2008 .NET SerialPort を使用する方法

.NET の SerialPort を使用してシリアル送受信する方法を示します。
SerialPort はそのまま使うよりクラス(CUartクラス)にラップするほうがすっきりします。
シリアル受信はスレッドで行い、受信データをデリゲート渡しにしてテキストボックスに表示します。

■■ Uart.h (CUartクラス) ■■■■■■■■■■■■■■■■■■■■■■■
#pragma once
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::IO::Ports; // シリアルポート

#define BUF_SIZE 10

ref class CUart
{
///------------------------------------------------
/// テーブル宣言
///------------------------------------------------
public: SerialPort^ port;
public: System::String^ nowPort;

///------------------------------------------------
/// コンストラクタ
///------------------------------------------------
public:CUart()
{
nowPort = "----"; // 現在ポート名クリア
port = gcnew SerialPort(); // シリアルポートインスタンス化
}
///------------------------------------------------
/// オープン可能ポート調査
///------------------------------------------------
public: System::String^ portInvestigation()
{
String^ na = nowPort;
if( na != "----" ){ // 現在ポートオープン中のとき
port->Close(); // ポートクローズ
}
array<String^>^ ports = SerialPort::GetPortNames(); // 全ポート名取得
ports->Sort( ports ); // 並び替え
char ct = ports->Length::get();

array<String^>^ names = gcnew array<String^>(ct); // 利用可能なポート名取得
char cnt = 0;
for(char ii=0; ii<ct; ii++)
{
if( portOpenTest( ports[ii] ) == true ){ // オープンに成功したとき
names[cnt++] = ports[ii]; // ポート名取得
}
}

char flg = 0;
if( nowPort == "----" ){
flg = 1;
}
for(char ii=0; ii<cnt; ii++)
{
if( names[ii] == nowPort ){ // 現在のポートと同じ時
flg = 1;
}
else{
if( flg == 1 ){
nowPort = names[ii];
break;
}
}
}

if( na == nowPort ){
nowPort = "----";
}
return nowPort;
}
///------------------------------------------------
/// ポートオープンテスト
///------------------------------------------------
private: bool portOpenTest( System::String^ name )
{
port->Close();
bool result = portOpen( name );
port->Close();
return result;
}
///------------------------------------------------
/// ポートオープン
/// SerialPort port = new SerialPort(COM4, 9600, Parity.None, 8, StopBits.One);
///------------------------------------------------
public: bool portOpen( System::String^ name )
{
System::String^ err = "";
port->PortName = name; // ポート名設定
port->BaudRate = 9600;
port->Parity = Parity::None;
port->StopBits = StopBits::One;
try
{
port->Open(); // ポートオープン
}
catch(Exception^ e)
{
err = System::String::Format("{0}",e);
}
if( err == "" ){
return true;
}
else{
return false;
}
}
///------------------------------------------------
/// 送信
///------------------------------------------------
public: void write( array<unsigned char>^ buffer, int offset, int count )
{
port->Write( buffer , offset , count );
}
///------------------------------------------------
/// 受信
///------------------------------------------------
public: void read( array<unsigned char>^ buffer, int offset, int count )
{
System::String^ err = "";
try
{
port->Read( buffer , offset , count );
}
catch(Exception^ e)
{
err = System::String::Format("{0}",e);
}
}
///------------------------------------------------
/// オープンしているかどうか
///------------------------------------------------
public: bool isOpen()
{
return port->IsOpen::get();
}
///------------------------------------------------
};
■■ Form1.h ■■■■■■■■■■■■■■■■■■■■■■■
※必要な部分だけを記載しています。
///------------------------------------------
/// ■インクリュード宣言
///------------------------------------------
#include "Uart.h"
///------------------------------------------
/// ■変数宣言
///------------------------------------------
private: CUart^ uart;
private: array<unsigned char>^ buff;
private: bool RxLoopFlg;
///------------------------------------------
/// ■ボタン1
/// シリアルポート設定(変更)
///------------------------------------------
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
String^ com = uart->portInvestigation();
if( com == "----" ){
textBox1->Text = com;
RxLoopFlg = false;
}
else{
if( uart->portOpen(com) == true ){
textBox1->Text = com;
RxLoopFlg = true;
}
else{
textBox1->Text = "失敗";
RxLoopFlg = false;
}
}
}
///------------------------------------------
/// ■ボタン2
/// シリアル送信(適当な値を送信)
///------------------------------------------
private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
array<unsigned char>^ buff = gcnew array<unsigned char>(BUF_SIZE);
for(char ii=0 ; ii<BUF_SIZE; ii++){
buff[ii] = 0x30 + ii;
}
uart->write( buff , 0 , BUF_SIZE);
}
///------------------------------------------
/// ■フォームロード
///------------------------------------------
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) {
RxLoopFlg = false;
buff = gcnew array<unsigned char>(BUF_SIZE); // シリアル受信バッファ領域宣言
buff->Clear(buff,0,BUF_SIZE); // シリアル受信バッファクリア
uart = gcnew CUart(); // シリアルポートインスタンス
button1_Click(sender,e); // シリアルポート設定(変更)
backgroundWorker1->RunWorkerAsync(); // スレッド開始(シリアル受信)
}
///------------------------------------------
/// ■デリゲート
/// ■テキストボックスセット
///------------------------------------------
delegate void SetTextCallback(array<unsigned char>^ buff);
private: void SetText_A(array<unsigned char>^ buff){
if( this->textBox2->InvokeRequired ){
SetTextCallback^ d = gcnew SetTextCallback(this, &at3200::Form1::SetText_A);
this->BeginInvoke( d, buff );
}
else{
unsigned char bf[BUF_SIZE];
for(char ii=0; ii<BUF_SIZE; ii++){
bf[ii] = buff[ii];
}
buff->Clear(buff,0,BUF_SIZE); // シリアル受信バッファクリア
// this->textBox2->Text += System::Runtime::InteropServices::Marshal::PtrToStringAnsi((IntPtr)bf);
this->textBox2->AppendText(System::Runtime::InteropServices::Marshal::PtrToStringAnsi((IntPtr)bf));
}
}
///------------------------------------------
/// ■スレッド1
///------------------------------------------
private: System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
while(1){
if( RxLoopFlg == true ){
if( uart->isOpen() == true ){
uart->read( buff , 0 , BUF_SIZE ); // シリアル受信
SetText_A( buff );
}
else{
}
}
else{
}
}
}
■■ Form1.h の終わり ■■■■■■■■■■■■■■■■■■

8 件のコメント:

匿名 さんのコメント...

このプログラムをビルドすると
error C2653: 'at3200' : 識別子がクラス名でも名前空間名でもありません。
error C2276: '&' : 仮想関数のアドレスを取ろうとしました。
error C3350: '通信::Form1::SetTextCallback' : delegate コンストラクタには 2 つの引数が必要です
というエラーが出てきてしまうのですが…

優瞬会 さんのコメント...

"at3200"のところをを自分のプロジェクト名に変更してみてください。

匿名 さんのコメント...

現在、受信したデータをリアルタイムで波形表示させるプログラムを作成しております。参考にさせて頂いたもので、1チャンネル分については、問題なく動作したので、非常に助かりました。ありがとうございました。
次の課題として、複数チャンネル(複数ポート)で使用したいのですが、シリアルポートインスタンス化のところで複数定義(port1、port2など)すれば良いのでしょうか。

優瞬会 さんのコメント...

複数化に必要なものをざっと調べてみましたので参考にしてください。

(1)変数・クラス
private: CUart^ uartA;
private: CUart^ uartB;

private: bool RxLoopFlgA;
private: bool RxLoopFlgB;

(2)インスタンス
uartA = gcnew CUart(button6); // シリアルポートインスタンス
uartB = gcnew CUart(button7); // シリアルポートインスタンス

(3)スレッドの
backgroundWorker1->RunWorkerAsync(); // スレッド開始(シリアル受信)
backgroundWorker2->RunWorkerAsync(); // スレッド開始(シリアル受信)

(4)デリゲート
private: void SetText_A( void ){
中身省略
}
private: void SetText_B( void ){
中身省略
}

(5)スレッド処理
private: System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
中身省略
}
private: System::Void backgroundWorker2_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
中身省略
}

(6)送信ボタン処理
private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
中身省略
}
private: System::Void button3_Click(System::Object^ sender, System::EventArgs^ e) {
中身省略
}

匿名 さんのコメント...

複数チャンネル(複数ポート)の同期表示は可能ですか?

優瞬会 さんのコメント...

複数チャンネル(複数ポート)の同期表示は、可能です。

匿名 さんのコメント...

同期表示について、ご教授をお願い致します。
現在のプログラムですと、backgroundWorkerを利用して、シリアル受信と画面表示を同じスレッド内にて行っていると思いますが、
他ポートから得られたデータを、時間軸を同じにしてデータを表示する(同期表示する)場合には、
このbackgroundWorkerスレッドにおいてそれぞれのポートのデータ受信のみ行い、
別スレッド(timerスレッド等)で、画面表示を行う方法が望ましいのでしょうか?

優瞬会 さんのコメント...

そうです。
タイマーで各チャネルの受信データを表示させれば同期表示が簡単にできると思います。