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 byenable()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 enoughCorrect 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.enableset to? What did it control?TIMER/LETIMER: Pattern B -
enablefield → conditionalstart()EUSART/USART: Pattern C -
enablefield →enable_tx()/enable_rx()callsOthers: Pattern A -
enablefield → conditionalenable()
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_cmuexists - 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 timerConfusion 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 startedPrevention:
Two-level model: Enable peripheral (hardware ready) → Start operation (function active)
Pattern B peripherals: Always need both
enable()andstart()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_ClockEnableundefinedUnresolved reference to
em_cmu.horsl_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_EnterEM2undefinedUnresolved 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:
Run EMLIB code on device, dump peripheral registers after
init()Run migrated HAL code, dump same registers after
init()Compare register values - they should be identical
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 baselineStrategy 2: Behavioral Equivalence Testing#
When to use: Verify that peripheral behaves identically after migration (same outputs for same inputs)
Approach:
Create test harness that exercises the expected functionality
Run tests with EMLIB code, capture outputs (GPIO states, UART data, timer counts, ADC readings)
Run same tests with HAL code, capture outputs
Compare - behavior must be identical
Strategy 3: Logic Analyzer Comparison#
When to use: Visual verification of signal timing and behavior
Approach:
Connect logic analyzer to peripheral pins (GPIO, UART TX/RX, PWM output, etc.)
Run EMLIB code, capture waveforms
Run HAL code, capture waveforms
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