home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1993 #3 / NN_1993_3.iso / spool / comp / sys / ibm / pc / 1109 < prev    next >
Encoding:
Internet Message Format  |  1993-01-25  |  17.0 KB

  1. Xref: sparky comp.sys.ibm.pc:1109 comp.sys.ibm.pc.programmer:803 comp.sys.ibm.pc.hardware:37050
  2. Path: sparky!uunet!cs.utexas.edu!qt.cs.utexas.edu!yale.edu!ira.uka.de!sbusol.rz.uni-sb.de!coli.uni-sb.de!sbustd!chbl
  3. From: chbl@sbustd.rz.uni-sb.de (Christian Blum)
  4. Newsgroups: comp.sys.ibm.pc,comp.sys.ibm.pc.programmer,comp.sys.ibm.pc.hardware
  5. Subject: FAQ serial port (part two)
  6. Date: 25 Jan 1993 10:05:57 GMT
  7. Organization: Studenten-Mail, Rechenzentrum Universitaet des Saarlandes
  8. Lines: 567
  9. Message-ID: <1k0e25INNglt@coli-gate.coli.uni-sb.de>
  10. NNTP-Posting-Host: sbustd.stud.uni-sb.de
  11.  
  12.  
  13.   This is a FAQ answer on serial communications using the TTY protocol. It
  14. contains information on the TTY protocol and hardware and software implemen-
  15. tations on IBM PCs which is derived from National Semiconductor data sheets
  16. and practical experience of the author and his supporters.
  17.   If you want to contribute to this FAQ in any way, please email me (probably
  18. by replying to this posting). My email address is: chbl@stud.uni-sb.de
  19.   See the end for details.
  20.   It's the second publication of this file. Lots of errors have been
  21. removed, and lots of information has been added (which has surely brought
  22. other errors with it, see Murphy's Law).
  23.  
  24.  
  25. PART TWO - PROGRAMMING
  26.  
  27.  
  28. Programming
  29. -----------
  30.  
  31.   Now for the clickety-clickety thing. I hope you're a bit keen in
  32. assembler programming (if not, you've got a problem B-). Programming the UART
  33. in high level languages is, of course, possible, but not at very high
  34. rates or interrupt-driven. I give you several routines in assembler (and,
  35. wherever possible, in C) that do the dirty work for you.
  36.  
  37.   First thing to do is detect which chip is used. It shouldn't be difficult
  38. to convert this C function into assembler; I'll omit the assembly version.
  39.  
  40. int detect_UART(unsigned baseaddr)
  41. {
  42.    // this function returns 0 if no UART is installed.
  43.    // 1: 8250, 2: 16450, 3: 16550, 4: 16550A
  44.    int x;
  45.    // first step: see if the LCR is there
  46.    outp(baseaddr+3,0x1b);
  47.    if (inp(baseaddr+3)!=0x1b) return 0;
  48.    outp(baseaddr+3,0x3);
  49.    if (inp(baseaddr+3)!=0x3) return 0;
  50.    // next thing to do is look for the scratch register
  51.    outp(baseaddr+7,0x55);
  52.    if (inp(baseaddr+7)!=0x55) return 1;
  53.    outp(baseaddr+7,0xAA);
  54.    if (inp(baseaddr+7)!=0xAA) return 1;
  55.    // then check if there's a FIFO
  56.    outp(baseaddr+2,0x1);
  57.    x=inp(baseaddr+2);
  58.    if ((x&0x80)==0) return 2;
  59.    if ((x&0x40)==0) return 3;
  60.    return 4;
  61. }
  62.  
  63.   Remember: if it's not a 16550A, don't use the FIFO mode!
  64.  
  65.  
  66.   Now the non-interrupt version of TX and RX.
  67.  
  68.   Let's assume the following constants are set correctly (either by
  69. 'CONSTANT EQU value' or by '#define CONSTANT value'). You can easily use
  70. variables instead, but I wanted to save the extra lines for the ADD
  71. commands necessary then...
  72.  
  73.   UART_BASEADDR   the base address of the UART
  74.   UART_BAUDRATE   the divisor value (e.g. 12 for 9600 baud)
  75.   UART_LCRVAL     the value to be written to the LCR (e.g. 0x1b for 8N1)
  76.   UART_FCRVAL     the value to be written to the FCR. Bit 0, 1 and 2 set,
  77.                   bits 6 & 7 according to trigger level wished (see above).
  78.                   0x87 is a good value.
  79.  
  80.   First thing to do is initializing the UART. This works as follows:
  81.  
  82. init_UART proc near
  83.   push ax  ; we are 'clean guys'
  84.   push dx
  85.   mov  dx,UART_BASEADDR+3  ; LCR
  86.   mov  al,80h  ; set DLAB
  87.   out  dx,al
  88.   mov  dx,UART_BASEADDR    ; divisor
  89.   mov  ax,UART_BAUDRATE
  90.   out  dx,ax
  91.   mov  dx,UART_BASEADDR+3  ; LCR
  92.   mov  al,UART_LCRVAL  ; params
  93.   out  dx,al
  94.   mov  dx,UART_BASEADDR+4  ; MCR
  95.   xor  ax,ax  ; clear loopback
  96.   out  dx,al
  97.   ;***
  98.   pop  dx
  99.   pop  ax
  100.   ret
  101. init_UART endp
  102.  
  103. void init_UART()
  104. {
  105.    outp(UART_BASEADDR+3,0x80);
  106.    outpw(UART_BASEADDR,UART_BAUDRATE);
  107.    outp(UART_BASEADDR+3,UART_LCRVAL);
  108.    outp(UART_BASEADDR+4,0);
  109.    //***
  110. }
  111.  
  112.   If we wanted to use the FIFO functions of the 16550A, we'd have to add
  113. some lines to the routines above (where the ***s are).
  114. In assembler:
  115.   mov  dx,UART_BASEADDR+2  ; FCR
  116.   mov  al,UART_FCRVAL
  117.   out  dx,al
  118. And in C:
  119.    outp(UART_BASEADDR+2,UART_FCRVAL);
  120.  
  121.   Don't forget to disable the FIFO when your program exits! Some other
  122. software may rely on this!
  123.  
  124.   Not very complex so far, isn't it? Well, I told you so at the very
  125. beginning, and we wanted to start easy. Now let's send a character.
  126.  
  127. UART_send proc near
  128.   ; character to be sent in AL
  129.   push dx
  130.   push ax
  131.   mov  dx,UART_BASEADDR+5
  132. us_wait:
  133.   in   al,dx  ; wait until we are allowed to write a byte to the THR
  134.   test al,20h
  135.   jz   us_wait
  136.   pop  ax
  137.   mov  dx,UART_BASEADDR
  138.   out  dx,al  ; then write the byte
  139.   pop  ax
  140.   pop  dx
  141.   ret
  142. UART_send endp
  143.  
  144. void UART_send(char character)
  145. {
  146.    while ((inp(UART_BASEADDR+5)&0x20)!=0) {;}
  147.    outp(UART_BASEADDR,(int)character);
  148. }
  149.  
  150.   This one sends a null-terminated string.
  151.  
  152. UART_send_string proc near
  153.   ; DS:SI contains a pointer to the string to be sent.
  154.   push si
  155.   push ax
  156.   push dx
  157.   cld  ; we want to read the string in its correct order
  158. uss_loop:
  159.   lodsb
  160.   or   al,al  ; last character sent?
  161.   jz   uss_ende
  162.   ;*1*
  163.   mov  dx,UART_BASEADDR+5
  164.   push ax
  165. uss_wait:
  166.   in   al,dx
  167.   test al,20h
  168.   jz   uss_wait
  169.   mov  dx,UART_BASEADDR
  170.   pop  ax
  171.   out  dx,al
  172.   ;*2*
  173.   jmp  uss_loop
  174. uss_ende:
  175.   pop  dx
  176.   pop  ax
  177.   pop  si
  178.   ret
  179. UART_send_string endp
  180.  
  181. void UART_send_string(char *string)
  182. {
  183.    int i;
  184.    for (i=0; string[i]!=0; i++)
  185.       {
  186.       //*1*
  187.       while ((inp(UART_BASEADDR+5)&0x20)!=0) {;}
  188.       outp(UART_BASEADDR,(int)string[i]);
  189.       //*2*
  190.       }
  191. }
  192.  
  193.   Of course, we could have used our already programmed function/procedure
  194. UART_send instead of the piece of code limited by *1* and *2*, but we are
  195. interested in high-speed code.
  196.  
  197.   It shouldn't be a hard nut for you to modify the above function/procedure
  198. so that it sends a block of data rather than a null-terminated string. I'll
  199. omit that here.
  200.  
  201.   Now for reception. We want to program routines that do the following:
  202.   - check if a character received or an error occured
  203.   - read a character if there's one available
  204.  
  205.   Both the C and the assembler routines return 0 (in AX) if there is
  206. neither an error condition nor a character available. If a character is
  207. available, Bit 8 is set and AL or the lower byte of the return value
  208. contains the character. Bit 9 is set if we lost data (overrun), bit 10
  209. signals a parity error, bit 11 signals a framing error, bit 12 shows if
  210. there is a break in the data stream and bit 15 signals if there are any
  211. errors in the FIFO (if we turned it on). The procedure/function is much
  212. smaller than this paragraph:
  213.  
  214. UART_get_char proc near
  215.   push dx
  216.   mov  dx,UART_BASEADDR+5
  217.   in   al,dx
  218.   mov  ah,al
  219.   and  ah,9fh
  220.   test al,1
  221.   jz   ugc_nochar
  222.   mov  dx,UART_BASEADDR
  223.   in   al,dx
  224. ugc_nochar:
  225.   pop  dx
  226.   ret
  227. UART_get_char endp
  228.  
  229. unsigned UART_get_char()
  230. {
  231.    unsigned x;
  232.    x=(inp(UART_BASEADDR+5)<<8)&0x9f;
  233.    if (x&0x100) x|=((unsigned)inp(UART_BASEADDR))&0xff);
  234.    return x;
  235. }
  236.  
  237.   This procedure/function lets us easily keep track of what's happening
  238. with the RxD pin. It does not provide any information on the modem status
  239. lines! We'll program that later on.
  240.  
  241.   If we wanted to show what's happening with the RxD pin, we'd just have to
  242. write a routine like the following (I use a macro in the assembler version
  243. to shorten the source code):
  244.  
  245. DOS_print macro pointer
  246.   ; prints a string in the code segment
  247.   push ax
  248.   push ds
  249.   push cs
  250.   pop  ds
  251.   mov  dx,pointer
  252.   mov  ah,9
  253.   int  21h
  254.   pop  ds
  255.   pop  ax
  256.   endm
  257.  
  258. UART_watch_rxd proc near
  259. uwr_loop:
  260.   ; check if keyboard hit; we want a possibility to break the loop
  261.   mov  ah,1
  262.   int  16h
  263.   jnz  uwr_exit
  264.   call UART_get_char
  265.   or   ax,ax
  266.   jz   uwr_loop
  267.   test ah,1  ; is there a character in AL?
  268.   jz   uwr_nodata
  269.   push ax    ; yes, print it
  270.   mov  dl,al
  271.   mov  ah,2
  272.   int  21h
  273.   pop  ax
  274. uwr_nodata:
  275.   test ah,0eh ; any error at all?
  276.   jz   uwr_loop  ; this speeds up things
  277.   test ah,2  ; overrun error?
  278.   jz   uwr_noover
  279.   DOS_print overrun_text
  280. uwr_noover:
  281.   test ah,4  ; parity error?
  282.   jz   uwr_nopar
  283.   DOS_print parity_text
  284. uwr_nopar:
  285.   test ah,8  ; framing error?
  286.   jz   uwr_loop
  287.   DOS_print framing_text
  288.   jmp  uwr_loop
  289. overrun_text    db "*** Overrun Error ***$"
  290. parity_text     db "*** Parity Error ***$"
  291. framing_text    db "*** Framing Error ***$"
  292. UART_watch_rxd endp
  293.  
  294. void UART_watch_rxd()
  295. {
  296.    union _useful_
  297.       {
  298.       unsigned val;
  299.       char character;
  300.       } x;
  301.    while (!kbhit())
  302.       {
  303.       x.val=UART_get_char();
  304.       if (!x.val) continue;  // nothing? Continue
  305.       if (x.val&0x100) putc(x.character);  // character? Print it
  306.       if (!(x.val&0x0e00)) continue;  // any error condidion? No, continue
  307.       if (x.val&0x200) printf("*** Overrun Error ***");
  308.       if (x.val&0x400) printf("*** Parity Error ***");
  309.       if (x.val&0x800) printf("*** Framing Error ***");
  310.       }
  311. }
  312.  
  313.   If you call these routines from a function/procedure as shown below,
  314. you've got a small terminal program!
  315.  
  316. terminal proc near
  317. ter_loop:
  318.   call UART_watch_rxd  ; watch line until a key is pressed
  319.   xor  ax,ax  ; get that key from the buffer
  320.   int  16h
  321.   cmp  al,27  ; is it ESC?
  322.   jz   ter_end  ; yes, then end this function
  323.   call UART_send  ; send the character typed if it's not ESC
  324.   jmp  ter_loop  ; don't forget to check if data comes in
  325. ter_end:
  326.   ret
  327. terminal endp
  328.  
  329. void terminal()
  330. {
  331.    int key;
  332.    while (1)
  333.       {
  334.       UART_watch_rxd();
  335.       key=getche();
  336.       if (key==27) break;
  337.       UART_send((char)key);
  338.       }
  339. }
  340.  
  341.   These, of course, should be called from an embedding routine like the
  342. following (the assembler routines concatenated will assemble as an .EXE
  343. file. Put the lines 'code segment' and 'assume cs:code,ss:stack' to the
  344. front).
  345.  
  346. main proc near
  347.   call UART_init
  348.   call terminal
  349.   mov  ax,4c00h
  350.   int  21h
  351. main endp
  352. code ends
  353. stack segment stack 'stack'
  354.   dw 128 dup (?)
  355. stack ends
  356. end main
  357.  
  358. void main()
  359. {
  360.    UART_init();
  361.    terminal();
  362. }
  363.  
  364.   Here we are. Now you've got everything you need to program null-modem
  365. polling UART software.
  366.   You know the way. Now go and add functions to check if a data set is
  367. there, then establish a connection. Don't know how? Set DTR, wait for DSR.
  368. If you want to send, set RTS and wait for CTS before you actually transmit
  369. data. You don't need to store old values of the MCR: this register is
  370. readable. Just read in the data, AND/OR the bit required and write the
  371. byte back.
  372.  
  373.  
  374.   Now for the interrupt-driven version of the program. This is going to be
  375. a bit voluminous, so I draw the scene and leave the painting to you. If you
  376. want to implement interrupt-driven routines in a C program use either the
  377. inline-assembler feature or link the objects together.
  378.  
  379.   First thing to do is initialize the UART the same way as shown above.
  380. But there is some more work to be done before you enable the UART
  381. interrupt: FIRST SET THE INTERRUPT VECTOR CORRECTLY! Use Function 0x25 of
  382. the DOS interrupt 0x21. See also the note on known bugs if you've got a
  383. 8250.
  384.  
  385. UART_INT      EQU 0Ch  ; for COM2 / COM4 use 0bh
  386. UART_ONMASK   EQU 11101111b  ; for COM2 / COM4 use 11110111b
  387. UART_OFFMASK  EQU 00010000b  ; for COM2 / COM4 use 00001000b
  388. UART_IERVAL   EQU ?   ; replace ? by any value between 0h and 0fh
  389.                       ; (dependent on which ints you want)
  390.               ; DON'T SET bit 1 yet!
  391.  
  392. initialize_UART_interrupt proc near
  393.   push ds
  394.   push cs  ; build a pointer in DS:DX
  395.   pop  ds
  396.   lea  dx,interrupt_service_routine
  397.   mov  ax,2500h+UART_INT
  398.   int  21h
  399.   pop  ds
  400.   mov  dx,UART_BASEADDR+4  ; MCR
  401.   in   al,dx
  402.   or   al,8  ; set OUT2 bit to enable interrupts
  403.   out  dx,al
  404.   mov  dx,UART_BASEADDR+1  ; IER
  405.   mov  al,UART_IERVAL
  406.   out  dx,al
  407.   in   al,21h  ; last thing to do is unmask the int in the ICU
  408.   and  al,UART_ONMASK
  409.   out  21h,al
  410.   sti  ; and free interrupts if they have been disabled
  411.   ret
  412. initialize_UART_interrupt endp
  413.  
  414.   Now the interrupt service routine. It has to follow several rules:
  415. first, it MUST NOT change the contents of any register of the CPU! Then it
  416. has to tell the ICU (did I tell you that this is the interrupt control
  417. unit?) that the interrupt is being serviced. Next thing is test which part
  418. of the UART needs service. Let's have a look at the following procedure:
  419.  
  420. interupt_service_routine proc far  ; define as near if you want to link .COM
  421.   ;*1*
  422.   push ax
  423.   push cx
  424.   push dx
  425.   push bx
  426.   push sp
  427.   push bp
  428.   push si
  429.   push di
  430.   ;*2*   replace the part between *1* and *2* by pusha on an 80186+ system
  431.   push ds
  432.   push es
  433.   mov  al,20h    ; remember: first thing to do in interrupt routines is tell
  434.   out  20h,al    ; the ICU about it. This avoids lock-up
  435. int_loop:  
  436.   mov  dx,UART_BASEADDR+2  ; IIR
  437.   xor  ax,ax  ; clear AH; this is the fastest and shortest possibility
  438.   in   al,dx  ; check IIR info
  439.   test al,1
  440.   jnz  int_end
  441.   and  al,6  ; we're interested in bit 1 & 2 (see data sheet info)
  442.   mov  si,ax ; this is already an index! Well-devised, huh?
  443.   call word ptr cs:int_servicetab[si]  ; ensure a near call is used...
  444.   jmp  int_loop
  445. int_end:
  446.   pop  es
  447.   pop  ds
  448.   ;*3*
  449.   pop  di
  450.   pop  si
  451.   pop  bp
  452.   pop  sp
  453.   pop  bx
  454.   pop  dx
  455.   pop  cx
  456.   pop  ax
  457.   ;*4*   *3* - *4* can be replaced by popa on an 80186+ based system
  458.   iret
  459. interupt_service_routine endp
  460.  
  461.   This is the part of the service routine that does the decisions. Now we
  462. need four different service routines to cover all four interrupt source
  463. possibilities (EVEN IF WE DIDN'T ENABLE THEM!! Since 'unexpected'
  464. interrupts can have higher priority than 'expected' ones, they can appear
  465. if an expected [not masked] interrupt situation shows up).
  466.  
  467. int_servicetab    DW int_modem, int_tx, int_rx, int_status
  468.  
  469. int_modem proc near
  470.   mov  dx,UART_BASE+6  ; MSR
  471.   in   al,dx
  472.   ; do with the info what you like; probably just ignore it...
  473.   ; but YOU MUST READ THE MSR or you'll lock up the system!
  474.   ret
  475. int_modem endp
  476.  
  477. int_tx proc near
  478.   ; get next byte of data from a buffer or something
  479.   ; (remember to set the segment registers correctly!)
  480.   ; and write it to the THR (offset 0)
  481.   ; if no more data is to be sent, disable the THRE interrupt
  482.   ; If the FIFO is used, you can write data as long as bit 5
  483.   ; of the LSR is 0
  484.  
  485.   ; end of data to be sent?
  486.   ; no, jump to end_int_tx
  487.   mov  dx,UART_BASEADDR+1
  488.   in   al,dx
  489.   and  al,00001101b
  490.   out  dx,al
  491. end_int_tx:
  492.   ret
  493. int_tx endp
  494.  
  495. int_rx proc near
  496.   mov  dx,UART_BASEADDR
  497.   in   al,dx
  498.   ; do with the character what you like (best write it to a
  499.   ; FIFO buffer)
  500.   ; the following lines speed up FIFO mode operation
  501.   mov  dx,UART_BASEADDR+5
  502.   in   al,dx
  503.   test al,1
  504.   jnz  int_rx
  505.   ret
  506. int_rx endp
  507.  
  508. int_status proc near
  509.   mov  dx,UART_BASEADDR+5
  510.   in   al,dx
  511.   ; do what you like. It's just important to read the LSR
  512.   ret
  513. int_status endp
  514.  
  515.   How is data sent now? Write it to a FIFO buffer that is read by the
  516. interrupt routine. Then set bit 1 of the IER and check if this has already
  517. started transmission. If not, you'll have to start it by yourself... THIS
  518. IS DUE TO THOSE NUTTY GUYS AT BIG BLUE WHO DECIDED TO USE EDGE TRIGGERED
  519. INTERRUPTS INSTEAD OF PROVIDING ONE SINGLE FLIP FLOP FOR THE 8253/8254!
  520.   This procedure can be a C function, too. It is not time-critical at all.
  521.  
  522.   ; copy data to buffer
  523.   mov  dx,UART_BASEADDR+1  ; IER
  524.   in   al,dx
  525.   or   al,2  ; set bit 1
  526.   out  dx,al
  527.   mov  dx,UART_BASEADDR+5  ; LSR
  528.   in   al,dx
  529.   test al,40h  ; is there a transmission running?
  530.   jz   dont_crank  ; yes, so don't mess it up
  531.   call int_tx  ; no, crank it up
  532. dont_crank:
  533.  
  534.   Well, that's it! Your main program has to take care about the buffers,
  535. nothing else!
  536.  
  537.   One more thing: always remember that at 115,200 baud there is service to
  538. be done at least every 8 microseconds! On an XT with 4.77 MHz this is
  539. about 5 assembler commands!! So forget about servicing the serial port at
  540. this rate in an interrupt-driven manner on such computers. An AT with 12
  541. MHz probably will manage it if you use 'macro commands' such as pusha and/or
  542. a 16550A in FIFO mode. An AT can perform about 20 instructions between two
  543. characters, a 386 with 25 MHz will do about 55, and a 486 with 33 MHz will
  544. manage about 150. Using a 16550A is strongly recommended at high rates.
  545.   The interrupt service routines can be accelerated by not pushing that
  546. much registers, and pusha and popa are fast instructions.
  547.  
  548.   Another last thing: due to the poor construction of the PC interrupt
  549. system, one interrupt line can only be driven by one device. This means if
  550. you want to use COM3 and your mouse is connected to COM1, you can't use
  551. interrupt features without disabling the mouse (write 0x0 to the mouse's
  552. MCR).
  553.  
  554.  
  555.  
  556.   Well, that's the end of my short :-) summary. Don't hesitate to correct
  557. me if I'm wrong (preferably via email) in the details (I hope not, but it's
  558. not easy to find typographical and other errors in a text that you've
  559. written yourself). And please help to complete this list! If you've got
  560. anything to add, email it to me or post it as an additional FAQ answer.
  561. Maybe anybody can bring himself to write a summary on modem communication?
  562.  
  563.   Please tell me what you think about it! Is it worth while to do the
  564. work? Is anybody interested in it? Shall I carry on, complete and re-post
  565. the list? Please give me feedback!
  566.  
  567.  
  568.   Yours
  569.  
  570.         Chris
  571.  
  572.  
  573. --
  574. ---------------------------------------------------------------------
  575. Christian Blum                   Universitaet des Saarlandes, Germany
  576. Friedrich-Ebert-Str. 50          chbl@stud.uni-sb.de
  577. W-6685 Heiligenwald              chbl@sbustd.rz.uni-sb.de
  578. Germany                          Tel (+49) (0) 6821 67476
  579.