linux下串口編程
標簽: linux c/c++ 串口編程 RS232 串口類
一、串口基本知識
1、什么是串口通信
串口通信(Serial Communication),是指外設和計算機間,通過數據信號線、地線等,按位進行傳輸數據的一種通訊方式。
串口是一種接口標準,它規定了接口的電氣標準,沒有規定接口插件電纜以及使用的協議。串口是計算機上一種非常通用設備通信的協議。大多數計算機包含兩個基于RS232的串口。串口同時也是儀器儀表設備通用的通信協議;很多GPIB兼容的設備也帶有RS-232口。同時,串口通信協議也可以用于獲取遠程采集設備的數據。串口通信的概念非常簡單,串口按位(bit)發送和接收字節。盡管比按字節(byte)的并行通信慢,但是串口可以在使用一根線發送數據的同時用另一根線接收數據。它很簡單并且能夠實現遠距離通信。
串口接頭
各個引腳功能說明
注:一般我們需要的就是2,3,5接口,
典型地,串口用于ASCII碼字符的傳輸。通信使用3根線完成:(1)地線,(2)發送,(3)接收。由于串口通信是異步的,端口能夠在一根線上發送數據同時在另一根線上接收數據。其他線用于握手,但是不是必須的。連接時是TXD接RXD,RXD接TXD,GND接GND。自己的TXD口接RXD口,自發自收,測試串口是否正常。串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對于兩個進行通行的端口,這些參數必須匹配:
a,波特率:這是一個衡量通信速度的參數。它表示每秒鐘傳送的bit的個數。例如300波特表示每秒鐘發送300個bit。當我們提到時鐘周期時,我們就是指波特率例如如果協議需要4800波特率,那么時鐘是4800Hz。這意味著串口通信在數據線上的采樣率為4800Hz。通常電話線的波特率為14400,28800和36600。波特率可以遠遠大于這些值,但是波特率和距離成反比。高波特率常常用于放置的很近的儀器間的通信,典型的例子就是GPIB設備的通信。
b,數據位:這是衡量通信中實際數據位的參數。當計算機發送一個信息包,實際的數據不會是8位的,標準的值是5、7和8位。如何設置取決于你想傳送的信息。比如,標準的ASCII碼是0~127(7位)。擴展的ASCII碼是0~255(8位)。如果數據使用簡單的文本(標準 ASCII碼),那么每個數據包使用7位數據。每個包是指一個字節,包括開始/停止位,數據位和奇偶校驗位。由于實際數據位取決于通信協議的選取,術語“包”指任何通信的情況。
c,停止位:用于表示單個包的最后一位。典型的值為1,1.5和2位。由于數據是在傳輸線上定時的,并且每一個設備有其自己的時鐘,很可能在通信中兩臺設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,并且提供計算機校正時鐘同步的機會。適用于停止位的位數越多,不同時鐘同步的容忍程度越大,但是數據傳輸率同時也越慢。
d,奇偶校驗位:在串口通信中一種簡單的檢錯方式。有四種檢錯方式:偶、奇、高和低。當然沒有校驗位也是可以的。對于偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對于偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位位1,這樣就有3個邏輯高位。高位和低位不真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步.
2、串口通信協議
在串口通信中,常用的協議包括RS-232、RS-422和RS-485。
RS-232:標準串口,最常用的一種串行通訊接口。有三種類型(A,B和C),它們分別采用不同的電壓來表示on和off。最被廣泛使用的是RS-232C,傳送距離最大為約15米,最高速率為20kb/s。RS-232是為點對點(即只用一對收、發設備)通訊而設計的,其驅動器負載為3~7kΩ。所以RS-232適合本地設備之間的通信。RS232標準是按負邏輯定義的,他的“1”電平在-5~-15 V之間,“0”電平在+5~+15 V之間。雖然RS232應用很廣,但由于數據傳輸速率慢,通訊距離短,特別是在100 m以上的遠程通訊中難以讓人滿意,因此通常采用RS422,RS449,RS423及RS485等接口標準來實現遠程通訊。
RS-422:最大傳輸距離為1219米,最大傳輸速率為10Mb/s。其平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能達到最大傳輸距離。只有在很短的距離下才能獲得最高速率傳輸。一般100米長的雙絞線上所能獲得的最大傳輸速率僅為1Mb/s。
RS-485:從RS-422基礎上發展而來的,最大傳輸距離約為1219米,最大傳輸速率為10Mb/s。平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能使用規定最長的電纜長度。只有在很短的距離下才能獲得最高速率傳輸。一般100米長雙絞線最大傳輸速率僅為1Mb/s。
3、同步通信和異步通信
同步通信:是一種比特同步通信技術,要求發收雙方具有同頻同相的同步時鐘信號,只需在傳送報文的最前面附加特定的同步字符,使發收雙方建立同步,此后便在同步時鐘的控制下逐位發送/接收。如:SPI總線。
異步通信:指兩個互不同步的設備通過計時機制或其他技術進行數據傳輸。也就是說,雙方不需要共同的時鐘。發送方可以隨時傳輸數據,而接收方必須在信息到達時準備好接收。如:串口(UART)。
UART和USART,實際上,從字面意思即可理解:
UART:universal asynchronous receiver and transmitter(通用異步收/發器)。
USART:universal synchronous asynchronous receiver and transmitter(通用同步/異步收/發器)。
USART在UART基礎上增加了同步功能,即USART是UART的增強型。
3、通信方式
單工模式(Simplex Communication):單向的數據傳輸。通信雙方中,一方為發送端,一方則為接收端。信息只能沿一個方向傳輸,使用一根傳輸線。雙方是固定的。
半雙工模式(Half Duplex):通信使用同一根傳輸線,既可以發送數據又可以接收數據,但不能同時進行發送和接收。數據傳輸允許數據在兩個方向上傳輸,但是,在任何時刻只能由其中的一方發送數據,另一方接收數據。
全雙工模式(Full Duplex)通信允許數據同時在兩個方向上傳輸。因此,全雙工通信是兩個單工通信方式的結合,它要求發送設備和接收設備都有獨立的接收和發送能力。在全雙工模式中,每一端都有發送器和接收器,有兩條傳輸線,信息傳輸效率高。
二、linux 下串口編程
Linux對所有設備的訪問是通過設備文件來進行的,串口也是這樣,為了訪問串口,只需打開其設備文件即可操作串口設備。在linux系統下面,每一個串口設備都有設備文件與其關聯,設備文件位于系統的/dev目錄下面。如linux下的/ttyS0,/ttyS1分別表示的是串口1和串口2。下面來詳細介紹linux下是如何使用串口的。
下面通過寫一個串口類來說明linux下的串口編程,并通過虛擬串口來測試代碼。
//SerialPort.h 這是串口類的頭文件,定義了一個串口了
#ifndef SERIALPORT_H
#define SERIALPORT_H
/*linux下串口需要使用到的頭文件*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<termios.h>
#include<errno.h>
#include<string.h>
#include<pthread.h>
class SerialPort
{
public:
SerialPort(int WhichCom);
~SerialPort();
bool InitSerialPort(int BaudRate,int DataBits,int StopBits,int ParityBit); // 初始化串口
bool CloseSerialPort();// 關閉串口
int Write(char *Buff, const int Len); // 向串口寫入數據
int Read(char *Buff, const int Len); // 從串口中讀取數據
void StartRead(); // 開啟一個線程來循環讀取
void StartWrite(); // 開啟一個 線程來循環寫入
private:
static int m_BaudRateArr[]; // 波特率數組
static int m_SpeedArr[]; // 波特率數組
static char *m_DevName; // 串口設備名稱
struct termios m_Setting; // 串口配置的結構體
int fd; // 打開串口設備后返回的文件描述符
};
#endif
//SerialPort.cpp 實現具體的串口類
#include "SerialPort.h"
#ifdef VITRUALPROT // 加上一個宏編譯,如果使用虛擬串口來調試就定義這個宏,如果使用硬件來調試就不用
const char *COM_NAME = "/dev/pts/"; // 這個是我設備中虛擬串口的名稱
#else
const char *COM_NAME = "/dev/ttymxc" ; // 這個是我硬件中串口的名稱
#endif
// 波特率數組
int SerialPort::m_BaudRateArr[] = {B115200, B57600, B9600,B38400, B19200, B4800, B2400, B1200, B300 };
int SerialPort::m_SpeedArr[] = {115200, 57600,9600,38400, 19200, 4800, 2400, 1200, 300 };
/*
構造函數,打開串口。參數WhichCom:第幾個串口
*/
SerialPort::SerialPort(int WhichCom)
{
char devName[100];
sprintf(devName, "%s%d", COM_NAME, WhichCom);
/*open函數打開串口
O_RDWR :串口可讀寫
O_NOCTTY:可以告訴Linux這個程序不會成為這個端口上的“控制終端”.如果不這樣做的話,所有的輸入,比如鍵盤上過來的Ctrl+C中止信號等等,會影響到你的進程。
O_NDELAY:標志則是告訴Linux,這個程序并不關心DCD信號線的狀態——也就是不關心端口另一端是否已經連接(不阻塞)。
*/
fd = open( devName, O_RDWR | O_NOCTTY |O_NDELAY);
if(fd < 0)
{
fd = -1;
printf("Can't Open the %s device.\n", devName);
return;
}
bzero(&m_Setting, sizeof(m_Setting));
/*重新將串口設置為阻塞模式,即執行read函數時,如果沒有數據就會阻塞等待,不往下執行,
如果設置為非阻塞模式為fcntl(fd, F_SETFL, O_NDELAY),此時執行read函數時,如果沒有數據,
則返回-1,程序繼續往下執行*/
fcntl(fd, F_SETFL, 0);
}
/*
初始化串口,配置串口的各種參數。
參數:BaudRate:波特率
DataBits:數據位
StopBits:停止位
ParityBit:校驗位
*/
bool SerialPort ::InitSerialPort(int BaudRate,int DataBits,int StopBits,int ParityBit)
{
if( -1 == fd)
return false;
if( 0!= tcgetattr (fd,&m_Setting))
{
printf("InitSerialPort tcgetattr() line:%d failed\n",__LINE__);
return false;
}
// 設置波特率
for(int i = 0 ; i<sizeof(m_SpeedArr)/sizeof(int);i++)
{
if( BaudRate == m_SpeedArr[i])
{
tcflush(fd, TCIOFLUSH); // 清空發送接收緩沖區
cfsetispeed(&m_Setting,m_BaudRateArr[i]); // 設置輸入波特率
cfsetospeed(&m_Setting,m_BaudRateArr[i]); // 設置輸出波特率
break;
}
if(i == sizeof(m_SpeedArr) / sizeof(int))
return false;
}
m_Setting.c_cflag |= CLOCAL;//控制模式, 保證程序不會成為端口的占有者
m_Setting.c_cflag |= CREAD; //控制模式, 使能端口讀取輸入的數據
// 設置數據位
m_Setting.c_cflag &= ~CSIZE;
switch(DataBits)
{
case 6:m_Setting.c_cflag |= CS6 ; break; //6位數據位
case 7:m_Setting.c_cflag |= CS7 ; break; //7位數據位
case 8:m_Setting.c_cflag |= CS8 ; break; //8位數據位
default:
fprintf(stderr,"unsupported dataBits\n");
return false;
}
// 設置停止位
switch(StopBits)
{
case 1: m_Setting.c_cflag &= ~CSTOPB;break; //1位停止位
case 2: m_Setting.c_cflag |= CSTOPB; break; //2位停止位
default:
return false;
}
// 設置奇偶校驗位
switch(ParityBit)
{
case 'n':
case 'N':
m_Setting.c_cflag &= ~PARENB; // 關閉c_cflag中的校驗位使能標志PARENB)
m_Setting.c_iflag &= ~INPCK; // 關閉輸入奇偶檢測
break;
case 'o':
case 'O':
m_Setting.c_cflag |= (PARODD | PARENB);//**c_cflag中的校驗位使能標志PARENB,同時進行奇校驗
m_Setting.c_iflag |= INPCK; // 開啟輸入奇偶檢測
break;
case 'e':
case 'E':
m_Setting.c_cflag |= PARENB;//**c_cflag中的校驗位使能標志PARENB
m_Setting.c_cflag &= ~PARODD;// 使用偶校驗
m_Setting.c_iflag |= INPCK;// 開啟輸入奇偶檢測
break;
case 's':
case 'S':
m_Setting.c_cflag &= ~PARENB; // 關閉c_cflag中的校驗位使能標志PARENB)
m_Setting.c_cflag &= ~CSTOPB; // 設置停止位位一位
break;
default:
fprintf(stderr,"unsupported parityBit\n");
return false;
}
m_Setting.c_oflag &= ~OPOST;// 設置為原始輸出模式
m_Setting.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 設置為原始輸入模式
/*所謂標準輸入模式是指輸入是以行為單位的,可以這樣理解,輸入的數據最開始存儲在一個緩沖區里面(但并未真正發送出去),
可以使用Backspace或者Delete鍵來刪除輸入的字符,從而達到修改字符的目的,當按下回車鍵時,輸入才真正的發送出去,這樣終端程序才能接收到。通常情況下我們都是使用的是原始輸入模式,也就是說輸入的數據并不組成行。在標準輸入模式下,系統每次返回的是一行數據,在原始輸入模式下,系統又是怎樣返回數據的呢?如果讀一次就返回一個字節,那么系統開銷就會很大,但在讀數據的時候,我們也并不知道一次要讀多少字節的數據,
解決辦法是使用c_cc數組中的VMIN和VTIME,如果已經讀到了VMIN個字節的數據或者已經超過VTIME時間,系統立即返回。*/
m_Setting.c_cc[VTIME] = 1;
m_Setting.c_cc[VMIN] = 1;
/*刷新串口數據
TCIFLUSH:刷新收到的數據但是不讀
TCOFLUSH:刷新寫入的數據但是不傳送
TCIOFLUSH:同時刷新收到的數據但是不讀,并且刷新寫入的數據但是不傳送。 */
tcflush(fd, TCIFLUSH);
// **配置
if( 0 != tcsetattr(fd,TCSANOW,&m_Setting))
{
printf("InitSerialPort tecsetattr() %d failed\n",__LINE__);
return false;
}
return true;
}
// 關閉串口
bool SerialPort::CloseSerialPort()
{
if( -1 == fd)
return false;
close(fd);
fd = -1;
return true;
}
//從串口讀取數據
int SerialPort::Read(char *readBuffer,const int bufferSize)
{
if( -1 == fd)
return -1;
return read(fd,readBuffer,bufferSize);
}
// 往串口寫入數據
int SerialPort::Write(char *writeBuffer,const int bufferSize)
{
if( -1 == fd)
return -1;
return write(fd,writeBuffer,bufferSize);
}
// 線程體,不斷地讀取數據
void *ReadFunction(void *arg)
{
SerialPort *serialPort = (SerialPort *)arg;
char buffer[100];
static int readSize = 99;
while(1)
{
int len = serialPort->Read(buffer,99);
if(len > 0)
{
buffer[len] = '\0';
printf("receive data:%s, len = %d\n",buffer,len);
}
else
{
printf("cannot receive data\n");
}
sleep(1);
}
}
// 線程體,不斷地寫入數據
void *WriteFunction(void *arg)
{
SerialPort *serialPort = (SerialPort *)arg;
char buffer[] = "test";
while(1)
{
if(!serialPort->Write(buffer,strlen(buffer)))
{
printf("write failed\n");
}
printf("write: %s\n",buffer);
sleep(1);
}
}
// 開啟讀線程
void SerialPort::StartRead()
{
if(fd == -1) return;
pthread_t readThread;
if ( pthread_create( &readThread, NULL, ReadFunction, this) )
{
printf("Error creating readThread.\n");
}
}
// 開啟寫線程
void SerialPort::StartWrite()
{
if(fd == -1) return;
pthread_t writeThread;
if ( pthread_create( &writeThread, NULL, WriteFunction, this) )
{
printf("Error creating writeThread.\n");
}
}
//main.cpp
#include "SerialPort.h"
int main(int argc ,char *argv[])
{
SerialPort *serialPort = new SerialPort(1);
serialPort->InitSerialPort(9600,8,1,'N');
#ifdef WRITE
serialPort->StartWrite();
#endif
#ifdef READ
serialPort->StartRead();
#endif
while(1);
return 0;
}
下面來編譯程序:
g++ main.cpp SerialPort.cpp -o read -lpthread -D READ // 讀寫串口數據的程序
g++ main.cpp SerialPort.cpp -o write-lpthread -D WRITE // 往串口寫入數據的程序
到此為止,程序就編譯完成了 。
三、測試串口類程序
如果要測試程序,需要用到開發板和串口助手。如果沒有硬件環境,那么也可以用虛擬串口來調試。下面的python程序可以建立兩個虛擬串口:
#! /usr/bin/env python
#coding=utf-8
import pty
import os
import select
def mkpty():
master1, slave = pty.openpty()
slaveName1 = os.ttyname(slave)
master2, slave = pty.openpty()
slaveName2 = os.ttyname(slave)
print ('Virtual serial port : ', slaveName1, slaveName2)
return master1, master2
if __name__ == "__main__":
master1, master2 = mkpty()
while True:
rl, wl, el = select.select([master1,master2], [], [], 1)
for master in rl:
data = os.read(master, 128)
print ("read %d data." % len(data))
if master==master1:
os.write(master2, data)
else:
os.write(master1, data)
如果是使用虛擬串口,那么程序的編譯命令如下:
g++ main.cpp SerialPort.cpp -o read -lpthread -D VITRUALPROT -D READ
g++ main.cpp SerialPort.cpp -o write -lpthread -D VITRUALPROT -D WRITE
運行這個虛擬串口的程序,前提是你的linux 下已經安裝好了python。
如果搭好了python的環境,在linux下鍵入命令
python3 virtualPort.py //virtualPort.py 為我上面虛擬串口程序的文件名
那么就會出現以下信息
圖中的,/dev/pts/5 和 /dev/pts8 就是我們建立起來的兩個虛擬串口,我們就可以利用這兩個虛擬串口來模擬串口之間的通信了。
接著另外打開兩個linux 終端,在其中一個終端上運行read程序,在另外一個終端上運行write程序,運行結果如下
可以看到,write中寫入的字符串:test,已經成功被read程序中接收。程序測試正常。
————————————END——————————————————
參考:
https://blog.csdn.net/baweiyaoji/article/details/72885633
http://www.21ic.com/jichuzhishi/datasheet/RS232/jiekou/187596.html
智能推薦
Linux 串口編程
串行接口 串行接口 (Serial Interface) 是指數據一位一位地順序傳送,其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信,從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。一條信息的各位數據被逐位按順序傳送的通訊方式稱為串行通訊。 串行通訊的特點是:數據位的傳送,按位順序進行,最少只需一根傳輸線即可完成;成本低但傳送速度慢。串行通訊的距離可以從幾米到幾千米...
七、Linux串口編程
Linux下的串口編程過程如下(我就不給大家再講串口是什么了,不懂得朋友自行補上串口相關知識): 目錄 一、打開串口 二、初始化串口 1、 串口的初始化結構介紹 2、串口的初始化常用函數介紹 函數 tcgetattr 波特率相關的函數 cfsetispeed 和 cfsetospeed、cfgetispeed 、cfgetospeed 函數 tcflush 函數 tcseta...
linux下串口開發
一、串口通信 通信的基本方式可分為并行通信與串行通信兩種方式: 并行通信是指數據的各個位同時進行傳輸的一種通信方式 優點:傳輸速度快 缺點:數據有多少位就需要多少根傳輸線,所以在位數多、傳輸距 離遠時,通信線路復雜,成本高。 串行通信是指數據一位一位地傳輸的方式。 優點:這種通信方式的數據傳輸只需一、兩根傳輸線,通信線路簡單,成本低,特別適用于遠程通信 缺點:傳輸速度較慢。多用于傳輸距離長、...
Linux下的串口編程(FL2440實現串口接收發送)
前面已經了解了串口的基礎知識,下面將介紹在Linux下如何編程。以下的程序是,直接串口讀,讀完寫的例程。 串口編程思路: 打開串口,設置波特率,數據位,停止位,奇偶校驗位; 讀寫串口;(直接用read,write函數) 關閉串口。 直接見代碼 串口的頭文件,先定義一個結構體,含有其串口必備的要設置的數據位,奇偶校驗位,停止位,是否連接,波特率,設備名等。 聲明串口初始化函數,設置端口,關閉串口,打...
Linux基礎(7)--串口編程
串口編程 1. 流程分析 2. 開機啟動程序 3. 打開串口 4. 串口初始化 5. 串口發送 6. 串口接收 1. 流程分析 Linux下的串口編程流程主要有四個部分,即打開串口,初始化串口,發送和接收數據以及關閉串口: 2. 開機啟動程序 在講述串口編程之間,先說明一下如何將一個程序設置成開機啟動的程序。以我們在Linux基礎(3)中的helloworld程序為例,其本身就在/bin下,如果不...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...