Hi,

I’ve been going through and expanding the NRF51/52 HAL, and there are a couple of things I’ve noticed that I think we should talk about. I’ll provide a patch for this on the ADC HAL and then slowly work through the rest, but I wanted to talk about the changes prior to revising this.

But, first, I think it’s worth talking through ideally what the layering _should_ look like here. I’ve found it’s often hard to come to a common definition the differences between the BSP, HAL and Drivers, and where the separation of concerns should be. In my view, the layout should be:

+———————————————————————————+
|            app            |
+———————————————————————————+
|          (n)drivers       |
+———————————————————————————+
|     HAL     |     BSP     |
+—————————————+—————————————+

Where the role of the HAL is _solely_ to abstract MCU peripheral APIs, in a low layer, simple abstraction.

The role of the BSP is to abstract board specific configurations.

The drivers is where you can have diversity, and interaction with the rest of the system. ADC may not be the best example of this, but I’ve been looking at it most closely at the moment.

In the case of an ADC, the HAL should be designed to be as portable _and low layer_ as possible across the various MCUs that we support. Every chip has a different set of peripherals for managing an ADC, but there are a few modes that are quite common across chipsets:

- One shot
- Continuous
- Scan

Beyond that, you can implement continuous and scan pretty easily, on the few chipsets that don’t have them.

Implementation of these can be fairly different across chipsets. Sometimes you need to use the ADC + comparator, at other times the ADC peripheral will do it for you. However, in most cases these modes can be abstracted with minimal code. There is value in doing this across processors, because it allows for portability at higher layers.

However, while the HAL is necessary, it is not sufficient, without information that in most cases comes from the board. Taking the ADC example here, how do you convert a reading from the ADC without knowing the reference voltage? I’ll come back to this later.

The role of the BSP is to provide configuration and abstraction of board level features. This includes things like memory and flash layout, as well as some form of a pinmux, and abstraction of things like CPU frequency, input voltage, etc.

Above both of these is the drivers API. While in the ADC HAL there is only _one_ implementation, for drivers there should be multiple implementations, and potentially complex helper functions to make it easy to develop against. So, you might have a very simple/basic driver that does blocking ADC reads and uses the HAL only. You might have a more complex driver that can deal with both internal and external ADCs, and has chip specific support for doing things like DMA’ing ADC reads into a buffer and posting an event to a task every ’n’ samples. The drivers are the ones would should register with the kernel’s power management APIs, and manage turning on and off peripherals and external chipsets, etc.

And the drivers need not be one-size fit all: it’s beneficial to have complexity here in some cases, and simplicity in others, and some times you can’t maintain the balance between the two. It’s the HAL’s job to be one-size fit all (or as close as possible), so that we can gain abstraction across the diverse of chip peripherals.

OK, on to changes that I think we should make to how things are done now…

Right now, we don’t have a drivers interface, and the HAL is dual-purposing both. The ADC is a good example of this, so I’ll focus in on that API.

* hal_adc_init() takes a system device id, which is defined by the BSP.

* hal_adc_init() calls bsp_get_hal_adc(), which maps the system device id to the correct HAL peripheral

* bsp_get_hal_adc() calls the specific MCU HAL ADC create function (e.g. samd21_adc_create()), which creates and returns a hal_adc structure.

* the various hal functions are then implemented, right now these are:
  — hal_adc_read(): blocking read of raw ADC value
- hal_adc_getrefmv(): Get the reference voltage of the ADC read (using BSP functions)


There are some design decisions made here that I’d like to call out:

* The mapping from the “system device IDs” to peripheral IDs in the BSP. This is to abstract the definition across boards of which ADCs to map either to an internal peripheral or an external ADC

* The hal_adc_read() function blocks on read. This is for user convenience, but does not represent how the underlying hardware implements an ADC read.

* hal_adc_getrefmv() comes from the BSP, which defines these in hal_bsp.c. The samd21 specific implementation of getrefmv() then maps these to peripheral specific APIs.

My suggestions on how to change this API, and the general design philosophy are as follows:

* A #define should be added HAL_ADC_MAX which indicates the number of peripherals enabled on a given board

* hal_adc_init() should be renamed to hal_adc_config() and should configure the peripheral provided, which abstracted (cross-platform) values for reference voltage, resolution and gain.

* hal_adc_read() should be broken into a set of functions that more appropriately map to underlying peripheral operation (e.g. hal_adc_read_once(periph_num, callback)).

* There should be BSP APIs that provide necessary information. For example, reference voltage will most likely come from the board configuration, so bsp_get_board_voltage() should be provided, as a generally implemented function. This function could then be passed into hal_adc_config() as the reference voltage (as an example.)

* Power APIs will be added to the HAL (hal_adc_on(), hal_adc_suspend(), hal_adc_off().)

* There should be a drivers interface that runs above this, added to the base directory of mynewt-core. Mynewt-core should come with a base set of drivers that can use this. The drivers APIs will be what maps internal and external ADCs, provides blocking interfaces, etc.

* A default ADC driver will be developed, which uses the HAL to read peripherals, and provides blocking functions. Over time this will be evolved (or broken apart if too complex) to support external ADCs or directly support more complex chip features.

* The driver will be responsible for managing power states.

* The mapping of SYSTEM ID -> specific ADC configuration is an exercise that is left to the user. This can be done wherever the drivers are created / initialized. In some cases this will be BSP, in other cases it can be done directly by the application. This can be everything from provided a generic sensor API that the drivers plug into, to providing a factory function that returns the appropriate driver structure per board.

OK, that was a long email. I’d like people’s thoughts on this — so if you’ve made it this far, please chime in. :-)

Sterling

Reply via email to