Bruceou 发表于 2024-3-3 10:19

【米尔-瑞萨RZ/G2UL开发板-试用评测】串口应用开发

<div class='showpostmsg'><div>&nbsp;</div>

<h1>1 MYC-YG2UL串口简介</h1>

<div>MYC-YG2UL平台支持多路串口,核心板默认配置了 5 路串口,其中 UART0、UART1、UART2 带有流控制(RTS 和 CTS 信号)功能。笔者这里将测试RS232接口。</div>

<h1>2 串口概述</h1>

<div>随着嵌入式系统应用的发展,Linux操作系统的应用也越来越广泛。Linux作为一款免费的并且开放源代码的操作系统,与Windows操作系统相比有许多独特的优势。Linux可以进行定制内核;Linux的GUI图形界面能够任意选择;Linux可以更方便、更安全地进行远程操作。随着Linux操作系统的不断发展和完善,基于Linux操作系统的软件开发也得到了长足的发展和应用。如果在工控领域引入Linux,不可避免的会遇到在嵌入式Linux下如何实现串行通信的问题。</div>

<div>在Linux操作系统下,对设备和文件的操作都等同于文件的操作,这样大大简化了系统对不同设备的操作,提高了效率。在程序中,设备和文件都是通过文件描述符来操作的。文件描述符是一个非负数的索引值,指向内核中每个进程打开的文件记录表。当打开一个现存的文件或者创建一个新文件时,内核就向进程返回一个文件描述符。当需要对设备进行读写操作时,也需要把文件描述符作为参数传递给相应的函数。</div>

<div>Linux的设备文件都存放在&ldquo;/dev&rdquo;目录下,串口资源对应的设备名是&ldquo;/dev/ttys+编号&rdquo;,因此串口对应的设备文件的路径是&ldquo;/dev/ttys*&rdquo;。而且USB转串口的设备名通常为&ldquo;/dev/ttyUSB0&rdquo;,在Linux下对设备的操作方法与对文件的操作方法一样。</div>

<h1>3 串口设置详解</h1>

<div>串口的设置主要是设置struct termios结构体的各成员值,如下所示:</div>

<div>#include&lt;termios.h&gt;</div>

<div>struct termios</div>

<div>{</div>

<div>unsigned short c_iflag; /* 输入模式标志 */</div>

<div>unsigned short c_oflag; /* 输出模式标志 */</div>

<div>unsigned short c_cflag; /* 控制模式标志 */</div>

<div>unsigned short c_lflag; /* 本地模式标志 */</div>

<div>unsigned char c_line; /* 线路规程 */</div>

<div>unsigned char c_cc; /* 控制特性 */</div>

<div>speed_t c_ispeed; /* 输入速度 */</div>

<div>speed_t c_ospeed; /* 输出速度 */</div>

<div>};</div>

<div>termios是在Posix规范中定义的标准接口,表示终端设备(包括虚拟终端、串口等)。因为串口是一种终端设备,所以通过终端编程接口对其进行配置和控制。因此在具体讨论串口相关编程之前,需要先了解一下终端的相关知识。</div>

<div>终端是指用户与计算机进行对话的接口,如键盘、显示器和串口设备等物理设备,Windows上的虚拟终端。类UNIX操作系统都有文本式虚拟终端,使用【Ctrl+Alt】+F1~F6键可以进入文本式虚拟终端,在X Window上可以打开几十个以上的图形式虚拟终端。类UNIX操作系统的虚拟终端有xterm、rxvt、zterm、eterm等,而Windows上有crt、putty等虚拟终端。</div>

<div>终端有三种工作模式,分别为规范模式(canonical mode)、非规范模式(non-canonical mode)和原始模式(raw mode)。</div>

<div>通过在termios结构的c_lflag中设置ICANNON标志来定义终端是以规范模式(设置ICANNON标志)还是以非规范模式(清除ICANNON标志)工作,默认情况为规范模式。</div>

<div>在规范模式下,所有的输入是基于行进行处理的。在用户输入一个行结束符(回车符、EOF等)之前,系统调用read()函数是读不到用户输入的任何字符的。除了EOF之外的行结束符(回车符等)与普通字符一样会被read()函数读取到缓冲区中。在规范模式中,行编辑是可行的,而且一次调用read()函数最多只能读取一行数据。如果在read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则read()函数只会读取被请求的字节数,剩下的字节下次再被读取。</div>

<div>在非规范模式下,所有的输入是即时有效的,不需要用户另外输入行结束符,而且不可进行行编辑。在非规范模式下,对参数MIN(c_cc)和TIME(c_cc)的设置决定read()函数的调用方式。设置可以有4种不同的情况。</div>

<div>●MIN = 0和TIME = 0:read()函数立即返回。若有可读数据,则读取数据并返回被读取的字节数,否则读取失败并返回0。</div>

<div>●MIN &gt; 0和TIME = 0:read()函数会被阻塞,直到MIN个字节数据可被读取。</div>

<div>●MIN = 0和TIME &gt; 0:只要有数据可读或者经过TIME个十分之一秒的时间,read()函数则立即返回,返回值为被读取的字节数。如果超时并且未读到数据,则read()函数返回0。</div>

<div>●MIN &gt; 0和TIME &gt; 0:当有MIN个字节可读或者两个输入字符之间的时间间隔超过TIME个十分之一秒时,read()函数才返回。因为在输入第一个字符后系统才会启动定时器,所以,在这种情况下,read()函数至少读取一个字节后才返回。</div>

<div>按照严格意义来讲,原始模式是一种特殊的非规范模式。在原始模式下,所有的输入数据以字节为单位被处理。在这个模式下,终端是不可回显的,而且所有特定的终端输入/输出控制处理不可用。通过调用cfmakeraw()函数可以将终端设置为原始模式,而且该函数通过以下代码可以得到实现:</div>

<div>termios_p-&gt;c_iflag &amp;= ~(IGNBRK | BRKINT | PARMRK | ISTRIP| INLCR | IGNCR | ICRNL | IXON);</div>

<div>termios_p-&gt;c_oflag &amp;= ~OPOST;</div>

<div>termios_p-&gt;c_lflag &amp;= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);</div>

<div>termios_p-&gt;c_cflag &amp;= ~(CSIZE | PARENB);</div>

<div>termios_p-&gt;c_cflag |= CS8;</div>

<div>现在讲解设置串口的基本方法。如上所述,串口设置最基本的操作包括波特率设置,校验位和停止位设置。在这个结构中最为重要的是c_cflag,通过对它的赋值,用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬软流控等。另外,c_iflag和c_cc也是比较常用的标志。在此主要对这3个成员进行详细说明。c_cflag支持的常量名称如下表所示。其中设置波特率宏名为相应的波特率数值前加上B,由于数值较多,本表没有全部列出。</div>

<div>Table 3-1 c_cflag支持的常量名称</div>

<table border="1">
        <tbody>
                <tr>
                        <td>CBAUD</td>
                        <td>波特率的位掩码</td>
                </tr>
                <tr>
                        <td>B0</td>
                        <td>0波特率(放弃DTR)</td>
                </tr>
                <tr>
                        <td>&hellip;</td>
                        <td>&hellip;</td>
                </tr>
                <tr>
                        <td>B1800</td>
                        <td>1800波特率</td>
                </tr>
                <tr>
                        <td>B2400</td>
                        <td>2400波特率</td>
                </tr>
                <tr>
                        <td>B4800</td>
                        <td>4800波特率</td>
                </tr>
                <tr>
                        <td>B9600</td>
                        <td>9600波特率</td>
                </tr>
                <tr>
                        <td>B19200</td>
                        <td>19200波特率</td>
                </tr>
                <tr>
                        <td>B38400</td>
                        <td>38400波特率</td>
                </tr>
                <tr>
                        <td>B57600</td>
                        <td>57600波特率</td>
                </tr>
                <tr>
                        <td>B115200</td>
                        <td>115200波特率</td>
                </tr>
                <tr>
                        <td>EXTA</td>
                        <td>外部时钟率</td>
                </tr>
                <tr>
                        <td>EXTB</td>
                        <td>外部时钟率</td>
                </tr>
                <tr>
                        <td>CSIZE</td>
                        <td>数据位的位掩码</td>
                </tr>
                <tr>
                        <td>CS5</td>
                        <td>5个数据位</td>
                </tr>
                <tr>
                        <td>CS6</td>
                        <td>6个数据位</td>
                </tr>
                <tr>
                        <td>CS7</td>
                        <td>7个数据位</td>
                </tr>
                <tr>
                        <td>CS8</td>
                        <td>8个数据位</td>
                </tr>
                <tr>
                        <td>CSTOPB</td>
                        <td>2个停止位(不设则是1个停止位)</td>
                </tr>
                <tr>
                        <td>CREAD</td>
                        <td>接收使能</td>
                </tr>
                <tr>
                        <td>PARENB</td>
                        <td>校验位使能</td>
                </tr>
                <tr>
                        <td>PARODD</td>
                        <td>使用奇校验而不使用偶校验</td>
                </tr>
                <tr>
                        <td>HUPCL</td>
                        <td>最后关闭时挂线(放弃DTR)</td>
                </tr>
                <tr>
                        <td>CLOCAL</td>
                        <td>本地连接(不改变端口所有者)</td>
                </tr>
                <tr>
                        <td>CRTSCTS</td>
                        <td>硬件流控</td>
                </tr>
        </tbody>
</table>

<div>在这里,对于c_cflag成员不能直接对其初始化,而要将其通过&ldquo;与&rdquo;、&ldquo;或&rdquo;操作使用其中的某些选项。</div>

<div>输入模式标志c_iflag用于控制端口接收端的字符输入处理。c_iflag支持的常量名称,如下表所示。</div>

<div>Table 3-2 c_iflag支持的常量名称</div>

<table border="1">
        <tbody>
                <tr>
                        <td>INPCK</td>
                        <td>奇偶校验使能</td>
                </tr>
                <tr>
                        <td>IGNPAR</td>
                        <td>忽略奇偶校验错误</td>
                </tr>
                <tr>
                        <td>PARMRK</td>
                        <td>奇偶校验错误掩码</td>
                </tr>
                <tr>
                        <td>ISTRIP</td>
                        <td>裁减掉第8位比特</td>
                </tr>
                <tr>
                        <td>IXON</td>
                        <td>启动输出软件流控</td>
                </tr>
                <tr>
                        <td>IXOFF</td>
                        <td>启动输入软件流控</td>
                </tr>
                <tr>
                        <td>INPCK</td>
                        <td>奇偶校验使能</td>
                </tr>
                <tr>
                        <td>IXANY</td>
                        <td>允许输入任意字符可以重新启动输出(默认为输入起始字符才重启输出)</td>
                </tr>
                <tr>
                        <td>IGNBRK</td>
                        <td>忽略输入终止条件</td>
                </tr>
                <tr>
                        <td>BRKINT</td>
                        <td>当检测到输入终止条件时发送SIGINT信号</td>
                </tr>
                <tr>
                        <td>INLCR</td>
                        <td>将接收到的NL(换行符)转换为CR(回车符)</td>
                </tr>
                <tr>
                        <td>IGNCR</td>
                        <td>忽略接收到的CR(回车符)</td>
                </tr>
                <tr>
                        <td>ICRNL</td>
                        <td>将接收到的CR(回车符)转换为NL(换行符)</td>
                </tr>
                <tr>
                        <td>IUCLC</td>
                        <td>将接收到的大写字符映射为小写字符</td>
                </tr>
                <tr>
                        <td>IMAXBEL</td>
                        <td>当输入队列满时响铃</td>
                </tr>
        </tbody>
</table>

<div>c_oflag用于控制终端端口发送出去的字符处理,c_oflag支持的常量名称如表3所示。因为现在终端的速度比以前快得多,所以大部分延时掩码几乎没什么用途。</div>

<div>Table 3-3 c_oflag支持的常量名称</div>

<table border="1">
        <tbody>
                <tr>
                        <td>OPOST</td>
                        <td>启用输出处理功能,如果不设置该标志则其他标志都被忽略</td>
                </tr>
                <tr>
                        <td>OLCUC</td>
                        <td>将输出中的大写字符转换成小写字符</td>
                </tr>
                <tr>
                        <td>ONLCR</td>
                        <td>将输出中的换行符(&#39;\n&#39;)转换成回车符(&#39;\r&#39;)</td>
                </tr>
                <tr>
                        <td>ONOCR</td>
                        <td>如果当前列号为0,则不输出回车符</td>
                </tr>
                <tr>
                        <td>OCRNL</td>
                        <td>将输出中的回车符(&#39;\r&#39;)转换成换行符(&#39;\n&#39;)</td>
                </tr>
                <tr>
                        <td>ONLRET</td>
                        <td>不输出回车符</td>
                </tr>
                <tr>
                        <td>OFILL</td>
                        <td>发送填充字符以提供延时</td>
                </tr>
                <tr>
                        <td>OFDEL</td>
                        <td>如果设置该标志,则表示填充字符为DEL字符,否则为NUL字符</td>
                </tr>
                <tr>
                        <td>NLDLY</td>
                        <td>换行符延时掩码</td>
                </tr>
                <tr>
                        <td>CRDLY</td>
                        <td>回车符延时掩码</td>
                </tr>
                <tr>
                        <td>TABDLY</td>
                        <td>制表符延时掩码</td>
                </tr>
                <tr>
                        <td>BSDLY</td>
                        <td>水平退格符延时掩码</td>
                </tr>
                <tr>
                        <td>VTDLY</td>
                        <td>垂直退格符延时掩码</td>
                </tr>
                <tr>
                        <td>FFLDY</td>
                        <td>换页符延时掩码</td>
                </tr>
        </tbody>
</table>

<div>c_lflag用于控制终端的本地数据处理和工作模式,c_lflag所支持的常量名称如下表所示。</div>

<div>Table 3-4 c_lflag支持的常量名称</div>

<table border="1">
        <tbody>
                <tr>
                        <td>ISIG</td>
                        <td>若收到信号字符(INTR、QUIT等),则会产生相应的信号</td>
                </tr>
                <tr>
                        <td>ICANON</td>
                        <td>启用规范模式</td>
                </tr>
                <tr>
                        <td>ECHO</td>
                        <td>启用本地回显功能</td>
                </tr>
                <tr>
                        <td>ECHOE</td>
                        <td>若设置ICANON,则允许退格操作</td>
                </tr>
                <tr>
                        <td>ECHOK</td>
                        <td>若设置ICANON,则KILL字符会删除当前行</td>
                </tr>
                <tr>
                        <td>ECHONL</td>
                        <td>若设置ICANON,则允许回显换行符</td>
                </tr>
                <tr>
                        <td>ECHOCTL</td>
                        <td>若设置ECHO,则控制字符(制表符、换行符等)会显示成&ldquo;^X&rdquo;,其中X的ASCII码等于给相应控制字符的ASCII码加上0x40。例如,退格字符(0x08)会显示为&ldquo;^H&rdquo;(&#39;H&#39;的ASCII码为0x48)</td>
                </tr>
                <tr>
                        <td>EC HOPRT</td>
                        <td>若设置ICANON和IECHO,则删除字符(退格符等)和被删除的字符都会被显示</td>
                </tr>
                <tr>
                        <td>ECHOKE</td>
                        <td>若设置ICANON,则允许回显在ECHOE和ECHOPRT中设定的KILL字符</td>
                </tr>
                <tr>
                        <td>NOFLSH</td>
                        <td>在通常情况下,当接收到INTR、QUIT和SUSP控制字符时,会清空输入和输出队列。如果设置该标志,则所有的队列不会被清空</td>
                </tr>
                <tr>
                        <td>TOSTOP</td>
                        <td>若一个后台进程试图向它的控制终端进行写操作,则系统向该后台进程的进程组发送SIGTTOU信号。该信号通常终止进程的执行</td>
                </tr>
                <tr>
                        <td>IEXTEN</td>
                        <td>启用输入处理功能</td>
                </tr>
        </tbody>
</table>

<div>c_cc定义特殊控制特性,c_cc所支持的常量名称如下表所示。</div>

<div>Table 3-5 c_cc支持的常量名称</div>

<table border="1">
        <tbody>
                <tr>
                        <td>VINTR</td>
                        <td>中断控制字符,对应键为Ctrl+C</td>
                </tr>
                <tr>
                        <td>VQUIT</td>
                        <td>退出操作符,对应键为Ctrl+Z</td>
                </tr>
                <tr>
                        <td>VERASE</td>
                        <td>删除操作符,对应键为Backspace(BS)</td>
                </tr>
                <tr>
                        <td>VKILL</td>
                        <td>删除行符,对应键为Ctrl+U</td>
                </tr>
                <tr>
                        <td>VEOF</td>
                        <td>文件结尾符,对应键为Ctrl+D</td>
                </tr>
                <tr>
                        <td>VEOL</td>
                        <td>附加行结尾符,对应键为Carriage return(CR)</td>
                </tr>
                <tr>
                        <td>VEOL2</td>
                        <td>第二行结尾符,对应键为Line feed(LF)</td>
                </tr>
                <tr>
                        <td>VMIN</td>
                        <td>指定最少读取的字符数</td>
                </tr>
                <tr>
                        <td>VTIME</td>
                        <td>指定读取的每个字符之间的超时时间</td>
                </tr>
        </tbody>
</table>

<div>下面就详细讲解设置串口属性的基本流程。</div>

<div><strong>1</strong><strong>.</strong><strong>保存原先串口配置</strong></div>

<div>首先,为了安全起见和以后调试程序方便,可以先保存原先串口的配置,在这里可以使用函数tcgetattr(fd, &amp;old_cfg)。该函数得到由fd指向的终端的配置参数,并将它们保存于termios结构变量old_cfg中。该函数还可以测试配置是否正确、该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为-1,其使用如下所示:</div>

<div>if (tcgetattr(fd, &amp;old_cfg) != 0)</div>

<div>{</div>

<div>perror(&quot;tcgetattr&quot;);</div>

<div>return -1;</div>

<div>}</div>

<div><strong>2.</strong><strong>激活选项</strong></div>

<div>CLOCAL和CREAD分别用于本地连接和接收使能,因此,首先要通过位掩码的方式激活这两个选项。</div>

<div>newtio.c_cflag |= CLOCAL | CREAD;</div>

<div>调用cfmakeraw()函数可以将终端设置为原始模式,在后面的实例中,采用原始模式进行串口数据通信。</div>

<div>cfmakeraw(&amp;new_cfg);</div>

<div><strong>3.</strong><strong>设置波特率</strong></div>

<div>设置波特率有专门的函数,用户不能直接通过位掩码来操作。设置波特率的主要函数有cfsetispeed()和cfsetospeed()。这两个函数的使用很简单,如下所示:</div>

<div>cfsetispeed(&amp;\&amp;new_cfg, B115200);</div>

<div>cfsetospeed(&amp;new_cfg, B115200);</div>

<div>cfsetispeed()函数在termios结构中设置数据输入波特率,而cfsetospeed()函数在termios结构中设置数据输入波特率。一般来说,用户需将终端的输入和输出波特率设置成一样的。这几个函数在成功时返回0,失败时返回-1。</div>

<div><strong>4.</strong><strong>设置字符大小</strong></div>

<div>与设置波特率不同,设置字符大小并没有现成可用的函数,需要用位掩码。一般首先去除数据位中的位掩码,再重新按要求设置,如下所示:</div>

<div>new_cfg.c_cflag &amp;= ~CSIZE; /* 用数据位掩码清空数据位设置 */</div>

<div>new_cfg.c_cflag |= CS8;</div>

<div><strong>5</strong><strong>.</strong><strong>设置奇偶校验位</strong></div>

<div>设置奇偶校验位需要用到termios中的两个成员:c_cflag和c_iflag。首先要激活c_cflag中的校验位使能标志PARENB和确认是否要进行校验,这样会对输出数据产生校验位,而对输入数据进行校验检查。同时还要激活c_iflag中的对于输入数据的奇偶校验使能(INPCK)。如使能奇校验时,代码如下所示:</div>

<div>new_cfg.c_cflag |= (PARODD | PARENB);</div>

<div>new_cfg.c_iflag |= INPCK;</div>

<div>而使能偶校验时,代码如下所示:</div>

<div>new_cfg.c_cflag |= PARENB;</div>

<div>new_cfg.c_cflag &amp;= ~PARODD; /* 清除偶奇校验标志,则配置为偶校验 */</div>

<div>new_cfg.c_iflag |= INPCK;</div>

<div><strong>6</strong><strong>.</strong><strong>设置停止位</strong></div>

<div>设置停止位是通过激活c_cflag中的CSTOPB而实现的。若停止位为一个比特,则清除CSTOPB;若停止位为两个,则激活CSTOPB。以下分别是停止位为一个和两个比特时的代码:</div>

<div>new_cfg.c_cflag &amp;= ~CSTOPB; /* 将停止位设置为一个比特 */</div>

<div>new_cfg.c_cflag |= CSTOPB; /* 将停止位设置为两个比特 */</div>

<div><strong>7.</strong><strong>设置最少字符和等待时间</strong></div>

<div>在对接收字符和等待时间没有特别要求的情况下,可以将其设置为0,则在任何情况下read()函数立即返回,此时串口操作会设置为非阻塞方式,如下所示:</div>

<div>new_cfg.c_cc = 0;</div>

<div>new_cfg.c_cc = 0;</div>

<div><strong>8.</strong><strong>清除串口缓冲</strong></div>

<div>由于串口在重新设置后,需要对当前的串口设备进行适当的处理,这时就可调用在&lt;termios.h&gt;中声明的tcdrain()、tcflow()、tcflush()等函数来处理目前串口缓冲中的数据,它们的格式如下所示:</div>

<div>int tcdrain(int fd); /* 使程序阻塞,直到输出缓冲区的数据全部发送完毕 */</div>

<div>int tcflow(int fd, int action); /* 用于暂停或重新开始输出 */</div>

<div>int tcflush(int fd, int queue_selector); /* 用于清空输入/输出缓冲区 */</div>

<div>在本实例中使用tcflush()函数,对于在缓冲区中尚未传输的数据,或者收到的但是尚未读取的数据,其处理方法取决于queue_selector的值,它可能的取值有以下几种。</div>

<div>●TCIFLUSH:对接收到而未被读取的数据进行清空处理。</div>

<div>●TCOFLUSH:对尚未传送成功的输出数据进行清空处理。</div>

<div>●TCIOFLUSH:包括前两种功能,即对尚未处理的输入/输出数据进行清空处理。</div>

<div>如在本例中所采用的是第一种方法,当然可以使用TCIOFLUSH参数:</div>

<div>tcflush(fd, TCIFLUSH);</div>

<div><strong>9.</strong><strong>激活配置</strong></div>

<div>在完成全部串口配置后,要激活刚才的配置并使配置生效。这里用到的函数是tcsetattr(),它的函数原型是:</div>

<div>tcsetattr(int fd, int optional_actions, const struct termios *termios_p);</div>

<div>其中,参数termios_p是termios类型的新配置变量。</div>

<div>参数optional_actions可能的取值有以下3种。</div>

<div>●TCSANOW:配置的修改立即生效。</div>

<div>●TCSADRAIN:配置的修改在所有写入fd的输出都传输完毕之后生效。</div>

<div>●TCSAFLUSH:所有已接收但未读入的输入都将在修改生效之前被丢弃。</div>

<div>该函数若调用成功则返回0,若失败则返回-1,代码如下所示:</div>

<div>if ((tcsetattr(fd, TCSANOW, &amp;new_cfg)) != 0)</div>

<div>{</div>

<div>perror(&quot;tcsetattr&quot;);</div>

<div>return -1;</div>

<div>}</div>

<div>下面给出了串口配置的完整函数。为了函数的通用性,通常将常用的选项都在函数中列出,这样可以大大方便以后用户的调试使用。该设置函数如下所示:</div>

<div>/*</div>

<div>* @函数名:set_com_config</div>

<div>* @函数功能:串口设置函数</div>

<div>*/</div>

<div>int set_com_config(int fd,int baud_rate,int data_bits, char parity, int stop_bits)</div>

<div>{</div>

<div>struct termios new_cfg;</div>

<div>int speed;</div>

<div>/* 保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息 */</div>

<div>if (tcgetattr(fd, &amp;new_cfg) != 0)</div>

<div>{</div>

<div>perror(&quot;tcgetattr save&quot;);</div>

<div>return -1;</div>

<div>}</div>

<div>//修改控制模式,保证程序不会占用串口</div>

<div>new_cfg.c_cflag |= CLOCAL;</div>

<div>//修改控制模式,使得能够从串口中读取输入数据</div>

<div>new_cfg.c_cflag |= CREAD;</div>

<div>new_cfg.c_oflag &amp;= ~(ONLCR | OCRNL);</div>

<div>new_cfg.c_iflag &amp;= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);</div>

<div>new_cfg.c_iflag &amp;= ~(ICRNL | INLCR);</div>

<div>new_cfg.c_iflag &amp;= ~(IXON | IXOFF | IXANY);</div>

<div>/* 设置波特率 */</div>

<div>switch (baud_rate)</div>

<div>{</div>

<div>case 2400:</div>

<div>{</div>

<div>speed = B2400;</div>

<div>}</div>

<div>break;</div>

<div>case 4800:</div>

<div>{</div>

<div>speed = B4800;</div>

<div>}</div>

<div>break;</div>

<div>case 9600:</div>

<div>{</div>

<div>speed = B9600;</div>

<div>}</div>

<div>break;</div>

<div>case 19200:</div>

<div>{</div>

<div>speed = B19200;</div>

<div>}</div>

<div>break;</div>

<div>case 38400:</div>

<div>{</div>

<div>speed = B38400;</div>

<div>}</div>

<div>break;</div>

<div>default:</div>

<div>case 115200:</div>

<div>{</div>

<div>speed = B115200;</div>

<div>}</div>

<div>break;</div>

<div>}</div>

<div>cfsetispeed(&amp;new_cfg, speed);//输入波特率</div>

<div>cfsetospeed(&amp;new_cfg, speed);//输出波特率</div>

<div>switch (data_bits) /* 设置数据位 */</div>

<div>{</div>

<div>case 7:</div>

<div>{</div>

<div>new_cfg.c_cflag |= CS7;</div>

<div>}</div>

<div>break;</div>

<div>default:</div>

<div>case 8:</div>

<div>{</div>

<div>new_cfg.c_cflag |= CS8;</div>

<div>}</div>

<div>break;</div>

<div>}</div>

<div>switch (parity) /* 设置奇偶校验位 */</div>

<div>{</div>

<div>default:</div>

<div>case &#39;n&#39;:</div>

<div>case &#39;N&#39;:</div>

<div>{</div>

<div>new_cfg.c_cflag &amp;= ~PARENB;</div>

<div>new_cfg.c_iflag &amp;= ~INPCK;</div>

<div>}</div>

<div>break;</div>

<div>case &#39;o&#39;:</div>

<div>case &#39;O&#39;:</div>

<div>{</div>

<div>new_cfg.c_cflag |= (PARODD | PARENB);</div>

<div>new_cfg.c_iflag |= INPCK;</div>

<div>}</div>

<div>break;</div>

<div>case &#39;e&#39;:</div>

<div>case &#39;E&#39;:</div>

<div>{</div>

<div>new_cfg.c_cflag |= PARENB;</div>

<div>new_cfg.c_cflag &amp;= ~PARODD;</div>

<div>new_cfg.c_iflag |= INPCK;</div>

<div>}</div>

<div>break;</div>

<div>case &#39;s&#39;: /* as no parity */</div>

<div>case &#39;S&#39;:</div>

<div>{</div>

<div>new_cfg.c_cflag &amp;= ~PARENB;</div>

<div>new_cfg.c_cflag &amp;= ~CSTOPB;</div>

<div>}</div>

<div>break;</div>

<div>}</div>

<div>switch (stop_bits) /* 设置停止位 */</div>

<div>{</div>

<div>default:</div>

<div>case 1:</div>

<div>{</div>

<div>new_cfg.c_cflag &amp;= ~CSTOPB;</div>

<div>}</div>

<div>break;</div>

<div>case 2:</div>

<div>{</div>

<div>new_cfg.c_cflag |= CSTOPB;</div>

<div>}</div>

<div>}</div>

<div>//修改输出模式,原始数据输出</div>

<div>new_cfg.c_oflag &amp;= ~OPOST;</div>

<div>new_cfg.c_lflag &amp;= ~(ICANON | ECHO | ECHOE | ISIG);</div>

<div>new_cfg.c_lflag &amp;= ~(ISIG | ICANON);</div>

<div>//设置等待时间和最小接收字符</div>

<div>new_cfg.c_cc = 0; /* 读取一个字符等待0*(0/10)s */</div>

<div>new_cfg.c_cc = 1; /* 读取字符的最少个数为0 */</div>

<div>//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读</div>

<div>tcflush(fd, TCIFLUSH); /* 处理未接收字符 */</div>

<div>if ((tcsetattr(fd, TCSANOW, &amp;new_cfg)) != 0) /* 激活新配置 */</div>

<div>{</div>

<div>perror(&quot;tcsetattr action&quot;);</div>

<div>return -1;</div>

<div>}</div>

<div>printf(&quot;serial set success\n&quot;);</div>

<div>return 0;</div>

<div>}</div>

<h1>4 串口使用详解</h1>

<div>在配置完串口的相关属性后,就可以对串口进行打开和读写操作了。它所使用的函数和普通文件的读写函数一样,都是open()、write()和 read()。它们之间的区别的只是串口是一个终端设备,因此在选择函数的具体参数时会有一些区别。另外,这里会用到一些附加的函数,用于测试终端设备的 连接情况等。下面将对其进行具体讲解。</div>

<h2>4.1 打开串口</h2>

<div>打开串口和打开普通文件一样,都是使用open()函数,如下所示:</div>

<div>fd = open( &quot;/dev/ttyS0&quot;, O_RDWR|O_NOCTTY|O_NDELAY);</div>

<div>可以看到,这里除了普通的读写参数外,还有两个参数O_NOCTTY和O_NDELAY。</div>

<div>O_NOCTTY标志用于通知Linux系统,该参数不会使打开的文件成为这个进程的控制终端。如果没有指定这个标志,那么任何一个输入(诸如键盘中止信号等)都将会影响用户的进程。</div>

<div>O_NDELAY标志通知Linux系统,这个程序不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。如果用户指定了这个标志,则进程将会一直处在睡眠状态,直到DCD信号线被激活。</div>

<div>接下来可恢复串口的状态为阻塞状态,用于等待串口数据的读入,可用fcntl()函数实现,如下所示:</div>

<div>fcntl(fd, F_SETFL, 0);</div>

<div>再接着可以测试打开文件描述符是否连接到一个终端设备,以进一步确认串口是否正确打开,如下所示:</div>

<div>isatty(STDIN_FILENO);</div>

<div>该函数调用成功则返回0,若失败则返回-1。</div>

<div>这时,一个串口就已经成功打开了。接下来就可以对这个串口进行读和写操作。</div>

<h2>4.2 读写串口</h2>

<div>读写串口操作和读写普通文件一样,使用read()和write()函数即可,如下所示:</div>

<div>write(fd, buff, strlen(buff));</div>

<div>read(fd, buff, BUFFER_SIZE);</div>

<div>下面两个实例给出了串口读和写的两个程序,其中用到前面所讲述的open_port()和set_com_config ()函数。写串口的程序将在宿主机上运行,读串口的程序将在目标板上运行。</div>

<div>写串口的程序如下所示。</div>

<div>/*com_writer.c*/</div>

<div>#include&lt;stdio.h&gt;</div>

<div>#include&lt;stdlib.h&gt;</div>

<div>#include&lt;string.h&gt;</div>

<div>#include&lt;sys/types.h&gt;</div>

<div>#include&lt;sys/stat.h&gt;</div>

<div>#include&lt;errno.h&gt;</div>

<div>#include &quot;uart_api.h&quot;</div>

<div>int main(void)</div>

<div>{</div>

<div>int fd;</div>

<div>char buff;</div>

<div>if((fd=open_port(TARGET_COM_PORT))&lt;0) /*打开串口*/</div>

<div>{</div>

<div>perror(&quot;open_port&quot;);</div>

<div>return 1;</div>

<div>}</div>

<div>if(set_com_config(fd,115200,8,&#39;N&#39;,1)&lt;0) /*配置串口*/</div>

<div>{</div>

<div>perror(&quot;set_com_config error&quot;);</div>

<div>return 1;</div>

<div>}</div>

<div>do</div>

<div>{</div>

<div>printf(&quot;Input some words(enter &#39;quit&#39; to exit):&quot;);</div>

<div>memset(buff,0,BUFFER_SIZE);</div>

<div>if(fgets(buff,BUFFER_SIZE,stdin)==NULL)</div>

<div>{</div>

<div>perror(&quot;fgets&quot;);</div>

<div>break;</div>

<div>}</div>

<div>write(fd,buff,strlen(buff));</div>

<div>}while(strncmp(buff,&quot;quit&quot;,4));</div>

<div>close(fd);</div>

<div>return 0;</div>

<div>}</div>

<div>读串口的程序如下所示:</div>

<div>/*com_reader.c*/</div>

<div>#include&lt;stdio.h&gt;</div>

<div>#include&lt;stdlib.h&gt;</div>

<div>#include&lt;string.h&gt;</div>

<div>#include&lt;sys/types.h&gt;</div>

<div>#include&lt;sys/stat.h&gt;</div>

<div>#include&lt;errno.h&gt;</div>

<div>#include &quot;uart_api.h&quot;</div>

<div>int main(void)</div>

<div>{</div>

<div>int fd;</div>

<div>char buff;</div>

<div>if((fd=open_port(TARGET_COM_PORT))&lt;0)</div>

<div>{</div>

<div>perror(&quot;open_port&quot;);</div>

<div>return 1;</div>

<div>}</div>

<div>if(set_com_config(fd,115200,8,&#39;N&#39;,1)&lt;0) /*配置串口*/</div>

<div>{</div>

<div>perror(&quot;set_com_config &quot;);</div>

<div>return 1;</div>

<div>}</div>

<div>do</div>

<div>{</div>

<div>memset(buff,0,BUFFER_SIZE);</div>

<div>if(read(fd,buff,BUFFER_SIZE)&gt;0)</div>

<div>{</div>

<div>printf(&quot;the receive words are:%s&quot;,buff);</div>

<div>}</div>

<div>}while(strncmp(buff,&quot;quit&quot;,4));</div>

<div>close(fd);</div>

<div>return 0;</div>

<div>}</div>

<div>/*uart_api.h*/</div>

<div>#ifndef UART_API_H</div>

<div>#define UART_API_H</div>

<div>#include&lt;errno.h&gt;</div>

<div>#include&lt;stdio.h&gt;</div>

<div>#include&lt;stdlib.h&gt;</div>

<div>#include&lt;string.h&gt;</div>

<div>#include&lt;sys/types.h&gt;</div>

<div>#include&lt;sys/stat.h&gt;</div>

<div>#include &lt;termios.h&gt;</div>

<div>#include &lt;fcntl.h&gt;</div>

<div>#include &lt;time.h&gt;</div>

<div>#include &lt;unistd.h&gt;</div>

<div>#include &lt;ctype.h&gt;</div>

<div>#define BUFFER_SIZE 36</div>

<div>#define TARGET_COM_PORT &quot;/dev/ttySC4&quot;</div>

<div>int set_com_config(int fd,int baud_rate, int data_bits,char parity,int stop_bits);</div>

<div>int open_port(char *com_port);</div>

<div>int init_port(char *com_port);</div>

<div>#endif</div>

<h1>5 串口测试</h1>

<div>笔者这里使用设备节点是ttySC4,可以接RS232与PC通信。</div>

<div></div>

<div></div>

<div>在开发板上运行写串口的程序,而在目标板上运行读串口的程序,运行结果如下所示。</div>

<div>串口写数据:</div>

<div></div>

<div>串口收数据:</div>

<div></div>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

lugl4313820 发表于 2024-4-17 07:51

<p>感谢大佬这么详细的知识分享,好好学习,收获不少呀。</p>
页: [1]
查看完整版本: 【米尔-瑞萨RZ/G2UL开发板-试用评测】串口应用开发