Computer Engineering II
  MP3

Schedule    Lab schedule
Homework    NASM docs
Machine Problems    Resources
Final Project    Photos
Gradebook    Feedback
Syllabus    Archives
Lectures    Download NASM
Home    Restricted access
MP3  

Machine Problem 3: Chat 291

Assigned 9:00 AM, Monday, October 8, 2001
Purpose Text-Mode Graphics, Serial communication,  and Keyboard Interrupts
Points 75
Due Date Monday, October 22 - 5:00pm

Introduction

This MP will introduce you to text-mode video,  interrupt service routines, and serial communication.  You will need to develop a firm understanding of these concepts in order to complete it.

Problem Description

 

In this MP, we're going to hook into the computer's keyboard and timer interrupts, to run some of the code asynchronously from the main loop. Asynchronously means, in this sense, that you never know ahead of time when it will run. You could have two move opcodes, or a compare and a conditional jump, and either could be interrupted to run the Interrupt Service Routine (ISR). More on this shortly.

Implementation

        

Interrupt Service Routines

Hardware Interrupts are generated when various hardware events occur.  Examples include the timer, which triggers 18.2 times per second, the keyboard, which triggers for every key press and release, the serial port, which depending on the interrupt enable register, triggers each time the receive buffer is full, and the mouse which triggers for any movement or button press or release. These interrupts are the hardware's way of saying "I need attention," so that the rest of the time the CPU can completely ignore such devices, and just execute code.

When the interrupt happens, the CPU is informed.  The current opcode finishes execution, and then the current program is interrupted. This is when your interrupt service routine (ISR) is executed. As it happens between two effectively random lines of your program, the ISR needs to save all registers and the flags. The hardware takes care of part of this (by pushing of the flags); the rest you must take care of by using IRET instead of RET (to pop the flags), and saving your registers like normal.

Text-Mode Video

We will be using memory mapped I/O.  Which means that you will want to move 0B800h into ES and explicitly make it your segment when writing to  the screen. e.g. [es:bx].  Remember that each character in text-mode video has both an attribute and an ascii component.  The size of the screen is 80 by 25 characters.  Consult lecture eleven of this semester for more details on how text-mode video works.  We will also be calling interrupt 10h with ah=2 to set the cursor position in several functions.

The Keyboard ISR

When our keyboard handler is called, we know a key has been pressed or released. Unfortunately, we don't know what key, whether it was pressed or released, and whether the press is a true press or an auto-repeat press. There's no way for us find out the last (except keeping track), but as for which key and press or release, we can (and must) find this out by asking the keyboard for the scancode. See section 10.1.2.2 of the  lab manual for information on retrieving these scancodes. The pages after that describe how scancodes work.

If you look at the scancodes and then at the ascii table, the first thing you'll probably notice is that there's no connection. If you then look down at the keyboard, however, you may notice a general correlation between the scancode and the location of the key, with ESC being scancode 1, and the numbers following an increasing pattern. We will be using two look-up tables to convert the scancodes into their ascii equivalent.  We will also maintain two bits to tell us the state of the shift keys at all times.  One bit will tell us whether the left shift is currently pressed and the other the same information about the right shift.  Of course we have to update the bits accordingly when shift keys are pressed or released.

Serial Communication

There is a simple asynchronous transfer protocol that governs how data is transmitted over the serial port.  Basically there is a port, either COM1BASE or COM2BASE, that is written the data value to be transmitted.  The other port is read by the receiving program after it determines that there is something to be read.  To determine this, it just looks at a certain bit of a register at another port.  I am being deliberately vague to get the main idea of polled I/O across.  Essentially, check a certain bit to determine if there is data available to read; if there is then read it.

Well your job in this MP will be harder because instead of polled I/O you will be using interrupt driven I/O.  But it is a very similar concept. It boils down to setting up an interrupt service routine (ISR) that gets triggered if there is data available to be read; if there is then read it and say that you did.  The hard part is setting up the ISR in the first place.  What this entails is setting the baud rate, basically how fast the information is being sent, the data size, unmasking the ISR, and setting up information about when the ISR is triggered.  Each of these things is done by writing to I/O ports.  Looking in section 10.1.2.2 will show you examples of writing to and reading from ports.

Okay now to get down to the details.  I got the following table from http://www.beyondlogic.org/serial/serial.htm

Base Address DLAB Read/Write Abr. Register Name
+ 0 =0 Write - Transmitter Holding Buffer
=0 Read - Receiver Buffer
=1 Read/Write - Divisor Latch Low Byte
+ 1 =0 Read/Write IER Interrupt Enable Register
=1 Read/Write - Divisor Latch High Byte
+ 2 - Read IIR Interrupt Identification Register
- Write FCR FIFO Control Register
+ 3 - Read/Write LCR Line Control Register
+ 4 - Read/Write MCR Modem Control Register
+ 5 - Read LSR Line Status Register
+ 6 - Read MSR Modem Status Register
+ 7 - Read/Write - Scratch Register
Table 5 : Table of Registers

All the registers associated with a certain port are just offsets from that port's base address.  For the two ports we will be using the base addresses are given as constants, namely COM1BASE and COM2BASE.  There are actually twelve logical registers associated with each serial port, but only eight actual registers.  This is possible because depending on the value of DLAB, some registers, base address+0 and +1, mean two different things.  Also a register may have a different meaning depending on whether the operation is a read or write.  Incidentally, the page I got this table from is a pretty decent resource.

DLAB is just the most significant bit of the Line Control Register and can thus be set or cleared rather easily simply by writing to port COMxBASE+3.  The least significant bit of the Interrupt Enable Register when set causes the ISR to be jumped to only when the Receiver Buffer is full. 

Speed (BPS) Divisor (Dec) Divisor Latch High Byte Divisor Latch Low Byte
50 2304 09h 00h
300 384 01h 80h
600 192 00h C0h
2400 48 00h 30h
4800 24 00h 18h
9600 12 00h 0Ch
19200 6 00h 06h
38400 3 00h 03h
57600 2 00h 02h
115200 1 00h 01h
Table 6 : Table of Commonly Used Baudrate Divisors

I got this table from the same website. We will want a baud rate of 9600 BPS, so we need to clear the Divisor Latch High Byte and set the Divisor Latch Low Byte to 0Ch.  Luckily, the Divisor Latch High Byte defaults to 00h, so we only need to set the Low Byte.  Also the IRQs for both ports start masked.  What this means is that those IRQs are essentially out of use.  To unmask them just write a 0 in port 21h.  This feels kind of sloppy but it does the job.   

FIFO

A queue is a very simple data structure.  It can be modeled by an array, an offset into it representing the oldest element, and a variable representing the number of elements currently in the array.  Of course the elements in the array are cyclically ordered.  The rest of the details I am sure you can figure out on your own. 

The Lookup Tables

Lookup tables are an efficient way to translate one set of inputs to another set of outputs as seen with currency conversions in MP2.  In this MP QwertyNames and QwertyShift will be used to translate a scancode into its appropriate ASCII, while colorTable will be used to translate the function keys to the appropriate color.  The outputs for the ascii values of the function keys from the lookup tables have been arbitrarily set to be 200 through 209.  In this MP we will be using the following two lookup tables:

  • QwertyNames
    • This table is designed to facilitate converting scan codes into their equivalent ascii characters.
  • QwertyShift
    • This table is designed to facilitate converting scan codes into their equivalent ascii shift characters.
  • colorTable
    • Each entry in colorTable is a byte representing an attribute in text-mode video.
     

Procedures

  • This assignment has 13 procedures. You will receive credit for this assignment by replacing each of the thirteen procedures listed below with your own code.
  • Experiment with the working code to gain a full understanding of how the program works.
  • Use defined constants/variables where appropriate.
  • Comment ALL the functions and make sure that they preserve all register values.  Be sure to include function headers for ALL functions that you write.

 

  • MP3Main
    • Purpose : Setup the ISRs and handle each key press.
    • Inputs : None
    • Outputs : None
    • Calls : InstallKeyboard, InstallPort, DrawBorder, GetNextKey, TypeKey, RemovePort, RemoveKeyboard, Interrupt 10h ah=2
    • Notes :
      • Sets up position of cursor to be the upper left corner of the top text box by using int 10h.  
      • In main loop, wait for a keypress, then handle the case when the key pressed was one of the function keys; lastly, if  it wasn't a function key pressed, type the key to the screen
      • You will want to install and uninstall your interrupt service routines as well.
    • Points : 10
  • DrawBorder
    • Purpose : Draw the rectangular box that represent the two text windows
    • Inputs : cx = offset of upper left corner of location to start displaying the text box
    • Outputs : None
    • Calls : None
    • Notes :  
      • Use ATTR_BORDER as the attribute, and consult an ascii table to determine the ascii values of the characters in the border.
    • Points : 6

 

  • InstallPort
    • Purpose : Sets up the baud rate, data size, saves old ISR address, installs new ISR, unmasks all IRQs, and sets up the trigger for the interrupt
    • Inputs : None
    • Outputs : None 
    • Calls : Interrupt 21h
    • Notes :
      • Use IRQ3 or 4 depending on the port
      • [recvPort] = the base address of the correct communication port to use
      • Remember to set DLAB in order to access the divisor latch register
      • Remember to set the data transfer size to 8 bits, by writing PARAMS to LCR
      • Save and set ISRs to [PortV] as you did in InstallKeyboard
      • In order to unmask all IRQs, write a 0 to port 21h
      • Set the interrupt enable register appropriately so that an interrupt is triggered when the receive register is full
    • Points : 4

 

  • RemovePort
    • Purpose : To restore the old handler and to mask the IRQs corresponding to the serial port.
    • Inputs : None
    • Outputs : None
    • Calls : Interrupt 21h
    • Points : 3

 

  • PortISR
    • Purpose : To add to the FIFO any data received over the serial port
    • Inputs : None
    • Outputs : None
    • Calls : None
    • Notes :
      • Examine [recvPort] to figure out which port address to read from
      • Before accessing the [recvPort], it might be smart to clear DLAB and set transfer to 8bits, although this may not be necessary
      • Use [bufsize] and [bufhead] to determine if [recvBuf] is full; if it isn't, then use them to determine the proper spot to add the next entry---basically you are simulating an queue
      • Remember to update [bufsize] if necessary
      • See Lecture 14 for more information on writing and installing ISRs
      • Don't forget to send a generic end of interrupt by writing 20h to port 20h, just like in the KeyboardISR
    • Points : 5

 

  • InstallKeyboard, RemoveKeyboard

 

  • KeyboardISR
    • Purpose : Set [nextKey] to be the proper ASCII representation of the character typed.
    • Inputs : None
    • Outputs : None
    • Calls : None
    • Notes :
      • See the lab manual for a skeleton ISR
      • Set [quit] to 1 if escape is pressed
      • Remember to check for key presses and releases (highest bit of scancode set => key release)
      • Upon a key press access the QwertyNames or QwertyShift look-up table to determine the correct ASCII representation of the input
      • The least significant bit of the [shift] variable is set if RSHIFT is currently pressed and zero otherwise; similarly with the second least significant bit of [shift] for LSHIFT
      • If input is shift, then update the shift state correctly by modifying [shift]
      • Upon a key release, if the key released was a shift then adjust the shift state accordingly
      • Otherwise set [nextKey] to the correct ASCII representation of the typed key 
    • Points : 10

 

  • GetNextKey
    • Purpose : Polls two buffers to get the next key to be displayed onto the screen.
    • Inputs : None
    • Outputs : al = ascii of next key, dx = TOP_OFF if user typed, else BOTTOM_OFF
    • Calls : TransmitKey
    • Notes : 
      • Exits if [quit] is nonzero; otherwise it loops until [nextKey] is nonzero, or the [recvBuf] FIFO is non-empty.
      • If [nextKey] is nonzero, then it transmits it to the other user and clears [nextKey]
      • It also sets dx appropriately depending on where the input came from..
    • Points : 10

 

  • TransmitKey
    • Purpose : Transmits the data in al onto the serial port
    • Inputs : al = ASCII char to transmit
    • Outputs : None
    • Calls : None
    • Notes : 
      • Use [recvPort] as always
      • This function is very short.
    • Points : 2

 

  • DrawNewLine
    • Purpose : Clears a line of text on the screen
    • Inputs : di = offset of a location in the line directly above where the new line will go, dx = offset of upper left corner of text box
    • Outputs : al = row#, ah = col#, di = offset of next character
    • Calls : None
    • Notes : 
      • Remember the row, and col#'s start at 0
      • Remember not to zero the attribute when clearing a line.
      • Remember di is always the offset from the upper left corner of the screen.
    • Points : 4

 

  • DrawBackspace
    • Purpose : Backspaces and moves the cursor back
    • Inputs : di = offset of character from which to backspace, dx = offset of upper left corner of text box
    • Outputs : di = offset of next character
    • Calls : Interrupt 10h ah=2
    • Notes : 
      • Remember the library doesn't backspace unto the previous line, so you don't have to either.
      • The library doesn't use dx, but if you want to implement backspacing to the previous line you will need dx.
      • Remember di is always the offset from the upper left corner of the screen.
    • Points : 4

 

  • TypeKey
    • Purpose : To display at the proper location the input ascii value
    • Inputs : al = input ascii value, di,si = offsets of current locations in top and bottom text box respectively, dx=offset of upper left corner of the correct text box
    • Outputs : di,si = offset of next character's in the appropriate text box
    • Calls : DrawBackspace, DrawNewLine, Interrupt 10h ah=2
    • Notes : 
      • Check whether input is backspace or if input is enter, or if current location is at the end of a line; in each case call the proper function with the correct inputs
      • Notice DrawBackspace takes care of the cursor for you while DrawNewLine does not
      • Otherwise just output the character and cursor as normal
      • Remember di is always the offset from the upper left corner of the screen.
    • Points : 8

Monitor the newsgroup and this on-line section for revisions to the MP or to the write-up

General Notes

  • The serial communication cable that you will need may not already be hooked-up to a free computer in the lab.  If this happens just talk to a TA.
  • Remember to save and restore all modified registers that are not outputs.
  • Remember to start early! This MP has more difficult concepts, but when you understand them, it is easier to code then the last one.
  • Monitor the newsgroup!  There may be corrections made on this write-up or on the newsgroup.

Procedure

  • You will begin this MP with the following files:
    • MP3.ASM: Program Framework
    • Makefile: Specifies how and when programs are assembled and linked.
    • LIBMP3.LIB: Library functions for MP3
    • LIB291.LIB: General-purpose library functions
  • You may copy these files from the network drive to your home directory with the following command:
    xcopy /s V:\ece291\mp3 W:\mp3
    or download the files from this server as mp3.zip

  • Add your code to MP3.ASM.
  • Assemble and link your program by typing
    make
    This command reads Makefile then invokes NASM and LINK to build an executable program.
  • Use Turbo Debugger (TD) to find and correct program errors.
  • Verify your program operation by testing the input test file. You should alter the test file to check different scenarios with your code.

Final Steps

1. Demonstrate your MP3.EXE to a TA. You may be asked to recompile and demo the program. Your program must work with all given input.

2. Be prepared to answer questions about any aspect of the operation of your program. The TAs will not accept an MP if you cannot fully explain your code and your implementation. Delayed MPs will be subject to late penalties as described in the course syllabus (10/pts per day).

3. The TA will complete the code submission procedure.

 


mp3.asm

Return to ECE291 Home Page Fall 2001