Com funciona l'ascensor de 3 plantes

Guia de lectura del codi elevator01

IoT-Vertebrae · Simulador Python en línia


Introducció

El programa elevator01 és un controlador d'ascensor de 3 plantes escrit en Python. Controla un motor, portes, botons de planta i cabina, un encoder de posició i un sistema de seguretat. Funciona tant en el simulador web com connectat a maquinari real via Raspberry Pi.

Si alguna vegada has vist o dissenyat el control d'un ascensor amb PLC, reconeixeràs immediatament el que fa aquest codi. La diferència és que aquí, en lloc de blocs Ladder o un GRAFCET, el programa s'escriu en Python.


La idea central: la màquina d'estats

El controlador no és un programa que "fa les coses en ordre". És un programa que sap en quin estat es troba i decideix què fer en funció d'aquest estat i de les condicions actuals.

L'ascensor sempre és en exactament un d'aquests sis estats:

Estat Nom Què significa
ST_WAIT Espera L'ascensor és quiet. Espera que algú premi un botó.
ST_MOVE Moviment El motor està en marxa. S'acosta a la planta destí.
ST_OPEN Obrint El motor de portes actua per obrir-les.
ST_OPENED Portes obertes Les portes estan obertes. Espera el passatger.
ST_CLOSE Tancant El motor de portes actua per tancar-les.
ST_EMERG Emergència S'ha detectat una alarma o sobrecàrrega. Motor aturat.

El programa guarda l'estat actual en una variable: state = ST_WAIT.

Diagrama d'estats de l'ascensor de 3 plantes: ST_WAIT, ST_MOVE, ST_OPEN, ST_OPENED, ST_CLOSE i ST_EMERG amb totes les transicions etiquetades

Diagrama d'estats complet de l'ascensor. Cada caixa és un estat; cada fletxa, una transició amb la condició que la desencadena.


Les entrades i sortides

Abans de llegir el codi, cal saber a quin actuador o sensor correspon cada bit. El programa ho explica als comentaris de la capçalera:

Sortides digitals (el programa controla):

Vèrtebra "0x0", costat A:
  bit 0 → Motor amunt      (contactor de pujada)
  bit 1 → Motor avall      (contactor de baixada)
  bit 2 → Fre              (fre electromagnètic)
  bit 3 → Obrir portes     (motor de portes, sentit obertura)
  bit 4 → Tancar portes    (motor de portes, sentit tancament)
  bit 5 → LED planta 0
  bit 6 → LED planta 1
  bit 7 → LED planta 2

Vèrtebra "0x1", costat A:
  bit 0 → Llum de portes obertes
  bit 1 → Llum d'alarma

Entrades digitals (el programa llegeix):

Vèrtebra "0x0", costat B:
  bit 0 → Botó cabina planta 0
  bit 1 → Botó cabina planta 1
  bit 2 → Botó cabina planta 2
  bit 3 → Alarma tèrmica (sobreescalfament del motor)
  bit 4 → Sobrecàrrega
  bit 5 → Botó de planta 0 (puja)
  bit 6 → Botó de planta 1 (puja)
  bit 7 → Botó de planta 1 (baixa)

Vèrtebra "0x1", costat B:
  bit 0 → Botó de planta 2 (baixa)
  bit 1 → Botó d'obrir portes (manual)
  bit 2 → Botó de tancar portes (manual)
  bit 3 → Sensor de seguretat de portes (fotocel·le)

Entrada analògica (el programa llegeix):

Vèrtebra "0x0", costat B, canal 1 → Encoder de posició
  −10 V = planta 0 (0 metres)
  +10 V = planta 2 (6 metres)

Les constants i les funcions auxiliars

Al principi del codi hi ha les constants que defineixen la geometria de l'ascensor:

FLOOR_POS_0 = 0.0    # planta 0 = 0 metres
FLOOR_POS_1 = 3.0    # planta 1 = 3 metres
FLOOR_POS_2 = 6.0    # planta 2 = 6 metres
MARGIN = 0.2         # tolerància de posicionament: ±20 cm
SPEED_MAX = 4095     # velocitat màxima (valor DAC)
SPEED_MED = 2048     # velocitat mitja
SPEED_LOW = 800      # velocitat baixa (aproximació)
DOOR_TIME = 3.0      # temps mínim de portes obertes (s)
WAIT_TIME = 5.0      # temps addicional d'espera (s)

Les funcions auxiliars simplifiquen el codi principal:

read_encoder() — Llegeix el canal analògic i el converteix a metres:

def read_encoder():
    raw = iotv.ain("0x0", "b", 1)         # valor cru del ADC
    voltage = iotv.ain2v(raw)              # converteix a volts (−10 a +10)
    pos = (voltage + 10.0) / 20.0 * 6.0  # remapeja a metres (0 a 6)
    ...
    return pos

El slider del simulador que va de −10 V a +10 V representa la posició de la cabina de 0 a 6 metres.

stop_motor() — Atura el motor i activa el fre:

def stop_motor():
    iotv.doutbit("0x0", "a", bMotorUp, 0)    # desactiva pujada
    iotv.doutbit("0x0", "a", bMotorDown, 0)  # desactiva baixada
    iotv.doutbit("0x0", "a", bBrake, 1)      # activa fre
    iotv.aout("0x0", "a", 1, 0)             # consigna de velocitat = 0

show_floor(floor) — Actualitza els LEDs indicadors de planta:

def show_floor(floor):
    iotv.doutbit("0x0", "a", bDispP0, 0)    # apaga tots
    iotv.doutbit("0x0", "a", bDispP1, 0)
    iotv.doutbit("0x0", "a", bDispP2, 0)
    if floor == 0:
        iotv.doutbit("0x0", "a", bDispP0, 1)   # encén el de la planta actual
    ...

El bucle principal

El cor del programa és un bucle while True que es repeteix cada 100 ms. En cada iteració fa tres coses:

1. Llegeix tots els botons i actualitza les "plantes pendents":

if iotv.dinbit("0x0", "b", bCabCall0) == 1:
    pending_0 = 1
if iotv.dinbit("0x0", "b", bP0Up) == 1:
    pending_0 = 1
...

pending_0 = 1 vol dir "hi ha algú esperant a la planta 0 o volent anar-hi". Quan l'ascensor arriba, es posa a 0.

2. Executa la lògica de l'estat actual.

3. Espera 100 ms i torna a començar.


Què fa cada estat

ST_WAIT — Espera

El programa comprova si hi ha alguna planta pendent. Si n'hi ha, escull la més propera en la direcció actual i transita a ST_MOVE. Si no n'hi ha, no fa res.

if state == ST_WAIT:
    if is_emergency() == 1:
        state = ST_EMERG
    else:
        # escull la pròxima planta destí...
        if target_floor >= 0:
            if target_floor == current_floor:
                state = ST_OPEN      # ja hi som, obrim portes
            else:
                # engega el motor en la direcció correcta
                state = ST_MOVE

ST_MOVE — Moviment

El programa llegeix l'encoder contínuament i calcula la distància a la planta destí. Ajusta la velocitat del motor segons la distància:

if state == ST_MOVE:
    pos = read_encoder()
    dist = abs(target_pos - pos)

    if dist > 1.5:
        iotv.aout("0x0", "a", 1, SPEED_MAX)    # velocitat màxima (lluny)
    elif dist > 0.5:
        iotv.aout("0x0", "a", 1, SPEED_MED)    # velocitat mitja (s'apropa)
    else:
        iotv.aout("0x0", "a", 1, SPEED_LOW)    # velocitat baixa (aterrant)

    if dist < MARGIN:                           # ha arribat!
        stop_motor()
        state = ST_OPEN

Això és un perfil de velocitat trapezial: va ràpid, desaccelera a temps i s'atura amb precisió. En un ascensor real hi hauria una rampa de jerk (derivada de l'acceleració) per donar comoditat als passatgers, però el principi és el mateix.

ST_OPEN — Obrint portes

El programa comprova el sensor de seguretat. Si no hi ha obstacle, activa el motor de portes en sentit obertura i passa a ST_OPENED.

if state == ST_OPEN:
    safety = iotv.dinbit("0x1", "b", bDoorSafety)
    if safety == 1:
        state = ST_WAIT      # sensor actiu, no obrim
    else:
        iotv.doutbit("0x0", "a", bDoorOpen, 1)
        door_timer = time.time()   # marca l'hora d'obertura
        state = ST_OPENED

ST_OPENED — Portes obertes

El programa espera que el passatger entri. Les portes es tanquen si:

if state == ST_OPENED:
    elapsed = time.time() - door_timer

    close_btn = iotv.dinbit("0x1", "b", bDoorCloseBtn)
    if close_btn == 1:
        if elapsed > 1.0:            # marge mínim d'1 s per evitar tancaments accidentals
            state = ST_CLOSE

    if elapsed > DOOR_TIME + WAIT_TIME:
        state = ST_CLOSE

ST_CLOSE — Tancant portes

El programa comprova el sensor de seguretat de portes. Si detecta un obstacle mentre es tanquen, reobre immediatament. Si no, tanca i torna a espera.

if state == ST_CLOSE:
    safety = iotv.dinbit("0x1", "b", bDoorSafety)
    if safety == 1:
        print("Obstacle! Reopening")
        state = ST_OPEN             # reobre sense discussió
    else:
        iotv.doutbit("0x0", "a", bDoorClose, 1)
        time.sleep(1.5)
        iotv.doutbit("0x0", "a", bDoorClose, 0)
        state = ST_WAIT

ST_EMERG — Emergència

El programa atura el motor, activa la llum d'alarma i obre les portes. Llavors entra en un bucle intern esperant que l'alarma o la sobrecàrrega es resolgui. Quan es resol, recupera la posició i torna a ST_WAIT.

if state == ST_EMERG:
    stop_motor()
    iotv.doutbit("0x1", "a", bAlarmLight, 1)
    iotv.doutbit("0x0", "a", bDoorOpen, 1)

    while True:                          # espera activa fins que s'esborra l'alarma
        a = iotv.dinbit("0x0", "b", bCabAlarm)
        o = iotv.dinbit("0x0", "b", bCabOverload)
        if a == 0:
            if o == 0:
                break
        time.sleep(0.2)
    ...
    state = ST_WAIT

Com provar-ho al simulador

Un cop carregat i executat el codi:

  1. Prem el botó P2Down al bessó digital → l'ascensor puja cap a la planta 2. Observa com augmenta la posició a la consola.
  2. Mentres puja, prem P1Up → la planta 1 queda pendent. Quan l'ascensor baixi de la planta 2, s'aturarà a la 1.
  3. Activa l'emergència mentres es mou → el motor s'atura immediatament, les portes s'obren i s'encén la llum d'alarma.
  4. Desactiva l'emergència → l'ascensor recupera la posició i torna a espera.
  5. Activa DoorSafety mentre les portes es tanquen → les portes es reobren.

Per modificar-lo

El canvi més senzill és afegir un comptador de viatges. Cerca al codi el bloc # Arrived? dins de ST_MOVE i afegeix-hi:

# Al principi del programa, fora del bucle:
comptador_viatges = 0

# Dins del bloc "if dist < MARGIN:", just abans de "state = ST_OPEN":
comptador_viatges = comptador_viatges + 1
print("Viatge nº", comptador_viatges, ": Planta", current_floor)

Un altre canvi fàcil: modificar DOOR_TIME i WAIT_TIME per ajustar quan es tanquen les portes.


Connexió amb la indústria

Aquest controlador és equivalent a una SFC (Sequential Function Chart) de PLC IEC 61131-3, que és el que s'usa en ascensors industrials reals. Cada estat de la variable state correspon a un pas de la SFC. Les transicions (les condicions que canvien l'estat) corresponen als arcs de la SFC.

La diferència principal entre aquest codi Python i un PLC de producció és la certificació de seguretat (SIL2 o SIL3 per a ascensors). El codi és conceptualment idèntic — el que diferencia el PLC és que el hardware és determinista i ha superat auditories de fiabilitat. Per a prototipatge, formació i sistemes no crítics, IoT-Vertebrae ofereix la mateixa lògica a una fracció del cost.