The project team had to develop a toy experimental setup to play with the hybrid testing concept on the desk. Hybrid testing (a.k.a. hardware-in-the-loop) combines a physical and a numerical model coupled with actuators and sensors. In our setup, the physical model is a 3D printed cantilever beam, which is coupled with a numerical model implemented as a Python script. Mechanical loading is applied to the 3D printed sample with a mini actuator while thermal loading with a wire resistor.
The overall design philosophy is that all computations involved in hybrid testing (numerical substructure and coordination algorithm) are performed in Python whereas Arduino is used as controller and DAQ for the experimental testing of the physical substructure.
Step 1: Electrical workThe first step consists of preparing the Arduino breadboard with all electrical components as described in the Fritzing drawing file uploaded under "Schematics."
The mechanical work consists of 3D printing the sample to test and the support block for the actuator and load cell. The STL files for 3D printing are available under "CAD." Once parts are printed, the actuator, the load cell, and the thermocouples are installed.
The first part of the software work consists of implementing the Arduino code that handles PID controllers for the actuator+load-cell (position/force controllers) and the relay+wire-resistor+thermocouples (temperature controller). The *.INO file can be downloaded from the "Code" section. One can test the correct functioning of the setup by sending displacement/force/temperature commands using the serial port. The first part of the code defines the I/O setting:
#include <LCD_I2C.h> // for LCD display
#include "HX711.h" // for Weatstone bridge
#include <Temperature_LM75_Derived.h> // for termocouples
LCD_I2C lcd(0x3F); // LCD
HX711 scale; // Weathstone bridge
// The Generic_LM75 class will provide 9-bit (±0.5°C) temperature for any
// LM75-derived sensor. More specific classes may provide better resolution.
Generic_LM75 temperature1(0x48);
Generic_LM75 temperature2(0x49);
Generic_LM75 temperature3(0x4A);
Generic_LM75 temperature4(0x4B);
// parameters for the temperature controller
int switchHeaterPin = 10;
int potentiometerPin = A6;
int actuatorOutwardPin = A0;
int actuatorInwardPin = A1;
int actuatorPositionPin = A2; // pin of the actuator potentiometer
int switchInwardPin = 5; // pin of the switch move inward
int switchOutwardPin = 6; // pin of the switch move outward
// HX711 circuit wiring
const int LOADCELL_DOUT_PIN = 2;
const int LOADCELL_SCK_PIN = 3;
The following code snippet shows the variables utilized to implement displacement, force, and temperature controllers. For each controller, the "Cmd" variable indicates the command, the "Fbk" variable indicates the corresponding feedback, the "Err" variable indicates the controller error computed as "Cmd-Fbk", the "Min" and "Max" variables indicate command limits and the "Tol" variable indicates the controller error tolerance which determines the end of a PID loop. Finally the "New" Boolean variable is True when a new command is received and the set to False.
// int tempAdjRead;
float temp1;
float temp2;
float temp3;
float temp4;
float temperatureCmd;
float temperatureFbk; // not used anymore
float temperatureErr;
float temperatureMin = 20.0;
float temperatureMax = 50.0;
float temperatureTol = 1.0;
bool temperatureNew = false;
float positionCmd; // mm Potmeter set point value (-25,+25 mm)
float positionFbk = 0.0; // mm
float positionErr = 0.0; // mm
float positionTol = 0.25; // mm
float positionLim = 15.0; // mm
bool positionNew = false;
float forceTare = 5.18; // this was measured without actuator connected
float forceCmd; // N
float forceFbk = 0.0; // N
float forceErr = 0.0; // N
float forceTol = 0.25; // N
float forceLim = 30.0; // N
bool forceNew = false;
float forceSca = 101232/9.81; // Calibrated scale value for load cell (N)
int forceCha = 128;
The following variables are used to handle the serial communication:
int serialBufferSize = 0;
String incomingArray; // for serial.readString()
String outcomingArray;
The following code snippet is executed at the initialization of Arduino:
void setup() {
// configure pin setting (input or output or something else)
pinMode(switchHeaterPin, OUTPUT);
pinMode(potentiometerPin, INPUT);
pinMode(switchInwardPin, INPUT_PULLUP);
pinMode(switchOutwardPin, INPUT_PULLUP);
pinMode(actuatorInwardPin, OUTPUT);
pinMode(actuatorOutwardPin, OUTPUT);
pinMode(actuatorPositionPin, INPUT);
Serial.begin(9600);
// wait until serial port is initialized
while (!Serial) {}
// Initialize library with data output pin, clock input pin and gain factor.
// Channel selection is made by passing the appropriate gain:
// - With a gain factor of 64 or 128, channel A is selected
// - With a gain factor of 32, channel B is selected
// By omitting the gain factor parameter, the library
// default "128" (Channel A) is used here.
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
scale.set_gain(forceCha); // Channel A is active
scale.set_scale(forceSca); // this value is obtained by calibrating the scale with known weights; see the README for details
//scale.tare(); // reset the scale to 0 (not good if the actuator is connected to the sample nad there is pre-load!!!)
// turn off the heater
digitalWrite(switchHeaterPin, 1);
// temperature setpoint to current feedback to avoid heater turning on
temperatureCmd = readTemperature();
// turn on LCD
initializeLCD();
// read sensors
positionFbk = readPosition(actuatorPositionPin);
forceFbk = readForce(forceCha, forceSca, 1);
temperatureFbk = readTemperature();
// update measurements on the LCD
writeOutput(positionFbk, forceFbk, temperatureFbk);
}
whereas the code snippets reported hereinafter are executed within the Arduino loop. At the beginning of the loop, Arduino reads the serial port buffer and checks if there are new displacement/force/temperature commands. For example, a position command of 2 mm is submitted as a string 'D2.0', a force command of 5 N is submitted as a string 'F5.0' and a temperature command of 50 C is submitted as a string 'T50.0'.
serialBufferSize = Serial.available();
if (serialBufferSize > 0) {
// read the buffer
incomingArray = Serial.readString();
// if the first byte is D, set the displacement
if (incomingArray.charAt(0) == 'D') {
incomingArray.remove(0, 1);
positionCmd = incomingArray.toFloat();
positionCmd = max(positionCmd, -positionLim); // lower bound
positionCmd = min(positionCmd, positionLim); // upper bound
positionNew = true;
}
// if the first byte is F, set the force
else if (incomingArray.charAt(0) == 'F') {
incomingArray.remove(0, 1);
forceCmd = incomingArray.toFloat();
forceCmd = max(forceCmd, -forceLim); // lower bound
forceCmd = min(forceCmd, forceLim); // upper bound
forceNew = true;
}
// if the first byte is S, read the displacement, force and temperature
else if (incomingArray.charAt(0) == 'S') {
positionFbk = readPosition(actuatorPositionPin);
forceFbk = readForce(forceCha, forceSca, 1);
temperatureFbk = readTemperature();
writeOutput(positionFbk, forceFbk, temperatureFbk);
}
else if (incomingArray.charAt(0) == 'T') {
incomingArray.remove(0, 1);
temperatureCmd = incomingArray.toFloat();
temperatureCmd = max(temperatureCmd, temperatureMin);
temperatureCmd = min(temperatureCmd, temperatureMax);
temperatureNew = true;
}
else if (incomingArray.charAt(0) == 'R') {
// reset something
// scale.tare();
}
}
else {
positionNew = false;
forceNew = false;
}
The following 3 code blocks are the position controller, the force controller, and the temperature controller, respectively.
// PID loop (for displacement)
if (positionNew == true) {
positionFbk = readPosition(actuatorPositionPin);
positionErr = positionCmd - positionFbk;
while (abs(positionErr) > positionTol) {
if (positionErr > 0) {
actuatorControl(2);
}
else if (positionErr < 0) {
actuatorControl(1);
}
positionFbk = readPosition(actuatorPositionPin);
positionErr = positionCmd - positionFbk;
}
forceFbk = readForce(forceCha, forceSca, 1);
actuatorControl(0);
}
---
// PID loop (for force)
if (forceNew == true) {
forceFbk = readForce(forceCha, forceSca, 1);
forceErr = forceCmd - forceFbk;
while (abs(forceErr) > forceTol) {
if (forceErr > 0) {
actuatorControl(2);
}
else if (forceErr < 0) {
actuatorControl(1);
}
forceFbk = readForce(forceCha, forceSca, 1);
forceErr = forceCmd - forceFbk;
}
positionFbk = readPosition(actuatorPositionPin);
actuatorControl(0);
}
---
// PID loop (temperature)
temperatureFbk = readTemperature();
temperatureErr = temperatureCmd - temperatureFbk;
if (abs(temperatureErr) > temperatureTol){
if (temperatureErr > 0 ) {
// temperatureCmd > temperatureFbk
digitalWrite(switchHeaterPin, 0); // turn on
}
if (temperatureErr < 0) {
// temperatureCmd < temperatureFbk
digitalWrite(switchHeaterPin, 1); // turn off
}
temperatureFbk = readTemperature();
temperatureErr = temperatureCmd - temperatureFbk;
}
The final code block packs the output feedback, sends it to the serial port buffer and updates the LCD.
// send output to serial port
if (positionNew == true || forceNew == true || temperatureNew == true) {
writeOutput(positionFbk, forceFbk, temperatureFbk);
positionNew == false;
forceNew == false;
temperatureNew = false;
}
// update LCD with same measurements
updateLCD(positionFbk, forceFbk, temperatureFbk);
The detailed implementation of all functions called in init() and loop() is not documented here but comments are provided within the "ArduinoTransferSytem.ino" code.
The second part of the software work consists of implementing a Python module that communicates with ARDUINO using the serial port through the PySerial Python module. One can test the correct functioning of the setup by sending displacement/force/temperature commands using the developed Python module.
Step 4: Validation of the setupTo validate the setup, one can implement hybrid testing for a simple single-DoF spring-dashpot-mass system using the dynamic relaxation algorithm as the coordination algorithm (scheme suggested for hybrid fire testing developed by one of the authors). The physical substructure is interfaced with the software using the developed Python module. The related Python code is also part of this project.








_t9PF3orMPd.png?auto=compress%2Cformat&w=40&h=40&fit=fillmax&bg=fff&dpr=2)
Comments