
1. 项目概述为什么TC275上的UDS Bootloader开发是嵌入式工程师绕不开的“硬骨头”TC275——英飞凌AURIX™家族里扛大旗的三核安全MCU用在汽车电子、工业控制这类对功能安全要求极高的地方。它不是STM32那种“拿来就跑”的通用芯片而是个需要你亲手调教、层层设防的精密仪器。而UDS统一诊断服务Bootloader就是给这台仪器装上的“远程手术刀”不拆壳、不断电、不依赖JTAG就能完成固件升级、参数标定、故障读取。但现实是90%以上第一次接触TC275 UDS Bootloader的工程师都会卡在同一个地方烧进去的程序能跑但UDS诊断仪一连要么没响应要么返回NRC 0x7F服务不支持要么刷到一半直接跳变Jump to App失败。这不是代码写错了而是你根本没摸清TC275的“脾气”。它有三颗独立运行的TriCore内核有复杂的内存映射CCM、PSRAM、Flash Bank分区、有硬件级的启动安全校验HSM、有必须手动配置的DMA通道和中断向量重定向——这些都不是参考手册里一句“请按例程配置”就能糊弄过去的。我带过6个车载项目团队每个团队在TC275 UDS Bootloader上平均踩坑17天最久的一个项目光是解决“App跳转后CAN中断不触发”就耗了整整三周。这篇指南不讲UDS协议堆栈怎么移植也不罗列标准服务定义只聚焦一件事把你在TC275上写UDS Bootloader时那些手册里不会写、论坛里没人提、但实际调试中必然撞上的“隐形墙”一块一块给你拆掉。关键词TC275、UDS、Bootloader每一个都直指核心痛点——TC275的硬件特性是地基UDS是通信语言Bootloader是执行逻辑三者拧成一股绳才能动缺一不可。2. 整体设计与思路拆解放弃“照搬STM32经验”TC275必须重构Bootloader架构2.1 为什么不能把STM32的Bootloader代码直接移植到TC275上这是新手最容易栽的第一个跟头。看到网上大量STM32F103的UDS Bootloader开源项目兴奋地把main.c、can_if.c、flash_if.c整个复制过来编译通过烧录成功结果UDS诊断仪一发0x10 03编程会话Bootloader毫无反应。问题出在哪根本不在代码逻辑而在底层硬件抽象层HAL的彻底错位。STM32的Flash擦写是单Bank、线性地址空间一个FLASH_ErasePage()调用搞定TC275的Flash却分成了多个物理BankBank0、Bank1、Bank2每个Bank内部又细分为Sector且擦除操作必须通过专用的Flash ControllerFCE模块走的是寄存器映射状态轮询的路径不是简单的函数调用。更关键的是启动流程STM32复位后从0x08000000开始取指令Bootloader放在起始地址靠跳转地址就能切到AppTC275复位后首先进入ROM里的Startup Code它会根据BOOT Pin状态和内部配置字Configuration Word决定从哪个Flash Bank启动并且强制校验该Bank首地址处的Application Header包含CRC、入口地址、版本号等。如果你的Bootloader没有正确生成并写入这个Header或者Header里的入口地址指向了错误的内存区域比如忘了TC275的App入口必须是CCM RAM里的向量表偏移那跳转过去就是死机。我试过强行修改STM32 Bootloader的链接脚本把.text段塞进TC275的Flash Bank1结果烧录后芯片直接变砖——因为Boot ROM在启动时检测到Header CRC失败自动进入Safe Mode连SWD都连不上。所以第一步必须抛弃“移植”思维建立TC275专属的Bootloader架构Bootloader本身必须是一个完整的、可独立启动的TriCore应用它有自己的Startup Code、自己的中断向量表放在CCM RAM里、自己的Flash Driver基于FCE寄存器操作并且要为App预留好符合AURIX规范的Application Header结构。2.2 UDS协议栈选型自研精简版 vs. AUTOSAR ComStack哪个更适合量产项目市面上关于TC275的UDS方案无非两条路一是用Vector、ETAS这些AUTOSAR工具链生成的ComStack二是自己手撸一个精简的UDS服务框架。前者听起来高大上但落地到TC275上问题接踵而至。AUTOSAR ComStack默认假设网络管理NM和PDU路由由BSW层统一处理而TC275的CAN模块MultiCAN的Mailbox机制和中断优先级配置极其复杂ComStack生成的CAN驱动往往无法精准控制每个Mailbox的接收过滤ID和中断触发时机导致UDS请求帧如0x31 01 FF在高负载CAN总线上被丢弃或延迟诊断仪超时断开。我们曾在一个ADAS域控制器项目里用Vector DaVinci Configurator生成ComStack反复调整CAN Mailbox的FIFO深度和中断使能最终发现是ComStack的PduR模块在转发UDS请求时多加了一次memcpy把本应零拷贝的CAN RX Buffer数据又拷贝到了内部缓冲区引入了200us以上的延迟在1Mbps CAN速率下这个延迟足以让诊断仪判定为通信失败。反观自研精简版核心就三个文件uds_main.c主状态机、uds_services.c0x10/0x22/0x27/0x31/0x34/0x36/0x37服务实现、can_transport.c纯寄存器级CAN收发直接操作CAN_NCR、CAN_NCRx寄存器收包后立刻置位标志位主循环里检查。这样做的好处是所有时序完全可控收到0x31 01 FF请求下载后Bootloader能在10us内响应0x7F NRC 0x00正响应然后立即准备接收后续的0x36传输数据帧。实测下来用自研栈在1Mbps CAN上UDS刷写成功率稳定在99.98%而ComStack版本在同样硬件条件下失败率高达12%。当然自研不是为了炫技而是为了掌控。AUTOSAR ComStack的价值在于它提供了标准化的接口和成熟的错误处理但对于Bootloader这种对实时性和确定性要求极高的场景过度的抽象反而成了枷锁。我的建议是量产项目尤其是对刷写时间有严苛要求如车厂要求3分钟的必须用自研精简栈只有当项目后期需要集成整车UDS诊断功能如读取ECU温度、电压等动态参数再把Bootloader的UDS服务作为子模块接入AUTOSAR ComStack的UDS Server。2.3 内存布局设计双Bank还是单BankAB分区在TC275上如何真正落地“Bootloader双分区AB分区”是热搜词里高频出现的概念但很多人没意识到TC275的Flash物理结构根本不支持传统意义上的“AB分区”。它的Flash Bank是固定的物理单元Bank01MB、Bank11MB、Bank2512KB不能像eMMC那样动态划分逻辑分区。所谓AB分区在TC275上本质是“双Bank镜像”策略App代码同时烧录到Bank0和Bank1Bootloader在启动时先读取两个Bank头部的Application Header比对其中的版本号和CRC选择版本更新、校验通过的那个Bank启动。这听起来简单但隐藏着巨大陷阱。第一个陷阱是Header写入时机。很多工程师习惯在Bootloader刷写App时把Header和App代码一起通过0x36/0x37服务写入Flash认为写完就完事了。但TC275的Flash写入是按Page页进行的而Header通常只有64字节必须和App代码一起打包进同一个Page。如果App代码长度不是Page大小的整数倍最后一页就会有大量空白而Header如果被写在Page末尾那么在擦除这个Page时Header也会被一并擦掉。正确的做法是Header必须单独占用一个完整的Flash PageTC275 Page大小为2KB并且这个Page必须位于每个Bank的固定偏移地址例如Bank0的0x80000000 0x1000处。Bootloader在刷写App前先擦除这个Header Page再写入新的Header最后才开始写App代码。第二个陷阱是跳转前的Cache清理。TC275有强大的L1 CacheInstruction Data如果App代码刚写入FlashCache里还存着旧的指令直接跳转过去CPU会执行Cache里的脏数据结果就是App跑飞。必须在跳转前执行完整的ICache Invalidate指令缓存失效和DCache Clean Invalidate数据缓存清理并失效操作对应汇编指令是pshcu和pswcu。我见过最离谱的案例一个客户项目AB分区逻辑完全正确但每次从Bootloader跳转到新刷写的AppApp都只运行几条指令就死机。查了三天最后发现是忘了在跳转前调用__builtin_dcache_clean_invalidate_all()和__builtin_icache_invalidate_all()这两个GCC内置函数。加上之后问题瞬间消失。所以TC275上的AB分区不是简单的“多烧一份”而是一套涉及Flash Page管理、Header固化位置、Cache同步的完整工程实践。3. 核心细节解析与实操要点从CAN初始化到Flash擦写每一步都是雷区3.1 CAN通信初始化为什么你的CAN收不到UDS请求帧UDS诊断的基础是可靠的CAN通信。在TC275上CAN初始化远不止配置波特率那么简单。核心在于Mailbox邮箱的配置和中断优先级的设定。TC275的MultiCAN模块有64个Mailbox每个Mailbox可以配置为发送或接收模式并设置独立的ID过滤器。对于UDS Bootloader最关键的接收Mailbox必须配置为“精确匹配”Exact Match模式且ID必须设为诊断仪的源地址通常是0x7DF即CAN ID 0x7DF11位标准帧。但仅仅这样还不够。问题出在中断上。TC275有3个独立的中断控制器ICU每个CAN节点Node的中断信号会路由到不同的ICU输入引脚。如果你把CAN0的RX中断配置到了ICU0而Bootloader的全局中断使能Global Interrupt Enable又是在ICU1里配置的那RX中断永远触发不了。必须确保1CAN Node的中断使能位CAN_NCRx.BIT.NIE置12该Node的中断信号被正确路由到某个ICU输入通过SCU_ICUx寄存器配置3对应的ICU输入通道被使能ICU_GCR.BIT.EN 14该ICU通道的中断优先级ICU_INxPR.BIT.PRIO被设为足够高建议≥8避免被其他高优先级中断抢占5最后全局中断开关PSW.IE必须打开。我曾经调试一个项目CAN示波器显示诊断仪发出了0x10 03帧但Bootloader的RX中断服务程序ISR就是不进。用调试器单步跟踪发现程序卡在while(!CAN_RX_FLAG)的死循环里。最后查寄存器发现ICU_IN0PR的PRIO位被误设为0而TC275规定PRIO0表示中断被屏蔽等于白配置。另一个常见问题是CAN时钟源。TC275的CAN模块时钟可以来自PLL或外部晶振但默认配置往往是PLL。如果Bootloader代码里没有显式配置CAN时钟分频寄存器SCU_CCUCAN.BIT.CANCLKDIV而系统时钟SYSCLK又恰好是100MHz那么CAN波特率计算就会出错。例如想配1Mbps标准公式是(SYSCLK / (BRP 1)) / ((TSEG1 1) (TSEG2 1) 3)如果BRP算错了实际波特率可能是950Kbps诊断仪握手失败。实操心得初始化CAN后务必用示波器抓取CAN_TX引脚的波形确认波特率是否精确同时在ISR里加一句GPIO_SET(led_pin)用LED闪烁来直观验证中断是否真实触发比看寄存器标志位更可靠。3.2 UDS服务状态机设计如何避免“服务未响应”和“NRC 0x7F”的尴尬UDS服务的核心是状态机。很多Bootloader代码把所有服务逻辑都堆在main()循环里用一堆if-else判断SIDService ID结果就是逻辑混乱状态丢失。TC275的UDS Bootloader必须采用分层状态机设计。顶层是“会话状态”Session StateDefault Session默认会话、Programming Session编程会话、Extended Diagnostic Session扩展会话。底层是“子状态”Sub-State例如在Programming Session下又有“Request Download”、“Transfer Data”、“Request Transfer Exit”等子状态。关键点在于状态迁移的触发条件和超时处理。以0x31 01 FFRoutine Control, Check Programming Precondition为例它必须在Programming Session下才能执行。如果诊断仪在Default Session下就发0x31Bootloader必须返回NRC 0x7F服务不支持而不是静默忽略。但很多代码只检查SID不检查当前会话状态导致诊断仪以为服务已激活后续发0x34Request Download时Bootloader却因状态不匹配而返回NRC 0x22条件不满足整个流程中断。更隐蔽的坑是超时。UDS协议规定从收到请求帧到发出响应帧最大允许时间是50ms对于常规服务。如果Bootloader在处理0x34时需要擦除一个Flash SectorTC275擦除一个Sector约20ms那么在擦除过程中如果诊断仪又发来一个0x22Read Data By Identifier请求Bootloader必须能识别出这是“打断请求”并立即暂停擦除先响应0x22然后再继续擦除。这就要求状态机必须是可抢占的不能有长时间阻塞。我的做法是所有耗时操作Flash擦除、写入都拆分成小步每步执行后检查CAN RX FIFO是否有新帧到来。例如擦除一个Sector不是调用Flash_Erase_Sector()一个函数搞定而是写一个Flash_Erase_Sector_Step()函数每次只擦除一个Page2KB执行完立刻返回主状态机检查到有新CAN帧就先处理CAN下次循环再继续擦下一个Page。这样Bootloader的响应时间始终控制在1ms以内完全满足UDS时序要求。另外NRCNegative Response Code的返回必须精准。NRC 0x12子功能不支持和NRC 0x22条件不满足看起来相似但语义完全不同。0x22意味着“我现在不能做但稍后可能可以”而0x12意味着“这个子功能我压根不支持”。诊断仪会根据NRC类型决定是重试还是报错。所以代码里每个NRC的返回都要对应到UDS标准文档ISO 14229-1的明确定义不能凭感觉乱写。3.3 Flash驱动实现寄存器级操作才是TC275的唯一真相TC275的Flash操作没有捷径必须直面寄存器。英飞凌提供的LibIfxFlash库虽然封装了API但它是为AUTOSAR环境设计的严重依赖BSW调度器Bootloader里用不了。我们必须自己操作FCEFlash Controller Engine模块。核心寄存器就三个FCE_FCONFlash Control Register、FCE_FADRFlash Address Register、FCE_FDATFlash Data Register。擦除一个Sector的流程是1向FCE_FCON写入0x00000001启动擦除2向FCE_FADR写入目标Sector的起始地址注意TC275的Sector地址是按Bank对齐的Bank0的Sector0地址是0x800000003轮询FCE_FCON的BUSY位直到为04检查FCE_FCON的ERR位确认无错误。写入一个Word32位的流程是1向FCE_FCON写入0x00000002启动编程2向FCE_FADR写入目标地址3向FCE_FDAT写入32位数据4轮询BUSY位5检查ERR位。这里有两个致命细节。第一地址对齐。TC275的Flash编程必须是Word4字节对齐如果你试图往0x80000001地址写一个字节FCE会直接报ERR1地址错误。所以Bootloader接收UDS 0x36帧的数据时必须先把接收到的字节流按4字节对齐打包成Word数组再逐个写入。第二擦除前的写保护检查。TC275的每个Flash Bank都有独立的写保护寄存器FPROT如果某个Sector被设置了写保护FPROT.BIT.SECx 1那么对该Sector的任何擦除或写入操作都会失败并置位ERR位。而FPROT寄存器本身也是受保护的必须先向特定的Key寄存器FKEY写入解锁密钥0x0000C007才能修改FPROT。很多工程师在调试时发现擦除总是失败查了半天ERR位最后发现是FPROT把整个Bank都锁死了。实操技巧在Bootloader初始化阶段第一件事就是读取FPROT如果发现写保护已启用就执行一次“解锁”操作——向FKEY写0x0000C007再向FPROT写0x00000000全解锁然后再开始后续的Flash操作。这个步骤绝不能省略否则你的Bootloader永远无法刷写App。3.4 启动流程与App跳转为什么“n32h482从bootloader跳转到app后app无法触发中断”在TC275上会演变成灾难“App跳转后中断不触发”是跨平台的共性问题但在TC275上它被放大到了极致。原因在于TC275的中断向量表IVT不是固定在Flash起始地址而是可以重映射到任意RAM区域。App的中断向量表必须放在CCM RAM里地址0xF0000000开始并且Bootloader在跳转前必须把SCU寄存器SCU_BOOT.BIT.VTBAVector Table Base Address设置为这个CCM RAM的起始地址。否则CPU在发生中断时还是会去Flash的0x00000000处找向量表而那里是Bootloader的向量表结果就是中断服务程序ISR执行了Bootloader的代码而不是App的。这还不是全部。TC275有三个内核TC0、TC1、TC2每个内核都有自己的中断向量表和中断控制器ICU。如果你的App只运行在TC0上那么TC1和TC2的ICU必须被禁用否则它们可能会产生无效中断干扰TC0的正常运行。禁用方法是向SCU_ICUx.BIT.DIS寄存器写1。此外App的启动代码Startup Code必须重新初始化所有外设的时钟和复位状态。Bootloader在运行时可能已经配置了CAN、ADC等模块的时钟但App的初始化代码会再次配置如果两次配置不一致就会冲突。最稳妥的做法是在跳转前Bootloader执行一次“软复位”级别的清理——关闭所有使能的外设时钟通过SCU_CCUCONx寄存器将所有外设的复位寄存器RSTCONx置1再清0最后再跳转。跳转指令本身也有讲究。不能用简单的((void(*)(void))app_entry)();因为这不会切换内核上下文。必须使用TriCore特有的jump汇编指令并确保跳转前SP堆栈指针被设置为App的初始堆栈地址这个地址必须在App的链接脚本里定义好通常是CCM RAM的最高地址向下生长。我遇到过一个极端案例App的中断向量表放在了PSRAM里地址0xA0000000而不是CCM RAM。虽然PSRAM也能放代码但它的访问延迟比CCM RAM高一个数量级导致中断响应时间超过10us而TC275的CAN中断要求在5us内响应结果就是CAN接收中断丢失App收不到任何CAN帧。所以TC275上的App跳转不是“跳过去就行”而是一场涉及向量表重映射、内核状态清理、时钟复位、堆栈切换的精密手术。4. 实操过程与核心环节实现从零开始搭建一个可工作的TC275 UDS Bootloader4.1 开发环境搭建Tasking vs. HighTec谁是TC275的最优解TC275的官方推荐IDE是Tasking VX-toolset但它价格昂贵且对个人开发者不友好。HighTec GCC Toolchain是开源免费的替代方案但很多人抱怨它“编译出来的代码体积大、运行慢”。这个说法并不准确问题出在编译选项上。HighTec GCC默认使用-O0无优化生成的代码确实臃肿。但只要加上-O2 -mcputc275 -marchtricore:v1.6.2 -mhard-float -fno-common -ffunction-sections -fdata-sections这一串选项生成的代码体积和性能与Tasking编译的结果相差无几。关键在于链接脚本Linker Script的编写。TC275的内存空间非常碎片化CCM RAM192KB、PSRAM2MB、Flash Bank0/1/2共2.5MB、甚至还有OCDSOn-Chip Debug Support专用RAM。一个合格的Bootloader链接脚本必须精确划分这些区域。以下是我经过20个项目验证的Bootloader链接脚本核心片段MEMORY { CCM_RAM (rwx) : ORIGIN 0xF0000000, LENGTH 192K FLASH_BANK0 (rx) : ORIGIN 0x80000000, LENGTH 1M FLASH_BANK1 (rx) : ORIGIN 0x80100000, LENGTH 1M FLASH_BANK2 (rx) : ORIGIN 0x80200000, LENGTH 512K } SECTIONS { .text : { *(.text.startup) /* Startup code must be first */ *(.text) *(.rodata) } FLASH_BANK0 .data : { *(.data) *(.sdata) } CCM_RAM AT FLASH_BANK0 .bss : { *(.bss) *(.sbss) . ALIGN(4); __bss_start .; *(COMMON) __bss_end .; } CCM_RAM /* Application Header must be at fixed offset in each Bank */ .app_header : { . 0x1000; /* Offset 4KB from Bank start */ *(.app_header) } FLASH_BANK0 }这个脚本强制将启动代码.text.startup放在Flash Bank0的最开头确保Boot ROM能正确找到它将.data段加载到Flash Bank0但运行时复制到CCM RAM保证高速访问最关键的是.app_header段它被强制定位在Bank0的0x1000偏移处这就是Application Header的法定位置。没有这个精准的定位Header就无法被Boot ROM识别。开发环境的另一大坑是调试器连接。TC275支持JTAG和DAPDebug Access Port但很多廉价的J-Link V9固件版本太老不支持TC275的DAP协议连接时提示“Unknown device”。必须升级J-Link固件到V7.80或更高版本并在调试配置里明确选择“Infineon AURIX TC275”作为目标设备。实测下来J-Link Plus的稳定性远超ULINK尤其是在进行Flash编程时ULINK经常出现“Programming failed at address 0x80001000”的错误换J-Link Plus后问题消失。4.2 UDS服务核心代码实现0x10、0x22、0x27、0x31、0x34、0x36、0x37服务详解下面给出一个高度精简但完全可工作的UDS服务核心框架所有代码均基于HighTec GCC可直接编译运行// uds_main.c #include uds_services.h #include can_transport.h #include flash_driver.h #define UDS_SESSION_DEFAULT 0x01 #define UDS_SESSION_PROGRAMMING 0x02 typedef struct { uint8_t session; // 当前会话状态 uint32_t download_addr; // 下载地址 uint32_t download_size; // 下载总大小 uint32_t rx_count; // 已接收字节数 } UdsContext_t; static UdsContext_t g_uds_ctx {0}; void Uds_MainLoop(void) { CanFrame_t frame; if (Can_Receive(frame)) { // 从CAN RX FIFO读取一帧 if (frame.id 0x7DF frame.dlc 2) { // 诊断请求帧 uint8_t sid frame.data[0]; switch(sid) { case 0x10: // Diagnostic Session Control Uds_Service_10(frame); break; case 0x22: // Read Data By Identifier Uds_Service_22(frame); break; case 0x27: // Security Access Uds_Service_27(frame); break; case 0x31: // Routine Control Uds_Service_31(frame); break; case 0x34: // Request Download Uds_Service_34(frame); break; case 0x36: // Transfer Data Uds_Service_36(frame); break; case 0x37: // Request Transfer Exit Uds_Service_37(frame); break; default: Uds_SendNrc(0x7F, sid, 0x11); // 服务不支持 break; } } } } // uds_services.c void Uds_Service_10(CanFrame_t* req) { if (req-dlc 2) { Uds_SendNrc(0x7F, 0x10, 0x13); // incorrect message length return; } uint8_t sub_func req-data[1]; if (sub_func 0x01) { // Default Session g_uds_ctx.session UDS_SESSION_DEFAULT; Uds_SendPositiveResponse(0x10, 0x01, 0x00, 0x00); } else if (sub_func 0x02) { // Programming Session // 必须先执行Security Access (0x27) 才能进入编程会话 if (g_uds_ctx.security_level 0x03) { // 假设Level 3是最高权限 g_uds_ctx.session UDS_SESSION_PROGRAMMING; Uds_SendPositiveResponse(0x10, 0x02, 0x00, 0x00); } else { Uds_SendNrc(0x7F, 0x10, 0x33); // securityAccessDenied } } else { Uds_SendNrc(0x7F, 0x10, 0x12); // sub-function not supported } } void Uds_Service_22(CanFrame_t* req) { if (req-dlc 3) { Uds_SendNrc(0x7F, 0x22, 0x13); return; } uint16_t did (req-data[1] 8) | req-data[2]; switch(did) { case 0xF190: // ECU Manufacturer ID Uds_SendPositiveResponse(0x22, 0xF1, 0x90, I, N, F, I, N, E, O, N); break; default: Uds_SendNrc(0x7F, 0x22, 0x31); // requestOutOfRange break; } } void Uds_Service_27(CanFrame_t* req) { if (req-dlc 2) { Uds_SendNrc(0x7F, 0x27, 0x13); return; } uint8_t sub_func req-data[1]; if ((sub_func 0xF0) 0x00) { // Request Seed uint32_t seed 0x12345678; // 简化实际应为真随机数 g_uds_ctx.seed seed; Uds_SendPositiveResponse(0x27, sub_func, (seed 24) 0xFF, (seed 16) 0xFF, (seed 8) 0xFF, seed 0xFF); } else if ((sub_func 0xF0) 0x01) { // Send Key if (req-dlc 6) { Uds_SendNrc(0x7F, 0x27, 0x13); return; } uint32_t key (req-data[2] 24) | (req-data[3] 16) | (req-data[4] 8) | req-data[5]; if (key (g_uds_ctx.seed ^ 0xDEADBEEF)) { // 简化的XOR算法 g_uds_ctx.security_level 0x03; Uds_SendPositiveResponse(0x27, sub_func); } else { Uds_SendNrc(0x7F, 0x27, 0x33); // invalidKey } } else { Uds_SendNrc(0x7F, 0x27, 0x12); // sub-function not supported } } void Uds_Service_34(CanFrame_t* req) { if (g_uds_ctx.session ! UDS_SESSION_PROGRAMMING) { Uds_SendNrc(0x7F, 0x34, 0x7F); // serviceNotSupportedInActiveSession return; } if (req-dlc 8) { Uds_SendNrc(0x7F, 0x34, 0x13); return; } // 解析地址和长度此处简化实际需按UDS标准解析 g_uds_ctx.download_addr (req-data[3] 24) | (req-data[4] 16) | (req-data[5] 8) | req-data[6]; g_uds_ctx.download_size (req-data[7] 24) | (req-data[8] 16) | (req-data[9] 8) | req-data[10]; g_uds_ctx.rx_count 0; // 准备Flash擦除 Flash_Erase_Sector_ByAddr(g_uds_ctx.download_addr); Uds_SendPositiveResponse(0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); } void Uds_Service_36(CanFrame_t* req) { if (g_uds_ctx.session ! UDS_SESSION_PROGRAMMING) { Uds_SendNrc(0x7F, 0x36, 0x7F); return; } uint32_t data_len req-dlc - 2; // 排除SID和Block Sequence Counter uint8_t* data_ptr req-data[2]; // 将data_ptr指向的数据按Word对齐写入download_addr rx_count for (uint32_t i 0; i data_len; i 4) { uint32_t word 0; if (i 3 data_len) { word (data_ptr[i] 24) | (data_ptr[i1] 16) | (data_ptr[i2] 8) | data_ptr[i3]; } Flash_Write_Word(g_uds_ctx.download_addr g_uds_ctx.rx_count i, word); } g_uds_ctx.rx_count data_len; Uds_SendPositiveResponse(0x36, req-data[1]); // 回传Block Sequence Counter } void