尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

ARM7TDMI-S微控制器ISP/IAP编程与JTAG调试实战指南

ARM7TDMI-S微控制器ISP/IAP编程与JTAG调试实战指南
📅 发布时间:2026/6/20 23:33:23

1. 项目概述:ARM7TDMI-S微控制器的编程与调试基石

在嵌入式开发领域,尤其是基于ARM7TDMI-S内核的经典微控制器如NXP的LPC21xx/22xx系列,固件的烧录、更新与调试是贯穿产品生命周期的核心任务。很多刚入行的工程师可能会觉得,用个现成的JTAG仿真器连上IDE点一下“Download”就完事了,但当你需要实现产线批量烧录、设备现场远程升级,或者深入追踪一个只在特定时序下才出现的诡异Bug时,你就会发现,仅仅停留在“点按钮”的层面是远远不够的。这时,深入理解芯片内置的串行编程(ISP)、在应用编程(IAP)机制以及硬件调试架构(如EmbeddedICE),就从“锦上添花”变成了“雪中送炭”的硬核技能。

ISP和IAP,听起来像是两个相近的缩写,但它们解决的问题场景和实现层级截然不同。ISP(In-System Programming)通常指通过芯片自带的Bootloader,利用UART等简单串行接口,在系统板上直接对空白或已有程序的Flash存储器进行编程。它不依赖于昂贵的专用编程器,是生产环节和现场维修的利器。而IAP(In-Application Programming)则更进一步,它允许正在运行的用户应用程序,主动调用芯片内部固化的程序,去擦写自身的Flash存储区。这意味着设备在野外连上网后,可以自己下载新固件并完成升级,无需人工干预,这是实现产品“终身可升级”功能的关键。

至于调试,JTAG接口和其背后的EmbeddedICE逻辑则是我们窥探芯片内部运行状态的“显微镜”。它允许我们在不占用任何目标系统资源(如串口、内存)的情况下,设置断点、观察变量、单步执行,是解决复杂逻辑问题和进行性能分析的终极武器。本文将结合LPC21xx/22xx的用户手册,不仅解读这些技术的协议和命令,更会分享在实际项目中如何组合运用它们,以及那些手册里不会写的“踩坑”经验和操作细节。

2. 核心原理深度剖析:ISP、IAP与调试架构如何工作

2.1 ISP Bootloader:芯片的“出厂恢复模式”

LPC21xx/22xx芯片内部固化了一段不可修改的Bootloader程序,通常映射在内存地址的最开始区域(例如重启后的0x0000 0000)。当芯片满足特定条件(如某些引脚在复位时被拉低)启动时,它会运行这段Bootloader,而不是跳转到用户应用程序。Bootloader会初始化一个UART接口(通常是P0.0和P0.1),然后等待主机(通常是PC)通过串口发送来的命令。

这个通信过程是纯文本的、交互式的。主机发送一个ASCII命令字符串,以回车换行(<CR><LF>)结束,Bootloader执行后返回一个状态码(也是ASCII文本)。例如,发送R 1073741824 4<CR><LF>就是请求读取从地址0x4000 0000(即1073741824的十进制表示)开始的4个字节数据。这里的关键细节在于地址对齐和数据编码。几乎所有内存操作命令都要求地址是“字对齐”的(即能被4整除),字节数也必须是4的倍数,这是因为ARM7TDMI-S内核是32位架构,以字(4字节)为单位访问内存效率最高。如果参数不对齐,会返回ADDR_ERROR或COUNT_ERROR。

数据传输采用了UU编码。这是一种将二进制数据编码为可打印ASCII字符的方法,目的是为了确保通过可能只支持7位数据、或有流量控制的串行链路时,数据能可靠传输。Bootloader每发送20行UU编码数据(每行最多45字节原始数据)后,会跟随一个校验和。主机必须校验这个和,并回复OK或RESEND。这个设计体现了在低速、不可靠链路上的鲁棒性思想。

2.2 IAP:运行中程序的“自我手术刀”

IAP更像是提供给用户程序的一组系统调用API。它不是通过串口交互,而是由用户程序直接调用位于固定地址(在LPC21xx/22xx上是0x7FFF FFF0,注意最低位为1表示Thumb模式)的一段固件例程。用户程序需要在RAM中准备好一个命令数组,填充命令码和参数,然后通过函数指针跳转到这个IAP入口地址去执行。

其参数传递机制非常经典,采用了寄存器传参的方式:R0寄存器指向命令参数表,R1寄存器指向结果返回表。这种设计与ARM的ATPCS(ARM-Thumb过程调用标准)一脉相承,确保了不同编译器产生的代码都能正确调用。IAP的命令比ISP少,主要聚焦于芯片识别(读ID、读版本)和内存比较,更复杂的Flash编程操作通常由ISP完成,或者由用户基于IAP/ISP的基础命令构建更上层的逻辑。

IAP的精妙之处在于它在用户模式下运行,却能够操作Flash控制器等特权资源。这依赖于芯片内部精密的硬件设计和对内存保护单元的合理配置。调用IAP时,就像应用程序发起了一个“系统调用”,陷入到一段受信任的、拥有更高权限的ROM代码中执行。

2.3 EmbeddedICE与JTAG:硬件的“调试探针”

如果说ISP/IAP是管理程序的“手”,那么EmbeddedICE就是观察程序运行的“眼”。它是内置于ARM7TDMI-S内核中的一个调试模块,通过标准的JTAG接口与外部调试器(如J-Link、ULINK)通信。

它的核心是两个实时观察点寄存器。你可以为每个寄存器设置一个复杂的触发条件:比如当地址总线等于0x4000 0200、数据总线等于0xDEADBEEF、且操作为“写”时,让内核停止。每个条件都可以用掩码(Mask)来忽略某些位的比较,从而实现“地址范围”触发。更强大的是,两个观察点可以链式(CHAIN)工作,实现“先触发A,再触发B才停止”的复杂序列断点,这对于调试状态机或任务调度代码极其有用。

调试通信通道(DCC)是另一个宝藏功能。它被映射为协处理器14,允许运行在目标板上的程序,直接通过JTAG接口与主机调试器交换数据,而完全不用停止程序运行、不占用串口等外设。你可以用它来输出高性能的日志(远比串口快),或者由主机向目标程序发送控制命令,实现一种“后台通信”。

2.4 ETM:追踪执行的“飞行记录仪”

嵌入式追踪宏单元(ETM)是更高阶的调试工具,用于实时指令追踪。当芯片以全速运行时,ETM会压缩并实时输出处理器执行的指令流信息(主要是分支地址和流水线状态),通过一个专用的跟踪端口(Trace Port)发送给外部的跟踪分析仪。这相当于给程序执行装了一个“黑匣子”,事后可以完整回放CPU到底执行了哪些指令,对于分析偶发的、与时间紧密相关的崩溃问题至关重要。LPC21xx/22xx的ETM配置较为基础,支持1对地址比较器和1个计数器,足以实现基本的触发追踪。

3. 实操指南:从命令调用到系统集成

3.1 搭建ISP编程环境

要进行ISP,你需要一个USB转TTL串口模块、几条杜邦线和一个终端软件(如Tera Term、PuTTY或简单的screen命令)。

硬件连接:

  1. 将USB转TTL模块的TX连接到MCU的P0.0(RXD0),RX连接到P0.1(TXD0)。
  2. 共地连接(GND to GND)。
  3. 最关键的一步:在MCU复位期间,将P0.14引脚拉低(通常接地)。这是LPC21xx/22xx进入ISP模式的硬件条件。有些开发板会设计一个“ISP按钮”,按下时同时触发复位和拉低P0.14。

软件操作:

  1. 打开终端软件,设置正确的串口号、波特率(初始通讯波特率通常是9600或115200,具体需查芯片数据手册)。
  2. 给目标板重新上电或复位,此时终端应收到Bootloader的提示符(例如一串字符或“?”)。
  3. 你可以手动输入命令,例如:
    J<CR><LF> // 读取芯片ID
    更实际的做法是编写一个主机脚本(Python、C#等),自动化整个编程流程:擦除、发送二进制文件(需转换为Intel HEX或S-Record格式,再分解为写内存命令)、校验、最后执行“Go”命令启动应用程序。

实操心得:ISP波特率自适应手册中提到ISP支持不同的波特率,但初始通讯波特率是固定的。一个常见的坑是,如果目标板之前运行的用户程序修改了UART时钟分频并发生了崩溃,可能导致Bootloader无法以默认波特率通讯。稳妥的做法是,在硬件设计时,确保ISP控制引脚(如P0.14)可以通过跳线帽或测试点可靠拉低,并且在软件中,用户程序不要永久性地改变UART0的时钟配置,或者在跳转到用户程序前将其恢复为默认状态。

3.2 在应用程序中集成IAP调用

在C语言中调用IAP,需要正确定义函数指针和命令结构。下面是一个读取芯片ID的示例:

// 定义IAP入口地址(Thumb模式,地址最低位置1) #define IAP_ENTRY_LOCATION 0x7FFFFFF1 // 定义IAP命令和结果数组(根据命令最大参数数量定义大小) unsigned long iap_command[5]; unsigned long iap_result[2]; // 定义IAP函数类型:参数为两个unsigned int数组指针,返回void typedef void (*IAP_PROC)(unsigned int[], unsigned int[]); IAP_PROC iap_call; // 初始化函数指针 iap_call = (IAP_PROC)IAP_ENTRY_LOCATION; // 准备读取芯片ID的命令(命令码54) iap_command[0] = 54; // Command: Read Part ID // 调用IAP iap_call(iap_command, iap_result); // 检查结果 if (iap_result[0] == 0) { // CMD_SUCCESS unsigned long part_id = iap_result[1]; printf("Chip ID: 0x%08lX\n", part_id); } else { printf("IAP failed with code: %lu\n", iap_result[0]); }

关键细节:

  1. 栈空间:IAP函数内部会使用栈,因此必须确保在调用IAP时,系统的栈指针(SP)指向一段有效且足够的RAM空间。通常在主函数初始化时就设置好栈。
  2. 中断:在调用IAP进行Flash擦写操作时(如果是更复杂的IAP命令),必须禁用全局中断。因为Flash编程期间CPU访问Flash会暂停,如果此时发生中断,程序指针跳转,可能导致不可预料的后果。
  3. 代码位置:调用IAP的代码不能从正在被擦写的Flash扇区中执行。通常的做法是将执行IAP调用的函数链接到RAM中运行,或者确保它位于不会被操作的Flash区域。

3.3 配置与使用JTAG调试

使用JTAG调试,你需要一个兼容的调试探针(如SEGGER J-Link)和IDE(如Keil MDK、IAR Embedded Workbench或Eclipse+GCC+OpenOCD)。

连接:标准20针或10针JTAG接口,需要连接TMS、TCK、TDI、TDO、nTRST(可选)和GND。特别注意,LPC21xx/22xx的JTAG引脚与GPIO P1.26-P1.31复用。芯片复位时,会采样P1.26/RTCK的状态:如果被拉低(通过一个4.7k-10k电阻接地),这些引脚就初始化为JTAG功能;如果为高或悬空,则初始化为GPIO。如果你的板子调试不了,首先检查这个引脚的电平。

调试器配置:在IDE中,选择正确的设备型号(如LPC2294),调试器选择J-Link,接口选择JTAG(速度可设为自适应或固定值,如4MHz)。下载算法(Flash Programming Algorithm)要选择对应你芯片Flash型号的。

利用观察点(Watchpoint)调试内存问题:假设你发现某个全局变量g_sensor_value在某个时刻被意外修改,但不知道是谁干的。你可以设置一个数据观察点:

  1. 在调试器中,找到“Breakpoints”窗口,通常会有“Access Watchpoint”或类似的选项。
  2. 地址设置为&g_sensor_value。
  3. 条件设置为“Write”(当被写入时触发)。
  4. 当程序运行,任何指令(哪怕是库函数或中断服务程序)向这个地址写入时,CPU都会立刻暂停,你就可以查看调用栈,找到“元凶”。

3.4 实现一个简单的IAP固件升级流程

结合ISP和IAP,可以设计一个支持现场升级的Bootloader。这是一个简化的双分区升级方案:

  1. 内存布局规划:

    • Bootloader区(0x0000 0000 - 0x0000 3FFF): 存放自己的升级引导程序,包含串口驱动、Flash驱动、IAP调用和跳转逻辑。
    • 应用程序A区(0x0000 4000 - 0x0001 BFFF): 主程序。
    • 应用程序B区(0x0001 C000 - 0x0003 3FFF): 备用区,用于存放新下载的固件。
    • 标志区(Flash最后一个扇区): 存放升级标志、CRC校验值等元数据。
  2. Bootloader工作流程:

    • 上电后,Bootloader检查“升级标志”。
    • 如果标志有效,则从应用程序B区读取固件,校验CRC,然后调用IAP将其擦写至应用程序A区。
    • 擦写完成后,清除标志,跳转到应用程序A区执行。
    • 如果标志无效,直接跳转到应用程序A区。
  3. 应用程序中的升级触发:

    • 应用程序通过网络、串口等方式收到新固件包。
    • 验证固件包头部和CRC。
    • 调用IAP,将固件包写入应用程序B区。
    • 在Flash的标志区写入升级标志和CRC。
    • 执行软复位,将控制权交还给Bootloader。

核心注意事项:IAP编程的“原子性”与电源安全Flash擦写是以“扇区”为单位的,一个扇区擦除和编程的过程不能被打断。因此,在调用IAP进行擦写操作的前后,必须:

  1. 关闭总中断(__disable_irq())。
  2. 确保操作期间供电稳定。最危险的情况是编程到一半突然断电,可能导致该扇区数据损坏,无法启动。工业级设计通常会加入大电容或备用电源,确保完成一个扇区操作的最低时间。更稳健的做法是,在每个扇区编程完成后,立即计算该扇区的CRC或校验和,写入标志区。这样即使升级中断,下次Bootloader也能知道最后一个完好扇区的位置,实现断点续传或回滚。

4. 高级技巧与深度优化

4.1 提升ISP编程速度

默认的ISP协议,每个命令都需要等待返回码,且数据采用UU编码,效率较低。对于生产批量烧录,可以采取以下优化:

  1. 使用最高支持波特率:在ISP握手成功后,立即使用U命令将波特率切换到最高值(如115200甚至更高,取决于芯片和时钟精度)。
  2. 批量写操作:虽然协议是交互式的,但主机可以在发送一个“写内存”命令后,持续发送多块数据,只要及时回复OK即可。编写主机软件时,应采用双缓冲或流水线方式,在等待上一块数据校验回复的同时,准备下一块数据并发送,充分利用串口带宽。
  3. 避开UU编码(如果可能):有些Bootloader版本或定制Bootloader支持直接发送二进制数据。如果可行,可以节省编码/解码的时间。

4.2 利用DCC进行非侵入式调试日志输出

当你的系统资源紧张,或者串口已被用于产品通信时,DCC是输出调试信息的完美通道。你需要编写一个简单的DCC驱动函数:

// 简化的DCC数据发送函数(轮询方式) void DCC_SendChar(char ch) { // 等待DCC发送通道就绪 while (!(*((volatile unsigned long *)0xE000EDF0) & (1 << 30))) { // 空循环等待 } // 写入数据到DCC数据寄存器 *((volatile unsigned long *)0xE000EDF8) = ch; } void DCC_SendString(const char *str) { while (*str) { DCC_SendChar(*str++); } }

在Keil或IAR的调试窗口中,有一个“Debug (printf) Viewer”或类似的窗口,可以直接显示通过DCC通道发送过来的字符串。这样,你可以在不停止程序、不占用任何外设的情况下,实时观察程序运行日志。

4.3 为ETM追踪配置引脚

要使用ETM功能,除了连接复杂的跟踪线(TRACECLK, TRACEPKT[3:0]等),最关键的是硬件上要让芯片复位后识别到Trace模式。如手册所述,需要将P1.20/TRACESYNC引脚通过一个4.7k电阻下拉到地。在设计PCB时,这个电阻应该作为预留位置(DNP),默认不焊接。当需要深度调试时,再焊上这个电阻。同时,要确保在复位期间,没有其他驱动源将P1.20拉高。

5. 故障排查与常见问题实录

在实际项目中,与ISP/IAP/JTAG相关的问题层出不穷。下面是一个常见问题速查表:

问题现象可能原因排查步骤与解决方案
ISP无法连接,终端无任何输出1. 硬件连接错误(TX/RX接反)。
2. 目标板未进入ISP模式(P0.14未在复位时拉低)。
3. 波特率不匹配。
4. 目标芯片Bootloader损坏。
1. 交换TX/RX线序再试。
2. 用万用表或示波器确认复位瞬间P0.14为低电平。
3. 尝试常见的波特率:9600, 19200, 38400, 57600, 115200。
4. 尝试通过JTAG擦除整个Flash后再试,或考虑芯片是否物理损坏。
ISP可以连接但发送命令后无响应或乱码1. 串口流控(RTS/CTS)影响。
2. 主机发送的字符串格式错误(缺少<CR><LF>)。
3. 目标板电源噪声大,导致串口数据错误。
1. 在终端软件中禁用硬件流控(RTS/CTS)。
2. 确认命令字符串以\r\n结束。可以用十六进制模式查看发送的数据。
3. 检查电源,在MCU的VDD和GND之间靠近芯片引脚处并联一个0.1uF和10uF的电容。
IAP调用后程序跑飞或硬件错误1. 调用IAP时中断未关闭。
2. 栈空间不足或栈指针错误。
3. 代码在Flash中执行时,试图擦写所在扇区。
4. Flash编程时间超时(时钟配置错误)。
1. 在IAP调用前后用__disable_irq()和__enable_irq()包裹。
2. 检查启动文件中的栈大小设置,确保足够(至少几百字节)。
3. 将调用IAP的函数用__attribute__((section(".ramfunc")))定义,并修改链接脚本将其放入RAM。
4. 检查系统时钟配置,特别是给Flash控制器提供时钟的PLL设置,确保符合芯片手册要求。
JTAG连接失败,调试器报“No device found”1. JTAG接口线序错误或虚焊。
2. nTRST或RTCK引脚处理不当。
3. 芯片处于低功耗模式或时钟未开启。
4. 调试器供电不足(如果使用调试器给目标板供电)。
1. 对照原理图,用万用表逐根检查JTAG信号线到芯片引脚的连通性。
2. 确认P1.26/RTCK在复位时被正确拉低(通过电阻)以启用JTAG。nTRST可上拉或直接连接。
3. 确保芯片已正常复位,有源晶振起振,内核供电正常。有时需要先通过ISP下载一个简单的“点灯”程序,确保芯片基本功能正常。
4. 改为由目标板自己供电,或检查调试器的供电能力。
观察点(Watchpoint)不触发1. 观察点数量超过硬件限制(ARM7TDMI-S只有2个)。
2. 设置的地址或数据值不正确(如地址未对齐)。
3. 观察点类型(读/写/访问)设置错误。
4. 代码被编译器优化,变量被放入寄存器,未触发内存访问。
1. 检查已设置的断点/观察点数量,先删除其他再试。
2. 确认地址是有效的内存地址,并且是你想监控的变量地址(&variable)。
3. 明确你的意图是监控“读取”、“写入”还是“访问”,选择正确的触发类型。
4. 尝试将被观察的变量用volatile关键字修饰,或关闭编译器优化(-O0)进行调试。
使用DCC输出,但调试器窗口看不到信息1. DCC驱动代码未正确初始化或编写错误。
2. 调试器未启用DCC消息捕获功能。
3. 内核时钟配置异常,导致DCC逻辑无时钟。
1. 检查DCC发送函数,确保它轮询的是正确的状态位(bit 30 of DEMCR寄存器)。
2. 在Keil中,确认“View -> Serial Windows -> Debug (printf) Viewer”窗口已打开。在IAR中,需要配置“Debugger -> Plugins -> Terminal I/O”。
3. 确保系统时钟(CCLK)已正确配置并运行。

一个真实的坑:IAP擦写后程序异常我曾遇到一个案例,IAP升级后程序能启动,但运行几分钟后必然死机。排查后发现,问题出在中断向量表的重映射上。LPC21xx/22xx芯片复位后,从0x0地址开始执行。但用户程序通常被链接到0x0000 4000或更后的地址。Bootloader跳转到用户程序后,如果发生了中断,CPU还是会去0x0地址附近寻找向量表。我们的解决方案是,在用户程序的启动代码中,尽早地通过设置芯片的“存储器重映射”寄存器,将0x0地址重新映射到用户程序实际的向量表所在位置(通常是Flash起始地址),或者直接将向量表拷贝到RAM的0x0地址并重映射到RAM。忘记处理这个细节,中断就会跳到错误的地址,导致不可预测的崩溃。

6. 总结与资源推荐

深入理解ARM7TDMI-S的ISP、IAP和调试子系统,绝非一朝一夕之功。它要求开发者不仅会写应用代码,还要了解硬件启动流程、内存架构、编译链接原理以及调试器的工作方式。这份手册摘录的内容是一个坚实的起点,但真正的掌握来自于动手实践和解决问题。

对于希望继续深入的朋友,我强烈建议:

  1. 阅读原始文档:NXP的UM10114用户手册是根本,但ARM的官方文档同样重要,如《ARM7TDMI-S Technical Reference Manual》(DDI 0234)和《Embedded Trace Macrocell Specification》(IHI 0014),它们提供了内核和调试组件的权威说明。
  2. 研究开源实现:在GitHub上搜索“LPC2000 ISP”、“LPC IAP Bootloader”,有很多成熟的开源主机工具和Bootloader示例,阅读这些代码能学到很多协议处理和错误恢复的实际技巧。
  3. 动手搭建最小系统:用一块LPC21xx/22xx的最小系统板,亲手连接ISP和JTAG,从零开始编写一个LED闪烁程序,然后用ISP烧录,用JTAG调试,再用IAP实现一个简单的自更新功能。这个过程中遇到的每一个错误,都是最好的学习材料。

最后,记住一个原则:嵌入式开发中,越是底层、基础的技术,其生命力往往越长久。尽管ARM7TDMI-S已是经典内核,但其中涉及的Bootloader设计思想、固件升级方案、硬件调试原理,在更现代的Cortex-M甚至Cortex-A系列芯片中依然一脉相承。掌握了这些,你就拥有了应对更复杂嵌入式系统的底气。

相关新闻

  • 5个AI技能让你的Obsidian笔记效率提升300%
  • 嵌入式GUI显示驱动配置指南:以emWin的GUIDRV_CompactColor_16为例
  • Developer-Portfolio SEO 优化指南:10个技巧让你的作品集在 Google 排名更高 [特殊字符]

最新新闻

  • 如何快速实现PC游戏分屏多人联机:Nucleus Co-Op完全指南
  • 魔兽争霸3终极兼容指南:WarcraftHelper让经典游戏重获新生
  • 2026十堰防水补漏避坑指南:卫生间/厨房/阳台/屋顶/地下室漏水检测维修全攻略,正规施工+透明报价+口碑榜靠谱服务商推荐 - 安佳防水
  • 2026动物实验选哪家?临床前研究机构选择指南 - 品牌排行榜
  • 2026南平防水补漏避坑指南:卫生间/厨房/阳台/屋顶/地下室漏水检测维修全攻略,正规施工+透明报价+口碑榜靠谱服务商推荐 - 安佳防水
  • 终极窗口置顶工具:让你的重要窗口始终保持在最上层

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号