串口的基本原理
1 串口通訊
串口通訊(Serial Communication),是指外設和計算機間,通過數據信號線、地線等,按位進行傳輸數據的一種通訊方式。
串口是一種接口標準,它規定了接口的電氣標準,沒有規定接口插件電纜以及使用的協議。
2 串口通訊的數據格式
一個字符一個字符地傳輸,每個字符一位一位地傳輸,并且傳輸一個字符時,總是以“起始位”開始,以“停止位”結束,字符之間沒有固定的時間間隔要求。
每一個字符的前面都有一位起始位(低電平),字符本身由7位數據位組成,接著字符后面是一位校驗位(檢驗位可以是奇校驗、偶校驗或無校驗位),最后是一位或一位半或二位停止位,停止位后面是不定長的空閑位,停止位和空閑位都規定為高電平。實際傳輸時每一位的信號寬度與波特率有關,波特率越高,寬度越小,在進行傳輸之前,雙方一定要使用同一個波特率設置。
3 通訊方式
單工模式(Simplex Communication)的數據傳輸是單向的。通信雙方中,一方固定為發送端,一方則固定為接收端。信息只能沿一個方向傳輸,使用一根傳輸線。
半雙工模式(Half Duplex)通信使用同一根傳輸線,既可以發送數據又可以接收數據,但不能同時進行發送和接收。數據傳輸允許數據在兩個方向上傳輸,但是,在任何時刻只能由其中的一方發送數據,另一方接收數據。因此半雙工模式既可以使用一條數據線,也可以使用兩條數據線。半雙工通信中每端需有一個收發切換電子開關,通過切換來決定數據向哪個方向傳輸。因為有切換,所以會產生時間延遲,信息傳輸效率低些。
全雙工模式(Full Duplex)通信允許數據同時在兩個方向上傳輸。因此,全雙工通信是兩個單工通信方式的結合,它要求發送設備和接收設備都有獨立的接收和發送能力。在全雙工模式中,每一端都有發送器和接收器,有兩條傳輸線,信息傳輸效率高。
顯然,在其它參數都一樣的情況下,全雙工比半雙工傳輸速度要快,效率要高。
4 偶校驗與奇校驗
在標準ASCII碼中,其最高位(b7)用作奇偶校驗位。所謂奇偶校驗,是指在代碼傳送過程中用來檢驗是否出現錯誤的一種方法,一般分奇校驗和偶校驗兩種。奇校驗規定:正確的代碼一個字節中1的個數必須是奇數,若非奇數,則在最高位b7添1;偶校驗規定:正確的代碼一個字節中1的個數必須是偶數,若非偶數,則在最高位b7添1。
5 停止位
停止位是按長度來算的。串行異步通信從計時開始,以單位時間為間隔(一個單位時間就是波特率的倒數),依次接受所規定的數據位和奇偶校驗位,并拼裝成一個字符的并行字節;此后應接收到規定長度的停止位“1”。所以說,停止位都是“1”,1.5是它的長度,即停止位的高電平保持1.5個單位時間長度。一般來講,停止位有1,1.5,2個單位時間三種長度。
6 波特率
波特率就是每秒鐘傳輸的數據位數。
波特率的單位是每秒比特數(bps),常用的單位還有:每秒千比特數Kbps,每秒兆比特數Mbps。串口典型的傳輸波特率600bps,1200bps,2400bps,4800bps,9600bps,19200bps,38400bps。
PLC/PC與稱重儀表通訊時,最常用的波特率是9600bps,19200bps。PLC/PC或儀表與大屏幕通訊時,最常用的波特率是600bps。
7 典型的串口通訊標準
EIA RS232(通常簡稱“RS232”): 1962年由美國電子工業協會(EIA)制定。
EIA RS485(通常簡稱“RS485”): 1983年由美國電子工業協會(EIA)制定。
8 RS232串口
RS232是計算機與通信工業應用中最廣泛一種串行接口。它以全雙工方式工作,需要地線、發送線和接收線三條線。RS232只能實現點對點的通信方式。
8.1 RS232串口缺點
●接口信號電平值較高,接口電路芯片容易損壞。
●傳輸速率低,最高波特率19200bps。
●抗干擾能力較差。
●傳輸距離有限,一般在15m以內。
●只能實現點對點的通訊方式。
8.2 RS232串口接口定義
RXD:接收數據,TXD:發送數據,GND/SG:信號地。
8.3 電腦DB9針接口定義
電腦DB9針接口是常見的RS232串口,其引腳定義如下:
2號腳:RXD(接收數據)
3號腳:TXD(發送數據)
5號腳:SG或GND(信號地)
其它腳:我們不用
電腦RS232串口與儀表串口連接圖:
9 RS485串口
9.1 RS485串口特點
●RS485采用平衡發送和差分接收,具有良好的抗干擾能力,信號能傳輸上千米。
●RS485有兩線制和四線制兩種接線。采用四線制時,只能實現點對多的通訊(即只能有一個主設備,其余為從設備)。四線制現在很少采用,現在多采用兩線制接線方式。
●兩線制RS485只能以半雙式方式工作,收發不能同時進行。
●RS485在同一總線上最多可以接32個結點,可實現真正的多點通訊,但一般采用的是主從通信方式,即一個主機帶多個從機。
●因RS485接口具有良好的抗干擾能力,長的傳輸距離和多站能力等優點使其成為首選的串行接口。
10 串口通訊硬件常見的注意事項
●通訊電纜端子一定接牢,不可有任何松動,否則,可能會燒壞儀表或上位機的通訊板。
●不可帶電拔插通訊端子,否則,可能會燒壞儀表或上位機的通訊板,一定要關閉儀表電源后才能去拔插通訊端子或接通訊線。
●通訊用的屏蔽電纜最好選用雙層隔離型屏蔽電纜,其次選用單層屏蔽電纜,最好不要選用無屏蔽層的電纜,且電纜屏蔽層一定要能完全屏蔽,有些質量差的電纜,屏蔽層很松散,根本起不到屏蔽的作用。單層屏蔽的電纜屏蔽層應一端接地,雙層屏蔽的電纜屏蔽層其外層(含鎧裝)應兩端接地,內層屏蔽則應一端接地。
●儀表使用RS232通訊時,通訊電纜長度不得超過15米。
●一般RS485協議的接頭沒有固定的標準,可能根據廠家的不同引腳順序和管腳功能可能不盡相同,用戶可以查閱相關產品RS485的引腳圖。
●RS485通訊電纜最好選用阻阬匹配、低衰減的RS485專用通訊電纜(雙絞線),不要使用普通的雙絞電纜或質量較差的通訊電纜。因為普通電纜或質量差的通訊電纜,可能阻抗不匹配、衰減大、絞合度不夠、屏蔽層太松散,這樣會導致干擾將非常大,會造成通訊不暢,甚至通訊不上。
●儀表使用RS485通訊時,每臺儀表必須手牽手地串下去,不可以有星型連接或者分叉,如果有星型連接或者分叉,干擾將非常大,會造成通訊不暢,甚至通訊不上。
●485總線結構理論上傳輸距離達到1200米,一般是指通訊線材優質達標,波特率9600,只有一臺485設備才能使得通訊距離達到1200米,而且能通訊并不代表每次通訊都正常,所以通常485總線實際的穩定通訊距離遠遠達不到1200米。負載485設備多,線材阻抗不同時,通訊距離更短。
●儀表使用RS485通訊時,必要時,請接入終端電阻,以增強系統的抗干擾性,典型的終端電阻阻值是120歐。
11 串口通訊軟件設置要點
11.1 有關通訊的一些基本概念
●主機與從機:在通訊系統中起主要作用、發布主要命令的稱為主機,接受命令的稱為從機。
●連續方式:指主機不需要發布命令,從機就能自動地向主機發送數據。
●指令方式:指主機向從機發布命令,從機根據指令執行動作,并將結果“應答”給主機的模式。
●輸出數據類型:指在連續方式通訊時,從機輸出給主機的數據類型。
●通訊協議:指主機與從機通訊時,按哪一種編碼規則來通訊。
●波特率:主從機之間通訊的速度。
●數據位:每次傳輸數據時,數據由幾位組成。
●校驗位:數據傳輸錯誤檢測,可以是奇校驗、偶校驗或無校驗。
●地址:每一臺從機的編號。
11.2 主從機之間通訊設置要點
●要點一:主/從RS232/485硬件有無設置正確,通訊線有無接對。有些通訊板卡是RS422與RS485共用的,依靠板上跳線來實現的,有些儀表RS232/485也需要通訊跳線來實現。
●要點二:主機上的通訊端口有無設置正確;超時(一般設置為2s)、通訊延時(一般設置為5~20ms)、ACK信號延時(一般設置為0ms)有無設置正確。
●要點三:主/從機通訊協議有無選擇正確。
●要點四:主/從機波特率有無選擇正確。
●要點五:主/從機數據位有無選擇正確。數據位可以選擇7位,8位。
●要點六:主/從機校驗位有無選擇正確。校驗位一般可選擇偶校驗、奇校驗、無校驗。
●要點七:主/從機停止位有無選擇正確。停止位可以選擇1位、1.5位還是2位。
●要點八:從機地址有無選擇正確。
●要點九:主/從機的通訊方式有無選擇正確。
進行通訊測試的時候經常會進行線路測試,測試所用的串口線是否可用,方法有二如下:
1》 把串口線接到不同的串口,用串口調試工具從一個串口發數據,另一個能正常收到說明串口線是OK的。
2》 把串口線的一端短接(用金屬把2,3號腳連通),用萬用表測另一端的2,3號如果正常的話會有嘀嘀的短接報警聲。
二、linux下串口的基本操作
1、串口的操作
1.1打開:fd = open(“/dev/ttySAC1”, O_RDWR | O_NOCTTY | O_NDELAY);
O_RDWR 讀寫方式打開;
O_NOCTTY 不允許進程管理串口(不太理解,一般都選上);
O_NDELAY 非阻塞(默認為阻塞,打開后也可以使用fcntl()重新設置)
1.2寫入:n = write(fd, “linux”, 5);
n實際寫入字節數;
1.3讀取:res = read(fd,buf,len);
res 讀取的字節數;
1.4設置:fcntl(fd, F_SETFL, FNDELAY); //非阻塞
fcntl(fd, F_SETFL, 0); // 阻塞
1.5關閉:close(fd);
2、串口配置
struct termios options; // 串口配置結構體
tcgetattr(fd,&options); //獲取當前設置
bzero(&options,sizeof(options));
options.c_cflag |= B115200 | CLOCAL | CREAD; // 設置波特率,本地連接,接收使能
options.c_cflag &= ~CSIZE; //屏蔽數據位
options.c_cflag |= CS8; // 數據位為 8 ,CS7 for 7
options.c_cflag &= ~CSTOPB; // 一位停止位, 兩位停止為 |= CSTOPB
options.c_cflag &= ~PARENB; // 無校驗
//options.c_cflag |= PARENB; //有校驗
//options.c_cflag &= ~PARODD // 偶校驗
//options.c_cflag |= PARODD // 奇校驗
options.c_cc[VTIME] = 0; // 等待時間,單位百毫秒 (讀)。后有詳細說明
options.c_cc[VMIN] = 0; // 最小字節數 (讀)。后有詳細說明
tcflush(fd, TCIOFLUSH); // TCIFLUSH刷清輸入隊列。
TCOFLUSH刷清輸出隊列。
TCIOFLUSH刷清輸入、輸出隊列。
tcsetattr(fd, TCSANOW, &options); // TCSANOW立即生效;
TCSADRAIN:Wait until everything has been transmitted;
TCSAFLUSH:Flush input and output buffers and make the change
3、VTIME 和 VMIN
VTIME 定義要求等待的零到幾百毫秒的值(通常是一個8位的unsigned char變量)。
VMIN 定義了要求等待的最小字節數, 這個字節數可能是0。
只有設置為阻塞時這兩個參數才有效,僅針對于讀操作。
說起來比較復雜,舉個例子吧,設置為阻塞狀態,寫操作未進行實驗,這里僅討論讀操作,
read(fd,&buf,8); // 讀串口
3.1
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 0;
VMIN = 0,當緩沖區字節數 》= 0 時進行讀操作,實際上這時讀串口操作并未被阻塞,因為條件始終被滿足。
3.2
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
VMIN = 1,當緩沖區字節數 》= 1 時進行讀操作,當沒有數據時讀串口操作被阻塞。
3.3
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 4;
VMIN = 4,當緩沖區字節數 》= 4 時進行讀操作,否則讀串口操作被阻塞。每次讀出的最大字節數由read函數中第三個參數決定。直到緩沖區剩下的數據《 read 第三個參數 并且《 4 (如果這時read第三參數為 1 則進行4次讀操作直至讀完緩沖區,如read第三參數為2,連續進行讀操作,直至緩沖區空或還剩一個字符)。沒有設置VTIME,剩下的字符沒有確定的期限,直到下次滿足讀條件的時候才被讀出。
----------------------------------考慮VTIME-----------------------------
3.4
options.c_cc[VTIME] = 10; //單位百毫秒
options.c_cc[VMIN] = 4;
同3.3的區別就是,沒滿足條件或讀緩沖區中剩下的數據會在1秒(10百毫秒)后讀出。另外特別注意的是當設置VTIME后,如果read第三個參數小于VMIN ,將會將VMIN 修改為read的第三個參數,即使用read(fd,&buf,2);,以上設置變為:
options.c_cc[VTIME] = 10;
options.c_cc[VMIN] = 2;
1》打開串口函數open_port()中要實現的函數:
(1)open(“/dev/ttys0”,O_RDWR | O_NOCTTY | O_NDELAY);/*打開串口0*/
(2)fcntl(fd,F_SETFL,0)/*恢復串口為阻塞狀態*/
(3)isatty(STDIN_FILENO) /*測試是否為中斷設備 非0即是中斷設備*/
2》 配置串口參數函數set_opt()中要實現的函數:
(1)保存原先有串口配置
tcgetattr(fd,&oldtio);
(2)先將新串口配置清0
bzore(&newtio,sizeof(newito));
(3)激活選項CLOCAL和CREAD 并設置數據位大小
newtio.c_cflag |=CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |=CS8;
(4)設置奇偶校驗
奇校驗:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
偶校驗:
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PAREND;
newtio.c_cflag &= ~PARODD;
無奇偶校驗:
newtio.c_cflag &= ~PARENB;
(5) 設置停止位
newtio.c_cflag &= ~CSTOPB; /*停止位為1*/
newtio.c_cflag |= CSTOPB;/*停止位為0*/
(6)設置波特率:
cfsetispeed(&newtio,B115200);
cfsetospeed(&newtio,B115200);
(7)設置等待時間和最小接受字符:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
(8)處理為接收字符:
tcflush(fd,TCIFLUSH);
(9)激活新配置:
tcsetattr(fd,TCSANOW,&newtio);
3.讀寫串口
write(fd,buff,8);
read(fd,buff,8);
三、串口編程實例:
[cpp] view plain copy#include 《stdio.h》
#include 《string.h》
#include 《sys/types.h》
#include 《errno.h》
#include 《sys/stat.h》
#include 《fcntl.h》
#include 《unistd.h》
#include 《termios.h》
#include 《stdlib.h》
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
/* 五個參量 fd打開文件 speed設置波特率 bit數據位設置 neent奇偶校驗位 stop停止位 */
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror(“SetupSerial 1”);
return -1;
}
bzero( &newtio, sizeof( newtio ) );
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;
}
switch( nEvent )
{
case ‘O’:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case ‘E’:
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case ‘N’:
newtio.c_cflag &= ~PARENB;
break;
}
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;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror(“com set error”);
return -1;
}
printf(“set done! ”);
return 0;
}
int open_port(int fd,int comport)
{
/* fd 打開串口 comport表示第幾個串口 */
char *dev[]={“/dev/ttyS0”,“/dev/ttyS1”,“/dev/ttyS2”};
long vdisable;
if (comport==1)
{ fd = open( “/dev/ttyS0”, O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror(“Can‘t Open Serial Port”);
return(-1);
}
else
printf(“open ttyS0 。。.。。 ”);
}
else if(comport==2)
{ fd = open( “/dev/ttyS1”, O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror(“Can’t Open Serial Port”);
return(-1);
}
else
printf(“open ttyS1 。。.。。 ”);
}
else if (comport==3)
{
fd = open( “/dev/ttyS2”, O_RDWR|O_NOCTTY|O_NDELAY);
if (-1 == fd){
perror(“Can‘t Open Serial Port”);
return(-1);
}
else
printf(“open ttyS2 。。.。。 ”);
}
if(fcntl(fd, F_SETFL, 0)《0)
printf(“fcntl failed! ”);
else
printf(“fcntl=%d ”,fcntl(fd, F_SETFL,0));
if(isatty(STDIN_FILENO)==0)
printf(“standard input is not a terminal device ”);
else
printf(“isatty success! ”);
printf(“fd-open=%d ”,fd);
return fd;
}
int main(void)
{
int fd;
int nread,i;
char buff[]=“Hello ”;
if((fd=open_port(fd,1))《0){
perror(“open_port error”);
return;
}
if((i=set_opt(fd,115200,8,’N‘,1))《0){
perror(“set_opt error”);
return;
}
printf(“fd=%d ”,fd);
// fd=3;
nread=read(fd,buff,8);
printf(“nread=%d,%s ”,nread,buff);
close(fd);
return;
}
評論
查看更多