{
  "$schema": "https://raw.githubusercontent.com/nobrega8/mbmap-library/main/schema/mbmap-2.schema.json",
  "mbmap": 2,
  "vendor": "Electools",
  "model": "EXM-3112",
  "models": ["EXM-3112"],
  "vendor_website": "electools.com.br",
  "description": "Modbus RTU extension module with 12 digital inputs and 12 relay outputs: input and relay states, pulse counters, status, channel settings, local DI-to-relay switching and a master watchdog",
  "revision": 3,
  "revised": "2026-11-08",
  "source": "Electools Linha Extensão Modbus register map for the EXM-3112 (attached to issue #112)",
  "sources": [
    {"title": "Electools Linha Extensão Modbus - Mapa de Registradores Modbus RTU (EXM-3112)", "date": "2026-11-03", "url": "https://github.com/user-attachments/files/34334442/Mapa.Modbus.EXM3112.pdf", "note": "linked in issue #112"},
    {"title": "Mapa Modbus EXM (corrected)", "date": "2026-11-07", "url": "https://github.com/user-attachments/files/34527796/Mapa.Modbus.EXM.pdf", "note": "serial format codes, channel count layout and register 128 access; attached to test report #139"}
  ],
  "notes": [
    "Functions: FC01, FC05 and FC15 for coils, FC02 for discrete inputs, FC04 for input registers, FC03, FC06 and FC16 for holding registers. Addresses are 0-based, as sent on the wire.",
    "The 32-bit bitmaps have bits 16-31 at the lower address and bits 0-15 at the higher one. Read and write both registers together: FC06 or a partial write fails. Bit N stands for channel N+1, and the bits above channel 12 are reserved.",
    "Factory defaults: 9600 bit/s, 8E1. The Modbus address is 1 unless the ADDR1/2/4 DIP switches are set (1-7); when they are, they always take precedence over register 1792.",
    "The document does not say whether changed settings apply at once. Coil 240 saves the configuration to flash.",
    "The factory reset (coil 242) does not restore the input inversion, DirectSwitch, idle state, output inversion or watchdog enable, timeout and action settings: those are kept in FRAM.",
    "Register 128 (relay state bitmap) is read-only: the first document marked it read/write, but a write returns exception 3 and the corrected document marks it read-only. Relays are written through the coils.",
    "Tested on an EXM-3112, firmware V1.00 (hardware v1.0), November 2026: identity, uptime, digital inputs, relays and their bitmap, debounce and pulse width. A pulse counter past 65535 was not tested."
  ],
  "protocol": "rtu",
  "unit_id": 1,
  "baudrate": 9600,
  "parity": "E",
  "databits": 8,
  "stopbits": 1,
  "byte_order": "big",
  "word_order": "big",
  "address_base": 0,
  "x_status": "verified",
  "enums": {
    "baud_rate": {"0": "2400", "1": "4800", "2": "9600", "3": "19200", "4": "38400", "5": "57600", "6": "115200"},
    "output_mode": {"0": "Level", "1": "Pulse"},
    "off_on": {"0": "Off", "1": "On"},
    "watchdog_action": {"0": "Fail-safe outputs", "1": "Reboot"},
    "float_word_order": {"0": "ABCD", "1": "CDAB"},
    "serial_format": {"0": "8N1", "1": "8N2", "2": "8E1", "3": "8O1"},
    "model": {"3112": "EXM-3112"}
  },
  "registers": [
    {"name": "Relay output 1", "group": "Relay outputs", "area": "coil", "address": 0, "type": "bool", "access": "RW", "description": "RO1; starts from the idle state in register 1024"},
    {"name": "Relay output 2", "group": "Relay outputs", "area": "coil", "address": 1, "type": "bool", "access": "RW", "description": "RO2; starts from the idle state in register 1024"},
    {"name": "Relay output 3", "group": "Relay outputs", "area": "coil", "address": 2, "type": "bool", "access": "RW", "description": "RO3; starts from the idle state in register 1024"},
    {"name": "Relay output 4", "group": "Relay outputs", "area": "coil", "address": 3, "type": "bool", "access": "RW", "description": "RO4; starts from the idle state in register 1024"},
    {"name": "Relay output 5", "group": "Relay outputs", "area": "coil", "address": 4, "type": "bool", "access": "RW", "description": "RO5; starts from the idle state in register 1024"},
    {"name": "Relay output 6", "group": "Relay outputs", "area": "coil", "address": 5, "type": "bool", "access": "RW", "description": "RO6; starts from the idle state in register 1024"},
    {"name": "Relay output 7", "group": "Relay outputs", "area": "coil", "address": 6, "type": "bool", "access": "RW", "description": "RO7; starts from the idle state in register 1024"},
    {"name": "Relay output 8", "group": "Relay outputs", "area": "coil", "address": 7, "type": "bool", "access": "RW", "description": "RO8; starts from the idle state in register 1024"},
    {"name": "Relay output 9", "group": "Relay outputs", "area": "coil", "address": 8, "type": "bool", "access": "RW", "description": "RO9; starts from the idle state in register 1024"},
    {"name": "Relay output 10", "group": "Relay outputs", "area": "coil", "address": 9, "type": "bool", "access": "RW", "description": "RO10; starts from the idle state in register 1024"},
    {"name": "Relay output 11", "group": "Relay outputs", "area": "coil", "address": 10, "type": "bool", "access": "RW", "description": "RO11; starts from the idle state in register 1024"},
    {"name": "Relay output 12", "group": "Relay outputs", "area": "coil", "address": 11, "type": "bool", "access": "RW", "description": "RO12; starts from the idle state in register 1024"},
    {"name": "Relay state bitmap", "group": "Relay outputs", "area": "holding", "address": 128, "type": "uint32", "access": "R", "description": "Physical relay state after inversion, not the commanded coil value. Bit 0 = RO1 ... bit 11 = RO12"},

    {"name": "Digital input 1", "group": "Digital inputs", "area": "discrete", "address": 0, "type": "bool", "access": "R", "description": "DI1 after debounce and inversion"},
    {"name": "Digital input 2", "group": "Digital inputs", "area": "discrete", "address": 1, "type": "bool", "access": "R", "description": "DI2 after debounce and inversion"},
    {"name": "Digital input 3", "group": "Digital inputs", "area": "discrete", "address": 2, "type": "bool", "access": "R", "description": "DI3 after debounce and inversion"},
    {"name": "Digital input 4", "group": "Digital inputs", "area": "discrete", "address": 3, "type": "bool", "access": "R", "description": "DI4 after debounce and inversion"},
    {"name": "Digital input 5", "group": "Digital inputs", "area": "discrete", "address": 4, "type": "bool", "access": "R", "description": "DI5 after debounce and inversion"},
    {"name": "Digital input 6", "group": "Digital inputs", "area": "discrete", "address": 5, "type": "bool", "access": "R", "description": "DI6 after debounce and inversion"},
    {"name": "Digital input 7", "group": "Digital inputs", "area": "discrete", "address": 6, "type": "bool", "access": "R", "description": "DI7 after debounce and inversion"},
    {"name": "Digital input 8", "group": "Digital inputs", "area": "discrete", "address": 7, "type": "bool", "access": "R", "description": "DI8 after debounce and inversion"},
    {"name": "Digital input 9", "group": "Digital inputs", "area": "discrete", "address": 8, "type": "bool", "access": "R", "description": "DI9 after debounce and inversion"},
    {"name": "Digital input 10", "group": "Digital inputs", "area": "discrete", "address": 9, "type": "bool", "access": "R", "description": "DI10 after debounce and inversion"},
    {"name": "Digital input 11", "group": "Digital inputs", "area": "discrete", "address": 10, "type": "bool", "access": "R", "description": "DI11 after debounce and inversion"},
    {"name": "Digital input 12", "group": "Digital inputs", "area": "discrete", "address": 11, "type": "bool", "access": "R", "description": "DI12 after debounce and inversion"},
    {"name": "Digital input bitmap", "group": "Digital inputs", "area": "input", "address": 128, "type": "uint32", "access": "R", "description": "Bit 0 = DI1 ... bit 11 = DI12"},

    {"name": "Pulse counter DI 1", "group": "Pulse counters", "area": "input", "address": 144, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 2", "group": "Pulse counters", "area": "input", "address": 146, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 3", "group": "Pulse counters", "area": "input", "address": 148, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 4", "group": "Pulse counters", "area": "input", "address": 150, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 5", "group": "Pulse counters", "area": "input", "address": 152, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 6", "group": "Pulse counters", "area": "input", "address": 154, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 7", "group": "Pulse counters", "area": "input", "address": 156, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 8", "group": "Pulse counters", "area": "input", "address": 158, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 9", "group": "Pulse counters", "area": "input", "address": 160, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 10", "group": "Pulse counters", "area": "input", "address": 162, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 11", "group": "Pulse counters", "area": "input", "address": 164, "type": "uint32", "access": "R"},
    {"name": "Pulse counter DI 12", "group": "Pulse counters", "area": "input", "address": 166, "type": "uint32", "access": "R"},

    {"name": "Watchdog tripped", "group": "Status", "area": "input", "address": 132, "type": "bool", "bit": 0, "access": "R"},
    {"name": "Configuration saved", "group": "Status", "area": "input", "address": 132, "type": "bool", "bit": 1, "access": "R"},
    {"name": "Analog sensor error", "group": "Status", "area": "input", "address": 132, "type": "bool", "bit": 2, "access": "R", "description": "The document lists no analog channels for this model"},
    {"name": "Fail-safe active", "group": "Status", "area": "input", "address": 132, "type": "bool", "bit": 3, "access": "R"},
    {"name": "Communication quality", "group": "Status", "area": "input", "address": 133, "type": "uint16", "unit": "%", "access": "R", "description": "0-100"},
    {"name": "Uptime", "group": "Status", "area": "input", "address": 134, "type": "uint32", "unit": "s", "access": "R", "description": "Since the last boot"},

    {"name": "User LED X1", "group": "User LEDs", "area": "coil", "address": 32, "type": "bool", "access": "RW"},
    {"name": "User LED X2", "group": "User LEDs", "area": "coil", "address": 33, "type": "bool", "access": "RW"},
    {"name": "User LED X3", "group": "User LEDs", "area": "coil", "address": 34, "type": "bool", "access": "RW"},

    {"name": "Save configuration", "group": "Commands", "area": "coil", "address": 240, "type": "bool", "access": "W", "description": "Write 1 to save to flash; clears itself"},
    {"name": "Restart module", "group": "Commands", "area": "coil", "address": 241, "type": "bool", "access": "W", "description": "Write 1; clears itself"},
    {"name": "Restore factory defaults", "group": "Commands", "area": "coil", "address": 242, "type": "bool", "access": "W", "description": "Write 1; clears itself. Does not restore the settings kept in FRAM (see notes)"},
    {"name": "Clear watchdog fault", "group": "Commands", "area": "coil", "address": 243, "type": "bool", "access": "W", "description": "Write 1 to acknowledge and clear the flag; clears itself"},
    {"name": "Clear pulse counters", "group": "Commands", "area": "coil", "address": 244, "type": "bool", "access": "W", "description": "Write 1 to zero all the digital input counters; clears itself"},

    {"name": "Debounce DI 1", "group": "Digital input settings", "area": "holding", "address": 768, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 2", "group": "Digital input settings", "area": "holding", "address": 769, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 3", "group": "Digital input settings", "area": "holding", "address": 770, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 4", "group": "Digital input settings", "area": "holding", "address": 771, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 5", "group": "Digital input settings", "area": "holding", "address": 772, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 6", "group": "Digital input settings", "area": "holding", "address": 773, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 7", "group": "Digital input settings", "area": "holding", "address": 774, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 8", "group": "Digital input settings", "area": "holding", "address": 775, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 9", "group": "Digital input settings", "area": "holding", "address": 776, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 10", "group": "Digital input settings", "area": "holding", "address": 777, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 11", "group": "Digital input settings", "area": "holding", "address": 778, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Debounce DI 12", "group": "Digital input settings", "area": "holding", "address": 779, "type": "uint16", "scale": 0.1, "unit": "ms", "access": "RW", "description": "Default 200 raw (20 ms)"},
    {"name": "Input inversion bitmap", "group": "Digital input settings", "area": "holding", "address": 800, "type": "uint32", "access": "RW", "description": "Bit N = DI(N+1)"},

    {"name": "DirectSwitch enable", "group": "DirectSwitch", "area": "holding", "address": 848, "type": "uint32", "access": "RW", "description": "Bit N links DI(N+1) to RO(N+1), so it works without the master"},
    {"name": "DirectSwitch invert polarity", "group": "DirectSwitch", "area": "holding", "address": 850, "type": "uint32", "access": "RW", "description": "Bit N = DI(N+1)/RO(N+1); only with toggle off"},
    {"name": "DirectSwitch toggle", "group": "DirectSwitch", "area": "holding", "address": 852, "type": "uint32", "access": "RW", "description": "Bit N = DI(N+1)/RO(N+1); switch mode, on the rising edge"},

    {"name": "Output idle state bitmap", "group": "Relay output settings", "area": "holding", "address": 1024, "type": "uint32", "access": "RW", "description": "Idle and power-on state; bit N = RO(N+1)"},
    {"name": "Output inversion bitmap", "group": "Relay output settings", "area": "holding", "address": 1026, "type": "uint32", "access": "RW", "description": "Physical inversion, also affects the state bitmap and LED; bit N = RO(N+1)"},
    {"name": "Output mode RO 1", "group": "Relay output settings", "area": "holding", "address": 1040, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 2", "group": "Relay output settings", "area": "holding", "address": 1041, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 3", "group": "Relay output settings", "area": "holding", "address": 1042, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 4", "group": "Relay output settings", "area": "holding", "address": 1043, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 5", "group": "Relay output settings", "area": "holding", "address": 1044, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 6", "group": "Relay output settings", "area": "holding", "address": 1045, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 7", "group": "Relay output settings", "area": "holding", "address": 1046, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 8", "group": "Relay output settings", "area": "holding", "address": 1047, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 9", "group": "Relay output settings", "area": "holding", "address": 1048, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 10", "group": "Relay output settings", "area": "holding", "address": 1049, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 11", "group": "Relay output settings", "area": "holding", "address": 1050, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Output mode RO 12", "group": "Relay output settings", "area": "holding", "address": 1051, "type": "uint16", "access": "RW", "enum": "output_mode", "write_min": 0, "write_max": 1},
    {"name": "Pulse width RO 1", "group": "Relay output settings", "area": "holding", "address": 1072, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 2", "group": "Relay output settings", "area": "holding", "address": 1073, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 3", "group": "Relay output settings", "area": "holding", "address": 1074, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 4", "group": "Relay output settings", "area": "holding", "address": 1075, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 5", "group": "Relay output settings", "area": "holding", "address": 1076, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 6", "group": "Relay output settings", "area": "holding", "address": 1077, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 7", "group": "Relay output settings", "area": "holding", "address": 1078, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 8", "group": "Relay output settings", "area": "holding", "address": 1079, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 9", "group": "Relay output settings", "area": "holding", "address": 1080, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 10", "group": "Relay output settings", "area": "holding", "address": 1081, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 11", "group": "Relay output settings", "area": "holding", "address": 1082, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},
    {"name": "Pulse width RO 12", "group": "Relay output settings", "area": "holding", "address": 1083, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 500"},

    {"name": "Watchdog enable", "group": "Master watchdog", "area": "holding", "address": 1280, "type": "uint16", "access": "RW", "enum": "off_on", "write_min": 0, "write_max": 1},
    {"name": "Watchdog timeout", "group": "Master watchdog", "area": "holding", "address": 1281, "type": "uint16", "unit": "ms", "access": "RW", "description": "Default 5000"},
    {"name": "Watchdog action", "group": "Master watchdog", "area": "holding", "address": 1282, "type": "uint16", "access": "RW", "enum": "watchdog_action", "write_min": 0, "write_max": 1, "description": "What happens on timeout; does not apply to channels with DirectSwitch active"},
    {"name": "Watchdog tripped flag", "group": "Master watchdog", "area": "holding", "address": 1283, "type": "uint16", "access": "RW", "description": "Write to clear; not saved, 0 after boot"},

    {"name": "Modbus address", "group": "Communication", "area": "holding", "address": 1792, "type": "uint16", "access": "RW", "write_min": 1, "write_max": 247, "description": "The DIP switches take precedence when set"},
    {"name": "Baud rate", "group": "Communication", "area": "holding", "address": 1793, "type": "uint16", "access": "RW", "enum": "baud_rate", "write_min": 0, "write_max": 6},
    {"name": "Serial format", "group": "Communication", "area": "holding", "address": 1794, "type": "uint16", "access": "RW", "enum": "serial_format", "description": "Parity and stop bits"},
    {"name": "Float word order", "group": "Communication", "area": "holding", "address": 1795, "type": "uint16", "access": "RW", "enum": "float_word_order", "write_min": 0, "write_max": 1, "description": "Configuration only: this model has no float32 registers"},

    {"name": "Model code", "group": "Identity", "area": "holding", "address": 1808, "type": "uint16", "access": "R", "enum": "model", "role": "model", "reference": 3112},
    {"name": "Hardware version", "group": "Identity", "area": "holding", "address": 1809, "type": "uint16", "scale": 0.1, "access": "R", "description": "10 is v1.0"},
    {"name": "Firmware version", "group": "Identity", "area": "holding", "address": 1810, "type": "uint16", "scale": 0.01, "access": "R", "role": "firmware", "description": "100 is V1.00"},
    {"name": "Serial number", "group": "Identity", "area": "holding", "address": 1811, "type": "uint16", "access": "R", "role": "serial", "description": "CRC16 of the MCU's unique ID"},
    {"name": "Module label", "group": "Identity", "area": "holding", "address": 1824, "type": "string", "length": 10, "access": "RW", "description": "Up to 20 ASCII characters; default EXM3112"},
    {"name": "Digital input count", "group": "Identity", "area": "holding", "address": 1840, "type": "uint16", "bit": 12, "bits": 4, "access": "R", "reference": 12},
    {"name": "Digital output count", "group": "Identity", "area": "holding", "address": 1840, "type": "uint16", "bit": 8, "bits": 4, "access": "R", "reference": 12},
    {"name": "Analogue input count", "group": "Identity", "area": "holding", "address": 1840, "type": "uint16", "bit": 4, "bits": 4, "access": "R"},
    {"name": "Analogue output count", "group": "Identity", "area": "holding", "address": 1840, "type": "uint16", "bit": 0, "bits": 4, "access": "R"}
  ]
}
