EMLIB to Peripheral HAL Migration Guide: Migration Patterns#

Purpose: This section documents common transformation patterns that occur repeatedly across the migration. Understanding these patterns enables systematic code transformation and reduces errors.

Pattern Overview#

EMLIB Pattern

Applies To

Complexity

Key Change in Peripheral HAL

[A] Init with Peripheral Enable

All peripherals that has an enable (EN) register

Low

Split init() and enable()

[B] Init with Conditional Operation Start

TIMER, LETIMER

Medium

Handle enable field semantics

[C] Init with Feature Enable

EUSART, USART

Medium

Three-step enable sequence

[D] Boolean Parameter Split

Runtime control functions with boolean parameters

Low

Boolean → separate functions

[E] Parameter Consolidation

GPIO pin functions

Medium

Separate params → structure


Pattern A: Init with Peripheral Enable#

EMLIB Pattern:

LDMA_Init_TypeDef init = LDMA_INIT_DEFAULT;
// Single function call: configure + enable peripheral
LDMA_Init(LDMA0, &init);

Peripheral HAL Pattern:

sl_hal_ldma_init_t init = SL_HAL_LDMA_INIT_DEFAULT;
// Split into two explicit calls
sl_hal_ldma_init(LDMA0, &init);  // Step 1: Configure
sl_hal_ldma_enable(LDMA0);       // Step 2: Enable

Transformation Rules:

  1. Replace <PERIPHERAL>_Init() with sl_hal_<peripheral>_init()

  2. Add sl_hal_<peripheral>_enable() call after init()

  3. More might be needed depending on the peripheral (see the next migration patterns)

Rationale:

  • Peripheral HAL separates configuration from enablement

  • This allows configuration changes in WSTATIC registers without disabling the peripheral

  • Explicit control over when peripheral becomes enabled


Pattern B: Init with Conditional Operation Start#

When this pattern applies:

  • Peripherals with ongoing operations (TIMER, LETIMER)

  • EMLIB init.enable field controls operation start, not peripheral enable

  • Need to distinguish between peripheral enable and operation start

EMLIB Pattern:

// init.enable controls whether counting starts
TIMER_Init_TypeDef init = TIMER_INIT_DEFAULT;
init.enable = true;  // Start counting after init
TIMER_Init(TIMER0, &init);
// ↑ After this: peripheral enabled AND counting started

Peripheral HAL Pattern:

// Three separate operations: config, enable peripheral, start operation
sl_hal_timer_init_t init = SL_HAL_TIMER_INIT_DEFAULT;
sl_hal_timer_init(TIMER0, &init);    // Step 1: Configure
sl_hal_timer_enable(TIMER0);         // Step 2: Enable peripheral
sl_hal_timer_start(TIMER0);          // Step 3: Start counting

Transformation Rules:

  1. Replace TIMER_Init() with sl_hal_timer_init()

  2. Always add sl_hal_timer_enable() after init() (peripheral must be enabled)

  3. Check if original init.enable was true

  4. If yes, add sl_hal_timer_start() after enable()

  5. If no, omit the start() call (peripheral enabled but not counting)

Rationale:

  • Series 2/3 hardware has separate enable and start/stop mechanisms

  • Peripheral enable must be done first

  • Operation start/stop can be done while enabled

  • This pattern gives flexibility: enable peripheral once, start/stop multiple times


Pattern C: Init with Feature Enable#

When this pattern applies:

  • Communication peripherals (EUSART, USART)

  • EMLIB init.enable field controls transmitter/receiver enable, not peripheral enable

  • Three-level enable hierarchy: bus clock → peripheral → TX/RX

EMLIB Pattern:

// init.enable controls TX/RX, peripheral enable is a side effect
EUSART_UartInitHf_TypeDef init = EUSART_UART_INIT_DEFAULT_HF;
init.enable = eusartEnable;  // Enable both TX and RX
EUSART_UartInitHf(EUSART0, &init);
// ↑ After this: peripheral enabled AND TX/RX enabled

Peripheral HAL Pattern:

// Three explicit steps: config, enable peripheral, enable TX/RX
sl_hal_eusart_config_uart_t config = SL_HAL_EUSART_CONFIG_UART_DEFAULT;
sl_hal_eusart_init_uart(EUSART0, &config);  // Step 1: Configure
sl_hal_eusart_enable(EUSART0);              // Step 2: Enable peripheral
sl_hal_eusart_enable_tx(EUSART0);           // Step 3a: Enable transmitter
sl_hal_eusart_enable_rx(EUSART0);           // Step 3b: Enable receiver

Transformation Rules:

  1. Replace EUSART_UartInitHf() with sl_hal_eusart_init_uart()

  2. Always add sl_hal_eusart_enable() after init()

  3. Map init.enable value to appropriate enable functions:

    • eusartEnable → call both sl_hal_eusart_enable_tx() and sl_hal_eusart_enable_rx()

    • eusartEnableTx → call only sl_hal_eusart_enable_tx()

    • eusartEnableRx → call only sl_hal_eusart_enable_rx()

    • eusartDisable → omit TX/RX enable calls

Rationale:

  • Separates peripheral-level enable from feature-level enable

  • Allows reconfiguration of TX/RX without full reinitialization

  • Matches hardware's hierarchical enable structure


Pattern D: Boolean Parameter Split#

When this pattern applies:

  • Runtime control functions (not init() functions) with boolean parameters

  • EMLIB uses single function with boolean to control binary operations (enable/disable, start/stop, etc.)

  • HAL provides separate, semantically clear functions

EMLIB Pattern:

// Single function with boolean parameter
TIMER_Enable(TIMER0, true);              // Start counting
TIMER_Enable(TIMER0, false);             // Stop counting

TIMER_EnableDTI(TIMER0, true);           // Enable DTI
TIMER_EnableDTI(TIMER0, false);          // Disable DTI

CMU_ClockEnable(cmuClock_GPIO, true);    // Enable clock
CMU_ClockEnable(cmuClock_GPIO, false);   // Disable clock

Peripheral HAL Pattern:

// Separate, semantically clear functions
sl_hal_timer_start(TIMER0);
sl_hal_timer_stop(TIMER0);

sl_hal_timer_dti_enable(TIMER0);
sl_hal_timer_dti_disable(TIMER0);

sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_GPIO);
sl_clock_manager_disable_bus_clock(SL_BUS_CLOCK_GPIO);

Transformation Rules:

  1. Identify function calls with boolean parameter controlling binary operations

  2. Understand what the function actually does (not just what the name suggests)

  3. Replace with appropriate HAL function pair:

    • truesl_hal_*_enable(), sl_hal_*_start(), or sl_clock_manager_enable_bus_clock()

    • falsesl_hal_*_disable(), sl_hal_*_stop(), or sl_clock_manager_disable_bus_clock()

  4. Remove the boolean parameter

Rationale:

  • Separate functions are more explicit and self-documenting

  • Eliminates conditional logic in favor of direct function calls

  • HAL uses accurate, descriptive function names that reflect actual behavior

  • Clearer intent at call sites


Pattern E: Parameter Consolidation#

When this pattern applies:

  • GPIO functions that take separate port and pin parameters

  • EMLIB uses two separate parameters for port and pin identification

  • HAL consolidates these into a single sl_gpio_t{} structure

EMLIB Pattern:

// Separate port and pin parameters
GPIO_PinModeSet(gpioPortA, 5, gpioModePushPull, 1);
GPIO_PinOutClear(gpioPortA, 5);
GPIO_PinOutSet(gpioPortA, 5);

Peripheral HAL Pattern:

// Consolidated into sl_gpio_t{} structure
sl_gpio_t gpio = {.port = SL_GPIO_PORT_A, .pin = 5};
sl_hal_gpio_set_pin_mode(&gpio, SL_GPIO_MODE_PUSH_PULL, true);
sl_hal_gpio_clear_pin(&gpio);
sl_hal_gpio_set_pin(&gpio);

Transformation Rules:

  1. Create sl_gpio_t{} structure with port and pin fields

  2. Convert port constant (gpioPortXSL_GPIO_PORT_X)

  3. Pass structure by pointer to HAL functions

  4. Convert mode constants (gpioModeXSL_GPIO_MODE_X)

  5. Convert output value from unsigned int to bool

Rationale:

  • Structure provides better type safety

  • Easier to pass GPIO pin information as single parameter

  • Reduces parameter count in function signatures

  • Aligns with modern C practices


Pattern Summary#

When migrating code:

  1. Identify the EMLIB pattern by checking the peripheral and function type

  2. Follow the transformation rules specific to that pattern

  3. Test for behavioral equivalence (see Common Pitfalls and Solutions)

Advice: Achieve behavioral equivalence at first - the sequence of register access should the same state after the migration. You can rework the code afterwards to better optimize using the simpler APIs of HAL peripherals.

Quick Reference:

EMLIB Code

Pattern

HAL Equivalent

LDMA_init(&init)

A

sl_hal_ldma_init(ldma, &init) + sl_hal_ldma_enable(ldma)

IADC_init(iadc, &init, &allConfigs)

A

sl_hal_iadc_init(iadc, &init, src_clk_freq) + sl_hal_iadc_enable(iadc)

TIMER_Init(timer, &init)

B

sl_hal_timer_init(timer, &init) + sl_hal_timer_enable(timer) + conditional sl_hal_timer_start(timer)

LETIMER_Init(letimer, &init)

B

sl_hal_letimer_init(letimer, &init) + sl_hal_letimer_enable(letimer) + conditional sl_hal_letimer_start(letimer)

EUSART_UartInitHf(eusart)

C

sl_hal_eusart_init_uart(eusart) + sl_hal_eusart_enable(eusart) + conditional sl_hal_eusart_enable_tx(eusart) / sl_hal_eusart_enable_rx(eusart)

USART_InitAsync(usart)

C

sl_hal_usart_init_async(usart) + sl_hal_usart_enable(usart) + conditional sl_hal_usart_enable_tx(usart) / sl_hal_usart_enable_rx(usart)

TIMER_Enable(timer, true/false)

D

sl_hal_timer_start(timer) / sl_hal_timer_stop(timer)

CMU_ClockEnable(clock, true/false)

D

sl_clock_manager_enable_bus_clock(clock) / sl_clock_manager_disable_bus_clock(clock)

GPIO_PinModeSet(port, pin, mode, out)

E

Create sl_gpio_t{}, call sl_hal_gpio_set_pin_mode(&gpio, mode, out)

GPIO_PinOutSet(port, pin)

E

Create sl_gpio_t{}, call sl_hal_gpio_set_pin(&gpio)