Introduction

Good old printf is one of the first ways you learn to debug an application. This page will show why printf is not the best API to use to debug on embedded devices. Instead we'll explore the following APIs that are also available in the SimpleLink™ SDKs:

  • Display_printf
  • System_printf
  • UART_PRINT
  • UARTprintf

We'll look at all the above APIs along with printf and compare them based on footprint size, stack implications, and calling context. We'll focus on outputting the debug information via UART and the IDE's console.

For the hands-on tasks, we'll move between the different APIs. During this portion, the focus will be for the TI CCS compiler, but the concepts are applicable for IAR and other compilers.

The goals of this lab is to show options that are available. It's up to you to decide which API works best for you.

What's not covered...

This module is focused on printf-like APIs and does not cover log (or trace) capabilities in the SDK.

Prerequisites

Software

  • CCS (version specified in the SimpleLink SDK's Release Notes)
  • Any SimpleLink SDK
  • Any terminal program with the following settings (if you are using Windows, use Device Manager to determine the port)
Baud-rate:  115200
Data bits:       8
Stop bits:       1
Parity:       None
Flow Control: None

Hardware

  • Any supported SimpleLink LaunchPad™ Development Kit

API Descriptions

This section gives an overview of each API and any related nuances.

printf

printf has been part of the 'C' language from the beginning. It is a well-known API that has been used by probably every 'C' programmer. printf works on TI-RTOS, FreeRTOS and no-RTOS applications.

printf is not ideal for embedded development though. Let's see how printf works to see the reason why.

IDE Console Output

By default in CCS (and similarily in IAR), printf output goes to a CIO ('C' Input/Output) buffer. When the CIO buffer is full or a EOL character (i.e. '\n') is written, a breakpoint is hit and CCS reads the contents of the buffer. CCS then resumes the target. As one can guess, this action can have very bad implications for real-time performance.

Non IDE Console Output

printf output can be redirected. For the below comparisons, we've redirected it to a UART via the add_device, freopen and setvbuf APIs.

IAR uses a different mechanism for redirecting printf. Please refer to IAR documentation.

Calling Restrictions

IDE Console

printf is callable from any location (e.g. main, ISR, task, etc) when outputting to the IDE console with one exception. By default, TI-RTOS plugs in a locking mechanism (GateMutex) into the TI Compiler to guarantee thread-safety. The TI compiler's implementation of printf calls this locking mechanism. Therefore, by default, printf cannot be called in a Hwi or Swi in applications that use TI-RTOS and the TI Compiler. If you have asserts enabled in the TI-RTOS kernel, you'll get an assert failure. If you don't have asserts enabled, you might get nondeterministic results.

The lock can be disabled by setting the setting the BIOS.rtsGateType variable in the kernel configuration file (i.e. .cfg file) to BIOS.NoLocking.

UART

When outputting to the UART, the allowable calling context depends on the type of UART API call you are making. For the examples in the labs below, printf can be called from a task on an RTOS-based application or from main (or function called within main's context) in no-RTOS based applications.

Misc.

The TI Compiler offers a couple of different options with printf. We'll compare the "minimal" printf along with the "full" printf.

Additional Information

Display_printf

The Display_printf allows a user to send output to various locations: LCD, IDE console, or UART. We'll focus on the IDE console and UART. Display_printf's format options are very similar to printf. Display_printf is available on both RTOS and no-RTOS (bare-metal) based applications. Many of the SDK examples use Display to output data via the UART to allow isolation from an OS and IDE.

The parameters specified in the Display_open API and Display_config variable dictates what physical mechanism is used.

IDE Console (Host)

Display_printf can be directed to the IDE's console also by using DisplayHost. One of the below labs will show how to move to DisplayHost. The line and column parameters in Display_printf are ignored when using the HOST.

#include <ti/display/Display.h>

Display_Handle display;
display = Display_open(Display_Type_HOST, NULL);
...
Display_printf(display, 0, 0, "Count = %d and it's address is 0x%x\n", count, &count);

UART

There are two types of UART interfaces:

  • DisplayUartAnsi: Feature rich module (e.g. better cursor management) at the expense of footprint.
  • DisplayUartMin: Simple UART management with less features, but smaller.

The default for the majority of the examples is DisplayUartMin. The line and column parameters in Display_printf are ignored when using the UART.

#include <ti/display/Display.h>

Display_Handle display;
display = Display_open(Display_Type_UART, NULL);
...
Display_printf(display, 0, 0, "Count = %d and it's address is 0x%x\n", count, &count);

Calling Restrictions

IDE Console

DisplayHost has no calling restriction (as long as the potential real-time impact incurred by writting to CIO buffer is acceptable).

UART

If you are using an RTOS, DisplayUart can only be called from a task (or pthread). If you are not using an RTOS, DisplayUart can be called from main (or by any function called within main's context).

Additional Information

More details about the Display module can be found in the "SimpleLink MCU SDK User's Guide" and "Drivers Runtime APIs (doxygen)"

System_printf

System_printf is only available if the application is using TI-RTOS. System_printf supports the same format strings as printf and some additional ones.

The System module allows an application to plug in a different System "Proxy". The proxy is responsible for the underlying implementation. The two main Support proxies are

  • SysMin
  • SysCallback

The proxy can be assigned in the kernel configuration file (e.g. .cfg file). Here is an example of the code to assign the SysMin as the Support proxy.

System.SupportProxy = SysMin;

The top-level System_printf is the same no matter what the underlying proxy is. Here's an example of the API.

#include <xdc/runtime/System.h>

System_printf("Count = %d and it's address is 0x%x\n", count, &count);

Let's look at the two proxies in more details.

SysMin (ROV/IDE Console Output)

The SysMin module stores the ASCII data into an internal buffer. The size of the internal buffer is configurable. The data is not flushed to the CIO buffer until instructed to do so (e.g. call System_flush or configured to flush on an application exit). So SysMin does not impact real-time performance like Display_printf (out the IDE console) or printf.

The buffer can be viewed by looking at the ROV → SysMin → OutputBuffer. For example here are the contents of SysMin's internal buffer with the Hello example after the System_printf is called.

The "debug" kernel project uses the SysMin module.

Please refer to the SimpleLink SDK User's Guide for details about the kernel projects.

SysCallback (UART Output)

The SysCallback module allows a user to plug in a putc-like function (putchFxn) that is called within System_printf after the formatting is done. The default putchFxn function does nothing. A common use-case is to plug in a putchFxn function that writes the character to a UART. One of the below labs shows this.

The "release" kernel project uses the SysCallback module.

The functions plugged into SysCallback in the release.cfg file are empty to minimize code footprint. Therefore the output goes no where. Please refer to the SimpleLink SDK User's Guide for details about the kernel projects.

Calling Restrictions

IDE Console

When using SysMin, there are no calling restrictions.

UART

When using SysCallback and using the UART, the calling restrictions are dependent on the underlying code. For the examples in the labs, System_printf can be called from a task (or pthread) on a TI-RTOS based application.

Additional Information

Additional information about System, SysMin and SysCallback can be found in the <SimpleLink_SDK_Install_Dir>\Docs\Documentation_Overview.html and click "TI-RTOS Kernel Runtime APIs and Configuration (cdoc)".

UART_PRINT

UART_PRINT is only available in the SimpleLink CC32xx SDK. It is included for legacy reasons. Only output via the UART is supported with this API. UART_PRINT supports similar format strings that printf supports.

The code is maintained in the uart_term.c and uart_term.h files that are included with some of the SimpleLink CC322x SDK examples.

#include "uart_term.h"

InitTerm();
UART_PRINT("Count = %d and it's address is 0x%x\n", count, &count);

Calling Restrictions

IDE Console

N/A

UART

There are no calling restrictions. The code uses the UART_writePolling API, so the API can be called from any calling context after the board has been initialized.

Additional Information

Please refer to the source code in the uart_term.c and uart_term.h files in the SDK examples that use it.

UARTprintf

UARTprintf is only available for no-RTOS examples in the SimpleLink MSP432E4 SDK. It is included for legacy reasons. Only output via the UART is supported with this API. UARTprintf supports similar format strings that printf supports.

The code is maintained in the uartstdio.c and uartstdio.h files that are included with some of the SimpleLink MSP432E4 SDK examples.

#include "uartstdio.h"

UARTprintf("Count = %d and it's address is 0x%x\n", count, &count);

Calling Restrictions

IDE Console

N/A

UART

This API can be called from any calling context, but has only been tested on no-RTOS examples for the MSP432E4 device.

Additional Information

Please refer to the source code in the uartstdio.c and uartstdio.h files in the SDK examples that use the API.

Footprint Comparisons

The following is a comparison of the different debug APIs when displaying to the IDE console and UART. All the test were done with

  • TI Compiler v17.6.0.STS
  • SimpleLink SDK v1.50.00.xx
  • The CCS Debug Build Configuration was used as the baseline
  • The GPIO Interrupt example was used as the base

The below tables show the additional memory impact of adding one debug call. So for the IDE Console Output (for CC3220SF w/ TI-RTOS), adding the first printf adds 10025 bytes of flash and 936 bytes of RAM. Alternatively, adding one Display_printf instead adds 6679 bytes of flash and 1188 bytes of RAM. Of course the majority of the memory impact is due to bringing in the basic functionality. The overhead for additional debug calls would be relatively minimal.

The test string was one of the following:

printf("This is a debug string %d, 0x%x, %f\n", 1, &someVariable, 3.14);
Display_printf(display, 0, 0, "This is a debug string %d, 0x%x, %f\n", 1, &someVariable, 3.14);
System_printf("This is a debug string %d, 0x%x, %f\n", 1, &someVariable, 3.14);
UART_PRINT("This is a debug string %d, 0x%x, %f\n", 1, &someVariable, 3.14);

Please note: TI-RTOS has mechanisms to determine the stack usage for an API call so we've included the stack usage column for TI-RTOS tables.

Note: for the printf (min) test case, the format string used %d instead of %f.

Please note the numbers can vary based on what is already in the application, compiler , compiler options, and device.

IDE Console Output

Here is the summary of the changes made to the GPIO Interrupt example:

  • Added debug API along with the necessary initialization calls/changes.
  • SysMin was used as the System Proxy and its internal buffer was 256 bytes for testing System_printf.
  • MAXPRINTLEN was set to 256 to reduce the size of the Display buffer (displayBuf).

GPIO Interrupt on CC3220SF_LAUNCHXL with TI-RTOS

API Flash Memory RAM Memory Stack Usage
Display_printf 6679 1188 344
printf 10025 936 936
printf (min) 4064 932 400
System_printf 3580 564 240

GPIO Interrupt on CC3220SF_LAUNCHXL with FreeRTOS

API Flash Memory RAM Memory
Display_printf 6198 1184
printf 10044 924
printf (min) 4056 920

GPIO Interrupt on CC3220SF_LAUNCHXL with no-RTOS

API Flash Memory RAM Memory
Display_printf 6532 1198
printf 10962 938
printf (min) 4974 934

UART Output

Using the same test debug API, these tables show the impact when moving the data out of a UART. We are going to omit the FreeRTOS and no-RTOS tables since they are basically the same as the TI-RTOS ones (as shown in the Console numbers above).

We've also included CC2640R2_LAUNCHXL for additional comparisons.

We are not including UARTprintf since it has only been tested for MSP432E4 no-RTOS applications.

Here is the summary of the changes made to the GPIO Interrupt example

  • Added debug APIs along with the necessary initialization calls/changes.
  • THREADSTACKSIZE in main_tirtos.c was increased to 2048 since a couple of the APIs required more stack space. All the examples had the change made (including the base example with no debug API calls) to keep the comparison meaningful.
  • SysCallback was used but different SysCallback.putchFxn and SysCallback.readyFxn functions were used. Please refer to the below labs for more details.

GPIO Interrupt on CC3220SF_LAUNCHXL with TI-RTOS

API Flash Memory RAM Memory Stack Usage
Display_printf 9293 629 496
printf 18541 1317 1320
printf (min) 13636 1313 572
System_printf 9284 385 472
UART_PRINT 12897 365 1348

GPIO Interrupt on CC2640R2_LAUNCHXL with TI-RTOS

API Flash Memory RAM Memory Stack Usage
Display_printf 7176 479 420
printf 16360 1291 1328
printf (min) 11492 1287 468
System_printf 7148 359 368

API Summary

API RTOS Dependency Summary
printf None Easy to use at the expensive of impacting real-time performance when using the default setup
printf over UART None Easy to use with possible calling restrictions
Display_printf None Additional setup but is flexible and OS/IDE independent
System_printf TI-RTOS Flexible and potentially the smallest impact on the application
UART_PRINT None Available on CC3220 devices only
UARTprintf No-RTOS Available on MSP432E4 when using no-RTOS

Receiving on the UART

When using the UART, the Display_printf API sets up the UART instance to be transmit only (unless you want to modify the code in the SDK). The other debug APIs allow the UART instance to be set up for transmit and receive.

Be careful using the different debug APIs together

While using the UART, care must be taken to make sure there is only one call to UART_open and the handle is shared. A common issue is that an application calls Display_open (over a UART) and uses UART_PRINT. If both modules are trying to use the same UART, the second one to call UART_open will fail.

Labs: Getting started

We have several tasks below that show the different debug APIs and how to add them into your application. Please refer to the prerequisites listed at the beginning of this page.

Task 1: Showing printf Downside

For this task, we are going to import the GPIO Interrupt example. We are going to change the buttons to call printf and System_printf instead of toggling LEDs. The goal is to show that printf has an impact on real-time.

  1. Please import the GPIO Interrupt example with TI-RTOS and the CCS compiler for your desired LaunchPad.

  2. Remove the GPIO_toggle calls from the first GPIO callback function and replace it with the following printf in the gpiointerrupt.c file. Please add the include also to get rid of any compiler warnings.

    #include <stdio.h>
    void gpioButtonFxn0(uint_least8_t index)
    {
        printf("Hello world via printf\n");
    }
    

    gpiointerrupt.c

    ...wait a minute

    Question: I thought you said that printf could not be called in the context of an interrupt for TI-RTOS based applications that are using the TI Compiler. Why does this work?!

    Answer: The release kernel configuration does not have asserts enabled and with such a simple application, it's not a problem. Doing this on a real application is not recommended!

  3. Remove the GPIO_toggle calls from the second GPIO callback function and replace the with the following System_printf. Please add the include also to get rid of any compiler warnings/errors.

    #include <xdc/runtime/System.h>
    void gpioButtonFxn1(uint_least8_t index)
    {
         System_printf("Hello world via System_printf\n");
    }
    

    gpiointerrupt.c

  4. Add the following code at the end (but before the return) of mainThread to toggle the LED. Note: this will probably give you an unreachable compiler warning. Please ignore the warning.

    while (1) {
        GPIO_toggle(CONFIG_GPIO_LED_0);
    }
    

    gpiointerrupt.c :: mainThread() – right before the return

  5. Build, load and run the application. Note: the brightness of LED0.

  6. Press Button0. What happens? (more than one answer)

    printf real-time impact

    The moral of the story here is that while printf is easy to use, it can have negative impact on your real-time performance.

  7. Press Button1. What happens? (one answer)

Task 2: Getting System_printf to work

In the previous task we added printf and saw the negative real-time impact it can have. We also added System_printf but it seemed to do nothing...what's up with that?!

Remember that most of the examples have SysCallback as the default System proxy and the default functions do nothing.

var SysCallback = xdc.useModule('xdc.runtime.SysCallback');
System.SupportProxy = SysCallback;
//SysCallback.abortFxn = "&myUserAbort";
//SysCallback.exitFxn  = "&myUserExit";
//SysCallback.flushFxn = "&myUserFlush";
//SysCallback.putchFxn = "&myUserPutch";
//SysCallback.readyFxn = "&myUserReady";

release.cfg

On this task, we'll enable move to the SysMin System proxy. This can be accomplished in one of three ways to allow the output string to be placed in an internal buffer that can be viewed via ROV.

  • Change the release.cfg file to use SysMin: Easiest way, but you should see if this will have an impact on other projects using this kernel project.

    For CC13xx/CC26xx Devices

    To use SysMin, you cannot use the kernel in the ROM. The release kernel project uses the kernel in the ROM.

  • Create new kernel project and use it: You could copy/paste the release kernel project, rename it, use SysMin and point GPIO Interrupt to the new kernel project. This would minimize impact on other projects.
  • Move to the debug kernel project: This will also work since the debug kernel project uses SysMin as well as many other debug features.

    Footprint impact

    The debug kernel project enables many debug features. This has a non-trivial impact on footprint. Additionally for CC13xx/CC26xx devices, the debug kernel configuration does not use the kernel in the ROM, thus causing an even larger footprint impact.

    printf usage

    The debug kernel project enables assert checking in the TI-RTOS kernel. So as noted earlier, if the printf in the gpioButtonFxn0 function executes, an assert occurs.

We are going to do the last one. Note for more details on the kernel projects, please refer to the SDK's User Guide and TI-RTOS Basic Lab

  1. Import the debug kernel for your LaunchPad (Project → Import CCS Projects...) while in the edit perspective. The kernel project is located in the <SimpleLink_SDK_Install_Dir>\kernel\tirtos\builds\<Board>\debug directory.

  2. Select Project Properties on the GPIO Interrupt project and navigate to Build → Dependencies. Select Add and select the debug kernel. Now Remove the release kernel. Once completed, your dependency list should look like this (the board name may be different).

  3. Build, load and run the GPIO Interrupt project. Note: the debug kernel project will get built first.

  4. Let's repeat the last step in the previous task...Press Button1. What happens (one answer)?

  5. Let's make the System_printf output come out on the CCS console. Remember writing to CIO will impact real-time performance. We can simply add a System_flush to the while loop in mainThread:

    while (1) {
       GPIO_toggle(CONFIG_GPIO_LED_0);
       System_flush();
    }
    

    gpiointerrupt.c

    Note that adding a call to System_flush in gpioButtonFxn1 would result in an error, as flushing is an expensive operation that should not be called in a hardware interrupt such as a GPIO callback.

Task 3: Display_printf out the UART

For this task, we are going to import the drivers/empty example. We are going to add in a Display_printf into the empty task that is toggling the LED.

  1. Please import the driver's Empty example with TI-RTOS and the CCS compiler for your favorite LaunchPad.

  2. We need to add the Display driver into the application. Open the empty.syscfg (double-click it). Select "+" on the Display module. This will create a Display instance that defaults to use a UART. Now select "XDS110 UART" for the "Use Hardware" field (and save the file). This will use the UART back-channel on the XDS110 USB connection.

  3. Add the following Display lines at the beginning of the mainThread function in empty.c.

    void *mainThread(void *arg0)
    {
        Display_Handle display;
    
        display = Display_open(Display_Type_UART, NULL);
    

    empty.c

  4. Add the following Display_printf line in the while loop.

    while (1) {
        sleep(time);
        GPIO_toggle(CONFIG_GPIO_LED_0);
        Display_printf(display, 0, 0, "Hello world via Display_printf");
    }
    

    empty.c

  5. Then finally add the following header include

    // #include <ti/drivers/Watchdog.h>
    #include <ti/display/Display.h>
    

    empty.c

  6. Build, load and run that application.

  7. Connect your favorite terminal program to the UART port.

  8. You should be seeing the debug string coming out every second.

  9. Quiz

    Where can Display_printf be placed when it's sending data out the UART and you are using an RTOS (more than one answer)?

    Where can Display_printf be placed when it's sending data out the UART and you are using an not RTOS (more than one answer)?

Task 4: Display_printf out the IDE console

Let's take the example we did in Task 3 and have it use the IDE Console instead of the UART. The two areas to change will be

  • Display_open call
  • Display instance settings in the empty.syscfg file

Let's get started...

  1. Modify the Display_open call to use Display_Type_HOST.

    void *mainThread(void *arg0)
    {
        Display_Handle display;
    
        display = Display_open(Display_Type_HOST, NULL);
    

    empty.c

  2. Open the empty.syscfg file and select "None" for the "Use Hardware" and then "Host" for the "Display Type" (and save).

  3. Build, load and run that application. The debug output should be coming out the CCS Console now.

  4. Quiz

    Where can Display_printf be placed when it's sending data out the IDE console?

Task 5: System_printf output via UART

Let's have the System_printf output go out the UART now. So building upon Task 4, do the following steps.

  1. First modify the release kernel configuration to change the proxy and send the characters out the UART. In the release kernel, change the System proxy to the following:

    ///var SysMin = xdc.useModule('xdc.runtime.SysMin');
    ///SysMin.bufSize = 1024;
    ///System.SupportProxy = SysMin;
    var SysCallback = xdc.useModule('xdc.runtime.SysCallback');
    System.SupportProxy = SysCallback;
    //SysCallback.abortFxn = "&myUserAbort";
    //SysCallback.exitFxn  = "&myUserExit";
    //SysCallback.flushFxn = "&myUserFlush";
    SysCallback.putchFxn = "&UARTUtils_systemPutch";
    SysCallback.readyFxn = "&UARTUtils_systemReady";
    

    release.cfg

  2. Add the UARTUtils.c and UARTUtils.h files to the GPIO Interrupt project. These files can be found at the bottom of this page.

  3. Add the header and UART initialization code into mainThread

    #include <xdc/runtime/System.h>
    #include <ti/drivers/UART.h>
    #include "UARTUtils.h"
    void *mainThread(void *arg0)
    {
      Display_Handle display;
    
      display = Display_open(Display_Type_HOST, NULL);
      UART_init();
      UARTUtils_systemInit(0);
    

    empty.c

  4. Add the System_printf call along side the Display_printf

     while (1) {
         sleep(time);
         GPIO_toggle(CONFIG_GPIO_LED_0);
         Display_printf(display, 0, 0, "Hello world via Display_printf");
         System_printf("Hello world via System_printf\r\n");
     }
    

    empty.c

  5. Build, load and run that application.

  6. Connect your terminal application to the desired port to see the debug strings.

  7. Quiz

    Can we have both System_printf and Display_printf go out the same UART?

Please note that UARTUtils.c/UARTUtils.h is just one example implementation. It can be used as a template for other implementations. For example: Different Implementation

Task 6: printf out a UART

Following from Task 5, let's add a printf that sends data out the UART. The UARTUtil.c and UARTUtil.h files that were added in Task 5 also contain support for sending printf output via the UART.

  1. Add the initialization code to direct the printf output to the UART

    #include <xdc/runtime/System.h>
    #include <ti/drivers/UART.h>
    #include "UARTUtils.h"
    void *mainThread(void *arg0)
    {
        Display_Handle display;
    
        display = Display_open(Display_Type_HOST, NULL);
        UART_init();
        UARTUtils_systemInit(0);
    
        add_device("UART", _MSA, UARTUtils_deviceopen,
                   UARTUtils_deviceclose, UARTUtils_deviceread,
                   UARTUtils_devicewrite, UARTUtils_devicelseek,
                   UARTUtils_deviceunlink, UARTUtils_devicerename);
        /* Open UART0 for writing to stdout and set buffer */
        freopen("UART:0", "w", stdout);
        setvbuf(stdout, NULL, _IOLBF, 128);
    

    empty.c

  2. Add the printf call along side the Display_printf

     while (1) {
         sleep(time);
         GPIO_toggle(CONFIG_GPIO_LED_0);
         Display_printf(display, 0, 0, "Hello world via Display_printf");
         System_printf("Hello world via System_printf\r\n");
         printf("Hello world via printf\r\n");
     }
    

    empty.c

  3. Finally increase the stack size (1024 → 2048 of mainThread in main_tirtos.c) since printf out the UART needs a larger stack.

    #define THREADSTACKSIZE    2048
    

    main_tirtos.c

    • Build, load and run that application. You should see both the System_printf and printf output now.

  4. Quiz

    Why did the Display_printf output start going to the UART?!

    How could be make the output look a little nicer?

Lab Files

/*
 * Copyright (c) 2017, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  ======== UARTUtils.c ========
 */

/* XDCtools Header files */
#include <xdc/std.h>
#include <xdc/runtime/Assert.h>
#include <xdc/runtime/Diags.h>
#include <xdc/runtime/Log.h>
#include <xdc/runtime/System.h>

/* BIOS Header files */
#include <ti/sysbios/BIOS.h>

/* TI-RTOS Header files */
#include <ti/drivers/UART.h>

/* Example/Board Header files */
#include "ti_drivers_config.h"
#include "UARTUtils.h"

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#define NUM_PORTS 1

/* Typedefs */
typedef struct {
    UART_Handle handle; /* Handle for all UART APIs */
    unsigned int base;   /* Base address of the UART */
    unsigned int open;   /* Number of users for this UART */
    bool         binary; /* UART has been opened in binary mode */
} UARTPorts;

/* Static variables and handles */
static UART_Handle systemHandle = NULL;
static UART_Handle loggerHandle = NULL;

/* This example only uses UART0 */
static UARTPorts ports[NUM_PORTS] = {{NULL, CONFIG_UART_0, 0, false}};

/*
 *  ======== openHandle ========
 *  The UART driver will return NULL if there was an error creating a UART
 *
 *  @param  index  Index into the ports array of UARTPorts
 *  @param  binary Open the UART in binary mode
 *  @return        UART_Handle to the opened UART
 */
static UART_Handle openHandle(unsigned int index, bool binary)
{
    UART_Params uartParams;

    /* Only UART 0 is supported in this example. */
    if (index >= NUM_PORTS) {
        System_printf("UART index %d not supported, valid range is 0-%d", index, (NUM_PORTS - 1));
        return (NULL);
    }

    /* The UART driver only allows creating once, return if its already open. */
    if (ports[index].open) {
        /* Make sure the index is not already opened in the wrong mode */
        if (binary != ports[index].binary) {
            return (NULL);
        }
        ports[index].open++;
        return (ports[index].handle);
    }

    /* Create a UART with the parameters below. */
    UART_Params_init(&uartParams);
    if (binary == true) {
        uartParams.readEcho = UART_ECHO_OFF;
        uartParams.writeDataMode = UART_DATA_BINARY;
        ports[index].binary = true;
    }
    else {
        ports[index].binary = false;
    }
    ports[index].handle = UART_open(ports[index].base, &uartParams);
    if (ports[index].handle != NULL) {
        ports[index].open = 1;
    }

    return (ports[index].handle);
}

/*
 *  ======== closeHandle ========
 */
static void closeHandle(unsigned int index)
{
    ports[index].open--;
    if (ports[index].open == 0) {
        UART_close(ports[index].handle);
    }
}

/*
 *  ======== UARTUtils_loggerIdleInit ========
 */
void UARTUtils_loggerIdleInit(unsigned int index)
{
    Assert_isTrue(ports[index].open == false, NULL);
    loggerHandle = openHandle(index, true);
    if (loggerHandle == NULL) {
        System_printf("Failed to open UART %d", index);
    }
}

/*
 *  ======== UARTUtils_loggerIdleSend ========
 *  Plugged into LoggerIdle to send log data during idle.
 */
Int UARTUtils_loggerIdleSend(UChar *a, Int size)
{
    /* Make sure UART is initialized */
    if (loggerHandle) {
        /*
         * Write up to 16 bytes at a time.  This function runs during idle and
         * should not tie up other idle functions from running.  The idle loop
         * is generally short enough that this function will run again before all
         * 16 bytes have been transmitted from the FIFO.
         */
        if (size < 16) {
            return (UART_writePolling(loggerHandle, (void *)a, size));
        }
        else {
            return (UART_writePolling(loggerHandle, (void *)a, 16));
        }
    }
    else {
        return (0);
    }
}

/*
 *  ======== UARTUtils_deviceclose ========
 */
int UARTUtils_deviceclose(int fd)
{
    /* Return if a UART other than UART 0 was specified. */
    if (fd != 0) {
        return (-1);
    }

    closeHandle(fd);

    return (0);
}

/*
 *  ======== UARTUtils_devicelseek ========
 */
off_t UARTUtils_devicelseek(int fd, off_t offset, int origin)
{
    return (-1);
}

/*
 *  ======== UARTUtils_deviceopen ========
 */
int UARTUtils_deviceopen(const char *path, unsigned flags, int mode)
{
    int fd;
    UART_Handle handle;


    /* Get the UART specified for opening. */
    fd = path[0] - '0';

    handle = openHandle(fd, false);

    if(handle == NULL) {
        return (-1);
    }
    else {
        return (fd);
    }
}

/*
 *  ======== UARTUtils_deviceread ========
 */
int UARTUtils_deviceread(int fd, char *buffer, unsigned size)
{
    int ret;

    /* Return if a UART other than UART 0 was specified. */
    if (fd != 0) {
        return (-1);
    }

    /* Read character from the UART and block until a newline is received. */
    ret = UART_read(ports[fd].handle, (uint8_t *)buffer, size);

    return (ret);
}

/*
 *  ======== UARTUtils_devicewrite ========
 */
int UARTUtils_devicewrite(int fd, const char *buffer, unsigned size)
{
    int ret;

    /* Return if a UART other than UART 0 was specified. */
    if (fd != 0) {
        return (-1);
    }

    /* Write to the UART and block until the transfer is finished. */
    ret = UART_write(ports[fd].handle, (uint8_t *)buffer, size);

    return (ret);
}

/*
 *  ======== UARTUtils_deviceunlink ========
 */
int UARTUtils_deviceunlink(const char *path)
{
    return (-1);
}

/*
 *  ======== UARTUtils_devicerename ========
 */
int UARTUtils_devicerename(const char *old_name, const char *new_name)
{
    return (-1);
}

/*
 *  ======== UARTUtils_systemAbort ========
 */
Void UARTUtils_systemAbort(String str)
{
    /* Make sure UART is initialized */
    if (systemHandle) {
        UART_writePolling(systemHandle, (uint8_t *)str, strlen(str));
    }
}

/*
 *  ======== UARTUtils_systemInit ========
 */
void UARTUtils_systemInit(unsigned int index)
{
    systemHandle = openHandle(index, false);
    if (systemHandle == NULL) {
        Log_print1(Diags_USER1, "Failed to open UART %d", index);
    }
}

/*
 *  ======== UARTUtils_systemPutch ========
 */
Void UARTUtils_systemPutch(Char a)
{
    /* Make sure UART is initialized */
    if (systemHandle) {
        UART_write(systemHandle, (void *)&a, 1);
    }
}

/*
 *  ======== UARTUtils_systemReady ========
 */
Bool UARTUtils_systemReady(Void)
{
    return (systemHandle ? TRUE : FALSE);
}

UARTUtils.c

/*
 * Copyright (c) 2017, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/** ============================================================================
 *  @file       UARTUtils.h
 *
 *  @brief      UART related utilities
 *
 *  The UARTUtils header file should be included in an application as follows:
 *  @code
 *  #include <UARTUtils.h>
 *  @endcode
 *
 *  ============================================================================
 */

#ifndef __UARTUtils_H
#define __UARTUtils_H

#ifdef __cplusplus
extern "C" {
#endif

#include <xdc/std.h>

#include <stdio.h>

#if defined(__TI_COMPILER_VERSION__)
#include <file.h>
#endif

/*!
 *  @brief  Open the UART to be used with LoggerIdle
 *
 *  This function opens the UART to be used by the LoggerIdle plugged function.
 *
 *  @param      index UART to use for LoggerIdle's send function from the ports
 *                    array. Matches the same number uart base (0 -> UART_BASE0).
 */
extern void UARTUtils_loggerIdleInit(unsigned int index);

/*!
 *  @brief  Sends Log data out the UART
 *
 *  This function should not be called directly, it will be called by the
 *  LoggerIdle module during Idle.
 *
 *  @param    a    Pointer to unsigned char buffer
 *  @param    len  length of unsigned char buffer
 *  @return        Number of bytes sent
 *
 *  To configure LoggerIdle to upload via UART in the .cfg:
 *  @code
 *  var LoggingSetup = xdc.useModule('ti.uia.sysbios.LoggingSetup');
 *  LoggingSetup.loggerType = LoggingSetup.UploadMode_IDLE;
 *  var LoggerIdle = xdc.useModule('ti.uia.sysbios.LoggerIdle');
 *  LoggerIdle.transportFxn = "&UARTUtils_loggerIdleSend";
 *  LoggerIdle.transportType = LoggerIdle.TransportType_UART;
 *  @endcode
 *
 */
extern Int UARTUtils_loggerIdleSend(UChar *a, Int len);

/*!
 *  @brief   close function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_deviceclose(int fd);

/*!
 *  @brief   seek function for add_device rts function
 *
 *  This function should not be called directly
 */
extern off_t UARTUtils_devicelseek(int fd, off_t offset, int origin);

/*!
 *  @brief   open function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_deviceopen(const char *path, unsigned flags, int mode);

/*!
 *  @brief   read function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_deviceread(int fd, char *buffer, unsigned size);

/*!
 *  @brief   rename function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_devicerename(const char *old_name, const char *new_name);

/*!
 *  @brief   unlink function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_deviceunlink(const char *path);

/*!
 *  @brief   write function for add_device rts function
 *
 *  This function should not be called directly
 */
extern int UARTUtils_devicewrite(int fd, const char *buffer, unsigned size);

/*!
 *  @brief   abort function called within System_abort
 *
 *  This function should not be called directly
 */
extern Void UARTUtils_systemAbort(String str);

/*!
 *  @brief  Opens the UART to be used with SysCallback
 *
 *  This function opens the UART to be used by the SysCallback plugged functions.
 *
 *  @param      index UART to use for System output from ports array. Matches the
 *                    same number uart base (0 -> UART_BASE0).
 */
extern void UARTUtils_systemInit(unsigned int index);

/*!
 *  @brief   putch function for System_printf
 *
 *  This function should not be called directly
 */
extern Void UARTUtils_systemPutch(Char a);

/*!
 *  @brief   ready function for System module
 *
 *  This function should not be called directly
 */
extern Bool UARTUtils_systemReady();

#ifdef __cplusplus
}
#endif

#endif /* __UARTUtils_H */

UARTUtils.h

Additional Resources

Forums

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.