Free Pascal ha una caratteristica che lo distingue nettamente da Delphi nell'ambito embedded: il compilatore supporta nativamente la cross-compilazione verso architetture ARM, il che lo rende adatto a progetti su Raspberry Pi anche senza installare un IDE completo sulla scheda. Da un PC Windows o Linux si può compilare un binario ARM da eseguire poi via SSH o SFTP sul dispositivo. Vediamo un caso pratico: controllo di un LED tramite pulsante e misura di una distanza con un sensore a ultrasuoni.
Cross-compilare per Raspberry Pi
Il flag principale da conoscere è -P per la piattaforma di destinazione (ad esempio -Parm per ARM a 32 bit) e -T per il sistema operativo target (-Tlinux). In Lazarus questi parametri si impostano nelle opzioni di progetto, sezione "Target CPU family / OS", senza dover richiamare il compilatore da riga di comando ogni volta. Il codice applicativo resta lo stesso: cambia solo la libreria che effettivamente parla con i pin fisici, tipicamente un binding a sysfs o a libgpiod.
GPIO digitale: uscite e ingressi
Una volta impostata la modalità di un pin (input/output) le operazioni di base sono semplici: scrittura digitale su un'uscita e lettura su un ingresso. Ecco un esempio, ripulito, di controllo di un LED bicolore in funzione dello stato di un pulsante:
program GPIOLedControl;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, CThreads, RPiGPIO; // unit di binding GPIO, non inclusa in FPC
const
BtnPin = 11;
GreenPin = 12;
RedPin = 13;
procedure AggiornaLed(statoPulsante: integer);
begin
if statoPulsante = 0 then
begin
RPiGPIO.DigitalWrite(RedPin, HIGH);
RPiGPIO.DigitalWrite(GreenPin, LOW);
end
else
begin
RPiGPIO.DigitalWrite(RedPin, LOW);
RPiGPIO.DigitalWrite(GreenPin, HIGH);
end;
end;
procedure Setup;
begin
RPiGPIO.SetMode(BOARD);
RPiGPIO.PinMode(GreenPin, OUTPUT);
RPiGPIO.PinMode(RedPin, OUTPUT);
RPiGPIO.PinMode(BtnPin, INPUT);
RPiGPIO.PullUpDn(BtnPin, PUD_UP);
end;Eventi asincroni sui pin: niente polling continuo
Invece di interrogare il pin in un ciclo stretto — che consuma CPU inutilmente — molte librerie GPIO per Raspberry Pi offrono un meccanismo a interrupt: si registra una callback che viene invocata dal sistema solo quando il segnale cambia stato, con un tempo di debounce configurabile.
procedure OnPulsanteCambiato(pin: integer);
begin
AggiornaLed(RPiGPIO.DigitalRead(pin));
end;
// Registra la callback su fronte di salita e discesa, con 200 ms di debounce
RPiGPIO.AddEventDetect(BtnPin, BOTH, @OnPulsanteCambiato, 200);Misurare tempi con precisione: il sensore a ultrasuoni
Un sensore a ultrasuoni tipo HC-SR04 calcola la distanza misurando il tempo che l'eco impiega a tornare dopo un impulso sul pin trigger. In Free Pascal questo si traduce in un'attesa attiva sui due pin e in un calcolo basato su DateUtils:
function MisuraDistanza: Double;
var
inizio, fine: TDateTime;
begin
GPIO.Output(TRIG, 0);
Sleep(1);
GPIO.Output(TRIG, 1);
Sleep(1);
GPIO.Output(TRIG, 0);
while GPIO.Input(ECHO) = 0 do ; // attesa inizio eco
inizio := Now;
while GPIO.Input(ECHO) = 1 do ; // attesa fine eco
fine := Now;
// Velocità del suono ~340 m/s, tempo diviso 2 (andata e ritorno)
Result := MilliSecondsBetween(fine, inizio) / 1000 * 340 / 2 * 100;
end;Va detto che la risoluzione dei timer di sistema su cui si basa Now non è pensata per misure a livello di microsecondi: per progetti reali conviene appoggiarsi a funzioni di timing più fini offerte dalla libreria GPIO scelta, oppure a un timer hardware dedicato. Il codice qui sopra va bene per capire il principio, meno per un uso in produzione dove serva precisione millimetrica.