原创 DOS串口编程

2008-7-17 22:44 2631 3 3 分类: 工业电子
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
设置DSR


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寄存器对串口编程的方法就介绍完了,希望给这些能给我们开发的项目提供一些帮助,我们在具体使用的时候还要结合实际灵活运用。
总结
更具体的代码还是参考dongsuoying的过电压程序比接好。至于中断的传输方式,它适用于使用较快的速率传输大量的数据,现在一般都采用网口或者USB,串口的这种方式在实际应用中很少见了,所以在这里就暂不介绍了。

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
3
关闭 站长推荐上一条 /6 下一条