接触过linux编程的应该都知道,学习linux应用最好的参考资料就是系统自带的手册——通过man命令查找编程手册。
通过搜索引擎搜索与linux串口编程相关的关键字,找到与串口编程相关的结构体或者函数就可以开始自学串口应用编程了。
学习环境:deepin 15.3 64位系统,开发板:nanopc-t1,运行ubuntu 14.04LTS,内核版本:3.8.13.16,交叉编译链:arm-linux-gnueabihf-gcc,版本4.7.3
我是从串口相关的termios结构体开始入手的,终端中输入man 3 termios
hokamyuen@hokamyuen-deepin:~$ man 3 termios
就能打开串口编程手册了,学习过程中遇到的串口相关的问题都可以在这里找到答案。
打开手册,可以看到串口相关的许多函数,而且很多函数都含有struct termios这个结构体,所以这个结构体一定是重点,继续往下翻手册可以看到这个结构体的介绍
DESCRIPTION
The termios functions describe a general terminal interface that is provided to control asynchronous communi‐
cations ports.
The termios structure
Many of the functions described here have a termios_p argument that is a pointer to a termios structure. This
structure contains at least the following members:
tcflag_t c_iflag; /* input modes */
tcflag_t c_oflag; /* output modes */
tcflag_t c_cflag; /* control modes */
tcflag_t c_lflag; /* local modes */
cc_t c_cc[NCCS]; /* special characters */
描述中说到,termios这个结构体是用于描述一个串口的各个参数的,结构体包含输入,输出模式等参数。不过,这些参数跟学单片机时配置的参数完全不一样,而且最重要的一点貌似没有——这个结构体描述的是哪个串口。继续翻手册,又发现了一个读取设置和生效配置的说明
Retrieving and changing terminal settings
tcgetattr() gets the parameters associated with the object referred by fd and stores them in the termios
structure referenced by termios_p. This function may be invoked from a background process; however, the ter‐
minal attributes may be subsequently changed by a foreground process.
tcsetattr() sets the parameters associated with the terminal (unless support is required from the underlying
hardware that is not available) from the termios structure referred to by termios_p. optional_actions speci‐
fies when the changes take effect:
这样的话我们只需要修改跟我们预期的配置不一样的地方然后再生效配置就好了。那首先我们就要开始使用这个函数看看串口原本的参数了。
回到手册的头部
SYNOPSIS
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
可以看到所有的函数生明和需要的头文件,首先把手册列出来的头文件添加到我们的代码中
#include <termios.h>
#include <unistd.h>
int main(void)
{
return 0;
}
再看tcgetattr函数的第一个入口参数为fd,很明显这个为文件描述符,不难想到我们要控制的是哪个串口就是由这个参数来决定的。文件描述符在打开文件的时候生成,所以我们需要打开串口设备,同样利用man 命令查找open函数的使用方法和需要的头文件
hokamyuen@hokamyuen-deepin:~$ man 3 open
SYNOPSIS
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int oflag, ...);
int openat(int fd, const char *path, int oflag, ...);
可以看到open函数需要sys/stat.h,fcntl.h两个头文件,open函数的第一个入口参数为文件的路径,至于第二个参数oflag我们可以继续翻手册寻找可以选用的参数
Values for oflag are constructed by a bitwise-inclusive OR of flags from the following list, defined in
<fcntl.h>. Applications shall specify exactly one of the first five values (file access modes) below in the
value of oflag:
O_EXEC Open for execute only (non-directory files). The result is unspecified if this flag is applied
to a directory.
O_RDONLY Open for reading only.
O_RDWR Open for reading and writing. The result is undefined if this flag is applied to a FIFO.
O_SEARCH Open directory for search only. The result is unspecified if this flag is applied to a non-
directory file.
O_WRONLY Open for writing only.
Any combination of the following may be used:
O_APPEND If set, the file offset shall be set to the end of the file prior to each write.
O_CLOEXEC If set, the FD_CLOEXEC flag for the new file descriptor shall be set.
O_CREAT If the file exists, this flag has no effect except as noted under O_EXCL below. Otherwise, the
file shall be created; the user ID of the file shall be set to the effective user ID of the
process; the group ID of the file shall be set to the group ID of the file's parent directory or
to the effective group ID of the process; and the access permission bits (see <sys/stat.h>) of
the file mode shall be set to the value of the argument following the oflag argument taken as
type mode_t modified as follows: a bitwise AND is performed on the file-mode bits and the corre‐
sponding bits in the complement of the process' file mode creation mask. Thus, all bits in the
file mode whose corresponding bit in the file mode creation mask is set are cleared. When bits
other than the file permission bits are set, the effect is unspecified. The argument following
the oflag argument does not affect whether the file is open for reading, writing, or for both.
Implementations shall provide a way to initialize the file's group ID to the group ID of the
parent directory. Implementations may, but need not, provide an implementation-defined way to
initialize the file's group ID to the effective group ID of the calling process.
O_DIRECTORY If path resolves to a non-directory file, fail and set errno to [ENOTDIR].
O_DSYNC Write I/O operations on the file descriptor shall complete as defined by synchronized I/O data
integrity completion.
O_EXCL If O_CREAT and O_EXCL are set, open() shall fail if the file exists. The check for the existence
of the file and the creation of the file if it does not exist shall be atomic with respect to
other threads executing open() naming the same filename in the same directory with O_EXCL and
O_CREAT set. If O_EXCL and O_CREAT are set, and path names a symbolic link, open() shall fail
and set errno to [EEXIST], regardless of the contents of the symbolic link. If O_EXCL is set and
O_CREAT is not set, the result is undefined.
O_NOCTTY If set and path identifies a terminal device, open() shall not cause the terminal device to
become the controlling terminal for the process. If path does not identify a terminal device,
O_NOCTTY shall be ignored.
O_NOFOLLOW If path names a symbolic link, fail and set errno to [ELOOP].
O_NONBLOCK When opening a FIFO with O_RDONLY or O_WRONLY set:
* If O_NONBLOCK is set, an open() for reading-only shall return without delay. An open() for
writing-only shall return an error if no process currently has the file open for reading.
* If O_NONBLOCK is clear, an open() for reading-only shall block the calling thread until a
thread opens the file for writing. An open() for writing-only shall block the calling thread
until a thread opens the file for reading.
When opening a block special or character special file that supports non-blocking opens:
* If O_NONBLOCK is set, the open() function shall return without blocking for the device to be
ready or available. Subsequent behavior of the device is device-specific.
* If O_NONBLOCK is clear, the open() function shall block the calling thread until the device
is ready or available before returning.
Otherwise, the O_NONBLOCK flag shall not cause an error, but it is unspecified whether the file
status flags will include the O_NONBLOCK flag.
O_RSYNC Read I/O operations on the file descriptor shall complete at the same level of integrity as
specified by the O_DSYNC and O_SYNC flags. If both O_DSYNC and O_RSYNC are set in oflag, all I/O
operations on the file descriptor shall complete as defined by synchronized I/O data integrity
completion. If both O_SYNC and O_RSYNC are set in flags, all I/O operations on the file descrip‐
tor shall complete as defined by synchronized I/O file integrity completion.
O_SYNC Write I/O operations on the file descriptor shall complete as defined by synchronized I/O file
integrity completion.
The O_SYNC flag shall be supported for regular files, even if the Synchronized Input and Output
option is not supported.
O_TRUNC If the file exists and is a regular file, and the file is successfully opened O_RDWR or
O_WRONLY, its length shall be truncated to 0, and the mode and owner shall be unchanged. It
shall have no effect on FIFO special files or terminal device files. Its effect on other file
types is implementation-defined. The result of using O_TRUNC without either O_RDWR or O_WRONLY
is undefined.
O_TTY_INIT If path identifies a terminal device other than a pseudo-terminal, the device is not already
open in any process, and either O_TTY_INIT is set in oflag or O_TTY_INIT has the value zero,
open() shall set any non-standard termios structure terminal parameters to a state that provides
conforming behavior; see the Base Definitions volume of POSIX.1‐2008, Section 11.2, Parameters
that Can be Set. It is unspecified whether O_TTY_INIT has any effect if the device is already
open in any process. If path identifies the slave side of a pseudo-terminal that is not already
open in any process, open() shall set any non-standard termios structure terminal parameters to
a state that provides conforming behavior, regardless of whether O_TTY_INIT is set. If path does
not identify a terminal device, O_TTY_INIT shall be ignored.
这里我选择了读写权限标志O_RDWR和非控制终端标志O_NOCTTY两个选项,打开文件这句代码的参数就找齐了,打开文件后要记得关闭
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
char tty_addr[] = "/dev/ttySAC2";
fd = open(tty_addr,O_RDWR|O_NOCTTY);
if(fd < 0)
{
printf("打开%s失败\r\n",tty_addr);
return 0;
}
printf("打开%s成功\r\n",tty_addr);
close(fd);
return 0;
}
每做完一步先编译并且放上开发板测试一遍,我用的是nanopc-t1
hokamyuen@hokamyuen-deepin:~/nanopc/usart-test$ arm-linux-gnueabihf-gcc usart-test.c -o usart-test -static
没有错误提示而且生成了usart-test文件就证明编译成功了。用自己决觉得方便的方法吧程序下载的板子里,我比较喜欢用scp方式。运行程序并观察输出结果
root@NanoPC:~# ./usart-test
打开/dev/ttySAC2成功
root@NanoPC:~#
可以看到,程序顺利的运行了,接着进行下一步——获取串口的参数。我们首先看看串口的波特率。
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd,uart_in_speed,uart_out_speed;
char tty_addr[] = "/dev/ttySAC2";
struct termios uart_setting_struct;
fd = open(tty_addr,O_RDWR|O_NOCTTY);
if(fd < 0)
{
printf("打开%s失败\r\n",tty_addr);
return 0;
}
printf("打开%s成功\r\n",tty_addr);
tcgetattr(fd,&uart_setting_struct);
uart_in_speed = cfgetispeed(&uart_setting_struct);
uart_out_speed = cfgetospeed(&uart_setting_struct);
printf("串口输入速度:%d,输出速度:%d\r\n",uart_in_speed,uart_out_speed);
close(fd);
return 0;
}
编译并下载到板子上运行
root@NanoPC:~# ./usart-test
打开/dev/ttySAC2成功
串口输入速度:13,输出速度:13
root@NanoPC:~#
运行之后可以看到串口的输入和输出的速度都不是常见的波特率,有疑惑就要重新去翻看手册寻找答案。
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.
RETURN VALUE
cfgetispeed() returns the input baud rate stored in the termios structure.
cfgetospeed() returns the output baud rate stored in the termios structure.
All other functions return:
0 on success.
-1 on failure and set errno to indicate the error.
Note that tcsetattr() returns success if any of the requested changes could be successfully carried out.
Therefore, when making multiple changes it may be necessary to follow this call with a further call to tcge‐
tattr() to check that all changes have been performed successfully.
通过手册,我们发现串口的波特率不是直接输入数字设置的,而是通过一组宏,刚刚获取到的速度为13,即指B9600宏,表示的波特率为9600,那下一步就可以尝试修改串口的波特率了,修改设置后记得使设置生效。
Retrieving and changing terminal settings
tcgetattr() gets the parameters associated with the object referred by fd and stores them in the termios
structure referenced by termios_p. This function may be invoked from a background process; however, the ter‐
minal attributes may be subsequently changed by a foreground process.
tcsetattr() sets the parameters associated with the terminal (unless support is required from the underlying
hardware that is not available) from the termios structure referred to by termios_p. optional_actions speci‐
fies when the changes take effect:
TCSANOW
the change occurs immediately.
TCSADRAIN
the change occurs after all output written to fd has been transmitted. This option should be used when
changing parameters that affect output.
TCSAFLUSH
the change occurs after all output written to the object referred by fd has been transmitted, and all
input that has been received but not read will be discarded before the change is made.
在tcsetattr函数的说明后面有几个生效设置选项,通过各个宏的说明,可以看到这些宏代表设置生效的时间,立刻生效,发送数据完成后生效,发送数据完成后生效而且接收到的而且没被程序读取的数据将被丢弃。按需选择就好,在当前的简单应用中,可以选择任意一个,我选择了立刻生效。源码如下:
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define UART_BAUD B115200
int main(void)
{
int fd,uart_in_speed,uart_out_speed;
char tty_addr[] = "/dev/ttySAC2";
char send_buf[] = "hello";
struct termios uart_setting_struct;
fd = open(tty_addr,O_RDWR|O_NOCTTY);
if(fd < 0)
{
printf("打开%s失败\r\n",tty_addr);
return 0;
}
printf("打开%s成功\r\n",tty_addr);
tcgetattr(fd,&uart_setting_struct);
uart_in_speed = cfgetispeed(&uart_setting_struct);
if(uart_in_speed != UART_BAUD)
{
cfsetispeed(&uart_setting_struct,UART_BAUD);
}
uart_out_speed = cfgetospeed(&uart_setting_struct);
if(uart_out_speed != UART_BAUD)
{
cfsetospeed(&uart_setting_struct,UART_BAUD);
}
tcsetattr(fd,TCSANOW,&uart_setting_struct);
write(fd,send_buf,strlen(send_buf));
close(fd);
return 0;
}
以115200的波特率打开串口调试助手,我用的是picocom,-b表示使用的波特率,/dev/ttyUSB0为usb转串口模块的串口号,ctrl+a,ctrl+q可以退出调试助手
hokamyuen@hokamyuen-deepin:~$ sudo picocom -b 115200 /dev/ttyUSB0
打开调试助手之后,把编译号好的程序下载的板子上并运行,可以在调试助手中看到输出的信息
picocom v1.7
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : 115200
parity is : none
databits are : 8
escape is : C-a
local echo is : no
noinit is : no
noreset is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv
imap is :
omap is :
emap is : crcrlf,delbs,
Terminal ready
hello
Thanks for using picocom
串口设置好之后就当做普通文件来读写就好了。
此文章为本人的学习过程,如有错误请指出!