DOSBOX虚拟环境能在WIN10和LINUX平台上支持DOS、WIN3x、Win9x程序运行。串口是当年计算机硬件的标配,经RS232-RS485转换后轻松实现远距离数据通讯,这些当年的程序和设备在今天的DOSBOX下也还是可以使用的。DOS、QuickBasic、TurboBasic、PowerBasic,都曾经陪伴了许多人的青春年华,回味起来既温馨又充实,至今依然很耐人玩味,因此,还是把心得体会写下来。
DOSBOX不仅支持串口,还支持打印口和游戏口。串口和打印口都有Direct I/O能力,游戏口也是如此,而DOSBOX中的程序大多是实模式下运行的,通过Direct I/O即可在串口、并口、游戏口的支持下与外部硬件打交道(其它非标口是不行的)。在QBasic、TBasic、PBasic的On COM(n), On Strig(n)支持下,串口游戏口又都是优于60ms的中断源,合理安排下做中断处理还是比较容易使用的(优先级高的中断会中断优先级低的中断)。反正就是拿它玩的,也不必纠结太多其它什么东西,能用就好。
想让DOSBOX支持串口,要在它的CONF文件中配置一下串口。真实串口要映射成DOSBOX的串口,一般就一对一好了。将serial1 = dummy改成serial1 = directserial realport:COM1,这样真实串口就挂到了DOSBOX的串口serial1,即DOSBOX的COM1口。
[Serial]
serial1 = directserial realport:COM1
串口通讯是既让人兴奋又让人讨厌的东西,无论你在哪个BASIC下常规使用它都有毛病,在数据读取时会出问题,要么缓冲区溢出程序不运行了,要么串口线断线程序死着不动了。研华的STD-DRV能支持四个普通串口,在DOS下调用它的函数比BASIC带的OPEN类的东西好用多了,它是不加密的,移动哪个DOS下都能使用,而用还带C、BASIC、CLIPPER等SAMPLE程序。只是,把它用到DOSBOX下时就不好用了。其实,直接操作串口的状态和数据寄存器就方便了,而且DOSBOX支持这种直接操作。下面是个TuroBasic的实验程序,在其它BASIC下应该也是运行的,这些平台都差不多而且应该是代码兼容的。
ON ERROR GOTO ERRORTRAP1
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE GOTO INSTEAD
CLS
'SET COM1 BAUD RATE AND MODE
OUT &H3F8 + 3, &H80
OUT &H3F8 + 0, &H0C
OUT &H3F8 + 1, &H00
OUT &H3F8 + 3, &H03
OUT &H3F8 + 4, &H00
OUT &H3F8 + 1, &H00
ZZZ=32
DO WHILE INKEY$<>CHR$(27)
ZZZ = ZZZ + 1
FOR L = 1 TO 1000
COMSTATUSOUT = INP(&H3F8 + 5)
IF (COMSTATUSOUT AND &H20) = &H20 THEN
OUT &H3F8, ZZZ
EXIT FOR
END IF
NEXT L
IF ZZZ >= 126 THEN ZZZ = 32
FOR L = 1 TO 1000
COMSTATUSIN = INP(&H3F8+5)
IF (COMSTATUSIN AND 1) <> 0 THEN
FFF$ = CHR$(INP(&H3F8)): PRINT FFF$+" ";
EXIT FOR
END IF
NEXT L
LOOP
END
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE A SUBROUTINE INSTEAD
ERRORTRAP1:
RESUME NEXT
这段程序是寄存器直接发送和接收,然后显示ASCII字符到屏幕上。因为不存在INPUT类的内部等待,和其它封闭起来的毛病,因此,即使是串口线断了,最多是收不到数据,一旦恢复连接则数据又开始接收了,这在与外部硬件实时打交道时可靠多了。如果发送端定义成!nnn#类指令,那收到指令时可按指令行事了,与Adam或NuDam模块类似了,与单片机打交道也可以如此操作。看看它的实际输出吧。
这是将九针串口的2和3脚,TxD和RxD连在一起得到的即时收发反应,正常情况下是稳稳的不丢失字符的。
除了如此操作之外,DOSBOX还可借助PCAP或SLIRP联网,不过是有局限的。毕竟DOSBOX不是基于虚拟网卡结构的,它没有虚拟网上就只能寄生在物理网卡,从底层收发数据,就是CAP抓包数据或叫截获数据,这网联得好像不怎么正道似的,但却是能联接上网的。
另外就是共享数据,DOSBOX和主机WINx用的是相同的文件系统,通过对文件系统的加解锁就可以将砂箱DOSBOX中的数据文件共享给主机程序读取。DOSBOX对文件加锁,写完数据关闭文件,释放文件锁;主机程序加锁文件,读取数据,并释放文件锁。这种方法做大数据交换还算方便吧,怎么也比串口收发数据方便,如果Mount的是Ramdisk那还可以不停的读写操作提高数据交换的即时性。
总之,DOS虽然老,但在64位Win10和Linux上还是能用的。相比Mono和Wine, DOSBOX轻便多了,不用建一堆Windows的目录结构和注册表。Watcom c/c++带上Pmode/W,DOSBOX下的DOS也能保护模式编程,上Win3x和Win9x也挺好玩的。
上述基于DOSBOX的串口通讯,也可以在QUICK BASIC下用On Com(1) gosub的中断方式实现。下面的代码编译:
BC nonam6.bas /w /v /x
Link nonam6
最终产生nonam.exe
ON ERROR GOTO ERRORTRAP1
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE GOTO INSTEAD
ON COM(1) GOSUB COMTRAP1
COM(1) ON
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,BIN" FOR RANDOM AS #1
CLS
ERRORID = 0
START:
ZZZ = 32
SDATAID = 0
DO WHILE INKEY$ <> CHR$(27)
TBUFFERID = INP(&H3F8 + 5) AND &H20
IF ERRORID = 1 THEN
'RECOVERED FROM AN ERROR, UNCONDITIONAL SEND
SDATAID = 1
ERRORID = 0
ZZZ = ZZZ + 1
IF ZZZ >= 126 THEN ZZZ = 32
PRINT #1, ZZZ
ELSEIF SDATAID = 0 AND TBUFFERID = 32 THEN
SDATAID = 1
ZZZ = ZZZ + 1
IF ZZZ >= 126 THEN ZZZ = 32
PRINT #1, ZZZ
END IF
LOOP
CLOSE #1
END
COMTRAP1:
COM(1) OFF
INPUT #1, COMDATA
PRINT CHR$(COMDATA) + " ";
SDATAID = 0
COM(1) ON
RETURN
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE A SUBROUTINE INSTEAD
ERRORTRAP1:
'CLOSE COMPORT AND REOPEN THE PORT
COM(1) OFF
CLOSE #1
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,BIN" FOR RANDOM AS #1
COM(1) ON
ERRORID = 1 'FLAG - RETURN FROM AN ERROR
RESUME START
上述程序运行时,将串口2、3针短路,得到如下屏幕
上述程序稍做改变,发送由单字符改为字符串,比如 $AAB,每次发送一个ASC(串),收端也做判断,以$开始并以CHR$(13)结尾的作为一个指令串辨识,然后依指令串定义去做相应的执行。上面中断方式的QBASIC程序若是在PBASIC上运行,去掉串口打开时字符串中的,PB不使用这个参数,否则编译出错:BAD FILENAME。
下面是收发指令方式的测试,因为其它BASIC不支持SUB或FUNCTION类的子过程,就用带标号的原始GOSUB和RETURN方式了,可以尽量保持在不同BASIC下的编译运行,但所附的这个例子是在QBASIC 7下编译运行的。
某些模块使用类似的指令,所以,就参照它们的指令写法。键盘上按键 1、2、3、4,分别发出不同的字符串指令,而中断方式的子过程返加收到的指令并附加了一些字符,这些字符的地方可以是数据采集卡的高8位和低8位转换值的合成结果,如此便完成了收指令并按指令返回数据采集结果的测试,测试条件是串口的2、3脚短接。程序如下:
CLS
DIM COMSTRING(20) AS STRING
CPOS = 1
ON ERROR GOTO ERRORTRAP1
ON COM(1) GOSUB COMTRAP1
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,RS,BIN" FOR RANDOM AS #1
COM(1) ON
COMMANDSET = 0
PRINT
PRINT "Press key 1, 2, 3, and 4 on the keyboard, and see..."
PRINT "Press ESC to exit"
PRINT
DO
KEYIN$ = INKEY$
IF KEYIN$ = CHR$(27) THEN EXIT DO
IF KEYIN$ = "1" THEN
INSTRUCT$ = "$AAB" + CHR$(13)
GOSUB SENDOUT 'SEND OUT A COMMAND
ELSEIF KEYIN$ = "2" THEN
INSTRUCT$ = "$AAC" + CHR$(13)
GOSUB SENDOUT 'SEND OUT A COMMAND
ELSEIF KEYIN$ = "3" THEN
INSTRUCT$ = "$AAD" + CHR$(13)
GOSUB SENDOUT 'SEND OUT A COMMAND
ELSEIF KEYIN$ = "4" THEN
INSTRUCT$ = "$AAE" + CHR$(13)
GOSUB SENDOUT 'SEND OUT A COMMAND
END IF
LOOP
END
SENDOUT:
COMIN$ = LTRIM$(RTRIM$(INSTRUCT$))
'PRINT COMIN$ 'TO SEE WHATS IN
IF COMIN$ <> "" THEN
FOR NN = 1 TO LEN(COMIN$)
PRINT #1, ASC(MID$(COMIN$, NN, 1))
NEXT NN
END IF
RETURN
COMTRAP1:
COM(1) OFF
INPUT #1, COMDATA
IF COMDATA = ASC("$") THEN
COMSTRING(1) = CHR$(COMDATA)
CPOS = 1
ELSEIF COMDATA <> 13 THEN
CPOS = CPOS + 1
IF CPOS > 20 THEN CPOS = 20
COMSTRING(CPOS) = CHR$(COMDATA)
ELSE
'A FULL STRING OF COMMAND IDENTIFIED
LOCALCOMMAND$ = ""
FOR OO = 1 TO CPOS
LOCALCOMMAND$ = LOCALCOMMAND$ + COMSTRING(OO)
NEXT OO
'*******************************************
'CHECK LOCALCOMMAND$ VS COMMAND TABLE OF ICP
IF LOCALCOMMAND$ = "$AAB" THEN
PRINT "IT IS $AAB, I WILL SEND BACK ACQUIRED DATA"
ELSEIF LOCALCOMMAND$ = "$AAC" THEN
PRINT "IT IS $AAC, I WILL SEND BACK ACQUIRED DATA"
ELSEIF LOCALCOMMAND$ = "$AAD" THEN
PRINT "IT IS $AAD, I WILL SEND BACK ACQUIRED DATA"
ELSEIF LOCALCOMMAND$ = "$AAE" THEN
PRINT "IT IS $AAE, I WILL SEND BACK ACQUIRED DATA"
END IF
'*******************************************
END IF
COM(1) ON
RETURN
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE A SUBROUTINE INSTEAD
ERRORTRAP1:
'CLOSE COMPORT AND REOPEN THE PORT
COM(1) STOP: COM(1) OFF
CLOSE #1
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,RS,BIN" FOR RANDOM AS #1
PRINT #1, ZZZ: INPUT #1, COMDATA
COM(1) ON
RESUME NEXT
上面的程序都是基于串口2、3脚直接短接测试的。将指令发送程序和接受应答程序分开运行在两台机器上的话,大致是这个样子的。
发送程序
CLS
DIM COMSTRING(20) AS STRING
BRATE$ = RTRIM$(LTRIM$(COMMAND$))
IF BRATE$ = "" THEN BRATE$ = "9600"
CPOS = 1
DATASET = 0
ON ERROR GOTO ERRORTRAP1
ON COM(1) GOSUB COMTRAP1
OPEN "COM1:" + BRATE$ + ",N,8,1,CD0,CS0,DS0,RS" FOR RANDOM AS #1
COM(1) ON
START:
CLS
DATASET = 0
PRINT
PRINT "Press key 1, 2, 3, and 4 on the keyboard, and see..."
PRINT "Press ESC to exit"
PRINT
DO
KEYIN$ = INKEY$
IF KEYIN$ = CHR$(27) THEN EXIT DO
IF DATASET = 0 THEN
DATASET = 1
INSTRUCT$ = "$AAB" + CHR$(13)
TBUFFERID = INP(&H3F8 + 5) AND &H20
IF TBUFFERID = 32 THEN
GOSUB SENDOUT 'SEND OUT A COMMAND
END IF
END IF
' IF KEYIN$ = "1" THEN
' INSTRUCT$ = "$AAB" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "2" THEN
' INSTRUCT$ = "$AAC" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "3" THEN
' INSTRUCT$ = "$AAD" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "4" THEN
' INSTRUCT$ = "$AAE" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' END IF
LOOP
END
SENDOUT:
COMIN$ = LTRIM$(RTRIM$(INSTRUCT$))
'PRINT COMIN$ 'TO SEE WHATS IN
IF COMIN$ <> "" THEN
FOR NN = 1 TO LEN(COMIN$)
PRINT #1, ASC(MID$(COMIN$, NN, 1))
NEXT NN
END IF
RETURN
COMTRAP1:
COM(1) OFF
INPUT #1, COMDATA
IF COMDATA = ASC("!") THEN
COMSTRING(1) = CHR$(COMDATA)
CPOS = 1
ELSEIF COMDATA <> 13 THEN
CPOS = CPOS + 1
IF CPOS > 20 THEN CPOS = 20
COMSTRING(CPOS) = CHR$(COMDATA)
ELSE
'A FULL STRING OF COMMAND IDENTIFIED
LOCALCOMMAND$ = ""
FOR OO = 1 TO CPOS
LOCALCOMMAND$ = LOCALCOMMAND$ + COMSTRING(OO)
NEXT OO
'*******************************************
'PRINT ACCEPTED DATA
PRINT LOCALCOMMAND$ + " ";
DATASET = 0
'*******************************************
END IF
COM(1) ON
RETURN
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE A SUBROUTINE INSTEAD
ERRORTRAP1:
'CLOSE COMPORT AND REOPEN THE PORT
COM(1) STOP: COM(1) OFF
CLOSE #1
OPEN "COM1:" + BRATE$ + ",N,8,1,CD0,CS0,DS0,RS" FOR RANDOM AS #1
PRINT #1, ZZZ: INPUT #1, COMDATA
COM(1) ON
RESUME START
接收程序
CLS
DIM COMSTRING(20) AS STRING
BRATE$ = RTRIM$(LTRIM$(COMMAND$))
IF BRATE$ = "" THEN BRATE$ = "9600"
CPOS = 1
PORTDO = &H378
PORTDI = &H379
ON ERROR GOTO ERRORTRAP1
ON COM(1) GOSUB COMTRAP1
OPEN "COM1:" + BRATE$ + ",N,8,1,CD0,CS0,DS0,RS" FOR RANDOM AS #1
COM(1) ON
START:
CLS
MIDPOS = (80 - LEN("DATA ACQUISITION KERNEL")) / 2
LOCATE 11, MIDPOS: PRINT "DAQSBC VER1.0"
LOCATE 12, MIDPOS: PRINT "DATA ACQUISITION KERNEL"
LOCATE 13, MIDPOS: PRINT "DATE:" + DATE$
LOCATE 14, MIDPOS: PRINT "TIME:" + TIME$ + "..."
DO WHILE INKEY$ <> CHR$(27)
LOCATE 13, MIDPOS: PRINT "DATE:" + DATE$
LOCATE 14, MIDPOS: PRINT "TIME:" + TIME$ + "..."
'****************************************
'* SELET ADC ADDRESS *
'* ACQUIRE HIGHER BITS, THEN LOWER BITS *
'* ACQUIRE DI *
'****************************************
LOOP
END
' DO
' KEYIN$ = INKEY$
' IF KEYIN$ = CHR$(27) THEN EXIT DO
' IF KEYIN$ = "1" THEN
' INSTRUCT$ = "$AAB" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "2" THEN
' INSTRUCT$ = "$AAC" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "3" THEN
' INSTRUCT$ = "$AAD" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' ELSEIF KEYIN$ = "4" THEN
' INSTRUCT$ = "$AAE" + CHR$(13)
' GOSUB SENDOUT 'SEND OUT A COMMAND
' END IF
' LOOP
' END
'
'SENDOUT:
' COMIN$ = LTRIM$(RTRIM$(INSTRUCT$))
' 'PRINT COMIN$ 'TO SEE WHATS IN
'
' IF COMIN$ <> "" THEN
' FOR NN = 1 TO LEN(COMIN$)
' PRINT #1, ASC(MID$(COMIN$, NN, 1))
' NEXT NN
' END IF
' RETURN
COMTRAP1:
COM(1) OFF
INPUT #1, COMDATA
IF COMDATA = ASC("$") THEN
COMSTRING(1) = CHR$(COMDATA)
CPOS = 1
ELSEIF COMDATA <> 13 THEN
CPOS = CPOS + 1
IF CPOS > 20 THEN CPOS = 20
COMSTRING(CPOS) = CHR$(COMDATA)
ELSE
'A FULL STRING OF COMMAND IDENTIFIED
LOCALCOMMAND$ = ""
FOR OO = 1 TO CPOS
LOCALCOMMAND$ = LOCALCOMMAND$ + COMSTRING(OO)
NEXT OO
'*******************************************
IF LOCALCOMMAND$ = "$AAB" THEN
'******************************
OUTVAL = INP(PORTDI) + VAL(RIGHT$(TIME$, 1))
OUTSTRING$ = STR$(OUTVAL)
PRINT #1, ASC("!")
'SEND ASCII CHARACTERS FOR PORT VALUE ACQUIRED
FOR PP = 1 TO LEN(OUTSTRING$)
PRINT #1, ASC(MID$(OUTSTRING$, PP, 1))
NEXT PP
PRINT #1, 13
'******************************
END IF
END IF
COM(1) ON
RETURN
'TURBOBASIC DOESN'T ALLOW ON_ERROR_ RESUME_NEXT, USE A SUBROUTINE INSTEAD
ERRORTRAP1:
'CLOSE COMPORT AND REOPEN THE PORT
COM(1) STOP: COM(1) OFF
CLOSE #1
OPEN "COM1:" + BRATE$ + ",N,8,1,CD0,CS0,DS0,RS" FOR RANDOM AS #1
PRINT #1, ZZZ: INPUT #1, COMDATA
COM(1) ON
RESUME START
发送与应答指令可以根据自己的喜好定义,应答程序可以安装在SBC板子的CF/IDE卡上或DOM电子盘上,采集量可是DI、计数或ADC,输出量可是DO或DAC,或是直接使用串行总线模组(或单片机串口通讯,功能与上述应答程序是类同的)。
DOSBOX 文件方式的数据交换
DOSBOX是可以多份考贝同时运行的,两个DOSBOX中的程序可通过文件锁方式同时操作同一数据文件,比竟它们是基于同一平台的文件,操作系统会管理好文件锁,这也是DOSBOX完备的能力。不仅两个DOSBOX程序能同时操作同一文件,DOSBOX砂箱中的文件还可以和主平台的程序进行文件操作。下图是两个DOSBOX中的不同程序操作同一数据文件的情形。
写文件操作程序 - DOSBOX1
CLS
ON ERROR RESUME NEXT
ON TIMER(1) GOSUB TIMERTRAP
TIMER ON
WHILE INKEY$ <> CHR$(27): WEND
END
TIMERTRAP:
OUTSTRING$ = TIME$
OPEN "FSHARE.TXT" FOR OUTPUT SHARED AS #1
WRITE #1, OUTSTRING$
CLOSE #1
PRINT "SENT TIMESTAMP: " + OUTSTRING$
RETURN
读文件操作程序 - DOSBOX2
CLS
ON ERROR RESUME NEXT
ON TIMER(1) GOSUB TIMERTRAP
TIMER ON
WHILE INKEY$ <> CHR$(27): WEND
END
TIMERTRAP:
OPEN "FSHARE.TXT" FOR INPUT SHARED AS #1
INPUT #1, ACCEPTSTRING$
CLOSE #1
PRINT "RECEIVED TIMESTAMP: " + ACCEPTSTRING$
RETURN
打开文件时加上SHARED就解决文件锁问题,如果加上ON ERROR除错就更好了,实际应用时宜判断一下错误代号,比笼统的RESUME要更精准。
上面的程序是用TIMER读写的,改一下让它在LOOP中不断更新,速度就不一样了。
FSHARE.BAS
CLS
ON ERROR RESUME NEXT
WHILE INKEY$ <> CHR$(27)
OUTSTRING$ = STR$(ABS(1000 * RND(1)))
OPEN "FSHARE.TXT" FOR OUTPUT SHARED AS #1
WRITE #1, OUTSTRING$
CLOSE #1
PRINT "SENT DATA: " + OUTSTRING$
WEND
END
SHAREF.BAS
CLS
ON ERROR RESUME NEXT
WHILE INKEY$ <> CHR$(27)
OPEN "FSHARE.TXT" FOR INPUT SHARED AS #1
INPUT #1, ACCEPTSTRING$
CLOSE #1
PRINT "RECEIVED DATA: " + ACCEPTSTRING$
WEND
END
对如此反复读写的磁盘耐受问题
现在的硬盘都有足够的缓冲,这一点数据磁盘是不发生物理机械动作的。更好的方法是使用固态电子盘,或是RAM虚拟盘,就不会有机械性磁盘动作了。
小结:
使用DOSBOX砂箱的好处有许多,比如:
1. 不需要注册表注册,直接考贝到WINDOWS10就能运行它,而砂箱里的DOS、WIN3x、WIN9x也就随时可运行了,是软件虚拟化的硬件,因此,没有硬件冲突。
2. 它是跨平台的,可以在各种LINUX、BSD、UNIX下运行,这也意味着,砂箱里的程序都可以在其它平台上依托于砂箱而运行。
3. 现在已经打通了砂箱与外界的联系,包括串口和文件共享,自己的程序也就不再被外界孤立隔离。
即然在DOSBOX砂箱DOS下的QBASIC能通过串口与外部设备交换数据,那砂箱里WINx16下的VB3或其它比如DELPHI 2又如何呢?
我们在砂箱里用VB3编写一个串口交互程序,形同下图。
在砂箱的WIN3.2环境下的VB3中运行它
通过串口发送给外部设备并由外部设备的数据清楚顺畅不间断地显示在列表框中,说明在砂箱里,不论是DOS程序还是WINx16程序,都能完成基于串口的数据交互。
WINx16独立在WINx64平台上运行
砂箱里的VB3程序,可以直接编译成EXE文件,通常情况下它必须有基于DOSBOX的WIN3.x环境才能运行,而实际上,我们可以另辟蹊径,甩掉不必要的砂箱,让程序直接在WINx64平台上运行。下图显示的非常清楚,背景是WIN10x64界面,前台程序PROJECT1.EXE就是VB3编译的WINx16程序,完全不需要砂箱支持。
但是,一定是有背景程序负责在WIN3.2和WIN64之间翻译,否则肯定会因兼容性问题报错的。背景支持程序就是OTVDM,它是一个独立的EXE文件,直接运行它即会在WIN64平台安装好支持,一旦安装好就不需要再干涉它了,启动计算机它自动做无名英雄,用户是感觉不到它存在的,老旧WIN3.2程序直接双击就行了。
Rx、Tx、GND三线串口是各类单片机的最基本配备,也是各类PC主板卡必配的通讯口,是计算机最方便的通讯口,还是很有必要用好的。