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以后的版本,稳定性好了很多。
创建工程的基本步骤:
- 启动Vitis:在终端输入
vitis,或者从桌面图标启动 - 创建工作空间:建议用英文路径,别带空格。我吃过这个亏——中文路径导致编译报错,查了半天才发现是路径问题
- 创建应用工程:File → New → Application Project
- 选择硬件平台:导入从Vivado导出的.xsa文件
- 选择模板:裸机选“Hello World”,Linux选“Linux Application”
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");
3.6 裸机 vs Linux:怎么选?
| 对比项 | 裸机 | Linux |
|---|---|---|
| 实时性 | 微秒级 | 毫秒级(RT补丁可到微秒) |
| 开发难度 | 低,直接操作寄存器 | 高,需理解内核机制 |
| 功能扩展 | 麻烦,需自己实现协议栈 | 方便,有现成驱动和网络栈 |
| 适用场景 | 电流环、编码器读取 | 轨迹规划、人机交互、网络通信 |
我个人建议:电机控制的底层(电流环、速度环)用裸机,上层(轨迹规划、状态机)跑Linux。Zynq的优势就在这里——FPGA做硬件加速,ARM双核一个跑裸机一个跑Linux,各司其职。
3.7 本章知识体系
下面这张图,是我梳理的本章核心逻辑。你看一眼,心里就有谱了:
这张图把本章的核心逻辑串起来了:底层环境搭好之后,你面临两个选择——裸机还是Linux。选哪个,取决于你的实时性要求和功能复杂度。我在实际项目中,经常是两者混用,ARM的Cortex-A核跑Linux做管理,Cortex-R核跑裸机做实时控制。
好了,环境搭好了,下一章咱们就开始真正动手——用FPGA做一个PWM发生器,让电机转起来。
公众号:蓝海资料掘金营,微信deep3321