Close

System Design

A project log for commonCode (not exclusively for AVRs)

A shit-ton of things that are useful for a shit-ton of projects. (and, Think 'apt-get' for reusable project-code)

eric-hertzEric Hertz 08/17/2015 at 16:120 Comments

Each commonThing typically has three files: thing.h, thing.c and thing.mk (represented as a whole by the green boxes in the following diagrams).

The commonCode system handles automatic inclusion of nearly all aspects of each commonThing. E.G. for a simple test program for the 'heartbeat' commonThing, many other commonThings are included: hfModulation (which handles modulating/fading the LED), tcnter (which handles timing and is useful for many other purposes), and timerCommon (which handles the interface with the AVR's timer peripherals).

When using defaults, main.c can be as simple as:

#include _HEARTBEAT_HEADER

int main(void)
{
    heart_init();

    while(1)
    {
        heart_update();
    }
}

Optionally, the following can be added to the while() loop:

static int blink = 0; //0 = fading in and out, not blinking

if(heart_getButton())
    heart_setBlink(blink++);

Since heartbeat is a good starting-point for (and useful in) most projects, its “test” code is a great place to start any project. Its 'makefile' contains all the necessary defaults and detailed explanations for each configuration option.

Note that heartbeat.h contains a usage-example, as shown above.

In the following examples, each of the additions are shown assuming you've started with the heartbeat test application.


Addition of polled_uat (the bit-banged universal-asynchronous-transmitter) is as simple as adding the following lines to the original 'heartbeatTest' makefile:

VER_POLLED_UAT = 0.40
POLLED_UAT_LIB = $(COMDIR)/polled_uat/$(VER_POLLED_UAT)/polled_uat
include $(POLLED_UAT_LIB).mk

And this optional line defines the baud-rate:

CFLAGS += -D'BIT_TCNT=((TCNTER_SEC/9600))'

This process may seem ugly/complicated, but it is thoroughly standardized throughout the commonCode system, so that adding another commonThing, regardless of its dependencies, is as simple as adding the first three lines, as appropriate. Doing-so includes several pieces of each commonThing, including: a makefile snippet which adds the commonThing to SRC for compilation, creates a macro for the #include statement which contains the proper version, (and, again, handles inclusion of dependencies, if not already included).

Then in the previous heartbeatTest's main.c add the following three lines in their respective locations (note the argument: 0. There can be multiple puats!):

#include _POLLED_UAT_HEADER_
puat_init(0);
puat_update(0); 

(and optionally: puat_sendByte(0, byte);, etc. as-desired).


Again, polled_uat.h contains a basic usage-example showing these lines and where they belong.

That's it!

To make it a little bit more sophisticated, the following adds transmission of a Period '.' once every second (in the while() loop):

static tcnter_t lastTime = 0;

if(tcnter_isItTime(&lastTime, TCNTER_SEC)
    puat_sendByte(0, '.');

Note that tcnter_update() isn't called in the while() loop, because tcnter and heartbeat are both so commonly-used, tcnter_update() is by default automatically included in heart_update(). (This like many such defaults, can be changed and/or disabled by options in the makefile).



Now, let's add the polled_uar (the bit-banged universal-asynchronous-receiver):

polled_uar is another commonThing, so it's added in the same way as polled_uat (and, indeed heartbeat). Add these lines to the makefile:

VER_POLLED_UAR = 0.40
POLLED_UAR_LIB = $(COMDIR)/polled_uar/$(VER_POLLED_UAR)/polled_uar
include $(POLLED_UAR_LIB).mk 

(BIT_TCNT, defined earlier, also applies to polled_uar)

And add these lines, in their appropriate locations, to main.c:

#include _POLLED_UAR_HEADER
puar_init(0);
puar_update(0);

The polled-uar, of course, has the ability to receive data, so something needs to be done with the data it receives, so to implement 'echo' you can add the following to the while() loop, after puar_update(0):

if(puar_dataWaiting(0))
    puat_sendByte(0, puar_getByte(0));

Conclusion:

As you can see, this standardized methodology is used throughout the commonCode system to include commonThings assuring only those necessary pieces get compiled into your application. Multiple versions of the same commonThings can exist on your development system, allowing improvement of one without interfering with another project being developed concurrently. Yet, well-tested/implemented bug-fixes, etc. can be implemented such that they trickle-down to all the projects which make use of that version of that commonThing. Options for each commonThing allow for sophisticated control over your project's implementation... These allow for speed-improvements, code-size reduction, control of things like baud-rate which will never change during run-time... Some specific examples include: changing the tcnter source to another timer-source (rather than the default AVR timer0), changing the heartbeat to use a different pin (rather than the AVR's default MISO pin), changing the baud-rates, pin-assignments, and/or number of puar/ts, and much more. These options are clearly explained in the respective commonThings' test-code's makefile. No changes are necessary to the commonThing's code itself, all options are implemented via project-specific configuration-options (usually added to the project makefile).

You can imagine, it gets significantly more complicated as the commonThings become higher-level. E.G. motion-control (with a motor) has many factors: Is speed-ramping used to accelerate/decelerate the motor(s) as they move from one position to another? The inclusion-tree can become quite complicated! Ramping requires sineTravel, which requires sineTable, positioning requires coordStuff, xyTracker, gotoPosition which requires holdPosition... these can all be included automatically by adding the three-lines in your makefile for 'gotoRamped', the appropriate gotoRamped_init() and gotoRamped_update() functions to main.c, (and, of course, your actual gotoRamp_setup() calls, which initiate the motion).

TODO:

It gets a bit more complicated when considering the lower-level/hardware details, such as: Positioning a motor... what kind of position-detection system does it use, a quadrature encoder? Is that quadrature-encoder connected directly to I/O pins, or does it go through a quadrature-decoder chip? What kind of modulation is used to control the motor power? What are the specific pin-requirements for the motor-driver/H-Bridge chip? These sorts of specifics could be handled by a driver when using an operating-system, but we're working “bare-metal...” so there needs to be some sort of standardized interface for these sorts of things. Similarly, these things don't change during runtime, so it really doesn't make sense to have, say, a motion_init() function which takes pointers to motor-control functions. Function-pointers, function arguments, etc. weigh heavily on resources! Especially if, in the end, the entire call could be boiled down (optimized, by the compiler) to nothing more than a couple lines of code, e.g. “writePin()” (which may boil down to nothing more than a single assembly instruction!)

These sorts of things have definitely been considered in much of commonCode. And as commonCode becomes more architecture-independent, it still attempts to retain these levels of optimization which are ideal for embedded architectures.

Discussions