Q: Hardware Abstraction Architecture

Do you have a question? Post it now! No Registration Necessary

Translate This Thread From English to

Threaded View
I'm trying to come up with a simple yet extensible method of writing
firmware with hardware abstraction. For example, suppose one has a two
different boards with some overlap in software functionality and similar
CPUs (same arch., different mfgr). Furthermore, these two boards have
different revision levels which cause some of the functions to do things
slightly differently. I've been talking to my workmates and we've been
oozing in the direction of the following:

i)  a board depends on its rev. and the CPU type
ii) an application depends on its board

So if I have board a and board be and an application foo then I'd have
foo.c, board_a.c, and board_b.c. Now foo.c would have foo application
generic functions while board_a.c and board_b.c would have functions with
identical names and signatures, prototyped in board.h, but each file would
have board-specific differences in implementation (see Fig. 1).
Furthermore, board_a,b.c file would have rev. difference files and CPU
primative files to isolate them from the containing the annoying rev.
based changes and minutia of CPU differences.

I just can't seem to get my arms around this problem though. Any help
appreciated.

Figure 1:          foo.c
                       #include "board.h"

                       init_board();
                       main();

                    board.h
                       #ifdef BOARD_TYPE == A
                       #    include "board_a.h"
                       #elif BOARD_TYPE == B
                       #    include "board_b.h"
                       #else
                       #    error "Bad board choice"
                       #endif

                       init_board();


                    board_a.h
                       #define BOARD_MEM_SIZE 1024
                       #define BOARD_CLOCK_HZ 100000000

                    board_a.c
                       #include "board.h"

                       init_board() { }


                    board_b.h
                       #define BOARD_MEM_SIZE 4096
                       #define BOARD_CLOCK_HZ 400000000

                    board_b.c
                       #include "board.h"

                       init_board() { }


Thanks,

--
- Mark ->
--

Re: Q: Hardware Abstraction Architecture

Oh, the linker is told, via the makefile, which .o file to use, e.g. if
the board is board A then link with board_a.o.

--
- Mark ->
--

Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

Instead of architecting the interface of an entire board (which I assume has
different devices), I suggest trying to abstract the interface of each of
the devices on the board as a "class".  This limits the scope of your
interface to just a particular device;  if, later on, other devices are
added/removed, the effort to change your interfaces is minimized.

If your interfaces are abstract enough, it is not necessary to supply
different header files for different boards.  You can use the same header
file, but with different implementations that hide the differences in
devices on the boards.

Regards,

Travis



Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

Those are good ideas.

One issue to look at is whether to have separate source files for the
different versions or to use conditional inclusion to differentiate.  I
lean towards the latter when they are fairly similar in order to use as
much "single source" definition as possible, so that a change which
would effect both versions is only changed in one place.  That advantage
needs to weighed against the cost of making the code harder to read with
lots of conditional code.  When I do use conditional code, I try to
avoid a lot of small conditional sections crammed together.

Thad

Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it
has
of
header

That's a good point, too.  At my workplace, there used to be a bias against
conditional coding because some people had some very bad experiences with
poorly designed conditional code.  However, using separate source files for
devices that were very similar caused an enormous amount of confusion and
inconsistency during maintenance.  (Some files would get new features,
others would not;  bugs would be fixed in some files, but not others;  etc.)
We are currently migrating towards a more sensible combination of
conditional code (when possible) and separate source (when necessary).

Regards,

Travis



Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

You appear to have duplicated more than was good for maintainability,
then.  As the saying goes, don't do that, then!

Quoted text here. Click to load it

Yep. Maybe the most sensible possibility would be: never use #ifdef
for anything else but to decide which platform-specific file to
#include, from a generic #include.  And never put code in a
platform-specific source file that doesn't absolutely have to be in
there.

It may be worthwile investigating OO-like techniques, most prominently
inheritance and polymorphism.  I.e. have a "default platform
abstraction" as a (kind of) class, and overwrite only those parts of
it that you must, for the individual variations.  Using macros to map
generic names to specific ones, you won't even need function pointers
for that, so no loss of efficiency.  It could look something like
this:

------- generic.h:
#ifdef USING_VARIANT1
# include "variant1.h"
#endif

#ifndef INCLUDED_FROM_GENERIC_C
 #ifndef actual_foo
   extern void generic_foo(void);
   #define actual_foo generic_foo
 #endif
 #ifndef actual_bar
   extern void generic_bar(void);
   #define actual_bar generic_bar
 #endif
#endif

------- variant1.h:
/* to keep generic foo(): do nothing */

/* to overwrite generic bar() with our specialized version: */
extern void variant1_bar(void);
#define actual_bar variant1_bar
------- ends

Same tricks can be applied to global variables.  Macros are #ifdef
tested on their own names, so definitions from variant1.h survive
through generic.h.  

--
Hans-Bernhard Broeker ( snipped-for-privacy@physik.rwth-aachen.de)
Even if all the snow were burnt, ashes would remain.

Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

I wholly agree with this.
 
Quoted text here. Click to load it

This trick seems to be unnecessary as the linker will be in control of
which variant .o file is used. E.g. if I have cpu_a_cache.c file and a
cpu_b_cache.c file, each would have functions with identical names and
signatures, such as void disable_icache(void); Since the linker will pull
one and only one of these .o files, there is no name conflict. The common
prototype goes in cpu_cache.h and consumers of the cache functionality
refer to the function disable_icache() without need of #define renaming.

--
- Mark ->
--

Re: Q: Hardware Abstraction Architecture
[...]
Quoted text here. Click to load it

But that scheme starts to break when CPU board c comes along, which
needs to have *another* function specialized, which a and b shared.

One idea behind my plan, which I failed to mention, is that having
more than one source file specific to a given variant creates another
maintenance nightmare.  I for one wouldn't like to have all of

    cpu_b_cache.c
    cpu_b_mymemcpy.c
    cpu_b_whatever.c
    cpu_b_foo.c

and so on.  You'ld end up with O(F*V) source files for F features to
be done on V board variants.  Rather collect as many hardware
dependencies as possible in a single file, cpu_b.c.

Quoted text here. Click to load it

To put it closer to your terms, I would suggest not only having a
cpu_cache.h, but also a cpu_cache.c, where cpu_cache.o would always be
built and linked in.  Or one could use a library of generic functions
and per-variant source files to override only what's needed.  One
could link to both cpu_a.o and generic.lib, in that order, such that
functions not defined by cpu_a.o would automatically be supplied by
generic.lib.  And if only some tiny detail changed, it may even work
to call the generic function from the specific version.

Even a somewhat automatic "updated all variants with this patch?"
check could be done: each time any of the generic library functions is
modified, you increment a #define'd version number in the header for
this function.  Every override implementation checks against that
number and refuses to compile until edited.  Doesn't help against
active stupidity, but avoids passive oversight.

--
Hans-Bernhard Broeker ( snipped-for-privacy@physik.rwth-aachen.de)
Even if all the snow were burnt, ashes would remain.

Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

I'd definitely have a board_n.c file that would initialize a given
hardware design properly based upon the CPU and its peripherals as well as
off-chip periperals.

Quoted text here. Click to load it

I see your point but I oft times the core doesn't change but the
periperals do (slightly sometimes). That is, the IBM405GPr and the 440GX
share some peripherals exactly but are different on others. It'd be nice
to have a family file for the 4xx series, say for the UART, and then use
it for all families of the 4xx CPU.

Quoted text here. Click to load it

I see taht cpu_cache.c file as an extra layer of wrapping if all the
cpu_X_cache.c files all have same-named functions. Unless there is some
common code shared by every cache on earth, I can't see putting anything
at the level of cpu_cache.c. I'd rather have the linker choose which .o
file to link against than have the C preprocessor re-defining function
names to a common name I guess.

Thanks for replies.

--
- Mark ->
--

Re: Q: Hardware Abstraction Architecture

Quoted text here. Click to load it

Another issue with collecting everything into only one source file is that
the application pays a memory penalty for peripherals that it links in but
doesn't use.  (Some -- but not all -- linkers can extract from a .o file
only those functions that are actually used.)  Maintenance may be more of a
hassle with several files, but the cost is usually outweighed by the benefit
of fitting a viable application into a smaller footprint.  Of course, if the
given platform has sufficient memory resources, then this is not an issue --
but I'm not lucky enough to work on such platforms.

Quoted text here. Click to load it

Whether cpu_device.c is necessary depends upon the nature of the given
device and its family.  I can think of a few cases where using such a scheme
would be beneficial.  In general, though, one can't always tell whether
using cpu_device.c provides real benefit until after one has developed
support for, or has anticipated the use of, several similar devices.  At
least initially, I would keep things simple.

Regardless of how device support is implemented, I think we all agree that
it is far more important to design a single set of clear interfaces for a
given device.  The underlying implementation (and its organization) can then
change whenever necessary without affecting an application that uses the
interfaces.

Regards,

Travis



Re: Q: Hardware Abstraction Architecture
hi,

 I'm using the Linux approach, based on the following consideration:


 every application has files that fall into four categories:


 1. architecture dependent code

    some microcontrollers, for example, need to deprotect some i/o
    registers before writing to the actual ping. PD9, port direction
register,
   of the M16C family is protected.


 2. application dependent code (board, peripherals like EEPROM, RTC,analog)

  two differente board designs will likely have different peripherals,
therefore
  different device drivers.


 3. compiler dependent code

    IAR compilers, for examples, offers some "intrinsic" functions to access
special
    registers _enable_interrupts( )


4. application code

   this is the business logic of the application and is mounted on top of the

   other three categories of code.


To make the application portable, separate the source files based on
the categaory where they fall.

Enrico


Re: Q: Hardware Abstraction Architecture
hi,

 I'm using the Linux approach, based on the following consideration:


 every application has files that fall into four categories:


 1. architecture dependent code

    some microcontrollers, for example, need to deprotect some i/o
    registers before writing to the actual ping. PD9, port direction
register,
   of the M16C family is protected.


 2. application dependent code (board, peripherals like EEPROM, RTC,analog)

  two differente board designs will likely have different peripherals,
therefore
  different device drivers.


 3. compiler dependent code

    IAR compilers, for examples, offers some "intrinsic" functions to access
special
    registers _enable_interrupts( )


4. application code

   this is the business logic of the application and is mounted on top of the

   other three categories of code.


To make the application portable, separate the source files based on
the categaory where they fall.

Enrico


Re: Q: Hardware Abstraction Architecture
    There are any number of "standard" answers to the problem. Atmel has
done a reasonably good job with their BSPs.
    Take a look at the software they have for the EB40,eb42,eb55 and eb62
(your numbers may vary). Most of it is in the same set of files with a
targets directory and a parts directory for the board specific things.
    ....JW

Mark A. Odell wrote:
Quoted text here. Click to load it


Re: Q: Hardware Abstraction Architecture
           snipped-for-privacy@embeddedfw.com "Mark A. Odell" writes:

Quoted text here. Click to load it

For me (and I suspect most other Forther's) the hardware is easy to
abstract out of the way. I am currently doing a project that uses the
same model micro-controller board as a plug-in processing facility
(which is part of a distributed control network) but where the attached
interfacing (one of three possible configurations) is quite different.
The controller is going to down-load the appropriate software from the
host system to perform its tasks.

I have also, in the past, run both ends of a communications protocol
as part of link testing on quite different processors (one was a PC
the other was the 6502 based BBC Micro system). I had to be sure that
I had both ends of the protocol running correctly (including timing)
on either machine in order to check the other part. The same code ran
on both machines with just the exception of the serial port assembly
coded portions. The abstracted serial inteface code had the interface
made common so that upper level code would behave the same on both
machines. Forth was a great help in this case too and I had both ends
of the protocol coded, tested and working properly within 8 hours.

You have indicated that there is enough information in your board/cpu
hardware to be able to determine differences in the machines. Use
such information in your coding to enable proper selection of the
appropriate software functionality. Keep it simple though or you will
regret it during testing later on.

--
********************************************************************
We've slightly trimmed the long signature. Click to see the full one.
Re: Q: Hardware Abstraction Architecture
Mark, here is a suggestion

what I do is have 3 major ''layers'' with different responsibilities

the HAL definition, the board-specific  implementation of the HAL and
the CPU specific iface and implementation that my app or OS will need
which cannot (and should not) be genericised into the HAL. I also do
my own C/C++ runtime.

Here is an example from a small ARM7 project using some old cogent
boards


Quickly:
    + The Hardware Abstraction Layer is defined in hal.h. In there we
      define interfaces to the board that can be generic to any board
      and CPU. This is the interface to which any embedded app or OS
          will call to access the board.

    + The implementation of these interfaces, since it is hardware and
          in fact board specific go into the board specific files, in
our
          case cogent.cpp/cogent.h; which thus implement hal.h
services.

    + Although the HAL can be portable across many devices and
          architectures, it is inevitable that in any embedded
development
      we will need to employ CPU specifics. In our case these are
      defined and implemented in arm.h and arm.cpp, as well as in
      crt0.S.

    + Basic initialisation and C/C++ runtime support lives in crt0.S


In the ''build'' stage I make sure that the hal.h interfaces that are
used from my app/RTOS are
implemented by cogent.cpp/cogent.h and that the ARM710 specific
ifnterfaces are implemented in ARM.cpp.

that way the next time i port my app and/or RTOS to another platform
I'll have to redo the board specific implementations of the interfaces
defined in hal.h and if needed the ones in ARM.h.

As you understand you can make it even more fine grained, but there is
always a line _you_ need to draw based on your experience in ports
needed etc. My advice start from something small like i propose here
and when you know more then do more ;-)

I use strict  C++ but you can do this of course with C :-)

Also avoid #ifdefs etc as much as possible for such work. I put all
constants for the board in the board-specific header only (e.g
cogent.h) so you know always where to look. any board specific linking
happens where it belongs at link time ;-)

the other thing you may want to do which is always good (at least for
me) is to separate the drivers in 2 layers. the  Logical and the
Physical. You'll find that you can reuse  the logical across boards
and architectures and you'll only have to redo the physical. Trust me
this saves alot of time.

cheers
NN


Quoted text here. Click to load it

Re: Q: Hardware Abstraction Architecture
snipped-for-privacy@yahoo.com (Nick) wrote in

Quoted text here. Click to load it

[snip]

Thanks for the excellent reply. I appreciate your time and info.

--
- Mark ->
--

Site Timeline