nios2 C语言编程方法有哪些关键步骤?

99ANYc3cd6
预计阅读时长 28 分钟
位置: 首页 C语言 正文

核心概念:Nios II 嵌入式编程与标准 C 的区别

在开始之前,必须理解以下几点,这是 Nios II C 编程的基石:

nios2 c语言编程方法
(图片来源网络,侵删)
特性 标准 PC (如 Windows/Linux) Nios II 嵌入式系统
运行环境 操作系统(Windows, Linux)提供丰富的库和抽象。 裸机实时操作系统,通常没有标准库。
标准库 完整的 stdio.h, stdlib.h, string.h 等。 受限的 Newlib,只提供部分功能,且可能不支持文件 I/O(因为没有文件系统)。
I/O 操作 printf 输出到控制台,fopen 打开文件。 printf 重定向到 UART(串口),fopen 通常不可用,I/O 通过读写特定内存地址(硬件寄存器)完成。
内存管理 动态内存分配 (malloc, free) 是常态。 谨慎使用动态内存malloc 可能导致内存碎片,在嵌入式系统中常被禁用或使用静态内存池。
启动流程 操作系统加载程序并设置环境。 Bootloader硬件初始化代码 设置时钟、栈、堆,并调用 main 函数。
调试 GDB, IDE 调试器。 JTAG 调试,通过 FPGA 的 JTAG 接口,在硬件或模拟器中单步执行、查看内存/寄存器。

开发环境:Nios II Software Build Tools (SBT)

这是官方的、也是推荐的集成开发环境,它不是一个传统的 IDE(如 Visual Studio),而是一套命令行工具和 Eclipse 插件的集合。

1 安装环境

你需要安装以下软件:

  1. Intel Quartus Prime:用于 FPGA 的设计、综合和配置,其中包含了 Nios II SBT 的核心组件。
  2. Nios II SBT for Eclipse:这是一个 Eclipse IDE 插件,提供了图形化的项目管理、代码编辑和调试功能,强烈推荐使用它,而不是纯命令行。

安装步骤

  1. 安装 Quartus Prime。
  2. 运行 Nios II SBT 安装包,它会自动检测已安装的 Quartus 并集成。
  3. 安装完成后,启动 Eclipse,你将能看到 Nios II 的相关菜单和图标。

2 创建一个 Nios II 工程

Nios II 工程不是孤立存在的,它必须与一个 FPGA 工程紧密绑定。

nios2 c语言编程方法
(图片来源网络,侵删)

流程

  1. 创建/打开一个 Quartus FPGA 工程:这个工程已经包含了你的 Nios II 处理器系统,通常使用 SOPC Builder (旧版) 或 Platform Designer (新版) 来创建这个系统。

    • 在 Platform Designer 中,你需要添加:
      • Nios II Processor (选择 e.g., Nios II/e, Nios II/s, Nios II/f)
      • On-Chip Memory (用作程序和数据存储)
      • JTAG UART (用于 printf 调试和终端通信)
      • Interval Timer (用于 usleep, sleep 等延时函数)
      • 其他外设 (e.g., GPIO, SPI, I2C, Ethernet 等)
    • 生成系统后,Quartus 会生成一个 .ptf.sopcinfo 文件,这是 SBT 工程的“蓝图”。
  2. 在 Eclipse 中创建 Nios II 应用程序工程

    • 打开 Eclipse,进入 File -> New -> Nios II Application and BSP from Template
    • Select a system:选择你刚刚在 Quartus 中生成的 .sopcinfo 文件。
    • Select a template:选择一个模板,Hello World
    • Project name:给你的应用程序工程命名,my_app
    • BSP (Board Support Package) name:BSP 是连接你的 C 代码和硬件外设的桥梁,会自动生成,名称通常是 my_app_bsp
    • 点击 Finish。
  3. 工程结构

    nios2 c语言编程方法
    (图片来源网络,侵删)
    • my_app/:你的 C 源代码目录。
      • main.c:程序入口。
    • my_app_bsp/:BSP 目录,由 SBT 自动管理,不要手动修改。
      • system.h最重要的头文件!它定义了所有外设的基地址、中断号等硬件信息。
      • sys/alt_stdio.h:重定向标准 I/O 的头文件。
      • hal/:硬件抽象层库,提供访问外设的 API。

C 语言编程实践

1 Hello World - 标准输出重定向

默认情况下,printf 是不可用的,你需要将其重定向到 JTAG UART。

main.c 示例

#include <stdio.h> // 包含标准 I/O 头文件
#include "system.h" // 包含硬件定义头文件
#include "alt_printf.h" // 使用 alt_printf 更简单,它直接输出到 JTAG UART
int main()
{
    // 方法一:使用 alt_printf (推荐)
    // alt_printf 会自动重定向,无需额外设置
    alt_printf("Hello from Nios II! (using alt_printf)\n");
    // 方法二:使用标准 printf 并手动重定向
    // 需要在 main 函数开始时设置
    // 设置标准输入输出为 JTAG UART
    // setvbuf(stdin, NULL, _IONBF, 0);
    // setvbuf(stdout, NULL, _IONBF, 0);
    // setvbuf(stderr, NULL, _IONBF, 0);
    // printf("Hello from Nios II! (using standard printf)\n");
    // 简单延时
    for (volatile int i = 0; i < 10000000; i++);
    return 0;
}

关键点

  • #include "system.h":必须包含,它定义了 JTAG_UART_BASE 等宏。
  • alt_printf():是 Nios II BSP 提供的轻量级打印函数,直接与 JTAG UART 通信,非常方便。
  • 编译并运行程序后,打开 Nios II Console (在 Eclipse 的 Window -> Show View -> Nios II Console),就能看到打印信息。

2 硬件访问:直接寄存器操作 vs. HAL API

这是嵌入式编程的核心,假设你在 Platform Designer 中添加了一个名为 my_led_pio 的 PIO (Parallel I/O) 外设。

直接寄存器操作 (高效,但可移植性差)

每个外设都有一组内存映射的寄存器,PIO 通常至少有:

  • Data Register:读写数据。
  • Direction Register:设置引脚方向(输入/输出)。
#include "system.h"
#include "io.h" // 提供简单的内存读写宏
#define LED_PIO_BASE MY_LED_PIO_BASE // 假设在 system.h 中定义了 MY_LED_PIO_BASE
int main()
{
    // 设置所有 LED 引脚为输出
    IOWR(LED_PIO_BASE, 1, 0xFFFF); // 参数1:基地址, 参数2:寄存器偏移(0=Data, 1=Direction), 参数3:要写入的值
    while (1)
    {
        // 点亮所有 LED
        IOWR(LED_PIO_BASE, 0, 0xFFFF);
        for (volatile int i = 0; i < 10000000; i++);
        // 熄灭所有 LED
        IOWR(LED_PIO_BASE, 0, 0x0000);
        for (volatile int i = 0; i < 10000000; i++);
    }
    return 0;
}
  • #include "io.h":提供了 IOWR (I/O Write) 和 IORD (I/O Read) 宏。
  • 这种方法速度快,但代码与硬件地址紧密耦合。

使用 HAL API (推荐,可移植性好)

BSP 会为每个外设生成一个 C 驱动,你只需要包含相应的头文件并调用 API。

#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h" // PIO 的通用驱动头文件
#include "alt_types.h" // 提供数据类型定义
// 假设 PIO 实例名称为 "my_led_pio"
#define LED_PIO_BASE MY_LED_PIO_BASE
int main()
{
    // 设置所有引脚为输出
    IOWR_ALTERA_AVALON_PIO_DIRECTION(LED_PIO_BASE, 0xFFFF);
    while (1)
    {
        // 点亮所有 LED
        IOWR_ALTERA_AVALON_PIO_DATA(LED_PIO_BASE, 0xFFFF);
        for (volatile int i = 0; i < 10000000; i++);
        // 熄灭所有 LED
        IOWR_ALTERA_AVALON_PIO_DATA(LED_PIO_BASE, 0x0000);
        for (volatile int i = 0; i < 10000000; i++);
    }
    return 0;
}
  • #include "altera_avalon_pio_regs.h":这是由 BSP 自动生成的驱动文件。
  • IOWR_ALTERA_AVALON_PIO_DATAIOWR_ALTERA_AVALON_PIO_DIRECTION 是更清晰的 API。
  • 优点:代码可读性好,与具体硬件地址解耦,如果将来更换 PIO 实例,只需修改 system.h 中的基地址定义,代码无需改动。

3 使用中断

中断是高效处理外设事件的关键。

步骤

  1. 在硬件层面:在 Platform Designer 中,确保你的外设(如 Interval Timer)被连接到了 Nios II 的中断线上,并记下它的中断 ID。
  2. 在软件层面: a. 编写中断服务程序。 b. 注册 ISR:告诉 CPU 中断发生时应该调用哪个函数。 c. 使能中断:在 CPU 和外设上使能中断。

示例:使用 Interval Timer 产生周期性中断

#include <stdio.h>
#include "system.h"
#include "altera_avalon_timer_regs.h"
#include "alt_types.h"
#include "alt_irq.h"
// 假设 Timer 实例名为 "my_timer"
#define TIMER_BASE MY_TIMER_BASE
#define TIMER_IRQ MY_TIMER_IRQ // 中断号
volatile int counter = 0;
// 中断服务程序
void timer_isr(void* context)
{
    // 清除中断状态,否则会连续进入中断
    IOWR_ALTERA_AVALON_TIMER_STATUS(TIMER_BASE, 0);
    // 执行中断处理任务
    counter++;
    alt_printf("ISR called! Counter = %d\n", counter);
}
int main()
{
    // 安装中断服务程序
    alt_ic_isr_register(
        TIMER_IRQ,          // 中断号
        timer_isr,          // ISR 函数指针
        NULL,               // 传递给 ISR 的上下文
        NULL                // 用于错误返回,通常设为 NULL
    );
    // 配置 Timer
    // 设置周期 (1秒的时钟频率)
    IOWR_ALTERA_AVALON_TIMER_PERIOD_L(TIMER_BASE, 0x00F42400); // ~1 second @ 50MHz
    IOWR_ALTERA_AVALON_TIMER_PERIOD_H(TIMER_BASE, 0x00000000);
    // 启动 Timer 并使能中断
    IOWR_ALTERA_AVALON_TIMER_CONTROL(TIMER_BASE, ALTERA_AVALON_TIMER_CONTROL_START_MSK | ALTERA_AVALON_TIMER_CONTROL_ITO_MSK);
    // 全局使能中断
    alt_irq_enable(TIMER_IRQ);
    alt_printf("Main loop started...\n");
    while (1)
    {
        // 主循环可以执行其他任务
        // ...
    }
    return 0;
}

编译、链接与部署

1 编译流程

SBT 使用 make 工具,当你点击 Eclipse 中的 "Build Project" 时,SBT 会执行以下步骤:

  1. 编译:将 C 文件 (.c) 编译成目标文件 (.o)。
  2. 链接:将你的目标文件与 BSP 提供的库文件(如 hal.lib)链接在一起,生成一个可执行文件(在 Nios II 中通常是 .elf 格式)。
  3. 生成镜像:将 .elf 文件转换成 .flash.ram 文件,这些是二进制镜像,可以直接被 FPGA 的 Bootloader 或 EPCS/EPCQ 闪存加载。

2 部署到硬件

  1. 连接硬件:将开发板(如 DE0-Nano, DE10-Lite)通过 USB Blaster 连接到电脑。
  2. 配置 FPGA:在 Quartus 中编译整个工程,并使用 "Programmer" 工具将最终生成的 .sof 文件烧录到 FPGA 中,这一步会加载你的 Nios II 处理器系统。
  3. 下载程序
    • 在 Eclipse 中,右键点击 Nios II 应用工程,选择 Run As -> Nios II Hardware
    • SBT 会自动将编译好的程序通过 JTAG 下载到 FPGA 的片上内存中,并启动程序。
    • 你可以在 Nios II Console 中查看输出,也可以用逻辑分析仪或示波器观察 GPIO 引脚上的变化。

调试技巧

  • JTAG 调试:这是最强大的调试方式,设置断点、单步执行、查看变量、监视内存和寄存器,Eclipse 调试器与 Nios II GDB 服务器无缝集成。
  • alt_printf:你的“眼睛和耳朵”,合理使用打印语句来跟踪程序流程和变量状态。
  • 逻辑分析仪:对于硬件信号时序问题(如 SPI, I2C),使用 SignalTap II (Quartus 自带) 或外部逻辑分析仪是必不可少的。
  • 检查 system.h:当你不确定某个外设的基地址或中断号时,system.h 是你的第一手资料。

Nios II C 语言编程是一个结合了软件编程和硬件设计的领域,其核心流程是:

  1. 用 Platform Designer 设计硬件系统
  2. 用 SBT for Eclipse 创建软件工程,该工程会自动关联硬件设计。
  3. 在 C 代码中
    • 包含 system.h 获取硬件信息。
    • 使用 HAL API 或直接寄存器操作访问外设。
    • 使用 alt_printf 进行调试输出。
    • 编写并注册 ISR 来处理中断。
  4. 编译、下载到硬件,并通过 JTAG 调试。

掌握这种方法,你就可以充分利用 FPGA 的灵活性,为你的项目定制一个完美的嵌入式处理器系统。

-- 展开阅读全文 --
头像
main函数返回值到底该怎么用?
« 上一篇 03-08
织梦手机图片为何不自适应?
下一篇 » 03-08

相关文章

取消
微信二维码
支付宝二维码

目录[+]