EMLIB to Peripheral HAL Migration Guide: Common Pitfalls and Solutions#

Pitfall 1: Forgetting to Enable Peripheral After Init#

Symptom:

  • Peripheral doesn't work despite correct configuration

  • No data transfer, no interrupts, no counting

  • Registers show correct configuration but peripheral inactive

Root Cause: In EMLIB, <PERIPHERAL>_Init() often enabled the peripheral as a side effect. In Peripheral HAL, sl_hal_<peripheral>_init() only configures; you must explicitly call sl_hal_<peripheral>_enable().

Incorrect Approach:

// WRONG: Missing enable call
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_TIMER0);
sl_hal_timer_config_t config = SL_HAL_TIMER_CONFIG_DEFAULT;
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_start(TIMER0);  // Start will fail - peripheral not enabled!

Correct Solution:

// CORRECT: Always enable after init
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_TIMER0);
sl_hal_timer_config_t config = SL_HAL_TIMER_CONFIG_DEFAULT;
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);          // ← Don't forget this!
sl_hal_timer_start(TIMER0);

Prevention:

  • Mental model: Think "init, enable, operate" as three distinct steps

  • Code review checklist: Every init() must be followed by enable()

  • Pattern application: Always apply patterns A, B, or C completely, don't skip steps


Pitfall 2: Misunderstanding enable Field Semantics#

Symptom:

  • Unexpected peripheral behavior after migration

  • TIMER doesn't start even though code looks correct

  • UART transmits but doesn't receive (or vice versa)

Root Cause: The init.enable field in EMLIB init structures means different things for different peripherals:

  • TIMER: Controls whether counting starts (true = start counting)

  • EUSART: Controls TX/RX enable (eusartEnable = enable both)

  • IADC: Controls warmup (true = perform warmup)

Incorrect Approach:

// WRONG: Assuming enable field always means "peripheral enable"
// EMLIB had: init.enable = true
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);
// Missing: sl_hal_timer_start() because developer thought enable() was enough

Correct Solution:

// CORRECT: Understand that init.enable controlled operation start for TIMER
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);    // Enable peripheral
sl_hal_timer_start(TIMER0);     // Start operation (was init.enable=true)

Prevention:

  • Check the pattern: Refer to Migration Patterns for your peripheral

  • Read original code carefully: What was init.enable set to? What did it control?

  • TIMER/LETIMER: Pattern B - enable field → conditional start()

  • EUSART/USART: Pattern C - enable field → enable_tx() / enable_rx() calls

  • Others: Pattern A - enable field → conditional enable()


Pitfall 3: Missing Clock Manager Setup#

Symptom:

  • Peripheral doesn't respond at all

  • Hard fault on peripheral register access

  • All peripheral operations fail silently

Root Cause: Peripheral HAL drivers do not manage clocks. The application must explicitly enable bus clocks using the Clock Manager service before using any peripheral.

Incorrect Approach:

// WRONG: No clock management (assuming driver handles it)
sl_hal_timer_init_t init = SL_HAL_TIMER_INIT_DEFAULT;
sl_hal_timer_init(TIMER0, &config);  // May cause hard fault!

Correct Solution:

// CORRECT: Enable clock before any peripheral operations
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_LDMA0);      // ← Must do this first!
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_LDMAXBAR0);
sl_hal_ldma_init_t init = SL_HAL_LDMA_INIT_DEFAULT;
sl_hal_ldma_init(LDMA0, &init);

Prevention:

  • Clock management pattern: Enable clock → init → enable → operate

  • Remember: No sl_hal_cmu exists - use Clock Manager service


Pitfall 4: Confusing Peripheral Enable with Operation Start#

Symptom:

  • TIMER peripheral enabled but not counting

  • Developer expects sl_hal_timer_enable() to start the timer

  • Confusion about when operations actually begin

Root Cause: In Series 2/3 architecture, peripheral enable and operation start are separate concepts:

  • Peripheral enable: Powers on the peripheral and allows configuration

  • Operation start: Begins the peripheral's active function (e.g., counting, transmitting)

For TIMER:

  • sl_hal_timer_enable() → Enables the peripheral (does NOT start counting)

  • sl_hal_timer_start() → Starts counting

Incorrect Approach:

// WRONG: Thinking enable() starts counting
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);
// Timer is enabled but NOT counting - need start()!

Correct Solution:

// CORRECT: Enable then start
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);   // Peripheral enabled
sl_hal_timer_start(TIMER0);    // Counting started

Prevention:

  • Two-level model: Enable peripheral (hardware ready) → Start operation (function active)

  • Pattern B peripherals: Always need both enable() and start()

  • Benefit: Can enable once, start/stop many times without reconfiguration


Pitfall 5: Using CMU Functions for Clock Management#

Symptom:

  • Compiler errors on Series 3: CMU_ClockEnable undefined

  • Unresolved reference to em_cmu.h or sl_hal_cmu.h

Root Cause: There is no sl_hal_cmu in Peripheral HAL. The em_cmu has been replaced by the Clock Manager service (sl_clock_manager) which provides a higher-level, device-agnostic clock management API.

Incorrect Approach:

// WRONG: Looking for sl_hal_cmu (doesn't exist)
#include "sl_hal_cmu.h"  // ← This file doesn't exist!
sl_hal_cmu_enable_clock(...);  // ← This function doesn't exist!

Correct Solution:

// CORRECT: Use Clock Manager service
#include "sl_clock_manager.h"

// Enable peripheral bus clock
sl_clock_manager_enable_bus_clock(SL_BUS_CLOCK_TIMER0);

// When done, disable to save power
sl_clock_manager_disable_bus_clock(SL_BUS_CLOCK_TIMER0);

Design Differences - em_cmu vs. Clock Manager:

Aspect

em_cmu (EMLIB)

sl_clock_manager (Service)

Abstraction Level

Low-level, register-based

High-level, service-based

API Style

Enable/disable individual clocks

Enable/disable bus clocks by peripheral

Clock Tree Management

Manual, developer-controlled

Automatic dependency handling

Power Optimization

Manual tracking of clock usage

Service tracks all clock requests

Device Differences

Device-specific clock names

Abstracted, portable clock identifiers

Usage Pattern

Direct hardware control

Service request/release model


Pitfall 6: Using EMU Functions for Sleep Modes#

Symptom:

  • Compiler errors on Series 3: EMU_EnterEM1, EMU_EnterEM2 undefined

  • Unresolved reference to em_emu.h

Root Cause: Not all em_emu.h functions have equivalents in sl_hal_emu.h. Specifically, sleep mode entry functions like EMU_EnterEM1() and EMU_EnterEM2() have been replaced by the Power Manager service (sl_power_manager) which provides a higher-level, coordinated power management approach for Series 2/3 devices.

Incorrect Approach:

// WRONG: Looking for sl_hal_emu sleep functions (don't exist)
#include "sl_hal_emu.h"
sl_hal_emu_enter_em1();  // ← This function doesn't exist!

Correct Solution:

// CORRECT: Use Power Manager service
#include "sl_power_manager.h"

// Add power requirement when peripheral needs to stay awake
sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM1);

// Do work...

// Remove requirement when peripheral can sleep
sl_power_manager_remove_em_requirement(SL_POWER_MANAGER_EM1);

// Let Power Manager decide the energy mode
// (happens automatically in idle task or sl_power_manager_sleep())

Design Differences - em_emu vs. Power Manager:

Aspect

em_emu (EMLIB)

sl_power_manager (Service)

Abstraction Level

Low-level, direct sleep entry

High-level, coordinated power management

Sleep Mode Control

Application directly enters modes

Service determines deepest safe mode

Multi-Component Support

Manual coordination required

Automatic arbitration across components

Peripheral Awareness

Developer tracks peripheral constraints

Service tracks all power requirements

Wakeup Management

Manual configuration

Automatic based on requirements

RTOS Integration

Separate manual integration

Built-in tickless idle support

What IS in sl_hal_emu?

sl_hal_emu.h exists but provides only hardware-level EMU configuration, not sleep entry:

  • Reset management

  • DCDC configuration

  • Temperature sensor control


Debugging Strategies#

Strategy 1: Register Dump Verification#

When to use: Verify that migrated code produces identical peripheral configuration to original EMLIB code

Approach:

  1. Run EMLIB code on device, dump peripheral registers after init()

  2. Run migrated HAL code, dump same registers after init()

  3. Compare register values - they should be identical

  4. If different, trace which HAL function writes which register

Code Example:

// Add this debug function to dump peripheral state
void debug_dump_timer_registers(void) {
  printf("TIMER0_CFG: 0x%08lX\n", TIMER0->CFG);
  printf("TIMER0_CTRL: 0x%08lX\n", TIMER0->CTRL);
  printf("TIMER0_CMD: 0x%08lX\n", TIMER0->CMD);
  printf("TIMER0_STATUS: 0x%08lX\n", TIMER0->STATUS);
  printf("TIMER0_IEN: 0x%08lX\n", TIMER0->IEN);
  printf("TIMER0_TOP: 0x%08lX\n", TIMER0->TOP);
  // Add other relevant registers
}

// Call after EMLIB init:
TIMER_Init(TIMER0, &init);
debug_dump_timer_registers();  // Capture baseline

// Call after HAL init:
sl_hal_timer_init(TIMER0, &config);
sl_hal_timer_enable(TIMER0);
sl_hal_timer_start(TIMER0);
debug_dump_timer_registers();  // Compare to baseline

Strategy 2: Behavioral Equivalence Testing#

When to use: Verify that peripheral behaves identically after migration (same outputs for same inputs)

Approach:

  1. Create test harness that exercises the expected functionality

  2. Run tests with EMLIB code, capture outputs (GPIO states, UART data, timer counts, ADC readings)

  3. Run same tests with HAL code, capture outputs

  4. Compare - behavior must be identical

Strategy 3: Logic Analyzer Comparison#

When to use: Visual verification of signal timing and behavior

Approach:

  1. Connect logic analyzer to peripheral pins (GPIO, UART TX/RX, PWM output, etc.)

  2. Run EMLIB code, capture waveforms

  3. Run HAL code, capture waveforms

  4. Compare visually - timing, frequency, duty cycle, etc. should match

Benefits:

  • Catches timing-related issues

  • Visual confirmation of correct behavior

  • Helps debug interrupt timing

  • Reveals glitches or state transitions