March 20, 2025

Black Pill boards - F411 USB -- USB classes - include file hell

On a couple of occasions I have moved include files, or commented out what looked like a needless include and ended up opening a Pandora's box. This should not be, and I intend to clean up the mess.

Guidelines

Here they are up front, I will explain and dicuss a bit below.

gcc and include files

There is an amazing amount of confusion and double talk (and poor documentation) about how this works. It is "implementation dependent", so it is important to be clear that we are talking about gcc here.

An include with a filename in quotes ("") first looks for the file in the same directory as the one containing the file being compiled. Calling this the current directory is bogus and confusing because the compiler may have been invoked in some other directory and may have located the source file in some way. For example when I compile this USB source I use options like this:

-o usb_core.o -c driver/usb_core.c
If an include with quotes doesn't find the file in the same directory as the one being compiled, it will then search a list of other directories.

An include with angle brackets is intended for "system headers" and is the same as the above, except that it does not first look in the directory containing the file being compiled. It goes immediately to the list and only uses the list.

What about "the list"? You can add to the list using the -Idir compiler command line option. This adds to the start of the list, but for includes with quotes, it will always look in the directory containing the file being compiled first. You can get rid of the list entirely using -nostdinc.

On the system I am now working on, typing cpp -v shows me: #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-redhat-linux/14/include /usr/local/include /usr/include End of search list.

See this link:

Note that there can be separate search paths for quotes and angle bracket searches. Using the -Idir option adds to the start of both. If you want to add to just one or the other, it is possible, see the link above.

Analysis of the existing code

Having include files include other include files may seem like a way to deal with dependencies. However, it is lazy and leads to big trouble. It hides bad design, as well as dependencies that later show up and bite you.

I have seen projects that have one include file that includes all others. Then every C file in the project includes that master include file and you are done. I find it hard to buy into this, though it certainly does work. I like a middle approach. I may have a master include file that has carefully selected things that apply to the whole project.

I try to keep my include file references "local". This is a lot like the benefit of encapsulation in OO programming. Only what needs to be exposed is exposed. For example, an include file might specify hardware register layout and bits in those registers. This file should only be referenced from within the "driver" directory.

The direction of dependencies is also important. For example, the current scheme has code in the library directory including "vcp/usbd_conf.h". This is wrong. The idea of course is that code in "library" needs to know about an "ops" structure with callbacks in vcp, but ....

The right way to do this is the other way around. In "library" the callback structure should be defined. This should be then referenced by code in vcp and set up properly. Done this way, suppose we replace vcp with hid or an msc class someday -- then these would do the same and no code in library would need to be changed.

I am now sold on the idea of using "" includes at all times unless actual system files (like stdio.h) are intended. I now understand that "" just says "look in the same directory as the file you are now compiling -- first". After that it searches the same list that <> includes do.

Along with using -I. at the base level where the Makefile is, or even -Imyinc if you commit the crime of having a directory to hold project global include files, this gets the job done.

a visit to hell

The present software has a myriad of unfortunate interdependencies. Hardware information is not only in the driver directory. The main thing is that the "regs" strcture is common knowledge to everyone, which certainly needs to change. As soon as you start commenting out includes within include files, these dependencies jump out and bite you. Fixing this will require significant work.

The reward in the case of the driver code will be that we will work our way towards "library" being able to support other hardware (like the F103 or even the RP2040).

Our immediate goal is to clean up the connections to the VCP directory so we can subtitute a TTY (as per CP2102 emulation) in lieu of the CDC/ACM/VCP we currently have.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org