Device drivers for bare metal Xilinx Zynq (C / C++)

Introduction

Last time (HERE) I showed you how to create first bare metal C++ application project for Xilinx Zynq. It was a simple Hello world that just prints the text on the serial port. Today we will try to use some of the peripherals available in Zynq devices and learn the pros and cons of writing the code in a C and C++ style. The name mangling term will also show up to scare you a little bit!

Software tools used:

Vivado 2014.4, Xilinx SDK 2014.4

Hardware platform: Zedboard

Preparing the hardware platform and software project

If you missed the last installment of our series go back and DO IT, as it is necessary for today task as we will be using last week’s project.

With SDK started, add a new (second) C++ application to the project (menu File->New->Application Project). Choose the Board Support Platform (BSP) created for zynq_cplusplus project as shown in Figure 1.

Untitled

Figure 1 – creating new project that uses already created BSP

Remove the existing linker script and .cc file. Download the files from github (lscript.ld, app_zynq_ps7_timer/app_zynq_ps7_timer.cc, platform/platform.c, platform/platform.h, platform_config.h, zynq_ps7_timer/zynq_ps7_timer.c, zynq_ps7_timer/zynq_ps7_timer.h, zynq_ps7_timer/zynq_ps7_timer_cpp.cc, zynq_ps7_timer/zynq_ps7_timer_cpp.h)  and import them into the project – this step was described in detail in the previous article (fill the import card according to Figure 2). At the end add appropriate folder to Include Paths of the project (C/C++ Build Settings->ARM g++ compiler->Directories).

importing_the_sources_updated

Figure 2 – importing the sources

After this exhausting procedure we can start writing the program!

Problem definition

Lets say we need to measure time, for example to check how long it takes to send the data to specially designed hardware accelerator. We need a stopwatch that we can start, stop and check how much time has passed in micro- and mili- seconds. In addition, we would like to be able to take lap times.

To sum this up we need following functionality:

  • initialization – create a timer, do all the necessary things
  • start
  • stop
  • get start-stop time in miliseconds
  • get start-stop time in microseconds
  • (for lap functionality) get start-lap or lap-stop time is microseconds

The idea behind this was C# StopWatch class, which is simple and easy to use.

As Xilinx is providing the code to use the timers (xscutimer.h) in Zynq Processing System we just need a wrapper that will made using them a pleasure 🙂 If you want to know more about how to separate your own code from the third party look at the Chapter 8: Boundaries in Clean Code: A Handbook of Agile Software Craftsmanship book.

Device driver in C style

To offer a user described functionality, we will prepare a collection of functions with TIMER_ prefix for easier usage. All of them are declared in zynq_ps7_timer.h file. Support variables are defined in zynq_ps7_timer.c file and are marked as static to stress that they are used only locally. Short code inspection shows that TIMER_init, TIMER_start, TIMER_stop and TIMER_getCurrentValue functions are calling the Xilinx API. The rest of provided functions are just a companion operations for recalculating clock cycles to micro- and mili- seconds.

Example code for time measuring:

// init the timer by calling the function and passing the timer ID
TIMER_init(XPAR_PS7_SCUTIMER_0_DEVICE_ID);

TIMER_start();

// put code to measure its execution here
xil_printf("Using C style!\r\n");

TIMER_stop();
// get elapsed time (from start to stop) in microseconds
float elapsedTime = TIMER_getTimeInUs();
printf("xil_printf took: %f us to execute\r\n", elapsedTime);

Side note – multiplication over division

You can find such fragment in few functions: some_value * (1.0F / TIMER_FREQ_IN_MHZ). This is used so that the compiler can precalculate the value in bracket during the compilation process and use multiplication with some_value instead of division (which can be faster).

Side note – name mangling

C style files should use:

</pre>
#ifdef __cplusplus
extern "C" {
#endif

// code goes here

#ifdef __cplusplus
}
#endif

to properly create function names. I encourage you to look at for more details HERE or HERE.

Device driver in C++ style

This time, thanks to the use of C++, we can create the device as a class. All static variables became fields of the class and the usage of prefix is not needed (functions are now grouped as methods of the class). The usage is even simpler – just create an instance of StopWatch class, and the constructor will take care of initialization. Then just use the object and call its methods.

Example code for time measuring using the C++ class:

// init the timer by creating class object and passing timer ID to the constructor
StopWatch sw(XPAR_PS7_SCUTIMER_0_DEVICE_ID);

// this time starting is done by using method of the class
sw.Start();

// put code to measure its execution here
xil_printf("Using C style!\r\n");

sw.Stop();

// get elapsed time (from start to stop) in microseconds
float elapsedTime = sw.GetTimeInUs();
printf("xil_printf took: %f us to execute\r\n", elapsedTime);

Side note – instances of classes

It should be noted, that the class does not allow us to create infinite number of timers (they are bounded by the number of timers in the hardware). Our simple implementation requires specifying device id and each timer should get its own. In Zynq device there is only one timer in Processing Subsytem. Details about hardware configuration can be found in xparameters.h file (look for /* Definitions for driver SCUTIMER */).

Summary

As shown in the main function both the C and C++ style driver perform the same operations. In my opinion usage of the class makes it more intuitive and does not pollute the namespace with many functions (albeit TIMER_ prefix reduce the problem a little bit). In addition in case of more than one hardware timer, the C++ style driver will just work, while the C style one will need a rework to be capable of offering simultaneous usage of two timers.

As the low-level drivers are provided by Xilinx, using C++ classes as a wrapper should be the preferred solution when creating new applications. Be aware that interrupts are rather problematic to deal with classes. We will cover this topic in later installments of this series.

Also feel free to use this code! It is using very liberal MIT license!

Michał Fularz

Acknowledge

This research was financed by the Polish National Science Centre grant funded according to the decision DEC-2011/03/N/ST6/03022, which is gratefully acknowledged.

More info:
http://www.vision.put.poznan.pl/?page_id=237

Leave a Reply

Your email address will not be published. Required fields are marked *