TC275 UDS Bootloader开发实战:突破三核安全MCU固件升级瓶颈
2026/9/14 14:09:53 网站建设 项目流程

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却分成了多个物理Bank(Bank0、Bank1、Bank2),每个Bank内部又细分为Sector,且擦除操作必须通过专用的Flash Controller(FCE)模块,走的是寄存器映射+状态轮询的路径,不是简单的函数调用。更关键的是启动流程:STM32复位后从0x08000000开始取指令,Bootloader放在起始地址,靠跳转地址就能切到App;TC275复位后首先进入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.c(0x10/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还是单Bank?AB分区在TC275上如何真正落地?

“Bootloader双分区AB分区”是热搜词里高频出现的概念,但很多人没意识到,TC275的Flash物理结构根本不支持传统意义上的“AB分区”。它的Flash Bank是固定的物理单元,Bank0(1MB)、Bank1(1MB)、Bank2(512KB),不能像eMMC那样动态划分逻辑分区。所谓AB分区,在TC275上,本质是“双Bank镜像”策略:App代码同时烧录到Bank0和Bank1,Bootloader在启动时,先读取两个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 Page(TC275 Page大小为2KB),并且这个Page必须位于每个Bank的固定偏移地址(例如Bank0的0x80000000 + 0x1000处)。Bootloader在刷写App前,先擦除这个Header Page,再写入新的Header,最后才开始写App代码。第二个陷阱是跳转前的Cache清理。TC275有强大的L1 Cache(Instruction & Data),如果App代码刚写入Flash,Cache里还存着旧的指令,直接跳转过去,CPU会执行Cache里的脏数据,结果就是App跑飞。必须在跳转前,执行完整的ICache Invalidate(指令缓存失效)和DCache Clean & Invalidate(数据缓存清理并失效)操作,对应汇编指令是pshcupswcu。我见过最离谱的案例:一个客户项目,AB分区逻辑完全正确,但每次从Bootloader跳转到新刷写的App,App都只运行几条指令就死机。查了三天,最后发现是忘了在跳转前调用__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 0x7DF,11位标准帧)。但仅仅这样还不够。问题出在中断上。TC275有3个独立的中断控制器(ICU),每个CAN节点(Node)的中断信号会路由到不同的ICU输入引脚。如果你把CAN0的RX中断配置到了ICU0,而Bootloader的全局中断使能(Global Interrupt Enable)又是在ICU1里配置的,那RX中断永远触发不了。必须确保:1)CAN Node的中断使能位(CAN_NCRx.BIT.NIE)置1;2)该Node的中断信号被正确路由到某个ICU输入(通过SCU_ICUx寄存器配置);3)对应的ICU输入通道被使能(ICU_GCR.BIT.EN = 1);4)该ICU通道的中断优先级(ICU_INxPR.BIT.PRIO)被设为足够高(建议≥8,避免被其他高优先级中断抢占);5)最后,全局中断开关(PSW.IE)必须打开。我曾经调试一个项目,CAN示波器显示诊断仪发出了0x10 03帧,但Bootloader的RX中断服务程序(ISR)就是不进。用调试器单步跟踪,发现程序卡在while(!CAN_RX_FLAG)的死循环里。最后查寄存器,发现ICU_IN0PR的PRIO位被误设为0,而TC275规定PRIO=0表示中断被屏蔽,等于白配置。另一个常见问题是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判断SID(Service ID),结果就是逻辑混乱,状态丢失。TC275的UDS Bootloader,必须采用分层状态机设计。顶层是“会话状态”(Session State):Default Session(默认会话)、Programming Session(编程会话)、Extended Diagnostic Session(扩展会话)。底层是“子状态”(Sub-State),例如在Programming Session下,又有“Request Download”、“Transfer Data”、“Request Transfer Exit”等子状态。关键点在于状态迁移的触发条件和超时处理。以0x31 01 FF(Routine Control, Check Programming Precondition)为例,它必须在Programming Session下才能执行。如果诊断仪在Default Session下就发0x31,Bootloader必须返回NRC 0x7F(服务不支持),而不是静默忽略。但很多代码只检查SID,不检查当前会话状态,导致诊断仪以为服务已激活,后续发0x34(Request Download)时,Bootloader却因状态不匹配而返回NRC 0x22(条件不满足),整个流程中断。更隐蔽的坑是超时。UDS协议规定,从收到请求帧到发出响应帧,最大允许时间是50ms(对于常规服务)。如果Bootloader在处理0x34时,需要擦除一个Flash Sector(TC275擦除一个Sector约20ms),那么在擦除过程中,如果诊断仪又发来一个0x22(Read Data By Identifier)请求,Bootloader必须能识别出这是“打断请求”,并立即暂停擦除,先响应0x22,然后再继续擦除。这就要求状态机必须是可抢占的,不能有长时间阻塞。我的做法是:所有耗时操作(Flash擦除、写入)都拆分成小步,每步执行后检查CAN RX FIFO是否有新帧到来。例如,擦除一个Sector,不是调用Flash_Erase_Sector()一个函数搞定,而是写一个Flash_Erase_Sector_Step()函数,每次只擦除一个Page(2KB),执行完立刻返回,主状态机检查到有新CAN帧,就先处理CAN,下次循环再继续擦下一个Page。这样,Bootloader的响应时间始终控制在1ms以内,完全满足UDS时序要求。另外,NRC(Negative 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里用不了。我们必须自己操作FCE(Flash Controller Engine)模块。核心寄存器就三个:FCE_FCON(Flash Control Register)、FCE_FADR(Flash Address Register)、FCE_FDAT(Flash Data Register)。擦除一个Sector的流程是:1)向FCE_FCON写入0x00000001(启动擦除);2)向FCE_FADR写入目标Sector的起始地址(注意,TC275的Sector地址是按Bank对齐的,Bank0的Sector0地址是0x80000000);3)轮询FCE_FCON的BUSY位,直到为0;4)检查FCE_FCON的ERR位,确认无错误。写入一个Word(32位)的流程是:1)向FCE_FCON写入0x00000002(启动编程);2)向FCE_FADR写入目标地址;3)向FCE_FDAT写入32位数据;4)轮询BUSY位;5)检查ERR位。这里有两个致命细节。第一,地址对齐。TC275的Flash编程必须是Word(4字节)对齐,如果你试图往0x80000001地址写一个字节,FCE会直接报ERR=1(地址错误)。所以,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.VTBA(Vector 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 Bootloader

4.1 开发环境搭建:Tasking vs. HighTec,谁是TC275的最优解?

TC275的官方推荐IDE是Tasking VX-toolset,但它价格昂贵,且对个人开发者不友好。HighTec GCC Toolchain是开源免费的替代方案,但很多人抱怨它“编译出来的代码体积大、运行慢”。这个说法并不准确,问题出在编译选项上。HighTec GCC默认使用-O0(无优化),生成的代码确实臃肿。但只要加上-O2 -mcpu=tc275 -march=tricore:v1.6.2 -mhard-float -fno-common -ffunction-sections -fdata-sections这一串选项,生成的代码体积和性能,与Tasking编译的结果相差无几。关键在于链接脚本(Linker Script)的编写。TC275的内存空间非常碎片化:CCM RAM(192KB)、PSRAM(2MB)、Flash Bank0/1/2(共2.5MB)、甚至还有OCDS(On-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和DAP(Debug 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[i+1] << 16) | (data_ptr[i+2] << 8) | data_ptr[i+3]; } 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询