Application Details
An Autumn 2001 CSE466 Project by Mike Fernandes and Shirley Gaw

Home I2C Details Application Details Links

Summary of Serial Port Interface

Functionality Source Files
Serial I/O interface
Serial.h
Serial.c

 

We implemented printf and scanf-like functions for serial I/O.  To use, include Serial.c in your project.  Include Serial.h in any files that use our functions.
Call Serial_Init(unsigned char baudRate) before calling any other serial I/O functions.
Printf-like functions:
Serial_SendCharacter(unsigned char character) sends a single character over the serial port.
Serial_SendDecimal(unsigned char character) sends a one byte integer as a decimal string over the serial port. For example, if character = 201, the function would send the ASCII characters '2', '0', and '1'.
Serial_SendNewLine() emulates an "<< endl" terminal line feed by sending line feed and carriage return ASCII values.
Serial_SendString(unsigned char* string, unsigned char delimiter, unsigned char maxSize) sends a character string over the serial port.  This means a series of up to maxSize characters or all characters up to the delimiter character are sent over the serial port.
Scanf-like functions:
Serial_ReceiveCharacter(unsigned char* character) gets a character over the serial port.  This function busy waits for a character to be received and only returns after one has been received.
Serial_ReceiveString(unsigned char* string, unsigned char delimiter, unsigned char maxSize) gets a string over the serial port.  Because it is likely that we need to receive multiple characters, this function simple sets does not busy wait for the entire string to return.  Thus, after calling  Serial_ReceiveString(unsigned char* string, unsigned char delimiter, unsigned char maxSize), applications must test that SERIAL_getReceivingString() to return true before processing the string.

Remember: A return from a call to Serial_ReceiveString(...) does not mean the string has been received!  The string is received only after SERIAL_getReceivingString() returns false.

Summary of Application (Chatting and Shared Memory)

Functionality Source Files
Main Program
main.c
Shared Memory/Chat Application
SharedMemory.h
SharedMemory.c

 

To run the application:
  1. Build the circuit shown here.  This shows only the setup for one node.
  2. Build a project with main.c, SharedMemory.c, I2C.c, and Serial.c.
  3. Make sure to #define ADDRESS_SELF to a lower-case character from 'a' to 'z' in SharedMemory.h.
  4. Set up a terminals for each node you want to watch.
    1. Open Tera Term.
    2. In the  "Tera Term:New connection window" select Serial and the COM port number the 8051 is connected to.
    3. Select the Setup Menu.
    4. Select Serial Port.
    5. Set the baud rate to 9600 if it is not already set this way.
Messages are not queued, meaning a node that receives a lot of traffic may lose messages.
The application does not acknowledge messages and does not guarantee to service all received messages.
The following commands are supported: 
Command Parameters
<address> address of processor ('a' to 'z')
<memoryID> memory location ('0' to '9')
<string> message to send
<value> value to assign ('0' to '255')

 

Command Format Function
HELP '?' Displays all commands and command formats
PING 'P' Attempts to ask each of the 26 possible nodes if they are alive and display the results.
VIEW 'V' Display all the values of shared memory variables on this node in the terminal window.
CHAT <address> 'c' <string> Send a message that asynchronously displays on another node's terminal window.
READ <address> 'r' <memoryID> Read the value of a shared memory variable on another node.
WRITE <address> 'w' <memoryID> <value> Write a value to a shared variable on another node.
Buffer Dump {Press Enter key only} Display the last message received in the message buffer.
The following messages are passed between nodes:
Message Members
<dest> address of processor ('a' to 'z') message is sent to
<source> message sender's processor address
<message> a chat message to display 
<varID> shared variable identifier.  In this case, an index into a shared variable array 
<value> value to assign ('0' to '255') to a shared memory variable

 

Message Type Format
CHAT

READ
WRITE
RETURNED READ

Application Implementation Details

The application we made implemented a simple node to node chat program with a node to node shared variable system.

The main program and SharedMemory_Input() function

The main program simply calls on SharedMemory_Init() function and then enters an infinite loop that calls SharedMemory_Input(), the heart of our application. 

The SharedMemory_Init() function initializes the serial I/O interface and the I2C bus controller interface.  It prints a welcome menu to the serial I/O device (terminal window in our case) and initializes local and shared variable values.

The SharedMemory_Input() prints a prompt for the user to enter commands and waits for a command.  The prompt appear like "I2C Chat #:\>" where # would actually be the network address of the processor.  

While waiting, it checks if the processor needs to respond to tasks that could not be handled in the interrupt handler for incoming messages.  There are only two possibilities of our implementation.  The first is responding to read requests.  Another node has requested to read the value from one of our node's shared memory variables.  We need to transmit a RETURNED READ message to this node.  The interrupt handler already composed the message, we just have to transmit it (this is explain in our description of the interrupt handler).  The second request the node has to handle while waiting for a command is displaying received chat messages.  Again, the interrupt handler has already buffered the message we need to display (see description of interrupt handler), we just have to print the message across the serial I/O port.

After receiving a command, the command string is processed to determine which service to invoke.  Local operations are simple to execute:

Buffer dump: the program calls the SharedMemory_DisplayReceive() function.  This function prints the contents of the last message, specially formatted based on message type. 

HELP: the program calls SharedMemory_Help(), which serializes a help menu to the terminal window.

ZERO: the program calls SharedMemory_Zero(), which assigns the value of zero to each shared variable owned by the node.

VIEW: the program calls SharedMemory_View(), which serializes the values of each shared variable in a list to the terminal window.

Remote operations involve the I2C bus controller:

PING: the program asks each of the possible 26 nodes if they are alive by sending a read request to a shared variable on that node.  If the node is alive, the read request is responded with a RETURNED READ message.1

 

READ: if the command asks for a shared variable owned by the local node, the program simply calls SharedMemory_Read(unsigned char i) to print the variable's value to the terminal window.  Otherwise it must send a message over the network.  The format of the message is:

The node then does a timed wait for a response to the READ request.  If it times out, it prints an update message to the serial terminal.  Otherwise it prints the value of the shared variable from RETURNED READ message's fifth field:

 

WRITE: if the command asks to write to a shared variable owned by the local node, the program simply calls SharedMemory_Write(unsigned char i, unsigned char value) to assign a value to the variable.  Otherwise the program must send a message over the network.  The format of the message is:

Notice that the node does not wait for a message in return.  In fact, a node that is overwhelmed by write requests may not service the request at all!  Our implementation is best-effort, with no guarantees the requested service is provided.

 

CHAT: the program creates a message with the user's string in the following format:

The program than transmits the message over the network.  Again, there are no guarantees that the requested service is provided.

Each command implementation also performs some rudimentary error checking on the command passed by the user.

Receiving Messages

The I2C bus controller calls SharedMemory_Receive(unsigned char* receiveBuffer, unsigned char size) whenever it received a whole frame from the network.  Since the I2C bus controller calls this SharedMemory function from inside the interrupt handler for the I2C interface and because we do not use a priority system for interrupts, we cannot send messages or do serial I/O in SharedMemory_Receive(...) because these operations both require the use of interrupt handling. This is problem for receiving READ messages (another node expects a response) and both the CHAT messages and RETURNED READ messages (the local node is expected to serialize the message contents to the terminal window).  When the node receives these messages, it buffers the message locally and signals the message has arrived.  The program then expects SharedMemory_Input() to handle the rest of the task, except for RETURNED READ, where the command interpreter is already waiting for the message.

Receiving a write request is then simple because the node simply copies the assigned new value specified in the message to the shared variable.

The biggest issue with our implementation of receiving messages is that it does not scale.  High network traffic could cause one message to overwrite another message.  This is not an issue for receiving WRITE messages, since the node services this request immediately and this service cannot be interrupted.  Likewise, this is not an issue for RETURNED READ.  Since the node is busy waiting for the READ request to be responded by a RETURNED READ message, we know that this node has only sent one read request so we needn't worry about another RETURNED READ message happening before we could handle it.  Since we simply buffer the RETURNED READ message and return to the READ command interpreter, we are assured the RETURNED READ will be serviced before the buffer is rewritten.

The problem lies in CHAT and READ messages, which do no service their requests immediately.  The buffers for CHAT and READ messages can be overwritten by new CHAT and READ messages, respectively.  A possible fix for this would be using dynamic memory to queue the message, but we were running out of memory space; dynamic allocation seemed like it would exacerbate the problem.

Footnotes:

1There is the precondition that no other node is executing a ping at the same time.  When a node is pinging the network, it cannot respond to read requests (responding to a read request is not handled in an interrupt handler).  Since the node that pings the network is doing a timed wait for a response, it will not see that the other pinging node as alive.  Since the program handles the read request later, this could interfere with the pinging node.  Our implementation should have checked the sender of the RETURNED READ matches the node we are probing for.  Otherwise, we get odd displays.