拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用MCU搭建功耗分析采集系统:从采样电阻到同步触发

做硬件安全评估和嵌入式底层开发的朋友迟早会遇到一个词功耗分析Power Analysis。我第一次接触这个概念是看到一篇材料里展示了一条功耗曲线然后从曲线里反推出了加密密钥当时整个人是有点震撼的——原来芯片在执行不同指令时电流波形的细微差别能泄露这么多信息。而“采集”这一步是整个功耗分析里最核心、也最容易被低估的环节。这篇文章想聊的就是怎么用一块普通的MCU搭出一个功耗分析采集系统自己动手把目标芯片运行时的瞬时功耗变化抓下来。这个方案适合硬件安全方向的学生、嵌入式开发中对侧信道感兴趣的人也适合想快速验证自家固件是否抗泄漏的工程师。它不需要昂贵的专用设备成本可能就是你手头那块开发板加几个电阻电容却能跑通完整的采集流程为后续做CPA、DPA之类的分析打好基础。很多人在这一步卡住往往不是不会写代码而是对“功耗信号从哪来、采集链路怎么设计、触发怎么同步”没有整体概念。这篇文章我会按从信号到数据处理的顺序拆开讲把我自己踩过的坑和调通的细节都写出来。1. 为什么选择MCU来做功耗采集1.1 功耗分析到底在采什么先理清一个基本问题功耗分析采集的“信号”到底是什么。CMOS芯片在工作时内部逻辑门的翻转会产生动态功耗而不同数据在总线上翻转时翻转的位数和功耗直接相关。加密算法执行过程中中间值往往是密钥的函数这些中间值又通过寄存器和总线的数据翻转反映到瞬态电流上。于是芯片工作时的功耗波形就成了一个侧信道。要采集这个信号传统方式是拿示波器加电流探头但示波器价格不低电流探头更是大头而且做一次对齐好的批量采集手动操作非常痛苦。MCU方案的思路很简单用MCU内置的ADC或者外挂一个高速ADC对目标芯片电源路径上的电压降进行连续采样。因为电流流过采样电阻会产生电压降测这个电压就等于测电流再配合上触发同步就能得到一条条可对齐的功耗轨迹。这里必须强调一个观念功耗分析的成功率很大程度上取决于采集端的同步精度和采样质量。采样率不够、触发抖动大、噪声过多后面算法再强也很难救回来。所以用MCU做采集难点不在“能不能采”而在“采得准不准、能不能同步”。1.2 专用设备与MCU方案的取舍市面上专业的侧信道采集方案比如ChipWhisperer系列或者高端的示波器加探头组合确实采样率高、同步机制完善、软件生态也成熟。但它们的问题也很明显贵而且很多工具的软件是封闭的想定制触发逻辑、想改动采集流程用户能操作的空间有限。MCU方案最适合的场景是目标设备的工作频率不高比如常见的8位MCU、智能卡、或者是跑在几十MHz以内的加密协处理器。这类设备的功耗信号主要成分在几百kHz到几MHz之间用一片STM32的内置ADC约2Msps左右采样率配合合适的触发硬件足够抓取到可用于初步分析的数据。如果你的目标是GHz级别的SoC那MCU方案确实力不从心这种场景就该考虑动辄几个GSps的示波器了。所以我的看法是MCU采集不是一个“替代品”而是一个“入口”。它让你用非常低的成本理解功耗分析全流程把同步、采集、对齐这些核心概念跑通。等你真的需要做高精度评估再上专业设备时你已经非常清楚自己需要关注哪些参数了。而且自己做出来的采集板灵活性极好想加什么触发逻辑、想接什么样的目标板自己改固件就行。2. 从信号源头开始设计采集链路2.1 采样电阻与信号放大采集链路的起点是在目标芯片的供电路径上串一颗采样电阻。电流瞬时变化通过电阻转成电压变化这个电压就是ADC要采的信号。采样电阻的选择有几个陷阱。阻值不能太大否则目标芯片的供电电压会被拉低甚至导致它无法正常工作阻值也不能太小否则压降信号太微弱容易被噪声淹没。一般来说对于工作电流几mA到几十mA的MCU级别目标采样电阻选1Ω到10Ω比较合适。举个实际例子目标设备平均电流10mA用5Ω电阻静态压降是50mV瞬时电流波动到20mA时压降变成100mV这个动态范围对ADC来说比较友好。还要注意电阻的封装和类型。普通贴片电阻存在寄生电感高频瞬态下电感会引入额外压降导致采集到的波形失真。尽量选0603或0402封装的薄膜电阻避免用绕线电阻。我试过用直插电阻做原型结果波形的上升沿明显变钝换贴片电阻后改善很多。另外电阻功率也要算一下长时间工作发热会改变阻值一般11Ω电桥电阻用不了太大电流。压降只有几十mV直接进ADC肯定不行。12位ADC满量程3.3V的话一个LSB约0.8mV50mV的动态范围连64个码都不到分辨率太差。所以需要放大级。这里有一个关键取舍放大器带宽要足但增益也不能太小。理论上功耗信号的带宽取决于目标芯片的工作频率和指令执行速度经验上采集目标主频5到10倍的带宽能覆盖大部分能量。比如目标芯片工作频率8MHz放大链路带宽最好做到40MHz以上。运放选型上我推荐用高速运放或者仪表放大器。仪表放大器比如INA333差分输入可以直接放在采样电阻两端抑制共模干扰但带宽通常偏低适合低频目标。高速运放比如AD8099、OPA837这类带宽足够但要自己搭差分转单端电路。如果是初学建议先用仪表放大器跑通流程再考虑优化带宽。2.2 ADC分辨率与采样率的权衡ADC参数的选择是整个方案的核心这一节值得多想一步。采样率方面奈奎斯特定律是底线但是在功耗分析里采样率最好是目标信号最高频率的5到10倍以上这样轨迹的峰形才够细腻便于后续对齐和分析。例如目标芯片主频8MHz它的功耗信号在时域上会呈现出对应指令周期的波动采样率至少要到几Msps才能看到比较清晰的指令级特征。如果目标是AES这类软件实现的密码算法操作级功耗波动通常集中在1MHz以内2Msps的采样率是基本够用的。分辨率方面12位是起步10位会非常吃力。因为功耗信号的微小变化可能只有几个mV低分辨率ADC量化噪声会直接淹没信号。这里还要提一个容易被忽略的概念有效位数ENOB。MCU内置ADC标称12位但实际受电源噪声、参考电压抖动、布局布线质量影响有效位可能只有9到10位。所以设计时尽量不要用满量程范围留足裕量让信号摆幅落在ADC输入范围的中段避开上下接近满偏的非线性区。如果希望变相提高分辨率可以用过采样技术。原理很简单对同一个信号连续采16次求平均理论上可以把有效位提升2位。但这要求输入信号中有足够的随机噪声如果信号过于干净过采样效果会打折扣。实际使用中我会在放大器输出端故意留一点无害的背景噪声比如从参考电压轻微纹波让它帮助过采样抬升有效位。2.3 同步触发是灵魂功耗分析最容易被新手忽略的是同步触发。想想看你想比较不同明文输入下两条功耗轨迹的差异前提是它们从同一个算法起点开始对齐。如果每次采集的启动时刻差了几十微秒后续做差分分析时信号就被时序抖动“抹平”了。解决思路是在目标设备上产生一个硬件同步信号。常见做法是目标设备在进入加密函数之前把一个GPIO引脚拉高或拉低这个引脚接到采集MCU的一个外部中断或者定时器输入上。MCU检测到边沿后立刻启动ADC采样。这样采集窗口和加密操作严格对齐。这里有一个细节同步信号本身也可能波动。目标设备内部中断、分支跳转都会让触发信号和实际密码操作之间产生微小的抖动。要缓解这个问题可以尽量在目标固件里把触发语句放在固定位置用内联汇编保证执行周期固定。另外采集端可以预留一点点预采样时间也就是触发前就持续采样把触发点之前一小段数据也记录下来这样后续可以用软件做更精细的对齐。3. 硬件搭建与MCU选型3.1 采集板最小系统参考设计接下来是我自己验证过的一版参考设计目标设备是一个3.3V供电的Cortex-M0开发板采集用的MCU是STM32F4系列内置ADC在2.4Msps左右。目标板供电线路外接5V电源经过LDO降压到3.3V3.3V输出先经过一颗5Ω采样电阻再连到目标板的VDD引脚。采样电阻两端引线接仪表放大器的差分输入仪表放大器增益设为50倍输出接STM32的ADC输入引脚。采集板与目标板共地共地点选择在电源入口处避免地环路。这里要特别提醒目标板上的去耦电容不能太大。标准开发板上MCU电源引脚附近通常有10μF甚至100μF的电容这些电容会把瞬态电流波动平滑掉直接导致功耗信号被“吃掉”。我在调试时遇到过波形几乎是一条直线排查半天发现是目标板电源上有100μF大电容。后来把电容换成1μF以下波形立刻就出来了。如果目标板电容不好拆也可以把采样点放在电容前面的电阻路径上但效果会差一些。采集端MCU需要一个干净参考电压。使用STM32内部VREF或者外接一个低噪声基准芯片比如REF3033都会比直接用电源电压做参考更稳。3.2 常见MCU型号对比并不是所有MCU都适合做采集主要看ADC能力和DMA能力。我列一个实际选型时的对比MCU系列ADC采样率有效位表现适合场景STM32F103典型1Msps12位一般受时钟噪声影响入门教学、低频目标STM32F42.4Msps12位中等偏上多通道可交错最常用的方案STM32H73.6Msps12/16位较好双ADC可交错提升稍高采样率需求RP20400.5Msps12位采样率偏低但SDK简单纯教学原型外置ADC 任意MCU10Msps以上取决于ADC芯片较高带宽需求如果你有精力用外置ADC比如AD9226这类配上FPGA或者高速MCU可以获得更好的效果但复杂度和成本都会明显上升。我的建议是先用STM32F4系列跑通全套流程不要一上来就追求高速。4. 固件实现让ADC稳定地连续采样4.1 定时器触发ADC加DMA双缓冲单片机的ADC如果靠CPU在循环里不断启动转换采样间隔一致性很难保证因为中断、指令周期都会带来抖动。正确做法是用定时器产生固定频率的触发事件ADC在触发事件到来时自动启动转换转换完成后由DMA自动把数据搬到内存全程不占用CPU。这样采样间隔严格等于定时器周期抖动极小。数据搬移方面单独一块连续缓冲区就够了但如果你希望数据不丢、或者边采边传双缓冲更靠谱。所谓双缓冲就是分配两块内存区DMA先把数据填满A区填满后自动切换到B区同时触发中断通知CPU把A区数据通过串口或USB发送出去。如此交替CPU处理数据的时间不会阻碍采样。STM32的HAL库把这些过程封装得很好但新手容易在几个配置环节出错。下面我详细拆解一下关键配置的流程。4.2 关键代码与配置细节以STM32F407为例目标采样率设为2Msps使用定时器TRGO事件触发ADC1ADC1转换结果通过DMA2 Stream0传输到缓冲区。先用代码说明ADC初始化void ADC_Init(void) { ADC_HandleTypeDef hadc1; ADC_ChannelConfTypeDef sConfig {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV2; // 将ADC时钟控制在21MHz hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode DISABLE; // 由定时器触发不连续 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); sConfig.Channel ADC_CHANNEL_0; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_3CYCLES; // 1.5周期采样 HAL_ADC_ConfigChannel(hadc1, sConfig); }定时器配置部分需要让TIM2的更新事件通过TRGO输出触发ADC。采样率由定时器频率决定定时器时钟84MHz分频84得到1MHz频率计数值1则产生1MHz触发输出。如果要2Msps可以改为分频42计数值1。void TIM_Init(void) { TIM_HandleTypeDef htim2; htim2.Instance TIM2; htim2.Init.Prescaler 42 - 1; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 1 - 1; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim2); TIM_MasterConfigTypeDef sMasterConfig {0}; sMasterConfig.MasterOutputTrigger TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim2, sMasterConfig); HAL_TIM_Base_Start(htim2); }DMA配置中最重要的参数是把DMA模式设为Circular循环模式这样DMA会在填满缓冲区后自动回到起点继续写入不依赖重复配置。但这样会和信号量逻辑冲突所以实践中通常是半传输中断和传输完成中断交替分别处理前半段和后半段数据。void DMA_Init(void) { DMA_HandleTypeDef hdma_adc1; hdma_adc1.Instance DMA2_Stream0; hdma_adc1.Init.Channel DMA_CHANNEL_0; hdma_adc1.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc DMA_PINC_DISABLE; hdma_adc1.Init.MemInc DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode DMA_CIRCULAR; hdma_adc1.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_adc1); }编写到这里我想强调一个容易踩的坑ADC采样时间不能设得太短。虽然1.5周期的采样时间能提高转换速度但ADC内部采样电容需要时间充电如果信号源阻抗较高采样时间太短会导致测量值不准。实际工程中如果信号源阻抗低于1kΩ3周期以上比较安全高阻抗信号源则需要更长采样时间。这里我在前端放大级输出加了一级电压跟随器把输出阻抗压到很低才敢用短采样时间。4.3 采样率与缓冲区计算2Msps采样率、12位精度每个样本2字节意味着每秒产生4MB数据。这个数据量对USB也好、对串口也好都是一个需要正视的带宽问题。假设你想连续采集1毫秒的功耗轨迹数据量是8KB这个体量通过串口可以先保存再分批上传如果要连续采集就必须用USB高速或者把采样率降到合理范围。缓冲区设计要兼顾DMA连续性的内存占用。我通常会开两个8KB缓冲区一个正在被DMA填充时另一个在通过USB发送。USB CDC虚拟串口的实际吞吐量比理论值低不少传输大块数据时容易因为系统调度产生间隙。如果要求极致速度可以把单个缓冲区再拆成半传输中断机制保证发送操作不会把一个缓冲区拖满。如果你的采集板支持USB High Speed数据带宽会宽裕很多。否则稳妥的做法是先采集一小段暂停采样上传完数据再继续下一次采集。5. 上位机与数据处理5.1 数据接收与波形观察MCU把ADC数据包通过USB CDC或者串口上传上位机这边我用Python配合pySerial接收然后转成numpy数组直接matplotlib画图。协议可以很简略比如固定帧头0xAA55加上长度后面接原始数据。重点在于MCU端上传的速度必须和上位机接收匹配否则上位机读到的数据会出现错位。import serial import numpy as np import matplotlib.pyplot as plt ser serial.Serial(/dev/ttyACM0, 115200, timeout1) # 读取过程中不断查找帧头将有效数据拼接 # 这里省略去帧头逻辑假设buf已经是完整一帧 data np.frombuffer(buf, dtypeu2) plt.plot(data) plt.title(Power trace) plt.xlabel(sample index) plt.ylabel(ADC code) plt.show()观察到波形之后第一件事是检查它是否符合预期。如果目标设备在运行AES加密或固定循环操作你应该能看到清晰的周期性波动。如果波形完全平坦优先检查采集链路是否工作。5.2 轨迹对齐与简单处理采集到的原始轨迹往往不是严格对齐的因为触发信号到达ADC实际启动之间可能有几个周期的偏差。做功耗分析之前要对齐。最简单的方法是用互相关以第一条轨迹为参考把后面的轨迹平移若干样本点使互相关值最大。实际项目中我用互相关做完初步对齐后还会检查目标设备的功耗波形中的某个特征点作为二次基准。 一个更加粗糙但常用的方法在固件层面让目标设备在算法执行前后执行固定时长的延迟 从而让触发信号和加密操作的相对时间固定。对齐之后还可以做均值滤波或者去直流。去直流很简单就是每个样本减去整条轨迹的平均值把功耗的静态部分去掉保留动态波动部分。如果是批量采集多条轨迹把多条轨迹逐点求平均能有效降低随机噪声但前提是对齐必须做得足够好否则会把信号也抹掉。6. 调试与常见问题速查6.1 波形乱七八糟先查电源纹波我调试中遇到频率最高的问题是采集到的波形上叠加了一个大正弦包络或高频毛刺。这个很多时候不是目标设备的功耗信号而是电源本身的纹波。开关电源的输出纹波通常在几mV到几十mV量级经过50倍放大后就是几百mV完全淹没功耗信号。解决方法是采集端用线性稳压器的电池或者低噪声LDO供电另一个关键是采样电阻后面的目标板电源引脚要确保没有被其他开关电路干扰。还有一点采集板接USB供电时电脑的开关电源噪声会顺着USB线传进来导致共地噪声异常大。有条件的话尽量用隔离电源或者电池供电。6.2 采样率上不去、数据断裂配置成高采样率后波形出现明显断裂往往不是ADC问题而是DMA缓冲区切换和USB传输之间发生了资源竞争。比如我在中断服务函数里直接做数据处理导致中断过长DMA半传输中断来不及响应缓冲区被覆盖。解决办法是中断服务函数只做标志位操作把数据发送逻辑放到主循环或单独的任务里。如果采样率计算后达不到预期请看ADC时钟。STM32的ADC时钟不能超过36MHz不同型号上限不同时钟分频系数和APB2时钟要匹配。很多人在这一步直接用默认配置ADC时钟超过上限导致转换异常。6.3 触发抖动导致轨迹对不齐如果目标设备是带操作系统的或者代码里有复杂的指令分支触发信号到实际加密操作的延迟会有抖动。一个有效方案是在目标固件里把触发信号尽量靠近核心操作并且在触发信号之后插入一段固定指令周期的延迟再执行加密。这样即使外部触发有微秒级抖动实际加密操作的对齐误差也会被固定在一个很小的范围内。采集端也可以留预采样窗口来缓解。比如让MCU始终在采样并不断刷新缓冲区当检测到触发信号后继续采集一小段时间然后把缓冲区内包含触发前、触发后在内的数据全部上传。后处理时你可以去掉触发前太远的部分找到真正的起点。6.4 ADC精度与电噪声的纠缠如果你发现波形整体有量化台阶感看起来一根根阶梯状说明信号幅值相对ADC的LSB来说太小了。这时不是加大增益就能解决因为增益放大信号的同时也放大噪声。更值得检查的是放大器的电源去耦、PCB布局和参考电压。我在做第二版采集板时特意把模拟部分和数字部分分区布局ADC参考电压引脚单独加了一个低噪声基准芯片并串一个小磁珠隔离数字噪声。直接效果就是波形毛刺大幅减少有效位明显提升。如果只是实验环境至少做到放大器和MCU的供电分开走线、加足够的去耦电容。调试阶段做个简单的“自检模式”非常有用让目标设备固定运行一个重复性很强的操作比如循环填充寄存器采集信号应该稳定重复。如果重复性不好基本可以判定是采集链路或者触发同步的问题而不是目标算法本身的问题。6.5 常见问题速查表现象可能原因排查方向波形完全平坦采样电阻被短路、放大器没输出、目标设备没运行万用表测电阻两端电压波形有大包络电源纹波、共地引入噪声换低纹波电源、检查地线波形阶梯感强信号幅值太小、ENOB不足增加增益、检查参考电压波形在某个点突然断裂DMA缓冲溢出、USB传输阻塞加缓冲、降低采样率、精简中断两条轨迹明显错位触发抖动、目标代码顺序不稳定加固定延迟、使用互相关对齐瞬时波形过冲严重采样电阻寄生电感过大换贴片低感电阻写在最后的一点经验用MCU做功耗分析采集这条路我前后折腾了两个多月才把信号调干净。回过头看最大的教训就是不要一开始就追求采样率先把放大链路的信噪比做上去把触发同步做稳定成功率反而比盲目堆采样率提升得更快。后续如果想把这套东西往深了做可以考虑外置高速ADC加FPGA的组合把采样率推到几十Msps级别配合一些硬件随机数触发机制能处理的目标类型会更多。但万变不离其宗你对采集链路里每个环节的理解越深扩展开来就越容易。希望这篇文章能帮你少踩几个坑早点拿到干净漂亮的功耗轨迹。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门