2.2. Test Stand
The first main component of the test stand hardware is the motor (A in
Figure 1). A high-torque-capacity closed loop stepper motor (34HS46-6004D-E100, OMC Co., Ltd., Jiangning Nanjing City, China) was used as it was affordable and versatile, and able to provide at least 5 Nm with the 48 V power supply used. The motor driver (CL86T, OMC Co., Ltd.) uses feedback from a 1000 PPR quadrature encoder (4000 CPR) built into the motor to intelligently control the stepper motor, providing only the required current to maintain positional accuracy, reducing heat and noise compared to a traditional open loop stepper driver, and compensating for any potential missed steps. This closed-loop stepper motor provides similar functionality to an industrial servo motor, but at a lower price, with the disadvantage of lower efficiency and performance; however, these disadvantages are non-critical for this application.
The second main component is the torque sensor (B in
Figure 1). The final version of the test rig utilised a 5 Nm range rotational torque sensor (NCTE-2300-5-1-AU-0-0, NCTE AG, Oberhaching, Germany), although testing was also conducted using a 20 Nm sensor (NCTE-2300-20-1-AU-0-0, NCTE AG) before finding that the 5 Nm range was sufficient, and changing to the lower range sensor to improve accuracy. A higher capacity like 10 or 20 Nm could still be used if durability is a concern or if stronger materials requiring higher torque were to be tested; however, the motor would also need to be upgraded in the latter case to provide the increased torque. This torque sensor also contained a 360 PPR (1440 CPR) quadrature encoder which was used in the data collection, providing 0.25 deg resolution. The torque sensor provides analogue 1–9 V output (linearly mapped to −5 to +5 Nm), and a digital USB interface for torque readings; for ease of use, the analogue output was used to route the signal through the microcontroller and provide an integrated control and data-acquisition experience, and also allow additional data correction (specifically, calibrating rotational variation out of the torque signal).
To allow easy interchanging of different screws, a magnetic 1/4″ hexagonal drive socket was attached after the torque sensor to allow interchangeable screwdriver tips to be used (C in
Figure 1).
To connect the rotational components, off-the-shelf couplers were used (D in
Figure 1). They are listed in the BOM (Bill of Materials) below. The type of coupler was not strongly considered, as the availability of couplers with the correct sizes was too limited. The stepper motor to torque sensor connection had an adaptor from the 14 mm shaft with a key to a 9 mm shaft with a key (only set screws to prevent axial slippage), and between the sensor and screw bit holder was a coupler with a 9 mm keyed connection to a 10 mm clamp/friction connection. If any substitutions are to be made with these parts, the coupler selections should be re-evaluated. The couplers allow a small degree of misalignment; however, they were not specifically selected to allow large misalignments, so care should be taken in assembly. As the motor shaft height with respect to the mounting surface was greater than that for the torque sensor, a 3D-printed stand-off/mount was made for the torque sensor.
To measure the linear motion of the screw, a draw-wire encoder was used (E in
Figure 1). This contains a spring-loaded 100 mm circumference drum with 2000 mm of wire wound around it attached to a 1000 PPR (4000 CPR) quadrature encoder. The wire is pulled through a grommet in the side of the sensor, rotating the drum proportional to the wire extension, with a linear resolution of 0.025 mm.
The motor, torque sensor, and draw-wire encoder were all mounted on an aluminium plate (F in
Figure 1). The torque sensor (on the stand-off) and the motor were mounted with screws through slots to allow the exact spacing to be adjusted and allow a little alignment. This plate was then mounted with linear bearing blocks on 16 mm cylindrical linear rails (G in
Figure 1). A metal wire was attached through a hole in a small block screwed underneath the platform; this was then run over a pulley and connected to a counterweight (H in
Figure 1) to pull the sliding platform into the test samples and provide axial force. The counterweight was a simple steel cylinder machined to be approximately 2.0 kg, a threaded hole was made in the top of the counterweight, and the wire was threaded through and bent around a hollow screw, which was then screwed into the counterweight to secure the wire.
The base of the test rig (I in
Figure 1) was made out of aluminium extrusion sections. These were secured together with screws and T-nuts to form the frame and base of the sample clamp as shown in the build instructions below. The counterweight pulley was attached on the opposite side of the clamp with a hole through the clamp base to pass the wire through.
The clamp (J in
Figure 1) was formed with two milled aluminium plates. Technical drawings are supplied in the
Supplementary Information. The lower plate had a step milled in it to align test samples against. The upper plate also had the back-end milled, to focus the clamping forces on the test sample. Threaded rods were used to make the clamping mechanism with a metal block at the back to keep the plate flat. The lower clamping plate was screwed onto the aluminium extrusion with spacers so that the holes in the test samples were aligned with the screwdriver shaft on the sliding platform.
The overall dimensions of the text stand including clearance for cabling and excluding the counterweight are 700 mm × 300 mm × 300 mm. The device weighs approx 30 kg. This is in comparison to the previously-mentioned Instron E3000 Universal testing machine which is approximately 700 mm × 450 mm × 1800 mm including clearances, and weighs 250 kg; also with a substantially larger and heavier controller. This cannot be compared quantitatively with the designs from Betts et al. [
18] and Thomas et al. [
11] as they do not list dimensions or weight, however, qualitatively Betts et al. [
18] appears lighter and more compact, and Thomas et al. [
11] appears heavier and a little larger. In all cases, the size/weight differences are in combination with different functionalities and load capacities, so care should be taken to interpret the comparison.
2.3. Controller Hardware
The test rig controller, shown in
Figure 2, safely packages the power supply and motor driver, and provides an interface between these, the PC, and the sensors on the test stand.
Note, that this design contains an AC-DC power supply with 220 V/110 V input. These electrical voltages are hazardous and usage may be subject to safety regulations. The controller box should always be unplugged before the case is opened.
Based on the stepper motor/driver requirements (48 VDC, 6 A), a 300 W, 48 VDC PSU was selected (A in
Figure 2). A switched and fused electrical socket (B in
Figure 2) was installed in the back of the case, and wires with appropriately crimped connectors were used between the connector and the PSU, with insulated spade connectors to the power socket connector, and ferrule crimps to the power-supply clamp terminals.
The stepper motor driver (C in
Figure 2) was purchased as part of a kit with the stepper motor (CL86T, OMC Co., Ltd.). This was wired to the 48V DC output of the PSU with appropriately sized wires. The motor power and feedback encoder cables were passed through individual grommets (D in
Figure 2) in the front of the case and then directly wired to the driver. The grommets provided a degree of strain relief for the cables, however, excessive force must still be avoided.
The “Tiva” Launchpad/EK-TM4C123GXL development board (E in
Figure 2) was used as the micro-controller for the test rig. This contained some notable features, such as hardware quadrature encoder peripherals, and a built-in ADC peripheral. The 80 MHz Arm Cortex-M4F core and DMA functionality also provides the required performance for real-time signal processing, and the 32 KB RAM was also sufficient for the application.
Connectors were mounted on the front of the control box as much as possible to allow easy attachment/detachment of the test stand for storage or transport. The motor cables were the exception to this; however, they still had inline connectors to detach the motor. A female case mount plug for the torque sensor used a circular connector (F in
Figure 2) to match the torque sensor. The torque sensor came with a single-end female terminated cable, and the other end was terminated with a matching male connector. Although any (ideally shielded) 8-pin connector could be used, as long as the cable termination and case connector mate. An additional male was intended for USB pass-through; however, this remains unimplemented, so it is visible in the pictures but will not be discussed further. The draw-wire encoder cable came unterminated, so an 8-pin standard DIN connector (G in
Figure 2) was used with a case-mount connector on the control box and a cable connector to terminate the cable; however, any appropriate connector could be used. See BOM for the exact part numbers used.
The signal from the torque sensor had a range of 0–10 V (1–9 V outside of the error state). A voltage divider was used to reduce this within the range of the Tiva ADC. 56 k and 27 k resistors were used to convert 0–10 V to approximately 0–3.25 V. Inexpensive 5% resistors were used and then the ADC values were calibrated with a linear correction using a precision voltage source. The input resistance exceeding 56 k and the output resistance of the torque sensor being specified less than 1 resulted in little loading on the signal, and the resistance was still small enough for the thermal (Johnson) noise from the resistors to be on the order of 1 µV.
Everything was mounted in an RS PRO Instrument Case, mainly using 3D printed parts (H in
Figure 2), or screws as appropriate for case connectors. This case was chosen as it fit all of the components with little room for connection. Any similar (or dissimilar) case could have been used instead, however, all provided 3D print files for mounting components were based on this model. The components were mounted to the sides of the case, allowing the top or bottom to be removed (not both at once for stability reasons) to allow easy access to internal hardware if needed. All connections were made through the front/back plates. The vent grates were positioned over the power supply fan to promote airflow there. The details are in the assembly section.
2.4. Controller Firmware
The firmware was developed in Code Composer Studio (CCS) which is an eclipse-based IDE by Texas Instruments (TI) for C/C++. CCS supports GCC or the proprietary TI compiler; in this case, the TI compiler was used. The code is provided in a Zip archive below.
FreeRTOS was used to make the firmware. This is a preemptive multitasking real-time operating system. This allows a number of functions of the firmware to be separated into independent “Tasks” for simpler design and modularity. Simple C++ object-oriented programming principles were used to simplify/modularise the design as appropriate. The tasks and main interfaces are summarised in
Figure 3.
The “void main()” entry function serves to create all “globally” shared resources, and tasks with appropriate pointers to the shared resources and peripherals (defined in “PeriphDefines(.cpp/.hpp)”), then it passes control to the task scheduler. Where practical, peripherals and RTOS functions were wrapped in helper classes to abstract/simplify access and allow easier porting. The tasks were all instantiated from classes derived from “BaseTask”, which handles setting task priority, memory, etc. All tasks had a “Setup()” method to run startup code before the scheduler began, and a “TaskMethod()” method to be run from the scheduler. As is common with FreeRTOS, generally the tasks ran infinite loops with a FreeRTOS delay function at the end of the loop block to control their repetition frequency and prevent them from using all the CPU time. If a slow task takes a long time and other higher-priority tasks are due, the FreeRTOS scheduler could pause the slower/low-priority task to quickly run the faster/high-priority task before returning to the lower-priority one. FreeRTOS did this by periodically interrupting the running task and checking if other tasks were due to run; by default, this was conducted 1000 times per second to reduce the overhead from the interrupts/context-switching, but here the frequency was increased to 10,000 as some tasks in this firmware were designed to run with sub-millisecond timing.
A “Settings” and “Status” class was made to provide access to common global variables, with one instance of each being made in “main()” and pointers being passed to all tasks that needed them. The Settings class was intended for more persistent variables/preferences that could be saved and loaded from EEPROM. The settings were generally all directly externally configurable with the exception of the “Raw Mode” settings (described below) which had some additional logic. The Status object was for more transient/rapidly changing variables like sensor readings or status text; these were generally only changed by the firmware and could not be externally controlled.
One of the lower-level tasks was “MotorTask” which directly controlled the GPIO pins to the stepper driver. Notably, this task had two modes selected by a pre-processor flag “PWM_BASED”. If PWM_BASED is not defined, the function will simply send the appropriate number of pulses to the stepper motor as fast as possible (allowed by the driver data sheet) at the beginning of every 1 ms using the standard GPIO pin-set functions. This worked well but led to slightly less smooth/louder motor control, and resulted in the task using more CPU time as the motor needed to spin faster and obtain more pulses per millisecond, possibly preventing other tasks running as needed. If PWM_BASED was defined, it instead used the device-specific PWM peripheral functions to generate a pulse train at the appropriate frequency to spread the same number of pulses out over the whole 1 ms period; setting this frequency only required a few small calculations/instructions/register writes within a few microseconds, then the task suspended for the rest of the 1 ms period, leading to a constant CPU load regardless of motor speed. This potentially allowed a few motor steps to be missed due to quantisation in the pulse frequency calculations/settings; however, this amounts to only a degree or so over a full insertion and the advantages are considered worthwhile. In the event that perfect positional accuracy is more pertinent, or when porting to a device without appropriate PWM (or for easier porting without needing the device-specific PWM API calls), the PWM_BASED mode can be simply disabled at the top of MotorTask.hpp.
A higher-level task is “MotionTask”, which was closely related to motor control. This used FreeRTOS queues and read high-level commands (Start, Stop, Reset, etc.) from an event queue, and based on the current Settings it generated motion profiles as ramping velocity segments to pass to the MotorTask using another queue. The current code was designed for constant velocity or repeating trapezoidal velocity profiles; however, further types could be implemented in this task as needed (e.g., using an arbitrary waveform). When idle it checked every 10 ms for commands, and when running it continually enqueued motion segments, blocking the task and consuming no CPU time whenever the queue is filled. Only a shallow queue of 2 was used for motion segments; however, the control queue length is 16 to ensure events are not dropped (there should not be large numbers of commands in quick succession, normally at most 2 or 3 if stop and reset commands are enqueued simultaneously, for example).
The “DatalogTask” was another low-level task designed to read sensor inputs. It interfaced with the quadrature encoder interfaces (QEIs) of the microcontroller to read the linear and angular displacements from the test rig. It also utilised direct memory access (DMA) and associated interrupts to allow high-rate sampling of the analogue torque input at 1 MSa (Mega-sample/s) and applied an FIR filter to the data to anti-alias the data before down-sampling to 1000 Hz via decimation. The sensor values were always written to the Status object. Depending on the mode of operation, this task also outputs values over the serial connection. If “Raw Mode” was enabled, then for every cycle (1 ms) the task would output a compact form of the current sensor values. Raw mode was primarily used to stream data to the desktop application during experiments, allowing high-rate measurements with minimal text processing overhead; in this mode, the DatalogTask also took over reporting status text to avoid conflicts with using the serial port. Outside of Raw Mode, the data and status text were reported by “UartStatusTask” in a more human-readable manner. This task did not use the FreeRTOS timing functions and instead ran based on the timing of DMA operations (which trigger an interrupt every 250 data points) which were, in turn, regulated by the speed of ADC readings which ran at the maximum rate of 1 MSa, with four-sample hardware averaging to 250 kSa. Fixed-point operations were used to apply the filter to torque data in an efficient way, and the hard-coded voltage calibration was applied to the resulting fixed-point value before it was converted into a decimal value for serial output. The coefficients of the FIR filter were stored in the FirCoeffs.cpp file, and only half of the coefficients are stored as the filter is symmetric. These coefficients were designed using the MATLAB R2020a fixed-point toolbox (script linked in
Supplementary Files). The 4-point hardware average was utilised to reduce the computation requirements/filter length when down-sampling. The simulated filter including hardware down-sampling exceeded 60 dB stop-band attenuation.
Some additional code in the DatalogTask was used to self-calibrate/zero the torque sensor. It was noted that there was some rotation-dependent offset on the torque sensor, so this code worked with the UartControlTask and MotionTask to record unloaded torque samples while rotating the sensor. The averages were saved in a lookup array and were applied as corrections to the readings during data recording.
The “UartStatusTask” operated when Raw Mode was disabled to provide a user interface over a serial console. This was used in earlier stages of development but is mostly unused due to the current GUI. It printed the current sensor readings and status text to a serial console every 100 ms. Special terminal control sequences were used to move the cursor around and overwrite the previous readings to make the display smoother when using a compatible console.
The “UartControlTask” was the interface to send commands to the test rig. Most of the commands were reflected as buttons in the GUI described below. The “set” and “get” commands were used to directly change the settings in the Settings object based on three-character variable names, e.g., “set ind 4000” set the insertion distance to 4000 motor pulses (1 revolution). “start”, “stop”, “pause”, and “jog” sent commands to the MotionTask and set the status as appropriate to enable or disable the motor. “selfcal” initiated the previously mentioned self-calibration/zeroing. “zero” performed a quick re-zero of an encoder axis. “sysrst” rebooted the micro-controller. “rawmode [0/1]” controlled the previously mentioned Raw Mode. “save_eeprom”, “load_eeprom” and “reset_eeprom” saved or loaded the persistent settings in the micro-controller that were configured on boot, or reset the hard-coded defaults.
2.5. Software
The main functions of the interface software were to coordinate data between a variety of input devices (e.g., this test rig) and outputs (usually files), and to handle any synchronisation/triggering of dissimilar devices. Some additional ad-hoc functions have been added to aid other research tasks.
Devices could either be set as polling or interrupting. Polling devices send a data point whenever they receive a trigger signal while interrupting devices send them periodically (or aperiodically) regardless of triggers. The software was set up so that any interrupting device can be used to trigger other devices when a data-point is sent. The software offered some options to save data in one file together, or multiple files with one per device; additionally the data could be synchronised at the file saving stage (useful in the case of multiple interrupting devices), in which one devices data point would save a row with the last data point received from all other devices. The software timestamps all data-points saved using the time from the beginning of the recording and the filenames with the current date/time to simplify data labelling. If only polling devices were used, the software provided an “internal trigger” function to regularly trigger the sampling.
All of the devices were programmed as implementations of the “IDevice” interface (which includes the “ITriggerSource” interface). As serial devices were expected to be common, and were already implemented twice in the software, lower level/common serial functionality was incorporated in the “BaseSerialDevice” class to avoid repeating it. The devices had a number of events that were used for the devices to update a common user interface (e.g., buttons enabled/disabled) based on the status of the device in an abstracted/encapsulated manner (e.g., a “Connected” serial device will not allow the port to be changed). The devices also had a number of functions that must be implemented to allow the user interface to control the devices. To differentiate base classes like “BaseSerialDevice” from real devices like “BoneScrewTestRigDevice”, an attribute was applied to the classes that should be listed in the device types list, also allowing a user-friendly name string to be set.
The main important device for the test rig was the “BoneScrewTestRigDevice”. This was based on the “BaseSerialDevice” class. Notably, when connecting to the test rig, it set it into “Raw Mode” described above. Then, all serial lines from the test rig were processed either as status messages (lines beginning with “!”), or sensor readings (always in the form “T:[Torque];R:[Rotation];D[Linear position];\r\n”). The test rig also used the software functionality to support a custom dialogue to configure it and control it in a simple manner. The custom dialog (Demonstrated in
Section 4) was relatively simple, displaying all values from the test rig and allowing most commands to be accessed with individual buttons. Some functions were intended to enhance ease of use, for example, the settings were referred to using human-readable names instead of their three-letter acronyms, and a slider allowed the UI to be locked to prevent accidental changes during long (e.g., low-speed) tests.
As discussed in
Section 5.1, the torque sensor in the test rig was compared to an off-the-shelf stationary torque sensor. This was also implemented in this software as another derived class from “BaseSerialDevice” as “SauterTorqueSensor”. As most of the serial complexities were already contained in the “BaseSerialDevice” class, the interface required less than 50 lines of code (including generous white space) to simply configure options, parse readings, and send basic commands.
A prototype smart screwdriver was also interfaced with. This used a notably different interface using HTTP server-sent events. As previous devices all had a form of port number, and this used an IP address, an empty “IIPDevice” interface was added to indicate a device used IP instead of Serial. This indicated that the UI should semantically interpret the “Port” of the device as an IP address, and the display was changed slightly. In this case, HTTP was used but this could feasibly be used for any IP/URL address input. The implementation of this device opened an HTTP stream to receive data points from the specified IP. As the points were received, they were parsed and sent to the rest of the software.
Some project-specific functionality was added to the software to demonstrate insertion torque optimisation models at a trade show. These are shown in
Figure A4 and
Figure A5 in
Appendix B.
Some example Test/Simulated devices were also added to verify the functioning of the software when a test rig was unavailable or inconvenient. The test device was simply a minimal implementation of a serial device and can be used as a template for adding other serial devices. The simulated device emulated a test rig with basic torque-rotation data to test that the torque-optimisation demos are working, e.g., for presentations, videos, or similar, where an experimental demonstration may be impractical.