Lezione 41
🎓 PIC16 — From Zero to Hero · Lezione 41 · Modulo 7
Progetto 4: Servo Controller 8 Canali 🤖
PWM software su 8 pin PORTB · Interpolazione lineare per movimenti fluidi
Sequenze automatiche hard-coded · USART bidirezionale con parser a stati · EEPROM posizioni
🎯 Cosa costruisci in questa lezione
- PWM software 8 canali: Timer0 interrupt ogni 50µs (TMR0 precaricato a 206). Ogni frame da 20ms ospita 8 impulsi consecutivi, uno per canale. SERVO_N = tick di durata (20=1ms=0°, 30=1.5ms=90°, 40=2ms=180°)
- ISR a stati: decrementa PULSE_TICK; quando arriva a 0 abbassa il pin, carica il canale successivo via FSR/INDF, alza il nuovo pin
- Interpolazione lineare: ogni frame confronta SERVO_N con TGT_N e incrementa o decrementa di 1 tick. Con STEPS_N=50 e aggiornamento ogni frame (20ms) il movimento dura 1 secondo — nessun virgola mobile, solo INCF/DECF
- Sequenza automatica hard-coded in Flash come tabella RETLW: (canale, target, durata) × N mosse + 0xFF per loop. UPDATE_SEQUENCE avanza la lista ogni frame e imposta nuovi target
- USART bidirezionale 9600 baud: parser a 3 stati (attendi CMD, attendi CH, attendi VAL). Comandi: S(set), G(get), R(reset), P(play), X(stop), W(write EEPROM)
- Risposta ‘K’ per OK, ‘E’ per errore — comunicazione seriale dal PC con qualsiasi terminale (PuTTY, minicom, screen)
- EEPROM: salva e carica 8 byte con validazione range — se i dati EEPROM sono fuori range usa default 90°
📋 Scheda progetto
Lezione: 41 / 42
Modulo: 7 — Progetti Completi
Device: PIC16F877A
Prerequisiti: tutto il corso
Difficoltà: ★★★★★
Hardware: 8× servo SG90, transistor buffer
Comandi: PC via USB-UART 9600 baud
Modulo 1 · PWM software 8 canali + parser USART base
⚠️ Corrente servo e driver: ogni servo SG90 può assorbire fino a 200mA in movimento. I pin PORTB del PIC16F877A erogano al massimo 25mA ciascuno e 200mA totali su tutto il PORTB. Con 8 servo si supera abbondantemente questo limite — è obbligatorio usare un driver tra il PIC e i servo: transistor NPN (BC547) in configurazione emitter-follower, o un buffer come ULN2003 (7 canali open-collector), oppure alimentare i servo direttamente dalla sorgente 5V e usare i pin PIC solo per il segnale PWM (che assorbe ~1mA dal servo).
; ============================================================ ; Lezione 41 — Servo controller: PWM software 8 canali ; PIC16F877A @ 4 MHz ; Servo standard: impulso 1ms=0°, 1.5ms=90°, 2ms=180° ; Periodo frame: 20ms (50Hz) ; Tecnica: Timer0 interrupt ogni 50µs → risoluzione 1/400 del frame ; 8 canali su PORTB bit7:0, uno alla volta (non tutti insieme!) ; Ogni canale ha il proprio contatore impulso ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Posizioni servo (0-40: 0°..180°, step = 50µs) ; 1ms = 20 tick, 1.5ms = 30 tick, 2ms = 40 tick SERVO_0 ; canale 0: tick impulso (20-40) SERVO_1 SERVO_2 SERVO_3 SERVO_4 SERVO_5 SERVO_6 SERVO_7 ; Stato PWM FRAME_TICK ; tick corrente nel frame (0-399) FRAME_TICK_H ; byte alto (frame 400 tick × 50µs = 20ms) ACTIVE_CH ; canale attivo in questo tick (0-7) PULSE_TICK ; tick rimanenti per l'impulso corrente FLAGS ; bit0=nuovo_frame ; Target positions (per interpolazione) TGT_0 TGT_1 TGT_2 TGT_3 ; USART RX_BYTE RX_CMD ; comando ricevuto RX_CH ; canale dal comando RX_VAL ; valore dal comando PARSE_STATE ; 0=attendi cmd, 1=attendi ch, 2=attendi val TMP DLY ENDC ORG 0x0000 GOTO INIT ORG 0x0004 ; ============================================================ ; ISR — Timer0 ogni 50µs (TMR0 precaricato a 56) ; Prescaler 1:4: 1µs × 4 = 4µs/tick, 50µs/4 = 12.5 → uso 1:1 ; @ 4MHz: 1 ciclo = 1µs, TMR0 senza prescaler overflow ogni 256µs ; Per 50µs: precarica TMR0 = 256 - 50 = 206 ; ============================================================ ISR BTFSS INTCON,T0IF RETFIE BCF INTCON,T0IF MOVLW D'206' ; ricarica TMR0 per prossimo 50µs MOVWF TMR0 ; Avanza tick frame INCF FRAME_TICK,F BTFSC STATUS,Z INCF FRAME_TICK_H,F ; Frame completo ogni 400 tick (400×50µs = 20ms) MOVF FRAME_TICK_H,F BTFSS STATUS,Z GOTO ISR_CHECK_400 MOVF FRAME_TICK,W SUBLW D'144' ; 400 - 256 = 144 (overflow conta da 0) BTFSC STATUS,Z GOTO ISR_RESET_FRAME ISR_CHECK_400 ; Versione semplice: solo byte basso, frame = 256 tick = 12.8ms ; (semplificazione didattica: frame ~13ms invece di 20ms) MOVF FRAME_TICK,F BTFSS STATUS,Z GOTO ISR_PULSE ISR_RESET_FRAME CLRF FRAME_TICK CLRF FRAME_TICK_H BSF FLAGS,0 ; nuovo frame CLRF ACTIVE_CH ; ricomincia dal canale 0 ; Carica impulso canale 0 MOVF SERVO_0,W MOVWF PULSE_TICK BSF PORTB,0 ; alza pin canale 0 ISR_PULSE ; Gestione impulso canale attivo MOVF PULSE_TICK,F BTFSC STATUS,Z GOTO ISR_NEXT_CH ; impulso finito → prossimo canale DECF PULSE_TICK,F ; decrementa tick rimanenti RETFIE ISR_NEXT_CH ; Abbassa il pin del canale corrente MOVF ACTIVE_CH,W ADDWF PCL,F GOTO LOW_CH0 GOTO LOW_CH1 GOTO LOW_CH2 GOTO LOW_CH3 GOTO LOW_CH4 GOTO LOW_CH5 GOTO LOW_CH6 GOTO LOW_CH7 LOW_CH0 BCF PORTB,0 : GOTO NEXT_CH LOW_CH1 BCF PORTB,1 : GOTO NEXT_CH LOW_CH2 BCF PORTB,2 : GOTO NEXT_CH LOW_CH3 BCF PORTB,3 : GOTO NEXT_CH LOW_CH4 BCF PORTB,4 : GOTO NEXT_CH LOW_CH5 BCF PORTB,5 : GOTO NEXT_CH LOW_CH6 BCF PORTB,6 : GOTO NEXT_CH LOW_CH7 BCF PORTB,7 : GOTO NEXT_CH NEXT_CH ; Avanza al prossimo canale INCF ACTIVE_CH,F MOVF ACTIVE_CH,W SUBLW D'8' BTFSC STATUS,Z GOTO ISR_EXIT ; tutti 8 canali serviti ; Carica impulso canale successivo MOVLW SERVO_0 ADDWF ACTIVE_CH,W MOVWF TMP ; Leggi SERVO_N via FSR MOVF TMP,W MOVWF FSR MOVF INDF,W MOVWF PULSE_TICK ; Alza pin canale successivo MOVF ACTIVE_CH,W ADDWF PCL,F BSF PORTB,0 ; ch0 (non raggiunto qui) BSF PORTB,1 BSF PORTB,2 BSF PORTB,3 BSF PORTB,4 BSF PORTB,5 BSF PORTB,6 BSF PORTB,7 ISR_EXIT RETFIE ORG 0x0080 INIT BSF STATUS,RP0 CLRF TRISB ; PORTB output (tutti i servo) ; USART: 9600 baud @ 4MHz ; SPBRG = FOSC/(16×baud) - 1 = 4000000/(16×9600) - 1 = 25 MOVLW D'25' MOVWF SPBRG MOVLW b'00100100' ; TXEN=1, BRGH=1 (alta velocità) MOVWF TXSTA ; TMR0: no prescaler (PSA=1), sorgente interna MOVLW b'11001000' ; PSA=1 (no prescaler TMR0) MOVWF OPTION_REG BCF STATUS,RP0 ; RCSTA: abilita USART e ricezione continua MOVLW b'10010000' ; SPEN=1, CREN=1 MOVWF RCSTA ; Posizioni iniziali: tutti a 90° (30 tick = 1.5ms) MOVLW D'30' MOVWF SERVO_0 MOVWF SERVO_1 MOVWF SERVO_2 MOVWF SERVO_3 MOVWF SERVO_4 MOVWF SERVO_5 MOVWF SERVO_6 MOVWF SERVO_7 CLRF FRAME_TICK CLRF FRAME_TICK_H CLRF ACTIVE_CH CLRF FLAGS CLRF PARSE_STATE MOVLW D'30' MOVWF PULSE_TICK ; primo impulso a 90° BSF PORTB,0 ; alza canale 0 ; Abilita interrupt TMR0 MOVLW b'10100000' ; GIE=1, T0IE=1 MOVWF INTCON MOVLW D'206' MOVWF TMR0 ; ============================================================ ; MAIN — gestisce comandi USART e interpolazione ; Protocollo seriale: 'S' CH VAL → imposta servo ; CH = '0'-'7' (canale), VAL = 0x00-0x28 (0°-180°) ; Esempio: 'S' 0x02 0x1E → servo 2 a 90° ; ============================================================ MAIN ; Controlla USART RX BSF STATUS,RP0 BTFSS PIR1,RCIF ; dato disponibile? GOTO MAIN_NO_RX BCF STATUS,RP0 ; Leggi byte MOVF RCREG,W MOVWF RX_BYTE ; Parsing stato: 0=attendi 'S', 1=attendi canale, 2=attendi valore MOVF PARSE_STATE,W ADDWF PCL,F GOTO PARSE_WAIT_CMD GOTO PARSE_WAIT_CH GOTO PARSE_WAIT_VAL PARSE_WAIT_CMD ; Attendi 'S' (0x53) MOVF RX_BYTE,W SUBLW 'S' BTFSS STATUS,Z GOTO MAIN_NO_RX ; non 'S': ignora INCF PARSE_STATE,F ; → stato 1 GOTO MAIN_NO_RX PARSE_WAIT_CH MOVF RX_BYTE,W MOVWF RX_CH INCF PARSE_STATE,F ; → stato 2 GOTO MAIN_NO_RX PARSE_WAIT_VAL MOVF RX_BYTE,W MOVWF RX_VAL CLRF PARSE_STATE ; → stato 0 ; Valida canale (0-7) MOVF RX_CH,W SUBLW D'7' BTFSS STATUS,C GOTO MAIN_NO_RX ; canale > 7: ignora ; Valida valore (20-40: 1ms-2ms) MOVF RX_VAL,W SUBLW D'19' BTFSC STATUS,C ; < 20? GOTO MAIN_NO_RX MOVF RX_VAL,W SUBLW D'40' BTFSS STATUS,C ; > 40? GOTO MAIN_NO_RX ; Imposta SERVO_N = RX_VAL BCF INTCON,GIE ; disabilita interrupt durante modifica MOVLW SERVO_0 ADDWF RX_CH,W MOVWF FSR MOVF RX_VAL,W MOVWF INDF ; SERVO[CH] = VAL BSF INTCON,GIE MAIN_NO_RX BCF STATUS,RP0 GOTO MAIN END
Modulo 2 · Interpolazione lineare e sequenze automatiche
💡 Perché ±1 tick per frame funziona: la risoluzione del servo è 20 tick (1ms) per 180° → ogni tick = 9°. Con ±1 tick/frame a 50Hz (frame ogni 20ms) la velocità massima è 1 tick × 50 frame/s = 50 tick/s = 450°/s. Un servo SG90 ruota a ~500°/s — quindi l’interpolazione ±1/frame è vicina alla velocità massima del servo. Per movimenti più lenti: aggiornare solo ogni N frame (STEPS conta frame, non tick). Per movimenti più veloci con salti grandi: incremento proporzionale — ma serve la divisione, e torniamo all’algoritmo Bresenham completo.
; ============================================================ ; Lezione 41 — Interpolazione lineare e sequenze ; PIC16F877A @ 4 MHz ; Interpolazione: muovi il servo da posizione A a B in N passi ; Ogni passo = (B - A) / N, aggiornato ogni frame (20ms) ; Con N=50 frame: movimento fluido in 1 secondo ; Sequenza: lista di (canale, target, durata) eseguita automaticamente ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Posizioni correnti (come modulo 1) SERVO_0 SERVO_1 SERVO_2 SERVO_3 ; Posizioni target per interpolazione TGT_0 TGT_1 TGT_2 TGT_3 ; Passi rimanenti per ogni canale STEPS_0 ; 0 = fermo STEPS_1 STEPS_2 STEPS_3 ; Direzione: 0=avanza, 1=indietro (per calcolo) ; (usiamo il segno della differenza target-corrente) ; Errore accumulato (fixed point 4.4) ERR_0 ERR_1 ERR_2 ERR_3 ; Sequenza automatica SEQ_PTR ; puntatore alla sequenza corrente (indice) SEQ_PAUSE ; frame rimanenti di pausa tra mosse SEQ_ACTIVE ; 1=sequenza in esecuzione FLAGS ; Temporanei TMP TMP2 ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; Sequenza hard-coded: 8 mosse ; Formato: CH, TARGET (tick 20-40), DURATA (frame) ; 0xFF = fine sequenza (loop) ; ============================================================ ORG 0x0020 SEQUENCE ; ch0 → 20 (0°), 50 frame (1s) RETLW 0x00 RETLW D'20' RETLW D'50' ; ch1 → 40 (180°), 50 frame RETLW 0x01 RETLW D'40' RETLW D'50' ; ch0 → 40 (180°), 100 frame (2s) RETLW 0x00 RETLW D'40' RETLW D'100' ; ch1 → 20 (0°), 100 frame RETLW 0x01 RETLW D'20' RETLW D'100' ; ch2 → 30 (90°), 30 frame RETLW 0x02 RETLW D'30' RETLW D'30' ; ch3 → 25 (45°), 30 frame RETLW 0x03 RETLW D'25' RETLW D'30' ; ch2 → 30 (90°), 50 frame (torna centro) RETLW 0x02 RETLW D'30' RETLW D'50' ; fine: loop RETLW 0xFF ; ============================================================ ; READ_SEQ — legge byte N dalla sequenza in W ; ============================================================ READ_SEQ ADDWF PCL,F ; salta nella tabella SEQUENCE ; (non funziona così direttamente, usiamo call) RETURN ; ============================================================ ; GET_SEQ_BYTE — legge SEQUENCE[SEQ_PTR] in W ; ============================================================ GET_SEQ_BYTE MOVF SEQ_PTR,W CALL SEQUENCE RETURN ; ============================================================ ; UPDATE_SEQUENCE — avanza sequenza se attiva ; Chiamato ogni frame (FLAGS,0=1) ; ============================================================ UPDATE_SEQUENCE BTFSS SEQ_ACTIVE,0 RETURN ; Controlla pausa MOVF SEQ_PAUSE,F BTFSC STATUS,Z GOTO SEQ_NEXT_MOVE DECF SEQ_PAUSE,F RETURN SEQ_NEXT_MOVE ; Leggi prossima mossa dalla sequenza CALL GET_SEQ_BYTE MOVWF TMP ; canale ADDLW 0 ; check 0xFF SUBLW 0xFF BTFSC STATUS,Z GOTO SEQ_LOOP ; fine: ricomincia ; Avanza puntatore e leggi target INCF SEQ_PTR,F CALL GET_SEQ_BYTE MOVWF TMP2 ; target INCF SEQ_PTR,F CALL GET_SEQ_BYTE MOVWF SEQ_PAUSE ; durata come pausa INCF SEQ_PTR,F ; Imposta interpolazione per il canale TMP MOVF TMP,W ADDWF PCL,F GOTO SET_CH0 GOTO SET_CH1 GOTO SET_CH2 GOTO SET_CH3 GOTO SEQ_DONE ; canali > 3: ignora GOTO SEQ_DONE GOTO SEQ_DONE GOTO SEQ_DONE SET_CH0 MOVF TMP2,W MOVWF TGT_0 MOVLW D'50' MOVWF STEPS_0 CLRF ERR_0 GOTO SEQ_DONE SET_CH1 MOVF TMP2,W MOVWF TGT_1 MOVLW D'50' MOVWF STEPS_1 CLRF ERR_1 GOTO SEQ_DONE SET_CH2 MOVF TMP2,W MOVWF TGT_2 MOVLW D'50' MOVWF STEPS_2 CLRF ERR_2 GOTO SEQ_DONE SET_CH3 MOVF TMP2,W MOVWF TGT_3 MOVLW D'50' MOVWF STEPS_3 CLRF ERR_3 SEQ_DONE RETURN SEQ_LOOP CLRF SEQ_PTR ; ricomincia sequenza CLRF SEQ_PAUSE GOTO SEQ_NEXT_MOVE ; ============================================================ ; INTERPOLATE_CH — avvicina SERVO_N di un passo verso TGT_N ; Algoritmo Bresenham lineare: evita divisione floating point ; Ogni frame: SERVO += (TGT - SERVO) > 0 ? +1 : -1 ; Fine quando SERVO == TGT ; ============================================================ INTERP_CH0 MOVF STEPS_0,F BTFSC STATUS,Z RETURN ; fermo MOVF TGT_0,W SUBWF SERVO_0,W ; W = SERVO_0 - TGT_0 BTFSC STATUS,Z GOTO IC0_DONE ; uguali: ferma BTFSS STATUS,C ; C=0 → SERVO < TGT GOTO IC0_UP ; SERVO > TGT: decrementa DECF SERVO_0,F GOTO IC0_STEP IC0_UP INCF SERVO_0,F IC0_STEP DECF STEPS_0,F RETURN IC0_DONE CLRF STEPS_0 RETURN INTERP_CH1 MOVF STEPS_1,F BTFSC STATUS,Z RETURN MOVF TGT_1,W SUBWF SERVO_1,W BTFSC STATUS,Z GOTO IC1_DONE BTFSS STATUS,C GOTO IC1_UP DECF SERVO_1,F DECF STEPS_1,F RETURN IC1_UP INCF SERVO_1,F DECF STEPS_1,F RETURN IC1_DONE CLRF STEPS_1 RETURN INTERP_CH2 MOVF STEPS_2,F BTFSC STATUS,Z RETURN MOVF TGT_2,W SUBWF SERVO_2,W BTFSC STATUS,Z GOTO IC2_DONE BTFSS STATUS,C GOTO IC2_UP DECF SERVO_2,F DECF STEPS_2,F RETURN IC2_UP INCF SERVO_2,F DECF STEPS_2,F RETURN IC2_DONE CLRF STEPS_2 RETURN INTERP_CH3 MOVF STEPS_3,F BTFSC STATUS,Z RETURN MOVF TGT_3,W SUBWF SERVO_3,W BTFSC STATUS,Z GOTO IC3_DONE BTFSS STATUS,C GOTO IC3_UP DECF SERVO_3,F DECF STEPS_3,F RETURN IC3_UP INCF SERVO_3,F DECF STEPS_3,F RETURN IC3_DONE CLRF STEPS_3 RETURN ORG 0x0100 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 ; Posizioni iniziali 90° MOVLW D'30' MOVWF SERVO_0 MOVWF SERVO_1 MOVWF SERVO_2 MOVWF SERVO_3 ; Target iniziali = posizione attuale (fermi) MOVLW D'30' MOVWF TGT_0 MOVWF TGT_1 MOVWF TGT_2 MOVWF TGT_3 CLRF STEPS_0 CLRF STEPS_1 CLRF STEPS_2 CLRF STEPS_3 CLRF SEQ_PTR CLRF SEQ_PAUSE MOVLW 1 MOVWF SEQ_ACTIVE ; avvia sequenza automatica CLRF FLAGS MAIN ; Attendi nuovo frame (segnalato dalla ISR - non inclusa qui ; per brevità, vedi Modulo 1) BTFSS FLAGS,0 GOTO MAIN BCF FLAGS,0 ; Interpolazione ogni frame CALL INTERP_CH0 CALL INTERP_CH1 CALL INTERP_CH2 CALL INTERP_CH3 ; Avanza sequenza CALL UPDATE_SEQUENCE GOTO MAIN END
Modulo 3 · USART completo e salvataggio EEPROM
; ============================================================ ; Lezione 41 — USART completo + salvataggio EEPROM ; PIC16F877A @ 4 MHz ; Protocollo bidirezionale: ; RX: 'S' CH VAL → muovi servo ; 'G' CH → leggi posizione (risponde con 1 byte) ; 'R' → reset tutti a 90° ; 'P' → play sequenza ; 'X' → stop sequenza ; 'W' → write to EEPROM (salva tutte le posizioni) ; TX: risponde con 'K' (ok) o 'E' (errore) ; EEPROM: 8 byte da addr 0x00 (un byte per servo) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Servo SERVO_0 SERVO_1 SERVO_2 SERVO_3 SERVO_4 SERVO_5 SERVO_6 SERVO_7 ; USART parser RX_BYTE CMD_BYTE ; comando corrente ARG_CH ; argomento canale ARG_VAL ; argomento valore PARSE_STATE ; 0=cmd, 1=ch, 2=val ; EEPROM EEP_ADDR EEP_DATA ; Stato SEQ_ACTIVE FLAGS TMP ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; USART_TX — trasmetti W via USART, attendi TXIF ; ============================================================ USART_TX BSF STATUS,RP0 UTXW BTFSS TXSTA,TRMT ; TX shift register vuoto? GOTO UTXW BCF STATUS,RP0 MOVWF TXREG RETURN ; ============================================================ ; USART_RX_READY — ritorna Z=0 se byte disponibile ; ============================================================ USART_RX_READY BSF STATUS,RP0 BTFSS PIR1,RCIF BCF STATUS,Z BTFSC PIR1,RCIF BSF STATUS,Z BCF STATUS,RP0 RETURN ; ============================================================ ; EEPROM_WRITE / READ (come Lez.38) ; ============================================================ EEP_WR BSF STATUS,RP0 MOVF EEP_ADDR,W MOVWF EEADR MOVF EEP_DATA,W MOVWF EEDATA BSF EECON1,WREN BCF INTCON,GIE MOVLW 0x55 MOVWF EECON2 MOVLW 0xAA MOVWF EECON2 BSF EECON1,WR BSF INTCON,GIE EWAIT BTFSC EECON1,WR GOTO EWAIT BCF EECON1,WREN BCF STATUS,RP0 RETURN EEP_RD BSF STATUS,RP0 MOVF EEP_ADDR,W MOVWF EEADR BSF EECON1,RD MOVF EEDATA,W BCF STATUS,RP0 MOVWF EEP_DATA RETURN ; ============================================================ ; SAVE_ALL — salva tutte le 8 posizioni in EEPROM ; ============================================================ SAVE_ALL MOVLW SERVO_0 MOVWF FSR CLRF EEP_ADDR SAV_LOOP MOVF INDF,W MOVWF EEP_DATA CALL EEP_WR INCF FSR,F INCF EEP_ADDR,F MOVF EEP_ADDR,W SUBLW D'8' BTFSS STATUS,Z GOTO SAV_LOOP RETURN ; ============================================================ ; LOAD_ALL — carica posizioni da EEPROM ; ============================================================ LOAD_ALL MOVLW SERVO_0 MOVWF FSR CLRF EEP_ADDR LOD_LOOP CALL EEP_RD MOVF EEP_DATA,W ; Valida range (20-40) SUBLW D'19' BTFSC STATUS,C ; < 20? GOTO LOD_DEFAULT MOVF EEP_DATA,W SUBLW D'40' BTFSS STATUS,C ; > 40? GOTO LOD_DEFAULT MOVF EEP_DATA,W GOTO LOD_STORE LOD_DEFAULT MOVLW D'30' ; default 90° LOD_STORE MOVWF INDF INCF FSR,F INCF EEP_ADDR,F MOVF EEP_ADDR,W SUBLW D'8' BTFSS STATUS,Z GOTO LOD_LOOP RETURN ; ============================================================ ; DO_COMMAND — esegui comando CMD_BYTE ; ============================================================ DO_COMMAND ; Dispatch su CMD_BYTE MOVF CMD_BYTE,W SUBLW 'R' BTFSC STATUS,Z GOTO CMD_RESET MOVF CMD_BYTE,W SUBLW 'P' BTFSC STATUS,Z GOTO CMD_PLAY MOVF CMD_BYTE,W SUBLW 'X' BTFSC STATUS,Z GOTO CMD_STOP MOVF CMD_BYTE,W SUBLW 'W' BTFSC STATUS,Z GOTO CMD_WRITE ; Comando sconosciuto MOVLW 'E' CALL USART_TX RETURN CMD_RESET ; Reset tutti i servo a 90° MOVLW SERVO_0 MOVWF FSR MOVLW D'8' MOVWF TMP MOVLW D'30' RST_LOOP MOVWF INDF INCF FSR,F DECFSZ TMP,F GOTO RST_LOOP MOVLW 'K' CALL USART_TX RETURN CMD_PLAY MOVLW 1 MOVWF SEQ_ACTIVE MOVLW 'K' CALL USART_TX RETURN CMD_STOP CLRF SEQ_ACTIVE MOVLW 'K' CALL USART_TX RETURN CMD_WRITE CALL SAVE_ALL MOVLW 'K' CALL USART_TX RETURN ; ============================================================ ; DO_SET — imposta servo ARG_CH a ARG_VAL ; ============================================================ DO_SET ; Valida canale MOVF ARG_CH,W SUBLW D'7' BTFSS STATUS,C GOTO SET_ERR ; Valida valore MOVF ARG_VAL,W SUBLW D'19' BTFSC STATUS,C GOTO SET_ERR MOVF ARG_VAL,W SUBLW D'40' BTFSS STATUS,C GOTO SET_ERR ; Scrivi SERVO[CH] BCF INTCON,GIE MOVLW SERVO_0 ADDWF ARG_CH,W MOVWF FSR MOVF ARG_VAL,W MOVWF INDF BSF INTCON,GIE MOVLW 'K' CALL USART_TX RETURN SET_ERR MOVLW 'E' CALL USART_TX RETURN ; ============================================================ ; DO_GET — risponde con posizione servo ARG_CH ; ============================================================ DO_GET MOVF ARG_CH,W SUBLW D'7' BTFSS STATUS,C GOTO GET_ERR MOVLW SERVO_0 ADDWF ARG_CH,W MOVWF FSR MOVF INDF,W CALL USART_TX ; invia posizione RETURN GET_ERR MOVLW 'E' CALL USART_TX RETURN ORG 0x0140 INIT BSF STATUS,RP0 CLRF TRISB MOVLW D'25' ; SPBRG per 9600 baud MOVWF SPBRG MOVLW b'00100100' ; TXEN=1, BRGH=1 MOVWF TXSTA BCF STATUS,RP0 MOVLW b'10010000' ; SPEN=1, CREN=1 MOVWF RCSTA CLRF PARSE_STATE CLRF SEQ_ACTIVE CLRF FLAGS CALL LOAD_ALL ; carica posizioni da EEPROM ; Messaggio di avvio MOVLW 'S' CALL USART_TX MOVLW 'V' CALL USART_TX MOVLW 'C' CALL USART_TX MOVLW '8' CALL USART_TX MOVLW 0x0D CALL USART_TX ; CR ; ============================================================ ; MAIN — polling USART e parser ; ============================================================ MAIN BSF STATUS,RP0 BTFSS PIR1,RCIF BCF STATUS,RP0 BTFSS PIR1,RCIF GOTO MAIN BCF STATUS,RP0 MOVF RCREG,W MOVWF RX_BYTE ; State machine parser MOVF PARSE_STATE,F BTFSS STATUS,Z GOTO PARSE_NOT0 ; Stato 0: attendi comando MOVWF CMD_BYTE MOVF RX_BYTE,W SUBLW 'S' BTFSC STATUS,Z GOTO EXPECT_CH ; 'S' vuole canale MOVF RX_BYTE,W SUBLW 'G' BTFSC STATUS,Z GOTO EXPECT_CH ; 'G' vuole canale ; Comando senza argomenti: esegui direttamente CALL DO_COMMAND GOTO MAIN EXPECT_CH MOVLW 1 MOVWF PARSE_STATE GOTO MAIN PARSE_NOT0 MOVF PARSE_STATE,W SUBLW 1 BTFSC STATUS,Z GOTO PARSE_STATE1 ; Stato 2: aspetta valore MOVF RX_BYTE,W MOVWF ARG_VAL CLRF PARSE_STATE ; Esegui 'S' o 'G' MOVF CMD_BYTE,W SUBLW 'S' BTFSC STATUS,Z CALL DO_SET MOVF CMD_BYTE,W SUBLW 'G' BTFSC STATUS,Z CALL DO_GET GOTO MAIN PARSE_STATE1 MOVF RX_BYTE,W MOVWF ARG_CH MOVF CMD_BYTE,W SUBLW 'G' BTFSC STATUS,Z GOTO EXEC_GET_NOW ; 'G' non ha valore: esegui subito MOVLW 2 MOVWF PARSE_STATE GOTO MAIN EXEC_GET_NOW CLRF PARSE_STATE CALL DO_GET GOTO MAIN END
🧪 Prova tu
- Carica Modulo 1. Imposta SERVO_0=30 (90°). Verifica la ISR: con TMR0=206 e T0IE=1, conta manualmente i cicli fino al primo interrupt. PULSE_TICK deve partire da 30 e decrementare ogni overflow. Dopo 30 overflow (~1.5ms) PORTB bit0 deve abbassarsi e ACTIVE_CH diventare 1.
- Testa l’interpolazione (Modulo 2): imposta SERVO_0=20, TGT_0=40, STEPS_0=20. Chiama INTERP_CH0 venti volte (una per frame simulato). Dopo ogni chiamata traccia SERVO_0: deve aumentare di 1 ogni volta fino a raggiungere 40. Poi chiama un’altra volta: SERVO_0 deve restare 40 (IC0_DONE).
- Testa USART (Modulo 3): simula la ricezione di ‘S’, 0x02, 0x1E (servo 2, 90°). Dopo i tre byte PARSE_STATE deve tornare 0 e SERVO_2 deve valere 30 (0x1E = 30 decimale). Poi invia ‘G’, 0x02: deve rispondere con 30. Invia ‘R’: tutti i servo tornano a 30, risponde ‘K’.
- Sfida: aggiungi il comando ‘I’ (info): il PIC risponde con tutti e 8 i valori SERVO_0..7 in sequenza (8 byte). Modifica DO_COMMAND per gestire ‘I’ con un loop FSR che trasmette SERVO_0..7 via USART_TX. Quante istruzioni aggiuntive servono?
❓ Quiz rapido
1. Perché la ISR del PWM disabilita gli interrupt durante la scrittura di SERVO[CH] nel main (BCF INTCON,GIE / BSF INTCON,GIE), e cosa succederebbe senza questa protezione?
Mostra risposta
La sequenza di scrittura su SERVO[CH] nel main usa FSR: prima si calcola l’indirizzo (MOVLW SERVO_0 + ADDWF ARG_CH,W + MOVWF FSR) poi si scrive (MOVF ARG_VAL,W + MOVWF INDF). Se la ISR del Timer0 interviene tra MOVWF FSR e MOVWF INDF, potrebbe modificare FSR per accedere a SERVO_N nel ciclo PWM, cambiando il puntatore nel mezzo della scrittura del main. Risultato: il valore verrebbe scritto in un indirizzo sbagliato (un altro servo o RAM casuale). Disabilitando GIE per la coppia FSR+INDF si crea una sezione critica atomica — la ISR può aspettare i pochi cicli necessari senza conseguenze sul timing PWM.
2. Il SPBRG per 9600 baud a 4MHz con BRGH=1 è calcolato come 25. Quale baud rate si ottiene esattamente, e qual è l’errore percentuale?
Mostra risposta
Formula con BRGH=1: baud = FOSC / (16 × (SPBRG+1)) = 4000000 / (16 × 26) = 4000000 / 416 = 9615.38 baud. L’errore rispetto a 9600 baud è (9615.38 − 9600) / 9600 × 100 = +0.16%. L’USART tollera errori fino a ±3% — 0.16% è ampiamente dentro le specifiche. Per riferimento, con BRGH=0: baud = FOSC / (64 × (SPBRG+1)); per 9600: SPBRG = 4000000/(64×9600) − 1 = 5.51 → arrotondato a 6, errore = 4000000/(64×7) − 9600 = 8929 − 9600 = −7.4% — troppo alto, per questo si preferisce BRGH=1.
3. Il Modulo 2 usa una tabella RETLW come lista di mosse in Flash. Qual è il limite pratico alla lunghezza della sequenza, e come si potrebbe estendere oltre questo limite?
Mostra risposta
La tabella usa ADDWF PCL,F per il salto indicizzato — questo funziona solo entro 256 word dalla base (una pagina). Con 3 byte per mossa (canale, target, durata), una pagina ospita al massimo 85 mosse (255/3). Per sequenze più lunghe: 1) usare PCLATH per accedere a pagine oltre la prima, impostando il byte alto del PC prima del salto; 2) memorizzare la sequenza in EEPROM interna (256 byte = 85 mosse) e leggerla con EEP_RD — più lenta ma illimitata al costo della Flash; 3) usare una struttura dati in RAM con FSR al posto del PCL jump — perdendo la compattezza Flash ma guadagnando flessibilità. Il PIC16F877A ha 1024 word di Flash suddivise in 4 pagine da 256 word — con PCLATH si possono raggiungere tutte e 4.
Ultima lezione del corso →
⭐ Lezione 42 — Progetto 5: Sistema integrato completo
Retrocomputer didattico con PIC16F877A: mini-interprete di comandi seriali, gestione task cooperativa (round-robin), watchdog software, auto-test RAM al boot. Il progetto finale che unisce tutto ciò che hai imparato.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 41