Abstracting Your Hardware With an AI-Generated HAL
Creating a hardware abstraction layer using artificial intelligence (AI) may help you streamline embedded software and hardware development.
March 12, 2024
At a Glance
- Effective prompts can help you develop the right code
- Consider starting with a digital input/output (dio) HAL
- You can also develop and improve interfaces, add error handling, generate unit test cases, and more
If you’ve worked with a microcontroller, you’re probably somewhat familiar with vendor-supplied hardware abstraction layers. A hardware abstraction layer (HAL) is a software layer that provides a high-level interface to hardware devices or peripherals, abstracting away the details of the hardware from the rest of the software application or system. The HAL enables software to interact with hardware through a consistent set of APIs (application programming interfaces), regardless of the underlying hardware specifics.
While many microcontrollers come with some form of HAL today, they are often designed in such a way that they don’t really provide you with flexibility. Sure, you don’t have to worry about low-level hardware interfacing, but your application code becomes tightly coupled to the provided HAL, making it difficult to pivot to other hardware.
In this post, I’ll show you a simple example on how to create your own HALs using your favorite GPT (Generative Pre-Trained Transformer). I’ll be using ChatGPT.
Principles for Effective Prompts
When you are using AI to generate code, you need to make sure that you follow a few best practices to get the best results. Just like with any machine that is being programmed, garbage in will give you garbage out. There are several principles that I recommend you follow when attempting to generate code with an AI:
Be specific and clear
Use technical language where appropriate
Provide context
Outline the desired logic or algorithm in pseudocode
Mention the programming language the result will be in
Specify the scope
Include examples or use cases
State any constraints
Iterate and refine
The more specific you are, the greater the chances that you’ll receive the desired output. Don’t expect a miracle though. You are going to have to iterate and refine what is produced by the AI. In fact, you may even at some point need to ditch the AI and perform the final adjustments yourself.
Preparing to Generate a Digital Input/Output HAL
A good first HAL to create for any project or example is a digital input/output (dio) HAL. It’s more commonly called GPIO, but I find that digital input/output is a little more specific. There are several reasons why dio is a good first interface to tackle such as:
It allows you to get all the pins on your microcontroller configured and doing something
You can create a “Hello Blinky” application to prove that it works
Standards like Arms CMSIS don’t include a GPIO or dio HAL, so even if you plan to use standardized HALs, you’ll need to create this one from scratch.
When you first get started, I recommend that you spend the first couple of prompts preparing the AI for what you want to do. For example, I would start by telling the AI that I don’t want any action yet, but that I’m going to be generating a dio hal in C that uses the following coding standard. I would then provide details about naming conventions, formatting, and so forth. In most cases, the AI can’t help but generate something, so it will usually analyze or repeat back what you’ve already told it. Do make sure that you provide it will information like:
Naming conventions
Formatting
Comment and documentation structure
Error handling preferences
How memory will managed
Optimizations like speed or code size
Etc.
It can also be helpful to provide examples. If you are using STM32 HALs you might provide an example of their HALs as a reference with the improvements you’d like to see. For this example, I provided an example from my book Reusable Firmware Development:
void Dio_Init(DioConfig_t const * const Config);
DioPinState_t Dio_ChannelRead(DioChannel_t const Channel);
void Dio_ChannelWrite(DioChannel_t const Channel, DioPinState_t const State);
void Dio_ChannelToggle(DioChannel_t const Channel);
void Dio_ChannelModeSet(DioChannel_t const Channel, DioMode_t const Mode);
void Dio_ChannelDirectionSet(DioChannel_t const Channel, PinModeEnum_t const Mode);
void Dio_RegisterWrite(uint32_t const Address, TYPE const Value);
TYPE Dio_RegisterRead(uint32_t const Address);
void Dio_CallbackRegister(DioCallback_t const Function, TYPE (*CallbackFunction)(type));
While this HAL could be used as is, it doesn’t really provide a mechanism to easily assign the hardware function. The HAL basically expected a C module to be provided that had implementations for each of the specified functions. While this does abstract the hardware, it suffers the same issue that many vendor provided HALs have which is coupling of the application to the implementation. We can do better!
Generating a First-Pass dio HAL
To generate the dio HAL, I provide a few simple instructions to the AI:
“Using the information I’ve provided about coding style, update the provided HAL so that it is encapsulated within a dioInterface_t struct. Each member of the structure should be a function pointer so that its use looks like an object: dio.Init()”
The result generated by the AI was something like the following:
SOURCE: JACOB BENINGO
The benefit to using this type of interface is that we do in fact have a HAL! I can assign functions to those function pointers as follows:
dioInterface_t dio =
{
.Init = McuDioInit,
.ChannelWrite = McuDioChannelWrite,
// etc
}
We could reassign what functions are used based on our build. So, if I’m building for my target microcontroller, I may have one definition for dio. If I’m building for a host build or simulator, then I’d have a different definition for dio where the assigned functions to the interface are different. My application code would not change, but the underlying code would!
More Things to Do with AI
So far, I’ve looked at modifying an example I already had. There is a lot more that I could do with AI though. I could:
Ask it to create a recommended interface
List functions that should be included in the interface
Make improvements to the interface
Add error handling
Generate unit test cases
Create a C++ interface using the same specification
Generate other peripheral interfaces based on the approved dio HAL
You can iterate and drive the AI to build out not just a single interface but multiple interfaces quite rapidly. The real power I believe is that once you solidify on a particular style of HAL, you can ask the AI to generate a similar interface for SPI, I2C, PWM, ADC, and so forth. In seconds, you’ll have a consistent interface of HALs that can be used to truly abstract your hardware and provide you with the flexibility to develop scalable, portable, and reusable embedded software.
Conclusions
In this post we’ve just scratched the surface on how AI can be used to help you develop embedded software faster. We looked at how we might generate hardware abstraction layers, which are a critical piece of the puzzle for writing reusable firmware. In fact, it’s a critical piece of the puzzle for writing modern embedded software. If you don’t have good abstractions, you can’t simulate or test your code well! That means more bugs, more manual testing, which leads to more expensive development cycles.
While AI is a newer technology and certainly not perfect, you can still leverage it to get you to the ~80% mark. With a few tweaks and adjustments, you might find that it can help you to accelerate your development a bit. Using it to create a HAL is a simple, and first step to experiment with.
About the Author
You May Also Like