在嵌入式的工具链选择上,我一直是个“实用主义至上”的人。所以当我拿到一块复旦微FMQL开发板,准备用IAR做PS侧裸机开发的时候,身边不少朋友的第一反应都是:国产Zynq用IAR?能行吗?这大概也是很多刚接触FMQL的工程师心里最大的疑问。这里先解释一下,标题里的PS不是Photoshop,而是Zynq这种SoC FPGA架构里的Processing System,也就是芯片里的ARM处理器子系统;PL则是可编程逻辑,也就是FPGA那部分。这篇是系列的第一篇前言,我会把这个系列到底要做什么、FMQL这颗芯片该怎么理解、为什么选IAR裸机这条路、开发前需要准备什么,以及后面几期打算怎么写,一次说清楚。如果你正在评估国产SoC FPGA,或者已经拿到FMQL的开发板但对ARM侧的开发方式还有点拿不准,这篇内容会比较适合你。
1. 项目背景:为什么我会选复旦微FMQL
1.1 复旦微FMQL到底是个什么东西
简单说,复旦微FMQL是国产SoC FPGA里非常有代表性的一类芯片。它内部同时集成了一个ARM处理器核心和一片可编程逻辑阵列,整体架构思路对标的就是Zynq-7000那一代产品,所以你在网上经常能看到“国产Zynq”这个叫法。我手头这块是FMQL20S400(不同批次的FMQL型号在逻辑资源规模和ARM核主频上会有差异,具体以官方选型手册为准),PS侧跑的是一个带浮点运算能力的ARM处理器核心,PL侧则是有着相当规模LUT、DSP和BRAM的FPGA逻辑。这种芯片最大的价值,在于“一个片上系统搞定需要CPU+FPGA配合的活儿”,不用再在外面挂两颗主控芯片,也不需要把ARM和FPGA之间的通信做成跨芯片的并行总线或者高速串行链路。
从实际应用来看,FMQL最常见的场景就是“ARM负责协议栈和业务逻辑,FPGA负责高速数据采集、信号处理和并行计算”。比如工业控制里的多轴运动控制,ARM侧跑EtherCAT主站或者Modbus协议,FPGA侧做编码器计数、PWM输出和位置闭环;再比如通信、机器视觉、仪器仪表领域,FPGA把ADC采集、滤波、FFT做完,ARM侧负责图像识别算法、网络上传和用户交互。如果一个产品只需要单片机加一个简单FPGA,那确实用不上FMQL;但如果你的系统需要“高吞吐数据通路+复杂业务流程”同时存在,FMQL这种架构就是最贴合需求的选择。
1.2 PS、PL,以及ARM和FPGA怎么分工
我经常用“厨房分工”来给刚入门的同事解释PS和PL。ARM核就像大厨,擅长判断和决策:什么时候起锅、盐放多少、菜品接单顺序都由它管,但一个人切菜备料速度有限。FPGA则是那组“流水线工位”,切丝、切丁、腌肉、摆盘全都是并行进行的,吞吐量极高,但你要让它自己决定今天做什么菜,它没这个能力。PS和PL之间,就是大厨和工位的关系。
在FMQL这颗芯片里,PS和PL不是两个独立芯片放在同一块板子上,而是通过芯片内部的AXI总线直接连在一起。也就是说,ARM侧可以把FPGA里的逻辑资源当作自己的“外设寄存器”来访问,读写一个AXI地址就能让PL侧的逻辑干一件事;反过来,FPGA侧也可以通过AXI互联发起对PS侧DDR内存的访问。比如机器视觉系统里,PL把摄像头数据实时搬到DDR的固定区域,ARM侧的算法程序直接从这块内存读数据,数据不需要经过外部总线拷贝,延迟低且带宽高。这个写系列的过程中,我会把PS访问PL、PL访问PS外挂DDR这类话题单独拆开讲,因为这里是SoC FPGA开发中最有嚼头,也是坑最多的地方。
1.3 为什么值得开一个系列来写
其实复旦微FMQL的硬件资料和官方SDK是能拿到的,网上也有零散的介绍,但大多数文档还是“器件手册式”的写法,告诉你寄存器叫什么、地址是多少,却不太会告诉你“一个没有任何FPGA背景的ARM工程师,怎么把手里的IAR工程跑起来”。再加上FMQL的开发方式和传统单片机有一些重要差异,比如上电启动顺序、DDR初始化、链接脚本的RAM空间定义,任何一个环节没搞对,程序就黑屏白屏,完全不知道问题出在哪。我决定把这个过程完整记录下来,一方面是自己踩坑的记录,避免过一段时间忘了又要重新查;另一方面也是因为这套组合确实有门槛,我希望后来的人能在我整理的基础上少走一点弯路。
这个系列不会只写点灯和串口这种皮毛,也不会一上来就搬Linux。核心路线是:先用IAR把PS侧裸机程序跑通,把ARM核的启动流程、时钟配置、DDR初始化、中断控制器、GPIO、UART、定时器都过一遍,然后再往里走,讲PS和PL之间的AXI通信、共享内存、中断协作,最后再根据进度决定是否切入Linux或者轻量级RTOS。抛掉操作系统的干扰,用最直接的方式理解这颗芯片的ARM侧是怎么工作的,这是整个系列的基调。
2. 选型思考:为什么是IAR,为什么是裸机
2.1 IAR for ARM到底能不能玩FMQL PS侧开发
如果你去查复旦微的官方资料,会发现他们更多推荐的是Linaro GCC工具链或者自家的SDK环境,很少有人提IAR。这并不奇怪,因为国内做SoC FPGA的工程师,很多是从FPGA背景转过来的,习惯的是Vivado、ISE那一套,ARM侧开发则倾向于用GCC和Linux。但如果你的技术底子是STM32这类单片机出身,那IAR是你最顺手、最有肌肉记忆的开发工具。IAR for ARM本来就是一个通用ARM编译器,只要目标芯片是ARM内核,它都能编译,区别只在于你有没有这个内核对应的设备描述文件、启动文件和链接脚本。
我自己就是因为常年用IAR,觉得Keil在某些大型工程的组织方式上不够利索,GCC的Makefile系统又要花额外精力维护,而IAR的工程管理、编译器优化选项、调试器集成度都比较均衡。FMQL的PS侧是标准ARM核,IAR完全有能力编译出能跑的裸机程序。这一步走通之后,后续调试体验其实比命令行工具链舒服不少。当然,我这么说不是让你无脑选IAR,如果你本身熟悉GCC,用VS Code加交叉编译器也能达到同样的目的,工具只是手段。这个系列选IAR,纯粹是因为它在Windows下IDE体验好、导入官方库方便、配合J-Link调试很顺畅,适合大多数嵌入式工程师的上手习惯。
2.2 裸机还是Linux,这里面的取舍很现实
很多拿到FMQL的人都会纠结一个问题:PS侧到底跑Linux还是裸机?我的看法是,先不用急着二选一,裸机不是一个比Linux“低级”的方案,而是一种完全不同的产品决策。跑Linux的好处是生态丰富、文件系统、网络协议栈、驱动框架都现成,但坏处也很明显:启动时间慢、实时性不可控、开发调试时需要维护设备树和内核,对团队的软件能力要求更高。如果你的产品只需要开机后几百毫秒内干活,或者有严格的中断响应时间要求,又不需要复杂的文件系统,那裸机反而是更合适的选择。
我见过不少工程师一上来就在FMQL上跑Linux,结果发现外设驱动就要折腾一两周,最后不得不回来用裸机。先裸机把PS侧每个外设都摸清楚,就算未来真的上了Linux,你也会对底层硬件行为有更准确的理解,排查内核问题的时候会轻松很多。尤其是FMQL这种集成芯片,PS侧的时钟、DDR控制器、中断控制器都是要主动初始化的,这些知识CFG操作在裸机环境里看得清清楚楚,放到Linux里就成了黑盒。
2.3 几种开发方案的真实对比
为了让还没入坑的朋友有个直观判断,我把几种常用的FMQL PS侧开发方案放在一起比较一下。这里不是要分个高下,而是想让你根据自己团队的技术栈,先想清楚哪条路对你最友好。
| 方案 | 优势 | 劣势 | 适合人群 |
|---|---|---|---|
| IAR + J-Link | IDE集成度高、调试体验好、单片机工程师上手快 | IAR是商业授权,设备支持包可能需要自己找 | 从STM32等MCU转SoC FPGA的工程师 |
| GCC交叉编译 + Makefile/CMake | 免费开源、可脚本化、方便持续集成 | 调试器配置麻烦、工程组织要靠自己、新手容易卡在工具链上 | 熟悉命令行开发的Linux玩家 |
| 官方SDK(Eclipse类图形界面) | 原厂配套、BSP驱动全、基本零门槛 | 环境依赖重、调试和代码编辑体验一般 | 走官方推荐路线的项目工程师 |
| Keil MDK | 也是ARM IDE、资源多 | 对Cortex-A级别的支持不如IAR成熟、大工程编译速度一般 | 纯粹习惯Keil且不接受改变的人 |
我这里再补充一个个人观点:选工具链的时候,真正应该看重的是“能不能断点停在汇编那行、能不能看寄存器和内存、能不能让你快速明白程序为什么跑飞”。IAR在这一点上做得很成熟,配合J-Link能在调试窗口里直接看到C代码和汇编的对应关系、外设寄存器的实时值,对裸机开发来说是实打实的效率提升。
3. 开工前的环境准备
3.1 软件清单:不是装了IAR就能直接开工
很多人拿到FMQL开发板后,第一件事就是装IAR,然后发现自己连芯片型号都找不到。这不是因为IAR不认识复旦微,而是因为IAR默认的设备库更新速度没那么快,你需要先找到复旦微官方提供的IAR设备支持包,装好之后才能在Target设备列表里看到FMQL系列。
除了IAR本身,我建议你提前把下面这几类软件都准备好:
- IAR Embedded Workbench for ARM,版本尽量新。我这次用的是9.60.4,新版本对新款调试器固件兼容性更好,对Cortex-A9这种应用处理器级别的内核支持也更完整。
- 复旦微官方设备支持包以及对应的BSP驱动库。IAR的设备描述文件、Flash Loader、启动文件模板都在这个包里。
- J-Link驱动软件。如果用J-Link调试,SEGGER的驱动和J-Link Commander最好装上,部分奇怪问题可以在命令行里直接发命令排查。
- 一个串口终端工具。裸机开发里printf重定向到UART是标配,常用的SecureCRT、MobaXterm、串口助手都行,能看16进制和ASCII显示就行。
还有一个容易忽略的:IAR的license。IAR是商业软件,得用正版授权或者合法的评估license,公司如果没有授权,可以用官方社区版的替代,或者老老实实申请评估序列号。千万别去下载网上那些来路不明的注册机,既可能踩法律风险,也不能保证编译工具链的完整性。
3.2 硬件清单:其实没比单片机复杂多少
FMQL裸机开发前期不需要很贵的硬件。开发板厂家一般会送配套电源、JTAG调试口和串口引出,你额外需要准备的其实就两样:调试器和串口线。
调试器方面,我用的是SEGGER J-Link。FMQL的PS侧是ARM核,JTAG的TCK、TMS、TDI、TDO引脚都会引到调试接口上,J-Link完全可以识别。如果你的开发板没有独立JTAG口而是用板载的调试器芯片,那看具体是哪一种CMSIS-DAP还是FTDI方案,在IAR的Debugger设置里选对对应的驱动类型就行。另外提醒一句,别图便宜买劣质拷贝J-Link,固件和驱动一更新就会出现掉线、断连,调试裸机程序最怕这种玄学问题。
串口线要特别注意电平标准,FMQL的PS侧UART引脚一般是1.8V或者3.3V电平,如果你的USB转串口模块是5V供电信号,要确认是否兼容,最好选带电平转换功能、支持3.3V/1.8V切换的模块。稳压电源的输出纹波也要关注,FMQL启动时对电源上电时序有一定要求,开发板上的电源管理电路一般会处理好,但外接电源电压不干净会导致PS偶尔启动异常,这个坑在后面排查部分会再提到。
3.3 IAR新建工程的最小流程
一开始不要在工程配置上追求完美,先跑通再说。我在IAR里建FMQL PS侧工程,核心步骤大概是这样:
- 打开IAR,选择File->New->Project,模板选Empty project,工具链选ARM。
- 在Project->Options->General Options->Target里,选择合适的FMQL设备(前提是设备支持包装好了)。选择设备后,IAR会自动使用对应的寄存器描述文件和启动配置。
- 把官方BSP里的启动文件和链接脚本加入工程。IAR的链接脚本后缀是.icf,这个文件决定了你的代码段、数据段、堆栈放在哪个地址区域,非常重要。官方BSP里通常有现成的模板,别自己从零写,先拿来用。
- 配置Debugger:在Options->Debugger里选择J-Link/J-Trace,然后在对应的Driver设置里选择Cortex-A9接口,下载速度先别拉满,设到1MHz左右比较稳。
- 确认C/C++ Compiler里的优化选项,最开始建议用Low或者None,方便调试时变量实时刷新。把Include路径指向BSP的include目录。
几步操作完,理论上一个空壳工程就能编译通过了。然后你在这个骨架里加自己的main函数、加外设初始化代码,整个开发流程就清晰了。
提示:新建工程这一步,最容易出问题的是.icf文件和启动文件不匹配。如果你的.icf文件定义的内存区域起始地址和FMQL实际DDR初始化之后的地图对不上,程序一启动就访问非法地址,直接hardfault。这部分属于启动流程核心知识,下一章展开说。
3.4 调试器配置的三个关键项
IAR的调试器配置说简单也简单,说复杂也复杂,关键参数字段就三个。
第一个是连接接口。FMQL的JTAG支持JTAG模式和SWD模式,Cortex-A9通常用JTAG模式,我在IAR里选择J-Link后,默认接口如果没有选对,连接会卡在“正在探测CPU”这一步。插上J-Link但识别不到设备,十有八九是接口模式选错了。
第二个是时钟频率。不是频率越高越好。调试器时钟设置太高容易因为线缆过长或者信号干扰导致连接不稳定,我一般先用默认的自动协商,不行就手动降到1MHz以下,稳定之后再逐步调高。
第三个是复位行为。裸机程序下载后需要复位一次让CPU从头执行,IAR里可以选择“复位并停止”或者“运行到main”。在开发初期我建议选“运行到main”,它能帮你避过启动文件里那些晦涩的CPU初始化过程,直接停在C语言入口,对调试体验的提升非常明显。
4. 把第一个裸机程序跑起来
4.1 点灯:MIO和EMIO,其实和单片机点灯一样
FMQL的PS侧有很多互联引脚叫MIO,可以配置成GPIO、UART、SPI等功能;还有一部分引脚是走EMIO,也就是通过PL侧的IO连出去的。如果只是验证最基础的PS能不能跑,直接操作MIO管脚上的GPIO点一盏LED就够。
因为PS侧的外设控制器的寄存器映射地址是固定的,裸机程序里点灯的本质就是三件事:使能GPIO外设时钟、把MIO引脚配置成输出模式、往数据寄存器写1或0。这一套逻辑和你在STM32上操作GPIO一模一样,只不过FMQL的地址不是像STM32那样从0x40000000开始,而是从0xE000A000这类地址偏移开始(具体以芯片手册为准)。在IAR里,通过Debugger的寄存器窗口直接修改GPIO数据寄存器,能立即看到LED亮灭,这是验证调试链路是否正常的最快方式。
不过点灯之前有一个前提:把PS侧的全局时钟使能配置好。FMQL不像单片机那样上电后外设时钟全开,很多外设控制器的时钟默认是关着的,不去打开时钟就直接操作寄存器,你会在总线上卡死或者读回全0。这个“先开时钟再配外设”的习惯,是FMQL裸机开发和STM32开发之间最大的思维差异,一定得记住。
4.2 串口输出:printf重定向的完整套路
裸机开发没有比串口更重要的调试手段了。我用IAR做printf重定向已经养成固定套路,FMQL上同样适用。你需要做两件事:第一,初始化UART控制器,包括波特率、数据位、停止位,以及对应的MIO引脚复用;第二,把标准库的fputc或者_write函数重定向到UART发送寄存器上。
具体到IAR,比较省事的做法是在工程里实现一个底层发送函数,比如在代码里写一个简单的putchar实现,轮询UART的发送FIFO空标志位,然后把数据写进去。有的BSP库已经封装好了串口驱动,你只要调用库函数把UART初始化好,再在自己实现的重定向函数里调用这个库函数发送字符就行。有一点很关键:IAR工程默认可能开启了半主机模式(semihosting),这时候printf会尝试通过调试器回传到IDE控制台,而不是从串口发出,表现就是板子上串口毫无反应。解决方法是链接选项里禁用半主机,或者使用支持重定向的库配置。
我习惯在main函数最开始就加一行串口初始化,然后用printf打一条启动日志,包含编译时间和当前时钟频率。这样每次连接调试器、每次重新上电,第一眼就能确认串口链路通不通、时钟初始化对不对。只要这行日志能正常打印,后续调试所有外设就有了一个“看得见”的观察窗口。
4.3 时钟树、DDR初始化和启动流程简述
这一节本来是后面要专门展开讲的,但前言里我觉得有必要先给个概念,因为它是FMQL裸机开发最特别的地方。
FMQL复位之后,PS侧的ARM核并不是以最终工作频率运行的,而是以比较低的内置参考时钟启动。要让CPU全速跑、要让DDR可用,你得在启动文件里配置PLL锁相环、时钟分频器、DDR控制器时序参数。这里要敲一下黑板:很多第一次用FMQL的人,会把代码放到DDR里,但DDR控制器还没初始化,CPU一取指就访问到一个无效地址,程序直接跑飞。正确的流程应该是:启动文件先做必要的CPU模式设置,然后配置时钟,初始化DDR控制器,之后再进入main函数,此时你的.icf文件里定义的DDR内存空间才是真实可用的。
这个流程跟PC上BIOS先初始化内存再加载操作系统是一个道理。你在IAR里虽然看不到这些启动细节,但只要启动文件用的官方模板,基本路径就没错。后续系列我会专门写一篇FMQL的启动流程分析,从复位向量、MMU、异常向量,到PLL配置、DDR参数选择,一条条拆清楚。
5. 前期踩坑实录:IAR + FMQL最容易翻车的4件事
5.1 芯片型号在IAR里找不到
这个问题很多人第一次用FMQL时都会遇到。安装IAR后,Target设备列表里没有复旦微FMQL,并不是IAR不支持国产芯片,而是缺少设备支持描述文件。解决办法是先确认官方BSP包是否正确安装,然后在IAR里用Project->Options->General Options->Target,通过“手动添加设备描述文件”的方式把复旦微的设备包加进去。如果这样还不行,也有一个变通方法:选择通用Cortex-A9内核。IAR编译时只要用对了ARM指令集,链接脚本的地址定义正确,代码运行效果是一样的,只不过部分调试信息没有专用设备那么全。
5.2 能编译,但J-Link连接不上
连接不上通常有几种原因。第一,接口模式不对或者线序接错,FMQL的JTAG引脚虽然引出来了,但不同开发板的丝印排序可能不一样,对照原理图确认一遍最稳妥。第二,调试器供电不足。J-Link给目标板提供参考电压的引脚如果有接触不良,J-Link就无法确认目标电压,自然识别不到芯片。第三,目标板本身没复位进入正常状态。FMQL的启动配置引脚如果被外部跳线设置成了从某种非易失存储器启动,而该存储器里是空数据或者无效数据,内核可能会一直处于异常状态。这种时候可以先通过调试器强制复位,再尝试连接;如果不行就把启动模式跳到JTAG调试模式。
5.3 程序跑起来了,但只要访问DDR就hardfault
这个现象一眼看上去像代码问题,实际上基本都是DDR初始化没做好。我调试时遇到过一种情况:程序点灯和串口都正常,但一旦定义一个大数组往里面写数据,跑不了几下就崩。后来查下来是DDR控制器的时序参数和实际内存颗粒不匹配,读写的训练没有通过,表面上看内存“能访问”,但偶发错误不断累积,最终导致hardfault。这个问题的排查方法比较麻烦,建议先确认IAR里.icf文件定义的内存区域起始地址是否和你配置的DDR控制器基地址一致,再做一遍内存读写测试。如果你只是做PS侧简单裸机程序,前期甚至可以先只使用芯片内部的RAM和OCM区域,不碰外挂DDR,先把逻辑调通再引入DDR。
5.4 printf不输出,或者输出乱码
printf没输出最常见的原因就是我前面说的半主机模式,编译链接时库使用了半主机方式,printf的数据通过调试器回传而不是UART,串口上自然没有内容。解决方法是链接选项里选择支持“无半主机”的库配置,或者在代码里显式禁用半主机。输出乱码则多半是波特率配置问题,尤其是FMQL时钟树还没完全配好的时候,UART的外设时钟源不是整数值,计算出来的分频结果误差偏大,乱码就会出现。建议串口调试时先把波特率降到9600,误差容忍度更高,等时钟配置稳定后再用115200。
注意:FMQL裸机开发里有一个很容易被忽略但又很关键的原则——做任何外设操作之前,先确认对应外设的时钟是否使能。这不仅适用于GPIO和UART,也适用于中断控制器、定时器、DMA这些模块。养成“时钟优先”的习惯后,很多古怪问题都能从根本上避免。
6. 一系列踩坑之后,我对这套组合的几点真实看法
说实话,刚开始用IAR做FMQL PS侧裸机开发的时候,我心里也是打鼓的。毕竟复旦微的FMQL定位偏高端,大家更习惯的方式是用官方工具链或者Linux,IAR裸机这种组合显得有点像“非主流”。但一个系列跑下来,我的体会是:这套组合不仅走得通,而且对特定背景的人特别友好。如果你之前一直在用IAR写单片机程序,你会发现自己大部分的工程管理经验、调试习惯、代码组织方式都能直接搬到FMQL上,唯一要适应的是FMQL芯片的启动流程和内存地图,以及被ARM核和外设总线带宽进一步提升后的调试思维。一旦跨过这个坎,你会发现FMQL没有想象中那么神秘,它本质上就是一个性能更强的ARM处理器,外面挂了一片你可以当作“可重构外设”来用的FPGA,仅此而已。
最后再分享一个小技巧:在IAR里同时打开反汇编窗口、寄存器窗口和内存窗口,把窗口布局保存成一个调试布局,每次调试固件更新时直接调出这套布局,看代码和硬件状态的速度会快很多。后续这个系列我会按计划深入FMQL的启动流程、中断系统、GPIO和UART完整驱动、定时器、以及PS与PL的AXI通信和共享内存实战,尽量每一篇都把原理讲透,把坑填平。如果你在跟着文章操作的过程中发现了不同版本芯片或工具链的差异,欢迎在评论区指出来,一起把这条路趟得更平。