Debug driver

Debug port communication [VIDEO]

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
MPG1
MPG2
USB RS-232 Converter Master User Code
Debug Basic
AP2 Emulator 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.

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

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.

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 u8String[] = "A string to print that returns cursor to start of next line.\n\r"
u8 u8String2[] = "Here's a number: ";
u8 u8String3[] = " The 'cursor' was here."
u32 u32Number = 1234567;

DebugPrintf(u8String);
DebugPrintf(u8String2);
DebugPrintNumber(u32Number);
DebugPrintf(u8String3);
DebugLineFeed();
DebugPrintf(u8String3);
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.
The cursor was here.

If you have a dot matrix LCD development board, add this line into UserAppInitialize():

  CapTouchOn();

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

If you have a dot matrix LCD development board, 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 behaviour).

CapTouchTest

Lastly, try “en+c02” which activates the warning when a 1ms violation occurs. For the ASCII LCD development board, no warning messages should be printed. 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.

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 -- should all contain "extern" */
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

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
**********************************************************************/
#define USER_INPUT_BUFFER_SIZE  (u16)(DEBUG_SCANF_BUFFER_SIZE + 1)    /* Size of buffer for scanf messages */

user_app.c

/***********************************************************************
Global variable definitions with scope limited to this local application.
Variable names shall start with "UserApp_" and be declared as static.
************************************************************************/
static u8 au8UserInputBuffer[USER_INPUT_BUFFER_SIZE];  /* Char buffer */
...

void UserAppInitialize(void)
{
  for(u8 i = 0; i < USER_INPUT_BUFFER_SIZE; i++)
  {
    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(au8UserInputBuffer).

  static u8 u8NumCharsMessage[] = "\n\rCharacters in buffer: ";
  static u8 u8BufferMessage[]   = "\n\rBuffer contents:\n\r";
  u8 u8CharCount;
   
  if(WasButtonPressed(BUTTON1))
  {
    ButtonAcknowledge(BUTTON1);
    
    /* Read the buffer and print the contents */
    u8CharCount = DebugScanf(au8UserInputBuffer);
    au8UserInputBuffer[u8CharCount] = '\0';
    DebugPrintf(u8BufferMessage);
  }
  
  ...
} /* 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(u8EmptyMessage);
    }

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.

[STATUS: RELEASED. LAST UPDATE: 2016-MAR-08]