Contact Sales & After-Sales Service

Contact & Quotation

  • Inquire: Call 0086-755-23203480, or reach out via the form below/your sales contact to discuss our design, manufacturing, and assembly capabilities.
  • Quote: Email your PCB files to Sales@pcbsync.com (Preferred for large files) or submit online. We will contact you promptly. Please ensure your email is correct.
Drag & Drop Files, Choose Files to Upload You can upload up to 3 files.

Notes:
For PCB fabrication, we require PCB design file in Gerber RS-274X format (most preferred), *.PCB/DDB (Protel, inform your program version) format or *.BRD (Eagle) format. For PCB assembly, we require PCB design file in above mentioned format, drilling file and BOM. Click to download BOM template To avoid file missing, please include all files into one folder and compress it into .zip or .rar format.

Zynq Programming Guide: From Bare-Metal to RTOS

After spending years developing firmware for Zynq-based systems, I’ve learned that choosing the right software approach can make or break a project. Whether you need the raw speed of bare-metal code, the task management of Zynq FreeRTOS, or the determinism of Zynq VxWorks, understanding your options matters. This guide walks through Zynq programming from the ground up, covering everything from simple register access to full Zynq RTOS implementations.

Understanding the Zynq Programming Landscape

The Zynq-7000 SoC combines dual ARM Cortex-A9 processors with programmable logic, creating a unique development challenge. Unlike traditional microcontrollers where you pick one approach and stick with it, Zynq programming offers multiple paths depending on your application requirements.

Three Approaches to Zynq Software Development

ApproachComplexityBoot TimeReal-Time CapabilityUse Case
Bare-MetalLowFast (<100ms)Excellent (no OS overhead)Simple control loops, fast prototyping
Zynq RTOSMediumMedium (100-500ms)Good to ExcellentMulti-task applications, networking
LinuxHighSlow (5-30s)LimitedComplex applications, UI, networking

Each approach has its place. I’ve used bare-metal for motor control applications where every microsecond counts, Zynq FreeRTOS for industrial controllers with multiple concurrent tasks, and Linux for systems requiring extensive networking and user interfaces. The key is matching the approach to your requirements.

Getting Started with Bare-Metal Zynq Programming

Bare-metal programming means your code runs directly on the hardware without any operating system layer. It’s the most straightforward approach and where most engineers start their Zynq programming journey.

Setting Up the Vitis Development Environment

AMD’s Vitis IDE (which replaced SDK) handles bare-metal development. After exporting your hardware design from Vivado as an XSA file, you create a platform project that generates the Board Support Package (BSP).

The BSP contains drivers for all peripherals in your design. For a typical Zynq-7000 system, you get drivers for:

PeripheralDriverHeader File
GPIO (PS)xgpiopsxgpiops.h
GPIO (AXI)xgpioxgpio.h
UARTxuartpsxuartps.h
Timerxscutimerxscutimer.h
Interrupt Controllerxscugicxscugic.h
SPIxspipsxspips.h
I2Cxiicpsxiicps.h
Ethernetxemacpsxemacps.h

Writing Your First Bare-Metal Application

A basic bare-metal application follows a predictable structure. Here’s what a typical GPIO control application looks like:

#include “xparameters.h”

#include “xgpiops.h”

#include “xil_printf.h”

#define GPIO_DEVICE_ID  XPAR_XGPIOPS_0_DEVICE_ID

#define LED_PIN         7

XGpioPs Gpio;

int main(void)

{

    XGpioPs_Config *ConfigPtr;

    // Initialize GPIO driver

    ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);

    XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);

    // Set LED pin as output

    XGpioPs_SetDirectionPin(&Gpio, LED_PIN, 1);

    XGpioPs_SetOutputEnablePin(&Gpio, LED_PIN, 1);

    // Toggle LED forever

    while(1) {

        XGpioPs_WritePin(&Gpio, LED_PIN, 1);

        usleep(500000);

        XGpioPs_WritePin(&Gpio, LED_PIN, 0);

        usleep(500000);

    }

    return 0;

}

This pattern of lookup config, initialize, configure, and use appears throughout Zynq programming regardless of which peripheral you’re working with.

Read more Xilinx FPGA Series:

Implementing Interrupts in Bare-Metal Applications

Most real applications need interrupts. The Zynq-7000 uses a Generic Interrupt Controller (GIC) that handles both private peripheral interrupts (PPI) and shared peripheral interrupts (SPI).

Setting up interrupts requires several steps:

  1. Initialize the GIC driver
  2. Connect your interrupt handler
  3. Enable the specific interrupt
  4. Enable interrupts globally

#include “xscugic.h”

XScuGic InterruptController;

int SetupInterruptSystem(void)

{

    XScuGic_Config *IntcConfig;

    // Initialize the interrupt controller

    IntcConfig = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID);

    XScuGic_CfgInitialize(&InterruptController, IntcConfig,

                          IntcConfig->CpuBaseAddress);

    // Connect to the hardware

    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,

                (Xil_ExceptionHandler)XScuGic_InterruptHandler,

                &InterruptController);

    // Enable interrupts

    Xil_ExceptionEnable();

    return XST_SUCCESS;

}

Running Both ARM Cores Simultaneously

The Zynq-7000’s dual Cortex-A9 cores can run independent bare-metal applications. CPU0 boots first and can wake CPU1 using the System Level Control Register (SLCR). This asymmetric multiprocessing (AMP) configuration works well for applications requiring hard real-time on one core while the other handles less critical tasks.

Key considerations for dual-core bare-metal:

AspectCPU0CPU1
Boot SequenceBoots first from FSBLWoken by CPU0
MemoryFull DDR accessMust avoid CPU0 regions
InterruptsConfigure GIC firstUses GIC configured by CPU0
CacheEnable independentlyEnable independently
CommunicationShared OCM or DDRShared OCM or DDR

The On-Chip Memory (OCM) is perfect for inter-processor communication because both cores can access it without cache coherency issues.

Migrating to Zynq FreeRTOS

When your bare-metal application grows beyond a simple control loop, managing multiple tasks becomes challenging. That’s where Zynq FreeRTOS comes in. It’s an open-source real-time operating system that adds task scheduling, inter-task communication, and timing services while keeping overhead minimal.

Why Choose Zynq FreeRTOS

FreeRTOS offers several advantages over bare-metal:

FeatureBare-MetalZynq FreeRTOS
Task ManagementManual (state machines)Built-in scheduler
Timing ServicesPolling or interruptsvTaskDelay, software timers
Inter-task CommunicationGlobal variablesQueues, semaphores, mutexes
Memory ManagementStatic allocationDynamic or static allocation
Priority HandlingManual implementationPriority-based preemption
Code PortabilityPlatform-specificPortable API

The learning curve is manageable, and the benefits become apparent as soon as you have more than two or three concurrent activities to manage.

Creating a Zynq FreeRTOS Project

In Vitis, creating a FreeRTOS project is straightforward. When creating a new platform project, select “freertos10_xilinx” as the operating system instead of “standalone.”

The platform generates a FreeRTOS BSP that includes:

  • FreeRTOS kernel source files
  • Zynq-specific port layer
  • Xilinx driver libraries
  • Configuration header (FreeRTOSConfig.h)

FreeRTOS Task Structure for Zynq

Every FreeRTOS task follows the same basic structure. Tasks run in an infinite loop and should never return:

#include “FreeRTOS.h”

#include “task.h”

void vLEDTask(void *pvParameters)

{

    // Task initialization

    XGpioPs_SetDirectionPin(&Gpio, LED_PIN, 1);

    XGpioPs_SetOutputEnablePin(&Gpio, LED_PIN, 1);

    // Task loop – runs forever

    for(;;)

    {

        XGpioPs_WritePin(&Gpio, LED_PIN, 1);

        vTaskDelay(pdMS_TO_TICKS(500));

        XGpioPs_WritePin(&Gpio, LED_PIN, 0);

        vTaskDelay(pdMS_TO_TICKS(500));

    }

}

int main(void)

{

    // Create the LED task

    xTaskCreate(vLEDTask,

                “LED”,

                configMINIMAL_STACK_SIZE,

                NULL,

                tskIDLE_PRIORITY + 1,

                NULL);

    // Start the scheduler

    vTaskStartScheduler();

    // Should never reach here

    for(;;);

}

The vTaskDelay() function is crucial because it yields CPU time to other tasks instead of busy-waiting.

Inter-Task Communication with Queues

Queues provide thread-safe data passing between tasks. They’re essential for producer-consumer patterns common in embedded systems:

#include “queue.h”

QueueHandle_t xSensorQueue;

void vSensorTask(void *pvParameters)

{

    uint32_t ulSensorValue;

    for(;;)

    {

        // Read sensor

        ulSensorValue = ReadADC();

        // Send to queue (block for 100ms if queue full)

        xQueueSend(xSensorQueue, &ulSensorValue, pdMS_TO_TICKS(100));

        vTaskDelay(pdMS_TO_TICKS(10));

    }

}

void vProcessingTask(void *pvParameters)

{

    uint32_t ulReceivedValue;

    for(;;)

    {

        // Wait for data from queue

        if(xQueueReceive(xSensorQueue, &ulReceivedValue, portMAX_DELAY))

        {

            ProcessSensorData(ulReceivedValue);

        }

    }

}

int main(void)

{

    // Create queue for 10 sensor readings

    xSensorQueue = xQueueCreate(10, sizeof(uint32_t));

    xTaskCreate(vSensorTask, “Sensor”, 1024, NULL, 2, NULL);

    xTaskCreate(vProcessingTask, “Process”, 1024, NULL, 1, NULL);

    vTaskStartScheduler();

    for(;;);

}

Configuring FreeRTOS for Zynq

The FreeRTOSConfig.h file controls kernel behavior. Key settings for Zynq-7000 include:

Configuration OptionTypical ValuePurpose
configCPU_CLOCK_HZ666666687CPU frequency (from PS)
configTICK_RATE_HZ1000Tick frequency (1ms ticks)
configMAX_PRIORITIES8Number of priority levels
configMINIMAL_STACK_SIZE200Minimum task stack (words)
configTOTAL_HEAP_SIZE65536Heap for dynamic allocation
configUSE_PREEMPTION1Enable preemptive scheduling
configUSE_TIME_SLICING1Round-robin for same priority

The tick timer typically uses the ARM private timer (SCU timer), which FreeRTOS configures automatically through the Xilinx port layer.

Read more Xilinx Products:

Integrating lwIP with Zynq FreeRTOS

Many Zynq applications need networking. The lightweight IP (lwIP) stack integrates well with FreeRTOS and supports TCP/IP, UDP, DHCP, and more.

AMD provides example projects that demonstrate FreeRTOS with lwIP:

Example ApplicationDescription
freertos_lwip_echo_serverTCP echo server for testing
freertos_lwip_tcp_perf_clientTCP performance testing client
freertos_lwip_tcp_perf_serverTCP performance testing server
freertos_lwip_udp_perf_clientUDP performance testing client

These examples provide excellent starting points for networked applications.

Commercial Zynq RTOS Options: VxWorks

While FreeRTOS handles many applications, some industries require certified, commercially supported operating systems. Zynq VxWorks from Wind River is the industry standard for safety-critical and mission-critical applications.

When to Consider Zynq VxWorks

RequirementFreeRTOSZynq VxWorks
Safety Certification (DO-178C, IEC 62304)Not certifiedVxWorks Cert available
Commercial SupportCommunity + AWSFull Wind River support
Long-term MaintenanceOpen source updatesGuaranteed lifecycle
Deterministic SchedulingGoodExcellent
Memory ProtectionLimitedFull MMU support
POSIX CompatibilityVia wrapperNative support
License CostFree (MIT)Commercial license required

Aerospace, defense, medical devices, and industrial safety systems often mandate Zynq VxWorks or similar certified RTOS solutions.

VxWorks Architecture on Zynq-7000

VxWorks uses the same boot sequence as other Zynq software: FSBL loads the VxWorks bootloader, which then loads the VxWorks kernel. The VxWorks BSP for Zynq-7000 includes:

ComponentFunction
Board Support PackageHardware abstraction layer
VxWorks KernelReal-time scheduler, memory management
Device DriversUART, Ethernet, SPI, I2C, GPIO
Network StackFull TCP/IP, IPv6 support
File SystemsdosFs, HRFS, NFS client
Development ToolsWind River Workbench IDE

Setting Up Zynq VxWorks Development

VxWorks development requires Wind River Workbench and a valid license. The workflow follows these steps:

  1. Create a VxWorks Source Build (VSB) project
  2. Configure the VSB for Zynq-7000 ARM Cortex-A9
  3. Build the VxWorks Image Project (VIP)
  4. Configure bootloader (typically U-Boot)
  5. Deploy to target via SD card, QSPI, or TFTP

The cross-compilation toolchain compiles your application code on a development host, producing ARM binaries for the Zynq target.

VxWorks Real-Time Performance

VxWorks excels at deterministic performance. Interrupt latency and task switching times are guaranteed and documented:

MetricTypical Value (Zynq-7000 @ 667MHz)
Interrupt Latency<1 µs
Task Switch Time<2 µs
Semaphore Give/Take<1 µs
Message Queue Send<2 µs

These numbers make Zynq VxWorks suitable for hard real-time applications where missing a deadline causes system failure.

Asymmetric Multiprocessing: Mixing Operating Systems

One of Zynq’s most powerful capabilities is running different operating systems on each ARM core. You might run Linux on CPU0 for networking and user interface while CPU1 runs Zynq FreeRTOS or bare-metal code for real-time control.

OpenAMP Framework for Mixed OS

AMD supports the OpenAMP framework, which provides:

FeaturePurpose
RemoteprocLife cycle management (load, start, stop)
RPMsgInter-processor messaging
VirtIOVirtualized I/O queues

Linux acts as the master, managing the remote processor running FreeRTOS or bare-metal firmware. The remote processor can be loaded dynamically without rebooting the system.

Memory Partitioning for AMP

When running multiple operating systems, careful memory partitioning prevents conflicts:

Memory RegionSizeOwner
0x00000000 – 0x3FFFFFFF1 GBLinux (DDR)
0x3FF00000 – 0x3FFFFFFF1 MBShared (RPMsg buffers)
0x3FE00000 – 0x3FEFFFFF1 MBFreeRTOS code/data
0xFFFC0000 – 0xFFFFFFFF256 KBOCM (shared)

The device tree for Linux must be configured to avoid the regions reserved for the remote processor.

Debugging Zynq RTOS Applications

Debugging RTOS applications requires different techniques than bare-metal debugging. Task states, queue contents, and timing relationships all matter.

JTAG Debugging with Vitis

Vitis provides RTOS-aware debugging for FreeRTOS:

Debug FeatureDescription
Task ListView all tasks, states, priorities
Queue BrowserInspect queue contents
Semaphore StatusSee semaphore owners and waiters
Stack UsageMonitor task stack consumption
CPU LoadTask-level CPU utilization

Set breakpoints in task code, and the debugger shows which task hit the breakpoint along with the full call stack.

Using FreeRTOS Trace Hooks

FreeRTOS includes trace hook macros that third-party tools like Percepio Tracealyzer can capture. These provide non-intrusive insight into system behavior:

// In FreeRTOSConfig.h

#define configUSE_TRACE_FACILITY 1

#define configGENERATE_RUN_TIME_STATS 1

The trace output shows task execution patterns, timing, and resource usage graphically.

Essential Resources for Zynq Programming

Having the right documentation and tools makes Zynq programming significantly easier. Here are the resources I keep bookmarked:

Official AMD Documentation

DocumentNumberDescription
Zynq-7000 TRMUG585Complete technical reference
Embedded Design TutorialUG1165Step-by-step examples
Software Developers GuideUG821Software architecture overview
Standalone Library DocumentationUG643BSP driver API reference

Download Links

Vitis Unified Software Platform: https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vitis.html

Embedded Software Repository (GitHub): https://github.com/Xilinx/embeddedsw

FreeRTOS Kernel Source: https://github.com/FreeRTOS/FreeRTOS-Kernel

FreeRTOS Zynq Port Documentation: https://www.freertos.org/RTOS-Xilinx-Zynq.html

Wind River VxWorks Evaluation: https://www.windriver.com/evaluations/vxworks-eval/

Community Resources

AMD Adaptive Computing Wiki: https://xilinx-wiki.atlassian.net/wiki/spaces/A/overview

AMD Support Forums: https://support.xilinx.com/s/

FreeRTOS Community Forums: https://forums.freertos.org/

Frequently Asked Questions About Zynq Programming

What is the difference between bare-metal and RTOS programming on Zynq?

Bare-metal programming runs your code directly on the hardware without any operating system. You get maximum performance and minimal overhead, but you must manage everything yourself including timing, task switching, and resource sharing. Zynq RTOS programming adds a kernel layer that handles task scheduling, timing services, and inter-task communication. The RTOS takes care of context switching and priority management, letting you focus on application logic. For simple applications with one or two activities, bare-metal works fine. Once you have multiple concurrent tasks with different priorities and timing requirements, an RTOS dramatically simplifies development.

Can I run Zynq FreeRTOS on both ARM cores simultaneously?

Yes, but it’s not the typical configuration. The more common approach is running FreeRTOS on one core while the other runs bare-metal code or Linux. If you need FreeRTOS on both cores, you would run two separate FreeRTOS instances in an AMP configuration, not a single SMP instance. Each core runs its own scheduler with its own tasks. Communication between cores uses shared memory regions, typically the On-Chip Memory (OCM). AMD’s OpenAMP framework provides standardized mechanisms for this inter-processor communication. For true SMP FreeRTOS (single scheduler managing both cores), the standard Xilinx FreeRTOS port doesn’t support this out of the box.

How do I choose between Zynq FreeRTOS and Zynq VxWorks?

The decision usually comes down to three factors: certification requirements, budget, and support needs. If your application requires safety certification (aerospace DO-178C, medical IEC 62304, industrial IEC 61508), VxWorks Cert provides pre-certified components that dramatically reduce certification effort. FreeRTOS lacks these certifications. Budget matters because VxWorks requires commercial licensing while FreeRTOS is free under the MIT license. For startups or cost-sensitive projects, FreeRTOS is attractive. Finally, consider support requirements. VxWorks includes commercial support from Wind River with guaranteed response times and long-term maintenance commitments. FreeRTOS relies on community support and AWS stewardship. For mission-critical applications in regulated industries, Zynq VxWorks is usually the right choice despite higher cost.

What timer does FreeRTOS use for the tick on Zynq-7000?

FreeRTOS on Zynq-7000 typically uses the ARM Cortex-A9 private timer (also called the SCU timer) for the tick interrupt. This timer is part of the Snoop Control Unit and runs at half the CPU frequency. The Xilinx FreeRTOS port configures this timer automatically based on the configCPU_CLOCK_HZ and configTICK_RATE_HZ settings in FreeRTOSConfig.h. You can also use the Triple Timer Counter (TTC) if you need the private timer for other purposes, but this requires modifying the port layer. The tick rate is commonly set to 1000 Hz (1ms ticks), providing a good balance between timing resolution and overhead.

How do I share peripherals between tasks in Zynq FreeRTOS?

Peripheral sharing requires protection to prevent concurrent access corruption. FreeRTOS provides mutexes (mutual exclusion semaphores) for this purpose. Before accessing a shared peripheral like UART or SPI, a task takes the mutex. After completing the access, it gives the mutex back. Other tasks attempting to use the peripheral block until the mutex becomes available. For simple cases, binary semaphores work, but mutexes are preferred because they include priority inheritance, which prevents priority inversion problems. Here’s the pattern: create a mutex during initialization, then bracket each peripheral access with xSemaphoreTake() and xSemaphoreGive() calls. For interrupt-driven peripherals, use task notifications or queues to signal the task from the ISR rather than blocking in the interrupt handler.

Performance Optimization Tips for Zynq RTOS

Getting your Zynq RTOS application running is one thing. Getting it running efficiently is another. Here are techniques I’ve found effective across many projects.

Stack Size Optimization

Each FreeRTOS task needs its own stack, and over-allocating wastes precious memory. Use the uxTaskGetStackHighWaterMark() function to measure actual stack usage during development:

UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);

xil_printf(“Stack remaining: %d words\n”, uxHighWaterMark);

Set your stack sizes to about 20-30% above the measured high water mark to provide safety margin without wasting memory.

Interrupt Priority Configuration

Zynq’s GIC supports priority levels for interrupts. Keep high-frequency interrupts at higher priority than lower-frequency ones. Also ensure that any interrupt calling FreeRTOS API functions uses priority levels at or below configMAX_SYSCALL_INTERRUPT_PRIORITY.

Cache Configuration for Real-Time Performance

The Cortex-A9’s L1 and L2 caches dramatically improve performance but can introduce timing variability. For hard real-time code sections, consider using the OCM which doesn’t require cache management. For cached memory regions used with DMA, use Xil_DCacheFlushRange() and Xil_DCacheInvalidateRange() appropriately.

Choosing Your Zynq Programming Path

After working through numerous Zynq projects, I’ve developed a simple decision framework. Start with bare-metal for initial hardware bring-up and simple applications. Move to Zynq FreeRTOS when you need multiple concurrent tasks, timing services, or networking. Consider Zynq VxWorks when certification, long-term support, or hard real-time guarantees are required.

The beauty of Zynq programming is that you’re not locked into one approach. You can run bare-metal on CPU1 for real-time control while CPU0 runs Linux for user interface and networking. Or run FreeRTOS on one core handling sensors while VxWorks handles safety-critical functions on the other.

The key is understanding what each approach offers and matching it to your application requirements. With the tools and techniques covered in this guide, you have the foundation to make that decision and implement it successfully.

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Sales & After-Sales Service

Contact & Quotation

  • Inquire: Call 0086-755-23203480, or reach out via the form below/your sales contact to discuss our design, manufacturing, and assembly capabilities.

  • Quote: Email your PCB files to Sales@pcbsync.com (Preferred for large files) or submit online. We will contact you promptly. Please ensure your email is correct.

Drag & Drop Files, Choose Files to Upload You can upload up to 3 files.

Notes:
For PCB fabrication, we require PCB design file in Gerber RS-274X format (most preferred), *.PCB/DDB (Protel, inform your program version) format or *.BRD (Eagle) format. For PCB assembly, we require PCB design file in above mentioned format, drilling file and BOM. Click to download BOM template To avoid file missing, please include all files into one folder and compress it into .zip or .rar format.