简介:一份基于51单片机的直流系统漏电绝缘检测程序源码,面向电子工程师、嵌入式开发者及电力安全监测领域学习者。程序针对直流系统中小电流漏电难以察觉的问题,通过对地电阻测量评估绝缘性能,实现实时检测与报警,可应用于直流屏、变电站等场景。资源压缩包共1个文件,为C语言编写的Mainok.c,包体大小约9KB,结构精简便于直接分析。目前已有132人学习浏览。透过源码可学习51单片机端口控制、定时器中断、ADC数据采集与阈值判断等核心编程方法,并理解漏电检测中信号采样、绝缘电阻计算及异常触发的完整实现逻辑。对于想掌握嵌入式底层开发或直流漏电监测方案的读者,是一份小巧而实用的参考案例。
1. 用51单片机写漏电绝缘检测程序,先分清测的是电阻不是电流
拿到Mainok_漏电_绝缘检测51程序_源码这个项目,先说结论:这类程序的核心不是“检测漏电”,而是“检测绝缘电阻”。漏电只是结果,绝缘电阻跌到阈值以下才是原因。Mainok 这块板子的应用场景通常是充电桩、光伏逆变器、储能电池包或者直流母线监控,这些系统的共同特点是无法用零序电流互感器直接测漏电,必须用直流激励法测正负极对地的绝缘电阻。51 单片机做这件事完全够用,关键在于把激励源控制、ADC 采样、电阻换算出阈值判断这几段逻辑梳理清楚。适合正在做充电桩从机、BMS 维护口监控或者刚接触绝缘检测的嵌入式工程师参考。
2. 漏电与绝缘检测原理:为什么用直流恒流激励而不是万用表直连
2.1 从绝缘电阻看漏电:漏电不是电流异常而是对地阻抗下降
交流设备的漏电检测用零序电流互感器,三相或单相电流矢量和不等于零,说明有对地泄漏。但直流母线不存在“矢量”的概念——电池包正极对地、负极对地是两个独立的绝缘通路,任何一个通路的阻抗跌到阈值以下就是漏电。
具体到电路模型,可以把电池包正极对PE、负极对PE分别看成一个等效电阻,用R_pos和R_neg表示。绝缘检测要回答的问题只有一个:这两个等效电阻当前是多少欧姆。搞清楚这一点,后面所有程序逻辑都是围绕“测电阻”而不是“测电流”展开的。
2.2 为什么不能用万用表直接量绝缘电阻
绝缘电阻和普通电阻有个本质差别:绝缘材料在直流电压施加的瞬间会产生极化电流和充电电流,阻值不是静止的。万用表电阻档输出的电压低、持续时间短,测到的只是表笔间分布电容的充电结果,和生产工况下的绝缘状态根本不是一回事。
所以绝缘电阻测试仪内部都是一台恒流源加高精度电压表:施加一个恒定的微小电流,等极化过程稳定后测量两端压降,再用R = U / I换算。51 单片机的绝缘检测程序沿袭同样的方法,单片机控制恒流源的开关,读回 ADC 值,再做换算和阈值判断。
2.3 恒流激励法在51程序里的实现逻辑
程序里需要控制外部恒流源电路完成一次完整的测量流程,逻辑如下:
void insulation_test_once(void) { // 先切断激励,让母线分布电容放电,等待5s让电压回落到安全范围 CURRENT_SOURCE_OFF; delay_ms(5000); // 接通恒流源,等待信号稳定(约200ms),跳过极化过程的剧烈变化段 CURRENT_SOURCE_ON; delay_ms(200); // 采集正极-地、负极-地的压降 uint16_t adc_p = adc_read_ch(0); // CH0接正极运放输出 uint16_t adc_n = adc_read_ch(1); // CH1接负极运放输出 // 换算成绝缘电阻(以Vref=5V, 10bit ADC为例) // 恒流源电流按100uA计算,运放增益为1 float vol_p = (float)adc_p * 5.0f / 1024.0f; float vol_n = (float)adc_n * 5.0f / 1024.0f; float r_ins_p = vol_p / 0.0001f; // R = U / I float r_ins_n = vol_n / 0.0001f; compare_and_report(r_ins_p, r_ins_n); }CURRENT_SOURCE_ON是普通 IO 口拉高驱动 MOS 管导通,adc_read_ch是单片机内置 ADC 的读取函数。延时 5 秒是为了让母线电容放电彻底,200 毫秒是跳过极化过程前段的快速变化区。
2.4 各电压等级下绝缘电阻阈值参考
阈值不能乱设,行业惯例和国标里有明确参考:
| 系统类型 | 标称电压 | 告警阈值(参考) | 说明 |
|---|---|---|---|
| 48V电动车电池包 | DC 48V | 10kΩ | 按100Ω/V估算 |
| 单相220V交流设备 | AC 220V | 0.5MΩ | 对应GB/T 4703.1 |
| 400V直流充电桩 | DC 400V | 40kΩ | 按100Ω/V,新能源行业常见设定 |
| 800V高压直流母线 | DC 800V | 80kΩ | 高压系统通常要求更高 |
阈值建议写入 EEPROM,程序启动时统一读取,调试时通过串口命令修改,避免每次改程序重新烧录。
提示:阈值不是固定的,具体数值要以现场安全规范为准,上面的表只作为程序初始化的参考值。
3. 51单片机绝缘检测硬件电路:信号链设计和采样电阻选型
3.1 激励通路的器件选型
硬件决定程序上限。51 单片机绝缘检测最常见的硬件错误是运放选偏,导致 ADC 读数在小电阻段饱和,大电阻段又测不准。
恒流源建议用低压差恒流源芯片或三极管加基准电路,电流设为 100μA 比较合适。采样电阻要选低温漂金属膜电阻,精度 0.1%,阻值在 100Ω~1kΩ 之间,根据恒流源电流和运放增益决定。
一个实用的设计思路:恒流源电流 100μA 流过被测绝缘电阻 R,压降为U = R × 100μA。当 R=40kΩ 时,压降 4V,接近 5V ADC 满量程;当 R=100kΩ 时,压降 10V,超出运放供电范围,所以大电阻段需要分压或切换运放倍率。这也是很多绝缘检测电路做双档位的原因:小阻值段用 1:1 放大,大阻值段用 1:10 分压,由 IO 口切换。程序里对应两套换算系数。
3.2 ADC采样参考与输入保护
STC12C5A60S2、STC15W408AS 这些 51 内核单片机自带 10 位 ADC,满足绝缘检测需求。外部基准更稳定,直接用 5V 供电也能跑,只是温漂略大。
输入保护必须做:运放输出端到 ADC 引脚之间串联 1kΩ 限流电阻,对地并联 TVS 管,再并一个 RC 低通滤波器,时间常数取 1ms 量级。
// ADC初始化配置,STC12C5A60S2为例 void adc_init(void) { P1ASF = 0x03; // P1.0和P1.1为模拟输入 ADC_CONTR = ADC_POWER | ADC_SPEED; // 打开ADC电源,选择速度 delay_ms(10); // 等待ADC电源稳定 }P1ASF是模拟输入通道选择寄存器,ADC_POWER打开 ADC 模块电源,ADC_SPEED位决定转换速度,一般取 210 个时钟周期档位,稳定性更好。
3.3 PCB布局的一个常见坑
运放输入走线不要和母线功率走线平行,恒流源的地要单点回源。51 单片机抗干扰能力一般,继电器或 MOS 开关切换瞬间会产生很大的 dv/dt,干扰会通过地线或空间耦合到 ADC 输入。程序里不能用中断启动激励,应该在主循环里等 ADC 转换完成,用查询方式采样,防止采样值被干扰污染。
4. 51单片机绝缘检测主程序:ADC滤波、电阻换算与阈值判断
4.1 主程序架构:主循环加状态机
绝缘检测程序建议用主循环加定时器中断的结构,不要用纯中断触发。原因是单个检测周期可能持续 5 秒以上,中断里放延时会让串口和显示完全卡死。主循环里用状态机分步执行,每个步骤都是一个独立状态,定时器中断只负责累计时间。
主循环 ├── 状态机调度(检测流程) │ ├── ST_IDLE 等待检测触发 │ ├── ST_DISCHARGE 母线放电 │ ├── ST_SETTLE 恒流源建立时间 │ ├── ST_SAMPLE ADC采样 │ └── ST_COMPARE 换算与阈值判断 ├── 串口命令处理 └── LED/蜂鸣器刷新每一帧循环都先执行状态机,再处理串口,最后刷新输出。这样一个检测周期内,工程师可以通过串口随时看到中间过程的运行状态。
4.2 ADC读取与软件滤波
STC12C5A60S2 的 ADC 读取直接操作寄存器:
static uint16_t adc_read(uint8_t ch) { uint16_t value = 0; uint8_t i = 0; for (i = 0; i < 8; i++) { ADC_CONTR = ADC_POWER | ADC_SPEED | ch | ADC_START; // 启动通道转换 while (!(ADC_CONTR & ADC_FLAG)); // 等待ADC转换完成 ADC_CONTR &= ~ADC_FLAG; // 清除转换完成标志 value += ADC_RES * 4 + ADC_LOW; // 高8位左移4位拼上低2位 } return value >> 3; // 8次累加平均 }ADC_RES * 4 + ADC_LOW是 STC 单片机高八位和低两位拼接 10 位结果的标准写法,不同型号寄存器布局略有差异,比如 STC15 系列直接读ADC_RES和ADC_RESL即可。value >> 3等价于除以 8。连续 8 次转换后取平均值,降低随机噪声的影响。
单靠平均还不够,继电器切换瞬间产生的脉冲干扰会把平均值拉偏,需要先做中值滤波再平均:
uint16_t adc_median_filter(uint16_t *buf, uint8_t len) { uint8_t i = 0, j = 0; uint16_t tmp = 0; // 冒泡排序取中间值,len=5时排序次数很少,CPU开销可接受 for (i = 0; i < len - 1; i++) for (j = 0; j < len - i - 1; j++) if (buf[j] > buf[j + 1]) { tmp = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = tmp; } return buf[len >> 1]; }采样流程是先采 5 个原始值做中值滤波,把中值结果放进滑动平均缓冲,再做 8 次滑动平均。两级滤波能同时抑制脉冲干扰和高频噪声。
4.3 绝缘电阻换算公式要标定不能硬算
最关键的换算公式不能靠原理计算硬写,程序里的换算表必须用电阻箱在产线上标定。因为每一块板子的运放增益、基准电压、采样电阻阻值都有离散性,硬算的线性公式会和实际读数偏差很大。
标定步骤:
- 断开负载,把标准电阻箱的一端接在母线上,另一端接地
- 从 1kΩ 起步,每个阻值点等读数稳定 10 秒后记录
- 依次标定 5kΩ、10kΩ、20kΩ、40kΩ、80kΩ、100kΩ
- 把记录点填入程序里的标定表
typedef struct { uint16_t adc_value; // ADC滤波后的读数 uint32_t resistance; // 对应的绝缘电阻,单位Ω } CAL_POINT; // 标定表必须用电阻箱实测填充,下面是示意值 const CAL_POINT cal_table[] = { { 512, 5000 }, { 410, 10000 }, { 300, 20000 }, { 180, 40000 }, { 100, 80000 }, { 60, 100000 }, }; #define CAL_TABLE_LEN (sizeof(cal_table) / sizeof(cal_table[0]))查表时用二分法找到 ADC 值所在区间,再线性插值:
uint32_t cal_get_resistance(uint16_t adc_value) { uint8_t low = 0, high = CAL_TABLE_LEN - 1, mid = 0; // 二分查找ADC值落入的区间 while (low < high) { mid = (low + high) >> 1; if (cal_table[mid].adc_value > adc_value) high = mid; else low = mid + 1; } // 越界保护 if (low == 0) return cal_table[0].resistance; if (low >= CAL_TABLE_LEN) return cal_table[CAL_TABLE_LEN - 1].resistance; // 线性插值:两点拟合一条直线 uint32_t adc_range = cal_table[low].adc_value - cal_table[low - 1].adc_value; uint32_t r_range = cal_table[low].resistance - cal_table[low - 1].resistance; uint32_t offset = adc_value - cal_table[low - 1].adc_value; return cal_table[low - 1].resistance + r_range * offset / adc_range; }用标定表配合线性插值比用拟合公式稳定,重新校准的时候只需更新表项,不用改程序逻辑,产线维护成本低很多。
4.4 阈值判断要带滞回区间
绝缘电阻在阈值附近抖动时,报警会反复触发。解决办法是设置两个阈值:报警阈值和恢复阈值,恢复阈值比报警阈值高 20% 左右。
#define ALARM_THRESHOLD 40000 // 40kΩ触发报警 #define RECOVER_THRESHOLD 48000 // 48kΩ才恢复 void check_insulation(uint32_t r_value, uint8_t *alarm_state) { if (*alarm_state == 0 && r_value < ALARM_THRESHOLD) { *alarm_state = 1; // 进入报警状态 BEEP_ON; // 打开蜂鸣器 ALARM_LED_ON; // 点亮报警灯 } else if (*alarm_state == 1 && r_value > RECOVER_THRESHOLD) { *alarm_state = 0; // 退出报警状态 BEEP_OFF; ALARM_LED_OFF; } }滞回区间的宽度可以调节,现场干扰大的环境可以调到 30%,但要注意过宽会导致恢复过慢。
4.5 检测周期管理
检测周期一般为 5~30 秒,用定时器 0 产生 1ms 时基,主循环里累计到设定秒数后置位检测标志。定时器中断只做时间累计,不做采样动作,防止采样被中断抢占导致时序错乱。
如果设备进入低功耗模式,检测完成后进入掉电模式,用定时器 0 中断唤醒。唤醒后要先等待晶振稳定再启动 ADC,这是 51 单片机低功耗设计的常见遗漏。
5. 调试与校准:绝缘检测程序最常踩的5个坑
5.1 用电阻箱做静态校准,别用可调电阻
可调电阻旋动时不连续,接触电阻变化大,标定出来的数据不可信。标定前先把报警和蜂鸣器关掉,只用在串口打印 ADC 值和换算结果,一把五位精度电阻箱就能完成标定。
标定过程按前面 4.3 节的步骤执行,每标定一个点就把数据填入cal_table,全部完成后重新上电验证 100kΩ 以上不误报、5kΩ 以下必报警。
5.2 母线对地电容拖慢信号建立时间
母线对地电容可能达到数百微法,恒流源接通的瞬间,电流大量流进电容充电,ADC 读到的是电容电压而不是绝缘电阻上的稳定压降。200ms 的建立时间在电容特别大时不够用。
排查方法是通过串口打印建立期间 ADC 值的变化曲线。如果曲线尾部还在上升,说明电容还没充满,不能开始采样。建议把建立时间做成可配置参数:
#define SETTLE_TIME_DEFAULT 800 // 默认800ms,可通过串口修改 static uint16_t g_settle_time = SETTLE_TIME_DEFAULT;5.3 双档位切换在交叉点读数对不上
两个档位切换的交叉区域要保留重叠量。比如小电阻档量程覆盖到 10kΩ,大电阻档从 8kΩ 开始,8~10kΩ 区间两档各自读一次,取误差较小的一路。切换档位后必须清空滤波缓冲,否则旧档位的残留值会污染新档位结果。
5.4 现场干扰误报先查地线再调滤波
现场误报往往是采样电路地和恒流源地没有连接在一起,共模电压叠加到采样信号上。先用示波器看 ADC 引脚的噪声幅度,超过 100mV 就优先检查 PCB 布局和接地方式,只调滤波参数治标不治本。
5.5 常见错误对照表
| 现象 | 原因 | 排查方向 |
|---|---|---|
| 读数一直偏大 | 恒流源电流偏小 | 用万用表量恒流源输出电流,修正换算表 |
| 读数一直偏小 | 运放输出饱和 | 检查分压电阻和档位切换逻辑 |
| 报警抖动 | 阈值没有滞回 | 设置RECOVER_THRESHOLD |
| 断电后读数异常 | 母线电容未放电 | 增加放电时间或泄放电阻 |
| 干扰大 | ADC基准不稳或地线环 | 检查参考电压、单点接地 |
| 双档位交叉点突变 | 滤波缓冲未清 | 切换档位后清空滤波数据 |
5.6 和漏电开关的区别
漏电开关(RCD)用零序互感器测交流负载的剩余电流,是保护动作装置。本文说的绝缘检测是持续监测直流系统或设备的对地绝缘电阻,属于预警机制。51 程序里如果要求输出“漏电隔离”信号,实际是把绝缘报警信号接到接触器控制回路,切断负载供电,而不是直接去驱动脱扣机构。两者在安全设计上的等级不同,程序里要区分告警和保护两个级别。
6. 进阶:绝缘检测结果通过Modbus接入充电桩后台
当设备需要接入充电桩后台或储能监控系统时,绝缘检测主机要把测量结果发出去。常见做法是 RS485 加 Modbus RTU 从站协议,51 单片机自带串口,配合定时器做波特率,可以自己写一个精简从站。
寄存器映射表如下:
| 寄存器地址 | 内容 | 类型 |
|---|---|---|
| 0x0000 | 绝缘电阻值(单位Ω) | 只读 |
| 0x0001 | 报警状态(0正常/1报警) | 只读 |
| 0x0002 | 检测周期(秒) | 读写 |
| 0x0003 | 报警阈值(Ω) | 读写 |
从站核心代码只需要串口接收状态机和 CRC 校验:
uint8_t modbus_rx_buf[64]; uint8_t modbus_rx_len = 0; void modbus_poll(uint8_t *frame, uint8_t len) { uint16_t crc = modbus_crc16(frame, len - 2); if ((uint16_t)(frame[len - 1] << 8 | frame[len - 2]) != crc) return; // CRC错误直接丢弃,不回应 switch (frame[1]) // 功能码 { case 0x03: modbus_read_holding_regs(frame); break; // 读多个寄存器 case 0x06: modbus_write_single_reg(frame); break; // 写单个寄存器 } }主循环每 100ms 调用一次modbus_poll,从站处理不占用绝缘检测状态机的时间。整体方案在 51 单片机上完整实现后,一个 MCU 同时完成 ADC 采样、滤波、换算、报警、通信这五件事,这也是充电桩绝缘监测从机最常见的架构。如果项目需要双通道检测或多档电压切换,把状态机和换算表扩展成数组即可,不需要换主控。
本文还有配套的精品资源,点击获取