Home > On-Demand Archives > Workshops >
MCU Driver Design Techniques
Jacob Beningo - Watch Now - EOC 2022 - Duration: 02:02:06
Driver design is still a critical need in nearly every system whether it’s to improve access to a microcontroller peripheral or interface to an external chip. Designing drivers that meet performance, scalability, and reuse requirements can be challenging. In this workshop, we will explore various driver design techniques and walk through the design of both “internal and external” drivers on a microcontroller-based system.
Topics covered in this workshop:
- Characteristics of good drivers
- The software stack-up
- API Design
- Peripheral Driver Design and Implementation
- External device driver design and implementation
- C/C++ Driver Techniques
This workshop is designed to be interactive. There will be generic hands-on examples that can be applied to nearly any microcontroller-based development board.
Thanks, Javi the comment! I don't use POSIX layers too often, but I definitely agree that it can be very useful. I'll give this topic some thought for a future talk at the conference, or in some other form. Thanks!
Any advances on doing HAL POSIX-style?
We see a huge possible advantage of going that route. Especially with upcoming switch from baremetal to RTOS and later Linux in our project.
You can. I've seen several companies adopting POSIX for that same flexibility.
I've meant my question more about this part of your original reply :)
"I'll give this topic some thought for a future talk at the conference, or in some other form."
Ah. I've not gotten around to looking at the details myself. So I don't have much to contribute on that part of it at this time. Sorry!
Timeless topic, indeed :)
We are exactly at the point in our project where we switch from one hardware SPI/UART pair to another one. And it's a pain. First, the register maps are absolutely different, but that's okay, we will hide it under low-level drivers. Second, old SPI has some modes of operation that new one doesn't. Third, new SPI has much more variations in interrupt handling: more status bits and variable "almost full/empty" threshold.
We're trying to come up with the same interface for both old and new hardware modules but I'm not sure it's achievable without some compromises (probably, ditching some specific modes of operation that are not supported by another hardware). Hence the questions, and a lot of them!
Once again I am confused at the separation between HAL and Driver.
What you show here and in your book for SPI:
SPI_Init
SPI_DeInit
SPI_TransferData
SPI_CallbackRegister
I would consider this as HAL in a sense that the code with these functions is what you switch to port your application to another hardware.
Now on a slides where different drivers are switched under the HAL, what is the HAL in that case?
Could you provide some examples what HAL above the drivers consists of? This looks like unchangeable part of the software.
Also, from the practical point of view, is it really achievable to get to a pack of interfaces that are well defined and stable so you really stop fiddling with them and able to switch drivers back and forth?
Or in reality, once starting this route of HALs, you're bound to support and modify all your old drivers to an interface you come up with for a newer ones.
And how to come around the fact that even simple things like SPI and UART vary greatly between different hardware implementations? How do you implement specific behavior? Do you make specific functions for one implementation and define them as NULL for another?
Jacob: Thanks for the comments and questions. Let me try to answer some of these for you.
Question: Now on a slides where different drivers are switched under the HAL, what is the HAL in that case?
Jacob: The HAL should be a header file that defines what the driver interface. The interface might or might not match the driver implementation functions.
Questions: Could you provide some examples what HAL above the drivers consists of? This looks like unchangeable part of the software.
Jacob: For an I2C driver, this might look something like the following:
typedef struct {
bool (Init)(uint32_t (Time_Get)(void));
bool (Write)(uint16_t const SlaveAddress, uint8_t const const Data, uint32_t const DataLength);
bool (Read)(uint16_t const SlaveAddress, uint8_t Data, uint32_t const DataLength);
bool (WriteRead)(uint16_t const SlaveAddress, uint8_t const const Data, uint32_t const DataLength, uint8_t * const DataOut, uint32_t const DataOutLength);
}I2C_t;
then you can assign whatever function you.
static I2C_t I2C0 = {.Init = I2C_Initialize,
.Write = I2C_Write,
.Read = I2C_Read,
.WriteRead = I2C_WriteRead};
If you have library functions for an MCU that don't match that pattern, you can create an implementation. For example, you can create your own mapped function that uses your MCU vendor drivers:
static bool I2C_Initialize(uint32_t (*Time_Get)(void)) {
assert(Time_Get != NULL);
SystemTime_Get = Time_Get;
SERCOM0_I2C_Initialize();
return true;
}
Question: Also, from the practical point of view, is it really achievable to get to a pack of interfaces that are well defined and stable so you really stop fiddling with them and able to switch drivers back and forth?
Or in reality, once starting this route of HALs, you're bound to support and modify all your old drivers to an interface you come up with for a newer ones.
Jacob: I think it is possible. It does depend on your company needs and projects though. I have interfaces that I've used for years with different clients and projects that haven't changed.
Question: And how to come around the fact that even simple things like SPI and UART vary greatly between different hardware implementations? How do you implement specific behavior? Do you make specific functions for one implementation and define them as NULL for another?
Jacob: You could have some functions that are just NULL. Typically, I define the core set of functionality for a peripheral. If one MCU has an extra feature like a speed boost, validation, etc, I extend my HAL interface in a separate header to cover those functions. They are then just used for that specific chip.
Thanks for explanations, Jacob!
This clarifies things a lot!
Great overview with good detailed examples. Thanks a lot!
Thanks!
Great talk! Do you ever implement a POSIX layer? I find that very useful. That way you can program your application using well known APIs that make your code very portable and you can run/test your application in any POSIX (semi)compliance system such as Linux, with all the tools and techniques that it provides.
I think that would be a great talk for next conference as well as setting/defining newlib's system calls and stubs.
Thanks!
Javi