This is a C-language tutorial for the 8-core Propeller microcontroller. It features the Propeller Activity Board (original or WX version) but other Propeller boards will work, with special considerations. Watch for board compatibility notes.
This tutorial shows you step-by-step how to make your own custom library that works well with the Propeller C Tutorials. You will:
We advise completing all of the following steps before beginning Library Studies:
Imagine you have created an interesting application. Along the way, you discovered that your code needed to repeat a number of certain tasks frequently, so you put those tasks into functions. Now those functions are easy to re-use, and your code is tidy — great job!
But what if you want to use those handy functions in your next interesting application? You could copy and paste them from one applciation to another every time you want to use them. But there's a better way: create a simple library for them. Then, you can include that library in the beginning of any future program, and call any function in that library in much the same way you’ve been calling pause, high, low, and other functions from the simpletools library.
Make sure you have at least gone through the Propeller C – Start Simple [5] and Propeller C – Functions [6] tutorials before continuing here.
Start with some working code that has functions you might want to reuse. Here we have a very simple application with two functions to append whomever’s name you have printed with “is awesome!\n” or “ is epic!\n”.
/* Awesome Messages.c */ #include "simpletools.h" // Include simple tools void awesome(void); // Forward declarations void epic(void); int main() // main function { print("Nick"); // Print a name awesome(); // Append with "is awesome!\n" print("Jessica"); // Print another name epic(); // Append with "is epic!\n" } void awesome(void) // awesome function { print(" is awesome!\n"); } void epic(void) // epic function { print(" is epic!\n"); }
Before moving the awesome and epic functions to libraries, let’s test this code to make sure it works.
Alright, so we have working code with two functions that are destined for a simple library. After creating an awesome library, you won’t have to keep the forward declarations and functions in your code any more. They’ll be neatly tucked away in the awesome library, and all that you'll have to do in order to access them will be to add #include “awesome.h” to the beginning of your code.
The first step to creating a library named awesome is to save your application as libawesome inside a folder named libawesome. This folder has to be located in the Simple Libraries folder for SimpleIDE to access it, and to keep things neat we’ll place it all in a subfolder named My Libraries.
This should create a libawesome project.side project file and libawesome.c soure inside the libawesome folder. Both files must be there in the right place for creating the simple library. This is an important step, so let’s double-check that before moving on.
A simple library needs a header file. Recall from the Reusable Code Functions tutorial [7] that each function you created needed a forward declaration of its function prototype, to let the C compiler know what it might encounter. A library's header file collects these function prototypes in once place, so you don't have to add a forward declaration when using a function from the library. If a library uses functions from other libraries, its header file should also #include those other libraries.
So, let's make a .h file for your awesome library.
Now you have two files open in SimpleIDE: libawesome.c and awesome.h
Why #include simpletools.h?
The awesome and epic functions both use the print function. The print function is part of the simpletools library, so we need to #include it in the awesome library.
Simple libraries tend to have one or more C language source files. This is where the complete function definitions and their code blocks go. This file also has to #include awesome.h.
Now that the library has its header file with forward declarations and its source file with functions, the libawesome application doesn’t need them. The libawesome application is still important though. You will use it to build your library's output, and to test the functions to make sure they work as expected. The library's application file is sometimes called a test harness.
When your applications call the simpletools library’s pause function, the compiler doesn’t actually get anything from the .c file. Instead, it goes looking for a file named libsimpletools.a. The .a is short for archive, and the file is a binary image of the compiled C language source code. These files can help reduce the size of your program code.
Let's make the archive for your awesome library.
The Project Manager windowpane that opens up should look like this:
Propeller GCC is designed to work with several different memory models. In the Propeller C Learning System tutorials [8], we’ve been sticking with CMM (compact memory model). We’ll start by compiling the library for that memory model first.
Using a different board? Look for it by name in the Board Type list. If it's not there, click Help and look up Board Types in the SimpleIDE User Guide.
The next step is important; we need to configure the project to generate the library archive files.
After you have configured the project settings to create a project library and selected the memory model, you’re ready to build the project and create a library archive for the compact memory model.
After you do this, there’ll be a file named libawesome.a inside a cmm subfolder.
You can compile your code for other memory models. Let’s try LMM (large memory model), which executes the code faster, but also takes more memory for the same program (compared to compact memory model).
Memory models are different ways to store and execute the program code on the Propeller microcontroller and connected memory devices. The CMM Main Ram Compact memory model used most often the Propeller C Tutorials puts all the program code into a 32 KB file that gets loaded into the Propeller chip's main RAM, either directly or from an EEPROM chip like the one on the Activity Board (original or WX version). Other memory models might use a very large EEPROM, Flash memory, or just the memory in a single cog.
For more information on this advanced topic, see the user guide (click Help and select SimpleIDE User Guide).
SimpleIDE will have to be restarted before it can become aware of this library for use by other projects. After that, it’s a good idea to test and make sure a different project can use it.
/* Test Awesome Library.c */ #include "simpletools.h" // Include simple tools #include "awesome.h" // Include awesome library int main() // main function { print("Ken"); // Print a name awesome(); // Append with "is awesome!\n" print("Stephanie"); // Print another name epic(); // Append with "is epic!\n" }
Simple libraries often have more than one .c source file that gets compiled. Simple libraries are set up this way to conserve program memory. If each function in a given simple library resides in a different .c file, the application that uses the simple library will only get code for the functions it uses. If the simple library instead has one long, wandering .c file with a bunch of functions, the application will get the code for all the functions even if it only calls for one of them.
This tutorial builds on the How to Create a Simple Library tutorial, and shows how to split the single .c file into multiple .c files to save memory. It also demonstrates how a library can share global variables.
Let’s go back and try the test code from My Documents, and take some notes about the memory usage. We can use this as the “before” entry in a before/after comparison of how much memory a single function call to the awesome library takes.
Not much savings there! Removing the epic function call reduced the program size by four bytes. But, the code for the epic function is still in the program. The call to awesome brings in all the code from the .c source file where the .c function lives, even if it is not needed. Let’s restructure our library a little so that a call to the awesome function doesn’t bring in the code for both the awesome and epic functions.
Next, let's put the source code for the awesome function and epic function into separate .c source files. Then, you will be able to see a much larger program size difference when calling just one function instead of both.
Now, let’s move the epic function into a different file, and recompile.
Now that the library has been modified, a call to awesome should only add the awesome function code to the program size, not both the awesome and epic functions' code. Let’s test that.
Look how much memory was saved this time, the total code size dropped from 6,924 to 6,888 — a savings of 236 bytes! Much better than 4 bytes.
This activity illustrated the advantage to putting your awesome library's two functions into two separate c files. The more functions your library has, the more effective this strategy will be for saving code space.
However, you don't always need one .c file per function. If two or more functions always work together, there wouldn’t be any advantage to putting them in different files since the code for each of those would always be added to the total program size. Aside from that, if you have functions that can stand alone, you will benefit from putting them into separate files.
Within a library, it is quite common to have one function depend upon information provided by another function. The other function can be inside the same source file, in a different source file in the same library, or even in a different library. Oftentimes, functions can share information directly through parameter arguments and return values, without the need for anything more than local variables. Sometimes, variables that are global to the source file or to a library are necessary.
Below is an example from the simple library source file ping.c, showing two of its functions. If your project has a PING))) Ultransonic Distance Sensor connected to an I/O pin, you can call ping_cm to get a distance measurement reading from the sensor in centimeters.
Lets's see how local variables, parameters and return values pass information around inside the ping.c library to get that distance measurement. Imagine your application code makes a function call: int dist = ping_cm(8);
If your library launches a function into another cog with cogstart or cog_run, your code may need to use global variables to exchange information. In the next activity, we'll modify the awesome library so that multiple functions in the same file share a variable. In the activity after that we’ll look at how functions in multiple files can share variables.
Sometimes it’s useful to group a few functions that always work together in the same file and let them share variables that are global to that file.
Let’s say you want your awesome function to track how many times it has been called in your application. And, you want a function named awesome_getCount to report the number of times awesome was called. Here’s one way to make those changes to awesome.c:
Now, each time the awesome function gets called, it adds 1 to a variable named calls. Your application will also be able to call the awesome_getCount function to find out how many times the awesome function was called.
The static modifier makes it so that only functions within the same file can see that variable. There are advantages to making your library file variables static; for instance, you can decide to add a static variable named calls to epic.c using similar code, and not have to worry that functions of one file could read the calls variable from another. The awesome.c static calls variable would only keep track of the number of times awesome is called, and the epic.c static calls variable would only keep track of the number of times epic was called.
This modified library isn’t quite ready to use yet. Since we added a function to the library, we need to make sure to update the header file with an awesome_getCount function prototype.
Make sure to update your tests harness code in libawesome.c to exercise this new function and make sure it works.
If the test harness code works correctly, you can build the library for the memory models the library is designed to support.
Remember from Multicore Example [9] that a function launched into another cog using the CMM or LMM memory model needs to exchange information with volatile global variables. Sometimes a library launches functions into other cogs, and the same rules will apply. Let's recap some global variable vocabulary, and look at some rules for using them in library source files.
A global variable is accessible to all functions in every source file where it is declared. To avoid problems:
Initialization — if a global variable is declared in more than one source file in a library, it should be initialized in only one place or you will get a compiler error.
Static — use the static keyword to make a global variable visible only to functions within the same source file whenever possible. This removes any potential for conflict with variables of the same name in any other library source files or user application code. You can use static and volatile together.
Volatile — Use the volatile keyword for global variables that need to be used by functions running in different cogs. This keeps the C compiler's size optimizer from removing code that affects other functions' ability to read or write to that variable from another cog. You can use static and volatile together.
Naming — if a programmer happens to give a global variable in their application code the same name as a non-static global variable in a library, the names will conflict and give unexpected results. To help avoid this, name your variables in a libName_varName format. If you run into a mystery bug when writing an application, it is worth checking the documentation for the libraries you are using to see if you have a variable name conflict.
This activity starts by adding a new set of functions to the awesome library, in a source file named timer.c. Since all the variables are in the same file, they are declared static to prevent other files using the same variable names from corrupting this file’s data. Later, we'll move some of the functions to separate source files, with care to set up the global variables for multi-cog use.
/* timer.c Track time since the last awesome call. */ #include "simpletools.h" #include "awesome.h" static int cog = 0; static int stack[60]; static volatile int seconds = 0; void secondCtr(void *par); int awesome_startTimer(void) { if(cog == 0) cog = 1 + cogstart(&secondCtr, NULL, stack, sizeof stack); } void awesome_stopTimer(void) { if(cog > 0) { cogstop(cog - 1); cog = 0; } } int awesome_secondsSince(void) { return seconds; } int awesome_secondsReset(void) { seconds = 0; } void secondCtr(void *par) { int dt = CLKFREQ; int t = CNT; while(1) { waitcnt(t += dt); seconds++; } }
You have added more functions to the library, so:
After adding this to the library, make sure to test it. The easiest way to do this is to modify the test harness so that it uses the new functions, and verify that each one runs as expected.
In the example below, we want to verify that start awesome_startTimer returns nonzero, and awesome_secondsSince() reports the correct seconds count after the start function was called.
The modified test harness also checks to make sure that awesome_secondsReset can reset the seconds counter to zero, and awesome_stopTimer stops the counting process. There are two calls to awesome_secondsSince that follow the stop call to make sure it really has stopped counting.
/* libawesome.c Test harness for libawesome library. */ #include "simpletools.h" // Include simple tools #include "awesome.h" // Include awesome header int main() // Main function { int cog = awesome_startTimer(); // Test the start print("cog = %d\n", cog); // Print cog value for(int n = 1; n <= 10; n++) // Call awesome certain number of times { print("Nick"); // Print a name awesome(); // Append with " is awesome!\n" } int callCount = awesome_getCount(); // Test awesome_getcount print("callCount = %d\n", callCount); pause(2000); int seconds = awesome_secondsSince(); // Test seconds since print("seconds = %d\n", seconds); awesome_secondsReset(); // Test seconds reset seconds = awesome_secondsSince(); print("seconds = %d\n", seconds); print("Jessica"); // Print another name epic(); // Append with " is epic!\n pause(2000); awesome_stopTimer(); // Test stop timer seconds = awesome_secondsSince(); print("seconds = %d\n", seconds); // Make sure timer stopped counting pause(1000); seconds = awesome_secondsSince(); print("seconds = %d\n", seconds); }
Here's what your SimpleIDE Terminal output should look like if all went well:
Library-Only Functions — Forward declarations for all the functions were added to awesome.h with one exception: secondCtr. This function is not one that we want the user's application to call, so we didn’t advertise it in the header file. Instead, the timer.c file just has a forward declaration letting the compiler know that it will find the code for it eventually.
Now, imagine that there are 26 more functions in the timer.c source file (yikes!). In that case, it would be better to move functions to separate files whenever possible so your application code does not get stuffed with code for unneeded functions if you only call one or two. Not all functions would have to be separated. For example, if two or more functions always work together, or if the functions are small and interrelated, it’s okay to leave them in the same file.
With these guidelines in mind, we’ll keep awesome_startTimer and awesome_stopTimer functions in the same file with secondCtr. An advantage here is that the cog and stack variables can remain static and keep their generic names.
In contrast, awesome_secondsSince and awesome_secondsReset should be moved to separate files. Since they share the seconds variable, it should be made global to the application (no static modifier). Its name should also be changed from seconds to something like awesome_cog_seconds. That way if a user makes a global variable named seconds in their application code, it won't interfere with this library’s seconds value.
The result is the three files shown below. All three files have to share the awesome_cog_seconds variable, so all three declare it without the static modifier. Remember, only one source file should optionally initialize the variable, and that is already done in timer.c.
/* timer.c Track time since the last awesome call. */ #include "simpletools.h" #include "awesome.h" static int cog = 0; static int stack[60]; volatile int awesome_cog_seconds = 0; void secondCtr(void *par); int awesome_startTimer(void) { if(cog == 0) cog = 1 + cogstart(&secondCtr, NULL, stack, sizeof stack); } void awesome_stopTimer(void) { if(cog > 0) { cogstop(cog - 1); cog = 0; } } void secondCtr(void *par) { int dt = CLKFREQ; int t = CNT; while(1) { waitcnt(t += dt); awesome_cog_seconds++; } }
Next, we need to make a source file for the awesome_secondsSince function removed from timer.c Note that the awesome_cog_seconds variable that is still in timer.c is also needed here. So, it needs to be declared (but not initialized!) as a global volatile variable in this source file as well. Don't forget to update the variable name in the function!
/* seconds_since.c Track time since the last awesome call. */ #include "simpletools.h" #include "awesome.h" volatile int awesome_cog_seconds; int awesome_secondsSince(void) { return awesome_cog_seconds; }
A source file for awesome_secondsReset also needs to be added to the library project. It too accesses the awesome_timer_seconds variable. So, so again we will need to declare the variable, and update the variable name used in the function call.
/* seconds_reset.c Track time since the last awesome call. */ #include "simpletools.h" #include "awesome.h" volatile int awesome_cog_seconds; int awesome_secondsReset(void) { awesome_cog_seconds = 0; }
At this point, the test harness code in libawesome.c should run the same as it did the last time. The only difference is that the library is arranged so that calling seconds_since will not bring in the code for seconds_reset if the application never calls it.
Up to this point, all the awesome library’s header file has are the forward declarations for the library’s functions:
/* awesome.h Header file for the awesome library. */ #include "simpletools.h" // Include simple tools int awesome_getCount(void); void awesome(void); void epic(void); int awesome_startTimer(void); void awesome_stopTimer(void); int awesome_secondsSince(void); int awesome_secondsReset(void);
We can add two more features to make our library robust:
An include guard prevents compiler errors that could result if two different parts of an application #include the same header file. Without a guard, the C preprocessor could end up trying to process the header twice if two different libraries #include it. With a guard, the C preprocessor keeps the information from the header with the first #include, and skips it the second time because it sees the guard.
An include guard looks like this:
#ifndef UNIQUE_NAME_FOR_HEADER_FILE // Include guard #define UNIQUE_NAME_FOR_HEADER_FILE ...All header file declarations... #endif // End Include guard /* UNIQUE_NAME_FOR_HEADER_FILE */
The first lines say "if the UNIQUE_NAME_FOR_HEADER_FILE macro name has not already been defined, keep going, otherwise, skip everything until #endif is reached."
The first time the preprocessor goes through the header it will process all the information. Since one of the items in there is #define UNIQUE_NAME_FOR_HEADER_FILE, that macro name will be defined. So the second time it tries to process that header, it’ll see the #ifdef, realize that UNIQUE_NAME_FOR_HEADER_FILE is already defined and skip it.
The header information should also be placed inside a block of code that allows it to be used by a C++ compiler. A C++ compiler updates function names with some additional information to support a C++ feature called overloading, but this can cause compiler errors when used on C code. The compatibity block code tells a C++ compiler to just compile the function names with C instead of C++.
#if defined(__cplusplus) // If compiling for C++ extern "C" { // Compile for C #endif ...All header file declarations... #if defined(__cplusplus) } // End compile for C block #endif /* __cplusplus */
If SimpleIDE’s compiler is set to C++ in the project settings, __cplusplus will automatically get defined. In that case, #if defined(__cplusplus) will add extern “C” { above the header info and } below it, so that it gets compiled without the extra overloading information and the compiler errors that would cause.
Let’s apply the include guard and C++ compatibility block to awesome.h.
/* awesome.h Header file for the awesome library. */ #ifndef AWESOME_H // Prevents duplicate #define AWESOME_H // declarations #if defined(__cplusplus) // If compiling for C++ extern "C" { // Compile for C #endif #include "simpletools.h" // Requires simpletools void awesome(void); // Forward declarations int awesome_getCount(void); int awesome_startTimer(void); void awesome_stopTimer(void); int awesome_secondsSince(void); int awesome_secondsReset(void); void epic(void); #if defined(__cplusplus) } // End compile for C block #endif /* __cplusplus */ #endif // End prevent duplicate forward /* AWESOME_H */ // declarations block
Congratulations! Now there's only one thing left to do: optionally document your library to make it easier for other people to use (and easier for you to remember what you did!)
Each Simple Library comes with a web page that documents its features. This activity demonstrates how to document the functions in the header file so that a software package called Doxygen [12] can automatically create the web page.
Here is the awesome.h header file we’ve been working with, reorganized alphabetically.
/* awesome.h Header file for the awesome library. */ #ifndef AWESOME_H // Prevents duplicate #define AWESOME_H // declarations #if defined(__cplusplus) // If compiling for C++ extern "C" { // Compile for C #endif #include "simpletools.h" // Requires simpletools void awesome(void); // Forward declarations int awesome_getCount(void); int awesome_startTimer(void); void awesome_stopTimer(void); int awesome_secondsSince(void); int awesome_secondsReset(void); void epic(void); #if defined(__cplusplus) } // End compile for C block #endif /* __cplusplus */ #endif // End prevent duplicate forward /* AWESOME_H */ // declarations block
Below is the header, revised with comments for the programmer using the library. Each documentation block comment has to start with /**, and end with the customary */. The /** tells Doxygen to expect keywards with @ signs in front of them to guide its web page formatting. If you have a comment that’s not intended for converting to web documentation, simply start it with /* and Doxygen will ignore it. You’ll notice @file, @brief, @param, and @return in the example below. Used together, they format the web page so it is easier to read and understand how to use each function in the library.
For a full list of Doxygen formatting flags, see: http://www.stack.nl/~dimitri/doxygen/manual/commands.html [13]
/** * @file awesome.h * * @author Andy Lindsay * * @version 0.5 * * @copyright * Copyright (C) Parallax, Inc. 2012. All Rights MIT Licensed. * * @brief This library was created for the Library Studies tutorial, and * isn’t really all that useful otherwise. It has functions for appending * what has been printed last with " is awesome!\n" and " is epic!\n" along * with some second counting features that run in another cog. It’s main * point is to provide example code for going step-by-step through creating * a Simple Library. */ #ifndef AWESOME_H // Prevents duplicate #define AWESOME_H // forward declarations #if defined(__cplusplus) // Keeps declarations extern "C" { // C++ compatible #endif #include "simpletools.h" // Requires simpletools /** * @brief Append " is awesome!\n" to most recently printed text. Also * append an internal count of number of times awesome was called. This * count can be accessed by calling awesome_getCount. */ void awesome(void); // Forward declarations /** * @brief Get the number of times awesome has been called. * * @returns number of times awesome has been called since the * application started. */ int awesome_getCount(void); /** * @brief Set the number of times awesome has been called to some * arbitrary value. * * @details This function doesn’t actually exist. It’s just here to show * how the (at)param and (at)detail formatters can be used. * * @param countVal is the new value of number of times awesome has been called. * * @returns number of times awesome has been called before it was updated by * this function. */ int awesome_setCount(int countVal); /** * @brief Start a second timer in another cog. This timer can be used * to get the time since the cog started. * * @returns 1 + the number of the cog that the process was launched into. */ int awesome_startTimer(void); /** * @brief Stop the second timer and reclaim the cog for other uses. */ void awesome_stopTimer(void); /** * @brief Get seconds since the timer was started. * * @returns seconds measurement. */ int awesome_secondsSince(void); /** * @brief Reset the seconds count. */ int awesome_secondsReset(void); /** * @brief Append " is epic!\n" to most recently printed text. */ void epic(void); #if defined(__cplusplus) // End C++ compatible block } #endif /* __cplusplus */ #endif // End prevent duplicate forward /* AWESOME_H */ // declarations block /** * TERMS OF USE: MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */
TERMS OF USE: MIT License is added to all of the Simple Libraries provided by Parallax Inc. It is up to you whether or not you add it to your own libraries. It is also required for all objects in the Propeller Object Exchange [14], so if you want to share your libraries there, add the MIT license to them as well.
In the next activity, we will use Doxygen to create an HTML page documenting the awesome library. So, you need to add the formatting flags to your awesome.h file. You can either add them by hand to match the file, or copy and paste from this page (much faster!).
If you have updated your awesome.h file with the Doxygen markup as shown in the last activity, you can
Doxygen will tell you when it’s finished. If there are errors or warnings in the Output produced by doxygen pane, you may need to find and correct some documentation in your header file.
We aren’t quite done yet, but now would be a good time to take a look at the page.
The page should resemble this awesome_8h.html. Each function name is a live link that you can follow to see documentation further down the page for that function.
The awesome_8h.html lives in an html subfolder that Doxygen created:
Simple Libraries compiled by Parallax have a copy of this web page named Documentation libawesome Library.html at the same level as the .side project, .h file, etc. Since this copy is one directory up from the one in the html folder, the tags inside the html page need to be updated slightly so that it can still navigate correctly.
Congratulations! You have made a complete simple library.
Links
[1] http://learn.parallax.com/propeller-c-set-simpleide
[2] http://learn.parallax.com/propeller-c-set-simpleide/update-your-learn-folder
[3] http://learn.parallax.com/propeller-c-start-simple
[4] http://learn.parallax.com/propeller-c-functions
[5] https://learn.parallax.com/propeller-c-start-simple
[6] https://learn.parallax.com/propeller-c-functions
[7] https://learn.parallax.com/propeller-c-functions/reusable-code-functions
[8] https://learn.parallax.com/propellerc
[9] https://learn.parallax.com/propeller-c-functions/multicore-example
[10] https://learn.parallax.com/library-studies/reducing-program-size-simple-library
[11] https://learn.parallax.com/library-studies/how-create-simple-library
[12] http://www.stack.nl/~dimitri/doxygen/
[13] http://www.stack.nl/~dimitri/doxygen/manual/commands.html
[14] http://obex.parallax.com/
[15] http://www.stack.nl/~dimitri/doxygen/download.html
[16] https://learn.parallax.com/sites/default/files/content/propeller-c-tutorials/Library-Studies/Document-Library/Doxyfile.zip