Author: Luis Samaniego
FPP-3 is an Arduino UNO based controller for a home-built rotary film/paper processor (JOBO tanks). It drives a DC motor through an H-bridge, shows DEV/TEMP information on a 20x4 I2C LCD, and is operated via a 4x4 keypad.
This repository contains:
rotary_processor.ino: current firmware (v5.1.2 tag)src_old/: archived older firmware versions (v3, v4, etc.)
- Two user pages:
- DEV page: timing + motor run/stop + step recall/store
- TEMP page: temperature monitoring + profile selection + PREHEAT start/stop
- Rotary motor control:
- Smooth acceleration/braking
- Periodic direction reversal
- EEPROM persistence:
- Last used timing (min/sec)
- Last selected profile
- Calibration offsets (0.1 C) for Bath/Tank/Bottle and Heater (HTR)
- 9 development step presets (1..9)
- DS18B20 temperature sensors (1-wire) via OneWire + DallasTemperature
- ROM-based assignment (addresses stored in code)
- Kodak thermometer reference offsets stored in EEPROM (0.1 C)
- Darkroom mode:
- LCD backlight toggle via keypad combo
Typical build:
- Arduino UNO (or compatible)
- IBT-2 / BTS7960 H-bridge motor driver
- DC motor driving JOBO roller base
- 20x4 I2C LCD (LiquidCrystal_I2C)
- 4x4 matrix keypad
- Buzzer
- DS18B20 sensors (Bath, Tank, Bottle) on one 1-wire bus
Power:
- 12 V for motor (via external supply)
- 5 V for UNO + sensors (via DC-DC converter)
IMPORTANT:
- Keep motor power and logic power grounds common (single reference).
- Keep sensor wiring neat; use proper pull-up on the 1-wire data line.
Required includes (as used in v5.1.2):
- <Wire.h>
- <LiquidCrystal_I2C.h>
- <Keypad.h>
- <EEPROM.h>
- <OneWire.h>
- <DallasTemperature.h>
Install missing libs in Arduino IDE: Tools -> Manage Libraries... Search and install:
- "OneWire" (Paul Stoffregen)
- "DallasTemperature" (Miles Burton)
- DS18B20 VDD -> +5V
- DS18B20 GND -> GND
- DS18B20 DQ -> chosen Arduino pin (your "DG" / data pin)
- Pull-up resistor between DQ and +5V:
- Typical: 4.7 kOhm (5% OK)
- 100 nF ceramic capacitor between +5V and GND near the sensor side.
- Pages:
- PAGE_DEV : development timing + motor control
- PAGE_TEMP : temperature monitoring + PREHEAT + profile selection
- Run modes:
- MODE_IDLE : not running
- MODE_DEV : motor running with countdown
- MODE_PREHEAT : temperature preheat control active
- Calibration:
- CAL mode entered from TEMP page when IDLE (see below)
LCD menu row (20 chars) follows the v5.1 patched UX:
- DEV page: shows A/B timing, C Go/Stop, D Pg
- TEMP page: shows A/B profile, C Go/Stop (PREHEAT), D Pg
Keypad has: digits 0..9, A/B/C/D, * and #.
| Keys | Action |
|---|---|
* |
If backlight OFF: turn ON. If ON: arm "OFF combo". |
* then # |
Turn LCD backlight OFF (darkroom). Must be within ~1 s. |
| Keys | Action |
|---|---|
A |
Edit minutes (enter minute edit state) |
B |
Edit seconds (enter second edit state) |
C |
Go/Stop DEV run (toggle) |
D |
Switch page DEV <-> TEMP |
1..9 |
Recall stored step (if defined) |
# then 1..9 |
Store current time into step slot 1..9 |
Notes:
- "Store step" is a short sub-mode: press
#, then one digit 1..9.
| Keys | Action |
|---|---|
A |
Profile forward (cycle) |
B |
Profile backward (cycle) |
C |
Start/Stop PREHEAT (toggle) |
D |
Switch page TEMP <-> DEV |
0 |
Toggle TEMP diagnostics display (if enabled in firmware) |
# ... # |
Jump to profile by ID (1-2 digits) e.g. #15# |
Profile jump notes:
- Start: press
# - Enter 1 or 2 digits (profile ID)
- Confirm: press
#again - Example:
#1#,#9#,#15#
CAL entry combo (kept as in v5.1 patched UX):
- Press
AthenBwithin ~800 ms -> enter CAL.
| Keys | Action |
|---|---|
A |
+0.1 C |
B |
-0.1 C |
C |
Save (write to EEPROM) |
D |
Exit (discard) |
# |
Next CAL page |
* |
Previous CAL page |
CAL pages include:
- HTR (heater suggested setting)
- Bath/Tank/Bottle offsets (Kodak thermometer reference)
profiles[] defines the working presets (film and paper).
In v5.1.2 you use (example):
- Film:
- BW 25.0 C (typical)
- C41 38.0 C
- E6 38.0 C
- Paper:
- BW 20.0 C
- RA4 35.0 C with small volumes and different mixing dynamics.
Each profile includes:
id(shown/used for jump selection)- chemistry label ("BW ", "C41", "E6 ", "RA4")
- setpoint (0.1 C)
- tank code (e.g., 2521, 2551, 2561, 2821, ...)
- chemistry volume (mL)
dT_pour_10initial pour cooling in 0.1 C (developer enters cooler)- boost time (s) optional pre-boost window
dT_boost_10optional boost magnitude in 0.1 C
Operational meaning:
dT_pour_10is applied once at DEV start as the initial condition for Tdev*.- The lag model (Tdev*) then relaxes toward the chosen driver temperature.
stateDiagram-v2
direction LR
[*] --> ST_DISPLAY_MAINMENU
ST_DISPLAY_MAINMENU --> ST_WAIT: draw current page<br/> (DEV or TEMP)
%% -------------------------
%% DEV PAGE (PAGE_DEV)
%% -------------------------
ST_WAIT --> ST_SETTIMING_M: (DEV) A minutes
ST_WAIT --> ST_SETTIMING_S: (DEV) B seconds
ST_WAIT --> ST_STARTMOTOR: (DEV) C Go/Stop (DEV)
ST_WAIT --> ST_DISPLAY_MAINMENU: (DEV) D page->TEMP
ST_SETTIMING_M --> ST_DISPLAY_MAINMENU: # confirm / * cancel
ST_SETTIMING_S --> ST_DISPLAY_MAINMENU: # confirm / * cancel
ST_STARTMOTOR --> ST_DISPLAY_MAINMENU: runMotor(START) <br/> runMode=DEV
ST_WAIT --> ST_IDLE: (DEV) C (while running) <br/> Stop
ST_IDLE --> ST_DISPLAY_MAINMENU: runMotor(FORCESTOP) <br/> runMode=IDLE
%% -------------------------
%% TEMP PAGE (PAGE_TEMP)
%% -------------------------
ST_WAIT --> ST_PREHEAT: (TEMP) C Go/Stop (PREHEAT)
ST_PREHEAT --> ST_WAIT: runMode=PREHEAT <br/> motor single dir
ST_WAIT --> ST_IDLE: (TEMP) C (while preheating) <br/> Stop
ST_WAIT --> ST_DISPLAY_MAINMENU: (TEMP) D page->DEV
%% -------------------------
%% CALIBRATION (TEMP only)
%% -------------------------
ST_WAIT --> ST_CAL: (TEMP+IDLE) A+B within 800 ms
ST_CAL --> ST_DISPLAY_MAINMENU: C Save
ST_CAL --> ST_DISPLAY_MAINMENU: D Exit (discard)
ST_CAL --> ST_CAL: A +0.1 / B -0.1
ST_CAL --> ST_CAL: # next / * prev
%% keep the loop explicit (Mermaid likes it)
ST_WAIT --> ST_WAIT
note right of ST_WAIT
DEV page:
1..9 recall stored step
# + 1..9 store current time in step
TEMP page:
A next profile (A-alone)
B prev profile
#..# jump to profile id (e.g. #15#)
0 toggle diag
Global:
* backlight ON
* + # backlight OFF
end note
Changing Displays:
flowchart LR
DEV[PAGE_DEV];
TEMP[PAGE_TEMP];
CAL[CAL];
DEVK["A: minutes <br/> B: seconds <br/> C: Go/Stop <br/> 1..9: recall <br/> #: store"];
TEMPK["A: next <br/> B: prev <br/> A+B: CAL (IDLE) <br/> C: preheat <br/> #..#: jump <br/> 0: diag"];
CALK["A: +0.1 <br/> B: -0.1 <br/> C: Save <br/> D: Exit discard <br/> #: next item <br/> *: prev item"];
DEV -->|D| TEMP;
TEMP -->|D| DEV;
DEV --> DEVK;
TEMP --> TEMPK;
TEMP -->|"A+B (IDLE)"| CAL;
CAL --> CALK;
CAL -->|D| TEMP;
- Switch to DEV page (key
Dif needed). - Select a chemistry/tank profile (normally from TEMP page, then return to DEV).
- Set development time:
A-> enter minutes, type digits,#to finish,*to cancelB-> enter seconds, type digits,#to finish,*to cancel
- Start development rotation:
- Press
C(Go). Motor starts, countdown begins.
- Press
- Optional: store this time as a preset step:
- Press
#then1..9
- Press
- End:
- Motor stops automatically at 00:00.
- A short beep occurs ~5 s before end (one-shot).
- Menu returns after a short delay.
- Switch to TEMP page (
Dif needed). - Choose the profile (setpoint) that matches your process.
- Press
Cto start PREHEAT rotation. - Wait until Bath temperature stabilizes in-band (READY latch).
- Stop PREHEAT:
- Press
Cagain.
- Press
- TEMP page must be active and MODE_IDLE (not preheating, not developing).
- Enter CAL:
- Press
AthenBwithin ~800 ms.
- Press
- Navigate CAL pages:
#next,*previous
- Adjust offset:
A+0.1 C,B-0.1 C
- Save:
Cwrites offsets to EEPROM
- Exit without saving:
Ddiscards and returns
Recommended practice:
- Calibrate Bath, Tank, Bottle at the temperatures you actually use (e.g. 20C and 38C), because small sensor non-linearities can exist.
- Arduino IDE (2.x recommended)
- Board: Arduino UNO (or UNO-compatible)
Libraries used in v5.1.2:
- Wire
- LiquidCrystal_I2C
- Keypad
- EEPROM
- OneWire
- DallasTemperature
Install missing libraries: Arduino IDE -> Tools -> Manage Libraries... Search and install:
- "OneWire" (Paul Stoffregen)
- "DallasTemperature" (Miles Burton)
In the sketch header:
USE_DS18B20- 0: simulated/proxy temps
- 1: real DS18B20 reads (DallasTemperature)
DS18B20_SCAN- 1: prints ROM scan to Serial (for mapping sensors)
Serial:
- Baud: 57600
- DS18B20 VDD -> +5V
- DS18B20 GND -> GND
- DS18B20 DQ -> chosen Arduino pin (1-wire data)
- Pull-up resistor between DQ and +5V:
- typical: 4.7 kOhm (recommended)
- 5.1 kOhm often works for short cables but 4.7 k is the default
Optional (recommended with long cables / noise):
- 100 nF ceramic capacitor between +5V and GND near the sensor end.
- Enable ROM scan (if not already):
USE_DS18B20 = 1DS18B20_SCAN = 1
- Upload firmware.
- Open Serial Monitor (57600 baud).
- Copy the ROM code printed for each sensor and paste into:
addrBath,addrTank,addrBottle
- Label cables physically (recommended):
- "Bh", "Tk", "Bo"
If you change EEPROM structure version, the firmware resets settings automatically
when settings.version != SETTINGS_VERSION.
Main components:
-
State machine with the following states (see figure):
ST_DISPLAY_MAINMENUST_WAITST_SETTIMING_M(edit minutes)ST_SETTIMING_S(edit seconds)ST_STARTMOTORST_IDLE
-
Motor control:
- Bidirectional rotation (CW / CCW)
- Acceleration, constant speed, and braking phases
- Direction reversal after
nFullRevtank revolutions
-
EEPROM persistence:
tMinutes,tSeconds(last-used timing)- 9 development steps (1..9) with minutes, seconds, and "defined" flag
The main loop performs:
- Reads the potentiometer and updates tank RPM (with anti-jitter filter).
- Calls
runMotor(MOTOR_CONTINUE)to maintain motor state. - Updates the countdown display while the motor is running.
- Handles keypad input depending on the current state.
-
FPP-3 software is licensed under the GNU General Public License, version 3 or (at your option) any later version. See the file
COPYINGfor the full license text. -
FPP-3 hardware design files (mechanical and electrical) are licensed under the license documented in
LICENSE-HARDWARE.md. -
Additional details and third-party attributions are documented in
LICENSE.md.