简介:这份资源是面向FPGA初学者与嵌入式开发者的Actel SmartFusion串口通信例程,围绕在混合信号SoC上实现UART功能展开,帮助读者打通从硬件描述语言编程到固件开发的完整链路。压缩包共842个文件,约4.72MB,以vhd、v源码与prw、psm工程文件为主,配合c、h固件代码、dat与dbs数据文件及xml、tcl脚本,覆盖Libero工程配置、时钟管理与仿真验证等环节。资源中涉及UART原理、波特率发生器、移位寄存器与状态机设计,以及Cortex-M3中断处理和外设接口等知识点,可对照学习发送接收逻辑与错误检测的实现方式。目前已有228人学习下载,适合希望掌握SmartFusion平台UART通信、理解FPGA开发全流程的读者参考实践。
1. 从一份 SmartFusion 串口例程说起:为什么它值得你花时间
如果你手头正好有一块 Actel(现 Microsemi)的 SmartFusion 系列开发板,或者你正在维护一套基于 Cortex-M3 + FPGA 架构的老工控代码,那你大概率会遇到一个很现实的问题:串口不通,后面什么都别谈。这份例程解决的就是这个最基础但也最容易翻车的一环——在 SmartFusion 的 ARM Cortex-M3 硬核上跑通 UART 收发,同时把 FPGA fabric 侧的引脚约束和时钟配置一起理清楚。它适合两类人:一是刚拿到板子、连点灯都还没跑通的新手,二是需要把串口作为调试通道或通信链路的二次开发者。整份例程围绕 SmartFusion 的 MSS(Microcontroller Subsystem)展开,核心是 UART 外设的初始化和中断收发,配套的 FPGA 逻辑主要用于引脚复用和时钟分频。你拿到手之后,最直接的价值就是:不用再从零翻 800 页的寄存器手册,直接看代码就能把串口跑起来。
2. 工程结构拆解:从目录到启动文件的完整链路
2.1 顶层目录与文件职责
拿到这份例程,第一件事不是急着编译,而是把目录结构看清楚。典型的 SmartFusion 串口例程会包含以下几个顶层目录:
| 目录/文件 | 作用 |
|---|---|
src/ | 存放 Cortex-M3 的应用代码,包括main.c、uart.c、uart.h |
fpga/ | Libero 工程文件,负责引脚约束和 fabric 逻辑 |
startup/ | 启动文件startup_m3.c和链接脚本linker.ld |
drivers/ | MSS 外设驱动,通常包含mss_uart.c、mss_gpio.c |
config/ | 系统时钟配置和中断向量表重映射 |
这个结构不是随便摆的。SmartFusion 的特点是 ARM 核和 FPGA fabric 共存,所以代码必须分成两部分:一部分跑在硬核里,一部分烧到 fabric 里。串口例程里,fabric 部分通常只做一件事——把 UART 的 TX/RX 信号从 MSS 引到外部引脚,顺便做个时钟分频。如果你只改应用逻辑,基本只需要动src/和drivers/;如果要换引脚或改波特率时钟源,那就得进fpga/重新综合。
2.2 启动流程与时钟初始化
SmartFusion 上电后的执行顺序和普通 STM32 不太一样。它先由 MSS 内部的复位控制器释放 Cortex-M3,然后 M3 从0x00000000取向量表。但此时系统时钟还是内部 RC 振荡器,频率低且不准,直接用来跑 UART 会出玄学问题。所以启动文件里必须先把时钟切到外部晶振或 PLL。
// startup_m3.c 中的时钟初始化片段 void SystemInit(void) { // 第一步:使能外部 32MHz 晶振 MSS_SYS->MSS_CR = 0x01; // 启动主晶振 while ((MSS_SYS->MSS_SR & 0x01) == 0); // 等待稳定 // 第二步:配置 PLL,输出 100MHz 给 Cortex-M3 MSS_SYS->PLL_CR = 0x0000000A; // 反馈分频比 MSS_SYS->PLL_CR |= 0x00000001; // 使能 PLL while ((MSS_SYS->PLL_SR & 0x01) == 0); // 第三步:切换系统时钟源到 PLL MSS_SYS->MSS_CR |= 0x00000010; while ((MSS_SYS->MSS_CR & 0x00000010) == 0); }这段代码的逻辑很直白:先让外部晶振起振,再配 PLL 倍频,最后把 M3 的时钟源切过去。参数说明:MSS_CR是主控制寄存器,bit0 控制晶振使能,bit4 控制时钟源选择;PLL_CR里的分频比要根据你板子上的晶振频率算,常见做法是 32MHz 输入、100MHz 输出。如果你跳过这一步直接初始化 UART,波特率会偏得离谱,接收到的全是乱码——这是新手最容易踩的坑。
2.3 UART 外设初始化与引脚映射
时钟稳了之后,才轮到 UART。SmartFusion 的 MSS UART 外设寄存器不算多,但有一个地方要注意:UART 的引脚不是直接固定的,而是通过 FPGA fabric 的 I/O 复用矩阵引出来的。也就是说,你在代码里配置的是 MSS 内部的 UART 控制器,但信号要经过 fabric 才能到物理引脚。
// uart.c 中的初始化函数 void UART_Init(uint32_t baudrate) { // 1. 使能 UART 外设时钟 MSS_SYS->MSS_APB0_CR |= 0x00000040; // bit6 对应 UART0 // 2. 配置波特率,假设 PCLK = 100MHz uint32_t divisor = 100000000 / (16 * baudrate); MSS_UART0->DLL = divisor & 0xFF; // 低 8 位 MSS_UART0->DLM = (divisor >> 8) & 0xFF; // 高 8 位 // 3. 设置数据格式:8 位数据、1 位停止、无校验 MSS_UART0->LCR = 0x03; // 4. 使能 FIFO 和中断 MSS_UART0->FCR = 0x07; // 清 FIFO 并使能 MSS_UART0->IER = 0x01; // 接收中断使能 // 5. 在 FPGA fabric 中配置引脚复用 // 这一步通常由 Libero 生成的寄存器写入完成 FPGA_IO->UART_MUX = 0x01; // 把 UART0 TX/RX 引到指定引脚 }逐段解释:第一步打开 APB 总线上的 UART 时钟,没有这一步后面写寄存器全是无效操作。第二步算分频值,公式是PCLK / (16 * 波特率),这里 PCLK 是 100MHz,如果你前面时钟没配对,这个值就是错的。第三步LCR = 0x03表示 8N1,这是最常用的串口格式。第四步开 FIFO 和接收中断,避免每收一个字节就进一次中断。第五步是 SmartFusion 特有的,必须通过 fabric 把内部信号引到外部引脚,否则你示波器上什么都看不到。
提示:如果你用的是 SoftConsole 开发环境,Libero 生成的引脚配置会以一个头文件的形式提供,直接调用里面的宏就行,不用自己算寄存器值。
3. 编译、烧录与串口验证:从代码到硬件的完整操作
3.1 工具链准备与工程导入
这份例程默认是用 SoftConsole + Libero 的组合。SoftConsole 负责 Cortex-M3 的 C 代码编译和调试,Libero 负责 FPGA fabric 的综合与烧录。你需要在同一台机器上装好这两个工具,并且版本要匹配——比如 SoftConsole 4.0 配 Libero 11.8,版本错位会导致 FlashPro 识别不到器件。
导入工程的步骤不复杂,但顺序有讲究:
- 先打开 Libero,加载
fpga/下的工程文件,综合并生成 FPGA 配置文件(.stp或.pdb)。 - 再打开 SoftConsole,新建工作区,通过
File -> Import -> Existing Projects导入src/和drivers/。 - 在工程属性里设置链接脚本路径,指向
startup/linker.ld。 - 检查头文件搜索路径,确保
drivers/和config/都在 include 列表里。
常见翻车点:有人只导入了src/,忘了把drivers/加进去,编译时报一堆undefined reference to MSS_UART_Init。这不是代码问题,是路径没配全。
3.2 编译配置与链接脚本调整
SmartFusion 的 Cortex-M3 有 256KB 的 eNVM 和 64KB 的 SRAM。默认链接脚本会把代码放在 eNVM 的起始地址0x00000000,数据段放在 SRAM 的0x20000000。如果你要加 printf 重定向或者大的缓冲区,得留意 SRAM 够不够。
/* linker.ld 中的内存布局片段 */ MEMORY { eNVM (rx) : ORIGIN = 0x00000000, LENGTH = 256K SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .text : { *(.vectors) /* 中断向量表必须放在最前面 */ *(.text) *(.rodata) } > eNVM .data : { *(.data) } > SRAM AT > eNVM /* 数据段运行时在 SRAM,加载时在 eNVM */ .bss : { *(.bss) } > SRAM }关键点:.vectors段必须放在 eNVM 的最开头,因为 Cortex-M3 复位后从0x00000000取栈顶指针和复位向量。如果你不小心把别的段排到前面,程序一上电就 HardFault。.data段的AT > eNVM表示初始化值存在 eNVM 里,启动时由启动代码拷贝到 SRAM,这是嵌入式链接脚本的标准做法。
3.3 烧录与串口回环测试
编译通过后,用 FlashPro 把 FPGA 配置和 M3 的 hex 文件一起烧进去。烧录顺序建议先烧 fabric 再烧 M3,因为 fabric 里的引脚复用配置会影响 M3 能不能正常驱动外部引脚。
烧完之后,用 USB 转串口模块接到板子的 UART 引脚上,打开串口助手,波特率设成代码里配置的值(比如 115200),数据位 8,停止位 1,无校验。然后给板子复位,你应该能看到类似下面的输出:
SmartFusion UART Demo System Clock: 100 MHz UART Baudrate: 115200 Echo mode ready.如果你发一个字符A过去,板子会回一个A,说明收发都通了。如果只收到乱码,先查时钟配置;如果什么都没收到,先查引脚复用和硬件连线。这一步没有捷径,就是拿示波器或者逻辑分析仪看 TX 引脚有没有波形。
4. 避坑与常见问题排查:串口不通时先看这几条
4.1 现象:串口助手收到全是乱码
原因:系统时钟没切到 PLL,或者波特率分频值算错了。SmartFusion 上电默认用内部 RC 振荡器,频率大概 20MHz 左右,而且随温度漂移。如果你在SystemInit里没等晶振稳定就切时钟,PLL 输出会抖,UART 分频值跟着变,接收端采样点全错。
解决:在时钟切换的while循环里加超时计数,别死等。然后用示波器测一下 PCLK 输出引脚,确认频率是 100MHz 再往下走。波特率分频值建议用宏定义算,别手写十六进制。
4.2 现象:能发不能收,或者能收不能发
原因:FPGA fabric 里的引脚复用只配了一半。SmartFusion 的 UART TX 和 RX 是分开复用的,有人只把 TX 引出来,忘了 RX;或者 RX 引脚的方向配置错了,配成了输出。
解决:打开 Libero 的引脚约束文件,确认 UART_RX 对应的 fabric 引脚方向是 input,UART_TX 是 output。然后在代码里检查FPGA_IO->UART_MUX的 bit 定义,通常 bit0 是 TX,bit1 是 RX,两个都要置位。
4.3 现象:进不了接收中断
原因:中断向量表没重映射,或者 NVIC 没使能对应的中断号。SmartFusion 的 UART0 中断号通常是 10,但如果你在启动文件里没把向量表地址写到SCB->VTOR,M3 会去默认地址取中断服务函数,结果跳到一个空函数。
解决:在main开头加一句SCB->VTOR = 0x00000000;,确保向量表指向 eNVM 起始地址。然后调用NVIC_EnableIRQ(UART0_IRQn);打开中断。最后检查MSS_UART0->IER的 bit0 是不是 1。
4.4 现象:烧录后板子没反应,连点灯都不亮
原因:链接脚本里.vectors段没排在最前面,或者烧录时只烧了 fabric 没烧 M3。FlashPro 的烧录界面里,FPGA 配置文件和 M3 hex 文件是两个独立的选项,漏掉任何一个都不行。
解决:用 FlashPro 的“Program”功能时,勾选“Erase”和“Verify”,确保两个文件都写进去。然后读一下 eNVM 的起始地址,看前 8 个字节是不是栈顶指针和复位向量。如果不是,说明链接脚本有问题,回去检查SECTIONS里的顺序。
4.5 现象:串口能通,但跑一会儿就死机
原因:接收中断里做了太多事情,比如在 ISR 里调用printf或者浮点运算。SmartFusion 的 M3 没有 FPU,浮点运算靠软件模拟,在中断里跑会阻塞其他中断,导致 FIFO 溢出或者看门狗复位。
解决:ISR 里只做最轻量的操作——把接收到的字节存进环形缓冲区,然后置一个标志位。主循环里再根据标志位去处理数据。环形缓冲区的读写指针用volatile修饰,防止编译器优化掉。
5. 二次开发改造:把串口例程变成你自己的通信框架
5.1 从回环测试到协议解析
例程本身只是个回环,但你可以把它改成 Modbus RTU 从站或者自定义的帧协议。改造的核心是把 ISR 里的“收到就回发”换成“收到就存缓冲区,主循环里解析”。下面是一个简单的帧解析框架:
// 环形缓冲区定义 #define RX_BUF_SIZE 128 static volatile uint8_t rx_buf[RX_BUF_SIZE]; static volatile uint16_t rx_head = 0; static volatile uint16_t rx_tail = 0; // 中断服务函数:只负责存数据 void UART0_IRQHandler(void) { if (MSS_UART0->IIR & 0x01) { // 接收中断 uint8_t data = MSS_UART0->RBR; uint16_t next = (rx_head + 1) % RX_BUF_SIZE; if (next != rx_tail) { // 缓冲区没满 rx_buf[rx_head] = data; rx_head = next; } } } // 主循环里的解析函数 void UART_Process(void) { while (rx_tail != rx_head) { uint8_t byte = rx_buf[rx_tail]; rx_tail = (rx_tail + 1) % RX_BUF_SIZE; // 在这里做协议解析,比如判断帧头、校验和 Protocol_ParseByte(byte); } }这个框架的好处是中断里只做搬运,解析逻辑放在主循环,不会因为解析耗时导致丢数据。参数说明:RX_BUF_SIZE根据你的最大帧长来定,一般 128 或 256 够用;rx_head和rx_tail用volatile修饰,因为它们在 ISR 和主循环里都会被修改。
5.2 波特率自适应与错误处理
实际项目里,串口波特率可能不是固定的。你可以利用 SmartFusion UART 的自动波特率检测功能,或者自己写一个简单的自适应逻辑:先按 9600 收,如果收到帧头就切到目标波特率。错误处理方面,要关注LSR寄存器里的 overrun、parity、framing 错误标志,一旦置位就清 FIFO 并重新同步。
// 错误检查与恢复 void UART_CheckError(void) { uint8_t lsr = MSS_UART0->LSR; if (lsr & 0x02) { // Overrun error MSS_UART0->FCR = 0x07; // 清 FIFO rx_head = rx_tail = 0; // 重置缓冲区 } if (lsr & 0x04) { // Parity error // 记录错误计数,必要时通知上层 } }我一般会在主循环里每 10ms 调一次这个检查,别放在中断里,因为清 FIFO 本身需要时间。
5.3 验证方法与调试习惯
改完代码后,别急着上真实设备。先用 PC 端的串口调试助手发固定帧,看板子返回的数据对不对。然后拿逻辑分析仪抓 TX/RX 波形,确认波特率误差在 2% 以内。最后再接到实际设备上跑长时间测试,至少连续跑 24 小时,看有没有丢帧或死机。
从那以后我每次改串口代码,都强制走一遍“时钟确认 -> 引脚复用检查 -> 中断向量表核对 -> 缓冲区压力测试”这个流程。少一步,后面就可能花一整天查一个低级问题。希望这份例程和上面的改造思路能帮你把 SmartFusion 的串口跑通,剩下的协议和业务逻辑,就看你自己的了。
本文还有配套的精品资源,点击获取