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.

Jacob Beningo

March 12, 2024

6 Min Read
Creating a hardware abstraction layer using artificial intelligence
Artificial intelligence can help you develop code for embedded systems.monsitj/iStock/Getty Images Plus via Getty Images

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:

Related:Writing Hardware Abstraction Layers (HALs) in C

  1. Be specific and clear

  2. Use technical language where appropriate

  3. Provide context

  4. Outline the desired logic or algorithm in pseudocode

  5. Mention the programming language the result will be in

  6. Specify the scope

  7. Include examples or use cases

  8. State any constraints

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

Related:5 Surprising Ways a Hardware Abstraction Layer (HAL) Can Transform Your Projects

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:

Creating a hardware abstraction layer using artificial intelligence

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(s)

Jacob Beningo

Jacob Beningo is an embedded software consultant who currently works with clients in more than a dozen countries to dramatically transform their businesses by improving product quality, cost and time to market. He has published more than 300 articles on embedded software development techniques, has published several books, is a sought-after speaker and technical trainer and holds three degrees which include a Masters of Engineering from the University of Michigan.

Sign up for the Design News Daily newsletter.

You May Also Like