简介:面向智能电网、电能表计与嵌入式开发领域,这是一份关于单相费控智能电能表(CPU卡)设计的专业参考文献。文档基于瑞萨R7F0C004M2DFB单片机,结合ESAM安全模块与CPU卡,提出完整的费控功能硬件设计框架和系统插卡主流程,涵盖ESAM芯片结构、CPU卡接口原理、7816通信及CRC校验等关键内容,适合电能表研发工程师、相关专业学生及技术支持人员参考。资源包仅含1个PDF文件,大小约1.27MB,内容精炼、图文并茂,便于快速阅读与查阅。目前已有150人学习。通过该文档,读者可系统了解基于ESAM安全模块的单相费控电能表设计思路,掌握SGC1118B安全认证芯片、RN8209C高精度计量芯片等器件的选型与接口设计方法,理解电源控制、7816通信、插卡流程等关键环节,对开展同类计量产品或安全认证方案开发具有直接借鉴意义。
1. ESAM不是卡,是电能表的安全边界
预付费电表早年用非接触射频卡,卡丢了被复制、扇区被读写器改写的案例不少。这类表最大的问题不是计量不准,而是“写进卡里”的电费余额可以被伪造。换成CPU卡后,卡内自带处理器和文件系统,但这只解决了本地介质的防伪,真正的安全边界其实是表内那颗不起眼的ESAM芯片。ESAM负责与主站加密机做安全认证,CPU卡只是搬运指令和电费的载体。这篇《基于ESAM安全模块的单相费控智能电能表(CPU卡)设计》给的方案里,MCU是瑞萨R7F0C004M2DFB,ESAM是南瑞SGC1118B,计量端是锐能微RN8209C,三者通过7816总线和脉冲计数配合完成远程、本地两套费控。做智能电表、物联网嵌入式、计量设备底层开发的人,可以从这套框图里找到可复现的接口设计、上电时序和扣费策略。
2. 从R7F0C004M2DFB到ESAM:单相费控表的硬件链路
2.1 ESAM的电源控制与上电时序
ESAM被设计成“按需上电”的工作方式。SGC1118B在空闲时不参与任何运算,MCU通过一根ESAM_SD控制线去开关外部三极管Q1,再通过Q1切断或接入ESAM的VCC。这样做的原因很直接:ESAM内部有硬件随机数发生器和加密协处理器,空闲时也是耗电的,国网表对整机功耗有严格限制,尤其是待机功耗,所以PDF里特意强调“不进行操作时,关掉电源,降低功耗”。
上电时序是硬件设计上最容易踩的坑。ESAM上电之后不能马上发7816指令,需要等内部时钟稳定。PDF中给出的要求是上电时间控制在100微秒以内,这个指标指的是从ESAM_SD置低到VCC稳定到工作电压的时间窗口——如果管子选型不当、基极电阻太大,上升沿会变缓,ESAM可能复位失败,后续ATR读取就会超时。常见做法是选低Vce(sat)的MOS管或三极管,并在VCC输出端并一个0.1微法陶瓷电容加4.7微法钽电容做去耦。
引脚分配在实物上大概长这样:
| 信号名 | 方向 | 功能 | 关键参数 |
|---|---|---|---|
| ESAM_SD | MCU输出 | 电源控制线 | 低电平使能,控制Q1通断 |
| SAM_IO | 双向 | 7816数据线 | 开漏输出,需上拉电阻 |
| CLK_SAM | MCU输出 | 7816时钟 | 4MHz方波,由MCU提供 |
| ESAM_VCC | ESAM电源 | 电压输入 | 工作电压2.7V~5.5V,实际用3.3V |
MCU初始化的伪代码如下:
void esam_power_on(void) { ESAM_SD = 1; // 先关闭ESAM电源,确保从冷启动开始 delay_us(50); // 等待残余电荷释放 ESAM_SD = 0; // 打开电源控制管,VCC开始上升 delay_us(100); // 等待VCC稳定,约100us r7816_set_clock(CLK_SAM, MHZ_4); // 提供4MHz通信时钟 esam_cold_reset(); // 发送7816冷复位信号,进入ATR流程 }这段代码里有几个点值得注意。ESAM_SD = 1是关闭ESAM电源而不是打开,因为PDF里明确说“ESAM_SD为电源控制线,通过控制Q1的通断来开关ESAM的电源”,这里设计为低电平驱动三极管导通,所以置1是断开、置0是导通。delay_us(100)在合入正式代码前最好用示波器实测一下ESAM_VCC的上升沿,如果VCC稳定时间超过100微秒,说明管子驱动能力不足,这种情况下延长延时时间只能掩盖问题,治标不治本。r7816_set_clock需要在芯片手册确认该MCU的7816外设是否支持直接分频输出4MHz,R7F0C004M2DFB内置两路7816接口,可以直接配置,不需要用定时器翻转IO模拟时钟。
2.2 CPU卡座的机械检测与中断机制
CPU卡插卡检测是另一个容易出问题的点。卡座内部有一根机械弹片,插卡时弹片被顶下去,把CARD_INT信号线和GND接通,电平从高变低,MCU的外部中断脚检测到下降沿,就知道有卡插入。抽出时弹片复位,CARD_INT被主板上的上拉电阻R53拉回高电平。这个机制不依赖IC卡的电信号,纯粹是机械结构触发的,所以哪怕卡没插到位、7816触点和芯片没接通,MCU至少能先收到插卡事件。
这里有个设计细节:上拉电阻的值不能随便选。R53阻值太小,插卡瞬间拉低电平时的电流会偏大,磨损卡座簧片;阻值太大,在潮湿环境下噪声干扰容易把高电平误判成低电平触发中断。我一般在10k到47k之间选,配合MCU内部的上拉,取22k比较稳妥。
检测代码一般长这样:
void EXTI_IRQHandler(void) { if (CARD_INT == 0) { // 电平被拉低,卡已插入 card_power_on(); // 置CARD_SD为低,CPU卡上电 rtos_evt_set(CARD_INSERT_EVT); // 通知应用层处理插卡事件 } }注意CARD_INT == 0的判断不能省。外部中断有毛刺,如果只依赖下降沿触发,一次插卡动作可能因为弹片抖动而触发多次,卡还没完全到位就初始化读卡,ATR解析会失败。处理方式是进入中断后延时去抖,再确认电平状态确实为低才给卡上电。
2.3 RN8209C计量芯片与脉冲累计
计量芯片选的是锐能微RN8209C,这是单相计量里用得比较多的方案。它负责电压电流采样、有功功率计算,最终把电能转换成脉冲输出给MCU。MCU拿到的是和用电量成比例的脉冲数,每累计到一定数量,就代表消耗了0.01度电。MCU内部用定时器捕获脉冲上升沿,配合RTC时间戳记录事件,形成一条可追溯的阶梯电价记录。硬件上需要注意脉冲输出脚和MCU捕获脚之间的滤波,RN8209C的脉冲输出本身有固定脉宽,但如果PCB布线过长,建议加一个RC滤波,避免脉冲信号叠加上高频干扰导致多次计数。
3. 7816通信与SM1安全认证的实现
3.1 T=0协议下的APDU报交互流程
ESAM和MCU之间的通信基于ISO/IEC 7816规范,SGC1118B支持T=0协议和SPI两种模式。在实际电表设计里,通信首选7816,因为CPU卡和ESAM可以共用MCU的两路7816外设,不需要额外复用IO。7816的T=0是异步半双工字符传输协议,一帧数据有起始位、8个数据位和一个奇偶校验位,MCU作为主设备提供时钟,ESAM作为从设备接收指令并响应。
APDU的交互格式是统一的:MCU发送命令头(CLA、INS、P1、P2),后面跟着命令数据域和数据长度,ESAM处理后返回状态字。以远程身份认证为例,命令交互格式大致如下:
| 字段 | 值 | 含义 |
|---|---|---|
| CLA | 0x00 | 命令类别,ISO7816标准类别 |
| INS | 0x88 | 内部认证指令 |
| P1-P2 | 0x00 0x00 | 认证类型参数 |
| LC | 0x08 | 随机数长度,8字节 |
| DATA | 8字节随机数 | MCU从ESAM读取的挑战值 |
| LE | 0x08 | 期望返回8字节认证结果 |
uint8_t esam_transmit_apdu(const uint8_t *apdu, uint16_t apdu_len, uint8_t *resp, uint16_t *resp_len) { uint16_t i = 0; r7816_clear_rx_fifo(SAM_PORT); r7816_write_buf(SAM_PORT, apdu, apdu_len); // 发送APDU命令头和数据 i = 0; while (i < 2) { // 等待状态字SW1/SW2 if (r7816_rx_available(SAM_PORT)) { resp[(*resp_len)++] = r7816_read_byte(SAM_PORT); if (*resp_len >= apdu[4] + 2) break; // 数据接收完后补读状态字 } if (timeout_expired()) return ERR_TIMEOUT; } return (*resp_len >= 2) ? ERR_NONE : ERR_SHORT_RESP; }这段代码里有几个参数需要根据实际ESAM的手册调整。apdu[4]是LC字段的位置,代表命令数据域的长度,ESAM返回的数据长度未必和LC一致,有些命令是定长返回,有些是变长返回,稳妥的做法是循环读取直到收齐SW1/SW2状态字,状态字0x9000代表成功,0x6982表示安全状态不满足,需要查ESAM的指令手册定位具体原因。
PC/SC规范里PC_to_RDR_IccPowerOn的冷复位时序和这里ESAM冷复位是同一个概念,只是载体从读卡器换成了MCU。如果7816通信不稳定,先别急着调代码,用示波器看数据线IO上的波形,确认重点是起始位后的第一个字节是否完整——我之前碰到过ESAM偶发无响应,最后定位到是MCU配置的波特率因子和时钟分频不匹配,导致采样点跑到了错误的边沿上。
3.2 从随机数到外部认证的两次握手
ESAM最核心的能力是安全认证。远程费控时,主站加密机和表端ESAM之间做身份认证,流程里有个关键动作:主站下发随机数给ESAM,ESAM用内部存储的国密SM1密钥对随机数做加密运算,再配合MAC值一起返回主站。主站验证通过后,后续的充值、参数更新命令才被允许执行。
这里值得展开说的是这个流程为什么反过来也成立。ESAM反向认证主站是另一回事:主站给表下发指令时,ESAM要对指令中的MAC码做验签,MAC码不对,命令直接拒绝执行。所以ESAM的加密引擎是双向的,既能产生密文,也能验证MAC。设计PDF里提到“重要数据增加CRC校验”要和MAC区分开:CRC防止的是传输噪声造成的随机错误,MAC防的是攻击者人为篡改报文,两套机制不能互相替代。
3.3 关键数据的CRC校验实现
CRC校验在这里不是为了防攻击,而是防丢电瞬间的数据写坏。智能电表常数掉电,如果正在写EEPROM中途断电,很可能写入一半的脏数据——加上CRC后在读回时就能发现数据不完整,回退到上一次备份。
static uint8_t crc8_table[256]; // 查表法,多项式0x07,与国网表常用规范保持一致 void crc8_init(void) { uint8_t i, j, crc; for (i = 0; i < 256; i++) { crc = i; for (j = 0; j < 8; j++) { if (crc & 0x80) crc = (crc << 1) ^ 0x07; else crc <<= 1; } crc8_table[i] = crc; } } uint8_t crc8_update(uint8_t crc, const uint8_t *data, uint32_t len) { for (uint32_t i = 0; i < len; i++) { crc = crc8_table[crc ^ data[i]]; } return crc; }多项式选择0x07是CRC-8的常用标准,和国网表内部常用的校验方式一致。计算时初始值设为0x00,计算完直接追加到数据块末尾。读数据时重新对“数据区”做一次CRC计算,和存储的CRC字节做比对,不一致就说明数据异常,此时需要从备份区恢复上次正常值。这段代码建议放到掉电检测中断里配合执行——检测到掉电即将发生时,优先把CRC计算结果写入到EEPROM的当前帧末尾,再进行整表复位。
4. 本地费控与远程费控:一套安全体系两条业务链
4.1 CPU卡的类型划分与生命周期
CPU卡在本地费控里承担的是“资金介质”角色。PDF中把卡分成三类:用户卡、参数预置卡、补卡。用户卡负责开户、充值和查询,参数预置卡用来设置阶梯电价、费率等参数,补卡是为用户丢卡后重新发卡准备的。这三类卡在文件系统上是有隔离的,互相不能越权读写。
| 卡类型 | 主要功能 | 关键文件区 |
|---|---|---|
| 用户卡 | 开户、充值、查询余额 | 钱包文件、末次交易记录 |
| 参数预置卡 | 阶梯电价、费率时段参数下发 | 费率表文件、密钥更新文件 |
| 补卡 | 丢卡后重发,继承原卡余额 | 用户信息文件、交易序号文件 |
卡片插入后,MCU先执行复位和ATR解析,拿到卡的操作系统AID,再根据发卡方信息判断是哪种卡片类型,进入对应的处理分支。这里不能根据用户说什么就处理什么,而是每张卡的MF(主文件)下挂了安全属性,MCU读卡时先检查目录结构,确认当前卡是否有权限访问目标文件。用户卡去读参数预置卡的费率文件,会被卡操作系统拒绝,不需要MCU做额外的业务判断。
4.2 插卡中断与读卡主流程
插卡事件来了以后,处理流程是固定的:卡上电、复位、解析ATR、选择应用、身份验证、执行操作、写卡、卡下电。PDF中给了主流程,其中有两个细节值得关注。一是卡片认证失败时要立即断电下卡,不能重试超过三次,防止有人用暴力试探的方式破解读卡密钥;二是每一步写卡操作之前都要先读卡上的旧CRC和数据,如果发现异常,置卡为异常状态,转人工处理。
void card_insert_task(void) { card_power_on(); // CARD_SD置低,卡座上电 if (atr_reset() != ERR_NONE) { card_power_off(); // 复位无响应,直接下电 return; } if (card_select_app(APP_ID_ELECTRICITY) != ERR_NONE) { card_power_off(); return; } if (card_verify_security() != ERR_NONE) { card_power_off(); // 身份验证失败,立即下电 return; } process_user_card(); // 执行充值或查询 card_power_off(); // 操作完成,关闭卡座电源 }这段代码的逻辑清楚,但真正实现时要注意process_user_card内部一定不能被中断嵌套调用外部中断去读卡。卡操作是时序敏感的,如果一个中段跑到一半再回来,7816的接收缓冲可能已经溢出,ATR和APDU响应都会乱掉。常见做法是插卡事件用信号量通知一个独立的低优先级任务去处理,主循环只负责挂起等待。
4.3 远程费控的安全通道建立
远程费控的通信链路上,MCU和主站之间的报文会经过集中器、通信模块,最后才到表端。中间任何一段都有可能被篡改,所以远程命令不能直接信任网络层。ESAM在这里充当安全网关的角色:MCU把收到的远程充值命令原样转发给ESAM,ESAM验MAC,验通过才执行扣减,并在表内保存一条记录,供后续主站数据回抄时核对。
回抄流程同样要过ESAM。主站下发“读取当前累计电量”的命令,MCU收到后先从计量芯片读电量,再把电量数据交给ESAM计算MAC,然后组装成上行报文返回集中器。主站解密验MAC后,确认这条数据确实来自该表端ESAM,没有经过中间人篡改。这样做的好处是主站与表端之间逻辑上建立了端到端的信任关系,中间节点即使被攻击也不影响资金安全。
5. 扣费窗口与中断服务:费控的实时性设计
5.1 电能脉冲累计与0.01度触发逻辑
费控电能表的计量粒度细到0.01度。RN8209C输出的脉冲代表固定的电能当量,MCU每捕获到一个脉冲就在RAM里累加,当累加值达到0.01度对应的脉冲数时,触发一次计费逻辑。
#define PULSES_PER_CENT_KWH 100 // 假设每度电10000脉冲,则0.01度=100脉冲 void PULSE_IRQHandler(void) { pulse_count++; if (pulse_count >= PULSES_PER_CENT_KWH) { pulse_count = 0; billing_pending = 1; // 置位计费标志,不在中断里直接算钱 } }这个设计的关键是中断里只做计数和标志置位,不执行乘法运算和ESAM写操作。因为ESAM扣款要走7816协议,一次完整交互在几十毫秒到几百毫秒之间,中断里做这件事会占用大量时间,把其他低优先级的时隙都挤掉。正确做法是主循环里检测到billing_pending后,调用ESAM扣款接口。ESAM扣款的原子性由ESAM自身的文件系统来保证,即使扣款中途掉电,ESAM也会在下次上电时通过内部事务日志回滚或重放未完成的扣款操作。
5.2 扣费触发的五种条件与15分钟窗口
PDF里列了五种扣款场景,虽然只有几行文字,但每条背后都有讲究:
| 触发条件 | 执行动作 | 设计意图 |
|---|---|---|
| 收到远程查询余额、插卡、掉电异常时 | 立即扣款 | 触发金额和电量对账 |
| 15分钟内连续收到查询余额命令 | 后续命令不扣款 | 防止高频查询刷掉余额 |
| 报警2且余额为0 | 立即扣款并拉闸 | 欠费断电 |
| 认证通过的卡插入 | 立即扣款 | 卡上余额同步到表内 |
| 正常运行时每15分钟判断一次 | 用电金额≥1元才扣款,否则顺延 | 最低扣款间隔约束 |
这里最有意思的是第二条。为什么15分钟内连续查询余额就不扣款?因为远程查询余额命令如果不加限制,主站端异常或攻击者伪造成主站反复查询,每次都会触发ESAM的写操作。EEPROM的擦写寿命是有限度的,SGC1118B标称50万次重复擦写,虽然不低,但经不住高频拆写。限制扣款频率,本质上是保护ESAM存储介质的寿命,同时也是防止异常流量导致ESAM始终处于忙状态,影响正常计量和通信。
5.3 中断优先级分配与功耗控制
电能表里涉及的中断资源很多:外部中断(插卡检测)、定时器中断(脉冲捕获和15分钟窗口)、UART/7816中断(ESAM和CPU卡通信)、RTC中断(定时任务)以及掉电检测。中断优先级分配不当,会出现插卡瞬间脉冲计数丢失,或者ESAM通信被打断导致ATR超时。
| 中断源 | 优先级 | 说明 |
|---|---|---|
| 掉电检测 | 最高 | 掉电瞬间必须保存关键数据到EEPROM |
| 脉冲捕获 | 高 | 计量脉冲不能丢,丢一个脉冲就丢电量 |
| RTC闹钟 | 中 | 15分钟窗口和阶梯电价切换 |
| 外部插卡中断 | 中 | 检测插卡事件,从休眠中唤醒 |
| 7816通信 | 低 | ESAM/CPU卡交互可以稍等片刻 |
实践中我一般把脉冲捕获放在外部中断最高等级的下一级,因为掉电检测触发后MCU可能正在跟ESAM通信,这时通信可以放弃,DSRAM中的数据必须在电压跌出工作范围之前写下。7816放在最低位是等清醒状态再来处理,反正对上电时序的要求是100微秒内稳定,通信时错个几百微秒问题也不大。
6. 验证方法:示波器抓ESAM时序与CRC探针
6.1 上电与插卡时序的实测判据
代码调通之后,验证重点放在硬件时序上。先用示波器勾在ESAM_VCC上,触发源选ESAM_SD的下降沿,测量从置低到VCC稳定到3.3V所需的时间。合格的波形应该是单次单调上升、无回沟、无台阶,稳定时间不超过100微秒。如果波形出现先冲高再回落再爬升,说明电源部分存在过冲,ESAM在上电瞬间可能误复位,表现为ATR返回不稳定。
然后测7816的时钟信号。CLK_SAM在ESAM上电后应该立刻出现4MHz方波,频率稳定,占空比接近50%。用示波器看数据线IO时,把探头夹在SAM_IO上,在MCU发ATR冷复位指令时抓波形,应该能看到先低后高的复位信号。
如果手头有逻辑分析仪,把它的通道挂到SAM_IO和CLK_SAM上,可以直接解码7816 T=0协议,读到ESAM返回的ATR字符串。ATR中的TS字节是0x3B,代表直接约定模式,后续字节里包含TA1、TB1、TC1等参数,能直接读出ESAM支持的工作参数——比如时钟速率、最大波特率因子等。
6.2 用CRC探针定位数据篡改
最后分享一个调试技巧:在代码里留一个“CRC探针”。当读写EEPROM里的关键参数时,把读出的数据和计算出的CRC一起通过调试串口打印出来。正常状态下,CRC应该是固定的;如果某次断电后重启,CRC校验失败,通过串口打印就知道是哪一条数据被写坏了,或者哪个地址的EEPROM实际擦写次数已经接近上限。
这个探针在尾调阶段很有用。新版固件上线后,现场反馈某批次表经常“掉参数”,远程查看日志一切正常。这时打印的CRC日志就能看出是某一条数据固定过不去校验,指向具体的EEPROM地址范围——大概率是掉电瞬间正在写该地址且写操作被中断了。修复方案是把单帧数据写入改成双备份交替写,并加上掉电检测中断提前介入,而不是简单增加延时等待。
SGC1118B的数据保持时间是10年、重复擦写50万次,这两个指标是给设计上限做参考的,不代表你可以每天高频写卡。代码层面加扣费窗口、电源层面做掉电保护、存储层面做CRC校验,这三层一起配合,才算是把ESAM的能力用到位了。想做进一步优化的人,可以再去翻PDF末尾引用的那篇《ESAM安全模块在预付费电表中的研究》,里面的应用场景和本文互补,对比着读会有收获。
本文还有配套的精品资源,点击获取