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