系列导读
本文面向首次接触 Arduino 嵌入式开发链路的传统 MCU 工程师(熟悉 Keil、IAR、STM32CubeIDE),以 Microchip LAN8650/LAN8651 + Arduino UNO R4 Minima + Arduino_10BASE_T1S v0.1.1 为蓝本,把 Arduino IDE 这一”看似点击即上传”工具背后的完整编译架构讲清楚。
同系列其它文章
1. 核心问题解答
1.1 两个芯片,Arduino IDE 烧录到哪个?
Arduino UNO R4 Minima + 10BASE-T 扩展板系统架构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌─────────────────────────────────────────────────────────────┐ │ Arduino UNO R4 Minima 主板 │ │ │ │ ┌──────────────────────┐ ┌────────────────────┐ │ │ │ Renesas RA4M1 │ │ 你的扩展板 │ │ │ │ (主MCU) │◄───SPI──►│ Microchip LAN8650 │ │ │ │ │ │ /LAN8651 MAC-PHY │ │ │ │ Cortex-M4 48MHz │ └────────────────────┘ │ │ │ 256KB Flash │ │ │ │ 32KB RAM │ ┌────────────────────┐ │ │ │ │ │ 以太网变压器 │ │ │ │ 运行的代码: │ │ + RJ45接口 │ │ │ │ - UDP_Client.ino │ └────────────────────┘ │ │ │ - lwIP协议栈 │ │ │ │ - libtc6 协议 │ │ │ │ - SPI 驱动 │ │ │ │ - MAC-PHY 寄存器配置 │ │ │ └──────────┬───────────┘ │ │ │ │ │ ┌──────────▼───────────┐ │ │ │ USB转串口芯片 │ │ │ │ (用于烧录和串口通信) │ │ │ └──────────┬───────────┘ │ │ │ │ └─────────────┼──────────────────────────────────────────────┘ │ USB-C 线缆 ▼ PC (Arduino IDE)
|
明确答案:
- Arduino IDE 只烧录主 MCU (Renesas RA4M1)
- Microchip LAN8650/LAN8651 MAC-PHY 芯片不需要烧录,它是 SPI 外设
- 应用代码运行在 RA4M1 上,通过 SPI + OA TC6 协议控制 MAC-PHY
- MAC-PHY 内部可能有 ROM 固件(OA TC6 协议栈),但不由 Arduino IDE 烧录
类比:
- RA4M1 主 MCU = STM32 主控芯片(运行你的代码)
- LAN8650/LAN8651 = 外围的 W5500 网络芯片(通过 SPI 控制),区别是 LAN865x 使用 OA TC6 私有协议而非裸 SPI 寄存器
1.2 为什么你看不到底层库的编译过程?
当你在 Arduino IDE 2 中打开 UDP_Client.ino 并点击”上传”时,背后发生了这些事情(默认在 UI 中不可见):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 你看到的: ┌─────────────────────────────┐ │ UDP_Client.ino │ <-- 你只编辑这个文件 │ #include <Arduino_10BASE… │ │ void setup() { ... } │ │ void loop() { ... } │ └─────────────────────────────┘ ↓ 点击"上传" [黑盒过程] ↓ ┌─────────────────────────────┐ │ 烧录到 Arduino R4 │ └─────────────────────────────┘
实际发生的(隐藏过程): ┌─────────────────────────────────────────────────────────────┐ │ 1. Arduino IDE 扫描 #include 语句 │ │ 发现: Arduino_10BASE_T1S.h │ ├─────────────────────────────────────────────────────────────┤ │ 2. 在库目录中查找: │ │ Windows: %USERPROFILE%\Documents\Arduino\libraries\ │ │ 或: %LOCALAPPDATA%\Arduino15\libraries\ │ │ 找到: Arduino_10BASE_T1S/ │ ├─────────────────────────────────────────────────────────────┤ │ 3. Arduino-CLI 递归扫描库的 src/ 目录: │ │ src/Arduino_10BASE_T1S_UDP.cpp ✓ 加入编译 │ │ src/MacAddress.cpp ✓ 加入编译 │ │ src/microchip/TC6_Arduino_10BASE_T1S.cpp ✓ 加入编译 │ │ src/microchip/TC6_Io.cpp ✓ 加入编译 │ │ src/microchip/lib/libtc6/src/tc6.cpp ✓ 加入编译 │ │ src/microchip/lib/libtc6/src/tc6-regs.cpp ✓ 加入编译 │ │ src/lib/liblwip/*.c ✓ 全部加入编译 │ ├─────────────────────────────────────────────────────────────┤ │ 4. 调用 arm-none-eabi-g++ 编译器: │ │ 编译每个 .cpp/.c 文件 → .o 目标文件 │ │ 链接所有 .o + Arduino 核心库 → .elf 可执行文件 │ │ 转换 .elf → .bin 二进制镜像 │ ├─────────────────────────────────────────────────────────────┤ │ 5. 调用 bossac 烧录工具: │ │ 通过 USB 串口上传 .bin 到 Arduino R4 Flash │ └─────────────────────────────────────────────────────────────┘
|
关键点:Arduino IDE 会自动编译 src/ 目录下的所有 .cpp 和 .c 文件,你不需要手动指定。这就是为什么看起来”魔法”,但本质是 GNU Make + GCC 的工程化封装。
2. Arduino 库的标准结构与 Microchip 方案实例
2.1 Arduino 库的目录约定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| Arduino_10BASE_T1S/ # 库根目录 │ ├── library.properties # 库元数据(IDE 读取这个识别库) │ name=Arduino_10BASE_T1S │ version=0.1.1 │ architectures=renesas_uno # 支持 UNO R4 │ ├── examples/ # 示例代码(用户可见) │ ├── UDP_Client/ │ │ └── UDP_Client.ino # ← 用户编辑这个 │ ├── UDP_Server/ │ │ └── UDP_Server.ino │ └── pingpong/ │ └── pingpong.ino │ └── src/ # 库源码(自动编译,用户不可见) ├── Arduino_10BASE_T1S.h # 主头文件 ├── Arduino_10BASE_T1S_PHY_Interface.h # PHY 抽象基类 ├── Arduino_10BASE_T1S_UDP.h # UDP Socket API ├── Arduino_10BASE_T1S_UDP.cpp # ← IDE 自动编译 ├── MacAddress.h/.cpp # MAC 地址工具类 ├── T1SPlcaSettings.h/.cpp # PLCA 配置 ├── T1SMacSettings.h/.cpp # MAC 配置 │ ├── microchip/ # Microchip 特定代码(vendor 子目录) │ ├── TC6_Arduino_10BASE_T1S.h/.cpp # TC6 PHY 实现 │ ├── TC6_Io.h/.cpp # SPI HAL │ └── lib/ │ ├── libtc6/ # Microchip OA TC6 核心(C API) │ │ ├── inc/tc6.h # 公共 API │ │ ├── inc/tc6-regs.h # LAN865x 寄存器表 │ │ ├── src/tc6.cpp # 协议状态机 │ │ ├── src/tc6-regs.cpp # 寄存器访问实现 │ │ └── cfg-example/tc6-conf.h # libtc6 配置宏 │ └── liblwip/ # 移植后的 lwIP 协议栈 │ ├── core/ # ~80 个 .c 文件 │ │ ├── ipv4/ip4.c │ │ ├── udp.c │ │ ├── tcp.c │ │ └── ... │ ├── netif/ │ │ ├── ethernet.c │ │ └── etharp.c │ └── cfg/lwipopts.h # lwIP 配置 │ └── lib/ # 第三方库(可与 vendor 同级) └── liblwip/ # lwIP 协议栈(本仓库实际位置) └── ...
|
2.2 Arduino IDE 的自动发现规则
- 任何在
src/ 目录下的 .cpp / .c / .S 文件都会被编译
- 包括所有子目录(递归扫描)—— 这是为什么
src/microchip/lib/libtc6/src/ 里的 100+ 文件会自动加入
src/**/include/ 下的头文件自动加入编译器的 include 路径
- 用户只需要在
.ino 文件中 #include 主头文件
- 链接顺序与文件顺序无关,由符号解析自动完成(除非有 circular reference)
2.3 示例工程的可见文件数量
一个完整的 UDP_Client 编译大约涉及:
| 来源 |
文件数 |
说明 |
Sketch (UDP_Client.ino) |
1 |
用户编辑 |
Arduino_10BASE_T1S src/ |
~10 |
库本身 |
libtc6 src/ |
~10 |
协议状态机、寄存器访问 |
liblwip core/ |
~80 |
完整 TCP/IP 协议栈(仅用到的部分编译进去) |
| Arduino Core |
~30 |
main.cpp, wiring.c, HardwareSerial.cpp 等 |
| Renesas FSP 静态库 |
1 个 .a |
链接形式提供,不可见 |
| 合计编译单元 |
~150 个 .o |
|
3. Arduino IDE vs Keil 工程对比
3.1 工程文件位置对比
| 特性 |
Keil uVision |
Arduino IDE |
| 工程文件 |
.uvproj (单一 XML 文件) |
分散在多个 .txt 文件 |
| 位置 |
项目目录下 |
%LOCALAPPDATA%\Arduino15\packages\ |
| 源文件列表 |
手动添加到工程树 |
自动扫描 src/ 目录 |
| 编译选项 |
Project Options 对话框 |
platform.txt 文本配置 |
| 链接脚本 |
.sct (Scatter file) |
.ld (GNU Linker script) |
| 板卡配置 |
Device 数据库 |
boards.txt |
| 输出文件 |
.axf + .hex |
.elf + .bin + .map |
| 内存视图 |
Memory Map 窗口 |
读 .map 文件 |
| 烧录工具 |
ULINK/J-Link |
dfu-util / bossac |
| 调试 |
硬件调试器 (SWD/JTAG) |
无硬件调试,仅串口 |
3.2 配置文件层次结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| %LOCALAPPDATA%\Arduino15\packages\arduino\ └── hardware\renesas_uno\1.6.0\ ✓ 已安装 │ ├── boards.txt ← 板卡定义(选择哪个板子) │ 定义内容: │ - minima.name=Arduino UNO R4 Minima │ - minima.build.mcu=cortex-m4 │ - minima.upload.maximum_size=262144 (256KB Flash) │ - minima.upload.maximum_data_size=32768 (32KB RAM) │ ├── platform.txt ← 编译规则(如何编译) │ 定义内容: │ - 编译器路径和参数 │ - 链接器参数 │ - 如何生成 .bin 文件 │ - 烧录工具配置 │ └── variants\MINIMA\ ← 板卡特定文件 ✓ 已安装 ├── memory_regions.ld ← 内存映射(重要!)✓ ├── fsp.ld ← 主链接脚本 ✓ ├── pins_arduino.h ← 引脚定义 ✓ └── libs\libfsp.a ← Renesas FSP 静态库
编译工具链 (%LOCALAPPDATA%\Arduino15\packages\arduino\tools\): ├── arm-none-eabi-gcc\7-2017q4\ ← GCC 编译器 ✓ ├── dfu-util\0.11.0-arduino5\ ← DFU 烧录工具 ✓ ├── bossac\1.9.1-arduino5\ ← BOSSA 烧录工具 ✓ └── openocd\0.11.0-arduino2\ ← 调试工具(仅 IDE 内部用)✓
|
4. 内存映射详解
4.1 memory_regions.ld 文件内容
1 2 3 4 5 6 7 8 9 10 11 12
| /* Arduino UNO R4 Minima 内存映射 */ RAM_START = 0x20000000; /* SRAM 起始地址 */ RAM_LENGTH = 0x8000; /* 32KB = 32768 字节 */
FLASH_START = 0x00000000; /* Flash 起始地址 */ FLASH_LENGTH = 0x40000; /* 256KB = 262144 字节 */
FLASH_IMAGE_START = 0x4000; /* 应用程序起始(重要!)*/ /* 16KB 偏移,前面是 Bootloader */
DATA_FLASH_START = 0x40100000; /* 数据 Flash(用于 EEPROM 模拟)*/ DATA_FLASH_LENGTH = 0x2000; /* 8KB */
|
4.2 Flash 布局图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| RA4M1 Flash (256KB) 完整布局:
地址范围 大小 用途 烧录方式 ┌──────────────────────────────────────────────────────────────┐ │ 0x00000000 - 0x00003FFF 16KB DFU Bootloader 出厂固化 │ │ (Arduino 官方烧录的) │ │ 负责 USB DFU 协议 │ ├──────────────────────────────────────────────────────────────┤ │ 0x00004000 - 0x0003FFFF 240KB 你的应用程序 ← IDE 烧录到这里 │ │ - .text (代码段) │ │ - .rodata (常量) │ │ - .data 初始值 │ │ - 中断向量表 │ └──────────────────────────────────────────────────────────────┘
RAM (32KB) 布局:
地址范围 用途 ┌──────────────────────────────────────────────────────────────┐ │ 0x20000000 - 0x200007FF .data 段 (初始化全局变量) │ ├──────────────────────────────────────────────────────────────┤ │ 0x20000800 - 0x20006FFF .bss 段 (未初始化全局变量) │ │ - lwIP heap │ │ - pbuf pool │ ├──────────────────────────────────────────────────────────────┤ │ 0x20007000 - 0x20007BFF Heap (动态分配) │ ├──────────────────────────────────────────────────────────────┤ │ 0x20007C00 - 0x20007FFF Stack (函数调用栈) │ └──────────────────────────────────────────────────────────────┘
|
关键点:
- 用户程序从
0x4000 开始,不是 0x0000
- 前 16KB 被 Bootloader 占用(不可覆盖)
boards.txt 中 upload.address=0x00010000 是 DFU 协议的参数(不是实际烧录偏移)
5. 完整编译链接烧录流程(7 阶段)
5.1 阶段 1:源文件扫描
Arduino IDE 递归扫描以下目录(约 120-150 个源文件):
1 2 3 4 5 6 7 8 9 10
| 你的 Sketch: └── UDP_Client.ino
库目录 (自动发现): └── libraries/Arduino_10BASE_T1S/src/ ├── *.cpp/*.c (约 10 个文件) └── lib/liblwip/ (约 80 个 .c 文件)
Arduino 核心库: └── cores/arduino/ (约 30 个文件)
|
5.2 阶段 2:编译 (.c/.cpp → .o)
编译命令示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| arm-none-eabi-g++ \ -c -Os -g3 \ -std=gnu++17 \ -mcpu=cortex-m4 \ -mfpu=fpv4-sp-d16 \ -mfloat-abi=hard \ -mthumb \ -ffunction-sections \ -fdata-sections \ -fno-rtti \ -fno-exceptions \ -DARDUINO_UNOR4_MINIMA \ -DF_CPU=48000000 \ -I... \ Arduino_10BASE_T1S_UDP.cpp \ -o Arduino_10BASE_T1S_UDP.cpp.o
|
关键编译选项:
| 选项 |
作用 |
为什么需要 |
-Os |
优化大小 |
Flash 只有 256KB |
-ffunction-sections |
每个函数独立段 |
链接时可以移除未使用函数 |
-fno-rtti |
禁用 RTTI |
节省 Flash 空间 |
-fno-exceptions |
禁用异常 |
节省 Flash 空间 |
-mcpu=cortex-m4 |
目标 CPU |
生成 Cortex-M4 指令 |
-mfloat-abi=hard |
硬件浮点 |
使用 FPU 加速浮点运算 |
-g3 |
最高级别调试信息 |
.map 与 .elf 仍可定位符号 |
5.3 阶段 3:打包核心库 (.o → core.a)
1 2 3 4 5
| arm-none-eabi-ar rcs core.a \ main.cpp.o \ wiring.c.o \ HardwareSerial.cpp.o \ ... (所有 Arduino 核心 .o 文件)
|
5.4 阶段 4:链接 (.o + .a → .elf)
链接命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| arm-none-eabi-g++ \ -mcpu=cortex-m4 \ -mfpu=fpv4-sp-d16 \ -mfloat-abi=hard \ -Wl,--gc-sections \ --specs=nano.specs \ -T fsp.ld \ -o UDP_Client.ino.elf \ \ *.o \ -Wl,--whole-archive \ libfsp.a \ core.a \ -Wl,--no-whole-archive \ -lstdc++ -lm -lc -lgcc \ -Wl,-Map,UDP_Client.ino.map
|
链接脚本 fsp.ld 的作用:
- 定义内存区域 (Flash @ 0x0, RAM @ 0x20000000)
- 定义段布局 (
.text, .data, .bss, .heap, .stack)
- 设置入口点 (
Reset_Handler)
- 定义堆栈大小
精简版 fsp.ld:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x40000 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x8000 }
SECTIONS { .vectors 0x00004000 : { KEEP(*(.vectors)) } > FLASH .text : { *(.text*) *(.rodata*) } > FLASH .data : { _sdata = .; *(.data*) _edata = .; } > RAM AT > FLASH .bss : { _sbss = .; *(.bss*) _ebss = .; } > RAM .heap : { _heap_start = .; . = . + 0x2000; /* 8KB heap */ _heap_end = .; } > RAM .stack : { . = ALIGN(8); _stack_end = .; . = . + 0x400; /* 1KB stack */ _stack_start = .; } > RAM }
|
5.5 阶段 5:生成二进制 (.elf → .bin)
1 2 3 4 5 6
| arm-none-eabi-objcopy \ -O binary \ -j .text \ -j .data \ UDP_Client.ino.elf \ UDP_Client.ino.bin
|
5.6 阶段 6:计算大小
1
| arm-none-eabi-size -A UDP_Client.ino.elf
|
典型输出(UDP_Client + Arduino_10BASE_T1S v0.1.1):
1 2 3 4 5 6 7 8
| section size addr .text 98234 0x4100 ← 代码段 .rodata 12456 0x1c01a ← 只读数据 .data 2048 0x20000000 ← 初始化数据 .bss 26408 0x20000800 ← 未初始化数据
Total Flash: 110690 / 262144 (42%) Total RAM: 28456 / 32768 (86%) ← 警告:RAM 几乎用完!
|
5.7 阶段 7:烧录到 MCU
使用 bossac 工具(UNO R4 默认烧录器):
1
| bossac --port=COM3 -e -w -v -b UDP_Client.ino.bin -R
|
烧录过程:
- PC 打开串口,触发 RA4M1 复位进入 Bootloader
- Bootloader 擦除 Flash (0x4000-0x3FFFF)
- 逐块写入
.bin 内容
- 验证后复位,从 0x4000 开始运行
注意:一些板子(Portenta H7)使用 DFU 而非 BOSSA,烧录命令变为 dfu-util --device ... -D *.bin。
6. 关键产物详解
6.1 .map 文件 — 最重要的调试工具
位置:build/UDP_Client.ino.map
用途:
- 查看每个函数的 Flash 地址
- 查看全局变量的 RAM 地址
- 找出最大的函数(优化 Flash 使用)
- 定位链接错误(undefined reference / multiple definition)
示例内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| .text 0x00004100 0x17fda .text 0x00004100 0x48 main.cpp.o 0x00004100 main .text 0x00004148 0x128 Arduino_10BASE_T1S_UDP.cpp.o 0x00004148 Arduino_10BASE_T1S_UDP::begin 0x00004198 Arduino_10BASE_T1S_UDP::endPacket .text 0x00004270 0x234 TC6_Io.cpp.o 0x00004270 TC6_Io::spiTransaction .data 0x20000000 0x800 .data 0x20000000 0x10 Arduino_10BASE_T1S_UDP.cpp.o 0x20000000 _rx_pkt_list .bss 0x20000800 0x6738 .bss 0x20000800 0x1000 lwip_mem.c.o 0x20000800 lwip_heap
|
使用技巧:
1 2 3 4 5 6 7 8 9 10 11
| Select-String -Path build/*.map -Pattern "TC6_Io::spiTransaction"
Get-Content build/*.map | Select-String "\.text\." | ForEach-Object { $_.ToString().Split() } | Where-Object { $_ -match "^0x[0-9a-f]{4,}$" } | Sort-Object -Descending | Select-Object -First 10
Select-String -Path build/*.map -Pattern "lwip" | Measure-Object
|
6.2 三种产物文件
| 文件 |
大小 |
用途 |
UDP_Client.ino.elf |
~400KB |
完整 ELF,含调试符号、.map 信息、.bin 内容 |
UDP_Client.ino.bin |
~110KB |
纯二进制,烧录用 |
UDP_Client.ino.map |
~200KB |
文本格式符号表,调试用 |
7. 调试技巧
7.1 启用详细编译输出
Arduino IDE:
1 2 3
| 文件 → 首选项 ☑ 编译时显示详细输出 ☑ 上传时显示详细输出
|
你将看到完整的编译命令,例如:
1 2 3 4 5 6 7
| "C:\...\arm-none-eabi-g++" -c -Os -g3 -mcpu=cortex-m4 ... -o "Arduino_10BASE_T1S_UDP.cpp.o" "Arduino_10BASE_T1S_UDP.cpp"
编译完成后: Sketch uses 110834 bytes (42%) of program storage space. Global variables use 28456 bytes (86%) of dynamic memory.
|
7.2 使用 arduino-cli 命令行编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| arduino-cli compile --fqbn arduino:renesas_uno:minima ` --verbose ` --build-path ./build ` UDP_Client.ino
ls build/
arduino-cli upload --fqbn arduino:renesas_uno:minima ` --port COM3 ` --input-dir build
|
7.3 反汇编查看生成的代码
1 2 3 4 5 6 7 8
| arm-none-eabi-objdump -D build/UDP_Client.ino.elf > disassembly.txt
arm-none-eabi-objdump -S build/UDP_Client.ino.elf > disassembly_with_source.txt
arm-none-eabi-objdump -D build/UDP_Client.ino.elf | Select-String -Pattern "TC6_Io::spiTransaction" -Context 0,50
|
输出示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 00004270 <TC6_Io::spiTransaction>: 4270: b580 push {r7, lr} 4272: b084 sub sp, #16 4274: af00 add r7, sp, #0 4276: 60f8 str r0, [r7, #12] 4278: 60b9 str r1, [r7, #8] 427a: 607a str r2, [r7, #4] 427c: 803b strh r3, [r7, #0] 427e: 68fb ldr r3, [r7, #12] 4280: 68db ldr r3, [r3, #12] # _cs_pin 4282: 2200 movs r2, #0 4284: 4619 mov r1, r3 4286: 2000 movs r0, #0 4288: f7ff fffe bl digitalWrite ...
|
7.4 分析 Flash 使用情况
1 2 3 4 5 6 7 8
| Get-Content build/UDP_Client.ino.map | Select-String "^\.text"
arm-none-eabi-size -A -d build/UDP_Client.ino.elf
arm-none-eabi-nm --size-sort -C -r build/UDP_Client.ino.elf | Select-Object -First 20
|
7.5 分析 RAM 使用情况
1 2 3 4 5 6
| arm-none-eabi-size -A build/UDP_Client.ino.elf | Select-String "data|bss"
arm-none-eabi-nm --size-sort -C -r build/UDP_Client.ino.elf | Select-String " B | b " | Select-Object -First 20
|
7.6 各模块 Flash/RAM 占比速查
依据 Arduino_10BASE_T1S v0.1.1 实测:
| 模块 |
Flash 占比 |
备注 |
lwIP (liblwip/) |
~45 KB |
UDP + ARP + ICMP 全开约 45 KB;禁 TCP 后降至 ~38 KB |
libtc6 (libtc6/) |
~15 KB |
OA TC6 协议状态机 + 寄存器访问 |
库包装层(TC6_Arduino_10BASE_T1S 等) |
~8 KB |
包括 lwIP 集成回调 |
| Arduino Core |
~30 KB |
main.cpp, HardwareSerial, Print, Stream 等 |
| FSP HAL(Renesas 静态库) |
~12 KB |
libfsp.a 中实际被链入的部分 |
8. 常见错误与解决方案
8.1 Flash 空间不足
错误信息:
1 2
| Sketch uses 268000 bytes (102%) of program storage space. Maximum is 262144 bytes.
|
根因分析:通常因为开启了 TCP / DHCP / DNS 等未用功能。
解决方案:
1. 禁用 TCP(如果只用 UDP):
编辑 src/lib/liblwip/cfg/lwipopts.h:
1 2
| #define LWIP_TCP 0 #define MEMP_NUM_TCP_PCB 0
|
2. 减少 pbuf pool:
1 2
| #define PBUF_POOL_SIZE 8 #define MEMP_NUM_PBUF 8
|
3. 移除未使用的功能:
1 2 3 4
| #define LWIP_DHCP 0 #define LWIP_AUTOIP 0 #define LWIP_IGMP 0 #define LWIP_DNS 0
|
8.2 RAM 空间不足
错误信息:
1 2
| Global variables use 33000 bytes (101%) of dynamic memory, leaving -768 bytes for local variables.
|
根因:UNO R4 Minima 只有 32KB RAM,lwIP heap + pbuf pool + 你的全局变量很容易突破。
解决方案:
1. 减少 lwIP 内存池:
1 2
| #define MEM_SIZE (12*1024) #define PBUF_POOL_SIZE 6
|
2. 减少 TCP 缓冲区(如果仍启用 TCP):
1 2
| #define TCP_SND_BUF (2*TCP_MSS) #define TCP_WND (2*TCP_MSS)
|
3. 使用动态内存(小心内存碎片):
将大的静态缓冲区改为 malloc/free 或 C++ new/delete。
8.3 链接错误:undefined reference
错误示例:
1
| undefined reference to `TC6_Service'
|
原因:
- 函数声明了但没有定义
- 库文件没有正确链接(被 IDE 自动排除)
解决:
- 确认源文件在
src/ 目录下(含子目录)
- 检查函数名是否正确(C++ 注意命名空间
Mchp::TC6::)
- 重启 Arduino IDE 刷新库缓存
- 用
arduino-cli compile --verbose 看哪些 .cpp 被加入编译列表
8.4 链接错误:multiple definition
错误示例:
1
| multiple definition of `TC6_Service'
|
原因:通常是把同一库复制了多份,或多个库包含同名符号。
解决:
- 检查
~/Documents/Arduino/libraries/ 下是否有多份相同库
- 检查 IDE 内置库与用户库是否冲突
- 必要时给某个库的符号加前缀(例如
mchp_tc6_service vs vendor_tc6_service)
8.5 烧录失败
错误信息:
1 2 3
| No DFU capable USB device found (dfu-util) 或 Could not find device on COM3 (bossac)
|
解决:
- 双击 Arduino 板上的 RESET 按钮,让 Bootloader 接管
- 检查 USB 线缆是否支持数据传输(部分便宜线只供电)
- 更新 USB 驱动(CH340 / RA4M1 内置)
- 检查串口是否被其他程序占用(关闭串口监视器再烧录)
- UNO R4 Minima 默认用 BOSSA,要确保 Board 选择正确
8.6 找不到库
错误信息:
1 2 3
| fatal error: Arduino_10BASE_T1S.h: No such file or directory #include <Arduino_10BASE_T1S.h> ^~~~~~~~~~~~~~~~~~~~~~~~~
|
原因:库没有安装或路径不对。
解决:
- 检查库目录是否存在:
~/Documents/Arduino/libraries/Arduino_10BASE_T1S/
- 确认
library.properties 文件存在
- 重启 Arduino IDE(IDE 启动时扫描库目录)
arduino-cli 用户:arduino-cli lib install Arduino_10BASE_T1S
9. 开发工作流建议
9.1 阶段化集成策略
强烈推荐自底向上逐步集成,每一步可独立验证:
1 2 3 4 5 6 7 8 9 10 11
| 阶段 1: SPI 通信 ─── 不依赖 lwIP 与协议栈 ↓ 阶段 2: libtc6 协议 ─── 验证 SPI 帧收发、寄存器读写 ↓ 阶段 3: lwIP 初始化 ─── 仅 netif + IP 配置,不发包 ↓ 阶段 4: ARP / ICMP ─── 用 ping 验证链路 ↓ 阶段 5: UDP ─── UDP echo / 简单数据收发 ↓ 阶段 6: 完整应用 ─── 集成到 sketch 中
|
9.2 阶段 1 模板:仅验证 SPI(不依赖完整库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <SPI.h>
#define CS_PIN 9 #define RESET_PIN 6
void setup() { Serial.begin(115200); while (!Serial);
pinMode(CS_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH);
pinMode(RESET_PIN, OUTPUT); digitalWrite(RESET_PIN, LOW); delay(100); digitalWrite(RESET_PIN, HIGH); delay(100);
SPI.begin();
uint32_t chip_id = readReg(0x00000001); Serial.print("Chip ID: 0x"); Serial.println(chip_id, HEX); }
void loop() {}
uint32_t readReg(uint32_t addr) { }
|
优势:快速验证硬件连接,不需要完整的网络栈,单个文件即可调试。
9.3 阶段 3 模板:lwIP loopback 测试
1 2 3 4 5 6 7 8
| netif_add(&netif, &ipaddr, &netmask, &gw, NULL, netif_init_loopback, ethernet_input); netif_set_link_up(&netif);
udp.beginPacket(IPAddress(127, 0, 0, 1), 8888); udp.write("test"); udp.endPacket();
|
此阶段不接 MAC-PHY,验证 lwIP 协议栈本身的配置是否正确。
9.4 完整工作流 checklist
10. 推荐的工具链
10.1 VSCode + Arduino 插件
1 2 3 4 5 6 7 8 9
| { "arduino.path": "C:/Program Files/Arduino IDE", "arduino.useArduinoCli": true, "C_Cpp.default.includePath": [ "C:/Users/你的用户名/AppData/Local/Arduino15/packages/arduino/hardware/renesas_uno/1.x.x/cores/arduino", "C:/Users/你的用户名/Documents/Arduino/libraries/**" ] }
|
优势:
- 代码补全(IntelliSense)
- 定义跳转(F12)
- Git 集成
- 多文件编辑友好
10.2 命令行:arduino-cli
安装:
1 2 3 4
| scoop install arduino-cli
|
完整编译+烧录+监视串口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| arduino-cli core update-index arduino-cli core install arduino:renesas_uno
arduino-cli compile --fqbn arduino:renesas_uno:minima ` --output-dir build ` .\examples\UDP_Client\UDP_Client.ino
arduino-cli upload --fqbn arduino:renesas_uno:minima ` --port COM3 ` --input-dir build
arduino-cli monitor --port COM3 --config baudrate=115200
|
10.3 版本控制
.gitignore(至少包含):
1 2 3 4 5 6
| build/ *.bin *.elf *.hex *.map .DS_Store
|
10.4 持续集成(GitHub Actions)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| name: Arduino CI
on: [push, pull_request]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: arduino/setup-arduino-cli@v1 - run: | arduino-cli core update-index arduino-cli core install arduino:renesas_uno arduino-cli compile --fqbn arduino:renesas_uno:minima examples/UDP_Client arduino-cli compile --fqbn arduino:renesas_uno:minima examples/UDP_Server arduino-cli compile --fqbn arduino:renesas_uno:minima examples/pingpong
|
11. 与 Keil 工程的完整操作对比
| 任务 |
Keil uVision |
Arduino IDE |
| 新建工程 |
New Project → 选择 Device |
新建 Sketch,IDE 自动配置 |
| 添加源文件 |
右键工程 → Add Files |
放到 src/ 目录,自动扫描 |
| 配置编译选项 |
Project Options → C/C++ |
编辑 platform.txt(高级) |
| 配置链接器 |
Project Options → Linker |
编辑 fsp.ld(高级) |
| 编译 |
Build 按钮 (F7) |
验证按钮 (Ctrl+R) |
| 查看输出 |
Build Output 窗口 |
控制台 |
| 查看内存使用 |
Linker → Memory Map |
读 .map 文件 |
| 烧录 |
Download 按钮 (F8) |
上传按钮 (Ctrl+U) |
| 调试 |
Debug → Start (F5) |
无硬件调试器 |
| 断点调试 |
支持 |
不支持,只能 Serial.print() |
| 查看寄存器 |
Peripherals 窗口 |
不支持 |
| 查看变量 |
Watch 窗口 |
不支持 |
12. 总结
12.1 关键要点
- 烧录目标明确:Arduino IDE 只烧录 RA4M1 主 MCU,不烧录 LAN865x MAC-PHY
- 自动化程度高:源文件自动扫描(递归
src/),无需手动管理工程文件
- 配置三件套:
boards.txt(板卡)、platform.txt(工具链)、fsp.ld(链接脚本)
- Flash 布局:0x0000-0x3FFF 是 Bootloader,用户程序从 0x4000 开始
- 调试受限:无硬件调试器,只能用
Serial.print() 和 .map 文件
12.2 文件流程总结
1 2 3 4 5 6 7 8 9 10 11
| 源文件 (.ino/.cpp/.c) ← Arduino_10BASE_T1S/src/ + Sketch ↓ arm-none-eabi-gcc/g++ -c 目标文件 (.o) × 约 150 个 ↓ arm-none-eabi-ar 静态库 (core.a, libfsp.a) ↓ arm-none-eabi-g++ -T fsp.ld ELF 可执行文件 (.elf) + Map 文件 (.map) ↓ arm-none-eabi-objcopy 纯二进制文件 (.bin) ↓ bossac (UNO R4) 或 dfu-util (Portenta H7) RA4M1 Flash @ 0x4000
|
12.3 推荐工作流
- 开发阶段:使用 Arduino IDE,利用自动化
- 调试阶段:读
.map 文件,用 Serial.print() + 串口监视器
- 优化阶段:分析
.map、反汇编 .elf、裁剪 lwipopts.h
- 生产阶段:使用
arduino-cli 自动化编译烧录,集成 CI
12.4 何时跳出 Arduino 框架
以下场景应考虑切到 PlatformIO + 自定义 platform.txt,或直接用 CMake + GNU Arm 工具链:
- 需要精细控制链接脚本(如多段 Flash 分配)
- 需要硬件调试器(GDB + OpenOCD)
- 需要集成 RTOS(FreeRTOS 已有 Arduino 绑定但配置受限)
- 库需要跨 MCU 平台(不只是 renesas_uno)
13. 参考资源
文档结束
下一步阅读:
调试建议:使用 arduino-cli compile --verbose 查看完整编译过程。