Djair Guilherme
Published

M5Stack STAMP PLC as real PLC!

An application of the amazing M5Stack STAM PLC using the Schneider Electrics ATV12 phase inverter and ESPHome.

AdvancedWork in progress3 hours538
M5Stack STAMP PLC as real PLC!

Things used in this project

Hardware components

Variable Speed Drive, Altivar 12, 0.37kW, 0.55hp, 200 to 240V, 1 phase, with heat sink
×1
M5Stack STAM PLC
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1

Software apps and online services

ESPHome
Home Assistant
Home Assistant

Story

Read more

Code

Full YAML to use with STAM PLC and ATV12 using ESPHome Firmware

YAML
substitutions:
  name: m5stamplc
  friendly_name: 'M5Stack STAMPLC'

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  platformio_options:
    build_flags:
      - -DESP32S3
      - -DCORE_DEBUG_LEVEL=5
      - -DARDUINO_USB_CDC_ON_BOOT=1
      - -DARDUINO_USB_MODE=1
  on_boot:
    - then:
        rx8130.read_time:
    - priority: 1000
      then:
        - lambda: |-
            pinMode(03, OUTPUT);
            digitalWrite(03, HIGH);
    - priority: -100
      then:
       component.update: vdu

# Enable Home Assistant API
api:
  encryption:
    key: "" # add your key here

ota:
  - platform: esphome
    password: "" # add your password here

wifi:
  id: wifi_1
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Set up a wifi access point
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "M5Stampplc Config Wifi"
    password: "" # add your password here

  on_connect:
    then:
      - component.update: vdu
      - select.set:
          id: select_led_color
          option: "Blue"
  on_disconnect:
    then:
      - component.update: vdu
      - select.set:
          id: select_led_color
          option: "Red"   

# In combination with the `ap` this allows the user
# to provision wifi credentials to the device via WiFi AP.
captive_portal:

# Import custom components...
external_components:
  - source: github://beormund/esphome-m5stamplc@main
    components: [aw9523, pi4ioe5v6408, lm75b, rx8130]

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 8MB
  variant: ESP32S3
  framework:
    type: arduino

# I found OTA updating failed unless safe_mode was disabled
safe_mode:
  disabled: true

#Enable logging
logger:

# Allow provisioning Wi-Fi via serial
improv_serial:


# To have a "next url" for improv serial
web_server:
  port: 80
  version: 3
  ota: true
  log: true
  local: true

# Time
time:
  - platform: rx8130
    id: rx8130_time
    update_interval: 30 min
    on_time_sync:
      then:
        - component.update: vdu
  - platform: sntp
    id: sntp_time
    timezone: America/Sao_Paulo
    on_time_sync:
      then:
        - rx8130.write_time:
            id: rx8130_time
        - component.update: vdu
    on_time:
      - cron: '0 * * * * *'
        then:
          - component.update: vdu

# I2C Bus
i2c:
  sda: GPIO13
  scl: GPIO15
  scan: true

# Configuration of i2c GPIO Expander 1 
pi4ioe5v6408:
  - id: pi4ioe5v6408_1
    address: 0x43

# Configuration of i2c GPIO Expander 2
aw9523:
  - id: aw9523_1
    address: 0x59
    divider: 3
    latch_inputs: true

# SPI Bus. Could use SD Card?
spi:
  clk_pin: GPIO7
  mosi_pin: GPIO8
  miso_pin: GPIO9

# RS485 pin config
uart:
  - id: mod_bus
    tx_pin: GPIO0
    rx_pin: GPIO39
    baud_rate: 19200
    parity: EVEN
    stop_bits: 1
    debug:
      direction: BOTH
      dummy_receiver: false


# ModBus Creation
modbus:
  uart_id: mod_bus
  id: modbus1
  send_wait_time: 300ms

modbus_controller:
  - id: atv12_controller
    address: 1  # Endereço MODBUS (1-247)
    modbus_id: modbus1
    command_throttle: 200ms
    update_interval: 500ms
    on_online:
      then:
        - binary_sensor.template.publish: # Publish ModBus Connection State
            id: modbus_status
            state: true
        - component.update: vdu
    on_offline:
      then:
        - binary_sensor.template.publish: # Publish ModBus Connection State
            id: modbus_status
            state: false
        - component.update: vdu



# Update ATV States at 0.5s
interval:
  - interval: 500ms
    then:
      - component.update: vdu
      - lambda: |-
          if (id(atv12_fault_state).state || id(atv12_overcurrent).state || id(atv12_overtemp).state) {
            ESP_LOGW("atv12", "Detect  FAIL. Sending Reset Command");
            id(reset_atv12).execute();
          }

# Dealing with Relays
# Connect COM and NO to the logic inputs on ATV12

switch:
  # Relays 1-4
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF
    name: "Relay 1" # Connected to LI1 on ATV12 >> MODBUS or MANUAL
    id: r1
    pin:
      aw9523: aw9523_1
      number: 0
      mode:
        output: true
    on_turn_on:
      - component.update: vdu
    on_turn_off:
      - component.update: vdu
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF

    name: "Relay 2" # Connected to LI2 on ATV12 >> STOP/RUN
    id: r2
    pin:
      aw9523: aw9523_1
      number: 1
      mode:
        output: true
    on_turn_on:
      - component.update: vdu
    on_turn_off:
      - component.update: vdu
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF

    name: "Relay 3" # Connected to LI3 on ATV12 >> FWD/REV
    id: r3
    pin:
      aw9523: aw9523_1
      number: 2
      mode:
        output: true
    on_turn_on:
      - component.update: vdu
    on_turn_off:
      - component.update: vdu
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF

    name: "Relay 4"# Connected to LI4 on ATV12 >> FULL STOP
    id: r4
    pin:
      aw9523: aw9523_1
      number: 3
      mode:
        output: true
    on_turn_on:
      - component.update: vdu
    on_turn_off:
      - component.update: vdu

  # LCD backlight (on/off only)
  - platform: gpio
    restore_mode: ALWAYS_ON
    name: "LCD Backlight"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 7
      inverted: true
      mode:
        output: true
        pulldown: true

  # led indicator 3-bit (8 colors only)
  - platform: gpio
    restore_mode: ALWAYS_ON
    id: "led_red"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 6
      inverted: true
      mode:
        output: true
        pulldown: true

  - platform: gpio
    restore_mode: ALWAYS_OFF
    id: "led_green"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 5
      inverted: true
      mode:
        output: true
        pulldown: true

  - platform: gpio
    restore_mode: ALWAYS_OFF  
    id: "led_blue"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 4
      inverted: true
      mode:
        output: true
        pulldown: true        

# ATV12 Control Switches on Web Interface
  - platform: template
    name: "ATV12 STOP/RUN"
    id: atv12_run
    lambda: |-
      return (id(atv12_status).state == 0x0037);
    turn_on_action:
      - lambda: |-
          id(atv12_cmd).publish_state(0x000F);
    turn_off_action:
      - lambda: |-
          id(atv12_cmd).publish_state(0x0006);

  - platform: template
    name: "ATV12 Direction"
    id: atv12_direction
    lambda: |-
      return (id(atv12_cmd).state == 0x001F);
    turn_on_action:
      - lambda: |-
          id(atv12_cmd).publish_state(0x001F); // Reverse
    turn_off_action:
      - lambda: |-
          id(atv12_cmd).publish_state(0x000F); // Forward


# Enable Web/HA UI to set 3-bit colors for LED Status
select:
  - platform: template
    id: select_led_color
    name: "Select LED Color"
    entity_category: config
    initial_option: Red
    optimistic: true
    options:
      - Black
      - Red
      - Green
      - Yellow
      - Blue
      - Magenta
      - Cyan
      - White
    on_value: 
      then:
        - lambda: "id(set_led)->execute(i);"

script:
  - id: set_led
    parameters:
      color_id: int
    then:
      lambda: |-
        if ((color_id & 1) == 1) {
          id(led_red).turn_on();
        } else {
          id(led_red).turn_off();
        }
        if ((color_id & 2) == 2) {
          id(led_green).turn_on();
        } else {
          id(led_green).turn_off();
        }
        if ((color_id & 4) == 4) {
          id(led_blue).turn_on();
        } else {
          id(led_blue).turn_off();
        }
  # Script to Reset ATV 12
  - id: reset_atv12
    then:
      - lambda: |-
          ESP_LOGI("reset", "RESETING ATV12.");
          id(atv12_cmd).publish_state(0x0008);  // Reset ATV12 Command

output:
  # Set up the pwm buzzer on pin 44
  # See https://esphome.io/components/rtttl.html
  - platform: ledc
    pin: GPIO44
    id: buzzer

rtttl:
  output: buzzer
  id: buzzer_1

# Inputs 1-8
# Using Digital Inputs on STAM PLC to control Relays and Digital Inputs on ATV12
binary_sensor:
  - platform: gpio
    id: i1
    name: "Activate ModBus"
    pin:
      aw9523: aw9523_1
      number: 4
      mode:
        input: true
    on_state:
      then:
        - component.update: vdu
        - lambda: id(r1).toggle();

  - platform: gpio
    id: i2
    name: "Manual STOP/RUN"
    pin:
      aw9523: aw9523_1
      number: 5
      mode:
        input: true
    on_state:
      then:
        - component.update: vdu
        - lambda: id(r2).toggle();

  - platform: gpio
    id: i3
    name: "Manual FWD/REV"
    pin:
      aw9523: aw9523_1
      number: 6
      mode:
        input: true
    on_state:
      then:
        - component.update: vdu
        - lambda: id(r3).toggle();

  - platform: gpio
    id: i4
    name: "Emergency STOP"
    pin:
      aw9523: aw9523_1
      number: 7
      mode:
        input: true
    on_state:
      then:
        - component.update: vdu
        - lambda: id(r3).toggle();


# Adding Binary Sensor to detect ModBus Connection
  - platform: template
    id: modbus_status
    name: "Modbus Online"
    device_class: connectivity

  # Using STAM PLC Buttons to control Relays 1 to 3
  - platform: gpio
    name: "Activate Modbus"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 2
      inverted: true
      mode:
        input: true
        pullup: true
    on_press:
      - rtttl.play: "two_short:d=4,o=5,b=100:16e6,16e6"
      - lambda: id(r1).toggle();

  - platform: gpio
    name: "STOP / RUN"
    pin:
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 1
      inverted: true
      mode:
        input: true
        pullup: true
    on_press:
      - rtttl.play: "two_short:d=4,o=5,b=100:16e6,16e6"
      - lambda: id(r2).toggle();

  - platform: gpio
    name: "FWD / REV"
    pin:    
      pi4ioe5v6408: pi4ioe5v6408_1
      number: 0
      inverted: true
      mode:
        input: true
        pullup: true
    on_press:
      - rtttl.play: "two_short:d=4,o=5,b=100:16e6,16e6"
      - lambda: id(r3).toggle();

# Monitoring ATV 12 common Fails.

  - platform: template
    name: "ATV12 FAIL"
    id: atv12_fault_state
    lambda: |-
      return (id(atv12_status).state == 0x0008);

  - platform: template
    name: "ATV12 Overcurrent"
    id: atv12_overcurrent
    lambda: |-
      return (id(atv12_current).state > 1.9);

  - platform: template
    name: "ATV12 Overheat"
    id: atv12_overtemp
    lambda: |-
      return (id(atv12_temperature).state > 70);


sensor:
  # INA226 Voltage/Current Sensor on i2c default Address 0x40
  - platform: ina226
    shunt_resistance: 0.01
    max_current: 8.192
    current:
      name: "STAM PLC Current"
      entity_category: "diagnostic"
    power:
      name: "STAM PLC Power"
      entity_category: "diagnostic"
    bus_voltage:
      name: "STAM PLC Bus Voltage"
      entity_category: "diagnostic"
    shunt_voltage:
      name: "STAM PLC Shunt Voltage"
      entity_category: "diagnostic"

  # LM75B Temp Sensor on i2c default address 0x48 
  - platform: lm75b
    name: "STAM PLC Temperature"
    update_interval: 60s
    entity_category: "diagnostic"

# Rotary Encoder on Grove PORT A - ATV12 Speed Control
  - platform: rotary_encoder
    name: "ATV12 Speed Control"
    pin_a: GPIO2
    pin_b: GPIO1
    resolution: 1
    min_value: 0
    max_value: 600
    on_clockwise:
      - lambda: |-
          float new_value = id(atv12_speed_setpoint).state + 1.0f;
          if (new_value > 600.0f) new_value = 600.0f;
          id(atv12_speed_setpoint).publish_state(new_value);

    on_anticlockwise:
      - lambda: |-
          float new_value = id(atv12_speed_setpoint).state - 1.0f;
          if (new_value < 0.0f) new_value = 0.0f;
          id(atv12_speed_setpoint).publish_state(new_value);


# ATV 12 COMMAND, STATUS, SPEED, CURRENT, TEMPERATURE and FAIL SENSORS
# This is the correct way to send and receive commands and data from MODBUS
# See it: https://esphome.io/components/modbus_controller.html

  - platform: modbus_controller
    modbus_controller_id: atv12_controller
    id: atv12_cmd
    name: "ATV12 Command"
    address: 8501
    register_type: holding
    value_type: U_WORD
    lambda: |-
      ESP_LOGI("modbus", "Command sent: %d", x);
      return (uint16_t)x;

  - platform: modbus_controller
    id: atv12_status
    name: "ATV12 Status"
    register_type: holding
    address: 3201
    value_type: U_WORD

  - platform: modbus_controller
    id: atv12_actual_speed
    name: "ATV12 Actual Speed"
    register_type: holding
    address: 8604
    value_type: U_WORD
    accuracy_decimals: 1
    filters:
      - multiply: 0.1

  - platform: modbus_controller
    id: atv12_current
    name: "ATV12 Current"
    register_type: holding
    address: 8608
    value_type: U_WORD
    accuracy_decimals: 2
    filters:
      - multiply: 0.01

  - platform: modbus_controller
    id: atv12_temperature
    name: "ATV12 Temperature"
    register_type: holding
    address: 8610
    value_type: U_WORD
    unit_of_measurement: "°C"

  - platform: modbus_controller
    id: atv12_error_code
    name: "ATV12 Error Code"
    register_type: holding
    address: 7200
    value_type: U_WORD

# Some colors for the LED display
color:
  - id: orange
    hex: fff099
  - id: grey
    hex: 3A3B3C
  - id: blue
    hex: 9fcff9
  - id: green
    hex: 51af73
  - id: red
    hex: db6676
  - id: purple
    hex: af69e3

display:
  platform: ili9xxx
  id: vdu
  model: ST7789V
  rotation: 90
  dimensions:
    height: 240
    width: 135
    offset_height: 40
    offset_width: 52
  dc_pin: GPIO06
  cs_pin: GPIO12
  invert_colors: true
  update_interval: never
  lambda: |-
    // Date Time 
    if (id(rx8130_time).now().is_valid()) {
      it.strftime(5, 0, id(font1), id(orange), "%d/%m/%Y %H:%M", id(rx8130_time).now());
    }
    
    // Sep Line
    it.line(0, 20, 135, 20, id(grey));
    
    // IP Address
    it.print(5, 25, id(font1), id(blue), "IP:");
    if (id(wifi_1).is_connected()) {
      it.print(40, 25, id(font1), id(green), WiFi.localIP().toString().c_str());
    } else {
      it.print(40, 25, id(font1), id(red), "Offline");
    }

    // ModBus Status
    it.print(5, 45, id(font1), id(blue), "Modbus:");
    if (id(modbus_status).state) {
      it.print(70, 45, id(font1), id(green), "ONLINE");
    } else {
      it.print(70, 45, id(font1), id(red), "OFFLINE");
    }

    // Motor Status
    it.print(5, 65, id(font1), id(blue), "Motor:");
    if (id(atv12_status).state == 0x0037) {
      it.print(70, 65, id(font1), id(green), "RUN");
    } else {
      it.print(70, 65, id(font1), id(red), "STOP");
    }

    // Motor Direction
    it.print(5, 85, id(font1), id(blue), "Dir:");
    if (id(atv12_cmd).state == 0x001F) {
      it.print(80, 85, id(font1), id(green), "REV");
    } else {
      it.print(80, 85, id(font1), id(grey), "FWD");
    }

    // Motor Speed (Hz)
    it.print(5, 105, id(font1), id(blue), "Speed:");
    it.printf(100, 105, id(font1), id(orange), "%.1f Hz", id(atv12_actual_speed).state);


font:
  file: "gfonts://Roboto"
  id: font1
  size: 15

# ATV 12 Speed Control on Web Interface
number:
  - platform: modbus_controller
    id: atv12_speed_setpoint
    name: "ATV12 Speed Setpoint"
    register_type: holding
    address: 8602
    value_type: U_WORD
    min_value: 0
    max_value: 600
    step: 1

Beormund EspHome M5Stam PLC Repository

Credits

Djair Guilherme
24 projects • 21 followers
Brazilian artist. Puzzle maker, Arduino lover.
Thanks to Beormund.

Comments