Q: Hardware Abstraction Architecture

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 ->
--
Reply to
Mark A. Odell
Loading thread data ...

"Mark A. Odell" wrote in news:Xns9453AD8DE154CopyrightMarkOdell@130.133.1.4:

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 ->
--
Reply to
Mark A. Odell

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

Reply to
Travis Breitkreutz

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

Reply to
Thad Smith

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

Reply to
Travis Breitkreutz

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

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 (broeker@physik.rwth-aachen.de)
Even if all the snow were burnt, ashes would remain.
Reply to
Hans-Bernhard Broeker

Hans-Bernhard Broeker wrote in news:brprhm$dkv$ snipped-for-privacy@nets3.rz.RWTH-Aachen.DE:

I wholly agree with this.

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 ->
--
Reply to
Mark A. Odell

Mark A. Odell wrote: [...]

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.

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 (broeker@physik.rwth-aachen.de)
Even if all the snow were burnt, ashes would remain.
Reply to
Hans-Bernhard Broeker

Hans-Bernhard Broeker wrote in news:brpvl2$ifn$ snipped-for-privacy@nets3.rz.RWTH-Aachen.DE:

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.

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.

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 ->
--
Reply to
Mark A. Odell

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.

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

Reply to
Travis Breitkreutz

Reply to
John Marland

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.

--
********************************************************************
Paul E. Bennett ....................
 Click to see the full signature
Reply to
Paul E. Bennett

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

Reply to
Nick

snipped-for-privacy@yahoo.com (Nick) wrote in news: snipped-for-privacy@posting.google.com:

[snip]

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

--
- Mark ->
--
Reply to
Mark A. Odell

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.

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

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

  1. compiler dependent code

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

  1. 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

Reply to
Enrico Migliore

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.

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

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

  1. compiler dependent code

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

  1. 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

Reply to
Enrico Migliore

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.