
简介面向电力自动化继电保护场景这套IEC103协议实现资料可在PIC33F微控制器上完成规约栈的移植与通信对接帮助开发者解决保护装置与上位机之间数据交换、远程监控及故障处理等问题。资料以ASDU/TCU报文结构为主线覆盖串口参数配置、协议栈构造与解析、启动/响应/确认/否定等消息处理、CRC校验与错误重传机制并兼顾身份验证等安全设计同时提供P103GenDef.h、P103Gen.c、P103.c、P103.h源码可直接结合PIC33F外设接口进行二次开发。压缩包共4个文件以C源文件和头文件为主整体仅25KB代码结构清晰也适合通过串行RS232、RS485等链路层快速集成。已有498人学习浏览适合具备基础单片机知识、正在从事继电保护或电力系统通信研发的工程师参考实践无论是协议学习还是产品原型验证这套资料都能提供较完整的落地参考。1. 为什么保护继电器要跑 IEC103以及 PIC33F 的边界IEC103 是 IEC 60870-5-103 的常见简称它专门解决保护继电器protection relay与站级监控后台之间的信息交换保护动作、告警、故障报告、定值整定和遥控分合闸。相比 Modbus 要自己定寄存器表IEC103 的 ASDU 类型和信息序号已经为电力语义预制好了后台侧几乎不用改应用就能接上不同厂家的保护装置。PIC33F 是 Microchip 的 dsPIC33F 系列16 位 DSC片上带 UART、DMA、定时器和 Flash做单装置从站很合适但它没有像 ARM 那样大的内存跑完整协议栈时 flash 和 RAM 都会紧张。所以用 PIC33F 做 IEC103 从站核心不是把协议栈代码堆进去而是把状态机、缓冲区、参数索引做小并做对。这篇文章从协议帧格式、ASDU 语义、PIC33F 外设配置到过检参数按一个可复现的从站工程来展开。2. IEC103 协议栈拆解ASDU、监视方向与控制方向的帧格式2.1 从站侧必须理解的帧类型和链路启动流程IEC103 的物理层走串行口波特率常见为 9600、19200 或 38400。链路层采用 FT1.2 帧一种固定帧长4 个字节用于链路启动、召唤确认等另一种可变帧长用于携带 ASDU。固定帧长帧以0x10开头可变帧长帧以0x68开头。很多新手把0x68后面的长度字段直接当总长度其实它后面还有一个长度复制字节实际有效长度只出现一次两边不一致时应当把这一帧丢掉回退到等待帧头。从站上电后的典型流程是从站发0xE5呼叫主站或等待主站发复位流程主站回确认再发复位链路命令然后开始周期召唤。在 PIC33F 上这段流程不需要做成多线程串口中断加状态机就够了。关键是把以下 4 类报文区分开主站到从站的复位、总召唤命令从站到主站的确认帧从站自发事件保护动作、告警上送主站到从站的遥控、定值写操作。固定帧长帧结构简单接收时只要判断控制字和地址是否符合本装置。可变帧长帧则需要按状态机逐字节解析下面是一段适用于 PIC33F 的接收初始化逻辑#define FRAME_START_VAR 0x68 #define FRAME_END 0x16 typedef enum { WAIT_HEAD, WAIT_LEN, WAIT_LEN_REP, WAIT_DATA, WAIT_END } fsm_state; unsigned char rx_buf[128]; unsigned char rx_len; unsigned char rx_index; unsigned char rx_sum; fsm_state st WAIT_HEAD;rx_buf保存从控制域开始的内容rx_sum用于计算帧校验和。这里的校验和是控制域、地址域和 ASDU 所有字节的算术累加取低 8 位不包含0x68、长度字节和结束符0x16。服务端处理时把累加结果和收尾的校验字节比较一致再等待0x16这样能过滤掉大部分干扰。2.2 ASDU 的公共地址和功能类型怎么映射到保护装置应用层数据单元 ASDU 由类型标识、可变结构限定词、传送原因、公共地址、功能类型、信息序号和数据块组成。保护继电器的每条信息在 IEC103 里用“功能类型 FUN 信息序号 INF”定位。一个保护装置可能有多个功能比如过流、接地、重合闸每种功能分一个 FUN。公共地址则是一个装置在站里的编号范围通常是 1 到 255。下面的表格列出从站组包时最常用到的字段ASDU 字段长度典型值/范围在保护继电器中的含义类型标识11单点信息决定 ASDU 的数据结构可变结构限定词10x81高位为 1低 7 位表示信息对象个数传送原因11、2、3、20周期、突发、总召唤、响应等公共地址11 到 255装置在站内的地址功能类型 FUN1128 以上保护功能类型信息序号 INF10 到 255功能内的具体事件或状态这里的 FUN 和 INF 需要和后台组态严格对齐。后台组态里往往写的是“过流一段动作”对应到从站端就是某个 INF 的数值。PIC33F 资源少不需要为每一个 INF 建一个结构体常见做法是准备一张线性表info_table[下标]存 FUN、INF、当前值、变化标志。每次收到总召唤从站沿着这张表把所有状态按周期传送 ASDU 上送保护动作产生时再把对应表项标记为突发放到发送队列。2.3 控制方向命令的接收与确认遥控命令在主站到从站方向上常见类型有 C_SC_NA_1单点命令、C_DC_NA_1双点命令等。以单点命令为例ASDU 里会带一个命令值字节其中 bit0 是执行值bit3 是选择/执行标志选择为 1执行为 0。从站收到命令后要分两步先回确认帧再执行装置动作动作完成后通过监视方向发带变位标识的事件。下面是一个处理控制方向 ASDU 的简化函数类型标识取常见的0x2D实际工程以自己的协议文档为准int handle_control_asdu(unsigned char *asdu, unsigned char len) { unsigned char type asdu[0]; if (type ! 0x2D) { return -1; } unsigned char fun asdu[4]; unsigned char inf asdu[5]; unsigned char cmd asdu[6]; unsigned char sel (cmd 3) 0x01; unsigned char exec cmd 0x01; if (sel 0 exec 1) { relay_set_output(fun, inf, 1); } else if (sel 0 exec 0) { relay_set_output(fun, inf, 0); } return 0; }这个函数不做直接输出而是把fun/inf对应的输出口或继电器线圈写一遍。真正的装置逻辑里执行前还要检查闭锁条件比如重合闸未充电、手动分闸、远方禁止这些都要在relay_set_output之前判断。收到sel 1时不少装置要求只置“预演”状态不驱动出口收到exec 0的后续帧才真正变位。3. 在 PIC33F 上搭建 IEC103 从站的工程骨架3.1 串口与定时器资源分配UART DMA TMR 的最小配置PIC33F 跑协议栈最怕的是串口中断被其他中断长时间阻塞导致丢字。常见做法是串口接收用 DMA 环形队列定时器产生 1ms tick 驱动帧间隔判断。下面是 UART1 初始化示例假设系统时钟FCY 40 MHz#define FCY 40000000UL void UART1_Init(unsigned int baud) { U1MODE 0x8000; // UARTEN18位数据无校验1停止位 U1BRG (unsigned int)((FCY / (16UL * baud)) - 1); U1STA 0x0440; // TXEN1接收中断在收到一个字节时触发 IFS0bits.U1RXIF 0; IEC0bits.U1RXIE 1; // 使能接收中断 IPC2bits.U1RXIP 5; // 中断优先级 5 }PIC33F 的U1BRG公式是FCY / (16 * 波特率) - 1如果波特率是 19200计算结果约为 128实际误差低于 0.1%。这里没有开硬件奇偶校验因为 FT1.2 帧的标准做法是每个字符带一个偶校验位但很多站控后台和测试仪实际配置为“无校验”。如果后台严格按标准启用偶校验需要把U1MODE中的PDSEL设置为 01否则链路层会因为校验错误频繁丢帧。同一定时器还用来做帧间隔超时判断void Timer1_Init(void) { T1CON 0x8000; // 使能 TMR1内部时钟1:1 预分频 PR1 9999; // 1ms 中断FCY40MHz IFS0bits.T1IF 0; IEC0bits.T1IE 1; IPC1bits.T1IP 2; }1ms tick 递增一个全局变量从站间的快速帧间隔检查和链路超时都靠它。串口中断里只做字节入队和状态机推进ASDU 的解析放到主循环避免中断处理时间过长影响接收。3.2 用状态机解析 IEC103 可变帧长报文接收状态机每收到一个字节前进一步。整体流程是等待0x68读长度校验长度复制字节按长度收数据同时累加校验和收完数据后和收到的校验字节比较再等0x16。完整的中断处理如下void __attribute__((interrupt, no_auto_psv)) _U1RXInterrupt(void) { unsigned char b U1RXREG; IFS0bits.U1RXIF 0; switch (st) { case WAIT_HEAD: if (b 0x68) { st WAIT_LEN; } break; case WAIT_LEN: rx_len b; st WAIT_LEN_REP; break; case WAIT_LEN_REP: if (b ! rx_len) { st WAIT_HEAD; break; } rx_index 0; rx_sum 0; st WAIT_DATA; break; case WAIT_DATA: if (rx_index rx_len) { rx_sum b; rx_buf[rx_index] b; } else { if (b ! rx_sum) { st WAIT_HEAD; break; } rx_index; st WAIT_END; } break; case WAIT_END: if (b 0x16) { frame_ready 1; } st WAIT_HEAD; break; } }这段代码里有一个细节rx_index rx_len时累加的是数据字节进入数据完毕分支时下一个字节才是校验和所以校验和本身不算进rx_sum。这正好对应 FT1.2 的校验范围。中断收到完整帧后只设置frame_ready标志主循环检测到标志后从rx_buf里读控制字和 ASDU。好处是中断耗时短不容易在高波特率下丢字节。3.3 保护继电器数据采集与遥信变化上送保护继电器通常有若干路遥信输入和输出接点。PIC33F 用 GPIO 直接采样遥信时需要在主循环里做软件消抖常见做法是每 10ms 读一次连续两次一致才认为状态有效。状态变化后立即组 ASDU放入发送队列unsigned char old_status[8]; void scan_inputs(void) { unsigned char new_status[8]; for (int i 0; i 8; i) { new_status[i] (PORTB i) 0x01; if (new_status[i] ! old_status[i]) { old_status[i] new_status[i]; asdu_enqueue_event(128, i 1, new_status[i]); } } }这里的asdu_enqueue_event负责生成一个监视方向 ASDU功能类型 128 表示保护事件INF用通道号加 1 区分。实际项目中FUN128、INF1等编号要在后台组态里核对过不同厂家对功能类型的定义不完全一致。事件上送后后台会回一个确认从站需要把该事件从发送队列里清除如果当前总召唤正在进行事件 ASDU 要排在总召唤响应之后避免破坏帧序号。3.4 一个完整 IEC103 从站工程里常见的文件划分标题里带“完整”字样一般期望拿到的是一套能直接编译的工程而不只是一段协议。按常规做法我会把 PIC33F 的工程拆成下面几部分src/ link.c // 帧接收状态机、校验和、发送缓冲 asdu.c // ASDU 组包/拆包、FUN/INF 映射表 app.c // 保护功能逻辑、遥信采样、输出控制 uart.c // PIC33F 串口、DMA、定时器初始化 main.c // 主循环调度link.c不关心 ASDU 内容只负责把字节流里的链路帧还原出来asdu.c在上面一层处理应用数据app.c把保护继电器的实际输入输出映射到 INF。这样分层以后协议栈部分几乎不用改动换保护功能时只改app.c和映射表。4. 让 IEC103 通信过检时序参数、重发机制与常见坑4.1 帧间隔、超时与重发参数表拿到一套新后台或者综自测试仪第一步是对齐时序参数。IEC103 标准对很多时间没有强制值但现场联调一般用下面这组经验参数参数项建议值说明传输波特率19200 或 9600与后台和测试仪一致字符间隔小于 2 个字符时间超过则认为断帧帧间隔大于 10 ms发送相邻可变帧之间的最小间隙链路超时1000 ms从站收到命令后发出确认的时限重发次数3主站对未确认命令的重试次数单帧 ASDU 数量不超过 10 个避免事件风暴堵死缓冲区在 PIC33F 上实现并不难串口收到一个字节时记录当前 tick主循环里每 1ms 检查一次如果超过设定间隔还没收到下一个字节就把状态机复位到WAIT_HEAD。重发机制一般由主站处理从站只需要检测控制域里的FCB是否翻转。4.2 常见坑校验和范围、FCB/FCV 位翻转、数组越界最容易栽的坑是校验和范围算错。很多移植代码会把0x68和长度字节一起累加结果永远是错的。正确做法是从控制域开始到 ASDU 最后一个字节结束算术累加取低 8 位。调试时用协议分析仪抓一帧标准报文逐字节比对累加过程就能确定是组包还是收包的问题。第二个坑是FCB/FCV处理。主站发送带FCV1的命令时期望从站看到FCB翻转。如果从站没有保存上次的FCB会把重发命令当成新命令执行两次导致断路器重复分闸。处理逻辑如下static unsigned char last_fcb 0; unsigned char handle_control_field(unsigned char ctrl) { unsigned char fcb (ctrl 4) 0x01; unsigned char fcv (ctrl 5) 0x01; if (fcv) { if (fcb last_fcb) { return 1; // 重发帧只回确认不执行 } last_fcb fcb; } return 0; }第三个坑是 PIC33F 内存越界。rx_buf的大小不能只按最大帧长给还要留出后台可能一次性下发多个 ASDU 的余量。现场数据变化频繁时如果发送队列长度不够新的事件就会覆盖未发送的旧事件。常见做法是发送队列做成环形缓冲满了以后丢弃最旧的事件同时置一个“事件丢失”标志在下次总召唤时上送。4.3 用监控软件验证从站是否符合 IEC103联调时我一般先不用后台直接用 PC 串口工具模拟主站字节流。第一步发E5从站应回链路确认第二步发复位链路命令从站回确认第三步发总召唤命令从站应先回总召唤确认再按顺序上送全部状态。抓包时重点看这几个点每帧开头是不是0x68结尾是不是0x16长度字段和第二个长度字节一致校验和指向的字节范围正确总召唤响应帧的传送原因是“总召唤”而不是“突发”事件上送后后台是否回了确认确认后从站是否清空对应发送缓冲。串口工具上看不到标准 ASDU 语义遇到后台解析不了时再用正式综自测试仪抓完整报文。PIC33F 这边可以用一个调试 UART把接收到的原始帧打印出来或者用逻辑分析仪在 UART TX 脚上抓电平确认没有发送时序错误。5. 把故障录波和定值整定跑进同一个 IEC103 从站5.1 在有限 RAM 里做分块传输IEC103 比普通通信规约强的一点是支持故障录波和定值整定这两块正好是保护继电器最需要上报的数据。故障录波在 PIC33F 上做不了太大因为 RAM 限制最多存几个通道、几百个采样点。常见做法是在保护动作触发时把采样数组冻结到内部 Flash 的一块区域等主站召唤故障报告时再分块上送。故障录波的 ASDU 类型标识一般是 7故障录波数据部分包含故障时间、录波通道数、采样频率和原始采样值。PIC33F 上最容易出现的问题是 Flash 擦写次数和响应时间录波写入使用 DMA 或者逐字拷贝中断期间要关掉看门狗主站每帧最多要 10 个 ASDU每一块对应一小段时间窗口从站需要维护一个发送状态记录当前已经传到的通道号和采样点序号。定值整定则是控制方向的逆操作。主站下发定值组从站先把定值写入 RAM 中的影子区所有定值校验完成后才一次性写入 Flash。绝不能逐条直接写 Flash否则后台中途断开装置里会留下半套定值保护逻辑读出来可能就是错误的。影子区可以放在__attribute__((persistent))标注的内存段这样复位后还能保留上次未提交的数据帮助调试定位问题。对于一条新定值从站回确认后还要读一遍回传给主站让后台确认写进去的值没有翻转。这个“读回验证”在调试时很有用PIC33F 上定值一般用结构体数组保存每个定值带一个校验字主站召唤定值时直接从结构体里取不需要临时拼字节。最后一步是时间同步。IEC103 的故障录波和事件记录都带时标站控后台最看重这块。PIC33F 内部定时器做毫秒计数时间同步命令到达时校正基准时间并把second和millisecond存到同一个变量里。保护动作触发瞬间读取这个变量作为录波和事件的时间戳如果时间同步还没完成就置一个无效品质位后台收到后可按无效时间处理。调试时可以从读取时间同步报文开始验证确认 ASDU 里的时标字段与后台显示一致。本文还有配套的精品资源点击获取