GitHunt

ESP32 BACnet MS/TP WiFi Display

ESP32-based BACnet/IP device with ST7789 TFT display featuring 20 BACnet objects: 4 Analog Values, 4 Binary Values, 4 Analog Inputs, 4 Binary Inputs, and 4 Binary Outputs. Includes built-in PMS5003 air quality sensor for PM2.5/PM1.0/PM10 monitoring.

It can simultaneously connect the BACnet device through WiFi (BACnet/IP) and MS/TP (RS485 using a MAX485 module).

You can easily add extra BACnet objects and map them to ESP32 GPIO for analog and digital inputs/outputs.

Features

  • BACnet/IP Protocol: Full BACnet/IP stack implementation
  • BACnet MS/TP: RS485 MS/TP support alongside BACnet/IP (dual stack)
  • Live Display: Real-time monitoring of BACnet objects on 170x320 TFT display
  • 20 BACnet Objects:
    • 4 Analog Values (AV1-4) - read/write with COV and NVS persistence
    • 4 Binary Values (BV1-4) - read/write with COV and NVS persistence
    • 4 Analog Inputs (AI1-4) - sensor inputs with COV and NVS persistence
    • 4 Binary Inputs (BI1-4) - binary states with COV and NVS persistence
    • 4 Binary Outputs (BO1-4) - writable control outputs with COV and NVS persistence
  • Writable Metadata: Object Name and Description are writable for AV/BV/AI/BI/BO
  • WiFi Connectivity: ESP32 with built-in WiFi for BACnet/IP communication
  • Arduino Framework: Leverages Arduino ecosystem for easy hardware control
  • Change of Value (COV): Implements BACnet COV notifications for efficient real-time updates
  • Persistent Storage: Attribute values modifiable from BACnet supervisor are automatically saved to ESP32 non-volatile memory (NVS) for retention across power cycles
  • NVS Override: When USER_OVERRIDE_NVS_ON_FLASH=1, NVS is erased on boot and all values reset to defaults
  • Centralized Configuration: User settings are centralized in main/User_Settings.c
  • Air Quality Monitoring: PMS5003 PM2.5/PM1.0/PM10 sensor with automatic BACnet integration

Photos

ESP32 NAE
ESP32 Metasys
YABE Tool

Hardware Requirements

  • Microcontroller: ESP32-WROOM-32
  • Display: ST7789 SPI TFT (170x320 pixels)
  • Display Connections:
    • MOSI: GPIO 23
    • SCLK: GPIO 18
    • CS: GPIO 15
    • DC: GPIO 2
    • RST: GPIO 4
    • BL (Backlight): GPIO 32

Hardware Components

ST7789 TFT Display

  • Resolution: 170x320 pixels
  • Interface: SPI (4-wire)
  • Driver: Custom TFT_eSPI component with offset calibration for clone displays

PMS5003 Air Quality Sensor

  • Model: Plantower PMS5003
  • Communication: UART (9600 baud, 8N1)
  • Connections:
    • PMS5003 TX → ESP32 GPIO25 (RX1)
    • PMS5003 RX → ESP32 GPIO26 (TX1)
    • SET (Sleep Control): GPIO 27 (LOW = AWAKE, HIGH = SLEEP) — controlled by BO1 (PMS5003_SET)
    • Power: 5V (requires 5V supply, not 3.3V)
    • GND: ESP32 GND
  • Measurements:
    • PM1.0 (atmospheric)
    • PM2.5 (atmospheric) → mapped to Analog Value 1 in BACnet (configurable)
    • PM10 (atmospheric)
    • Particle counts (0.3µm - 10µm ranges)
  • BACnet Mapping: By default, PM2.5 is written to AV1. You can select any sensor parameter (PM1.0, PM2.5, PM10, or particle counts) and map it to any Analog Value object (AV1-AV4) by modifying the pms5003_task in main/main.c.
  • Update Frequency: 2-second intervals
  • Response: ~2 seconds to environmental changes
  • Features:
    • Fast and responsive sensor readings
    • Automatic byte-swapping for big-endian protocol
    • Checksum validation on all frames
    • Sensor disconnect detection with BACnet error indication (-1 value)

WiFi Connectivity

BACnet MS/TP (RS485)

  • Transceiver: MAX485 or equivalent RS485 converter
  • UART: UART2
  • Connections:
    • DI (TX) → ESP32 GPIO17
    • RO (RX) → ESP32 GPIO16
    • DE/RE → ESP32 GPIO5
  • Baud Rate: 38400 (default)
  • MS/TP Settings: MAC 6, Max Master 127, Max Info Frames 80
  • Discovery: Some controllers (e.g., NAE) require manual add on the MS/TP field bus

GPIO Summary

Pin Component Signal Definition
GPIO 2 TFT Display DC (Data/Command) components/TFT_eSPI/User_Setup.h
GPIO 4 TFT Display RST (Reset) components/TFT_eSPI/User_Setup.h
GPIO 15 TFT Display CS (Chip Select) components/TFT_eSPI/User_Setup.h
GPIO 18 TFT Display SCLK (SPI Clock) components/TFT_eSPI/User_Setup.h
GPIO 23 TFT Display MOSI (SPI Data) components/TFT_eSPI/User_Setup.h
GPIO 32 TFT Display BACKLIGHT components/TFT_eSPI/User_Setup.h
GPIO 16 MAX485 RO (RX) main/mstp_rs485.c
GPIO 17 MAX485 DI (TX) main/mstp_rs485.c
GPIO 5 MAX485 DE/RE main/mstp_rs485.c
GPIO 25 PMS5003 RX (sensor TX) components/pms5003/pms5003.h
GPIO 26 PMS5003 TX (sensor RX) components/pms5003/pms5003.h
GPIO 27 PMS5003 SET (Sleep Control) components/pms5003/pms5003.h

Build Requirements

  • ESP-IDF v5.5.1
  • Python 3.11+
  • xtensa-esp-elf toolchain

Building

cd c:\git\BACnet-ESP32-Display
idf.py build

Flashing

idf.py flash -p COM3

Or use the provided build/flash tasks in VS Code.

Monitoring Serial Output

idf.py monitor -p COM3

Configuration

Display Offset Calibration

The ST7789 display has a framebuffer offset that's compensated in components/TFT_eSPI/User_Setup.h:

#define TFT_OFFSET_X 0   // Horizontal offset
#define TFT_OFFSET_Y 0   // Vertical offset

Legacy TFT_COLSTART/TFT_ROWSTART examples are also present in comments in the same file; use one offset method consistently.

FreeRTOS Configuration

Arduino framework requires FreeRTOS tick rate of 1000Hz. This is set in sdkconfig:

CONFIG_FREERTOS_HZ=1000

User Settings (Centralized Configuration)

Most user-configurable settings are centralized in main/User_Settings.c and declared in main/User_Settings.h, including:

  • WiFi SSID/password and static IP settings
  • BACnet Device Instance and BBMD registration
  • BACnet/IP and MS/TP enable flags (USER_ENABLE_BACNET_IP, USER_ENABLE_BACNET_MSTP)
  • MS/TP parameters (MAC, baud rate, max master, max info frames)
  • Default object names, descriptions, units, and initial values

BACnet Object Configuration

  • Analog Values (AV1-4): Configure names, descriptions, units, and initial values in main/User_Settings.c

  • Binary Values (BV1-4): Configure names, descriptions, active/inactive text, and initial states in main/User_Settings.c

  • Analog Inputs (AI1-4): Configure names, descriptions, units, and COV increments in main/User_Settings.c. Read-only inputs suitable for sensor integration.

  • Binary Inputs (BI1-4): Configure names, descriptions, active/inactive text in main/User_Settings.c. Read-only binary states.

  • Binary Outputs (BO1-4): Configure names, descriptions, active/inactive text, and initial states in main/User_Settings.c. Writable control outputs with priority support.

Sensor Data Mapping

  • PMS5003 Parameters: Select which sensor parameter (PM1.0, PM2.5, PM10, or particle counts) to map to each Analog Value object in main/main.c - look for pms5003_task() function where sensor data is written to BACnet objects. Currently, PM2.5 atmospheric is written to AV1.

Architecture

Components

  • components/bacnet-stack - BACnet/IP stack (modified from bacnet-stack/bacnet-stack)
  • components/TFT_eSPI - TFT graphics library
  • main - Application code
    • main.c - BACnet initialization and main loop
    • analog_value.c/h - Analog Value object creation and NVS persistence
    • binary_value.c/h - Binary Value object creation and NVS persistence
    • analog_input.c/h - Analog Input object creation and NVS persistence
    • binary_input.c/h - Binary Input object creation and NVS persistence
    • binary_output.c/h - Binary Output object creation and NVS persistence
    • display.cpp - TFT display driver
    • wifi_helper.c - WiFi configuration helpers

Display Layout

Item Type Display
AV1 Analog Value Numeric (1 decimal)
AV2 Analog Value Numeric (1 decimal)
AV3 Analog Value Numeric (1 decimal)
AV4 Analog Value Numeric (1 decimal)
BV1 Binary Value ON/OFF + Status Dot (Blue=OFF, Green=ON)
BV2 Binary Value ON/OFF + Status Dot (Blue=OFF, Green=ON)
BV3 Binary Value ON/OFF + Status Dot (Blue=OFF, Green=ON)
BV4 Binary Value ON/OFF + Status Dot (Blue=OFF, Green=ON)

BACnet Integration

The device broadcasts its Device ID and manages BACnet objects that can be read/written by any BACnet/IP or BACnet MS/TP client (e.g., YABE, Tridium Niagara, Metasys).

BACnet Objects Exposed

  • Device: 31416 (configurable in main/User_Settings.c)
  • Analog Values: Instance 1, 2, 3, 4
  • Binary Values: Instance 1, 2, 3, 4
  • Analog Inputs: Instance 1, 2, 3, 4
  • Binary Inputs: Instance 1, 2, 3, 4
  • Binary Outputs: Instance 1, 2, 3, 4

Modifications to bacnet-stack

This project uses the official bacnet-stack with the following modifications:

  • components/bacnet-stack/ - Configured as ESP-IDF component
  • Simplified for embedded systems (reduced features, optimized for ESP32)
  • WiFi-based BACnet/IP instead of Ethernet

For a list of specific changes, see BACNET_STACK_CHANGES.md (if available).

Development Notes

Display Boundary Constants

The display code uses boundary constants for easy layout modification:

#define DISP_X0    17      // Left edge
#define DISP_Y0    40      // Top edge
#define DISP_X1    151     // Right edge
#define DISP_Y1    278     // Bottom edge
#define DISP_WIDTH 135
#define DISP_HEIGHT 239

Position all elements relative to these constants to avoid hardcoding coordinates.

Troubleshooting

Display offset issues

If text appears misaligned, adjust TFT_OFFSET_X and TFT_OFFSET_Y in components/TFT_eSPI/User_Setup.h and recompile.

WiFi connection fails

Check SSID/password in main/User_Settings.c, then verify WiFi init/connection flow in main/wifi_helper.c.

Linker errors with Arduino

Ensure CONFIG_FREERTOS_HZ=1000 is set in sdkconfig and rebuild with idf.py fullclean && idf.py build.

References