Community project
Programmeerbare Tandheugelpositionering
This guide builds a programmable rack-and-pinion positioning system controlled by an Arduino Uno with a closed-loop stepper motor and real-time touchscreen interface. The system uses three inductive proximity sensors to establish reference positions, a 60 V motor supply for high-torque actuation, and a safety-rated contactor circuit with emergency stop for reliable operation. Readers will receive a complete wiring diagram, parts list with supplier details, Arduino firmware with motion control and UI logic, and step-by-step assembly instructions covering sensor calibration, motor driver configuration, and safety circuit integration.
The project demonstrates industrial automation principles including closed-loop motor feedback, multi-sensor homing sequences, and touch-based parameter adjustment. Assembly requires careful attention to high-voltage routing, proper grounding of the isolation module, and verification of the safety interlock before commissioning.
Wiring diagram

Gather all the parts
Assemble it in 6 steps
1. Plaats scherm en bedieningsknoppen
Schuif het 3,5-inch ILI9486-touchshield recht en volledig op de Arduino Mega. Monteer de snelheidspotmeter, slaglengtepotmeter en ALPS-motorfader in de bedieningskast. Houd alle laagspanningsbedrading weg van de 60-V motorkabels.
- Gebruik afgeschermde of getwiste draad voor de drie analoge lopers als ze verder dan ongeveer 20 cm van de Mega zitten.
- Forceer het scherm niet op de headers; scheef geplaatste pennen kunnen de Mega of het scherm beschadigen.
2. Sluit de twee draaipotmeters aan
Verbind bij elke 10-kΩ draaipotmeter één buitenste poot met Mega 5V en de andere buitenste poot met Mega GND. Verbind de middelste poot van de snelheidspotmeter met A5 en de middelste poot van de slaglengtepotmeter met A6. Draai de twee buitenste draden om wanneer een knop in de verkeerde richting werkt.
- De snelheidspotmeter kiest 10–150 mm/s; de slaglengtepotmeter bepaalt de grens voor SCAN.
- Sluit nooit 24 V of 60 V op A5 of A6 aan; die Mega-ingangen zijn alleen voor 0–5-V instelsignalen.
3. Bedraad de handfader en zijn aanraakspoor
Verbind motorfader POT 1 met Mega GND, POT 3 met Mega 5V en de middelste aansluiting POT 2 WIPER met A7. Verbind TOUCH T met D26. Plaats de 1-MΩ weerstand tussen Mega 5V en dezelfde TOUCH T/D26-verbinding. Gebruik een geleidende metalen faderknop; de handdetectie werkt niet betrouwbaar met een volledig kunststof knop.
- De fader bepaalt alleen tijdelijk een doelpositie. Bij loslaten keert hij terug naar zijn middenstand; de tandheugel beweegt pas wanneer je daarna GO aanraakt.
- De 1-MΩ weerstand is geen motorweerstand: sluit hem uitsluitend tussen 5V en het TOUCH T/D26-punt aan.
4. Sluit de 10-V fader-motor veilig aan
Verbind de 24-V plus en min van de sensorvoeding met VIN+ en VIN− van de geïsoleerde 24-naar-10-V converter. Verbind VOUT+ met VM van de DRV8833. Verbind VOUT− met GND van de DRV8833 én met Mega GND. Verbind AOUT1 en AOUT2 van de DRV8833 met MOTOR A en MOTOR B van de fader. Verbind AIN1 met D44, AIN2 met D45 en nSLEEP met D25. Laat BOUT1 en BOUT2 los en isoleer hun metalen aansluitingen; verbind BIN1 en BIN2 met GND.
- De converter moet galvanisch gescheiden zijn en minimaal 1 A kunnen leveren. De DRV8833 mag niet warmer dan handwarm worden tijdens teruglopen.
- Gebruik geen gewone, niet-geïsoleerde buckconverter op dezelfde 24-V sensorvoeding: dat verbindt de sensormassa alsnog met Mega GND en maakt de PC817-scheiding ongedaan.
- Sluit de 10-V fader-motor nooit rechtstreeks op een Mega-pin aan; dat kan de Mega direct beschadigen.
5. Sluit de drie inductieve sensoren aan
Verbind bij elke SN04-sensor bruin met 24 V plus en blauw met 24 V min. Verbind zwart van links met IN1−, zwart van midden met IN2− en zwart van rechts met IN3− van de 4-kanaals PC817-module. Verbind IN1+, IN2+ en IN3+ met 24 V plus. Verbind VOUT met Mega 5V, GNDOUT met Mega GND en OUT1, OUT2, OUT3 met respectievelijk D18, D19 en D29. Plaats de isolatiejumper op de PC817-module niet.
- Zet elk gebruikt kanaal van de PC817-module op low-level output. Controleer eerst de sensor-LED's met alleen 24 V en USB aangesloten.
- Verbind de 24-V min van de sensoren niet met Mega GND; daardoor verdwijnt de elektrische scheiding.
6. Bedraad noodstop, contactor en 60-V aandrijving
Verbind 24 V plus met NC1 van de noodstop, NC2 met COIL+ van de contactor en COIL− met 24 V min. Verbind 60 V plus via een geschikte DC-zekering met MAIN IN van de contactor, MAIN OUT met CL86T V+ en 60 V min met CL86T GND. Verbind Mega D22 met PUL+, D23 met DIR+, D24 met ENA+ en Mega GND met PUL−, DIR− en ENA−. Verbind de motorfasen uitsluitend volgens het CL86T-motorlabel.
- Test eerst zonder 60 V: noodstop ingedrukt moet de contactor direct laten afvallen. Daarna: HOME zoekt links en vervolgens de middensensor; pas dan zijn GO en SCAN beschikbaar.
- De 60-V voeding mag uitsluitend naar contactor en CL86T gaan — nooit naar de Mega, de PC817-module, de fader of het display.
- Een noodstop is geen gewone stopknop: druk hem alleen in bij gevaar en voer na ontgrendelen altijd opnieuw HOME uit.
Review all connections
1. Connections between "psu_60v_1" and "Arduino"
2. Connections between "cl86t_1" and "Arduino"
3. Connections between "tft_shield_ili9486_1" and "Arduino"
4. Connections between "sensor_left_1" and "Arduino"
5. Connections between "sensor_center_1" and "Arduino"
6. Connections between "sensor_right_1" and "Arduino"
7. Connections between "opto_module_1" and "Arduino"
8. Connections between "contactor_60v_1" and "Arduino"
9. Connections between "psu_24v_1" and "Arduino"
10. Connections between "estop_1" and "Arduino"
11. Connections between "pot_speed_1" and "Arduino"
12. Connections between "pot_stroke_1" and "Arduino"
13. Connections between "fader_supply_1" and "Arduino"
14. Connections between "fader_driver_1" and "Arduino"
15. Connections between "motorfader_1" and "Arduino"
16. Connections between "touch_pullup_1" and "Arduino"
Deploy the firmware
#include <Arduino.h>
#include <AccelStepper.h>
#include <MCUFRIEND_kbv.h>
#include <TouchScreen.h>
const uint16_t BLACK=0x0000, WHITE=0xFFFF, RED=0xF800, GREEN=0x07E0, BLUE=0x001F, YELLOW=0xFFE0, CYAN=0x07FF, MAGENTA=0xF81F, ORANGE=0xFD20;
const byte ST=22, DI=23, EN=24, L=18, C=19, R=29, SAFE=30;
const byte SPEED_POT=A5, STROKE_POT=A6;
const byte FADER_POT=A7, FADER_IN1=44, FADER_IN2=45, FADER_SLEEP=25, FADER_TOUCH=26;
const byte XP=8, YP=A3, XM=A2, YM=9;
const float SPM=200.0f/187.5f, LIM=230.0f, HS=15.0f;
MCUFRIEND_kbv g;
TouchScreen touch(XP, YP, XM, YM, 300);
AccelStepper m(AccelStepper::DRIVER, ST, DI);
enum Q { IDLE, CLR, LEFT, MID, MOVE, ERR };
Q q=IDLE; bool zero=false, scan=false, redraw=true, faderHeld=false; float pos=0, spd=30, stroke=200; uint32_t ht;
uint16_t touchBase=0;
void say(const char*s);
char msg[29]="Druk HOME voor nulstelling";
bool le(){ return !digitalRead(L); } bool ri(){ return !digitalRead(R); } bool mi(){ return !digitalRead(C); }
// OUT4 is LOW only while the 24-V safety circuit energises the contactor coil.
bool safetyReady(){ return !digitalRead(SAFE); }
void ena(bool x){ digitalWrite(EN,x); }
void faderStop(){ analogWrite(FADER_IN1,0); analogWrite(FADER_IN2,0); digitalWrite(FADER_SLEEP,LOW); }
uint16_t touchTime(){
pinMode(FADER_TOUCH,OUTPUT); digitalWrite(FADER_TOUCH,LOW); delayMicroseconds(10);
pinMode(FADER_TOUCH,INPUT); uint32_t t=micros();
while(!digitalRead(FADER_TOUCH) && micros()-t<2000UL) {}
return (uint16_t)(micros()-t);
}
void centerFader(){
int error=analogRead(FADER_POT)-512;
if(abs(error)<18){ faderStop(); return; }
digitalWrite(FADER_SLEEP,HIGH);
// Set this to true only when a test shows the fader moves away from centre.
const bool FADER_REVERSED=false;
bool towardHigh=(error<0);
if(FADER_REVERSED) towardHigh=!towardHigh;
if(towardHigh){ analogWrite(FADER_IN1,180); analogWrite(FADER_IN2,0); }
else { analogWrite(FADER_IN1,0); analogWrite(FADER_IN2,180); }
}
void readHandFader(){
static uint32_t lastRead=0;
if(millis()-lastRead<30) return;
lastRead=millis();
uint16_t sample=touchTime();
if(touchBase==0){ touchBase=sample; return; }
bool touched=sample>(uint16_t)(touchBase+8);
if(!touched) touchBase=(uint16_t)((touchBase*15UL+sample)/16UL);
if(touched && zero && q!=MOVE && q!=ERR && safetyReady()){
faderHeld=true; faderStop();
float newPos=map(analogRead(FADER_POT),0,1023,(long)-stroke,(long)stroke);
if(fabs(newPos-pos)>=1){ pos=newPos; say("Handfader: doel gekozen"); }
} else {
if(faderHeld){ faderHeld=false; say("Handdoel vast; druk GO"); redraw=true; }
centerFader();
}
}
void say(const char*s){ strncpy(msg,s,28); msg[28]=0; redraw=true; }
void bt(int x,int y,int w,const char*s,uint16_t z){ g.fillRoundRect(x,y,w,42,6,z); g.setTextColor(WHITE,z); g.setTextSize(2); g.setCursor(x+(w-strlen(s)*12)/2,y+13); g.print(s); }
void ui(){ g.fillScreen(BLACK); g.setTextColor(CYAN,BLACK); g.setTextSize(3); g.setCursor(10,8); g.print(F("Tandheugel besturing")); g.setTextColor(zero&&q!=ERR?GREEN:YELLOW,BLACK); g.setTextSize(2); g.setCursor(10,44); g.print(msg); g.setTextColor(WHITE,BLACK); g.setCursor(10,76); g.print(F("Positie vanaf midden")); g.setTextSize(4); g.setCursor(12,102); g.print(pos,0); g.setTextSize(2); g.print(F(" mm")); bt(10,145,100,"-10",BLUE); bt(122,145,100,"+10",BLUE); bt(234,145,100,"NUL",BLUE); bt(346,145,124,q==MOVE?"STOP":"GO",q==MOVE?RED:GREEN); g.setCursor(10,205); g.print(F("Snelheid: ")); g.print(spd,0); g.print(F(" mm/s")); g.setCursor(10,226); g.print(F("Slag vanaf midden: ")); g.print(stroke,0); g.print(F(" mm")); bt(234,255,112,"HOME",ORANGE); bt(358,255,112,scan?"SCAN*":"SCAN",MAGENTA); redraw=false; }
bool hit(int&x,int&y){ TSPoint p=touch.getPoint(); pinMode(XM,OUTPUT); pinMode(YP,OUTPUT); if(p.z<80||p.z>1000)return false; x=constrain(map(p.y,120,900,0,479),0,479); y=constrain(map(p.x,120,900,0,319),0,319); return true; }
void readPots(){
static uint32_t lastRead=0;
if(millis()-lastRead<100) return;
lastRead=millis();
float newSpeed=map(analogRead(SPEED_POT),0,1023,10,150);
float newStroke=map(analogRead(STROKE_POT),0,1023,10,230);
if(fabs(newSpeed-spd)>=1){ spd=newSpeed; if(q==MOVE) m.setMaxSpeed(spd*SPM); redraw=true; }
if(fabs(newStroke-stroke)>=1){ stroke=newStroke; if(q!=MOVE) pos=constrain(pos,-stroke,stroke); redraw=true; }
}
void bad(const char*s){m.stop();ena(false);scan=zero=false;q=ERR;say(s);} void home(){if(!safetyReady()){bad("Noodstop actief / geen 24V");return;}zero=scan=false;ena(true);ht=millis();if(le()){m.setSpeed(HS*SPM);q=CLR;say("Links vrijmaken");}else{m.setSpeed(-HS*SPM);q=LEFT;say("Zoekt linker eindstop");}}
void go(float x){if(!safetyReady()){bad("Noodstop actief / geen 24V");return;}if(!zero||q==ERR){say("Eerst HOME uitvoeren");return;}pos=constrain(x,-stroke,stroke);m.setMaxSpeed(spd*SPM);m.setAcceleration(100*SPM);m.moveTo(lround(pos*SPM));ena(true);q=MOVE;say("Beweging actief");}
void runMotion(){if(q==IDLE||q==ERR)return;if(q!=MOVE&&millis()-ht>35000UL){bad("HOME timeout: sensoren?");return;}if(q==CLR){if(ri())bad("Rechter eindstop actief");else if(!le()){m.setSpeed(-HS*SPM);q=LEFT;say("Zoekt linker eindstop");}else m.runSpeed();}else if(q==LEFT){if(le()){m.setSpeed(HS*SPM);q=MID;say("Zoekt middenmarkering");}else m.runSpeed();}else if(q==MID){if(ri())bad("Rechter eindstop actief");else if(mi()){m.setCurrentPosition(0);ena(false);zero=true;q=IDLE;pos=0;say("Midden gereed: 0 mm");}else m.runSpeed();}else{long a=m.currentPosition(),z=m.targetPosition();if((z<a&&le())||(z>a&&ri()))bad("Eindstop bereikt");else{m.run();if(!m.distanceToGo()){ena(false);q=IDLE;if(scan)go(pos<0?stroke:-stroke);else say("Positie bereikt");}}}}
void press(int x,int y){if(y>145&&y<187){if(x<110)pos=max(-stroke,pos-10);else if(x<222)pos=min(stroke,pos+10);else if(x<334)pos=0;else if(q==MOVE){m.stop();ena(false);scan=false;q=IDLE;say("Gestopt door gebruiker");}else go(pos);}else if(y>255&&y<297){if(x>=234&&x<346)home();else if(x>=358){scan=true;go(-stroke);}}redraw=true;}
void setup(){pinMode(L,INPUT_PULLUP);pinMode(C,INPUT_PULLUP);pinMode(R,INPUT_PULLUP);pinMode(SAFE,INPUT_PULLUP);pinMode(SPEED_POT,INPUT);pinMode(STROKE_POT,INPUT);pinMode(FADER_POT,INPUT);pinMode(FADER_IN1,OUTPUT);pinMode(FADER_IN2,OUTPUT);pinMode(FADER_SLEEP,OUTPUT);faderStop();pinMode(EN,OUTPUT);ena(false);m.setMinPulseWidth(5);uint16_t id=g.readID();if(id==0xD3D3||id==0x0101||id==0x0000)id=0x9486;g.begin(id);g.setRotation(1);say("Controleer noodstop; HOME nodig");ui();}
void loop(){static uint32_t lastTouch=0;int x,y;readPots();if(!safetyReady()&&q!=ERR){bad("Noodstop actief / geen 24V");faderStop();}readHandFader();if(millis()-lastTouch>220&&hit(x,y)){press(x,y);lastTouch=millis();}runMotion();if(redraw)ui();delay(1);}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




