大多數(shù)的電腦設(shè)備都具有RS-232C接口,盡管它的性能指標(biāo)并非很好。在廣泛的市場支持下依然常勝不衰。就使用而言,RS-232也確實(shí)有其優(yōu)勢:僅需3根線便可在兩個數(shù)字設(shè)備之間全雙工的傳送數(shù)據(jù)。不過,RS-232C的控制要比使用并行通訊的打印機(jī)接口更難于控制。RS-232C使用了遠(yuǎn)較并行口更多的寄存器。這些寄存器用來實(shí)現(xiàn)串行數(shù)據(jù)的傳送及RS-232C設(shè)備之間的握手與流量控制。本文將分別描述 PC機(jī)及單片機(jī)MCS-51 的串行通訊的原理及具體的軟件設(shè)計(jì)。
-
RS-232C介紹與PC硬件
-
使用查詢方法的串行通訊程序設(shè)計(jì)
-
使用中斷的串行通訊程序設(shè)計(jì)
-
MCS-51串行通訊
-
關(guān)于RS485
(1) RS-232C介紹與PC硬件:
RS-232C使用-3到-25V表示數(shù)字“1”,使用3V到25V表示數(shù)字“0”,RS-232C在空閑時處于邏輯“1”狀態(tài),在開始傳送時,首先產(chǎn)生一起始位,起始位為一個寬度的邏輯“0”,緊隨其后為所要傳送的數(shù)據(jù),所要傳送的數(shù)據(jù)有最低位開始依此送出,并以一個結(jié)束位標(biāo)志該字節(jié)傳送結(jié)束,結(jié)束位為一個寬度的邏輯“1”狀態(tài)。
PC機(jī)一般使用8250或16550作為串行通訊的控制器,使用9針或25針的接插件將串行口的信號送出。該插座的信號定義如下:
DB-25 |
DB-9 |
信號名稱 |
方向 |
含 義 |
2 |
3 |
TXD |
輸出 |
數(shù)據(jù)發(fā)送端 |
3 |
2 |
RXD |
輸入 |
數(shù)據(jù)接收端 |
4 |
7 |
RTS |
輸出 |
請求發(fā)送(計(jì)算機(jī)要求發(fā)送數(shù)據(jù)) |
5 |
8 |
CTS |
輸入 |
清除發(fā)送(MODEM準(zhǔn)備接收數(shù)據(jù)) |
6 |
6 |
DSR |
輸入 |
數(shù)據(jù)設(shè)備準(zhǔn)備就緒 |
7 |
5 |
SG |
- |
信號地 |
8 |
1 |
DCD |
輸入 |
數(shù)據(jù)載波檢測 |
20 |
4 |
DTR |
輸出 |
數(shù)據(jù)終端準(zhǔn)備就緒(計(jì)算機(jī)) |
22 |
9 |
RI |
輸入 |
響鈴指示 |
以上信號在通訊過程之中可能會被全部或部分使用,最簡單的通訊僅需TXD及RXD及SG即可完成,其他的握手信號可以做適當(dāng)處理或直接懸空,至于是否可以懸空這視乎你的通訊軟件。比如說,如果使用DOS所提供的BIOS通訊驅(qū)動程序,那么,這些握手信號則需要做如下處理,因?yàn)锽IOS的通訊驅(qū)動使用了這些信號。如果使用自己編寫的串行驅(qū)動程序則可以完全不使用這些握手信號(詳見下面有關(guān)章節(jié))。
PC機(jī)一般使用8250或16550的作為串行通訊控制器,8250及16550的管腳排列如下:
8250(16550)的寄存器如下表所示:
基地址 |
讀/寫 |
寄存器縮寫 |
注 釋 |
0 |
Write |
- |
發(fā)送保持寄存器(DLAB=0) |
0 |
Read |
- |
接收數(shù)據(jù)寄存器(DLAB=0) |
0 |
Read/Write |
- |
波特率低八位(DLAB=1) |
1 |
Read/Write |
IER |
中斷允許寄存器 |
1 |
Read/Write |
- |
波特率高八位(DLAB=1) |
2 |
Read |
IIR |
中斷標(biāo)識寄存器 |
2 |
Write |
FCR |
FIFO控制寄存器 |
3 |
Read/Write |
LCR |
線路控制寄存器 |
4 |
Read/Write |
MCR |
MODEM控制寄存器 |
5 |
Read |
LSR |
線路狀態(tài)寄存器 |
6 |
Read |
MSR |
MODEM狀態(tài)寄存器 |
7 |
Read/Write |
- |
Scratch Register |
PC機(jī)支持1-4個串行口,即COM1-COM4,其基地址在BIOS數(shù)據(jù)區(qū)0000:0400-0000:0406中描述,對應(yīng)地址分別為3F8/2F8/3E8/2E8,COM1及COM3使用PC機(jī)中斷4,COM2及COM4使用中斷3。
在上表中,8250共有12個寄存器,使用了8個地址,其中部分寄存器共用一個地址,由DLAB=0/1來區(qū)分,在DLAB=1用于設(shè)定通訊所需的波特率。常用的波特率參數(shù)見下表:
速率(BPS) |
波特率高八位 |
波特率低八位 |
50 |
09h |
00h |
300 |
01h |
80h |
600 |
00h |
C0h |
2400 |
00h |
30h |
4800 |
00h |
18h |
9600 |
00h |
0Ch |
19200 |
00h |
06h |
38400 |
00h |
03h |
57600 |
00h |
02h |
115200 |
00h |
01h |
以下幾個表格為8250的寄存器的功能描述:
中斷允許寄存器(IER):
位 |
注 釋 |
7 |
未使用 |
6 |
未使用 |
5 |
進(jìn)入低功耗模式(16750) |
4 |
進(jìn)入睡眠模式(16750) |
3 |
允許MODEM狀態(tài)中斷 |
2 |
允許接收線路狀態(tài)中斷 |
1 |
允許發(fā)送保持器空中斷 |
0 |
允許接收數(shù)據(jù)就緒中斷 |
Bit0置1將允許接收到數(shù)據(jù)時產(chǎn)生中斷,Bit1置1時允許發(fā)送保持寄存器空時產(chǎn)生中斷,Bit2置1將在LSR變化時產(chǎn)生中斷,相應(yīng)的Bit3置位將在MSR變化時產(chǎn)生中斷。
中斷識別寄存器(IIR):
位 |
注 釋 |
Bit6:7=00 |
無FIFO |
Bit6:7=01 |
允許FIFO,但不可用 |
Bit6:7=11 |
允許FIFO |
Bit5 |
允許64字節(jié)FIFO(16750) |
Bit4 |
未使用 |
Bit3 |
16550超時中斷 |
Bit2:1=00 |
MODEM狀態(tài)中斷(CTS/RI/DTR/DCD) |
Bit2:1=01 |
發(fā)送保持寄存器空中斷 |
Bit2:1=10 |
接收數(shù)據(jù)就緒中斷 |
Bit2:1=11 |
接收線路狀態(tài)中斷 |
Bit0=0 |
有中斷產(chǎn)生 |
Bit0=1 |
無中斷產(chǎn)生 |
IIR為只讀寄存器,Bit6:7用來指示FIFO的狀態(tài),均為0時則無FIFO,此時為8250或16450芯片,為01時有FIFO但不可以使用,為11時FIFO有效并可以正常工作。Bit3用來指示超時中斷(16550/16750)。
Bit0用來指示是否有中斷發(fā)生,Bit1:2標(biāo)識具體的中斷類型,這些中斷具有不同的優(yōu)先級別,其中LSR中斷級別最高,其次是數(shù)據(jù)就緒中斷,然后是發(fā)送寄存器空中斷,而MSR中斷級別最低。
FIFO控制寄存器(FCR):
位 |
注 釋 |
Bit7:6=00 |
1Byte產(chǎn)生中斷 |
Bit7:6=01 |
4Byte產(chǎn)生中斷 |
Bit7:6=10 |
8Byte產(chǎn)生中斷 |
Bit7:6=11 |
14Byte產(chǎn)生中斷 |
Bit5 |
允許64字節(jié)FIFO |
Bit4 |
未使用 |
Bit3 |
DMA模式選擇 |
Bit2 |
清除發(fā)送FIFO |
Bit1 |
清除接收FIFO |
Bit0 |
允許FIFO |
FCR可寫但不可以讀,該寄存器用來控制16550或16750的FIFO寄存器。Bit0置1將允許發(fā)送/接收的FIFO工作,Bit1和Bit2置1分別用來清除接收及發(fā)送FIFO。清除接收及發(fā)送FIFO并不影響移位寄存器。Bit1:2可自行復(fù)位,因此無需使用軟件對其清零。Bit6:7用來設(shè)定產(chǎn)生中斷的級別,發(fā)送/接收中斷將在發(fā)送/接收到對應(yīng)字節(jié)數(shù)時產(chǎn)生。
線路控制寄存器(LCR):
位 |
注 釋 |
Bit7=1 |
允許訪問波特率因子寄存器 |
Bit7=0 |
允許訪問接收/發(fā)送及中斷允許寄存器 |
Bit6 |
設(shè)置間斷,0-禁止,1-設(shè)置 |
Bit5:3=XX0 |
無校驗(yàn) |
Bit5:3=001 |
奇校驗(yàn) |
Bit5:3=011 |
偶校驗(yàn) |
Bit5:3=101 |
奇偶保持為1 |
Bit5:3=111 |
奇偶保持為0 |
Bit2=0 |
1位停止位 |
Bit2=1 |
2位停止位(數(shù)據(jù)位6-8位),1.5位停止位(5位數(shù)據(jù)位) |
Bit1:0=00 |
5位數(shù)據(jù)位 |
Bit1:0=01 |
6位數(shù)據(jù)位 |
Bit1:0=10 |
7位數(shù)據(jù)位 |
Bit1:0=11 |
8位數(shù)據(jù)位 |
LCR用來設(shè)定通訊所需的一些基本參數(shù)。Bit7為1指定波特率因子寄存器有效,為0則指定發(fā)送/接收及IER有效。Bit6置1會將發(fā)送端置為0,這將會使接收端產(chǎn)生一個“間斷”。Bit3-5用來設(shè)定是否使用奇偶校驗(yàn)以及奇偶校驗(yàn)的類型,Bit3=1時使用校驗(yàn),Bit4為0則為奇校驗(yàn),1為偶校驗(yàn),而Bit5則強(qiáng)制校驗(yàn)為1或0,并由Bit4決定具體為0或1。Bit2用來設(shè)定停止位的長度,0表示1位停止位,為1則根據(jù)數(shù)據(jù)長度的不同使用1.5-2位停止位。Bit0:1用來設(shè)定數(shù)據(jù)長度。
MODEM控制寄存器(MCR):
位 |
注 釋 |
Bit7 |
未使用 |
Bit6 |
未使用 |
Bit5 |
自動流量控制(僅16750) |
Bit4 |
環(huán)路測試 |
Bit3 |
輔助輸出2 |
Bit2 |
輔助輸出1 |
Bit1 |
設(shè)置RTS |
Bit0 |
設(shè)置DSR |
MCR寄存器可讀可寫,Bit4=1進(jìn)入環(huán)路測試模式。Bit3-0用來控制對應(yīng)的管腳。
線路狀態(tài)寄存器(LSR):
位 |
注 釋 |
Bit7 |
FIFO中接收數(shù)據(jù)錯誤 |
Bit6 |
發(fā)送移位寄存器空 |
Bit5 |
發(fā)送保持寄存器空 |
Bit4 |
間斷 |
Bit3 |
幀格式錯 |
Bit2 |
奇偶錯 |
Bit1 |
超越錯 |
Bit0 |
接收數(shù)據(jù)就緒 |
LSR為只讀寄存器,當(dāng)發(fā)生錯誤時Bit7為1,Bit6為1時標(biāo)示發(fā)送保持及發(fā)送移位寄存器均空,Bit5為1時標(biāo)示僅發(fā)送保持寄存器空,此時,可以由軟件發(fā)送下一數(shù)據(jù)。當(dāng)線路狀態(tài)為0時Bit4置位為1,幀格式錯時Bit3置位為1,奇偶錯和超越錯分別將Bit2及Bit1置位為1。Bit0置位為1表示接收數(shù)據(jù)就緒。
MODEM狀態(tài)寄存器(MSR):
位 |
注 釋 |
Bit7 |
載波檢測 |
Bit6 |
響鈴指示 |
Bit5 |
DSR準(zhǔn)備就緒 |
Bit4 |
CTS有效 |
Bit3 |
DCD已改變 |
Bit2 |
RI已改變 |
Bit1 |
DSR已改變 |
Bit0 |
CTS已改變 |
MSR寄存器的高4位分別對應(yīng)MODEM的狀態(tài)線,低4位表示MODEM的狀態(tài)線是否發(fā)生了變化。
以上我們詳細(xì)介紹了PC機(jī)的串行通訊硬件環(huán)境,以下將分別給出使用查詢及中斷驅(qū)動的方法編寫的串行口驅(qū)動程序。這些程序僅使用RXD/TXD,無需硬件握手信號。
(2) 使用查詢方法的串行通訊程序設(shè)計(jì):
polling.c
#include #include #include #define PortBase 0x2F8
void com_putch(unsigned char); int com_chkch(void);
main() { int c; unsigned char ch;
outportb(PortBase + 1 , 0); /* Turn off interrupts - Port1 */
/* Set COM1: 9600,8,N,1*/ outportb(PortBase + 3 , 0x80); outportb(PortBase + 0 , 0x0C); outportb(PortBase + 1 , 0x00); outportb(PortBase + 3 , 0x03);
clrscr();
while(1) {
c = com_chkch(); if(c!=-1) { c &= 0xff; putch(c); if(c=='
') putch('
'); }
if(kbhit()) { ch = getch(); com_putch(ch); } }
}
void com_putch(unsigned char ch) { unsigned char status;
while(1) { status = inportb(PortBase+5); if(status&0x01) inportb(PortBase+0); else break; }
outportb(PortBase,ch); }
int com_chkch(void) { unsigned char status;
status = inportb(PortBase+5); status &= 0x01; if(status) return((int)inportb(PortBase+0)); else return(-1);
}
使用查詢方式的通訊程序適合9600bps以下的應(yīng)用。
(3) 使用中斷的串行通訊程序設(shè)計(jì):
該程序由兩部分組成,serial.c及sercom.c,sercom.c為通訊的底層驅(qū)動,使用中斷的串行通訊程序可以工作到115.2Kbps.
serial.c
#include #include #include #include #include #include "sercom.c"
COM *c;
main() { unsigned char ch;
c = ser_init( PORT_B,BAUD_9600,_COM_CHR8,_COM_NOPARITY,4096,4096 );
while(1) {
if( serhit(c)) { ch = getser(c); putchar(ch); }
if(kbhit()) { ch = getch(); putser(ch,c); }
} }
llio.c
#include #include #include #include
#define CR 0x0d #define TRUE 0xff #define FALSE 0
#define PORT_A 0 /* COM1 */ #define PORT_B 1 /* COM2 */ #define BAUD_9600 _COM_9600 #define BAUD_4800 _COM_4800 #define BAUD_2400 _COM_2400 #define BAUD_1200 _COM_1200 #define BAUD_600 _COM_600 #define BAUD_300 _COM_300 #define BAUD_110 _COM_110
typedef struct { char ready; /* TRUE when ready */ unsigned com_base; /* 8250 Base Address */ char irq_mask; /* IRQ Enable Mask */ char irq_eoi; /* EOI reply for this port */ char int_number; /* Interrupt # used */ void (_interrupt _far *old)( void ); /* Old Interrupt */
/* Buffers for I/O */
char *in_buf; /* Input buffer */ int in_tail; /* Input buffer TAIL ptr */ int in_head; /* Input buffer HEAD ptr */ int in_size; /* Input buffer size */ int in_crcnt; /* Input count */ char in_mt; /* Input buffer FLAG */
char *out_buf; /* Output buffer */ int out_tail; /* Output buffer TAIL ptr */ int out_head; /* Output buffer HEAD ptr */ int out_size; /* Output buffer size */ char out_full; /* Output buffer FLAG */ char out_mt; /* Output buffer MT */ } COM;
COM *ser_init( int port,int baud,int bit,int parity,int isize,int osize ); void ser_close( COM *c );
int getsers( COM *c,int len,char *str ); int putsers( char *str, COM *c ); char serline( COM *c ); int getser( COM *c ); char serhit(COM *c); char putser(char outch,COM *c); void cntl_rts(int flag,COM *c); void cntl_dtr(int flag,COM *c); void clean_ser( COM *c );
#define COM1_BASE 0x03F8 #define COM1_IRQ_MASK 0xEF /*11101111B IRQ 4 For COM1 */ #define COM1_IRQ_EOI 0x64 /* IRQ 4 Spec EOI */ #define COM1_INT_NUM 0x0C /* Int # for IRQ4 */
#define COM2_BASE 0x02F8 #define COM2_IRQ_MASK 0xF7 /*11110111B IRQ 3 For COM2 */ #define COM2_IRQ_EOI 0x63 /* IRQ 3 Spec EOI */ #define COM2_INT_NUM 0x0B /* Int # for IRQ3 */
/* 8250 ACE register defs */
#define THR 0 /* Offset to Xmit hld reg (write) */ #define RBR 0 /* Receiver holding buffer (read) */ #define IER 1 /* Interrupt enable register */ #define IIR 2 /* Interrupt identification reg */ #define LCR 3 /* Line control register */ #define MCR 4 /* Modem control register */ #define LSR 5 /* Line status register */ #define MSR 6 /* Modem status register */
#define SREG(x) ((unsigned)((unsigned)x + c->com_base))
/* 8259 Int controller registers */
#define INTC_MASK 0x21 /* Interrupt controller MASK reg */ #define INTC_EOI 0x20 /* Interrupt controller EOI reg */
#define MAX_PORTS 2 /* # I/O ports (DOS limit) */ static int count = 0; static COM com_list[MAX_PORTS]; /* I/O data structure */
static COM *com1; /* Pointers for interrupt actions */ static COM *com2; static COM *com_xfer; /* Transfer interrupt data structure */
COM *ser_init0(int port,char *ibuf,int isize, char *obuf,int osize); void ser_close0( COM *c );
void (_interrupt _far int_ser1)( void ); /* Int rtn for serial I/O COM 1 */ void (_interrupt _far int_ser2)( void ); /* Int rtn for serial I/O COM 2 */ void (_interrupt _far int_ser_sup)( void ); /* Support int actions */
COM *ser_init( int port,int baud,int bit,int parity,int isize,int osize ) { unsigned status; char ch; COM *c; char *in_buf,*out_buf;
status = _bios_serialcom(_COM_INIT,port,(bit | parity | _COM_STOP2| baud ));
in_buf = malloc( isize ); if( in_buf == NULL ) return( NULL );
out_buf = malloc( osize ); if( out_buf == NULL ) return( NULL );
c = ser_init0(port,in_buf,isize,out_buf,osize );
clean_ser(c);
return( c ); }
void ser_close(COM *c) { int i;
if( !c->ready ) return;
ser_close0(c);
free( c->in_buf ); free( c->out_buf );
}
char serline( COM *c ) {
if( !c->ready ) return(FALSE);
if( c->in_crcnt > 0 ) return( TRUE ); else return( FALSE ); }
int getsers( COM *c,int len,char *str ) { char ch; int i;
i = 0; while( iwhile( !serhit(c) ) { if(kbhit()) return( -1 ); }
ch = 0x7f & getser(c); switch( ch ) {
case 0x0d: str[i++] = '
|