Program Structure Proposal

Sharing is caring. Please find attached a RobotC competition program structure that my team uses (the proposed template is a cleaned-up structural extract of the code they have converged to).

The central idea is a layered structure with a well-defined HAL (Hardware Abstraction Layer) at the bottom. Let’s look at the layers:

  • HAL (a C file with motor/sensor definitions and some defines/functions) abstracts out the hardware parts using mostly trivial, yet abstract accessors. It can hide how many motors you have on the drivebase and, thinking of it, a well-implemented HAL could even hide the fact that you’re sharing half of them with a “passive” MOGO lift. HAL is also the only file allowed to directly address the ports.
  • function-library contains the higher-level functions built on top of the HAL. Think of stuff like “drive this distance”, “turn 90deg left”,
    “lift the mogo asynchronously”. The idea is that the library should be generic enough to work the same way on different robot builds, as long as it gets well implemented HAL for each of them.
  • teleop contains the driver controls implementation, using the HAL and/or the function library as needed. Thanks to this structure, you can easily migrate your teleop implementation (which channel drives what) between different robots, hopefully with no or minimal modifications.
  • A set of auton-helper includes (more on these later)
  • a number of anton implementations (each, or a subset of them in a separate file). Parametrized if possible.
  • The main file that ties everything together.

Success story:
The team (experienced roboteers, but 1st year in VRC) started the season building a simple 4-bar and also had a clawbot to get some headstart in programming. Over the time they built up their library of functions, testing them both on the clawbot and the real robot
and then spent long grumpy hours refactoring. It took them time to retrofit the growing codebase into the HAL-based structure, while the builders progressed on RD4B design. Then, when the RD4B was field-ready (not finished yet, but good enough for some programming) , it only took writing new HAL (and commenting out the so-far unfinished mechanisms) and voila! The new robot immediately listened to the same joystick commands and better yet, the auton routines (like 22pt scoring) still worked!
All of that despite the fact that the new robot has the gyro mounted upside-down (intentionally for a few reasons) and the MoGo lift potentiometer on the other side (turning backwards and with a different working range), all easy to abstract in the HAL.

Part of the template assumes you have some form of an LCD program selection implemented that takes a list of strings and returns the selected index. It uses auto-generated dispatch code based on my discussion with @jpearman (all bugs are mine, all wits is James)

It also has a built-in helper for running a single auton (the one you’re currently working on) directly, skipping the above mentioned (but tedious during debugging) selection on LCD.

Implementation notes - auton files are .c, so you can directly run them with the auton helper.
Other files are .h so RobotC disables the run button (as you don’t want to run, say, a library, right?) - a lousy workaround for the lack of RobotC’s lack of the notion of a project.
HAL needs to be a .c file, unfortunately, as RobotC would also disable the Motor/Sensor setup button - also, whenever you need to edit the port setup, do it from the HAL file, not from the main.

Possible further extension: Testing - imagine you could easily simulate your drivebase having more friction on one side. How would your library routines behave? Would “drive straight” still drive straight? That’s now trivial to test - you have one single entry point to inject your “fault” into.

1 Like

And the attachement… Hopefully it will make it through this time…
Template.zip (5.94 KB)

1 Like

@nenik,

Thanks for the project structure. In my opinion, this is very versatile and well-organized. The project is a great boilerplate for most VRC teams. This decouples robot-specific code with the general-purpose and autonomous-specific code, and it looks well-designed for running autonomous programs both in and out of a testing environment. It is also well-documented in the files themselves, which helps a lot.

I wanted to contribute some thoughts to the project.

Firstly, a question about how the DISPATCH macro is implemented. What is CODE? is this a typo? that macro is apparently the only place the word CODE shows up, and it is unclear to me how this works, and I would appreciate a clarification.


#define DISPATCH(indexVar)                                      \
    int _index = indexVar;                                      \
    PROGRAMS(CODE)

Secondly,

Agreed.

Thirdly, Please consider the inclusion of an all-purpose PID structure and functions to use it with. PID controllers are so universally implemented by VRC teams in my experience. if this were to be a direct boilerplate for teams to start up a new ROBOTC project, then including a PID structure and some functions could be a helpful guiding presence along with the rest of the project. What comes to mind is some PID struct with this kind of interface via functions:


PID liftPid; // has its own kP, kI, kD, iCap, IAccumulator, etc...
pidInit(&liftPid, 0.1, 0.2, 0.3, 2); // initializes a PID controller.
power = pidUpdate(&liftPid, (target - getLiftHeight()), 20) // updates the controller, and returns a power output from the liftPid controller.
pidReset(&liftPid) // resets iAccumulator

Something like this would go a long way towards helping new teams get their functionality up and running. I would be happy to repost the project with a ‘pid.h’ or something of the sort. In my opinion, a PID controller implementation is “boilerplate” enough to be a worthy include, and, worse-case scenario, easily customizable / replacable anyhow.

Thanks again for providing a well-designed project structure for the VRC community.

No. See this thread.
The best way to explain this to students is to actually simulate the preprocessor on paper (or using copy/paste/search/replace). Let’s do it here while having just one program defined.
Our custom auto-programs.h define the macro PROGRAMS(X) as AUTO_PROGRAM_##X(“Sample”, autoSample(50)). The DISPATCH macro, when expanding PROGRAMS(CODE) will fill in the argument named X, so PROGRAMS(CODE) will first expand to AUTO_PROGRAM_CODE(“Sample”, autoSample(50)).
(Side note: ## is preprocessor concatenation operator)
But AUTO_PROGRAM_CODE is another macro (defined in program-macros.h) that will expand into the following snippet (reformatted for readability, no newlines from the preprocessor) for each entry:


    if (_index-- == 0) {
        autoSample(50);
    } else

With two programs defined, the full DISPATCH macro becomes:


    int _index = autonomousProgramIndex;
    if (_index-- == 0) {
        autoSample(50);
    } else
    if (_index-- == 0) {
        autoSample(60);
    } else
    ;

(notice how the last semicolon, the one behind last else comes from the initial macro invocation of DISPATCH(autonomousProgramIndex);)

BTW: You can always use real compiler to run the preprocess pass for you, such as:


gcc -E -o full.lst main.c

That would certainly be helpful, though easily pluggable with no interdependency with the rest of the project structure. As is, the template allows using whatever library for such a purpose. Or motivates the students to properly incorporate PID (which they’d copy/paste from somewhere anyway) into the library portion. Also, this template is an extract from my students codebase and they don’t have a full generic PID yet (they have few P-only or I-only usage-specific implementations in the library. My rule of thumb is, if you do something similar 3 times, refactor it into a reusable block).

@nenik ,

Thanks for the reply, the higher-level preprocessor usage here is something new to me. Very cool.

I agree, thanks for the explanation.