bassam sandoussi hadik equipo de diagnÓstico para...
TRANSCRIPT
Bassam Sandoussi Hadik
EQUIPO DE DIAGNÓSTICO PARA VEHÍCULOS (OBD-II)
TRABAJO DE FIN DE GRADO
Esteban del Castillo Pérez
Ingenieria electrónica industrial y automática
Tarragona
2019
1
2
ÍNDICE
1. Introducción al proyecto .............................................................................................................. 4
1.1 Objetivo ............................................................................................................................. 4
1.2 Motivación ........................................................................................................................ 4
2. Introducción al sistema OBD2 .................................................................................................... 5
2.1 Historia de la electrónica en el automóvil ............................................................................ 5
2.2 ¿Que es el sistema OBD2? ..................................................................................................... 9
2.3 Historia del sistema OBD2 .................................................................................................... 9
3. El sistema OBD2 ......................................................................................................................... 12
3.1 Protocolos del sistema OBD2 .............................................................................................. 12
3.1.1 SAE J1850 PWM ............................................................................................................ 13
3.1.2 SAE J1850 VPW ............................................................................................................. 17
3.1.3 ISO 9141-2 ...................................................................................................................... 20
3.1.4 ISO 14230 ....................................................................................................................... 26
3.1.5 ISO 15765-1 .................................................................................................................... 28
3.2 Servicios del sistema OBD2 ................................................................................................. 40
4. Desarrollo del hardware ............................................................................................................ 48
4.1 Interfaz hardware del ISO 9141-2 ...................................................................................... 48
4.1.1 Uso de la UART .............................................................................................................. 48
4.1.2 El MC33290 .................................................................................................................... 50
4.1.3 Circuito de la interfaz ..................................................................................................... 53
4.2 Interfaz hardware del ISO 15765-4 .................................................................................... 54
4.2.1 EL MCP2515 .................................................................................................................. 54
4.2.2 Circuito de la interfaz del ISO 15765-4 ......................................................................... 68
4.3 Interfaz hardware entre el microcontrolador y el ordenador .......................................... 69
4.3.1 Descripción de la interfaz ............................................................................................... 69
4.3.2 Circuito de la interfaz ..................................................................................................... 70
4.4 El microcontrolador PIC18F4550 ....................................................................................... 71
4.4.1 Componentes externos del PIC18f4550 ......................................................................... 72
4.4.2 Configuración del oscilador ........................................................................................... 73
4.4.3 Periféricos del microcontrolador ................................................................................... 76
4.4.4 Circuito del microcontrolador ........................................................................................ 85
4.5 Circuito de alimentación ...................................................................................................... 85
4.5.1 Módulo Mini-360 ............................................................................................................ 85
3
4.5.2 Circuito del módulo ........................................................................................................ 86
4.6 Circuito completo ................................................................................................................. 87
5. Protocolo de comunicación entre el dispositivo OBD2 y el ordenador .................................. 90
6. Desarrollo del software .............................................................................................................. 95
6.1 Objetivo del software ........................................................................................................... 95
6.2 Proceso de desarrollo del software................................................................................ 96
6.3 Descripción del software ................................................................................................ 98
6.3.1 Funciones generales ....................................................................................................... 99
6.3.2 Funciones del ISO 9141-2 ........................................................................................... 101
6.3.3 Funciones del ISO15765-4 .......................................................................................... 107
6.3.4 Funciones main() y initProtocol() ............................................................................... 115
7. Aplicación de escritorio ........................................................................................................... 117
7.1 Desarrollo de la aplicación ................................................................................................ 117
7.2 Descripción de la aplicación .............................................................................................. 120
8. Construcción y test del prototipo ............................................................................................ 130
8.1 Construcción del prototipo ................................................................................................ 130
8.2 Test del prototipo ............................................................................................................... 130
9. Conclusiones ............................................................................................................................. 140
10. Webgrafía ................................................................................................................................ 142
11. ANEXO 1: Código del microcontrolador PIC18F4550 ...................................................... 143
12. ANEXO 2: Código de la aplicación de escritorio ................................................................ 190
Introducción al proyecto
4
1. Introducción al proyecto
1.1 Objetivo
Son dos los objetivos principales de este proyecto. De una parte, desarrollar un dispositivo
capaz de establecer una comunicación con el sistema OBD2 de un vehículo. De otra,
desarrollar una aplicación software capaz de hacer uso de manera eficiente y amigable de
esa comunicación. El propósito último es facilitar a un usuario no experto la interacción con
el sistema de control de un vehículo. Para poder cumplir estos objetivos se deberán
desarrollar los siguientes puntos:
• Entender la evolución de la electrónica en los automóviles desde sus inicios hasta el
día de hoy.
• Estudiar en profundidad el sistema OBD2.
• Obtener y estudiar la información necesaria para poder implantar los distintos
protocolos con los que se puede comunicar con el sistema OBD2 de un vehículo.
• Diseñar el hardware y el software para implementar los protocolos ISO 9141-2 y ISO
15765-1.
• Diseñar la aplicación de escritorio que permita interactuar de forma amigable con el
sistema OBD2 del vehículo.
1.2 Motivación
Actualmente vivimos en un mundo donde la tecnología se esta expandiendo en todos los
aspectos de nuestra vida. Un claro ejemplo son los vehículos que utilizamos en nuestro día
a día. Desde hace unas décadas observamos la tendencia de añadir cada vez más sistemas
electrónicos dentro de la arquitectura de los automóviles; esto ha permitido mejorar la
seguridad, la eficiencia y el confort. Dentro de estos sistemas se encuentran los encargados
de reducir las emisiones del vehículo y mantenerlas por debajo de los límites legales. Para
poder asegurar el correcto funcionamiento de los sistemas antipolución, los ingenieros
diseñaron el sistema OBD2 el cual permitia monitorizarlos y gobernarlos en cualquier
momento.
Aunque pocos sepan de la existencia de este sistema, los conductores interactuamos con el
frecuentemente ya que es este el que nos avisa cuando hay una avería.
Ante la gran importancia del OBD2 dentro de la industria del automóvil, surge el interés
personal en profundizar en el conocimiento del sistema OBD2 y por esta razón, he decidido
realizar este proyecto.
Introducción al sistema OBD2
5
2. Introducción al sistema OBD2
2.1 Historia de la electrónica en el automóvil
Si nos fijamos en cómo han evolucionado los automóviles desde su primera aparición
observamos grandes diferencias en general. Pero es en un aspecto muy concreto del
automóvil donde se destaca notablemente esta evolución, este es la incorporación de
sistemas electrónicos.
Figura 1: Interior de un coche de los años 60.
Figura 2: Interior de un coche actual.
Con el paso de los años se ha ido incrementando la importancia de los sistemas electrónicos
en los coches. La tendencia inicial en este proceso de cambio era la de sustituir los sistemas
mecánicos presentes en el coche por sistemas electrónicos. Esto ha permitido numerosos
beneficios tanto por parte de los fabricantes como para el consumidor final. Estos cambios
podían simplemente sustituir interruptores mecánicos por transistores. Un ejemplo de estos
cambios seria la sustitución del encendido por ruptor por un encendido con transistores. Este
Introducción al sistema OBD2
6
simple cambio mejoró la fiabilidad de este sistema ya que los interruptores mecánicos
estaban sometidos a altas tensiones que producían arcos y por lo tanto, con el tiempo los
interruptores se dañaban por la erosión y dejaban de funcionar. Este es solo un ejemplo de
los beneficios que ha aportado la inclusión de sistemas electrónicos en los coches.
La incorporación de la electrónica en los coches empezó a principios de los años 70, época
en la que la electrónica en sí no estaba tan desarrollada como la conocemos actualmente. Por
eso, al principio de la electrónica en el automóvil observamos sistemas electrónicos simples,
como el comentado anteriormente. Además, los costes de fabricación de los componentes
electrónicos eran elevados por lo que solo los coches de gamma alta llevaban incorporados
estos sistemas.
Con la evolución de la electrónica, se abarataron los costes de los componentes electrónicos
y se empezó a introducir más la electrónica en los coches. Por la diferencia de costes, los
fabricantes empezaron a preferir utilizar sistemas electrónicos frente a los tradicionales
sistemas mecánicos con lo que se abarató el preció de los coches. Por lo tanto, podemos decir
que la electrónica ha sido clave para disminuir los precios de los coches y hacerlos más
accesibles para el ciudadano medio.
Pero no ha sido hasta la aparición de la electrónica digital cuando realmente se ha producido
una revolución en la industria del automóvil. La electrónica digital permite diseñar sistemas
de control más complejos que los que permite la electrónica analógica.
Cuando hablamos de sistemas digitales no referimos principalmente al microcontrolador.
Este pequeño dispositivo ha revolucionado casi todos los aspectos de nuestra vida,
incluyendo el coche. Un microcontrolador nos permite tratar unos datos de entrada, que
pueden proceder de sensores, y actuar en base a esos datos sobre unas salidas, como pueden
ser los actuadores. Este dispositivo es programable, es decir, mediante un lenguaje de
programación podemos definir cual debe ser el comportamiento del dispositivo y cómo debe
tratar la información.
Figura 3: Ejemplo de microcontrolador.
Con el avance de los procesos de fabricación de la electrónica digital, la complejidad y
funcionalidad de los microcontroladores ha ido aumentando con el tiempo y esto ha
permitido diseñar y incorporar sistemas electrónicos verdaderamente útiles. El precio de
estos sistemas también ha ido disminuyendo la cual cosa ha supuesto una reducción en el
precio de los vehículos.
Muchos son los sistemas electrónicos que se han ido incorporando a lo largo de los años.
Estos sistemas forman parte de todas y cada una de las partes que conforman el automóvil:
sistema de propulsión, sistemas de seguridad, sistemas de confort…
Un coche moderno puede llevar entre 25 a 35 microcontroladores, y si son de lujo pueden
llevar entre 60 y 120.
Introducción al sistema OBD2
7
A continuación se presenta una lista de los principales sistemas electrónicos presentes
actualmente en los coches:
1. Seguridad
• Antibloqueo de ruedas.
Anti patinaje.
• Control dinámico de la trayectoria.
• Suspensión pilotada.
• Dirección asistida.
• Iluminación de xenón.
• Iluminación dinámica.
• Regulación de la limpieza.
• Indicador de mantenimiento.
• Vigilancia de la presión de los neumáticos. placa-ecu-con-lupa-guanaca
• Airbags y pretensores.
2. Confort
• Regulación adaptativa de la velocidad.
• Climatización automática.
• Memorización del puesto de conducción.
• Control de acceso sin llave.
• Cierre centralizado.
• Automatismo de limpieza e iluminación.
• Ayuda de aparcamiento.
3. Comunicación
• Audio.
• Vídeo.
• Ordenador de a bordo.
• Mandos vocales.
• Telefonía móvil.
• Navegación.
• Pantalla centralizada.
• Visualización en el parabrisas.
4. Grupo moto propulsor
• Gestión motor gasolina con regulación de inyección, encendido, riqueza y
devolución.
• Regulación electrónica diésel con regulación del caudal y del comienzo de inyección.
• Gestión motor diésel con rampa común.
• Gestión electrónica de la caja de velocidades (automática, manual robotizada, por
correa).
• Gestión electrónica de la refrigeración motor.
Introducción al sistema OBD2
8
• Gestión electrónica de la sobrealimentación.
• Motorización híbrida.
En la siguiente figura se muestra el momento en el que aparece cada sistema.
Figura 4: Diagrama temporal de la aparición de los sistemas electrónicos más comunes en
los coches.
Analizando el diagrama anterior se puede observar que a medida que avanza el tiempo
aparecen sistemas más complejos. Esto va acorde con lo comentado anteriormente, los
avances en la electrónica han permitido la aparición de sistemas más complejos.
Es evidente que la electrónica ha mejorado notablemente el mundo del automóvil, de otra
forma su evolución dentro de esta industria no habría sido la misma. Estos sistemas han
mejorado desde la seguridad hasta la comodidad. La electrónica ha permitido implementar
sistemas de seguridad activa (ESP, ABS…) y de seguridad pasiva (Airbag, ) que han hecho
que actualmente la peligrosidad de conducir un coche sea 200 veces menor que en 1960.
Además, con la sustitución del carburador por la inyección electrónica la eficiencia de los
vehículos se ha visto mejorada. Estas son algunas de las mejoras que ha llevado la electrónica
al automóvil.
Introducción al sistema OBD2
9
2.2 ¿Que es el sistema OBD2?
Desde hace unas décadas, la electrónica del automóvil está constituida por un conjunto de
sistemas electrónicos independientes (cada uno con una función) distribuidos por distintas
zonas del coche. Estos sistemas están conectados a través de un bus de comunicaciones
permitiendo así el intercambio de información.
Figura 5: Diagrama del sistema de comunicación interno de un coche.
Como se ha mencionado en el apartado anterior, estos sistemas están basados en un
microcontrolador. Un sistema microcontrolador básico consta de sensores, un
microcontrolador y actuadores. Estos sensores miden una determinada variable y
proporcionan su valor al microcontrolador. Dentro del automóvil, estos sensores suelen ser
de temperatura, de velocidad (angular/lineal), de presión, de luz, de oxigeno, de CO2… Hay
multitud de datos en tiempo real sobre los parámetros que envuelven el conjunto de sistemas
del vehículo. Estos datos son necesarios para que los propios sistemas funcionen
correctamente, y por lo tanto, para que el coche funcione correctamente. Sin embargo, estos
datos también podrían ser útiles para las personas ya que a partir de ellos se puede llegar a
conocer el estado del vehículo, es decir, se puede hacer una diagnosis.
De aquí surge la idea de incorporar un sistema que permita extraer del vehículo esta
información para que así sea tratada y con ella realizar un diagnosis de los distintos sistemas
del coche. Este sistema es el sistema OBD (On board diagnostics) el cual es el precursor del
OBD2.
2.3 Historia del sistema OBD2
Anterior a la incorporación del sistema OBD en los coches los especialistas solo tenían una
forma para detectar y diagnosticar una avería. Los mecánicos utilizaban todos sus sentidos
para averiguar cual era la avería: intentaban escuchar ruidos extraños, percibir olores
extraños etc.
Introducción al sistema OBD2
10
Con el avance de la tecnología, aparecen los indicadores analógicos (medidores) los cuales
permitían obtener algunas mediciones como puede ser la temperatura del motor o la presión
del aceite.
Pero no ha sido hasta la aparición del sistema OBD en el 1968 cuando realmente se podía
detectar y diagnosticar una avería sin la necesidad de abrir el capó del vehículo. El sistema
OBD ha permitido realizar diagnósticos utilizando únicamente los datos obtenidos del
conjunto de sensores integrados en el vehículo.
Con la integración de la electrónica en los vehículos, el número de sensores en un coche se
ha ido incrementando con el tiempo. En la actualidad un vehículo moderno puede llegar a
tener hasta 50 sensores, muchos más si se trata de un coche de lujo. Estos proporcionan a los
propios sistemas del vehículo la información necesaria para poder funcionar. Algunos de
estos sensores son: flujo de aire de entrada (sensores MAF), sensor de concentración de O2
(sonda lambda), sensor de presión de combustible etc.
El sistema OBD tiene acceso a toda esta información, por lo que se ha convertido en un
excelente aliado para el diagnóstico de los vehículos.
El motivo que realmente llevó a la implementación del sistema OBD a tan gran escala, no
ha sido principalmente la facilidad que da para el diagnostico de los vehículos, sino que se
trata de un tema de índole ecológica.
En los años 80, se produjo un despertar de conciencia mundial en torno a los temas
ecológicos y ambientales. La población se empezó a preocupar por la contaminación
ambiental que ocasionaban los vehículos de combustión. Este preocupación se veía
fortalecida por temas como el deterioro de la capa de ozono así como el calentamiento global.
Ante este movimiento social, el gobierno del estado del california (EEUU ) se vio obligado
a implementar una serie de regulaciones que afectaban a las emisiones de los vehículos
fabricados a partir del año 1968. Estas regulaciones se extendieron por todo el país en 1968.
Con el objetivo de asegurar el cumplimiento de estas regulaciones, el congreso aprobó el
Clean Air Act (Acta Anti polución) en 1970 y creo la EPA (Enviromental Protection
Agency). La EPA promovió una serie de estándares en la emisión de gases.
Para asegurar el cumplimiento de estas regulaciones, los fabricantes empezaron a añadir
sistemas de control y reducción de emisiones como puede ser el convertidor catalítico,
introducido en el año 1970. Cuando estos sistemas de control empezaron a incorporar
electrónica, los fabricantes de coches emperezaron a integrar el sistema OBD para poder
monitorear estos sistemas de control y asegurar su correcto funcionamiento. Es esta la razón
principal por la que los vehículos empezaron a integrar el sistema OBD.
Tan necesaria fue la implementación del sistema OBD para asegurar el correcto
funcionamiento de los sistemas de control y reducción de emisiones que un organismo del
gobierno de california (CARB, Californian Air Resources Board) sacó en el los años 80 una
ley que obligaba a los fabricantes a integrar el sistema OBD en sus vehículos.
El sistema OBD no solo permitía monitorear los datos obtenidos de los sensores integrados
en los sistemas de control y reducción de emisiones sino que también realizaba
continuamente un autodiagnóstico a partir de los datos de los sensores. Si el sistema
detectaba que había una magnitud que se salía de su rango normal, lo indicaba a través de
un registro mediante un código de avería (DTC, diagnostic trouble codes). Mediante los
códigos DTC se codificaba la información relacionada con la avería: el sistema donde se ha
producido, el sensor que la ha detectado… Además, cuando el sistema OBD detectaba una
avería, este lo indicaba a través de la luz MIL en el cuadro de instrumentos(Figura 6).
Introducción al sistema OBD2
11
Figura 6: Coche con la luz MIL encendida.
Cuando se producía una avería, el propietario del vehículo se percataba mediante la luz
MIL y llevaba el coche a un taller. El técnico especialista obtenía información especifica
del problema a través del registro de averías y mediante esta información podía llegar a la
pieza averiada para repararla o sustituirla.
Esto permitió, entre otras cosas, asegurar el funcionamiento continuo de los sistemas de
control y reducción de emisiones y por ende, mantener las emisiones por debajo de los
limites que marcaba la ley.
Pero la primera versión del sistema OBD no era todo ventajas, también había ciertos
inconvenientes que eran principalmente fruto de lo poco desarrollada que estaba esta
tecnología entonces. Uno de los inconvenientes es que cada fabricante codificaba las averías
de forma distinta, por lo que los talleres debían conocer los códigos de cada fabricante.
También había un problema con el conector utilizado para acceder al sistema OBD y es que
cada fabricante utilizaba uno distinto. Esto requería a los talleres disponer de una
herramienta de enlace para cada fabricante.
Otro inconveniente es que esta versión del OBD solo monitorizaba algunos sistemas
relacionados con la emisión de gases.
Con el paso de los años, los problemas anteriormente mencionado fueron desapareciendo
gracias a la aparición de distintos estándares. En 1988 la SAE (Society of Automotive
Engineers) definió un conector estándar OBD y un conjunto de códigos de avería.
El sistema OBD-2 empezó a sustituir completamente el OBD cuando la segunda versión
pasó a ser obligada para los vehículos fabricados a partir del 1996.
En el caso de Europa, el OBD-2 se conoció como el EOBD y paso a ser obligatorio a partir
del 2001.
A parte de la ventaja que supone ser un sistema estandarizado, el OBD-2 era mucho más
efectivo que el OBD ya que abarcaba más sistemas y detectaba tanto fallos eléctricos como
químicos o mecánicos.
El sistema OBD2
12
3. El sistema OBD2
3.1 Protocolos del sistema OBD2
Dentro de la arquitectura de un automóvil actual el sistema OBD2 se comunica con los
distintos sistemas del vehículo, y al mismo tiempo puede establecer una conexión con un
equipo de test externo. En términos simplificados, esta comunicación entre dos puntos
permite lo siguiente:
• El sistema OBD2 puede realizar un autodiagnóstico completo de todos los sistemas
de los cuales dispone información (sistemas con los que se comunica)
• Mediante un equipo externo podemos conectarnos al sistema OBD2 y obtener datos
en tiempo real de todos los sistemas a los que está conectado el OBD2. Además, se
puede ver cualquier avería detectada por la auto diagnosis.
Actualmente el sistema OBD2 que incorporan los fabricantes a sus vehículos no está
limitado a la obtención de datos sino que también permite realizar ajustes para modificar
algunas funcionalidades del vehículo. También permite llevar un determinado sistema al
modo de prueba en el cual se realiza una serie de testeos para comprobar su funcionamiento.
El sistema OBD2 permite incluso acceder a los parámetros del motor y ajustarlos para
modificar el comportamiento del vehículo. Aunque los fabricantes incorporan aplicaciones
propias, lo hacen sin alterar lo esencial de este sistema por lo que se mantiene estandarizado.
Como el objetivo de este proyecto es el diseño de un equipo de test, nos interesa saber cómo
el sistema OBD2 se comunica con los equipos externos, es decir, debemos estudiar los
protocolos involucrados.
No todos los fabricantes utilizan el mismo protocolo en sus vehículos ya que existen distintos
protocolos dentro del estándar. Los protocolos que hay para la comunicación entre el equipo
de test y el sistema OBD2 son los siguientes:
• SAE J1850 PWM
• SAE J1850 VPW
• ISO 9141-2
• ISO 14230-1
• ISO 15765-1
En los siguiente apartados se explicará cada uno de los protocolos siguiendo el modelo
OSI.
El sistema OBD2
13
3.1.1 SAE J1850 PWM
Este estándar fue definido por la SAE (Sociedad de Ingenieros de Automoción) en 1994.
Actualmente solo es utilizado por la marca Ford por lo que no es un estándar muy común
dentro de la industria del automóvil. Este protocolo permite una tasa de transferencia de
hasta 41,6 Kbps.
3.1.1.1 Capa Física
La capa física de este estándar se rige por el protocolo SAE J1850.
Topología de red
En el estándar SAE J1850 PWM se define un sistema de comunicación multipunto ya que
se pueden conectar varias ECUs (Electronic control units) y el equipo de test externo. Todos
los nodos de este sistema están conectados a un mismo medio a través del cual se comunican,
por lo tanto, la topología de red es BUS.
El siguiente diagrama muestra cómo es la conexión física:
Figura 7: Topología de red tipo BUS.
Medio de transmisión
En este sistema de comunicación los nodos se conectan a 2 hilos que son los que forman el
BUS. Son dos hilos ya que el señal que se transmite es un señal diferencial.
El medio físico de transmisión consiste en un par trenzado.
Codificación
Este estándar utiliza la técnica PWM (pulse-width modulation) para generar la señal eléctrica
que codifica la información a transmitir.
El sistema OBD2
14
La técnica PWM consiste en generar una onda cuadrada de periodo constante y cicle de
trabajo variable. El ciclo de trabajo es la relación entre el tiempo en estado alto y el periodo
de la señal:
𝐷 =𝑇𝑜𝑛
𝑇 (1)
Por lo tanto, al variar el ciclo de trabajo y mantener el mismo periodo estamos variando el
tiempo en estado alto del señal.
Figura 8: Señales cuadradas con distintos ciclos de trabajo.
Este estándar codifica el estado lógico de un bit mediante el ciclo de trabajo de la señal
cuadrada que se transmite. Cada ciclo de trabajo corresponde a un nivel lógico concreto. Un
ciclo de trabajo alto corresponde a un 1 y un ciclo de trabajo bajo corresponde a un 0.
En cada periodo de la señal PWM se transmite un bit. El tiempo de bit es el periodo de la
señal y por lo tanto, la tasa de bits es la frecuencia de la misma. En este caso al tener una
tasa de bits de 41,6 kbps sabemos que la frecuencia de la señal cuadrado es de 41,6 kHz.
En este bus el nivel lógico dominante es el 0.
Sincronismo de bit
En este sistema de comunicación no hay presente una señal de reloj que sincronice los nodos
que se están comunicando, por lo tanto, se trata de una comunicación asíncrona. El nodo
receptor conoce el tiempo de bit por lo que puede identificar cuando se ha dejado de
transmitir la información de un bit y se ha pasado a transmitir el siguiente bit.
El sistema OBD2
15
Serie/ paralelo
Tal y como se ha mencionado, los nodos de este sistema utilizan una señal diferencial para
transmitir la información. Como solo hay una única señal, solo se puede transmitir la
información de un bit a la vez, por lo tanto, se trata de una comunicación serie.
Pines y conector
Este sistema de comunicación requiere de un conector que suministre alimentación al equipo
de test y también que lo conecte al bus. Por lo tanto, debe tener los siguientes pines:
• BUS +
• BUS –
• 12 v
• Tierra
El conector del protocolo SAE J1850 PWM viene definido por el estándar SAE J1962. En
la siguiente figura se muestra la forma del conector así como la posición de cada uno de los
pines.
Figura 9: Conector estándar del sistema obd2.
Este mismo conector es utilizado por los otros protocolos y por eso tiene más pines de los
mencionados.
El sistema OBD2
16
3.1.1.2 Capa enlace
La estructura de la trama se puede observar en la siguiente figura:
Figura 10: Formato de trama del SAE J1850 PWM
A continuación se explicará la función de cada uno de los campos que forman la trama.
• SOF (Start Of Frame): indica el inicio de una trama. El SOF consiste en un pulso
de 200 us de ancho.
• Header: Tiene un tamaño de 3 bytes y contiene la siguiente información (en orden):
- Prioridad (3 bits)
- Longitud de cabecera (1 bit)
- IFR (In frame response) (1 bit)
- Modo de direccion (1 bit)
- Tipo de mensaje (2 bits)
- Direccion nodo destino (1 byte)
- Dirección nodo origen (1 byte)
• Datos: Máximo 7 bytes.
• CRC (Cyclic Redundance Check): 1 byte, esta información sirve para detectar
errores en la trama.
• EOF (End Of Frame): indica el final de la trama.
El sistema OBD2
17
El valor de algunos campos de la trama ya están definidos por el propio estándar tal y como
se puede observar en la siguiente tabla:
Tabla 1: Valores estándar de algunos campos de la trama.
ECU addr es la dirección de la ECU con la que el tester se está comunicando
3.1.2 SAE J1850 VPW
3.1.2.1 Capa Física
La capa física de este estándar se rige por el protocolo SAE J1850.
Topología de red
Al igual que el SAE J1850 VPW este protocolo también tiene una topología de red tipo BUS.
Todos los nodos de este sistema de comunicación están conectados al bus que es el medio
físico a través del cual se transmite la información.
Medio de transmisión
En este caso el bus lo forma un único hilo ya que la señal que se transmite esta referida a
masa. El medio físico que constituye el bus es un cable unipolar con aislamiento.
Codificación de bit
En el protocolo SAE J1850 VPW el bus puede estar en dos estados distintos: estado activo
y estado pasivo. Los símbolos empleados para codificar los bits son distintos según el estado
en el que esté el bus.
Si el bus está en estado activo el nivel lógico 0 se transmite manteniendo el bus a un potencial
alto (de 4,25 V a 20 V) durante un tiempo de 128 us y el nivel lógico 1 se transmite
manteniendo el bus a un potencial alto durante un tiempo de 64 us.
Si el bus está en estado pasivo el nivel lógico 0 se transmite manteniendo el bus a un
potencial bajo (menos de 4,25 V) durante un tiempo de 64 us y el nivel lógico 1 se transmite
manteniendo el bus a un potencial bajo durante un tiempo de 128 us.
El sistema OBD2
18
El bus utiliza una resistencia de pull-down la cual lleva el bus a un nivel de tensión bajo. Los
nodos conectados disponen de un driver que puede llevar la tensión del bus a un nivel alto.
Un nivel de tensión alto domina sobre uno bajo por lo que el nivel lógico dominante es el 0
(tanto si el bus es activo como pasivo).
En la siguiente figura se ilustra como se codifican los bits en el bus:
Figura 11: Codificación de los niveles lógicos.
Sincronismo de bit
La comunicación es asíncrona ya que, al igual que con el anterior protocolo, no se dispone
de un seña de reloj que sincronice los nodos.
Serie/ paralelo
La información se transmite bit a bit ya que el bus solo esta formado por un único hilo. Por
lo tanto, se trata de una comunicación serial.
Pines y conector
Este sistema de comunicación requiere de un conector que subministre alimentación al
equipo de test y también que lo conecte al bus. Por lo tanto, debe tener los siguientes pines:
• BUS +
• 12 v
• Tierra
El conector del protocolo SAE J1850 VPW viene definido por el estándar SAE J1962. En la
siguiente figura se muestra la forma del conector así como la posición de los pines:
El sistema OBD2
19
Figura 12: Conector estándar del sistema obd2.
Este mismo conector es utilizado por los otros protocolos y por eso tiene más pines de los
mencionados.
3.1.2.2 Capa enlace
La capa de enlace de este protocolo es la misma que la del SAE J1850 PWM. La única
diferencia se encuentra en el valor que toman algunos campos.
Para este proyecto se aplica la siguiente tabla:
Tabla 2: Valores estándar de algunos campos de la trama.
ECU addr es la dirección de la ECU con la que el tester se esta comunicando
El sistema OBD2
20
3.1.3 ISO 9141-2
Este protocolo está basado en la norma RS232 la cual es muy conocida dentro del mundo de
la informática ya que históricamente se ha empleado en muchos dispositivos. Algunas de
estos dispositivos han sido impresoras, módems, Ordenadores, cámaras PTZ (pan-tilt-
zoom)…
Dentro de la industria del automóvil, este protocolo es muy popular entre los fabricantes
europeos (Sobre todo el grupo VAG) y asiáticos. El ISO9141 permite una velocidad de
transferencia de 10400 baudios.
3.1.3.1 Capa Fisica
Topología de red
Al igual que en los anteriores protocolos, la topología de red del ISO9141 es del tipo BUS.
Las distintas ECU’s y el equipo de test se conectan a un bus formado por un solo hilo. Este
hilo recibe el nombre de línea K. Este estándar también define otro hilo denominado línea
L. La función de la línea L no es la de transmitir la información sino que sirve solamente
para inicializar la comunicación entre el tester y una de las ECU’s. Habitualmente se suele
utilizar la propia línea k para realizar la inicialización por lo que la línea L queda en desuso
y pocos fabricantes la implementan.
En la siguiente figura se puede ver como están conectados los distintos nodos en el bus
(tester + ECUs):
Figura 13: Conexión del equipo de test al bus.
La forma en la que están conectados los nodos permite que todos puedan comunicarse con
todos pero, como el bus esta formado por una única línea, solo se puede transmitir
información en un sentido a la vez. Es decir, el modo de envió de información es semidúplex
(bidireccional pero no simultaneo).
El sistema OBD2
21
Cuando el tester requiere obtener un determinado dato del vehículo (velocidad, temperatura
refrigerante, carga del motor…) este debe transmitir una trama a la ECU correspondiente.
Después de un tiempo de la transmisión de la trama, la ECU responde con otra trama con los
datos que se han pedidos.
El flujo de información es siempre el siguiente: primero el tester envía una trama de request
(para pedir un dato) y un tiempo después, le ECU responde con otra trama (o tramas) de
respuesta. Es decir, quien inicia la comunicación siempre es el tester y la ECU solo responde
a las peticiones hechas por el tester.
Medio de transmisión
El bus lo constituye un solo hilo por lo que el medio de transmisión es un conductor
unipolar con aislamiento.
Codificación de bit
La forma en la que se codifican los bits en este protocolo es muy sencilla ya que no se utiliza
ningún tipo de modulación. El valor del bit que se está transmitiendo depende directamente
del nivel de tensión del bus. Si el bus esta a un potencial alto (70 - 100 % de Vbat) el bit
significa que se esta transmitiendo un 1 lógico, en cambio, si esta a un potencial bajo (0 –
30 % Vbat) significa que se esta transmitiendo un 0 lógico. El nivel lógico del bus cuando
esta inactivo (ningún nodo esta transmitiendo ) es 1.
El bus es llevado a un potencial alto mediante una resistencia pull-up. Cada nodo conectado
al bus dispone de un driver (tipo open-collector) que le permite conectar el bus a tierra, con
lo que se consigue llevarlo a un potencial bajo. El esquema se muestra en la siguiente figura:
Figura 14: Driver tipo open-collector.
El nivel de tensión dominante es el bajo, por lo tanto, el nivel lógico dominante es el 0.
Sincronismo de bit
Este estándar establece una comunicación asíncrona.
El sistema OBD2
22
Serie/paralelo
La información se transmite bit a bit, es decir, es una comunicación serial.
Influencia del RS232
Tal y como se ha mencionado anteriormente, este protocolo nace del RS232. Esto implica
que el ISO 9141 comparte algunos aspectos con el RS232.
Cuando se quiere transmitir una trama con el ISO 9141 esta no se transmite de forma seguida
sino que cada byte de la trama se envía por separado y encapsulado en otra trama.
Esta trama tiene la siguiente forma:
Figura 15: Trama del RS-232.
En primer lugar se transmite el bit de Start (un 0), posteriormente se transmiten el byte de
data y finalmente se transmite el bit de stop (un 1). Esta configuración recibe el nombre de
8N1 (sin bit de paridad y 1 bit de stop). Los bits del byte de data se envían empezando por
el bit de menos peso (LSB).
Pines y conector
Este sistema de comunicación requiere de un conector que subministre alimentación al
equipo de test y también que lo conecte al bus. Suponiendo que la línea L no se utiliza, se
requieren los siguientes pines:
• Línea k
• 12 v
• Tierra
El sistema OBD2
23
El conector del protocolo ISO 9141-2 viene definido por el estándar SAE J1962. En la
siguiente figura se muestra la forma del conector así como la posición de los pines:
Figura 16: Conector estándar del sistema obd2.
Este mismo conector es utilizado por los otros protocolos y por eso tiene más pines de los
mencionados.
3.1.3.2 Capa Enlace
En este apartado se describirá la trama con la que trabaja el estándar ISO 9141-2.
Figura 17: Formato de trama del ISO 9141-2
• Campos del Header (cabecera)
o Priority/Type: indica el tipo de trama (1 byte).
o Target adress: indica la dirección del nodo destino (1 byte)
o Source adress: indica la dirección del nodo origen (1 byte)
• Data bytes: datos que se quieren transmitir (hasta 7 bytes)
• Checksum: El checksum sirve para la detección de errores (1 byte)
El sistema OBD2
24
El valor que toman los bytes de la cabecera dependen de si la trama se envía del tester a la
ECU o de la ECU al tester. La siguiente tabla muestra los valores para cada caso:
Tabla 3: Valores que toman algunos campos.
La trama anteriormente descrita solo permite transmitir un máximo de 7 bytes. Pueden haber
casos en los que se requiera transmitir más de 7 bytes, sobre todo si se trata de una trama
respuesta de la ECU. Por ejemplo, cuando pedimos el número VIN (Vehicle identification
number) del vehículo la ECU debe transmitir un total de 24 bytes de datos (tamaño típico
del número VIN).
En estos casos la ECU simplemente envía tantas tramas como sean necesarias para transmitir
los n bytes de datos.
Proceso de inicialización
Antes de poder intercambia información con una determinada ECU, el tester debe
inicializarla. El proceso de inicialización esta descrito dentro del estándar y es el siguiente:
En primer lugar el tester debe transmitir a través de la línea k el byte 0x33 a una velocidad
de 5 baudios. La señal transmitida se debería ver de la siguiente forma:
Figura 18: Transmisión del byte 0x33
Como la tasa de bits es muy pequeña, el tiempo de transmisión de este byte es de 2 segundos.
Después de un tiempo conocido como W1 (entre 20 y 300 ms) la ECU responde con el byte
0x55 a una velocidad de 10,4 kbps. A partir de ahora la comunicación se debe realizar a
10400 baudios. Posteriormente el vehículo espera un tiempo W2 (entre 5 y 20 ms) para que
el tester pueda reconfigurar los baudios. Pasado el tiempo W2 la ECU transmite los bytes
0x08 y 0x08 o 0x94 y 0x94 (según el vehículo) con una separación entre ellos de un tiempo
W3 (entre 0 y 20 ms), estos bytes se conocen como Key Bytes. Para confirmar la recepción
de estos dos últimos bytes, el tester debe invertir y transmitir el Key Byte #2 después de un
tiempo W4 (entre 25 y 50 ms). Finalmente la ECU responde con el byte 0x33 invertido,
El sistema OBD2
25
llegado a este punto la ECU se encuentra correctamente inicializada y se puede empezar a
enviar las tramas de request transcurrido tras la espera de un tiempo P3 (entre 55 y 5000 ms).
Cabe destacar que si la ECU no recibe ninguna trama en el transcurso de 5 segundos esta se
pone al estado previo a su inicialización, es decir, deja de aceptar peticiones. Para evitar esto
el tester debe estar continuamente enviando tramas durante el tiempo que queremos
mantener la sesión. Hay que tener en cuenta que una vez recibida la respuesta de una petición
no se puede enviar una nueva petición inmediatamente después, se debe esperar un tiempo
P3 (entre 55 y 5000 ms) antes de enviar una nueva petición.
En la siguiente figura se puede apreciar el proceso de inicialización de la ECU:
Figura 19: Representación del proceso de inicialización.
Los tiempos W1, W2, W3, W4 Y P3 son los descritos anteriormente.
Hay otros tiempos que se deben respetar para asegurar una comunicación satisfactoria. Estos
tiempos se reúnen en la siguiente tabla:
Parámetro Tiempo
Min
Tiempo
Max
Descripción
P1 0 ms 20 ms Tiempo entre bytes enviados desde
la ECU al tester
P4 5 ms 20 ms Tiempo entre bytes enviados desde
el tester a la ECU
Tabla 4: Tabla de tiempos que se deben respetar.
El sistema OBD2
26
3.1.4 ISO 14230
Este protocolo es muy parecido al ISO 9141-2 ya que comparten la misma capa física, las
únicas diferencias se encuentran en la capa enlace. El ISO 14230 es comúnmente utilizado
entre los fabricantes asiáticos y permite una velocidad de transferencia de 10,4 kbps (igual
que ISO 9141-2). Se suele conocer también con el nombre KWP2000 (keyword protocol
2000).
3.1.4.1 Capa Física
La capa física de este protocolo es la misma que la del ISO 9141-2.
3.1.4.2 Capa Enlace
La trama con la que trabaja este protocolo es la misma que en el caso del ISO 9141-2. La diferencia
radica en los valores que toman los campos de cabecera. Estos se describen en la siguiente tabla:
Tabla 5: Valores que toman algunos campos.
Tal y como se puede ver, cambian los campos target adress y source adress de ambas tramas (ECU
a tester y tester a ECU). Pero la diferencia principal radica en el campo Priority/Type. Mientras que
en el ISO 9141-2 este campo tomaba un valor fijo (0x68 o 0x48) en este caso los 6 bits de menos
peso representan el tamaño del campo de bits, es decir, podemos indicar dentro de este campo
cuantos bytes de datos se enviarán. Este pequeño cambio supone una mejora muy significativa ya
que con el ISO 9141-2 el nodo receptor no podía conocer cuantos bytes iba a recibir, por lo que se
complicaba un poco la recepción de las tramas.
Proceso de inicialización
En el caso de este protocolo disponemos de dos métodos de inicialización de las ECUs. Estos
métodos reciben el nombre de KWP 5-baud init y KWP fast init.
• KWP 5-baud init: Este procedimiento es idéntico al que se emplea en el ISO 9141-
2. La única diferencia radica en los Key Bytes recibidos desde la ECU, en este caso
son los byes 0x8F y 0xE9.
El sistema OBD2
27
• KWP fast init: Este procedimiento solo es soportado por el protocolo KWP2000.
En este caso la comunicación referente a la inicialización se realiza completamente
a 10400 baudios por lo que resulta un método más rápido que los anteriores. El
procedimiento se explica en el siguiente apartado.
KWP fast init
Esta inicialización empieza con el tester transmitiendo un patrón de “wake-up” seguido de
una petición de inicio de comunicación. El patrón de “wake-up” consiste en llevar el bus a
un nivel bajo durante 25 ms y después llevarlo a un nivel alto durante otros 25 ms. Una vez
enviada la petición de inicio de comunicación la ECU debería transmitir una respuesta de
inicio de comunicación.
La trama para la petición del inicio de comunicación es la siguiente:
<0xC1>< 0x33><0xF1><0x81><0x66>
Esta trama sigue el formato anteriormente descrito por lo que se puede desglosar de la
siguiente forma:
• 0xC1: Es el campo de priority/type. Los 6 bits de menos peso representan la longitud
del campo de datos, por lo tanto, esta trama contiene 1 byte de datos
• 0x33: Este byte es la dirección del nodo destino(ECU)
• 0xF1: Este byte es la dirección del nodo origen(Tester)
• 0x81: Es el byte de datos. El valor 0x81 es el ID para la petición de inicio de
comunicación
• 0x66: es el campo de checksum.
La trama con la que responde la ECU es la siguiente:
<0x83><0xF1><0x10><0xC1><0x8F><0xE9><0xBD>
En este caso se puede desglosar de la siguiente forma:
• 0x83: Esta trama contiene 3 bytes de datos.
• 0xF1: Este byte es la direccion del nodo destino(Tester)
• 0x10: Este byte es la direccion del nodo origen (ECU)
El sistema OBD2
28
• 0xC1: Es el primer byte del campo de datos. El valor 0xC1 es el ID para la respuesta
de inicio de comunicación.
• 0x8F, 0xE9: Estos bytes son los key bytes.
• 0xBD: Este byte es el checksum de la trama.
Una vez recibida esta trama podemos dar por acabado el proceso de inicialización. A partir
de este momento el tester puede proceder a la petición de datos.
3.1.5 ISO 15765-1
El protocolo ISO 15765-1 empezó a implantarse en el año 2003 y actualmente es el protocolo
mas extendido por las ventajas que ofrece frente a los otros estándares. Este protocolo se
puede encontrar principalmente en los modelos de vehículos más modernos ya que a partir
del 2008 su implantación empezó a ser obligatoria en los EEUU.
El principal motivo del éxito de este protocolo es que utiliza el bus CAN para establecer la
comunicación entre los nodos del sistema. Las ventajas que ofrece el bus CAN son
numerosas y por eso no solo se utiliza en la industria del automóvil sino que es la opción por
excelencia en muchos ámbitos de la industria. Sus ventajas se verán a lo largo de los
siguientes capítulos.
Para poder explicar mejor el protocolo ISO 15765-1, primero se explicará en detalle el
protocolo CAN BUS ya que este es la base del ISO 15765-1.
3.1.5.1 BUS CAN
Este protocolo empezó a ser desarrollado por la empresa Robert Bosch GmbH (BOSCH) en
1983 y fue finalmente presentado en el 1986. En 1993 el protocolo CAN fue estandarizado
por la ISO bajo el nombre ISO 11898.
El motivo que llevó a BOSCH a desarrollar este protocolo fue la inmensa cantidad de cables
que eran necesarios para poder conectar los distintos sistemas electrónicos de los que
disponía un coche. Previamente al bus CAN, los distintos sistemas electrónicos del vehículo
se comunicaban entre ellos mediante una conexión punto a punto. Esta forma de conectar la
electrónica de los vehículos requería una gran cantidad de cables y esto representaba una
serie de inconvenientes: mayor costo en materiales, vehículo más pesado, mayor consumo
etc.
Por estos inconvenientes BOSCH decidió desarrollar el bus CAN. Este sistema de
comunicación tipo bus permitía interconectar todos los sistemas electrónicos del coche con
solo 2 cables.
El sistema OBD2
29
Figura 20: Coche sin BUS CAN y coche con BUS CAN.
En la figura 20 se ilustra el número de conexiones necesarias para conectar las distintas
ECUs utilizando un sistema de comunicación punto a punto (parte de la izquierda) y
utilizando el bus CAN (parte de la derecha). Tal y como se puede ver, las diferencias son
significativas.
A parte de la ventaja evidente que supone el uso del bus CAN, hay otras más que han hecho
que este protocolo sea de los más utilizados en la industria del automóvil. Estas ventajas de
explican en detalle en los siguientes párrafos:
• Velocidad: Las tasas de bits de este protocolo son mucho mayores que las que
ofrecen los protocolos tradicionales.
• Flexibilidad: Al tratarse de un bus formado por solo dos cables su instalación es
considerablemente más fácil que otros protocolos. Por la misma razón, añadir más
componentes al sistema requiere de un esfuerzo menor. Se pueden conectar hasta
110 nodos en el bus por lo que los fabricantes suelen tener margen para añadir más
nodos al sistema.
• Fiabilidad: Como el bus CAN trabaja con señales diferenciales, es menos
susceptible a las EMI’s (Electromagnética interferentes). Por esta misma razón es un
protocolo muy utilizado en la industria. Por otra parte, al disponer de menos piezas
(menos cables, menos conectores etc.) hay menos elementos que pueden fallar con
lo cual su fiabilidad es mayor. Además, el fallo de un nodo no afecta al
funcionamiento del bus.
• Costos: menos elementos significa menos costes, por lo tanto, el bus CAN es más
económico que otros protocolos tradicionales.
• Espacio: Al reducir el número de cables dentro del vehículo hay más espacio
disponible para incorporar otros sistemas y por lo tanto, otras funcionalidades.
• Normalización: Al ser un estándar, las ECU’s de distintos fabricantes que estén
preparadas para este protocolo son completamente compatibles y pueden
intercambiar información entre sí.
El sistema OBD2
30
• Sistema multimaestro: Todos los nodos en un bus CAN tienen el papel de master,
es decir, pueden transmitir a través del bus cuando quieran (siempre que no haya otro
nodo transmitiendo en ese momento). Esto permite definir dentro del mismo bus
distintos subsistemas de comunicación.
• Acceso al bus: Aplica la metodología CSMA/CD-A para gestionar los accesos al
BUS. Esto permite detectar y solucionar las colisiones de una forma simple y
efectiva.
3.1.5.1.1 Capa física
Topología de red
Tal y como se ha mencionado, este estándar define una tipología de red tipo bus. Los nodos
del sistema de comunicación se conectan a 2 hilos (CAN high y CAN low) que son las líneas
que constituyen el bus. A través de estas dos líneas se transmite un señal diferencial que es
la que lleva la información codificada.
El bus permite una comunicación de todos con todos, pero como el medio de transmisión es
compartido, solo un nodo puede estar transmitiendo en un instante de tiempo dado.
Medio de transmisión
Los distintos nodos del sistema se conectan mediante un par de cables trenzados con una
impedancia característica de 120 Ohm’s. Los cables pueden estar apantallados o sin
apantallar. El uso de un par trenzado es uno de los aspectos que da a este protocolo
inmunidad frente a las EMI’s. En caso de trabajar en un ambiento ruidoso (con alta actividad
electromagnética) las señales inducidas en cada uno de los cables del par se cancelan por lo
que la señal diferencial se mantiene intacta.
Figura 21: Ventajas del par trenzado frente a un cable unipolar.
Codificación de bit:
El bus CAN utiliza un señal diferencial para codificar los bits de información. Este bus tiene
dos estados definidos: estado dominante y estado recesivo. En cada estado las líneas CAN
High y CAN Low tienen un potencial determinado.
El sistema OBD2
31
Estos potenciales depende del tipo de CAN que se este utilizando ya que hay dos variantes
denominadas CAN de alta velocidad (o ISO 11898-2) y CAN de baja velocidad(o ISO
11898-3). Tal y como su nombre indica, la diferencia radica en la tasa de bits con la que
trabaja cada versión. En el caso del CAN de alta velocidad se permite una tasas de bits de
hasta 1 Mbps. En el caso del CAN de baja velocidad se permite una tasa de bits de hasta 125
kbps.
La siguiente tabla muestra los potenciales de las líneas según el estado del bus en el caso del
CAN de alta velocidad:
Señal Estado
Recesivo
Estado
Dominante
CAN High 2,5 V 3,5 V
CAN Low 2,5 V 1,5 V
Vdiff 0 2 V
Tabla 6: Potenciales del bus según el estado.
En el estado recesivo las dos líneas están al mismo potencial por lo que la tensión diferencial
es de 0 V (caso ideal). En el caso del estado dominante la línea CAN High esta a 3,5 V y la
línea CAN Low esta a 1,5 V, entonces la tensión diferencial es de 2 V.
La siguiente tabla muestra los potenciales de las líneas según el estado del bus en el caso del
CAN de baja velocidad:
Señal Estado
Recesivo
Estado
Dominante
CAN High 0 V 3,6 V
CAN Low 5 V 1,4 V
Vdiff - 5 V 2,2 V
Tabla 7: Potenciales del bus según el estado.
En el caso del estado recesivo la línea CAN High esta a 0 V y la línea CAN Low esta a 5 V
por lo que la tensión diferencial es de -5 V. En el caso del estado dominante la línea CAN
High esta a 3,6 V y la línea CAN Low esta a 1,4 V, entonces la tensión diferencial es de 2,2
V.
Mediante estos dos estados se codifican los niveles lógicos de los bits que se transmiten. El
1 lógico está asociado al estado recesivo y el 0 lógico esta asociado al estado dominante.
Cuando el bus está en reposo este se mantiene en estado recesivo, es decir, transmitiendo un
1.
El sistema OBD2
32
Driver del BUS
El circuito a través del cual los nodos establecen el estado del bus se puede modelar de la
siguiente forma:
Figura 22: Modelo del driver del BUS.
RL es la resistencia que se debe conectar entre los terminales CAN High y CAN Low de cada
extremo del bus para evitar rebotes de la señal que se transmite. El valor de esta resistencia
suele ser de 120 Ohm’s
Circuito en estado recesivo y en estado dominante:
Figura 23: Circuito del driver en los dos estados del BUS.
El sistema OBD2
33
Para poner el bus en estado recesivo los dos transistores se ponen en corte. En este caso la
corriente que fluye entre Vcc y tierra es muy pequeña (despreciable) y la tensión Vod(tensión
diferencial del bus) es 0 V.
Para poner el bus en estado dominante los dos transistores se ponen en conducción. En este
caso si hay corriente a través de la resistencia y por lo tanto, hay una caída de tensión entre
los nodos sus nodos. Esta caída de tensión es justamente la tensión diferencial del bus y tiene
un valor de 2,5 V.
Sincronismo de bit
Este estándar define un sistema de comunicación asíncrono por lo que los nodos deben
conocer el tiempo de bit. Aunque todos los nodos compartan un mismo tiempo de bit, cada
nodo dispone de una frecuencia de reloj por lo que en la práctica cada nodo puede transmitir
o muestrear a una velocidad distinta, es decir, pueden estar desincronizados.
Para evitar la desincronización de los nodos, el bus CAN utiliza un sistema tipo PLL (Phase-
Locked Loop) el cual permite sincronizar los nodos del bus a través de los flancos producidos
en la señal transmitido. Para mantener los nodos sincronizados las tramas de deben contener
1’s y 0’s de forma regular. Como no se puede asegurar que dentro de las tramas no haya una
considerable cantidad de bits consecutivos de igual valor, el bus CAN recurre a una técnica
denominada bit stuffing para asegurar la existencia de flancos durante la transmisión de
datos. Esta técnica consiste en añadir después de cada 5 bits iguales un bit opuesto.
Tiempo de bit
Como se ha mencionado al principio del apartado, una de las características que dan
popularidad al bus CAN es su fiabilidad. Muchos aspectos de su diseño se han pensado
teniendo en mente asegurar que la información llegue del nodo origen al nodo destino con
total éxito. Un buen ejemplo de ello son los mecanismos de sincronización utilizados. Aparte
de la técnica del bit stuffing, cada nodo del bus CAN puede adaptar su tiempo de bit de
acuerdo a una serie de circunstancias (retardos de propagación, retardos en los propios
nodos) y así, asegurar la correcta recepción de los bits.
El estándar CAN divide el tiempo de bit en los siguientes segmentos:
Figura 24: Segmentos del tiempo de bit del BUS CAN.
El sistema OBD2
34
• Segmento de sincronización: Este segmento de tiempo es el primero del tiempo de
bit. Se utiliza para sincronizar los nodos en el bus. Se espera que los flancos se
produzcan dentro de este segmento. Tiene una durada de 1 TQ (Time Quanta).
• Segmento de propagación: Este segmento tiene la función de compensar por los
retardos físicos entre nodos. El tiempo de propagación se define como el doble de la
suma del tiempo de propagación del señal dentro del bus y los retardos asociados al
propio nodo. Tiene una durada de entre 1 y 8 TQ.
• Segmento de fase 1 y segmento de fase 2: Estos dos segmentos se utilizan para
compensar por el error en la sincronización de los flancos. Se puede alargar el
segmento de fase 1 o bien acortar el segmento de fase 2 para ajustar el punto de
muestreo. El segmento de fase 1 tiene una duración de entre 1 y 8 TQ y el segmento
de fase 2 tiene una duración de entre 2 y 8 TQ.
Mediante la variación de la duración de estos segmentos el controlador CAN sitúa el punto
de muestreo en el momento ideal para que la lectura sea correcta.
3.1.5.1.2 Capa Enlace
El protocolo CAN define los siguientes tipos de tramas:
• Trama de datos estándar/extendida: Esta trama permite enviar datos a uno o más
nodos del BUS.
• Trama remota: Esta trama permite pedir datos a otro nodo.
• Trama de error: Mediante esta trama se indica al nodo emisor que la recepción no
ha sido correcta para que se vuelva a repetir la transmisión.
• Trama de sobrecarga: Esta trama permite a un nodo indicar a los otros nodos del
BUS que no está preparado para recibir.
Dentro del ISO 15765-1 solo se utilizan tramas de datos ya que mediante este tipo de tramas
ya se implementan todo un conjunto de subtramas. A continuación se explicará la trama de
datos y sus dos versiones.
El sistema OBD2
35
Trama de datos estándar
Los campos de esta trama se pueden observar en la siguiente figura.
Figura 25: Trama de datos estándar.
• SOF(Start Of Frame): Campo de 1 bit y valor 0. Sirve para indicar el inicio de la
trama.
• ID: Este campo es el identificador de la trama. Tiene una longitud de 11 bits. Cuando
un nodo recibe una trama la filtra y solo la acepta si el identificador cumple unas
determinadas condiciones.
• RTR(Remote Transmisión Request): Campo de 1 bit y valor 0.
• IDE: Este campo tiene una longitud de 1 bit e indica si se trata de una trama estándar
o extendida. En este caso su valor debe ser 0 (trama estándar).
• R0: Bit reservado, su valor está fijado a 0.
• DLC(Data Length Code): Campo de 4 bits que indica el tamaño del campo de datos.
• Campo de datos: Datos a enviar. Longitud máxima de 8 bytes.
• CRC(Cyclic Redundance Check): Campo de 15 bits. El CRC permite detectar
errores en la trama.
• Ack: Bit de acknowledge. Cuando el nodo emisor llega a este campo pone un 1 en
el bus y si algún nodo ha recibido la trama este pone un 0. Como el bit dominante es
el 0, el nodo emisor verá un 0 en el bus y supondrá que se ha recibido la trama. Si el
nodo emisor ve un 1 significa que ningún nodo ha recibido la trama.
• Ack delimiter: bit de valor 1.
• EOF(End Of Frame): Campo de 7 bits y valor 0x7F. Sirve para indicar el fin de la
trama.
Trama de datos extendida
Esta trama es muy similar a la anterior ya que, como su nombre indica, es simplemente una
extensión de la trama estándar. La diferencia es que en este caso el identificador de la trama
tiene 29 bits.
Figura 26: Campos de la trama de datos extendida.
El sistema OBD2
36
Las diferencias en la forma de la trama son:
• El campo IDE toma el valor 1 en vez de 0.
• Entre los campos IDE y R0 se añade otro campo de 18 bits que son los 18 bits de
menos peso del identificador. Los 11 bits de más peso del identificador se sitúan en
el campo ID.
3.1.5.2 Capa Enlace
El protocolo ISO 15765-1 utiliza el BUS CAN para enviar las tramas. Las tramas del ISO
15765-1 se envían dentro del campo de datos de las tramas CAN. El estándar fija la longitud
del campo de datos de las tramas CAN en 8 bytes (la máxima posible), esto permite que las
tramas ISO 15765-1 puedan tener una longitud de hasta 8 bytes. Como la longitud de las
tramas ISO 15765-1 puede ser inferior a 8 bytes, podemos encontrarnos con bytes nulos
dentro del campo de datos de las tramas CAN.
El estándar ISO 15765-1 define varios tipos de tramas, estas se explican a continuación:
Single Frame
Esta trama se utiliza cuando uno de los nodos quiere transmitir datos que caben
perfectamente dentro de una única trama. En estos casos los datos a enviar deben tener una
longitud inferior o igual a los 7 bytes. Los campos de esta trama se muestran en la siguiente
figura:
Figura 27: Campos de la trama Single Frame.
El primer byte de la trama se utiliza para identificar el tipo de trama y la longitud del campo
de datos. Los 4 bits de más peso del primer byte indican el tipo de trama, en este caso su
valor debe ser 0x0. Los 4 bits de menos peso indican la longitud del campo de datos, dentro
del rango permisible su valor máximo es 7. Los siguientes n bytes forman parte del campo
de datos de la trama First Frame. Los bytes no utilizados del campo de datos de la trama
CAN se deben llenar con ceros.
No se admite que se envíe este tipo de trama con una longitud del campo de datos igual a 0
ya que este valor se reserva al mismo protocolo.
First Frame
Este tipo de trama se utiliza cuando un nodo quiere enviar datos que no caben dentro de una
Single Frame, es decir, datos de longitud superior a 7 bytes. Mediante esta trama el nodo
emisor hace saber al nodo receptor que este deberá recibir más de una trama. Dentro de esta
misma trama se envían los primeros 6 bytes del total de bytes que se desean enviar.
El sistema OBD2
37
El formato de la trama es el siguiente:
Figura 28: Campos de la trama First Frame
En este caso la longitud de la trama es siempre de 8 bytes. Como en el caso anterior, los 4
bits de más peso del primer byte indican el tipo de trama, en este caso debe ser 1. Los 4 bits
de menos peso del primer byte y el segundo byte indican el número de bytes que se quieren
enviar. En este caso el valor de este campo (FF_DL) debe ser superior a 7. Los siguientes 6
bytes forman parte de los datos que se quieren enviar.
Consecutive Frame
Como en el caso anterior, la longitud de la trama es siempre de 8 bytes. Este tipo de trama
se utiliza conjuntamente con el tipo anterior para enviar datos de longitud superior a 7 bytes.
Las tramas Consecutive Frame son las que se envían después de la trama First Frame para
enviar los bytes restantes. Este tipo de trama permite enviar 6 bytes de datos. El formato de
la trama es el siguiente:
Figura 29: Campos de la trama Consecutive Frame.
Los 4 bits de más peso son el identificador de la trama y en este caso su valor es 2. Los 4
bits de menos peso indican el número de Consecutive Frame. Los siguientes 6 bytes
corresponden a los datos.
Como el nodo emisor debe enviar los datos en un número entero de Consecutive Frames se
puede dar el caso de que no se requiera utilizar todos los 6 bytes de la última Consecutive
Frame, en este caso los bytes sobrantes se llenan con el valor nulo.
Flow Control
Este tipo de trama solo interviene cuando se quieren enviar diversas tramas. La trama Flow
Control la utiliza el nodo receptor para indicar al nodo emisor las circunstancias en las que
se quieren recibir las tramas. La longitud de esta trama es siempre de 3 bytes.
El sistema OBD2
38
El formato de la trama es el siguiente:
Figura 30: Campos de la trama Flow Control
Los 4 bits de más peso del primer byte son el identificador de la trama, en este caso su valor
es 3. Los 4 bits de menos peso corresponden al parámetro FS(Flow Status). El segundo byte
corresponde al parámetro BS(Block Size) y el tercer byte corresponde al parámetro Stmin.
En la siguiente tabla se explica el significado de cada parámetro:
Parámetro Definición
FS Indica al nodo emisor si se debe proceder
con la transmisión o no. Un 0 indica al
emisor que puede continuar con la
transmisión. Un 1 indica que el emisor debe
reintentar la transmisión pasado un cierto
tiempo. Un 2 indica al emisor que el
receptor está ocupado y debe abortar la
transmisión.
BS Este parámetro indica el número de
Consecutive Frames que se pueden enviar
antes que el emisor deba pausar la
transmisión y esperar al siguiente Flow
Control. Si su valor es 0 significa que el
receptor dispone de los recursos suficientes
para recibir las tramas (memoria) y el
emisor puede enviar las tramas que quiera.
Stmin Este parámetro indica el tiempo mínimo
entre el envió de las Consecutive Frames.
Tabla 8: Parámetros de la trama Flow Control.
A continuación se explicará las tramas que se intercambian según la longitud de los datos
que se quieren enviar:
Envió de 7 o menos bytes de datos
En este caso el emisor solo debe enviar una trama tipo Single Frame.
El sistema OBD2
39
Envió de más de 7 bytes
Figura 31: Intercambio de tramas múltiples.
En primer lugar el nodo emisor debe enviar una trama tipo First Frame con los primeros 6
bytes de datos. Cuando el receptor recibe la First Frame este debe responder con una trama
tipo Flow Control. Una vez el nodo emisor recibe la trama de Flow Control el nodo emisor
debe empezar a transmitir las Consecutive Frames con el resto de bytes de datos. Este
intercambio solo se da si los parámetros FS y BS de la trama Flow Control tienen el valor 0,
en otro caso el intercambio sería distinto. Dentro de este proyecto solo los parámetros FS y
BS se ajustarán a 0 ya que el escáner OBD2 no tendrá otra tarea más que la de comunicarse
con el vehículo, además se espera que las respuestas tengan un tamaño muy inferior a la
memoria disponible en el microcontrolador.
El sistema OBD2
40
3.2 Servicios del sistema OBD2
En los apartados anteriores se han explicado los distintos protocolos con los que un equipo
de test puede comunicarse con el sistema OBD2 de un vehículo. Este apartado se centrará
en los datos que se deben enviar para obtener los parámetros deseados así como el formato
de los datos de respuesta.
El estándar SAE J1979 se refiere a los datos que se pueden obtener del vehículo como PIDs
(Parameter IDs). Estos PIDs están divididos en servicios según el tipo de parámetro.
En todos los protocolos, el campo de datos de una trama request debe tener el siguiente
formato:
<Service><PID>
Y el campo de datos de la trama de respuesta presenta el siguiente formato:
<0x40 + Service><PID><byte/s del PID>
En muchos casos el valor de respuesta de un determinado PID se fragmenta en un conjunto
de bytes y para obtenerlo se debe realizar una operación aritmética. En otros casos la
información se codifica en uno o varios bits y para obtenerla se deben realizar operaciones
a nivel bit. En todo caso el estándar OBD2 especifica como tratar los datos recibidos para
cada PID.
Tal y como se verá mas adelante, algunos servicios no disponen de PIDs. En estos casos se
omite el campo <PID> de las tramas.
A continuación se explicará en detalle los distintos servicios y PIDs que define el estándar.
El estándar SAE J1979 define los siguientes servicios:
• Servicio 1: Este servicio permite obtener el valor en tiempo real de los sensores del
vehículo. Ejemplo de estos datos son la temperatura del refrigerante o la carga del
motor.
• Servicio 2: A través de este servicio se pueden obtener el valor que tenían los
sensores en el momento en el que se ha producido un fallo. Ofrece los mismos PIDs
que el servicio 1 pero a diferencia del primero, este servicio no da valores en tiempo
real
• Servicio3: Este servicio permite obtener el código de las fallas detectadas (DTC) por
las ECUs.
• Servicio 4: Mediante este servicio se pueden borrar de la memoria las fallas
detectadas. También borra los datos del servicio 2.
El sistema OBD2
41
• Servicio 5: Este modo permite obtener los resultados de las pruebas realizadas a los
sensores de oxigeno del vehículo.
• Servicio 6: Este servicio permite obtener el resultado de todas las pruebas de abordo.
• Servicio 7: Este servicio permite obtener los DTCs pendientes de la memoria de las
ECUs.
• Servicio 8: Mediante este modo se puede realizar una prueba a los actuadores.
• Servicio 9: Este servicio permite obtener información legal del vehículo. Un ejemplo
es el VIN (Vehicle Identification Number)
Servicio 1
Algunos de los PIDs de este servicio se muestran en la siguiente tabla:
Mode (hex) PID
(hex)
Data bytes
returned
Description Min value Max
value
Units Formula
01 00 4 PIDs supported [01 - 20] Bit encoded [A7..D0]
== [PID 0x01..PID
0x20] See below.
01 01 4 Monitor status since
DTCs cleared. (Includes
malfunction indicator
lamp (MIL) status and
number of DTCs.)
Bit encoded. See below.
01 02 2 Freeze DTC
01 03 2 Fuel system status Bit encoded. See below.
01 04 1 Calculated engine load
value
0 100 % A*100/255
01 05 1 Engine coolant temperature -40 215 °C A-40
01 06 1 Short term fuel % trim—
Bank 1 -100
Subtra
cting
Fuel
(Rich
Conditi
on)
99.22
Adding
Fuel
(Lean
Conditi
on)
% (A-128) * 100/128
01 07 1 Long term fuel % trim—
Bank 1 -100
Subtra
cting
Fuel
(Rich
Conditi
on)
99.22
Adding
Fuel
(Lean
Conditi
on)
% (A-128) * 100/128
01 08 1 Short term fuel % trim—
Bank 2 -100
Subtra
cting
Fuel
(Rich
Conditi
on)
99.22
Adding
Fuel
(Lean
Conditi
on)
% (A-128) * 100/128
El sistema OBD2
42
01 09 1 Long term fuel % trim—
Bank 2 -100
Subtra
cting
Fuel
(Rich
Conditi
on)
99.22
Adding
Fuel
(Lean
Conditi
on)
% (A-128) * 100/128
01 0A 1 Fuel pressure 0 765 kPa A*3
01 0B 1 Intake manifold
absolute pressure
0 255 kPa
(absol
ute)
A
01 0C 2 Engine RPM 0 16,383.7
5
rpm ((A*256)+B)/4
01 0D 1 Vehicle speed 0 255 km/h A
01 0E 1 Timing advance -64 63.5 °
relative
to #1
cylinde
r
A/2 - 64
01 0F 1 Intake air temperature -40 215 °C A-40
01 10 2 MAF air flow rate 0 655.35 grams/s
ec
((A*256)+B) / 100
01 11 1 Throttle position 0 100 % A*100/255
01 12 1 Commanded secondary air
status
Bit encoded. See below.
01 13 1 Oxygen sensors present [A0..A3] == Bank 1,
Sensors 1-4. [A4..A7] ==
Bank 2...
01 14 2 Bank 1, Sensor 1:
Oxygen sensor
voltage, Short
term fuel trim
0
-100(lean)
1.275
99.2(rich
)
Volts
%
A/200
(B-128) * 100/128 (if B==0xFF,
sensor is not used in trim calc)
Tabla 9: Primeros 20 PIDs del servicio 1.
Servicio 2
Para este servicio se aplica la misma tabla que la del servicio 1.
Servicio 3
Este servicio no dispone de PIDs.
Dentro del campo de datos de la respuesta a este servicio se encuentran los DTCs
almacenados en memoria. Por cada DTC detectado, la ECU manda dos bytes con los cuales
se codifica la información de la falla. Un DTC esta compuesto por un conjunto de 5
caracteres y el valor de estos caracteres se codifica utilizando un determinado conjunto de
bits. La obtención de los DTCs a partir de sus bytes se debe realizar siguiendo estas tablas:
El sistema OBD2
43
Tabla 10: Tabla de obtención del primer carácter.
Tabla 11: Tabla de obtención del segundo carácter.
Tabla 12: Tabla de obtención del tercer carácter.
El sistema OBD2
44
Los caracteres cuarto y quinto se definen de la misma manera que el tercero, pero utilizando
los bits B7..B4 y B3..B0.
Servicio 4
Este servicio tampoco dispone de PIDs.
Servicio 5
Algunos de los PIDs de este servicio se encuentran en la siguiente tabla:
PID
(hexadecimal)
Bytes de
datos
devuelto
Descripción Valor
mín.
Valor
máx.
Unidades Fórmula
0100 OBD Monitor IDs apoyado ($01 –
$20)
0101
O2 Sensor Monitor Banco 1
Sensor 1
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0102
O2 Sensor Monitor Banco 1
Sensor 2
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0103
O2 Sensor Monitor Banco 1
Sensor 3
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0104
Sensor de O2 Sensor Monitor
Banco 1 4
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0105
O2 Sensor Monitor banco 2 Sensor
1
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0106
O2 Sensor Monitor banco 2 Sensor
2
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0107
O2 Sensor Monitor banco 2 Sensor
3
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0108
Sensor de O2 Sensor Monitor
banco 2 4
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
0109
Sensor de O2 Sensor Monitor
Banco 3 1
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
010A
Sensor de O2 Sensor Monitor
Banco 3 2
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
010B
Sensor de O2 Sensor Monitor
Banco 3 3
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
El sistema OBD2
45
010C
Sensor de O2 Sensor Monitor
Banco 3 4
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
010D
Sensor de O2 Sensor Monitor
Banco 4 1
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
010E
Sensor de O2 Sensor Monitor
Banco 4 2
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
010F
Sensor de O2 Sensor Monitor
Banco 4 3
0.00
1,275
Voltios Rich 0,005 a voltaje del umbral
del sensor de inclinación
Tabla 13: Algunos PIDs del servicio 5.
Servicio 6
Los PIDs de este servicio no están definidos por el estándar sino que cada fabricante
implementa unos PIDs en particular.
Servicio 7
Este funcionamiento de este servicio es equivalente al del servicio 3.
Servicio 8
Al igual que con el servicio 6, el fabricante es quien determina los PIDs de este servicio.
Servicio 9
Los PIDs de este servicio se muestran en la siguiente tabla:
PID
(hexadecimal)
Bytes de datos devuelto Descripción Valor
mín.
Valor
máx.
Unidades Fórmula
00
4
9 el modo compatible PIDs
(01 a 20)
Bit codificado. [A7...D0]
= [PID $01..
PID $20] Vea a
continuación
01
1
VIN mensaje Conde en el PID 02.
Sólo para ISO 9141-2, ISO
14230- 4 y SAE J1850.
Generalmente el valor
será 5.
02
17-20
Número de identificación del
vehículo (VIN)
17-char VIN,
codificación ASCII e
izquierda-rellenado con
caracteres nulos (0 x 00)
si necesario.
El sistema OBD2
46
03
1
Conde de mensaje de ID de
calibración para el PID 04. Sólo
para ISO 9141-2, ISO 14230-4 y
SAE J1850.
Será un múltiplo de 4 (4
mensajes son necesarios
para cada ID).
04
16
ID de calibración
Hasta 16 caracteres
ASCII. bytes de datos no
utilizados serán
reportados como bytes
nulos (0 x 00).
05
1
Número de verificación de
calibración (CVN) mensaje
cuenta por PID 06. Sólo para
ISO 9141-2, ISO 14230-4 y SAE
J1850.
06
4
Número de verificación de
calibración (CVN)
Izquierda-rellenado con
caracteres nulos (datos en
bruto0 x 00).
Generalmente aparece
como cadena
hexadecimal.
07
1
Conde de mensaje por PID de
seguimiento del rendimiento en
uso08 y 0B. Sólo para ISO 9141-
2, ISO 14230- 4 y SAE J1850.
8
10
8 si dieciséis 16 valores
están obligados a
informar, 9 18 18 valores
están obligados a
informar, y 10 si veinte 20
valores deben ser
registrados (un mensaje
informes dos valores,
cada uno que consiste en
dos bytes).
08
4
Seguimiento para los vehículos
de ignición de chispa del
rendimiento en uso
4 o 5 mensajes, cada
uno conteniendo 4 bytes
(dos valores). Vea a
continuación
09 1 ECU nombre mensaje no cuentan
para PID 0A
Tabla 14: PIDs del servicio 9.
Ejemplo práctico: Obtención de las revoluciones del motor.
Las revoluciones del motor están asociadas al PID 0xC del servicio 1, por lo tanto, el equipo
de test envia los siguientes bytes a la ECU del vehículo:
<0x1><0xC>
Tras un tiempo acotado, el tester recibe los siguientes bytes:
<0x40 + 0x1><0xC><0x14><0x3>
El sistema OBD2
47
Los bytes que nos interesan son 0x14 y 0x3. Según la tabla 9, la fórmula que debemos
aplicar a estos valores para obtener las revoluciones del motor es la siguiente:
𝑛 =256 × 𝐴 + 𝐵
4 (2)
Realizamos los cálculos indicados:
𝑛 =256 × 20 + 3
4= 𝟏𝟐𝟖𝟎 𝒓𝒑𝒎 (𝟑)
Así es como se obtendría la velocidad de giro del motor.
Desarrollo del hardware
48
4. Desarrollo del hardware
En este apartado se explicará todo el proceso de diseño seguido para desarrollar el hardware
necesario. La idea de la que se parte para el diseño del hardware es que permita a un
microcontrolador conectarse a los buses de los protocolos deseados, en este caso ISO 9141-
2 y ISO 15765-4. Al mismo tiempo se debe diseñar un hardware que permita al dispositivo
comunicarse con un ordenador. En primer lugar se explicará en detalle el hardware necesario
para implementar cada uno de los protocolo y seguidamente se explicará el circuito del
microcontrolador que será el que manejará los demás circuitos.
4.1 Interfaz hardware del ISO 9141-2
4.1.1 Uso de la UART
Tal y como se ha especificado en el apartado del ISO 9141-2, este protocolo implementa una
capa física que es similar a la del protocolo RS-232. En el caso del RS-232, el hardware que
implementa las funciones de la capa física se denomina UART (Universal Asíncronos
Receiver-Transmitir).
La UART es un periférico imprescindible dentro de los microcontroladores por su
practicidad y simplicidad a la hora de establecer comunicación entre dos dispositivos. Este
dispositivo se permite establecer una comunicación punto a punto y full-dúplex (se puede
dar en ambos sentidos simultáneamente). Las líneas de comunicación a través de las que se
comunican los dos dispositivos se pueden visualizar en el siguiente diagrama:
Figura 32: Conexión entre dos dispositivos a través de la UART.
Tal y como se puede ver, cada uno de los dispositivos presenta un módulo UART. La líneas
que conectan los dos nodos son 2 y en cada una de la líneas la información fluye en un
sentido (nodo A a nodo B y nodo B a nodo A), esto permite que la comunicación sea full-
dúplex.
En cada una de las línea hay conectado el pin TX de un nodo y el pin RX de otro nodo. El
nodo que tiene su pin TX conectado a la línea la gobierna para transmitir la señal eléctrica
que codifica la información mientras que el nodo que tiene el pin RX conectado recibe la
señal. Es decir, dentro de una línea un nodo transmite mientras que el otro recibe.
Desarrollo del hardware
49
Los estados lógicos 1 y 0 se codifican utilizando el potencial eléctrico de la línea: un
potencial alto (habitualmente 5 V) equivale a un 1 lógico y un potencial bajo (habitualmente
0 V) equivale a un 0 lógico.
Como se puede deducir a partir de la figura, este dispositivo establece una comunicación
serie asíncrona permitiendo una tasa de bits de hasta 921,6 Kbps.
La trama de datos con la que trabaja habitualmente este hardware es la siguiente:
Figura 33: Formato de trama de la UART.
La configuración de este paquete de datos recibe el nombre de 8N1 y esta constituido por 1
bit de start + 8 bits de datos + 1 bit de stop. El número de bits de datos que se transmiten en
cada paquete es de 1 byte por lo que la unidad de datos de este protocolo es de 1 byte.
Tal y como se puede ver, hay muchas similitudes entre la forma en la que se comunica una
UART y la capa física del ISO 9141-2, aunque también hay diferencias.
En la siguiente tabla se comparan los dos protocolos y se enumeran la similitudes y
diferencias:
Similitudes Diferencias
→ En ambos se establece una
comunicación serie asíncrona.
→ Ambos no utilizan ninguna modulación.
Es decir, codifican los bits directamente con
el nivel de tensión.
→ El paquete de datos o trama es igual en
ambos casos.
→ Mediante la UART se establece una
comunicación punto a punto mientras que
en el caso del ISO 9141-2 se trata de un bus
(comunicación multinodo).
→ Los niveles de tensión utilizados son
distintos. ISO 9141-2 utiliza 12 V y 0 V,
UART utiliza 5 V y 0 V.
→ La comunicación mediante la UART es
full-dúplex mientras que con ISO 9141-2 no
lo es.
Tabla 15: Comparación entre el ISO 9141-2 y la UART.
Desarrollo del hardware
50
A partir de la tabla anterior se deduce que las diferencias se encuentran básicamente en el
medio físico. Por lo tanto, solucionando estas diferencias podemos utilizar la UART de un
microcontrolador para conectarnos al bus de un sistema de comunicación basado en ISO
9141-2.
4.1.2 El MC33290
En el mercado existen multitud de circuitos integrados que funcionan de interfaz entre un
microcontrolador y una línea k (bus del ISO 9141-2). Un claro ejemplo es el MC33290.
El MCP33290 es un circuito integrado fabricado por la empresa NXP y sirve de interfaz
entre las ECUs del vehículo y el microcontrolador del equipo de test. Este integrado ha sido
diseñado para ser usado dentro de la industria del automóvil por lo que satisface nuestras
necesidades.
Pines del circuito integrado
Figura 34: Pinout del MC33290
Descripción de los pines
Número Nombre Definición
1 VBB Alimentación de 12 V (de la batería del coche).
2 NC No se debe conectar.
3 GND Tierra.
4 ISO Conexión al bus.
5 TX Entrada digital para la señal a transmitir.
6 RX Salida digital para la señal recibida
7 VDD Alimentación de 5 V.
8 CEN Entrada digital para habilitar el integrado. 1 para activo y 0 para
inactivo.
Tabla 16: Descripción de los pines del MC33290.
Desarrollo del hardware
51
Circuito recomendado para conectar el circuito integrado
Figura 35: Circuito recomendado del MC33290.
En el esquema anterior hay más componentes de los que son necesarios para el
funcionamiento del circuito integrado ya que se han añadido protecciones contra las ESD
(electrostatic discharge). En este caso se va a omitir el uso de estos componentes ya que la
versión que se desarrolla en este proyecto se trata de un prototipo. Si se quiere lanzar una
versión comercial estos componentes son imprescindibles para cumplir con los estándares.
Los componentes que se omiten son: R1, C1, C2, C3, D1, D2 y D3.
Se dejará solamente la resistencia R2 que es la resistencia de pull-up que lleva el bus a un
potencial alto (12 V). Sin esta resistencia no se podría transmitir correctamente la
información.
Funcionamiento del integrado
El funcionamiento del MC33290 es equivalente al funcionamiento del siguiente circuito:
Figura 36: Circuito equivalente al MC33290.
R1
D1
D2 C1
C2
D3
C3 R2
Desarrollo del hardware
52
La idea de esta circuito es que el pin TX de la UART permita gobernar el estado del bus
mientras que el pin RX permita leer su estado. En el caso del pin RX la solución es muy
sencilla, mediante un simple divisor de tensión adaptamos la señal del bus a los niveles de
tensión del microcontrolador (5V y 0V) y la llevamos al pin RX. Entre el divisor de tensión
y el bus se debe conectar un seguidor de tensión para evitar efectos de carga. En el caso de
del pin TX el esquema algo más complejo. Como se ha explicado anteriormente, el estándar
ISO 9141-2 especifica que los niveles de tensión se deben obtener mediante una resistencia
de pull-up i un driver de colector abierto. Si implementamos esta solución y conectamos el
pin RX del MCU a la base del transistor tendremos los siguientes casos:
• Si el pin RX está a 1 entonces el transistor conducirá y conectará el bus a masa.
• Si el pin RX está a el transistor no conducirá y la resistencia de pull-up llevará el bus
a 12 V.
Con esta solución podríamos gobernar el bus pero la señal que se transmitiría desde el pin
RX se invertiría. Por esta razón se debe añadir un inversor entre el pin RX y la puerta del
driver tal y como se observa en la figura anterior.
Mediante este integrado podremos transmitir y recibir bytes con las ECUs del coche de la
misma forma que se haría con otro microcontrolador. Hay que tener en cuanta que entre el
microcontrolador y el vehículo hay un bus que impide que haya más de un emisor en un
instante de tiempo dado, esto hace que la UART pierda su bidireccionalidad. Otra cosa que
hay que tener en cuenta es que los pines TX y RX de la UART están conectados de forma
lógica al bus, es decir, lo que se transmite a través de TX llega a RX por lo que tendremos
una eco que habrá que tratar por software.
Con el circuito anteriormente mostrado tenemos todo el hardware necesario para establecer
una conexión con el sistema OBD2 de los vehículos que implementen el protocolo ISO
9141-2.
Desarrollo del hardware
53
4.1.3 Circuito de la interfaz
Figura 37: Circuito de la Interfaz ISO 9141-2.
R2 es una resistencia de 500 Ohm’s que el fabricante recomienda conectar para llevar la
linea k a un potencial alto.
Desarrollo del hardware
54
4.2 Interfaz hardware del ISO 15765-4
Para poder establecer comunicación con vehículos que implementen el protocolo ISO
15765-4 necesitamos un controlador CAN. El microcontrolador que se utilizará en este
proyecto no dispone de CAN, por lo tanto, debemos utilizar un controlador externo. El
circuito integrado que se va a utilizar será el MCP2515.
4.2.1 EL MCP2515
El MCP2515 es un integrado desarrollado por la empresa Microchip. Este modulo se trata
de un controlador CAN que implementa la versión 2.0B del protocolo. También dispone de
comunicación SPI la cual permite un fácil enlace con un microcontrolador.
Este circuito integrado puede conectarse a un bus CAN y actuar como un nodo más del bus,
es decir, una vez conectado al bus puede enviar y recibir tramas.
El modulo dispone de una memoria RAM estructurada en registros que puede almacenar la
siguiente información:
• La configuración del chip así como los parámetros de la comunicación CAN (tipo de
identificador, tiempo de bit etc.).
• Los campos de las tramas a enviar o de las tramas recibidas.
El microcontrolador puede acceder a estos registros mediante SPI y realizar operaciones de
lectura o escritura controlando así el circuito integrado y el BUS CAN.
La siguiente figura muestra como quedaría conectado el MCP2515 entre el microcontrolador
y el bus CAN.
Figura 38: Conexión del MCP2515 al BUS CAN.
Desarrollo del hardware
55
Observamos en la figura anterior que hay otro componente entre el MCP2515 y el bus CAN.
Este componente es el transciever y tiene la función de adaptar los niveles de tensión del
MCP2515 a los del bus CAN. Por lo tanto, es un componente imprescindible y se debe incluir
en el circuito.
Pines del MCP2515
Figura 39: Pinout del MCP2515.
Descripción de los pines
Número
pin
Nombre
pin
Definición
1 TXCAN Salida digital del señal a transmitir a través del bus CAN
2 RXCAN Entrada digital del señal recibido a través del bus CAN
8 OSC1 Entrada oscilador
9 OSC2 Salida oscilador
10 VSS Massa
20 VDD Alimentación positiva
18 /CS Pin de Chip Select del SPI
17 SO Salida del SPI
16 SI Entrada del SPI
Tabla 17: Descripción de los pines del MCP2515.
4.2.1.1 Componentes externos del MCP2515
4.2.1.1.1 Circuito oscilador
El integrado MCP2515 requiere de un oscilador externo que proporcione el señal de reloj.
El circuito oscilador puede estar basado en un oscilador de cristal o bien un resonador con
resistencias. En este caso se ha decidido utilizar un oscilador de cristal ya que proporciona
una salida más estable. La frecuencia del oscilador será de 16 MHz. Esta frecuencia es la
mínima que nos va a permitir llegar a la velocidad de transferencia máxima del sistema
OBD2(500 Kbps).
Desarrollo del hardware
56
Esquema de conexión del oscilador
Figura 40: Circuito oscilador del MCP2515.
Tal y como se puede observar, se requiere 2 condensadores adicionales para el correcto
funcionamiento del oscilador. El valor de estos condensadores debe ser de 15 pF, según
recomienda el propio fabricante.
4.2.1.1.2 El transceiver
El transceiver que se utiliza es el TJA1050. Este integrado ha sido desarrollado por la marca
NXP. Permite una tasa de bits de hasta 1 Mbaud, la cual es superior a la máxima que se
empleará (500 Kbps).
Pines del TJA1050
Figura 41: Pines del TJA1050.
Desarrollo del hardware
57
Descripción de los pines
Número
pin
Nombre pin Descripción del pin
1 TXD Entrada digital. El señal de este pin se transmite al BUS.
2 GND Tierra.
3 VCC Alimentación positiva (5 V).
4 RXD Salida digital. El señal del bus se transmite a través de este pin
5 Vref Salida de la tensión de referencia.
6 CANL Conexión a la linea CANL del bus.
7 CANH Conexión a la linea CANH del bus.
8 S Entrada digital. Habilita el integrado si está a nivel alto.
Tabla 19: Descripción de los pines del TJA1050.
Esquema de conexión del TJA1050
Figura 42: Conexión del transceiver entre el controlador CAN y el BUS CAN.
El condensador de 100 nF sirve para filtrar el posible ruido de la alimentación.
Desarrollo del hardware
58
4.2.1.2 Comunicación SPI
El MCP2515 dispone de una interfaz SPI con la que se puede comunicar con un
microcontrolador. Las instrucciones que soporta el MC2515 son las siguientes:
• Reset Instruction: Esta instrucción permite reinicializar los valores de los registros
del integrado.
• Read Instruction: Esta instrucción permite leer el valor de un registro.
• Write Instruction: Esta instrucción permite escribir en un registro.
• Bit Modify Instruction: Esta instrucción permite modificar los bits de un registro
A continuación se explicaran el intercambio de datos que se produce con cada una de las
instrucciones.
Reset Instruction
Para esta instrucción solo se debe enviar el byte 0xC0.
Read Instruction
Figura 43: Diagrama de la instrucción Read.
En primer lugar el microcontrolador debe enviar el byte que identifica la instrucción, en este
caso es 0x03. Seguidamente el MCU debe enviar la dirección del byte que se quiere leer.
Finalmente el MCP2515 devuelve el valor que se encuentra en la dirección previamente
enviada.
Desarrollo del hardware
59
Write Instruction
Figura 44: Diagrama de la instrucción Write.
En primer lugar el microcontrolador envía el byte asociado a la instrucción, el cual es 0x02.
Posteriormente se envía la dirección del registro a modificar y finalmente se envía el nuevo
valor que se le quiere asignar.
Bit Modify Instruction
Figura 45: Diagrama de la instrucción Bit Modify.
En primer lugar se envía el byte de la instrucción, en este caso es 0x05. Posteriormente se
debe enviar la dirección del byte a modificar. Seguidamente se tiene que enviar la máscara
de bits con la que se indica los bits se quieren modificar. Finalmente se envía el byte con el
nuevo valor de los bits que se desean modificar. Solo se modifican los bits indicados en la
máscara de bits, los demás mantienen su valor.
Este circuito integrado dispone de más instrucciones pero solo se utilizan las descritas
anteriormente. Mediante estas instrucciones se pueden aprovechar todas las funciones que
presenta este circuito integrado.
Desarrollo del hardware
60
4.2.1.3 Modos de operación
El circuito integrado MCP2515 dispone de distintos modos de operación. Estos se explican
a continuación:
Configuration mode
Este modo es el único que permite modificar la configuración del integrado. Es
imprescindible pasar por este modo antes de utilizar otro ya que se deben inicializar los
registros de configuración para asegurar el correcto funcionamiento del integrado. Este
modo permite modificar los siguientes registros:
Registro Definición
CNF1, CNF2, CNF3
TXRTSCTRL
Filter registers Registros para configurar el filtro
aplicado a las tramas recibidas Mask registers
Tabla 19: Registros del MCP2515 para configurar el modo de funcionamiento.
Sleep mode
el integrado dispone de un modo de bajo consumo. Este modo es muy útil para aplicaciones
portátiles (alimentación con batería) ya que permite reducir el consumo del dispositivo
cuando este no es utilizado. Una vez entrado en este modo el microcontrolador puede
devolverlo a su modo normal a través del SPI.
Listen-Only Mode
Este modo permite escuchar las tramas que se envían a través del bus sin que los otros nodos
se percaten de su presencia (no actúa sobre el bit de ACK). Este modo es muy útil si se quiere
realizar un análisis de las tramas que envía un nodo a través del bus. Cuando se activa el
modo Listen-Only se deshabilita la opción de transmitir tramas a través del bus.
Loopback Mode
Dentro de este modo el integrado no tiene que estar conectado al bus ya que lo que se hace
es redirigir las tramas enviadas al buffer de entrada, es decir, las tramas que se envían las
vuelve a recibir el mismo integrado. El objetivo de este modo es poder probar algunas de las
funcionalidades del integrado sin la necesidad de conectarlo a un bus CAN.
Desarrollo del hardware
61
Normal Mode
Este modo es el modo de operación estándar. Dentro de este modo el MC2515 puede
transmitir y recibir tramas a través del bus CAN.
Los bits que permiten cambiar el modo de funcionamiento del integrado son los 3 bits de
mayor peso del registro CANCTRL. El modo que corresponde a cada combinación binaria
se encuentra en la siguiente tabla:
REQOP2 REQOP1 REQOP0 Modo de operación
0 0 0 Normal Mode
0 0 1 Sleep Mode
0 1 0 Loopback Mode
0 1 1 Listen-Only Mode
1 0 0 Configuration Mode
Tabla 20: Bits para configurar el modo de funcionamiento.
4.2.1.4 Transmisión y recepción de mensajes
4.2.1.4.1 Configuración del tiempo de bit
Como el bus CAN implementa un sistema de comunicación asíncrona, se tiene que
configurar el tiempo de bit del MCP2515 para que este sea compatible con el resto de nodos
del bus. Tal y como se ha especificado en su correspondiente apartado 3.1.5, ISO 15765-4
dispone de 4 variantes según la longitud del identificador y la tasa de bits, estas variantes
son:
• ISO 15765-4 CAN (11 bit ID,500 Kbaud)
• ISO 15765-4 CAN (29 bit ID,500 Kbaud)
• ISO 15765-4 CAN (11 bit ID,250 Kbaud)
• ISO 15765-4 CAN (29 bit ID,250 Kbaud)
Por lo tanto, la configuración que le demos al integrado debe permitir que este se comunique
a 250 kbps y 500 kbps.
El tiempo de bit dentro del estándar CAN se divide en 4 segmentos: SyncSeg, PropSeg,
PhaseSeg1 y PhaseSeg2. La duración de cada segmento esta compuesta por un número
entero de veces un parámetro denominado Time Quanta. El propio fabricante recomienda
cuantos Time Quanta debe durar cada segmento.
En el caso del primer segmento la duración se fija a 1 Tq. Para los segmentos tres y cuatro
se recomienda una duración entre 1 y 8 Tq. En el caso del ultimo se segmento se debe ajustar
una duración entre 2 y 8 Tq.
Desarrollo del hardware
62
En la siguiente tabla se muestra la duración elegida en función de Tq de cada uno de los
segmentos:
Segmento Tq
SyncSeg 1
PropSeg 2
Phase Segment 1 7
Phase Segment 2 6
Tabla 21: Duración de los segmentos del tiempo de bit.
Ahora podemos expresar el tiempo de bit en función de Time Quanta:
𝑡𝑏𝑖𝑡 = 𝑇𝑄 + 2 · 𝑇𝑄 + 7 · 𝑇𝑄 + 6 · 𝑇𝑄 = 16 · 𝑇𝑄 (3)
Finalmente solo queda definir el parametro Time Quanta para que el tiempo de bit sea el
deseado. Queremos que el MC2515 pueda transmitir a 250 kbps y 500 kbps, entonces
debemos calcular el valor de Time Quanta para cada caso. El valor de Time Quanta se puede
calcular a partir de la siguiente formula:
𝑇𝑄 =2𝐵𝑅𝑃
𝐹𝑜𝑠𝑐 (4)
Tal y como se puede observar en la formula 3, el valor de Time Quanta depende de la
frecuencia del oscilador y del valor de BRP. Mientras que el la frecuencia de oscilador es un
parametro fijo, BRP es el valor de un registro que se puede ajustar para obtener el Time
Quanta deseado. Entonces debemos aislar BRP para poder obtener el valor que debe tomar.
𝐵𝑅𝑃 =𝑇𝑄 · 𝐹𝑜𝑠𝑐
2 (5)
Tasa de bits de 250 kbps
En este caso el tiempo de bit debe tener el siguiente valor:
𝑡𝑏𝑖𝑡 =1
𝑓𝑏𝑖𝑡=
1
250 · 103= 0.04 · 10−4𝑠 (6)
Ahora podemos calcular el valor de Time Quanta:
Desarrollo del hardware
63
𝑡𝑏𝑖𝑡 = 16 · 𝑇𝑄 → 𝑇𝑄 =𝑡𝑏𝑖𝑡
16=
0.04 · 10−4
16= 0.0025 · 10−4𝑠 (7)
Finalmente podemos calcular el valor de BRP para fijar el tiempo de bit deseado:
Fosc = 16 MHz
𝐵𝑅𝑃 =0.0025 · 10−4 · 16 · 106
2= 2 (8)
Tasa de bits de 500 kbps
Realizando las mismas operaciones que en el caso anterior:
𝑡𝑏𝑖𝑡 =1
𝑓𝑏𝑖𝑡=
1
500 · 103= 0.02 · 10−4𝑠 (9)
𝑡𝑏𝑖𝑡 = 16 · 𝑇𝑄 → 𝑇𝑄 =𝑡𝑏𝑖𝑡
16=
0.02 · 10−4
16= 0.00125 · 10−4𝑠 (10)
𝐵𝑅𝑃 =0.00125 · 10−4 · 16 · 106
2= 1 (11)
Ahora ya tenemos los valores de BRP para cada uno de los casos. Utilizando las
instrucciones anteriormente descritas podremos asignar un 1 o un 2 al registro BRP según si
queremos transmitir a 500 kbps o a 250 kbps.
4.2.1.4.2 Transmisión de mensajes
El MCP2515 dispone de 3 buffers de transmisión pero en nuestro caso solo se utilizará uno
de ellos. Los buffers de transmisión se pueden configurar mediante un total de 14 registros
mapeados en la memoria SRAM. Los registros asociados al buffer que se utilizará son:
• TXB0CTRL: este registro indica las condiciones bajo las que se enviaran los
mensajes y el estado de las transmisiones.
• TXB0SIDH, TXB0SIDL, TXB0EID8 y TXB0EID0: son los registros que
contienen el identificador de los mensajes. En caso de enviar tramas estándar
(identificador de 11 bits) solo se deben utilizar los 2 primeros registro. Si por lo
contrario se envían tramas extendidas (identificador de 29 bits) se deben emplear
todos los registros.
Desarrollo del hardware
64
• TXB0DLC: este registro contiene la longitud del campo de datos.
• TXB0D0, TXB0D1, TXB0D2,…, TXB0D7: Estos registros contienen los bytes de
datos.
A continuación se explicaran con mas detalles algunos de los anteriores registros:
Registro TXB0CTRL
Los bits que forman este registro son los siguientes:
Figura 46: Bits del registro TXB0CTRL
• ABTF: Bit de solo lectura. Sirve para monitorizar el estado de la transmisión. Si
toma el valor 1 significa que el mensaje que se ha abortado, en caso de tomar el valor
0 indicaría que la transmisión se ha completado con éxito.
• MLOA: Bit de solo lectura. Al igual que en el caso anterior, sirve para ver el estado
de la transmisión. Si toma el valor 1 significa que el nodo ha perdido el arbitrio y
debe reintentar la transmisión.
• TXERR: Bit de solo lectura. Este bit indica si se ha detectado un error en el bus. Si
vale 1 significa que se ha detectado un error.
• TXREQ: Bit de lectura y escritura. Mediante este bit podemos iniciar la transmisión
de una trama y al mismo tiempo podemos ver su estado. Asignamos un 1 a este bit
se inicia la transmisión de una trama y cunado esta es finalmente enviada el valor del
bit vuelve a 0.
• TXP[1:0]: Bits de lectura y escritura. Mediante estos bits se puede configurar la
prioridad del mensaje. Esta prioridad no afecta a la prioridad del mensaje dentro del
bus sino que se utiliza para resolver conflictos internos como por ejemplo cuando se
pide iniciar la transmisión de mas de un buffer a la vez. Como solo se utilizara un
buffer el valor de estos bits no nos afectan.
Desarrollo del hardware
65
Registro TXB0SIDH
Figura 47: Bits del registro TXB0SIDH
Este registro contiene el byte de mas peso del identificador.
Registro TXB0SIDL
Figura 48: Bits del registro TXB0SIDL
Este registro contiene los 3 bits de menos peso del identificador de 11 bits o bien los bits 18,
19 y 20 del identificador de 29 bits.
También contiene el bit EXIDE. Este bit de lectura y escritura permite definir el tipo de
identificador de los mensajes. Si vale 1 se transmitirán mensajes con identificador extendido,
si vale 0 se transmitirán mensajes con identificador estándar.
Registros TXB0EID8 y TXB0EID0
Estos registros contienen los 16 bits de menos peso del identificador extendido. Solo se
utilizan cuando el bit EXIDE vale 1.
Proceso de transmisión de una trama
Teniendo en cuenta los registros que intervienen en la transmisión de los mensajes, podemos
seguir el siguiente procedimiento cada vez que se quiera enviar una trama a través del bus
CAN:
• Escribir la información de los campos de la trama a enviar a los registros
correspondientes. Esta información es el identificador, los bytes de datos y el número
de bytes de datos.
• Poner a 1 el bit T0REQ y esperar a que vuelva a valer 0. Esto nos indica que la
transmisión ha finalizado.
• Si uno de los bits T0ERR o MLOA se pone a 1 antes de que finalice la transmisión
significa que se ha producido un error y el controlador debe reintentar la transmisión.
Si el error persiste debemos abortar la transmisión poniendo un 0 en el bit T0REQ.
Con la información proporcionado en este apartado se pueden enviar tramas a través del bus
CAN.
Desarrollo del hardware
66
4.2.1.4.3 Recepción de mensajes
Configuración del filtro del buffer de recepción
Al igual que en la transmisión de mensajes, el MCP2515 dispone de varios buffers de
recepción. Cada buffer de recepción dispone de un filtro y una mascara que permiten aceptar
solo mensajes con un cierto identificador. Para esta aplicación solo se utilizará uno de los
buffers de recepción.
Los registros asociados al buffer de recepción son los siguientes:
• RXB0CTRL: Este registro contiene los bits asociados al estado del buffer de
recepción.
• RXB0SIDH, RXB0SIDL, RXB0EID8, RXB0EID8, RXB0DLC, RXB0D0,
RXB0D1, RXB0D2,…, RXB0D7: Estos registros contienen el valor de los campos
del mensaje recibido. Son registros equivalentes a los del buffer de transmisión pero
en este caso su valor depende de los mensajes recibidos.
• RXF0SIDH, RXF0SIDL, RXF0EID8, RXF0EID0, RXM0SIDH, RXM0SIDL,
RXM0EID8, RXM0EID0: Estos registros están asociados al filtro y la máscara del
buffer. Su función se explica más adelante.
Como se ha comentado anteriormente, el filtro y la mascara permiten descartar mensajes del
bus para que lleguen al microcontrolador solo un tipo de mensajes. Los registros para
configurar el filtro y la mascara son los siguientes: RXF0SIDH, RXF0SIDL, RXF0EID8,
RXF0EID0, RXM0SIDH, RXM0SIDL, RXM0EID8 y RXM0EID0.
En primer lugar se debe configurar el tipo de identificador al que queremos aplicar el filtro
y la máscara. Esto se realiza a través del bit EXIDE del registro RXF0SIDL. Si queremos
recibir tramas estándar tenemos que asignar un 1 al bit EXIDE, y en caso de querer recibir
tramas extendidas se tiene que asignar un 0.
Seguidamente se deben configurar los bits de la máscara. Mediante la máscara se puede
elegir los bits del identificador a los que se aplicará el filtro.
Si se ha configurado el buffer para recibir tramas estándar debemos asignar un valor solo a
los registros RXM0SIDH y RXM0SIDL, si se ha configurado para recibir tramas extendida
también se tendrá que asignar un valor a RXM0EID8 y RXM0EID0.
Ahora solo queda configurar los bits del filtro. El filtro solo se aplica a los bits que se han
indicado en la máscara, es decir, para que un mensaje sea aceptado todos los bits indicados
a la máscara deberán tener el valor indicado en el filtro.
Al igual que con la máscara, si se ha configurado el buffer para recibir tramas estándar
debemos asignar un valor solo a los registros RXF0SIDH y RXF0SIDL, si se ha configurado
para recibir tramas extendida también se tendrá que asignar un valor a RXF0EID8 y
RXF0EID0.
Desarrollo del hardware
67
Figura 49: Funcionamiento de la mascara de bits.
Para que el filtro sea aplicado no es suficiente con asignarle un valor, además, se debe activar
a través del registro RXB0CTRL. Dentro de este registro los bits asociados a la activación
del filtro son los bits RXM1 y RXM0. Para que el filtro esté activo se debe asignar un 0 a
ambos bits.
Proceso de recepción de mensajes
Cuando se transmite un mensaje a través del bus el MCP2515 lo recibe y seguidamente
aplica los filtros al identificador, si este cumple las condiciones del filtro se guarda en el
buffer de recepción y se pone a 1 el bit RX0IF del registro CANINTF. Entonces utilizando
el método de polling el microcontrolador puede percatarse de la recepción de un mensaje
para así obtener su contenido el cual está en los registros anteriormente mencionados.
Desarrollo del hardware
68
4.2.2 Circuito de la interfaz del ISO 15765-4
Figura 50: Circuito de la interfaz ISO 15765-4.
A continuación se recuerda la función de algunos de los componentes:
• R3: Esta resistencia de 120 Ohms es necesaria para evitar que se produzcan rebotes
en el BUS CAN.
• C5: Este condensador de 100 nF sirve para filtrar posibles ruidos en la
alimentación.
Desarrollo del hardware
69
4.3 Interfaz hardware entre el microcontrolador y el ordenador
4.3.1 Descripción de la interfaz
Con el fin de comunicarse con el equipo de test y poder gobernarlo desde otro dispositivo
(un ordenador por ejemplo) se debe establecer un sistema de comunicación a través del cual
se mande la información necesaria. Este sistema nos va a permitir:
• Mandar comandos al dispositivo para conectarse o desconectarse con el vehículo.
• Mandar comandos al dispositivo para modificar algunos parámetros.
• Enviar peticiones el vehículo.
• Recibir las respuestas mandadas por el vehículo.
Este sistema de comunicación debe ser un sistema punto a punto ya que nos interesa
controlar el equipo de test desde un solo dispositivo. Una solución barata y fácil de
implementar seria una comunicación mediante UART. Utilizando una UART solo
tendremos que diseñar la capa de enlace de nuestro sistema ya que la capa física ya está
implementada.
El único inconveniente que tenemos con esta solución es que actualmente no hay
ordenadores que dispongan de módulos UART para conectar periféricos externos. Una
solución seria utilizar un módulo bluetooth que disponga de UART para hacer de puente
entre la UART del equipo de test y el ordenador. El siguiente diagrama representa cómo
sería la conexión entre el equipo de test y el ordenador:
Figura 51: Diagrama de conexión entre el MCU, el HC-06 y el ordenador.
El módulo bluetooth que se utilizara es el HC-06. Cuando el ordenador se conecta al módulo
bluetooth, se crea un puerto serie (en el ordenador) a través del cual un programa puede
intercambiar bytes con el módulo.
Desarrollo del hardware
70
Cuando el módulo recibe un byte del ordenador, este byte es transmitido al microcontrolador
a través de la UART. Por otra parte, si el modulo recibe un byte del microcontrolador, este
byte es transmitido al ordenador. Es decir, todo lo que envía el ordenador se transmite al
microcontrolador y todo lo que envía el microcontrolador se transmite al ordenador.
Esto permite emular una conexión por puerto serie entre el microcontrolador y el
ordenador:
Figura 52: Conexión equivalente entre el escáner OBD2 y el ordenador.
4.3.2 Circuito de la interfaz
Figura 53: Circuito de la interfaz Bluetooth.
Desarrollo del hardware
71
4.4 El microcontrolador PIC18F4550
El microcontrolador es el cerebro del dispositivo objeto de este proyecto. Un
microcontrolador es un circuito integrado programable que dispone de una memoria en la
que se puede almacena un programa, esta memoria se denomina memoria de programa. Un
programa consiste en un conjunto de instrucciones que son interpretadas por el
microcontrolador. Estas instrucciones suelen indicar al microcontrolador que realice una
cierta operación con uno o varios datos (operaciones aritméticas, operaciones booleanas
etc.). Dentro del microcontrolador hay otra memoria denominada memoria de datos que es
la que contiene las variables del programa. Esta memoria de datos suele ser del tipo SRAM.
Además de las memorias, el microcontrolador dispone de una serie de periféricos que
realizan unas determinadas funciones. Los periféricos que podemos encontrar habitualmente
dentro de los microcontroladores son: UART, SPI, I2C, PWM, ADC, DAC etc.
Mediante un programa podemos configurar y operar estos periféricos ya que dentro de la
memoria de datos se reservan espacios de memoria o registros para cada uno de estos
dispositivos. Modificando los valor de estos registros podemos configurar estos periféricos
para que funcionen de una determinada forma. En caso de tratarse de periféricos de
entrada/salida (Entradas/salidas digitales, ADC, DAC etc.) podemos obtener o mandar los
datos realizando una lectura o escritura a los registros correspondientes.
Dentro de este proyecto el microcontrolador será el encargado de implantar los dos
protocolos OBD2 elegidos (ISO9141-2 y ISO 15765-4). Esto supone que tendrá que utilizar
el hardware anteriormente descrito más otros módulos que llevará integrados dentro del
propio silicio. Además, tendrá que integrar otro protocolo de comunicación que será el que
permitirá mandarle ordenes desde un ordenador. Estas ordenes permitirán hacer peticiones
al sistema OBD2 de un automóvil o bien modificar la configuración del propio dispositivo.
En el mercado existe una gran variedad de microcontroladores, esto permite al desarrollador
elegir el más adecuado a su proyecto. Para este proyecto se ha decidido utilizar
microcontrolador PIC18F4550. Este microcontrolador es desarrollado y fabricado por la
empresa Microchip Technology inc. la cual se encuentra en el top de empresas fabricantes
de semiconductores.
El PIC18F4550 es un microcontrolador de 8 bits con arquitectura RISC (Reduced Instruction
Set Computer). Como su propio fabricante indica, el diseño de este microcontrolador es apto
para que sea utilizado dentro de la industria del automóvil con lo cual es una opción acertada
para este proyecto.
Desarrollo del hardware
72
A continuación se mostraran algunas de las características de este dispositivo, esto incluye
su memoria, los periféricos de los que dispone, sus características eléctricas etc.
Característica Valor
Tipo de memoria de programa Flash
Tamaño memoria de programa 32 KB
Velocidad máxima CPU (MIPS) 12
Tipo memoria de datos SRAM
Tamaño memoria de datos 2048 Bytes
Tamaño memoria EEPROM 256 Bytes
Periféricos de comunicación 1 UART, 1 SPI, 1 I2C, 1 USB
Conversor ADC 13 canales y 10 bits
Rango de temperatura -40 a 85 °C
Tensión de alimentación 2 a 5,5 V
Tabla 22: Algunas características del PIC18F4550.
Estas son solo algunas de las características de este microcontrolador. Toda la información
acerca de este dispositivo se encuentra en la hoja de datos proporcionada por el fabricante.
Este apartado se explicará el hardware que se ha tenido que añadir para que el
microcontrolador funcione correctamente. Además, se explicará la configuración aplicada a
los periféricos que se han utilizado.
4.4.1 Componentes externos del PIC18f4550
4.4.1.1 Circuito oscilador.
El oscilador que se utilizará es un oscilador de cristal de 4 MHz.
Figura 54: Oscilador de cristal de 4 MHz.
El siguiente esquema indica como se debe conectar el oscilador de cristal al
microcontrolador:
Figura 55: Esquema de conexión del oscilador.
Los componentes C1 y C2 son condensadores cerámicos de 22 pF.
Desarrollo del hardware
73
4.4.1.2 Circuito de reset del microcontrolador
Es necesario disponer de un mecanismo de reset para reiniciar el microcontrolador y llevarlo
a un estado conocido. Hay diversos mecanismos a través de los cuales se puede realizar un
reset del microcontrolador, el más habitual es utilizando un pin denominado MCLR.
El microcontrolador PIC18F4550 permite reset por hardware a través del pin MCLR por lo
que será el mecanismo usado en este dispositivo. Para poder hacer un reset a través de este
pin se debe añadir el siguiente circuito:
Figura 56: Circuito de reset del PIC18F4550.
Cuando el pin MCLR se encuentra en un potencial alto (5 V), el PC (program counter) se
incrementa con cada flanco del señal de reloj de la CPU con lo cual el MCU está ejecutando
el programa. En cambio cuando esta en un potencial bajo (0 V) el PC se reinicia a 0 (o a la
dirección de inicio del programa) y se mantiene en ese valor con lo que se paraliza la
ejecución del programa.
Si se aprieta el pulsador del circuito anterior, el contador del programa vuelve a la primera
instrucción y se mantiene durante el tiempo que se esta apretando el pulsador. Una vez se
deja de pulsar el PC vuelve a correr con lo que se vuelve a ejecutar el programa pero desde
la primera instrucción. Así es como se consigue realizar un reset del microcontrolador.
El Condensador C2 tiene la función de filtrar los efectos de los rebotes producidos al
accionar el polsador. Su valor típico es de 100 nF.
4.4.2 Configuración del oscilador
Una de las primeras cosas que se deben configurar dentro del microcontrolador es su señal
de reloj. Esta consiste en una señal cuadrada y tiene la función de marcar el ritmo de
ejecución de las instrucciones. En el caso de este microcontrolador, se ejecuta una
instrucción cada 4 flancos de la señal de reloj. Por lo tanto la frecuencia de ejecución de las
instrucciones depende directamente del señal de reloj utilizado, la expresión que los
relaciona es la siguiente:
𝑓𝑐𝑦𝑐 =𝑓𝑜𝑠𝑐
4 (12)
Desarrollo del hardware
74
Esta frecuencia de reloj suele proveer de un circuito externo aunque hay microcontroladores,
como es el caso, que disponen de un oscilador interno que proporciona este señal.
Para este proyecto se ha decidido utilizar un oscilador externo ya que se requiere de precisión
y estos suelen ser mas precisos que los internos.
El oscilador utilizado en este proyecto es un oscilador de cristal de cuarzo del tipo XT (X-
Tall). Hay otro tipo de osciladores como los HS (High Speed) que permiten frecuencias
mayores. Para este proyecto es suficiente con un oscilador del tipo XT.
El PIC18F4550 permite conectar varias fuentes de reloj aunque solo una de ellas puede ser
utilizada por la CPU. Además, en cada fuente de reloj permite conectar diversos tipos de
osciladores.
El siguiente diagrama muestra la conexión interna de las distintas fuentes de reloj de las que
dispone el MCU.
Figura 57: Clock Diagram del PIC18F4550.
En este caso queremos conectar el oscilador a la fuente primaria. Tal y como se puede
observar, la señal del oscilador puede seguir diversos caminos hasta llegar a la CPU. Entre
Desarrollo del hardware
75
uno de estos caminos se encuentra un componente denominado PLL. Este dispositivo es
capaz de obtener un señal de reloj de 96 MHz a partir de uno de 4 MHz. Para que la CPU
pueda utilizar este camino, el multiplexor indicado en la figura debe estar configurado para
que conecte su salida con la entrada 1. Además del PLL, este camino dispone de una serie
de multiplexores y divisores. La función de estos componentes es la de adaptar las señales
de entrada y salida del PLL. Como se ha mencionado la señal de entrada del PLL debe ser
de 4 MHz, si esta señal tiene otra frecuencia el dispositivo no funcionara correctamente.
El bloque divisor situado a la entrada del PLL multiplica la señal del oscilador por una serie
de ganancias, estas son 1/1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/10 y 1/12. Mediante el multiplexor
conectado al divisor podemos elegir que señal de las obtenidas por el divisor queremos
conectar a la entrada del PLL.
A la salida del PLL tenemos una configuración parecida a la que hay en la entrada. Un bloque
divide la frecuencia de la señal de salida del PLL y obtiene una serie de señales cuyas
frecuencias son 48, 32, 24 y 16 MHz. Mediante otro multiplexor podemos elegir cuál señal
utilizar como señal de reloj de la CPU.
En este caso se dispone de un oscilador de cristal de 4 MHz y queremos hacer funcionar la
CPU a 48 MHz. Teniendo en cuenta los elementos anteriormente descritos y las variables
que influyen, para obtener la frecuencia de 48 MHz debemos utilizar la configuración
descrita a continuación.
La siguiente tabla muestra los registros de configuración del dispositivo, entro los cuales se
encuentran los relacionados con la configuración de la señal de reloj.
Tabla 23: Registros de configuración del PIC18F4550.
Los registros que debemos modificar son CONFIG1L y CONFIG1H. Los valores que se
tiene que asignar a estos registros son los siguientes.
Desarrollo del hardware
76
CONFIG1L
Figura 58: Bits del registro CONFIG1L.
El valor de este registro debe ser de 0x20.
CONFIG1H
Figura 59: Bits del registro CONFIG1H.
El valor de este registro debe ser de 0x3.
4.4.3 Periféricos del microcontrolador
En este apartado se explica con detalle la configuración de los periféricos del
microcontrolador PIC18F4550 utilizados para este proyecto. Estos periféricos son: UART,
SPI, Timer 0 y Timer 3.
4.4.3.1 El módulo UART
Como se ha explicado en apartados anteriores, la UART es un módulo que permite que dos
dispositivos establezcan una comunicación punto a punto y full-dúplex. Para este proyecto
la UART se utilizará para establecer comunicación con vehículos que dispongan del
protocolo ISO9141-2.
En el caso del PIC18F4550, el pin RX de la UART corresponde al pin 26 del MCU y el pin
TX corresponde al pin 25.
A continuación se explicará la configuración que se debe aplicar para que la UART funcione
correctamente.
Los registros asociados al modulo UART son los siguientes:
• TXSTA: Permite controlar y ver el estado de las transmisiones.
• RCSTA: La misma función que el anterior pero para las recepciones.
• BAUDCON: Permite configurar la tasa de bits.
Desarrollo del hardware
77
Debemos asignar el valor correcto a estos registros para que la UART trabaje con la
configuración 8N1 y tenga una tasa de bits de 10,4 kbps.
Registro TXSTA
Este registro dispone de los siguientes bits:
Figura 60: Bits del registro TXSTA.
• CSRC: Este bit solo se aplica si la UART trabaja en modo síncrono, como no es el
caso no importa su valor.
• TX9: Este bit permite habilitar la transmisión de 9 bits. Queremos que la UART
transmita 1 byte por lo que se debe asignar un 0 a este bit.
• TXEN: Mediante este bit se habilita la transmisión. Su valor debe ser 1.
• SYNC: Este bit permite elegir el modo síncrono o asíncrono de la UART. En este
caso queremos el modo asíncrono y por eso se debe asignar un 0.
• BRGH: Este bit permite elegir entre el modo rápido o el modo lento de la UART.
Como queremos transmitir a 10,4 kbps y esta es una tasa de bits lenta, se debe elegir
el modo lento y para ello se tiene que asignar un 0 a este bit.
• TRMT: Mediante este bit podemos saber el estado de una transmisión, si esta a 0
significa que todavía se esta transmitiendo un byte, en caso contrario, el buffer de
transmisión esta vacío.
• TX9D: Este bit solo se aplica en la transmisión de 9 bits.
Registro RCSTA
Este registro dispone de los siguientes bits:
Figura 61: Bits del registro RCSTA.
• SPEN: Sirve para habilitar el puerto serie. Su valor debe ser 1.
Desarrollo del hardware
78
• RX9: Este bit activa la recepción de 9 bits. Como en el caso de la transmisión
queremos recibir solo 8 bits de datos por lo que se le debe asignar un 0.
• SREN: Este bit no se aplica en el modo asíncrono.
• CREN: Este bit habilita la recepción de datos por lo que debe valer un 1.
• ADDEN: Este bit no se aplica en el modo de 8 bits.
• FERR: Indica que se ha producido un error en la trama.
• OERR: Cuando se sobrescribe el buffer de recepción antes de que sea leído se pone
a 1 este bit.
• RX9D: Este bit no se aplica en el modo de 9 bits.
Registro BAUDCON
Este registro dispone de los siguientes bits:
Figura 62: Bits del registro BAUDCON.
Los bits que debemos inicializar para ajustar la tasa de bits son los siguientes:
• BRG16: Este bit permite determinar la longitud del BRG(baud rate generator) la cual
puede ser de 8 o 16 bits. Cuanto mayor sea la longitud del BRG menor podrá ser la
tasa de bits. Como el microcontrolador funcionara a una frecuencia relativamente
alta (48 MHz) y la tasa de bits que se requiere es baja, conviene tener un BRG mayor.
Por lo tanto, debemos elegir la opción de 16 bits asignando un 1 a este bit.
Configuración de la tasa de bits.
La tasa de bits con la que transmite y recibe la UART depende de un valor n de 16 bits
almacenado en los registros SPBRGH y SPBRG. La expresión que relaciona este valor con
la tasa de bits es la siguiente:
𝑏𝑎𝑢𝑑𝑖𝑜𝑠 =𝐹𝑜𝑠𝑐
4(𝑛 + 1)→ 𝑛 =
𝐹𝑜𝑠𝑐 − 4 · 𝑏𝑎𝑢𝑑𝑖𝑜𝑠
4 · 𝑏𝑎𝑢𝑑𝑖𝑜𝑠 (13)
Si se quiere una tasa de bits de 10,4 kbps:
Desarrollo del hardware
79
𝑛 =48 · 106 − 4 · 10400
4 · 10400= 1152 (14)
Los registros SPBRGH y SPBRG deben tener los valores 4 y 128, respectivamente
Transmisión de datos
Una vez configurado correctamente el modulo UART, el procedimiento a seguir para enviar
1 byte al otro nodo es el siguiente:
• Para iniciar la transmisión se debe escribir en el registro TXREG el byte que se desea
enviar.
• Una vez iniciada la transmisión se debe esperar a que termine. Cuando la transmisión
finaliza se pone a 1 el bit TXIF por lo que realizando polling sobre este bit el MCU
puede saber cuando a terminado la transmisión.
• Finalmente se tiene que poner un 0 al bit TXIF para dejarlo preparado para la
siguiente transmisión.
Recepción de datos
Para la recepción de datos se debe seguir el siguiente procedimiento:
• El MCU debe esperar a que se reciba un byte y para ello debe realizar polling sobre
el bit RCIF. Este bit se pone a 1 cuando se recibe un byte.
• Una vez recibido el byte se debe leer a través del registro RCREG.
• Finalmente se debe volver a poner un 0 al bit RCIF para dejarlo preparado para la
siguiente recepción.
4.4.3.2 El módulo SPI
Para que el microcontrolador pueda comunicarse con el integrado MCP2515 y establecer
una conexión con vehículos que implementen el protocolo ISO 15765-4 se debe utilizar el
modulo SPI en modo master. En este apartado se explicará todo lo relacionado con la
configuración de este módulo así como las conexiones que se deben realizar entre el
PIC18F4550 y el MCP2515.
Este microcontrolador dispone de un módulo denominado MSSP (Master Síncronos Serial
Port) el cual implementa los protocolos SPI (Master/Slave) y I2C (Master/Slave), aunque no
puede funcionar con los dos a la vez. Por lo tanto, en primer lugar se debe configurar para
que funcione como SPI o I2C. Los registros asociados con en modo SPI del MSSP son los
siguientes:
• SSPCON1: es el registro de control del SPI.
Desarrollo del hardware
80
• SSPSTAT: es el registro de estado.
• SSPBUF: es el buffer de transmisión/recepción.
Registro SSPSTAT
Figura 63: Bits del registro SSPSTAT.
• SMP: Mediante este bit se elige el momento en el que se muestrea la señal del pin
SDI. En este caso queremos que se muestre en el medio del tiempo de bit y para ello
el bit SMP debe valer 0.
• El bit CKE junto al bit CKP (registro SSPCON1) permiten elegir el modo con el debe
funcionar el SPI. Existen 4 modos de funcionamiento: Modo 0.0, Modo 1.1, Modo
0.1 y Modo 1.0. Estos modos se diferencian en el estado de reposo de la señal de
reloj y en que flanco (ascendente o descendente) se envían los bits. La siguiente
figura muestra la configuración de cada modo:
Figura 64: Señal de SCK para los diferentes modos del SPI.
En la siguiente figura se encuentran los valores de CKP y CKE para cada uno de los
modos:
Tabla 24: Valores de CKP y CKE para cada uno de los modos.
Desarrollo del hardware
81
El integrado MCP2515 esta preparado para que funcione con modo 1,1 por lo que debemos
configurar el PIC para que funcione en este mismo modo. Tal y como se puede observar en
la figura, para el modo 1,1 los bits CKP y CKE deben valer 1 y 0 respectivamente.
• BF: Este bit indica el estado del buffer. Si vale 1 significa que se ha finalizado la
recepción y el buffer SSPBUF esta lleno, en caso contrario, la recepción no ha
finalizado todavía.
Registro SSPCON1
Figura 65: Bits del registro SSPCON1.
• WCOL: Este bit indica cuando se produce una colisión en el buffer SSPBUF, es
decir, cuando este registro es modificado durante una transmisión.
• SSPEN: Mediante este bit se activa el MSSP. Para activarlo el bit debe valer 1.
• SSPM3:SSPM0: Mediante estos bits se puede configurar el modo y la frecuencia de
funcionamiento del MSSP. La siguiente figura muestra el modo de funcionamiento
para cada caso:
Figura 66: Valores de SSPM3:SSPM0 para cada modo y frecuencia del SPI.
Queremos que funcione como SPI en modo master y a una frecuencia de FOSC/16 por lo
que los bits SSPM3:SSPM0 deben valer 0001.
Configuración de los pines del modulo MSSP
Para que este módulo pueda funcionar como SPI, los pines del MCU asociados al modulo
deben estar configurados de una forma concreta.
La configuración a aplicar es la siguiente:
Desarrollo del hardware
82
• SDI: Se debe inicializar como entrada digital (TRISB<0> = 1).
• SDO: Se debe inicializar como salida digital(TRISC<7> = 0).
• SCK: También se debe inicializar como salida(TRISB<1> = 0).
• CS: Este pin debe ser una salida digital. Tal y como se puede observar en la figura,
este pin no esta definido sino que puede ser cualquier pin que pueda funcionar como
salida digital. En este caso se ha utilizado el pin RB2
Transmisión de bytes:
Para poder enviar un byte a través del modulo SPI se debe seguir el siguiente procedimiento:
• En primer lugar se debe poner el pin CS en un potencial bajo, esto indica al otro nodo
que se ha inicializado una transmisión.
• Posteriormente se debe escribir el byte a enviar en el registro SSPBUF. Esto inicia
automáticamente la transmisión.
• El MCU debe esperar a que finalice la transmisión realizando polling al bit BF.
Cuando este bit valga 1 se debe dar la transmisión por finalizada. Este paso se puede
realizar por cada byte que se quiera enviar.
• Finalmente se debe poner el pin CS en un potencial alto para indicar que la
transmisión ha finalizado.
Recepción de bytes
En este caso el procedimiento es muy parecido al anterior:
• En primer lugar se debe poner el pin CS en un potencial bajo.
• Posteriormente se debe escribir cualquier cosa en el registro SSPBUF. Esto inicia
automáticamente la recepción del byte.
• El MCU debe esperar a que finalice la recepción de la misma forma que en el caso
anterior. Una vez finalizada la recepción se debe leer el registro SSPBUF para
guardar el byte recibido en otro espacio de memoria. Este paso se puede realizar para
cada uno de los bytes que se quieren recibir.
• Finalmente se debe poner el pin CS en un potencial alto para indicar que la recepción
ha finalizado.
Desarrollo del hardware
83
4.4.3.3 El Timer 0
El microcontrolador PIC18F4550 dispone de varios módulos timer como es el TIMER0. Un
módulo timer consiste básicamente en un contador que se incrementa cuando se produce un
flanco de una señal cuadrada. El uso principal que se le da a los timers es como base de
tiempo para el MCU.
En este caso el TIMER0 se utilizara para generar una interrupción de tiempo cada 1 ms así
se obtiene una basa de tiempo para los retardos.
Los registros asociados al TIMER0 son los siguientes:
• T0CON: Permite configurar el timer
• TMR0: Este registro es el contador del timer.
Registro T0CON
Figura 67: Bits del registro T0CON.
• TMR0ON: Este bit activa o desactiva el timer. Para activarlo debe estar a 1.
• T08BIT: Este bit determina la longitud del contador del timer, es decir, del registro
TMR0. Si este bit esta a 1 el tamaño de TMR0 es de 8 bits, en caso contrario es de
16 bits. Queremos un contador de 8 bits por lo que este bit debe valer 1.
• TOCS: Este bit permite elegir la señal del timer la cual puede ser externa (a través
del pin T0CKI) o interna (Fcyc). En este caso se utilizara la interna por lo que este
bit debe valer 0.
• T0SE: Este bit permite elegir entre incrementará el contador en cada flanco
ascendente o en cada flanco descendente.
• PSA: Este MCU dispone de un prescaler que divide la señal de reloj de la CPU y
obtiene otra señal que se puede utilizar para el timer 0. Para que la señal del timer 0
sea la de la salida del prescaler el bit PSA debe valer 0.
• T0PS2:T0PS0: Estos bits permiten elegir la ganancia del prescaler. En este caso
queremos una ganancia de 1/128 por lo que debemos asignar a T0PS2:T0PS0 los
valores 1100. Aplicando esta ganancia y considerando que Fcyc = 12 MHz tenemos
que el TIMER0 funcionará a 93750 Hz
Desarrollo del hardware
84
4.4.3.4 El Timer 3
Como se ha dicho en el apartado anterior, el PIC18f4550 dispone de varios timers. Para este
proyecto se necesita más de un timer por lo que debemos configurar otro a parte del
TIMER0. Este segundo timer es el TIMER3 y su función va a ser la de generar el tiempo de
bit para una UART por software (explicada más adelante). Aunque la UART por software
transmitirá a una baja tasa de bits (9600 baudios), la resolución que ofrece el TIMER0 no es
suficiente para generar el tiempo de bit. Por esta razón se debe configurar otro timer que
trabaje a una frecuencia mayor.
El TIMER3 es un timer de 16 bits el cual dispone de los siguientes registros:
• T3CON: Registro de configuración del timer.
• TMR3H: byte de mas peso del contador del TMR3.
• TMR3L: byte de menos peso del contador del TMR3.
Registro T3CON
Figura 68: Bits del registro T3CON.
• RD16: Mediante este bit se configura el modo de lectura/escritura del contador del
timer, el cual es de 16 bits. En el primer modo las operaciones de lectura/escritura
del contador se realizan mediante los registros TMR3L y TMR3H por separado. En
el segundo modo al realizar una operación sobre el registro TRM3L se guarda el byte
de más peso del contador en ese instante en el registro TMR3H. En caso se
configurará el segundo modo ya que permite evitar errores de lectura/escritura, para
ello el bit debe valer 1.
• T3CKPS1:T3CKPS0: Estos bits permiten elegir la ganancia del prescaler de
entrada del TIMER3. En este caso se asigna a T3CKPS1:T3CKPS0 de 0b10 con lo
que se obtiene una ganancia de 1/4. Esto supone que el TIMER3 trabajara a una
frecuencia de 3 MHz.
• TMR3CS: Mediante este bit se elige la fuente de la señal del TIMER3. Queremos
que esta sea Fcyc por lo que se debe asignar un 0 a este bit.
• TM3ON: Este bit permite activar o desactivar el timer, para activarlo se le debe
asignar un 1.
Desarrollo del hardware
85
4.4.4 Circuito del microcontrolador
Figura 69: Circuito del MCU PIC18F4550.
Como los módulos SPI y UART comparten el pin RC7, se debe añadir la resistencia R1 de
1k Ohm para aislar las señales y que los módulos no interfieran entre ellos. Añadir esta
resistencia no implica que se puedan utilizar a la vez, esto solo permite que un módulo pueda
estar activo sin que el otro (inactivo) afecta a sus señales.
4.5 Circuito de alimentación
4.5.1 Módulo Mini-360
Como se puede ver en los apartados anteriores muchos de los componentes que se utilizan
requieren una tensión de +5 V, sin embargo, el conector OBD2 solamente ofrece una tensión
de +12 V. Por esta razón se debe añadir un circuito que convierta los +12 V a +5V. La
solución que se implantará consiste en utilizar un convertidor buck realimentado que permita
bajar la tensión a +5 V. El uso de un convertidor buck realimentado permite que se mantenga
una salida de +5V aunque se produzcan variaciones en la tensión de entrada.
Figura 70: Módulo Mini-360
Desarrollo del hardware
86
El módulo que se utilizará dispone del convertidor Buck MP1584. Este módulo ya viene con
todos los componentes necesarios para que funcione el convertidor. La tensión de salida se
ajusta mediante un potenciómetro.
Características del módulo:
Característica
Tensión de entrada 4.75 – 23 V
Tensión de salida 1 – 17 V
Corriente de salida máxima 3 A de pico y 1.8 A en régimen permanente
Eficiencia 96 %
Regulación de carga +/- 0.5%
Regulación de linea +/- 2.5%
Tabla 25: Características del convertidor Buck.
4.5.2 Circuito del módulo
Figura 71: Circuito del módulo Mini-360
Tal y como se puede ver en el esquema anterior, simplemente se deben conectar los +12 V
a la entrada y se obtienen los +5 V que se necesitan.
Desarrollo del hardware
87
4.6 Circuito completo
Desarrollo del hardware
88
Desarrollo del hardware
89
BOM (Bill Of Materials)
ID Designator Type Value Quantity
1 U2 Connector OBD2 Male connector 1
2 U3 IC MC33290 1
3 R2 Resistor 500R 1
4 U4 IC MCP2515 1
5 U5 IC TJA1050 1
6 R3 Resistor 120R 1
7 C3,C4 Capacitor 15pF 2
8 X2,X3 Oscillator 16 MHz 2
9 C5 Capacitor 100nF 1
10 U6 Module buck mini 360 1
11 HC-1 Module hc-06 1
12 U7 IC PIC18F4550- I/PT 1
13 R4,R1 Resistor 1k 2
14 KEY2 Switch - 1
15 C6,C7 Capacitor 22pF 2
Tabla 26: BOM del circuito
Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
90
5. Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
En el apartado 4.3 se ha definido el hardware que se utilizará para enviar la información
entre los dos nodos. En este apartado se explicarán el formato de las tramas que se
intercambiaran los nodos, es decir, se definirá la capa de enlace.
Para esta aplicación vamos a implementar los siguientes tipos de trama:
• Trama de request: A través de esta trama el ordenador puede pedir un PID de un
determinado servicio.
• Trama de respuesta: A través de esta trama el equipo de test envía la respuesta del
vehículo al ordenador.
• Trama de comando: A través de esta trama el ordenador puede enviar un
comando al equipo de test.
• Trama de confirmación de comanda: Mediante esta trama el equipo de test
confirma la realización de una comanda.
• Trama de respuesta negativa: A través de esta trama el equipo de test indica al
ordenador que se ha producido un error.
Trama de request
Campos de la trama y longitud:
<0x2><service><PID><checksum>
• 0x2: Es el byte que identifica la trama.
• Service: Este byte indica el servicio al cual pertenece el PID.
• PID: como su nombre indica, es el PID del cual se quiere conocer su valor. Los
limites de este campo no están definidos.
• Checksum: es el checksum de la trama. El tamaño es de un byte.
Trama de respuesta
Campos de la trama y longitud:
<0x1><service><PID><dataLen><data><checksum>
Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
91
• 0x1: Es el byte que identifica la trama.
• Service: Este byte indica el servicio al cual pertenece el PID.
• PID: como su nombre indica, es el PID del cual se quiere conocer su valor. Los
limites de este campo no están definidos.
• dataLen: Este byte indica la longitud del campo de datos.
• data: Dentro de este campo se encuentra la respuesta del vehículo. La longitud está
limitada por el valor máximo de dataLen, que es 255.
• Checksum: es el checksum de la trama. El tamaño es de un byte.
Trama de comanda
Campos de la trama y longitud:
<0x3><command><paramsLen><params><checksum>
• 0x3: Es el byte que identifica la trama.
• Command: Es el byte que indica la comanda a realizar.
• paramsLen: este byte indica la longitud del campo params.
• Params: Son los bytes de los parámetros relativos a la comanda. Su longitud no
esta definida.
• Checksum: es el checksum de la trama. El tamaño es de un byte.
Trama de error
Campos de la trama y longitud:
<0x4><errorCode><checksum>
• 0x4: Es el byte que identifica la trama.
• errorCode: Este byte indica el código del error.
• Checksum: es el checksum de la trama. El tamaño es de un byte.
Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
92
Trama de confirmación de comanda
Campos de la trama y longitud:
<0x5><command><paramsLen><params><checksum>
• 0x5: Es el byte que identifica la trama.
• Command: Es el byte que indica la comanda a realizar.
• paramsLen: este byte indica la longitud del campo params.
• Params: Son los bytes de los parámetros relativos a la comanda. Su longitud no
esta definida.
• Checksum: es el checksum de la trama. El tamaño es de un byte.
Flujo de tramas entre el dispositivo obd2 y el ordenador
Dentro de este sistema de comunicación formado por el dispositivo OBD2 y el ordenador,
el nodo que toma el papel de master es el ordenador. El dispositivo OBD2 nunca puede
iniciar la comunicación, está siempre es iniciada por el ordenador y el dispositivo OBD2
simplemente responde.
Las tramas que puede utilizar el ordenador para iniciar la comunicación son la trama de
request y la trama de comanda. Ante esta petición, el dispositivo OBD2 puede responder con
las siguientes tramas: trama de respuesta, trama de error y trama de confirmación de
comanda.
En las siguientes figuras se puede ver los distintos intercambios que pueden haber:
Figura 72: Envío de una trama request y recepción de una trama respuesta
Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
93
Figura 73: Envío de una trama request y recepción de una trama error.
Figura 74: Envío de una trama comanda y recepción de una confirmación.
Protocolo de comunicación entre el dispositivo OBD2 y el ordenador
94
Figura 75: Envío de una trama comanda y recepción de una trama error.
Desarrollo del software
95
6. Desarrollo del software
En este apartado se explicara todo lo relacionado con el proceso de desarrollo del software
así como el software en sí.
El programa que se grabará en el microcontrolador está escrito en embedded C y se ha
utilizado el entorno de desarrollo MPLAB X IDE v5.10 para su elaboración. También se ha
utilizado el programa el mismo programa para grabar el código al MCU a través del
programador PICKIT3.
6.1 Objetivo del software
El primer paso en el desarrollo del software es determinar cuáles serán sus funcionalidades.
Para este proyecto, el código elaborado debe implementar las siguientes funcionalidades:
• Inicializar y configurar todos los periféricos que se deben utilizar para el proyecto.
Ejemplos de estos periféricos son la UART o el SPI.
• El programa debe implementar los protocolos ISO 9141-2 e ISO 15765-4. Esto
implica desarrollar el software para implementar y gestionar las distintas capas del
protocolo. Para las capas físicas se deben elaborar los drivers que permitan
gestionar el hardware estas capas, en el caso de del ISO 15765-4 se deben diseñar
todo un conjunto de funciones para controlar el integrado MCP2515.
• También se debe implementar el protocolo de comunicaciones entre el escáner
OBD2 y el ordenador. Para esta parte se debe desarrollar una UART por software
ya que el PIC18F4550 solo dispone de 1 UART, la cual es utilizada para el ISO
9141-2.
• Por último el programa debe ser capaz de gestionar todos los protocolos
implementados para que el usuario pueda utilizarlos desde el ordenador. Esta parte
seria la interfaz entre los protocolos del sistema OBD2 y el protocolo de
comunicación entre el escáner OBD2 y el ordenador.
En resumen el programa debe gestionar las comunicaciones entre el ordenador y el escáner
OBD2 y entre el escáner OBD2 y el vehículo, tal y como se muestra en la siguiente figura.
Figura 76: Interacción entre el ordenador, el escáner OBD2 y el vehículo.
Desarrollo del software
96
6.2 Proceso de desarrollo del software
En este apartado se explicará el procedimiento seguido para desarrollar el software y las
herramientas utilizadas durante el proceso.
En primer lugar se debe preparar el entorno de desarrollo del software, este es el MPLAB X
IDE v5.10. Este programa informático permite escribir código en C, C++ y ASM
(Ensamblador) aunque solo se utilizará para programar en C.
Figura 77: Entorno de desarrollo del MPLAB.
Descargada e instalada la herramienta, se debe proceder a instalar el compilador adecuado
al microcontrolador que se quiere utilizar. El compilador es el encargado de traducir el
programa escrito en C a lenguaje máquina que es el que entiende el microcontrolador. Existe
un compilador para cada arquitectura de MCU, en este caso necesitamos un compilador para
microcontroladores de 8 bits. El compilador que finalmente se ha instalado es el XC8 v2.0.
Una vez preparado el entorno de desarrollo (MPLAB v5.10 + XC8 v2.0) se debe crear y
configurar un proyecto. El procedimiento a seguir es muy intuitivo gracias a su entorno
grafico.
Creado el proyecto ya podemos añadir los ficheros en los que se escribirá el código de
nuestro programa.
Desarrollo del software
97
A partir de aquí empieza el proceso de desarrollo del software. Podemos representar este
proceso mediante el siguiente diagrama:
Figura 78: Diagrama del proceso de desarrollo del software.
El programa MPLAB también incluye una herramienta de simulación mediante la cual
podemos realizar una primera revisión del código. Una vez asegurado que el código tenga
el comportamiento deseado se debe proceder a programar el microcontrolador mediante el
PICKIT3 y el programa informático.
Desarrollo del software
98
Figura 79: Dispositivo programador PICKIT3.
Este dispositivo debe estar conectado al ordenador y al microcontrolador. La conexión al
ordenador se realiza mediante un puerto USB y la conexión al microcontrolador se realizar
a través de los pines VPP, VDD, VSS, PGD, PGC de ambos dispositivos (MCU y PICKIT3).
En primer lugar se debe realizar la programación en modo debug, este modo permite grabar
y ejecutar el programa teniendo control del MCU desde el MPLAB. En este modo se puede
pausar la ejecución del código, reanudarla, pausarla en un punto concreto indicado mediante
breakpoints, ver la memoria de datos etc.
Este modo permite realizar una revisión más exhaustiva del programa escrito. Finalizada la
revisión en modo debug se puede proceder a grabar el código en el modo normal. Si en el
modo debug se observa algún problema se debe reiniciar el proceso para poder corregirlo.
Con esto se da por acabado el proceso de desarrollo del software.
El proceso descrito en este apartado es muy generalista ya que este puede variar según el
proyecto del que se trate.
6.3 Descripción del software
Las funciones desarrolladas se pueden dividir en las siguientes categorías:
• Funciones generales: para el manejo de los periféricos utilizados (UART, SPI,
TIMERS etc.) y otras funciones que no están directamente relacionadas con la
implantación de los protocoles OBD2.
• Funciones del ISO 9141-2: funciones que en conjunto implementan dicho
protocolo.
• Funciones del ISO 15765-4: ídem. Se incluye además el driver del MCP2515.
• Funciones del protocolo propio: Implementan el protocolo entre el equipo de test
y el ordenador.
Desarrollo del software
99
• Función main() y initProtocol(): gestionan las tramas recibidas desde el ordenador.
Las estructuras de datos a las que se hacen referencia se encuentran dentro del archivo
Types.h(ver en anexos).
6.3.1 Funciones generales
long pow(char baseNumber, char exponentNumber)
Esta función realiza la operación baseNumberexponentNumber y la devuelve el resultado.
void setPinMode(uchar* port, uchar pin, uchar mode)
Esta función permite configurar un determinado pin como entrada o salida digital (siempre
que sea un pin de entrada/salida).
void setPin(uchar* port, uchar pin)
Esta función pone a 1 una determinada salida digital.
void resetPin(uchar* port, uchar pin)
Esta función pone a 0 una determinada salida digital.
uchar readPin(uchar* port, uchar pin)
Esta función devuelve el estado (1 o 0) de una determinada entrada digital.
void initTMR0()
Esta función inicializa el TIMER0 de acuerdo a las especificaciones determinadas en el
apartado 4.4.3.3.
void initSPIMaster()
ídem para el modulo SPI.
uchar transferSPI(uchar byteToSend)
Mediante esta función se puede realizar una transferencia a través del SPI. byteToSend es el
byte a enviar y el byte recibido lo devuelve la función.
void wait(uint delay)
Esta función permite realizar retardos. El retardo se indica en ms a través de la variable delay.
void initTMR3()
Desarrollo del software
100
Esta función inicializa el TIMER3.
void initSoftwareUART()
Esta función inicializa una UART por software. Los pines TX y RX de esta UART están
fijados a RD2 y RD1 respectivamente. La tasa de bits a la que transmite y recibe esta UART
es de 9600 baudios.
sendByteSoftwareUART(uchar byteToSend)
Esta función envía un byte a través de la UART por software.
uchar receiveByteSoftwareUART(uchar* receivedByte)
Mediante esta función se puede recibir un byte a través de la UART por software. La función
devuelve un 1 o un 0 según se ha recibido el byte a tiempo o bien se ha producido un timeout.
En caso de recibir el byte, se guarda a la dirección apuntada por receivedByte.
uchar reverseByte(uchar x)
Esta función permite invertir el orden de los bits de un byte. El byte a invertir debe estar en
la variable x y el resultado es devuelto por la función.
void initUart()
Esta función inicializa el modulo UART del MCU.
void closeUart()
Esta función desactiva el modulo UART. Esta función es necesaria ya que como se ha
mencionado anteriormente, los módulos UART y SPI comparten pines y para utilizar uno el
otro debe estar desactivado.
void setUartBaud(uint baud)
Mediante esta función se puede configurar los baudios de la UART.
void flushRxBufferUart()
Esta función sirve para vaciar el buffer de entrada del modulo UART.
void sendByteUart(uchar byteToSend)
Esta función permite enviar un byte a través de la UART.
sendBytesUart(uchar dataLen, uchar* bytesToSend)
Esta función utiliza la función anterior para enviar un conjunto de bytes. bytesToSend es un
puntero que apunta al primer byte del array y dataLen es una variable que indica el tamaño
del array.
Desarrollo del software
101
uchar receiveByteUart(uchar* byteReceived)
Mediante esta función se puede recibir un byte a través de la UART. Su funcionamiento es
parecido al de la función receiveByteSoftwareUART().
uchar receiveBytesUart(dataFrame* bytesToReceive)
Mediante esta función se pueden recibir un conjunto de bytes a través de la UART. Esta
función se basa en la función anterior. bytesToReceive es un puntero que apunta a una
estructura de datos del tipo dataFrame. Esta estructura contiene las variables uchar dataLen
y uchar data[120]. La variable dataLen indica el número de bytes a recibir y se debe asignar
antes de llamar a la función. Los bytes recibidos se guardan en el array data[].
6.3.2 Funciones del ISO 9141-2
void sendByte5baud(uchar byteToSend, uchar* trisPort, uchar* latPort, uchar TXPin)
Esta función toma el control del pin TX de la UART y transmite un byte a una velocidad de
5 baudios. Esta función es necesaria para realizar la inicialización de la ECU de un vehículo
con ISO 9141-2. El modulo UART del PIC no permite velocidades de transmisión tan bajas
y por esta razón se ha tenido que recurrir a una solución por software. Antes de realizar la
transmisión, esta función debe desactivar la UART utilizando la función closeUart().
void sendFrameISO9141(frameFieldsISO9141 frameFields)
Mediante esta función se puede enviar una trama a la ECU del vehículo. frameFields es una
estructura de datos del tipo frameFieldsISO9141. Esta estructura de datos contiene el valor
de los campos de la trama que se desea enviar.
En primer lugar se guardan en un array los campos priority, target, source y data.
Posteriormente la función calcula el checksum y lo añade a la primera posición libre del
array. Finalmente se envía cada un de los bytes del array (solo los bytes de la trama) dejando
un retardo de 10 ms entre transmisión y transmisión (especificación del protocolo ISO 9141-
2).
void sendFrameISO9141(frameFieldsISO9141 frameFields) {
uchar txBuffer[11];
uchar checksum = 0;
txBuffer[0] = frameFields.priority; // First byte of ISO9141 frame is format byte
txBuffer[1] = frameFields.target; // Second byte is target adress
txBuffer[2] = frameFields.source; // Third byte is source adress
// Next lines add data bytes to frame
for(uchar i = 0; i < frameFields.dataLen; i++) txBuffer[3 + i] = frameFields.data[i];
// Checksum of frame is computed
for(uchar i = 0; i < (3 + frameFields.dataLen); i++) checksum += txBuffer[i];
txBuffer[3 + frameFields.dataLen] = checksum; // Add checksum of frame
// Finally the built frame is sended
}
}
Desarrollo del software
102
uchar initISO9141()
Esta función realiza el procedimiento de inicialización descrito en el apartado 3.1.3.2. La
función devuelve 1 si se ha podido inicializar la ECU o 0 si se ha producido algún error. El
siguiente diagrama muestra lo que hace esta función:
Desarrollo del software
103
Figura 80: Inicialización del protocolo ISO 9141-2.
uchar receiveSingleFrameISO9141(frameFieldsISO9141* frameFields)
Desarrollo del software
104
Mediante esta función se puede recibir una trama enviada por el vehículo. Los campos de la
trama se guardan en la estructura de datos que apunta el puntero frameFields. Antes de llamar
a esta función, se debe guardar en la estructura de datos el número de bytes que se espera
que tenga el campo de datos de la trama enviada por el vehículo. La función devuelve un 1
o un 0 según si se ha recibido correctamente la trama o se ha producido algún error.
Lo primero que realiza esta función es recibir los bytes de la trama. Si por alguna razón no
se reciben los bytes esperados la función termina y devuelve un 0 indicando que ha habido
un error. Si se reciben todos los bytes la función procede a calcular el checksum para
compararlo con el enviado dentro de la trama. Si el checksum calculado coincide con el
checksum recibido se guardan los campos de la trama dentro de la estructura de datos y la
función devuelve un 1, si no coinciden los checksum la función termina y devuelve un 0.
uchar receiveMultipleFramesISO9141(frameFieldsISO9141* framesFields, uchar
framesToReceive)
Esta función permite recibir múltiples tramas enviadas por el vehículo y está basada en la
función anterior. El uso de esta función es imprescindible cuando la respuesta del PID pedido
no cabe en una única trama y el vehículo debe responder con un numero determinado de
tramas. frameFields es un puntero a un array del tipo frameFieldsISO9141 y
framesToReceive es una variable que indica el numero de tramas que se espera recibir.
La función devuelve un 1 o un 0 según si se ha recibido correctamente la trama o se ha
producido algún error.
uchar getResponseLengthISO9141(OBD2Request requestedPID, uchar* dataLength)
uchar receiveSingleFrameISO9141(frameFieldsISO9141* frameFields) {
uchar rxBuffer[11];
uchar checksum = 0;
for(uchar i = 0; i < (frameFields->dataLen + 4); i++) {
if(!receiveByteUart(rxBuffer + i)) return 5;
}
for(uchar i = 0; i < (frameFields->dataLen + 3); i++) checksum += rxBuffer[i];
if(checksum != rxBuffer[frameFields->dataLen + 3]) return 0;
frameFields->priority = rxBuffer[0];
frameFields->target = rxBuffer[1];
frameFields->source = rxBuffer[2];
for(uchar i = 3; i < (frameFields->dataLen + 3); i++) {
frameFields->data[i - 3] = rxBuffer[i];
}
return 1;
}
Desarrollo del software
105
Como se ha explicado en el apartado 3.1.3.2, la trama del ISO9141-2 no incluye un campo
que indica el tamaño del campo de datos. La función receiveSingleFrameISO9141() requiere
saber el número de bytes del campo de datos por lo que se debe implementar otra función
que proporcione este valor. Tal y como se puede ver en el apartado 3.2 cada PID tiene una
respuesta acotada por lo que solo debemos integrar esa información en otra función. Esta
función también debe indicar el tipo de respuesta, es decir, si es única o múltiple para saber
cuál función utilizar.
La variable requestedPID es una estructura de datos del tipo OBD2Request que contiene el
PID del cual se requiere saber la longitud de la respuesta y el servicio al cual pertenece el
PID. El tipo de respuesta es devuelto por la misma función y la longitud se almacena al byte
apuntado por dataLength. El valor almacenado en dataLenght se debe interpretar de forma
diferente según se trate de una respuesta simple o múltiple: si es una respuesta simple
dataLenght indica el número de bytes de datos, en cambio si se trata de una respuesta
multiple el valor de dataLenght indica el número de tramas a recibir.
uchar sendOBD2RequestISO9141(OBD2Request requestedPID, OBD2Response*
respondedData)
Desarrollo del software
106
Esta función utiliza las funciones descritas hasta ahora (ISO9141-2) para mandar una trama
request, recibir la respuesta y guardar los datos de la respuesta. El PID pedido y el servicio
al cual pertenece se indica a través de la estructura de datos requestedPID. Los datos de
respuesta así como su longitud se guardan a la estructura de datos apuntada por
respondedData. La función devuelve un 1 o un 0 según si se ha recibido correctamente la
trama o se ha producido algún error. El siguiente diagrama muestra el comportamiento de
esta función:
Figura 81: Diagrama de la función sendOBD2RequestISO9141().
En primer lugar se envia la trama con el PID y el servicio al cual pertenece mediante la
función sendFrameISO9141(). Seguidamente se obtiene el tipo de respuesta y su longitud
mediante la función getResponseLenghtISO9141(). Si la respuesta es única se recibe
Desarrollo del software
107
mediante la función receiveSingleFrameISO9141(), si la respuesta es múltiple se recibe
mediante la función receiveMultipleFramesISO9141(). Si se recibe correctamente la
respuesta esta es guardada a la estructura de datos respondedData, la función devuelve un 1
y termina. Si se produce un error o el PID no es soportado (no se dispone del tamaño de su
respuesta) la función devuelve un 0 y termina.
6.3.3 Funciones del ISO15765-4
void initMCP2515()
Esta funcion inicializa el pin CS para el MCP2515. Este pin es el RB2.
void startCOMMCP2515()
Esta función pone el pin CS a un estado bajo para indicar al MCP2515 que se quiere realizar
una transferencia a través del SPI.
void stopCOMMCP2515()
Esta función devuelve el pin CS a un estado alto indicando al MCP2515 que se ha finalizado
la transferencia.
uchar readRegisterMCP2515(uchar regAdress)
Esta función implementa la instrucción READ explicada en el apartado del MCP2515. La
variable regAdress contiene la dirección del registro a leer y el valor de este registro es
devuelto por la función.
void writeRegisterMCP2515(uchar regAdress, uchar value)
Esta función implementa la instrucción WRITE del MCP2515. La variable regAdress
contiene la dirección del registro a escribir y value es el valor que se le quiere asignar.
void bitModifyMCP2515(uchar regAdress, uchar mask, uchar value)
Esta función implementa la función BIT MODIFY del MCP2515.regAdress es la dirección
del registro, mask es la mascara de bits y value contiene el nuevo valor de los bits a
modificar.
uchar setModeMCP2515(uchar mode)
Mediante esta función se puede cambiar el modo de funcionamiento del MCP2515. La
variable mode contiene el modo a configurar. La función devuelve 1 o 0 según si se ha
podido configurar el modo deseado o ha habido un error.
void initCAN(uchar baudRate)
Desarrollo del software
108
Esta función inicializa el controlador CAN del MCP2515. Lo que se configura con esta
función es básicamente la tasa de bits. Solo se permiten velocidades de 250 kbps y 500 kbps
ya que son las tasas que define el estándar ISO15765-4.
uchar sendCANFrameBuffer0(CANDataFrame dataFrame)
Esta función permite enviar una trama CAN a través del MCP2515. dataFrame es una
estructura de datos del tipo CANDataFrame que contiene los campos de la trama a enviar.
Mediante una variable de la estructura dataFrame se puede elegir si enviar una trama estándar
o una trama extendida. La tasa de bits a la que se envía la trama la determina la función
anterior. La función devuelve un 1 o un 0 según si se ha podido enviar la trama o bien se ha
producido algún error (en el MCP2515, en el bus etc.). Diagrama de flujo de la función:
Figura 82: Diagrama de la función sendCANFrameBuffer0
void configRX0BufferMCP2515(uchar IdType, ulong bufferFilter, ulong bufferMask)
Desarrollo del software
109
Esta función permite configurar el buffer de recepción 0 para que solo acepte tramas con un
determinado Id (o unos determinados Ids). La variable IdType indica el tipo de tramas que
se debe aceptar, puede tomar uno de los siguientes valores: CAN_STANDARD_FRAME
(tramas con id de 11 bits) y CAN_EXTENDED_FRAME (tramas con id de 29 bits).
bufferFilter contiene el filtro del buffer y bufferMask contiene la mascara de bits del filtro,
el funcionamiento del filtro esta explicado con más detalle en el apartado 4.2.1.4.3.
void emptyRX0BufferMCP2515()
Esta función permite vaciar el buffer de recepción del MCP2515.
uchar receiveRX0BufferMCP2515(CANDataFrame* dataFrame)
Mediante esta función se puede recibir una trama CAN. Los campos de la trama se guardan
a la estructura apuntada por el puntero dataFrame. La función devuelve 1 o 0 según si se ha
recibido una trama o no (se ha producido un timeout). Diagrama de la función:
Figura 83: Diagrama de la función receiveRX0BufferMCP2515().
Desarrollo del software
110
En primer lugar se entra dentro de un bucle del cual se sale si se recibe una trama o bien si
se produce un timeout. En caso de que salga porque se ha producido un timeout, la función
devuelve un 0 y termina. Si se recibe la trama, los campos de esta se guardan a la
estructura dataFrame, la función devuelve un 1 y termina.
uchar sendSingleFrameISOTP(dataFrame dataToSend)
Esta función permite enviar una trama del tipo single frame. dataToSend contiene el campo
de datos de la trama a enviar. La función devuelve 1 o 0 según si se ha enviado la trama o si
se ha producido un error.
uchar sendFlowControlFrameISOTP(uchar flowStatus, uchar blockSize, uchar
separationTime)
Esta función permite enviar una trama del tipo flow control. Como se ha explicado en el
apartado 3.1.5.2, esta trama permite indicar los parámetros Flow Status, Block Size y
Separation Time. La función devuelve 1 o 0 según si se ha enviado la trama o si se ha
producido un error.
uchar receiveResponseISOTP(dataFrame* dataReceived)
Esta función permite recibir la respuesta del vehículo, tanto si es simple como múltiple. El
campo de datos de la trama recibida se guarda en la estructura de datos apuntada por
dataReceived. El diagrama de esta función es el siguiente(siguiente hoja):
Desarrollo del software
111
Figura 84: Diagrama de la función receiveResponseISOTP.
Desarrollo del software
112
En primer lugar se llama a la función receiveRX0BufferMCP2515(), si esta función
devuelve un 1 significa que se ha recibido una trama; en cambio, si devuelve un 0 significa
que no se ha recibido nada, en este caso la función receiveResponseISOTP() devuelve un 0
y termina. Si se ha recibido una trama la función comprueba cuál tipo de trama es, en caso
de ser una trama tipo SINGLE_FRAME se guardan los campos de la trama a la estructura
dataReceived y la función termina. En caso de tratarse de una FIRST_FRAME se guardan
los campos de la trama a la estructura dataReceived y se procede a recibir las siguientes
tramas, el número de tramas a recibir se obtiene mediante la función nFramesToReceive().
En primer lugar se debe responder con una trama del tipo FLOW_CONTROL mediante la
función sendFlowControlFrameISOTP(), una vez enviada esta trama se empezarán a recibir
las tramas del tipo CONSECUTIVE_FRAMES. Cada vez que se recibe una trama mediante
la función receiveConsecutiveFrameISOTP() se añaden los datos recibidos a la estructura
dataReceived. Recibidas todas las tramas la función devuelve un 1 y finaliza. Si durante la
recepción de alguna de las tramas se produce un error, la función devuelve un 0 y termina.
uchar sendOBD2RequestISO15765_4(OBD2Request request, OBD2Response*
response)
Esta función es equivalente a la función sendOBD2RequestISO9141() pero para el protocolo
ISO 15765-4. Esta función se encarga de enviar una trama con el PID indicado en la
estructura de datos request y de recibir la trama de respuesta (o tramas), los datos de la cual
se guardan en la estructura de datos response. La función devuelve un 1 o un 0 según si se
ha recibido una respuesta o no. Código de la función:
En primer lugar se prepara el campo de datos de la trama a enviar añadiendo el PID y el
service al que pertenece el PID de la estructura request. Posteriormente se envía la trama
mediante la función y se llama a la función sendSingleFrameISOTP() y se llama a la función
uchar sendOBD2RequestISO15765_4(OBD2Request request, OBD2Response* response) {
dataFrame dataToSend;
dataFrame dataReceived;
dataToSend.dataLen = 1 + request.PIDLength;
dataToSend.data[0] = request.service;
for(uchar i = 1; i < dataToSend.dataLen; i++) dataToSend.data[i] = request.PID[i-1];
if(!sendSingleFrameISOTP(dataToSend)) return 0;
if(!receiveResponseISOTP(&dataReceived)) return 0;
response->service = request.service;
response->PIDLength = request.PIDLength;
for(uchar i = 0; i < request.PIDLength; i++) response->PID[i] = request.PID[i];
response->dataLen = dataReceived.dataLen - request.PIDLength - 1;
for(uchar i = 0; i < response->dataLen; i++) response->data[i] =
dataReceived.data[request.PIDLength + 1 + i];
return 1;
}
Desarrollo del software
113
receiveResponseISOTP(). Si se produce un error en el envió o la recepción de la trama la
función devuelve un 0 y finaliza. Si se recibe correctamente la respuesta, se guardan los
datos recibidos a la estructura response. Llegado a este punto la función devuelve un 1 y
finaliza.
4.3.1 Funciones del protocolo propio:
void sendResponseFrame(responseFrame response)
Mediante esta función se puede enviar una trama de respuesta al ordenador a través de la
UART por software y el módulo Bluetooth. Los campos de la trama se obtienen de la
estructura de datos response.
void sendErrorFrame(negativeResponseFrame negativeResponse)
Esta función envía una trama de error al ordenador. Los campos de la trama se encuentran
en la estructura negativeResponse.
void sendCommandConfirmation(commandFrame command)
Esta función envía una trama de confirmación de comanda. Los campos de la trama se
encuentran en la estructura command.
uchar receiveFrame(uchar* frameReceivedId, requestFrame* request,
commandFrame* command)
Mediante este función se puede recibir una trama del tipo request o command. El tipo de
trama recibida se indica a través del byte frameReceivedId. Si se trata de una trama tipo
request se guardan sus campos en la estructura de datos request, en cambio si se trata de una
trama tipo command se guardan sus campos en la estructura de datos command. La función
devuelve 1 o 0 según si se ha recibido una trama o se ha producido un error. El diagrama de
la función es el siguiente:
Desarrollo del software
114
Figura 85: Diagrama de la función receiveFrame()
Desarrollo del software
115
En primer lugar la función intenta recibir el primer byte de la trama mediante la función
receiveByteSoftwareUART(), este byte indica el tipo de trama que se esta recibiendo. Si no
se recibe ningún byte la función devuelve un 0 y termina. A continuación se procede a recibir
los bytes de la trama indicada por el primer byte recibido. Si se reciben todos los bytes de la
trama y el checksum es correcto, se guardan los campos de la trama que nos interesan a la
estructura de datos correspondiente, se indica el tipo de trama mediante la variable
frameReceivedId y se finaliza la función devolviendo un 1. Si no se reciben todos los bytes
o hay un error en el checksum la función devuelve un 0 y termina.
6.3.4 Funciones main() y initProtocol()
uchar initProtocol(uchar protocol):
Esta función se encarga de comprobar si el protocolo introducido por el usuario es el que
implementa el vehículo. La variable protocol indica el protocolo que ha elegido el usuario.
Si se ha podido comunicar con el vehículo mediante el protocolo elegido la función devuelve
un 1, en caso contrario devuelve un 0. Si el protocolo es el correcto el programa puede
empezar a enviar peticiones ya que la función también inicializa el protocolo (solo con ISO
9141-2 ya que con ISO 15765-4 no hace falta).
void main():
Esta función se encarga de inicializar todos los módulos llamando a las funciones
correspondientes. Además, dentro de esta función se encuentra el código que recibe las
tramas enviadas desde la aplicación, las procesa y envia las respuestas. El código gestiona
tramas de request y tramas de comanda, las comandas implementadas en esta versión son
solo las de conectar al vehículo y desconectarse. El diagrama de la función es el siguiente:
Desarrollo del software
116
Figura 86: Diagrama de la función main().
Aplicación de escritorio
117
7. Aplicación de escritorio
En este apartado se explicarán las diferentes funcionalidades de la aplicación de escritorio
desarrollada para poder comunicarse con el escáner OBD2. Esta aplicación permitirá
controlar el dispositivo OBD2 desde el ordenador y dispondrá de una interfaz gráfica que
facilitará el uso por parte del usuario. Las funcionalidades principales de esta aplicación son:
• Conectarse al escáner OBD2 a través del puerto serie creado.
• Gestionar las diferentes tramas del protocolo diseñado para la comunicación con el
escáner OBD2. Esto permitirá que se puedan enviar comandos al dispositivo y
realizar peticiones al vehículo.
• La aplicación permiten almacenar los datos obtenidos en distintos formatos para así
facilitar su posterior manejo e interpretación. De esta forma todos los datos obtenidos
durante una sesión no se pierden.
Las funcionalidades de la aplicación se explicarán más en detalle en los próximos apartados.
7.1 Desarrollo de la aplicación
La aplicación de escritorio del dispositivo OBD2 ha sido completamente desarrollada
mediante el lenguaje de programación Python. Python es un lenguaje de programación
interpretado que ha adquirido mucha popularidad durante los últimos años, sobre todo dentro
de la comunidad educativa. La razón principal por la que se ha hecho tan popular es la
facilidad en su uso ya que dispone de una sintaxis muy parecida al lenguaje humano. Otra
punto positivo es toda la comunidad que hay detrás de este lenguaje, esto permite disponer
de multitud de librerías que facilitan significativamente el proceso de desarrollo del software.
Otra característica que lo hace interesante es que es un lenguaje orientado a objetos.
Para escribir el código de la aplicación se ha utilizado el editor de código Notepad++ v 7.5.4.
Esta herramienta es muy simple y permite escribir en diferentes lenguajes de programación.
Figura 87: Entorno de desarrollo del Notepad++.
Aplicación de escritorio
118
Para poder ejecutar un código escrito en Python se debe disponer de interprete de Python.
Para este proyecto se ha utilizado la versión 3.7.4. El interprete de Python no dispone de una
interfaz gráfica sino que se maneja desde una línea de comandos como la que ofrece el
programa informático Git. Para poder ejecutar un script de Python mediante la línea de
comandos de Git simplemente se debe ir al directorio donde esta el archivo y seguidamente
ejecutar la comanda mostrada en la siguiente figura:
Figura 88: Línea de comandos de Git.
Si el nombre del archivo es correcto y se dispone del interprete de Python, el código se
empezará a ejecutar después de apretar la tecla intro.
Tal y como se ha comentado anteriormente, el Python dispone de un gran número de librerías
que facilitan el desarrollo de aplicaciones. Un gran ejemplo es PyQt la cual es la librería
gráfica que se utilizará para el desarrollo de la interfaz gráfica.
El PyQt permite añadir ventanas de distintos tipos a nuestra aplicación. Dentro de estas
ventanas se pueden añadir distintos elementos de entrada o salida como pueden ser botones,
listas de selección, cuadros de texto etc.
Para el diseño de la interfaz gráfica en sí se ha utilizado el programa QtDesigner. Este
herramienta permite diseñar cada una de las ventanas de nuestra interfaz gráfica.
Figura 89: Entorno del QtDesigner.
Aplicación de escritorio
119
Tal y como se puede ver a la figura anterior, el QtDesigner permite diseñar de forma gráfica
las ventanas de nuestra aplicación, podemos incluir todos los elementos que deseemos
simplemente arrastrándolos a la ventana que se esta diseñando.
Una vez completado el diseño de las distintas ventanas de la interfaz gráfica podemos
generar los archivos que contendrán la información de estas ventanas, el formato de estos
archivos es .ui. Como se verá más adelante, estos archivos se deberán añadir al código de la
aplicación.
En el código se deberá añadir una clase para cada una de las ventanas de la aplicación. Estas
clases deben ser heredadas de otras clases que incluye la librería PyQt. La clase padre de
cada clase añadida depende del tipo de ventana, por ejemplo, para una ventana principal
(Main Window) se debe crear una clase heredada de la clase QmainWindow.
Una vez creada la clase, se debe asociar al archivo de la ventana generado mediante el
QtDesigner.
El fragmento de código anterior corresponde a una clase asociada a una ventana del tipo
dialogo. Tal y como se puede ver, la clase debe ser heredada de la clase Qdialog.
editProfile.ui es el archivo generado mediante el QtDesigner y contiene la información
gráfica de la ventana. La asociación de este archivo a la clase se realiza mediante la función
uic.loadUi() dentro del constructor de la clase.
Creadas las clases para cada una de las ventanas podemos proceder a crear objetos de estas
clases. Los objetos creados permitirán gestionar las ventanas de la aplicación mediante los
distintos métodos de las clases a las que pertenecen. Estos métodos permiten mostrar/ocultar
la ventana, ver/modificar las propiedades de los distintos elementos de la ventana etc.,es
decir, a partir de estos objetos podemos interactuar con la interfaz gráfica desde el código.
Una vez creados los objetos para cada una de las ventanas debemos empezar a elaborar el
código que dará vida a la interfaz gráfica.
class editProfileDialogClass(QDialog):
def __init__(self):
super().__init__()
uic.loadUi("editProfile.ui", self)
Aplicación de escritorio
120
7.2 Descripción de la aplicación
En este aparatado se explicará en detalle las distintas funcionalidades de la aplicación. Este
apartado también puede servir de manual del usuario de la aplicación.
Conexión al escáner OBD2
Antes de conectarse al dispositivo OBD2 debemos asegurarnos de que esté correctamente
conectado al conector OBD2 del vehículo. Además, el ordenador debe estar conectado al
dispositivo OBD2 mediante Bluetooth. Asegurado lo anterior podemos proceder a
conectarnos al escáner OBD2 mediante la aplicación.
Ir a File > Open Connection y se abrirá la siguiente ventana:
Figura 90: Ventana de conexión.
Esta ventana nos permite introducir la información necesaria para establecer conexión con
el vehículo. Esta información es el protocolo OBD2 del vehículo y el puerto COM al que
está conectado el escáner OBD2. Además podemos introducir otra información como el
fabricante del vehículo o la matrícula, estos datos no son obligatorios pero se recomienda
introducirlos para tener la identidad del vehículo en los archivos generados.
Una vez introducidos los datos se debe hacer clic al botón Connect(1) saldrá una ventana
que nos indicará si se ha realizado la conexión con éxito o no. Si se ha podido conectar con
el vehículo, la aplicación desbloqueará todas sus funciones para que el usuario pueda
proceder a utilizarlas.
1
Aplicación de escritorio
121
Obtención de datos en tiempo real
Una vez conectado al vehículo, el programa habilita las pestañas que permiten navegar a
través de sus distintas herramientas. La primera pestaña de la aplicación recibe el nombre de
“Live Data Meter”. Esta pestaña permite obtener datos en tiempo real del vehículo. El
contenido de la pestaña se puede ver en la siguiente figura:
Figura 91: Pestaña Live Data Meter.
Tal y como se puede observar, esta pestaña incluye 4 listas de selección con lo que se permite
obtener simultáneamente el valor de 4 sensores. El dato elegido en cada lista de selección se
muestra en el cuadro de texto que hay justo abajo. Los datos seleccionados también se
pueden observar de forma gráfica mediante las barras de progreso.
Figura 92: Sección de la pestaña Live Data Meter
Barra de progreso
Cuadro de texto
Lista de selección
1 2
Aplicación de escritorio
122
En esta versión del programa se permite obtener los siguientes datos del vehículo:
• Velocity(km/h): velocidad del vehículo en km/h.
• Engine Speed(rpm): revoluciones del motor en rpm.
• Throttle Position(%): porcentaje accionado del pedal acelerador.
• Engine Load(%): porcentaje de carga del motor.
• Coolant Temperature(°C): temperatura del líquido refrigerante.
• Absolute Intake Manifold Pressure(kPa): presión de aire en la admisión.
• Intake Manifold Temperature(°C): temperatura del aire en la admisión.
• Mass Air Flow(g/s): flujo másico de aire en g/s.
Aunque se elijan los parámetros deseados, el programa no empezará a obtener los datos hasta
hacer clic en el botón Read(1), una vez apretado este botón el programa empezará a obtener
los datos seleccionados. Si se desea pausar la obtención de los datos simplemente se debe
volver a hacer clic en el mismo botón.
Como se puede observar en la figura 91, al lado del botón Read hay otro botón con el nombre
Clean. Este botón permite borrar el valor que hay en los cuadros de texto y en las barras de
progreso.
Obtención de los DTCs del vehículo
La siguiente pestaña de la aplicación recibe el nombre Trouble Codes. Esta pestaña permite
obtener los DTCs del vehículo. El contenido de la pestaña se puede observar en la siguiente
figura:
Aplicación de escritorio
123
Figura 93: Pestaña Trouble Codes.
Dentro de esta pestaña tenemos el botón Read(1) el cual da el orden de leer los DTCs del
vehículo y mostrarlos en pantalla. Los DTCs obtenidos se organizan en la pestaña según la
categoría a la que pertenecen. La categorías disponibles son:
• Powertrain: en esta categoría se muestran los fallos relacionados con el tren motriz:
motor, transmisión etc.
• Body Systems: en esta categoría se muestran los fallos relacionados con los sistemas
situados dentro del compartimiento de los pasajeros: sistemas de seguridad, de
conforte etc.
• Chassis Systems: en esta categoría se muestran los fallos relacionados con los
sistemas situados fuera del compartimiento de los pasajeros: sistema de frenado,
dirección asistida, suspensión hidráulica etc.
• Network Systems: en esta categoría se muestran los fallos relacionados con los
sistemas de comunicación de los diferentes módulos del vehículo.
El botón Clear(2) situado al lado del botón Read(1) permite borrar los fallos almacenados
dentro de la ECU del vehículo. Una vez enviada la orden de borrar los DTCs al vehículo, el
programa vuelve a pedir los DTCs almacenados para que el usuario pueda comprobar si
realmente se han borrado.
1 2 3
Aplicación de escritorio
124
El botón Export to text(3) file situado a la parte inferior-derecha de la pestaña permite
generar un documento de texto con los DTCs obtenidos. Además de los DTCs, el archivo
generado contiene la siguiente información:
• Fecha de creación del documento.
• Fabricante del vehículo.
• Modelo del vehículo.
• Matrícula del vehículo(en el nombre del archivo).
El fabricante, el modelo y la matricula son los introducidos por el usuario en el momento de
conectarse. Esta información es de gran ayuda para poder identificar el vehículo al cual
pertenece el archivo, por eso se recomienda proporcionar la información al programa.
Obtención del Freeze Frame
La pestaña Freeze Frames permite obtener el cuadro de datos congelados disponible en el
vehículo. Además, se muestra el DTC al cual pertenece el Freeze Frame.
Figura 94: Pestaña Freeze Frame.
Para solicitar el Freeze Frame al vehículo se debe hacer clic sobre el botón Read(1). Una vez
recibidos los datos estos se mostraran en la pestaña.
1 2
Aplicación de escritorio
125
El botón Export to text file(2) situado a la parte inferior-derecha permite generar un archivo
con el Freeze Frame obtenido. En el archivo también se incluye información para identificar
el vehículo. Esta información es la misma que se incluye en el archivo generado desde la
pestaña Trouble Codes.
Obtención de la información del vehículo
Esta pestaña permite obtener información especifica del vehículo como el estándar OBD
implementado o el VIN.
Figura 95: Pestaña Vehicle Info
Para enviar una petición de los datos al vehículo se debe hacer clic sobre el botón Read(1).
Una vez se obtengan los datos se muestran en su lugar correspondiente.
El botón Copy to Clipboard(2) permite copiar al portapapeles los datos mostrados en la
pestaña.
Dentro de esta pestaña también se muestra la matrícula y el protocolo de comunicación del
vehículo. Estos datos no proceden del vehículo directamente sino que son los introducidos
por el usuario.
1 2
Aplicación de escritorio
126
Herramientas de decodificación de los DTCs
La pestaña Search DTC Codes dispone de dos herramientas que permiten obtener la
información codificada en los DTCs.
Figura 96: pestaña Search DTC Codes.
Tal y como se puede observar a la figura anterior, dentro de esta pestaña se dispone de dos
herramientas: DTC Decoder y DTC Searcher.
La herramienta DTC Decoder permite decodificar los DTC dentro de la misma aplicación.
Introduciendo el DTC dentro de las casillas y haciendo clic sobre el botón (1) se obtiene el
significado de cada carácter del DTC.
La herramienta DTC Searcher también permite decodificar DTCs pero en este caso se utiliza
una página web de terceros para obtener la información. Al introducir los DTCs dentro de
los cuadros de texto y hacer clic sobre el botón (2) se abre una página web que contiene la
información de los DTC. Lógicamente se requiere de conexión internet para poder utilizar
esta herramienta. Mediante esta herramienta se pueden decodificar hasta 3 DTC a la vez.
La ventaja que presenta la herramienta DTC Searcher frente a la primera es que la
información proporcionada es mucho más completa.
1 2
Aplicación de escritorio
127
Herramienta de gráficos
La pestaña Plotting Tool permite obtener un dato del vehículo de forma periódica durante
un tiempo determinado. La pestaña también permite guardar los datos obtenidos en un
archivo excel.
Figura 97: Pestaña Plotting Tool
En primer lugar se deben seleccionar los parámetros que se quieran muestrear mediante las
listas de selección, la aplicación permite seleccionar hasta 3 parámetros diferentes.
También se debe seleccionar el tiempo de muestreo mediante la lista de selección.
Figura 98: Sección de la pestaña Plotting Tool.
Selección del tiempo
de muestreo
Selección de los parámetros
1 2 3
Aplicación de escritorio
128
Una vez finalizada la configuración se puede proceder a obtener les datos mediante el botón
Get Data(1). La barra de progreso indica el porcentaje de muestras obtenidas respecto a las
que se deben obtener. Cuando termina la obtención de datos se activa el botón Export to
Excel(3) mediante el cual podemos generar un archivo excel con los datos obtenidos. En el
archivo excel también se incluyen datos de identificación del vehículo como la matrícula o
el fabricante.
El botón Clean Data(2) permite eliminar los datos obtenidos.
Depurador de las comunicación con el dispositivo OBD2
La pestaña Interpreter Log permite visualizar todas las tramas que son intercambiadas entre
el escáner OBD2 y el programa. Esta herramienta es muy útil para poder detectar y
solucionar errores en la comunicación.
Figura 99: Pestaña Interpreter Log.
Mediante el botón Pause(1) se puede activar/desactivar el log de las tramas. El botón Clear
Log(2) permite borrar las tramas registradas hasta el momento.
1 2
Aplicación de escritorio
129
Desconexión del escáner OBD2
Para desconectarse del escáner OBD2 simplemente se debe ir a File > Close Connection.
Construcción y test del prototipo
130
8. Construcción y test del prototipo
8.1 Construcción del prototipo
El prototipo del proyecto se ha realizado sobre una protoboard. Algunas partes del circuito
se han añadido como módulos que se comercializan, es un ejemplo la interfaz del ISO
15765-4.
Para su elaboración simplemente se ha seguido el esquema mostrado en el apartado 4.6.
Figura 100: Prototipo del dispositivo OBD2.
8.2 Test del prototipo
En este apartado realizaremos un test del producto final mediante la aplicación de escritorio.
El objetivo es verificar que el dispositivo OBD2 es capaz de conectarse a un vehículo y
obtener los datos que pidamos. Además, mediante este test también se va a comprobar que
la aplicación funcione correctamente y permita acceder a los distintos servicios del sistema
OBD2 (Datos actuales, DTC, Información del vehículo etc.)
El test se realizará con un vehículo que implementa el protocolo ISO 9141-2.
Para empezar el test en primer lugar debemos conectar el dispositivo al vehículo mediante
el conector OBD2 y poner la llave del vehículo en la posición de contacto.
También se debemos asociar el dispositivo OBD al ordenador mediante una conexión
Bluetooth. Una vez conectado se abrirá un puerto serie, debemos apuntar el número para
introducirlo a la aplicación más adelante.
Construcción y test del prototipo
131
A continuación ejecutamos la aplicación y abrimos el diálogo de conexión:
Figura 101: Dialogo de conexión.
Una vez introducidos los datos damos clic al botón Connect.
Tal y como se puede ver en la siguiente figura, el dispositivo OBD2 se ha conectado
correctamente al vehículo y la aplicación a desbloqueado todas sus funcionalidades:
Figura 102: Conexión al vehículo realizada.
Construcción y test del prototipo
132
Obtención de datos actuales
A continuación obtendremos datos del vehículo en tiempo real
Figura 103: Datos en tiempo real.
Tal y como se puede ver en la figura anterior, la aplicación nos proporciona correctamente
los datos en tiempo real de los parámetros elegidos. Se puede verificar que algunos datos
son correctos a través del cuadro de instrumentos del vehículo.
Obtención de los DTCs
A continuación comprobaremos que el dispositivo pueda obtener correctamente los códigos
de falla del vehículo. Para ello debemos provocar que se produzca un fallo en el vehículo.
Una forma fácil de generar un DTC es desconectar el sensor MAF y circular con el vehículo
hasta que aparezca el DTC. Cuando la luz MIL se encienda significa que el sistema OBD2
ha detectado la falla y ha generado un DTC.
Figura 104: Luz MIL encendida.
Construcción y test del prototipo
133
Ahora ya podemos leer los DTCs mediante la aplicación:
Figura 105: Obtención de las DTCs del vehículo.
Tal y como se puede ver en la figura anterior se ha encontrado 1 DTC. Este DTC es el
P0102 y pertenece a la categoría Powertrain (tren de transmisión). Podemos decodificar
este DTC mediante la ventana Search DTC Codes.
Construcción y test del prototipo
134
Figura 106: Decodificación del DTC P0102.
Mediante la herramienta DTC Decoder podemos ver que el DTC es genérico (es estándar).
Vemos además que el DTC hace referencia a un elemento relacionado con la medición del
aire lo cual es lógico ya que el DTC se ha producido por la desconexión del sensor MAF.
Para obtener más información usamos la herramienta DTC Searcher.
Figura 107: Página web www.dtcsearch.com
Comprobamos que efectivamente la aplicación nos redirige a una página web donde
encontramos información sobre el DTC introducido.
Por ultimo intentaremos guardar un listado de los DTCs encontrados en un fichero de texto.
Para ello debemos dar clic en el botón Export to text file.
Construcción y test del prototipo
135
Figura 108: Fichero de DTCs generado
Tal y como se puede ver en la figura anterior, la aplicación genera correctamente el fichero
con los DTCs encontrados. Se añade además datos del vehículo y otra información como la
fecha de creación del fichero.
Borrado de DTC
Ahora comprobaremos que el dispositivo OBD2 borra correctamente los DTCs del
Vehículo. Para ello nos situaremos a la pestaña Trouble Codes y haremos clic sobre el botón
Clear.
Figura 109: Borrado de los DTCs.
Tal y como se puede ver en la figura anterior, el dispositivo ha borrado correctamente el
DTC que había almacenado en la ECU.
Construcción y test del prototipo
136
Observamos también que la luz MIL se apaga una vez borrado el DTC.
Figura 110: Luz MIL apagada.
Información del vehículo
A continuación comprobaremos que el dispositivo OBD permita obtener datos del servicio
9. Para ello nos situaremos a la pestaña Vehicle Info y daremos clic al botón Read.
Figura 111: Información del vehículo.
Tal y como se puede ver en la figura anterior, la aplicación nos ha proporcionado el número
de bastidor del vehículo.
Aunque no pertenezca al servicio 9, la aplicación también nos proporciona correctamente la
versión del OBD implementada.
Construcción y test del prototipo
137
Generación de gráficas
Ahora comprobaremos que la aplicación permita muestrear uno o varios parámetros del
vehículo y generar un archivo Excel con los datos para crear gráficas.
Nos situamos en la pestaña Plotting Tool, elegimos los parámetros y el tiempo de muestreo
y damos clic al botón Get Data.
Figura 112: Muestreo de los parámetros Engine Speed y Velocity.
Una vez terminado el proceso, generamos el fichero Excel mediante el botón Export to
Excel.
Figura 113: Fichero Excel generado.
Construcción y test del prototipo
138
Tal y como se puede observar en la figura anterior, la aplicación genera correctamente el
archivo Excel con los datos obtenidos.
Con los datos anteriores obtenemos la siguiente gráfica:
Gráfica 1: Revoluciones del motor y velocidad del coche.
Observamos que hay tramos de la gráfica donde la velocidad del motor y del vehículo son
proporcionales mientras que en otros no. Esto es porque en algunos tramos hay una marcha
engranada y en otros no o bien se está cambiando de marcha.
Por lo tanto, la aplicación permite obtener gráficas con los parámetros del vehículo
correctamente.
Log de la comunicación
Finalmente comprobaremos que la aplicación registra correctamente todas las tramas
intercambiadas.
Para ello nos situamos en la pestaña Interpreter LOG.
0
10
20
30
40
50
60
0 2 4 6 8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
Revoluciones del motor y velocidad del coche
Engine Speed(rpm) / 100 Velocity(km/h)
Construcción y test del prototipo
139
Figura 114: Log de la aplicación
Tal y como se puede ver, la aplicación registra correctamente las tramas intercambiadas entre
el ordenador y el dispositivo OBD2.
A partir del test realizado podemos concluir que el dispositivo OBD2 y la aplicación de
escritorio funcionan según las especificaciones del proyecto.
Conclusiones
140
9. Conclusiones
Sobre el producto final
El objetivo de este proyecto era el de diseñar un dispositivo que fuera capaz de comunicarse
con el sistema OBD2 de los vehículos que implementen el protocolo ISO 9141-2 o ISO
15765-4. Finalizado el proyecto podemos concluir que el objetivo se ha cumplido
satisfactoriamente.
Tal y como se puede ver en el apartado 8, el dispositivo diseñado permite conectarse con el
sistema OBD2 de un vehículo que implementa el protocolo ISO 9141-2. El dispositivo
también se ha probado con un emulador del protocolo ISO 15765-4 y ha respondido
correctamente a las distintas pruebas realizadas.
Mediante este dispositivo se ha podido acceder a los servicios 1, 2, 3, 4 y 9 del sistema OBD2
del vehículo con el que se ha realizado el test. Esto ha permitido obtener datos en tiempo
real, graficar estos datos, ver los códigos de falla almacenados, borra los códigos de falla etc.
Es decir, el producto diseñado es práctico y sus prestaciones son comparables a las que
ofrecen algunos escáner OBD2 comerciales.
Sobre el sistema OBD2
A partir del estudio de la historia del sistema OBD2 vemos que este sistema no surge
simplemente por la necesidad de disponer de algo que nos proporcione las lecturas de los
distintos sensores del vehículo sino que tiene todo un trasfondo medioambiental. Gracias al
sistema OBD2 se asegura el correcto funcionamiento de los sistemas anti polución y por lo
tanto, gracias al sistema OBD2 podemos respirar un aire más limpio.
Habiendo trabajado con los protocolos ISO 9141-2 y ISO 15765-4 podemos ver porqué
desde el año 2008 los fabricantes están obligados a utilizar el ISO 15765-4. El ISO 9141-2
tiene unas especificaciones que limitan sus prestaciones: baja velocidad de transmisión (10,4
kbps), requiere una inicialización que dura 2 s, el bus no está muy protegido contra las EMIs,
el manejo de las tramas es más caótico… El ISO 15765-4 en cambio está basado en el BUS
CAN el cual permite tasas de transmisión de hasta 1 Mbps, aunque el estándar ISO 15765-4
solo admite tasas de 250 kbps y 500 kbps. Además, el BUS CAN está protegido contra las
EMIs lo cual lo hace ideal para usarlo dentro de la industria del automóvil.
Mejoras del diseño
Para una futura revisión del diseño de este proyecto se pretenderá introducir las siguientes
mejoras:
• En la versión actual del diseño, se utiliza un circuito integrado que implementa el
protocolo CAN ya que el microcontrolador utilizado no dispone de un módulo CAN.
Utilizando el microcontrolador con un módulo CAN integrado abarataría el coste del
producto.
• Como el PIC18F4550 solo dispone de 1 UART y esta se utiliza para implementar el
ISO 9141-2, se ha tenido que implementar una UART por software para comunicarse
Conclusiones
141
con el módulo Bluetooth. Esta UART por software transmite a 9600 baudios por lo
que es el cuello de botella de todo el sistema de comunicación. La solución sería
buscar otro MCU que disponga de 2 o más módulos UART, así podríamos reducir el
tiempo de transmisión entre el dispositivo OBD2 y el ordenador.
• Muchos escáner OBD2 disponen de un conjunto de LEDs a través de los cuales el
usuario puede saber el estado del dispositivo. En una versión futura se añadirán estos
LEDs para así mejorar la experiencia del usuario.
• Otra funcionalidad que se pretende añadir es la de poder leer el voltaje de la batería
mediante el conversor AD del MCU y proporcionar esta información al usuario. Esto
permitiría saber el estado de la batería y del alternador cuando esté cargando la
batería.
• Por último, para mejorar la estética del producto se diseñará una PCB donde irán
soldados todos los componentes.
Webgrafía
142
10. Webgrafía
OBD2 EXPLAINED - A SIMPLE INTRO, https://www.csselectronics.com/screen/page/simple-
intro-obd2-explained/language/en
OBD-II Reader, https://github.com/arashn/obdii-reader/wiki
OBD9141, https://github.com/iwanders/OBD9141
OBD-II, http://blog.perquin.com/blog/category/odbii/
K-line Communication Description, https://obdclearinghouse.com/Files/viewFile?fileID=1380
El OBDII Completo, https://es.wikibooks.org/wiki/El_OBDII_Completo#Parte_III_-_Los_PIDs
OBD-II PIDs, http://obdcon.sourceforge.net/2010/06/obd-ii-pids/
Protocolos de conexión OBD2, https://obd2-elm327.es/protocolos-conexion-obd2
Introducción a CAN, http://www.ni.com/es-es/innovations/white-papers/06/controller-area-
network--can--overview.html
Línea K, https://es.wikipedia.org/wiki/L%C3%ADnea_K
Acerca de los Códigos de falla o DTC, http://www.cise.com/portal/notas-tecnicas/item/228-
acerca-de-los-c%C3%B3digos-de-falla-o-dtc.html
SAE J1850 Description, http://www.interfacebus.com/Automotive_SAE_J1850_Bus.html
Class B Data Communications Network Interface,
https://www.sae.org/standards/content/j1850_201510/
ISO 15765-4 CAN, https://hackaday.com/tag/iso-15765-4-can/
CAN-TP Protocol, http://www.piembsystech.com/protocol/automotive-protocols/can-tp-
protocol/
ISO 15765-2, http://canbushack.com/iso-15765-2/
OBD2 to RS232 Interpreter, https://www.elmelectronics.com/wp-
content/uploads/2016/07/ELM327DS.pdf
SAEJ1979, http://212.113.105.12/library/BOOKS/CAR/OBDII/saeJ1979_2006-08-25Ballot.pdf
Anexos
143
11. ANEXO 1: Código del microcontrolador PIC18F4550
BT_Interface.h
#include "Types.h"
void sendResponseFrame(responseFrame response);
void sendErrorFrame(negativeResponseFrame negativeResponse);
void sendCommandConfirmation(commandFrame command);
uchar receiveFrame(uchar* frameReceivedId, requestFrame* request, commandFrame*
command);
ISO15765_4.h
#include "Types.h"
uchar nFramesToReceive(uchar dataLen);
uchar sendFlowControlFrameISOTP(uchar flowStatus, uchar blockSize, uchar
separationTime);
uchar sendSingleFrameISOTP(dataFrame dataToSend);
uchar receiveConsecutiveFrameISOTP(uchar* sequenceNumber, uchar data[7]);
uchar receiveResponseISOTP(dataFrame* dataReceived);
uchar sendOBD2RequestISO15765_4(OBD2Request request, OBD2Response* response);
ISO9141_2.h
#include "Types.h"
void sendByte5baud(uchar byteToSend, uchar* trisPort, uchar* latPort, uchar TXPin);
uchar initISO9141();
void sendFrameISO9141(frameFieldsISO9141 frameFields);
uchar receiveSingleFrameISO9141(frameFieldsISO9141* frameFields);
uchar receiveMultipleFramesISO9141(frameFieldsISO9141* framesFields, uchar
framesToReceive);
uchar getVINLengthISO9141(uchar* framesToReceive);
uchar getDTCBytesISO9141(uchar* bytesToReceive);
uchar nFramesToReceive2(uchar dataLen);
uchar getResponseLengthISO9141(OBD2Request requestedPID, uchar* dataLength);
uchar sendOBD2RequestISO9141(OBD2Request requestedPID, OBD2Response* respondedData);
MCP2515.h
#include "Types.h"
void initMCP2515();
void startCOMMCP2515();
void stopCOMMCP2515();
uchar readRegisterMCP2515(uchar regAdress);
void writeRegisterMCP2515(uchar regAdress, uchar value);
void bitModifyMCP2515(uchar regAdress, uchar mask, uchar value);
void resetMCP2515(void);
uchar setModeMCP2515(uchar mode);
void initCAN(uchar baudRate);
uchar sendCANFrameBuffer0(CANDataFrame dataFrame);
void configRX0BufferMCP2515(uchar IdType, ulong bufferFilter, ulong bufferMask);
void emptyRX0BufferMCP2515();
uchar receiveRX0BufferMCP2515(CANDataFrame* dataFrame);
Anexos
144
Macros.h
#define FOSC 48000000
#define TRISA_ADRESS 0xF92
#define TRISB_ADRESS 0xF93
#define TRISC_ADRESS 0xF94
#define TRISD_ADRESS 0xF95
#define TRISE_ADRESS 0xF96
#define PORTA_ADRESS 0xF80
#define PORTB_ADRESS 0xF81
#define PORTC_ADRESS 0xF82
#define PORTD_ADRESS 0xF83
#define PORTE_ADRESS 0xF84
#define CAN_RX_TIMEOUT 500
#define RX_TIMEOUT 1000
#define initByteISO9141 0x33
#define SINGLE_FRAME_RESPONSE 0
#define MULTIPLE_FRAMES_RESPONSE 1
#define PID_NOT_SUPPORTED 2
#define PRIORITY_FIELD_ISO9141 0x68
#define TARGET_FIELD_ISO9141 0x6A
#define SOURCE_FIELD_ISO9141 0xF1
#define INPUT 1
#define OUTPUT 0
#define nop(); asm("nop");
// MCP2515 control registers
#define BFPCTRL 0X0C
#define TXRTSCTRL 0x0D
#define CANSTAT 0xE
#define CANCTRL 0xF
#define TEC 0x1C
#define REC 0x1D
#define CNF3 0x28
#define CNF2 0x29
#define CNF1 0x2A
#define CANINTE 0x2B
#define CANINTF 0x2C
#define EFLG 0x2D
#define TXB0CTRL 0x30
#define TXB1CTRL 0x40
#define TXB2CTRL 0x50
#define RXB0CTRL 0x60
#define RXB1CTRL 0x70
// MCP2515 modes
#define MCP2515_CONFIG_MODE 0
#define MCP2515_NORMAL_MODE 1
#define MCP2515_SLEEP_MODE 2
#define MCP2515_LISTEN_ONLY_MODE 3
#define MCP2515_LOOPBACK_MODE 4
// MCP2515 TX BUFFER 1 (CONTROL AND DATA)
Anexos
145
#define TXB0CTRL 0x40
#define TXB0SIDH 0x41
#define TXB0SIDL 0x42
#define TXB0EID8 0x43
#define TXB0EID0 0x44
#define TXB0DLC 0x45
#define TXB0D0 0x46
// MCP2515 RX BUFFER 0 (CONTROL AND DATA)
#define RXB0CTRL 0x60
#define RXB0SIDH 0x61
#define RXB0SIDL 0x62
#define RXB0EID8 0x63
#define RXB0EID0 0x64
#define RXB0DLC 0x65
#define RXB0D0 0x66
// MCP2515 RX BUFFER 0 FILTER AND MASK
#define RXF0SIDH 0x00
#define RXF0SIDL 0x01
#define RXF0EID8 0x02
#define RXF0EID0 0x03
#define RXM0SIDH 0x20
#define RXM0SIDL 0x21
#define RXM0EID8 0x22
#define RXM0EID0 0x23
// MCP2515 BAUD RATE OPTIONS
#define CAN_SPEED_250KBPS 0
#define CAN_SPEED_500KBPS 1
// MCP2515 FRAME ID TYPES
#define CAN_EXTENDED_FRAME 0
#define CAN_STANDARD_FRAME 1
// MCP2515 REMOTE REQUEST
#define CAN_FRAME_IS_RTR 0x40
#define CAN_FRAME_IS_NOT_RTR 0x00
// MCP2515 MESSAGE PRIORITIES
#define HIGHEST_MESSAGE_PRIORITY 3
#define HIGH_INTERMEDIATE_MESSAGE_PRIORITY 2
#define LOW_INTERMEDIATE_MESSAGE_PRIORITY 1
#define LOWEST_MESSAGE_PRIORITY 0
// BT COM
#define responseFrameId 1
#define requestFrameId 2
#define commandFrameId 3
#define negativeResponseFrameId 4
#define commandConfirmationFrameId 5
// ISO-TP FRAMES
#define SINGLE_FRAME 0
#define FIRST_FRAME 1
#define CONSECUTIVE_FRAME 2
#define FLOW_CONTROL 3
//PROTOCOLS
#define ISO9141_2 0
#define ISO14230_1 1
#define SAEJ1850PWM 2
#define SAEJ1850VPW 3
#define ISO15765_1_11bit_250KB 4
Anexos
146
#define ISO15765_1_11bit_500KB 5
#define ISO15765_1_29bit_250KB 6
#define ISO15765_1_29bit_500KB 7
Math.h
#include "Types.h"
long pow(char baseNumber, char exponentNumber);
SW_UART.h
#include "Types.h"
uchar reverseByte(uchar x);
void initSoftwareUART();
void sendByteSoftwareUART(uchar byteToSend);
uchar receiveByteSoftwareUART(uchar* receivedByte);
void sendBytesSoftwareUART(dataFrame dataToSend);
uchar receiveBytesSoftwareUART(dataFrame* dataToReceive);
Time.h
#include "Types.h"
void wait(uint delay);
void delay19200baud();
void delay9600baud();
Types.h
typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef struct {
uchar cmdLen;
uchar dataLen;
uchar dest;
uchar src;
uchar cmd[10];
} requestFieldsISO9141;
typedef struct {
uchar cmdLen;
uchar dest;
uchar src;
uchar cmd[10];
uchar data[10];
uchar dataLen;
} responseFieldsISO9141;
typedef struct {
uchar idType;
ulong messageId;
uchar RTR;
Anexos
147
uchar DLC;
uchar data[8];
} CANDataFrame;
typedef struct {
uint dataLen;
uchar data[120];
} dataFrame;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[5];
} OBD2Request;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[5];
uint dataLen;
uchar data[100];
} OBD2Response;
uchar frameType;
typedef struct {
uchar idType;
ulong messageId;
uchar RTR;
uchar DLC;
uchar data[8];
} CANDataFrame;
typedef struct {
uint dataLen;
uchar data[100];
} dataFrame;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[5];
} OBD2Request;
typedef struct {
uchar mode;
uchar PID;
} PIDInfo;
Anexos
148
typedef struct {
uchar priority;
uchar source;
uchar target;
uchar dataLen;
uchar data[7];
} frameFieldsISO9141;
typedef struct {
uchar data[7][10];
uchar dataLength;
uchar responseType;
} OBD2ResponseISO9141;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[5];
uint dataLen;
uchar data[100];
} OBD2Response;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[10];
uchar dataLen;
uchar data[100];
} responseFrame;
typedef struct {
uchar service;
uchar PIDLength;
uchar PID[10];
} requestFrame;
typedef struct {
uchar command;
uchar paramsLen;
uchar params[10];
} commandFrame;
typedef struct {
uchar errorCode;
} negativeResponseFrame;
Anexos
149
IO.h
#include "Types.h"
void setPinMode(uchar* port, uchar pin, uchar mode);
void setPin(uchar* port, uchar pin);
void resetPin(uchar* port, uchar pin);
uchar readPin(uchar* port, uchar pin);
SPI_Master.h
#include "Types.h"
void initSPIMaster();
void closeSPIMaster();
uchar transferSPI(uchar byteToSend);
TIMER0.h
#include "Types.h"
void initTMR0();
TIMER3.h
#include "Types.h"
void initTMR3();
USART.h
#include "Types.h"
void sendByteUart(uchar byteToSend);
void sendBytesUart(uchar dataLen, uchar* bytesToSend);
uchar receiveByteUart(uchar* byteReceived);
uchar receiveBytesUart(dataFrame* bytesToReceive);
void flushRxBufferUart();
void initUart();
void closeUart();
void setUartBaud(uint baud);
BT_Interface.c
#include "BT_Interface.h"
#include "Types.h"
#include "Macros.h"
#include "SW_UART.h"
#include <xc.h>
void sendResponseFrame(responseFrame response) {
dataFrame txBuffer;
uchar chksum = 0;
txBuffer.data[0] = responseFrameId;
txBuffer.data[1] = response.service;
txBuffer.data[2] = response.PIDLength;
for(uchar i = 0; i < response.PIDLength; i++) {
Anexos
150
txBuffer.data[3 + i] = response.PID[i];
}
txBuffer.data[3 + response.PIDLength] = response.dataLen;
for(uchar i = 0; i < response.dataLen; i++) {
txBuffer.data[4 + response.PIDLength + i] = response.data[i];
}
for(uchar i = 0; i < (4 + response.PIDLength + response.dataLen); i++) {
chksum += txBuffer.data[i];
}
txBuffer.data[4 + response.PIDLength + response.dataLen] = chksum;
txBuffer.dataLen = 5 + response.PIDLength + response.dataLen;
sendBytesSoftwareUART(txBuffer);
}
void sendErrorFrame(negativeResponseFrame negativeResponse) {
dataFrame txBuffer;
uchar chksum = 0;
txBuffer.data[0] = negativeResponseFrameId;
txBuffer.data[1] = negativeResponse.errorCode;
chksum = txBuffer.data[0] + txBuffer.data[1];
txBuffer.data[2] = chksum;
txBuffer.dataLen = 3; // On a Error Frame thera are always 3 bytes(id + errorCode +
chksum)
sendBytesSoftwareUART(txBuffer);
}
void sendCommandConfirmation(commandFrame command) {
dataFrame txBuffer;
uchar chksum = 0;
txBuffer.data[0] = commandConfirmationFrameId;
txBuffer.data[1] = command.command;
txBuffer.data[2] = command.paramsLen;
for(uchar i = 0; i < command.paramsLen; i++) {
txBuffer.data[3 + i] = command.params[i];
}
for(uchar i = 0; i < (3 + command.paramsLen); i++) {
chksum += txBuffer.data[i];
}
Anexos
151
txBuffer.data[3 + command.paramsLen] = chksum;
txBuffer.dataLen = 4 + command.paramsLen;
sendBytesSoftwareUART(txBuffer);
}
uchar receiveFrame(uchar* frameReceivedId, requestFrame* request, commandFrame*
command) {
uchar receivedChksum;
uchar computedChksum = 0;
if(receiveByteSoftwareUART(frameReceivedId)) {
switch(*frameReceivedId) {
case requestFrameId:
if(!receiveByteSoftwareUART(&request->service)) return 0;
if(!receiveByteSoftwareUART(&request->PIDLength)) return 0;
for(uchar i = 0; i < request->PIDLength; i++) {
if(!receiveByteSoftwareUART(&request->PID[i])) return 0;
}
if(!receiveByteSoftwareUART(&receivedChksum)) return 0;
computedChksum += requestFrameId;
computedChksum += request->service;
computedChksum += request->PIDLength;
for(uchar i = 0; i < request->PIDLength; i++) {
computedChksum += request->PID[i];
}
if(receivedChksum != computedChksum) return 0;
return 1;
break;
case commandFrameId:
if(!receiveByteSoftwareUART(&command->command)) return 0;
if(!receiveByteSoftwareUART(&command->paramsLen)) return 0;
for(uchar i = 0; i < command->paramsLen; i++) {
if(!receiveByteSoftwareUART(&command->params[i])) return 0;
}
if(!receiveByteSoftwareUART(&receivedChksum)) return 0;
computedChksum += commandFrameId;
computedChksum += command->command;
computedChksum += command->paramsLen;
for(uchar i = 0; i < command->paramsLen; i++) {
Anexos
152
computedChksum += command->params[i];
}
if(receivedChksum != computedChksum) return 0;
return 1;
break;
default:
//Unidentified receieved frame
return 0;
}
} else return 0;
}
ISO15765_4.c
#include "ISO15765_4.h"
#include "Types.h"
#include "Macros.h"
#include "MCP2515.h"
#include <xc.h>
uchar nFramesToReceive(uchar dataLen) {
uchar n = 1;
while(n*7 < dataLen) n++; // each received frame contains 7 bytes of data.
return n;
}
uchar sendFlowControlFrameISOTP(uchar flowStatus, uchar blockSize, uchar
separationTime) {
CANDataFrame frameToSend;
frameToSend.idType = frameType;
frameToSend.messageId = 0x7E0;
frameToSend.RTR = CAN_FRAME_IS_NOT_RTR;
frameToSend.DLC = 8;
frameToSend.data[0] = (FLOW_CONTROL << 4) | (flowStatus & 0x0F);
frameToSend.data[1] = blockSize;
frameToSend.data[2] = separationTime;
frameToSend.data[3] = 0xAA;
frameToSend.data[4] = 0xAA;
frameToSend.data[5] = 0xAA;
frameToSend.data[6] = 0xAA;
frameToSend.data[7] = 0xAA;
return sendCANFrameBuffer0(frameToSend);
}
uchar sendSingleFrameISOTP(dataFrame dataToSend) {
CANDataFrame frameToSend;
Anexos
153
frameToSend.idType = frameType;
frameToSend.messageId = 0x7DF;
frameToSend.RTR = CAN_FRAME_IS_NOT_RTR;
frameToSend.DLC = 8;
frameToSend.data[0] = (SINGLE_FRAME << 4) | (dataToSend.dataLen & 0x0F);
for(uchar i = 0; i < dataToSend.dataLen; i++) {
frameToSend.data[i + 1] = dataToSend.data[i];
}
for(uchar i = (dataToSend.dataLen + 1); i < 8; i++) {
frameToSend.data[i] = 0xAA;
}
return sendCANFrameBuffer0(frameToSend);
}
uchar receiveConsecutiveFrameISOTP(uchar* sequenceNumber, uchar data[7]) {
CANDataFrame frameToReceive;
if(!receiveRX0BufferMCP2515(&frameToReceive)) return 0;
if(((frameToReceive.data[0] & 0xF0) >> 4) != CONSECUTIVE_FRAME) return 0;
*sequenceNumber = frameToReceive.data[0] & 0x0F;
for(uchar i = 0; i < 7; i++) {
data[i] = frameToReceive.data[i + 1];
}
return 1;
}
uchar receiveResponseISOTP(dataFrame* dataReceived) {
CANDataFrame frameToReceive;
uchar nConsecutiveFrames;
uchar rxBuffer[7];
uchar sequenceNumber;
if(!receiveRX0BufferMCP2515(&frameToReceive)) return 0;
switch((frameToReceive.data[0] & 0xF0) >> 4) {
case SINGLE_FRAME:
dataReceived->dataLen = frameToReceive.data[0] & 0x0F;
for(uchar i = 0; i < dataReceived->dataLen; i++) {
dataReceived->data[i] = frameToReceive.data[i + 1];
}
return 1;
Anexos
154
break;
case FIRST_FRAME:
dataReceived->dataLen = ((frameToReceive.data[0] & 0x0F) << 8) |
frameToReceive.data[1];
for(uchar i = 0; i < 6; i++) {
dataReceived->data[i] = frameToReceive.data[i + 2];
}
nConsecutiveFrames = nFramesToReceive(dataReceived->dataLen - 6);
if(!sendFlowControlFrameISOTP(0, 0, 5)) return 0; // 0, 0 and 5 are FS, BS
and STmin parameters.
for(uchar i = 0; i < nConsecutiveFrames; i++) {
if(!receiveConsecutiveFrameISOTP(&sequenceNumber, rxBuffer)) return 0;
for(uchar j = 0; j < 7; j++) {
dataReceived->data[6 + i*7 + j] = rxBuffer[j];
}
}
return 1;
break;
default:
// Unidentified response type.
return 0;
break;
}
}
uchar sendOBD2RequestISO15765_4(OBD2Request request, OBD2Response* response) {
dataFrame dataToSend;
dataFrame dataReceived;
dataToSend.dataLen = 1 + request.PIDLength;
dataToSend.data[0] = request.service;
for(uchar i = 1; i < dataToSend.dataLen; i++) {
dataToSend.data[i] = request.PID[i - 1];
}
if(!sendSingleFrameISOTP(dataToSend)) return 0;
if(!receiveResponseISOTP(&dataReceived)) return 0;
response->service = request.service;
response->PIDLength = request.PIDLength;
Anexos
155
for(uchar i = 0; i < request.PIDLength; i++) {
response->PID[i] = request.PID[i];
}
response->dataLen = dataReceived.dataLen - request.PIDLength - 1;
for(uchar i = 0; i < response->dataLen; i++) {
response->data[i] = dataReceived.data[request.PIDLength + 1 + i];
}
return 1;
}
ISO9141_2.h
#include "ISO9141_2.h"
#include "Macros.h"
#include "Types.h"
#include "USART.h"
#include "math.h"
#include "IO.h"
#include "Time.h"
#include <xc.h>
void sendByte5baud(uchar byteToSend, uchar* trisPort, uchar* latPort, uchar TXPin) {
uchar bitMask;
setPinMode(trisPort, TXPin, OUTPUT); // Set pin TX as an output
setPin(latPort, TXPin); // Set pin TX
wait(1000); // Wait till k-line gets high
resetPin(latPort, TXPin);
wait(200);
//Now the transmission starts
for(uchar i = 0; i < 8; i++) {
bitMask = pow(2, i);
if(byteToSend & bitMask) setPin(latPort, TXPin); else resetPin(latPort, TXPin);
wait(200); // Tbit is 200 ms
}
setPin(latPort, TXPin);
wait(200);
}
uchar initISO9141() {
uchar byteReceived;
dataFrame keyBytes;
keyBytes.dataLen = 2;
Anexos
156
closeUart(); // Disable hardware UART so we can work with a software UART in the
same pins
sendByte5baud(initByteISO9141, TRISC_ADRESS, PORTC_ADRESS, 6); // Send
initialization byte 0x33 at 5 Baud
initUart();
setUartBaud(10400);
if(!receiveByteUart(&byteReceived)) return 0;
if(byteReceived != 0x55) return 0; // If first car response is invalid then return 0
(valid response is 0x55)
if(!receiveBytesUart(&keyBytes)) return 0;
if((keyBytes.data[0] != 0x08 && keyBytes.data[0] != 0x94) || (keyBytes.data[1] !=
0x08 && keyBytes.data[1] != 0x94) || keyBytes.data[0] != keyBytes.data[1]) return 0; //
If key bytes are invalid then retorn 0, vali keybytes are 08 08 or 94 94
wait(30); // This delay is a protocol spec
sendByteUart(~keyBytes.data[0]); // Send keyByte #2 inverted
receiveByteUart(&byteReceived); // receive echo..
if(!receiveByteUart(&byteReceived)) return 0;
if(byteReceived != 0xCC) return 0;
return 1; // If reached here means that initilitzation has been succesfull, so 1 is
returned
}
void sendFrameISO9141(frameFieldsISO9141 frameFields) {
uchar txBuffer[11]; // Length of TX buffer is setted considering that a maximum of
7 bytes of data can be sended with a single frame
uchar checksum = 0;
txBuffer[0] = frameFields.priority; // First byte of ISO9141 frame is format byte
txBuffer[1] = frameFields.target; // Second byte is target adress
txBuffer[2] = frameFields.source; // Third byte is source adress
// Next lines add data bytes to frame
for(uchar i = 0; i < frameFields.dataLen; i++) {
txBuffer[3 + i] = frameFields.data[i];
}
// Checksum of frame is computed
for(uchar i = 0; i < (3 + frameFields.dataLen); i++) {
checksum += txBuffer[i];
}
txBuffer[3 + frameFields.dataLen] = checksum; // Add checksum of frame
// Finally the built frame is sended
for(uchar i = 0; i < (4 + frameFields.dataLen); i++) {
Anexos
157
sendByteUart(txBuffer[i]);
wait(10); // Wait 10 ms between sended bytes as ISO9141 specifies
flushRxBufferUart(); // each byte that is sended produces an echo that is
received through UART, so it has to be cleared
}
}
uchar receiveSingleFrameISO9141(frameFieldsISO9141* frameFields) {
uchar rxBuffer[11];
uchar checksum = 0;
for(uchar i = 0; i < (frameFields->dataLen + 4); i++) {
if(!receiveByteUart(rxBuffer + i)) return 5;
}
for(uchar i = 0; i < (frameFields->dataLen + 3); i++) {
checksum += rxBuffer[i];
}
if(checksum != rxBuffer[frameFields->dataLen + 3]) return 0;
frameFields->priority = rxBuffer[0];
frameFields->target = rxBuffer[1];
frameFields->source = rxBuffer[2];
for(uchar i = 3; i < (frameFields->dataLen + 3); i++) {
frameFields->data[i - 3] = rxBuffer[i];
}
return 1;
}
uchar receiveMultipleFramesISO9141(frameFieldsISO9141* framesFields, uchar
framesToReceive) {
uchar rxBuffer[110];
uchar checksum = 0;
for(uchar i = 0; i < (framesToReceive*11); i++) {
if(!receiveByteUart(rxBuffer + i)) return 0;
}
for(uchar frame = 0; frame < framesToReceive; frame++) {
for(uchar i = (frame*11); i < (frame*11 + 10); i++) {
checksum += rxBuffer[i];
}
if(checksum != rxBuffer[frame*11 + 10]) return 0;
Anexos
158
checksum = 0;
}
for(uchar frame = 0; frame < framesToReceive; frame++) {
(framesFields + frame)->priority = rxBuffer[frame*11];
(framesFields + frame)->target = rxBuffer[frame*11 + 1];
(framesFields + frame)->source = rxBuffer[frame*11 + 2];
(framesFields + frame)->dataLen = 7;
for(uchar i = 0; i < 7; i++) {
(framesFields + frame)->data[i] = rxBuffer[frame*11 + 3 + i];
}
}
return 1;
}
uchar getVINLengthISO9141(uchar* framesToReceive) {
frameFieldsISO9141 frameToSend;
frameFieldsISO9141 frameReceived;
frameToSend.priority = PRIORITY_FIELD_ISO9141;
frameToSend.target = TARGET_FIELD_ISO9141;
frameToSend.source = SOURCE_FIELD_ISO9141;
frameToSend.dataLen = 2;
frameToSend.data[0] = 9;
frameToSend.data[1] = 1;
frameReceived.dataLen = 3;
sendFrameISO9141(frameToSend);
if(!receiveSingleFrameISO9141(&frameReceived)) return 0;
*framesToReceive = frameReceived.data[2];
return 1;
}
uchar getDTCBytesISO9141(uchar* bytesToReceive) {
frameFieldsISO9141 frameToSend;
frameFieldsISO9141 frameReceived;
frameToSend.priority = PRIORITY_FIELD_ISO9141;
frameToSend.target = TARGET_FIELD_ISO9141;
frameToSend.source = SOURCE_FIELD_ISO9141;
frameToSend.dataLen = 2;
frameToSend.data[0] = 1;
frameToSend.data[1] = 1;
frameReceived.dataLen = 6;
sendFrameISO9141(frameToSend);
if(!receiveSingleFrameISO9141(&frameReceived)) return 0;
Anexos
159
*bytesToReceive = (frameReceived.data[2] & 0x7F)*2;
return 1;
}
uchar nFramesToReceive2(uchar dataLen) {
uchar n = 1;
while(n*6 < dataLen) n++; // each received frames contains 6 bytes of data.
return n;
}
uchar getResponseLengthISO9141(OBD2Request requestedPID, uchar* dataLength) {
uchar queryResult;
uint PID = 0;
uchar aux;
for(uchar i = 0; i < requestedPID.PIDLength; i++) {
PID |= requestedPID.PID[i] << i*8;
}
if((requestedPID.service == 1) || (requestedPID.service == 2)) {
switch(PID) {
case 0x0:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x6:
Anexos
160
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x7:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x8:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x9:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xA:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xB:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xC:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xD:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xE:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0xF:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x10:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x11:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x12:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
Anexos
161
case 0x13:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x14:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x15:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x16:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x17:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x18:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x19:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1A:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1B:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1C:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1D:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1E:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x1F:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
Anexos
162
case 0x20:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x21:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x22:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x23:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x24:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x25:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x26:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x27:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x28:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x29:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2A:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2B:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2C:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
Anexos
163
break;
case 0x2D:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2E:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x2F:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x30:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x31:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x32:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x33:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x34:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x35:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x36:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x37:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x38:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x39:
*dataLength = 4;
Anexos
164
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3A:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3B:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3C:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3D:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3E:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x3F:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x40:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x41:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x42:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x43:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x44:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x45:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x46:
Anexos
165
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x47:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x48:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x49:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4A:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4B:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4C:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4D:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4E:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x4F:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x50:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x51:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x52:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
Anexos
166
case 0x53:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x54:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x55:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x56:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x57:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x58:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x59:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5A:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5B:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5C:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5D:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5E:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x5F:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
Anexos
167
case 0x60:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x61:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x62:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x63:
*dataLength = 2;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x64:
*dataLength = 5;
queryResult = SINGLE_FRAME_RESPONSE;
break;
default:
queryResult = PID_NOT_SUPPORTED;
break;
}
} else if(requestedPID.service == 3) {
// In this case we don't know length of response so we have to request it
if(getDTCBytesISO9141(&aux)) queryResult = MULTIPLE_FRAMES_RESPONSE; else
queryResult = PID_NOT_SUPPORTED;
*dataLength = nFramesToReceive2(aux);
} else if(requestedPID.service == 4) {
*dataLength = 0;
queryResult = SINGLE_FRAME_RESPONSE;
} else if(requestedPID.service == 5) {
} else if(requestedPID.service == 6) {
} else if(requestedPID.service == 7) {
} else if(requestedPID.service == 8) {
} else if(requestedPID.service == 9) {
switch(PID) {
case 0x0:
*dataLength = 4;
queryResult = SINGLE_FRAME_RESPONSE;
Anexos
168
case 0x01:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
break;
case 0x02:
// In this case we don't know length of response so we have to request it
if(getVINLengthISO9141(dataLength)) queryResult = MULTIPLE_FRAMES_RESPONSE;
else queryResult = PID_NOT_SUPPORTED;
break;
case 0x03:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
case 0x04:
if(getVINLengthISO9141(dataLength)) queryResult = MULTIPLE_FRAMES_RESPONSE;
else queryResult = PID_NOT_SUPPORTED;
case 0x05:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
case 0x06:
if(getVINLengthISO9141(dataLength)) queryResult = MULTIPLE_FRAMES_RESPONSE;
else queryResult = PID_NOT_SUPPORTED;
case 0x07:
*dataLength = 1;
queryResult = SINGLE_FRAME_RESPONSE;
case 0x0A:
if(getVINLengthISO9141(dataLength)) queryResult = MULTIPLE_FRAMES_RESPONSE;
else queryResult = PID_NOT_SUPPORTED;
default:
queryResult = PID_NOT_SUPPORTED;
break;
}
} else if(requestedPID.service == 10) {
} else if(requestedPID.service == 11) {
} else queryResult = PID_NOT_SUPPORTED;
return queryResult;
}
uchar sendOBD2RequestISO9141(OBD2Request requestedPID, OBD2Response* respondedData) {
uchar aux1;
frameFieldsISO9141 requestFrame;
frameFieldsISO9141 responseFrames[10];
uchar responseType;
uchar aux = 0;
uchar dataa[100];
Anexos
169
uchar ji = 0;
// Value of fields are assigned on the following lines
requestFrame.priority = PRIORITY_FIELD_ISO9141;
requestFrame.target = TARGET_FIELD_ISO9141;
requestFrame.source = SOURCE_FIELD_ISO9141;
requestFrame.dataLen = requestedPID.PIDLength + 1; // Data length is pid length + 1
byte of service
requestFrame.data[0] = requestedPID.service; // First data byte is always service
// Now we have to add the PID to data field
for(uchar i = 0; i < requestedPID.PIDLength; i++) {
requestFrame.data[i + 1] = requestedPID.PID[i];
}
responseType = getResponseLengthISO9141(requestedPID, &aux1); // ????Data length
and response type is obtained form another function
sendFrameISO9141(requestFrame); // Built frame with PID request is sended
switch(responseType) {
// If response is delivered with a single frame we have to call
receiveSingleFrameISO9141
case SINGLE_FRAME_RESPONSE:
responseFrames[0].dataLen = aux1 + 1 + requestedPID.PIDLength;
if(!receiveSingleFrameISO9141(responseFrames)) return 0; // if response not
received or not valid abort
for(uchar i = 0; i < aux1; i++) {
respondedData->data[i] = responseFrames[0].data[i + 1 +
requestedPID.PIDLength]; // copy data bytes from responded frame to output struct
respondedData
}
respondedData->service = requestedPID.service;
respondedData->PIDLength = requestedPID.PIDLength;
for(uchar i = 0; i < requestedPID.PIDLength; i++) respondedData->PID[i] =
requestedPID.PID[i];
respondedData->dataLen = aux1;
return 1; // request done with success
break;
// If response is delivered with multiple frames we have to call
receiveMultipleFramesISO9141
case MULTIPLE_FRAMES_RESPONSE:
if(!receiveMultipleFramesISO9141(responseFrames, aux1)) return 0; // if frame
not received or not valid abort
if(requestedPID.service != 3) {
for(uchar i = 0; i < aux1; i++) {
for(uchar j = 3; j < 7; j++) {
Anexos
170
respondedData->data[aux] = responseFrames[i].data[j]; // Copy data
bytes from received frame to output struct respondedData
aux++;
}
}
} else {
for(uchar i = 0; i < aux1; i++) {
for(uchar j = 1; j < 7; j++) {
respondedData->data[aux] = responseFrames[i].data[j]; // Copy data
bytes from received frame to output struct respondedData
aux++;
}
}
}
respondedData->dataLen = aux;
respondedData->service = requestedPID.service;
respondedData->PIDLength = requestedPID.PIDLength;
for(uchar i = 0; i < requestedPID.PIDLength; i++) respondedData->PID[i] =
requestedPID.PID[i];
return 1; // request done with success
break;
// If data length is not known then PID is not supprted
case PID_NOT_SUPPORTED:
return 0;
break;
}
}
MCP2515.c
#include "MCP2515.h"
#include "Types.h"
#include "Macros.h"
#include "SPI_Master.h"
#include "Time.h"
#include <xc.h>
extern volatile uint tics;
void initMCP2515() {
TRISBbits.RB2 = 0; // Set pin connected to /CS of MCP2515 as an output
PORTBbits.RB2 = 1; // MCP2515 not selected
}
Anexos
171
void startCOMMCP2515() {
PORTBbits.RB2 = 0;
}
void stopCOMMCP2515() {
PORTBbits.RB2 = 1;
}
uchar readRegisterMCP2515(uchar regAdress) {
uchar regValue;
startCOMMCP2515();
transferSPI(0x03); // First byte to send is instruction ID, 0x03 is to read a
register (dummy data received)
transferSPI(regAdress); // Second byte is register adress (dummy data received)
regValue = transferSPI(0); // Received byte is value of register (dummy data
sended)
stopCOMMCP2515();
return regValue;
}
void writeRegisterMCP2515(uchar regAdress, uchar value) {
startCOMMCP2515(); // Set MCP2515 /CS low
transferSPI(0x02); // First byte to send is instruction ID, 0x02 is to write a
register (dummy data received)
transferSPI(regAdress); // Second byte is register adress (dummy data received)
transferSPI(value); // Received byte is value of register (dummy data sended)
stopCOMMCP2515(); // Set MCP2515 /CS HIGH
return;
}
void bitModifyMCP2515(uchar regAdress, uchar mask, uchar value) {
startCOMMCP2515(); // Set MCP2515 /CS low
transferSPI(0x05); // First byte to send is instruction ID, 0x02 is to write a
register (dummy data received)
transferSPI(regAdress); // Second byte is register adress (dummy data received)
transferSPI(mask); // Third byte the bit mask (dummy data received)
transferSPI(value); // Fourth byte is the value to set to those bits that are going
to be modified
stopCOMMCP2515(); // Set MCP2515 /CS HIGH
return;
}
void resetMCP2515(void) {
Anexos
172
startCOMMCP2515(); // Set MCP2515 /CS low
transferSPI(0xC0); // First byte to send is instruction ID, 0xC0 is to reset
MCP2515
stopCOMMCP2515(); // Set MCP2515 /CS HIGH
return;
}
uchar setModeMCP2515(uchar mode) {
uchar value;
uchar readingAttempts = 0;
// Value will have a value according to selected mode
switch(mode) {
case MCP2515_CONFIG_MODE:
value = 0x80;
break;
case MCP2515_NORMAL_MODE:
value = 0x00;
break;
case MCP2515_SLEEP_MODE:
value = 0x20;
break;
case MCP2515_LISTEN_ONLY_MODE:
value = 0x60;
break;
case MCP2515_LOOPBACK_MODE:
value = 0x40;
break;
default:
value = 0x80;
}
bitModifyMCP2515(CANCTRL, 0xE0, value); // Modify mode bits to set desired mode
while(1) {
if(readingAttempts == 10) return 0; // if after 10 attempts mode isn't set then 0
is returned
wait(200); // Give time to MCP2515 to change his mode
if((readRegisterMCP2515(CANSTAT) & 0xE0) == value) return 1; // if mode is
correctly set return 1
readingAttempts++;
}
}
void initCAN(uchar baudRate) {
Anexos
173
setModeMCP2515(MCP2515_CONFIG_MODE); // First we have to put MCP2515 in donfig mode
bitModifyMCP2515(CNF2, 0x07, 0x01); // PropSeg is 2 time quanta
bitModifyMCP2515(CNF2, 0x38, 0x30); // PS1 segment is 7 time quanta
bitModifyMCP2515(CNF2, 0x80, 0x80); // BTLMODE bit = 1 so we can manually set PS2
time quanta
bitModifyMCP2515(CNF3, 0x07, 0x05); // PS2 segment is 6 time quanta
bitModifyMCP2515(CNF1, 0xC0, 0x00); // SJW segment is 1 time quanta
// Baud rate will be set according to baudRate parameter
switch(baudRate) {
case CAN_SPEED_250KBPS:
bitModifyMCP2515(CNF1, 0x3F, 0x01);
break;
case CAN_SPEED_500KBPS:
bitModifyMCP2515(CNF1, 0x3F, 0x00);
break;
default:
bitModifyMCP2515(CNF1, 0x3F, 0x01); // Default speed is 250KBPS
}
setModeMCP2515(MCP2515_NORMAL_MODE); // To end initialitzation normal mode has to
be set
}
uchar sendCANFrameBuffer0(CANDataFrame dataFrame) {
uchar TXB0CTRL_;
uchar TXB0SIDL_;
uchar TXB0SIDH_;
uchar TXB0EID8_;
uchar TXB0EID0_;
bitModifyMCP2515(TXB0CTRL, 0x03, HIGHEST_MESSAGE_PRIORITY); // Give buffer 0
highest priority
// Set id type and id of data frame to transmit
switch(dataFrame.idType) {
case CAN_STANDARD_FRAME:
TXB0SIDH_ = dataFrame.messageId >> 3;
TXB0SIDL_ = dataFrame.messageId << 5;
writeRegisterMCP2515(TXB0SIDH, TXB0SIDH_);
writeRegisterMCP2515(TXB0SIDL, TXB0SIDL_);
break;
case CAN_EXTENDED_FRAME:
TXB0SIDH_ = dataFrame.messageId >> 21;
TXB0SIDL_ = ((dataFrame.messageId >> 13) & 0x00E0) | ((dataFrame.messageId >> 16)
& 0x0003) | 0x0008;
TXB0EID8_ = dataFrame.messageId >> 8;
TXB0EID0_ = dataFrame.messageId;
writeRegisterMCP2515(TXB0SIDH, TXB0SIDH_);
writeRegisterMCP2515(TXB0SIDL, TXB0SIDL_);
writeRegisterMCP2515(TXB0EID8, TXB0EID8_);
writeRegisterMCP2515(TXB0EID0, TXB0EID0_);
break;
}
Anexos
174
bitModifyMCP2515(TXB0DLC, 0x40, 0x40); // RTR bit
bitModifyMCP2515(TXB0DLC, 0x0F, dataFrame.DLC); // DLC field
// Write n bytes in TX buffer0
for(uchar i = 0; i < dataFrame.DLC; i++) {
writeRegisterMCP2515(TXB0D0 + i, dataFrame.data[i]);
}
bitModifyMCP2515(TXB0CTRL, 0x08, 0x08); // Start transmission
// Now we have to wait until transmission has been completed
while(1) {
wait(10); // Check transmission bits every 10 ms
TXB0CTRL_ = readRegisterMCP2515(TXB0CTRL);
if(!(TXB0CTRL_ & 0x08)) return 1; // Message succesfully transmited, return 1
if(TXB0CTRL_ & 0x10) {
bitModifyMCP2515(TXB0CTRL, 0x08, 0x00); // Abort transmission
return 0; // Message not transmited, return 0
}
}
}
void configRX0BufferMCP2515(uchar IdType, ulong bufferFilter, ulong bufferMask) {
uchar RXF0SIDH_;
uchar RXF0SIDL_;
uchar RXF0EID8_;
uchar RXF0EID0_;
uchar RXM0SIDH_;
uchar RXM0SIDL_;
uchar RXM0EID8_;
uchar RXM0EID0_;
setModeMCP2515(MCP2515_CONFIG_MODE);
switch(IdType) {
case CAN_EXTENDED_FRAME:
writeRegisterMCP2515(RXB0CTRL, 0x40); // Only receive extended frames
// Write filter value
RXF0SIDH_ = bufferFilter >> 21;
RXF0SIDL_ = ((bufferFilter >> 13) & 0xE0) | 0x08 | ((bufferFilter >> 16) &
0x03);
RXF0EID8_ = bufferFilter >> 8;
RXF0EID0_ = bufferFilter;
writeRegisterMCP2515(RXF0SIDH, RXF0SIDH_);
writeRegisterMCP2515(RXF0SIDL, RXF0SIDL_);
writeRegisterMCP2515(RXF0EID8, RXF0EID8_);
writeRegisterMCP2515(RXF0EID0, RXF0EID0_);
// Write filter mask
RXM0SIDH_ = bufferMask >> 21;
RXM0SIDL_ = ((bufferMask >> 13) & 0xE0) | ((bufferMask >> 16) & 0x03);
Anexos
175
RXM0EID8_ = bufferMask >> 8;
RXM0EID0_ = bufferMask;
writeRegisterMCP2515(RXM0SIDH, RXM0SIDH_);
writeRegisterMCP2515(RXM0SIDL, RXM0SIDL_);
writeRegisterMCP2515(RXM0EID8, RXM0EID8_);
writeRegisterMCP2515(RXM0EID0, RXM0EID0_);
break;
case CAN_STANDARD_FRAME:
writeRegisterMCP2515(RXB0CTRL, 0x20); // Only receive standard frames
// Write filter value
RXF0SIDH_ = bufferFilter >> 3;
RXF0SIDL_ = bufferFilter << 5;
writeRegisterMCP2515(RXF0SIDH, RXF0SIDH_);
writeRegisterMCP2515(RXF0SIDL, RXF0SIDL_);
// Write filter mask
RXM0SIDH_ = bufferMask >> 3;
RXM0SIDL_ = bufferMask << 5;
writeRegisterMCP2515(RXM0SIDH, RXM0SIDH_);
writeRegisterMCP2515(RXM0SIDL, RXM0SIDL_);
break;
default:
writeRegisterMCP2515(RXB0CTRL, 0x20); // Only receive standard frames
// Write filter value
RXF0SIDH_ = bufferFilter >> 3;
RXF0SIDL_ = bufferFilter << 5;
writeRegisterMCP2515(RXF0SIDH, RXF0SIDH_);
writeRegisterMCP2515(RXF0SIDL, RXF0SIDL_);
// Write filter mask
RXM0SIDH_ = bufferMask >> 3;
RXM0SIDL_ = bufferMask << 5;
writeRegisterMCP2515(RXM0SIDH, RXM0SIDH_);
writeRegisterMCP2515(RXM0SIDL, RXM0SIDL_);
}
setModeMCP2515(MCP2515_NORMAL_MODE);
}
void emptyRX0BufferMCP2515() {
bitModifyMCP2515(CANINTF, 0x01, 0);
}
uchar receiveRX0BufferMCP2515(CANDataFrame* dataFrame) { // THIS FUNCTION WAITS FOR RX0
FLAG TO SET
uchar CANINTF_;
uchar RXB0SIDL_;
uchar RXB0SIDH_;
uchar RXB0EID8_;
uchar RXB0EID0_;
Anexos
176
CANINTF_ = readRegisterMCP2515(CANINTF);
tics = 0;
while(!(CANINTF_ & 0x01) && (tics < CAN_RX_TIMEOUT)) { // Falta el timeout
CANINTF_ = readRegisterMCP2515(CANINTF);
}
if(CANINTF_ & 0x01) {
RXB0SIDL_ = readRegisterMCP2515(RXB0SIDL);
if(RXB0SIDL_ & 0x08) {
// Extended frame received
dataFrame->idType = CAN_EXTENDED_FRAME;
RXB0SIDH_ = readRegisterMCP2515(RXB0SIDH);
RXB0EID8_ = readRegisterMCP2515(RXB0EID8);
RXB0EID0_ = readRegisterMCP2515(RXB0EID0);
dataFrame->messageId = (RXB0SIDH_ << 21) | ((RXB0SIDL_ & 0xE0) << 13) |
(RXB0EID8_ << 8) | RXB0EID0_;
} else {
// Standard frame received
dataFrame->idType = CAN_STANDARD_FRAME;
RXB0SIDH_ = readRegisterMCP2515(RXB0SIDH);
dataFrame->messageId = (RXB0SIDH_ << 3) | (RXB0SIDL_ >> 5);
}
dataFrame->RTR = readRegisterMCP2515(RXB0DLC) & 0x40;
dataFrame->DLC = readRegisterMCP2515(RXB0DLC) & 0x0F;
for(uchar i = 0; i < dataFrame->DLC; i++) {
dataFrame->data[i] = readRegisterMCP2515(RXB0D0 + i);
}
emptyRX0BufferMCP2515();
return 1;
} else return 0;
}
math.c
#include "math.h"
#include "Types.h"
#include "Macros.h"
#include <xc.h>
Anexos
177
long pow(char baseNumber, char exponentNumber) {
long result = 1;
for(char i = 0; i < exponentNumber; i++) result *= baseNumber;
return result;
};
SW_UART.h
#include "SW_UART.h"
#include "Types.h"
#include "Macros.h"
#include "Time.h"
#include <xc.h>
extern volatile uint tics;
uchar reverseByte(uchar x) {
// a simple table is used to obatin reversed byte
static const uchar table[] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};
return table[x];
}
void initSoftwareUART() {
Anexos
178
TRISDbits.TRISD2 = 0; // Set TX pin as an output
TRISDbits.TRISD1 = 1; // Set RX pin as an input
PORTDbits.RD2 = 1; // Set TX pin high
wait(1000); // delay to ensure that node 2 sees bus high
}
void sendByteSoftwareUART(uchar byteToSend) {
uchar bitmask = 1;
// Start Bit
PORTDbits.RD2 = 0;
delay9600baud();
// 8-bit data
for(uchar i = 0; i < 8; i++) {
if(byteToSend & bitmask) PORTDbits.RD2 = 1; else PORTDbits.RD2 = 0;
delay9600baud();
bitmask = bitmask << 1;
}
// Stop Bit
PORTDbits.RD2 = 1;
delay9600baud();
}
uchar receiveByteSoftwareUART(uchar* receivedByte) {
uint sampledBits = 0;
tics = 0;
while(PORTDbits.RD1 && (tics < 5000));
if(PORTDbits.RD1) return 0;
delay19200baud();
for(uchar i = 0; i < 10; i++) {
sampledBits = sampledBits << 1;
if(PORTDbits.RD1) sampledBits++;
delay9600baud();
}
if(((sampledBits & 0x0200) == 0) && ((sampledBits & 0x0001) > 0)) {
*receivedByte = (sampledBits >> 1) & 0x00FF;
*receivedByte = reverseByte(*receivedByte); // reverse bytes as it's received
MSBF
return 1;
} else return 0;
}
void sendBytesSoftwareUART(dataFrame dataToSend) {
for(uchar i = 0; i < dataToSend.dataLen; i++) {
sendByteSoftwareUART(dataToSend.data[i]);
Anexos
179
wait(5);
}
}
uchar receiveBytesSoftwareUART(dataFrame* dataToReceive) {
uchar i = 0;
while(1) {
if(i == 100) break; // Max data to receive is 100 bytes
if(receiveByteSoftwareUART(&dataToReceive->data[i])) i++; else break;
}
dataToReceive->dataLen = i;
if(dataToReceive->dataLen) return 1; else return 0;
}
Time.c
#include "Time.h"
#include "Types.h"
#include "Macros.h"
#include <xc.h>
volatile uint tics;
void wait(uint delay) {
tics = 0;
while(tics < delay);
}
void delay19200baud() {
uint TMR3Count;
TMR3H = 0;
TMR3L = 0;
do {
TMR3Count = TMR3L & 0x00FF;
TMR3Count |= (TMR3H << 8) & 0xFF00;
} while (TMR3Count < 156);
}
void delay9600baud() {
uint TMR3Count;
TMR3H = 0;
TMR3L = 0;
do {
TMR3Count = TMR3L & 0x00FF;
TMR3Count |= (TMR3H << 8) & 0xFF00;
Anexos
180
} while (TMR3Count < 312);
}
IO.c
#include "IO.h"
#include "Types.h"
#include "math.h"
#include "Macros.h"
#include <xc.h>
void setPinMode(uchar* port, uchar pin, uchar mode) {
uchar bitMask = pow(2, pin);
if(mode) {
*port = *port | bitMask;
} else {
bitMask = ~bitMask;
*port = *port & bitMask;
}
}
void setPin(uchar* port, uchar pin) {
uchar bitMask = pow(2, pin);
*port = *port | bitMask;
}
void resetPin(uchar* port, uchar pin) {
uchar bitMask = ~pow(2, pin);
*port = *port & bitMask;
}
uchar readPin(uchar* port, uchar pin) {
uchar bitMask = pow(2, pin);
uchar pinValue;
if(*port & bitMask) pinValue = 1; else pinValue = 0;
return pinValue;
}
SPI_Master.h
#include "SPI_Master.h"
#include "Types.h"
#include "Macros.h"
Anexos
181
#include <xc.h>
void initSPIMaster() {
// Master mode, clk = FOSC/16, in this case 1 MHz (FOSC = 16 MHz)
SSPCON1bits.SSPM3 = 0;
SSPCON1bits.SSPM2 = 0;
SSPCON1bits.SSPM1 = 0;
SSPCON1bits.SSPM0 = 1;
ADCON0 = 0; //
// Set all analog inputs as digital pins
ADCON1bits.PCFG3 = 1;
ADCON1bits.PCFG2 = 1;
ADCON1bits.PCFG1 = 1;
ADCON1bits.PCFG0 = 1;
TRISBbits.RB0 = 1; // Set SDI pin as input
TRISCbits.RC7 = 0; // Set SDO pin as output
TRISBbits.RB1 = 0; // Set SCL pin as output
SMP = 0; // SDI sample is done in the middle of time bit
BF = 0; // Clear buffer full bit
PIR1bits.SSPIF = 0; // Clear interrupt flag of SPI
// Mode 1,1 is utilized in this case (CKP = 0, CKE = 1)
SSPCON1bits.CKP = 1;
SSPSTATbits.CKE = 0;
SSPCON1bits.SSPEN = 1; // Enable serial port
}
void closeSPIMaster() {
SSPCON1bits.SSPEN = 0; // Disable MSSP
}
uchar transferSPI(uchar byteToSend) {
PIR1bits.SSPIF = 0; // Clear interrupt flag of SPI
SSPBUF = byteToSend; // Start transfer of data
while(!PIR1bits.SSPIF); // Wait until byte has been shifted
return SSPBUF; // return received data, might be dummy data
}
TIMER0.c
#include "TIMER0.h"
#include "Types.h"
#include "Macros.h"
#include <xc.h>
void initTMR0() {
T08BIT = 1;
T0CS = 0;
PSA = 0;
Anexos
182
T0PS2 = 1;
T0PS1 = 1;
T0PS0 = 0;
TMR0 = 0xA2;
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
ei();
TMR0ON = 1;
}
TIMER3.c
#include "TIMER3.h"
#include <xc.h>
void initTMR3() {
T3CONbits.RD16 = 1;
T3CONbits.T3CKPS1 = 1;
T3CONbits.T3CKPS0 = 0;
T3CONbits.TMR3CS = 0;
T3CONbits.TMR3ON = 1;
}
USART.c
#include "Macros.h"
#include "USART.h"
#include "Types.h"
#include "time.h"
#include "IO.h"
#include <xc.h>
extern volatile uint tics;
void sendByteUart(uchar byteToSend) {
TXREG = byteToSend;
nop(); // one instruction cycle delay
while(!TXIF);
}
void sendBytesUart(uchar dataLen, uchar* bytesToSend) {
uchar byteToSend;
for(uchar i = 0; i < dataLen; i++) {
byteToSend = *(bytesToSend + i);
sendByteUart(byteToSend);
wait(10);
}
}
uchar receiveByteUart(uchar* byteReceived) {
tics = 0;
Anexos
183
while(!RCIF && (tics < RX_TIMEOUT)); // Wait until a byte is available to read(tics
< RX_TIMEOUT)
if(RCIF) {
*byteReceived = RCREG;
RCIF = 0;
return 1;
} else return 0;
}
uchar receiveBytesUart(dataFrame* bytesToReceive) {
char byteReceived;
for(uchar i = 0; i < bytesToReceive->dataLen; i++) {
if(!receiveByteUart(&byteReceived)) return 0;
bytesToReceive->data[i] = byteReceived;
}
return 1;
}
void flushRxBufferUart() {
uchar byteReceived;
tics = 0;
while(!RCIF && tics < RX_TIMEOUT); // Wait until a byte is available to read
if(RCIF) {
byteReceived = RCREG;
RCIF = 0;
}
}
void initUart() {
setPinMode(TRISC_ADRESS, 6, INPUT); // Configure RC6/TX/CK pin as an input
setPinMode(TRISC_ADRESS, 7, INPUT); // Configure RC7/RX/DT/SDO pin as an input
SYNC = 0; // Asynchronous mode selected
SPEN = 1; // Serial port enabled
BRGH = 1; // High speed mode
BRG16 = 1; // 16-bit BRG
TXEN = 1; // Enable transmission
CREN = 1; // Enable reception
}
void closeUart() {
SPEN = 0; // Disable serial port
TXEN = 0; // Disable transmission
Anexos
184
CREN = 0; // Disable reception
}
void setUartBaud(uint baud) {
uint n;
n = FOSC / (4*baud) - 1;
SPBRGH = (n & 0xFF00) >> 8;
SPBRG = n & 0x00FF;
}
main.c
#pragma config PLLDIV = 1 // PLL Prescaler Selection bits (No prescale (4 MHz
oscillator input drives PLL directly))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary
Oscillator Src: /4][96 MHz PLL Src: /6])
#pragma config USBDIV = 2 // USB Clock Selection bit (used in Full-Speed USB mode
only; UCFG:FSEN = 1) (USB clock source comes from the 96 MHz PLL divided by 2)
// CONFIG1H
#pragma config FOSC = XTPLL_XT // Oscillator Selection bits (XT oscillator, PLL
enabled (XTPLL))
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock
Monitor disabled)
#pragma config IESO = OFF // Internal/External Oscillator Switchover bit
(Oscillator Switchover mode disabled)
// CONFIG2L
#pragma config PWRT = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF // Brown-out Reset Enable bits (Brown-out Reset
disabled in hardware and software)
#pragma config BORV = 3 // Brown-out Reset Voltage bits (Minimum setting 2.05V)
#pragma config VREGEN = OFF // USB Voltage Regulator Enable bit (USB voltage
regulator disabled)
// CONFIG2H
#pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled (control is
placed on the SWDTEN bit))
#pragma config WDTPS = 32768 // Watchdog Timer Postscale Select bits (1:32768)
// CONFIG3H
#pragma config CCP2MX = OFF // CCP2 MUX bit (CCP2 input/output is multiplexed with
RC1)
#pragma config PBADEN = OFF // PORTB A/D Enable bit (PORTB<4:0> pins are configured
as digital I/O on Reset)
#pragma config LPT1OSC = OFF // Low-Power Timer 1 Oscillator Enable bit (Timer1
configured for higher power operation)
#pragma config MCLRE = OFF // MCLR Pin Enable bit (RE3 input pin enabled; MCLR pin
disabled)
// CONFIG4L
#pragma config STVREN = OFF // Stack Full/Underflow Reset Enable bit (Stack
full/underflow will cause Reset)
#pragma config LVP = OFF // Single-Supply ICSP Enable bit (Single-Supply ICSP
disabled)
#pragma config ICPRT = OFF // Dedicated In-Circuit Debug/Programming Port (ICPORT)
Enable bit (ICPORT disabled)
Anexos
185
#pragma config XINST = OFF // Extended Instruction Set Enable bit (Instruction set
extension and Indexed Addressing mode disabled (Legacy mode))
// CONFIG5L
#pragma config CP0 = OFF // Code Protection bit (Block 0 (000800-001FFFh) is not
code-protected)
#pragma config CP1 = OFF // Code Protection bit (Block 1 (002000-003FFFh) is not
code-protected)
#pragma config CP2 = OFF // Code Protection bit (Block 2 (004000-005FFFh) is not
code-protected)
#pragma config CP3 = OFF // Code Protection bit (Block 3 (006000-007FFFh) is not
code-protected)
// CONFIG5H
#pragma config CPB = OFF // Boot Block Code Protection bit (Boot block (000000-
0007FFh) is not code-protected)
#pragma config CPD = OFF // Data EEPROM Code Protection bit (Data EEPROM is not
code-protected)
// CONFIG6L
#pragma config WRT0 = OFF // Write Protection bit (Block 0 (000800-001FFFh) is
not write-protected)
#pragma config WRT1 = OFF // Write Protection bit (Block 1 (002000-003FFFh) is
not write-protected)
#pragma config WRT2 = OFF // Write Protection bit (Block 2 (004000-005FFFh) is
not write-protected)
#pragma config WRT3 = OFF // Write Protection bit (Block 3 (006000-007FFFh) is
not write-protected)
// CONFIG6H
#pragma config WRTC = OFF // Configuration Register Write Protection bit
(Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF // Boot Block Write Protection bit (Boot block (000000-
0007FFh) is not write-protected)
#pragma config WRTD = OFF // Data EEPROM Write Protection bit (Data EEPROM is not
write-protected)
// CONFIG7L
#pragma config EBTR0 = OFF // Table Read Protection bit (Block 0 (000800-001FFFh)
is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF // Table Read Protection bit (Block 1 (002000-003FFFh)
is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF // Table Read Protection bit (Block 2 (004000-005FFFh)
is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF // Table Read Protection bit (Block 3 (006000-007FFFh)
is not protected from table reads executed in other blocks)
// CONFIG7H
#pragma config EBTRB = OFF // Boot Block Table Read Protection bit (Boot block
(000000-0007FFh) is not protected from table reads executed in other blocks)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#include "Src/Types.h"
#include "Src/Macros.h"
#include "Src/USART.h"
#include "Src/SPI_Master.h"
#include "Src/MCP2515.h"
#include "Src/SW_UART.h"
#include "Src/ISO9141_2.h"
#include "Src/ISO15765_4.h"
#include "Src/BT_Interface.h"
Anexos
186
#include "Src/TIMER0.h"
#include "Src/TIMER3.h"
volatile uint tics = 0;
void interrupt ISR() {
di();
if(INTCONbits.TMR0IF) {
tics++;
INTCONbits.TMR0IF = 0;
TMR0 = 0xA2;
}
ei();
}
uchar initProtocol(uchar protocol) {
OBD2Request testRequest;
OBD2Response testResponse;
testRequest.service = 1;
testRequest.PIDLength = 1;
testRequest.PID[0] = 0xC;
switch(protocol) {
case SAEJ1850PWM:
return 0;
break;
case SAEJ1850VPW:
return 0;
break;
case ISO9141_2:
closeSPIMaster();
if(!initISO9141()) return 0;
break;
case ISO14230_1:
return 0;
break;
case ISO15765_1_11bit_250KB:
closeUart();
initSPIMaster();
initCAN(CAN_SPEED_250KBPS);
frameType = CAN_STANDARD_FRAME;
Anexos
187
configRX0BufferMCP2515(frameType, 0x7E8, 0xFFF);
if(!sendOBD2RequestISO15765_4(testRequest, &testResponse)) return 0;
break;
case ISO15765_1_11bit_500KB:
closeUart();
initSPIMaster();
initCAN(CAN_SPEED_500KBPS);
frameType = CAN_STANDARD_FRAME;
configRX0BufferMCP2515(frameType, 0x7E8, 0xFFF);
if(!sendOBD2RequestISO15765_4(testRequest, &testResponse)) return 0;
break;
case ISO15765_1_29bit_250KB:
closeUart();
initSPIMaster();
initCAN(CAN_SPEED_250KBPS);
frameType = CAN_EXTENDED_FRAME;
configRX0BufferMCP2515(frameType, 0x7E8, 0xFFF);
if(!sendOBD2RequestISO15765_4(testRequest, &testResponse)) return 0;
break;
case ISO15765_1_29bit_500KB:
closeUart();
initSPIMaster();
initCAN(CAN_SPEED_500KBPS);
frameType = CAN_EXTENDED_FRAME;
configRX0BufferMCP2515(frameType, 0x7E8, 0xFFF);
if(!sendOBD2RequestISO15765_4(testRequest, &testResponse)) return 0;
break;
default:
return 0;
}
return 1;
}
void main(void) {
INTCON2bits.nRBPU = 1;
// Init modules
initTMR0();
initTMR3();
initSoftwareUART();
initMCP2515();
requestFrame btRequest;
responseFrame btResponse;
commandFrame command;
negativeResponseFrame errorFrame;
uchar frameReceivedId;
uchar connectedToCar = 0;
Anexos
188
uchar connectedProtocol;
OBD2Request obd2Request;
OBD2Response obd2Response;
OBD2ResponseISO9141 obd2ResponseISO9141;
uchar requestState;
while(1) {
while(1) {
// Wait for a frame from computer.
if(receiveFrame(&frameReceivedId, &btRequest, &command)) break;
}
if(frameReceivedId == requestFrameId) {
if(connectedToCar) {
obd2Request.service = btRequest.service;
obd2Request.PIDLength = btRequest.PIDLength;
for(uchar i = 0; i < btRequest.PIDLength; i++) obd2Request.PID[i] =
btRequest.PID[i];
switch(connectedProtocol) {
case SAEJ1850PWM:
break;
case SAEJ1850VPW:
break;
case ISO9141_2:
requestState = sendOBD2RequestISO9141(obd2Request, &obd2Response);
break;
case ISO14230_1:
break;
case ISO15765_1_11bit_250KB:
requestState = sendOBD2RequestISO15765_4(obd2Request, &obd2Response);
break;
case ISO15765_1_11bit_500KB:
requestState = sendOBD2RequestISO15765_4(obd2Request, &obd2Response);
break;
case ISO15765_1_29bit_250KB:
requestState = sendOBD2RequestISO15765_4(obd2Request, &obd2Response);
break;
case ISO15765_1_29bit_500KB:
Anexos
189
requestState = sendOBD2RequestISO15765_4(obd2Request, &obd2Response);
break;
}
if(requestState == 1) {
btResponse.service = obd2Response.service;
btResponse.PIDLength = obd2Response.PIDLength;
for(uchar i = 0; i < obd2Response.PIDLength; i++) btResponse.PID[i] =
obd2Response.PID[i];
btResponse.dataLen = obd2Response.dataLen;
for(uchar i = 0; i < obd2Response.dataLen; i++) btResponse.data[i] =
obd2Response.data[i];
sendResponseFrame(btResponse);
} else {
errorFrame.errorCode = 0x11; // This error code means no response from
car
sendErrorFrame(errorFrame);
}
nop();
} else {
errorFrame.errorCode = 0x12; // This error code means not connected to car
sendErrorFrame(errorFrame);
}
} else if(frameReceivedId == commandFrameId) {
switch(command.command) {
case 1:
if(initProtocol(command.params[0])) {
connectedToCar = 1;
connectedProtocol = command.params[0];
sendCommandConfirmation(command);
} else {
connectedToCar = 0;
errorFrame.errorCode = 0x18; // This error code means no response
from car
sendErrorFrame(errorFrame);
}
break;
case 2:
connectedToCar = 0; // disconnected
closeSPIMaster(); // disable SPI
closeUart(); // disable Uart
sendCommandConfirmation(command);
Anexos
190
break;
case 3:
break;
default:
errorFrame.errorCode = 0x52; // This error code means command not
supported
sendErrorFrame(errorFrame);
break;
}
} else {
errorFrame.errorCode = 0x77; // This error code means frame not identified
sendErrorFrame(errorFrame);
}
}
}
12. ANEXO 2: Código de la aplicación de escritorio
import sys
import serial
import serial.tools.list_ports
import pyperclip
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QPainter, QColor, QPen
from time import sleep
import time, threading
import struct
import math
import numpy as np
import random
import webbrowser
import xlwt
from xlwt import Workbook
import datetime
interpreterSerial = serial.Serial()
PID = []
data = []
command = []
params = []
errorCode = []
receivedFrameType = 0
requestsQueue = []
minVelocity = 255
Anexos
191
maxVelocity = 0
liveDataEnabled = False
service1Enable = 0
engineSpeed = 3500
carVelocity = 180
throttlePosition = 100
engineLoad = 0
coolerTemp = 0
absPressureAirIntake = 0
tempAirIntake = 0
MAF = 0
DTCs = []
carManufacturer = "NA"
carModel = "NA"
licensePlate = "NA"
obd2Protocol = "NA"
progressBarValues = [0,0,0,0]
plotterPbValue = 0
plotTitle1 = ''
plotTitle2 = ''
plotTitle3 = ''
plotting1 = []
plotting2 = []
plotting3 = []
plottingLen = 20
startPlotting = 0
interpreterLog = True
def clamp(n):
if n < 0:
return 0
else:
return n
def sendCommandFrame(command, params = []):
#This function sends a command frame as described on the project documentation
global interpreterLog
chksum = 0
paramsLen = len(params)
byteToSend = bytearray()
i = 0
if interpreterLog == True:
arrayLog = []
del arrayLog[:]
arrayLog.append(3)
arrayLog.append(command)
arrayLog.append(paramsLen)
arrayLog.append(params)
textLog = str(arrayLog)
formatedTextLog1 = textLog.replace("[", "")
formatedTextLog2 = formatedTextLog1.replace("]", "")
formatedTextLog3 = formatedTextLog2.replace(",", " ")
item = QListWidgetItem(formatedTextLog3)
item.setBackground(QColor(239,239,239))
Anexos
192
GUI.framesList.addItem(item)
GUI.framesList.addItem(item)
chksum = 3 + command + paramsLen
for x in params:
chksum = chksum + x
interpreterSerial.write(b'\x03')
sleep(0.01)
byteToSend.append(command)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
byteToSend.append(paramsLen)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
while i < paramsLen:
byteToSend.append(params[i])
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
i = i + 1
byteToSend.append(chksum)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
interpreterSerial.reset_output_buffer()
def sendRequestFrame(service, PID = []):
#This function sends a request frame as described on the project documentation
global interpreterLog
chksum = 0
byteToSend = bytearray()
PIDLen = len(PID)
i = 0
if interpreterLog == True:
arrayLog = []
del arrayLog[:]
arrayLog.append(2)
arrayLog.append(service)
arrayLog.append(PIDLen)
arrayLog.append(PID)
textLog = str(arrayLog)
formatedTextLog1 = textLog.replace("[", "")
formatedTextLog2 = formatedTextLog1.replace("]", "")
formatedTextLog3 = formatedTextLog2.replace(",", " ")
item = QListWidgetItem(formatedTextLog3)
item.setBackground(QColor(239,239,239))
GUI.framesList.addItem(item)
chksum = 2 + service + PIDLen
for x in PID:
chksum = chksum + x
interpreterSerial.write(b'\x02')
Anexos
193
sleep(0.01)
byteToSend.append(service)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
byteToSend.append(PIDLen)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
while i < PIDLen:
byteToSend.append(PID[i])
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
sleep(0.01)
i = i + 1
byteToSend.append(chksum)
interpreterSerial.write(bytes(byteToSend))
del byteToSend[0]
interpreterSerial.reset_output_buffer()
def receiveFrame():
#This function received a frame. This frame can be a response frame, a command
confirmation frame or an error frame
global PID
global data
global command
global params
global errorCode
global receiveFrameType
global interpreterLog
i = 0
computedChksum = 0
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
if receivedInt[0] == 1:
#in this case a response frame is received
computedChksum += 1
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
service = receivedInt[0]
computedChksum += service
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
PIDLen = receivedInt[0]
computedChksum += PIDLen
while i < PIDLen:
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
PID.append(receivedInt[0])
computedChksum += receivedInt[0]
i = i + 1
receivedByte = interpreterSerial.read()
Anexos
194
receivedInt = list(receivedByte)
dataLen = receivedInt[0]
computedChksum += dataLen
i = 0
while i < dataLen:
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
data.append(receivedInt[0])
computedChksum += receivedInt[0]
i = i + 1
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
receivedChksum = receivedInt[0]
computedChksum &= 0xFF
if computedChksum == receivedChksum:
if interpreterLog == True:
arrayLog = []
del arrayLog[:]
arrayLog.append(1)
arrayLog.append(service)
arrayLog.append(PIDLen)
arrayLog.append(PID)
arrayLog.append(dataLen)
arrayLog.append(data)
textLog = str(arrayLog)
formatedTextLog1 = textLog.replace("[", "")
formatedTextLog2 = formatedTextLog1.replace("]", "")
formatedTextLog3 = formatedTextLog2.replace(",", " ")
item = QListWidgetItem(formatedTextLog3)
item.setBackground(QColor(232,232,232))
GUI.framesList.addItem(item)
receiveFrameType = 1
return 1
else:
return 0
elif receivedInt[0] == 4:
#in this case an error frame is received
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
errorCode = receivedInt[0]
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
receivedChksum = receivedInt[0]
computedChksum = 4 + errorCode
computedChksum &= 0xFF
if computedChksum == receivedChksum:
receiveFrameType = 4
Anexos
195
if interpreterLog == True:
arrayLog = []
del arrayLog[:]
arrayLog.append(4)
arrayLog.append(errorCode)
textLog = str(arrayLog)
formatedTextLog1 = textLog.replace("[", "")
formatedTextLog2 = formatedTextLog1.replace("]", "")
formatedTextLog3 = formatedTextLog2.replace(",", " ")
item = QListWidgetItem(formatedTextLog3)
item.setBackground(QColor(232,232,232))
GUI.framesList.addItem(item)
return 0
else:
return 0
elif receivedInt[0] == 5:
computedChksum += 5
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
command = receivedInt[0]
computedChksum += command
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
paramsLen = receivedInt[0]
computedChksum += paramsLen
while i < paramsLen:
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
params.append(receivedInt[0])
computedChksum += receivedInt[0]
i = i + 1
receivedByte = interpreterSerial.read()
receivedInt = list(receivedByte)
receivedChksum = receivedInt[0]
computedChksum &= 0xFF
if computedChksum == receivedChksum:
receiveFrameType = 5
if interpreterLog == True:
arrayLog = []
del arrayLog[:]
arrayLog.append(5)
arrayLog.append(command)
arrayLog.append(paramsLen)
arrayLog.append(params)
textLog = str(arrayLog)
formatedTextLog1 = textLog.replace("[", "")
formatedTextLog2 = formatedTextLog1.replace("]", "")
formatedTextLog3 = formatedTextLog2.replace(",", " ")
item = QListWidgetItem(formatedTextLog3)
Anexos
196
item.setBackground(QColor(232,232,232))
GUI.framesList.addItem(item)
interpreterSerial.reset_input_buffer()
return 1
else:
interpreterSerial.reset_input_buffer()
return 0
else:
return 0
class mainWindowObj(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("firstgui.ui", self)
#Set callback functions of buttons
self.actionOpenConnection.triggered.connect(openConnectionDialogObj.show)
self.actionCloseConnection.triggered.connect(self.disconnectFromInterpreter)
self.actionEditProfile.triggered.connect(editProfileDialogObj.show)
self.readVehicleInfo.clicked.connect(self.getVehicleInformation)
self.copyToClipboard.clicked.connect(self.copyVehicleInformationToClipboard)
self.searchDTCButton.clicked.connect(self.searchOnBrowser)
self.clearDTCButton.clicked.connect(self.clearDTCs)
self.actionAboutXpressCarScanner.triggered.connect(aboutUsDialogObj.show)
self.clearLogButton.clicked.connect(self.clearLog)
self.pauseStartLogButton.clicked.connect(self.pauseStartLog)
self.showPlottingToolInfoButton.clicked.connect(self.showPlottingToolInfo)
self.showInterpreterLogInfoButton.clicked.connect(self.showInterpreterLogInfo)
self.readDTCsButton.clicked.connect(self.getDTCs)
self.powertrainList.itemActivated.connect(self.powertrainListClicked)
self.bodySystemsList.itemActivated.connect(self.bodySystemsListClicked)
self.chassisSystemsList.itemActivated.connect(self.chassisSystemsListClicked)
self.networkList.itemActivated.connect(self.networkListClicked)
self.exportFFButton.clicked.connect(self.exportFreezeFrame)
self.readStopLiveDataButton.clicked.connect(self.enDisLiveData)
self.cleanLiveDataButton.clicked.connect(self.cleanLiveData)
self.decodeDTCButton.clicked.connect(self.decodeDTC)
self.exportDTCsButton.clicked.connect(self.exportDTCs)
self.startPlotting.clicked.connect(self.startp)
self.savePlottingButton.clicked.connect(self.savePlotting)
self.cleanPlotterButton.clicked.connect(self.cleanPlotterData)
self.readFFButton.clicked.connect(self.getFreezeFrame)
self.showFFInfoButton.clicked.connect(self.showFFInfo)
self.plottingThObj.plottingEnded.connect(self.plottingEndedFunc)
self.infoSearchDTC.clicked.connect(self.showSearchDTCInfo)
self.infoInterpreterSettings.clicked.connect(self.showInterpreterSettingsInfo)
self.infoVehicleInfo.clicked.connect(self.showVehicleInfoInfo)
self.infoTroubleCodes.clicked.connect(self.showTroubleCodesInfo)
self.infoLiveDataMeter.clicked.connect(self.showLiveDataMeterInfo)
#define initial state of some items
self.mainWidget.setEnabled(False)
self.currentDataTab.setEnabled(False)
self.storedDTCTab.setEnabled(False)
self.freezeFramesTab.setEnabled(False)
self.vehicleInfoTab.setEnabled(False)
self.interpreterSettingsTab.setEnabled(False)
Anexos
197
self.plottingTab.setEnabled(False)
self.logTab.setEnabled(False)
#Start app threads
self.updatePbObj = progressBarTh()
self.updatePbObj.start()
self.updatePbObj.mySignal.connect(self.updatePb)
self.plottingThObj = plottingThClass()
self.plottingThObj.start()
self.liveDataMeterThObj = liveDataMeterTh()
self.liveDataMeterThObj.start()
def getFreezeFrame(self):
getDTCFreezeFrame()
getEngineSpeedFreezeFrame()
getEngineLoadFreezeFrame()
getCoolerTempFreezeFrame()
getTempAirIntakeFreezeFrame()
getMAFFreezeFrame()
getThrottlePositionFreezeFrame()
getAbsPressureAirIntakeFreezeFrame()
def exportDTCs(self):
#This function saves DTCs in a txt file
global licensePlate
global carManufacturer
global carModel
if licensePlate == 'NA':
fileName = "Unknown Car - DTCs.txt"
else:
fileName = licensePlate + " - DTCs.txt"
textFile = open(fileName, "w+")
x = datetime.datetime.now()
textFile.write(x.strftime("%c") + "\n\n")
textFile.write("FILE GENERATED WITH XPRESS CAR SCANNER \n\n")
textFile.write("________________________________________" + "\n\n")
textFile.write("MANUFACTURER: " + carManufacturer + "\n")
textFile.write("CAR MODEL: " + carModel + "\n")
textFile.write("________________________________________" + "\n\n")
textFile.write("POWERTRAIN DTCs:\n")
for i in range(self.powertrainList.count()):
textFile.write(str(self.powertrainList.item(i).text()) + "\n")
textFile.write("\nBODYSYSTEMS DTCs:\n")
for i in range(self.bodySystemsList.count()):
textFile.write(str(self.bodySystemsList.item(i).text()) + "\n")
textFile.write("\nCHASSIS SYSTEMS DTCs:\n")
for i in range(self.chassisSystemsList.count()):
textFile.write(str(self.chassisSystemsList.item(i).text()) + "\n")
textFile.write("\nNETWORK DTCs:\n")
for i in range(self.networkList.count()):
textFile.write(str(self.networkList.item(i).text()) + "\n")
Anexos
198
textFile.close()
notificationObj.setText("File created succesfully")
notificationObj.exec_()
def exportFreezeFrame(self):
#This function exports Freeze Fram in a txt file
global licensePlate
global carManufacturer
global carModel
DTC = self.DTCFF.text()
throttlePosition = self.throttlePositionFF.text()
engineSpeed = self.engineSpeedFF.text()
engineLoad = self.engineLoadFF.text()
intakeManifoldPressure = self.intakeManifoldPressureFF.text()
MAF = self.MAFFF.text()
coolerTemp = self.coolantTempFF.text()
intakeTemp = self.intakeTempFF.text()
if licensePlate == 'NA':
fileName = "Unknown Car - DTCs.txt"
else:
fileName = licensePlate + " - DTCs.txt"
textFile = open(fileName, "w+")
x = datetime.datetime.now()
textFile.write(x.strftime("%c") + "\n\n")
textFile.write("FILE GENERATED WITH XPRESS CAR SCANNER \n\n")
textFile.write("________________________________________" + "\n\n")
textFile.write("MANUFACTURER: " + carManufacturer + "\n")
textFile.write("CAR MODEL: " + carModel + "\n")
textFile.write("________________________________________" + "\n\n")
textFile.write("Freeze Frame for DTC: " + DTC + '\n\n')
textFile.write("Throttle Position: " + throttlePosition + ' %\n')
textFile.write("Engine Speed: " + engineSpeed + ' rpm\n')
textFile.write("Engine Load: " + engineLoad + ' %\n')
textFile.write("Intake Manifold Temperature: " + intakeTemp + ' ° C\n')
textFile.write("MAF(Mass Air Flow): " + MAF + ' g/s\n')
textFile.write("Coolant Temperature: " + coolerTemp + ' ° C\n')
textFile.write("Intake Manifold Pressure: " + intakeManifoldPressure + '
kPa\n')
textFile.close()
notificationObj.setText("File created succesfully")
notificationObj.exec_()
def powertrainListClicked(self, item):
pyperclip.copy(item.text())
def bodySystemsListClicked(self, item):
pyperclip.copy(item.text())
def chassisSystemsListClicked(self, item):
pyperclip.copy(item.text())
def networkListClicked(self, item):
Anexos
199
pyperclip.copy(item.text())
def getDTCs(self):
#This function gets, decodes and shows DTCs of the vehicle.
global DTCs
oneDTC = []
getDTC()
i = 0
self.powertrainList.clear()
self.bodySystemsList.clear()
self.chassisSystemsList.clear()
self.networkList.clear()
unifiedDTCs = []
if len(DTCs) > 0:
nDTCs = int(len(DTCs)/2)
while i < nDTCs:
unifiedDTCs.append(256*DTCs[i*2] + DTCs[i*2 + 1])
i = i + 1
for x in unifiedDTCs:
del oneDTC[:]
if ((x & 0xC000) >> 14) == 0:
oneDTC.append('P')
elif ((x & 0xC000) >> 14) == 1:
ondDTC.append('C')
elif ((x & 0xC000) >> 14) == 2:
ondDTC.append('B')
elif ((x & 0xC000) >> 14) == 3:
ondDTC.append('U')
oneDTC.append(hex((x & 0x3000) >> 12)[2:])
oneDTC.append(hex((x & 0x0F00) >> 8)[2:])
oneDTC.append(hex((x & 0x00F0) >> 4)[2:])
oneDTC.append(hex(x & 0x000F)[2:])
item = QListWidgetItem(''.join(oneDTC))
if oneDTC[0] == 'P':
self.powertrainList.addItem(item)
elif oneDTC[0] == 'C':
self.bodySystemsList.addItem(item)
elif oneDTC[0] == 'B':
self.chassisSystemsList.addItem(item)
elif oneDTC[0] == 'U':
self.networkList.addItem(item)
notificationObj.setText(str(nDTCs) + " DTCs Found. Check the DTC
Searcher to know more about this codes.")
notificationObj.exec_()
else:
notificationObj.setText("0 DTCs found!")
notificationObj.exec_()
def pauseStartLog(self):
global interpreterLog
if interpreterLog == True:
interpreterLog = False
Anexos
200
self.pauseStartLogButton.setText("Start")
else:
interpreterLog = True
self.pauseStartLogButton.setText("Pause")
def clearLog(self):
self.framesList.clear()
def cleanPlotterData(self):
self.savePlottingButton.setEnabled(False)
def plottingEndedFunc(self):
global plotterPbValue
plotterPbValue = 0
self.savePlottingButton.setEnabled(True)
notificationObj.setText("Data obtained succesfully! Now you can save it in
an excel file.")
notificationObj.exec_()
def savePlotting(self):
#this function saves obtained data in a excel file.
global licensePlate
global plottingLen
global plotting1
global plotTitle1
global plotting2
global plotTitle2
global plotting3
global plotTitle3
global carManufacturer
global carModel
wb = Workbook()
sheet1 = wb.add_sheet('Sheet 1')
sheet1.write(3, 1, 'Data obtained with XPRESS CAR SCANNER')
sheet1.write(5, 1, 'Manufacturer:')
sheet1.write(5, 2, carManufacturer)
sheet1.write(6, 1, 'Model:')
sheet1.write(6, 2, carModel)
sheet1.write(7, 1, 'License Plate:')
sheet1.write(7, 2, licensePlate)
sheet1.write(10, 1, 'Time(s)')
sheet1.write(10, 2, plotTitle1)
sheet1.write(10, 3, plotTitle2)
sheet1.write(10, 4, plotTitle3)
x = datetime.datetime.now()
sheet1.write(1, 4, x.strftime("%c"))
i = 0
while i < plottingLen:
sheet1.write(11 + i, 1, i*0.5)
i = i + 1
i = 0
while i < plottingLen:
sheet1.write(11 + i, 2, plotting1[i])
i = i + 1
i = 0
Anexos
201
while i < plottingLen:
sheet1.write(11 + i, 3, plotting2[i])
i = i + 1
i = 0
while i < plottingLen:
sheet1.write(11 + i, 4, plotting3[i])
i = i + 1
if licensePlate == '':
wb.save('Unknown Car - sampled PIDs.xls')
else:
wb.save(licensePlate+' - sampled PIDs.xls')
notificationObj.setText("Excel file created succesfully! Now you can use
Excel to make a graph from the data.")
notificationObj.exec_()
def copyDTC(self):
pyperclip.copy(self.powertrainList.selectedItems())
def showFFInfo(self):
notificationObj.setText("In this tab you can get the freeze frame of the
last registered DTC.")
notificationObj.exec_()
def showInterpreterLogInfo(self):
notificationObj.setText("In this tab you can see all the frames that have
been exchanged between PC and obd2 interpreter.")
notificationObj.exec_()
def showSearchDTCInfo(self):
notificationObj.setText("This tab contains tools to extract information
from DTC codes.")
notificationObj.exec_()
def showInterpreterSettingsInfo(self):
notificationObj.setText("This tab allows you to change some parameters from
the obd2 interpreter.")
notificationObj.exec_()
def showVehicleInfoInfo(self):
notificationObj.setText("From this tab you can obtain some information
about the car.")
notificationObj.exec_()
def showPlottingToolInfo(self):
notificationObj.setText("From this tab you can sample up to 3 different
parameters. The data obtained can be used with excel to make a graphical
representation.")
notificationObj.exec_()
def showTroubleCodesInfo(self):
notificationObj.setText("From this tab you can extract stored DTCs in the
car's ECU.")
notificationObj.exec_()
def showLiveDataMeterInfo(self):
notificationObj.setText("From this tab you can obtain some real time data
about the car.")
notificationObj.exec_()
def enDisLiveData(self):
Anexos
202
global liveDataEnabled
if liveDataEnabled == True:
liveDataEnabled = False
self.readStopLiveDataButton.setText("Read")
self.cleanLiveDataButton.setEnabled(True)
else:
liveDataEnabled = True
self.readStopLiveDataButton.setText("Stop")
self.cleanLiveDataButton.setEnabled(False)
def cleanLiveData(self):
self.label1.setText('0,00');
self.label2.setText('0,00');
self.label3.setText('0,00');
self.label4.setText('0,00');
self.label5.setText('')
self.label6.setText('')
self.label7.setText('')
self.label8.setText('')
self.label1.setStyleSheet("color: black")
self.label5.setStyleSheet("color: black")
self.label2.setStyleSheet("color: black")
self.label6.setStyleSheet("color: black")
self.label3.setStyleSheet("color: black")
self.label7.setStyleSheet("color: black")
self.label4.setStyleSheet("color: black")
self.label8.setStyleSheet("color: black")
progressBarValues[0] = 0
progressBarValues[1] = 0
progressBarValues[2] = 0
progressBarValues[3] = 0
def getVehicleInformation(self):
self.lpLabel.setText(licensePlate)
self.comProtocolLabel.setText(obd2Protocol)
getVIN()
getOBDStandard()
def disconnectFromInterpreter(self):
self.mainWidget.setEnabled(False)
self.currentDataTab.setEnabled(False)
self.storedDTCTab.setEnabled(False)
self.freezeFramesTab.setEnabled(False)
self.vehicleInfoTab.setEnabled(False)
self.interpreterSettingsTab.setEnabled(False)
self.plottingTab.setEnabled(False)
self.logTab.setEnabled(False)
self.actionOpenConnection.setEnabled(True)
self.actionCloseConnection.setEnabled(False)
self.statusLabel.setStyleSheet("background-color: red;");
self.protocolLabel.setText("NA");
sendCommandFrame(2, [])
interpreterSerial.close()
def updateProgressBars(self, values, ranges):
Anexos
203
self.pb1.setRange(0, ranges[0])
self.pb2.setRange(0, ranges[1])
self.pb3.setRange(0, ranges[2])
self.pb4.setRange(0, ranges[3])
self.pb1.setValue(values[0])
self.pb2.setValue(values[1])
self.pb3.setValue(values[2])
self.pb4.setValue(values[3])
def copyVehicleInformationToClipboard(self):
lp = self.lpLabel.text()
comProtocol = self.comProtocolLabel.text()
carVin = self.vinLabel.text()
obdStandard = self.obdLabel.text()
pyperclip.copy("License plate: " + lp + "\n" + "Comunication protocol: " +
comProtocol + '\n' + 'OBD Standard: ' + obdStandard + '\n' + 'VIN(Vehicle
Identification number): ' + carVin + '\n')
def clearDTCs(self):
clearDTCsRequest()
self.getDTCs()
def searchOnBrowser(self):
DTCCodes = []
if self.search1.text() != '':
DTCCodes.append(self.search1.text())
if self.search2.text() != '':
DTCCodes.append(self.search2.text())
if self.search3.text() != '':
DTCCodes.append(self.search3.text())
if len(DTCCodes) > 0:
if len(DTCCodes) == 1:
webbrowser.open('http://www.dtcsearch.com/' + DTCCodes[0] +
'/', new=2)
if len(DTCCodes) == 2:
webbrowser.open('http://www.dtcsearch.com/' + DTCCodes[0] + '+'
+ DTCCodes[1] + '/', new=2)
if len(DTCCodes) == 3:
webbrowser.open('http://www.dtcsearch.com/' + DTCCodes[0] + '+'
+ DTCCodes[1] + '+' + DTCCodes[2] + '/', new=2)
def decodeDTC(self):
#This function decodes DTCs
firstChar = self.dtc1.text()
secondChar = self.dtc2.text()
thirdChar = self.dtc3.text()
fourthChar = self.dtc4.text() + self.dtc5.text()
if firstChar == 'B':
domain = "Body"
elif firstChar == 'C':
domain = "Chassis"
elif firstChar == 'P':
domain = "Powertrain"
elif firstChar == 'U':
Anexos
204
domain = "Network"
else:
domain = "Unknown"
if secondChar == '0':
property = "Generic"
elif firstChar == '1':
property = "Specific"
else:
property = "Unknown"
if thirdChar == '1':
faultLocation = "Fuel & Air Metering"
elif thirdChar == '2':
faultLocation = "Injectors"
elif thirdChar == '3':
faultLocation = "Ignition System"
elif thirdChar == '4':
faultLocation = "Auxiliary Emission Control"
elif thirdChar == '5':
faultLocation = "Vehicle Speed & Idle"
elif thirdChar == '6':
faultLocation = "Computer Output Circuit"
elif thirdChar == '7':
faultLocation = "Transmission"
elif thirdChar == '8':
faultLocation = "Transmission"
else:
faultLocation = "Unknown"
self.dtcLab1.setText(domain)
self.dtcLab2.setText(property)
self.dtcLab3.setText(faultLocation)
def updatePb(self):
global progressBarValues
global plotterPbValue
self.progressBar1.setValue(progressBarValues[0])
self.progressBar2.setValue(progressBarValues[1])
self.progressBar3.setValue(progressBarValues[2])
self.progressBar4.setValue(progressBarValues[3])
self.plotterPb.setValue(plotterPbValue)
def startp(self):
global startPlotting
startPlotting = 1
def __del__(self):
if interpreterSerial.is_open:
interpreterSerial.close()
class progressBarTh(QThread):
#This thread sends a signal to another function that updates the progress Bars
values
mySignal = pyqtSignal()
def __init__(self):
QThread.__init__(self)
Anexos
205
def __del__(self):
self.wait()
def run(self):
while 1:
self.mySignal.emit()
sleep(0.2)
class liveDataMeterTh(QThread):
#This thread gets data desired from the vehicle when mode 1 is active
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
global liveDataEnabled
global progressBarValues
global carVelocity
global throttlePosition
global engineSpeed
global engineLoad
global coolerTemp
global absPressureAirIntake
global tempAirIntake
global MAF
while 1:
if liveDataEnabled == True:
pidSelections = []
queryResults = []
units = []
color = []
max = []
pidSelections.append(GUI.comboBox1.currentIndex())
pidSelections.append(GUI.comboBox2.currentIndex())
pidSelections.append(GUI.comboBox3.currentIndex())
pidSelections.append(GUI.comboBox4.currentIndex())
i = 0
while i < 4:
if pidSelections[i] == 0:
queryResults.append(0.00)
units.append('')
color.append("black")
max.append(100)
elif pidSelections[i] == 1:
sleep(0.05)
getVelocity()
queryResults.append(carVelocity)
units.append('m/s')
color.append("red")
max.append(300)
elif pidSelections[i] == 2:
sleep(0.05)
getEngineSpeed()
queryResults.append(engineSpeed)
Anexos
206
units.append('rpm')
color.append("green")
max.append(16000)
elif pidSelections[i] == 3:
sleep(0.05)
getThrottlePosition()
queryResults.append(throttlePosition)
units.append('%')
color.append("yellow")
max.append(100)
elif pidSelections[i] == 4:
sleep(0.05)
getEngineLoad()
queryResults.append(engineLoad)
units.append('%')
color.append("yellow")
max.append(100)
elif pidSelections[i] == 5:
sleep(0.05)
getCoolerTemp()
queryResults.append(coolerTemp)
units.append('°C')
color.append("yellow")
max.append(215)
elif pidSelections[i] == 6:
sleep(0.05)
getAbsPressureAirIntake()
queryResults.append(absPressureAirIntake)
units.append('kPa')
color.append("yellow")
max.append(255)
elif pidSelections[i] == 7:
sleep(0.05)
getTempAirIntake()
queryResults.append(tempAirIntake)
units.append('°C')
color.append("yellow")
max.append(215)
elif pidSelections[i] == 8:
sleep(0.05)
getMAF()
queryResults.append(MAF)
units.append('g/s')
color.append("yellow")
max.append(655)
i = i + 1
GUI.label1.setText(str(queryResults[0]));
GUI.label2.setText(str(queryResults[1]));
GUI.label3.setText(str(queryResults[2]));
GUI.label4.setText(str(queryResults[3]));
progressBarValues[0] = clamp((queryResults[0]/max[0])*100)
progressBarValues[1] = clamp((queryResults[1]/max[1])*100)
progressBarValues[2] = clamp((queryResults[2]/max[2])*100)
progressBarValues[3] = clamp((queryResults[3]/max[3])*100)
GUI.label5.setText(units[0])
GUI.label6.setText(units[1])
GUI.label7.setText(units[2])
GUI.label8.setText(units[3])
GUI.label1.setStyleSheet("color: " + color[0])
GUI.label5.setStyleSheet("color: " + color[0])
Anexos
207
GUI.label2.setStyleSheet("color: " + color[1])
GUI.label6.setStyleSheet("color: " + color[1])
GUI.label3.setStyleSheet("color: " + color[2])
GUI.label7.setStyleSheet("color: " + color[2])
GUI.label4.setStyleSheet("color: " + color[3])
GUI.label8.setStyleSheet("color: " + color[3])
sleep(0.2)
class plottingThClass(QThread):
#The job of this thread is to sample desired data periodically during n seconds.
plottingEnded = pyqtSignal()
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
global carVelocity
global throttlePosition
global engineSpeed
global engineLoad
global coolerTemp
global absPressureAirIntake
global tempAirIntake
global MAF
global startPlotting
global plotting1
global plotting2
global plotting3
global plottingLen
global plotTitle1
global plotTitle2
global plotTitle3
global plotterPbValue
while 1:
if startPlotting == 1:
i = 0
j = 0
del plotting1[:]
del plotting2[:]
del plotting3[:]
params = []
del params[:]
params.append(GUI.paramSel1.currentIndex())
params.append(GUI.paramSel2.currentIndex())
params.append(GUI.paramSel3.currentIndex())
plotTitle1 = GUI.paramSel1.currentText()
plotTitle2 = GUI.paramSel2.currentText()
plotTitle3 = GUI.paramSel3.currentText()
plottingLen = int(GUI.plottingTime.currentText())
Anexos
208
while i < plottingLen:
plotterPbValue = ((i+1)/plottingLen)*100
j = 0
while j < 3:
sleep(0.05)
if params[j] == 0:
receivedData = 0
elif params[j] == 1:
getVelocity()
receivedData = carVelocity
elif params[j] == 2:
getEngineSpeed()
receivedData = engineSpeed
elif params[j] == 3:
getThrottlePosition()
receivedData = throttlePosition
elif params[j] == 4:
getEngineLoad()
receivedData = engineLoad
elif params[j] == 5:
getCoolerTemp()
receivedData = coolerTemp
elif params[j] == 6:
getAbsPressureAirIntake()
receivedData = absPressureAirIntake
elif params[j] == 7:
getTempAirIntake()
receivedData = tempAirIntake
elif params[j] == 8:
getMAF()
receivedData = MAF
if j == 0:
plotting1.append(receivedData)
elif j == 1:
plotting2.append(receivedData)
elif j == 2:
plotting3.append(receivedData)
j = j + 1
i = i + 1
startPlotting = 0
self.plottingEnded.emit()
class openConnectionDialogClass(QDialog):
def __init__(self):
super().__init__()
uic.loadUi("connectWindow.ui", self)
self.refreshPorts.clicked.connect(self.refreshPortsList)
self.connectButton.clicked.connect(self.connectToInterpreter)
self.cancelButton.clicked.connect(self.close)
def closeDialog(self):
self.close()
Anexos
209
def refreshPortsList(self):
comlist = serial.tools.list_ports.comports()
connected = []
for element in comlist:
connected.append(element.device)
self.portComboBox.clear()
self.portComboBox.addItems(connected)
def connectToInterpreter(self):
#this function sends a request to the OBD2 scaner to connect to a vehicle
with a specific protocol.
global carManufacturer
global carModel
global licensePlate
global obd2Protocol
protocolSelection = self.protocolComboBox.currentText()
if protocolSelection == 'ISO 9141-2':
protocolSelectionIndex = 0
elif protocolSelection == 'ISO 15765-4 CAN (11 bit, 500kb)':
protocolSelectionIndex = 5
elif protocolSelection == 'ISO 15765-4 CAN (29 bit, 500kb)':
protocolSelectionIndex = 7
elif protocolSelection == 'ISO 15765-4 CAN (11 bit, 250kb)':
protocolSelectionIndex = 4
elif protocolSelection == 'ISO 15765-4 CAN (29 bit, 250kb)':
protocolSelectionIndex = 6
interpreterCOMPort = self.portComboBox.currentText()
if interpreterCOMPort == "":
notificationObj.setText("Select a COM port of obd2 interpreter in
order to establish communication.")
notificationObj.exec_()
return
interpreterSerial.baudrate = 9600
interpreterSerial.port = interpreterCOMPort
interpreterSerial.timeout = None
interpreterSerial.open()
if interpreterSerial.is_open == False:
notificationObj.setText("XPRESS CAR SCANNER has not been able to open
desired port. It might be already opened.")
notificationObj.exec_()
return
params = []
params.append(protocolSelectionIndex)
sendCommandFrame(1, params)
if receiveFrame() == 1:
#if connected enable all the functionalities of the app
GUI.actionOpenConnection.setEnabled(False)
GUI.actionCloseConnection.setEnabled(True)
GUI.mainWidget.setEnabled(True)
GUI.currentDataTab.setEnabled(True)
GUI.storedDTCTab.setEnabled(True)
GUI.freezeFramesTab.setEnabled(True)
GUI.vehicleInfoTab.setEnabled(True)
GUI.interpreterSettingsTab.setEnabled(True)
GUI.plottingTab.setEnabled(True)
Anexos
210
GUI.logTab.setEnabled(True)
GUI.statusLabel.setStyleSheet("background-color: green;");
GUI.protocolLabel.setText(self.protocolComboBox.currentText());
carManufacturer = self.manufacturerInput.text()
carModel = self.modelInput.text()
licensePlate = self.licensePlateInput.text()
obd2Protocol = protocolSelection
self.close()
else:
self.close()
notificationObj.setText("Car does not respond, check selected
protocol and make sure key is in ignition position.")
notificationObj.exec_()
interpreterSerial.close()
class editProfileDialogClass(QDialog):
def __init__(self):
super().__init__()
uic.loadUi("editProfile.ui", self)
self.updateButton.clicked.connect(self.updateProfile)
self.cancelButton.clicked.connect(self.closeDialog)
def updateProfile(self):
global carManufacturer
global carModel
global licensePlate
carManufacturer = self.manufacturerInput.text()
carModel = self.modelInput.text()
licensePlate = self.licensePlateInput.text()
self.close()
def closeDialog(self):
self.close()
class aboutUsDialogClass(QDialog):
def __init__(self):
super().__init__()
uic.loadUi("aboutUs.ui", self)
self.okButton.clicked.connect(self.closeDialog)
def closeDialog(self):
self.close()
def getVIN():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(9, [2])
if receiveFrame() == 1:
if receiveFrameType == 1:
while 1:
if data[0] == 0:
del data[0]
else:
Anexos
211
break;
GUI.vinLabel.setText(''.join(chr(x) for x in data))
dataLen = len(data)
del data[:]
del PID[:]
else:
print(receiveFrameType)
del data[:]
del PID[:]
def getVelocity():
global data
global receiveFrameType
global carVelocity
global PID
i = 0
sendRequestFrame(1, [0xD])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
carVelocity = data[0]
del data[:]
del PID[:]
def getEngineSpeed():
global data
global receiveFrameType
global engineSpeed
global PID
i = 0
sendRequestFrame(1, [0xC])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 2:
engineSpeed = round((data[0]*256 + data[1])/4, 2)
del data[:]
del PID[:]
def getThrottlePosition():
global data
global receiveFrameType
global throttlePosition
global PID
i = 0
sendRequestFrame(1, [0x11])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
Anexos
212
throttlePosition = round((data[0]*100)/255, 2)
del data[:]
del PID[:]
def getEngineLoad():
global data
global receiveFrameType
global engineLoad
global PID
i = 0
sendRequestFrame(1, [0x4])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
engineLoad = round(data[0]/2.55, 2)
del data[:]
del PID[:]
def getCoolerTemp():
global data
global receiveFrameType
global coolerTemp
global PID
i = 0
sendRequestFrame(1, [0x5])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
coolerTemp = data[0] - 40
del data[:]
del PID[:]
def getAbsPressureAirIntake():
global data
global receiveFrameType
global absPressureAirIntake
global PID
i = 0
sendRequestFrame(1, [0xB])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
absPressureAirIntake = data[0]
del data[:]
del PID[:]
def getTempAirIntake():
global data
global receiveFrameType
Anexos
213
global tempAirIntake
global PID
i = 0
sendRequestFrame(1, [0xF])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
tempAirIntake = data[0] - 40
del data[:]
del PID[:]
def getMAF():
global data
global receiveFrameType
global MAF
global PID
i = 0
sendRequestFrame(1, [0x10])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 2:
MAF = round((256*data[0] + data[1])/100, 2)
del data[:]
del PID[:]
def getOBDStandard():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(1, [0x1C])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
#data obtained is decoded to obtain the obd standard of the
vehicle
if data[0] == 0x01:
obdStandard = "OBD-II as defined by the CARB"
elif data[0] == 0x02:
obdStandard = "OBD as defined by the EPA"
elif data[0] == 0x03:
obdStandard = "OBD and OBD-II"
elif data[0] == 0x04:
obdStandard = "OBD-I"
elif data[0] == 0x05:
obdStandard = "Not meant to comply with any OBD standard"
elif data[0] == 0x06:
obdStandard = "EOBD (Europe)"
elif data[0] == 0x07:
obdStandard = "EOBD and OBD-II"
elif data[0] == 0x08:
obdStandard = "EOBD and OBD"
elif data[0] == 0x09:
obdStandard = "EOBD, OBD and OBD II"
Anexos
214
elif data[0] == 0x0A:
obdStandard = "JOBD (Japan)"
elif data[0] == 0x0B:
obdStandard = "JOBD and OBD II"
elif data[0] == 0x0C:
obdStandard = "JOBD and EOBD"
elif data[0] == 0x0D:
obdStandard = "JOBD, EOBD, and OBD II"
GUI.obdLabel.setText(obdStandard)
del data[:]
del PID[:]
def getDTC():
global data
global receiveFrameType
global DTCs
global PID
found = False
del DTCs[:]
i = 0
sendRequestFrame(3, [])
if receiveFrame() == 1:
if receiveFrameType == 1:
for x in data:
DTCs.append(x)
while found == False:
if DTCs[len(DTCs) - 1] == 0:
DTCs.pop()
else:
found = True
del data[:]
del PID[:]
def clearDTCsRequest():
global data
global PID
sendRequestFrame(4, [])
receiveFrame()
del data[:]
del PID[:]
def getDTCFreezeFrame():
global data
global receiveFrameType
global PID
dtcStr = []
i = 0
sendRequestFrame(2, [0x2])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 2:
Anexos
215
#DTC is decoded and stored
dtcInt = 256*data[0] + data[0]
if ((dtcInt & 0xC000) >> 14) == 0:
dtcStr.append('P')
elif ((dtcInt & 0xC000) >> 14) == 1:
dtcStr.append('C')
elif ((dtcInt & 0xC000) >> 14) == 2:
dtcStr.append('B')
elif ((dtcInt & 0xC000) >> 14) == 3:
dtcStr.append('U')
dtcStr.append(hex((dtcInt & 0x3000) >> 12)[2:])
dtcStr.append(hex((dtcInt & 0x0F00) >> 8)[2:])
dtcStr.append(hex((dtcInt & 0x00F0) >> 4)[2:])
dtcStr.append(hex(dtcInt & 0x000F)[2:])
GUI.DTCFF.setText(''.join(dtcStr))
del data[:]
del PID[:]
def getEngineSpeedFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0xC])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 2:
GUI.engineSpeedFF.setText(str(int((data[0]*256 + data[1])/4)))
del data[:]
del PID[:]
def getEngineLoadFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0x4])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
GUI.engineLoadFF.setText(str(int(data[0]/2.55)))
del data[:]
del PID[:]
def getCoolerTempFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0x5])
Anexos
216
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
GUI.coolantTempFF.setText(str(data[0] - 40))
del data[:]
del PID[:]
def getTempAirIntakeFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0xF])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
GUI.intakeTempFF.setText(str(data[0] - 40))
del data[:]
del PID[:]
def getMAFFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0x10])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 2:
GUI.MAFFF.setText(str(int((256*data[0] + data[1])/100)))
del data[:]
del PID[:]
def getThrottlePositionFreezeFrame():
global data
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0x11])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
GUI.throttlePositionFF.setText(str(round((data[0]*100)/255,
2)))
del data[:]
del PID[:]
def getAbsPressureAirIntakeFreezeFrame():
global data
Anexos
217
global receiveFrameType
global PID
i = 0
sendRequestFrame(2, [0xB])
if receiveFrame() == 1:
if receiveFrameType == 1:
if len(data) == 1:
GUI.intakeManifoldPressureFF.setText(str(data[0]))
del data[:]
del PID[:]
#Objects are created and main window is shown
app = QApplication(sys.argv)
notificationObj = QMessageBox()
editProfileDialogObj = editProfileDialogClass()
openConnectionDialogObj = openConnectionDialogClass()
aboutUsDialogObj = aboutUsDialogClass()
GUI = mainWindowObj()
GUI.show()
sys.exit(app.exec_())