PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转

2026-11-056
精华项目

 PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转

 

 

我的思路是典型的“低成本原型验证”

在工业自动化领域,直接上大设备有三个最大的风险:贵、危险、容错率低。如果我直接操作大功率变频器或伺服,一旦接线错位或逻辑死循环,可能会直接烧掉昂贵的设备,甚至导致机械损坏或人员受伤。如果用arduino控制直流电机就完全没这个顾虑。而且arduino的PWM和变频器的实现结果类似。熟悉PWM原理对理解变频器有很大帮助。而arduino的C和工业C#在基础语法上差别不大。如果未来想从创客过渡到PLC工程师,那会在程序设计上相当丝滑。

 

这节课我们让PLC和Arduino一起控制电机,谁负责启动?谁负责定时?一轮动作完成后,怎样避免自动重复运行?

PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_1.webp

这一课,我用西门子S7-200 SMART CR40、Arduino UNO、光耦隔离模块和DFRobot DRI0042直流电机驱动器,跑通了一套 **运行许可** 控制流程:

```
按下PLC启动按钮
       ↓
PLC输出运行许可
       ↓
Arduino执行:
正转3秒 → 刹车1秒 → 反转3秒 → 停止
       ↓
许可保持ON,也不重复执行
       ↓
撤销许可、重新启动,才能进入下一轮
```

## 一、系统分工:PLC管许可,Arduino管动作

这次没有让PLC直接控制电机的每一个动作,而是把任务分成两层。

PLC负责:

- 判断启动条件是否满足;
- 接受启动按钮的上升沿;
- 保持运行许可;
- 在允许条件撤销或收到取消信号时,撤销许可。

Arduino负责:

- 读取PLC许可;
- 执行正转、刹车等待、反转的动作顺序;
- 控制DRI0042的方向和PWM;
- 一轮结束后等待下一次有效启动。

const byte PERMIT_PIN = 2;
const byte IN1_PIN = 5;
const byte IN2_PIN = 4;
const byte PWM_PIN = 6;

const byte TEST_PWM = 80;
const unsigned long RUN_MS = 3000;
const unsigned long STOP_MS = 1000;
const unsigned long QUALIFY_MS = 50;

// 第一次保持false:只检查输入,不驱动电机。
const bool MOTOR_ENABLED = true;

// 暂定低电平有效,必须根据实际测试修改。
const byte PERMIT_ACTIVE_LEVEL = LOW;

enum State {
  WAIT_OFF,
  READY,
  FORWARD,
  WAIT_REVERSE,
  REVERSE
};

State state = WAIT_OFF;
unsigned long entered = 0;
unsigned long inputChanged = 0;

bool lastPermit = false;
int lastRaw = -1;

void motorStop() {
  analogWrite(PWM_PIN, 0);
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, LOW);
}

void runMotor(bool forward) {
  motorStop();
  digitalWrite(IN1_PIN, forward ? HIGH : LOW);
  digitalWrite(IN2_PIN, forward ? LOW : HIGH);
  analogWrite(PWM_PIN, TEST_PWM);
  entered = millis();
}

void setup() {
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, LOW);
  digitalWrite(PWM_PIN, LOW);

  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  pinMode(PWM_PIN, OUTPUT);
  pinMode(PERMIT_PIN, INPUT_PULLUP);

  motorStop();

  Serial.begin(9600);
  Serial.println("LESSON 11: PLC PERMIT");
  Serial.println("Boot: wait for permit OFF before accepting ON.");

  if (!MOTOR_ENABLED) {
    Serial.println("INPUT TEST ONLY: motor disabled.");
  }

  lastPermit =
      (digitalRead(PERMIT_PIN) == PERMIT_ACTIVE_LEVEL);
  inputChanged = millis();
}

void loop() {
  unsigned long now = millis();

  int raw = digitalRead(PERMIT_PIN);
  bool permit = (raw == PERMIT_ACTIVE_LEVEL);

  if (raw != lastRaw) {
    lastRaw = raw;
    Serial.print("D2=");
    Serial.print(raw == HIGH ? "HIGH" : "LOW");
    Serial.print("  interpreted permit=");
    Serial.println(permit ? "ON" : "OFF");
  }

  if (permit != lastPermit) {
    lastPermit = permit;
    inputChanged = now;
  }

  bool stable = (now - inputChanged >= QUALIFY_MS);

  if (!MOTOR_ENABLED) {
    motorStop();
    return;
  }

  // 串口s只允许停止,不提供绕过PLC的启动命令。
  bool stopRequested = false;
  while (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 's' || c == 'S') {
      stopRequested = true;
    }
  }

  if (stopRequested) {
    motorStop();
    state = WAIT_OFF;
    inputChanged = now;
    Serial.println("SERIAL STOP: wait for permit OFF.");
    return;
  }

  // 许可撤销不等待50ms:本次循环立即撤销驱动命令。
  if (!permit) {
    motorStop();

    if (state == FORWARD ||
        state == WAIT_REVERSE ||
        state == REVERSE) {
      state = WAIT_OFF;
      Serial.println("PERMIT LOST: cycle cancelled.");
    }

    if (state == WAIT_OFF && stable) {
      state = READY;
      Serial.println("READY: waiting for new permit ON.");
    }
    return;
  }

  // 许可ON需稳定50ms,且此前已确认OFF。
  if (state == READY && stable) {
    runMotor(true);
    state = FORWARD;
    Serial.println("FORWARD: 3 seconds.");
  }

  if (state == FORWARD && now - entered >= RUN_MS) {
    motorStop();
    state = WAIT_REVERSE;
    entered = now;
    Serial.println("BRAKE: 1 second.");
  } else if (state == WAIT_REVERSE &&
             now - entered >= STOP_MS) {
    runMotor(false);
    state = REVERSE;
    Serial.println("REVERSE: 3 seconds.");
  } else if (state == REVERSE &&
             now - entered >= RUN_MS) {
    motorStop();
    state = WAIT_OFF;
    Serial.println("DONE: permit must go OFF before next cycle.");
  }
}
PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_2.webp

整体链路如下:

```
CR40 PLC
  │ Q1.5
  ↓
光耦隔离模块
  │ O1
  ↓
Arduino UNO D2
  │ D5、D4、D6
  ↓
DFRobot DRI0042
  ↓
直流电机
```

## 二、PLC地址与控制逻辑

| 地址 | 用途 |
|---|---|
| I0.0 | 启动按钮,上升沿接受一次启动 |
| I0.1 | 运行允许:ON允许,OFF撤销许可 |
| I0.4 | 取消运行:ON撤销许可 |
| Q1.5 | 发送给Arduino的运行许可 |
| M11.0 | 保存启动按钮上一扫描周期的状态 |
| M11.1 | 启动脉冲 |
| M11.2 | 运行许可保持位 |

启动条件是:

```
I0.1 = ON
I0.4 = OFF
       ↓
按下I0.0
       ↓
Q1.5保持ON
```
取消条件是:
```text
I0.1变OFF,或I0.4变ON
       ↓
Q1.5变OFF
```

恢复允许条件后,不会直接恢复许可,仍需要重新按启动按钮。

PLC程序还加入了首次扫描处理:首次扫描撤销许可;如果启动按钮在PLC启动时一直按住,也不会直接接受启动,需要先释放再重新按下。

PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_3.webp

## 三、硬件接线

### UNO与DRI0042

| Arduino UNO | DRI0042 |
|---|---|
| D5 | IN1 |
| D4 | IN2 |
| D6 | PWM |
| GND | COM |

电机连接DRI0042的OUT1、OUT2,电机电源连接驱动器功率输入端。UNO在本次实验中使用USB供电,DRI0042的5VO不接UNO 5V。

### 光耦与UNO

| 光耦模块输出侧 | Arduino UNO |
|---|---|
| O1 | D2 |
| VCC | 5V |
| GND | GND |

这套接法以模块输入、输出电压规格已经核实为前提。本次实测中,PLC许可ON对应UNO的D2低电平,因此程序采用低电平有效。

模块上的D1是指示灯标识,O1才是第1路输出端,接线时不要混淆。

PLC侧24V不能直接接UNO数字输入。光耦两侧的电源与接地,也应按模块规格区分,不能为了“共地”随意跨接隔离两侧。

PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_4.webp
PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_5.webp

 

PLC+Arduino实战第11课:用运行许可,控制一轮电机正反转_image_6.webp## 四、调试中遇到的两个问题

### 1. PLC输出灯亮,光耦却没反应

排查时首先要分清:

```
PLC程序让Q1.5为ON
≠
输出端子一定已有24V
```

CR40采用继电器输出,除了程序输出状态,还要核对对应输出组公共端的回路供电。

本次接线重点检查了Q1.5所属组公共端4L的供电。实际接线仍应以自己CPU的端子图为准。

测量时,应以同一路24V电源负极为参考,分别检查:

```
电源正极对负极
公共端对负极
Q1.5对负极
光耦输入1+对1-
```

这样才能逐段定位,而不是看见输出灯亮,就跳到Arduino程序里找问题。

### 2. Arduino收到许可,电机仍然不动

串口已经显示:

```
D2=LOW  interpreted permit=ON
```

但电机没有动作。

原因是程序仍处于输入测试模式:

```
const bool MOTOR_ENABLED = false;
```
这个模式只检查许可输入,不驱动电机。确认输入极性后,改为:
```
const bool MOTOR_ENABLED = true;
```

重新上传,再撤销PLC许可并重新启动,动作流程才正式运行。

先验证输入,再开放电机动作,让调试范围清楚了很多。

## 五、Arduino采用单轮状态机,而不是自动循环

接入PLC后,原来的“正转—停止—反转—重复”程序就不适用了。

这一课使用的流程是:

```
上电
↓
等待许可OFF
↓
准备就绪
↓
接受新的许可ON
↓
正转3秒
↓
刹车1秒
↓
反转3秒
↓
停止,等待许可OFF
```

程序设计了以下规则:

- UNO启动时许可已经ON,不直接启动。
- 一轮完成后,许可保持ON也不重复执行。
- 许可撤销后,才能重新准备下一轮。
- 运行中许可撤销,取消本轮并撤销驱动命令。
- 使用millis()计时,让主循环持续检查输入。

其中,“运行中撤销许可”的处理已写入程序,但仍需要分别在正转、等待和反转阶段进行现场验证,不能把代码逻辑等同于测试结果。

## 六、实际运行结果

本次已连续完成两轮正常动作,串口记录如下:

```
D2=LOW  interpreted permit=ON
FORWARD: 3 seconds.
BRAKE: 1 second.
REVERSE: 3 seconds.
DONE: permit must go OFF before next cycle.
```

撤销PLC许可后:

```
D2=HIGH  interpreted permit=OFF
READY: waiting for new permit ON.
```

再次启动,才执行下一轮。

这验证了正常动作顺序,以及“完成后不自动重复、撤销许可后可重新启动”的基本行为。

## 七、复现实验时的注意事项

建议按以下顺序调试:

1. 不接电机,验证PLC启动、许可保持和取消逻辑。
2. 验证光耦输入、输出电压及有效电平。
3. 用Arduino输入测试模式观察D2。
4. 确认信号正确后,再启用电机动作。
5. 补测运行中取消、重启和断线等异常情况。

 

这一课真正跑通的,不只是电机正反转,而是“条件判断—许可传递—动作执行—完成等待—重新启动”这一套控制关系。

 

创作许可协议

本项目采用 CC BY(署名) 进行许可。

评论(0)
- 没有更多了 -