Load Cycle Test Jig
===================
![Test jig overview](img/test-jig-front.jpg)

This is the control software for a machine that runs repeated rotational load
cycles on (3d-printed) mechanical parts, until they fail. I was originally
motivated to build this to test ISO 5211 valve adapters (related to [ISO 5211
Adapter for PVC Ball Valve][iso-5211-pvc]). But it can be used to test any
mechanical joint.

You can use this codebase as an example for the
[`profirust`](https://github.com/rahix/profirust) PROFIBUS DP master stack
running on a Raspberry Pi.

[iso-5211-pvc]: https://www.printables.com/model/605515-iso-5211-adapter-for-pvc-ball-valve

## How it works
A pneumatic vane cylinder twists the part back and forth at a torque set by
the pressure regulator. The software counts each reversal and stops the run
as soon as one of the two end-stop sensors trips, which means the part has
given way and the cylinder is free to rotate past its normal travel.

#### Main hardware components
- pneumatic vane cylinder (FESTO [DSRL-40-180P-FW][festo-30658])
  * 5/3-way solenoid valve to control the cylinder
  * two inductive end-stop sensors on the cylinder (detect part failure)
- filter-regulator maintenance unit (regulator controls the maximum torque)
  * pressure switch (detect low air pressure)
- stack light (white, blue, orange) - see [Stack Lights][stack-light-post] on my blog
- HD44780 LCD (20×4 characters) as the operator display
- WAGO [750-303][wago-750-303] PROFIBUS fieldbus coupler for I/O signals
- Raspberry Pi as the PROFIBUS master, with a MAX485 transceiver (See
  [Running RS-485 with a MAX485 on a Raspberry Pi](#running-rs-485-with-a-max485-on-a-raspberry-pi)
  below)

[festo-30658]: https://www.festo.com/de/en/a/30658/
[stack-light-post]: https://blog.rahix.de/stack-lights/
[wago-750-303]: https://www.wago.com/de-en/io-systems/fieldbus-coupler-profibus-dp/p/750-303

![Control side with WAGO stack, stack light, FRL and solenoid valve](img/test-jig-controls.jpg)

## Operating modes
There are two switches which control the machine:
- Mode selector (continuous or single)
- Start/stop button

The modes do the following:

- **Continuous**: The cylinder alternates left and right every 5 seconds, the
  counter increments on each reversal, and the run ends when a limit switch
  trips. This is the standard "how many cycles until it breaks" test.
- **Single**: One short preparation move, then a timed swing toward the opposite
  end stop. This is used to measure time to failure for a single event.

Once failure occurs, the buzzer on the stack light pulses to let you know it's done.

The LCD shows the current state together with either the cycle counter
(continuous mode) or the elapsed cycle time (single mode).

![LCD readout during a run](img/test-jig-display.jpg)

(LCD case is [20x4 LCD Enclosure](https://www.printables.com/model/957374-20x4-lcd-enclosure))

## Software architecture
This is a pattern that I find to work well for control system software on
hosted systems (the Linux on the Raspberry Pi in this case).  It is not
particularly efficient, but very simple and robust.

There are three threads:

- The main thread runs the 20 ms control loop that computes the control system
  logic (`Logic::run()`). Before and after, it copies I/O data to and from the
  fieldbus process image and display.

- A background thread runs the PROFIBUS DP master and the cyclic data exchange
  with the WAGO coupler ([`fieldbus.rs`]). The cycle time is adjusted
  to be optimal for the selected bus speed.

- A third thread drives the HD44780 LCD over I2C ([`display.rs`]). It is woken
  from a condvar whenever a line changes.

For any further I/O channels, you'd add more threads. Data is shared through
mutexes which are only locked briefly to read or update data. This is very
important to ensure no thread is ever blocked for long periods of time.

You can find another example of this pattern in the
[crab-control-center](https://github.com/muccc-rs/crab-control-center) code.

#### Source layout
- [`main.rs`] runs the control loop and does the data plumbing.
- [`logic.rs`] contains the actual control logic: machine mode state machine,
  cylinder sequencing, status reporting.
- [`fieldbus.rs`] contains the PROFIBUS communication, and peripheral configuration.
- [`display.rs`] is the HD44780 over I2C driver thread.
- [`util.rs`] contains small helpers that mirror the ones found in industrial PLC
  programming (e.g. `TimerOn`, `TimerOff`, `PulseTimer`).

## PROFIBUS
The fieldbus module ([`fieldbus.rs`]) contains everything related to PROFIBUS
communication:
- Setup a DP peripheral with `profirust` using the Linux RS-485 PHY implementation
- Peripheral parameters from a vendor GSD file (this was autogenerated with profirust's [`gsdtool`](https://github.com/rahix/profirust/tree/main/gsdtool))
- Exposing the process image to the rest of the application via Mutex

#### Running RS-485 with a MAX485 on a Raspberry Pi
PROFIBUS uses an 11-bit character frame with even parity. The Pi's default
"mini UART" (`/dev/ttyS0`) does not accept `PARENB` (=even parity bit) and is
therefore not usable. The proper PL011 UART has to be enabled instead, and the
MAX485's DE/RE driver-enable line has to be tied to the UART's RTS pin so the
kernel can switch the transceiver in step with each frame.

Switch the primary UART to the PL011 by adding the following to
`/boot/firmware/config.txt` and disabling the Bluetooth HCI service:

```
dtoverlay=disable-bt
```

```bash
sudo systemctl disable --now hciuart
```

After a reboot the PL011 is available as `/dev/ttyAMA0`, which is the device
the code opens in [`fieldbus.rs`].

Hardware flow control on the primary UART is not exposed by the stock
overlays, so a small device tree overlay is needed that routes RTS (and
optionally CTS) to a GPIO.

This overlay source can be used as is: <https://github.com/HiassofT/AtariSIO/blob/master/contrib/rpi/uart-ctsrts.dts>

Compile and install the overlay with:

```bash
dtc -@ -Hepapr -I dts -O dtb -o uart0-ctsrts.dtbo uart-ctsrts.dts
sudo cp uart0-ctsrts.dtbo /boot/firmware/overlays/
```

Then append the following line to `/boot/firmware/config.txt` and reboot:

```
dtoverlay=uart0-ctsrts
```

Wire the MAX485 so that
- DI to Pi's TXD (GPIO 14)
- RO to Pi's RXD (GPIO 15)
- combined DE and RE pins to the RTS GPIO chosen in the overlay (GPIO 17)

Add the usual 120 ohm termination at both physical ends of the bus.

## References
- [`profirust`](https://github.com/rahix/profirust)
  * [Blog post about `profirust`](https://blog.rahix.de/profirust/)
  * [Blog post generally explaining PROFIBUS](https://blog.rahix.de/profibus-primer/)
- [`hd44780-driver`](https://github.com/JohnDoneth/hd44780-driver) LCD display driver

[`main.rs`]: ./src/main.rs
[`logic.rs`]: ./src/logic.rs
[`fieldbus.rs`]: ./src/fieldbus.rs
[`display.rs`]: ./src/display.rs
[`util.rs`]: ./src/util.rs

## License
Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
  <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
  <http://opensource.org/licenses/MIT>)

at your option.
