I have just designed a customized Modbus DMX interface for a theater show that will premiere at SESC Pinheiros, in São Paulo. It is a research project that I have been developing to be able to relate stage automation with lighting and projections. I wrote a tutorial here on Hackster.io showing a way to interface DC motors, servo motors and Neopixel LEDs, using the DMX interface.
The demand for the show "Macuco" was a little more complex: I would need to interface a WEG W22 three-phase motor and implement direction, speed and stop controls so that it would work using the DMX light table.
The final solution was implemented using an Arduino Mega, but while I was working on the interface project, I decided to purchase an M5Stack STAM PLC as a possible device for the interface with Schneider Electric ATV12 Variable Speed Driver and decided to create an application that integrated these two devices using Modbus RTU.
I am not an expert in industrial devices, but for the project I was developing it was necessary to study a little more in depth to control the variable speed drive. What surprised me was that the commands and work protocols are relatively simpler than those I normally use when programming different microcontrollers and development boards.
The standardization of industrial devices, the extensive documentation and attentive support from Schneider allowed me to develop some simple applications in a short time.
The ATV12 variable speed drive from Schneider Electric utilizes a state machine to manage its operational modes and command processing. Understanding this state machine is crucial for effective control and troubleshooting. Here's how it works and why accessing commands through it is advantageous:
1. ATV12 State Machine OverviewThe state machine defines the drive's behavior based on its current state and received commands. The main states typically include:
- Power-off (STOP) – The drive is not powered.
- Ready (RDY) – The drive is powered but not running (awaiting commands).
- Running (RUN) – The drive is actively controlling the motor.
- Fault (FLT) – The drive has detected an error and requires intervention.
- Configuration (CONF) – The drive is in parameter-setting mode.
Transitions between these states occur based on:
- User commands** (e.g., start, stop, reset).
- Internal conditions** (e.g., faults, power loss).
- Communication signals** (if controlled via a network like Modbus).
Accessing and controlling the ATV12 through its state machine offers several benefits:
a) Predictable Behavior
- The state machine ensures that commands are processed only when the drive is in the correct state. For example:
- A "Start" command is ignored if the drive is in "Fault" state.
- A "Reset" command is only valid after a fault.
- This prevents illegal operations that could damage the drive or motor.
b) Simplified Troubleshooting
- By monitoring the current state, you can quickly diagnose issues:
- If stuck in "FLT", check error codes.
- If not switching to "RUN", verify enable signals and parameters.
c) Safe Control Sequencing
- The state machine enforces a logical flow (e.g., must be in "RDY" before "RUN").
- Prevents accidental starts/stops that could harm machinery.
d) Efficient Automation Integration
- When controlled via Modbus or a PLC, the state machine ensures synchronized communication:
- The PLC can poll the drive’s state before issuing commands.
- Reduces errors in automated systems.
M5Stack does an excellent job with its STAM PLC. The device is quite robust and versatile. It can be programmed via a web interface, using a command block structure. In my opinion, if M5Stack had already made available a programming structure using Ladder Logic, the migration of industrial users would be much easier. Even so, we have many possibilities to program the device, which makes it much easier for users in the maker territory, like me. We can choose between using ESP-IDF, Arduino, Micropython and even ESPHome, which is the objective of this tutorial.
Github user Beormund published this excellent YAML configuration file in his repository, which is the basis of the application I developed. I will transcribe it here exactly as it is in the repository currently.
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
# 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
# Allow Over-The-Air updates
ota:
- platform: esphome
#Enable logging
logger:
# Enable Home Assistant API
api:
# Allow provisioning Wi-Fi via serial
improv_serial:
wifi:
id: wifi_1
ssid: !secret wifi_ssid
password: !secret wifi_password
# Set up a wifi access point
ap: {}
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:
# 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:
sda: GPIO13
scl: GPIO15
scan: true
spi:
clk_pin: GPIO7
mosi_pin: GPIO8
miso_pin: GPIO9
# RS485 pin config
uart:
tx_pin: GPIO0
rx_pin: GPIO39
baud_rate: 9600
parity: EVEN
# 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
switch:
# Relays 1-4
- platform: gpio
restore_mode: RESTORE_DEFAULT_OFF
name: "Relay 1"
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"
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"
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"
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
# 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();
}
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
binary_sensor:
- platform: gpio
id: i1
name: "Input 1"
pin:
aw9523: aw9523_1
number: 4
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i2
name: "Input 2"
pin:
aw9523: aw9523_1
number: 5
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i3
name: "Input 3"
pin:
aw9523: aw9523_1
number: 6
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i4
name: "Input 4"
pin:
aw9523: aw9523_1
number: 7
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i5
name: "Input 5"
pin:
aw9523: aw9523_1
number: 12
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i6
name: "Input 6"
pin:
aw9523: aw9523_1
number: 13
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i7
name: "Input 7"
pin:
aw9523: aw9523_1
number: 14
mode:
input: true
on_state:
then:
- component.update: vdu
- platform: gpio
id: i8
name: "Input 8"
pin:
aw9523: aw9523_1
number: 15
mode:
input: true
on_state:
then:
- component.update: vdu
# Buttons 1-3
- platform: gpio
name: "Button A"
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"
- platform: gpio
name: "Button B"
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"
- platform: gpio
name: "Button C"
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"
sensor:
# INA226 Voltage/Current Sensor on i2c default Address 0x40
- platform: ina226
shunt_resistance: 0.01
max_current: 8.192
current:
name: "Current"
entity_category: "diagnostic"
power:
name: "Power"
entity_category: "diagnostic"
bus_voltage:
name: "Bus Voltage"
entity_category: "diagnostic"
shunt_voltage:
name: "Shunt Voltage"
entity_category: "diagnostic"
# LM75B Temp Sensor on i2c default address 0x48
- platform: lm75b
name: "Temperature"
update_interval: 60s
entity_category: "diagnostic"
# 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
#show_test_card: true
lambda: |-
if (id(rx8130_time).now().is_valid()) {
it.strftime(5, 0, id(font1), Color(orange), "%a %d %b %Y %H:%M %Z", id(rx8130_time).now());
}
it.line(5, 19, 230, 19, id(grey));
it.print(5, 28, id(font1), id(orange), "Inputs 1-8");
it.filled_rectangle(5, 47, 25, 25, id(i1).state ? id(purple) : id(grey));
it.filled_rectangle(34, 47, 25, 25, id(i2).state ? id(purple) : id(grey));
it.filled_rectangle(63, 47, 25, 25, id(i3).state ? id(purple) : id(grey));
it.filled_rectangle(92, 47, 25, 25, id(i4).state ? id(purple) : id(grey));
it.filled_rectangle(121, 47, 25, 25, id(i5).state ? id(purple) : id(grey));
it.filled_rectangle(150, 47, 25, 25, id(i6).state ? id(purple) : id(grey));
it.filled_rectangle(179, 47, 25, 25, id(i7).state ? id(purple) : id(grey));
it.filled_rectangle(208, 47, 25, 25, id(i8).state ? id(purple) : id(grey));
it.print(5, 76, id(font1), Color(orange), "Relays 1-4");
it.filled_rectangle(5, 95, 25, 25, id(r1).state ? id(red) : id(grey));
it.filled_rectangle(34, 95, 25, 25, id(r2).state ? id(red) : id(grey));
it.filled_rectangle(63, 95, 25, 25, id(r3).state ? id(red) : id(grey));
it.filled_rectangle(92, 95, 25, 25, id(r4).state ? id(red) : id(grey));
it.rectangle(150, 95, 81, 25, id(blue));
it.print(175, 100, id(font1), id(wifi_1).is_connected() ? id(green) : id(grey), "WiFi");
font:
file: "gfonts://Roboto"
id: font1
size: 15
Controlling the ATV12 from Modbus RTU: A Step-by-Step Logic GuideThis guide explains how to interact with the Schneider Electric ATV12 variable speed drive using Modbus RTU. You’ll learn how to:
✔ Start and stop the motor
✔ Change direction
✔ Adjust speed
✔ Monitor motor status
✔ Read fault codes
Configuration StepsAccess Configuration Mode
Press the MODE button until the display shows "ConF" (Configuration mode)
- Press the MODE button until the display shows "ConF" (Configuration mode)
- Access Configuration ModePress the MODE button until the display shows "ConF" (Configuration mode)
Navigate to Communication Menu
Scroll to find the "CON-" menu (Communication menu)
- Scroll to find the "CON-" menu (Communication menu)
- Navigate to Communication Menu - Scroll to find the "CON-" menu (Communication menu)
Set Modbus Address
Select "Add" (Modbus address)
- Select "Add" (Modbus address)
Set a unique address between 1-247 (default is OFF/inactive)
- Set a unique address between 1-247 (default is OFF/inactive)
Press ENT to confirm
- Press ENT to confirm
- Set Modbus Address: Select "Add" (Modbus address)
Set a unique address between 1-247 (default is OFF/inactive)
Press ENT to confirm
Configure Baud Rate
Select "tbr" (Modbus baud rate)
- Select "tbr" (Modbus baud rate)
Choose from available options (4.8, 9.6, 19.2, or 38.4 kbps)
- Choose from available options (4.8, 9.6, 19.2, or 38.4 kbps)
Default is 19.2 kbps
- Default is 19.2 kbps
Press ENT to confirm
- Press ENT to confirm
- Configure Baud RateSelect "tbr" (Modbus baud rate)
Choose from available options (4.8, 9.6, 19.2, or 38.4 kbps)
Default is 19.2 kbps
Press ENT to confirm
Set Communication Format
Select "tFD" (Modbus format)
- Select "tFD" (Modbus format)
Choose the matching format for your system:
8 data bits, even parity, 1 stop bit (8E1)
- 8 data bits, even parity, 1 stop bit (8E1)
8 data bits, no parity, 1 stop bit (8N1)
- 8 data bits, no parity, 1 stop bit (8N1)
etc. (match to your master device)
- etc. (match to your master device)
- Choose the matching format for your system:8 data bits, even parity, 1 stop bit (8E1)
8 data bits, no parity, 1 stop bit (8N1)etc. (match to your master device)
Default is 8E1
- Default is 8E1
Press ENT to confirm
- Press ENT to confirm
- Set Communication FormatSelect "tFD" (Modbus format)
Choose the matching format for your system:8 data bits, even parity, 1 stop bit (8E1)
8 data bits, no parity, 1 stop bit (8N1)etc. (match to your master device)
Default is 8E1
Press ENT to confirm
Set Timeout (Optional)
Select "ttD" (Modbus timeout)
- Select "ttD" (Modbus timeout)
Set time (0.1-30s) for communication failure detection
- Set time (0.1-30s) for communication failure detection
Default is 10s
- Default is 10s
Press ENT to confirm
- Press ENT to confirm
- Set Timeout (Optional): Select "ttD" (Modbus timeout)
Set time (0.1-30s) for communication failure detection
Default is 10s
Press ENT to confirm
Configure Command Source
Navigate to "CtL-" (Command menu)
- Navigate to "CtL-" (Command menu)
Set "Fr1" (Reference channel 1) to "db" (Modbus)
- Set "Fr1" (Reference channel 1) to "db" (Modbus)
Set "Ctl" (Command channel 1) to "db" (Modbus) if using separate channels
- Set "Ctl" (Command channel 1) to "db" (Modbus) if using separate channels
- Configure Command Source
Navigate to "CtL-" (Command menu)
Set "Fr1" (Reference channel 1) to "db" (Modbus)
Set "Ctl" (Command channel 1) to "db" (Modbus) if using separate channels
Enable Modbus Fault Management (Recommended)
Navigate to "FLt-" (Fault management menu)
- Navigate to "FLt-" (Fault management menu)
Set "MbL" (Modbus fault management) to "YES" (inertia stop on comms failure)
- Set "MbL" (Modbus fault management) to "YES" (inertia stop on comms failure)
- Enable Modbus Fault Management (Recommended)
Navigate to "FLt-" (Fault management menu)
Set "MbL" (Modbus fault management) to "YES" (inertia stop on comms failure)
Save Configuration
Press ESC multiple times to exit menus
- Press ESC multiple times to exit menus
The settings are automatically saved
- The settings are automatically saved
- Save Configuration - Press ESC multiple times to exit menus
The settings are automatically saved
Power cycle the ATV12
- Power cycle the ATV12
The drive should now respond to Modbus commands from your master device
- The drive should now respond to Modbus commands from your master device
You can monitor communication status in the "MOn" mode under parameter "{ }"
- You can monitor communication status in the "MOn" mode under parameter "{ }"
Ensure all devices on the Modbus network use the same baud rate and format
- Ensure all devices on the Modbus network use the same baud rate and format
The ATV12 supports standard Modbus RTU protocol
- The ATV12 supports standard Modbus RTU protocol
Refer to the manual's Modbus address map (page 83 onward) for register details
- Refer to the manual's Modbus address map (page 83 onward) for register details
For troubleshooting, check the last fault code in "dP1" parameter
- For troubleshooting, check the last fault code in "dP1" parameter
This configuration allows complete control of the ATV12 via Modbus, including start/stop commands, speed reference, and monitoring of drive parameters.
1. Modbus RTU SetupBefore sending commands, ensure:
✅ Physical connection is correct (RS485 on RJ45 pins 4 [D1], 5 [D0], and 8 [Common]).
✅ Baud rate, parity, and address match between the drive and your Modbus master (default: 19.2kbps, 8E1, address 1).
✅ Termination resistors (120Ω) are used if the drive is at the end of the bus.
2. Configuring Modbus ParametersSet these parameters in the COM- menu:
Modbus Address (Add)
Sets the drive's Modbus slave ID
- Sets the drive's Modbus slave ID
Valid range: 1 to 247
- Valid range: 1 to 247
Default: 1
- Default: 1
- Modbus Address (Add) - Sets the drive's Modbus slave IDValid range: 1 to 247Default: 1
Baud Rate (bFr)
Communication speed options:
4.8 kbps
- 4.8 kbps
9.6 kbps
- 9.6 kbps
19.2 kbps (default)
- 19.2 kbps (default)
38.4 kbps
- 38.4 kbps
- Communication speed options: 4.8 kbps9.6 kbps19.2 kbps (default)38.4 kbps
- Baud Rate (bFr)- Communication speed options:4.8 kbps9.6 kbps19.2 kbps (default)38.4 kbps
Data Format (Frd)
Parity and stop bits options:
8E1 (8-bit, Even parity, 1 stop bit) - recommended
- 8E1 (8-bit, Even parity, 1 stop bit) - recommended
8O1 (Odd parity)
- 8O1 (Odd parity)
8N1 (No parity)
- 8N1 (No parity)
8N2 (No parity, 2 stop bits)
- 8N2 (No parity, 2 stop bits)
- Parity and stop bits options:8E1 (8-bit, Even parity, 1 stop bit) - recommended8O1 (Odd parity)8N1 (No parity)8N2 (No parity, 2 stop bits)
- Data Format (Frd) - Parity and stop bits options:8E1 (8-bit, Even parity, 1 stop bit) - recommended8O1 (Odd parity)8N1 (No parity)8N2 (No parity, 2 stop bits)
Modbus Timeout (tOd)
Response timeout period
- Response timeout period
Range: 0.1 to 30 seconds
- Range: 0.1 to 30 seconds
Default: 10.0 sec
- Default: 10.0 sec
- Modbus Timeout (tOd) - Response timeout periodRange: 0.1 to 30 secondsDefault: 10.0 sec
Fault Management (SLL)
Critical parameter for safety
- Critical parameter for safety
Options:
n0: Ignore communication faults (not recommended)
n1: Freewheel stop on fault (default)
n2: Ramp stop on fault
n3: DC injection stop on fault
To configure:
Navigate to each parameter using ▲/▼ buttons
- Navigate to each parameter using ▲/▼ buttons
Press ENT to edit
- Press ENT to edit
Adjust value using ▲/▼
- Adjust value using ▲/▼
Press ENT to confirm
- Press ENT to confirm
Repeat for all parameters
- Repeat for all parameters
Note: After changing settings, cycle power or go to the 'SUP-' menu and select 'FCS' (Factory Communication Settings) then 'YES' to apply changes.
3. Controlling the MotorA. Starting the MotorThe ATV12 follows a state machine—you must transition through proper states.
Check the current state (read ETA
register 0x0C81
).
If ETA & 0x006F = 0x0031
, the drive is ready to start.
- If
ETA & 0x006F = 0x0031
, the drive is ready to start. - Check the current state (read
ETA
register0x0C81
).IfETA & 0x006F = 0x0031
, the drive is ready to start.
Send the "Switch On" command:
Write 0x0007
to CMD
(0x2135
).
- Write
0x0007
toCMD
(0x2135
). - Send the "Switch On" command:Write
0x0007
toCMD
(0x2135
).
Enable operation:
Write 0x000F
to CMD
.
- Write
0x000F
toCMD
. - Enable operation:Write
0x000F
toCMD
.
Verify the motor is running:
Check if ETA & 0x006F = 0x0037
.
- Check if
ETA & 0x006F = 0x0037
. - Verify the motor is running:Check if
ETA & 0x006F = 0x0037
.
Normal stop: Write 0x0007
to CMD
.
- Normal stop: Write
0x0007
toCMD
.
Emergency stop: Write 0x0002
to CMD
.
- Emergency stop: Write
0x0002
toCMD
.
Forward: Clear bit 11 (0x0800
) in the CMD
register.
- Forward: Clear bit 11 (
0x0800
) in theCMD
register.
Reverse: Set bit 11 (0x0800
) in the CMD
register.
- Reverse: Set bit 11 (
0x0800
) in theCMD
register.
Write a value (0–65535) to LFRD
(0x219A
), where:
0
= 0% speed
0
= 0% speed
32768
= 50% speed
32768
= 50% speed
65535
= 100% speed
65535
= 100% speed- Write a value (0–65535) to
LFRD
(0x219A
), where:0
= 0% speed32768
= 50% speed65535
= 100% speed
The ETA
register (0x0C81
) provides real-time status:
State: Ready to Switch On
ETA Value (Hex): 0x0031
Description: Drive is powered but inactive
State: Switched On
ETA Value (Hex): 0x0033
Description: Drive is enabled but motor not running
State: Operation Enabled
ETA Value (Hex): 0x0037
Description: Motor is running
State: Fault
ETA Value (Hex): 0x0018
Description: Drive has detected an error
B. Checking Faults at ATV12If ETA & 0x0008
is set, a fault is active.
- If
ETA & 0x0008
is set, a fault is active.
Read the fault code from 0x0E1A
(3610).
- Read the fault code from
0x0E1A
(3610).
Reset the fault:
Write 0x0080
to CMD
(rising edge on bit 7).
- Write
0x0080
toCMD
(rising edge on bit 7). - Reset the fault:Write
0x0080
toCMD
(rising edge on bit 7).
Initialize communication (correct baud rate, address).
- Initialize communication (correct baud rate, address).
Check ETA
register before sending commands.
- Check
ETA
register before sending commands.
Send CMD
values to transition states (start, stop, reverse).
- Send
CMD
values to transition states (start, stop, reverse).
Set speed via LFRD
.
- Set speed via
LFRD
.
Monitor ETA
for status updates.
- Monitor
ETA
for status updates.
Check for faults and reset if needed.
- Check for faults and reset if needed.
First, I will highlight how to properly configure the Uart bus and the Modbus controller. Then, I will show the ESPHome approach to handling the Modbus registers. Finally, I will share the complete YAML, with the changes.
I keep several unused inputs and outputs in the scope in my YAML, just to make it easier to understand.
# RS485 pin config - Where Modbus connection will occur.
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
# Now, the Modbus Controller Itself
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 ATV12 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", "Falha detectada. Enviando comando de reset...");
id(reset_atv12).execute();
}
This first block initializes the UART bus, the Modbus bus and the Modbus controller. Later on, we will have a binary sensor, to indicate whether the Modbus connection has been established or not. We also create a check of the important statuses in the ATV12 every 500ms.
In the example application, we will connect the Relay outputs of the STAM PLC to the digital inputs LI1~4 of the ATV12 to integrate manual commands into the system.
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
We can also create template switches to activate the motor and change its direction via the web interface. The template number is used to adjust the speed.
# 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
# ATV 12 Speed Control on Web Interface
number:
- platform: modbus_controller
id: atv12_speed_setpoint
name: "ATV12 Velocidade Setpoint"
register_type: holding
address: 8602
value_type: U_WORD
min_value: 0
max_value: 600
step: 1
Adding a Reset Script is also easy:
script:
# Script to Reset ATV 12
- id: reset_atv12
then:
- lambda: |-
ESP_LOGI("reset", "Reseting ATV12.");
id(atv12_cmd).publish_state(0x0008); // Reset ATV12 Command
Using STAM PLC digital inputs as binary sensors to control Relays, detect Modbus connection, detect common ATV12 Fails and using STAMP PLC Buttons A, B and C to control Relay outputs.
# 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(r4).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);
In EspHome, the correct approach is to add the modbus registers as sensors. The code block below indicates this.
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
Finally, the display of date and time, PLC IP address, Modbus bus and motor status, on the STAM PLC display.
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: |-
// Title and 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
Final YAML FileBelow is the complete YAML file, with all implementations.
Note that the fields referring to sensitive data such as passwords, calls to the Home Assistant API, etc. are blank and need to be filled in with the values referring to your setup.
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
Final ThoughtsThe goal of this tutorial was to show how it is possible to implement a Modbus controller via ESPHome. Each manufacturer and model of variable speed driver must have its own addresses and parameters, but I hope that the step-by-step explanation will help other people to use different devices with the M5Stack STAM PLC.
In a future tutorial, I will try to do the same implementation using M5Stack's UiFlow block language, which I am not very familiar with yet.
See you next time!
Comments