Debug driver

Debug port communication

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
MPG1
MPG2
USB RS-232 Converter Master User Code
Debug Basic
AP2 Emulator IAR
TeraTerm

Prerequisite Modules

API Description

Getting data in and out of an embedded system is important. Sending data out to a computer is very useful for status, debugging and datalogging; taking information in is a great way to attach a much more powerful interface to the embedded system (e.g. monitor and keyboard) and can be used to control the system or send configuration data.

The RS-232 serial port was once very common on PCs and lives on through USB-to-serial adapters like the one used in this course. The interface is essentially the “standard out” of the embedded world so we can create a “printf” style output function. printf is actually an extremely complicated function to implement and since we are limiting our code footprint, the API offers very simple text and numeric output functions.

Reading data in takes some work to properly receive, parse and buffer each character that comes in. The Debug driver is responsible for reading data. It monitors the input stream and will respond to certain input strings. A menu of available services will be displayed by typing “en+c00” from a terminal window. Any other text that is entered and terminated with a carriage return will produce a message “Invalid command” but that does not mean the input is lost. The Debug driver stores characters in a global buffer that can be read with a scanf style input function so other tasks can access the input.

A terminal application like Tera Term is required to interface to the development board. The Debug serial interface uses 115,200bps, 8 data bits, 1 stop bit, no parity, no flow control. The serial port settings of the PC terminal application you use must match these values for the two systems to communicate.


SKILL CHECK
Try changing the baud rate in TeraTerm to 9600bps and then watch what happens when the EiE development board boots. Explain why you see what you see.


Type Definitions
There are no type definitions unique to this API.

Globals
The following Globals may be imported into tasks:

  • G_au8DebugScanfBuffer[] is the DebugScanf() input buffer that can be read directly
  • G_u8DebugScanfCharCount holds number of characters in Debug_au8ScanfBuffer
  • DEBUG_SCANF_BUFFER_SIZE is the size of G_au8DebugScanfBuffer and thus the max of G_u8DebugScanfCharCount

To access the buffer or character counter in your own task, you must tell the compiler that they exist in another file.

/* Existing variables (defined in other files)      */
extern u8 G_au8DebugScanfBuffer[];  /* From debug.c */
extern u8 G_u8DebugScanfCharCount;  /* From debug.c */

Public Functions
The following functions may be used by any application in the system:

  • u32 DebugPrintf(u8* u8String_) – Queues the string pointed to by u8String_ to the Debug port. The string must be null-terminated. It may also contain control characters like newline (\n) and linefeed (\f).
  • void DebugPrintNumber(u32 u32Number_) – Formats a long into an ASCII string and queues to print. Leading zeros are not printed.
  • void DebugLineFeed(void) – Queues a sequence to the debug UART.
  • u8 DebugScanf(u8* au8Buffer_) – Copies the current input buffer to au8Buffer_ and returns the number of new characters. When DebugScanf is called, the G_au8DebugScanfBuffer buffer is cleared and G_u8DebugScanfCharCount is 0.
  • void DebugSetPassthrough(void) – Disables the part of the Debug task that processes character input (e.g. for detecting menu commands which is disabled in Passthrough mode). All characters received — including control characters — are placed in the DebugScanf buffer.
  • void DebugClearPassthrough(void) – turns off Passthrough mode. The Debug task will process input characters and Debug menu is available.

SKILL CHECK
Connect an oscilloscope to the incoming data line on the debug UART – you will need to look at the schematic to determine which pin this is. Set the scope to trigger when a character is typed in TeraTerm and comes in to the debug port. Have someone type a key on the keyboard and decode the scope trace to work out the bits in the byte and figure out what character was typed. You can use asciitable.com to help you.


Examples

Code the following in UserAppInitialize() and step through it to see the results. Try to predict what you will see with each line.

u8 au8String[] = "A string to print that returns cursor to start of next line.\n\r";
u8 au8String2[] = "Here's a number: ";
u8 au8String3[] = " < The 'cursor' was here after the number.";
u32 u32Number = 1234567;

DebugPrintf(au8String);
DebugPrintf(au8String2);
DebugPrintNumber(u32Number);
DebugPrintf(au8String3);
DebugLineFeed();
DebugPrintf(au8String3);
DebugLineFeed();

Run the program and observe the terminal output:

A string to print that returns cursor to start of next line.
Here's a number: 1234567 < The 'cursor' was here after the number.
The cursor was here.

SKILL CHECK
What are the ASCII "control characters?"

What are the hex values and character equivalents (e.g. '\0' for NULL) for the following control characters:
Carriage return:
Line feed:
NULL:
Backspace:

What does the EiE firmware system use for end-of-lines?
Find the setting in TeraTerm to make sure it is using the correct termination.


Build the code and let the program run through to main. Once the main loop is running, enter "en+c00" in the terminal window to display the menu. Activate the LED test by typing "en+c01". Then try typing the letters GRBB and see what happens as you type. Press enter to clear the entry then type "en+c01" again to toggle the LED test off.

LedTest

Try "en+c02" which activates the warning when a 1ms violation occurs. For the ASCII LCD development board, no warning messages should be printed.


SKILL CHECK
Add code in UserAppSM_Idle that will run every 1 second. The code that runs should be a FOR loop that takes at least 5ms to run. And empty FOR loop should take 4 or 5 instruction cycles. Name two ways to check this.

Build and run the code with the 1ms violation warning active. Make sure you see the warning messages print every second.

Add code in UserAppInitialize to set an LED to LED_8HZ. Increase your FOR loop time to 100ms. Build and run the code and make sure you see how this loop impacts the LedBlink function.


***DOT MATRIX BOARD ONLY***
If you have a dot matrix LCD development board, add this line into UserAppInitialize():

  CapTouchOn();

Build and run the code. Try "en+c03" and then slide your fingers on the two captouch sliders to see the values change. Turn off the test by typing "en+c03" again -- it is confusing because the text you type gets printed within the captouch output messages, but that is just the way it is (think of what would be required to change this behavior).

Try "en+c02" which activates the warning when a 1ms violation occurs. For the dot matrix LCD boards, warning messages will continuously be printed. This is due to the captouch functionality that is active which unfortunately violates the system timing every time it takes a measurement. This is a proprietary code library from Atmel that we do not have control over. You just have to manage it.

CapTouchTest

Exercise

To demonstrate all of the features of the Debug API, we will code a program that can read input values from the terminal. The following code should be written in UserAppSM_Idle().

First, make BUTTON0 display the current number of characters in the scanf buffer (i.e. the value of G_u8DebugScanfCharCount). To access the Global variable from Debug.c, expose it at the top of user_app.c

/*--------------------------------------------------*/
/* Existing variables (defined in other files)      */
extern u8 G_au8DebugScanfBuffer[];  /* From debug.c */
extern u8 G_u8DebugScanfCharCount;  /* From debug.c */

Set up some strings to be used to make the messages look good. Getting spaces, carriage returns and line feeds in the correct place tends to be the hardest part. Note there is a finite number of messages that can be queued to DebugPrintf(), so your application must ensure that you allow some time for buffered messages to get processed by the Debug task.

  static u8 u8NumCharsMessage[] = "\n\rCharacters in buffer: ";
  
  /* Print message with number of characters in scanf buffer */
  if(WasButtonPressed(BUTTON0))
  {
    ButtonAcknowledge(BUTTON0);
    
    DebugPrintf(u8NumCharsMessage);
    DebugPrintNumber(G_u8DebugScanfCharCount);
    DebugLineFeed();
  }

Build and test the code to make sure it works as expected. Pressing BUTTON0 when the code finishes booting should show "0". Type some characters and press BUTTON0 again. You do not have to press Enter.

BUTTON0


SKILL CHECK
Type some characters and halt the code. Find G_au8DebugScanfBuffer and verify that the characters you typed are in the buffer. Try typing some control characters like Backspace and Enter. Verify them in the buffer. How do you type other control characters?


Now add BUTTON1 functionality that will read the to a buffer in user_app and print the contents. Make this buffer global to user_app.c and initialize it in UserAppInitialize(). The constant USER_INPUT_BUFFER_SIZE is in user_app.h and is specifically defined as the Debug scanf buffer size +1 to allow us to add a '\0' at the end for use with DebugPrintf().

user_app.h

/****************************************************************
Constants / Definitions
*****************************************************************/
/* Size of buffer for scanf messages */
#define U16_USER_INPUT_BUFFER_SIZE  (u16)(DEBUG_SCANF_BUFFER_SIZE + 1)    

user_app.c

/****************************************************************
Global variable definitions with scope limited to this application.
Variable names shall start with "UserApp_" and be static.
*****************************************************************/
/* Char buffer: */
static u8 UserApp_au8UserInputBuffer[U16_USER_INPUT_BUFFER_SIZE  ];  
...
void UserAppInitialize(void)
{
  for(u16 i = 0; i < U16_USER_INPUT_BUFFER_SIZE  ; i++)
  {
    UserApp_au8UserInputBuffer[i] = 0;
  }
  ...
} /* end UserAppInitialize() */

The code to show the buffer is easy, right? Just add another string to show when the buffer contents are printed, then use u8CharCount to capture the number of characters returned from the call to DebugScanf(UserApp_au8UserInputBuffer).

  static u8 au8NumCharsMessage[] = "\n\rCharacters in buffer: ";
  static u8 au8BufferMessage[]   = "\n\rBuffer contents:\n\r";
  u8 u8CharCount;
   
  if(WasButtonPressed(BUTTON1))
  {
    ButtonAcknowledge(BUTTON1);
    
    /* Read the buffer and print the contents */
    u8CharCount = DebugScanf(UserApp_au8UserInputBuffer);
    au8UserInputBuffer[u8CharCount] = '\0';
    DebugPrintf(au8BufferMessage);
    DebugPrintf(UserApp_au8UserInputBuffer);
  }
  ...
} /* end UserAppSM_Idle() */

Build and run the code to test it. What did we forget? Try pressing BUTTON1 when there is nothing in the buffer. Your red LED should turn on and the board should appear to freeze. Halt the code and see where you are. Welcome to your first(?) Hard Fault.

HardFault

Hard faults occur usually when the processor commits a bad operation like divide by 0 or references a NULL pointer. It is the highest level hardware interrupt and the processor immediately goes to the Hard fault handler where the firmware system traps the code and turns on an LED. A production device would try to recover, but in most cases a system reset is the only option. In most cases this can be initiated by the processor and you can flag that the reset occurred so when the system boots back up it can log it or report it. Hard faults should never happen, but if they do, recovering gracefully is the best you can do. The root cause of Hard Faults should always be found before code is released.

When BUTTON1 is pressed with an empty buffer, DebugPrintf() ends up dereferencing a NULL pointer. Fix this by adding a check for an empty buffer that prints "EMPTY!" instead.

    /* Make sure there's at least one character in there! */
    if(u8CharCount > 0)
    {
      DebugPrintf(au8UserInputBuffer);
      DebugLineFeed();
    }
    else
    {
      DebugPrintf(au8EmptyMessage);
    }

That should give you plenty of capability to interface to the terminal and bring keyboard input and terminal output into your designs. Connecting a resource-limited embedded system to a PC offers great flexibility in working with your devices.


REVISION HISTORY
2018-MAR-21: Fix missing semi-colons in example message strings
2017-MAY-06: Correct types for debug buffer size from u8 to u16; fix some formatting. Update API to add Passthrough mode.
2017-MAR-08: Add skill checks.
2016-MAR-08: First release.