Changes for the Arduino Mega setups

This commit is contained in:
2024-12-07 10:20:29 +02:00
parent dc2b3429a6
commit 4c77b98388
45 changed files with 1705 additions and 0 deletions

View File

@@ -0,0 +1,356 @@
#include <Wire.h> // Library for I2C communication (used by RTC)
#include <RTClib.h> // Library for Real Time Clock
#include <ModbusMaster.h> // Library for Modbus communication
#include "util.h" // Custom utility functions
#include "register_map.h" // Map of Modbus registers to read
#include <SPI.h> // Library for SPI communication (used by SD card)
#include <SdFat.h> // Enhanced SD card library
// ==== PIN CONNECTIONS AND SETTINGS ====
/*
Physical Connections Guide:
SD CARD MODULE:
- CS (Chip Select) -> Arduino MEGA pin 53
- MOSI -> Arduino MEGA pin 51
- MISO -> Arduino MEGA pin 50
- SCK -> Arduino MEGA pin 52
- VCC -> 5V
- GND -> GND
RS485 MODULE:
- DI (Data In) -> Arduino MEGA TX1 (Pin 18)
- RO (Receive Out) -> Arduino MEGA RX1 (Pin 19)
- DE & RE (Data/Receive Enable) -> Arduino MEGA Pin 4
- VCC -> 5V
- GND -> GND
- A & B -> To Modbus device (polarity sensitive)
RTC MODULE (DS3231):
- SDA -> Arduino MEGA Pin 20
- SCL -> Arduino MEGA Pin 21
- VCC -> 5V
- GND -> GND
STATUS LEDs:
- LED A (Activity) -> Arduino MEGA Pin 3 (blinks during normal operation)
- LED B (Error) -> Arduino MEGA Pin 5 (blinks during errors)
*/
// ==== CONFIGURATION SETTINGS ====
#define SD_CS_PIN 53 // SD card chip select pin (uses MEGA's default SS pin)
#define DE_RE_PIN 4 // Controls RS485 direction (transmit/receive switching)
#define SLAVE_ID 1 // Modbus device address (change to match your device)
#define SERIAL_BAUDRATE 115200 // Speed for debug messages via USB
#define MODBUS_SERIAL_BAUDRATE 9600 // Speed for Modbus communication
#define LED_A_PIN 3 // Activity LED (blinks during normal operation)
#define LED_B_PIN 5 // Error LED (blinks when problems occur)
#define MAX_RETRIES 1 // Number of times to retry failed readings
#define ERROR_VALUE -999.99 // Value used to indicate reading errors
// ==== SD CARD CONFIGURATION ====
// Sets up the SD card for optimal performance with MEGA
#define SPI_CLOCK SD_SCK_MHZ(16) // //50 (fast) //16 (half) //4 (slow) SD card speed (50MHz)
// Choose the best SD card mode based on hardware capabilities
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO) // Use SDIO if available (faster)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) // Dedicated SPI bus
#else
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) // Shared SPI bus
#endif
// ==== GLOBAL OBJECTS ====
RTC_DS3231 rtc; // Real Time Clock object for timekeeping
SdFat32 sd; // SD card object for file operations
File dataFile; // File object for data logging
ModbusMaster node; // Modbus communication object
// ==== GLOBAL VARIABLES ====
unsigned long lastRefreshTime = 0; // Tracks time between readings
bool headerWritten = false; // Tracks if CSV header has been written
bool booted = false; // Tracks if setup completed successfully
// ==== UTILITY FUNCTIONS ====
// Makes an LED blink a specified number of times
// pin: which LED to blink
// times: how many blinks
// speed: how fast to blink (in milliseconds)
void flicker(uint8_t pin, uint8_t times, uint16_t speed) {
while(times--) {
delay(speed);
digitalWrite(pin, HIGH); // Turn LED on
delay(speed);
digitalWrite(pin, LOW); // Turn LED off
}
}
// ==== SETUP FUNCTION ====
// Runs once when the Arduino starts or resets
// Initializes all hardware and prepares for operation
void setup() {
booted = false; // Mark as not ready
// Setup status LEDs
pinMode(LED_A_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
digitalWrite(LED_A_PIN, LOW); // Activity LED off
digitalWrite(LED_B_PIN, HIGH); // Error LED on until setup complete
// Start serial communication for debugging via USB
Serial.begin(SERIAL_BAUDRATE);
Serial.println(F("Startup \n"));
// Start serial communication for Modbus (using hardware Serial1)
Serial1.begin(MODBUS_SERIAL_BAUDRATE);
// Initialize Real Time Clock
if (!rtc.begin()) {
Serial.println(F("Couldn't find RTC\n"));
flicker(LED_B_PIN, 4, 1000); // Error pattern: 4 slow blinks
digitalWrite(LED_B_PIN, HIGH);
digitalWrite(LED_A_PIN, HIGH);
return; // Stop if RTC fails
}
// Check if RTC lost power and reset time if needed
if (rtc.lostPower()) {
Serial.println(F("RTC lost power, let's set the time!\n"));
rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Set to compile time
flicker(LED_B_PIN, 4, 500); // Warning pattern: 4 medium blinks
}
// Initialize SD card
pinMode(SD_CS_PIN, OUTPUT);
// Set pin to high, hopefully fix Mega issue
digitalWrite(SD_CS_PIN, HIGH);
if (!sd.begin(SD_CONFIG)) {
flicker(LED_B_PIN, 2, 1000); // Error pattern: 2 slow blinks
digitalWrite(LED_B_PIN, HIGH);
sd.initErrorHalt(&Serial);
return; // Stop if SD card fails
}
// Setup RS485 communication direction control
pinMode(DE_RE_PIN, OUTPUT);
digitalWrite(DE_RE_PIN, LOW); // Start in receive mode
// Initialize Modbus communication
node.begin(SLAVE_ID, Serial1); // Using Hardware Serial1 for Modbus
node.preTransmission(preTransmission); // Set callbacks for RS485 direction control
node.postTransmission(postTransmission);
flicker(LED_B_PIN, 10, 100); // Success pattern: 10 quick blinks
digitalWrite(LED_B_PIN, LOW); // Turn off error LED
booted = true; // Mark setup as complete
}
// ==== RS485 CONTROL FUNCTIONS ====
// Called before Modbus transmission begins
void preTransmission() {
digitalWrite(DE_RE_PIN, HIGH); // Enable transmitter
digitalWrite(LED_A_PIN, HIGH); // Turn on activity LED
}
// Called after Modbus transmission completes
void postTransmission() {
digitalWrite(DE_RE_PIN, LOW); // Enable receiver
digitalWrite(LED_A_PIN, LOW); // Turn off activity LED
}
// ==== FILE OPERATIONS ====
// Writes the current date and time to the CSV file
void writeDateTime(File &file) {
DateTime now = rtc.now(); // Get current time from RTC
file.print('\n'); // Start new line
// Write date and time in format: YYYY-MM-DD HH:MM:SS,
file.print(now.year(), DEC);
file.print('-');
file.print(now.month(), DEC);
file.print('-');
file.print(now.day(), DEC);
file.print(' ');
file.print(now.hour(), DEC);
file.print(':');
file.print(now.minute(), DEC);
file.print(':');
file.print(now.second(), DEC);
file.print(',');
}
// Generates filename based on current date (format: pm8k_YYYYMMDD.csv)
void getFilename(char* buffer) {
DateTime now = rtc.now();
sprintf(buffer, "log_%d%02d%02d.csv", now.year(), now.month(), now.day());
}
// ==== MODBUS OPERATIONS ====
// Reads a Modbus register with retry capability
// addr: register address to read
// regtype: type of register (1=int, 2=float, 3=long)
// Returns: register value or ERROR_VALUE if failed
float readRegisterWithRetry(uint16_t addr, uint8_t regtype) {
for(uint8_t retry = 0; retry < MAX_RETRIES; retry++) {
delay(5); // Short delay between attempts
uint8_t result = node.readHoldingRegisters(addr - 1, 2); // Read register
if(result == node.ku8MBSuccess) {
// Convert register value based on type
switch(regtype) {
case 1: // Integer
return node.getResponseBuffer(0);
case 2: // Float
return getRegisterFloat(node.getResponseBuffer(0), node.getResponseBuffer(1));
case 3: // Long
return getRegisterInt64(node.getResponseBuffer(0), node.getResponseBuffer(1),
node.getResponseBuffer(2), node.getResponseBuffer(3));
}
}
// Log error if read failed
Serial.print(F("Read error at register "));
Serial.print(addr);
Serial.print(F(", attempt "));
Serial.print(retry + 1);
Serial.print(F(" of "));
Serial.print(MAX_RETRIES);
Serial.print(F(", error code: "));
Serial.println(result);
delay(5 * (retry + 1)); // Increasing delay between retries
flicker(LED_B_PIN, 1, 50); // Quick error blink
}
return ERROR_VALUE; // Return error value if all retries failed
}
// Writes the CSV header row if it hasn't been written yet
void writeHeader() {
if (!headerWritten) {
dataFile.print("\nDate Time,");
// Write register addresses as column headers
const uint16_t totalReg = sizeof(registers) / sizeof(registers[0]);
for (uint16_t i = 0; i < totalReg; i++) {
const uint16_t regaddr = pgm_read_word(&registers[i].regaddr);
dataFile.print("@");
dataFile.print(regaddr);
dataFile.print(",");
}
headerWritten = true;
flicker(LED_A_PIN, 50, 10); // Success pattern: 50 quick blinks
}
}
// ==== MAIN PROGRAM LOOP ====
void loop() {
// If setup failed, wait and try again
if (!booted) {
delay(10000); // Wait 10 seconds
digitalWrite(LED_A_PIN, LOW);
return;
}
// Check if it's time for next reading (every 1000ms)
if (millis() - lastRefreshTime >= 1000) {
lastRefreshTime += 1000;
// Create new file for today's date
char filename[20];
getFilename(filename);
// Try to open the data file
if (!dataFile.open(filename, FILE_WRITE)) {
flicker(LED_B_PIN, 6, 500); // Error pattern: 6 medium blinks
return;
}
// Write header if needed and timestamp
writeHeader();
writeDateTime(dataFile);
// Initialize variables for reading registers
const uint16_t totalReg = sizeof(registers) / sizeof(registers[0]);
float baseValues[4] = {ERROR_VALUE, ERROR_VALUE, ERROR_VALUE, ERROR_VALUE};
uint8_t errorCount = 0;
// Read and process all registers
for (uint16_t i = 0; i < totalReg; i++) {
// Get register information
const uint16_t regaddr = pgm_read_word(&registers[i].regaddr);
const uint8_t regtype = pgm_read_word(&registers[i].regtype);
const float scale = pgm_read_float(&registers[i].scale);
float value = ERROR_VALUE;
// Read basic register types
if (regtype <= 3 && regaddr > 0) {
value = readRegisterWithRetry(regaddr, regtype);
if (value == ERROR_VALUE) {
errorCount++;
if (errorCount > 5) { // Too many errors, abort
dataFile.close();
flicker(LED_B_PIN, 10, 100); // Error pattern: 10 quick blinks
return;
}
}
if (i < 4) baseValues[i] = value; // Store first 4 values for calculations
} else {
// Check if we have valid base values for calculations
bool validBase = true;
for(uint8_t j = 0; j < 4; j++) {
if (baseValues[j] == ERROR_VALUE) {
validBase = false;
break;
}
}
// Calculate derived values if base values are valid
if (validBase) {
switch(regtype) {
case 4:
value = calculateStatusWord(baseValues);
break;
case 5:
value = calculateThermal(baseValues);
break;
case 6:
value = calculatePower(baseValues);
break;
case 7:
value = calculateRPM(baseValues);
break;
}
}
}
// Apply scaling factor if value is valid
if (value != ERROR_VALUE) {
value *= scale;
}
// Write value to file
dataFile.print(value);
dataFile.print(',');
}
// Close file after writing
dataFile.close();
// Report status
if (errorCount > 0) {
Serial.print(F("Cycle completed with "));
Serial.print(errorCount);
Serial.println(F(" errors"));
flicker(LED_B_PIN, errorCount, 200); // Error pattern: blink count = error count
} else {
Serial.println(F("Cycle completed successfully"));
flicker(LED_A_PIN, 4, 100); // Success pattern: 4 quick blinks
}
// Abort if too many errors
if (errorCount > 5) {
Serial.println(F("Too many errors, aborting cycle"));
dataFile.close();
return;
}
}
}

View File

@@ -0,0 +1,135 @@
# Modbus Reading for Generic VSD Device - MEGA Implementation
This is a specification and implementation of the Arduino MEGA 2560-based Modbus data logger for a Generic VSD Device.
This software is designed for Vivarox EMS and only Vivarox has right to use and modify this software.
## Arduino Implementation:
This project uses an Arduino MEGA 2560 to connect to Modbus devices, read information, and log it onto an SD card with timestamps. The MEGA 2560 is particularly well-suited for this project due to its increased memory capacity and multiple hardware serial ports.
### Hardware needed:
1. Arduino Board
Required: Arduino MEGA 2560 (selected for its 256KB flash memory, 8KB SRAM, and multiple hardware serial ports)
- [Arduino MEGA @ R377.20](https://www.robotics.org.za/MEGA-16U2?search=Arduino%20MEGA%202560)
2. RS485 to TTL Module
Allows communication between the Arduino and Modbus devices using the RS485 protocol.
- [RS485 Module (TTL -> RS485) @ R25.30](https://www.robotics.org.za/RS485-MOD)
- [MAX485 Bus Transceiver (4 Pack) @ R16.00](https://www.robotics.org.za/MAX485-DIP?search=MAX485)
3. SD Card Module
Allows the Arduino to read from and write data to an SD card.
- [Micro SD Card Module @ R25.00](https://www.diyelectronics.co.za/store/memory/512-micro-sd-card-module.html?srsltid=AfmBOoptww8c6kx53xbZWiP2_C_qOE3r9xinyoCO-AZHrZkNQiyxU17c)
4. RTC Module
To keep track of the current date and time, even when the Arduino is powered off.
- [DS3231 Real Time Clock Module @ R55.20](https://www.robotics.org.za/DS3231-MOD?search=DS3231)
5. Power Supply
To power the Arduino and connected peripherals.
- [AC Adapter 9V with barrel jack @ R60](https://www.robotics.org.za/AC-9V-2A-2155?search=%20Power%20Supply)
6. LED Indicators
Two LEDs for status indication (not included in original cost estimate).
### Wiring for MEGA 2560
#### RS485 Module to Arduino MEGA:
1. RO (Receiver Output) to MEGA RX1 (pin 19) - Using Hardware Serial1
2. DI (Driver Input) to MEGA TX1 (pin 18) - Using Hardware Serial1
3. DE (Driver Enable) & RE (Receiver Enable) to MEGA digital pin 4
4. VCC to 5V on MEGA
5. GND to GND on MEGA
6. A & B (RS485 differential pair) to Modbus device
#### SD Card Module to Arduino MEGA:
1. VCC to 5V on MEGA
2. GND to GND on MEGA
3. MOSI to MOSI (pin 51)
4. MISO to MISO (pin 50)
5. SCK to SCK (pin 52)
6. CS (Chip Select) to digital pin 53
#### RTC Module to Arduino MEGA:
1. VCC to 5V on the MEGA
2. GND to GND on the MEGA
3. SDA to SDA (pin 20)
4. SCL to SCL (pin 21)
#### LED Indicators:
1. LED A to digital pin 3
2. LED B to digital pin 5
### Software
- Modbus Library: ModbusMaster
- SD Library: SdFat (more advanced than the standard SD library)
- RTC Library: RTClib by Adafruit
### Implementation Details
1. Modbus Configuration:
- Slave ID: 1
- Baud Rate: 9600
- Register map: Defined in separate "register_map.h" file
- Using Hardware Serial1 for improved reliability
2. Data Logging:
- Frequency: Readings taken every second
- File Format: CSV (Comma-Separated Values)
- Filename: "log_YYYYMMDD.csv" (generated daily based on current date)
- Data Structure: Timestamp, followed by register values
- Header Row: Includes register addresses for easy identification
- Larger buffer sizes possible due to MEGA's increased memory
3. Register Types Supported:
- Float (32-bit)
- Integer (32-bit)
- Long (64-bit)
- String (up to 20 characters)
- Multiple register reads supported simultaneously due to larger memory
4. Error Handling and Status Indication:
- LED A: Indicates successful data writing and transmission
- LED B: Indicates errors (e.g., SD card issues, RTC problems, Modbus communication errors)
- Serial output for debugging (115200 baud possible due to hardware serial)
5. Special Features:
- Automatic creation of new log file on date change
- Header row written only once per file
- Robust error handling for SD card, RTC, and Modbus communication
- Support for larger register maps due to increased memory
- Possibility to implement multiple Modbus device communication using additional hardware serial ports
### Programming Workflow
1. Initialize hardware (RTC, SD card, RS485 module)
2. Set up Modbus communication parameters using Hardware Serial1
3. Enter main loop:
- Read current time from RTC
- Read data from Modbus registers (larger batches possible)
- Write timestamped data to SD card
- Handle any errors and provide status indication via LEDs
- Delay for 1 second before next reading
## MEGA-Specific Advantages
- More memory allows reading more registers simultaneously
- Hardware serial ports provide more reliable communication
- Additional I/O pins available for expansion
- Possibility to monitor multiple Modbus devices using different serial ports
- Larger program space allows for more complex error handling and data processing
- No need to be selective about registers due to memory constraints
- Can implement additional features like local display or network connectivity
## Best Practices
- Use Hardware Serial1 (pins 18/19) for primary Modbus communication
- Additional Modbus devices can use Serial2 (pins 16/17) or Serial3 (pins 14/15)
- Take advantage of the extra memory to implement robust error checking
- Consider using the additional I/O pins for status displays or control interfaces
- You can include all registers from your register map without memory concerns
- Consider implementing a circular buffer for temporary data storage

View File

@@ -0,0 +1,63 @@
#include <stdint.h>
#ifndef REGISTER_MAP_VSD_H
#define REGISTER_MAP_VSD_H
struct RegisterMap {
uint16_t regaddr;
uint8_t regtype; // 1=UINT16, 2=FLOAT32, 3=INT64, 4=Status, 5=Thermal, 6=Power, 7=RPM
float scale;
};
const PROGMEM RegisterMap registers[] = {
{2910, 4, 1.0}, // Status Word
{2911, 6, 1.0}, // Min Active Value
{2912, 5, 1.0}, // Thermal Sense
{2913, 2, 10.0}, // Frequency
{2914, 1, 1.0}, // Running Hours
{2916, 1, 1.0}, // Operating Hours
{2918, 2, 1.0}, // kWh Counter
{2920, 2, 100.0}, // Input Power kW
{2922, 6, 134.102}, // Input Power HP
{2924, 2, 100.0}, // Motor Current
{2926, 2, 100.0}, // Phase I1
{2928, 2, 100.0}, // Phase I2
{2930, 2, 100.0}, // Phase I3
{2932, 7, 60.0}, // Motor RPM
{2934, 2, 10.0}, // Motor Voltage
{2935, 6, 1.0}, // Torque Nm
{2936, 5, 1.0}, // Motor Thermal
{2937, 5, 1.0}, // Heatsink Temp
{2938, 5, 1.0}, // Card Temp
{2939, 5, 1.0}, // Inverter Thermal
{2940, 2, 1.0}, // DC Link Voltage
{2941, 6, 1.0}, // Motor Torque %
{2942, 2, 100.0}, // Inverter Nominal Current
{2944, 2, 100.0}, // Inverter Max Current
{2946, 4, 1.0}, // Alarm Word 1
{2948, 4, 1.0}, // Alarm Word 2
{2950, 4, 1.0}, // Warning Word 1
{2952, 4, 1.0}, // Warning Word 2
{2954, 4, 1.0}, // Power Ups
{3000, 5, 1.0} // Over Temp Counter
};
float calculateStatusWord(float* values) {
uint16_t status = 0;
if(values[0] > 0) status |= 0x0001; // Running
if(values[1] > 100) status |= 0x0002; // Overload
return status;
}
float calculateThermal(float* values) {
return (values[0] / 100.0) * 100.0;
}
float calculatePower(float* values) {
return values[0] * 0.746; // kW to HP conversion
}
float calculateRPM(float* values) {
return values[0] * 60.0;
}
#endif

View File

@@ -0,0 +1,27 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
uint32_t getRegisterUInt32(uint16_t highWord, uint16_t lowWord) {
uint32_t val = (highWord << 16) + lowWord;
return val;
}
int32_t getRegisterInt32(uint16_t highWord, uint16_t lowWord) {
int32_t val = (highWord << 16) + lowWord;
return val;
}
int64_t getRegisterInt64(uint16_t word1, uint16_t word2, uint16_t word3, uint16_t word4) {
uint64_t val = ((uint64_t)word1 << 48) + ((uint64_t)word2 << 32) + (word3 << 16) + word4;
return val;
}
float getRegisterFloat(uint16_t highWord, uint16_t lowWord) {
uint32_t floatRaw = ((uint32_t)highWord << 16) | lowWord;
float floatValue;
memcpy(&floatValue, &floatRaw, sizeof(float));
return floatValue;
}