Linux下串列埠程式設計基礎
串列埠知識
序列介面 (SerialInterface) 是指資料一位一位地順序傳送,其特點是通訊線路簡單,只要一對傳輸線就可以實現雙向通訊(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用於遠距離通訊,但傳送速度較慢。
1. 波特率
表示每秒傳輸的位元數,串列埠通訊的雙方必須保持一致才能通訊資料位,若波特率為115200,它表示什麼呢?
對於傳送斷,即每秒鐘傳送115200bit。
對於接收端,115200波特率意味著串列埠通訊在資料線上的取樣率為115200Hz.
2. 資料位
這是衡量通訊中實際資料位的引數。當計算機發送一個資訊包,實際的資料不會是8位的,標準的值是5、6、7和8位。如何設定取決於你想傳送的資訊。比如,標準的ASCII碼是0~127(7位)。擴充套件的ASCII碼是0~255(8位)。如果資料使用簡單的文字(標準 ASCII碼),那麼每個資料包使用7位資料。每個包是指一個位元組,包括開始/停止位,資料位和奇偶校驗位。由於實際資料位取決於通訊協議的選取,術語“包”指任何通訊的情況。7位或8位資料中不僅僅是資料,還包括開始/停止位,資料位以及奇偶校驗位等
3. 停止位
用於表示單個包的最後一位。典型的值為1,1.5和2位。由於資料是在傳輸線上定時的,並且每一個裝置有其自己的時鐘,很可能在通訊中兩臺裝置間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鐘同步的機會。適用於停止位的位數越多,不同時鐘同步的容忍程度越大,但是資料傳輸率同時也越慢。
4. 奇偶校驗
一般奇偶校驗位的應用是由硬體完成的,軟體只需要在初始化的時候對MCU的串列埠外設設定一下
奇偶校驗的形式一般是在資料位後面跟一個奇偶校驗位,如果資料位是8位的,那麼加上校驗位就是9位資料;如果資料位是7位的,那麼加上校驗位就是8位資料,校驗位和資料之間沒有任何意義上的關聯,它不是資料的一部分,它只是關心資料中的“1”的個數是否為奇數(奇校驗),或者偶數(偶校驗)。奇校驗,奇校驗就是控制器在傳送的一個位元組或者一個數據幀裡面含有的“1”的個數進行奇數個的修正調整,這裡採用8位資料位+1位奇校驗位的形式舉個簡單的例子,A傳送資料0x35到B,後面緊跟一個奇校驗位X, 0x35的二進位制 = 0011 0101,可以看出8個數據位中一共有4個‘1’,那此時硬體會根據“1”的個數來設定X,這裡會將奇校驗位X調整為“1”,為什麼呢? 因為資料位的“1”的個數是偶數,而我們用的是奇校驗,所以為了達到有奇數個“1”的目的,調整奇偶校驗位X為“1”,那麼這9個位發出去就有奇數個“1”啦,當B接收的時候,B的硬體同樣會對接收到“資料+X”中“1”的個數進行計數判斷,如果是奇數個“1”,則認為資料接收正確,否則認為資料錯誤。而當A傳送資料0x25( 0010 0101)到B的時候,則X應該就是“0”了,因為要保證傳送出去的資料位+X位一共有奇數個“1”。
串列埠程式設計
串列埠程式設計的步驟:
a) 開啟串列埠
這裡是串列埠操作需要的一些標頭檔案
#include <stdio.h> /*標準輸入輸出定義*/ #include <stdlib.h> /*標準函式庫定義*/ #include <unistd.h> /*Unix 標準函式定義*/ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*檔案控制定義*/ #include <termios.h> /*PPSIX 終端控制定義*/ #include <errno.h> /*錯誤號定義*/
Linux下一切皆檔案,所以串列埠操作也是對檔案進行操作的
Linux的串列埠檔案位於 /dev下的
/dev/ttyS0 /* 串列埠0 */
/dev/ttyS1 /* 串列埠1 */
這裡我們通過標準的檔案開啟操作嘗試開啟串列埠 1
在這之前先建立一個 .c 檔案 這裡是 uatr2.c
#include <stdio.h> /*標準輸入輸出定義*/ #include <stdlib.h> /*標準函式庫定義*/ #include <unistd.h> /*Unix 標準函式定義*/ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*檔案控制定義*/ #include <termios.h> /*PPSIX 終端控制定義*/ #include <errno.h> /*錯誤號定義*/ int main() { int fd; /*以讀寫方式開啟串列埠*/ fd = open( "/dev/ttyS1", O_RDWR); if (-1 == fd) /* 不能開啟串列埠一*/ perror(" 提示錯誤!"); else printf("success\n"); close(fd); }
編譯 .c 檔案
gcc -o uart2.o uart2.c
執行
./uart2.o
這裡輸出success 說明串列埠能成功開啟
呼叫open()函式來代開串列埠裝置,對於串列埠的開啟操作,必須使用O_NOCTTY引數。
O_NOCTTY:表示開啟的是一個終端裝置,程式不會成為該埠的控制終端。如果不使用此標誌,任務一個輸入(eg:鍵盤中止訊號等)都將影響程序。
O_NDELAY:表示不關心DCD訊號線所處的狀態(埠的另一端是否啟用或者停止)。
1、 串列埠波特率設定
串列埠的設定主要是設定 struct termios 結構體的各成員值。
struct termio
{ unsigned short c_iflag; /* 輸入模式標誌 */
unsigned short c_oflag; /* 輸出模式標誌 */
unsigned short c_cflag; /* 控制模式標誌*/
unsigned short c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control characters */
};
設定這個結構體很複雜,這裡就只說說常見的一些設定:
波特率設定
下面是修改波特率的程式碼:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*設定為19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
設定波特率的例子函式:
/**
*@brief 設定串列埠通訊速率
*@param fd 型別 int 開啟串列埠的檔案控制代碼
*@param speed 型別 int 串列埠速度
*@return void
*/
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, };
void set_speed(int fd, int speed){
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd, TCSANOW, &Opt);
if (status != 0) {
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
3、校驗位和停止位的設定
設定效驗的函式:
/**
*@brief 設定串列埠資料位,停止位和效驗位
*@param fd 型別 int 開啟的串列埠檔案控制代碼
*@param databits 型別 int 資料位 取值 為 7 或者8
*@param stopbits 型別 int 停止位 取值為 1 或者2
*@param parity 型別 int 效驗型別 取值為N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{
struct termios options;
if ( tcgetattr( fd,&options) != 0) {
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE;
switch (databits) /*設定資料位數*/
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n"); return (FALSE);
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 設定為奇效驗*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 轉換為偶效驗*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
/* 設定停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 設定超時15 seconds*/
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
4、 讀寫串列埠
設定好串列埠之後,讀寫串列埠就很容易了,把串列埠當作檔案讀寫就是。
傳送資料
char buffer[1024];
int Length;
int nByte;nByte = write(fd, buffer ,Length);
讀取串列埠資料
使用檔案操作read函式讀取,如果設定為原始模式(Raw Mode)傳輸資料,那麼read函式返回的字元數是實際串列埠收到的字元數。
可以使用操作檔案的函式來實現非同步讀取,如fcntl,或者select等來操作。
char buff[1024];
int Len;
int readByte = read(fd,buff,Len);
5、 關閉串列埠
關閉串列埠就是關閉檔案。
close(fd);
下面是串列埠1的一個簡單的讀取與傳送的例子
#include <stdio.h> /*標準輸入輸出定義*/
#include <stdlib.h> /*標準函式庫定義*/
#include <unistd.h> /*Unix標準函式定義*/
#include <sys/types.h> /**/
#include <sys/types.h> /**/
#include <sys/stat.h> /**/
#include <fcntl.h> /*檔案控制定義*/
#include <termios.h> /*PPSIX終端控制定義*/
#include <errno.h> /*錯誤號定義*/
#define FALSE 0
#define TRUE 1
/***@brief 設定串列埠通訊速率
*@param fd 型別 int 開啟串列埠的檔案控制代碼
*@param speed 型別 int 串列埠速度
*@return void*/
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, };
void set_speed(int fd, int speed)
{
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
if (speed == name_arr[i])
{
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd, TCSANOW, &Opt);
if (status != 0)
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
/**
{
int fd = open( Dev, O_RDWR | O_NOCTTY | O_NDELAY); //| O_NOCTTY | O_NDELAY
if (-1 == fd)
{ /*設定資料位數*/
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
/**
*@breif main()
*/
int main(int argc, char **argv)
{
int fd , n;
int nread;
char buff[512];
char *dev ="/dev/ttyS1";
fd = OpenDev(dev);
if (fd>0)
set_speed(fd,19200);
else
{
printf("Can't Open Serial Port!\n");
exit(0);
}
if (set_Parity(fd,8,1,'N')== FALSE)
{
printf("Set Parity Error\n");
exit(1);
}
while(1)
{
while((nread = read(fd,buff,512))>0)
{
printf("\nLen %d\n",nread);
buff[nread+1]='\0';
printf("\n%s",buff);
n = write(fd, "I get\r", 4) //如果收到資料則向串列埠傳送I Get
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);
}
}
//close(fd);
//exit(0);
}