【genius_platform软件平台开发】第九十三讲:串口通信(485通信)

1. 485通信

  • 在进行代码撰写前先熟悉这个串口termios结构体

1.1 termios结构

  • termios结构定义于<termios.h>头文件中,描述了提供的通用终端接口,用于控制异步通信端口。该文件中还定义了一组函数调用,通过该结构体函数调用即可对终端进行一系列的设置。对于termios结构而言,可以简单地将其理解为是一个用于描述一个终端属性的结构体

  • struct termios 结构用来持有所有的当前线路设置, 给这个 tty 设备的一个特定端口. 这些线路设置控制当前波特率, 数据大小, 数据流控设置。

  • termios结构如下:

#include <termios.h>
struct termios {
	tcflag_t c_iflag;
	tcflag_t c_oflag;
	tcflag_t c_cflag;
	tcflag_t c_lflag;
	cc_t c_cc[NCCS];
}

该结构的5个成员分别代表了:

 输入模式
 输出模式
 控制模式
 本地模式
 特殊控制字符

关于函数调用,这篇笔记中先描述两个:

1.2 头文件

#include <termios.h>

1.3 函数讲解

1.3.1 tcgetattr

int tcgetattr(int fd, struct termios *termios_p);
  • 函数tcgetattr初始化的作用,它将一个终端与一个termios结构关联起来,然后用termios_p指针指向这个termios结构。成功调用后,termios_p所指向的结构中就保存着指定终端当前的属性。

1.3.2 tcsetattr

int tcsetattr(int fd, int actions, const struct termios *termios_p);
  • 函数tcsetattr一般用于重新配置终端,它会将指定的终端配置成第三个指针参数所指向的termios结构。至于第二个参数actions用于调整tcsetattr函数行为上的一些细节,它有三个可选值:
TCSANOW:立刻进行修改。
TCSADRAIN:等当前的输出完成后再进行修改
TCSAFLUSH:等当前的输出完成后再进行修改,但丢弃还未从read调用返回的当前可用的任何输入
  • 关于修改终端属性的代码一般有如下的结构:
struct termios initset, newset;
tcgetattr(fd , &initset);
newset = initset;
.......
tcsetattr(int fd , actions , &initset);
exit(0);
  • Linux 为我们提供了专门的设置修改参数的接口如下:
NAME
       termios,  tcgetattr,  tcsetattr,  tcsendbreak, tcdrain, tcflush, tcflow, cfmakeraw, cfgetospeed, cfgetispeed, cfsetispeed, cfsetospeed, cfsetspeed - get and set terminal attributes, line con‐
       trol, get and set baud rate

SYNOPSIS
       #include <termios.h>
       #include <unistd.h>

       int tcgetattr(int fd, struct termios *termios_p);

       int tcsetattr(int fd, int optional_actions,
                     const struct termios *termios_p);

       int tcsendbreak(int fd, int duration);

       int tcdrain(int fd);

       int tcflush(int fd, int queue_selector);

       int tcflow(int fd, int action);

       void cfmakeraw(struct termios *termios_p);

       speed_t cfgetispeed(const struct termios *termios_p);

       speed_t cfgetospeed(const struct termios *termios_p);

       int cfsetispeed(struct termios *termios_p, speed_t speed);

       int cfsetospeed(struct termios *termios_p, speed_t speed);

       int cfsetspeed(struct termios *termios_p, speed_t speed);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       cfsetspeed(), cfmakeraw(): _BSD_SOURCE

tcsetattr()

  • 从termios_p引用的termios结构中设置与终端关联的参数(除非需要不可用的基础硬件的支持)。 optional_actions指定更改何时生效:
    TCSANOW:更改立即发生。
    TCSADRAIN:在传输完所有写入fd的输出之后,将发生更改。 更改影响输出的参数时,应使用此功能。
    TCSAFLUSH:更改发生在所有写入fd引用的对象的输出已发送之后,并且在进行更改之前,已接收但未读取的所有输入将被丢弃。

原始模式
cfmakeraw()将终端设置为类似于旧版本7终端驱动程序的“原始”模式:逐字符可用输入,禁用回显,并且禁用终端输入和输出字符的所有特殊处理。 终端属性设置如下:

 termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
 termios_p->c_oflag &= ~OPOST;
 termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
 termios_p->c_cflag &= ~(CSIZE | PARENB);
 termios_p->c_cflag |= CS8;
 Line control
       tcsendbreak() transmits a continuous stream of zero-valued bits for a specific duration, if the terminal is using asynchronous serial data transmission.  If duration  is  zero,  it  transmits
       zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds.  If duration is not zero, it sends zero-valued bits for some implementation-defined length of time.

       If the terminal is not using asynchronous serial data transmission, tcsendbreak() returns without taking any action.

       tcdrain() waits until all output written to the object referred to by fd has been transmitted.

       tcflush() discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of queue_selector:

       TCIFLUSH
              flushes data received but not read.

       TCOFLUSH
              flushes data written but not transmitted.

       TCIOFLUSH
              flushes both data received but not read, and data written but not transmitted.

       tcflow() suspends transmission or reception of data on the object referred to by fd, depending on the value of action:

       TCOOFF suspends output.

       TCOON  restarts suspended output.

       TCIOFF transmits a STOP character, which stops the terminal device from transmitting data to the system.

       TCION  transmits a START character, which starts the terminal device transmitting data to the system.

       The default on open of a terminal file is that neither its input nor its output is suspended.

 Line speed
       The  baud  rate  functions are provided for getting and setting the values of the input and output baud rates in the termios structure.  The new values do not take effect until tcsetattr() is
       successfully called.

       Setting the speed to B0 instructs the modem to "hang up".  The actual bit rate corresponding to B38400 may be altered with setserial(8).

       The input and output baud rates are stored in the termios structure.

       cfgetospeed() returns the output baud rate stored in the termios structure pointed to by termios_p.

       cfsetospeed() sets the output baud rate stored in the termios structure pointed to by termios_p to speed, which must be one of these constants:

            B0
            B50
            B75
            B110
            B134
            B150
            B200
            B300
            B600
            B1200
            B1800
            B2400
            B4800
            B9600
            B19200
            B38400
            B57600
            B115200
            B230400

       The zero baud rate, B0, is used to terminate the connection.  If B0 is specified, the modem control lines shall no longer be asserted.  Normally, this will disconnect the line.  CBAUDEX is  a
       mask for the speeds beyond those defined in POSIX.1 (57600 and above).  Thus, B57600 & CBAUDEX is nonzero.

       cfgetispeed() returns the input baud rate stored in the termios structure.

       cfsetispeed()  sets  the input baud rate stored in the termios structure to speed, which must be specified as one of the Bnnn constants listed above for cfsetospeed().  If the input baud rate
       is set to zero, the input baud rate will be equal to the output baud rate.

       cfsetspeed() is a 4.4BSD extension.  It takes the same arguments as cfsetispeed(), and sets both input and output speed.

1.4 示例工程

  • dataType.h
#ifndef DATE_TYPE_H
#define DATE_TYPE_H

#include <stdio.h>

typedef unsigned char ubyte;
typedef char          BYTE;
typedef unsigned short ushort;
typedef unsigned int uint32;



#endif
  • rs485Service.h
#ifndef __RS485_SERVER_H__
#define __RS485_SERVER_H__

#include "dataType.h"

typedef enum
{
    OPEN_485_SUCCESS = 0,
    OPEN_485_FAIL,  
}Open485State;

class Rs485Service
{
    
private:
    Rs485Service();
    ~Rs485Service();
    
public:
    static Rs485Service& Get();
    
public:
    int Rs485Read(ubyte *buf, uint32 size);
    int Rs485Write(const ubyte *data, uint32 len);
    int InitRs485Dev(uint32 bound);
    void UninitRs485Dev();
    
private:
    int m_devFd;
    int m_bound;
    int32_t m_485WriteState; //true 485处于写状态
};

#endif
  • rs485Service.cpp
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <netinet/in.h>
#include "rs485Service.h"

#define RS485COM "/dev/ttyS1"

Rs485Service::Rs485Service()
{
    m_devFd = -1;
}

Rs485Service::~Rs485Service()
{
}

Rs485Service& Rs485Service::Get()
{
    static Rs485Service Rs485server;
    return Rs485server;
}

int Rs485Service::InitRs485Dev(uint32 bound)
{
    system("/usr/local/bin/amba_debug -g 71 -d 1");

    m_devFd = open(RS485COM, O_RDWR | O_NOCTTY); //加上O_NDELAY,就变为了非阻塞
    if (m_devFd != -1)
    {
        struct termios cfg;

        memset(&cfg, 0, sizeof(cfg));
        tcgetattr(0, &cfg);
        switch(bound)
        {
            case 19200:
                cfsetispeed(&cfg, B19200);
                cfsetispeed(&cfg, B19200);
                m_bound = 19200;
                break;
            case 9600:
                cfsetispeed(&cfg, B9600);
                cfsetispeed(&cfg, B9600);
                m_bound = 9600;
                break;
            default:
                cfsetispeed(&cfg, B19200);
                cfsetispeed(&cfg, B19200);
                m_bound = 19200;
                break;
        }
        cfg.c_oflag &= ~(ONLCR);
        cfg.c_oflag &= (OCRNL);
        cfg.c_iflag &= (INLCR);

        cfg.c_cflag |= CLOCAL | CREAD; //使能串口输入
        //cfg.c_lflag |= ICANON; //标准模式
        cfg.c_lflag &= ~ICANON;//原始模式
        cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

        //8bit数据
        cfg.c_cflag &= ~CSIZE;
        cfg.c_cflag |= CS8;
        //1bit停止位
        cfg.c_cflag &= ~CSTOPB;
        //无校验
        cfg.c_cflag &= ~PARENB;
        //禁用硬件流控制:
        cfg.c_cflag &= ~CRTSCTS;

      //  cfg.c_cc[VTIME] = 1; //设置超时时间,如果采用非阻塞模式则不设置
        cfg.c_cc[VMIN] = 1;     //设置最小接收的数据长度

        //清楚输入输出缓冲区
        tcflush(m_devFd, TCIOFLUSH);
        tcsetattr(m_devFd, TCSANOW, &cfg);
        return OPEN_485_SUCCESS;
    }
    else
    {
        printf("open dev err.\n");
        return OPEN_485_FAIL;
    }
}

void Rs485Service::UninitRs485Dev()
{
    if (m_devFd != -1)
    {
        struct termios cfg;
        memset(&cfg, 0, sizeof(cfg));
        tcgetattr(0, &cfg);
        cfg.c_cc[VTIME] = 1;
        tcflush(m_devFd, TCIOFLUSH);
        tcsetattr(m_devFd, TCSANOW, &cfg);
        close(m_devFd);
    }
}

int Rs485Service::Rs485Read(ubyte* buf, uint32 size)
{
    int rlen = -1;
    if (m_devFd != -1)
    {
        rlen = read(m_devFd, buf, size);
        tcflush(m_devFd, TCIOFLUSH);
    }
    return rlen;
}

int Rs485Service::Rs485Write(const ubyte* data, uint32 len)
{
    int wlen = -1;

    //485是半双工,置为发送状态
    system("/usr/local/bin/amba_debug -g 71 -d 1");
    if (m_devFd != -1)
    {
        wlen = write(m_devFd, data, len);
    }

   //等待数据输出完毕
    tcdrain(m_devFd);
    //清空输入输出缓冲区
    tcflush(m_devFd, TCIOFLUSH);
    //485是半双工,置为接收状态
    system("/usr/local/bin/amba_debug -g 71 -d 0");

    return wlen;
}

  • main.cpp
#include <iostream>
#include <string>
#include <cstring>
#include <thread>
#include <chrono>
#include "rs485Service.h"

int main()
{
    auto &ser = Rs485Service::Get();
    ser.InitRs485Dev(9600);

    std::thread r([&ser]()
                  {
                    const int SIZE = 1024;
                      char buf[SIZE] = {};
                      while (true)
                      {
                          std::this_thread::sleep_for(std::chrono::seconds(1));
                          std::memset(buf, 0, SIZE);
                          ser.Rs485Read((unsigned char *)buf, SIZE);
                          std::cout << "read: " << buf << std::endl;
                      } });

    std::thread w([&ser]()
                  {
                      char buf[16] = {};
                      while (true)
                      {
                          std::this_thread::sleep_for(std::chrono::seconds(1));
                          buf[0]++;
                          buf[15]++;
                          ser.Rs485Write((unsigned char *)buf, 16);
                          std::cout << "write: " << std::to_string(buf[0]) << std::endl;
                      } });

    r.join();
    w.join();
    return 0;
}
  • 说明.txt
# 示例编译
aarch64-linux-gnu-g++ main.cpp rs485Service.cpp -lpthread -o testcom

# 库错误修正
rs485Service.cpp 中有两处需要修改,
1. #define RS485COM "/dev/ttyS1", 旧的是海思版本的设备名

2. InitRs485Dev 当前只支持了19200和9600, 其他波特率需要增加case



# Smart485相机及串口线测试

相机终端1:

# 初始化
stty -F /dev/ttyS1 ispeed 115200 ospeed 115200 cs8
/usr/bin/microcom -s 115200 /dev/ttyS1



相机终端2:

# 设为发送
/usr/local/bin/amba_debug -g 71 -d 1
echo "abc" > /dev/ttyS1


# 设为接收
/usr/local/bin/amba_debug -g 71 -d 0

1.5 参考文献

1.5.1 stty命令

1.5.2 命令格式

用法:stty [-F 设备 | --file=设备] [设置]…
 或:stty [-F 设备 | --file=设备] [-a|–all]
 或:stty [-F 设备 | --file=设备] [-g|–save]
输出或修改终端参数。

-a, --all 以可读性较好的方式输出全部当前设置
-g, --save 以stty 可读取的格式输出当前全部设置
-F, --file=设备 打开并使用指定设备代替标准输入
–help 显示此帮助信息并退出
–version 显示版本信息并退出

可选- 在设置前的指示中,* 标记出了非POSIX 标准的设置。以下系
统定义象征了哪些设置是有效的。

特殊字符:

  • dsusp 字符 每当输入刷新时会发送一个用于终端阻塞信号的字符
    eof 字符 表示文件末尾而发送的字符(用于终止输入)
    eol 字符 为表示行尾而发送的字符
  • eol2 字符 为表示行尾而发送的另一个可选字符
    erase 字符 擦除前一个输入文字的字符
    intr 字符 用于发送中断信号的字符
    kill 字符 用于擦除当前终端行的字符
  • lnext 字符 用于输入下一个引用文字的字符
    quit 字符 用于发送退出信号的字符
  • rprnt 字符 用于重绘当前行的字符
    start 字符 在停止后重新开启输出的字符
    stop 字符 停止输出的字符
    susp 字符 发送终端阻断信号的字符
  • swtch 字符 在不同的shell 层次间切换的字符
  • werase 字符 擦除前一个输入的单词的字符

特殊设置:
N 设置输入输出速度为N 波特

  • cols N 统治内核终端上有N 栏
  • columns N 等于cols N
    ispeed N 设置输入速度为N 波特
  • line N 设置行约束规则为N
    min N 和 -icanon 配合使用,设置每次一完整读入的最小字符数为
    ospeed N 设置输出速度为N 波特
  • rows N 向内核通告此终端有N 行
  • size 根据内核信息输出当前终端的行数和列数
    speed 输出终端速度(单位为波特)
    time N 和-icanon 配合使用,设置读取超时为N 个十分之一秒

控制设置:
[-]clocal 禁用调制解调器控制信号
[-]cread 允许接收输入

  • [-]crtscts 启用RTS/CTS 握手
    csN 设置字符大小为N 位,N 的范围为5 到8
    [-]cstopb 每个字符使用2 位停止位 (要恢复成1 位配合"-“即可)
    [-]hup 当最后一个进程关闭标准终端后发送挂起信号
    [-]hupcl 等于[-]hup
    [-]parenb 对输出生成奇偶校验位并等待输入的奇偶校验位
    [-]parodd 设置校验位为奇数 (配合”-"则为偶数)

输入设置:
[-]brkint 任务中断会触发中断信号
[-]icrnl 将回车转换为换行符
[-]ignbrk 忽略中断字符
[-]igncr 忽略回车
[-]ignpar 忽略含有奇偶不对称错误的字符

  • [-]imaxbel 发出终端响铃但不刷新字符的完整输入缓冲
    [-]inlcr 将换行符转换为回车
    [-]inpck 启用输入奇偶性校验
    [-]istrip 剥除输入字符的高8 位比特
  • [-]iutf8 假定输入字符都是UTF-8 编码
  • [-]iuclc 将大写字母转换为小写
  • [-]ixany 使得任何字符都会重启输出,不仅仅是起始字符
    [-]ixoff 启用开始/停止字符传送
    [-]ixon 启用XON/XOFF 流控制
    [-]parmrk 标记奇偶校验错误 (结合255-0 字符序列)
    [-]tandem 等于[-]ixoff

输出设置:

  • bsN 退格延迟的风格,N 的值为0 至1
  • crN 回车延迟的风格,N 的值为0 至3
  • ffN 换页延迟的风格,N 的值为0 至1
  • nlN 换行延迟的风格,N 的值为0 至1
  • [-]ocrnl 将回车转换为换行符
  • [-]ofdel 使用删除字符代替空字符作填充
  • [-]ofill 延迟时使用字符填充代替定时器同步
  • [-]olcuc 转换小写字母为大写
  • [-]onlcr 将换行符转换为回车
  • [-]onlret 使得换行符的行为表现和回车相同
  • [-]onocr 不在第一列输出回车
    [-]opost 后续进程输出
  • tabN 水平制表符延迟的风格,N 的值为0 至3
  • tabs 等于tab0
  • -tabs 等于tab3
  • vtN 垂直制表符延迟的风格,N 的值为0 至1

本地设置:
[-]crterase 擦除字符回显为退格符

  • crtkill 依照echoprt 和echoe 的设置清除所有行
  • -crtkill 依照echoctl 和echol 的设置清除所有行
  • [-]ctlecho 在头字符中输出控制符号(“^c”)
    [-]echo 回显输入字符
  • [-]echoctl 等于[-]ctlecho
    [-]echoe 等于[-]crterase
    [-]echok 在每清除一个字符后输出一次换行
  • [-]echoke 等于[-]crtkill 意义相同
    [-]echonl 即使没有回显任何其它字符也输出换行
  • [-]echoprt 在"“和”/"之间向后显示擦除的字符
    [-]icanon 启用erase、kill、werase 和rprnt 等特殊字符
    [-]iexten 允许POSIX 标准以外的特殊字符
    [-]isig 启用interrupt、quit和suspend 等特殊字符
    [-]noflsh 在interrupt 和 quit 特殊字符后禁止刷新
  • [-]prterase 等于[-]echoprt
  • [-]tostop 中止尝试向终端写入数据的后台任务
  • [-]xcase 和icanon 配合使用,用转义符""退出大写状态

综合设置:

  • [-]LCASE 等于[-]lcase
    cbreak 等于-icanon
    -cbreak 等于icanon
    cooked 等于brkint ignpar istrip icrnl ixon opost isig icanon eof eol 等的默认值
    -cooked 等于-raw
    crt 等于echoe echoctl echoke
    dec 等于echoe echoctl echoke -ixany intr ^c erase 0177 kill ^u
  • [-]decctlq 等于[-]ixany
    ek 清除所有字符,将它们回溯为默认值
    evenp 等于parenb -parodd cs7
    -evenp 等于-parenb cs8
  • [-]lcase 等于xcase iuclc olcuc
    litout 等于-parenb -istrip -opost cs8
    -litout 等于parenb istrip opost cs7
    nl 等于-icrnl -onlcr
    -nl 等于icrnl -inlcr -igncr onlcr -ocrnl -onlret
    oddp 等于parenb parodd cs7
    -oddp 等于-parenb cs8
    [-]parity 等于[-]evenp
    pass8 等于-parenb -istrip cs8
    -pass8 等于parenb istrip cs7
    raw 等于-ignbrk -brkint -ignpar -parmrk -inpck -istrip
    -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany
    -imaxbel -opost -isig -icanon -xcase min 1 time 0
    -raw 等于cooked
    sane 等于cread -ignbrk brkint -inlcr -igncr icrnl -iutf8
    -ixoff -iuclc -ixany imaxbel 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,所有特殊字符均
    使用默认值

处理连接到标准输入的tty 终端行设置。当不附加参数时,程序会输出波特率、行约束
规则以及与标准stty 设置间的偏差。在设置中,字符会被逐字读取或是被编码为^c、
0x37、0177 或127 这样的字符,其中有特殊值^- 或undef 被用于禁止特殊字符。

1.5.2 microcom命令

1.5.2.1介绍

microcom是一个串口调试指令,经常用于串口调试,相当于linux自带的串口调试助手

1.5.2.2指令
microcom [-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY

参数如下:
-d 表示延时时间,一般我都不设置。
-t 表示超时时间,超多少时间就自动退出。单位为ms
-s 表示传输速度,波特率的意思,这个根据自己的情况而定。
-X 不加
最后指定你的串口设备。如 /dev/ttyO0 , 这是TI的串口设备节点

测试方式如下:

将要测试串口与pc端连接,在pc端开启串口调试工具,波特率设定跟等下microcom设定一样。

使用示例:

microcom -s 115200 /dev/ttyUSB2

1.5.3 echo命令

  • echo指令可以输出内容到标准输出,以空白分割字符串,并且后面增加换行。此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、Fedora。
1.5.3.1 语法
echo [-neE]  [arg ...]
1.5.3.2 选项列表

Linux基础命令:echo的使用Linux基础命令:echo的使用

1.5.3.3 使用示例

使用“\f”换行

[root@localhost ~]# echo -e "hello\fworld"              //必须使用-e选项,\f换行之后,光标还在结尾

hello

     world

[root@localhost ~]#
使用“\n”换行

[root@localhost ~]# echo -e "hello\nworld"              //必须使用-e选项,\n换行之后,光标在开头

hello

world

输出ascii字符

[root@localhost ~]# echo -e "\x31"                       //十六进制的31,换算成49,代表的ascii字符就是1

1
1.5.3.4 e cho > 输出重定向

用法:echo abc > 1.txt

这句话的意思即是:输出abc字符串到一个位置,如果1.txt存在,我们即【清空其1.txt内容,更新为abc】,不存在,创建之

这个过程,echo没有像之前那样向终端打印参数,为什么?

原因是因为,>输出重定向把echo的参数输出到某个文件(而不是输出到终端,这就是输出重定向)。

1.5.3.5 echo > >输出追加重定向

操作符>>输出追加重定向和>输出重定向功能类似。

相同的地方是:如果重定向的文件不存在,创建之

唯一不同的地方是:【如果重定向的文件存在,追加之(>符是清空后新增内容,>>是在文件末尾追加字符串)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值