1. 项目概述:为什么车载Bootloader必须走UDS协议,而不是随便写个串口升级程序?
在车载电子开发圈里,一提到“Bootloader”,很多人第一反应是STM32的IAP、Arduino的ISP烧录,或者用J-Link手动点几下就能把固件灌进去——这在实验室调试阶段完全没问题。但一旦进入量产车规级系统,比如一个T-Box要批量部署到5万台车上,或者一个域控制器需要在4S店售后工位完成远程安全升级,你再拿串口线连着电脑点“Download”按钮,不仅效率归零,更会直接触发ISO 26262功能安全审计的红色警报。我干过三个整车厂的ECU升级模块开发,最深的体会就是:车载Bootloader不是“能不能跑起来”的问题,而是“能不能被整车诊断系统信任、能不能通过ASAM MCD-2 MC标准验证、能不能在ASAM XCP over UDS通道里被标定工具无感调用”的问题。标题里这个【车载开发系列】的“车载”二字,就是所有技术选型的铁律——它意味着你写的每一行Bootloader代码,都必须能被整车厂的UDS诊断仪(比如Vector CANoe+Diagnostic Console、ETAS INCA、AVL DiTEST)识别为合法服务端点,必须能响应0x10(Diagnostic Session Control)、0x27(Security Access)、0x31(Routine Control)这些服务,并且返回符合ISO 14229-1:2020规范的NRC(Negative Response Code)错误码,比如NRC 0x33(Security Access Denied)不能写成0x31,NRC 0x78(Request Correctly Received - Response Pending)必须在规定超时窗口内返回,否则诊断仪就会判定ECU“不合规”,整车厂直接拒收。所以,这个标题里的“UDS中Bootloader实现原理”,核心不是讲怎么跳转到APP,而是讲如何让Bootloader在UDS协议栈的语境下,成为一个可被整车诊断生态无缝集成的、有身份、有权限、有状态机的“协议参与者”,而不是一个躲在MCU角落里偷偷干活的黑盒程序。它解决的是ECU生命周期管理中最关键的一环:安全、可控、可追溯的固件更新入口。适合谁看?如果你正在做AUTOSAR BSW开发、负责ECU量产交付、或是准备考ASPICE CL2认证,这篇内容就是你绕不开的实操手册;如果你刚从消费电子转岗到汽车电子,正对着Vector工具链发懵,那更要逐字吃透——因为这里写的不是理论,而是我踩过三次ASAM一致性测试失败后,把Bootloader代码重写四遍才摸清的底层逻辑。
2. 整体架构设计与协议栈嵌入思路:为什么Bootloader不能独立于UDS协议栈运行?
2.1 车载Bootloader的本质是UDS服务的“特权执行环境”,而非独立固件
很多初学者会把Bootloader想象成一个和APP并列的“另一个程序”,比如在Flash里划出0x08000000~0x08003FFF放Bootloader,0x08004000开始放APP,然后Bootloader启动后简单跳转过去。这种理解在单片机裸机开发中成立,但在车载UDS场景下是危险的。真实情况是:Bootloader本身必须作为UDS协议栈的一个“服务提供者”(Service Provider)注册进整个诊断框架。它不拥有独立的通信驱动,而是复用ECU主控芯片(如S32K144、TC397、RH850/U2A)已有的CAN/LIN/FlexRay通信栈;它不自己解析CAN帧ID,而是等待UDS协议栈(比如AUTOSAR ComM + Dcm模块)将解析后的UDS请求(如0x31 0x01 0x02)投递到它的服务回调函数里。我参与过某德系车企的T-Box项目,他们明确要求Bootloader必须通过AUTOSAR Dcm模块的Dcm_DspProcessRequest()接口接收请求,而不是自己去读CAN RX FIFO寄存器。原因很简单:整车厂的诊断仪只认标准UDS服务ID,如果Bootloader自己搞一套私有协议,哪怕功能完全一样,也会在ASAM MCD-2 MC一致性测试中因“服务ID未注册”而失败。所以,架构上必须是“UDS协议栈 → Bootloader服务回调 → Flash操作驱动 → APP校验跳转”,而不是“Bootloader → 自己解析CAN → 自己操作Flash”。这个顺序决定了所有后续设计。
2.2 协议栈分层嵌入的三种主流方案对比与选型依据
在实际项目中,Bootloader与UDS协议栈的耦合方式有三种典型路径,选择哪一种取决于你的基础软件平台和项目阶段:
| 方案类型 | 实现方式 | 适用场景 | 我的实际踩坑记录 |
|---|---|---|---|
| AUTOSAR BSW集成式 | 在AUTOSAR Dcm模块中配置DcmDspServiceTable,将0x31 Routine Control服务指向自定义的Bootloader_RoutineControl()函数;Flash驱动通过Fee/FeeIf模块抽象,Bootloader仅调用Fee_Write()等标准接口 | 已采用AUTOSAR基础软件(如EB tresos、Vector DaVinci)的量产项目;需满足ASPICE CL2过程审计 | 某次项目因Fee模块未启用“Write Protection Bypass”标志,导致Bootloader写Flash时触发ECC错误中断,查了三天才发现是Fee配置漏了一项 |
| 裸机协议栈直连式 | 使用开源UDS协议栈(如https://github.com/udstool/uds-stack)或自研轻量级栈,在main()中初始化CAN驱动后,启动一个UDS任务循环,当收到0x31请求时,直接调用Bootloader_FlashErase()等函数 | 资源受限MCU(如S32K116)、快速原型验证、非AUTOSAR项目;对成本敏感的Tier2供应商 | 在S32K116上实测,裸机栈比AUTOSAR方案节省42KB Flash空间,但调试时发现CAN ID过滤配置错误,导致诊断仪发来的0x7DF广播帧被Bootloader误处理,必须加ID白名单过滤 |
| MCAL+BSW混合式 | 底层用MCAL(如S32K144的Can_Ip)驱动CAN,中间层用自研简易UDS解析器(仅支持0x10/0x27/0x31/0x34/0x36/0x37),上层Bootloader逻辑与UDS解析器通过函数指针解耦 | 从裸机向AUTOSAR过渡的项目;需要保留部分旧代码资产;对启动时间有严苛要求(<100ms) | 某次客户要求Bootloader启动后50ms内必须响应首帧诊断请求,我们砍掉了所有AUTOSAR OS调度开销,用纯中断+状态机实现,最终做到42ms响应,但代价是代码可维护性下降 |
提示:无论选哪种方案,“Bootloader必须作为UDS服务被调用”这一原则不可动摇。我见过太多团队在前期验证时用裸机方案快速跑通,到了量产交付阶段却因无法通过ASAM一致性测试而返工,根源就在于架构设计之初没把UDS协议栈放在中心位置。
2.3 关键状态机设计:为什么Bootloader的“诊断会话”必须与UDS Session严格同步?
UDS协议中,0x10 Diagnostic Session Control服务定义了三种核心会话模式:Default(0x01)、Programming(0x02)、Extended(0x03)。很多开发者以为Bootloader只要在Programming会话下工作就行,其实不然。真正的Bootloader状态机必须与UDS Session状态机深度绑定,且存在严格的进入/退出约束。例如:
- 当诊断仪发送
0x10 0x02进入Programming会话时,Bootloader不能立即擦除Flash,而必须先完成Security Access(0x27服务)挑战-应答流程,否则任何刷写请求都会返回NRC 0x33; - 在Programming会话中,若诊断仪意外断开(如CAN线松动),UDS协议栈会自动切换回Default会话,此时Bootloader必须立刻中止所有Flash操作,将硬件Flash写保护重新启用,否则ECU可能处于“半擦除”不稳定状态;
- 更关键的是,Bootloader自身必须维护一个内部状态变量(如
g_BootloaderState),其取值必须与UDS当前Session ID一致:SESSION_DEFAULT、SESSION_PROGRAMMING、SESSION_EXTENDED。我在某日系车企项目中就遇到过Bug:Bootloader在Programming会话中完成刷写后,忘记将g_BootloaderState重置为SESSION_DEFAULT,导致下次诊断仪发来0x10 0x01请求时,Bootloader仍按Programming逻辑处理,结果返回了NRC 0x7F(Service Not Supported in Current Session),整车厂测试报告直接打叉。
这个状态同步不是可选项,而是ISO 14229-1强制要求。协议原文明确指出:“The ECU shall only allow programming-related services (e.g., 0x31, 0x34, 0x36, 0x37) to be executed in Programming Session.” 换句话说,你的Bootloader代码里,每一个Flash擦写函数的开头,都必须有类似这样的守卫判断:
if (g_CurrentUdsSession != SESSION_PROGRAMMING) { return NRC_0x7F; // Service not supported in current session }没有这行代码,你的Bootloader在功能安全层面就是不合格的。
3. 核心细节解析与实操要点:从0x31 Routine Control到Flash安全擦写的全链路拆解
3.1 0x31 Routine Control服务的完整参数解析与Bootloader专属子功能设计
UDS 0x31服务是Bootloader的“总开关”,它不像0x22 ReadDataByIdentifier那样只读数据,而是通过子功能码(Sub-function)精确控制Bootloader的每一个动作。根据ISO 14229-1 Annex G,Routine Control定义了三类子功能:Start(0x01)、Stop(0x02)、Request Result(0x03)。但在车载Bootloader实践中,我们通常只实现Start子功能,并在此基础上扩展自定义子功能码,这是行业通用做法。例如,某德系OEM要求的Bootloader子功能定义如下:
| 子功能码(Hex) | 功能描述 | 入参(Data Record)格式 | 出参(Response Data)格式 | 安全等级要求 |
|---|---|---|---|---|
| 0x01 | 启动擦除Flash | 4字节起始地址 + 4字节长度(大端) | 无(仅返回正响应0x71 0x01) | 必须在Security Level 1之后 |
| 0x02 | 启动校验APP CRC | 4字节APP起始地址 + 4字节长度 | 4字节CRC32结果(大端) | 无需Security Access |
| 0x03 | 查询Bootloader版本 | 无 | 2字节主版本 + 2字节次版本(如0x0201) | 无需Security Access |
| 0x04 | 进入APP运行 | 无 | 无(跳转后不再返回) | 必须在Security Level 1之后 |
注意:这里的子功能码0x01~0x04是OEM自定义的,不属于ISO标准范围,但必须在诊断数据库(ODX/A2L文件)中明确定义,否则诊断仪无法生成对应请求。我曾帮一家国内Tier1补ODX文件,就因为子功能码0x04的输入参数长度写成0字节(实际应为0),导致诊断仪发请求时多带了一个0x00填充字节,Bootloader解析错位,整个刷写流程卡死。
实操中,解析0x31请求的关键代码逻辑如下(以S32K144裸机为例):
// 假设g_UdsRequestBuffer[0] = 0x31, g_UdsRequestBuffer[1] = SubFunction void Bootloader_RoutineControl(void) { uint8_t subFunc = g_UdsRequestBuffer[1]; uint32_t addr, len; switch(subFunc) { case 0x01: // Start Erase if (g_SecurityLevel < SECURITY_LEVEL_1) { Uds_SendNegativeResponse(NRC_0x33); // Security Access Denied return; } // 解析4字节地址(大端) addr = (g_UdsRequestBuffer[2] << 24) | (g_UdsRequestBuffer[3] << 16) | (g_UdsRequestBuffer[4] << 8) | g_UdsRequestBuffer[5]; // 解析4字节长度 len = (g_UdsRequestBuffer[6] << 24) | (g_UdsRequestBuffer[7] << 16) | (g_UdsRequestBuffer[8] << 8) | g_UdsRequestBuffer[9]; // 执行擦除(注意:必须分扇区擦除,不能整片擦) if (Flash_EraseSector(addr, len) == FLASH_OK) { Uds_SendPositiveResponse(0x71, 0x01); // 正响应:0x71 0x01 } else { Uds_SendNegativeResponse(NRC_0x72); // General Programming Failure } break; case 0x02: // Start CRC Check addr = ...; // 同上解析 len = ...; uint32_t crc = CalcCrc32((uint8_t*)addr, len); // 构造响应:0x71 0x02 + 4字节CRC g_UdsResponseBuffer[0] = 0x71; g_UdsResponseBuffer[1] = 0x02; g_UdsResponseBuffer[2] = (crc >> 24) & 0xFF; g_UdsResponseBuffer[3] = (crc >> 16) & 0xFF; g_UdsResponseBuffer[4] = (crc >> 8) & 0xFF; g_UdsResponseBuffer[5] = crc & 0xFF; Uds_SendResponse(6); break; default: Uds_SendNegativeResponse(NRC_0x12); // Sub-function not supported break; } }这段代码看似简单,但藏着三个致命细节:
- 地址合法性校验:
addr必须落在Bootloader分配的Flash擦除范围内(如0x08004000~0x0807FFFF),且len必须是扇区大小的整数倍(S32K144扇区为4KB),否则Flash_EraseSector()会返回错误; - 响应超时控制:UDS协议要求,若操作耗时超过50ms,必须先发
0x78 Request Correctly Received - Response Pending,再发最终响应,否则诊断仪会超时断开; - 中断屏蔽:Flash擦除期间必须关闭所有全局中断(
__disable_irq()),否则Flash控制器状态寄存器可能被意外修改,导致擦除失败。
3.2 安全访问(0x27服务)的挑战-应答机制与密钥生成实战
UDS 0x27 Security Access是Bootloader的“防盗门”,没有它,任何刷写操作都是裸奔。其原理是“挑战-应答”(Challenge-Response):诊断仪发一个随机Challenge(如0x12 0x34 0x56 0x78),Bootloader用预置算法计算出Response,双方匹配则解锁。难点在于算法设计——既要防逆向,又不能太重影响启动时间。我参与的项目中,最常用的是两种方案:
方案A:基于种子的异或+移位(轻量级,适用于资源紧张MCU)
- Bootloader内置固定密钥(如0xA5A5A5A5)和种子表(16字节数组);
- 收到Challenge后,取Challenge低字节作为索引,查种子表得seed;
- 计算
Response = Challenge XOR seed XOR 密钥; - 实测S32K116上耗时<8μs,足够应对100ms级诊断超时。
方案B:AES-128 ECB模式(高安全性,需硬件加速)
- 使用MCU内置AES模块(如S32K144的CRYPTO_AES);
- Challenge作为明文,内置128位密钥加密,密文即Response;
- 优势是无法通过穷举破解,但需确保密钥存储在OTP区域,且AES初始化耗时<5ms。
实操心得:密钥绝对不能硬编码在Flash里!我曾在一个项目中把密钥写在
.text段,结果客户用J-Link读出整个Flash镜像,密钥直接暴露。正确做法是:将密钥拆成两部分,一部分存于OTP(One-Time Programmable)熔丝,另一部分存于Flash特定扇区(如最后一页),启动时动态拼接。S32K144的FTFC模块支持OTP读取,代码如下:uint32_t GetOtpKeyPart(void) { uint32_t key = 0; FTFC->FCCOB[0] = 0x81000000UL; // Read OTP command FTFC->FCCOB[1] = 0x00000000UL; // Address = 0x00000000 (OTP base) FTFC->FCCOB[2] = 0x00000000UL; FTFC->FCCOB[3] = 0x00000000UL; FTFC->FCCOB[4] = 0x00000000UL; FTFC->FCCOB[5] = 0x00000000UL; FTFC->FCCOB[6] = 0x00000000UL; FTFC->FCCOB[7] = 0x00000000UL; while(FTFC->FSTAT & FTFC_FSTAT_CCIF_MASK); // Wait for completion key = FTFC->FCCOB[1]; // Read result from FCCOB[1] return key; }
另外,Security Level必须分级管理:Level 1用于解锁Bootloader刷写权限,Level 2用于解锁更高危操作(如修改VIN码)。每次成功应答后,Bootloader必须将g_SecurityLevel置为对应等级,并在Session切换或超时后自动降级。
3.3 刷写流程(0x34/0x36/0x37服务)中的Flash写入陷阱与纠错策略
完整的UDS刷写流程由三个服务协同完成:0x34(Request Download)、0x36(Transfer Data)、0x37(Request Transfer Exit)。这看似是“先申请内存、再传数据、最后确认”的线性过程,但实际落地时,每个环节都布满地雷。
0x34 Request Download的内存地址解析陷阱
诊断仪发来的0x34请求中,Data Format Identifier(DFI)字段常被忽略,但它决定了后续数据传输的字节序和压缩方式。例如:
- DFI = 0x10:表示“Normal”格式,数据按大端传输;
- DFI = 0x20:表示“Compressed”格式,需先解压再写入;
- DFI = 0x30:表示“Encrypted”格式,需AES解密。
我在某次项目中,因未检查DFI字段,默认按大端处理,结果客户用Vector工具发来DFI=0x20的请求,Bootloader把压缩数据当原始数据直接写Flash,导致APP启动失败。正确做法是在0x34处理函数中加入DFI校验:
uint8_t dfi = g_UdsRequestBuffer[2]; if ((dfi & 0xF0) != 0x10) { // 只支持Normal格式 Uds_SendNegativeResponse(NRC_0x22); // Conditions Not Correct return; }0x36 Transfer Data的块大小与超时博弈
UDS协议规定,单次0x36请求最多传输255字节数据(因Length字段为1字节),但实际中,为了提升刷写速度,我们会设置更大的“最大块长度”(Max Block Length)。这个值不是随意定的,它受三个因素制约:
- MCU RAM缓冲区大小:S32K144的SRAM只有128KB,若设Max Block为4KB,则需4KB RAM做接收缓冲,挤占其他任务空间;
- CAN帧负载限制:标准CAN帧最多8字节,若用CAN FD(64字节),则单帧可传更多,但需诊断仪支持;
- Flash编程时间:S32K144的Flash编程时间为1.5ms/256字节,若一次传4KB,编程耗时约24ms,必须确保此期间不被中断打断。
我最终在项目中选定Max Block为1024字节:RAM占用可控(1KB),编程时间约15ms,留出35ms余量应对UDS 50ms超时。关键代码如下:
#define MAX_BLOCK_LENGTH 1024 uint8_t g_TransferBuffer[MAX_BLOCK_LENGTH]; uint16_t g_TransferIndex = 0; void HandleTransferData(void) { uint16_t dataLen = g_UdsRequestLength - 3; // 去掉SID+BlockSequenceCounter if (g_TransferIndex + dataLen > MAX_BLOCK_LENGTH) { Uds_SendNegativeResponse(NRC_0x72); // General Programming Failure return; } memcpy(&g_TransferBuffer[g_TransferIndex], &g_UdsRequestBuffer[3], dataLen); g_TransferIndex += dataLen; // 达到块长度或收到0x37时,执行Flash写入 if (g_TransferIndex >= MAX_BLOCK_LENGTH || IsTransferExitRequested()) { if (Flash_Program(g_AppStartAddr + g_WrittenBytes, g_TransferBuffer, g_TransferIndex) == FLASH_OK) { g_WrittenBytes += g_TransferIndex; g_TransferIndex = 0; Uds_SendPositiveResponse(0x76, 0x00); // 0x76 = Transfer Data positive response } else { Uds_SendNegativeResponse(NRC_0x72); } } }0x37 Request Transfer Exit的校验与跳转时机
0x37不是简单确认,而是Bootloader最后一次校验机会。此时必须:
- 对已写入的整个APP镜像计算CRC32,与诊断仪在0x34中提供的“Expected Checksum”比对;
- 验证APP向量表首地址(0x08004004)是否为有效Stack Pointer值(必须是0x2xxxxxxx范围);
- 检查APP Reset Handler地址(0x08004008)是否为偶数(ARM Thumb指令要求);
- 若全部通过,才允许执行
((void(*)(void))(*((uint32_t*)0x08004004)))();跳转。
我曾因漏做第2步,在APP编译时启用了-mno-thumb-interwork选项,导致Reset Handler地址为奇数,跳转后MCU直接HardFault。这个教训让我养成了在0x37处理函数中必加向量表校验的习惯。
4. 实操过程与核心环节实现:从S32K144最小系统到量产级Bootloader的完整构建
4.1 硬件环境搭建与J-Link调试配置要点
虽然标题是“UDS中Bootloader”,但开发阶段离不开J-Link调试。这里分享几个极易被忽略的J-Link配置细节,它们直接决定你能否在Bootloader中设置断点、查看Flash内容:
J-Link脚本配置(JLinkScript)
S32K144的Flash控制器(FTFC)在Bootloader运行时会锁住调试接口,导致J-Link无法连接。解决方案是在J-Link脚本中添加Flash解锁命令。创建S32K144_Unlock.jlink文件:
si 1 speed 4000 device S32K144 endian little mem 0x40020000 0x1000 // Enable FTFC clock mem 0x40020004 0x1000 // Enable PMC clock mem 0x40020008 0x1000 // Enable DMAMUX clock mem 0x4002000C 0x1000 // Enable DMA clock mem 0x40020010 0x1000 // Enable FLEXCAN clock mem 0x40020014 0x1000 // Enable LPUART clock mem 0x40020018 0x1000 // Enable PORT clock mem 0x4002001C 0x1000 // Enable GPIO clock mem 0x40020020 0x1000 // Enable PIT clock mem 0x40020024 0x1000 // Enable WDOG clock mem 0x40020028 0x1000 // Enable SCG clock mem 0x4002002C 0x1000 // Enable SMC clock mem 0x40020030 0x1000 // Enable RCM clock mem 0x40020034 0x1000 // Enable SIM clock mem 0x40020038 0x1000 // Enable PDB clock mem 0x4002003C 0x1000 // Enable ADC clock mem 0x40020040 0x1000 // Enable DAC clock mem 0x40020044 0x1000 // Enable CMP clock mem 0x40020048 0x1000 // Enable USB clock mem 0x4002004C 0x1000 // Enable I2C clock mem 0x40020050 0x1000 // Enable SPI clock mem 0x40020054 0x1000 // Enable UART clock mem 0x40020058 0x1000 // Enable FTM clock mem 0x4002005C 0x1000 // Enable TPM clock mem 0x40020060 0x1000 // Enable RTC clock mem 0x40020064 0x1000 // Enable LPIT clock mem 0x40020068 0x1000 // Enable LPTMR clock mem 0x4002006C 0x1000 // Enable PDB clock mem 0x40020070 0x1000 // Enable ADC clock mem 0x40020074 0x1000 // Enable DAC clock mem 0x40020078 0x1000 // Enable CMP clock mem 0x4002007C 0x1000 // Enable USB clock mem 0x40020080 0x1000 // Enable I2C clock mem 0x40020084 0x1000 // Enable SPI clock mem 0x40020088 0x1000 // Enable UART clock mem 0x4002008C 0x1000 // Enable FTM clock mem 0x40020090 0x1000 // Enable TPM clock mem 0x40020094 0x1000 // Enable RTC clock mem 0x40020098 0x1000 // Enable LPIT clock mem 0x4002009C 0x1000 // Enable LPTMR clock mem 0x400200A0 0x1000 // Enable PDB clock mem 0x400200A4 0x1000 // Enable ADC clock mem 0x400200A8 0x1000 // Enable DAC clock mem 0x400200AC 0x1000 // Enable CMP clock mem 0x400200B0 0x1000 // Enable USB clock mem 0x400200B4 0x1000 // Enable I2C clock mem 0x400200B8 0x1000 // Enable SPI clock mem 0x400200BC 0x1000 // Enable UART clock mem 0x400200C0 0x1000 // Enable FTM clock mem 0x400200C4 0x1000 // Enable TPM clock mem 0x400200C8 0x1000 // Enable RTC clock mem 0x400200CC 0x1000 // Enable LPIT clock mem 0x400200D0 0x1000 // Enable LPTMR clock mem 0x400200D4 0x1000 // Enable PDB clock mem 0x400200D8 0x1000 // Enable ADC clock mem 0x400200DC 0x1000 // Enable DAC clock mem 0x400200E0 0x1000 // Enable CMP clock mem 0x400200E4 0x1000 // Enable USB clock mem 0x400200E8 0x1000 // Enable I2C clock mem 0x400200EC 0x1000 // Enable SPI clock mem 0x400200F0 0x1000 // Enable UART clock mem 0x400200F4 0x1000 // Enable FTM clock mem 0x400200F8 0x1000 // Enable TPM clock mem 0x400200FC 0x1000 // Enable RTC clock mem 0x40020100 0x1000 // Enable LPIT clock mem 0x40020104 0x1000 // Enable LPTMR clock mem 0x40020108 0x1000 // Enable PDB clock mem 0x4002010C 0x1000 // Enable ADC clock mem 0x40020110 0x1000 // Enable DAC clock mem 0x40020114 0x1000 // Enable CMP clock mem 0x40020118 0x1000 // Enable USB clock mem 0x4002011C 0x1000 // Enable I2C clock mem 0x40020120 0x1000 // Enable SPI clock mem 0x40020124 0x1000 // Enable UART clock mem 0x40020128 0x1000 // Enable FTM clock mem 0x4002012C 0x1000 // Enable TPM clock mem 0x40020130 0x1000 // Enable RTC clock mem 0x40020134 0x1000 // Enable LPIT clock mem 0x40020138 0x1000 // Enable LPTMR clock mem 0x4002013C 0x1000 // Enable PDB clock mem 0x40020140 0x1000 // Enable ADC clock mem 0x40020144 0x1000 // Enable DAC clock mem 0x40020148 0x1000 // Enable CMP clock mem 0x4002014C 0x1000 // Enable USB