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
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.
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) |
Com1Data |
0x0030500E |
COM1 Data (16 bits) |
COM2 - Connected to QTerm
Name |
Address |
Description |
Com2Status |
0x00305024 |
COM2 Status (16 bits) |
Com2Data |
0x00305026 |
COM2 Data (16 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 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: ... ; 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 |
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?