转自:http://blog.csdn.net/xsm0921/article/details/2885021
DOS下的串口编程
第一章串行通信接口
串行通信使用单条数据线代替了并行通信的8位数据线,传输的距离更远。通信接口每次从CPU得到8位数据,然后通过一个并行入串行出的移位寄存器,转换成串行位,每次发送一位,将数据发送出去。同样,在接收端也必须有一个串行入并行出的移位寄存器来接收串行数据。并组合打包成一个字节。
以串行方式进入数据线的是由0和1组成的数据,一组这样的数叫做一个字符,一个字符可能有8位,或者7位,6位,5位。在传输中,每个字符都要加上起始位和终止位,起始位总是1位,终止位可以是1位或2位。为了保证传输数据的正确性,有时还包括一位效验位。一般芯片都允许编程时设定效验方式为奇效验,偶效验或者无效验。
串行通信的数据传输速率用bps(bits per second)来表示。另外,还有一种表示信号传输速率的单位是波特率(band rate)。波特率是一种信号调制单位,和bps不一定相等,它定义每秒钟传输的离散信号的数目。所谓的离散信号,就是指不均匀的,不连续的也不相关的信号。更详细的讲解请查阅相关文档。通信端口的传输速率从110bps到115200bps,经验表明,波特率相当于9600bps的时候,传输相当稳定。
1960年电子工业协会(Electronics Industries Association,EIA)制定了RS-232接口标准,以后又陆续发布了修订版本,这是目前广泛应用于个人计算机上的串行接口,用于近程数据通信,连接一些外部设备。下图就是我们经常用到的9针RS-232插头,
每个引脚的定义为:
引脚 |
方向 |
名称 |
描述 |
含义 |
1 |
输入 |
CD |
Carrier Detect |
数据载波检测 |
2 |
输入 |
RXD |
Receive Data |
数据接收端 |
3 |
输出 |
TXD |
Transmit Data |
数据发送端 |
4 |
输出 |
DTR |
Data Terminal Ready |
数据终端准备就绪(计算机) |
5 |
- |
SG |
System Ground |
信号地 |
6 |
输入 |
DSR |
Data Set Ready |
数据设备准备就绪 |
7 |
输出 |
RTS |
Request to Send |
请求发送(计算机要求发送数据) |
8 |
输入 |
CTS |
Clear to Send |
清除发送(MODEM准备接收数据) |
9 |
输入 |
RI |
Ring Indicator |
响铃指示 |
以上信号在通信过程中可能会被全部或者部分使用,把两台计算机通过串口连接起来,最简单的通讯仅需TXD及RXD及SG即可完成。
第二章端口设置
IBM PC和80x86兼容机可以连接4个串行端口,即COM1~COM4,相应的BIOS中的编号为COM0~COM3,但程序每次只能对其中一个端口进行存取。计算机启动时,自检程序就会测试4个COM端口是否存在,并把每个COM端口的I/O地址写到BIOS的数据区0040:0000~0040:0007共8个字节,每个COM地址占用2个字节。如果系统没有连接串行端口,BIOS数据区的这几个单元内容就成为0。用debug可以查看COM端口地址。
C>debug
-d 0040:0000 L08
0040:0000 F8 03 F8 02 E8 03 E8 02
上例查看结果表明系统中有4个COM端口,对应I/O地址分别为3F8,2F8,3E8,2E8。每个COM端口都包括一组8位的寄存器,这四个地址都叫做基地址,也就是第一个寄存器的I/O地址,其他寄存器的地址按照递增的顺序排列。COM1的基地址是3F8,COM2的基地址是2F8,COM3的基地址是3E8,COM4的基地址是2E8。我们通过这些寄存器编程控制数据接收或者发送。
COM1及COM3使用PC机中断4,COM2及COM4使用中断3。
第三章寄存器
1 寄存器组
COM端口的寄存器组如下表所示,共有12个寄存器,使用了8个地址,其中部分寄存器共用一个地址,由DLAB=0/1来区分。DLAB是线路控制寄存器的第7位。
基地址 |
读/写 |
寄存器缩写 |
描述 |
0 |
Write |
- |
发送保持寄存器(DLAB=0) |
0 |
Read |
- |
接收数据寄存器(DLAB=0) |
0 |
Read/Write |
- |
波特率低八位(DLAB=1) |
1 |
Read/Write |
IER |
中断允许寄存器 |
1 |
Read/Write |
- |
波特率高八位(DLAB=1) |
2 |
Read |
IIR |
中断标识寄存器 |
2 |
Write |
FCR |
FIFO控制寄存器 |
3 |
Read/Write |
LCR |
线路控制寄存器 |
4 |
Read/Write |
MCR |
MODEM控制寄存器 |
5 |
Read |
LSR |
线路状态寄存器 |
6 |
Read |
MSR |
MODEM状态寄存器 |
7 |
Read/Write |
- |
Scratch Register |
2 中断允许寄存器(IER)
位 |
描述 |
7 |
未使用 |
6 |
未使用 |
5 |
进入低功耗模式(16750) |
4 |
进入睡眠模式(16750) |
3 |
允许MODEM状态中断 |
2 |
允许接收线路状态中断 |
1 |
允许发送保持器空中断 |
0 |
允许接收数据就绪中断 |
Bit0置1将允许接收到数据时产生中断,Bit1置1时允许发送保持寄存器空时产生中断,Bit2置1将在LSR变化时产生中断,相应的Bit3置位将在MSR变化时产生中断。
3 中断识别寄存器(IIR)
位 |
描述 |
Bit6:7=00 |
无FIFO |
Bit6:7=01 |
允许FIFO,但不可用 |
Bit6:7=11 |
允许FIFO |
Bit5 |
允许64字节FIFO(16750) |
Bit4 |
未使用 |
Bit3 |
16550超时中断 |
Bit2:1=00 |
MODEM状态中断(CTS/RI/DTR/DCD) |
Bit2:1=01 |
发送保持寄存器空中断 |
Bit2:1=10 |
接收数据就绪中断 |
Bit2:1=11 |
接收线路状态中断 |
Bit0=0 |
有中断产生 |
Bit0=1 |
无中断产生 |
IIR为只读寄存器,Bit6:7用来指示FIFO的状态,均为0时则无FIFO,此时为8250或16450芯片,为01时有FIFO但不可以使用,为11时FIFO有效并可以正常工作。Bit3用来指示超时中断(16550/16750)。Bit0用来指示是否有中断发生,Bit1:2标识具体的中断类型,这些中断具有不同的优先级别,其中LSR中断级别最高,其次是数据就绪中断,然后是发送寄存器空中断,而MSR中断级别最低。
4 FIFO控制寄存器(FCR)
位 |
描述 |
Bit7:6=00 |
1Byte产生中断 |
Bit7:6=01 |
4Byte产生中断 |
Bit7:6=10 |
8Byte产生中断 |
Bit7:6=11 |
14Byte产生中断 |
Bit5 |
允许64字节FIFO |
Bit4 |
未使用 |
Bit3 |
DMA模式选择 |
Bit2 |
清除发送FIFO |
Bit1 |
清除接收FIFO |
Bit0 |
允许FIFO |
FCR可写但不可以读,该寄存器用来控制16550或16750的FIFO寄存器。Bit0置1将允许发送/接收的FIFO工作,Bit1和Bit2置1分别用来清除接收及发送FIFO。清除接收及发送FIFO并不影响移位寄存器。Bit1:2可自行复位,因此无需使用软件对其清零。Bit6:7用来设定产生中断的级别,发送/接收中断将在发送/接收到对应字节数时产生。
5 线路控制寄存器(LCR)
位 |
描述 |
Bit7=1 |
允许访问波特率因子寄存器 |
Bit7=0 |
允许访问接收/发送及中断允许寄存器 |
Bit6 |
设置间断,0-禁止,1-设置 |
Bit5:3=XX0 |
无校验 |
Bit5:3=001 |
奇校验 |
Bit5:3=011 |
偶校验 |
Bit5:3=101 |
奇偶保持为1 |
Bit5:3=111 |
奇偶保持为0 |
Bit2=0 |
1位停止位 |
Bit2=1 |
2位停止位(6-8位数据位),1.5位停止位(5位数据位) |
Bit1:0=00 |
5位数据位 |
Bit1:0=01 |
6位数据位 |
Bit1:0=10 |
7位数据位 |
Bit1:0=11 |
8位数据位 |
LCR用来设定通讯所需的一些基本参数。Bit7为1指定波特率因子寄存器有效,为0则指定发送/接收及IER有效。Bit6置1会将发送端置为0,这将会使接收端产生一个“间断”。Bit3-5用来设定是否使用奇偶校验以及奇偶校验的类型,Bit3=1时使用校验,Bit4为0则为奇校验,1为偶校验,而Bit5则强制校验为1或0,并由Bit4决定具体为0或1。Bit2用来设定停止位的长度,0表示1位停止位,为1则根据数据长度的不同使用1.5-2位停止位。Bit0:1用来设定数据长度。
6 MODEM控制寄存器(MCR)
位 |
描述 |
Bit7 |
未使用 |
Bit6 |
未使用 |
Bit5 |
自动流量控制(仅16750) |
Bit4 |
环路测试 |
Bit3 |
辅助输出2 |
Bit2 |
辅助输出1 |
Bit1 |
设置RTS |
Bit0 |
设置DTR |
MCR寄存器可读可写,Bit4=1进入环路测试模式。Bit3-0用来控制对应的管脚。
7 线路状态寄存器(LSR)
位 |
描述 |
Bit7 |
FIFO中接收数据错误 |
Bit6 |
发送移位寄存器空 |
Bit5 |
发送保持寄存器空 |
Bit4 |
间断 |
Bit3 |
帧格式错 |
Bit2 |
奇偶错 |
Bit1 |
超越错 |
Bit0 |
接收数据就绪 |
LSR为只读寄存器,当发生错误时Bit7为1,Bit6为1时标示发送保持及发送移位寄存器均空,Bit5为1时标示仅发送保持寄存器空,此时,可以由软件发送下一数据。当线路状态为0时Bit4置位为1,帧格式错时Bit3置位为1,奇偶错和超越错分别将Bit2及Bit1置位为1。Bit0置位为1表示接收数据就绪。
在接收和发送过程中,错误状态位(1,2,3,4位)一旦被置为1,则读入的接收数据已不是有效数据,所以在串行应用程序中,应检测数据传输是否出错。
奇偶错:通信线上(尤其是用电话线传输时)的噪声引起某些数据位的改变,产生奇偶错,通检测出奇偶错时,要求正在接受的数据至少应重新发送一段。
超越错:在上一个字符还未被CPU取走,又有字符要传送到数据寄存器里,则会引起超越错。
帧格式错:当接收/发送器未收到一个字符数据的终止位,会引起帧格式错。这种错误可能是由于通信线上的噪声引起终止位的丢失,或者是由于接收方和发送方初始化不匹配。
间断:间断有有时候并不能算是一个错误,而是为某些特殊的通信环境设置的“空格”状态。当间断为1时,这说明接收的“空格”状态超过了一个完整的数据字传输时间。
8 MODEM状态寄存器(MSR)
位 |
描述 |
Bit7 |
载波检测 |
Bit6 |
响铃指示 |
Bit5 |
DSR准备就绪 |
Bit4 |
CTS有效 |
Bit3 |
DCD已改变 |
Bit2 |
RI已改变 |
Bit1 |
DSR已改变 |
Bit0 |
CTS已改变 |
MSR寄存器的高4位分别对应MODEM的状态线,低4位表示MODEM的状态线是否发生了变化。到此,串口的寄存器就介绍完了,下面就要讲如何编程。
第四章 BIOS串行通信功能
BIOS int 14h提供了串行数据通信功能,包括将串行口初始化为指定的字节结构和传输速率,检查控制器的状态,读写字符等功能。
串行通信口BIOS功能(int 14h) |
|||
AH |
功能 |
调用参数 |
返回参数 |
0 |
初始化串行端口 |
AL=初始化参数 DX=通信口号 COM1=0,COM2=1 COM3=2,COM4=3 |
AH=线路状态 AL=MODEL状态 |
1 |
向串行通信口写字符 |
AL=要写的字符 DX=通信口号 |
写字符成功:AH=0,AL=字符。 写字符失败:AH中,Bit7=1,Bit6:0=线路状态。 |
2 |
从串行通信口读字符 |
DX=通信口号 |
读成功:AH中,Bit7=0;AL=字符 读失败:AH中,Bit7=1,Bit6:0=线路状态 |
3 |
取线路状态 |
DX=通信口号 |
AH=线路状态 AL=MODEL状态 |
1 初始化串口
int 14h的AH=0功能把指定的串行通信口初始化为希望的波特率,奇偶性,字长和终止位的位数,这些初始化参数设置在AL寄存器中,其各位的含义见下表:
AL |
意义 |
值 |
Bit7:5 |
波特率 |
000=110波特 001=150波特 010=300波特 011=600波特 100=1200波特 101=2400波特 110=4800波特 111=9600波特 |
Bit4:3 |
效验 |
1=2位 |
Bit2 |
终止位 |
0=1位 |
Bit1:0 |
字长 |
10=7位 11=8位 |
例如要求COM1口的传输率为2400波特,字长为8位,1位终止位,无奇偶效验:
__asm
{
mov AH, 0
mov AL, 0A03h // 0A03h =10100011
mov DX, 0 // COM1
int 14h // 调用BIOS
}
2 发送数据
char my_data = 65h; //假设要发送65h
__asm
{
mov AL, my_data
mov AH, 1
mov DX, 1 //通过COM2口发送出去
int 14h
}
3 接受数据
为了接收字符,首先要用int 14h,AH=3来获取COM端口的状态,其返回值在AH寄存器中。检查AH的第0位,它是数据准备好位,如果该位是1,说明COM端口已经接收到字符。然后就可以用int 14h,AH=2功能,把字符读到AL寄存器中来。
char LSR = 0; //线路状态
char my_data; //接收到的数据
__asm
{
mov AH, 3
mov DX, 0 //COM1的线路状态
int 14h
mov LSR, AH
}
if(! (LSR & 01h)) //没有数据到达
{
printf("no data arrived");
goto somewhere;
}
else //有数据到达,接受数据
{
__asm
{
mov AH, 2 //读字符
mov DX, 0
int 14h
mov my_data, AL //接受到的字符保存在my_data中
}
}
第五章 I/O寄存器编程
直接读写 I/O寄存器可以更灵活更可靠地操作串口通信,很多程序都是用这种方式编写的。
#define BASEADDR1 0x3F8 //COM1的寄存器基地址
1 初始化串口
void InitCom1()
{
//不允许中断
outportb(BASEADDR1 + 1, 0);
//设置DLAB=1, 允许访问波特率寄存器
outportb(BASEADDR1 + 3, 0x80);
//设置波特率9600, 设置的参数可以在下面表格中查到
outportb(BASEADDR1 + 0, 0x0C); //波特率低8位
outportb(BASEADDR1 + 1, 0x00); //波特率高8位
//设置8位数据位, 1位停止位, 无校验, DLAB=0
outportb(BASEADDR1 + 3, 0x03);
}
在DLAB=1时去设定通讯所需的波特率。常用的波特率参数见下表:
速率(BPS) |
波特率高八位 |
波特率低八位 |
50 |
09h |
00h |
300 |
01h |
80h |
600 |
00h |
C0h |
1200 |
00h |
60h |
2400 |
00h |
30h |
4800 |
00h |
18h |
9600 |
00h |
0Ch |
19200 |
00h |
06h |
38400 |
00h |
03h |
57600 |
00h |
02h |
115200 |
00h |
01h |
2 发送数据
再多的语言都不如代码表达得清楚,所以我还是把代码摆在下面,适当地做点注释。下面这个函数是把一个字符通过COM1口发送出去,my_data是要发送的字符。如果COM1不能够发送,就返回失败。
bool send_com1(char my_data)
{
char status = inportb(BASEADDR1 + 5); //检查线路状态
int count = 10000;
//一直等到发送寄存器空,或者超时
while(!(status & 0x20) && (count > 0))
{
count--;
status = inportb(BASEADDR1 + 5);
}
if(count > 0) //发送寄存器空
{
outportb(BASEADDR1 + 0, my_data); //发送字符
return true;
}
else
return false; //超时未发送
}
3 接收数据
下面这个函数是从COM1端口读一个字符,保存到pData指向的变量中,如果COM1口没有数据,就返回失败。
bool recv_com1(char* pData);
{
char status = inportb(BASEADDR1 + 5); //检查线路状态
if(status & 0x01) //接收数据就绪
{
*pData = inportb(BASEADDR1 + 0); //读一个字符
return true;
}
else
return false; //没有数据到达
}
4 打开FIFO功能
串口中所谓的FIFO,也就是先入先出队列功能,说的就是一组接收缓冲寄存器,和一组发送保持寄存器。接收缓冲寄存器和发送保持寄存器的大小是16个字节,当FIFO功能关闭的时候,接收缓冲寄存器和发送保持寄存器都只能放一个数据,即相当于一个字节大小。我们最好开始从数据的接收和发送说起。
数据接收:来自于线路上的数据首先进入接收移位寄存器(RSR),一个字符接收完成之后,数据移入接收缓冲寄存器(RBR),RBR实际就是一个16字节的FIFO队列。当中断设置时,串口控制器会根据FIFO的设置和RBR中数据的数目产生中断。FIFO功能关闭时,RBR只能放一个数据,如果主机设备来不及从RBR中读字符,那么串口接收到的下一个数据会把RBR覆盖,这样,就会有数据丢失。FIFO功能打开时,RBR中能够放16个数据,串口把完成的字符推入到RBR中,RBR原有的数就会前移,这样主机读字符的时间变得宽裕了。
数据发送:发送操作和接收操作相反,主机数据写入发送保持寄存器(THR),THR也是一个16字节的FIFO,然后数据移入发送移位寄存器(TSR),之后送到线路上。当中断设置时,串口控制器也会根据THR中数据的数目产生中断。FIFO功能打开时,主机可以把16字节的数据一次写入发送保持寄存器中。
我们接着就可以应用FIFO功能来完善先前的程序。发送数据:
//pData指向要发送的数据,Len是数据长度,返回是否发送成功
bool fifo_send(char* pData, int Len)
{
int count;
//禁用FIFO,目的是让程序第一时间感知字符是否发送成功
outportb(BASEADDR1 + 2, 0x00);
while(Len > 0)
{
//一直等到发送寄存器空,或者超时
count = 10000;
while(!(inportb(BASEADDR1 + 5) & 0x20) && (count > 0))
count--;
if(count <= 0) //超时未发送
return false;
outportb(BASEADDR1 + 0, *pData); //发送字符
Len--;
pData++;
}
//再次等到发送寄存器空,或者超时
count = 10000;
while(!(inportb(BASEADDR1 + 5) & 0x20) && (count > 0))
count--;
if(count <= 0)
return false; //超时未发送
else
return true; //确认,已经发送出去
}
接收数据:
//pData指向要接收的数据,Len是数据长度,返回是否接收成功
bool fifo_recv(char *pData, int Len)
{
int count;
//允许FIFO,清除接收FIFO,清除发送FIFO
outportb(BASEADDR1 + 2, 0x07);
while(Len > 0)
{
//一直等到接收数据就绪,或者超时
count = 10000;
while(!(inportb(BASEADDR1 + 5) & 0x01) && (count > 0))
count--;
if(count <= 0) //超时未收到
return false;
*pData = inportb(BASEADDR1 + 0); //读一个字符
Len--;
pDate++
}
return true;
}
到此,通过I/O寄存器对串口编程的方法就介绍完了,希望给这些能给我们开发的项目提供一些帮助,我们在具体使用的时候还要结合实际灵活运用。
总结
更具体的代码还是参考董锁英的过电压程序比接好。至于中断的传输方式,它适用于使用较快的速率传输大量的数据,现在一般都采用网口或者USB,串口的这种方式在实际应用中很少见了,所以在这里就暂不介绍了。