(转载)RS485---串口编程
最近被安排实现 linux rs485 串口通信。期间遇到各种问题,现在加以分析总结。一、硬件相关 1.1 单工、半双工、全双工首先,我使用的是芯片为 SP3485E 为半双工通信。那么先要明确什么是单工、半双工、全双工。单工数据传输只支持数据在一个方向上传输;半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方...
最近被安排实现 linux rs485 串口通信。期间遇到各种问题,现在加以分析总结。
一、硬件相关
1.1 单工、半双工、全双工
首先,我使用的是芯片为 SP3485E 为半双工通信。那么先要明确什么是单工、半双工、全双工。单工数据传输只支持数据在一个方向上传输;
半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。
网卡的全双工(Full Duplex)是指网卡在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。
提到全双工,就不能不提与之密切对应的另一个概念,那就是“半双工(Half Duplex)”,
所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,
当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。
1.2 关于RS485通信
RS232 标准是诞生于 RS485 之前的,但是 RS232 有几处不足的地方:接口的信号电平值较高,达到十几 V,使用不当容易损坏接口芯片,电平标准也与TTL 电平不兼容。
传输速率有局限,不可以过高,一般到一两百千比特每秒(Kb/s)就到极限了。
接口使用信号线和 GND 与其它设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。
传输距离有限,最多只能通信几十米。
通信的时候只能两点之间进行通信,不能够实现多机联网通信。
针对 RS232 接口的不足,就不断出现了一些新的接口标准,RS485 就是其中之一,它具备以下的特点:
采用差分信号。我们在讲 A/D 的时候,讲过差分信号输入的概念,同时也介绍了差分输入的好处,最大的优势是可以抑制共模干扰。
尤其当工业现场环境比较复杂,干扰比较多时,采用差分方式可以有效的提高通信可靠性。RS485 采用两根通信线,通常用 A 和 B 或者 D+和 D-来表示。逻辑“1”以两线之间的电压差为+(0.2~6)V 表示,逻辑“0”以两线间的电压差为-(0.2~6)V 来表示,是一种典型的差分通信。
RS485 通信速率快,最大传输速度可以达到 10Mb/s 以上。
RS485 内部的物理结构,采用的是平衡驱动器和差分接收器的组合,抗干扰能力也大大增加。
传输距离最远可以达到 1200 米左右,但是它的传输速率和传输距离是成反比的,只有在 100Kb/s 以下的传输速度,才能达到最大的通信距离,如果需要传输更远距离可以使用中继。
可以在总线上进行联网实现多机通信,总线上允许挂多个收发器,从现有的 RS485芯片来看,有可以挂 32、64、128、256 等不同个设备的驱动器。
RS485 的接口非常简单,与 RS232 所使用的 MAX232 是类似的,只需要一个 RS485转换器,就可以直接与单片机的 UART 串口连接起来,并且使用完全相同的异步串行通信协议。
但是由于 RS485 是差分通信,因此接收数据和发送数据是不能同时进行的,也就是说它是一种半双工通信。RS485为差分通信:最大的优势是可以抑制共模干扰。
1.3 关于sp3485硬件分析
上图为SP3485原理图
(1)引脚说明:
Pin1 - RO: 接收器输出
Pin2 - RE#:接收器输出使能 (低电平有效)
Pin3 - DE: 驱动器输出使能 (高电平有效)
Pin4 - DI: 驱动器输入
Pin5 - GND: 连接地
Pin6 - A: 驱动器输出/接收器输入 (同相)
Pin7 - B: 驱动器输出/接收器输入 (反相)
Pin8 - Vcc
注意将AB间120欧姆去掉,如果采用阻抗匹配的电缆,300米以内几乎可以不用加终端电阻。加终端电阻的缺点就是增大了线路的无用功耗,尤其是电池系统供电时,降低了电池的续航能力。
PS: 我一开始没有将它去掉,导致只能发送数据,无法接收数据。
(2)电气特性
RS-232电平的电气特性
EIA电平(串口)
逻辑1:-3V~-15V
逻辑0:+3V~+15V
TTL电平(TPAD)
逻辑1:+2V~+5V
逻辑0:+0V~+0.8V
接收数据:EIA->TTL 232转TTL
发送数据:TTL->EIA TTL转232
串口异步通信的重要参数:
波特率: bps (bit per second)
数据位的个数: 5 6 7 8
校验方式: 奇校验 偶校验 无校验
停止位: 1bit 2bit
RS485电平 和RS422电平 由于两者均采用 差分传输(平衡传输)的方式,所以他们的电平方式,一般有两个引脚 A,B
发送端 AB间的电压差
+2 ~ +6v 1
-2 ~ -6v 0
接收端 AB间的电压差
大于 +200mv 1
小于 -200mv 0
定义逻辑1为B>A的状态
定义逻辑0为A>B的状态
AB之间的电压差不小于200mv
一对一的接头的情况下:
RS232 可做到双向传输,全双工通讯 最高传输速率 20kbps
422 只能做到单向传输,半双工通讯,最高传输速率10Mbps
485 双向传输,半双工通讯, 最高传输速率10Mbps
(3)串行数据的格式
异步串行数据的一般格式是:起始位+数据位+停止位,(8-N-1格式) 其中起始位1 位,数据位可以是5、6、7、8位,停止位可以是1、1.5、2位。
起始位是一个值为0的位,所以对于正逻辑的TTL电平,起始位是一位时间的低电平;停止位是值为1的位,所以对于正逻辑的TTL电平,停止位是高电平。线路路空闲或者数据传输结束,对于正逻辑的TTL电平,线路总是1。对于负逻辑(如RS-232电平)则相反。
例如,对于16进制数据55aaH,当采用8位数据位、1位停止位传输时,它在信号线上的波形如图1(TTL电平)和图2(RS-232电平)所示。 (先传第一个字节55,再传第二个字节aa,每个字节都是从低位向高位逐位传输)
图1 TTL电平的串行数据帧格式(55aah)
图2 RS-232电平的串行数据帧格式(55aah)
(4)根据波形图计算波特率
如图3是图1在示波器中的显示示意,其中灰色线是示波器的时间分度线,此时假设是200us/格。图3 波特率计算示意图
可以看了,第一个字节的10位(1位起始位,8位数据位和1位停止位)共占约1.05ms,这样可计算出其波特率约为:
10bit / 1.05ms X 1000 ≈ 9600 bit/s
如果上图中的时间轴是100us/格,同样可以计算出波特率应是19200bit/s。
当通讯不正常,又能观察到波形时,就可根据上述方法,从波形图计算一下波特率是否正确。
(5)根据波形图判断RS-485收发数据的正确与否
RS-485是一种半双工的串行通讯方式(RS-422为全双工),485电平芯片所以要正确接收和发送数据,必需保证控制信号和数据的同步,否则要么发送数据丢失,要么接收数据可能丢失。
RS-485发送数据时的正确时序如图4所示。
图4 RS-485的正确发送数据时序
在图4中,发送控制信号的宽度基本与数据信号的宽度一致,所以能保证发送数据的正确和发送后及时转为接收。
图5 和图6 分别是控制信号太短和控制信号太长的情况。
图5 RS-485控制信号太短时的时序
图6 RS-485控制信号太短时的时序
在图5中,由于控制信号关闭过早,则第二个字节的后两位将发送错误;在图6中,由于控制信号关闭过迟,使485芯片在发送数据后,不能及时转到接收状态,此时总线若有数据过来,则本单元将不能正确接收。
总结:只要掌握上述波形分析方法,任何异步串行数据的接收和发送问题,基本都可以得到解决。
二、串口通信
2.1 串口的操作一般都通过四个步骤来完成:
1、打开串口2、配置串口:对串口的波特率、数据位、停止位、校验码、等进行设置。
3、读写串口
4、关闭串口
2.2 完整代码:
-
#include <fcntl.h> //文件控制定义
-
#include <stdio.h> //标准输入输出定义
-
#include <stdlib.h> //标准函数库定义
-
#include <unistd.h> //Unix标准函数定义
-
#include <errno.h> //错误好定义
-
#include <termios.h> //POSIX终端控制定义
-
#include <sys/ioctl.h> //ioctl函数定义
-
#include <string.h> //字符操作
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <pthread.h>
-
#include <sys/timeb.h>
-
-
//时间戳
-
long long getSystemTime() {
-
struct timeb t;
-
ftime(&t);
-
return
1000 * t.time + t.millitm;
-
}
-
-
long
long start;
-
long
long end;
-
-
-
//定义互斥量
-
pthread_mutex_t mutex;
-
int fd_gpio;
-
-
struct termios newtio, oldtio;
-
typedef
struct {
-
int pin_idx;
-
int pin_dir;
-
int pin_sta;
-
} davinci_gio_arg;
-
-
typedef
enum {
-
AT91PIO_DIR_OUT =
0,
-
AT91PIO_DIR_INP
-
} davinci_gio_dir;
-
//驱动判断输入输出模式
-
-
davinci_gio_arg arg;
-
-
#define DEV_PIO_LED "/dev/pio"
-
// 需要手动添加设备号 mknod /dev/pio c 203 0
-
#define PIO_NUM 47
-
// 47pin 为控制输入输出方向引脚
-
#define DEV_UART "/dev/ttyS1"
-
// /dev/ttyS1 为串口设备
-
-
#define IOCTL_PIO_SETDIR 1 //set gpio direct
-
#define IOCTL_PIO_GETDIR 2 //get gpio direct
-
#define IOCTL_PIO_SETSTA 3 //set gpio status
-
#define IOCTL_PIO_GETSTA 4 //get gpio status
-
-
//保存信息
-
int log_init( const char *strFileName )
-
{
-
int fdLog =
-1;
-
-
if(
-1 == (fdLog = open( strFileName, O_CREAT|O_TRUNC ) ) )
-
{
-
}
-
close( fdLog );
-
}
-
-
int log_out( const char *strFileName, const char * szLog )
-
{
-
int fdLog =
-1;
-
-
if(
-1 == ( fdLog = open( strFileName, O_CREAT|O_WRONLY|O_APPEND ) ) )
-
{
-
printf(
"LOG (%s) open error!\n", strFileName );
-
return
-1;
-
}
-
-
write( fdLog, szLog,
strlen( szLog ) );
-
-
close( fdLog );
-
-
return
0;
-
}
-
-
//配置串口
-
/* 参数说明:fd 设备文件描述符,nspeed 波特率,nbits 数据位数(7位或8位),
-
parity 奇偶校验位('n'或'N'为无校验位,'o'或'O'为偶校验,'e'或'E'奇校验),
-
nstop 停止位(1位或2位)
-
成功返回1,失败返回-1。
-
*/
-
int set_com_opt( int fd, int nspeed, int nbits, char parity, int nstop )
-
{
-
char szTmp[
128];
-
//打印配置信息
-
sprintf( szTmp,
"set_com_opt - speed:%d,bits:%d,parity:%c,stop:%d\n",
-
nspeed, nbits, parity, nstop );
-
-
log_out(
"./485.log", szTmp );
-
//保存并测试现在有串口参数设置,在这里如果串口号等出错,会有相关的出错信息
-
if( tcgetattr( fd, &oldtio ) !=
0 )
-
{
-
-
sprintf( szTmp,
"SetupSerial 1" );
-
-
log_out(
"./485.log", szTmp );
-
-
perror(
"SetupSerial 1" );
-
return
-1;
-
}
-
-
//修改输出模式,原始数据输出
-
bzero( &newtio,
sizeof( newtio ));
-
newtio.c_cflag &=~(OPOST);
-
-
//屏蔽其他标志位
-
newtio.c_cflag |= (CLOCAL | CREAD );
-
newtio.c_cflag &= ~CSIZE;
-
-
//设置数据位
-
switch( nbits )
-
{
-
case
7:
-
newtio.c_cflag |= CS7;
-
break;
-
case
8:
-
newtio.c_cflag |= CS8;
-
break;
-
default:
-
perror(
"Unsupported date bit!\n");
-
return
-1;
-
}
-
-
//设置校验位
-
switch( parity )
-
{
-
case
'n':
-
case
'N':
//无奇偶校验位
-
newtio.c_cflag &= ~PARENB;
-
newtio.c_iflag &= ~INPCK;
-
break;
-
case
'o':
-
case
'O':
//设置为奇校验
-
newtio.c_cflag |= ( PARODD | PARENB );
-
newtio.c_iflag |= ( INPCK | ISTRIP );
-
break;
-
case
'e':
-
case
'E':
//设置为偶校验
-
newtio.c_iflag |= ( INPCK |ISTRIP );
-
newtio.c_cflag |= PARENB;
-
newtio.c_cflag &= ~PARODD;
-
break;
-
default:
-
perror(
"unsupported parity\n");
-
return
-1;
-
}
-
-
//设置停止位
-
switch( nstop )
-
{
-
case
1:
-
newtio.c_cflag &= ~CSTOPB;
-
break;
-
case
2:
-
newtio.c_cflag |= CSTOPB;
-
break;
-
default :
-
perror(
"Unsupported stop bit\n");
-
return
-1;
-
}
-
-
//设置波特率
-
switch( nspeed )
-
{
-
case
2400:
-
cfsetispeed( &newtio, B2400 );
-
cfsetospeed( &newtio, B2400 );
-
break;
-
case
4800:
-
cfsetispeed( &newtio, B4800 );
-
cfsetospeed( &newtio, B4800 );
-
break;
-
case
9600:
-
cfsetispeed( &newtio, B9600 );
-
cfsetospeed( &newtio, B9600 );
-
break;
-
case
115200:
-
cfsetispeed( &newtio, B115200 );
-
cfsetospeed( &newtio, B115200 );
-
break;
-
case
460800:
-
cfsetispeed( &newtio, B460800 );
-
cfsetospeed( &newtio, B460800 );
-
break;
-
default:
-
cfsetispeed( &newtio, B9600 );
-
cfsetospeed( &newtio, B9600 );
-
break;
-
}
-
-
//设置等待时间和最小接收字符
-
newtio.c_cc[VTIME] =
0;
-
newtio.c_cc[VMIN] =
0;
-
//VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。
-
-
//输入模式
-
newtio.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);
-
//设置数据流控制
-
newtio.c_iflag &= ~(IXON|IXOFF|IXANY);
//使用软件流控制
-
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
-
tcflush( fd, TCIFLUSH );
-
//激活配置 (将修改后的termios数据设置到串口中)
-
if( tcsetattr( fd, TCSANOW, &newtio ) !=
0 )
-
{
-
sprintf( szTmp,
"serial set error!\n" );
-
-
log_out(
"./485.log", szTmp );
-
perror(
"serial set error!" );
-
return
-1;
-
}
-
-
log_out(
"./485.log",
"serial set ok!\n" );
-
return
1;
-
}
-
-
//打开串口并返回串口设备文件描述
-
int open_com_dev( char *dev_name )
-
{
-
int fd;
-
char szTmp[
128];
-
-
log_init(
"./485.log" );
-
if(( fd = open( dev_name, O_RDWR|O_NOCTTY|O_NDELAY)) ==
-1 )
-
{
-
-
perror(
"open\n");
-
//printf("Can't open Serial %s Port!\n", dev_name );
-
sprintf( szTmp,
"Can't open Serial %s Port!\n", dev_name );
-
-
log_out(
"./485.log", szTmp );
-
-
return
-1;
-
}
-
-
sprintf( szTmp,
"open %s ok!\n", dev_name );
-
log_out(
"./485.log", szTmp );
-
-
if(fcntl(fd,F_SETFL,
0)<
0)
-
{
-
printf(
"fcntl failed!\n");
-
}
-
//printf("Open %s ok\n",dev_name );
-
return fd;
-
}
-
-
//发送云台数据
-
void* task(void* p)
-
{
-
char ch;
-
int j =
0, nread =
0;
-
while(
scanf (
"%s", &ch) ==
1)
-
{
-
pthread_mutex_lock (&mutex);
-
arg.pin_sta =
1;
//设为高电平 发送态
-
ioctl(fd_gpio, IOCTL_PIO_SETSTA, &arg);
-
-
int fd = open_com_dev( DEV_UART );
-
if( fd <
0 )
-
{
-
printf(
"open UART device error! %s\n", DEV_UART );
-
}
-
else
-
set_com_opt(fd,
2400,
8,
'n',
1);
-
//set_com_opt(fd, 9600,8,'n',1);
-
-
char buff[] = {
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11,
0x11};
-
int len = write(fd,buff,
sizeof (buff));
-
if (len <
0)
-
{
-
perror (
"write err");
-
exit (
-1);
-
}
-
//打印发送数据
-
printf (
"sead: ");
-
for (j =
0; j <
sizeof(buff); j++)
-
{
-
printf (
"%02X ", buff[j]);
-
}
-
printf (
"\n");
-
-
//清除scanf缓冲区
-
scanf (
"%*[^\n]");
-
scanf (
"%*c");
-
close (fd);
-
pthread_mutex_unlock (&mutex);
-
}
-
}
-
-
//单片机数据收发
-
void* task1(void* p)
-
{
-
char buf[
255];
-
int j =
0, res =
0, nread =
0, i =
0;
-
while (
1) {
-
pthread_mutex_lock (&mutex);
-
-
arg.pin_sta =
1;
//设为高电平 发送态
-
ioctl(fd_gpio, IOCTL_PIO_SETDIR, &arg);
-
//打开/dev/pio
-
int fd_s = open_com_dev( DEV_UART );
-
if( fd_s <
0 )
-
{
-
printf(
"open UART device error! %s\n", DEV_UART );
-
}
-
else
-
set_com_opt(fd_s,
2400,
8,
'n',
1);
-
//set_com_opt(fd_s, 9600,8,'n',1);
-
-
//发送数据
-
char buff[] = {
0xaa,
0x55,
0x05,
0x00,
0x33,
0x44,
0x14,
0x90,
0x00};
-
int len = write(fd_s,buff,
sizeof (buff));
-
if (len <
0)
-
{
-
perror (
"write err");
-
exit (
-1);
-
}
-
printf (
"sead: ");
-
for (j =
0; j <
sizeof (buff); j++)
-
{
-
printf (
"%02X ", buff[j]);
-
}
-
printf (
"\n");
-
-
close (fd_s);
-
start=getSystemTime();
-
arg.pin_sta =
0;
//设为低电平 接收态
-
ioctl(fd_gpio, IOCTL_PIO_SETSTA, &arg);
-
int fd_r=open_com_dev( DEV_UART );
-
if( fd_r <
0 )
-
{
-
printf(
"open UART device error! %s\n", DEV_UART );
-
}
-
else
-
set_com_opt(fd_r,
2400,
8,
'n',
1);
-
//set_com_opt(fd_r, 9600,8,'n',1);
-
-
//执行select
-
fd_set rd;
-
FD_ZERO(&rd);
-
FD_SET(fd_r, &rd);
-
if ((res = select (fd_r+
1,&rd,
NULL,
NULL,
NULL) )<
0)
-
{
-
perror (
"read err");
-
exit (
-1);
-
}
-
-
memset (buf,
0,
sizeof (buf));
-
if (FD_ISSET (fd_r, &rd))
-
{
-
//接收数据 8 8 2
-
int res1 =
0;
-
while ((nread = read(fd_r, buf,
8)) >
0)
-
{
-
//打印接收数据
-
for (i =
0; i < nread; i++)
-
{
-
printf (
"%02X ", buf[i]);
-
}
-
//退出循环, 这里有点疑问
-
res1 += nread;
-
if (res1 ==
18)
-
{
-
memset (buf,
0,
sizeof (buf));
-
printf (
"\n");
-
break;
-
}
-
}
-
}
-
close (fd_r);
-
pthread_mutex_unlock (&mutex);
-
end=getSystemTime();
-
printf(
"time: %lld ms\n", end-start);
-
usleep (
200000);
-
}
-
}
-
-
int main (void)
-
{
-
-
int error =
0, error1 =
0;
-
arg.pin_idx = PIO_NUM;
-
arg.pin_dir = AT91PIO_DIR_OUT;
-
//打开/dev/pio设备
-
fd_gpio = open(DEV_PIO_LED, O_RDWR);
-
if(fd_gpio <
0)
-
{
-
perror(
"fd_gpio open err");
-
exit (
-1);
-
}
-
-
//初始化互斥量
-
pthread_mutex_init (&mutex,
0);
-
pthread_t tid, tid1;
-
//创建线程
-
error = pthread_create (&tid,
NULL, task,
NULL);
-
error1 = pthread_create (&tid1,
NULL, task1,
NULL);
-
//等待线程结束
-
pthread_join (tid,
NULL);
-
pthread_join (tid1,
NULL);
-
//销毁互斥量
-
pthread_mutex_destroy(&mutex);
-
//关闭设备
-
close (fd_gpio);
-
return
0;
-
}
三、串口通信总结
虽然以上代码只有三百多行,但是其包含的内容确是很多的,下面就一一的来总结。
一般招聘信息上 都会有这样一项要求。了解Modbus基于RS485,RS232,以太网等总线的通讯协议,熟练操作Modbus相关软件。
上面我们对RS485,RS232硬件做了分析,接下来我们看一下软件上面该如何处理。主要分为下面部分来讲:
(1)串口编程详解
参看:Linux串口编程详解前面已经提到过Linux下皆为文件,这当然也包括我们今天的主角 UART0 串口。因此对他的一切操作都和文件的操作一样(涉及到了open,read,write,close等文件的基本操作)。
(一)Linux下的串口编程又那几部分组成
1. 打开串口
2. 串口初始化
3. 读串口或写串口
4. 关闭串口
(二)串口的打开
既然串口在linux中被看作了文件,那么在对文件进行操作前先要对其进行打开操作。
1.在Linxu中,串口设备是通过串口终端设备文件来访问的
即通过访问/dev/ttyS0,/dev/ttyS1,/dev/ttyS2这些设备文件实现对串口的访问。
==============================
这里有个问题:
你怎么知道访问的是哪个串口?
可以进行一下测试,echo hello > /dev/ttyS0
看看是否有hello输出。
如果串口使用不对,会出现错误:
setup serial:bad file descriptor
set parity Error
==============================
2.调用open()函数来代开串口设备,对于串口的打开操作,必须使用O_NOCTTY参数。
l O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务一个输入(eg:键盘中止信号等)都将影响进程。
l O_NDELAY:表示不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。
3.打开串口模块有那及部分组成
1> 调用open()函数打开串口,获取串口设备文件描述符
2> 获取串口状态,判断是否阻塞
3> 测试打开的文件描述符是否为终端设备
4程序:
- /*****************************************************************
- * 名称: UART0_Open
- * 功能: 打开串口并返回串口设备文件描述
- * 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
- * 出口参数: 正确返回为1,错误返回为0
- *****************************************************************/
- int UART0_Open(int fd,char* port)
- {
- fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY);
- if (FALSE == fd)
- {
- perror("Can't Open Serial Port");
- return(FASLE);
- }
- //判断串口的状态是否为阻塞状态
- if(fcntl(fd, F_SETFL, 0) < 0)
- {
- printf("fcntl failed!/n");
- return(FALSE);
- }
- else
- {
- printf("fcntl=%d/n",fcntl(fd, F_SETFL,0));
- }
- //测试是否为终端设备
- if(0 == isatty(STDIN_FILENO))
- {
- printf("standard input is not a terminal device/n");
- return(FALSE);
- }
- else
- {
- printf("isatty success!/n");
- }
- printf("fd->open=%d/n",fd);
- return fd;
- }
(三)串口的初始化
1. 在linux中的串口初始化和前面的串口初始化一样。
需要设置串口波特率,数据流控制,帧的格式(即数据位个数,停止位,校验位,数据流控制)
2. 串口初始化模块有那几部分组成:
1> 设置波特率
2> 设置数据流控制
3> 设置帧的格式(即数据位个数,停止位,校验位)
说明:
1> 设置串口参数时要用到termios结构体,因此先要通过函数 tcgettattr(fd,&options)获得串口指向termios结构的指针。termios.h头文件中定义的termios结构,如下:
- struct termios
- {
- tcflag_t c_iflag; //输入模式标志
- tcflag_t c_oflag; //输出模式标志
- tcflag_t c_cflag; //控制模式标志
- tcflag_t c_lflag; //本地模式标志
- cc_t c_line; //line discipline
- cc_t c_cc[NCC]; //control characters
- }
2> 通过cfsetispeed函数和cfsetospeed函数用来设置串口的输入/输出波特率。一般情况下,输入和输出波特率相等的。
3> 设置数据位可以通过修改termios机构体中c_flag来实现。其中CS5,CS6,CS7,CS8对应数据位的5,6,7,8。在设置数据位时,必须要用CSIZE做位屏蔽。
以下是几个数据位、停止位和校验位的设置方法:(以下均为1位停止位)
- 8位数据位、无校验位:
- Opt.c_cflag &= ~PARENB;
- Opt.c_cflag &= ~CSTOPB;
- Opt.c_cflag &= ~CSIZE;
- Opt.c_cflag |= CS8;
- 7位数据位、奇校验:
- Opt.c_cflag |= PARENB;
- Opt.c_cflag |= PARODD;
- Opt.c_cflag &= ~CSTOPB;
- Opt.c_cflag &= ~CSIZE;
- Opt.c_cflag |= CS7;
- 7位数据位、偶校验:
- Opt.c_cflag |= PARENB;
- Opt.c_cflag &= ~PARODD;
- Opt.c_cflag &= ~CSTOPB;
- Opt.c_cflag &= ~CSIZE;
- Opt.c_cflag |= CS7;
- 7位数据位、Space校验:
- Opt.c_cflag &= ~PARENB;
- Opt.c_cflag &= ~CSTOPB;
- Opt.c_cflag &= ~CSIZE;
- Opt.c_cflag |= CS7;
4> 数据流控制是使用何种方法来标志数据传输的开始和结束。
5> 在设置完波特率,数据流控制,数据位,校验位,停止位,停止位后,还要设置最小等待时间和最小接收字符。
6> 在完成配置后要通过tcsetattr()函数来激活配置。
3.程序:
- /*******************************************************************
- * 名称: UART0_Set
- * 功能: 设置串口数据位,停止位和效验位
- * 入口参数: fd 串口文件描述符
- * speed 串口速度
- * flow_ctrl 数据流控制
- * databits 数据位 取值为 7 或者8
- * stopbits 停止位 取值为 1 或者2
- * parity 效验类型 取值为N,E,O,,S
- *出口参数: 正确返回为1,错误返回为0
- *******************************************************************/
- int UART0_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)
- {
- int i;
- int status;
- int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
- B38400, B19200, B9600, B4800, B2400, B1200, B300 };
- int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300 };
- struct termios options;
- /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数,还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.
- */
- if ( tcgetattr( fd,&options) != 0)
- {
- perror("SetupSerial 1");
- return(FALSE);
- }
- //设置串口输入波特率和输出波特率
- for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
- {
- if (speed == name_arr[i])
- {
- cfsetispeed(&Options, speed_arr[i]);
- cfsetospeed(&Options, speed_arr[i]);
- }
- }
- //修改控制模式,保证程序不会占用串口
- options.c_cflag |= CLOCAL;
- //修改控制模式,使得能够从串口中读取输入数据
- options.c_cflag |= CREAD;
- //设置数据流控制
- switch(flow_ctrl)
- {
- case 0 ://不使用流控制
- options.c_cflag &= ~CRTSCTS;
- break;
- case 1 ://使用硬件流控制
- options.c_cflag |= CRTSCTS;
- break;
- case 2 ://使用软件流控制
- options.c_cflag |= IXON | IXOFF | IXANY;
- break;
- }
- //设置数据位
- options.c_cflag &= ~CSIZE; //屏蔽其他标志位
- switch (databits)
- {
- case 5 :
- options.c_cflag |= CS5;
- break;
- case 6 :
- options.c_cflag |= CS6;
- break;
- case 7 :
- options.c_cflag |= CS7;
- break;
- case 8:
- options.c_cflag |= CS8;
- break;
- default: &n
更多推荐
所有评论(0)