amazon

17.4.11

シリアルポート送信とその注意


CまたはC++でtermiosを使ってシリアル送信を試みた。
クラスを用いてオブジェクトを作ってみた。
始めうまく行かなかった原因が分かったのでメモ。

マシンはmacだが、シリアル接続に使っているのはELECOM製UC-SGT。これでUSB to シリアルケーブルしている。
UC-SGTには、macのドライバが付属していないが、別途使えるようにする方法があるので、mac UC-SGTなどとぐぐってみるとよい。

シリアル送受信の接続確認は
windowsだとTeratermなどが簡単だが、
macならターミナルでscreenかcuを使うとよい。

screenは林檎生活100さんのサイトを参照した。

接続する際のportのパスは、
/dev/cu.usbserial
または
/dev/tty.usbserial
僕の場合はどちらで接続できた。

ここから本題。
稚拙だがC++で接続するプログラムを書いてみた。

termiosの設定については、
FreeBSD 日本語マニュアル検索 (jman/japropos/jwhatis)

termios(4) Mac OS X Manual Page
を参照。

//serialsend.h
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>

#define BAUDRATE B9600
#define PORT "/dev/cu.usbserial"

class SerialPort{
private:
int fd;
struct termios oldtio,newtio;
public:
SerialPort();
int open(const char*);
int send(const char*, int);
void close(void);
};

//serialsend.cpp
/*シリアルポートに信号を送るプログラム*/
#include "serialsend.h"

SerialPort::SerialPort(){ //コンストラクタ
memset(&newtio, 0, sizeof(newtio));//ポート設定の初期化

cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);

newtio.c_iflag |= IGNPAR;

newtio.c_oflag |= OPOST;
newtio.c_oflag &= ~ONLCR;

newtio.c_cflag |= CS8;
newtio.c_cflag |= CSIZE;
newtio.c_cflag &= ~CSTOPB;
newtio.c_cflag |= CREAD;
newtio.c_cflag &= ~PARENB;
newtio.c_cflag &= ~PARODD;
newtio.c_cflag |= HUPCL;
newtio.c_cflag |= CLOCAL;

newtio.c_lflag |= ICANON;

newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
}

int SerialPort::open(const char* port){
//モデムデバイスを開く
fd = ::open(port, O_RDWR | O_NOCTTY | O_NDELAY);

//モデムデバイスが開けたかチェック
if(fd<0){
std::cerr<<"Error! "<<port<<" can not be opend."<<std::endl;
return -1;
}else{
fcntl(fd, F_SETFL, 0);
std::cout<<"Serial port fd="<<port<<" was opened."<<std::endl;
}

tcgetattr(fd,&oldtio); // 現在のポート設定を待避
tcflush(fd, TCIOFLUSH); //受信したが読み込んでいないデータ、および書き込んだが送信していないデータの両方を消去
tcsetattr(fd,TCSANOW,&newtio);
send("Port initiated. ",16); //ポートの初期化に一度何かを送っておくと調子がよさそう
return fd;
}

int SerialPort::send(const char* buf, int size){
int rt = write(fd, buf, size);

if(rt == size) {
std::cout<<buf[0]<<"... was sent."<<std::endl;
}else if(rt < 0){
std::cerr<<"Error! "<<buf[0]<<"... was not sent."<<std::endl;
}else{
std::cerr<<"Error! "<<buf[0]<<"... was partially sent."<<std::endl;
}

usleep(20000); //送信完了までの待ち時間

return rt;
}

void SerialPort::close(void){
tcsetattr(fd,TCSANOW,&oldtio);
::close(fd);
}

//main.cpp
#include "serial_send.h"

int main(void){
SerialPort a;
a.open(PORT);
a.send("something",9);
a.close();
return 0;
}

正しく送信するには、赤字の行が必須のようである。
試しにその行をコメントアウトすると、送信できないか、送信はできるが文字化けした。
usleep(20000)は20 msであるが、10 msだとほどんどの場合文字化けが起こり送信が正しく行われなかった。

シリアルポートではもっと細かい時間制御はできないのであろうか。
理論上1µsくらいまでいけそうだと思うのだが。
とにかく、上のプログラムでデータ送信を行う場合は処理に20 ms以上かかることを考慮に入れて設計をしなければならない。

12.4.11

C++ std::vectorのメソッドinsertでの注意


<メモ>
insertするとイテレータが無効になる

std::vector<std::string> vec;
vec.push_back("文字列1");
vec.push_back("文字列2");
vec.push_back("文字列3");
と作ったvectorオブジェクトvec = 文字列1, 文字列2, 文字列3(この順になっている)
の3要素目に文字列4を挿入して、vec = 文字列1, 文字列2, 文字列4, 文字列3 としたい。

std::vector<std::string>::iterator it = vec.begin(); //イテレータの初期化 この時点でitには文字列1の先頭アドレスが入る
for (int i = 0; i < 2; i++) ++it; //イテレータを2つ進める itには文字列3の先頭アドレス
vec.insert(it , "文字列4"); //文字列4の挿入
これで完成。
で、ここでこの後に
++it;
vec.insert(it , "文字列5");
とやると、4要素目には挿入できない。

4.4.11

char型の配列番号が変わってしまう現象


キーボード入力した一文字を取得して、char型配列に格納するプログラムを作っていたのだが、
変数ではなく配列の要素番号が変わってしまう現象が起きた。

#include <iostream>

class Obj{
  private:
    char c[10];
    int i;
  public:
    Obj(void);
    void func(void);
};

Obj::Obj(){
   i = 0;
}

void Obj::func(void){
   std::cout<<"i = "<<i<<std::endl;
   c[i] = 'a';
   std::cout<<"i = "<<i<<std::endl;
}

int main (void ){
   Obj obj;
   obj.func();
   return 0;
}
これを実行すると
$ ./a.out
i = 0
i = 97
となる。
i はいじってないのにと、思ったが、
原因は、char c[]の部分。
これをちゃんとchar c[10]とかにしておけば、よし。