Sunteți pe pagina 1din 70

Proyecto Integrador

Implementación de un panel de control con MATLAB en un bus


CAN, controlando un motor BLDC con el dsPICDEM MC1 y el
dsPIC30F y luces LED con dsPIC33F.

Profesor: Enric Vidal Idiarte

Carles Agorreta
Alfonso Arranz
Ramon Estalella
Jesús Fuente Porta
Proyecto Integrador

Contenido
1 Introducción ........................................................................................................... 1
2 Proyecto ................................................................................................................. 1
2.1 Descripción ............................................................................................................. 1
2.2 Estado del proyecto ................................................................................................ 2
2.3 Componentes ......................................................................................................... 4
2.3.1 Interfície CAN a nivell de Hardware ............................................................................................... 4
2.3.2 Placa dsPICDEM MC1...................................................................................................................... 5
2.3.3 Driver motor de Baja Potencia ....................................................................................................... 5
2.3.4 Motor Hurt ..................................................................................................................................... 5

3 MATLAB ................................................................................................................. 6
3.1 Vehicle Network Toolbox ........................................................................................ 6
3.2 Arxius DBC .............................................................................................................. 6
3.3 Interfície gràfica ...................................................................................................... 8
3.4 Interfície gràfica ...................................................................................................... 9
3.4.1 Consideracions a l’hora de fer la interfície ................................................................................... 12
3.4.2 Funcionament del codi de l’aplicació ........................................................................................... 13

4 Comunicación CAN ............................................................................................. 15


4.1 Introducció al bus CAN, i avantatges ...................................................................... 15
4.2 Avantatges del bus CAN en comparació amb d’altres (SPI, Ethernet...): .................. 15
4.3 Capa física del bus CAN (hardware) ....................................................................... 17
4.4 Configuración del bus CAN .................................................................................... 18
4.5 Capa superior: protocol de comunicació Ad Hoc vs OBDII ....................................... 18
4.6 Codi Font explicat ................................................................................................. 22
5 Librería placa dsPICDEM MC1 ............................................................................ 29
5.1 Estructuras Básicas ............................................................................................... 29
5.1.1 Información del motor ................................................................................................................. 29
5.2 Funciones Genéricas ............................................................................................. 29
5.2.1 Configuración de puertos ............................................................................................................. 29
5.2.2 Configuración de pulsadores de usuario ...................................................................................... 30
5.2.3 Configuración de potenciómetro de usuario................................................................................ 30
5.2.4 Generación de tiempos de espera................................................................................................ 30
5.3 Funciones asociadas al Display LCD........................................................................ 31
5.3.1 Inicialización driver LCD ................................................................................................................ 31
5.3.2 Consulta del estado del LCD ......................................................................................................... 31
5.3.3 Envío de un comando de instrucción al LCD ................................................................................ 32
Proyecto Integrador

5.3.4 Envío de un comando de datos al LCD ........................................................................................ 32


5.3.5 Borrado del LCD ........................................................................................................................... 33
5.3.6 Posición del cursor en el LCD ....................................................................................................... 33
5.3.7 Escritura de un texto en el LCD.................................................................................................... 33
5.3.8 Escritura de un número en el LCD ............................................................................................... 33
5.4 Funciones asociadas al control del Motor .............................................................. 34
5.4.1 Inicialización del sensor de posición del eje del motor (Hall) ....................................................... 34
5.4.2 Gestión de la posición de transistores del motor ......................................................................... 34
5.4.3 Inicialización del control PWM del motor ................................................................................... 35
5.4.4 Arranque del control PWM del motor ......................................................................................... 35
5.4.5 Paro del control PWM del motor ................................................................................................ 35
5.4.6 Rearme fallo driver del motor ...................................................................................................... 35
5.4.7 Escritura del duty-Cycle ................................................................................................................ 36

5.5 Funciones asociadas al encoder del Motor ............................................................. 36


5.5.1 Inicialización del encoder del motor ............................................................................................ 36
5.5.2 Actualización de la posición absoluta del motor .......................................................................... 37
5.5.3 Puesta a cero de la posición absoluta del motor .......................................................................... 37
5.6 Funciones auxiliares .............................................................................................. 37
5.6.1 Inicialización temporizador intermitencia .................................................................................... 37
6 Librería Lazo PID ................................................................................................. 38
6.1 Estructuras Básicas ............................................................................................... 38
6.1.1 Datos PID ...................................................................................................................................... 38

6.2 Funciones asociadas al lazo PID ............................................................................ 39


6.2.1 Gestión algoritmo lazo de control PID .......................................................................................... 39
6.2.2 Actualización de la consigna del Lazo (SP) .................................................................................... 39
6.2.3 Inicialización del Lazo de velocidad .............................................................................................. 40
6.2.4 Inicialización del Lazo de posición ................................................................................................ 40
7 Librería CAN ......................................................................................................... 41
7.1 Funciones asociadas a la configuración del puerto ................................................. 41
7.1.1 Inicialización puerto CAN1 ............................................................................................................ 41
7.2 Funciones asociadas a la lectura del puerto ........................................................... 41
7.2.1 Lectura datos tipo Float ................................................................................................................ 41
7.2.2 Lectura datos tipo Long ................................................................................................................ 41
7.2.3 Lectura datos tipo Int ................................................................................................................... 41
7.2.4 Lectura datos tipo Char ................................................................................................................ 41
7.3 Funciones asociadas a la escritura del puerto......................................................... 42
7.3.1 Escritura datos tipo Float .............................................................................................................. 42
Proyecto Integrador

7.3.2 Escritura datos tipo Long .............................................................................................................. 42


7.3.3 Escritura datos tipo Int ................................................................................................................. 42
7.3.4 Escritura datos tipo Char .............................................................................................................. 42
7.3.5 Inicialización temporizador envío datos ....................................................................................... 42
8 Rutina principal.................................................................................................... 43
9 Anexos ................................................................................................................. 44
9.1 Librería placa dsPICDEM MC1 ................................................................................ 44
9.1.1 Declaraciones dsPICDEM_MC1.h ................................................................................................. 44
9.1.2 Código dsPICDEM_MC1.c ............................................................................................................. 45
9.2 Librería lazo PID .................................................................................................... 56
9.2.1 Declaraciones PID.h ...................................................................................................................... 56
9.2.2 Código PID.c .................................................................................................................................. 56
9.3 Librería CAN ......................................................................................................... 59
9.3.1 Declaraciones CAN.h .................................................................................................................... 59
9.3.2 Código CAN.c ................................................................................................................................ 59

9.4 Rutina Principal .................................................................................................... 62


10 Proposta per a futurs treballs ............................................................................. 65
11 Bibliografia ........................................................................................................... 65
Proyecto Integrador

1 Introducción

En esta última entrega vamos a intentar omitir el máximo posible el proceso seguido,
partes modificadas, etc. que ya se ha explicado en los otros informes. Nos vamos a
centrar en los resultados obtenidos, como ha quedado cada una de las partes y como
queda el conjunto.

2 Proyecto

2.1 Descripción

Nuestro proyecto se basa en la implementación de un bus CAN con 3 nodos conectados


a éste (un PC y dos dsPIC), implementando un panel de control en el PC, que a su vez se
conecta al bus CAN con Kvaser (periférico que hace de glue lógic) que es soportado por
la vehicle Network Toolbox de MATLAB. Los otros dos nodos son dos placas de
desarrollo de Microchip con soporte CAN incorporado.

Estas dos placas están montadas con un dsPIC30F y un dsPIC33F, y tienen soporte CAN
incorporado, es decir, no necesitan ni controlador ni transceptor externo. La placa de
potencia dsPICDEM MC1 sirven para controlar el motor BLDC, mientras que el
microcontrolador que gestiona el control y la comunicación CAN de este nodo es el
dsPIC30F. Como el bus CAN es un bus multi-master que tiene su fuerte respecto a otros
buses en la facilidad para añadir nodos al bus, hemos conectado un dsPIC33F que
controla unos LEDs.

En el apartado 3 se explica el desarrollo realizado con Matlab: funcionamiento de la


toolbox VNT, diseño del código con VNT, diseño y funcionamiento de la aplicación
gráfica con AppDesigner.

Se podría decir que cada nodo del bus equivaldría a una ECU de un coche. Estos pueden
comunicarse entre ellos con facilidad. Las ventajas de uso de un bus CAN y sus
características se mencionan en el apartado 4.

En el apartado 5 explicamos el desarrollo y diseño del control del motor BLDC: el


funcionamiento de la placa de potencia, el código para hacer funcionar esta placa con el
motor, el control PID implementado y las librerías desarrolladas.

En el apartado 7 comentamos la librería CAN desarrollada y el programa desarrollado.

Página 1 de 66
Proyecto Integrador

2.2 Estado del proyecto

• Hemos conseguido enviar y recibir datos en el bus can con el dsPIC30F, que es el
mismo microcontrolador que usamos para controlar el motor BLDC. Ya hemos
conseguido también hacer funcionar el bus CAN con Matlab con el dsPIC30F.

• La aplicación de Matlab está casi terminada.

• El envío y recepción de los parámetros del motor mediante CAN todavía falta
hacerlo funcionar correctamente.

• Se descubrió cuál era el problema de que el motor BLDC no funcionase: los


mosfets internos del módulo de potencia estaban rotos. Los técnicos tuvieron
que pedir 6 mosfets nuevos y soldarlos. Los mosfets tardaron varias semanas en
llegar. Hemos comprobado con el código de ejemplo que el motor BLDC funciona
correctamente. Hemos tenido problemas al conectar el motor con las fases
correctamente. Al principio, el motor tan solo emitía un “beep”. Al consultarlo
con el profesor, cambiamos las fases y el motor ya giraba bien. El problema de
ruidos del motor era debido a la frecuencia de conmutación (4 kHz), el cuál
posteriormente modificamos a 16 kHz y dicho ruido ya no era perceptible para el
oído.

Conexiones de las Fases del motor Brushless DC con las salidas del módulo de baja
potencia de Microchip:

Fase C
Fase A
Fase B

Página 2 de 66
Proyecto Integrador

Verde  Tierra

R  Fase C

Y  Fase B

B  Fase A

En el código de ejemplo, podemos controlar el duty cycle (y por tanto las rpm) del motor
mediante un potenciómetro y una lectura del ADC.

Hay que alimentar el motor con la fuente de alimentación, a 24 V y limitar el amperaje a


2 A.

Con el programa de ejemplo de microchip, hemos comprobado que la frecuencia del


PWM era de 4 kHz, modificando el registro PTPER podemos cambiar el periodo, y por
tanto la frecuencia del PWM (inversa del periodo). PTPER está predefinido a 460, y el
comentario del código dice que está a 16 kHz, sin embargo, hemos comprobado con el
osciloscopio y la frecuencia es de 4 kHz, así que hemos divido por 4 el valor de dicho
registro para obtener la frecuencia deseada. Para 4 kHz el motor chirriaba cómo hemos
comentado anteriormente, para 16 kHz reales (los medidos con el osciloscopio) no
emitía tal “chirrido”.

Ahora nos falta hacer funcionar el motor con el código PID desarrollado (idealmente
determinar las constantes con el método de Ziegler-Nichols) y enviar y recibir los
parámetros a través del CAN, para lo cual ya hemos implementado un código closed-
source. En un principio, exploramos la posibilidad de implementar en CAN OBD2, que es
un protocolo de alta capa según el modelo OSI, en el que básicamente enviamos los
mensajes a través de CAN con unas normas públicas, que son las mismas que usan los
automóviles en su puerto OBD2, que es un puerto de diagnóstico que, por ejemplo, a
partir de este año se va a usar para las pruebas de la ITV o que ya se usa en los talleres
de automóviles para detectar averías. Incluso es posible a través de este puerto no solo
leer la comunicación CAN, sino también transmitir al bus CAN, ya que las
comunicaciones dentro de este BUS en los automóviles no están cifradas. De este modo,
algunas empresas externas (3rd parties) han conseguido implementar funciones de
autoconducción en automóviles.

Página 3 de 66
Proyecto Integrador

2.3 Componentes

2.3.1 Interfície CAN a nivell de Hardware

Utilitzem el Kvaser per a fer d’interfície entre l’ordinador i la resta de dispositius els
quals volem fer comunicacions amb protocol CAN amb ells. Això és degut a que
l’ordinador no permet la connexió

Nosaltres al laboratori disposem d’un adaptador USB-CAN del fabricant Kvaser.


Concretament del model Kvaser Leaf Light v2amb el connector (al costat CAN) 9-pin D-
SUB. Podem veure el connector amb la numeració dels pins a la Figura 1 i la funció de
cadascun en la

Taula 1.

Figura 1.Connector D-SUB del Kvaser amb la numeració dels pins

D-SUB pin number Function


1 NC
2 CAN_L
3 GND
4 NC
5 Shield
6 NC
7 CAN_H
8 NC
9 NC

Taula 1: Configuració dels pins del connector D_SUB del Kvaser

Haurem de comprovar que aquesta configuració de pins es correspon amb la de la placa


d’expansió “CAN/LIN/J2602 PICtail™”. Aquesta la usem per a dotar a les plaques
explorer 16 d’un bus CAN. En cas de no coincidir s’hauria de realitzar un adaptador,
segurament aprofitant la pròpia PCB que usem com a “bus”.

Página 4 de 66
Proyecto Integrador

Figura 2: Configuració del connector D-SUB de la PICtail

Podem comprovar amb la Figura 2 que efectivament haurien de ser compatibles sense
cap adaptador

2.3.2 Placa dsPICDEM MC1

2.3.3 Driver motor de Baja Potencia

2.3.4 Motor Hurt

Página 5 de 66
Proyecto Integrador

3 MATLAB

3.1 Vehicle Network Toolbox

La Vehicle Network Toolbox (VNT) és una eina de MATLAB que facilita treballar amb
busos CAN. De les “key features” que trobem a la pàgina web de MATLAB:

• MATLAB functions for transmitting and receiving CAN and J1939 messages

• Simulink blocks for connecting a model to a CAN or J1939 bus

• XCP support for communicating with ECUs

• Vector CAN database (.dbc) file and A2L description file support

• Signal packing and unpacking for simplified encoding and decoding of CAN
messages and J1939 parameter groups

• Vehicle CAN Bus Monitor app to configure devices and visualize live CAN network
traffic

• Support for Vector, Kvaser, PEAK-System, and National Instruments® CAN


interface devices

Bàsicament podrem usar funcions ja fetes per MATLAB que ens permetran tractar la
creació de missatges CAN, creació de canals (virtuals o que comuniquin a un hardware
extern), enviament i recepció de missatges CAN, monitorització de canals CAN.

3.2 Arxius DBC

Són arxius de bases de dades que faciliten el crear, enviar i rebre missatges.

Hem trobat la informació de com editar manualment els fitxers .DBC per tal de poder
usar missatges personalitzats: http://socialledge.com/sjsu/index.php/DBC_Format.

Un petit resum d’aquest format es pot observar en la Figura 3.

Figura 3. Estructura dels missatges dins del fitxer .DBC


Página 6 de 66
Proyecto Integrador

Explicarem un missatge creat per nosaltres per tal de deixar-ho més clar:

Figura 4. Exemple missatge CAN en arxiu .DBC

• “550” es refereix a la ID del missatge.

• “Blink” és el nom del missatge, es pot usar dins de MATLAB per a crear un
missatge d’aquest tipus i usar-lo.

• “1”, abans de “Panell” és la mida del missatge en bytes.

• “Panell” es refereix a qui envia el missatge.

• “Emergència, “Dreta” i “Esquerra” es refereixen a senyals dins del missatge, en


aquest cas les usem per a indicar el funcionament dels intermitents.

• “0|1”: el “0” indica el bit d’inici i “1” indica la mida de la senyal en bits.

• “(1,0)” s’usaria per a números negatius i decimals.

• “[0|1]” defineixen respectivament el valor mínim i màxim del senyal.

• El que està entre cometes en el codi original és les unitats, en aquest cas com
que la senyal només la usem per a encendre o apagar els intermitents
d’emergència, ho indiquem amb “0/1”.

• “Intermitents” és el receptor.

Les dades del missatge quedarien tal com es veu a continuació en la Figura 5.

Figura 5

Els intermitents d’esquerra i dreta tenen 3 estats, 0: aturat, 1:engecat o bé 2:engegat


durant un cert temps preestablert. La opció 2 podria quedar sense usar depenent de
com s’implementi el sistema.

Página 7 de 66
Proyecto Integrador

3.3 Interfície gràfica

Per a la interfície gràfica també usem eines de MATLAB. Teníem dos opcions, GUIDE
(Graphical Interface Development Enviroment) i App Designer. Aquest últim és una eina
més nova i té més opcions a l’hora de posar elements gràfics, i semblava més fàcil de
treballar. Així que ens vam decantar per a usar l’App Designer.

Ja havent acabat el desenvolupament de l’aplicació amb l’app Designer hem sembla una
eina molt fàcil d’usar si es tenen coneixements de programació en MATLAB i es busca en
l’extensa documentació el que un no sap fer. Si s’ha de fer una tasca repetidament que
involucri els mateixos o similars càlculs amb diferents paràmetres d’entrada una molt
bona opció és programar-se una eina amb l’App Designer.

Sembla bastant ineficaç i sense sentit adjuntar el codi generat amb l’AppDessigner o
comentar-lo detalladament, ja que la manera de visualitzar-lo a través del MATLAB és
molt més còmoda i amena. El codi s’ha intentat que estigues el millor comentat possible
així que fent a la memòria final una explicació general del funcionament i amb els
comentaris no hauria d’haver cap problema si en el futur algú vol entendre/usar el codi
de MATLAB creat en aquest treball.

En principi la interfície gràfica ens permetrà interactuar amb tots els elements instal·lats
en el bus CAN, inclòs el motor del qual s’encarrega l’altre grup (el que representaria la
tracció del vehicle).

Per al motor hem parlat amb l’altre grup i ens han dit que poden implementar és el %
del cicle de treball que li apliquen al motor, per tant estaria bé implementar un element
gràfic que representés l’accelerador d’un vehicle.

Si implementessin un control, podríem tenir una opció de velocitat creuer que ens
permetés mantenir una velocitat fixa sense haver l’usuari de regular la potencia
constantment a través del panell.

També ens poden enviar per comunicacions CAN la velocitat de gir del motor. Per tant
seria fàcil posar un indicador d’aquesta velocitat, o bé extrapolar la velocitat lineal
suposant un lliscament roda-calçada nul i mostrar aquesta última.

Si ells poguessin mesurar paràmetres com la potència que està absorbint en cada
moment el motor, estat de la bateria.... podríem complimentar amb altres indicadors
que mostressin la potència que revés el motor en el temps i l’estat de la bateria.

A més amb la informació recopilada hem pensat que seria interessant intentar fer
gràfiques de velocitat-temps, distància recorreguda, energia usada... . També estaria bé
implementar una funció per a guardar aquestes dades i poder analitzar-les més tard. En
MATLAB això no hauria de ser gaire problema ja que es podrien guardar quan l’usuari de

Página 8 de 66
Proyecto Integrador

la nostra aplicació volgués en un fitxer de dades de MATLAB (.mat) i tenir una aplicació o
script que permetés analitzar les dades de la manera desitjada.

En quant als altres elements que implementarà el nostre grup, hem pensat usar diversos
LEDs de les plaques que tenim per tal de simular llums, intermitents etc.. Per tant hem
afegit al panell de control els elements necessaris per a controlar i indicar els estats
d’intermitents i llums externs del vehicle.

El motor, teòricament amb les llibreries creades en C pot controlar-se (des de la placa de
Microchip) en mode llaç tancat de velocitat o posició de manera senzilla. Per a donar un
us realista des de el punt de vista de la aplicació en MATLAB pensarem que es una
finestra o un seient del qual controlem la posició. Aquesta posició de consigna es
representarà entre 0 i 100, que serà el valor que la placa de Microchip rebrà per CAN. La
posició real també s’enviarà per CAN de la placa de Microchip a la aplicació de MATLAB
per tal de mostrar aquesta informació a l’usuari.

3.4 Interfície gràfica

En aquest apartat donarem la informació de la interfície gràfica des de al punt de vista


de l’usuari. És a dir explicar la funcionalitat de l’aplicació però no el com funciona. El
com funciona s’explicarà més endavant.

Tenim dues pestanyes separades una per al panell de control i l’altra per a la
visualització de les dades recopilades. A la Figura 6 podem veure la pestanya “panell” i a
la Figura 7 podem veure la pestanya “Dades”.

Página 9 de 66
Proyecto Integrador

Figura 6. Interfície gràfica "Panell"

“Panell” és la finestra de control i visualització de l’estat dels diversos elements


controlables per l’usuari.

La part de “Finestra” amb el selector i indicador de posició és la que es comunicarà amb


el motor BLDC per a simular una aplicació on s’ha de controlar el motor per llaç tancat
de posició. Amb el selector es tira la posició entre 0 i 100, i en temps real l’indicador de
posició ens mostra la posició real que el mòdul de control del motor ens envia.

A la part inferior dreta tenim el control dels llums i intermitents. Hi ha tant els selectors
de en quin estat es vol tenir els llums i intermitents com els indicadors que indiquen si
està un llum encès o no.

A l’esquerra tenim el que seria l’accelerador que mentre modifiquem la seva posició va
actualitzant el mòdul de control de potència del motor de tracció per tal de que aquest
proporcioni més o menys energia a la tracció. També hi ha l’opció programada per a
enviar una consigna de velocitat i deixar que el mòdul de tracció intenti seguir-la. El
comportament d’aquesta segona part consisteix en que primer s’ha de seleccionar
mode creuer i després seleccionar la velocitat desitjada, aquesta velocitat pot canviar-se
en qualsevol moment. Si es mou l’accelerador es surt del mode de velocitat creuer i
s’entra una altra vegada en mode de control de la tracció a través de l’accelerador.

Página 10 de 66
Proyecto Integrador

Finalment a dalt a l’esquerra tenim un parell d’indicadors d’agulla que mostren la


velocitat actual del vehicle en km/h i la velocitat en rpms del motor, que en el nostre cas
és de la roda.

Figura 7. Interfície gràfica "Dades"

En la pestanya de dades a partir de la velocitat de rotació de la roda que ens haurien


d’enviar per CAN, deduïm de manera molt senzilla la velocitat lineal del vehicle,
suposant que el lliscament entre roda i calçada és zero. També integrem aquesta
velocitat per a obtenir la distància recorreguda.

Els botons dins de la pestanya de “Dades” són “Start/Stop”, “Reset” i”Save”.

El primer es comporta com un interruptor i activa(“Start”) o desactiva(“Stop”)


l’actualització de les gràfiques, si s’activa l’actualització de les gràfiques aquestes
s’actualitzaran amb les dades recopilades que s’han rebut per can i els valors extrets.
Està programat per a que l’actualització es faci cada 0,5 s. Això no vol dir que els punts
de dades estiguin cada 0,5 s o que l’interval d’integració ho estigui, aquests valors són
els mateixos i venen donats per el moment en el que es reben les dades per CAN.

“Rest” el que fa és tornar a inicialitzar tots els vectors on guardem les dades i elimina el
que hi havia representat a les gràfiques.

“Save” crea un fitxer de dades de MATLAB i guarda en ell totes les dades recopilades fins
el moment. El nom del fitxer és la data i hora en la que s’han guardat les dades.

Página 11 de 66
Proyecto Integrador

3.4.1 Consideracions a l’hora de fer la interfície

A l’hora de fer la interfície s’ha intentat posar èmfasi en fer un codi que robust, de
manera que independentment del que faci l’usuari l’aplicació no deixi de funcionar.
També s’ha intentat comentar el més bé possible i de forma entenedora.

Un punt que hem va ser una mica problemàtic és que tal com s’havia plantejat la
recepció de missatges de CAN amb el MATLAB, cada vegada que es rep un missatge
hauria de cridar-se una funció en concret. En principi aquesta funció només estava
pensada per a processar la informació d’un sol missatge, però hem vaig fixar que a
vegades quan s’entrava a la funció de recepció ja hi havia més d’un missatge. Llavors es
va adaptar el codi per a funcionar diferent si havien arribat més d’un missatge i intentar
mitigar els problemes que això podia suposar, com per exemple perdre informació de la
velocitat en diversos enviaments, per al velocímetre només suposaria un retard puntual
però per a mesurar la posició suposaria un error que s’aniria acumulant. En principi això
s’ha arreglat del tot i no hauria de ser un problema ni empitjorar el funcionament del
programa, de totes maneres quan succeeix s’avisa amb un missatge per el “command
window” de MATLAB.

Página 12 de 66
Proyecto Integrador

3.4.2 Funcionament del codi de l’aplicació

La recepció dels missatges de CAN es fa seguint l’esquema de la Figura 8.

Figura 8. Esquema del codi encarregat de la recepció dels missatges de CAN

Evidentment el bus CAN i el Kvaser són la part física. El filtre ja està dins de l’aplicació en
MATLAB.

Quan es rep un missatge salta una interrupció i s’executa una funció de recepció.
Aquesta en funció del missatge rebut executa un codi o un altre i tracta cada missatge
de forma diferent. Principalment té dues funcions, actualitzar els indicadors amb les
noves dades rebudes i guardar les dades que sabem que l’aplicació necessitarà
internament més endavant, com per exemple les de la velocitat que es guarden per a
poder mostrar les gràfiques i si es vol l’usuari pot triar guardar-les en un fitxer extern
per a posterior anàlisi.

El Reset eliminarà les dades guardades fins al moment, deixarà les variables
inicialitzades tal com estaven al principi de l’execució de l’aplicació per tal que la resta
del codi no tingui cap problema i borarà les gràfiques.

El botó de Start/Stop simplement inicia o para el timer que crida a la funció d’actualitzar
les gràfiques.

Save fa un guardat de les dades actuals un un fitxer de dades de MATLAB (.mat).

L’enviament de missatges és relativament senzill. Quan s’actua sobre un element que


enviarà un missatges es comprova si el missatge ja s’havia creat a partir de la base de
dades CAN que hem carregat al principi. Si no hi ah el missatge corresponent es crea i
s’actualitzen les dades a enviar, si ja hi havia el missatge creat només s’actualitzen les
dades. A continuació s’envia el missatge pel bus CAN en el canal seleccionat. Nosaltres
només tenim un canal que correspon al Kvaser.

Página 13 de 66
Proyecto Integrador

Els efectes de la interfície gràfica com els indicadors de que tenim els llum encesos són
molt senzills i es fan en el mateix “callback”. Els indicadors dels intermitents es fan tant
en el “callback” com en una funció cridada per un timer que ens permet indicar que
s’encenen i apaguen periòdicament. De totes maneres per a no enviar més missatges
del necessari pel bus CAN, qui s’encarrega de fer la temporització dels intermitents reals
és el mòdul que els controla (en el nostre cas una placa Microchip).

Figura 9. Codi del selector de la posició del motor auxiliar

Figura 10. Captura del "callback" de l'interruptor de llums llargues

En la Figura 9 podem veure un codi que simplement fa l’envio d’un missatge can en funció de
com s’ha tocat l’element gràfic a la interfície. En canvi a laFigura 10 es veu un codi que també
actualitza un indicador.

Página 14 de 66
Proyecto Integrador

4 Comunicación CAN

4.1 Introducció al bus CAN, i avantatges


La tecnologia CAN està estandaritzada i definida per la ISO en la especificació ISO 11898.

Aquesta especificació va ser desenvolupada pel fabricant de components d’automoció i


electrónica BOSCH, on especifica tres capes: capa d’objecte, capa de transferència i capa física.

4.2 Avantatges del bus CAN en comparació amb d’altres (SPI, Ethernet...):
Per una banda, els protocols de comunicació serial, UART, SPI i I2C estudiats son busos amb
limitacions de longitud del cable d’uns pocs metres (al voltant dels 5 metres) mentre que el
protocol Ethernet en xarxa local té una limitació de 30 metres. El bus CAN permet comunicació a
1 Mbit/s a una distància de 40 metres, fins a 50 kbit/s a una distància de 1000 metres.

Diagrama de connexió física d’un bus CAN.

Página 15 de 66
Proyecto Integrador

Una altra avantatge del bus CAN és l’estalvi de cablejat i facilitat de cablejat en comparació amb
altres busos de dades.

Les plaques de desenvolupament de Microchip utilitzades incorporen un controlador CAN i un


transceptor CAN, pel que no cal afegir perifèrics externs.

Exemple de funcionament de les dues línies del bus CAN, CAN_HIGH i CAN_LOW. Com és típic en
els busos de transmissió de dades, aquests dos cables van trenats per protegir-se del soroll, i
normalment van amb un cable apantallat, que és el que limita la velocitat màxima del bus CAN.

En estat dominant (0 lógic) es dóna una diferència de tensió de 1.5 V a 3 V entre les dues línies.
En estat recessiu (1 lògic) els dos cables es troben en voltatge en mode comú. Aquest bus, com
molts d’altres, funciona amb tensions diferencials per protegirse de tensions en mode comú a
massa que poden generar grans interferències.

Aquest gràfic mostra la trama sencera enviada per CAN. “Data” és el que nosaltres anomenem
“missatge”, però hem de tenir en compte que la trama total per missatge no és de 1 a 8 bytes,
sinó de 47 a 55 bytes. Si per exemple, a ull, volem enviar 10 missatges cada 10 ms on cada trama
és de 55 bytes, necessitaríem enviar 55 bytes / ms, osigui que necessitaríem com a mínim una
velotat d’enviament de dades en el bus CAN de 55 kbytes/s, que equivaldria a 440 kbits/s.

Página 16 de 66
Proyecto Integrador

Això sense tenir en compte l’enviament de dades en una xarxa més complexa, on els càlculs
serien més complexos.

En comparació amb l’estàndard de xarxes Ethernet, la xarxa implementada en el bus CAN té


l’avantatge de la robustesa, ja que resol el problema de col·lisió (quan dos nodes envien
un missatge alhora) amb un protocol basat en missatges amb identificadors jeràrquics
(estructura lògica AND, és a dir, els 0 són dominants i els 1 són recessius, amb
identificadors de 11 bits en CAN estandard o 29 bits amb CAN extended).

4.3 Capa física del bus CAN (hardware)


La capa física del bus CAN no està especificada a la ISO 11898, per permetre als dissenyadors de
sistemes optimitzar els nivells de voltatge i transmissió per la seva aplicació. Més endavant, es
van estandarditzar dos estàndards de capa física per part de National Instruments:

1) High Speed CAN: soporta baud rates de 40 Kbit/s a 1Mbit/s, variant la distància del cable
(40 metres per 1 Mbit/s, 500 metres per 125 kbit/s).

Per a

El protocol CAN està basat en dues senyals compartides per tots els nodes de la xarxa, el CAN_H
i el CAN_L, que normalment estàn protegits per a que siguin robustos mecànicament i
apantallats per evitar interferències electromagnètiques, que proporcionen una senyal
diferencial que permet la detecció de col·lisió. Enmig dels dos cables es col·loca una resistència
de 120 ohms per obtenir una caiguda de tensió diferencial entre els dos cables. Com més alta
sigui aquesta resistència, menor serà la corrent necessària que hi passi, estalviant energia. Però
sense fer-la massa gran, perquè sinó la qualitat de la senyal disminueix massa, així que cal
arribar a una solució de compromís. Si les dues línies estàn en HIGH, significa que dos o més
nodes estan tractant d’enviar dos senyals diferents, és a dir, hi ha una col·lisió. Aquesta col·lisió

Per què s’utilitzen resistències terminals de 120 ohms en el bus CAN?

-S’utilitzen exactament de 120 ohms perquè la majoria de cablejat en automoció és individual, si


trenes dos cables i els ajuntes en un parell, la medició dóna una impedància de 120 ohms,
podent arribat a baixar a 100 ohms quan s’utilitza un cable tipus CAT5 (trenat com l’ethernet).

Página 17 de 66
Proyecto Integrador

Amb un cable típic de CAT5 s’obté un bon slew-rate de 50 nanosegons a 1 Mbit/s però si
augmentéssim la velocitat del bus l’slew-rate es dispararia.

També existeixen dos motius, primer per millorar la qualitat de la senyal (evitar l’efecte ringing) i
evitar reflexions d’alta frequencia (estudiades a equips electrònics). D’altra banda s’utilitza per
definir l’1 lògic, el valor recessiu.

El bus CAN és un bus industrial que transmet una senyal digital, 0 o 1’s. Per obtenir una senyal
definida (1 lògic) es necessita un resistor, en configuració pull-up o pull-down, depenent del
tipus de CAN implementat (SWC, LS-CAN...).

Per maximitzar la qualitat de la senyal, es necessari utilitzar un parell trenat per anular les
interferències electromagnètiques dels cables i apantallar-lo per protegir-lo del soroll extern. El
parell trenat assegura la mateixa longitud del cable, el que permet que la impedància sigui la
mateixa. El fet que la impedància sigui la mateixa és vital per una bona qualitat de la senyal.

4.4 Configuración del bus CAN


Per configurar el bus CAN en el dsPIC30F, hem consultat el family datasheet proporcionat pel
fabricant, en concret la Section 23 del manual.

4.5 Capa superior: protocol de comunicació Ad Hoc vs OBDII


Alhora d’implementar el protocol de comunicació del nostre sistema en bus CAN, primer vam fer
una recerca sobre el tema, i vam trobar un protocol estàndard en la indústria de l’automòvil que
utilitza el bus CAN, aquest protocol estàndard s’anomena OBDII, i s’utilitza com a port de
diagnòstic en els cotxes.

Al final vam decantar-nos per implementar un protocol de comunicació ad hoc, per un tema de
simplicitat. Vam establir el protocol en una fulla excel per interaccionar el Matlab (PC) amb el
dsPIC30F (“ECU”).

Protocol OBDII

El port de diagnòstic OBD2 té també un connector propi, present a tots els cotxes. És possible
utilitzar el kvaser per comunicarse amb la xarxa CAN del cotxe a través del DATA LINK connector
amb un adaptador D-sub.

Página 18 de 66
Proyecto Integrador

El connector Data-Link també permet connectarse a altres busos de dades com el bus LIN.

Aquest és un esquema del model OSI adaptat al bus CAN, on s’organitza OBD2 i CAN.

OBD3 és un protocol en desenvolupament, que pretén connectar els cotxes sense fils (a partir
de 2018 les revisions de la ITV inclouran la revisió de les ECUs del cotxe, pel que la comunicació
amb CAN és necessària, però l’accés al bus CAN intern requereix de fils). Es presenten reptes per
a aquesta nova versió del protocol, com la seguretat, l’encriptació de les dades... Es pretén
connectar una xarxa CAN local amb internet. Les avantatges i desavantatges són òbvies:
permetria la monitorització en llargues distàncies dels cotxes, el control remot dels cotxes, però
el risc d’un error o un atac informàtic tiraria per sobre la robustesa del bus CAN.

Página 19 de 66
Proyecto Integrador

Aquest és un exemple pràctic de com funcionaria una comunicació CAN amb el protocol OBD2
implementat.

Quan volguéssim fer una requisició, s’hauria de transmetre un missatge amb l’identifier (ID) =
0x7DF, quan responguem, necessitaríem la ID = 0x7E8. Noti’s que la ID de requisició és més
petita que la de resposta, ergo té més 0, ergo és dominant en cas de col·lisió. Això té sentit,
perquè en cas de col·lisió no tindria sentit que hi hagués una resposta si no s’hagués fet cap
pregunta.

Primer de tot, tenir en compte que el missatge d’exemple es mostra en hexadecimals, dos
hexadecimals son 1 byte (8 bits).

El primer byte del missatge indica el nombre de bytes del missatge (OBD2 envia sempre un
missatge de 8 bytes, els utilitzi per envia informació o no, en cas de no utilitzar tots els bytes
disponibles, la resta de bytes es consideren “dummy loads”.)

El segon byte indica el mode de funcionament requerit. El mode 01 indica que es requereix que
es mostri la dada actual.

Els missatges es codifiquen tenint en compte els PIDs. El tercer byte indica el PID requerit, en
aquest cas, 0C indica que requereix la dada actual de les RPM del cotxe.

Página 20 de 66
Proyecto Integrador

Protocol Ad Hoc

Per tal de facilitar la comunicació dels nodes amb el bus CAN, es va descartar implementar el
protocol OBD2. Vam decidir fer un excel que estableix el nostre protocol propi de comunicació:

Per simplicitat, vam decidir enviar 1 byte de dades (un número de 0 a 255) per cada trama
(recordar, però, que cada trama enviada en CAN va de 47 bytes a 55 bytes, sent només variable
el tamany del missatge a enviar de 1 a 8 bytes). Utilitat i aplicacions del bus CAN: ECU’s en
automoció

El bus CAN, és l’estàndard en la indústria de l’automòvil, també en tancs militars i camions (però
cadascun amb estandards diferents).

Altres protocols que també estan basats en CAN a banda de l’anteriorment mencionat OBDII són
el J1939 (camions), el MilCAN (vehicles militars) el CANopen (automatització industrial).

En automoció, el bus CAN es com una medul·la espinal del cotxe que permet que tots els nodes
connectats a aquest bus estiguin comunicats. Cada ECU (unitat electrònica de control)
constitueix un node i pot dir la seva. En el nostre cas, tindríem 3 ECUS, l’ordinador amb Matlab
(que té la ID més baixa, i per tant té preferència per sobre de la resta), el dsPIC30F que controla
el motor i un altre dsPIC que controla les llums.

La ventatja del bus CAN es que permet connectar múltiples ECUs entre sí amb un cablejat simple
(només necessita el CAN_H, el CAN_L i massa). Això representa un estalvi important en cablejat,
que suposa una reducció en el pes del cotxe, el que fa augmentar l’eficiència del vehicle. Un
cotxe modern pot arribar a tenir 100 ECUs. Altres exemples d’ECUs són el control centralitzat de
finestres o els airbags.

5 avantatges crítiques del bus can:

Página 21 de 66
Proyecto Integrador

- Baix cost: els ECUs es comuniquen amb una única interfície CAN reduint error, pes i
cost.
- Bus centralitzat: Permet configurar tots els ECUs i permet centralitzar la diagnosis
d’errors (OBDII)
- Robustesa: Si un dels subsistemes falla, la resta contínua funcionant. Està protegit per
interferències electromagnètiques. No té problemes de col·lisió, ja que el seu protocol
està basat en missatges amb una ID jeràrquica que elimina aquest problema (que sí té
per exemple Ethernet, tot i ser més ràpid)

Com connectar un micro qualssevol a CAN: necessitem un controlador i un transceptor.

4.6 Codi Font explicat


Rutina de configuració del bus CAN. Vam haver de canviar l’oscil·lador de cristall per un
estàndard de 8 MHz que fos compatible amb el CAN. Vam configurar-lo a una velocitat de 500
kbit/s, tot i que en aquest cas ni la distància del cable ni la velocitat del bus eren crítics, ja que
no ens calia enviar massa informació. Una trama de CAN ocupa aproximadament de 48 a 55
bytes depenent de la llargada de les Dades a enviar (variable de 1 a 8 bytes), és a dir, de 384 a
440 bits. Vam decidir posar un període d’enviament de 500 ms mitjansant una interrupció, per
estalviar consum i càlcul computacional del dsPIC. Si haguéssim volgut mostrejar la resposta del
motor al PC, probablement hauríem d’haver enviat les dades mostrejades a un període inferior,
d’uns 10 ms, per exemple, per a que les gràfiques fossin més fidedignes. Si haguéssim d’haver
fet aquest cas, hauríem denviar 440 bits 100 vegades per segon, el que significaria que
necessitaríem una capacitat de canal mínima de 44 Kbit/s. Hauríem d’estudiar qué passaria si
conectéssim diversos ECUs amb bitrates crítics diferents, però això excedeix el marc d’aquest
projecte.

És a dir, que amb 500 Kbits/s, tenint en compte que una trama d’un missatge CAN ocupa com a
molt 440 bits, podríem enviar trames amb un període de 880 microsegons, és a dir, una
frequencia de 1.137 kHz (osigui es podria propagar, a ull, aproximadament 1 missatge cada
milisegon pel bus CAN a tots els nodes tenint en compte la latència, la resolució de col·lisions, el
filtratge local, etc).

Página 22 de 66
Proyecto Integrador

Per configurar el CAN en el dsPIC30F, vam consultar el manual del family datasheet a la Section
23 (veure referència) on s’explica què fa cada registre de configuració disponible. En el cas del
dsPIC33F i el PIC24F, hi ha codis d’exemple per fer funcionar ràpidament el CAN, però amb el
dsPIC30F el vam haver de configurar nosaltres mateixos.

Alhora d’ajuntar les rutines CAN del dsPIC30F amb el CAN del Matlab, s’ens van presentar dos
problemes. Un, que els bytes enviats i rebuts en el dsPIC30F s’enviaven i es rebien al revés, pel
que vam haver de dissenyar un algoritme per resoldre-ho. Més endavant, vam treure aquest
codi perquè només enviàvem i rebíem un sol byte, ja que rebíem una consigna del 0 al 100% (8
bits permet codificar de 0 a 255) i la tractàvem internament en el dsPIC30F.

Vam haver de fer una reconfiguració de la rutina IniCAN perquè la teníem en un mode
anomenat Remote Transmission Request (no havíem configurat el registre TXIDE directament).
Això creava problemes, perquè hi havia una incompatibilitat en la configuració que feia que el
bus CAN deixés de funcionar al cap d’una estona, ja que codificava les IDs amb extended, és a dir
29 bits en comptes dels 11 bits que havíem configurat (òbviament al haver establer una xarxa
molt simple amb 3 nodes no ens calia un protocol extended, que pot ser una solució òptima per
una xarxa de domòtica o en un entorn d’automatització industrial, per exemple).

Vam haver d’afegir a la transmissió (sortida) TXIDE = 0 per establir el CAN estandard (IDs a 11
bits) i SRR = 0 per desactivar el mode Remote Request.

Página 23 de 66
Proyecto Integrador

La seguent rutina serveix per enviar un missatge a la xarxa CAN. Envia un missatge amb Data de
1 byte codificat com a enter. Com ja s’ha mencionat, també s’ha provat d’enviar més bytes
alhora, resolent el problema dels bytes enviats creuats. Com que no ens calia enviar més
informació perquè hem volgut simplificar el procés, hem simplificat la rutina. Deixem per a
futurs projectes que vulguin implementar OBD2 el codi per enviar 8 bytes de dades, tot i que no
té més misteri que llegir la Section 23 del family datasheet, tractar les dades amb màscares i/o
desplaçaments de bit i enviar cada byte al seu registre corresponent (B1, B2, B3 i B4).

Página 24 de 66
Proyecto Integrador

Configuració del timer de la ISR (interrupció periòdica). Timer que s’activa cada 500 ms, que es
el període d’enviament desitjat. S’ha escollit aquest període per no saturar la capacitat de càlcul
del DSP, ja que si establíem una interrupció cada 1 ms (i enviar cada 500 execucions) només pel
fet d’interrompre masses cops per segon el programa, el DSP es saturava i el PID i la LCD de la
placa deixaven de funcionar correctament.

ISR que tracta el valor de la posició del motor del 0% al 100% (el PID està configurat per donar
10 voltes, i és controlar en graus, és a dir, va de 0 a 3600º). Osigui que el 50% significa que el
motor està en la posició 1800º, asumint un error degut a que la precisió és en graus i no s’envien
decimals, un error en contínua, un temps d’establiment, etc. També es va implementar el PID
per a control de velocitat.

Després tenim la gestió dels LEDs. Estàn definits a les capsaleres els registres corresponents a
cada LED. El programa funciona tal que el programa de Matlab pot encendre o apagar dos leds, i
apagar o fer-ne pampallugues amb els altres dos leds (aprofitant el timer de 500ms, és visible

Página 25 de 66
Proyecto Integrador

per a l’ésser humà).

Finalment, enviem la posició estandaritzada com a missatge al bus CAN.

També tractem els leds parpadejant a led’s blinking, ja que necessitem que parpadejin,
reaprofitem la ISR del missatge periòdic, com hem dit.

Página 26 de 66
Proyecto Integrador

Per últim, la rutina que llegeix el missatge CAN. És similar a la rutina que envia CAN, ja que
només s’ha de rebre un missatge de 1 byte. El registre SID conté la ID del missatge, que és
guardat en una variable, tot i que es podria definir a la capsalera per fer el codi més eficient
(també es podria fer una rutina amb ensamblador, però això ho deixem per a futurs projectes).

Com estem treballant amb molts pocs nodes, no ens cal utilitzar un protocol de missatges
complex com l’OBDII. Si a la xarxa CAN tinguéssim 50 nodes diferents, cada ID hauria d’estar
codificada d’una manera que es pogués identificar qui està fent la petició i quina és la petició. En
el protocol OBD2 això es resol amb els Paramets IDs (PIDs no confondre amb el controlador!)
que estàn al primer byte del missatge, és a dir a B1 (no confondre missatge que en aquest cas
ens referim a les dades amb la trama total del missatge, que inclou tota la configuració de nivell
baix amb la que no hem treballat al projecte, més que a la recerca indicada al comensament de
l’article).

Página 27 de 66
Proyecto Integrador

Página 28 de 66
Proyecto Integrador

5 Librería placa dsPICDEM MC1

Con el fin de simplificar la programación se ha optado por crear una librería que recoja
las funcionalidades necesarias de la placa de demostración dsPICDEM MC1.

Estas funcionalidades están implementadas en muchos casos, de manera que sean


capaces de ejecutar eventos a modo de interrupción e independientes del programa
principal, lo que permite una mayor modularidad en la programación.

5.1 Estructuras Básicas

5.1.1 Información del motor

• Estructura: MOTOR_INFO;

Esta estructura almacena la información básica del motor. Sus miembros son solo de
lectura y se actualizan en las funciones que se describen más adelante:

• Speed: Velocidad del motor en RPM calculada mediante el encoder del motor.
Puede coger valores positivos o negativos indicando el sentido del giro
• Position: Posición del motor en grados calculada mediante el encoder del motor.
Puede coger valores positivos o negativos indicando el sentido del giro
• Dcycle: Valor del duty-cycle en %. Puede coger valores positivos o negativos
indicando el sentido del giro
• Ecount: Pulsaciones totales generadas por el Encoder en un mismo sentido de giro.
Este valor sirve como base para el cálculo de la posición y puede coger valores
positivos o negativos indicando el sentido del giro.
• Running: 1 Indica si el motor está en marcha, 0 si está parado.
• Fault: 1 Indica si el driver del motor está en fallo, 0 si está correcto.
• Direction: 1 Indica si el motor gira en modo inverso, 0 si es en directo.

5.2 Funciones Genéricas

5.2.1 Configuración de puertos

• Llamada: void SetupPorts();

Se encarga de configurar los puertos como entrada o salida adecuando su estado a los
elementos de la placa conectados en él. Además, asegura que los puertos configurados
como salida se inicializan sin tensión.

Página 29 de 66
Proyecto Integrador

5.2.2 Configuración de pulsadores de usuario

• Llamada: void IniPB(void(*AttnFunction)(char PBnum, char PBsts));

La placa dispone de 4 pulsadores identificados como S4, S5, S6 y S7. Esta función se
encarga de inicializar las interrupciones asociadas a estos pulsadores.

void(*AttnFunction)(char PBnum, char PBsts)


Como parámetro necesita una función asociada que se ejecutará cada vez que se
produzca un evento de pulsación en uno de los 4 pulsadores.

char PBnum, char PBsts


A su vez, esta función asociada tiene que tener dos parámetros de tipo char. El primer
parámetro (PBnum) contendrá un número del 4 al 7 identificando que pulsador ha
provocado el evento, y el segundo parámetro (PBsts) indicará si el evento se ha
producido al pulsar el botón (1) o al soltarlo (0).

5.2.3 Configuración de potenciómetro de usuario

• Llamada: void IniVR2();

La placa dispone de 2 potenciómetros identificados como VR1 y VR2. VR2 está


conectado al puerto de entrada analógica AN7. Esta función se encarga de inicializar el
conversor analógico/digital (ADC) asociado a este puerto con una frecuencia de
muestreo de aproximadamente 2 MHz.

La lectura del valor se puede hacer mediante la función macro VR2Value() la cual
devuelve el valor del potenciómetro en %.

5.2.4 Generación de tiempos de espera

• Llamada: void Wait(long count);

Se encarga de crear tiempos de retardo entre instrucciones y se utiliza sobre todo en


comandos enviados al display LCD el cual requiere de estos tiempos.
Como parámetro necesita el número de iteraciones que se ejecutan en su bucle interior.
La relación entre este este parámetro y el tiempo consumido viene expresado por:
Tiempo = ((14 x count) + 24) / FCY

Donde FCY es la frecuencia de trabajo del procesador (en nuestro caso configurado a
14.75MHz). Así por ejemplo tendríamos que un retardo aproximado de 20 ms vendría
expresado por:

#define Delay_20ms (FCY * 0.02/14) => count=21071

Página 30 de 66
Proyecto Integrador

5.3 Funciones asociadas al Display LCD

5.3.1 Inicialización driver LCD

• Llamada: void IniLCD(void);

Se encarga de generar la secuencia de arranque específica para el driver Hitachi


HD44780 que controla el display LCD con una configuración para 4 bits de datos, dos
líneas y 5x8 puntos/carácter. Esta secuencia está especificada en el manual del
fabricante:

5.3.2 Consulta del estado del LCD

• Llamada: char BusyLCD();

Se encarga de consultar el estado del LCD para saber si ha procesado la última


instrucción enviada. Retorna un 0 si el LCD está libre o 1 si está ocupado.

Página 31 de 66
Proyecto Integrador

5.3.3 Envío de un comando de instrucción al LCD

• Llamada: void WriteCmdLCD(char cmd);

Se encarga de escribir un comando en el registro de instrucciones del LCD. Estos


comandos ejecutan funciones y modifican configuraciones del LCD como pueden ser el
modo del movimiento y la posición del cursor, si este tiene que parpadear, el número de
líneas LCD, el tamaño de los caracteres, el borrado completo del LCD, etc

Estos comandos se codifican en un byte que pueden ser pasados a la función a través de
su parámetro cmd. En el manual del driver Hitachi HD44780 se puede encontrar la lista
completa de instrucciones disponibles.

5.3.4 Envío de un comando de datos al LCD

• Llamada: void WriteDataLCD(char cmd);

Se encarga de escribir un comando en el registro de datos del LCD. Este dato representa
el código de carácter a escribir en la posición actual del cursor, según una tabla que
guarda el driver en su memoria ROM interna.

Página 32 de 66
Proyecto Integrador

5.3.5 Borrado del LCD

• Llamada: void DeleteLCD();

Se encarga de borrar el contenido del LCD y posicionar el cursor al inicio. Esta función
ejecuta la escritura del registro de instrucciones descrito anteriormente con los
comandos adecuados para realizar estas acciones.

5.3.6 Posición del cursor en el LCD

• Llamada: void PosLCD(char col, char row);

Se encarga de posicionar el cursor en la columna y fila indicada en sus parámetros.


Estos valores pueden ser 0 a 15 para la columna y 0 a 1 para la fila ya que disponemos
de un LCD de 16x2. Las siguientes escrituras de caracteres se realizarán a partir de la
posición indicada mediante esta instrucción.

5.3.7 Escritura de un texto en el LCD

• Llamada: void WriteTxtLCD(char *buffer, char col, char row);

Se encarga de escribir un texto con una longitud máxima de 16 caracteres a partir de la


posición indicada por los parámetros de columna (col) y fila (row).

El texto es pasado a la función como un puntero al primer carácter del array. Esta
función ejecuta la escritura del registro de datos descrito anteriormente enviando
secuencialmente cada uno de los caracteres que forman el array.

5.3.8 Escritura de un número en el LCD

• Llamada: void WriteNumLCD(float number,int len,int dec,char col,char row)

Se encarga de escribir el número definido en el parámetro number, con un formato


especificado, a partir de la posición indicada por los parámetros de columna (col) y fila
(row).

Para dar formato al número, se tiene que especificar el número máximo de caracteres
que lo representará (len), y cuantos decimales se tienen que mostrar (dec). Hay que
tener en cuenta a la hora de definir la longitud máxima (len), que el símbolo decimal “.”
representa un carácter y por lo tanto ocupa una posición.

Página 33 de 66
Proyecto Integrador

5.4 Funciones asociadas al control del Motor

5.4.1 Inicialización del sensor de posición del eje del motor (Hall)

• Llamada: void IniHall(void(*AttnFunction)());

Esta función se encarga de inicializar las interrupciones asociadas a las tres entradas
donde están conectados los sensores Hall que indican la posición del eje del motor en
cada momento.

Las interrupciones llaman a la función OutMngr() encargada de calcular la siguiente


posición de los transistores que alimentan las 3 fases del motor. Además existe la
posibilidad de configurar una función asociada que se ejecutará cada vez que se
produzca una de estas interrupciones y que es definida en el parámetro
void(*AttnFunction)()

5.4.2 Gestión de la posición de transistores del motor

• Llamada: OutMngr();

Mediante la lectura de la posición del eje del motor gracias a los sensores Hall, calcula la
siguiente posición de los seis transistores que alimentan al motor.
La función se encarga de unificar en un solo valor el estado de las 3 entradas de Hall
mediante la función macro: #define HallValue() (PORTD & 0x0700) >> 8;
Este valor representará el índice dentro del array de posiciones para ambos sentidos de
giro que será escrito en el registro de control del PWM.

unsigned int StepTableDIR[] = {0x0000,0x0210,0x2004,0x0204,0x0801,0x0810,0x2001,0x0000};


unsigned int StepTableREV[] = {0x0000,0x2001,0x0810,0x0801,0x0204,0x2004,0x0210,0x0000};

Página 34 de 66
Proyecto Integrador

5.4.3 Inicialización del control PWM del motor

• Llamada: void IniPWM(void(*AttnFunction)());

Esta función se encarga de inicializar el control PWM del motor integrado en el dsPIC. La
frecuencia de trabajo viene establecida en la constante FPWM siendo de 16KHz según
recomendaciones del manual para el driver de potencia del motor.

El control PWM modula solo los transistores de la alimentación positiva, dejando los de
la parte negativa fijos según la posición definida por la función OutMngr() descrita en el
apartado anterior.

El control es inicializado para desactivar todas sus salidas ante una señal de fallo del
driver. Esta señal que llega al dsPIC a través de su entrada FLTA genera una interrupción
la cual puede ejecutar una función asociada definida en el parámetro
void(*AttnFunction)() .

Señalar que esta señal de FLTA se puede activar también mediante el pulsador TRIP
disponible en la placa, permitiendo así simular fallos.

5.4.4 Arranque del control PWM del motor

• Llamada: void StartPWM(void);

Esta función se encarga de arrancar el control PWM del motor integrado en el dsPIC
siempre que no exista una señal de fallo en la entrada FLTA.

5.4.5 Paro del control PWM del motor

• Llamada: void StopPWM(void);

Esta función se encarga de parar el control PWM del motor integrado en el dsPIC.

5.4.6 Rearme fallo driver del motor

• Llamada: char RstDriver();

Esta función se encarga de enviar la señal de rearme de fallo al driver del motor durante
50 ms. El driver tiene protecciones que gestionan directamente posibles fallos del motor
o de la línea. Estos fallos son indicados a través de la señal FLTA provocando que el
driver permanezca desactive la alimentación del motor hasta recibir la señal de rearme.

La función retorna 1 si el rearme se ha ejecutado con éxito y ha desaparecido la señal de


FLTA, en caso contrario retorna un 0.

Página 35 de 66
Proyecto Integrador

5.4.7 Escritura del duty-Cycle

• Llamada: void SetDC(float val);

Se encarga de establecer el % de duty–cycle del control PWM que controla el motor. Su


valor está limitado entre -100% y +100%, siendo el signo del valor el que establece el
sentido de giro del motor. Así, valores negativos indican que el sentido tiene que ser
inverso, y positivos directo.

Esta posibilidad facilita la regulación de la posición del motor en lazo cerrado mediante
un control PID.

5.5 Funciones asociadas al encoder del Motor

5.5.1 Inicialización del encoder del motor

• Llamada: void IniQEI(void(*AttnFunction)())

Esta función se encarga de inicializar el controlador de encoder que tiene incorporado el


dsPIC. El control está configurado para que se ponga a cero cada 1000 pulsos ya que el
encoder del motor es de cuadratura con 250 líneas.

También configura el filtro de entrada que es capaz de discriminar pulsos inferiores a un


tiempo especificado. Para el cálculo de este tiempo mínimo tenemos que:

Pulso Mínimo=30/(RPM*IPR)=30/(4000*1000)= 7.5us

Donde RPM son las máximas del motor, y IPR las interrupciones por revolución del
encoder. De esta manera y seleccionando el filtro adecuado dentro del rango disponible
en el dsPIC, podemos evitar contar pulsos inferiores a 6.5us.

Otra característica del controlador del encoder, es la posibilidad de asignar la detección


del sentido de giro a una salida RD7. En nuestro caso la placa tiene unido a esta salida el
LED FWD/REV.

Esta función además, habilita la detección de paso por cero que se genera cada vez que
el contador interno pasa por este punto en cualquier sentido de giro. Este paso por cero
provoca una interrupción la cual puede ejecutar la función asociada definida en el
parámetro void(*AttnFunction)().

Por último, la función también se encarga de configurar e inicializar el temporizador T1


que se utilizará para el cálculo de la velocidad. Este temporizador es configurado para
generar una interrupción cada 15 ms lo que asegura obtener como mínimo una muestra
de velocidad cada revolución a velocidad máxima del motor (4000 RPM). La velocidad
puede ser consultada en la variable Motor.speed.

Página 36 de 66
Proyecto Integrador

5.5.2 Actualización de la posición absoluta del motor

• Llamada: void PosUpdate(void)

Esta función se encarga de actualizar las variables Motor.ecount y Motor.position de la


estructura MOTOR_INFO. La primera contiene el total de pulsos generados por el
encoder en el mismo sentido de giro. La segunda contiene este valor transformado en
grados.

La lectura de la posición en cualquier momento puede realizarse llamando a esta


función y posteriormente consultando las variables antes mencionadas. Además el
temporizador T1 encargado de calcular la velocidad también llama a esta función,
asegurando que el dato este actualizado como mínimo cada 15 ms.

5.5.3 Puesta a cero de la posición absoluta del motor

• Llamada: void RstQEIe(void)

Esta función se encarga de inicializar, poniendo a cero, las variables Motor.ecount y


Motor.position de la estructura MOTOR_INFO, así como el contador interno del número
de revoluciones.

5.6 Funciones auxiliares

5.6.1 Inicialización temporizador intermitencia

• Llamada: void iniTimerBlink(void(*AttnFunction)());

Esta función se encarga de inicializar el temporizador T4 que gestiona la intermitencia


de los LEDS. Aunque también se podría utilizar para otras funciones de carácter general.

El temporizador está configurado está configurado en 500 ms. El cual provoca una
interrupción la cual puede ejecutar la función asociada definida en el parámetro
void(*AttnFunction)().

Página 37 de 66
Proyecto Integrador

6 Librería Lazo PID

Como parte del diseño se pretende poder controlar el motor en lazo abierto y en lazo
cerrado. En este último caso, se pretende además poder tener el control por velocidad o
por posición del eje.

Para ello se ha implementado la librería que recoge todas las funcionalidades y


algoritmos que implementan un lazo de control PID.

6.1 Estructuras Básicas

6.1.1 Datos PID

• Estructura: PID_DATA;

Esta estructura almacena la información básica de configuración de un lazo de control


PID. Además sirve de interface con el lazo para modificar parámetros de configuración
tales como las constantes de regulación PID:

• SP: Consigna de control en %. El escalado viene definido por el rango de la variable


de proceso PV y configurado mediante las variables EUmin y EUmax
• PV: Variable de proceso en %. El escalado viene definido según las variables EUmin
y EUmax.
• MV: Salida del lazo en %. Contiene el último valor de salida del algoritmo PID
cuando el lazo está en Automático.
• KP: Constante proporcional en % del algoritmo PID.
• KI: Constante integral en segundos del algoritmo PID.
• KD: Constante derivativa en segundos del algoritmo PID.
• EUmin: Valor mínimo de SP y de PV en unidades de ingeniería. Se utiliza además
para escalar en % estas dos variables.
• EUmax: Valor máximo de SP y de PV en unidades de ingeniería. Se utiliza además
para escalar en % estas dos variables.
• MVmin: Valor mínimo de MV en %. Limita la salida mínima del PID cuando trabaja
en Automático.
• MVmax: Valor máximo de MV en %. Limita la salida máxima del PID cuando trabaja
en Automático.
• Tupdt: Periodo de muestreo del lazo PID en ms. Utilizado por el algoritmo para el
cálculo de los términos derivativo e integral.
• Acc_err: Usado por el algoritmo PID para almacenar el error acumulado del término
integral.
• Bias: Usado por el algoritmo para añadir un % adicional a MV independiente del
algoritmo PID.

Página 38 de 66
Proyecto Integrador

• Action: Comportamiento del lazo PID en función del error.


PIDACTION_REV (0) acción inversa: Un incremento positivo del error origina un
incremento positivo de MV

PIDACTION_DIR (1) acción directa: Un decremento positivo del error origina un


incremento positivo de MV

• Mode: Comportamiento del lazo PID en función del error.


PIDMODE_MAN (0) Modo Manual: El algoritmo PID no se ejecuta, el error
acumulado por el término integral se inicializa a cero.

PIDMODE_AUTO (1) Modo Automático: El algoritmo PID se ejecuta escribiendo su


resultado en la variable MV.

6.2 Funciones asociadas al lazo PID

6.2.1 Gestión algoritmo lazo de control PID

• Llamada: float PIDLoop(float PV,PID_DATA *PID);

Se encarga de ejecutar y calcular el algoritmo PID basado en los parámetros de la


estructura PID_DATA.

Como parámetros la función espera el valor de proceso PV que se quiere controlar, y


una estructura de tipo PID_DATA con los valores del control deseados. La función
retorna directamente el valor MV en %.

6.2.2 Actualización de la consigna del Lazo (SP)

• Llamada: void PIDSetSP(float SP,PID_DATA *PID);

Se encarga de normalizar el valor de consigna SP, el cual es pasado como parámetro a la


función en Unidades de Ingeniería. El valor es escalado en % para poder ser tratado
directamente por el algoritmo PID.

Página 39 de 66
Proyecto Integrador

6.2.3 Inicialización del Lazo de velocidad

• Llamada: void IniSpeedLoop();

Se encarga de parametrizar la estructura PID_DATA asociada al algoritmo PID de


velocidad e inicializar el temporizador T2 asociado al cálculo iterativo del algoritmo PID.
El periodo de este temporizador vendrá definido por la variable Tupdt de la estructura
PID_DATA.

6.2.4 Inicialización del Lazo de posición

• Llamada: void IniPositLoop();

Se encarga de parametrizar la estructura PID_DATA asociada al algoritmo PID de


posición e inicializar el temporizador T2 asociado al cálculo iterativo del algoritmo PID. El
periodo de este temporizador vendrá definido por la variable Tupdt de la estructura
PID_DATA.

Señalar que la estrategia de control se basa en un PID simple, y por lo tanto los
controles por velocidad y posición son incompatibles entre ellos. Por lo tanto el control
predominante será el último que se inicialice. Es decir, solo se ejecutará el algoritmo PID
de la última función InixxxxLoop(); llamada.

Destacar además, que por las inercias del motor, se ha limitado la salida máxima del PID
de posición a ±8% consiguiendo así un sobrepico prácticamente nulo y una mayor
precisión en la posición final.

Página 40 de 66
Proyecto Integrador

7 Librería CAN

Como parte del diseño se pretende poder controlar el motor en lazo abierto y en lazo
cerrado. En este último caso, se pretende además poder tener el control por velocidad o
por posición del eje.

Para ello se ha implementado la librería que recoge todas las funcionalidades y


algoritmos que implementan un lazo de control PID.

7.1 Funciones asociadas a la configuración del puerto

7.1.1 Inicialización puerto CAN1

• Llamada: void IniCAN(void(*AttnFunction)());

Explicar un poco la funcionalidad

7.2 Funciones asociadas a la lectura del puerto

7.2.1 Lectura datos tipo Float

• Llamada: float ReadCANFloat();

Lee el buffer de entrada del puerto CAN1 4 bytes y devuelve un dato de tipo float.

7.2.2 Lectura datos tipo Long

• Llamada: float ReadCANLong();

Lee el buffer de entrada del puerto CAN1 4 bytes y devuelve un dato de tipo Long.

7.2.3 Lectura datos tipo Int

• Llamada: float ReadCANInt();

Lee el buffer de entrada del puerto CAN1 2 bytes y devuelve un dato de tipo Int.

7.2.4 Lectura datos tipo Char

• Llamada: float ReadCANChar();

Lee el buffer de entrada del puerto CAN1 1 bytes y devuelve un dato de tipo Char.

Página 41 de 66
Proyecto Integrador

7.3 Funciones asociadas a la escritura del puerto

7.3.1 Escritura datos tipo Float

• Llamada: WriteCANFloat(unsigned int SID,float value);

Se encarga de enviar a través del puerto CAN1 un dato de tipo float con una longitud de
4 bytes. La función, además del dato a enviar espera el parámetro SID que representa el
identificador standard (11 bits) de mensaje.

7.3.2 Escritura datos tipo Long

• Llamada: WriteCANLong(unsigned int SID,long value);

Se encarga de enviar a través del puerto CAN1 un dato de tipo long con una longitud de
4 bytes. La función, además del dato a enviar espera el parámetro SID que representa el
identificador standard (11 bits) de mensaje.

7.3.3 Escritura datos tipo Int

• Llamada: WriteCANInt(unsigned int SID,int value);

Se encarga de enviar a través del puerto CAN1 un dato de tipo int con una longitud de 2
bytes. La función, además del dato a enviar espera el parámetro SID que representa el
identificador standard (11 bits) de mensaje.

7.3.4 Escritura datos tipo Char

• Llamada: WriteCANChar(unsigned int SID,char value);

Se encarga de enviar a través del puerto CAN1 un dato de tipo char con una longitud de
4 bytes. La función, además del dato a enviar espera el parámetro SID que representa el
identificador standard (11 bits) de mensaje.

7.3.5 Inicialización temporizador envío datos

• Llamada: void iniTimerCAN()

Se encarga de inicializar el temporizador T3 que gestiona en envío periódico de


información a través del puerto CAN1. El temporizador provoca una interrupción cada
500 ms la cual envía la posición actual del motor. Esta misma interrupción puede
configurarse para enviar otros datos si fuese necesario.

Página 42 de 66
Proyecto Integrador

8 Rutina principal

La rutina principal se apoya en las librerías descritas anteriormente para conseguir una
estructura muy simplificada. Aunque no se ha utilizado todo su potencial, permite
mostrar las posibilidades que ofrecen.

Para ello se ha generado un código muy sencillo por el que se controla la posición de un
motor entre 0 y 3600º. La consigna del algoritmo PID es recibida a través de puerto
CAN1, y a su vez la posición real es enviada a intervalos de 500ms.

El código cuenta además con interface por el cual se muestra a través del display LCD los
siguientes datos en tiempo real:

s xxxx.x o xxx.x
posic: xxxx.x g

• s: (SP)consigna actual recibida por el CAN1


• o: (OUT) % salida del lazo PID que representa el duty-cycle del control PWM
• posic: posición actual del motor.

También Se ha utilizado una pequeña interface a través de la botonera que permite


resetear el driver del motor en caso de fallo.

Por último, se ha programado una simulación de las luces e intermitentes de un vehículo


que se activan o desactivan en función de las órdenes recibidas por el puerto CAN1.

Página 43 de 66
Proyecto Integrador

9 Anexos

9.1 Librería placa dsPICDEM MC1

9.1.1 Declaraciones dsPICDEM_MC1.h


#include <xc.h>

//Frecuencia trabajo
#define FCY 14745600 // xtal = 7.3728 Mhz; PLLx8 FCY=Fosc/4

//LEDs
#define LED_D6 LATAbits.LATA9
#define LED_D7 LATAbits.LATA10
#define LED_D8 LATAbits.LATA14
#define LED_D9 LATAbits.LATA15

//Pulsadores
#define PBUTTON_S4 PORTGbits.RG6
#define PBUTTON_S5 PORTGbits.RG7
#define PBUTTON_S6 PORTGbits.RG8
#define PBUTTON_S7 PORTGbits.RG9

//LCD
#define LCDRS LATCbits.LATC3 //LCD Reg. Select
#define LCDENA LATDbits.LATD13 //LCD enable data
#define LCDRW LATCbits.LATC1 //LCD Read/write
#define LCDD0 PORTDbits.RD0 //LCD DB4
#define LCDD1 PORTDbits.RD1 //LCD DB5
#define LCDD2 PORTDbits.RD2 //LCD DB6
#define LCDD3 PORTDbits.RD3 //LCD DB7
// Retardos LCD aplicables a funcion wait()
#define Delay_20ms (FCY * 0.020000/14)
#define Delay_5ms (FCY * 0.005000/14)
#define Delay_10ms (FCY * 0.010000/14)
#define Delay_1ms (FCY * 0.001000/14)
#define Delay_200us (FCY * 0.000200/14)
#define Delay_5us (FCY * 0.000005/14)

//HALL
#define CAP1 PORTDbits.RD8
#define CAP2 PORTDbits.RD9
#define CAP3 PORTDbits.RD10

//PWM
#define FPWM 16000 //Fecuencia PWM 16 KHz
#define PHA1L LATEbits.LATE0
#define PHA1H LATEbits.LATE1
#define PHA2L LATEbits.LATE2
#define PHA2H LATEbits.LATE3
#define PHA3L LATEbits.LATE4
#define PHA4H LATEbits.LATE5
#define DRVFAIL !PORTEbits.RE8 //Fallo Driver
#define DRVRST LATEbits.LATE9 //Reset Fallo Driver
#define PFC LATDbits.LATD5
#define Delay_RST (FCY * 0.05/14) //Retardo Reset Driver

//ENCODER
#define PPR 250.0 //Pulsos por revolución
#define RPMMAX 4000.0 //RPM máximas (calc veloc)
#define IPR 1000.0 //Interrup. por revolución
#define SPESTM 60.0/RPMMAX //Tiempo muestreo velocidad (seg)
#define LED_FR LATDbits.LATD7 //LED FWD/REV
#define QEINDX PORTBbits.RB3 //INDX
#define QEA PORTBbits.RB4 //QEA
#define QEB PORTBbits.RB5 //QEB

// MACROS
#define VR2Value() (ADCBUF0*100.0)/1023.0; //ADC en %
#define HallValue() (PORTD & 0x0700) >> 8;

// TIPOS
typedef struct {
float speed, //Velocidad del motor
position, //Posición del motor
dcycle; //Valor duty-cycle
long ecount; //Contaje Encoder
char running, //Motor activo
direction, //Sentido de giro del motor
fault; //Driver en fallo
}MOTOR_INFO; //Guarda Info del motor

Página 44 de 66
Proyecto Integrador

void SetupPorts(); //Configura puertos


void IniPB(void(*AttnFunction)(char PBnum, char PBsts)); //Inicializa pulsadores
void IniVR2(); //Inicializa entrada analógica AN7
conectada a potenciometro VR2
void Wait(long count); //Espera nº ciclos especificados

//LCD
void IniLCD(); //Inicializa LCD (Driver HD44780)
char BusyLCD(); //Estado ocupado/libre del LCD
void WriteCmdLCD(char cmd); //Escribe un comando en elregistro de instrucciones del LCD
void WriteDataLCD(char data); //Escribe un comando en el registro de datos del LCD
void DeleteLCD(); //Borra el LCD
void PosLCD(char col,char row); //Posiciona curson LCD en columna, fila
void WriteTxtLCD(char *buffer, char col, char row); //Escribe texto en LCD
void WriteNumLCD(float number,int len,int dec,char col,char row); //Escribe numero en LCD

//MOTOR
void IniHall(void(*AttnFunction)()); //Inicializa sensor Hall
void IniPWM(void(*AttnFunction)()); //Inicializa control PWM
void OutMngr(); //Gestiona la activacion de las
salidas al motor
void StartPWM(); //Arranca PWM
void StopPWM(); //Para PWM
char RstDriver(); //Reset Fallo Driver
void SetDC(float val); //Escribe valor Duty-Cycle

//ENCODER
void IniQEI(void(*AttnIndxFunc)()); //Inicializa Encoder
void PosUpdate(); //Actualiza posicion Absoluta
void RstQEI(); //Reset posicion inicial Encoder

//AUXILIAR
void iniTimerBlink(void(*AttnFunction)()); //Inicializa Timer para Intermitencia

void __attribute__((interrupt, no_auto_psv)) _CNInterrupt(void); //Atención de irq de pulsadores


void __attribute__((interrupt, no_auto_psv)) _ADCInterrupt(void); //Atención de irq de conversor A/D
void __attribute__((interrupt, no_auto_psv)) _IC1Interrupt(void); //Atención de irq de pos Hall CAP1
void __attribute__((interrupt, no_auto_psv)) _IC2Interrupt(void); //Atención de irq de pos Hall CAP2
void __attribute__((interrupt, no_auto_psv)) _IC3Interrupt(void); //Atención de irq de pos Hall CAP3
void __attribute__((interrupt, no_auto_psv)) _FLTAInterrupt(void); //Atención de irq Fallo Driver
void __attribute__((interrupt, no_auto_psv)) _QEIInterrupt(void); //Atención de irq Encoder
void __attribute__((interrupt, no_auto_psv)) _T1Interrupt (void); //Atención de irq T1 calc. Veloc
void __attribute__((interrupt, no_auto_psv)) _T4Interrupt (void); //Atención de irq T4 Intermitencia

9.1.2 Código dsPICDEM_MC1.c


#include <xc.h>
#include "dsPICDEM_MC1.h"
#include <stdio.h>

unsigned int PORTG_PRE = 0; //Guarda estado previo puerto G (pulsadores)

unsigned int POSCNT_PRE = 0; //Guarda cont previo encoder para calc veloc.
int Sp_Acc_Revol = 0; //Guarda revoluc. acumuladas para calc veloc.
int Po_Acc_Revol = 0; //Guarda revoluc. acumuladas para calc posic.

unsigned int StepTableDIR[]= //Tabla de conmutacion giro directo


{0x0000,0x0210,0x2004,0x0204,0x0801,0x0810,0x2001,0x0000};
unsigned int StepTableREV[]= //Tabla de conmutacion giro inverso
{0x0000,0x2001,0x0810,0x0801,0x0204,0x2004,0x0210,0x0000};

MOTOR_INFO Motor;

void (*HallFn)(); //Función de atención a la irq del Hall


void (*PBFn)(char PBnum, char PBsts); //Función de atención a las irq de pulsadores
void (*DrvFailFn)(); //Función de atención a la irq del Fallo Driver
void (*EncIndxFn)(); //Función de atención a la irq del Index Encoder

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Configura puertos

void SetupPorts(void)
{
// ============= Port A ==============

// absent
// absent
// absent
// absent

// absent
// absent

Página 45 de 66
Proyecto Integrador

// absent
// absent

// absent
// RA9 VREF- O LED1 (Active high)
// RA10 VREF+ O LED2 (Active high)
// absent

// absent
// absent
// RA14 INT3 O LED3 (Active high)
// RA15 INT4 O LED4 (Active high)

LATA = 0x0000;
TRISA = 0x39FF;

// ============= Port B ==============

// RB0 PGD/EMUD/AN0/CN2 AI Phase1 I /EMUD


// RB1 PGC/EMUC/AN1/CN3 AI Phase2 I /EMUC
// RB2 AN2/SS1/LVDIN/CN4 AI Phase3 I
// RB3 AN3/INDX/CN5 I QEI Index

// RB4 AN4/QEA/CN6 I QEI A


// RB5 AN5/QEB/CN7 I QEI B
// RB6 AN6/OCFA AI PFC Hall
// RB7 AN7 AI Pot (VR2)

// RB8 AN8 AI Bus Shunt


// RB9 AN9 AI VAC Sense
// RB10 AN10 AI Phase4 Shunt
// RB11 AN11 AI VBus Sense

// RB12 AN12 AI V Phase1


// RB13 AN13 AI V Phase2
// RB14 AN14 AI V Phase3
// RB15 AN15/OCFB/CN12 O Fault'

LATB = 0x0000;
TRISB = 0xFFFF;

// ============= Port C ==============

// absent
// RC1 T2CK O LCD R/W'
// absent
// RC3 T4CK O LCD RS

// absent
// absent
// absent
// absent

// absent
// absent
// absent
// absent

// absent
// RC13 EMUD1/SOSC2/CN1 EMUD1
// RC14 EMUC1/SOSC1/T1CK/CN0 EMUC1
// RC15 OSC2/CLKO

LATC = 0x0000;
TRISC = 0xFFF5;

// ============= Port D ==============

// RD0 EMUC2/OC1 I/O LCD D0


// RD1 EMUD2/OC2 I/O LCD D1
// RD2 OC3 I/O LCD D2
// RD3 OC4 I/O LCD D3

// RD4 OC5/CN13 O Brake circuit firing line (Active high)


// RD5 OC6/CN14 O PFC switch firing line (Active high)
// RD6 OC7/CN15 O //*** Output compare 7 for diagnostics
// RD7 OC8/CN16/UPDN O LED FWD/REV

// RD8 IC1 I CAP1


// RD9 IC2 I CAP2
// RD10 IC3 I CAP3
// RD11 IC4 O Fire Enable (PWM Output Enable) (Active low)

// RD12 IC5
// RD13 IC6/CN19 O LCD ENA
// RD14 IC7/CN20

Página 46 de 66
Proyecto Integrador

// RD15 IC8/CN21

LATD = 0x0000;
TRISD = 0xD70F;

// ============= Port E ==============

// RE0 PWM1L O Phase1 L


// RE1 PWM1H O Phase1 H
// RE2 PWM2L O Phase2 L
// RE3 PWM2H O Phase2 H

// RE4 PWM3L O Phase3 L


// RE5 PWM3H O Phase3 H
// RE6 PWM4L O Phase4 L
// RE7 PWM4H O Phase4 H

// RE8 FLTA/INT1 I Fault'


// RE9 FLTB/INT2 O Fault Reset (Active high)
// absent
// absent

// absent
// absent
// absent
// absent

LATE = 0x0000;
TRISE = 0xFD00;

// ============= Port F ==============

// RF0 C1RX I CAN Rx


// RF1 C1TX O CAN Tx
// RF2 U1RX I 232 Rx
// RF3 U1TX O 232 Tx

// RF4 U2RX/CN17 I 485 Rx


// RF5 U2TX/CN18 O 485 Tx
// RF6 EMUC3/SCK1/INT0 I ISO SCK1
// RF7 SDI1 I ISO SDI1

// RF8 EMUD3/SDO1 O SDO1


// absent
// absent
// absent

// absent
// absent
// absent
// absent

LATF = 0x0000;
TRISF = 0xFED5;

// ============= Port G ==============

// RG0 C2RX O 485 RE'


// RG1 C2TX O 485 DE
// RG2 SCL I/O SCL
// RG3 SDA I/O SDA

// absent
// absent
// RG6 SCK2/CN8 I Button 1 (S4) (Active low)
// RG7 SDI2/CN9 I Button 2 (S5) (Active low)

// RG8 SDO2/CN10 I Button 3 (S6) (Active low)


// RG9 SS2/CN11 I Button 4 (S7) (Active low)
// absent
// absent

// absent
// absent
// absent
// absent

LATG = 0x0000;
TRISG = 0xFFF0;

Página 47 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa interrupciones pulsadores

void IniPB(void(*AttnFunction)(char PBnum, char PBsts))


{
PBFn=AttnFunction;

_TRISG6 = 1; //puerto PButton S4 como entrada


_TRISG7 = 1; //puerto PButton S5 como entrada
_TRISG8 = 1; //puerto PButton S6 como entrada
_TRISG9 = 1; //puerto PButton S7 como entrada

_CN8PUE = 1; //Desactiva pullups en CN8


_CN8IE = 1; //Activa interrupcion en PB_S4 (CN8)
_CN9PUE = 1; //Desactiva pullups en CN9
_CN9IE = 1; //Activa interrupcion en PB_S5 (CN9)
_CN10PUE = 1; //Desactiva pullups en CN10
_CN10IE = 1; //Activa interrupcion en PB_S6 (CN10)
_CN11PUE = 1; //Desactiva pullups en CN11
_CN11IE = 1; //Activa interrupcion en PB_S7 (CN11)

PORTG_PRE = PORTG; // Guarda estado anterior


_CNIF = 0; // Borra Flag Interrupcion
_CNIP = 3; // Establece interrupcion prioridad 3
_CNIE = 1; // Activa interrupciones CN
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generada por pulsadores

void __attribute__((interrupt, no_auto_psv)) _CNInterrupt (void)


{
unsigned int BitsChange;
char PBnum=0, PBsts=0;

BitsChange = PORTG ^ PORTG_PRE; // Cambio de pulsadores


if (BitsChange & 0x0040) // Pulsador S4
{
PBnum=4;
if (!PBUTTON_S4) PBsts=1;
else PBsts=0;
}
if (BitsChange & 0x0080) // Pulsador S5
{
PBnum=5;
if (!PBUTTON_S5) PBsts=1;
else PBsts=0;
}
if (BitsChange & 0x0100) // Pulsador S6
{
PBnum=6;
if (!PBUTTON_S6) PBsts=1;
else PBsts=0;
}
if (BitsChange & 0x0200) // Pulsador S7
{
PBnum=7;
if (!PBUTTON_S7) PBsts=1;
else PBsts=0;
}

if (PBFn) PBFn(PBnum,PBsts);
PORTG_PRE = PORTG; // Guarda estado anterior
_CNIF = 0; // Borra Flag Interrupcion
}

Página 48 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa entrada analógica AN7 conectada a potenciometro VR2

void IniVR2(void)
{
ADPCFG |= 0xFF7F; // RB7/AN7=Analog
ADCON1 = 0x00E0; // SSRC=111 Internal counter ends sampling and starts conversion
ADCON2 = 0x0004; // Interrupcion cada 2 muestras
ADCON3 = 0x0F00; // Sample time = 15Tad, Tad = internal Tcy/2
ADCHS = 0x0007; // Conecta AN7 al CH0
ADCSSL = 0; // Inputs no scanned
_ASAM = 1; // Auto start sampling

//_ADIF = 0; // Borra Flag Interrupcion


//_ADIE = 1; // Activa interrupciones A/D
//_ADIP = 2; // Establece interrupcion prioridad 2

_ADON = 1; // turn ADC ON


}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Genera tiempo de Espera
// ciclos = (14 x count) + 24 , Tiempo=ciclos/FCY

void Wait(long count)


{
while(count--);
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Genera secuencia de arranque especifica para el driver HD44780
// Se configura para 4 bits de datos, dos lineas, 5x8 puntos/caracter

void IniLCD(void)
{
Wait(Delay_20ms);
LATD = (LATD&0xFFF0)|0x0003; //Funcion set interface 8 bits
TRISD = TRISD&0xFFF0; //Puerto de datos como salida
LCDRW = 0; //R/!W escribir
LCDENA = 1; //Alterna señal enable
Wait(Delay_200us);
LCDENA = 0;

Wait(Delay_10ms);
LATD = (LATD&0xFFF0)|0x0003; //Funcion set interface 8 bits
LCDENA = 1; //Alterna señal enable
Wait(Delay_200us);
LCDENA = 0;

Wait(Delay_1ms);
LATD = (LATD&0xFFF0)|0x0003; //Funcion set interface 8 bits
LCDENA = 1; //Alterna señal enable
Wait(Delay_200us);
LCDENA = 0;

Wait(Delay_1ms);
LATD = (LATD&0xFFF0)|0x0002; //Funcion set interface 4 bits
LCDENA = 1; //Alterna señal enable
Wait(Delay_200us);
LCDENA = 0;

WriteCmdLCD(0x28); // Function set: 4 bits, 2 lineas, 5x8 puntos


WriteCmdLCD(0x06); // Entry Mode: Incr 1, NO shift
WriteCmdLCD(0x0C); // LCD ON, Cursor OFF, NO blink
WriteCmdLCD(0x01); // Borra LCD
WriteCmdLCD(0x80); // Cursor al inicio
}

Página 49 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Retorna estado de ocupado del LCD

char BusyLCD(void)
{
char fbusy;
TRISD = TRISD|0x000F; // Puerto de datos como entrada
LCDRS = 0;
LCDRW = 1; // R/!W leer
Wait(Delay_200us);
LCDENA = 1;
Wait(Delay_200us); // Espera datos del LCD

if(LCDD3) {fbusy = 1;} // Lee el flag de busy


else {fbusy = 0;}

LCDENA = 0;
Wait(Delay_200us);
LCDRW = 0; // R/!W escribir
return fbusy;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe un comando al registro de instrucciones del LCD

void WriteCmdLCD(char cmd)


{
char cmdTemp;
cmdTemp = cmd;

while(BusyLCD()); // Esperar lcd listo

LCDRS = 0; // Seleccion registro instruccion


LCDRW = 0; // R/!W escribir

// Parte alta del byte


LATD = (LATD & 0xFFF0) | (cmdTemp >> 4 & 0x000F);
TRISD = TRISD&0xFFF0; //Puerto de datos como salida
LCDENA = 1;
Wait(Delay_200us);
LCDENA = 0;
Wait(Delay_200us);

// Parte baja del byte


LATD = (LATD & 0xFFF0) | (cmd & 0x000F);
LCDENA = 1;
Wait(Delay_200us);
LCDENA = 0;
Wait(Delay_200us);

TRISD = TRISD|0x000F; // Puerto de datos como entrada


}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe un comando al registro de datos del LCD

void WriteDataLCD(char data)


{
char dataTemp=data;

while(BusyLCD()); // Esperar lcd listo

LCDRS = 1; // Seleccion registro Datos


LCDRW = 0; // R/!W escribir

// Parte alta del byte


LATD = (LATD & 0xFFF0) | (dataTemp >> 4 & 0x000F);
TRISD = TRISD&0xFFF0; //Puerto de datos como salida
LCDENA = 1;
Wait(Delay_200us);
LCDENA = 0;
Wait(Delay_200us);

// Parte baja del byte


LATD = (LATD & 0xFFF0) | (data & 0x000F);
LCDENA = 1;
Wait(Delay_200us);
LCDENA = 0;
Wait(Delay_200us);

TRISD = TRISD|0x000F; // Puerto de datos como entrada


}

Página 50 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Borra el LCD

void DeleteLCD(void)
{
while(BusyLCD()); // Esperar lcd listo
WriteCmdLCD(0x01); // Borra LCD
WriteCmdLCD(0x80); // Cursor al inicio
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Posiciona curson en columna, fila
// col: posicion columna inicio
// row: posicion fila inicio

void PosLCD(char col, char row)


{
WriteCmdLCD(0x80|col|(row << 6)); //Set DDRAM address
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe texto en LCD empezando por columna, fila
// buffer: cadena de caracteres
// col: posicion columna inicio
// row: posicion fila inicio

void WriteTxtLCD(char *buffer, char col, char row)


{
PosLCD(col,row);
while(*buffer!='\0')
{
WriteDataLCD(*buffer);
buffer++;
}
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe numero en LCD con formato especificado
// number: numero a escribir
// len: longitud
// dec: numero decimales
// col: posicion columna inicio
// row: posicion fila inicio

void WriteNumLCD(float number,int len,int dec,char col,char row)


{
char buffer[17];
int i=0;

sprintf(buffer, "%*.*f", len,dec,(double)number);


PosLCD(col,row);
while(buffer[i]!='\0')
{
WriteDataLCD(buffer[i]);
i++;
}
}

Página 51 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa posicion Hall
// AttnFunction: funcion de atencion a la interrupcion (opcional)

void IniHall(void(*AttnFunction)())
{
HallFn=AttnFunction; //Función de atención a la irq

IC1CON=1; // Capture mode, every edge


IC2CON=1; // Capture mode, every edge
IC3CON=1; // Capture mode, every edge

_TRISD8 = 1; // Puerto CAP1 como entrada


_TRISD9 = 1; // Puerto CAP2 como entrada
_TRISD10 = 1; // Puerto CAP3 como entrada

_IC1IF = 0; // Borra Flag Interrupcion IC1


_IC2IF = 0; // Borra Flag Interrupcion IC2
_IC3IF = 0; // Borra Flag Interrupcion IC3

_IC1IP = 4; // Interrupcion prioridad 4 IC1


_IC2IP = 4; // Interrupcion prioridad 4 IC2
_IC3IP = 4; // Interrupcion prioridad 4 IC3

_IC1IE = 1; // Activa interrupciones IC1


_IC2IE = 1; // Activa interrupciones IC2
_IC3IE = 1; // Activa interrupciones IC3
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupciones Generadas posiciones Hall

void __attribute__((interrupt, no_auto_psv)) _IC1Interrupt (void)


{
OutMngr();
if (HallFn) HallFn(); //Si hay funcion configurada se ejecuta
_IC1IF = 0;
}

void __attribute__((interrupt, no_auto_psv)) _IC2Interrupt (void)


{
OutMngr();
if (HallFn) HallFn(); //Si hay funcion configurada se ejecuta
_IC2IF = 0;
}

void __attribute__((interrupt, no_auto_psv)) _IC3Interrupt (void)


{
OutMngr();
if (HallFn) HallFn(); //Si hay funcion configurada se ejecuta
_IC3IF = 0;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa control PWM con control de fallo FLTA por interrupcion
// AttnFunction: funcion de atencion a la interrupcion de fallo (opcional)

void IniPWM(void(*AttnFunction)())
{
DrvFailFn=AttnFunction; //Función de atención a la irq

PTCON = 0x0000; // Tiempo base en "free running" sin escalados


OVDCON = 0x0000; // Desactiva salidas
PTPER = (FCY/FPWM)-1; // Tiempo base del periodo
SEVTCMP = PTPER; // Tiempo base del evento especial (ADC)
PWMCON1 = 0x0700; // Salidas sin control PWM
PWMCON2 = 0x0F00; // Postscale x16 evento especial (ADC) (no usado)
DTCON1 = 0x0000; // No hace falta deadtime (cambios diagonales)
DTCON2 = 0x0000;
FLTACON = 0x0007; // Posicion fallo todo desconectado

_FLTAIP = 4; // Interrupcion prioridad 4 FLTA


_FLTAIF = 0; // Borra Flag Interrupcion FLTA

_PTEN = 1; // Arranca PWM

SetDC(0); // DutyC a 0%

if (DRVFAIL)
{
Motor.fault=1; //Flag Motor en fallo
if (DrvFailFn) DrvFailFn(); //Si hay funcion configurada se ejecuta
}

Página 52 de 66
Proyecto Integrador

else _FLTAIE = 1; // Activa interrupciones FLTA


}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generadas fallo Driver FLTA

void __attribute__((interrupt, no_auto_psv)) _FLTAInterrupt (void)


{
_FLTAIE = 0; // Desactiva interrupciones FLTA
PWMCON1 = 0x0700; // Salidas desactivadas
Motor.fault=1; // Flag Motor en fallo
Motor.running=0; // Flag Motor parado
if (DrvFailFn) DrvFailFn();
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Gestiona la activacion de las salidas al motor

void OutMngr(void)
{
unsigned int HV=HallValue(); //Lee posicion actual

if (!DRVFAIL) //Si no está en fallo


{
//Activa las salidas apropiadas segun la posicion actual
// y el sentido de giro
if (Motor.direction) OVDCON = StepTableREV[HV];
else OVDCON = StepTableDIR[HV];

PFC=1;
}
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Arranca PWM

void StartPWM(void)
{
if (!DRVFAIL) //Si no está en fallo
{
OutMngr();
PWMCON1 = 0x0777; // Salidas independientes habilitadas para PWM
Motor.running=1; // Flag Motor en marcha
}
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Para PWM

void StopPWM(void)
{
PWMCON1 = 0x0700; // Salidas desactivadas
Motor.running=0; // Flag Motor parado
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Resetea fallo Driver motor
// Retorna 1 si se ha reseteado con exito

char RstDriver(void)
{
DRVRST=1;
//Wait(Delay_RST);
DRVRST=0;
if (!DRVFAIL)
{
Motor.fault=0; //Flag Motor en fallo
_FLTAIF = 0; //Borra Flag Interrupcion FLTA
_FLTAIE = 1; //Activa interrupciones FLTA
return 1;
}
else return 0;
}

Página 53 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Establece valor de Duty-Cycle
// DC: Valor de Duty-Cycle en % (+ giro directo, - giro inverso)

void SetDC(float DC)


{
if (DC>=0)
{
Motor.direction=0;
if (DC>100) DC=100;
}
else
{
Motor.direction=1;
DC=-DC;
if (DC>100) DC=100;
}
Motor.dcycle=DC;
PDC1 = (unsigned int)(DC*(FCY/FPWM)/50.0);
PDC2 = PDC1;
PDC3 = PDC1;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa encoder con control de fallo por interrupcion
// AttnIndxFunc: funcion de atencion a la interrupcion de paso por INDX (opci)

void IniQEI(void(*AttnFunction)())
{
EncIndxFn=AttnFunction; //Función de atención a la irq

ADPCFG |= 0x0038; // Configura QEI pins como digital

// QEICON - QEI Control Register


// x4 mode con reset de contador por contaje max
// Indicacion direccion en LED
QEICON = 0x0740;

// DFLTCON - Digital Filter Control Register


// Interrupcion error contaje activo
// Pulso Minimo=30/(RPM*IPR)=30/(4000*1000)= 7.5us
// Filtro = (FCY*Pulso Minimo)/3 => (14.75M*7.5u)/3=36.87
// 36.87 -> 1:32 -> Se filtran pulsos<6.5uS
DFLTCON = 0x00EE;

POSCNT = 0; // Inicializa Contaje


MAXCNT = IPR; // Contaje maximo

_QEIIP = 4; // Interrupcion prioridad 4


_QEIIF = 0; // Borra Flag Interrupcion QEI
_QEIIE = 1; // Activa interrupciones QEI

//INICIALIZA TIMER 1 PARA CALCULO VELOCIDAD


//Calculo de PR con prescalado a 8
unsigned int PRSPE=(SPESTM*FCY)/8;

T1CON = 0X0010; // TCY interno prescalado 8


TMR1 = 0; // Reset timer
PR1 = PRSPE;
_T1IF = 0; // Borra Flag Interrupcion T1
_T1IE = 1; // Activa interrupciones T1
T1CONbits.TON = 1; // Activa T1
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generadas ENCODER (POR CNTERR O INDEX)

void __attribute__((interrupt, no_auto_psv)) _QEIInterrupt (void)


{
if (_UPDN)
{
Sp_Acc_Revol++; //Incrementa revoluciones calc vel
Po_Acc_Revol++; //Incrementa revoluciones calc pos
}
else
{
Sp_Acc_Revol--; //Decrementa revoluciones calc vel
Po_Acc_Revol--; //Decrementa revoluciones calc pos
}

if (EncIndxFn) EncIndxFn();

_QEIIF = 0; //Borra Flag Interrupcion QEI

Página 54 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generadas T1
// Calcula velocidad instantanea RPM y la posicion en grad

void __attribute__((interrupt, no_auto_psv)) _T1Interrupt (void)


{
//Calculo velocidad en RPM
int PosInc=POSCNT-POSCNT_PRE+(Sp_Acc_Revol*IPR);
Motor.speed=(60.0*PosInc)/(SPESTM*IPR);
POSCNT_PRE=POSCNT;
Sp_Acc_Revol=0;

PosUpdate(); //Actualiza Info posicion

_T1IF = 0; //Borra Flag Interrupcion


}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Actualiza posicion absoluta Encoder

void PosUpdate(void)
{
float count;
//Calculo posicion en grados
Motor.ecount=POSCNT+(Po_Acc_Revol*IPR);
count=(float)Motor.ecount;
Motor.position=(360.0*count)/IPR;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Resetea posicion inicial Encoder

void RstQEI(void)
{
POSCNT = 0;
Po_Acc_Revol=0;
Motor.ecount=0;
Motor.position=0;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa Timer 4 para intermitencia (500 ms)

void iniTimerBlink(void(*AttnFunction)()){
//prescalado a 256
//tiempo muestreo 500 ms;

BlinkTmFn=AttnFunction; //Función de atención a la irq

T4CON=0x8030; // TCY interno prescalado 256


TMR4=0; // Reset timer
PR4=31250;
_T4IE = 1;
_T4IF = 0;
T4CONbits.TON = 1; // Activa T4
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generada por temporizador T4 (500 ms)

void __attribute__((interrupt, no_auto_psv)) _T4Interrupt (void)


{
if (BlinkTmFn) BlinkTmFn();
_T4IF = 0; //Borra Flag Interrupcion
}

Página 55 de 66
Proyecto Integrador

9.2 Librería lazo PID

9.2.1 Declaraciones PID.h


#define PIDMODE_MAN 0 //normal auto mode
#define PIDMODE_AUTO 1 //normal auto mode

#define PIDACTION_REV 0 //Accion Inversa


#define PIDACTION_DIR 1 //Accion Directa

#define TYPE_SPEED 0 //Control lazo velocidad


#define TYPE_POSIT 1 //Control lazo posición

#define SPEPIDTM 10 //Tiempo muestreo PID Veloc (ms)


#define POSPIDTM 10 //Tiempo muestreo PID Veloc (ms)

#define POSPMAX 36.0 //Escalado posición max 10 vueltas grados/%

typedef struct
{
float SP; //Setpoint %
float PV; //Process Value %
float MV; //Manipulated Value %

float KP; //Proporcional


float KI; //Integral (seg)
float KD; //Derivativa (seg)

float EUmin; //Unidades Ingen. min para PV y SP


float EUmax; //Unidades Ingen. max para PV y SP
float MVmin; //MV min en %
float MVmax; //MV max en %

float Tupdt; //Periodo actualizacion lazo (ms)


float Acc_err; //Error Acumulado
float Bias; //MV inicial
unsigned char action; //PID Accion
unsigned char mode; //PID Modo
} PID_DATA;

void IniSpeedLoop();
void PIDSetSP(float SP,PID_DATA *PID);
float PIDLoop(float PV,PID_DATA *PID);

void __attribute__((interrupt, no_auto_psv)) _T2Interrupt (void); //Atención de irq T2 PID

9.2.2 Código PID.c


#include "pid.h"
#include <xc.h>
#include "dsPICDEM_MC1.h"

PID_DATA SpeedLoop;
PID_DATA PositLoop;

extern MOTOR_INFO Motor;

char LoopType;

void IniSpeedLoop()
{
SpeedLoop.EUmax=4000.0;
SpeedLoop.EUmin=0.0;
SpeedLoop.MVmax=100.0;
SpeedLoop.MVmin=-100.0;

SpeedLoop.KP=1.0; //Proporcional
SpeedLoop.KI=0.5; //Integral (seg)
SpeedLoop.KD=0.0; //Derivativa (seg)

SpeedLoop.Tupdt=SPEPIDTM;
SpeedLoop.action=PIDACTION_REV;

SpeedLoop.mode=PIDMODE_MAN;
SpeedLoop.MV=0.0;
PIDSetSP(0.0,&SpeedLoop);
LoopType=TYPE_SPEED;

//INICIALIZA TIMER 2 PARA PID VELOCIDAD


//Calculo de PR con prescalado a 64

Página 56 de 66
Proyecto Integrador

unsigned int PR=(SPEPIDTM*FCY)/64000.0;

T2CON = 0X0020; // TCY interno prescalado 64


TMR2 = 0; // Reset timer
PR2 = PR;
_T2IF = 0; // Borra Flag Interrupcion T2
_T2IE = 1; // Activa interrupciones T2
T2CONbits.TON = 1; // Activa T2
}

void IniPositLoop()
{
PositLoop.EUmax=3700.0;
PositLoop.EUmin=-3700.0;
PositLoop.MVmax=8.0;
PositLoop.MVmin=-8.0;

PositLoop.KP=2; //Proporcional
PositLoop.KI=1.0; //Integral (seg)
PositLoop.KD=0.05; //Derivativa (seg)

PositLoop.Tupdt=POSPIDTM;
PositLoop.action=PIDACTION_REV;

PositLoop.mode=PIDMODE_MAN;
PositLoop.MV=0.0;
PIDSetSP(0.0,&PositLoop);
LoopType=TYPE_POSIT;

//INICIALIZA TIMER 2 PARA PID POSICION


//Calculo de PR con prescalado a 64
unsigned int PR=(POSPIDTM*FCY)/64000.0;

T2CON = 0X0020; // TCY interno prescalado 64


TMR2 = 0; // Reset timer
PR2 = PR;
_T2IF = 0; // Borra Flag Interrupcion T2
_T2IE = 1; // Activa interrupciones T2
T2CONbits.TON = 1; // Activa T2
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Establece valor de SP

void PIDSetSP(float SP,PID_DATA *PID)


{
float SPScale;

//Normalizamos SP en %
SPScale = 100*(SP - PID->EUmin)/(PID->EUmax - PID->EUmin);
//Acotamos límites
if (SPScale<0.0) SPScale=0.0;
if (SPScale>100.0) SPScale=100.0;

PID->SP=SPScale;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Lazo PID

float PIDLoop(float PV,PID_DATA *PID)


{
float Tp,Ti,Td;
float err,err_old;
float PVScale,MV;
char WINDUP=0;

//Normalizamos PV en %
PVScale=100*(PV - PID->EUmin)/(PID->EUmax - PID->EUmin);

//Acotamos límites
if (PVScale<0.0) PVScale=0.0;
if (PVScale>100.0) PVScale=100.0;

if (PID->mode == PIDMODE_AUTO)
{
//Calculo error
if (PID->action == PIDACTION_REV) //Accion Inversa
{
err=(PID->SP - PVScale);
err_old=(PID->SP - PID->PV);
}
else //Accion Directa
{

Página 57 de 66
Proyecto Integrador

err=(PVScale - PID->SP);
err_old=(PID->PV - PID->SP);
}

//Algoritmo PID
Tp= err*PID->KP;
Td=(1000.0 * (err-err_old) * PID->KD)/(PID->Tupdt);
//Anulacion Integral si es 0
if (PID->KI > 0.0) Ti=(PID->Acc_err * PID->Tupdt)/(1000.0 * PID->KI);
else Ti=0;

MV = Tp+Ti+Td+ PID->Bias;

//Control limites salida


if (MV < PID->MVmin)
{
MV=PID->MVmin;
if (err<0) WINDUP=1;
}
else if (MV > PID->MVmax)
{
MV=PID->MVmax;
if (err>0) WINDUP=1;
}
PID->MV = MV;

//Control Antisaturacion
if (!WINDUP)
{
PID->Acc_err += err;
}

}
else
{
PID->Acc_err=0;
PID->Bias=PID->MV;
}

PID->PV=PVScale;
return PID->MV;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generadas T2
// Lazo PID velocidad

void __attribute__((interrupt, no_auto_psv)) _T2Interrupt (void)


{
if (LoopType)
{
SetDC(PIDLoop(Motor.position,&PositLoop));
}
else
{
SetDC(PIDLoop(Motor.speed,&SpeedLoop));
}
_T2IF = 0; //Borra Flag Interrupcion
}

Página 58 de 66
Proyecto Integrador

9.3 Librería CAN

9.3.1 Declaraciones CAN.h

#include <xc.h>

#define CAN1RXCID C1RX0B1 //RX Datos CAN1 1º WORD CODE ID


#define CAN1RXDAT C1RX0B2 //RX Buffer Datos CAN1
#define CAN1RXLEN C1RX0DLCbits.DLC //RX Longitud datos CAN1
#define CAN1RXSID C1RX0SIDbits.SID //RX ID CAN1

void IniCAN(void(*AttnFunction)()); //Inicializa Bus CAN1


float ReadCANFloat(); //Lee dato tipo Float
int ReadCANInt(); //Lee dato tipo Int
long ReadCANLong(); //Lee dato tipo Long
char ReadCANChar(); //Lee dato tipo Char
void WriteCANFloat(unsigned int SID,unsigned int CODE,float value);
void WriteCANInt(unsigned int SID,int value);
void WriteCANLong(unsigned int SID,unsigned int CODE,long value);
void WriteCANChar(unsigned int SID,char value);
void iniTimerCAN(void); //Inicializa timer envio datos CAN

void __attribute__((interrupt, no_auto_psv)) _C1Interrupt (void); //Atención de irq C1 CAN1


void __attribute__((interrupt, no_auto_psv)) _T3Interrupt (void); //Atención de irq T3 envio datos CAN

9.3.2 Código CAN.c


#include <xc.h>
#include "dsPICDEM_MC1.h"
#include "PID.h"

extern MOTOR_INFO Motor;


extern PID_DATA PositLoop;

void (*CANFn)(); //Función de atención a la irq del Bus CAN

void IniCAN(void(*AttnFunction)())
{
CANFn=AttnFunction; //Función de atención a la irq

//INICIO REGISTROS CAN


C1CTRLbits.CANCAP=0; //Deshabilita módulo CAN
C1CTRLbits.CSIDL=0; //Continue CAN modile operation in IDLE mode
C1CTRLbits.ABAT=0; //No effect => No Abort pending transmissions bit
C1CTRLbits.CANCKS=1; //Fcan clock is Fcy

C1CTRLbits.REQOP=4; //Modo configuracion


while(C1CTRLbits.OPMODE!=4); //Espera modo configuracion

//Configuració del BitRate => Bit Rate = 500 000 bps


C1CFG1=0x0000; // Tq = 2/Fcan = 2/(16 MHz) = 1/(8 000 000 Hz) = 125 ns
C1CFG2=0x05B8; // Phase segment buffer 1 lenght = 8*Tq and Phase segment buffer 2 lenght = 6*Tq
=> Total = 16*Tq

// Mascaras del Buffer 0 de entrada


C1RXM0SIDbits.SID=0; // Mascara de aceptación, Acecta cualquier ID de recepcion
C1RXM0SIDbits.MIDE=1; // Tiene en cuenta EXIDE
C1RXF0SIDbits.EXIDE=0; // Acepta solo mensajes con ID estandar

// Transmision de salida
C1TX0CON = 3; // misstge d'altra prioritat
C1TX0EID = 0; // ID Mensaje estandard
C1TX0SIDbits.TXIDE = 0;
C1TX0SIDbits.SRR = 0;

// Interrupciones
IFS1bits.C1IF=0; // Borra Flag Interrupcion
IEC1bits.C1IE=1; // Habilitació d'interrupció del CAN 1
C1INTF=0; // Borra Flag Interrupcion
C1INTE=0xFF; // Habilitem les interrupcions

//Arranque CAN
C1CTRLbits.REQOP=0; // Modo Normal
}

Página 59 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Lee dato tipo Float del buffer de lectura CAN

float ReadCANFloat()
{
float *pfloat;
pfloat=&C1RX0B1;
return *pfloat;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe dato tipo Float del buffer de lectura CAN

void WriteCANFloat(unsigned int SID,float value)


{
float *pfloat;
pfloat=&C1TX0B1;

*pfloat=value;
C1TX0SIDbits.SID5_0= SID;
C1TX0SIDbits.SID10_6 = SID>>6;
C1TX0DLC=4<<3; //Longitud de datos 4 bytes
C1TX0CONbits.TXREQ=1;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Lee dato tipo Int del buffer de lectura CAN

int ReadCANInt()
{
int *pint;
pint=&C1RX0B1;
return *pint;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe dato tipo int del buffer de lectura CAN

void WriteCANInt(unsigned int SID,int value)


{
int *pint;
pint=&C1TX0B1;

*pint=value;
C1TX0SIDbits.SID5_0 = SID;
C1TX0SIDbits.SID10_6 = SID>>6;
C1TX0DLC=2<<3; //Longitud de datos 2 bytes.
C1TX0CONbits.TXREQ=1;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Lee dato tipo Char del buffer de lectura CAN

char ReadCANChar()
{
char *pint;
pint=&C1RX0B1;
return *pint;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe dato tipo char del buffer de lectura CAN

void WriteCANChar(unsigned int SID,char value)


{
int *pint;
pint=&C1TX0B1;

*pint=value;
C1TX0SIDbits.SID5_0 = SID;
C1TX0SIDbits.SID10_6 = SID>>6;
C1TX0DLC=1<<3; //Longitud de datos 1 bytes.
C1TX0CONbits.TXREQ=1;
}

Página 60 de 66
Proyecto Integrador

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Lee dato tipo long del buffer de lectura CAN

long ReadCANLong()
{
long *plong;
plong=&C1RX0B1;
return *plong;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Escribe dato tipo long del buffer de lectura CAN

void WriteCANLong(unsigned int SID,long value)


{
long *plong;
plong=&C1TX0B1;

*plong=value;
C1TX0SIDbits.SID5_0= SID;
C1TX0SIDbits.SID10_6 = SID>>6;
C1TX0DLC=4<<3; //Longitud de datos 4 bytes
C1TX0CONbits.TXREQ=1;
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generada por Bus CAN1

void __attribute__((interrupt, no_auto_psv)) _C1Interrupt (void)


{
if (C1INTFbits.TX0IF) C1INTFbits.TX0IF = 0;
if (C1INTFbits.TX1IF) C1INTFbits.TX1IF = 0;
if (C1INTFbits.TX2IF) C1INTFbits.TX2IF = 0;
if (C1INTFbits.RX0IF)
{
C1INTFbits.RX0IF = 0;
if (CANFn) CANFn();
C1RX0CONbits.RXFUL = 0;
}
if (C1INTFbits.RX1IF)
{
C1INTFbits.RX1IF = 0;
if (CANFn) CANFn();
C1RX1CONbits.RXFUL = 0;
}
if (C1INTFbits.WAKIF) C1INTFbits.WAKIF = 0;
if (C1INTFbits.ERRIF) C1INTFbits.ERRIF = 0;
if (C1INTFbits.IVRIF) C1INTFbits.IVRIF = 0;

C1INTF=0; // Borra Flag Interrupcion


IFS1bits.C1IF=0; // Borra Flag Interrupcion
}

//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Inicializa temporizador para envio datos a Matlab (cada 500 ms))

void iniTimerCAN(){
//prescalado a 256
//tiempo muestreo 500 ms;

T3CON=0x8030; // TCY interno prescalado 256


TMR3=0; // Reset timer
PR3=31250;
_T3IE = 1;
_T3IF = 0;
T3CONbits.TON = 1; // Activa T3
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Interrupcion Generada por temporizador T3 (500 ms)
// envia posicion actual al Matlab

void __attribute__((interrupt, no_auto_psv)) _T3Interrupt (void)


{
char pos=(Motor.position)/POSPMAX;
WriteCANChar(630,pos); //posicio
_T3IF = 0; //Borra Flag Interrupcion
}

Página 61 de 66
Proyecto Integrador

9.4 Rutina Principal

#include "PICSettings.h"
#include "dsPICDEM_MC1.h"
#include "PID.h"
#include "CAN.h"
#include <xc.h>
#include <dsp.h>

void PB_OnChange(char PBnum, char PBsts); //Función de atención a las irq de pulsadores
void Driver_Fail(); //Función de atención a la irq del Fallo Driver
void Encoder_Home(); //Función de atención a la irq de Encoder en INDX
void CANrx(); //Función de atención a la irq del CAN
void Blink(); //Función de atención a la irq del Timer Blink

extern MOTOR_INFO Motor;


extern PID_DATA SpeedLoop;
extern PID_DATA PositLoop;

float posSP=0.0;
int blinking=0;

int main(void)
{
SetupPorts(); //Configura puertos
IniPB(PB_OnChange); //Inicializa interrupcion pulsadores
IniLCD(); //Inicializa LCD (Driver HD44780)
IniHall(0); //Inicializa control posicion Hall
IniPWM(Driver_Fail); //Inicializa control PWM
IniQEI(Encoder_Home); //Inicializa Encoder
IniCAN(CANrx); //Inicializa CAN
IniPositLoop();
iniTimerCAN();
iniTimerBlink(Blink);

//Reset Driver
while(!RstDriver());

StartPWM();
PositLoop.mode=PIDMODE_AUTO;
RstQEI();

WriteTxtLCD("s xxxx.x o xxx.x", 0 , 0);


WriteTxtLCD("posic: xxxx.x g ", 0 , 1);

while (1)
{
if (!Motor.fault)
{
WriteNumLCD(posSP,6,1,2,0);
if (Motor.direction) WriteNumLCD(-1*Motor.dcycle,5,1,11,0);
else WriteNumLCD(Motor.dcycle,5,1,11,0);
WriteNumLCD(Motor.position,6,1,7,1);
}
}
return 0;
}

//*************************************************
void CANrx()
{
switch(CAN1RXSID){
case 620: //ID 620 correspon a comanda de consigna matlab
{
posSP=ReadCANChar()*POSPMAX;
PIDSetSP(posSP,&PositLoop);
break;
}
case 500: //Matlab demands LEDs ON or OFF
{
switch(ReadCANChar()){
case 0: //Luces apagadas
{
LED_D6=0;
LED_D7=0;
break;
}
case 1: //Luces cortas
{
LED_D6=1;
LED_D7=0;

Página 62 de 66
Proyecto Integrador

break;
}
case 3: //Luces cortas
{
LED_D6=1;
LED_D7=1;
break;
}
}
break;
}
case 550: //Matlab demands LED BLINKING
{
blinking=ReadCANChar();
break;
}
}
}

//*************************************************
void PB_OnChange(char PBnum, char PBsts)
{
if (PBsts==1)
{
switch(PBnum){
case 4:
{
RstDriver();
StartPWM();
PositLoop.mode=PIDMODE_AUTO;
break;
}
case 5:
{
break;
}
case 6:
{
break;
}
case 7:
{
break;
}

}
}

//*************************************************
void Blink()
{
switch(blinking){
case 0: //OFF
{
LED_D8=0;
LED_D9=0;
break;
}
case 1: //Both
{
if (LED_D8==0)
{
LED_D8=1;
LED_D9=1;
}
else
{
LED_D8=0;
LED_D9=0;
}
break;
}
case 2: //Right
{
LED_D9=0;
if (LED_D8==0) LED_D8=1;
else LED_D8=0;
break;
}
case 3: //Both
{
if (LED_D8==0)
{
LED_D8=1;

Página 63 de 66
Proyecto Integrador

LED_D9=1;
}
else
{
LED_D8=0;
LED_D9=0;
}
break;
}
case 4: //Left
{
LED_D8=0;
if (LED_D9==0) LED_D9=1;
else LED_D9=0;
break;
}
case 5: //Both
{
if (LED_D8==0)
{
LED_D8=1;
LED_D9=1;
}
else
{
LED_D8=0;
LED_D9=0;
}
break;
}
}
}

//*************************************************

void Driver_Fail(){} //Función de atención a la irq del Fallo Driver


void Encoder_Home(){} //Función de atención a la irq de Encoder en INDX

Página 64 de 66
Proyecto Integrador

10 Proposta per a futurs treballs

Per finalitzar, proposem per a futurs cursos d’aquesta assignatura una continuació del nostre
projecte. El nou projecte continuista es basaria en adaptar el bus CAN implementant el protocol
OBDII, per tal de poder-se comportar com un bus CAN real d’un cotxe. També proposem
realitzar un programa amb Matlab i Kvaser que pugui llegir els Parameter Identifiers (PIDs) dels
cotxes o fins i tot interaccionar amb ells. Existeixen productes similars en el mercat actualment
com el ELM327, que es pot comprar en preus que oscil·len dels 3€ als 15€. Aquests productes es
connecten al port OBDII dels cotxes i s’hi comuniquen amb ell, i serveis per detectar falles o
mostrejar la velocitat, les rpm del motor, la contaminació del vehicle...

A més a més, també proposem afegir un nou microcontrolador al bus CAN que permeti
connectar el bus sense cables, amb wifi, i inclús que aquest microcontrolador inclogui un
servidor web, permitint poder comunicar-se amb el bus CAN a través d’internet (enllaç amb
Ethernet sense fils). Per fer-ho proposem que s’utilitzi el microcontrolador ESP32, un
microcontrolador amb plaques de desenvolupament disponibles a preus molt competitius (5€) i
que inclou suport per a control CAN (caldria afegir un transceptor compatible amb 3.3V) que
només necessita un transceptor, com el MCP2515 per funcionar en una xarxa CAN.

La interacció del bus CAN sense fils i llur connexió a internet està actualment en
desenvolupament pels fabricants alemanys de cotxes de gama alta, en el protocol que han
anomenat OBDIII, on busquen que les ECU’s del cotxe conectades a la xarxa local CAN puguin
interaccionar amb l’exterior. OBDIII ha de lidiar amb grans problemes com la seguretat
informàtica. L’ESP32 també permet encriptar dades i té molt de suport en la comunitat a
internet.

També proposem continuar desenvolupant la part del motor BLDC, implementant el PID amb el
mètode experimental de Ziegler-Nichols, i mostrant per Matlab (enviat per CAN) la resposta a
una entrada esglaó i/o rampa, calculant els paràmetres d’error en contínua, temps
d’establiment, sobrepic... Així com també poder ajustar el PID de forma manual per l’usuari a
través de l’aplicació de Matlab i també implementar una auto-calibració del PID que s’ajusti a
uns valors de temps d’establiment, sobrepic i error en contínua proporcionats per l’usuari (en
aquest cas seria més fàcil fer-ho amb una simulació de Simulink i comparar el model teòric amb
les mesures reals).

11 Bibliografia

Referències bibliogràfiques:

“Control i Monitorització d’un motor elèctric auxiliar d’un vehicle”. Aleix Maixé Sas.

“A CAN Physical Layer Discussion”. Pat Richards, Microchip Technology Inc.

Referències web:

Página 65 de 66
Proyecto Integrador

https://en.wikipedia.org/wiki/CAN_bus

https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z0000019LzHSAU

https://barrgroup.com/Embedded-Systems/How-To/CAN-vs-SPI

https://www.csselectronics.com/screen/page/simple-intro-to-can-bus/language/en

https://www.csselectronics.com/screen/page/simple-intro-obd2-explained/language/en

http://ww1.microchip.com/downloads/en/DeviceDoc/70070D.pdf

Página 66 de 66

S-ar putea să vă placă și