CPR E 381x/382x - Lab12a

Serial Communications

 

1. Objectives

This lab will reinforce basics of assembly language programming as well as provide the opportunity to learn new features and skills.  You will continue to see how individual instructions work, and you will begin to see how certain arrangements of instructions perform “bigger” operations, such as C statements. You should start to understand how control and data structures in C are implemented in assembly language. You will also use assembly language to program the serial I/O port. This relates to work you have done previously in the lab using the serial I/O functions with the QTerm and HyperTerminal. This also gives you a more in-depth look at the serial I/O interface and I/O programming.  You will actually set up a stack frame for an assembly code function, thus seeing the mechanics of the concept of a runtime stack that was introduced earlier.

 

1.1 Reference Files for Lab

       Lab Evaluation Form

 

2. Prelab

As usual, reading through the sections of this lab and beginning to design your code before lab time would be useful.

 

3. Setup

As you did in previous labs, make sure you create the folder in your home directory U:\CPRE381\Labw12a to save all your work from this lab.

 

 

4. 7-segment Display

Consider this skeleton (Lab12a.asm) of assembly code. You will insert pre-written code into this skeleton and add new code.

Pre-written Code

Some assembly code has been written for you as pre-written code (decoder_asm.txt). This code translates a hex digit –  0x0 - 0xF, represented as a 4-bit nibble 0000 -1111 –  from a DIP switch to a 7-segment display.  A 7-segment display has the following configuration:

The segments a-g are lit to display a particular hex digit. For example, to display the digit "1", segments b and c are lit. Thus the segments abcdefg are assigned the binary values 0110000, where 1 turns on a segment, and 0 turns off the segment. A 7-segment display driver uses signals a-g to drive the display. Any hex digit is displayed by providing a specific bit pattern for a-g. Another special signal, called dp, for decimal point, turns on/off the dot included on the display.

On the PowerBox, notice the 7-segment display located to the right of the DIP switches. There is a dedicated 8-bit input connector for the 7-segment display, for bits a-g and dp.  To drive this 7-segment display, you will connect a ribbon cable from one of the digital output connectors (accessed in software using IO_DIGITAL_OUTPUT_1) to the 7-segment input connector.  For the 7-segment display, a digital 1 lights the segment, and a digital 0 blanks the segment. The ordering of the bits, MSb to LSb, is a-g followed by dp (i.e., a is the MSb, dp is the LSb). (Your lab instructor will provide the ribbon cable during lab.  Wires also work.  If you are testing your code without a cable or wires, you can simply output the a-g,dp bits to an LED bargraph.

Read through the pre-written code and notice the following behavior:

·         Read in the hex digit to be displayed (a hex digit is represented in 4 bits)

o        If bit 7 of DIP Switch 1 is set, use the upper nibble of DIP Switch 2 as the hex digit to be displayed

o        If bit 7 of DIP Switch 1 is not set, use the lower nibble of DIP Switch 2 as the hex digit to be displayed

·         Convert the hex digit to its 7-segment encoding (abcdefg, dp)

·         Output the 7-segment code to drive the display

o        Use Digital Out 1, i.e., IO_DIGITAL_OUTPUT_1, to connect to the 7-segment display input connector

o        You will need a special cable to do this part. The cable will be available in lab only. You can test your functionality when working outside of lab by sending your output to, for example, an LED Bargraph. Note that you will still be expected to use the digital output once you get to lab, but this should be a quick change to your code.

Coding Techniques of Interest

1.      The value in the nibble is used as an offset from SegTable.  The 32-bit address of the correct 7-segment code in the lookup table is calculated before reading from the table. This is done by initializing a register with the base address of the table, which must be done 16 bits at a time, and then adding the offset.

2.      The shift operation uses the following syntax (shift right word immediate):

srwi r2, r2, 4 ; r2 ¬ r2 >> 4

3.      To test if a bit is set (such as in Switch 1), consider how the following C code translates:

if (byVal & 0x40)

    byVal = byVal + 10;
}
else
{
   byVal = byVal - 3;
}

; Suppose r15 contains the value of byVal

    andi. r16, r15, $0040    ; r16 = r15 & 0x0040
    beq  IfFalse1            ; Branch if the last 
                             ; result was zero (i.e. false)

IfTrue1:
    addi r15, r15, 10        ; r15 = r15 + 10
    b     EndIf1             ; Skip ahead always
                             ; to EndIf1 because we don't want to
                             ; execute the else clause as well
IfFalse1:
    addi r15, r15, -3        ; r15 = r15 + (-3)
                             ; Branch to EndIf1 is not 
                             ; required since it is the next line
EndIf1:
    ; Other code to follow

 

1.      Insert the pre-written code as needed into the skeleton.

New Code

 

On the PowerBox, recall there is another 7-segment display located above the LED bargraphs. To drive this 7-segment display, you need only write the 4-bit hex digit to IO_DIGITAL_OUTPUT_7SEG. The decoding to the a-g segments is built into the hardware interface.

 

2.      Add the necessary assembly code to your current program to output the 4-bit hex digit directly (before decoding into segments) to the IO_DIGITAL_OUTPUT_7SEG port.

 

 

Note that you will use a default main C program to call the assembly code as in Lab 6.

Compile, debug, and test the pre-written and new code. Demonstrate the program to the TA.

 

5. Serial I/O on the MPC555

The PowerPC/MPC 555 has two built-in serial ports on the microcontroller, COM1 and COM2.  If you recall, a microcontroller has a microprocessor plus several other components on-chip.  In this case, you will be working with the Serial I/O subsystem of the MPC 555.  COM1 is set up to connect to the desktop PC; and COM2, to the QTerm terminal (keypad and LCD display).

The actual setup/initialization for the serial ports will still be done using the same methods as in previous labs. 

à You will need to call LCD_Init and PC_Init in your C main program.

These two functions initialize the serial ports to 9600 baud (bps) and clear out any outstanding communications that are pending.  See the Lab 1 code if you are unsure how to include and call these functions.

You can also manually check and configure the QTerm serial communications. It may be necessary if the QTerm does not work properly.

For the actual data transfer with the serial port, you will no longer be using LCD_PutString.  Now, you will be writing your own serial driver at the assembly level.  In order to interact with the serial ports, there are several memory addresses that you should be familiar with. 

Serial I/O Addresses

COM1 - Connected to PC

Name

Address

Description

Com1Status

0x0030500C

COM1 Status (16 bits)
  - Bit 8 - Ready to transmit/send/write
  - Bit 6 - Ready to receive/read

Com1Data

0x0030500E

COM1 Data (16 bits)
  - Only use the lower 8 bits

COM2 - Connected to QTerm

Name

Address

Description

Com2Status

0x00305024

COM2 Status (16 bits)
  - Bit 8 - Ready to transmit/send/write
  - Bit 6 - Ready to receive/read

Com2Data

0x00305026

COM2 Data (16 bits)
  - Only use the lower 8 bits

For the serial port, the status and the data are assigned to separate registers in memory, requiring two addresses.  Both the status and data registers are 16 bits each (i.e., you should read/write them 16 bits at a time, i.e., like a short, as a halfword).  

TO REITERATE: These are all 16-bit ports. The address of the port is the lowest address for the two-byte contents; i.e., the MSB is at that address, and the LSB is at that address plus one. For example, the COM2 status port is at address 0x00305024.  It consists of two bytes, the MSB at 0x00305024 and the LSB at 0x00305025. You can read/write both bytes at once (as a halfword or short) at the port address. That is, you should read and write a halfword at the port address.

In this lab, we are using “programmed I/O,” that is, testing a status bit in software to determine whether the I/O device is ready, and then when ready, doing the I/O. This is also called polling-based I/O. The alternative method of I/O is called interrupt-driven; we see that later on.

Reading Data

In order to read data, first check to see if there is data waiting at the serial port in the device’s receive buffer.  Bit 6 of the serial port status register will tell you when there is data ready to be read.  If this bit is a 1, there is a new byte of data waiting to be read.  The GetSByte, ReadSerialPort, and SerialReady functions in serial.c give examples in C.

Take a careful look at the GetSByte, ReadSerialPort, and SerialReady functions. For example, here is SerialReady:

/*----------------------------------------------------------------------------
 returns 0 if any new serial data has been received
 INPUT:  portnum  0= SCI1, 1= SCI2
 OUTPUT:  returns 0 when new data is ready, 1 otherwise.
----------------------------------------------------------------------------*/

unsigned char SerialReady(unsigned char portnum){
       short Status;
       ClearWatchdog(); // feed the dog
       if(portnum == 0) Status = Com1Status;
       else Status = Com2Status;
       if( (Status & 0x0040) == 0) return 1;       // no data ready
       return 0; // data ready
}

Don’t worry about ClearWatchdog (we will not implement that). Also, you will use the ports for COM2 only (i.e., you do not need to have a portnum parameter). Notice that Status is a short variable read from one of the two status ports. Bit 6 is then tested to determine if new data has been received and is ready to be read from the data port.

Writing Data

To write data to the serial port, check to see if there is room in the device’s transmit buffer to add another byte to be transmitted.  When a byte is added to the buffer, it is automatically transmitted out onto the serial link.  The ReadyToSend and putchar functions in serial.c gives examples in C.

Explore relevant code in serial.c and serial.h. You will write your own read/receive and write/send functions in assembly language, but the C examples illustrate basic statements used.

Take a careful look at the ReadyToSend and putchar functions.

You may also take a look at the header file diab.h that declares some of the data types used in the serial code.

Serial Interfacing

As you may have noticed, each serial port (COM1 or COM2) has only one data register.  You may ask how you can read and write using the same register.  Keep in mind that you are interfacing with a device.  Since it is a device, the serial port can do whatever it wants with the data and can also act differently with a read than it does with a write.  When you write to the serial port, it places the data in the outgoing transmit buffer.  When you read from the serial port, it reads from its incoming receive buffer.  The only interaction you can have with the serial port is through the data register (i.e., you cannot get at the incoming/outgoing buffers directly).

For more details on the MPC 555’s serial I/O subsystem, browse through part of the manual. Selected sections are highlighted that refer to the programming model (registers and operations) for the subsystem. We are using only a few features of the subsystem. Diagrams of the device interface are shown in the manual.

  

3.      Write an assembly program that reads a key from the QTerm keypad and writes it to the QTerm LCD display.

 

Code Structure

In order to make the code easier to integrate with other code (i.e., to add new tasks), set up the code to read/write only one character at a time over a serial connection per pass through the main loop.  The code should be written so that its functionality can be extended, e.g., by adding other tasks, such as flashing LEDs, reading keys, etc., to the main control loop.  Try to think of your code as something like this:

main ()
{
    while(1)
    {
        JumpAsm();
    }
}

Inside JumpAsm, you might start by breaking the code into sections for reading and writing the QTerm. For now, you can simply use labels to mark the different code sections and control the flow of execution between the sections.  

StartAsm:

Read_QTerm:

     Branch to Read_QTerm until key has been read (conditional branch)
Write_QTerm:

     Branch to Read_QTerm (branch always)


The behavior of the assembly code can be partitioned as follows:

·         Reading QTerm for input from its keypad (COM2)

·         Writing information to the QTerm (COM2)

NOTE: When you press a key on the QTerm keypad, it is not automatically displayed on the QTerm LCD.  You need to write it out via the serial port for it to appear.

Design Components

·         Read_QTerm section of code - Reads up to 1 character from COM2 (QTerm)

·         Write_QTerm section of code - Writes up to 1 character to the QTerm LCD

 

Code Snippets

Several coding examples related to this program are shown below. These are separate fragments. The code is only partially written, so you will need to add new code. You may write your code differently, however, it should have the same basic flow.

    ; r11 = Com2Status address

    ; r11 + 2 = Com2Data address
      ; r12 = status value

Read_QTerm:

    ...

      ; Check Data Ready status of Com2, if bit 6 == 0 NONE, if bit 6 == 1 DATA READY

    lhz    r12, 0(r11)       ; Load into r12 the value from Com2Status

    andi.        r12, r12, $0040  ; Com2Status & 0x0040, check bit 6

    beq    Read_QTerm     ; if == 0 then none ready so branch (loop back)

                                    ; if == 1 then new data ready (so read data next)

 

 

    ; r12 = character to display (in LSB of register)

Write_QTerm:
...
QTerm_Output:

      ...

    ; Write character to COM2 – if a character is one byte, why is a halfword written?

    sth         r12, 2(r11)      ; r12 -> M[r11 + 2]

                            ; store character to Com2Data = Com2Status + 2
      b     Read_QTerm       ; loop back to read another character

 

 

 

 

 

Compile, debug, and test the communications code. Demonstrate the program to the TA. You should be able to answer the following question: what does your program do when no key is pressed on the QTerm?