Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 63

AUTOSAR MCU Driver State Machine Implementation

Recommended resources

Hello! Welcome back to our course on AUTOSAR MCAL Development.

In our last lesson, we saw how to implement the Port and Dio drivers, establishing the core AUTOSAR principle of using a generated configuration structure to program the hardware. We learned how the Port driver handles one-time pin configuration, while the Dio driver manages runtime I/O.

However, before any pin can be configured or any digital signal can be read, the microcontroller unit (MCU) itself must be in a stable, operational state. This is the fundamental responsibility of the MCU driver. Today, we will implement the core of this driver. Our learning outcome is to implement the AUTOSAR Mcu driver state machine, including clock and RAM initialization. You'll see that a correct Mcu initialization is the bedrock upon which all other MCAL drivers stand.

1. The Mcu Driver: The Heart of the MCAL

The Mcu driver is the first piece of MCAL software to run after the low-level startup code. It is responsible for foundational MCU services that enable all other functionalities.

Specification of MCU Driver

Let's start with the official AUTOSAR specification to understand the scope of the Mcu driver. This document outlines its primary features.

Please read the 'Introduction and functional overview' section. Focus on the list of features provided, specifically noting the initialization of clock, PLL, and RAM sections.

As the specification highlights, the Mcu driver's main jobs during initialization are:

  • Clock Initialization: Configuring oscillators, PLLs, and clock prescalers to provide the correct operating frequencies for the CPU and peripherals.
  • RAM Initialization: Initializing specified RAM sections to a known default value.
  • Power Mode Management: Providing services to enter low-power modes (which we will explore in a later lesson).
AUTOSAR Layered Architecture with Highlighted Mcu Module
This diagram shows the Mcu driver's position within the Microcontroller Abstraction Layer (MCAL). Its central role is clear, as it provides the fundamental services, like clocking, required by nearly all other drivers.

2. The Mcu Initialization State Machine

Unlike a simple Port_Init call, the Mcu initialization follows a specific sequence, especially when a Phase-Locked Loop (PLL) is used to generate the main system clock. This sequence ensures that the system only switches to a new clock source after it has become stable.

The best way to understand this flow is by looking at the example sequence diagram from the AUTOSAR specification.

Specification of MCU Driver

The following sequence diagram from the AUTOSAR SWS for the Mcu Driver illustrates the standard initialization flow. It's a critical concept to grasp.

Please study Figure 9.1, 'Sequence Diagram - Mcu Initialization'. Pay close attention to the order of calls: Mcu_Init, Mcu_InitClock, Mcu_GetPllStatus, and Mcu_DistributePllClock.

This sequence defines an implicit state machine:

  1. UNINIT State: The initial state before any Mcu functions are called.
  2. Mcu_Init(): The application calls this first. The driver transitions to the INITIALIZED state. This function reads the configuration pointer and prepares the driver for other API calls.
  3. Mcu_InitClock(): The application calls this to configure the clock hardware based on a selected configuration set. Critically, if a PLL is being configured, this function starts the PLL locking procedure but does not wait for it to complete. The PLL is now in a PENDING state.
  4. Mcu_GetPllStatus(): The application must now poll the PLL status. It repeatedly calls this function until it returns MCU_PLL_LOCKED.
  5. Mcu_DistributePllClock(): Once the PLL is locked, the application calls this function. The driver switches the main system clock from the initial source (e.g., an internal oscillator) to the new, stable PLL output. The clock system is now in its final, high-speed state.
  6. Mcu_InitRamSection(): This can be called any time after Mcu_Init(). It iterates through a configured RAM section and fills it with a default value.

This asynchronous handling of the PLL is crucial. PLLs take a non-trivial amount of time to stabilize. Instead of blocking the entire startup process, AUTOSAR defines this non-blocking pattern, allowing other initializations (that don't depend on the final clock speed) to potentially run in parallel.


3. Implementing Clock Initialization

The real complexity of the Mcu driver lies in the clock configuration. A modern automotive MCU has a complex clock tree with multiple sources (internal/external oscillators) and paths.

Just like with the Port driver, this complexity is managed through configuration. In your configuration tool (e.g., EB Tresos), you define one or more McuClockSettingConfig containers. Each container represents a complete clock tree setup: PLL multipliers, bus prescalers, peripheral clock enables, etc.

MCU 3.0 driver user guide, TRAVEO™ T2G family

To get a practical feel for what goes into a clock configuration, let's look at a vendor-specific user guide. This document details the vast number of parameters for a real-world MCU.

Skim through section 4.5, 'MCU clock setting configuration', particularly subsections 4.5.2 ('MCU clock settings') and 4.5.2.3 ('MCU PLL clock settings'). You don't need to memorize the parameters; the goal is to appreciate the level of detail involved in specifying PLL sources, dividers, and output frequencies.

The Mcu_InitClock Implementation

The Mcu_InitClock function takes an ID that corresponds to one of the McuClockSettingConfig containers you've defined. The implementation then uses this ID to find the correct settings in the generated Mcu_ConfigType structure and programs the hardware.

Here is a conceptual implementation. The register names are for illustration but the logic is universal.

// In Mcu_Cfg.c (generated file)
const Mcu_ClockConfigType McuClockSettings[] = {
    [0] = { // Corresponds to McuClockSettingId 0
        .pllSource = MCU_PLL_SRC_HSE,
        .pllMul = 336,
        .pllPDiv = 4,
        .ahbPrescaler = 1,
        .apb1Prescaler = 4,
        .apb2Prescaler = 2
    },
    // ... other clock settings
};

// In Mcu.c (driver implementation)
Std_ReturnType Mcu_InitClock(Mcu_ClockType ClockSetting) {
    // 1. Validate ClockSetting ID and driver state (must be initialized)
    if (Mcu_DriverState != MCU_INITIALIZED) { /* report MCU_E_UNINIT */ return E_NOT_OK; }
    if (ClockSetting >= MAX_CLOCK_SETTINGS) { /* report MCU_E_PARAM_CLOCK */ return E_NOT_OK; }

    // 2. Get the specific clock configuration from the generated structure
    const Mcu_ClockConfigType* clkConf = &Mcu_ConfigPtr->ClockConfigs[ClockSetting];

    // 3. Configure the PLL. First, ensure PLL is off.
    // (Example for STM32 RCC peripheral)
    RCC->CR &= ~RCC_CR_PLLON;
    // Wait for PLL to be disabled
    while((RCC->CR & RCC_CR_PLLRDY) != 0);

    // 4. Write the configuration values from the struct to the PLL config register
    RCC->PLLCFGR = (clkConf->pllSource << 22) |
                   (clkConf->pllMul << 6) |
                   (clkConf->pllPDiv << 16);

    // 5. Enable the PLL and return. We DO NOT wait for the lock flag here.
    RCC->CR |= RCC_CR_PLLON;

    // 6. Set prescalers for AHB/APB buses
    RCC->CFGR |= (clkConf->ahbPrescaler << 4) | 
                 (clkConf->apb1Prescaler << 10) |
                 (clkConf->apb2Prescaler << 13);
    
    // Remember the PLL is now attempting to lock.
    Mcu_PllState = MCU_PLL_PENDING;

    return E_OK;
}

4. Implementing RAM Initialization

Initializing RAM sections is a more straightforward task. The configuration defines one or more RAM sectors, each with a base address, a size, and a default value to write.

Specification of MCU Driver

Let's review the standard configuration parameters for a RAM section.

Find section 10.2.7, 'McuRamSectorSettingConf'. Note the four key parameters: McuRamSectionBaseAddress, McuRamSectionSize, McuRamDefaultValue, and McuRamSectionWriteSize.

The Mcu_InitRamSection function takes the ID of a configured RAM section and performs what is essentially a memset operation.

The Mcu_InitRamSection Implementation

// In Mcu.c (driver implementation)
Std_ReturnType Mcu_InitRamSection(Mcu_RamSectionType RamSection) {
    // 1. Validate RamSection ID and driver state
    if (Mcu_DriverState != MCU_INITIALIZED) { /* report MCU_E_UNINIT */ return E_NOT_OK; }
    if (RamSection >= MAX_RAM_SECTIONS) { /* report MCU_E_PARAM_RAMSECTION */ return E_NOT_OK; }

    // 2. Get the specific RAM configuration from the generated structure
    const Mcu_RamConfigType* ramConf = &Mcu_ConfigPtr->RamConfigs[RamSection];
    
    // 3. Cast the base address to a volatile pointer. 'volatile' is critical to prevent
    //    the compiler from optimizing away these memory writes.
    volatile uint8_t* pRam = (volatile uint8_t*)ramConf->BaseAddress;
    uint32_t size = ramConf->Size;
    uint8_t defaultValue = ramConf->DefaultValue;

    // 4. Loop through the section and write the default value
    for (uint32_t i = 0; i < size; i++) {
        pRam[i] = defaultValue;
    }

    return E_OK;
}
Test your understanding!

An application requires a very fast startup time. The system clock needs to be switched to a 168 MHz PLL, but there's a CAN driver initialization that doesn't depend on this high speed and could run earlier. How would you structure the initialization calls to optimize startup time, and which Mcu function acts as the "gate" before the rest of the application can run at full speed?

Show answer

You would structure the calls to leverage the asynchronous nature of the PLL locking:

  1. Mcu_Init()
  2. Mcu_InitClock() // This starts the 168 MHz PLL locking but returns immediately.
  3. Can_Init() // The CAN driver initialization can run now, using the default, slower internal clock.
  4. while (Mcu_GetPllStatus() != MCU_PLL_LOCKED) { /* wait */ } // Poll for PLL stability.
  5. Mcu_DistributePllClock() // This is the "gate". After this call, the system is running at 168 MHz.
  6. Now, run the rest of the initializations that depend on the high-speed clock.

5. Interview Corner

Q1: Describe the typical initialization sequence for the AUTOSAR Mcu driver when using a PLL. Why is this sequence important?
A: The sequence is Mcu_Init(), Mcu_InitClock(), a polling loop with Mcu_GetPllStatus(), and finally Mcu_DistributePllClock(). This sequence is critical because PLLs require time to stabilize and achieve a frequency lock. Mcu_InitClock only starts the process. The polling loop ensures we wait until the clock is stable, and Mcu_DistributePllClock performs the final, safe switchover to the PLL as the system clock source. Attempting to switch before the PLL is locked would lead to an unstable system clock and likely a crash.

Q2: What is the difference between Mcu_InitClock and Mcu_DistributePllClock?
A: Mcu_InitClock is the configuration function. It writes settings from the configuration structure to the clock control registers, programming the PLL's dividers and multipliers, and then enables the PLL. It starts the locking process. Mcu_DistributePllClock is the activation function. It changes the system's clock multiplexer to select the (now stable) PLL output as the main clock source. It's the final step that makes the high-speed clock active for the CPU and peripherals.

Q3: You're bringing up a new board, and the application hangs during BSW initialization. You suspect a clock issue. Using a Lauterbach JTAG debugger, what registers would you inspect within the Mcu driver's logic?
A: I would trace the Mcu initialization sequence and inspect the relevant clock control registers at each step.

  1. After Mcu_InitClock: I'd check the PLL configuration register (e.g., RCC_PLLCFGR) to ensure the multiplier and divider values match my configuration. I'd also check the clock control register (e.g., RCC_CR) to confirm the PLLON bit is set, indicating the PLL has been enabled.
  2. During the Mcu_GetPllStatus loop: I would monitor the clock control register's PLLRDY (PLL Ready) bit. If this bit never gets set, it indicates a hardware problem or a misconfiguration of the PLL input clock or frequency range.
  3. After Mcu_DistributePllClock: I would inspect the main clock configuration register (e.g., RCC_CFGR) to check the SWS (System Clock Switch Status) bits. These bits confirm whether the system clock multiplexer has successfully switched to the PLL source. If it hasn't, the switchover failed for some reason.

Q4: What is the purpose of the Mcu_InitRamSection function, and why is it important in automotive systems?
A: Its purpose is to initialize a specific block of RAM to a known, constant value during startup. This is critical in safety-related automotive systems for several reasons. First, it satisfies the ISO 26262 requirement to avoid using variables with indeterminate values, preventing unpredictable behavior. Second, it can be part of a power-on self-test; by writing a pattern and reading it back, it can help diagnose RAM hardware faults, although a full March test is a more robust method for that.


Conclusion

Today, we've laid the most important foundation in our MCAL stack. We've implemented the state machine for the Mcu driver, understanding the critical, asynchronous sequence required to safely initialize and switch to a high-speed PLL clock. We've also seen how to initialize RAM sections, a key step for system safety and predictability.

Key Takeaways:

  • The Mcu driver provides foundational services for clock, RAM, and power management.
  • The PLL initialization follows a specific state machine: Init -> InitClock (starts lock) -> GetPllStatus (polls) -> DistributePllClock (switches).
  • Clock and RAM configurations are defined in the BSW configuration tool and passed to the driver via generated C structures.
  • Mcu_InitClock configures the hardware and starts the PLL, while Mcu_DistributePllClock performs the final switch to the stable PLL output.
  • Mcu_InitRamSection ensures memory regions start with a known, predictable state, which is vital for functional safety.

Next Lesson Preview:
With our MCU now running on a stable, high-speed clock, we can start using peripherals that rely on precise timing. In our next lesson, we will develop a driver for the General Purpose Timer (GPT). We will learn how to implement the AUTOSAR Gpt driver and integrate it with the OS to provide a time base for Alarms, a fundamental component for any real-time scheduling.