3、ARM端开发环境搭建:Ubuntu下交叉编译工具链安装、Vitis/SDK工程创建、裸机与Linux驱动开发基础

好,咱们进入第三章。这一章,说白了就是给你手里的“枪”上子弹。ARM端开发环境搭建,是所有后续实战的基础。我见过太多新手,代码写得飞起,结果环境配了三天,最后发现是工具链版本不对——这种坑,我踩过不止一次。

咱们的目标很明确:在Ubuntu下把交叉编译工具链装好,把Vitis/SDK工程创建流程跑通,再搞清楚裸机和Linux驱动开发到底有啥区别。嗯,一步步来。

3.1 为什么需要交叉编译?

你想想看,你的PC是x86架构,ARM芯片是ARM架构。直接在PC上编译出来的程序,ARM芯片跑不了。这就好比你在中文环境写了一份英文说明书,得找人翻译一下——交叉编译器就是这个“翻译官”。

我个人习惯把交叉编译链安装在/opt目录下,这样不会污染系统自带的gcc。当然,你也可以用~/tools,看个人喜好。

3.2 安装交叉编译工具链

以Xilinx的Zynq系列为例,我们常用的是arm-linux-gnueabihf-这套工具链。安装步骤其实不复杂:

# 下载工具链(以Linaro为例)
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

# 解压到指定目录
sudo tar -xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz -C /opt/

# 添加环境变量
echo 'export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin' >> ~/.bashrc
source ~/.bashrc

# 验证安装
arm-linux-gnueabihf-gcc --version
小提示: 我曾经遇到过解压后路径名带版本号,导致脚本里写死路径后升级工具链时全部报错。建议用软链接:ln -s /opt/gcc-linaro-7.5.0 /opt/arm-gcc,这样以后换版本只改软链接就行。

3.3 Vitis/SDK工程创建流程

Vitis(以前叫SDK)是Xilinx的统一开发环境。说实话,早期版本挺难用的,动不动就卡死。但到了2020以后的版本,稳定性好了很多。

创建工程的基本步骤:

  1. 启动Vitis:在终端输入vitis,或者从桌面图标启动
  2. 创建工作空间:建议用英文路径,别带空格。我吃过这个亏——中文路径导致编译报错,查了半天才发现是路径问题
  3. 创建应用工程:File → New → Application Project
  4. 选择硬件平台:导入从Vivado导出的.xsa文件
  5. 选择模板:裸机选“Hello World”,Linux选“Linux Application”
注意: 如果你用的是Zynq-7000系列,记得在Board Support Package (BSP)里勾选“xilffs”和“xilfpga”库。我之前做SD卡读写时,忘了加xilffs库,折腾了两天才发现是BSP配置漏了。

3.4 裸机开发基础

裸机开发,说白了就是没有操作系统,你的程序直接跑在硬件上。适合做实时性要求高的任务,比如电机控制中的电流环。

一个典型的裸机程序结构:

#include "xparameters.h"
#include "xgpio.h"
#include "xil_printf.h"

XGpio gpio_led;

int main() {
    // 初始化GPIO
    XGpio_Initialize(&gpio_led, XPAR_AXI_GPIO_0_DEVICE_ID);
    XGpio_SetDataDirection(&gpio_led, 1, 0x0);  // 设置为输出

    while(1) {
        XGpio_DiscreteWrite(&gpio_led, 1, 0xFF);  // 点亮LED
        sleep(1);
        XGpio_DiscreteWrite(&gpio_led, 1, 0x00);  // 熄灭LED
        sleep(1);
    }
    return 0;
}

嗯,这里要注意:裸机程序没有进程调度,你的while循环会一直占着CPU。如果要做多任务,得自己实现一个简单的调度器——我在做四轴飞行器飞控时就是这么干的。

3.5 Linux驱动开发基础

Linux驱动开发,和裸机完全是两码事。你想想看,Linux下有虚拟内存、进程调度、中断上下文的限制,写驱动得遵循内核的规则。

最简单的驱动——字符设备驱动:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "my_motor_ctrl"

static int major_num;
static char kernel_buffer[256];

static ssize_t device_read(struct file *filp, char __user *buffer, 
                           size_t length, loff_t *offset) {
    // 从内核空间拷贝到用户空间
    copy_to_user(buffer, kernel_buffer, length);
    return length;
}

static ssize_t device_write(struct file *filp, const char __user *buffer, 
                            size_t length, loff_t *offset) {
    // 从用户空间拷贝到内核空间
    copy_from_user(kernel_buffer, buffer, length);
    return length;
}

static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
};

static int __init my_driver_init(void) {
    major_num = register_chrdev(0, DEVICE_NAME, &fops);
    printk(KERN_INFO "Motor driver loaded with major number %d\n", major_num);
    return 0;
}

static void __exit my_driver_exit(void) {
    unregister_chrdev(major_num, DEVICE_NAME);
    printk(KERN_INFO "Motor driver unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
核心区别: 裸机开发直接操作物理地址,Linux驱动操作虚拟地址。裸机的中断响应在微秒级,Linux驱动的中断响应在毫秒级——做运动控制时,这个差异必须心里有数。

3.6 裸机 vs Linux:怎么选?

对比项 裸机 Linux
实时性 微秒级 毫秒级(RT补丁可到微秒)
开发难度 低,直接操作寄存器 高,需理解内核机制
功能扩展 麻烦,需自己实现协议栈 方便,有现成驱动和网络栈
适用场景 电流环、编码器读取 轨迹规划、人机交互、网络通信

我个人建议:电机控制的底层(电流环、速度环)用裸机,上层(轨迹规划、状态机)跑Linux。Zynq的优势就在这里——FPGA做硬件加速,ARM双核一个跑裸机一个跑Linux,各司其职。

3.7 本章知识体系

下面这张图,是我梳理的本章核心逻辑。你看一眼,心里就有谱了:

ARM端开发环境搭建知识体系 Ubuntu环境 交叉编译工具链 Vitis/SDK 裸机开发 Linux驱动开发 直接寄存器操作 字符设备/中断/IOCTL 协同决策 实时任务→裸机 | 复杂逻辑→Linux

这张图把本章的核心逻辑串起来了:底层环境搭好之后,你面临两个选择——裸机还是Linux。选哪个,取决于你的实时性要求和功能复杂度。我在实际项目中,经常是两者混用,ARM的Cortex-A核跑Linux做管理,Cortex-R核跑裸机做实时控制。

我的经验: 刚开始学的时候,别贪多。先把裸机开发跑通——点个LED、读个按键,找到感觉了再碰Linux驱动。Linux驱动调试起来比裸机麻烦得多,一个内核panic就得重启整个板子。

好了,环境搭好了,下一章咱们就开始真正动手——用FPGA做一个PWM发生器,让电机转起来。


公众号:蓝海资料掘金营,微信deep3321