MGuerrero31416/BACnet-ESP32-Display
ESP32 BACnet/IP device with display - Air Quality monitor
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
NameandDescriptionare 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
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
- Built-in ESP32 WiFi for BACnet/IP communication
- Configured via main/User_Settings.c
- Static IP option in main/User_Settings.c. Set
USER_WIFI_USE_STATIC_IPto 1 or 0
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 buildFlashing
idf.py flash -p COM3Or use the provided build/flash tasks in VS Code.
Monitoring Serial Output
idf.py monitor -p COM3Configuration
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 offsetLegacy 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 loopanalog_value.c/h- Analog Value object creation and NVS persistencebinary_value.c/h- Binary Value object creation and NVS persistenceanalog_input.c/h- Analog Input object creation and NVS persistencebinary_input.c/h- Binary Input object creation and NVS persistencebinary_output.c/h- Binary Output object creation and NVS persistencedisplay.cpp- TFT display driverwifi_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 239Position 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.


