Project 5.1 UARTs

Communicate with the Blackboard using a PC's COM port

1750

Introduction

This Project introduces the concept of Universal Asynchronus Recieve/Transmit (UART) Devices. UART is a communication standard that is very widely used in a range of applications. For this lab, we will use UART to transfer data between between your computer and the blackboard. It can also be used to talk between embedded devices (not necessarily to a PC). Using a terminal on the PC-side, you can create text interfaces to your programs where you can send and receive data from the blackboard.

Background

UART on blackboard

Most embedded controllers have at least one (but often many UART controllers). There are two UART controllers found in the Zynq system: Each allow the processor to receive and transmit data with connected devices. UART1 is connected to the FTDI controller on the blackboard, which converts the serial data to USB. This allows serial data to be read/written to/from your PC via the same USB connection that programs the Blackboard.

Terminals

Terminal emulators are a class of programs that can connect to devices and display input received and send output from the user. Xilinx’s SDK has a built in terminal emulator that can connect to serial ports. Other terminals can be used as well (like screen on linux), but only configuration for the XSDK terminal is covered.

Requirements

1. Send a character from the blackboard to a PC

Write a function that configures the Zynq’s UART 1 module for 115200 baud, 8 data bits, one stop bit, and no parity. After configuring the UART module, send a character to your PC by writing an ascii value of a character to the UART data FIFO. After sending the character, enter an endless loop. When you run the program the character sent should appear on the SDK terminal.

For sending the character, write a function that takes in a byte as a parameter. Make sure you check the transmit FIFO’s status before placing the data in the FIFO, wait for it to be ‘not full’ before writing to the UART FIFO.

Here is an example program structure:

main:
	bl configure_uart1
	
	mov r0,#70	@copy ascii 'F' to r0
	bl uart1_send_char	@send the data in r0 over uart

	b .	@endless loop

2. Receive a character over UART.

Create an assembly subroutine that receives a character from UART. In the function Have it wait until there is at least one entry in the receive fifo. When there is data in the FIFO, read the value from it and return it to the calling function.

Use your new function to write a program that waits for a character from UART and then based on the character received change the state of LEDs. If the program receives an ‘a’, then toggle LED 0’s state. Have ‘b’ toggle LED 1, ‘c’ toggle LED 2, and ‘d’ toggle LED 3. When an LED’s state is changed, send the number of the LED toggled to the terminal.

3. Use null-terminated strings to send a sequence of characters

Write a program capable of transmitting null-terminated strings (also known as c strings).

Before you write your program you should create a string that your program can transmit. First, find the ascii values for the letters in your name. Then create a data section in your program’s main source file. Within this section add the ascii values as a sequence of bytes, place an additional byte valued zero at the end. Make sure you add a label for your string giving you a symbolic constant to the first address. Check the background documentation if you need help creating and populating a data section to your program.

Now write your function for sending the string. The function should take the first address of the string as a parameter and send the characters in the string over uart, byte-by-byte, until a byte reads as zero. Make sure you use byte-wise access instructions (ldrb/strb) and increment your addresses by 1 (byte alignment) rather than 4 (word alignment).

Use your function in a program that sends the string once and then enters an endless loop.

4. Receive strings over UART

Make a subroutine that receives a line over uart and saves it as a null-terminated string in memory. The routine should take in the address of where to save the string, as well as the maximum length the string can be. Have the function read UART characters until it receives a newline or exceeds the maximum length. Ensure the program terminates the string with a null (zero value) byte, even if the maximum length for the string has been reached.

In your main source file, create a data section with 128 bytes set aside for holding your string with the .comm directive. For an example of how to use .comm check the background documents.

Use your new function to read in line of input and send it back to the terminal. Use both the function from this requirement and the previous requirement to send and receive strings. When your program runs, characters should not appear on the terminal until you press enter, then any characters you typed prior to pressing enter will appear.

5. Use the UART module with the C programming language

This is the first requirement that makes use of the C programming language. Before continuing you should read the Embedded C Primer. This document goes over how to deal with Memory-mapped I/O in C as well as other techniques that might be useful.

Create C equivalent functions for the previous requirements. You need to write C functions that can do the following

  • configure the UART module
  • Send and receive single characters
  • Send a c string
  • Receive a line over uart

Make sure you use a seperate header and source file for your UART functions. In general it’s a good idea to create a header/source pair for each perpipheral. This creates a well defined interface to those peripherals, making for organized and easily reusable code.

Example prototypes for functions are given below.

//configures UART1 for 115200 baud, 8-data bits, no parity, 1 stop bit.
void configure_uart1();

//returns next received character from uart.
//Blocks until character availible.
char uart1_getchar();

//Sends 'data' over uart.
//Blocks until data is placed in transmit FIFO.
void uart1_sendchar(char data);

//sends the null-terminated string in 'buffer' through uart1
void uart1_sendstr(char[] buffer);

//***
* Receives characters from uart and copies into 'buffer'.
* Returns when a newline is encountered or the max
* number of characters, 'n', have been received. 
* Newline is not copied and string is always 
* terminated with a null character.
* Function returns the number of characters copied
***/
int uart1_getln(char[] buffer, int n);

After you’ve written the functions test them by making a program with the same behaviour as in requirement 4.

Challenges

1. Write a function that can send integer values via the uart

Write a function that takes an decimal integer as an argument and sends the number through the uart (send the characters representing the number, not the ascii value). You can also make a version that sends hex numbers. Converting integers to a string of hex characters might be easier. Why? How do computers represent integers?

2. Read XADC voltage and display the result on the terminal

Configure the GTC to intterrupt every 1/2 second (500ms). When the interrupt occurs read data from the XADC channel corresponding to the potentiometer on the blackboard. Convert the Data so it appears as a value between 0 and 1 volts, with 4 digits of precision; 0.000 to 1.000. To do this you will need to know how the data is represented in the XADC regsiters.

When you convert the XADC read value to volts you will end up with a floating point number. If you’ve written functions in challenge 1, you cannot just pass it a floating point number as they are represented via their own format. Instead, you can convert the floating point number to an integer before sending it to the PC. This of course results in a loss of precision. Make sure you put the decimal point in the proper place and include a unit!.

3. Use interrupts from the UART module

Configure UART1 to generate interrupts when it receives data.

Write a program that counts upwards every second and sends the count value to the PC. Have the count reset when it receives any input from the PC and have your program send an acknoledgement of some kind to the terminal. Use interrupts to accomplish this. Polling the receiver fifo will not count.