Reply by Michael N. Moran●December 1, 20062006-12-01
Arlet wrote:
> Michael N. Moran wrote:
>> This is just abstraction at a different layer. This thread is about
>> hardware abstraction, and while LED's and motors are hardware in a
>> more generic sense, hardware abstraction is generally about the
>> peripherals used to control these devices.
>
>
> True. But my point is that I find it more productive to put the
> abstractions at a slightly higher layer rather than trying to
> abstract the low-level peripherals typically found on
> microcontrollers.
Abstractions are driven by the needs of the upper layers and
independent of their implementation. In this case, bit-banged
I/O applications are quite common and can be quite independent
of the specific hardware implementation.
For example, a bit-banged I2C bus driver doesn't care about
*how* the port is configured, how the I/O pins are read
or written, or which pins are used. The driver simply wants
to be given a reference to the two pins it needs (SDA + SCL).
The *driver* then can be reused on any hardware platform that
implements the abstract pin interface, and implementing that
interface for a new platform is "trivial".
> If you want a true peripheral abstraction, you'll be restricted to a
> common subset of all controllers, which will be very small. If you're
> going to extend your abstraction with controller specific features
> where they are supported, and you use those in your application,
> you'll still have to rewrite your application when porting to less
> capable hardware.
I don't consider this an abstraction. Rather, I consider this
as writing a low-level driver. The idea being that a particular
peripheral driver can be re-used on another micro-controller/SOC
that uses the *same* peripheral. In this case, we simply have
a software representation of the hardware function without any
abstraction involved. However, it is possible to add an
implementation of a common abstraction to such a driver.
>> Absolutely. However, the more experienced one becomes, the easier
>> it is to recognize and plan for future needs, reducing the need to
>> re-write.
>
> In my case, it's been the opposite. :) Coming from an academic CS
> background, I had been taught to build abstractions, and that's what
> I tried. After some years of real life experience, I discovered that
> using fewer of them actually saved time, and made the code more
> readable, as well as more efficient.
In my experience, many/most embedded programmers (such as myself)
are hardware guys that learned about software from the bottom/assembler
up. Many of us had no formal training in CS. I find that having
good CS skills (and even knowing what abstraction *is*)
is rare amongst our breed.
CS majors get more exposure to techniques for managing the
complexity of large software projects. However, they are not
exposed to many low level issues that are common in embedded
programming, and often learn techniques (playing fast and
loose with dynamic memory) that are difficult to un-learn
because they are so endemic and easy-to-use.
That's what I love about this embedded game ... there's
always something new to learn and there's sooo much room
for improvement :-)
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by ChrisQuayle●November 30, 20062006-11-30
Vladimir Vassilevsky wrote:
> Hello All,
>
> I am looking for a concept for abstracting of a hardware. It is desired
> that the concept should be convenient, clear, consistent, logical and
> pretty universal.
>
> My goal is developing more or less universal HAL and application
> framework for automotive, control and instrumental. It should not be
> tied to a particular OS, CPU or board.
>
> Can you recommend a good reading on that. I am not very interested in
> the specifics of a particular OS or board, but in the good concepts and
> ideas.
>
>
> Vladimir Vassilevsky
>
> DSP and Mixed Signal Design Consultant
>
> http://www.abvolt.com
Interesting. It's amazing how often the same kinds of problems crop in
embedded work. The underlying hardware may change, but the device
functionality remains the same. Have been trying to develop portable
embedded libraries for several years, with some success, though there is
much still to do. What seems to work for me is:
First and formost, take a system global view of the design. That is,
think of it a series of layers, with `applications' at the top,
protocols etc in the middle and hardware drivers at the bottom. Make all
layers communicate through defined interfaces, with no globals, all data
passing through the layers. Keep all low level hardware related code
to an absolute minimum and blackbox / isolate all such code and
associated data into dedicated modules with no external access other
than through a defined call interface. Keep all data tranfer at this
level transparent. That is, raw bits and bytes. Strictly limit
functionality at low level. Decide on a minimal set of access functions
for a device class and build the more complex functionality into higher
system layers. If you do it right, when the hardware changes, you find
that you only need to rewrite small slips of hardware specific code to
get the whole system up again. Write strict ansi C, with no clever
stuff, perhaps even a subset, to ensure portability. Stick all tested
code modules into function class based libraries, to grow a tested,
robust code base, so you don't continually have to reinvent the wheel
for every new project.
None of this is new - it's based on the model used for many desktop and
server os's, but it's just as applicable to small embedded designs. It
does require more effort and some say that the runtime overhead is
unacceptable, but even on 8051 class devices, it's never been a problen
here.
Chris
Reply by Arlet●November 30, 20062006-11-30
Michael N. Moran wrote:
> > Sure, if you have a huge application, where all you need to do is turn
> > a LED on or off once in a while. In that case I can just make a
> > led_on() and led_off() function, each with a one line assignment to a
> > GPIO port register. Similarly, I can make motor_on() and motor_off(),
> > and things like that. Put them all in a "io.c" file, which makes it
> > trivial to port later.
>
> This is just abstraction at a different layer. This thread is
> about hardware abstraction, and while LED's and motors are
> hardware in a more generic sense, hardware abstraction is
> generally about the peripherals used to control these devices.
True. But my point is that I find it more productive to put the
abstractions at a slightly higher layer rather than trying to abstract
the low-level peripherals typically found on microcontrollers. If you
want a true peripheral abstraction, you'll be restricted to a common
subset of all controllers, which will be very small. If you're going to
extend your abstraction with controller specific features where they
are supported, and you use those in your application, you'll still have
to rewrite your application when porting to less capable hardware.
If you put the abstraction on a more functional level, such as an LCD
display driver, you know the hardware has proper support (otherwise you
wouldn't have picked it for a design), so porting the code is at least
possible, and usually not that difficult.
> > Adding a separate generic GPIO layer inbetween
> > only makes things harder to design.
>
> We've spent a *lot* of time talking about GPIO pins, which were
> originally used as an example :-) However, I find that having
> a good foundation makes the application *easier* to design.
Agreed, it's just an example, but for me it's a good example of what
not to abstract.
> > I agree with you that "it depends" and "ymmv". Abstractions in general
> > make sense, and can make code reuse easier. However, it's easy to get
> > carried away. My motto is "don't abstract until you have more than one
> > case". So, the first time I just make a simple, concrete
> > implementation. If I find it needs to be ported and maintained on
> > multiple hardware platforms, I may decide to rewrite it into something
> > more abstract, based on actual needs.
>
> Absolutely. However, the more experienced one becomes, the easier
> it is to recognize and plan for future needs, reducing the need
> to re-write.
In my case, it's been the opposite. :) Coming from an academic CS
background, I had been taught to build abstractions, and that's what I
tried. After some years of real life experience, I discovered that
using fewer of them actually saved time, and made the code more
readable, as well as more efficient.
Reply by Michael N. Moran●November 30, 20062006-11-30
Arlet wrote:
> Michael N. Moran wrote:
>>>Frankly, I don't see where providing an abstraction for a single GPIO
>>>pin is going to make life easier.
>>
>>It enables an *application/module* which uses the abstract interface
>>to be reused without changing the application/module. In this
>>particular case, the a "bit banging" application of some sort.
>
> Sure, if you have a huge application, where all you need to do is turn
> a LED on or off once in a while. In that case I can just make a
> led_on() and led_off() function, each with a one line assignment to a
> GPIO port register. Similarly, I can make motor_on() and motor_off(),
> and things like that. Put them all in a "io.c" file, which makes it
> trivial to port later.
This is just abstraction at a different layer. This thread is
about hardware abstraction, and while LED's and motors are
hardware in a more generic sense, hardware abstraction is
generally about the peripherals used to control these devices.
> Adding a separate generic GPIO layer inbetween
> only makes things harder to design.
We've spent a *lot* of time talking about GPIO pins, which were
originally used as an example :-) However, I find that having
a good foundation makes the application *easier* to design.
> Sometimes it makes it even harder
> to understand what exactly is going on.
True that it may make it more difficult to discover
implementation details, but it actually makes the client
code application easier to read in the same way that
a subroutine makes an application easier to read.
> For more uncommon uses of a GPIO port, such as a 3 bit control + 8 bit
> data interface used to control a LCD display module, I would just make
> an LCD abstraction, and have it directly access the ports.
Abstractions don't access ports, specific implementations of an
abstract interface do. Nit-picky I know ... ;-)
> Quite possibly, this LCD abstraction may be the *only* place in the
> application where I need to write 8 bits in parallel to a GPIO port,
> which can be implemented by a 1 line statement similar to:
>
> PORTA = lcd_data;
>
> Which, on some platforms, translates directly to a single special ASM
> instruction designed to access IO registers.
>
> Compare this to the time to design a "8 bit parallel GPIO abstraction",
> and the effort to port *that* from platform to platform.
Sure. I hope I've not given the impression that all things
can/should be abstracted.
> I agree with you that "it depends" and "ymmv". Abstractions in general
> make sense, and can make code reuse easier. However, it's easy to get
> carried away. My motto is "don't abstract until you have more than one
> case". So, the first time I just make a simple, concrete
> implementation. If I find it needs to be ported and maintained on
> multiple hardware platforms, I may decide to rewrite it into something
> more abstract, based on actual needs.
Absolutely. However, the more experienced one becomes, the easier
it is to recognize and plan for future needs, reducing the need
to re-write.
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by Arlet●November 30, 20062006-11-30
Michael N. Moran wrote:
> > What if 8 of those GPIO pins are available on the same port, and I need
> > to access them simultaneously, for instance to access an 8 bit
> > peripheral ?
>
> Again, that can be offered via a different abstraction. For example
> an 8-bit wide port.
>
> Not *all* uses, however, can be abstracted. You only wish to abstract
> the common uses.
I find myself doing a lot of uncommon stuff. Besides, in 90% of the
time, a specific microcontroller is designed into a solution based on
its unique capabilities, so there's only a single hardware platform to
worry about.
> > Frankly, I don't see where providing an abstraction for a single GPIO
> > pin is going to make life easier.
>
> It enables an *application/module* which uses the abstract interface
> to be reused without changing the application/module. In this
> particular case, the a "bit banging" application of some sort.
Sure, if you have a huge application, where all you need to do is turn
a LED on or off once in a while. In that case I can just make a
led_on() and led_off() function, each with a one line assignment to a
GPIO port register. Similarly, I can make motor_on() and motor_off(),
and things like that. Put them all in a "io.c" file, which makes it
trivial to port later. Adding a separate generic GPIO layer inbetween
only makes things harder to design. Sometimes it makes it even harder
to understand what exactly is going on.
For more uncommon uses of a GPIO port, such as a 3 bit control + 8 bit
data interface used to control a LCD display module, I would just make
an LCD abstraction, and have it directly access the ports. This would
require about 10 lines of h/w specific code, allowing optimal use of
the hardware capabilities. Still it would be trivial to port to
completely different hardware by just changing those 10 lines.
Quite possibly, this LCD abstraction may be the *only* place in the
application where I need to write 8 bits in parallel to a GPIO port,
which can be implemented by a 1 line statement similar to:
PORTA = lcd_data;
Which, on some platforms, translates directly to a single special ASM
instruction designed to access IO registers.
Compare this to the time to design a "8 bit parallel GPIO abstraction",
and the effort to port *that* from platform to platform.
I agree with you that "it depends" and "ymmv". Abstractions in general
make sense, and can make code reuse easier. However, it's easy to get
carried away. My motto is "don't abstract until you have more than one
case". So, the first time I just make a simple, concrete
implementation. If I find it needs to be ported and maintained on
multiple hardware platforms, I may decide to rewrite it into something
more abstract, based on actual needs.
Reply by Michael N. Moran●November 29, 20062006-11-29
Rene Tschaggelar wrote:
> Michael N. Moran wrote:
>
>> Vladimir Vassilevsky wrote:
>>
> [snip]
>
>>
>> To use a GPIO pin as an example, a bit-banging application can
>> generally perform two operations:
>>
>> setHIGH()
>> setLOW()
>
>
> A matter of the language.
> There are some that want you write PortA &= ~bit(1<<5);
> while others allow to define TxEn[@portA,5]:bit;
> and use it as TxEn:=1;
That's a part of the implementation, not an abstract
interface. Its an implementation detail. Of course,
I'm a C++ bigot ;-)
>>
>> bool readAtPin()
>> bool readPinRegister()
>
>
> Also a matter of the language. The awkward language
> I'm talking about was never extended to cover
> controllers too. If the language is simply not readable,
> a set of API which is made to enhance readability and is
> basically a macro or a to-be-optimized-away function call
> is not really efficient.
As I've said in other posts in this thread, there is a
trade-off between abstraction (code reuse) and efficiency.
Without sufficient resources (and complexity) it doesn't
make sense to try to abstract too much. However, Shift-Right
Technologies has done a pretty thorough job using macros
in their XMK (eXtreme Minimal Kernel) and other code.
I call it "macro-hell" ;-)
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by Michael N. Moran●November 29, 20062006-11-29
Vladimir Vassilevsky wrote:
>
>
> Michael N. Moran wrote:
>> To use a GPIO pin as an example, a bit-banging application can
>> generally perform two operations:
>>
>> setHIGH()
>> setLOW()
>
>
> Actually, there should be a 3-level interface:
>
> Application: set_something_meaningful(BOOL param)
> Hardware abstraction: set_port_pin(BOOL param)
> Low level: PORTA.1 = param
Actually, I don't limit the number of "levels".
I generally prefer to have separate operations
rather than passing parameters as you've done here.
The operation names are more explicit.
>> The initialization, that selects mutiplexed function routing,
>> direction, open-drain operation and other configuration belongs
>> to another interface that is most likely not abstract.
>
>
> Initialization/deinitialization is a big potential source of errors. The
> initalizations should be consistent with the application interface.
Sure it is. However, the portion of the application that initializes
a mode of operation is not the same as the part of the application
that *uses* the device. Initialization usually only happens
infrequently. The part of the application that uses the device
in a particular mode after initialization is more portable by
depending upon a more abstract narower interface.
>> IMHO, operations which are not available on a particular
>> piece of hardware should not appear in the abstraction
>> supported by that hardware.
>
>
> Yes. And the attempt to use the unsupported function should be detected
> at the compile time.
Yup.
> For example, a having the
>
>> ability to read the value of a GPIO at the pin or as
>> a read-back of the register associated with the pin.
>> Some GPIO implementations have one or the other and
>> some have both. Thus there are two possible operations:
>>
>> bool readAtPin()
>> bool readPinRegister()
>>
>> A GPIO implementation that supports only one of these
>> should not have the other available in its interface.
>> The result is there is no error condition if an application
>> calls an "unsupported" operation.
>>
>> All of this falls under the "Interface Segregation Principle"
>> The result is several very narrow abstract interfaces rather
>> than a single abstract interface. Though this suggests MI,
>> composition is generally better suited for the implementation.
>>
>
> :) We are growing out of this concept at this moment.
> Initially it was copy/paste code reuse, then it came to a set of
> functions and macros, and finally it was realized that a new paradigm is
> required.
You are growing out of what concept? Copy/paste code reuse?
Or the "Interface Segregation Principle"?
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by Michael N. Moran●November 29, 20062006-11-29
Arlet wrote:
> Rocky wrote:
> Sure, but if you have a "uart" abstraction, and an "I2C" abstraction,
> and you build those directly on the hardware ports, you're not using a
> GPIO abstraction, which was my point.
You certainly *can* have a UART or I2C abstraction that has no
dependency on the the underlying implementation. And the
implementation of those abstractions may be bit banged, which
may use a GPIO abstraction.
> Abstracting a UART or I2C makes sense. The interface is fairly small,
> and relatively well-defined.
Again, different parts of a UART may be abstracted to different
interfaces, and not *all* UART implementation necessarily need
to implement all interfaces. The most obvious abstract interface
for a UART is an octet stream interface. UNIX loves this particular
abstract interface. The same interface is used for e.g. TCP/IP sockets.
> For other interfaces, such as GPIOs, but also timers, it's virtually
> impossible to define a generic model that can be ported to all kinds of
> hardware, but still allows you to exploit all the little optimizations
> and features that the MCU may have.
When it comes down to optimization, all bets are off. The rule is
to optimize when necessary, not *always*. Clearly, however, if your
operating at the limits of the hardware, your choices are somewhat
limited.
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by Michael N. Moran●November 29, 20062006-11-29
Arlet wrote:
> Michael N. Moran wrote:
>>The initialization, that selects mutiplexed function routing,
>>direction, open-drain operation and other configuration belongs
>>to another interface that is most likely not abstract.
> What if I need to dynamically change pin properties ? For instance,
> bitbanging an I2C port may involve changing SDA/SCL from input to
> output on some architectures.
If this is required by the upper layer application, and the hardware
is incapable of providing the function, then the application can't
use the hardware.
If one can abstract this functionality in such a way that it can
be used by various clients then by all means, create such an
abstraction. Though I would not add this to the simpler abstraction,
but instead put it in a separate interface.
> What if 8 of those GPIO pins are available on the same port, and I need
> to access them simultaneously, for instance to access an 8 bit
> peripheral ?
Again, that can be offered via a different abstraction. For example
an 8-bit wide port.
Not *all* uses, however, can be abstracted. You only wish to abstract
the common uses.
> What if precise timing is required, for instance bitbanging a UART
> peripheral ?
An engineer does what an engineer has to do :-)
There is no *requirement* that the abstraction be used, however,
it aids in reusing the code if you can use the abstraction.
> Frankly, I don't see where providing an abstraction for a single GPIO
> pin is going to make life easier.
It enables an *application/module* which uses the abstract interface
to be reused without changing the application/module. In this
particular case, the a "bit banging" application of some sort.
> There are just too many variations in
> hardware capabilities, and application requirements. Even if you could
> design a model, it would probably be slow and bloated, and a much
> bigger hassle than just accessing the hardware ports directly.
It all depends on your goals and constraints. One mans bloat is
another mans time saving device. Hassle is a function of experience
in the process. I find the process of maintaining broken variants
of the same function more ... laborious. ymmv.
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1
Reply by Michael N. Moran●November 29, 20062006-11-29
Rocky wrote:
> Michael N. Moran wrote:
>>IMHO, operations which are not available on a particular
>>piece of hardware should not appear in the abstraction
>>supported by that hardware. For example, a having the
>>ability to read the value of a GPIO at the pin or as
>>a read-back of the register associated with the pin.
>>Some GPIO implementations have one or the other and
>>some have both. Thus there are two possible operations:
>>
>>bool readAtPin()
>>bool readPinRegister()
>>
>>A GPIO implementation that supports only one of these
>>should not have the other available in its interface.
>>The result is there is no error condition if an application
>>calls an "unsupported" operation.
>
> If the higher level s/w requires a readPinRegister would it not be
> better to implement it for example by keeping a copy that is written to
> RAM?
Better? If it is required, then some lower layer must provide
this. Whether or not it should be provided by the lowest layer
is another question.
> When using a PIC it is sometimes necessary to do this anyway to prevent
> corruption of output pins that change slowly due to loading. One alway
> does the BSF or BCF on the RAM copy and then writes that out to the
> port.
If I were using a PIC, then it is unlikely that I would do
any significant hardware abstraction. I usually work with more
"resource full" processors ;-)
As for the use of shadow registers, that's a common solution
to a hardware ... shortcomming ;-)
--
Michael N. Moran (h) 770 516 7918
5009 Old Field Ct. (c) 678 521 5460
Kennesaw, GA, USA 30144 http://mnmoran.org
"So often times it happens, that we live our lives in chains
and we never even know we have the key."
The Eagles, "Already Gone"
The Beatles were wrong: 1 & 1 & 1 is 1