具體的封裝格式為C代碼,這樣做是為了很好的移植性,使它可以在C和C++環境下,都可以編譯和使用。代碼的標頭檔如下:
//filename:stty.h
#ifndef __STTY_H__
#define __STTY_H__
#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 <pthread.h>
//
// 串口裝置資訊結構
typedef struct tty_info_t
{
int fd; // 串口裝置ID
pthread_mutex_t mt; // 線程同步互斥對象
char name[24]; // 串口裝置名稱,例:"/dev/ttyS0"
struct termios ntm; // 新的串口裝置選項
struct termios otm; // 舊的串口裝置選項
} TTY_INFO;
//
// 串口操作函數
TTY_INFO *readyTTY(int id);
int setTTYSpeed(TTY_INFO *ptty, int speed);
int setTTYParity(TTY_INFO *ptty,int databits,int parity,int stopbits);
int cleanTTY(TTY_INFO *ptty);
int sendnTTY(TTY_INFO *ptty,char *pbuf,int size);
int recvnTTY(TTY_INFO *ptty,char *pbuf,int size);
int lockTTY(TTY_INFO *ptty);
int unlockTTY(TTY_INFO *ptty);
#endif
從標頭檔中的函數定義不難看出,函數的功能,使用過程如下:
(1) 開啟串口裝置,調用函數setTTYSpeed();
(2) 設定串口讀寫的傳輸速率,調用函數setTTYSpeed();
(3) 設定串口的屬性,包括停止位、校正位、資料位元等,調用函數setTTYParity();
(4) 向串口寫入資料,調用函數sendnTTY();
(5) 從串口讀出資料,調用函數recvnTTY();
(6) 操作完成後,需要調用函數cleanTTY()來釋放申請的串口資訊介面;
其中,lockTTY()和unlockTTY()是為了能夠在多線程中使用。在讀寫操作的前後,需要鎖定和釋放串口資源。
具體的使用方法,在代碼實現的原檔案中,main()函數中進行了示範。下面就是原始碼檔案:
////////////////////////////////////////////////////////////////////////////////
//stty.c
#include <stdio.h>
#include <sys/ioctl.h>
#include "stty.h"
///////////////////////////////////////////////////////////////////////////////
// 初始化串口裝置並進行原有設定的儲存
TTY_INFO *readyTTY(int id)
{
TTY_INFO *ptty;
ptty = (TTY_INFO *)malloc(sizeof(TTY_INFO));
if(ptty == NULL)
return NULL;
memset(ptty,0,sizeof(TTY_INFO));
pthread_mutex_init(&ptty->mt,NULL);
sprintf(ptty->name,"/dev/ttyS%d",id);
//
// 開啟並且設定串口
ptty->fd = open(ptty->name, O_RDWR | O_NOCTTY |O_NDELAY);
if (ptty->fd <0)
{
free(ptty);
return NULL;
}
//
// 取得並且儲存原來的設定
tcgetattr(ptty->fd,&ptty->otm);
return ptty;
}
///////////////////////////////////////////////////////////////////////////////
// 清理串口裝置資源
int cleanTTY(TTY_INFO *ptty)
{
//
// 關閉開啟的串口裝置
if(ptty->fd>0)
{
tcsetattr(ptty->fd,TCSANOW,&ptty->otm);
close(ptty->fd);
ptty->fd = -1;
free(ptty);
ptty = NULL;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// 設定串口通訊速率
// ptty 參數類型(TTY_INFO *),已經初始化的串口裝置資訊結構指標
// speed 參數類型(int),用來設定串口的傳輸速率
// return 傳回值類型(int),函數執行成功返回零值,否則返回大於零的值
///////////////////////////////////////////////////////////////////////////////
int setTTYSpeed(TTY_INFO *ptty, int speed)
{
int i;
//
// 進行新的串口設定,資料位元為8位
bzero(&ptty->ntm, sizeof(ptty->ntm));
tcgetattr(ptty->fd,&ptty->ntm);
ptty->ntm.c_cflag = CLOCAL | CREAD;
switch(speed)
{
case 300:
ptty->ntm.c_cflag |= B300;
break;
case 1200:
ptty->ntm.c_cflag |= B1200;
break;
case 2400:
ptty->ntm.c_cflag |= B2400;
break;
case 4800:
ptty->ntm.c_cflag |= B4800;
break;
case 9600:
ptty->ntm.c_cflag |= B9600;
break;
case 19200:
ptty->ntm.c_cflag |= B19200;
break;
case 38400:
ptty->ntm.c_cflag |= B38400;
break;
case 115200:
ptty->ntm.c_cflag |= B115200;
break;
}
ptty->ntm.c_iflag = IGNPAR;
ptty->ntm.c_oflag = 0;
//
//
tcflush(ptty->fd, TCIFLUSH);
tcsetattr(ptty->fd,TCSANOW,&ptty->ntm);
//
//
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// 設定串口資料位元,停止位和效驗位
// ptty 參數類型(TTY_INFO *),已經初始化的串口裝置資訊結構指標
// databits 參數類型(int), 資料位元,取值為7或者8
// stopbits 參數類型(int),停止位,取值為1或者2
// parity 參數類型(int),效驗類型 取值為N,E,O,,S
// return 傳回值類型(int),函數執行成功返回零值,否則返回大於零的值
///////////////////////////////////////////////////////////////////////////////
int setTTYParity(TTY_INFO *ptty,int databits,int parity,int stopbits)
{
//
// 取得串口設定
if( tcgetattr(ptty->fd,&ptty->ntm) != 0)
{
printf("SetupSerial [%s]\n",ptty->name);
return 1;
}
bzero(&ptty->ntm, sizeof(ptty->ntm));
ptty->ntm.c_cflag = CS8 | CLOCAL | CREAD;
ptty->ntm.c_iflag = IGNPAR;
ptty->ntm.c_oflag = 0;
//
// 設定串口的各種參數
ptty->ntm.c_cflag &= ~CSIZE;
switch (databits)
{ //設定資料位元數
case 7:
ptty->ntm.c_cflag |= CS7;
break;
case 8:
ptty->ntm.c_cflag |= CS8;
break;
default:
printf("Unsupported data size\n");
return 5;
}
//
switch (parity)
{ // 設定同位位元數
case 'n':
case 'N':
ptty->ntm.c_cflag &= ~PARENB;
ptty->ntm.c_iflag &= ~INPCK;
break;
case 'o':
case 'O':
ptty->ntm.c_cflag |= (PARODD|PARENB);
ptty->ntm.c_iflag |= INPCK;
break;
case 'e':
case 'E':
ptty->ntm.c_cflag |= PARENB;
ptty->ntm.c_cflag &= ~PARODD;
ptty->ntm.c_iflag |= INPCK;
break;
case 'S':
case 's':
ptty->ntm.c_cflag &= ~PARENB;
ptty->ntm.c_cflag &= ~CSTOPB;
break;
default:
printf("Unsupported parity\n");
return 2;
}
//
// 設定停止位
switch (stopbits)
{
case 1:
ptty->ntm.c_cflag &= ~CSTOPB;
break;
case 2:
ptty->ntm.c_cflag |= CSTOPB;
break;
default:
printf("Unsupported stop bits\n");
return 3;
}
//
//
ptty->ntm.c_lflag = 0;
ptty->ntm.c_cc[VTIME] = 0; // inter-character timer unused
ptty->ntm.c_cc[VMIN] = 1; // blocking read until 1 chars received
tcflush(ptty->fd, TCIFLUSH);
if (tcsetattr(ptty->fd,TCSANOW,&ptty->ntm) != 0)
{
printf("SetupSerial \n");
return 4;
}
return 0;
}
int recvnTTY(TTY_INFO *ptty,char *pbuf,int size)
{
int ret,left,bytes;
left = size;
while(left>0)
{
ret = 0;
bytes = 0;
pthread_mutex_lock(&ptty->mt);
ioctl(ptty->fd, FIONREAD, &bytes);
if(bytes>0)
{
ret = read(ptty->fd,pbuf,left);
}
pthread_mutex_unlock(&ptty->mt);
if(ret >0)
{
left -= ret;
pbuf += ret;
}
usleep(100);
}
return size - left;
}
int sendnTTY(TTY_INFO *ptty,char *pbuf,int size)
{
int ret,nleft;
char *ptmp;
ret = 0;
nleft = size;
ptmp = pbuf;
while(nleft>0)
{
pthread_mutex_lock(&ptty->mt);
ret = write(ptty->fd,ptmp,nleft);
pthread_mutex_unlock(&ptty->mt);
if(ret >0)
{
nleft -= ret;
ptmp += ret;
}
//usleep(100);
}
return size - nleft;
}
int lockTTY(TTY_INFO *ptty)
{
if(ptty->fd < 0)
{
return 1;
}
return flock(ptty->fd,LOCK_EX);
}
int unlockTTY(TTY_INFO *ptty)
{
if(ptty->fd < 0)
{
return 1;
}
return flock(ptty->fd,LOCK_UN);
}
#ifdef LEAF_TTY_TEST
///////////////////////////////////////////////////////////////////////////////
// 介面測試
int main(int argc,char **argv)
{
TTY_INFO *ptty;
int nbyte,idx;
unsigned char cc[16];
ptty = readyTTY(0);
if(ptty == NULL)
{
printf("readyTTY(0) error\n");
return 1;
}
//
//
lockTTY(ptty);
if(setTTYSpeed(ptty,9600)>0)
{
printf("setTTYSpeed() error\n");
return -1;
}
if(setTTYParity(ptty,8,'N',1)>0)
{
printf("setTTYParity() error\n");
return -1;
}
//
idx = 0;
while(1)
{
cc[0] = 0xFA;
sendnTTY(ptty,&cc[0],1);
nbyte = recvnTTY(ptty,cc,1);
printf("%d:X\n",idx++,cc[0]);
}
cleanTTY(ptty);
}
#endif
串口是電腦上一種非常通用裝置通訊的協議,常用PC機上包含的是RS232規格的串口,具有連接線少,通訊簡單,得到廣泛的使用。
Linux對所有裝置的訪問是通過裝置檔案來進行的,串口也是這樣,為了訪問串口,只需開啟其裝置檔案即可操作串口裝置。在linux系統下面,每一個串口裝置都有裝置檔案與其關聯,裝置檔案位於系統的/dev目錄下面。如linux下的/ttyS0,/ttyS1分別表示的是串口1和串口2。
在串口編程中,比較重要的是串口的設定,我們要設定的部分包括:傳輸速率,資料位元,停止位,同位位元;要注意的是,每台機器的串口預設設定可能是不同的,如果你沒設定這些,僅僅按照預設設定進行發送資料,很可能出現n多異想不到而又查不出來的情況。
1) 設定傳輸速率
#include <termios.h> #include <unistd.h> int cfsetispeed(struct termios *termios_p, speed_t speed); int cfsetospeed(struct termios *termios_p, speed_t speed); |
2) 設定屬性:同位位元、資料位元、停止位。主要設定<termbits.h>中的termios結構體即可:
#define NCCS 19 struct termios { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_line; cc_t c_cc[NCCS]; }; |
有相應的函數供擷取和設定屬性:
int tcgetattr(int fd, struct termios *termios_p); int tcsetattr(int fd, int optional_actions, struct termios *termios_p); |
3) 開啟、關閉和讀寫串口。串口作為裝置檔案,可以直接用檔案描述符來進行操作。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); #include <unistd.h> int close(int fd); ssize_t write(int fd, const void *buf, size_t count); ssize_t read(int fd, void *buf, size_t count); |
嵌入式Linux作業系統使用介面標準POSIX的termios介面來控制串口的行為。在Linux系統中,串口等裝置被當作檔案進行處理,其程式模組主體實現如下:
int fd=open("/dev/ttyS1",O_RDWRIO_NOCTTY);//開啟串口
……
new_options.c_cflag &=~PARENB;//無同位
new_options.c_cflag &=~CSIZE;//不隱藏資料位元
new_options.c_cflag &=~CSTOP8;//無停止位
new_options.c_cflag |=CS8;//8位元據位
cfsetispeed(&new_options,B4800);//設定傳輸速率4800bit/s
cfsetospeed(&new_options,B4800);
tcflush(fd,TCIOFLUSH);
tcsetattr(fd,TCSANOW,&new_options);//設定新的裝置方式
完成串口設定後,就可以使用read( )、write( )函數對串口進行操作。需注意的是,串口預設是阻塞型的,當沒有資料到達時,將會阻塞掛起,這時可以通過多線程編程、串口逾時設定或使用select輪詢 等方式進行調整控制。本系統主要採用多線程編程實現對串口阻塞的調控,使用的是QT的Qthread類,也可以直接使用Linux自身的多線程函數進行操作。