嵌入式Linux主板EM9160提供了6個(gè)標(biāo)準(zhǔn)異步串口:ttyS1——ttyS6,其中ttyS4、ttyS5、ttyS6和GPIO的管腳復(fù)用,每個(gè)串口都有獨(dú)立的中斷模式,使得多個(gè)串口能夠同時(shí)實(shí)時(shí)進(jìn)行數(shù)據(jù)收發(fā)。各個(gè)串口的驅(qū)動(dòng)均已經(jīng)包含在嵌入式Linux操作系統(tǒng)的內(nèi)核中,EM9160在嵌入式Linux系統(tǒng)啟動(dòng)完成時(shí),各個(gè)串口已作為字符設(shè)備完成了注冊(cè)加載,用戶的應(yīng)用程序可以以操作文件的方式對(duì)串口進(jìn)行讀寫(xiě),從而實(shí)現(xiàn)數(shù)據(jù)收發(fā)的功能。
在嵌入式Linux系統(tǒng)下,所有的設(shè)備文件都位于“/dev”目錄下,EM9160上6個(gè)串口所對(duì)應(yīng)的設(shè)備名依次為“/dev/ttyS1”——“/dev/ttyS6”。
嵌入式Linux下操作設(shè)備的方式和操作文件的方式是一樣的:調(diào)用open( )打開(kāi)設(shè)備文件,再調(diào)用read( )、write( )對(duì)串口進(jìn)行數(shù)據(jù)讀寫(xiě)操作。這里需要注意的是打開(kāi)串口除了設(shè)置普通的讀寫(xiě)之外,還需要設(shè)置O_NOCTTY和O_NDLEAY,以避免該串口成為一個(gè)控制終端,因?yàn)槿绻鳛橐粋€(gè)終端有可能會(huì)影響到用戶的進(jìn)程。打開(kāi)的方式如下:
sprintf( portname, ‘/dev/ttyS%d’, PortNo ); //PortNo為串口端口號(hào),從1開(kāi)始
m_fd = open( portname,O_RDWR | O_NOCTTY | O_NONBLOCK);
作為串口通訊還需要一些通訊參數(shù)的配置,包括波特率、數(shù)據(jù)位、停止位、校驗(yàn)位等參數(shù)。在實(shí)際的操作中,主要是通過(guò)設(shè)置struct termios結(jié)構(gòu)體的各個(gè)成員值來(lái)實(shí)現(xiàn),一般會(huì)用到的函數(shù)包括:
tcgetattr( ) ;
tcflush( );
cfsetispeed( );
cfsetospeed( );
tcsetattr( );
其中各個(gè)函數(shù)的具體使用方法這里就不一一介紹了,用戶可以參考嵌入式Linux應(yīng)用程序開(kāi)發(fā)的相關(guān)書(shū)籍,也可參看Step2_SerialTest中Serial.cpp模塊中set_port( )函數(shù)代碼。
串口應(yīng)用的C++設(shè)計(jì)
Step2 _SerialTest是一個(gè)支持異步串口數(shù)據(jù)通訊的示例,該例程采用了面向?qū)ο蟮腃++編程,把串口數(shù)據(jù)通訊作為一個(gè)對(duì)象進(jìn)行封裝,用戶調(diào)用該對(duì)象提供的接口函數(shù)即可方便地完成串口通訊的操作。
CSerial類介紹
利用上一小節(jié)中介紹的串口API函數(shù),封裝了一個(gè)支持異步讀寫(xiě)的串口類CSerial,CSerial類中提供了4個(gè)公共函數(shù)、一個(gè)串口數(shù)據(jù)接收線程以及數(shù)據(jù)接收用到的數(shù)據(jù)Buffer。
class CSerial
{
private:
//通訊線程標(biāo)識(shí)符ID
pthread_t m_thread;
// 串口數(shù)據(jù)接收線程
static int ReceiveThreadFunc( void* lparam );
public:
CSerial();
virtual ~CSerial();
int m_fd; // 已打開(kāi)的串口文件描述符
int m_DatLen;
char DatBuf[1500];
int m_ExitThreadFlag;
// 按照指定的串口參數(shù)打開(kāi)串口,并創(chuàng)建串口接收線程
int OpenPort( int PortNo, int baudrate, char databits, char stopbits, char parity );
// 關(guān)閉串口并釋放相關(guān)資源
int ClosePort( );
// 向串口寫(xiě)數(shù)據(jù)
int WritePort( char* Buf, int len );
// 接收串口數(shù)據(jù)處理函數(shù)
virtual int PackagePro( char* Buf, int len );
};
OpenPort函數(shù)用于根據(jù)輸入串口參數(shù)打開(kāi)串口,并創(chuàng)建串口數(shù)據(jù)接收線程。在嵌入式Linux環(huán)境中是通過(guò)函數(shù)pthread_create( )創(chuàng)建線程,通過(guò)函數(shù)pthread_exit( )退出線程。嵌入式Linux線程屬性存在有非分離(缺省)和分離兩種,在非分離情況下,當(dāng)一個(gè)線程結(jié)束時(shí),它所占用的系統(tǒng)資源并沒(méi)有被釋放,也就是沒(méi)有真正的終止;只有調(diào)用pthread_join( )函數(shù)返回時(shí),創(chuàng)建的線程才能釋放自己占有的資源。在分離屬性下,一個(gè)線程結(jié)束時(shí)立即釋放所占用的系統(tǒng)資源?;谶@個(gè)原因,在我們提供的例程中通過(guò)相關(guān)函數(shù)將數(shù)據(jù)接收線程的屬性設(shè)置為分離屬性。如:
// 設(shè)置線程綁定屬性
res = pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
// 設(shè)置線程分離屬性
res += pthread_attr_setdetachstate( &attr, THREAD_CREATE_DETACHED );
ReceiveThreadFunc函數(shù)是串口數(shù)據(jù)接收和處理的主要核心代碼,在該函數(shù)中調(diào)用select( ),阻塞等待串口數(shù)據(jù)的到來(lái)。對(duì)于接收到的數(shù)據(jù)處理也是在該函數(shù)中實(shí)現(xiàn),在本例程中處理為簡(jiǎn)單的數(shù)據(jù)回發(fā),用戶可結(jié)合實(shí)際的應(yīng)用修改此處代碼,修改PackagePro( )函數(shù)即可。流程如下:
int CSerial::ReceiveThreadFunc(void* lparam)
{
CSerial *pSer = (CSerial*)lparam;
//定義讀事件集合
fd_set fdRead;
int ret;
struct timeval aTime;
while( 1 )
{
//收到退出事件,結(jié)束線程
if( pSer-》m_ExitThreadFlag )
{
break;
}
FD_ZERO(&fdRead);
FD_SET(pSer-》m_fd,&fdRead);
aTime.tv_sec = 0;
aTime.tv_usec = 300000;
ret = select( pSer-》m_fd+1,&fdRead,NULL,NULL,&aTime );
if (ret 《 0 )
{
//關(guān)閉串口
pSer-》ClosePort( );
break;
}
if (ret 》 0)
{
//判斷是否讀事件
if (FD_ISSET(pSer-》m_fd,&fdRead))
{
//data available, so get it!
pSer-》m_DatLen = read( pSer-》m_fd, pSer-》DatBuf, 1500 );
// 對(duì)接收的數(shù)據(jù)進(jìn)行處理,這里為簡(jiǎn)單的數(shù)據(jù)回發(fā)
if( pSer-》m_DatLen 》 0 )
{
pSer-》PackagePro( pSer-》DatBuf, pSer-》m_DatLen);
}
// 處理完畢
}
}
}
printf( ‘ReceiveThreadFunc finished\n’);
pthread_exit( NULL );
return 0;
}
需要注意的是,select( )函數(shù)中的時(shí)間參數(shù)在嵌入式Linux中每次都需要重新賦值,否則會(huì)自動(dòng)歸0。
CSerial類的實(shí)現(xiàn)代碼請(qǐng)參見(jiàn)Serial.CPP文件。
CSerial類的調(diào)用
CSerial類的具體使用也比較簡(jiǎn)單,主要是對(duì)于類中定義的4個(gè)公共函數(shù)的調(diào)用,以下為 Step2_SerialTest.cpp中相關(guān)代碼。
class CSerial m_Serial;
int main( int argc,char* argv[] )
{
int i1;
int portno, baudRate;
char cmdline[256];
printf( ‘Step2_SerialTest V1.0\n’ );
// 解析命令行參數(shù):串口號(hào) 波特率
if( argc 》 1 ) strcpy( cmdline, argv[1] );
else portno = 1;
if( argc 》 2 )
{
strcat( cmdline, ‘ ’ );
strcat( cmdline, argv[2] );
scanf( cmdline, ‘%d %d’, &portno, &baudRate );
}
else
{
baudRate = 115200;
}
printf( ‘port:%d baudrate:%d\n’, portno, baudRate);
//打開(kāi)串口相應(yīng)地啟動(dòng)了串口數(shù)據(jù)接收線程
i1 = m_Serial.OpenPort( portno, baudRate, ‘8’, ‘1’, ‘N’);
if( i1《0 )
{
printf( ‘serial open fail\n’);
return -1;
}
//進(jìn)入主循環(huán),這里每隔1s輸出一個(gè)提示信息
for( i1=0; i1《10000;i1++)
{
sleep(1);
printf( ‘%d \n’, i1+1);
}
m_Serial.ClosePort( );
return 0;
}
從上面的代碼可以看出,程序的主循環(huán)只需要實(shí)現(xiàn)一些管理性的功能,在本例程中僅僅是每隔1s輸出一個(gè)提示信息,在實(shí)際的應(yīng)用中,可以把一些定時(shí)查詢狀態(tài)的操作、看門狗的喂狗等操作放在主循環(huán)中,這樣充分利用了嵌入式Linux多任務(wù)的編程優(yōu)勢(shì),利用內(nèi)核的任務(wù)調(diào)度機(jī)制,將各個(gè)應(yīng)用功能模塊化,以便于程序的設(shè)計(jì)和管理。這里順便再提一下,在進(jìn)行多個(gè)串口編程時(shí),也可以利用本例程中的CSerial類為基類,根據(jù)應(yīng)用需求派生多個(gè)CSerial派生類實(shí)例,每一個(gè)派生類只是重新實(shí)現(xiàn)虛函數(shù)PackagePro(…),這樣每個(gè)串口都具有一個(gè)獨(dú)立的串口數(shù)據(jù)處理線程,利用Linux內(nèi)核的任務(wù)調(diào)度機(jī)制以實(shí)現(xiàn)多串口通訊功能。
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208713 -
嵌入式主板
+關(guān)注
關(guān)注
7文章
6084瀏覽量
35154
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論