Community project

Fuoristrada inclinometro intelligente

Photo of Fuoristrada inclinometro intelligente
Generated with AI

gialstickers

Published November 13, 2026

This off-road inclinometer system uses an ESP32 microcontroller to monitor vehicle tilt angles in real time, helping drivers assess terrain difficulty and prevent rollovers. Built around the Witmotion WT61PC-TTL 3-axis accelerometer and a BH1750 light sensor, the system displays pitch and roll angles on a 480×480 canvas display, with configurable tilt alarms and ambient light monitoring for safe operation in varying conditions.

This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions for integrating the inclinometer into a vehicle's 12V electrical system. The included firmware handles sensor calibration, angle computation with drift compensation, alarm thresholds, and a menu interface for adjusting limits—everything needed to build a rugged, reliable tilt monitoring system for off-road adventures.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

Witmotion WT61PC-TTL 3.3–5V

Modulo UART con filtro Kalman, alimentato a 3.3V per livelli logici sicuri.

1

BH1750

Rohm BH1750FVI 16-bit digital ambient light sensor with I2C interface. Measures illuminance from 1 to 65535 lux. Two selectable I2C addresses (0x23 / 0x5C) via ADDR pin.

1

Greluma impermeabile 12V–5V 3A 15W

Ingresso 8–22V; non garantisce protezione dai transitori automotive.

1

Presa 12V DC sotto chiave del veicolo

Alimentazione esterna sotto chiave.

1

Fusibile a lama 2A con portafusibile

Protezione del cablaggio positivo vicino alla presa.

1

Rocker Switch

Interruttore DC almeno 24V 3A

Classic compact panel-mount rocker switch for on/off power control in enclosures.

1

LED

Verde 5mm

Standard 3mm/5mm through-hole LED. A current-limiting series resistor is added automatically.

1

Resistor

1 kohm 0.25W

Through-hole resistor (current-limiting in series with an LED)

Assemble it in 5 steps

1. Preparare alimentazione protetta

Con quadro spento collegare positivo sotto chiave al fusibile da 2A, poi all'interruttore DC e a IN+ del Greluma. Il negativo va a IN-. Posizionare il portafusibile vicino alla presa e usare cavi da almeno 0.5 mm².

  • Il Greluma accetta 8–22V: il fusibile non protegge dai picchi automotive. Usare esclusivamente una presa già protetta dai transitori del veicolo; prima di un collegamento diretto alla rete auto serve una protezione automotive qualificata. Non usare su impianti a 24V.

2. Collegare la linea da 5V

Verificare con multimetro 5V e polarità prima di collegare OUT+ a VBus del connettore 12 pin e OUT- a GND. Collegare la resistenza da 1 kohm fra 5V e anodo lungo del LED verde; catodo corto a GND.

  • Mai applicare 12V alla scheda. Scollegare alimentazione del veicolo prima di collegare USB al computer; evitare due fonti 5V contemporanee. Non collegare batterie ai connettori della scheda.

3. Collegare il sensore di inclinazione

WT61PC VCC a 3V3, GND a GND, TXD a RXD GPIO44. Lasciare RXD del sensore scollegato. Usare il connettore UART dedicato senza collegare la porta Type-C UART durante le misure. Il firmware riceve pacchetti a 9600 baud.

  • Fissare rigidamente il sensore con X verso il muso, Y verso sinistra e Z verso alto. Verificare segno e assi inclinando lentamente il modulo prima del montaggio definitivo.

4. Collegare il sensore luce

BH1750 VCC a 3V3 e GND a GND; SDA a GPIO15 (dati), SCL a GPIO7 (clock), ADDR a GND. Esporre il sensore alla luce ambiente evitando la luce diretta del display.

5. Installare e controllare da fermo

Proteggere scheda e sensori in un contenitore asciutto e fissare lontano da airbag e comandi. Su piano livellato mantenere ZERO sul display per due secondi. In SOGLIE usare meno e più per ciascun asse e SALVA per tornare. Soglie e zero rimangono memorizzati dopo lo spegnimento.

  • Strumento indicativo, non dispositivo di sicurezza anti-ribaltamento. Accelerazioni, vibrazioni e terreno dinamico alterano le misure: la soglia non determina il limite sicuro del mezzo. Regolare soltanto da fermi.

Review all connections

1. Connections between "supply" and "ESP32"

FunctionsupplyESP32
powerPOS → Fusibile a lama 2A con portafusibile INEXT
groundNEG → Greluma impermeabile 12V–5V 3A 15W IN-EXT

2. Connections between "fuse" and "ESP32"

FunctionfuseESP32
powerOUT → Rocker Switch T1EXT

3. Connections between "sw" and "ESP32"

FunctionswESP32
powerT2 → Greluma impermeabile 12V–5V 3A 15W IN+EXT

4. Connections between "buck" and "ESP32"

FunctionbuckESP32
groundOUT-GND
powerOUT+5V
powerOUT+ → Resistor P1EXT

5. Connections between "rled" and "ESP32"

FunctionrledESP32
digitalP2 → LED ANODEEXT

6. Connections between "led" and "ESP32"

FunctionledESP32
groundGNDGND

7. Connections between "imu" and "ESP32"

FunctionimuESP32
powerVCC3V3
groundGNDGND
uartTXDGPIO 44

8. Connections between "lux" and "ESP32"

FunctionluxESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 15
i2cSCLGPIO 7
groundADDRGND

Deploy the firmware

#include <Arduino.h>
#include <esp_display_panel.hpp>
#include <Arduino_GFX_Library.h>
#include <Preferences.h>
#include <driver/i2c.h>
using namespace esp_panel::board;
using namespace esp_panel::drivers;
Board board;
Arduino_Canvas canvas(480,480,nullptr);
Preferences prefs;
HardwareSerial sensor(1);
float rawR=0,rawP=0,zR=0,zP=0;
int limitR=25,limitP=30;
uint32_t lastFrame=0,lastData=0,hold=0,lastLux=0;
bool menu=false,dirty=true,tiltAlarm=false,flash=false,zeroDone=false,lightOK=false;
uint8_t packet[11]; int used=0; int lastAction=-1;
float wrap(float x){while(x>180)x-=360;while(x< -180)x+=360;return x;}
void save(){prefs.putFloat("zr",zR);prefs.putFloat("zp",zP);prefs.putInt("lr",limitR);prefs.putInt("lp",limitP);}
void text(int x,int y,const String &s,int size=2){canvas.setTextSize(size);canvas.setTextColor(0xffff);canvas.setCursor(x,y);canvas.print(s);}
void lineRot(int cx,int cy,float a,int x1,int y1,int x2,int y2,uint16_t col){float c=cos(a),s=sin(a);canvas.drawLine(cx+x1*c-y1*s,cy+x1*s+y1*c,cx+x2*c-y2*s,cy+x2*s+y2*c,col);}
void suv(int cx,int cy,float deg,bool side){float a=deg*PI/180; int w=side?65:45;
lineRot(cx,cy,a,-w,15,w,15,0x07ff);lineRot(cx,cy,a,-w,15,-w,-15,0x07ff);lineRot(cx,cy,a,w,15,w,-15,0x07ff);
lineRot(cx,cy,a,-w,-15,-w+18,-45,0x07ff);lineRot(cx,cy,a,-w+18,-45,w-18,-45,0x07ff);lineRot(cx,cy,a,w-18,-45,w,-15,0x07ff);lineRot(cx,cy,a,-w,-15,w,-15,0x07ff);
for(int k=-1;k<=1;k+=2){lineRot(cx,cy,a,k*(w-12)-8,16,k*(w-12)-8,30,0xffff);lineRot(cx,cy,a,k*(w-12)+8,16,k*(w-12)+8,30,0xffff);lineRot(cx,cy,a,k*(w-12)-8,30,k*(w-12)+8,30,0xffff);}
if(!side){lineRot(cx,cy,a,-18,-10,18,-10,0xffff);lineRot(cx,cy,a,-18,-10,-18,10,0xffff);lineRot(cx,cy,a,18,-10,18,10,0xffff);lineRot(cx,cy,a,-18,10,18,10,0xffff);}}
void scale(int x,float value){canvas.drawFastVLine(x,145,191,0x7bef);for(int d=-45;d<=45;d+=15){int y=240-d*2;canvas.drawFastHLine(x-6,y,13,0xffff);text(x<240?x+12:x-45,y-6,String(d),1);}int y=240-constrain(value,-45.f,45.f)*2;canvas.fillTriangle(x-8,y,x+8,y-6,x+8,y+6,0xffe0);}
void render(){float r=wrap(rawR-zR),p=wrap(rawP-zP);canvas.fillScreen(flash?0xf800:0x0841);text(143,55,"OFFROAD",3);
if(menu){text(90,120,"SOGLIE ALLARME");text(100,180,"ROLLIO  "+String(limitR)+" gradi");text(100,250,"BECC.   "+String(limitP)+" gradi");text(85,210,"-                 +");text(85,280,"-                 +");text(170,355,"SALVA");}
else {text(100,115,"ROLLIO");text(275,115,"BECC.");suv(155,220,r,false);suv(325,220,p,true);scale(55,r);scale(425,p);bool valid=lastData && millis()-lastData<1000;text(104,295,valid?String(r,1)+" deg":"---",2);text(280,295,valid?String(p,1)+" deg":"---",2);text(85,340,valid?(tiltAlarm?"ALLARME INCLINAZIONE":"WT61PC collegato"):"ERRORE: SENSORE ASSENTE",1);text(95,385,"ZERO");text(270,385,"SOGLIE");text(100,415,hold?"Mantieni ZERO per 2 secondi":"ZERO: pressione lunga",1);if(!lightOK)text(140,365,"LUCE: NON RILEVATA",1);}
board.getLCD()->drawBitmap(0,0,480,480,(uint8_t*)canvas.getFramebuffer());dirty=false;}
void setup(){prefs.begin("inclino",false);zR=prefs.getFloat("zr",0);zP=prefs.getFloat("zp",0);limitR=constrain(prefs.getInt("lr",25),5,60);limitP=constrain(prefs.getInt("lp",30),5,60);
if(!board.init()||!board.begin()||!canvas.begin())while(true)delay(1000);
sensor.begin(9600,SERIAL_8N1,44,-1);
uint8_t cmd=0x10;lightOK=i2c_master_write_to_device(I2C_NUM_0,0x23,&cmd,1,pdMS_TO_TICKS(20))==ESP_OK;board.getBacklight()->setBrightness(60);}
void loop(){uint32_t now=millis();while(sensor.available()){uint8_t b=sensor.read();if(used==0&&b!=0x55)continue;packet[used++]=b;if(used==11){used=0;uint8_t sum=0;for(int i=0;i<10;i++)sum+=packet[i];if(sum==packet[10]&&packet[1]==0x53){rawR=(int16_t)(packet[2]|packet[3]<<8)*180.f/32768;rawP=(int16_t)(packet[4]|packet[5]<<8)*180.f/32768;lastData=now;}}}
static int oldR=9999,oldP=9999;int r=lround(wrap(rawR-zR)*10),p=lround(wrap(rawP-zP)*10);if(r!=oldR||p!=oldP){dirty=true;oldR=r;oldP=p;}
static bool staleOld=true;bool stale=!lastData||now-lastData>1000;if(stale!=staleOld){dirty=true;staleOld=stale;}bool newAlarm=!stale&&(abs(r)>=limitR*10||abs(p)>=limitP*10);if(tiltAlarm&& !stale && abs(r)>(limitR-2)*10)newAlarm=true;if(tiltAlarm&&!stale&&abs(p)>(limitP-2)*10)newAlarm=true;tiltAlarm=newAlarm;bool nf=tiltAlarm&&((now/400)%2==0);if(nf!=flash){flash=nf;dirty=true;}
TouchPoint pt;bool down=board.getTouch()->readPoints(&pt,1)>0;int action=-1;if(down){if(!menu&&pt.y>=375&&pt.y<415){if(pt.x>=75&&pt.x<220)action=0;else if(pt.x>=250&&pt.x<400)action=1;}else if(menu){if(pt.y>=175&&pt.y<240)action=pt.x<240?2:3;else if(pt.y>=245&&pt.y<310)action=pt.x<240?4:5;else if(pt.y>=340&&pt.y<390)action=6;}}
if(action==0&&!stale){if(!hold){hold=now;dirty=true;}if(now-hold>=2000&&!zeroDone){zR=rawR;zP=rawP;save();zeroDone=true;dirty=true;}}
else{if(hold)dirty=true;hold=0;zeroDone=false;}
if(action>=1&&lastAction==-1){if(action==1)menu=true;if(action==2)limitR=max(5,limitR-1);if(action==3)limitR=min(60,limitR+1);if(action==4)limitP=max(5,limitP-1);if(action==5)limitP=min(60,limitP+1);if(action==6){save();menu=false;}dirty=true;}lastAction=down?action:-1;
if(now-lastLux>=500){lastLux=now;uint8_t data[2];bool ok=i2c_master_read_from_device(I2C_NUM_0,0x23,data,2,pdMS_TO_TICKS(20))==ESP_OK;if(ok!=lightOK){lightOK=ok;dirty=true;}static float filtered=100;static int previous=-1;if(ok){float lux=((data[0]<<8)|data[1])/1.2f;filtered=filtered*.8f+lux*.2f;}int bright=ok?constrain((int)(15+log10(1+filtered)*22),15,100):60;if(abs(bright-previous)>=3){board.getBacklight()->setBrightness(bright);previous=bright;}}
if(dirty&&now-lastFrame>=100){lastFrame=now;render();}delay(5);}

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.

Open in Schematik