我是靠谱客的博主 任性西装,最近开发中收集的这篇文章主要介绍【回顾】Linux下的串口编程(实现AT指令的收发)前言1. 相关知识2. minicom工具3. 代码具体实现4. 总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 前言
  • 1. 相关知识
    • 1.1 termios 结构
    • 1.2 相关API函数
      • 1.2.1 open函数
      • 1.2.2 fcntl函数
      • 1.2.3 isatty函数
      • 1.2.4 tcgetattr() 与 tcsetattr()
        • tcgetattr()
        • tcsetattr()
      • 1.2.5 cfsetispeed() 与 cfsetospeed()
      • 1.2.6 tcflush()
  • 2. minicom工具
    • 2.1 安装minicom工具
    • 2.2 端口配置
    • 2.3 设置回显
  • 3. 代码具体实现
    • 3.1 流程图
    • 3.2 代码如下
      • 3.2.1 serial.c
      • 3.2.2 serial.h
      • 3.2.3 erialinit.c
      • 3.2.4 serialinit.h
      • 3.2.5 serialswap.c
      • 3.2.6 serialswap.h
  • 4. 总结

前言

现树莓派下基于4G模块(EC200U-CN)的短信收发功能,其实就是通过串口发送AT指令来实现的,所以在这个项目中我们第一步首先要学习Liunx下的串口编程;学习linux下的串口编程,则需要了解串口通信协议。串口通信,主要设置好波特率、数据位、奇偶校验位、停止位等参数。

1. 相关知识

1.1 termios 结构

termios 结构是在POSIX规范中定义的标准接口,它类似于系统V中的termio接口,通过设置termios类型的数据结构中的值和使用一小组函数调用,你就可以对终端接口进行控制。

struct termios
{
tcflag_t c_iflag;
//输入模式标志
tcflag_t c_oflag;
//输出模式标志
tcflag_t c_cflag;
//控制模式标志
tcflag_t c_lflag;
//本地模式标志
cc_t
c_cc[NCCS];
//控制字符
speed_t c_isspeed;
//输入波特率
speed_t c_ospedd;
//输出波特率
}

通过设定对应功能的结构体成员以达到控制串口属性的目的,属性的设置是通过标志位来实现的,通过与,或和取反等方式,来将对应的功能模块的标志位置0或者置1(与非运算置0,或运算置1),从而告诉系统是否要有此功能,关于次结构体详细查看:termio结构体。

1.2 相关API函数

1.2.1 open函数

fd = open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NONBLOCK);

Open函数中除普通参数外,另有两个参数O_NOCTTY和O_NDELAY。
O_NOCTTY: 通知linux系统,这个程序不会成为这个端口的控制终端,如果不使用该选项,一些输入字符
可能会影响进程的运行(如一些产生中断信号的键盘输入字符等)。
O_NDELAY: 通知linux系统不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。

1.2.2 fcntl函数

fcntl() 函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup 、 dup2 作用相同)、获取 / 设置文件描述符标志、获取 / 设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ )

fd : 文件描述符。
cmd : 操作命令。此参数表示我们将要对 fd 进行什么操作,cmd 操作命令大致可以分为以下 5 种功能:
复制文件描述符( cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC );
获取 / 设置文件描述符标志( cmd=F_GETFD 或 cmd=F_SETFD );
获取 / 设置文件状态标志( cmd=F_GETFL 或 cmd=F_SETFL );
获取 / 设置异步 IO 所有权( cmd=F_GETOWN 或 cmd=F_SETOWN );
获取 / 设置记录锁( cmd=F_GETLK 或 cmd=F_SETLK);
fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使用。
返回值: 执行失败情况下,返回 -1 ,并且会设置 errno ;
执行成功的情况下,其返回值与 cmd (操作命 令)有关,
譬如 cmd=F_DUPFD (复制文件描述符)将返回一个新的文件描述符、 cmd=F_GETFD (获取文 件描述符标志)将返回文件描述符标志、cmd=F_GETFL (获取文件状态标志)将返回文件状态标志等。

1.2.3 isatty函数

#include<unistd.h>
int isatty(int desc);

如果参数desc所代表的文件描述词为一终端机则返回1,否则返回0。

返回值
如果文件为终端机则返回1,否则返回0。

1.2.4 tcgetattr() 与 tcsetattr()

tcgetattr()

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

参数
int fd: 打开串口文件后,获取到的文件描述符
struct termios &termios_p: termios 类型的结构体,包含在 <termios.h> 头文件中,这里需要传地址或指针
返回值:成功返回 0,失败返回 -1
函数功能:
功能:获取文件描述符对应串口的原始属性,并保存在第二个参数中,通常获取的原始属性需要进行备份,在程序退出之前要将其修改回来,否则无法继续使用串口。

tcsetattr()

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

参数
int fd: 要设置属性的文件描述符
int actions: 设置属性时,可以控制属性生效的时刻,actions可以取下面几个值:
TCSANOW: 立即生效
TCADRAIN: 改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变)
TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。
*termios termios_p: 用来设置的串口属性的结构体指针,通过设置好属性的termios后,传入函数即可
返回值: 成功返回 0 ,失败返回-1.
功能:设置文件描述符对应串口的属性。

1.2.5 cfsetispeed() 与 cfsetospeed()

cfsetispeed() —— 串口输入波特率的设置
cfsetospeed() —— 串口输出波特率的设置

int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
  • 参数

termios_p: 通过结构体来设置串口通信的属性,这是指向该结构体 termios 的指针;
speed: 因为串口通信没有时钟线,是一种异步通信,要想达到通信双发收发信息的统一,就需要设置输入输出波特率相同。

  • 返回值:

如果成功返回0,否则返回-1

  • 波特率常量:

CBAUD 掩码
  B0 0波特
  B50 50波特
  B75 75波特
  B110 100波特
  B134 134波特
  B150 150波特
  B200 200波特
  B300 300波特
  B600 600波特
  B1200 1200波特
  B1800 1800波特
  B2400 2400波特
  B9600 9600波特
  B19200 19200波特
  B38400 38400波特
  B57600 57600波特
  B115200 115200波特

1.2.6 tcflush()

#include <termios.h>
int tcflush(int fd,int quene)

参数
fd: 要操作的文件描述符
quene: 操作位置,可以取下面三个值:
TCIFLUSH:清空输入队列
TCOFLUSH:清空输出队列
TCIOFLUSH:清空输入输出队列
返回值:成功返回 0 ,失败返回 -1

注意:
在打开串口后,串口其实已经可以开始读取 数据了 ,这段时间用户如果没有读取,将保存在缓冲区里,如果用户不想要开始的一段数据,或者发现缓冲区数据有误,可以使用这个函数清空缓冲
需要注意,如果是在任务中,需要不停地写入数据到串口设备,千万不能在每次写入数据到设备前,进行flush以前数据的操作,因为两次写入的间隔是业务控制的,内核不会保证在两次写入之间一定把数据发送成功。flush操作一般在打开或者复位串口设备时进行操作。

2. minicom工具

minicom是LIUNX环境下的一个串口调试工具。比较主流,用户较广。也能找到它的源码,非常适合开发者使用与学习。下面就开始本文正篇。(为什么要说这个呢?因为我们接下来就是要用代码来实现这个minocom这个工具)

2.1 安装minicom工具

sudo apt-get install minicom

sudo minicom -h (查看命令的帮助信息)
在这里插入图片描述

2.2 端口配置

sudo minicom -s
在这里插入图片描述
然后选择第三个 Serial port setup(串口设置)
按下A键即可修改串口路径名(其他的值一般已经默认好,波特率这里设为115200),修改完后按下回车,再按一次回车返回上一级目录,保存设置。
在这里插入图片描述
在这里插入图片描述
选择 Exit 退出,就会调到如下界面:
在这里插入图片描述

2.3 设置回显

现在就可以输入AT指令但是不会有回显,接下来要设置回显
 字符回显有两种方式,一种是模块的字符回显,一种是minicom的字符回显。
  一、执行AT命令“ATE1”打开模块的字符回显功能。打开后输入一个字符就能看到模块回显一个字符了。
  二、 如果不想打开模块的字符回显,可以使用minicom提供的字符回显功能。在minicom中按下crtl键,再按下“A”键,再按下’Z‘键,看到如下:
在这里插入图片描述
在这里插入图片描述
再按下“E”键就可以打开了。再输入“AT”,就会回显两次了。
在这里插入图片描述

3. 代码具体实现

3.1 流程图

在这里插入图片描述

3.2 代码如下

3.2.1 serial.c

主函数的实现,主要功能:命令行参数解析(从命令行输入串口名,波特率,数据位,奇偶校验位,停止位),安转信号,使用多路复用select监听标准输入和串口。

#include "serial.h"
int
g_stop = 0;
int main(int argc, char **argv)
{
int
opt = -1;
int
rv = 0;
char
*progname = NULL;
fd_set
rdfds;
char
send_buf[128] = {0};
char
rece_buf[128] = {0};
serial_ctx_t
serial;
struct option longopts[] = {
{"name",required_argument,NULL,'n'},
{"baudrate",required_argument,NULL,'b'},
{"databits",required_argument,NULL,'d'},
{"even",no_argument,NULL,'e'},
{"odd",no_argument,NULL,'o'},
{"stopbits",required_argument,NULL,'s'},
{"help",no_argument,NULL,'h'},
{NULL,0,NULL,0}
};
progname = basename(argv[0]);
memset(&serial,0,sizeof(serial_ctx_t));
while((opt = getopt_long(argc,argv,":n:b:d:eos:h",longopts,NULL)) != -1)
{
switch(opt)
{
case 'n': strncpy(serial.serialname,optarg,sizeof(serial.serialname)); break;
case 'b': serial.baudrate = atoi(optarg); break;
case 'd': serial.databits = atoi(optarg); break;
case 'e': serial.parity = 0; break;
case 'o': serial.parity = 1; break;
case 's': serial.stopbits = atoi(optarg); break;
case 'h': printf_help(progname); return 0;
default: return 0;
}
}
if(0 == strlen(serial.serialname))
{
printf_help(progname);
return 1;
}
if((rv = serial_open(&serial)) < 0)
{
printf("open serial port: %s failure and rv: %dn",serial.serialname,rv);
return 2;
}
/* serial port init and set serial port related
value */
if((rv = serial_init(&serial)) < 0)
{
printf("serial port init failure rv: %dn",rv);
return 3;
}
/* ctrl+C capture SIGINT execute sig_hand1 function */
signal(SIGINT,sig_handl);
fflush(stdin);
printf("-----------------------------------------------------------------------------n");
printf("|
Start to comunicate with serial port
|n");
printf("-----------------------------------------------------------------------------n");
while(!g_stop)
{
FD_ZERO(&rdfds);
FD_SET(STDIN_FILENO,&rdfds); //Standard input joins the collection
FD_SET(serial.fd,&rdfds);
//serial port fd joins the collection
rv = select(serial.fd + 1,&rdfds,NULL,NULL,NULL);
if(rv < 0)
{
printf("main_select() serial post failure: %s and program exit!n",strerror(errno));
break;
}
else if(0 == rv)
{
printf("select() serial post get timeoutn");
continue;
}
if(FD_ISSET(STDIN_FILENO,&rdfds))
{
memset(send_buf,0,sizeof(send_buf));
fgets(send_buf,sizeof(send_buf),stdin);
packet_buf(send_buf);
rv = serial_send(send_buf,serial,strlen(send_buf));
if(rv < 0)
{
printf("serial port send data failure and rv: %dn",rv);
break;
}
fflush(stdin);
}
else if(FD_ISSET(serial.fd,&rdfds))
{
memset(rece_buf,0,sizeof(rece_buf));
rv = serial_rece(rece_buf,serial,sizeof(rece_buf),TIMEOUT);
if(rv < 0)
{
printf("serial port receive data failure and rv: %dn",rv);
break;
}
printf("%sn",rece_buf);
fflush(stdout);
}
}
printf("exitn");
if((rv = serial_close(&serial)) < 0)
{
printf("serial port close failure and rv: %dn",rv);
return 4;
}
return 0;
}
void packet_buf(char *buf)
{
int i = strlen(buf);
strcpy(&buf[i-1],"r");
}
/* SIGINT handle function */
void sig_handl(int signum)
{
g_stop = 1;
}
void printf_help(char *progname)
{
printf("-------------------------
The correct input is as follows
--------------------------n");
printf("../bin/%s --name /dev/xxx --baudrate xxx --databits xxx (--even) (--odd) --stopbits xxx (--help)n",progname);
printf("../bin/%s --name /dev/ttyUSB0 --baudrate 115200 --databits 8 --even --stopbits 2n",progname);
printf("[--name]
is name of the serial port device you want to use. eg: --name /dev/ttyUSB0n");
printf("[--baudrate]
is setting baudrate. eg: --baudrate 115200n");
printf("[--databits]
is setting databits. eg: --databits 8n");
printf("[(--even) (--odd)]
is setting even parity or odd parity or no parity. eg: --even or --odd or not fill(no parity)n");
printf("[--stopbits]
is setting stopbits. eg: --stopbits 1n");
printf("[--help]
is help messagen");
}

3.2.2 serial.h

#ifndef
_SERIAL_H_
#define
_SERIAL_H_
#include <libgen.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/select.h>
#include <signal.h>
#include "serialinit.h"
#include "serialswap.h"
#define TIMEOUT
2
void packet_buf(char *buf);
void sig_handl(int signum);
void handle_stop(int sig);
void printf_help(char *progname);
#endif
/* ----- #ifndef _COMPORT_H_
----- */

3.2.3 erialinit.c

串口初始化文件,主要功能:打开串口、串口的相关属性设置(包括波特率,数据位,奇偶校验位,停止位等)、恢复串口原来属性以及关闭串口。

#include "serialinit.h"
int serial_open(serial_ctx_t *serial)
{
if(!serial)
{
printf("%s,Invalid input argumentsn",__func__);
return -1;
}
/* open serial port */
serial->fd = open(serial->serialname,O_RDWR | O_NOCTTY | O_NONBLOCK);
if(serial->fd < 0)
{
printf("open serial port failure: %sn",strerror(errno));
return -2;
}
/* aet serial port is blocked */
if(fcntl(serial->fd,F_SETFL,0) < 0)
{
printf("fcntl check failuren");
return -3;
}
/* check whether it is a terminal device */
if(0 == isatty(serial->fd))
{
printf("[%s:%d] is not a terminal devicen",serial->serialname,serial->fd);
return -3;
}
printf("open serial port: %s successfullyn",serial->serialname);
return 0;
}
int serial_close(serial_ctx_t *serial)
{
if(!serial)
{
printf("%s,Invalid input argumentsn",__func__);
return -1;
}
if(tcsetattr(serial->fd,TCSANOW,&(serial->oldtermios)) < 0)
{
printf("set serial port old options failure: %sn",strerror(errno));
return -2;
}
close(serial->fd);
return 0;
}
int serial_init(serial_ctx_t *serial)
{
struct termios
newtermios;
char
setbaudrate[32] = {0};
if(!serial)
{
printf("%s,Invalid input argumentsn",__func__);
return -1;
}
memset(&newtermios,0,sizeof(newtermios));
memset(&(serial->oldtermios),0,sizeof(serial->oldtermios));
/* Get the original properties and copy them */
if(tcgetattr(serial->fd,&(serial->oldtermios)) < 0)
{
printf("get serial port oldtermios failure: %sn",strerror(errno));
return -2;
}
/* use serial port properties */
if(tcgetattr(serial->fd,&newtermios) < 0 )
{
printf("get serial port newtermios failure: %sn",strerror(errno));
return -3;
}
/* Modify the control mode to ensure that the program does not occupy the serial port */
newtermios.c_cflag |= CLOCAL;
/* Start the receiver and read the input data from the serial port */
newtermios.c_cflag |= CREAD;
/* CSIZE character size mask that will be associated with setting databits to the Peugeot position zero */
newtermios.c_cflag &= ~CSIZE;
/*
* ICANON: standard input
* ECHO:
the entered characters are displayed
* ECHOE:
If the ICANON flag is set, ERASE deletes the previous character and WERASE deletes the previous word
* ISIG:
When the INTR/QUIT/SUSP/DSUSP characters are received, a corresponding signal is generated
*/
newtermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/*
* BRKINT: BREAK discards the data in the input and output queues (flush)
* ICRNL:
Convert CR in the input to NL
* INPCK:
Parity check is allowed
* ISTRIP: Strip the eighth bit
* IXON:
Stripping the 8th bit allows XON/XOF flow control on the output
*/
newtermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
/* OPOST: Represents the output after processing, according to the original data output */
newtermios.c_oflag &= ~OPOST;
/* set input and output baudrate */
if(serial->baudrate)
{
memset(setbaudrate,0,sizeof(setbaudrate));
snprintf(setbaudrate,sizeof(setbaudrate),"B%d",serial->baudrate);
cfsetispeed(&newtermios,(speed_t)setbaudrate);
cfsetospeed(&newtermios,(speed_t)setbaudrate);
}
else
{
cfsetispeed(&newtermios,B115200);
cfsetospeed(&newtermios,B115200);
}
/* set databits */
switch(serial->databits)
{
case '5': newtermios.c_cflag |= CS5; break;
case '6': newtermios.c_cflag |= CS6; break;
case '7': newtermios.c_cflag |= CS7; break;
case '8': newtermios.c_cflag |= CS8; break;
default:
newtermios.c_cflag |= CS8; break; //default databits
}
/* parity bit */
switch(serial->parity)
{
/* even parity */
case 0:
newtermios.c_cflag |= PARENB;
newtermios.c_cflag &= ~PARODD;
newtermios.c_iflag |= INPCK;
break;
/* odd parity */
case 1:
newtermios.c_cflag |= PARENB;
newtermios.c_cflag |= PARODD;
newtermios.c_iflag |= INPCK;
break;
/* default no parity */
default:
newtermios.c_cflag &= ~PARENB;
newtermios.c_iflag &= ~INPCK;
break;
}
/* set stopbits */
switch(serial->stopbits)
{
case 1: newtermios.c_cflag &= ~CSTOPB; break;
case 2: newtermios.c_cflag |= CSTOPB; break;
default: newtermios.c_cflag &= ~CSTOPB; break;
}
newtermios.c_cc[VTIME] = 0;
//Maximum waiting time
newtermios.c_cc[VMIN] = 0;
//Minimum received character
if(tcflush(serial->fd,TCIFLUSH) < 0)
{
printf("flush input queue failure: %sn",strerror(errno));
return -4;
}
/* set serial port property */
if(tcsetattr(serial->fd,TCSANOW,&newtermios) < 0)
{
printf("set serial port property failure: %sn",strerror(errno));
return -5;
}
printf("--------------
serial port init successfully
---------------n");
return 0;
}

3.2.4 serialinit.h

#ifndef
_SERIALINIT_H_
#define
_SERIALINIT_H_
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
typedef struct serial_ctx_s
{
int
fd;
//serial fd
char
serialname[128];
int
baudrate;
int
databits;
int
parity;
int
stopbits;
struct termios
oldtermios; //the original properties of the serial port
}serial_ctx_t;
int serial_open(serial_ctx_t *serial);
int serial_close(serial_ctx_t *serial);
int serial_init(serial_ctx_t *serial);
#endif
/* ----- #ifndef _COMPORTINIT_H_
----- */

3.2.5 serialswap.c

对串口设备文件进行I/O操作,主要功能:实现串口的发送和接收数据

#include "serialswap.h"
int serial_send(char *send_buf,serial_ctx_t serial,int send_len)
{
char
*ptr,*end;
int
rv = 0;
if(!send_buf || send_len <= 0)
{
printf("%s,Invalid input argumentsn",__func__);
return -1;
}
if(send_len > SEND_BUF)
{
ptr = send_buf;
end = send_buf + send_len;
do
{
if(SEND_BUF < (end - ptr))
{
rv = write(serial.fd,ptr,SEND_BUF);
if(rv <= 0 || rv != SEND_BUF)
{
printf("Write to serial port failure: %sn",strerror(errno));
return -2;
}
ptr += SEND_BUF;
}
else
{
rv = write(serial.fd,ptr,(end - ptr));
if(rv <= 0 || rv != (end - ptr))
{
printf("Write to serial port failure: %sn",strerror(errno));
return -3;
}
ptr += (end - ptr);
}
}while(end > ptr);
}
else
{
rv = write(serial.fd,send_buf,send_len);
if(rv <= 0 || rv != send_len)
{
printf("Write to serial port failure: %sn",strerror(errno));
return -4;
}
}
return 0;
}
int serial_rece(char *rece_buf,serial_ctx_t serial,int rece_len,int timeout)
{
int
rv = 0;
fd_set
rdfds;
struct timeval
time_out;
if(!rece_buf || rece_buf <= 0)
{
printf("%s,Invalid input argumentsn",__func__);
return -1;
}
if(timeout)
{
time_out.tv_sec = (time_t)(timeout / 1000);
time_out.tv_usec = 0;
FD_ZERO(&rdfds);
FD_SET(serial.fd,&rdfds);
rv = select(serial.fd + 1,&rdfds,NULL,NULL,&time_out);
if(rv < 0)
{
printf("select() serial post failure: %sn",strerror(errno));
return -2;
}
else if(0 == rv)
{
printf("select() serial post get timeoutn");
return -3;
}
}
usleep(1000);
rv = read(serial.fd,rece_buf,rece_len);
if(rv <= 0)
{
printf("Read serial port data failure: %sn",strerror(errno));
return -4;
}
return 0;
}

3.2.6 serialswap.h

#ifndef
_SERIALSWAP_H_
#define
_SERIALSWAP_H_
#include <sys/select.h>
#include "serialinit.h"
#define SEND_BUF
128
int serial_send(char *send_buf,serial_ctx_t serial,int send_len);
int serial_rece(char *rece_buf,serial_ctx_t serial,int rece_len,int timeout);
#endif
/* ----- #ifndef _SERIALSWAP_H_
----- */

运行结果图:
在这里插入图片描述

4. 总结

1、以上代码只是实现类似minicom串口调式工具的其中的功能,只能实现串口AT指令的发送,并不能只实现发送和接收短信功能,后期会继续完善
2、用fgets()获取标准输入的字符串时,得到的字符串结尾会带上换行符"n",所以要把’n" 换成AT指令指定的结尾格式’r".

最后

以上就是任性西装为你收集整理的【回顾】Linux下的串口编程(实现AT指令的收发)前言1. 相关知识2. minicom工具3. 代码具体实现4. 总结的全部内容,希望文章能够帮你解决【回顾】Linux下的串口编程(实现AT指令的收发)前言1. 相关知识2. minicom工具3. 代码具体实现4. 总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(99)

评论列表共有 0 条评论

立即
投稿
返回
顶部