Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

WSLで制御コードのCRを送信するにはどうすればよいですか

解決したいこと

WSLでアスキーベースのシリアル通信で、パソコンから分析機器にCRを送信したいです。

発生している問題・エラー

https://atmarkit.itmedia.co.jp/ait/articles/1711/09/news032_3.html
より、WSLでEnterキーを押すとLFが送信されるのではないかと思います。
CRを送信する方法がわかりません。

追記 2022/01/28

目標
CMO,1[CR]と送信して、[ACK][CR][LF]と返事をもらいたい。

コマンドと実行結果

an@DESKTOP-HGVMRCT:~/demo$ stty -F /dev/ttyS1 speed 9600
1200
an@DESKTOP-HGVMRCT:~/demo$ stty -F /dev/ttyS1 speed 9600
9600
an@DESKTOP-HGVMRCT:~/demo$ stty -icrnl
an@DESKTOP-HGVMRCT:~/demo$ ./ssample
シリアルポートを初期化しました
メインループを開始
CMO,1^M

ssample.c

#include <stdio.h>
#include <stdlib.h> //追加
#include <unistd.h>
//#include <conio.h> 無い
#include <ctype.h> //追加
#include <fcntl.h> //追加
#include <string.h>
#include <termios.h> //追加
//#include <io.h> 無い

#define DEV_NAME "/dev/ttyS1"
#define BAUD_RATE B9600
#define BUFF_SIZE 2048

#define NUL 0x00
#define ETX 0x03
#define ENQ 0x05
#define ACK 0x06
#define CR 0x0D
#define LF 0x0A
#define BS 0x08
#define TAB 0x09
#define NAK 0x15
#define ESC 0x1b

char buf[BUFF_SIZE], send_buf[BUFF_SIZE];

void serial_init(int fd)
{
    // termios型の構造体tioを宣言
    struct termios tio;
    memset(&tio, 0, sizeof(tio));
    tio.c_iflag = 0;
    tio.c_oflag = 0;
    //文字サイズ・受信を有効にする・モデムの制御線を無視
    tio.c_cflag = CS8 | CREAD | CLOCAL;
    tio.c_lflag = 0;
    //最小文字数
    tio.c_cc[VMIN] = 1;
    //タイムアウト時間
    tio.c_cc[VTIME] = 0;
    //入力ボーレートの設定
    cfsetispeed(&tio, BAUD_RATE);
    //出力ボーレートの設定
    cfsetospeed(&tio, BAUD_RATE);
    //デバイスに設定をする
    tcsetattr(fd, TCSANOW, &tio);
}

int main(int argc, char **argv)
{
    int fd; //ファイルディスクリプタ
    unsigned char c;

    //デバイスファイル(シリアルポート)オープン
    fd = open(DEV_NAME, O_RDWR); 
    // fd = open(DEV_NAME, O_RDWR | O_NONBLOCK);

    if (fd < 0)
    {
        printf("デバイスを開いているときにエラーが発生しました\n");
        exit(1);
    }

    serial_init(fd); //シリアルポートの初期化
    printf("シリアルポートを初期化しました\n");

    printf("メインループを開始\n");

    //返り値が1の間繰り返す
    while (1)
    {
        // if new data is available on the serial port, print it out
        // 受信
        if (read(fd, &buf[0], BUFF_SIZE) > 0)
        {
            write(STDOUT_FILENO, &buf[0], BUFF_SIZE);
            printf("\x1b[32m"); //前景色を緑に

            if (isprint(c)) //印字可能な文字の場合
                printf("%02x", c); //受信データを16進数で表示

            else //印字できない文字の場合
            {
                switch (c)
                {
                case NUL:
                    printf("[NUL]\n");
                    break;
                case ETX:
                    printf("[ETX]\n");
                    break;
                case ENQ:
                    printf("[ENQ]\n");
                    break;
                case ACK:
                    printf("[ACK]\n");
                    break;
                case CR:
                    printf("[CR]\n");
                    break;
                case LF:
                    printf("[LF]\n");
                    break;
                case BS:
                    printf("[BS]\n");
                    break;
                case TAB:
                    printf("[TAB]\n");
                    break;
                case NAK:
                    printf("[NAK]\n");
                    break;
                default:
                    printf("[%x]\n", c);
                }
            }

            // if new data is available on the console, send it to the serial port
            // 送信
            if (read(STDIN_FILENO, &send_buf[0], BUFF_SIZE) > 0)
            {
                write(fd, &send_buf[0], BUFF_SIZE);
                printf("\x1b[37m"); //前景色を白に

                if (isprint(c))
                    printf("%c", c);

                switch (c)
                {
                case LF: //改行で送信「CR」 →「LF」に変更
                    write(fd, &send_buf[0], BUFF_SIZE);
                    printf("[[LF]]\n");
                    break;
                case CR: //改行で送信
                    write(fd, &send_buf[0], BUFF_SIZE);
                    printf("[[CR]]\n");
                    break;
                case ESC: //エスケープで終了
                    exit(-1);
                default:
                    write(fd, &send_buf[0], BUFF_SIZE);
                }
            }
        }
    }
    close(fd);
    return(0);
}

追記 2022/01/29

cmo,1^V^M^V^Jと入力し、63 6d 6f 2c 31 0d 0aと送信、06 0d 0aと受信できた。

an@DESKTOP-HGVMRCT:~/demo$ stty -icrnl
an@DESKTOP-HGVMRCT:~/demo$ stty -F /dev/ttyS1 speed 9600
9600
an@DESKTOP-HGVMRCT:~/demo$ ./ssample
シリアルポートを初期化しました
メインループを開始
-----------------------------------------------------------
cmo,1^V^M^V
63 6d 6f 2c 31 0d 0a
06 0d 0a

ssample.c [改]

#include <stdio.h>
#include <stdlib.h> // exit
#include <unistd.h> // read write
#include <fcntl.h> // open
#include <string.h>
#include <termios.h> // struct termios

#define DEV_NAME "/dev/ttyS1"
#define BAUD_RATE B9600
#define BUFF_SIZE 2048

char buf[BUFF_SIZE], send_buf[BUFF_SIZE];

void serial_init(int fd)
{
    // termios型の構造体tioを宣言
    struct termios tio;
    memset(&tio, 0, sizeof(tio));
    tio.c_iflag = 0;
    tio.c_oflag = 0;
    //文字サイズ・受信を有効にする・モデムの制御線を無視
    tio.c_cflag = CS8 | CREAD | CLOCAL;
    tio.c_lflag = 0;
    //最小文字数
    tio.c_cc[VMIN] = 1;
    //タイムアウト時間
    tio.c_cc[VTIME] = 0;
    //入力ボーレートの設定
    cfsetispeed(&tio, BAUD_RATE);
    //出力ボーレートの設定
    cfsetospeed(&tio, BAUD_RATE);
    //デバイスに設定をする
    tcsetattr(fd, TCSANOW, &tio);
}

int main(int argc, char **argv)
{
    int fd; //ファイルディスクリプタ
    unsigned char c;

    //デバイスファイル(シリアルポート)オープン
    fd = open(DEV_NAME, O_RDWR);
    // fd = open(DEV_NAME, O_RDWR | O_NONBLOCK);

    //シリアルポートが開けない場合
    if (fd < 0)
    {
        printf("デバイスを開いているときにエラーが発生しました\n");
        exit(1);
    }

    //シリアルポートの初期化
    serial_init(fd); 
    printf("シリアルポートを初期化しました\n");

    printf("メインループを開始\n");
    printf("-----------------------------------------------------------\n");
    while (1)
    {

        // 送信
        size_t readBytes;

        readBytes = read(STDIN_FILENO, &send_buf[0], BUFF_SIZE);
        if (readBytes > 0)
        {
            size_t i;

            printf("\x1b[36m"); //前景色をシアンに

            for (i = 0; i < readBytes; i++)
            {
                write(fd, &send_buf[i], BUFF_SIZE);
                printf("%02x ", send_buf[i]);
            }
            printf("\x1b[39m\n"); //前景色をデフォルトに
        }

        // 受信
        size_t receiveBytes;

        receiveBytes = read(fd, &buf[0], 3);
        if (receiveBytes > 0)
        {
            size_t i;

            printf("\x1b[32m"); //前景色を緑に

            for (i = 0; i < receiveBytes; i++)
            {
                printf("%02x ", buf[i]);
            }
            printf("\x1b[39m\n"); //前景色をデフォルトに
        }
    }
    close(fd);
    return (0);
}
0

3Answer

CRを入力するには^V^M
LFを入力するには^V^J
ただしWindows Terminalだとデフォルトの設定で^Vがクリップボードからの貼り付けにバインドされているので、このバインドを削除する必要があります。

Windows Terminalの設定→操作(「操作」が2つありますがキーボードアイコンのほう)→ctrl+vがバインドされているのを探してマウスをホバーして出てくる鉛筆アイコンをクリックしてゴミ箱アイコンをクリックして右下の保存ボタンを押す

0Like

Comments

  1. @anne_daigaku

    Questioner

    ご回答ありがとうございます。
    Windows Terminalを入れていなくて、Ubuntuとgtktermで操作しようとしてうまくいってなかったので、Windows Terminalを入れてみようと思います。
  2. @anne_daigaku

    Questioner

    Windows Terminalの Ubuntuタブに ^V^M と ^V^J を入力すると、どちらも ^V だけ表示されてカーソルが一段下に下がりました。これはCRまたはLFを送信できていると判断して大丈夫でしょうか?(機器から返事が来ないのですがそれはまた別の問題ですよね)
  3. うーん、私のところだと表示は ^M になるので、何か違うかも…

    cat | od -t x1

    で、^V^M を含めて適当に入力して ^D^D で入力を終わらせて、^V^M を入力したところに 0d が出てるか見てみてください。
  4. @anne_daigaku

    Questioner

    CMO,1^V^Mb^D^D と入力したら以下のようになりました。0a になっています。

    an@DESKTOP-HGVMRCT:~$ cat | od -t x1
    CMO,1^V
    b0000000 43 4d 4f 2c 31 0a 62
    0000007
  5. stty --all
    を実行して、lnext = ^V になっているかどうか見てください。
    違っていたら、
    stty lnext ^V
    を実行してみてください。このときの ^V は ^ と V の2文字で書いていいです。
  6. @anne_daigaku

    Questioner

    lnext = ^V になっていました。

    an@DESKTOP-HGVMRCT:~/demo$ stty --all
    speed 38400 baud; rows 30; columns 120; line = 0;
    intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S;
    susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
    -parenb -parodd -cmspar cs8 hupcl -cstopb cread -clocal -crtscts
    -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
    opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
  7. うーん、おかしいですね。

    もう一度 Windows Terminal の設定を見て、ctrl+v がバインドされていないか確認してみてください。また、削除はしたけど保存していなかった可能性があるかもしれません。保存して Windows Terminal を再起動してみてください。

    それでも ^V で制御文字を入力できないようなら、
    stty lnext ^X
    にして、^X^M でCRを入力できないか、cat | od -t x1 で見てみてください。
    もちろん ctrl+x が Windows Terminal でバインドされていないことも確認してください。
  8. @anne_daigaku

    Questioner

    ①プログラムを実行していないときに ^V^M を入力したら、^M と表示されました。

    ②設定を確認して再起動しました。

    cat | od -t x1 では相変わらず 0a でした。

    an@DESKTOP-HGVMRCT:~/demo$ cat | od -t x1
    CMO,1^V
    0000000 43 4d 4f 2c 31 0a
    0000006

    ③stty lnext ^X しました。

    ^X^M は 0aでした。

    an@DESKTOP-HGVMRCT:~/demo$ cat | od -t x1
    CMO,1^X
    0000000 43 4d 4f 18 0a
    0000005
  9. ^Vを打ったら

    ^

    となって、続いて^Mを打ったら、改行しないで

    ^M

    となるはずなんです。うーんおかしいですね。

    何かわかったらまたコメントします。
  10. @anne_daigaku

    Questioner

    丁寧に教えていただき本当にありがとうございました!よろしくお願いします。
  11. @anne_daigaku

    Questioner

    sttyのオプションの「入力の CR を LF に変換する(icrnl)」がオンになっていました。
    再起動するとオンに戻ってしまい不便ですが、オフにすると 0d を入力できるようになりました。

    an@DESKTOP-HGVMRCT:~/demo$ stty -icrnl
    an@DESKTOP-HGVMRCT:~/demo$ cat | od -t x1
    CMO,1^V^M0000000 43 4d 4f 2c 31 0d
    0000006
  12. なるほど、それで解決ですかね。目的の機器へのCR送信もできましたか?

    ログインシェルがbashなら ~/.bash_profile に、zshなら ~/.zprofile に
    stty -icrnl
    を書いておけば再起動/再ログイン時も大丈夫かと。
  13. @anne_daigaku

    Questioner

    CMO,1[CR]と入力して、[ACK][CR][LF]と返事をもらいたいのですが、返事の受信ができていないので送信ができているかわからない状態です……
    Windows2000と別のプログラムで送受信できているので、機器側の問題ではないと思います。
    コマンドと実行結果とプログラムを質問本文の方に追加したので、もし気づいたことがあれば教えていただけると助かります。


    CRの入力は問題なくなりました!

コメントではマークアップできないので新しい回答として書きます。

#include <stdio.h>
#include <unistd.h>

#define BUFF_SIZE 2048
char buf[BUFF_SIZE], send_buf[BUFF_SIZE];

int main(int argc, char **argv)
{
    while (1)
    {
        size_t readBytes;
        
        readBytes = read(STDIN_FILENO, &send_buf[0], BUFF_SIZE);
        if (readBytes > 0)
        {
            size_t i;
            
            printf("\x1b[32m");
            for (i = 0; i < readBytes; i++) {
                printf("%02x ", send_buf[i]);
            }
            printf("\x1b[37m\n");
        }
    }
    return 0;
}

これを試してみてください。
STDIN_FILENOが端末の場合、readは改行(ENTER)を入力しないと帰ってこないので、たとえばabc[ENTER]と入力すると、緑文字で 61 62 63 0a と表示されると思います。
そこでabc^V^M[ENTER]を入力するとどうでしょうか?
61 62 63 0d 0aと表示されるでしょうか?

0Like

Comments

  1. @anne_daigaku

    Questioner

    abc[ENTER] と入力すると白文字で「abc^M」
    abc^V^M[ENTER] を入力すると白文字で「abc^V^M^M」
    abc^V^M^V^J と入力すると白文字で「abc^V^M^V」改行をはさんで緑文字で「61 62 63 0d 0a」と返ってきました。

  2. 白文字で「abc^M」のような出力はこのコードからは出ないはずなので、それは端末がエコーバックしているしているものっぽいですね。Windows Terminalではなく別の端末でしょうか?

    そしてどうやら ^J でようやくreadから帰ってきているようですね。
    ^V^M でCR (0d) がバッファに入っていることは確認できましたね。
    もう一歩です。
  3. @anne_daigaku

    Questioner

    Windows Terminal の Ubuntu-20.04 を使っているつもりでした……新しい投稿にスクリーンショットを載せました。
    WSLのバージョンは1で、Windows10Proです。
  4. @anne_daigaku

    Questioner

    あ、エコーバックは問題ではないですね。①送信バッファの中身をwriteでデバイスファイルに書き込んで、②返事を受信バッファに入れて、③受信バッファの中身を端末に書き込んでいく進め方で合っていますか?
  5. まあ合っていると思います。

    もしLFを送る必要が無いのなら、LFはCRに置き換えてしまうのが簡単ですよね。
    sttyでどうこうする必要も無かったわけです。
    上記の私のコードで
    CMO,1[ENTER]
    をタイプして、
    43 4d 4f 2c 31 0a
    と表示されるなら、プログラムで 0a を 0d に変えて送信すれば良いということになります。

    LFは0aのまま、CRは0dのまま送信したいなら、sttyの設定で ^V^M ^V^J を期待どおりに入力できるようにしておけば、それも可能なわけですが
    CMO,1^V^M[ENTER]
    では
    43 4d 4f 2c 31 0d 0a
    となると思いますので、最後の0aは除いて送信するという手当てが必要です。


    ただ、端末との通信では、いずれにしても、おそらく次の問題があると思います。

    readにしろgetcharにしろなんにしろ、それらはユーザーが[ENTER]を押すまで待ってしまいます。
    ということは、その間に機器からデータが来ても表示できないことになります。
    それではあまり良くないのではないですか?
    それを解決するにはリアルタイムキー入力の仕組みを入れる必要があります。
    そのあたりはどうなんでしょうか?
  6. @anne_daigaku

    Questioner

    マニュアルに
    Transmit: CMO[,x] <CR>[<LF>]
    Receive: <ACK><CR><LF>
    とあるのですが、CRまたはCRLFを送る必要があるということですよね。
    この機器は現在RS232Cで制御して使っていて、変換ケーブルでUSBシリアル通信に換えてOSもwslに変えて通信出来るか確かめるところまでが私の目標です。私はLF単体を送る必要無さそうですが、将来的には使いそうです。
    リアルタイムキー入力の仕組みも将来的には必要になりそうです。初心者に使えそうなものであれば挑戦してみます。
  7. @anne_daigaku

    Questioner

    いただいたプログラムを参考に作ったプログラム(本文に追加しました)で、「63 6d 6f 2c 31 0d 0a」と送信、「06 0d 0a」と受信できました。解決です!

Your answer might help someone💌