石油勘探开采沙盘模型涉及地质构造(地层剖面、圈闭类型)、勘探设备(地震测线、井位标识)、陆地开采(抽油机、井口装置、注水井)、集输系统(计量站、接转站、联合站、三相分离器)等多个设备的协同动作与流程演示。一套完整的石油勘探开采沙盘控制系统,需要同时驱动多台微型电机模拟抽油机驴头上下往复运动、集输流程灯带时序流动、注水井工作状态切换等多设备协同,同时支持勘探成果标识的分区亮灯展示。本文聚焦抽油机往复运动的精确控制与多井场协同——抽油机驴头的往复频率、集输管道的流向时序、多口油井的交替演示,每个环节的控制逻辑必须与实际油田开采规程一致。

本文结合筑城世纪模型在工业沙盘控制系统开发中的项目经验(2008年成立,2000㎡制作工厂,控制系统自主研发,已为华北电力大学、国机集团、巴斯夫、国家电网、清华大学等500+企事业单位提供服务),梳理石油勘探开采沙盘多设备协同控制系统的设计与实现方案,涵盖系统架构、硬件选型、抽油机动作模拟、集输流程时序控制、Modbus RTU寄存器映射、多井场协同控制代码实现及现场调试要点。

一、为什么石油勘探开采沙盘必须用STM32+Modbus RTU方案
石油勘探开采沙盘的控制系统有几个特殊要求:

第一,抽油机动作模拟的时序精度要求高。 抽油机的驴头上下往复运动需要模拟真实抽油机的冲程频率(通常6-12次/分钟),电机正反转切换的时序误差需控制在±50ms以内。STM32的中断响应精度可达1ms级,配合状态机编程可实现精确的往复运动控制。

第二,多设备协同与远程监控需求明确。 石油开采沙盘通常涉及多口油井的交替演示、注水井与采油井的联动、集输管道的时序流向等。控制系统需支持多路电机独立PWM调速、多段灯带独立时序控制。Modbus RTU是工业标准协议,支持接入触摸屏、上位机监控平台等第三方设备。

第三,后期修改频繁且成本敏感。 石油勘探开采沙盘的控制逻辑经常根据客户需求调整(如调整抽油机冲程频率、修改集输流程演示顺序、新增“注水增产”工况模式)。PLC方案每次修改需专业软件授权和工程师到场,STM32方案通过寄存器在线修改参数即可。

STM32 vs PLC 选型对比
对比项 PLC方案 STM32方案
硬件成本 5000-10000元 500-1200元
电机控制精度 依赖专用运动模块 PWM直接驱动,精度1%
往复运动控制 梯形图实现复杂 C语言状态机灵活可控
后期修改 需编程软件授权+工程师到场 寄存器在线修改,无需重编译
数据上报 需额外网关模块 串口直接输出
扩展性 依赖专用模块 开放GPIO/USART,灵活扩展
为什么用RS485/Modbus RTU?
石油勘探开采沙盘模型内部设备分布广泛(多口油井、集输站、联合站等),RS485总线支持多点通讯(理论32个节点),一线串接所有设备。Modbus RTU是工业标准协议,支持接入触摸屏、上位机监控平台等第三方设备。

二、系统架构设计
2.1 总体架构
text
┌─────────────────────────────────────────────────────────────┐
│ 触摸屏/上位机(Modbus RTU 主站) │
│ 工况切换 速度调节 单井控制 模式切换 │
└─────────────────────────┬───────────────────────────────────┘
│ RS485(9600,8N1,120Ω终端电阻)
┌─────────────────────────▼───────────────────────────────────┐
│ STM32F103RCT6 从站 │
│ 指令解析 → 状态机调度 → GPIO/PWM输出 │
└───────┬─────────┬─────────┬─────────┬───────────────────────┘
│ │ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│抽油机 │ │注水井 │ │集输 │ │勘探 │
│电机群 │ │电机 │ │灯带 │ │标识 │
│(1-4口) │ │ │ │ │ │灯带 │
└───────┘ └───────┘ └───────┘ └───────┘
2.2 硬件选型
组件 选型 数量 选型理由
主控MCU STM32F103RCT6 1 256KB Flash,51个GPIO,3个USART
电机驱动 DRV8833(双路H桥) 依电机数 驱动微型直流电机,支持PWM正反转调速
抽油机电机 微型直流减速电机 依井口数 驱动驴头往复运动
灯带 WS2812B(60灯/米) 依集输路径数 单总线控制,每灯独立寻址
通讯接口 MAX485ESA 1 RS485收发器,工业级抗干扰
电源 24V→5V/3.3V隔离模块 1 隔离供电,降低电机对控制器的干扰
2.3 GPIO分配表(以4口油井为例)
端口 功能 信号类型 说明
PA9/PA10 USART1 通讯 与触摸屏RS485通讯
PA2/PA3 USART2 通讯 预留调试串口
PA0 抽油机1电机 PWM输出 驴头往复运动控制
PA1 抽油机2电机 PWM输出 驴头往复运动控制
PA4 抽油机3电机 PWM输出 驴头往复运动控制
PA5 抽油机4电机 PWM输出 驴头往复运动控制
PA6 注水井电机 PWM输出 注水动作模拟
PB0 集输主流程灯带 单总线输出 WS2812B
PB1 井口→计量站灯带 单总线输出 WS2812B
PB2 计量站→接转站灯带 单总线输出 WS2812B
PB3 接转站→联合站灯带 单总线输出 WS2812B
PB4 勘探成果标识灯带 单总线输出 井位/测线/含油气面积
三、Modbus寄存器映射设计
以下为石油勘探开采沙盘控制系统的寄存器定义(共28个保持寄存器):

3.1 系统控制寄存器(40001-40008)
寄存器地址 偏移地址 功能 数据类型 读写 取值范围 说明
40001 0x0000 系统启停 bit R/W 0/1 0=停止,1=启动
40002 0x0001 运行模式 uint16 R/W 1-3 1=自动演示,2=手动控制,3=单井巡检
40003 0x0002 当前工段 uint16 R 0-5 0=停止,1=勘探展示,2=开采演示,3=集输流程,4=全流程
40004 0x0003 速度档位 uint16 R/W 1-5 档位越高,抽油机冲程越快
40005 0x0004 循环模式 uint16 R/W 0/1 0=单次,1=无限循环
40006 0x0005 工况切换 uint16 R/W 1-4 1=正常生产,2=注水增产,3=修井作业,4=故障模拟
40007 0x0006 系统状态 uint16 R 0-3 0=停止,1=运行中,2=告警,3=故障
40008 0x0007 故障代码 uint16 R 0-99 0=无故障
3.2 抽油机控制寄存器(40009-40016)
寄存器地址 偏移地址 功能 数据类型 读写 说明
40009 0x0008 抽油机1启停 bit R/W 1=运行
40010 0x0009 抽油机1速度 uint16 R/W 0-100,PWM占空比
40011 0x000A 抽油机2启停 bit R/W 1=运行
40012 0x000B 抽油机2速度 uint16 R/W 0-100,PWM占空比
40013 0x000C 抽油机3启停 bit R/W 1=运行
40014 0x000D 抽油机3速度 uint16 R/W 0-100,PWM占空比
40015 0x000E 抽油机4启停 bit R/W 1=运行
40016 0x000F 抽油机4速度 uint16 R/W 0-100,PWM占空比
3.3 注水与集输控制寄存器(40017-40024)
寄存器地址 偏移地址 功能 数据类型 读写 说明
40017 0x0010 注水井启停 bit R/W 1=运行
40018 0x0011 注水井速度 uint16 R/W 0-100,PWM占空比
40019 0x0012 集输主流程启停 bit R/W 1=运行
40020 0x0013 集输流程速度 uint16 R/W 0-100,灯带流速
40021 0x0014 勘探标识展示 bit R/W 1=点亮井位/测线/含油气面积
40022 0x0015 单井选择 uint16 R/W 1-4,选择当前控制的油井
40023 0x0016 冲程频率 uint16 R/W 6-12次/分钟
40024 0x0017 演示模式 uint16 R/W 1=分区演示,2=全流程联动
四、核心代码实现
4.1 Modbus从站初始化与抽油机控制
cpp
#include “ModbusRTU.h”
#include “Adafruit_NeoPixel.h”

#define SLAVE_ID 1
#define BAUD_RATE 9600

// 抽油机电机GPIO定义(PWM正反转控制)
#define MOTOR_PUMP1 PA0
#define MOTOR_PUMP2 PA1
#define MOTOR_PUMP3 PA4
#define MOTOR_PUMP4 PA5
#define MOTOR_WATER PA6 // 注水井电机

// 灯带GPIO定义(WS2812B)
#define LED_MAIN PB0 // 集输主流程
#define LED_WELL PB1 // 井口→计量站
#define LED_STATION PB2 // 计量站→接转站
#define LED_UNION PB3 // 接转站→联合站
#define LED_EXPLORE PB4 // 勘探成果标识

#define WELL_NUM 4

ModbusRTU mb;
uint16_t holdingRegs[28] = {0};

// 抽油机状态
struct PumpStatus {
bool running;
uint8_t speed;
unsigned long lastTick;
};

PumpStatus pumps[WELL_NUM];

// 各阶段演示时长(ms)
uint16_t stageDurations[5] = {5000, 4000, 6000, 3000, 4000};
uint16_t baseDurations[5] = {5000, 4000, 6000, 3000, 4000};

// 状态机变量
enum SystemState { STOPPED, RUNNING, PAUSED, FAULT };
SystemState currentState = STOPPED;
volatile uint8_t currentStage = 0;
volatile unsigned long stageStartTime = 0;
volatile uint16_t stageElapsedMs = 0;

// 灯带对象
Adafruit_NeoPixel stripMain(LED_COUNT, LED_MAIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripWell(LED_COUNT, LED_WELL, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripStation(LED_COUNT, LED_STATION, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripUnion(LED_COUNT, LED_UNION, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripExplore(LED_COUNT, LED_EXPLORE, NEO_GRB + NEO_KHZ800);

void setup() {
SystemClock_Config(); // 72MHz
Serial1.begin(BAUD_RATE);
mb.begin(&Serial1);
mb.slave(SLAVE_ID);
mb.addHreg(0, holdingRegs, 28);

mb.onGet(handleRead);
mb.onSet(handleWrite);

// 初始化电机GPIO
pinMode(MOTOR_PUMP1, OUTPUT);
pinMode(MOTOR_PUMP2, OUTPUT);
pinMode(MOTOR_PUMP3, OUTPUT);
pinMode(MOTOR_PUMP4, OUTPUT);
pinMode(MOTOR_WATER, OUTPUT);

// 初始化灯带
stripMain.begin(); stripMain.show();
stripWell.begin(); stripWell.show();
stripStation.begin(); stripStation.show();
stripUnion.begin(); stripUnion.show();
stripExplore.begin(); stripExplore.show();

// 定时器中断(1ms周期)
Timer1.initialize(1000);
Timer1.attachInterrupt(stateMachineTick);

}

void loop() {
mb.task(); // Modbus协议处理
}
4.2 抽油机驴头往复运动控制
cpp
// 抽油机动作:模拟驴头上下往复运动(4相位控制)
void runPump(int motorPin, int speed, unsigned long &lastTick) {
unsigned long now = millis();
unsigned long interval = 60000 / holdingRegs[22]; // 冲程周期(ms)

if (now - lastTick >= interval / 4) {
    lastTick = now;
    // 4相位:上升→上死点→下降→下死点
    static int phase = 0;
    phase = (phase + 1) % 4;
    
    switch(phase) {
        case 0:  // 上升
            analogWrite(motorPin, speed);
            break;
        case 1:  // 上死点停止
            analogWrite(motorPin, 0);
            break;
        case 2:  // 下降
            analogWrite(motorPin, -speed);
            break;
        case 3:  // 下死点停止
            analogWrite(motorPin, 0);
            break;
    }
}

}

// 状态机定时器中断(1ms执行一次)
void stateMachineTick() {
if (currentState != RUNNING) return;

// 更新各抽油机状态
for (int i = 0; i < WELL_NUM; i++) {
    if (pumps[i].running) {
        int pin = (i == 0) ? MOTOR_PUMP1 : 
                 (i == 1) ? MOTOR_PUMP2 :
                 (i == 2) ? MOTOR_PUMP3 : MOTOR_PUMP4;
        runPump(pin, pumps[i].speed, pumps[i].lastTick);
    }
}

// 阶段切换逻辑(用于全流程演示)
stageElapsedMs++;
if (stageElapsedMs >= stageDurations[currentStage]) {
    stageElapsedMs = 0;
    currentStage++;
    if (currentStage >= 5) {
        if (holdingRegs[4] == 1) currentStage = 0;
        else { currentState = STOPPED; return; }
    }
    switchStage(currentStage);
    holdingRegs[2] = currentStage + 1;
}

}
4.3 集输流程灯带时序控制
cpp
void switchStage(uint8_t stage) {
allLightsOff();

switch(stage) {
    case 0:  // 勘探展示:点亮井位和测线
        flowLight(stripExplore, 0x00FF00, 500);
        break;
    case 1:  // 开采演示:启动抽油机
        for (int i = 0; i < WELL_NUM; i++) {
            pumps[i].running = true;
            pumps[i].speed = holdingRegs[10 + i*2];
        }
        flowLight(stripWell, 0xFFFF00, 400);
        break;
    case 2:  // 集输:井口→计量站
        flowLight(stripWell, 0xFF8800, 300);
        flowLight(stripStation, 0xFF8800, 300);
        break;
    case 3:  // 集输:计量站→接转站
        flowLight(stripStation, 0xFF6600, 300);
        flowLight(stripUnion, 0xFF6600, 300);
        break;
    case 4:  // 集输:接转站→联合站
        flowLight(stripUnion, 0xFF4400, 300);
        flowLight(stripMain, 0xFF4400, 300);
        break;
}

}

void flowLight(Adafruit_NeoPixel &strip, uint32_t color, int duration) {
int len = strip.numPixels();
int steps = 20;
for (int step = 0; step < steps; step++) {
strip.clear();
int pos = (len * step) / steps;
strip.setPixelColor(pos, color);
if (pos > 0) strip.setPixelColor(pos - 1, color);
strip.show();
delay(duration / steps);
}
}

void allLightsOff() {
stripMain.clear(); stripMain.show();
stripWell.clear(); stripWell.show();
stripStation.clear(); stripStation.show();
stripUnion.clear(); stripUnion.show();
stripExplore.clear(); stripExplore.show();
}
4.4 寄存器读写回调与故障模拟
cpp
uint16_t handleRead(uint16_t offset, uint8_t index) {
return holdingRegs[offset];
}

void handleWrite(uint16_t offset, uint8_t index, uint16_t value) {
switch(offset) {
case 0: // 系统启停
if (value == 1 && currentState != RUNNING) {
currentState = RUNNING;
currentStage = 0;
stageElapsedMs = 0;
stageStartTime = millis();
switchStage(0);
holdingRegs[6] = 1;
} else if (value == 0) {
currentState = STOPPED;
for (int i = 0; i < WELL_NUM; i++) pumps[i].running = false;
allMotorsStop();
allLightsOff();
holdingRegs[2] = 0;
holdingRegs[6] = 0;
}
break;

    case 1:  // 运行模式
        holdingRegs[1] = value;
        break;
        
    case 3:  // 速度档位
        if (value >= 1 && value <= 5) {
            for (int i = 0; i < 5; i++) {
                stageDurations[i] = baseDurations[i] / value;
            }
        }
        break;
        
    case 5:  // 工况切换
        switch(value) {
            case 1:  // 正常生产
                break;
            case 2:  // 注水增产
                analogWrite(MOTOR_WATER, 150);
                break;
            case 3:  // 修井作业
                emergencyStop();
                break;
            case 4:  // 故障模拟
                simulateFault();
                break;
        }
        break;
        
    case 8: case 10: case 12: case 14:  // 抽油机启停
        int idx = (offset - 8) / 2;
        pumps[idx].running = (value == 1);
        break;
        
    case 9: case 11: case 13: case 15:  // 抽油机速度
        int idx2 = (offset - 9) / 2;
        pumps[idx2].speed = value;
        break;
}
holdingRegs[offset] = value;

}

void emergencyStop() {
currentState = STOPPED;
for (int i = 0; i < WELL_NUM; i++) pumps[i].running = false;
allMotorsStop();
allLightsOff();
holdingRegs[6] = 3;
holdingRegs[7] = 1;
}

void simulateFault() {
// 模拟抽油机故障:1号井停止,灯光闪烁报警
pumps[0].running = false;
analogWrite(MOTOR_PUMP1, 0);
for (int i = 0; i < 5; i++) {
stripWell.clear();
for (int p = 0; p < stripWell.numPixels(); p++) {
stripWell.setPixelColor(p, (i % 2 == 0) ? 0xFF0000 : 0x000000);
}
stripWell.show();
delay(300);
}
holdingRegs[6] = 3;
holdingRegs[7] = 2;
}
五、关键技术细节
5.1 抽油机驴头往复运动的4相位控制
抽油机的驴头运动模拟是本文控制方案的核心。采用4相位状态机实现:

相位0:上升(PWM正转,速度可调)

相位1:上死点停止(模拟换向延迟)

相位2:下降(PWM反转,速度可调)

相位3:下死点停止(模拟换向延迟)

冲程频率寄存器(40023)可在线调节(6-12次/分钟),模拟真实抽油机的不同工况。上升和下降速度分别由各抽油机的速度寄存器独立控制。

5.2 多井场协同与交替演示
“单井巡检”模式(40002=3)依次演示各油井的工作状态,每口井演示时间可独立配置。该模式通过状态机按顺序激活各油井,结合集输灯带展示各井口的产液流向,适用于油田展厅的自动化演示。

5.3 集输流程多段时序控制
集输流程涉及井口→计量站→接转站→联合站的多段路径。WS2812B灯带支持独立控制每个灯珠,将各段路径映射到不同的灯带对象,分别控制流速和颜色。流速由寄存器40020控制,颜色随路径阶段变化。

5.4 通讯异常处理
策略 实现方式 适用场景
保持当前状态 不改变输出,等待通讯恢复 展厅演示
自动停止 超时后自动停止所有动作 安全要求较高
状态上报 向上位机发送故障码 远程监控
六、现场调试要点
6.1 抽油机动作异常排查
现象 排查方向 解决方案
电机不转 电源/驱动 检查DRV8833供电,确认EN引脚电平
电机只朝一个方向转 驱动逻辑 检查IN1/IN2逻辑电平是否正确
往复运动不连贯 PWM频率/状态机 确认PWM频率≥1kHz,检查相位切换逻辑
多抽油机不同步 定时器中断 检查定时器中断周期(1ms)
6.2 集输灯带异常排查
现象 排查方向 解决方案
灯带全亮白光 数据线虚接 重新焊接,确认GPIO输出电平
颜色偏色 电源电压不足 测量末端电压,<4.8V需增加供电点
灯带流速不均 时序参数 检查stageDurations配置
6.3 调试工具推荐
调试阶段推荐使用Modbus Poll(Windows)作为主站调试工具,配合USB转RS485转换器连接目标板。通讯正常后,依次测试各寄存器的读写功能,确认单路控制正常后再进行联调。

七、工程实践能力
基于STM32平台的多设备协同控制方案,已在筑城世纪多个工业模型项目中完成部署验证:

国机集团柬埔寨燃机联合循环电站沙盘:6级灯光联动、65米灯带,多节点时序控制

源网荷储沙盘模型:支持Modbus RTU通讯扩展,可接入中控平板

上述项目验证的核心能力(多路电机独立PWM正反转控制、多段灯带时序联动、多设备协同控制、Modbus RTU数据对接),与石油勘探开采沙盘的控制需求一致。

筑城世纪模型在石油勘探开采沙盘方向具备以下三项核心交付能力:

  1. 石油开采工艺理解能力

技术团队具备读懂石油开采工艺图纸的能力,能够按图纸精确还原抽油机结构、井口装置布局、集输流程路径。抽油机冲程频率、集输流向时序按图施工,可签署技术图纸确认单。

  1. 多设备协同控制能力

控制系统基于STM32平台自主研发,支持多路电机独立PWM正反转调速、多段灯带时序联动、多井场协同演示、注水-采油联动控制。后期可根据客户需求随时修改控制逻辑和工况模式,无需依赖第三方。

  1. 全流程自主交付能力

2000㎡制作工厂,位于北京通州,服务范围覆盖全国石油石化项目。设计、3D打印、CNC加工、喷涂、组装、控制系统编程全部自主完成,不外包。签约前出具《模型制作工艺说明》,零增项承诺写入合同。有图纸就能做。

八、总结
石油勘探开采沙盘多设备协同控制系统的设计,核心在于三个层面:

  1. 抽油机往复运动的精确控制是基础。 采用PWM正反转控制+4相位状态机,模拟驴头上下往复运动。冲程频率可在线调节(6-12次/分钟),匹配不同演示速度需求。

  2. 多井场协同与集输流程时序是核心。 采用状态机编程,将勘探展示、开采演示、集输流程定义为独立状态,各状态之间通过时序计数器触发切换,逻辑清晰、便于调试。

  3. 控制系统自主可控是保障。 自主研发的控制系统才能做到后期随时修改控制逻辑、新增工况模式。

筑城世纪模型的石油勘探开采沙盘控制系统方案,基于STM32平台自主研发,支持Modbus RTU协议,已在多套工业模型项目中完成部署验证。控制系统支持多路电机独立PWM正反转控制、多段灯带时序联动、多井场协同演示、注水-采油联动控制,后期可根据需求随时修改控制逻辑。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐