Posted 17 November 2018
In a previous post on this subject, I described a digital tension scale arrangement using a load cell incorporated into a 2-hook tension measurement setup, interfaced to a common HX711 ADC board, and a Sparkfun Pro Micro ATMega32U4 microcontroller.
After (finally) getting the setup to work and getting some initial real-time tension measurements on our rowing machine, I decided to see if I could improve the usability of the overall system, with the goal of constructing a fully automatic battery powered tension scale, with the ability to communicate wirelessly to my PC for data acquisition and programming.
Desired Features:
- Easy calibration: The system should offer a calibration option when connected to a PC/Smartphone, but otherwise should use the last calibration data (stored in EEPROM) for measurements.
- Battery operated: This implies a low-power mode to extend battery life if using primary batteries, and/or a charging arrangement if using secondary (rechargeable) cells.
- Local display: A low power display (LCD?) for local tension measurement display
- Wireless capability: A wireless connection to a PC or smartphone for real-time data acquisition.
- Small size: I would like to mount the entire system on the 2-hook tension measurement assembly itself.
Easy Calibration:
The calibration procedure associated with my previous post was a PITA, to say the least, so I decided to attack this problem first. I modified the software to allow the user to skip calibration entirely or to calibrate the tensionometer automatically using any known weight. The new software is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
/* Name: DigitalScaleAutoCal.ino Created: 11/15/2018 4:49:12 PM Author: FRANKWIN10\Frank This program is a modification of the November 2016 circuits4you.com program for interfacing with the HX711 Module coupled with a typical load cell. It's purpose is to automatically calibrate the combination to accurately display weight in Kg. The process is as follows: Step1: User opts to calibrate by entering 'Y' at the prompt. Entering any other key (or just letting the question time out) will skip the calibration process Step2: User establishes 'tare' weight physical configuration with any non-measured weight attached (in my case, the bucket used to hold measured amounts of water used for weight calibration), and then presses 'T' to zero out the 'tare weight' Step3: The user establishes a calibration weight condition (in my case, 1.8 liters of water) and enters the weight value (in Kg). The program then calculates the scale factor needed to calibrate the scale for the entered weight and then automatically drops into measurement mode Step5: The user enters 'q' to quit measurements. */ /* * circuits4you.com * 2016 November 25 * Load Cell HX711 Module Interface with Arduino to measure weight in Kgs Arduino pin 2 -> HX711 CLK 3 -> DOUT 5V -> VCC GND -> GND Most any pin on the Arduino Uno will be compatible with DOUT/CLK. The HX711 board can be powered from 2.7V to 5V so the Arduino 5V power should be fine. */ #include "HX711.h" //You must have this library in your arduino library folder #include <EEPROM.h> #include<EEPROMAnything.h> //so I can store the last calibration value in EEPROM #include <PrintEx.h> //allows printf-style printout syntax #include <elapsedMillis.h> StreamEx mySerial = Serial; //added 03/18/18 for printf-style printing elapsedMillis sinceLastWaitMsg; const int WAITINGMSGINTERVALMSEC = 1000; //1-sec intervals const int MAX_WAITING_INTERVALS = 10; //how long to wait before starting measurement int numWaitIntervals = 0; char inbuf[20]; //buffer for user input #define DOUT A1 #define CLK A0 HX711 scale(DOUT, CLK); //Change this calibration factor as per your load cell once it is found. You many need to vary it in thousands long calibration_factor = -96650; //-106600 worked for my 40Kg max scale setup long tare_offset = 0; bool bDoneMeasuring = false; const int LED_PIN = 13; const char* waitstr = "Waiting...Measurements will start in "; const int CAL_FACTOR_EEPROM_ADDR = 0; const int TARE_OFFSET_EEPROM_ADDR = CAL_FACTOR_EEPROM_ADDR + sizeof(calibration_factor); void setup() { delay(2000); //required to wait for Pro Micro to switch from programming to output serial port assignments Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); memset(inbuf, 0, sizeof(inbuf)); mySerial.printf("Digital Scale Program\n"); mySerial.printf("Program to automatically calibrate HX711/Load Cell to measure weight in Kg\n"); EEPROM_readAnything(CAL_FACTOR_EEPROM_ADDR, calibration_factor); //read current cal scale from EEPROM mySerial.printf("The current stored scale calibration factor is %li\n", calibration_factor); if (isnan(calibration_factor)) { mySerial.printf("invalid cal factor: Setting to -96650\n"); calibration_factor = -96650; } scale.set_scale(calibration_factor); EEPROM_readAnything(TARE_OFFSET_EEPROM_ADDR, tare_offset); //read current tare offset from EEPROM mySerial.printf("The current stored tare offset factor is %li\n", tare_offset); if (isnan(tare_offset)) { mySerial.printf("invalid cal factor: Setting to zero\n"); tare_offset = 0; } scale.set_offset(tare_offset); mySerial.printf("Enter 'Y' to start the calibration, any other key to skip\n"); mySerial.printf("Calibrate (Y/N) (Y)?\n"); if (GetUserInputStr(inbuf, 5)) //returns true if strlen(inbuf) > 0 { mySerial.printf("\nReceived "); Serial.print(inbuf); Serial.println(" from user"); if (strncasecmp("Y", inbuf, 1) == 0) //compare only 1st chars, as user input may have CR/LF appended { //calibrate with known weights Serial.println("HX711 Calibration"); Serial.println("Remove all non-fixture weight from scale, and press 't' to zero the scale"); Serial.println("Or any other character to skip this step"); memset(inbuf, 0, sizeof(inbuf)); if (GetUserInputStr(inbuf, 10)) //returns true if strlen(inbuf) > 0 { mySerial.printf("\nReceived "); Serial.print(inbuf); Serial.println(" from user"); if (strncasecmp("T", inbuf, 1) == 0) //compare only 1st chars, as user input may have CR/LF appended { tare_offset = scale.read_average(); //Get a baseline reading mySerial.printf("Applying offset %li as tare adjustment\n", tare_offset); scale.set_offset(tare_offset); //this value will be subtracted from all future measurements mySerial.printf("Writing offset %li to EEPROM\n", tare_offset); EEPROM_writeAnything(TARE_OFFSET_EEPROM_ADDR, tare_offset); } } Serial.println("Apply a known weight (the higher the better) and enter its value in Kg"); Serial.println("Enter 0 to skip this step"); memset(inbuf, 0, sizeof(inbuf)); if (GetUserInputStr(inbuf, 30)) //returns true if strlen(inbuf) > 0 { mySerial.printf("\nReceived "); Serial.print(inbuf); Serial.println(" from user"); //calculate scale factor resulting in user-input wt //wt(Kg) = (read_avg - OFFSET)/Scale //Scale = (read_avg - OFFSET)/wt(Kg) double wtKg = atof(inbuf); mySerial.printf("User weight input was %4.2f\n", wtKg); long read_avg = scale.read_average(); long offset = scale.get_offset(); calibration_factor = (double)(read_avg - offset) / wtKg; scale.set_scale(calibration_factor); //Adjust to this calibration factor mySerial.printf("User weight input was %4.2f, read_avg = %li, offset = %li, cal = ", wtKg, read_avg, offset); Serial.println(calibration_factor); //PrintEx won't print the cal factor value EEPROM_writeAnything(CAL_FACTOR_EEPROM_ADDR, calibration_factor); Serial.print("Wrote cal factor "); Serial.print(calibration_factor); //mySerial.printf doesn't work for cal factor mySerial.printf(" to EEPROM address %d\n",CAL_FACTOR_EEPROM_ADDR); } } } mySerial.printf("\n\nCalibration Complete... Starting Measurements\n\n"); mySerial.printf("\nTo quit measuring, enter 'Q'\n"); mySerial.printf("Time(mSec)\t\Wt(Kg)\n"); bDoneMeasuring = false; while (!bDoneMeasuring) { mySerial.printf("%lu\t%3.2f\n", millis(), scale.get_units(3)); delay(250); if (Serial.available()) { char temp = Serial.read(); mySerial.printf("got %c\n", temp); if (temp == 'q') { bDoneMeasuring = true; //all done } } } mySerial.printf("Measurements Completed - Have a nice day!\n"); } void loop() { } bool GetUserInputStr(char* inbuf, int numWaitIntervals) { //Purpose: Get user input //Inputs: // inbuf = pointer to char[10] array to receive user input //Outputs: // inbuf = String object containing user input. If inbuf.length() == 0, then the routine timed out // returns True if user input was received, False if the routine timed out //Plan: // Wait for user input or timeout whichever occurs first int intervalCount = 0; while (strlen(inbuf) <= 0 && intervalCount < numWaitIntervals) { Serial.readBytesUntil('\n', inbuf, 10); if (sinceLastWaitMsg >= WAITINGMSGINTERVALMSEC) { sinceLastWaitMsg -= WAITINGMSGINTERVALMSEC; intervalCount++; mySerial.printf("%s %d\n", waitstr, numWaitIntervals - intervalCount); } delay(250); } return (strlen(inbuf) > 0); } |
When run on my PC, this produced the following output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
Opening port Port open Digital Scale Program Program to automatically calibrate HX711/Load Cell to measure weight in Kg The current stored scale calibration factor is -2147483648 The current stored tare offset factor is -1119642 Enter 'Y' to start the calibration, any other key to skip Calibrate (Y/N) (Y)? Waiting...Measurements will start in 4 Waiting...Measurements will start in 3 Received y from user HX711 Calibration Remove all non-fixture weight from scale, and press 't' to zero the scale Or any other character to skip this step Waiting...Measurements will start in 9 Waiting...Measurements will start in 8 Waiting...Measurements will start in 7 Waiting...Measurements will start in 6 Waiting...Measurements will start in 5 Received t from user Applying offset -1148885 as tare adjustment Writing offset -1148885 to EEPROM Apply a known weight (the higher the better) and enter its value in Kg Enter 0 to skip this step Waiting...Measurements will start in 29 Waiting...Measurements will start in 28 Waiting...Measurements will start in 27 Waiting...Measurements will start in 26 Waiting...Measurements will start in 25 Waiting...Measurements will start in 24 Waiting...Measurements will start in 23 Waiting...Measurements will start in 22 Waiting...Measurements will start in 21 Waiting...Measurements will start in 20 Waiting...Measurements will start in 19 Received 1.8 from user User weight input was 1.80 User weight input was 1.80, read_avg = -986977, offset = -1148885, cal = 89948 Wrote cal factor 89948 to EEPROM address 0 Calibration Complete... Starting Measurements To quit measuring, enter 'Q' Time(mSec) Wt(Kg) 28329 1.80 28702 1.80 29075 1.80 29449 1.80 29821 1.80 30194 1.80 30567 1.80 30940 1.80 31312 1.80 31685 1.80 32058 1.80 32431 1.80 32803 1.80 33176 1.80 33549 1.80 33922 1.80 34295 1.82 34668 1.38 35041 0.26 35414 0.06 35786 0.15 36159 0.26 36532 0.44 36904 1.47 37277 1.81 37650 1.82 38024 1.61 38396 0.48 38769 0.21 39142 0.24 39515 0.28 39887 0.33 40260 0.99 40633 1.80 41006 1.80 41378 1.83 41751 0.96 42124 0.32 42498 0.30 42870 0.29 43243 0.29 43616 0.33 43988 0.56 44361 1.72 44734 1.80 45107 1.80 45479 1.80 45852 1.80 46225 1.80 46599 1.80 got q Measurements Completed - Have a nice day! |
Here are a couple of photos showing the calibration process with my current setup
After calibrating, I tested the the system by measuring tension vs time with an elastic strap I am using as part of my rotator cuff surgery rehab, as shown in the following short video clip
The data from this experiment was captured on my PC and plotted in Excel, as shown below
Then, at the request of my physical therapist, I measured the real-time tension for single & double orange straps, and single/double green straps, as shown below
Stay tuned!
Frank