Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Group7 Project - FreeRTOS Porting on Emulated S32K with LPUART/LPSPI

This document describes the steps to configure, compile, run, and debug the modified QEMU project to support the NXP S32K3 board.


1. Requirements

Before starting, make sure you have the following dependencies installed. Run this command from your terminal:

sudo apt update
sudo apt upgrade
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build

Note: If you encounter issues with ninja-build, ensure your system is fully updated by first running sudo apt update and sudo apt upgrade.


2. Project Build

Follow these steps to correctly download the source code and compile it.

2.1. Code Download

Clone the repository and initialize the necessary submodules:

git clone <YOUR_REPOSITORY_URL>

2.2. Configuration and Compilation

The ./configure command prepares the build environment. You can customize it with specific flags to enable debug logs for certain modules.

Generic configuration with debug enabled:

./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPUART:

CFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPUART_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

Configuration with debug for LPSPI:

CFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" CXXFLAGS="-g -O0 -DNXP_LPSPI_ERR_DEBUG=2" ./configure --target-list=arm-softmmu --enable-debug

After configuration, start the compilation using all available CPU cores:

make -j$(nproc)

To check the machines (boards) supported by your QEMU build, run:

./build/qemu-system-arm -M help

3. Execution and Testing

Below are several examples for testing the emulator with different firmwares.

3.1. Running FreeRTOS on QEMU

To run our FreeRTOS demo which uses LPUART3:

./build/qemu-system-arm -M nxps32k358evb -nographic -kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors

Explanation of the -serial flags: The emulated board has 16 LPUART interfaces. Since our DEMO project uses LPUART3 (the fourth interface, starting from 0), we disable the first three (-serial none) and connect the fourth to the terminal's standard input/output (-serial mon:stdio).


4. Debugging with GDB

For interactive debugging of the firmware running on QEMU, use GDB in combination with the -S -s flags.

4.1. Starting the Debug Session

Open two terminals.

Terminal 1: Start QEMU Run QEMU. The emulator will start and wait for a GDB connection.

./build/qemu-system-arm \
-M nxps32k358evb \
-nographic \
-kernel /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf \
-serial none -serial none -serial none -serial mon:stdio \
-d guest_errors \
-S -s
  • -S: Freezes the CPU at startup.
  • -s: Opens a GDB server on localhost:1234.

Terminal 2: Start GDB Launch gdb-multiarch to connect to QEMU.

gdb-multiarch /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf

4.2. Useful GDB Commands

Once GDB has started, run these commands:

# Connect to QEMU listening on port 1234target remote localhost:1234# Set the source file paths to allow GDB to find them# (adjust the paths for your environment)directory /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS
set substitute-path ../ /mnt/c/Users/vitoc/Desktop/workspace_group7/Demo_FreeRTOS/
# Now you can use standard GDB commands:# b main (set a breakpoint at main)# c (continue execution)# n (next, execute the next line)# p my_variable (print the value of a variable)

4.3. Executable Analysis

To view memory addresses and the disassembly of the ELF file, you can use objdump. This is useful for verifying the correct compilation and for low-level debugging.

arm-none-eabi-objdump -d /path/to/your/project/DEBUG_QEMU/Demo_FreeRTOS.elf > disassembly.txt

This command will save the entire code disassembly into a disassembly.txt file for easy reference.


5. Troubleshooting and Firmware Configuration

During development, some issues were identified when running code generated by S32 Design Studio on QEMU. The solutions are described below.

5.1. Issue: Infinite Loop on MC_ME

  • Symptom: The program gets stuck in a WaitForClock loop during the initialization of the MC_ME module.
  • Cause: The startup code generated by NXP contains a wait loop for clock stability that cannot be satisfied in the QEMU simulation environment. This block of code is protected by the preprocessor directive #ifndef SIM_TYPE_VDK.
  • Solution: You need to create a specific build configuration for QEMU that defines the SIM_TYPE_VDK symbol, thus excluding the problematic code from compilation.

Procedure in S32 Design Studio

  1. Create a New Build Configuration:
    • Go to Project -> Build Configurations -> Manage....
    • Select your "Debug" configuration and click New....
    • Name it Debug_QEMU and click OK.
    • Activate the new configuration: Project -> Build Configurations -> Set Active -> Debug_QEMU.
  2. Add the Preprocessor Symbol:
    • Right-click on your project and go to Properties.
    • Navigate to C/C++ Build -> Settings -> Tool Settings.
    • Under Standard S32DS C Compiler -> Preprocessor, click the "Add" icon (+) in the "Defined symbols (-D)" section.
    • Enter SIM_TYPE_VDK.
    • Important: Repeat the same step under S32 Assembler -> Preprocessor.
  3. Rebuild the Project:
    • Clean and rebuild the project (Project -> Clean... then Project -> Build Project). The output will be generated in the new Debug_QEMU folder.

5.2. Issue: TCM/ICM Initialization

  • The Problem: The standard firmware for NXP S32K3xx attempts to enable the Instruction Tightly Coupled Memory (ITCM) and Data Tightly Coupled Memory (DTCM) by writing to the ITCMCR (offset 0xF90) and DTCMCR (offset 0xF94) control registers. The default QEMU model for the ARMv7-M NVIC did not implement handlers for these addresses, causing an "unimplemented memory access" error and boot failure.
  • The Solution: A two-part solution was implemented:
    1. NVIC Patch: The qemu/hw/intc/armv7m_nvic.c file was modified to intercept and handle accesses to these registers, preventing the error and allowing the firmware to proceed.
    2. Memory Region Emulation: In the SoC model (hw/arm/nxps32k358_soc.c), the memory regions for ITCM (at address 0x00000000) and DTCM (at address 0x20000000) were declared, initialized, and mapped into the system memory map.
  • Implementation Status: It is important to note that the NVIC patch is a "dummy" implementation. It acknowledges the register writes but does not use the value to dynamically enable or disable the memory regions. As a result, ITCM and DTCM are always enabled in the current state of the emulation.

5.3. Issue: Enabling the Memory Protection Unit (MPU) in FreeRTOS

Enabling the MPU allows for task memory isolation, increasing system robustness and security. The configuration requires a two-level approach, both of which are mandatory.

Step 1: SDK-Level Enablement (S32 Design Studio)

This setting activates the hardware initialization of the MPU before the FreeRTOS scheduler starts.

  • Where: In the project properties in S32 Design Studio: Properties -> C/C++ Build -> Settings -> Standard S32 Compiler -> Preprocessor
  • What to do: Ensure that the MPU enable option is checked.
  • Purpose: This option adds a compiler directive (e.g., -D__MPU_ENABLE=1) that is used by the NXP startup code to configure basic memory regions (Flash, SRAM) at microcontroller startup.

Step 2: Operating System-Level Enablement (FreeRTOS)

This setting tells FreeRTOS to use MPU features for task management.

  • Where: In the FreeRTOSConfig.h configuration file.
  • What to do: Add or verify the presence of the following macros:
    /* Enable MPU support in FreeRTOS */#defineconfigENABLE_MPU 1
    /* Enable modern MPU wrappers, simplifying task management */#defineportUSING_MPU_WRAPPERS 1
    /* Static allocation is strongly recommended when using the MPU */#defineconfigSUPPORT_STATIC_ALLOCATION 1
    #defineconfigSUPPORT_DYNAMIC_ALLOCATION 1

This is the command that permits to check that the MPU is working, because in FreeRTOS implementation is present a function TestMPU that try to write on SRAM but the program crash. For this purpose we have builded another elf file.

```bash
./qemu-system-arm -M nxps32k358evb -nographic -kernel ../../Demo_FreeRTOS_MPU/Demo_FreeRTOS.elf -serial none -serial none -serial none -serial mon:stdio -d guest_errors
```

Why are both flags necessary?

Think of two levels that must work together:

  • S32 DS Compiler Flag (__MPU_ENABLE):This is the hardware level. Enabling it activates code in the NXP startup files that performs the very first MPU initialization at boot, setting up basic memory regions to allow the code to run before FreeRTOS starts. Without this, the MPU would remain off.
  • FreeRTOS Flag (configENABLE_MPU):This is the operating system level. Enabling it tells FreeRTOS to use the MPU APIs to manage task memory protection, save/restore their regions during context switches, and create "restricted" tasks.

In conclusion, you must enable both for correct operation.


6. Project Architecture

The purpose of the project is to test a FreeRTOS application that manages sensors without needing the physical board. To achieve this, QEMU was extended to simulate the necessary hardware components.

The interaction is based on a master-slave system:

  • Master: The FreeRTOS application running on the emulated processor.
  • Slave: A virtual sensor device (motor_speed) created specifically within QEMU.

6.1 Hardware Virtualization in QEMU

QEMU is an emulator and virtualizer that allows us to run code compiled for our NXP microcontroller directly on a PC, without needing the physical board. To do this, QEMU must simulate not only the CPU but also all the hardware peripherals.

In this project, we virtualized a complete master-slave system: the FreeRTOS application acts as the master, and a custom sensor device within QEMU acts as the slave.

a. LPUART for Debugging

LPUART Driver and Functions

The Lpuart_Uart_Ip driver is used to initialize and control the simulated serial port. In our project, its sole purpose is to provide a debug channel. The print() function uses it to send status messages from the microcontroller to the QEMU console, allowing us to monitor the application's behavior in real-time.

The UART_send_byte function transmits a single byte of data over a UART interface. It serves as a simplified wrapper for a more complex driver function, Lpuart_Uart_Ip_SyncSend.

When called, it passes the following parameters to the driver:

  • UART_LPUART_INTERNAL_CHANNEL: A constant that specifies which LPUART hardware peripheral to use.
  • &byte: A pointer to the single byte of data that needs to be sent.
  • 1: The number of bytes to transmit.
  • 100: A timeout value (likely in milliseconds) that the function will wait for the transmission to complete before failing.

b. The Virtual SPI System: Master and Slave

To test our firmware's logic realistically, we created a complete virtual SPI communication system composed of two main parts: the virtual hardware (the master controller and the slave sensor) and the software driver that the application uses to interact with them.

Virtual Hardware in QEMU

  • nxps32k358_lpspi.c (The Master Controller): This file, which we debugged and corrected, implements the model of the LPSPI peripheral inside QEMU. It acts as the master controller, simulating the hardware registers and behavior. It receives commands from our FreeRTOS application (via the Lpspi_Ip driver) and manages the data flow on the simulated SPI bus.

  • motor_speed.c (The Slave Device): This file defines a new virtual device for QEMU that behaves like a motor speed sensor. It is the slave in our system. It's programmed to listen on the SPI bus and respond to a specific command (CMD_GET_SPEED, defined as 0xAA) by sending back a random numerical value, simulating a real-world sensor.

6.2 The Lpspi_Ip Driver in the FreeRTOS Application

The Lpspi_Ip is the high-level software driver provided by NXP that our FreeRTOS application uses to control the LPSPI hardware. We interact with it primarily through the function Lpspi_Ip_SyncTransmit.

  • The Role of Lpspi_Ip_SyncTransmit

    This function is the bridge between our application logic and the SPI hardware. The Sync (Synchronous) part is crucial: it means that when a task calls this function, it stops and waits (it is "blocked") until the entire SPI data exchange is complete.

    In our Motor_Sensor_ReadValue function, we use it like this:

    Lpspi_Ip_SyncTransmit(&MASTER_EXTERNAL_DEVICE, &cmd, rx_buff, 1, 1000);

    Here is a breakdown of each parameter:

    1. &MASTER_EXTERNAL_DEVICE: A pointer to a configuration structure defining the slave device we want to talk to. It tells the driver which Chip Select (CS) pin to use and other specific settings for that slave.
    2. &cmd: A pointer to the data we want to send. In our case, this is the command 0xAA.
    3. rx_buff: A pointer to the buffer where the received data will be stored. While the master sends the command, the slave simultaneously sends a byte back, which is stored here.
    4. 1: The length of the transfer. This tells the driver to send one byte and receive one byte.
    5. 1000: A timeout value in milliseconds. This is a safety feature to prevent the application from freezing if the hardware gets stuck.

6.3 FreeRTOS Tasks

A task is a function that runs as an independent mini-program. Our system is composed of three main tasks that drive all the hardware interactions:

  • ReadSpeedTask: This is the primary active task. It is responsible for calling Motor_Sensor_ReadValue(), which in turn uses Lpspi_Ip_SyncTransmit to communicate with the virtual sensor and read the speed.
  • CheckSpeedTask: This task waits for ReadSpeedTask to finish. It then analyzes the speed value and uses the LPUART (via the print() function) to report the system's status.
  • TaskCodeC: An auxiliary task activated by a software timer to demonstrate asynchronous execution, independent of the main sensor-reading loop.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages