CS452 - Real-Time Programming - Spring 2017

Lecture 15a - Serial I/O, Debugging

Public Service Announcements

  1. Due date for kernel 4: 12 June, 2017.

Server-Notifier-Kernel Architecture

There are many different ways to spread the state requirements for transmission across the different tasks used when transmitting bytes through the UARTs. The three examples that follow are chosen to show how functionality can be moved. Using these ideas you can realise many different implementations than the sketches shown here.

Everything in the Server

Putting everything in the server is a common way to get started. Here is a sketch of one method for doing so.

AwaitEvent handling in kernel.

	Unblock notifier.
  

Transmit Notifier

	AwaitEvent( XIRQ )	// Returns when transmitter is ready.
	Send( server, , )
  

Transmit Server

	Receive( &requester, msg )
	switch ( msg.type )
		case CLIENT
			fifo-insert( msg.byte )	// fifo-empty = false
			Reply( requester, )
			if ( xrdy )
				write( fifo-extract( ) )
				xrdy = false
			else
				enable( XIRQ )
		case XNOTIFIER
			xrdy = true
			Reply( requester, )
			if ( !fifo-empty( ) )
				write( fifo-extract( ) )
				xrdy = false
			else
				disable( XIRQ )
  
xrdy keeps track of whether the transmitter is ready or not. fifo-empty( ) keeps track of whether or not there is a byte to transmit.

Enabling and Disabling in the Notifier

AwaitEvent handling in kernel.

	Unblock notifier.
  

Transmit Notifier

	Send( server, , reply )
	enable( XIRQ )
	AwaitEvent( XIRQ )
	write( reply.byte )
	disable( XIRQ)
  

Transmit Server

	Receive( client | xnotifier, msg )
	switch ( msg.type )
		case CLIENT
			fifo-insert( msg.byte )
			Reply( client )
			if ( xnot-waiting )
				Reply( xnotifier, fifo-extract( ) )
				xnot-waiting = false
		case XNOTIFIER
			xnot-waiting = true
			if ( !fifo-empty( ) )
				Reply( xnotifier, fifo-extract( ) )
				xnot-waiting = false
  

Minor alterations of this code can put the fifo in the notifier, and/or move more than one byte at a time.

Enabling and Disabling in the Kernel

AwaitEvent handling in the kernel

	//handling AwaitEvent( XIRQ, byte )
	enable XIRQ
	save byte
	//when XIRQ occurs
	write byte on UART
	disable XIRQ
	make AwaitEvent caller ready
  

Transmit Notifier

	Send( server, , byte )
	AwaitEvent( XIRQ, byte )
  

Transmit server: as above

Serial I/O Timing Inconsistencies

CTS timing.

What you expect to occur when sending to the train controller

  1. The train application puts a byte in the hold register. The byte is moved into the shift register and shifted out.
  2. The stop bit is transmitted.
  3. A train xmit interrupt occurs.
  4. The train application checks CTS.
  5. If CTS is set, you put the next byte in the hold register.

Sometimes an implementation based on this expectation surprises you. In reality what happens when you are sending to the train controller the following set of events occur.

  1. The train application puts a byte in the hold register, which is loaded and shifted out.
  2. The stop bit is transmitted.
  3. Two things happen in parallel.
    1. In the train controller,
      • The train controller sees the stop bit.
      • Then, probably on the next clock edge, it latches the character and negates CTS.
      • After one or two clock cycles the byte is clear of the train UART, and CTS is re-asserted.
    2. In the ARM CPU
      • The train xmit interrupt occurs.
      • The train application checks if there another byte to send, and
      • if there is, it checks the CTS bit in the modem status register (CTS-sr). What value does it read?
        • When the stop bit was transmitted CTS-sr was asserted.
        • Then, the train controller negates the CTS state internally, probably on the next edge of its UART clock; the ARM UART latches the negated signal on the edge of its UART clock, and into its CTS-sr on the next edge of the peripheral bus clock. From this point CTS-sr is negated.
        • Finally, when CTS is re-asserted in the train controller UART and has been latched a second time in the ARM UART CTS-rs is re-asserted.
    The train application is assumed to check CTS after the train controller has negated it. It may even be unaware that CTS is negated for a short while. Regardless, because the two sets of events happen in parallel the result is non-deterministic.
  4. If CTS is set, the train application puts the next byte in the hold register.
Whether the final action succeeds, drops a character or crashes the train controller is determined by the whims of the gods.

How do you solve this problem is for you to discover. Hint, interrupts occur in a deterministic order.

Primitives for Serial I/O

We are supposed to support

int Get( int server-tid, int port )

and

int Put( int server-tid, int port, char c )

These are wrappers for sends to one or more serial servers.

How should we handle a terminal?

Issues

  1. Line editing
  2. Echo
    Either Or

Many other issues come up below as we consider possible task structures.

How ahould we handle the train controller

Bandwidth of communication with the train controller will probably be the limiting factor in your trains project.


Debugging Real-time Programs

The most common set of debugging tools used by experienced programmers is the oldest: printf, grep & stack trace.

Debugging real-time programs, at its base, is just the same as any other debugging, just the same as discovery in any empirical science or any kind of problem solving.

  1. Gather data.
  2. Create a model that explains the data
  3. Test the model
  4. If the model is not correct, go to 1.
  5. Remember that the model is ALWAYS provisional: data collected later may invalidate it, no matter how much data has confirmed it.

But real-time programs are harder to debug. Why? There are new types of bugs.

Critical Races

There is no known method for eliminating critical races.

It is, in principle, impossible to test away critical races. Why?

Tools you might use to assist debugging

Redboot

The memory contents are not wiped by reset. Some of the most difficult errors can be detected only by using the contents of memory after a reset. Produce useful results by inserting

      str   pc, <magic location>
    
many places in your code. Redboot can tell you the contents of the magic places after the program crashes. Then, with the assistance of a load map, you can find out where you were in which code when the problem occurred.

You can often find out this information by looking at the garbage output that often appears at the bottom of the screen. One set of eight characters is the hex address of the instruction that could not be executed. With a load map and assembly listings you can find the instruction on which the program died.

In RedBoot you can, in principle, trace any of the kernel data structures by examining the kernel's stack, but it's extremely arduous to do so.

Breakpoint

What does it do?

How do you get it started?

Breakpoint is a special case of a particular sort of tool that is very common.

Getting information closer to real-time.

Stack Trace

In single-threaded programs this is often the most useful tool.

What is the equivalent of a stack trace in a real-time multi-tasking environment?


Return to: