-
-
Save icq4ever/878307f74130543cb46616bbff75db49 to your computer and use it in GitHub Desktop.
Modbus ESP32 TTGO LCD Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
The device I'm using is https://myduino.com/product/tgo-007/ | |
Before compiling make a WPA_secrets.h file with the following two lines | |
const char* SECRET_SSID = "your ssid"; | |
const char* SECRET_WIFI_PASS = "your WPA pass"; | |
Libraries: | |
modbus: https://github.com/eModbus/eModbus (and it's dependencies) | |
display: https://github.com/Xinyuan-LilyGO/TTGO-T-Display | |
Arduino IDE Setup: | |
This was a good video to follow: https://www.youtube.com/watch?v=UE1mtlsxfKM&t=307s, | |
A FEW NOTES ON THAT VIDEO: | |
*) Add this to File>Pref>Board Manager URL: https://dl.espressif.com/dl/package_esp32_index.json | |
*) I dont think USB serial drivers are necessary in WIN10? Maybe I had them already... | |
*) Edit the User_Setup_Select.h per the video | |
*) No need for the Data-Uploader for our projects | |
Reading the registers in python | |
``` | |
from pymodbus.client.sync import ModbusTcpClient | |
client = ModbusTcpClient('192.168.1.115', port=502) | |
client.connect() | |
rr = client.read_holding_registers(105,1, unit=1) | |
assert(rr.function_code < 0x80) # test that we are not an error | |
print(rr.registers) | |
#client.close() | |
``` | |
Writing registers in python | |
``` | |
client = ModbusTcpClient('192.168.1.115', port=502) | |
client.connect() | |
rr = client.write_register(5,220, unit=1) | |
#assert(rr.function_code < 0x80) # test that we are not an error | |
print(rr) | |
client.close() | |
``` | |
*/ | |
#include "WPA_secrets.h" | |
#include <Arduino.h> | |
#include <WiFi.h> | |
#include <SPI.h> | |
#include "ModbusServerWiFi.h" | |
#include <Ethernet.h> | |
#include <time.h> | |
#include <NTPClient.h> | |
#include <WiFiUdp.h> | |
const char* ssid = SECRET_SSID; | |
const char* password = SECRET_WIFI_PASS; | |
IPAddress local_IP(192, 168, 1, 115); | |
IPAddress gateway(192, 168, 1, 1); | |
IPAddress subnet(255, 255, 255, 0); | |
IPAddress primaryDNS(8, 8, 8, 8); //optional | |
IPAddress secondaryDNS(8, 8, 4, 4); //optional | |
/* Modbus | |
Input registers are 16-bit registers used for input, and may only be read. | |
Holding registers are the most universal 16-bit register, may be read or written | |
API: https://github.com/emelianov/modbus-esp8266/blob/master/API.md | |
REMEMBER: memo and address are offset by 1, so IREG_SEC is at addr 101 | |
*/ | |
const uint8_t MY_SERVER(1); | |
const uint8_t nInputRegisters = 128; | |
uint16_t MBregisters[nInputRegisters]; // Allocate memory for registers | |
bool MBWriteEnabled[nInputRegisters]; | |
const uint16_t IREG_SEC = 0; //seconds | |
const uint16_t IREG_MIN = 1; //minutes | |
const uint16_t IREG_HOUR = 2; //hours | |
const uint16_t IREG_ADC2 = 3; // reading the ADC on GPIO2 | |
const uint16_t IREG_RAND = 4; | |
const uint16_t HREG_01 = 5; | |
const uint16_t HREG_02U = 6; | |
const uint16_t HREG_02L = 7; | |
union { | |
float asFloat; | |
uint16_t asInt[2]; | |
} flreg; | |
ModbusServerWiFi MBserver; | |
// NTP Time keeping | |
WiFiUDP ntpUDP; | |
NTPClient timeClient(ntpUDP, "pool.ntp.org"); | |
struct tm utc; | |
/* Display Setup | |
int16_t h = 135; | |
int16_t w = 240; | |
*/ | |
#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip | |
#include <SPI.h> | |
TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h | |
// Loop timeing variables | |
unsigned long currentMillis; | |
unsigned long ntpUpdateMillis; | |
unsigned long modbusUpdateMillis; | |
unsigned long randomUpdateMillis; | |
unsigned long adcUpdateMillis; | |
unsigned long printMBMillis; | |
const int adcPin = 32; // My board GPIO2/12 did not work... | |
void setup() { | |
/* Setup Serial */ | |
Serial.begin(115200); | |
/* Setup WiFi */ | |
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { | |
Serial.println("STA Failed to configure"); | |
} | |
Serial.print("Connecting to "); | |
Serial.println(ssid); | |
WiFi.begin(ssid, password); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.println(""); | |
Serial.println("WiFi connected!"); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
Serial.print("ESP Mac Address: "); | |
Serial.println(WiFi.macAddress()); | |
Serial.print("Subnet Mask: "); | |
Serial.println(WiFi.subnetMask()); | |
Serial.print("Gateway IP: "); | |
Serial.println(WiFi.gatewayIP()); | |
Serial.print("DNS: "); | |
Serial.println(WiFi.dnsIP()); | |
timeClient.begin(); | |
Serial.print("NTP Begin: "); | |
timeClient.update(); | |
Serial.println(timeClient.getFormattedTime()); | |
/* Setup Screen */ | |
tft.init(); | |
tft.setRotation(2); | |
tft.fillScreen(TFT_BLACK); | |
tft.setTextColor(TFT_WHITE, TFT_BLACK); | |
delay(500); | |
// Register the worker function with the Modbus server | |
MBserver.registerWorker(MY_SERVER, READ_HOLD_REGISTER, &FC03); | |
// Register the worker function again for another FC | |
MBserver.registerWorker(MY_SERVER, READ_INPUT_REGISTER, &FC03); | |
// And write holding registers | |
MBserver.registerWorker(MY_SERVER, WRITE_HOLD_REGISTER, &FC06); | |
// Port number 502, maximum of 4 clients in parallel, 10 seconds timeout | |
MBserver.start(502, 4, 10000); | |
} | |
void loop() { | |
currentMillis = millis(); | |
if (currentMillis - ntpUpdateMillis > 360000) { | |
// update NTP clock occasionally | |
timeClient.update(); | |
Serial.println(timeClient.getFormattedTime()); | |
ntpUpdateMillis = currentMillis; | |
} | |
if (currentMillis - randomUpdateMillis > 10000){ | |
MBregisters[IREG_RAND] = (uint16_t) random(65000); | |
randomUpdateMillis = currentMillis; | |
} | |
if (currentMillis - adcUpdateMillis > 1000){ | |
MBregisters[IREG_ADC2] = analogRead(adcPin); | |
//Serial.println(analogRead(adcPin)); | |
adcUpdateMillis = currentMillis; | |
} | |
if (currentMillis - modbusUpdateMillis > 1000){ | |
modbusUpdateMillis = currentMillis; | |
MBregisters[IREG_SEC] = timeClient.getSeconds(); | |
MBregisters[IREG_MIN] = timeClient.getMinutes(); | |
MBregisters[IREG_HOUR]= timeClient.getHours(); | |
tft.fillScreen(TFT_BLACK); | |
tft.setTextColor(TFT_WHITE,TFT_BLACK); | |
tft.setTextSize(2); //I think each unit is 8 tall? | |
tft.setCursor(0,0); | |
tft.print(IREG_SEC); tft.print(" SEC "); tft.println(MBregisters[IREG_SEC]); | |
tft.print(IREG_MIN); tft.print(" MIN "); tft.println(MBregisters[IREG_MIN]); | |
tft.print(IREG_HOUR); tft.print(" HR "); tft.println(MBregisters[IREG_HOUR]); | |
tft.print(IREG_ADC2); tft.print(" ADC "); tft.println(MBregisters[IREG_ADC2]); | |
tft.print(IREG_RAND); tft.print(" RND "); tft.println(MBregisters[IREG_RAND]); | |
tft.print(HREG_01); tft.print(" U01 "); tft.println(MBregisters[HREG_01]); | |
tft.print(HREG_02U); tft.print(" U2U "); tft.println(MBregisters[HREG_02U]); | |
tft.print(HREG_02L); tft.print(" U2L "); tft.println(MBregisters[HREG_02L]); | |
tft.print("FLOAT "); | |
flreg.asInt[1] = MBregisters[HREG_02U]; | |
flreg.asInt[0] = MBregisters[HREG_02L]; | |
tft.println(flreg.asFloat); | |
} | |
// We will be idling around here - all is done in subtasks :D | |
if (currentMillis - printMBMillis > 2000) { | |
Serial.print(MBserver.activeClients()); | |
Serial.println(" clients running"); | |
printMBMillis = currentMillis; | |
} | |
} | |
ModbusMessage FC06(ModbusMessage request) { | |
/* | |
ModbusMessage(uint8_t serverID, uint8_t functionCode, uint16_t p1, uint16_t p2) | |
This constructor will be one of the most used throughout, as the most relevant Modbus | |
messages have this signature (taking two 16bit parameters). These are the FCs 0x01, 0x02, 0x03, 0x04, 0x05 and 0x06. | |
*/ | |
// WRITE_HOLD_REGISTER = 0x06, | |
// Worker function for serverID=1 | |
Serial.print("FC06: Function Code: "); | |
Serial.println(request.getFunctionCode()); | |
uint16_t addr = 0; // Start address to read | |
uint16_t datr = 0; // Data of a write_register() | |
ModbusMessage response; | |
// Prints the request message | |
for (auto& byte : request) { | |
Serial.printf("%02X ", byte); | |
} | |
Serial.println(); | |
// Get addr and words from data array. Values are MSB-first, get() will convert to binary | |
request.get(2, addr); | |
request.get(4, datr); | |
Serial.print("Addr:"); Serial.println(addr); | |
Serial.print("datr:"); Serial.println(datr); | |
// address valid? | |
if (!addr || addr > nInputRegisters) { | |
// No. Return error response | |
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS); | |
return response; | |
} | |
MBregisters[addr] = datr; | |
// Prepare response | |
response.setError(request.getServerID(), request.getFunctionCode(), SUCCESS); | |
// Return the data response | |
return ECHO_RESPONSE; | |
} | |
ModbusMessage FC03(ModbusMessage request) { | |
// READ_HOLD_REGISTER = 0x03, | |
// READ_INPUT_REGISTER = 0x04, | |
// Worker function for serverID=1 | |
// holding_memo[16] | |
uint16_t addr = 0; // Start address to read | |
uint16_t wrds = 0; // Number of words to read | |
ModbusMessage response; | |
// Get addr and words from data array. Values are MSB-first, get() will convert to binary | |
request.get(2, addr); | |
request.get(4, wrds); | |
// address valid? | |
if (!addr || addr > nInputRegisters) { | |
// No. Return error response | |
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS); | |
return response; | |
} | |
// Number of words valid? | |
if (!wrds || (addr + wrds) > (nInputRegisters-1)) { | |
// No. Return error response | |
response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS); | |
return response; | |
} | |
// Prepare response | |
response.add(request.getServerID(), request.getFunctionCode(), (uint8_t)(wrds * 2)); | |
// Loop over all words to be sent | |
for (uint16_t i = 0; i < wrds; i++) { | |
// Add word MSB-first to response buffer | |
response.add(MBregisters[addr + i]); | |
} | |
// Return the data response | |
return response; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment