基于68HC08与单线PWM协议的嵌入式系统低成本IP保护方案
2026/6/23 7:35:10 网站建设 项目流程

1. 项目概述与核心价值

在嵌入式产品开发中,最让人头疼的事情之一,莫过于辛辛苦苦研发出来的核心算法和固件,被竞争对手轻易地通过“抄板”复制过去。硬件PCB可以仿制,主处理器里的程序也能通过某些手段读取,整个产品的知识产权(IP)防线似乎一触即溃。这种困境我经历过不止一次,尤其是在一些成本敏感、但又蕴含独特价值的中小型设备上。后来,我在一个老项目的技术文档里,重新审视了飞思卡尔(现恩智浦)的68HC08系列微控制器,结合一种巧妙的单线通信协议,摸索出了一套高性价比的嵌入式系统IP保护方案。这套方案的核心思想,不是追求绝对无法破解的“铜墙铁壁”——那往往意味着高昂的成本——而是在有限的预算内,极大地提高仿制和逆向工程的难度与成本,让抄袭变得不划算。

简单来说,这个方案是在你的主系统之外,额外添加一颗像68HC908QT1这样的低成本协处理器MCU。这颗MCU自带Flash锁存安全机制,其内部的程序一旦锁定就无法被外部读取。然后,通过一根GPIO线,在主处理器和这颗“安全芯片”之间,建立一套自定义的、基于PWM占空比的单线异步通信协议。主处理器每次上电或执行关键功能前,都必须与这颗安全芯片进行一场“秘密握手”(双向身份认证),只有认证通过,系统才能正常工作。这样一来,即使攻击者完美复制了硬件PCB和主处理器里的程序,但由于缺少了那颗无法被读取的安全芯片及其内部的密钥与认证逻辑,复制品也只是一堆“砖头”。这个方案的魅力在于,它用极低的硬件成本(一颗几块钱的MCU),撬动了整个系统安全性的质变,特别适合那些主控芯片本身没有强大安全功能,但又迫切需要保护核心IP的嵌入式设计。

2. 系统安全架构与核心组件选型

2.1 为什么选择68HC08系列作为安全协处理器?

当主处理器(可能是任何一款通用MCU或MPU)自身缺乏有效的代码读保护或硬件加密引擎时,引入一个专门负责安全的协处理器是明智之举。在众多选择中,68HC08系列,特别是68HC908QT1/QT2/QT4,成为了我的首选,原因有几个方面。

首先当然是成本。在消费电子和工业控制领域,BOM成本是命脉。68HC908QT系列是8位MCU,价格极具竞争力,但其提供的Flash安全功能却毫不含糊。它的Flash存储器支持通过编程器设置安全位(Security Bit),一旦设置,所有对Flash的读取指令都将返回无效数据(通常是0xFF或随机值),而芯片内部的程序仍可正常执行。这意味着,你可以把最核心的认证算法、加密密钥存放在这里,而不用担心通过调试接口(如背景调试模式BDM)被轻易提取。

其次是足够的资源。以68HC908QT1为例,它拥有1.5KB的Flash和128B的RAM,内置RC振荡器。这个资源规模对于运行一个精简的加密算法(如TEA、XTEA或自定义的轻量级算法)、管理通信协议和完成认证逻辑来说,是绰绰有余的。其内置的振荡器在未经微调时精度约为±25%,经过微调后可达±5%。对于我们要实现的、对绝对时序不敏感的PWM占空比通信协议来说,这个精度完全足够,甚至省去了外部晶振的成本和PCB空间。

最后是生态与可靠性。68HC08架构虽然如今看来有些“经典”,但其工具链成熟,资料丰富。使用像CodeWarrior这样的开发环境,开发和调试过程是顺畅的。选择一款经过市场长期验证的芯片,也意味着更少的未知风险和更稳定的供货。

注意:安全位的设置通常在芯片量产编程的最后一步进行。一旦设置,除非整体擦除Flash(这也会擦除程序),否则无法解除。因此,务必在确认程序完全正确后再进行此操作,并做好程序备份。

2.2 单线通信协议的设计哲学与优势

在主处理器和安全协处理器之间建立通信链路,常见的选择有I2C、SPI或UART。但我们这里偏偏选择了一根GPIO线来实现全双工通信,这看似是退步,实则是为了安全而做的精心设计。

核心优势一:隐蔽性与迷惑性。I2C和SPI有明确的时钟线,UART有固定的波特率,这些特征在逻辑分析仪下非常明显,攻击者可以轻松识别出这是一个通信总线,进而监听数据。而单根GPIO线上变化的电平,在没有协议说明的情况下,很难被第一时间判定为数字通信信号。它可能被误认为是复位信号、使能信号或普通的状态指示。这增加了攻击者分析系统的第一步难度。

核心优势二:防止信号冲突的巧妙设计。单线通信最大的挑战是如何避免两个设备同时驱动总线导致的冲突(Contention)。本方案采用了一种非常巧妙的GPIO配置方法。双方初始化时,都将连接这根通信线的GPIO配置为输入模式,并使能内部上拉电阻。在输入模式下,GPIO呈现高阻态,内部上拉将总线拉至高电平(逻辑‘1’)。此时,总线是“释放”状态。

当一方需要发送逻辑‘0’时,它只需将该GPIO的方向寄存器配置为输出模式。由于数据寄存器在初始化时已被清零(输出‘0’),GPIO会立即驱动总线为低电平。发送完毕后,再将该GPIO重新配置为输入模式,总线又被内部上拉电阻拉回高电平(逻辑‘1’)。通过这种在“输入”和“输出”模式间切换的方式,完美实现了开漏(Open-Drain)类似的效果,且无需外部上拉电阻,从根本上杜绝了两个输出直接冲突的可能。

核心优势三:协议灵活性。我们可以自定义一套简单的异步串行协议,比如包含起始位、数据位、校验位和停止位。但这里更精妙的是,我们可以用PWM的占空比来编码信息。例如,定义一个“起始位”为持续时长超过比特周期80%的高电平脉冲。数据位‘0’可以用40%占空比(±5%容差)的脉冲表示,数据位‘1’用60%占空比的脉冲表示。停止位再用一个超过80%的高电平脉冲。接收方通过定时器测量高电平的持续时间来判断是哪种比特。这种基于“比例”而非“绝对时间”的编码方式,对双方MCU的时钟精度要求更低,抗干扰能力也更强。

3. 核心细节解析与实操要点

3.1 基于PWM占空比的通信协议实现细节

协议的具体实现是这套方案的技术核心。我们假设比特位总周期为T(例如2ms)。发送和接收都基于这个周期进行测量和判断。

发送端逻辑:

  1. 起始位:将总线拉低,启动定时器。定时器计时到0.2T(即0.4ms,占总周期20%)时,将总线释放(切换GPIO为输入,被上拉为高)。高电平持续到T时间到,形成一个占空比为80%的脉冲。
  2. 数据位‘0’:拉低总线,启动定时器。计时到0.6T(1.2ms,占60%)时,释放总线。高电平持续0.4T(0.8ms)直到周期结束,形成40%占空比脉冲。
  3. 数据位‘1’:拉低总线,启动定时器。计时到0.4T(0.8ms,占40%)时,释放总线。高电平持续0.6T(1.2ms)直到周期结束,形成60%占空比脉冲。
  4. 停止位:与起始位相同,发送80%占空比脉冲。

发送方的关键在于精确控制低电平的持续时间。这可以通过配置一个定时器在输出比较(Output Compare)模式下,在特定时间点产生中断来切换GPIO方向实现。

接收端逻辑:接收端GPIO始终配置为输入,并开启上升沿和下降沿中断,同时配合一个自由运行的定时器进行时间测量。

  1. 检测到下降沿(总线变低),记录当前定时器计数值T1,并清零一个用于测量脉宽的辅助计数器。
  2. 检测到上升沿(总线变高),记录当前定时器计数值T2。
  3. 计算高电平持续时间Thigh = T2 - T1
  4. 在一个比特周期T结束后,判断Thigh占总周期T的比例:
    • 如果Thigh > 0.8T,则判定为起始位或停止位。
    • 如果0.35T < Thigh < 0.45T(考虑±5%容差),则判定为数据位‘0’。
    • 如果0.55T < Thigh < 0.65T,则判定为数据位‘1’。
    • 其他情况,判定为传输错误,启动错误处理流程(如重发或认证失败计数)。

实操心得:定时器溢出的处理至关重要。由于T可能较长(几毫秒),而8位定时器可能很快溢出。务必使用16位定时器,或处理好8位定时器溢出中断,确保时间测量的连续性。一个实用的技巧是,在下降沿中断里,不仅记录定时器捕获值,还将一个16位的软件计数器清零;在定时器溢出中断中对该软件计数器加1。这样,Thigh = (溢出次数 * 定时器最大值) + (T2 - T1),就能准确测量长时段。

3.2 双向身份认证流程与加密算法集成

仅有隐蔽的通信还不够,必须确保通信的双方是“自己人”。这就需要双向身份认证。一个简单而有效的挑战-应答(Challenge-Response)协议流程如下:

  1. 主处理器发起挑战:主处理器生成一个随机数R1(Challenge A),使用加密算法A(可以是与68HC08芯片共享的一个简单变换或密钥)对其进行计算,得到C1。然后将R1和C1一起发送给68HC08安全芯片。
  2. 安全芯片验证并回应:安全芯片收到R1和C1后,使用相同的加密算法A对R1进行计算,得到C1‘。比较C1‘与收到的C1是否一致。如果不一致,认证失败,安全芯片可能返回一个随机乱码,并增加失败计数器。如果一致,则认证通过第一步。
  3. 安全芯片发起挑战:接着,安全芯片生成另一个随机数R2,使用加密算法B(另一个不同的算法或密钥)对R2进行计算,得到C2。将R2和C2发送给主处理器。
  4. 主处理器验证并完成认证:主处理器收到后,用加密算法B验证R2和C2。验证通过后,主处理器可以再使用加密算法C计算一个校验和(Checksum),比如对之前交换的所有消息(R1, C1, R2, C2)进行计算,将结果发给安全芯片做最终核对。
  5. 失败处理机制:任何一步验证失败,双方都应停止当前会话。系统应维护一个尝试计数器(可存储在Flash中)。当连续失败次数超过预设阈值(如3-5次),系统应触发安全策略,例如:永久锁定此功能、擦除Flash中的敏感数据、或仅仅是停止工作。这能有效防止暴力破解尝试。

加密算法的选择:在68HC08这类资源有限的MCU上,实现AES等标准算法可能有些吃力。更实际的选择是轻量级算法,如:

  • XTEA(eXtended Tiny Encryption Algorithm):代码小巧,安全性足够应对非国家级攻击。
  • 自定义的Feistel网络结构算法:可以自己设计一个简单的、使用移位、异或、加模运算的几轮变换。算法的安全性不在于其复杂性,而在于密钥的保密性。这正是“柯克霍夫原则”的体现:即使算法完全公开,只要密钥保密,系统就是安全的。
  • 查表+S盒混淆:将密钥作为索引,从一个预先计算好的、看似随机的大常量数组中取值进行运算。这个常量数组可以伪装成程序中的其他数据表(如字体、校准参数)。

密钥的隐藏:这是最后一道防线。不要将密钥明文写在代码里如const char key[] = {0x12, 0x34...};。可以采用以下方法:

  • 动态生成:编写一个密钥生成函数,其输入是芯片的某个唯一ID(如果支持)或几个固定常量,经过一系列复杂但确定的运算得到真正的密钥。这样静态反汇编看到的只是一段计算代码,而不是密钥本身。
  • 分散存储:将密钥拆分成多个片段,存放在程序空间的不同位置,甚至与普通数据混合存放。使用时再动态组合。
  • 利用Flash特性:在某些地址写入特定数据,利用Flash位只能从1写成0的特性,将密钥“熔断”写入。但这需要芯片支持。

4. 硬件连接与软件驱动实现

4.1 硬件电路设计要点

硬件连接极其简单,这也是本方案的一大优点。

  1. 连接线:只需一根导线,连接主处理器的任意一个GPIO引脚(记为MAIN_IO)与68HC908QT1的任意一个GPIO引脚(记为SECURE_IO)。
  2. 上拉电阻:强烈建议在通信线上并联一个外部上拉电阻(例如4.7kΩ - 10kΩ)到VCC。虽然两个MCU的内部上拉电阻可以工作,但外部上拉能提供更稳定、更强的上拉能力,尤其是在通信线较长或有轻微干扰时,能确保总线在空闲时稳定在高电平,提高通信可靠性。
  3. 电源与地:确保两个MCU有共同的、干净的电源和地平面。数字噪声可能会干扰对脉冲宽度的精确测量。
  4. 旁路电容:在每个MCU的VCC和GND引脚附近,按照数据手册要求放置足够的去耦电容(通常为0.1μF陶瓷电容),这是保证MCU稳定运行、尤其是内部振荡器稳定的基础。

下图展示了最简单的连接方式:

主处理器MCU 安全协处理器 (68HC908QT1) VCC -------------------- VCC GND -------------------- GND GPIO_PTx (MAIN_IO) ---[上拉电阻]--- GPIO_PTA0 (SECURE_IO) | 10kΩ | VCC

(示意图:一根通信线,两端接GPIO,并通过一个上拉电阻接到电源)

4.2 软件驱动层实现步骤

软件实现分为两层:底层驱动层(负责比特的收发)和上层协议层(负责组帧、校验和认证逻辑)。这里重点讲驱动层。

发送一个字节的函数SingleWire_SendByte(uint8_t data)

  1. 调用SendStartBit()发送起始位。
  2. 循环8次,取data的每一位,调用SendDataBit(bit)发送。
  3. (可选)计算并发送奇偶校验位。
  4. 调用SendStopBit()发送停止位。

SendDataBit函数伪代码:

void SendDataBit(bool bit_value) { if (bit_value == 0) { // 发送‘0’:低电平占60%,高电平占40% SET_GPIO_OUTPUT_LOW(); // 配置为输出低 delay_us(T * 0.6); // 保持低电平时间 SET_GPIO_INPUT(); // 释放总线(变输入,被上拉为高) delay_us(T * 0.4); // 保持高电平时间 } else { // 发送‘1’:低电平占40%,高电平占60% SET_GPIO_OUTPUT_LOW(); delay_us(T * 0.4); SET_GPIO_INPUT(); delay_us(T * 0.6); } }

注意:delay_us需要根据你的主频精确实现。SET_GPIO_OUTPUT_LOW()宏应实现:先确保数据寄存器为0,再将方向寄存器设为输出。

接收端的状态机实现:接收更适合用状态机配合中断实现。状态包括:IDLE(空闲)、RECV_START(检测起始位)、RECV_DATA(接收数据位)、RECV_PARITY(接收校验位)、RECV_STOP(接收停止位)、ERROR(错误)。

  1. 下降沿中断:在任何状态下,收到下降沿,记录时间戳T1_fall,并判断:
    • 若当前状态为IDLE,则进入RECV_START状态,启动一个比特周期T的超时定时器。
    • 若在接收数据状态,则可能是干扰或协议错误,可转入ERROR状态。
  2. 上升沿中断:记录时间戳T2_rise。根据当前状态处理:
    • RECV_START状态:计算高电平脉宽。若在(0.8T, T)范围内,则起始位有效,清空接收缓冲区,状态转为RECV_DATA,准备接收第一个数据位。否则,转ERROR
    • RECV_DATA状态:计算脉宽,判断是‘0’还是‘1’,存入接收移位寄存器。收满8位后,状态转为RECV_PARITY(或RECV_STOP,如果无校验)。
    • RECV_PARITY/RECV_STOP状态:验证脉宽是否符合停止位特征。符合则一帧接收完成,将数据存入应用层队列,状态回归IDLE
  3. 超时定时器中断:如果在预期时间内没有收到上升沿或下一个下降沿,说明通信中断,触发ERROR状态,进行错误恢复。

5. 系统集成、测试与安全加固策略

5.1 将安全模块集成到主系统

在主体应用程序中,安全认证不应是一个孤立的功能,而应深度集成到系统关键流程中。

  1. 上电初始化后立即认证:在主函数初始化完硬件后,第一时间调用Security_Authenticate()函数。只有该函数返回成功,系统才能继续执行后续的硬件初始化、加载用户配置、进入主循环等操作。
  2. 关键操作前的再次认证:对于特别敏感的操作(如固件升级、写入校准参数、进入工厂测试模式),可以在执行前再次进行一轮快速认证。这增加了攻击者在系统运行时进行中间人攻击的难度。
  3. 心跳保活机制:在主系统正常运行期间,可以定期(例如每分钟)与安全芯片进行一次简短的“心跳”通信。这不仅可以持续验证安全芯片的存在和功能正常,还能让通信线路保持“活跃”状态,避免因长期静默而被攻击者探测为无用线路并尝试切断或替换。
  4. 认证失败的处理:Security_Authenticate()函数内部应维护失败计数。连续失败达到阈值后,不应只是简单的返回错误码。更安全的做法是:
    • 主处理器锁定自身,跳转到一个死循环或重启(但重启后仍会认证失败)。
    • 通过单线协议向安全芯片发送“自毁”指令,触发其擦除内部Flash中的关键算法或密钥区域。
    • 将失败状态永久写入主处理器Flash的某个特定扇区,即使重启,也先读取该状态,如果已标记为“被入侵”,则直接进入故障安全模式。

5.2 测试方法与常见问题排查

开发完成后, rigorous 的测试是保证方案可靠性的关键。

1. 通信链路压力测试:

  • 目的:验证在不同环境条件下通信的可靠性。
  • 方法:编写一个测试循环,让主处理器和安全芯片之间以最高速率连续进行数千次随机数据的认证或数据交换。同时,可以尝试:
    • 在电源线上引入纹波噪声。
    • 用热风枪或冷喷雾改变局部温度,测试时钟漂移的影响。
    • 轻微弯曲PCB,观察接触是否可靠。
  • 预期与排查:应达到零错误。如果出现偶发错误,检查:电源稳定性、上拉电阻阻值是否合适(太弱易受干扰,太强则下降沿速度慢)、软件中的定时容差是否设置过紧、中断服务函数是否被高优先级中断打断导致计时不准。

2. 协议一致性测试:

  • 目的:确保发送和接收对波形的解读完全一致。
  • 方法:使用示波器或逻辑分析仪抓取通信线上的波形。测量起始位、数据位、停止位的实际高低电平时间,与软件中定义的T、40%T、60%T等参数进行对比。验证在时钟精度偏差±5%的极限情况下,双方是否仍能正确解码。
  • 工具使用技巧:逻辑分析仪可以设置协议解码器。你可以自定义一个解码器,根据占空比来解析“0”和“1”,这样能直观地看到传输的数据帧,极大提高调试效率。

3. 安全功能验证测试:

  • 克隆攻击模拟:使用另一块空白的主处理器板(克隆板),尝试运行从原板读取的(假设已破解)主程序,但不连接真正的68HC08安全芯片,或连接一个未编程的空白芯片。验证克隆板是否无法启动或功能异常。
  • 窃听攻击模拟:用逻辑分析仪长时间监听通信线,收集大量的挑战-应答数据包。尝试分析这些数据,看是否能找到规律或破解密钥。这需要一定的密码学分析能力,但可以验证加密算法的强度是否足够。
  • 故障注入测试(如果条件允许):在通信过程中,瞬间短接通信线到地或VCC,模拟glitch攻击。观察系统是否会崩溃、认证是否会意外通过(这是最危险的)。稳健的系统应能检测到通信异常并转入安全失败状态。

常见问题速查表:

问题现象可能原因排查步骤与解决方案
完全无法通信,总线始终为高1. 硬件连接断路。
2. 某一端GPIO未正确初始化(应为输入上拉)。
3. 外部上拉电阻损坏或未焊接。
1. 用万用表检查通路。
2. 检查代码中GPIO方向寄存器(DDR)和数据寄存器(DR)的初始化值。
3. 测量总线电压,无驱动时应为VCC。
通信不稳定,偶发错误1. 时序容差设置过紧。
2. 中断干扰导致计时不准。
3. 电源噪声大。
4. 软件未处理定时器溢出。
1. 适当增大协议中的时间容差范围(如±7%)。
2. 提高通信相关中断的优先级,或在不敏感时段关闭全局中断。
3. 加强电源滤波,检查旁路电容。
4. 在接收代码中加入对定时器溢出标志的检查和处理。
安全芯片能收到数据但认证总失败1. 主从双方加密算法或密钥不一致。
2. 随机数生成器种子相同,导致挑战序列可预测。
3. 字节序(大小端)处理错误。
1. 分别计算并打印(可通过调试口)双方的中间结果(如计算出的C1‘),进行比对。
2. 确保随机数种子来自ADC读取的悬空引脚噪声或未初始化RAM值。
3. 检查数据在内存中的存储和传输顺序是否一致。
系统运行一段时间后认证失败1. 看门狗复位导致状态不同步。
2. 安全芯片进入低功耗模式后时钟漂移。
3. 失败计数器达到阈值,系统已锁定。
1. 在认证关键流程中临时暂停看门狗喂狗。
2. 避免在通信间隙将安全芯片置于深度睡眠。
3. 检查Flash中存储的失败计数标志位。

5.3 超越基础方案的进阶安全加固思路

对于安全性要求更高的场合,可以在上述基础方案上进行强化:

  1. 动态密钥协商:每次认证不直接使用预置的静态密钥,而是基于一个预置的根密钥,结合当前会话的随机数,通过密钥派生函数(KDF)动态生成本次会话的临时密钥。这样即使一次会话的密钥被破解,也不会影响其他会话。
  2. 协议随机化:不要每次都发送固定结构的认证数据。可以随机化挑战值的长度、在数据流中插入随机填充字节、甚至轻微随机化PWM占空比的基准比例(例如‘0’的占空比在35%-45%之间随机选择)。这增加了通过简单模式匹配进行攻击的难度。
  3. 物理攻击防护:如果担心攻击者使用探针直接读取芯片间的电信号,可以考虑在PCB布局时将连接线走在内层,并用接地层包裹。或者使用芯片的“开漏”模式配合外部上拉,这样总线上的信号电压摆幅更小,更难探测。更极致的,可以在通信信号上叠加一个高频的、随机的小幅度噪声,让示波器难以捕捉到清晰的数字波形。
  4. 将关键功能分散:不要把所有安全逻辑都放在68HC08里。可以将一部分认证逻辑或密钥片段放在主处理器中,两者结合才能完成完整功能。这样,攻击者必须同时攻破两个芯片才能成功,难度呈指数级增加。

这套以68HC08和单线协议为核心的IP保护方案,其精髓在于“巧思”而非“蛮力”。它用最低的硬件追加成本,通过系统级的软硬件协同设计,构建了一个非对称的防御体系:对于合法开发者,集成和调试是直观的;对于潜在的抄袭者,逆向和仿制则变得异常繁琐和不确定。在实际项目中落地这一方案后,我最大的体会是,嵌入式安全是一个系统工程,没有一劳永逸的银弹。但这个方案提供了一个极佳的起点,它迫使你在设计之初就将安全作为一项功能来思考,而不是事后补救。当你看到克隆市场上迟迟没有出现你的产品的高仿品时,你就会觉得这些额外的工作都是值得的。最后一个小建议是,在资源允许的情况下,不妨在安全芯片里多预留一些Flash空间,未来如果需要升级认证算法或增加新的安全功能,你会感谢自己当初的这个决定。

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

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

立即咨询