Primeros Pasos Pic 2018

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 192

PRIMEROS PASOS CON

MICROCONTROLADORES PIC®
Y MPLAB X.

Ing. Ariel Coria

1
Coria, Ariel Jacinto
Primeros pasos con microcontroladores PIC® Y MPLAB X / Ariel Jacinto Coria;
editado por Alejandro Anibal Airoldi. - 1a ed . - Ciudad Autónoma de Buenos Aires
mcelectronics, 2018.

192 p. ; 23 x 16 cm.

ISBN 978-987-3702-09-9

1. Ingeniería Electrónica. I. Airoldi, Alejandro Anibal, ed. II. Título.


CDD 621.3

Fecha de catalogación: Julio de 2018

© mcelectronics

Hecho el depósito que marca la ley 11.723


Todos los derechos reservados.

Ninguna parte de este libro, incluido el diseño de la portada, puede reproducirse,


almacenarse o transmitirse de ninguna forma ni por ningún medio, sea este eléctrico,
químico, mecánico, óptico, de grabación o de fotocopia, sin la previa autorización escrita
por parte de mcelectronics. La infracción de los derechos mencionados puede ser
constitutiva de delito contra la propiedad intelectual.

La editorial no se pronuncia ni expresa implícitamente respecto a la exactitud de la


información contenida en este libro, razón por la cual no puede asumir ningún tipo de
responsabilidad en caso de error u omisión.

Las marcas mencionadas son propiedad exclusiva de sus registradores legales.

Diseño de Portada:
54Designers.com - Comunicación + Innovación

Seguinos en las redes sociales /mcelectronics


(C) 2018

2
A nuestras familias y amigos por el apoyo incondicional.

3
4
SUMARIO
MPLAB X IDE Y HERRAMIENTAS DE DESARROLLO 7
Instalando las herramientas necesarias para programar.
MPLAB X, XC8 y Code Configurator

CREAR UN NUEVO PROYECTO EN MPLAB X 34


En esta práctica se verán los pasos para crear un nuevo
proyecto utilizando el entorno de desarrollo del MPLAB X.

PRIMERAS PRÁCTICAS 40
Se verán los pasos necesarios para crear
nuestra primera aplicación completa. Lectura de pulsadores,
encendido, parpadeo y rotación de LEDs.

CONVERSOR ADC 67
Aprenda a utilizar el conversor analógino digital para leer
diferentes sensores conectados al PIC. Múltiples canales
de entrada.

CONTROL CON PWM 80


En esta práctica se verán los pasos para crear un proyecto
en donde utilizaremos el modulo CCP en su configuración
como PWM para generar un ancho de pulso variable.

USART 107
Aprenda a realizar comunicaciones de datos. Transmisión y
recepción por USART.

PRÁCTICAS AVANZADAS 119


Lectura de varios canales ADC, método polling, lectura/escritura
en la EEPROM interna.

5
PULSO CARDÍACO 147
Veremos cómo configurar y utilizar un sensor de pulso cardíaco.
Consideraciones importantes para lograr una buena medición.

PROYECTO INTEGRADOR 155


Se pretende realizar un sistema de monitoreo de temperatura
utilizando el sensor EMC1001. El sistema debe poder configurar
un valor de temperatura máximo para luego activar un alerta.

PROYECTOS CON PLACAS CLICK 167


Aprenda a utilizar sensores de temperatura, humedad y presión
atmosférica. Además, integre un acelerómetro y giróscopo en su
aplicación utilizando las placas click.

6
MPLAB X IDE Y HERRAMIENTAS DE DESARROLLO

INTRODUCCIÓN
Este libro pretende ser una ayuda auxiliar al curso de programación de
microcontroladores PIC en lenguaje C. Durante el curso podrá aprender a
programar los microcontroladores de manera eficiente y rápida gracias a las
herramientas de desarrollo provistas por la empresa Microchip. En una
primera etapa, aprenderemos cuales son las herramientas que disponemos
para desarrollar nuestros proyectos, cual será nuestro entorno de trabajo
diario con estos microcontroladores y además nos adentraremos a un nivel de
programación más poderoso que el lenguaje Assembler, realizando nuestros
proyectos en lenguaje C.

En el presente libro encontrará una serie de proyectos prácticos que se dicta


en el curso de programación de microcontroladores. Las prácticas arrancan
desde un nivel muy básico comenzando desde la instalación de las
herramientas de trabajo, hasta un nivel más avanzado con una práctica final
incluyendo todo lo aprendido, para realizar un sistema de control y monitoreo
de temperatura mediante comunicación I2C.

Todas las prácticas son realizadas utilizando la placa de desarrollo de


Microchip, la Xpress Board, que cuenta con un microcontrolador PIC16F18855,
el cual será analizado en detalle durante el curso.

La presente edición del libro cuenta además con prácticas especiales utilizando
las placas Click de MikroElectronica, donde aprenderemos a manejar sensores
de temperatura-humedad-presión y acelerómetros y giroscopios.

HERRAMIENTAS DE DESARROLLO
Para trabajar en el desarrollo de código para microcontroladores, debemos
primero familiarizarnos con las herramientas de desarrollo que nos provee, en
este caso, la empresa Microchip para la utilización de sus microcontroladores
PIC. La filosofía de Microchip es la de juntar todas las herramientas en un solo
entorno de desarrollo llamado MPLAB X IDE (Integrated Development

7
Enviroment). Este entorno de desarrollo posee todo lo necesario para trabajar
con los microcontroladores PIC. Veamos cuáles son sus características.

MPLAB X IDE
El MPLAB X IDE es el entorno de trabajo que nos provee de todas las
herramientas necesarias para el completo desarrollo de un proyecto con
microcontroladores PIC. Además abarca todas las familias de
microcontroladores PIC, desde la línea base de 8 bits, hasta los más avanzados
PIC de 32bit.

En este entorno nos encontramos con un Editor de texto inteligente, un gestor


de proyectos, un manejador de las herramientas de lenguaje, interfaz con el
hardware de programación y debugging y la posibilidad de agregar “Plug - ins “
para extender sus funcionalidades. Toda la plataforma está montado sobre
NetBeans IDE, el cual corre sobre una maquina virtual Java con soporte
multiplataforma, lo que permite ejecutar el MPLAB X sobre Windows, Mac OS
o Linux.

En la siguiente figura se puede ver el entorno de desarrollo del MPLAB X, con el


cual debemos familiarizarnos, ya que será nuestra herramienta diaria de
trabajo. Allí se puede visualizar el Editor de texto, a la izquierda se encuentra el
gestor de proyectos y en la parte inferior del Editor de texto se encuentra la
ventana de Output.

8
COMPILADOR XC8
Debido a la creciente complejidad en los desarrollos de sistemas embebidos, el
aumento de los recursos de los procesadores y que a su vez requieren de un
tiempo cada vez más acotado para el desarrollo de un proyecto para que este
salga al mercado, hicieron que se buscará una alternativa a las dificultades que
plantea el lenguaje assembler. Uno de los lenguajes de alto nivel más
populares y de gran potencialidad es el lenguaje C, ya que posee características
que lo hacen ideales en sistemas microcontrolados. A pesar de que C es un
lenguaje de alto nivel, también nos ofrece la posibilidad de acceder a
posiciones de memoria y posee un gran manejo de bits, lo que lo hace un
lenguaje ideal en los sistemas embebidos.

Por estas razones es que Microchip ofrece la posibilidad de programar sus


microcontroladores PIC en lenguaje C y para ello creó una serie de
compiladores que abarcan todas las familias de PIC, desde la línea base de 8
bits hasta los de 32 bits. En particular, en este curso veremos el compilador
para los PIC de 8 bits, el compilador XC8.

9
Estas son las características del compilador XC8:

- Compilador especialmente creado para los micros de arquitectura de 8


bits.
- Reúne las características de dos compiladores muy utilizados, el PIC C
Compiler (Hi-Tech) y el MPLAB C18.
- Soporta ANSI C89 (Salvo algunas restricciones). Esto permite mantener
Soporta ANSI C89 (Salvo algunas restricciones). Esto permite mantener
compatibilidad con otras plataformas.
- Agrega sintaxis que complementa al estándar ANSI C para mayor
facilidad en el manejo de recursos de la plataforma.

MPLAB CODE CONFIGURATOR (MCC)


Con el objetivo de minimizar los tiempos de desarrollo de un proyecto,
Microchip nos ofrece una herramienta que nos permite configurar de manera
grafica los periféricos del PIC. Esta herramienta se llama MPLAB Code
Configurator, y viene como un Plug-In que debe instalarse en el entorno de
desarrollo MPLAB X IDE. Pero no solo nos permite configurar de una manera
simple los periféricos del micro, sino que además nos provee de una librería de
funciones para cada uno de los periféricos del PIC y que se encuentra
totalmente escrito en lenguaje C.

MPLAB XPRESS BOARD


Para las prácticas que se realicen en el curso, haremos uso de la placa de
entrenamiento MPLAB XPress Board, el cual cuenta con un microcontrolador
de 8 bits, el PIC16F18855. Esta placa cuenta con su propio programador,
incluido en la misma placa, por lo que no hay necesidad de utilizar un
programador externo. En la siguiente figura se puede ver en detalle los
periféricos que posee la placa.

10
Entre los recursos básicos podemos ver que la placa Xpress cuenta con su
propio programador por USB, un potenciómetro, un pulsador, LEDs y un sensor
de temperatura.

Para la programación del PIC, la placa Xpress actúa como un dispositivo de


memoria externa USB al conectarlo a la PC. Dentro de este disco externo
puede enviarse el archivo “.hex” compilado para la programación del micro.
Luego de un Reset el programa cargado comienza a ejecutarse.

Pero la placa de desarrollo Xpress no solo se comporta como un dispositivo de


almacenamiento, también puede comportarse como un dispositivo CDC. La
clase CDC (Comunication Device Class) nos permite emular un puerto COM en
la PC. El firmware del PIC18F que se encuentra en el programador está
preparado para trabajar como un puente USART-USB con el PIC16F. Tener en
cuenta que es necesario instalar los drivers para un correcto funcionamiento.

11
PIC16F18855
Este microcontrolador de 8 bits, pertenece a la familia PIC16F18XX, los cuales
son microcontroladores diseñados para aplicaciones de propósitos generales y
poniendo hincapié en el bajo consumo. A continuación enunciaremos las
características más importantes de esta familia de PIC:

- 32MHz oscilador interno.


- Hasta 56 KB de Memoria Programa
- 256B Memoria EEPROM
- Hasta 4 KB de RAM
- 10-bit ADC with computational ADC²
- 2 Comparadores
- 5-bit DAC
- 10-bitPWMs
- 5 Capture, Compare, PWM (CCP)
- EUSART, SPI, I2C

Esta familia de Microcontroladores cuenta con la nueva arquitectura de rango


medio mejorado para los PIC16F. Con esta nueva arquitectura, se logra
extender la memoria de programa hasta unos 56KBytes y la memoria RAM
hasta unos 4KBytes (En comparación a los 14KBytes de memoria de programa
y los 446 Bytes de RAM en los PIC de rango medio base, como el PIC16F887).
Además aumenta la cantidad de instrucciones de 35 a 49 en los PIC de rango
medio mejorado y el nivel del Stack llega a 16. Todo este aumento en los
recursos del microcontrolador tiene como objetivo la posibilidad de programar
esta familia de micros utilizando el lenguaje C.

12
Nuestro microcontrolador, con el que trabajaremos en nuestras prácticas, es el
PIC16F18855, el cual posee 14KB de memoria de programa, 1KB de memoria
RAM, 256Bytes de memoria EEPROM interna y un oscilador interno que puede
alcanzar hasta los 32 MHz.

El PIC16F18855 cuenta con nuevos periféricos del tipo “Core Independent


Peripherals” los cuales permiten realizar determinadas funcionalidades sin la
necesidad de ocupar tiempo de procesamiento del procesador principal. Estos
nuevos periféricos son:

- Peripheral Module Disable


- Complementary Waveform Generator (CWG)
- Numerically Controled Oscillator (NCO)
- Data Signal Modulator
- 10-bit ADC whit computational ADC²
- Comunicación I2C con soporte a SMBus y PMBus

13
PRACTICAS MPLAB X

PRACTICAS DE INSTALACIÓN Y USO DEL MPLAB X

ELEMENTOS NECESARIOS:
Para esta clase es necesario contar con los siguientes elementos:
-Descargar instalador del MPLAB X IDE de la página de Microchip
-Descargar el instalador del Compilador XC8 de la página de Microchip
-Descargar los archivos fuentes provistos en la clase

PRÁCTICA 1: INSTALACIÓN DEL MPLAB X


En esta práctica se verán los pasos para la descarga y la instalación del entorno
de desarrollo del MPLAB X.

1) Descargar el instalador del MPLAB X desde la página de Microchip. Se


debe descargar desde el siguiente enlace: www.microchip.com/mplab
Dirigirse a la sección Download y descargar la última versión

14
2) Dirigirse a la ubicación del archivo descargado y ejecutar el instalador,
que para un usuario de Windows debe tener un nombre como con el
siguiente formato
MPLABX-vX.XX-windows-installer.exe.

Para los usuarios de Linux, deben realizar un paso intermedio que es


descomprimir el archivo tar y ejecutarlo.

Comenzar extrayendo el Shell Script con el comando tar.


tar -xvf MPLABX-vX.XX-linux-installer.tar

Hacer el archivo ejecutable ejecutando el comando chmod


chmod u+x MPLABX-vX.XX-linux-installer.sh
Luego como usuario root o mediante sudo ejecutar el instalador.
Seguir los pasos
sudo ./MPLABX-vX.XX-linux-installer.sh

3) En la ventana de Setup oprimir el botón Next

15
4) En la siguiente ventana seleccionar la opción “I accept agreement” y
oprimir Next.

5) El directorio por defecto donde se instala C:\Program Files


(x86)\Microchip\MPLABX. Es preferible dejar este directorio por
defecto. Presionar Next

16
6) La siguiente ventana nos pregunta que deseamos instalar, el MPLAB X
IDE, que es el entorno de desarrollo donde vamos a trabajar, o el
MPLAB IPE que es un entorno de programación. Seleccionar ambas
herramientas y Continuar.

17
7) Comenzar la instalación.

18
8) Una vez finalizada la instalación, la ventana final posee unos checkbox
los cuales debemos dejar habilitados si queremos que al finalizar el
buscador Web nos lleve a la página de descarga de los compiladores.

19
Luego de esto la instalación está completa solo resta ejecutar la
aplicación.

PRÁCTICA 2: INSTALACIÓN DEL COMPILADOR XC8


En esta práctica se verán los pasos para la descarga y la instalación del entorno
de desarrollo del MPLAB X.

1) Descargar el instalador del compilador XC8 desde la página de


Microchip. Se debe descargar desde el siguiente enlace:
www.microchip.com/xc8
Dirigirse a la sección Download y descargar la última versión
2) Dirigirse a la ubicación del archivo descargado y ejecutar el instalador,
que para un usuario de Windows debe tener un nombre como con el
siguiente formato
xc8-vX.XX-windows-installer.exe..
3) En la ventana de Bienvenida hacer clic en siguiente

20
4) Aceptar la licencia y oprimir Next

21
5) Seleccionamos el tipo de licencia con el que utilizaremos, si queremos
utilizarlo de forma gratuita seleccionamos Free. Si posee licencia para
trabajar en el modo Workstation o Network Client, seleccione el
correspondiente.

22
6) Seleccionamos el directorio donde se instalará el compilador. En este
caso es preferible dejarlo en el directorio por defecto.

23
7) En la siguiente ventana debemos configurar algunas características del
compilador. El primer CheckBox indica si la configuración de esta
ventana se aplica a todos los usuarios. Los siguientes dos CheckBox
crean un path del compilador XC8 en las variables de entorno. Si se
desea utilizar el compilador desde una ventana de comandos, dejar
estos checkbox habilitados, en caso contrario no. Los últimos dos
Checbox se sugieren dejar deshabilitados si se desea mantener los
antiguos proyectos con los antiguos compiladores.

24
8) Ya estamos listo para iniciar la instalación, hacemos click en Next y
aguardamos a que finalice la instalación.

25
9) Luego de la instalación, si posee la clave de activación puede agregarlo,
en caso contrario puede utilizar el compilador de manera gratuita
(Free) o activar la versión de prueba durante 60 dias. Seleccione alguna
de estas o simplemente presión Next para continuar en modo Free.

26
10) Finalmente completamos la instalación oprimiendo en Finish en la
última ventana. De esta manera ya podemos ejecutar el MPLAB X IDE y
utilizar el compilador desde este entorno de desarrollo.

27
PRÁCTICA 3: INSTALACIÓN DEL MPLAB CODE CONFIGURATOR
En esta práctica se verán los pasos para la descarga y la instalación de la
herramienta de configuración MPLAB Code Configurator.

1) Para instalar el MPLAB Code Configurator debemos comenzar


ejecutando el MPLAB X. Una vez que estamos en la interfaz de
desarrollo, hacemos click en Tools >> Plugins

28
2) La acción abrirá una nueva ventana dedicado al manejo e instalación
de los Plugins en MPLAB X. Allí seleccionamos la ventana la solapa
Available Plugins para ver los plugins disponibles en nuestra versión de
MPLAB X. Seleccionamos la que dice MPLAB Code Configurator v3 y
hacemos click en “install”.

29
3) Una vez que se oprime el botón “Install” se ejecutara el instalador del
MPLAB Code Configurator. En la ventana de bienvenida hacemos click
en Next.

30
4) Luego aceptamos la licencia y hacemos click en Install para comenzar
la instalación.

31
5) Esperamos a que la instalación finalice, una vez que lo hace, la ventana
final nos indicará que el proceso de instalación finalizo exitosamente y
hacemos click en Finish.

32
6) Para iniciar la ejecución del MPLAB Code configurator debemos
hacerlo desde la interfaz del MPLAB X haciendo click en Tools >>
Open/Close MPLAB Code Configurator v3.

33
PRÁCTICA 4: CREAR UN NUEVO PROYECTO EN MPLAB X
En esta práctica se verán los pasos para crear un nuevo proyecto utilizando el
entorno de desarrollo del MPLAB X.

1) Comenzamos ejecutando el MPLAB X y en el entorno oprimimos el


icono “New Project…” o bien vamos a File y seleccionamos la
opción New Project…
2) El paso anterior hace que se nos despliegue una ventana que nos
guiara en la creación del proyecto. Lo primero que debemos
seleccionar es el tipo de proyecto, para nuestro caso debemos
seleccionar el tipo de Proyecto “Standalone”.

34
3) En la siguiente ventana debemos seleccionar el microcontrolador a
utilizar, en este caso seleccionamos el micro utilizado en la placa
Microchip Xpress Board que es el PIC16F18855.

35
4) En la próxima ventana, de manera opcional, podemos incluir el soporte
para agregar un Header que se utiliza para aumentar algunas
funcionalidades para el Debug del Proyecto. En nuestro caso no
utilizaremos ningún hardware adicional para esto, así que dejamos
esta opción en “None”.

36
5) En la siguiente ventana seleccionamos el programador con el cual
trabajaremos. No podemos seleccionar la placa Xpress, ya que este tiene un
modo diferente de cargar el firmware que otras placas de desarrollo. En este
caso lo que debemos hacer es seleccionar el PICKit3 o el ICD3, porque en
realidad lo que necesitamos es que se genere un archivo “.hex”.

37
6) En la siguiente ventana seleccionamos el compilador disponible para el
Microcontrolador de la placa Xpress. Seleccionamos el compilador
XC8.

38
7) En la última ventana de la creación del proyecto debemos agregar el
nombre del proyecto y elegir una carpeta en donde se crearan los
archivos. En este caso, a nuestro primer proyecto lo llamaremos
“hello_world”

39
PRÁCTICA 5: ENCENDIDO DE UN LED.
En esta práctica se verán los pasos necesarios para crear nuestra primera
aplicación completa en cual aprenderemos a encender el LED D4 la placa
Xpress.

1) Comenzamos creando un nuevo proyecto, realizando los mismos pasos


que se vieron en la práctica de la primera clase. A este proyecto lo
llamaremos “led”

40
2) Una vez que esta creado el proyecto, ejecutamos el MPLAB Code
Configurator haciendo click en Tools >> Embedded >> MPLAB Code
Configurator v3. Esto ejecutara el MCC para configurar los periféricos
del PIC.

3) En la ventana “Project Resourcs” comenzamos haciendo click en


System Module. Esto hará que en la ventana central aparezca la
ventana que configura el sistema de manera general. Allí configuramos
el oscilador del sistema en modo oscilador interno a 1MHz como
muestra la figura. Además se debe tener especial atención en
mantener habilitado el LVP o el Low-voltage Programming.

41
4) Luego seleccionamos en la ventana Project Resources la sección Pin
Module. Esto abrirá la ventana central que se verá como muestra la
siguiente imagen, donde todavía no se selecciono ningún pin.

42
5) Para agregar pines a esta lista debemos dirigirnos a la ventana Pin
Manager y seleccionar el Pin del microcontrolador que actuara como
salida, manejando el encendido del LED. El LED D4 se encuentra
conectado al pin RA2, así que buscamos este pin en la fila Output y lo
seleccionamos haciendo click sobre el candado, para bloquear el
funcionamiento de este pin como solo salida. En la siguiente imagen
vemos como debería quedar esta ventana.

43
6) Una vez que seleccionamos el pin como salida, vamos a la ventana Pin
Module y modificamos el nombre IO_RA2 por el de D4. También
activamos el checkBox de Output, indicando que el pin se configura
como salida. Para que se encienda el Led al iniciar el programa
debemos habilitar la casilla de “Start High”.

44
7) Por último hacemos click en “Generate”, esto nos da la opción de
guardar la configuración en un archivo mc3. Guardamos y podemos
cerrar el MPLAB Code Configurator. En la ventana de proyectos
veremos que se generan nuevos archivos fuentes, entre ellos el
archivo “main.c” y las carpetas “MCC Generated Files” que contiene
los códigos fuentes para configurar el sistema y los pines.

45
8) Abrimos el archivo main.c generado por el MPLAB Code Configurator y
vemos que la función main tiene el formato visto en la clase, donde
nos encontramos con una función que realiza la configuración del
sistema SYSTEM_Initialize(); y luego el loop principal.

46
9) Finalmente procedemos a compilar el proyecto, hacemos click en el
botón Build, si todo está bien y no hay errores de sintaxis,
debemos ver el mensaje “BUILD SUCESSFUL”. Luego de esto podemos
programar el microcontrolador.

47
PRÁCTICA 6: BLINK LED.
En esta práctica se verán los pasos necesarios para crear una aplicación que
realice el encendido intermitente del LED.

1) Creamos un nuevo proyecto, siguiendo los pasos visto en la primera


práctica, y le ponemos el nombre blink_led.

2) Luego ejecutamos el MPLAB Code Configurator desde Tools >>


Embedded >> Open/Close MPLAB Code Configurator v3. Allí
seleccionamos dentro de la ventana “Project Resources” la sección
System Module, y configuramos el oscilador de sistema como lo
muestra la figura. Además se debe tener especial atención en
mantener habilitado el LVP o el Low-voltage Programming.

48
3) Luego dirigirse a la ventana de Pin Manager y seleccionar los pines
RA0, RA1, RA2 y RA3 como salida.

49
4) En la ventana Pin Module, verificar que el pin RA2 se encuentre
marcado como salida y colocarle nombre “D4” para indicar que es este
LED de la placa Xpress el que se va a encender.

50
5) Una vez hecho esto, hacemos Click en el botón Generate, lo cual
creara en nuestro proyecto los archivos necesarios para la
configuración del sistema y la inicialización del PIN que controlara el
encendido del LED. También se creara el archivo main.c, igual que al
ejercicio anterior, hacemos doble Click sobre este archivo para abrirlo
y modificarlo.

6) En el código fuente del archivo main.c escribimos las líneas de código


que se muestran en la siguiente figura. Dentro del bucle while principal
del programa comenzamos encendiendo el LED conectado al PIN RA5 y
que se llama “D4” mediante la función D4_SetHigh(); la cual se
encargará de poner este pin en uno. Esta función como vimos,
pertenece al set de funciones definido por el MPLAB Code
Configurator y su código puede verse en el archivo “pin_manager.h”.
Luego de encender el LED, generamos un retardo con la función
__delay_ms(500); la cual genera un retardo en milisegundos según la
cantidad que se le pase como parámetro en la función, de este modo
se genera un retardo de 500 milisegundos. Luego del retardo se apaga

51
el LED con la función D4_SetLow(); y se vuelve a esperar otros 500
milisegundos.

7) Una vez escrito el código, realizamos la compilación del ejercicio,


oprimiendo el icono Build. Si no hay errores de sintaxis, debemos
ver el mensaje “BUILD SUCCESFUL” y luego lo grabamos en el
microcontrolador.

52
PRÁCTICA 7: ESTADO DEL PULSADOR A LA SALIDA

En esta práctica se verán los pasos para crear un proyecto en donde


aprenderemos a configurar los pines GPIO del microcontrolador. El programa
consiste en mostrar el estado del pulsador con el LED D4 de la placa Xpress.

1) Comenzamos creando un nuevo proyecto al cual vamos a llamar


switch_led

2) Ejecutamos el MPLAB Code Configurator desde Tools y en la sección


System Module, configuramos el oscilador interno del sistema a una
frecuencia de 1MHz, con el Clock Divider en 4 y el LVP habilitado.

53
3) Luego de configurar las preferencias del sistema, vamos a configurar
los pines GPIO del microcontrolador. Para ello vamos a la ventana
Project Resources del MCC y seleccionamos Pin Module, esto abrirá en
la ventana central una tabla donde se listaran la configuración de los
pines. Para ir agregando filas a esta tabla debemos dirigirnos a la
ventana Pin Manager y vamos a configurar dos pines, uno como
entrada y otro como salida. El pin de entrada será RA5, el cual está
conectado al pulsador S2 y el pin RA2 como salida, donde se encuentra
el LED D4. La ventana Pin Manager se debe ver como lo muestra la
siguiente Figura.

54
4) Una vez seleccionados los pines, nos dirigimos a la ventana central del
MCC, donde deben estar listado los pines recién seleccionados, allí
modificamos el “Custom Name” de ambos pines, para que el de la
entrada diga S1, que es el nombre del pulsador en la placa y el de
salida el nombre D4, por el LED.

55
5) Luego de tener los pines de entrada y salida configurados, debemos
generar el código, para ello presionamos sobre el botón Generate,
para que se creen los fuentes del proyecto. Luego abrimos el archivo
main.c generado y agregamos la siguiente línea de código, como
muestra la figura. Esta línea de código realiza la asignación de la salida
que controla el LED con el valor leído del pulsador. La asignación se
hace negada por cómo está conectado el pulsador, ya que en estado
abierto la tensión leída en el pin es 5V y cuando se oprime se va a cero.

56
6) Finalmente procedemos a compilar el proyecto, hacemos click en el
botón Build, si todo está bien y no hay errores de sintaxis,
debemos ver el mensaje “BUILD SUCESSFUL”. Luego de esto podemos
programar el microcontrolador y verificamos el funcionamiento del
mismo, el cual debe encender el LED D4 siempre que se oprima el
pulsador.

57
PRÁCTICA 8: ROTACIÓN DE LEDS
En esta práctica se verán los pasos para crear un proyecto en donde
realizaremos el encendido secuencial de los LEDs de la placa Curiosity.

1) Comenzamos creando un nuevo proyecto, al cual llamaremos


“rótate_led”

2) Luego de terminar la creación del proyecto, abrimos el MCC y


comenzamos configurando el sistema del mismo modo que en la
práctica anterior, seleccionamos el oscilador interno a 1MHz (Clock
Interno de 4MHz y Clock Divider = 4) y revisar que el LVP o
programación de baja tensión se encuentre habilitado, ya que así lo
requiere el programador de la placa Xpress.

58
3) Luego Seleccionamos en la ventana de Project Resources el apartado
del Pin Module y nos dirigimos a la ventana de Pin Manager para
seleccionar como salida digital los cuatro pines que controlan el
encendido del LED, que son los Siguientes:

RA0 – LED D2
RA1 – LED D3
RA2 – LED D4
RA3 – LED D5

59
4) Luego en la ventana central, debemos agregar los Custom Name de
cada PIN, los cuales tendrán el nombre de cada LED que se encuentra
en la placa.

5) Luego de configurar los pines, hacemos click en el botón Generate,


para crear los archivos fuentes necesarios para continuar con el

60
proyecto. Abrimos el archivo main.c y agregamos el siguiente código,
para que se vea como el código siguiente.

#include "mcc_generated_files/mcc.h"
// prototype Delay Function
void Delay(void);
/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();
unsigned char rotateReg;
// Initialize temporary register to begin at 1
rotateReg = 1;
// Begin with D4 High
D2_LAT = 1;
// Clear LEDs
D3_LAT = D4_LAT = D5_LAT = 0;

while (1)
{
// Add your application code
// Call the Delay() function and keep LED on for 0.5 secs
Delay();
// Shift value in temp register to the right by 1 bit
rotateReg <<= 1;
// If the last LED has been lit, restart the pattern
if(rotateReg == 16)
rotateReg = 1;
// Determine which LED will light up
// ie. which bit in the register the 1 has rotated to.
D2_LAT = rotateReg & 1;
D3_LAT = (rotateReg & 2) >> 1;
D4_LAT = (rotateReg & 4) >> 2;
D5_LAT = (rotateReg & 8) >> 3;
}
}
// Delay function to keep the LED on for 0.5 secs before rotating
void Delay(void)
{
int i = 0;
for(i=0;i<25;i++){
__delay_ms(20);
}

61
}

En el Código podemos ver como se realiza la rotación o el encendido


secuencial de los LEDs. Primero se crea una variable char sin signo,
que almacenara el valor que queremos mostrar en los LEDs, es decir
que esta variable de 8 bits contendrá en sus primeros 4 bits el estado
en que se van a encontrar los LEDs. Luego de declarar esta variable se
lo inicializa en 1 y se enciende el primer LED, este será el estado inicial
con cual arranque el programa. Luego se entra en el bucle principal del
programa, en donde se comienza con un retardo de 500 milisegundos
realizado por una función declarada después del main. Esta función
simplemente realiza la ejecución de veinticinco retardos de 20
milisegundos, para alcanzar los 500 milisegundos y se realiza mediante
un bucle FOR. Luego de este retardo, se rota el registro que lleva el
esta de los LEDs a izquierda, una sola posición y a continuación se
mapean uno a uno los 4 bits menos significativos con cada una de las
salidas de los LEDs. Este mapeo se realiza mediante una operación de
rotación y una operación AND para quedarme con el bit que me
interesa en cada secuencia. Finalmente, si el valor del registro que
lleva el conteo es igual a 16, se resetea la cuenta y se vuelve a empezar

6) Por último Compilamos y ejecutamos el programa en la placa para ver


el resultado de la secuencia de encendido de los LEDs.

PRÁCTICA 9: DEBOUNCE DE UN PULSADOR


En esta práctica veremos cómo realizar el Debounce o el anti-rebote al
accionar un pulsador. La practica consiste en detectar la pulsación del switch
S2 ubicado en la placa y que este controle el encendido y apagado del LED, es
decir, si el pulsador se oprime una vez, se enciende el led y si vuelve a oprimir,
se apaga, evitando que los cambios de estados espurios que puedan existir
modifiquen el estado del LED.

Descripción:
El pulsador o switch es un elemento muy utilizado en la generación de
interfaces con el usuario, de esta manera se puede acceder datos al sistema
utilizando este elemento. Pero un botón mecánico tiene la desventaja de que
el cambio de estado no se produce de manera suave, sino que se producen

62
cambios erráticos hasta que se toma el estado deseado. Este comportamiento
puede generar que se detecten más de un cambio de estado cuando en
realidad solo se oprimió una vez el pulsador. Para evitar esto, normalmente lo
que se hace es detectar el primer cambio de estado y generar un retardo que
enmascare por un tiempo los siguientes valores hasta que se alcance la
estabilidad del accionamiento mecánico y evitar detectar otras posibles
activaciones “espurias” del pulsador. Otro modo de atacar este problema es el
de tomar muestras equi-espaciadas del estado del pulsador y determinar si se
produce la activación o no del mismo. Este segundo método es el que se
realizara en la práctica, se tomaran muestras del pulsador cada 50
milisegundos para detectar o no, los cambios de estados.

Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa y leer el estado del pulsador.

Configuración del MCC

63
Código:
#include "mcc_generated_files/mcc.h"

/*
Defines
*/
#define BUTTON_PRESSED 0
#define BUTTON_NOT_PRESSED 1

/*
Prototypes
*/
bool BUTTON_IsPressed(void);
bool BUTTON_Activate(void);
/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

while (1)
{
// Add your application code
if(BUTTON_Activate())
{
D2_Toggle();
}

64
__delay_ms(50);
}
}

En la función principal se inicia con el llamado a la función que inicializa los


periféricos del PIC, SYSTEM_Initialize(); Luego se encuentra el bucle principal del
programa el cual se testea una función que indica si se oprimió el botón o no.
Esta función es la encargada de detectar la pulsación, pero se necesita que se
ejecute cada 50 milisegundos, ya que compara el estado anterior con el actual
para determinar si se oprimió el pulsador. Una vez que la función devuelve la
activación del pulsador, se realiza un toggle del LED D2.

Funciones Auxiliares

//Funciones Auxiliares
bool BUTTON_IsPressed(void)
{
return ( (BUTTON_PORT == BUTTON_PRESSED) ? true : false);
}

bool BUTTON_Activate(void)
{
static bool lastState = false;
bool actualState;
bool activateButton;

actualState = BUTTON_IsPressed();
if( actualState && !lastState)
activateButton = true;
else
activateButton = false;

lastState = actualState;
return activateButton;
}

Para detectar si se oprime el pulsador y evitar el efecto rebote que se puede


originar por el manejo de partes mecánicas, se utilizan estas funciones
auxiliares. Como están dedicadas al pulsador de la placa, los nombres de estas
funciones comienzan con BUTTON. Una de las funciones, BUTTON_IsPressed, es
la encargada de tomar el valor actual del pulsador, si esta pulsado devuelve
True, sino, False. La otra función, BUTTON_Activate, debe ejecutarse cada cierto

65
tiempo, ya que esta toma una muestra del estado anterior y la compara con el
actual, si hubo un cambio, en este caso pasa de no oprimido a oprimido, la
función devuelve True. Por ello es importante que el tiempo de muestreo sea
lo suficientemente grande como para evitar el efecto rebote.

66
PRÁCTICAS CON PERIFERICOS DEL PIC

PRÁCTICA 10: CONVERSIÓN ADC PARA MODIFICAR VELOCIDAD


DE ROTACIÓN

En esta práctica se verán los pasos para crear un proyecto en donde realizara la
configuración del modulo ADC del microcontrolador para obtener el valor de la
velocidad de rotación de los LEDs de la placa utilizando como control la
posición del potenciómetro.

1) Creamos un nuevo Proyecto al cual llamaremos “adc_rotate”. Una vez


creado ejecutamos el MCC para configurar el oscilador del sistema en
modo INTOSC a una frecuencia de 500KHz, con el PLL deshabilitado.
Recordar que en la pestaña de Registros debemos verificar que el
modulo LVP se encuentre habilitado.

2) Seleccionamos el modulo Pin Manager y configuramos los pines que


controlan los cuatro LEDs de la placa Curiosity. Estos deben quedar
como lo muestra las siguientes imágenes.

67
Ventana Pin Module

3) Luego agregamos el modulo ADC desde la ventana de Device


Resources hacia la de Project Resources, haciendo doble click en el
modulo ADC. Se debe seleccionar el Pin RC0 como entrada analógica
en la ventana de Pin Manager y configurar el modulo ADC del mismo

68
modo que se hizo en la práctica anterior. En la siguiente imagen vemos
la configuración que debe quedar en el modulo ADC

4) Una vez configurado los pines GPIO y el conversor ADC, generamos los
códigos fuentes haciendo click en el botón Generate. Luego cerrar el
MCC, dirigirse a la ventana de proyecto y abrir el archivo main.c,
agregar el código que se muestra a continuación.

69
#include "mcc_generated_files/mcc.h"

/*
Main application
*/
void main(void) {
// initialize the device
SYSTEM_Initialize();

unsigned char rotateReg;


unsigned int delay;

// Clear LEDs
D2_LAT = D3_LAT = D4_LAT = D5_LAT = 0;

// Initialize temporary register to begin at 1


rotateReg = 1;

while (1)
{
// Add your application code
// Set DELAY from the top 8 MSbs of ADC
delay = ADCC_GetSingleConversion(POT1) >> 8;

// Delay for at least 5ms


__delay_ms(5);

// Decrement the 8 MSbs of the ADC and delay 2ms for each
while (delay-- != 0)
__delay_ms(2);

// Determine which LED will light up


// ie. which bit in the register the 1 has rotated to.
D2_LAT = rotateReg & 1;
D3_LAT = (rotateReg & 2) >> 1;
D4_LAT = (rotateReg & 4) >> 2;
D5_LAT = (rotateReg & 8) >> 3;

// Rotate position of LED


rotateReg = rotateReg << 1 ;

// Return to initial position of LED


if (rotateReg == 16)
rotateReg = 1;
}

70
}

El código es similar al que se incluyo en la práctica de rotación de LEDs,


salvo que aquí para generar el retardo, cargamos una variable llamado
delay con los 8 bits más significativos de la conversión del ADC. Esta
variable luego se utiliza para generar el retardo variable ya que
modifica la cantidad de ciclos que genera el bucle while para los
retardos de tiempos.

5) Finalmente procedemos a compilar el proyecto, hacemos click en el


botón Build, si todo está bien y no hay errores de sintaxis,
debemos ver el mensaje “BUILD SUCESSFUL”. Luego de esto podemos
programar el microcontrolador y verificar el funcionamiento del
mismo. El programa debe iniciar la secuencia de encendido de los LEDs
de la placa Curiosity y deben modificarse su velocidad de encendido
cada vez que movemos el potenciómetro.

PRÁCTICA 11: CONVERSOR ADC²


Utilizando el modulo el conversor ADC y el nuevo modulo computacional,
tomar sucesivas muestras de la tensión en el PIN conectado al potenciómetro
de la placa Xpress y si el valor de la conversión por encima o por debajo de un
determinado umbral, encender un LED indicador como advertencia de que se
han excedido los limites umbrales.

Descripción:
Gracias a la ayuda del nuevo modulo computacional, realizar la tarea
propuesta como ejercicio resulta fácil de generar y con simplemente una
cuantas líneas de código que configuran el modulo ADC²(Esto se realiza desde
el MCC). El enunciado pide dos cosas, una es la de tomar valores medios de
una cantidad de muestras y por otro lado la de detectar si el resultado de la
conversión no supera determinados umbrales. La primera parte, obtener el
valor medio, se puede generar configurando al modulo computacional en el
modo Burst Average, esto hace que la sección del Acumulador, sume sucesivas
muestras del conversor y luego realice un desplazamiento del registro para
generar la división y obtener el valor medio. La segunda parte, la detección de
los umbrales, se realiza en la etapa de comparación, para ello, se configura

71
esta etapa para que calcule la diferencia de la conversión contra un valor de
Setpoint que para este caso lo dejaremos en cero, y configuramos los valores
umbrales(Threshold) con valores arbitrarios. Cada vez que la comparación
excede los valores umbrales, el flag ADTIF se activa, pudiendo generarse
interrupciones.

Periféricos Utilizados:
En esta práctica se utilizaran los pines GPIO para indicar los excesos a los
valores umbrales y el conversor ADC con su modulo computacional, desde el
cual podemos obtener los valores medios de la conversión y la comparación
con los niveles. En la siguiente figura se encuentra el modulo computacional,
donde se pueden ver las etapas de acumulación de valores y la de
comparación.

Configuración MCC:

72
73
El modulo ADC se configura en modo Burst_average_mode, con una fuente de
clock obtenido por la Frecuencia del Oscilador principal divido 4, las
referencias serán VDD y VSS y el resultado justificado a derecha.

74
El modulo computacional se configura por un lado, para que los valores de
Repeat y Acc Right Shift generen el valor medio de obtener 32 muestras de la
conversión y por el otro lado, se configura para que el comparador calcule la
resta entre el valor actual de la conversión contra un valor de Setpoint que fue
configurado en cero. Esta diferencia pasa luego al comparador que se activa si
el valor del resultado está por debajo o por encima de los valores umbrales.

Código:
#include "mcc_generated_files/mcc.h"
/*
Defines
*/
#define BUTTON_PRESSED 0
#define BUTTON_NOT_PRESSED 1

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

uint16_t valor_medio_adc = 0;

while (1)
{
// Add your application code

//Iniciamos conversion ADCC


ADCC_StartConversion(POT);
//Esperamos a que termine la conversion
while(!ADCC_IsConversionDone());
//Termina conversion, obtengo el valor del registro
valor_medio_adc = ADCC_GetFilterValue();
//valor_medio_adc = ADCC_GetConversionResult();

//Lo cargo en los LEDs


//LED_LoadRegister((unsigned char)valor_medio_adc );
if(PIR1bits.ADTIF)
{
D2_LAT = 1;

75
PIR1bits.ADTIF = 0;
}
else
{
D2_LAT = 0;
}
}
}

El código principal es bastante corto y comienza nuevamente llamando a la


función SYSTEM_Initialize() para configurar los periféricos de esta práctica, que
son el GPIO y el conversor ADC. Luego en el loop principal, se inicia una
conversión con la función ADCC_StartConversion() y le indicamos que el canal
de conversión es el POT, que es donde se encuentra conectado el
potenciómetro de la placa Xpress. Luego de que termina de realizar la
conversión, tomaremos el valor almacenado en el registro ADFLTR, que es el
que almacena el valor medio de las sucesivas conversiones, y lo obtenemos
llamando a la función ADCC_GetFilterValue(). Luego observamos el estado del
flag ADTIF para saber si la comparación determina que esta se encuentra fuera
de los umbrales, si es así, encendemos el LED, sino lo apagamos. Recordar que
el flag se debe borrar manualmente para detectar la próxima activación.

PRÁCTICA 12: TIMER BLINK


En esta práctica se verán los pasos para crear un proyecto en donde
utilizaremos el Timer 0 como retardo para el encendido y apagado de un LED
en la placa Curiosity.

1) Comenzamos creando un nuevo proyecto al cual vamos a llamar


timer_blink.

76
2) Luego configuramos el oscilador del sistema, en este caso volveremos
a seleccionar el oscilador interno como la fuente de Clock del sistema a
500KHz con el PLL deshabilitado.

3) Configuramos el PIN RA2 del PIC como salida para controlar el


encendido del LED D4. A este PIN le pondremos el Custom Name “D4”.

77
4) Luego en la ventana de “Device Resources” seleccionamos haciendo
doble click el periférico Timer >> TMR0. Esto hará que el recurso del
timer 0 pase como recurso del proyecto y desplegará la ventana
central para configurar este timer.

5) Configuramos el timer 0 con los parámetros que se muestran en la


siguiente figura, es decir, habilitamos el prescaler con el valor 1:512,
seleccionamos la como fuente de clock para este timer la frecuencia
del oscilador sobre cuatro (FOSC/4). Luego elegimos el periodo con el
que trabajara el Timer que será de 500ms.

78
6) Hacen click en Generate y cierran el MCC. Esto creara los archivos que
configuran el sistema, los pines GPIO y el timer 0. También generara un
main.c, lo abrimos y definimos la función main de la siguiente manera:

void main(void)
{
// initialize the device
SYSTEM_Initialize();

// Clear LED
D4_LAT = 0;
while (1) {
// Add your application code
// Wait for Timer0 to overflow
while(!TMR0IF);

// Reload the initial value of TMR0


//TMR0_Reload();

// Toggle D4
D4_Toggle();

// Clear TMR0 Overflow flag

79
TMR0IF = 0;
}
}

En esta función main, hacemos uso de las funciones que provee el


MCC para el uso del Timer 0. Luego de inicializar el sistema,
inicializamos el Timer 0 con las configuraciones que elegimos en su
momento. Luego en el bucle principal esperamos a que el timer
termine su cuenta, para eso esperamos a que cambie el flag TMR0IF,
ese while se quedara esperando hasta que se active el flag. Luego se
recarga el valor del timer 0 con la función TMR0_Reload(); y se utiliza
la función Toggle del PIN que llamamos D4 para cambiar el estado del
LED y por último, es importante no olvidar que el flag debe borrarse de
manera manual.

7) Para finalizar compilamos el ejercicio y lo grabamos en el PIC para


verificar el funcionamiento.

PRÁCTICA 13: PWM


En esta práctica se verán los pasos para crear un proyecto en donde
utilizaremos el modulo CCP en su configuración como PWM para generar un
brillo variable en el LED D7.

1) Generamos un nuevo proyecto llamado “pwm”.

80
2) Ejecutamos el MCC y comenzamos configurando el oscilador del
sistema. Nuevamente configuramos el oscilador interno a 1MHz y el
LVP activado.

3) Luego vamos a configurar el conversor ADC para que tome la tensión


entregada por el potenciómetro de la placa. Agregamos el modulo ADC
desde la ventana de Device Resources y configuramos el ADC para que
la fuente de clock sea FOSC/4, la tensión de referencia positiva sea
VDD y la justificación de la conversión debe ser a derecha. Después en

81
la ventana PIN Manager seleccionamos el PIN RA4 como entrada
analógica y le ponemos el nombre POT.

4) Luego en la ventana de Recursos del Dispositivo, seleccionamos el


Timer >> TMR2 el cual funciona como fuente de clock del modulo
PWM.

82
5) Configuramos el Timer 2 con una fuente de reloj en FOSC/4, con el
Postscaler en 1 y el Prescaler en 1:2 y el periodo lo dejamos como esta,
en 2.048ms.

83
6) A continuación volvemos a la ventana de Recursos del Dispositivo y
buscamos el Periférico CCP >> CCP1. Esto se agregara a la ventana de
Recursos del Proyecto.

84
7) Luego seleccionamos el modo de trabajo del modulo CCP en PWM,
esto habilitara las configuraciones de este modulo. Configuramos para
que el timer 2 sea la fuente de clock del modulo, también
configuramos el duty Cycle para que sea del 50% al iniciar el modulo
PWM. Por último, la alineación debe estar a derecha, igual que la
alineación que seleccionamos en el conversor AD.

85
8) Una vez que tenemos el modulo PWM configurado, debemos
seleccionar el PIN RA3 como salida del modulo CPP5. Para esto vamos
al PIN manager y seleccionamos ese PIN como salida del CCP5.

86
9) Le ponemos como nombre a ese PIN D5, ya que controla el LED de la
placa.

87
10) Ya se configuraron todos los periféricos a utilizar en este proyecto, así
que apretamos el botón Generate para crear los archivos que
configuren el conversor ADC, el timer 2 y el modulo PWM. Abrimos el
main.c y copiamos el siguiente código.

void main(void)
{
// initialize the device
SYSTEM_Initialize();

// Start Timer2
TMR2_Start();

while (1) {
// Add your application code
// Start ADC conversion
ADCC_StartConversion(POT);

// Wait for the conversion to finish


while(ADCC_IsConversionDone());

// Store the top 8 MSbs into CCPR1H


CCPR5H = ADRESH;

// Store the 2 LSbs into CCPR1L register to complete the 10bit resolution
CCPR5L = ADRESL;

//PWM5_LoadDutyValue(ADCC_GetConversionResult());
}

Esta función main, una de las operaciones más importante es el


SYSTEM_Initialize(); el cual configura todos los periféricos
seleccionados. Aquí se configura el ADC, el Timer 2 y el modulo PWM y
configurando aquí también el PIN RA3 como salida del modulo CCP5.
Luego de la inicialización, se enciende el Timer2 que actúa como clock
del modulo PWM. Luego en el bucle principal, se inicia una conversión
ADC y se queda esperando a que este termine. Una vez finalizada la

88
conversión, el resultado se asigna a los registros del PWM que
manejan el Duty Cycle de la señal, así en el registro CCPR5H se le
asigna el valor del registro ADRESH que es la parte alta del resultado y
para el registro CCPR5L se le asigna el registro ADRESL, la parte baja de
la conversión. Para que esto funcione adecuadamente es necesario
que ambos módulos, el conversor AD y el PWM, se configuren con la
misma alineación.

11) Finalmente compilamos el proyecto y los grabamos en Micro para


verificar el funcionamiento. Al modificar la posición del potenciómetro
se debe ver la variación en el brillo del LED D5.

PRÁCTICA 14: CONTROL DE MOTOR CON PWM


En esta práctica se verán los pasos para crear un proyecto en donde
utilizaremos el modulo CCP en su configuración como PWM para generar la
variación de velocidad de un motor de corriente continua.

1) Para realizar la siguiente práctica debemos contar con un hardware


externo que sirva como driver para el manejo de corriente del motor
de continua. Para ello vamos a realizar el circuito que se muestra en la
imagen.

89
2) Crear un nuevo proyecto en MPLAB X y llamarlo “pwm_motor”

3) Ejecutamos el MCC y comenzamos configurando el oscilador del


sistema. Nuevamente configuramos el oscilador interno a 1MHz y el
LVP activado.

90
4) Luego vamos a configurar el conversor ADC para que tome la tensión
entregada por el potenciómetro de la placa. Agregamos el modulo ADC
desde la ventana de Device Resources y configuramos el ADC para que
la fuente de clock sea FOSC/4, la tensión de referencia positiva sea
VDD y la justificación de la conversión debe ser a derecha. Después en
la ventana PIN Manager seleccionamos el PIN RA4 como entrada
analógica y le ponemos el nombre POT.

91
5) Luego en la ventana de Recursos del Dispositivo, seleccionamos el
Timer >> TMR2 el cual funciona como fuente de clock del modulo
PWM.

92
6) Configuramos el Timer 2 con una fuente de reloj en FOSC/4, con el
Postscaler en 1 y el Prescaler en 1:2 y el periodo lo dejamos como esta,
en 2.048ms.

93
7) A continuación volvemos a la ventana de Recursos del Dispositivo y
buscamos el Periférico CCP >> CCP1. Esto se agregara a la ventana de
Recursos del Proyecto.

94
8) Luego seleccionamos el modo de trabajo del modulo CCP en PWM,
esto habilitara las configuraciones de este modulo. Configuramos para
que el timer 2 sea la fuente de clock del modulo, también
configuramos el duty Cycle para que sea del 50% al iniciar el modulo
PWM. Por último, la alineación debe estar a derecha, igual que la
alineación que seleccionamos en el conversor AD.

95
9) Una vez que tenemos el modulo PWM configurado, debemos
seleccionar el PIN RC7 como salida del modulo CPP5. Para esto vamos
al PIN manager y seleccionamos ese PIN como salida del CCP5.

96
10) Le ponemos como nombre a ese PIN D5, ya que controla el LED de la
placa.

97
11) Ya se configuraron todos los periféricos a utilizar en este proyecto, así
que apretamos el botón Generate para crear los archivos que
configuren el conversor ADC, el timer 2 y el modulo PWM. Abrimos el
main.c y copiamos el siguiente código.

void main(void)
{
// initialize the device
SYSTEM_Initialize();

// Start Timer2
TMR2_Start();

while (1) {
// Add your application code
// Start ADC conversion
ADCC_StartConversion(POT);

// Wait for the conversion to finish


while(ADCC_IsConversionDone());

// Store the top 8 MSbs into CCPR1H


CCPR5H = ADRESH;

98
// Store the 2 LSbs into CCPR1L register to complete the 10bit resolution
CCPR5L = ADRESL;

//PWM5_LoadDutyValue(ADCC_GetConversionResult());
}

Esta función main, una de las operaciones más importante es el


SYSTEM_Initialize(); el cual configura todos los periféricos
seleccionados. Aquí se configura el ADC, el Timer 2 y el modulo PWM y
configurando aquí también el PIN RC7 como salida del modulo CCP5.
Luego de la inicialización, se enciende el Timer2 que actúa como clock
del modulo PWM. Luego en el bucle principal, se inicia una conversión
ADC y se queda esperando a que este termine. Una vez finalizada la
conversión, el resultado se asigna a los registros del PWM que
manejan el Duty Cycle de la señal, así en el registro CCPR5H se le
asigna el valor del registro ADRESH que es la parte alta del resultado y
para el registro CCPR5L se le asigna el registro ADRESL, la parte baja de
la conversión. Para que esto funcione adecuadamente es necesario
que ambos módulos, el conversor AD y el PWM, se configuren con la
misma alineación.

12) Finalmente compilamos el proyecto y los grabamos en Micro para


verificar el funcionamiento. Al modificar la posición del potenciómetro
se debe ver la variación en la velocidad del motor.

PRÁCTICA 15: INTERRUPCIÓN POR TIMER0


Generar una secuencia de encendido de los LEDs de la placa Xpress que los
encienda de derecha a izquierda y de izquierda a derecha. El sentido de la
secuencia estará gobernado por el pulsador. Los retardos de tiempo deben ser
generados por interrupciones del Timer0.

Descripción:
Tomaremos el código realizado en la primera clase la cual genera la inversión
en la secuencia de encendido de los LEDs pero en lugar de utilizar retardos que

99
no realizan ninguna acción, usaremos un temporizador, en este caso el timer 0,
para generar los tiempos de muestreo del pulsador y del encendido de los
LEDs. Esto será aprovechado para explicar uno de los recursos del
Microcontrolador, las interrupciones.

Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa y el uso del pulsador, también se utilizara el temporizador
Timer0 para generar los retardos de la aplicación y utilizaremos el modulo de
interrupciones. En la siguiente Figura podemos ver un diagrama en bloque del
Timer 0 en el PIC16F18855, ya que tiene algunas diferencias con los timers de
otros PIC.

100
Este Timer posee distintos modos de funcionamientos, en esta práctica
utilizaremos el modo 8 bits con recarga.

101
Configuración del MCC

102
103
Código:
#include "mcc_generated_files/mcc.h"

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

104
// When using interrupts, you need to set the Global and Peripheral Interrupt Enable
bits
// Use the following macros to:

// Enable the Global Interrupts


INTERRUPT_GlobalInterruptEnable();

// Enable the Peripheral Interrupts


//INTERRUPT_PeripheralInterruptEnable();

// Disable the Global Interrupts


//INTERRUPT_GlobalInterruptDisable();

// Disable the Peripheral Interrupts


//INTERRUPT_PeripheralInterruptDisable();

while (1)
{
// Add your application code
}
}

En la función principal, únicamente se inicializan los periféricos del sistema con


la función generada por el MPLAB Code Configurator. Luego de configurar y
habilitar las interrupciones del Timer 0 desde el MCC, en el programa principal
debemos descomentar la función que habilita las interrupciones de manera
global. Si lo que queremos es también habilitar las interrupciones de los
periféricos, también debemos descomentar la función que lo habilita, pero en
este caso se generan interrupciones por Timer 0 que es considerado una
interrupción del sistema, como se vio en la clase. Luego simplemente la
aplicación se queda en el loop infinito esperando las interrupciones del Timer
0.

Rutina de interrupción del Timer 0


void TMR0_ISR(void)
{
static bool rotateLeft = true;
static int contador = 0;
static unsigned char rotateReg = 1;

// add your TMR0 interrupt custom code


if(BUTTON_Activated())
rotateLeft = !rotateLeft;

contador++;

105
if(contador == 10)
{
//reseteo contador
contador = 0;
if(rotateLeft)
{
rotateReg <<= 1;
if(rotateReg == 16)
rotateReg = 1;
}
else
{
rotateReg >>= 1;
if(rotateReg == 0)
rotateReg = 8;
}
LED_LoadRegister(rotateReg);
}

// clear the TMR0 interrupt flag


PIR0bits.TMR0IF = 0;
}

Al generarse los archivos desde el MCC, este crea el archivo tmr0.h, el cual
contiene la definición de la rutina de interrupción del Timer 0. Allí debemos
agregar el código a ejecutar cuando se generan las interrupciones. El código
agregado es básicamente el mismo código utilizado en la clase 1 para la
inversión de rotación, pero como ahora se genera en cada interrupción del
Timer, no es necesario utilizar la función de retardo, sino que simplemente
sabemos que esta porción de código se ejecuta en cada desborde del Timer.
Como en cada ejecución existen valores de variables que debemos conocer en
cada ejecución, se declaran variables del tipo estática, las cuales no se
destruyen al terminar la función, sino que retienen su valor para un siguiente
llamado de la función, de este modo podemos mantener los valores del
contador, el registro de rotación y la variable booleana que indica la dirección
de rotación.

Esta rutina de interrupción utiliza las funciones que encienden el LED,


LED_LoadRegister(), y las funciones que detectan la activación del pulsador,
BUTTON_IsPressed() y BUTTON_Activated(). Estas funciones no son accesibles
desde el archivo tmr0.c si estos se encuentran definidos en el archivo main.c,

106
por ello estas funciones fueron definidas y declaradas en los archivos
pin_manager.c y pin_manager.h.

PRÁCTICA 16: USART - TRANSMISIÓN


En esta práctica se verán los pasos para crear un proyecto en donde
utilizaremos el modulo USART para transmitir los valores de la conversión del
potenciómetro.

Elementos Necesarios:
Para esta práctica es necesario el software TeraTerm para pode recibir los
caracteres que se transmiten desde el microcontrolador

1) Creamos un nuevo proyecto que llamaremos “usart”.

2) Ejecutamos el MCC v3 y configuramos el Clock del sistema como


oscilador interno a 1MHz y habilitamos el LVP.

107
3) Luego vamos a configurar el conversor ADC para que tome la tensión
entregada por el potenciómetro de la placa. Agregamos el modulo ADC
desde la ventana de Device Resources y configuramos el ADC para que
la fuente de clock sea FOSC/4, la tensión de referencia positiva sea
VDD y la justificación de la conversión debe ser a derecha. Después en
la ventana PIN Manager seleccionamos el PIN RC0 como entrada
analógica y le ponemos el nombre POT1.

108
4) A continuación vamos a la ventana de Recursos del Dispositivo y
buscamos el Periférico USART >> USART. Esto agregará el periférico
USART a la ventana de Recursos del Proyecto.

109
5) Configuramos la comunicación USART para que sea Asincrónico, con
un baud rate de 9600 y habilitamos la transmisión. También
habilitamos la redirección del STDIO (Standar Input/Output) hacia la
USART del micro, así de este modo podemos utilizar funciones como el
printf para escribir sobre la USART.

110
6) Luego seleccionamos los pines del Microcontrolador que se utilizaran
para la transmisión. El pin RC0 se utilizara para la transmisión (TX).

7) Una vez que se terminan de configurar los periféricos del PIC, hacemos
click en Generate, para que se realicen los fuentes del proyecto.
Abrimos el archivo main.c y colocamos el siguiente código.

111
void main(void)
{
// initialize the device
SYSTEM_Initialize();

while (1)
{
// Add your application code
uint16_t value_pot = ADCC_GetSingleConversion (POT1);
printf("Resultado Conversion = %d\r\n", value_pot);
}
}

La función main comienza como siempre, configurando los periféricos


necesarios del PIC, allí se configura el conversor ADC y la USART. Luego
en el Loop tomamos el resultado de la conversión del ADC en el canal
donde está conectado el potenciómetro y lo almacena en la variable
value_pot. Este valor es pasado como parámetro de la función printf
que transmitará los caracteres ASCII por la USART. Esto se repite
constantemente por lo que en el terminal se verán varios mensajes
llegando cada vez que se termine una conversión.

8) Compilamos el proyecto y lo grabamos en el PIC. Para poder probarlo,


debemos hacer uso del Software TeraTerm. Luego seleccionar los
parámetros de la comunicación, el software permite elegir la velocidad
en baudios, debemos seleccionar 9600, el modo de mostrar los datos
lo dejamos en ASCII y los parámetros son 8 bits de datos, sin paridad y
un bit de Stop.

112
113
PRÁCTICA 17: RECEPCIÓN USART
En esta práctica se verán los pasos para crear un proyecto en donde
realizaremos la transmisión y recepción de un carácter, simulando un eco local,
pero desde el PIC.

1) Comenzamos creando un nuevo proyecto que llamaremos “usart_rec”

2) Ejecutamos el MCC v3 y comenzamos configurando el oscilador del


sistema. Nuevamente configuramos el oscilador interno a 1MHz y el
LVP habilitado.

114
3) Luego seleccionamos el modulo USART de la ventana de Recurso del
Dispositivo. Allí seleccionamos el modo asincrónico para la USART,
configuramos el BaudRate a 9600 y habilitamos la transmisión y la
recepción continua. También activamos la redirección del STDIO a la
UART, para poder utilizar funciones de la stdio.h.

115
4) Luego configuramos los pines para la transmisión y recepción, en este
volvemos a seleccionar el pin RB5 para la recepción y el pin RB7 para la
transmisión.

5) Luego apretamos en Generate, para generar los archivos de


configuración y el main y cerramos el MCC v3. Abrimos main.c y
escribimos el siguiente código:

116
void main(void) {
// initialize the device
SYSTEM_Initialize();

printf("Eco de caracteres\r\n");
while (1) {
// Add your application code
char caracter;
//Espero la llegada de un caracter
caracter = EUSART_Read();
//Lo escribo por la USART, simulando un eco
EUSART_Write(caracter);
}
}

En la función principal, como siempre, está el llamado al método


SYSTEM_Initialize(); para configurar los módulos del PIC, en este caso
los pines GPIO y el módulo USART. Luego hay un mensaje que se
imprime solamente al iniciar el micro, y luego, dentro del bucle
principal, lo que se hace es esperar la llegada de un carácter, utilizando
la función EUSART_Read();. Una vez que se recibe un carácter, este se
envía por la USART nuevamente mediante la función EUSART_Write.

6) Compilar el proyecto y probar que funcione, en la ventana del


software TeraTerm debemos poder escribir caracteres por la USART y
volver a recibirlos, esto lo imprimirá en pantalla, simulando un Eco
Local.

117
118
PRÁCTICAS AVANZADAS

PRÁCTICA 18: LECTURA DE VARIOS CANALES ADC


Generar un programa que realice la lectura de varios canales del conversor
ADC aprovechando las herramientas de diseño provistas por las técnicas de
maquinas de estados finitas (FSM). Los valores de conversión se mostraran en
los LEDs de la placa.

Descripción:
En esta práctica haremos uso de las técnicas de máquina de estados finitas
para obtener la conversión de diferentes canales del conversor ADC,
multiplexando los canales de entrada y que las conversiones no sean
bloqueantes, es decir que estas conversiones se puedan realizar en segundo
plano, mientras se realizan otras tareas.

Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa, el Timer 0 para generar los retardos y el conversor ADC.

Configuración del MCC

119
120
Código:
#include "mcc_generated_files/mcc.h"

/*** ADC States **************************************************/


typedef enum
{
ADC_STATE_START,
ADC_STATE_CONVERT,
ADC_STATE_END_CONVERTION
} ADC_STATE;

//Estructura para almacenar los valores de la lectura del ADC en la


//la placa trackme
typedef struct
{
adc_result_t potenciometer;
adc_result_t channel_anb0;
adc_result_t channel_anb1;
adc_result_t channel_anb5;
adcc_channel_t channel_actual;
adcc_channel_t channel_display;
} sADCResult;

121
/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

//Creo Variable de estado y la inicializo


ADC_STATE adc_state = ADC_STATE_START;
//Estructura donde se guarda los resultados
sADCResult resultado_adc;
//Inicio channel display
resultado_adc.channel_display = POT;

//Puntero que contiene el valor a mostrar


adc_result_t* pADC_result = &resultado_adc.potenciometer;

while (1)
{
// Add your application code
//Maquina de Estado del conversor ADC
switch(adc_state)
{
case ADC_STATE_START:
{
//Estado Inicial
//Si por algun motivo se llega a este estado y hay una conversion
//en proceso, espero a que este termine.
if(!ADCC_IsConversionDone())
break; //Salgo de la maquina sin cambiar de estado.
//Inicializamo estructura de datos
resultado_adc.channel_actual = POT;

//Transicion al siguiente estado


//Inicio conversion ADCC
ADCC_StartConversion(resultado_adc.channel_actual);
adc_state = ADC_STATE_CONVERT;
break;
}
case ADC_STATE_CONVERT:
{
//Estado Convirtiendo
//Observo si termino la conversion
if(!ADCC_IsConversionDone())

122
break; //Salgo de la maquina sin cambiar de estado.

//Termino conversion, guardo el resultado y transiciono al siguiente estado


switch(resultado_adc.channel_actual)
{
case POT:
{
//Guardo conversion
resultado_adc.potenciometer = ADCC_GetConversionResult();
//cambio de canal
resultado_adc.channel_actual = ANB0;
break;
}
case ANB0:
{
//Guardo conversion
resultado_adc.channel_anb0 = ADCC_GetConversionResult();
//cambio de canal
resultado_adc.channel_actual = ANB1;
break;
}
case ANB1:
{
//Guardo conversion
resultado_adc.channel_anb1 = ADCC_GetConversionResult();
//cambio de canal
resultado_adc.channel_actual = ANB5;
break;
}
case ANB5:
{
//Guardo conversion
resultado_adc.channel_anb5 = ADCC_GetConversionResult();
//cambio de canal
resultado_adc.channel_actual = POT;
break;
}
}
adc_state = ADC_STATE_END_CONVERTION;

}
case ADC_STATE_END_CONVERTION:
{
//Transicion al siguiente estado
//Inicio conversion ADCC
ADCC_StartConversion(resultado_adc.channel_actual);

123
adc_state = ADC_STATE_CONVERT;
break;
}
default:
//Si la maquina de estados aparece en cualquier lugar la reseteo
adc_state = ADC_STATE_START;
}

//Pulsador
if(TMR0_HasOverflowOccured())
{
//Cada 50mseg.
if(BUTTON_Activated())
{
//Cambio de canal
switch(resultado_adc.channel_display)
{
case POT:
pADC_result = &resultado_adc.channel_anb0;
resultado_adc.channel_display = ANB0;
break;
case ANB0:
pADC_result = &resultado_adc.channel_anb1;
resultado_adc.channel_display = ANB1;
break;
case ANB1:
pADC_result = &resultado_adc.channel_anb5;
resultado_adc.channel_display = ANB5;
break;
case ANB5:
pADC_result = &resultado_adc.potenciometer;
resultado_adc.channel_display = POT;
break;
}
}
LED_LoadRegister((*pADC_result) >> 6);

// clear the TMR0 interrupt flag


PIR0bits.TMR0IF = 0;
}
}

El código principal comienza definiendo una variable enumerada que lista la


cantidad de estados que tendrá la maquina, en este caso serán el estado

124
inicial, el de conversión y el de fin de conversión. Luego se define una
estructura que guardara el resultado de las conversiones de los distintos
canales del AD y además contiene unas variables que guardan el canal actual
de conversión y el canal actual que se está mostrando en el display de LEDs.

A continuación comienza el programa principal y con el se inicia la maquina de


estados, guardando en la varuiable adc_state, que es la variable de almacena
el estado de la maquina, el estado inicial, se configura en la estructura el canal
a mostrar en cannel_display iniciando en el canal del potenciómetro y se
define un puntero que apunta a uno de las variables que almacena el
resultado, en este caso el del potenciómetro, que es el primer canal a
muestrear.

Luego se inicia la máquina de estados en el loop principal y se inicia la


conversión del ADC. Luego de esto, la maquina pasa al estado CONVIRTIENDO,
donde se queda esperando la finalización de la conversión. Una vez finalizada
la conversión, se realiza la transición hacia el estado FIN_CONVERSION, y
durante esa transición, se modifica el actual canal de conversión al siguiente,
así al iniciar una nueva conversión, se obtiene desde el nuevo canal. Esto se
realiza indefinidamente para obtener la conversión de los canales que se
especificaron en la configuración.

Luego en paralelo a estas conversiones se realiza el muestreo del pulsador


cada 50 milisegundos, de la misma manera que lo realizamos en ejercicios
anteriores, pero con la salvedad de que los retardos son generados por el
timer 0. Si se detecta la activación del pulsador, este modifica el canal que se
muestra en el display de LEDs, multiplexando entre los distintos canales cada
vez que se oprime el pulsador. Observese que cada vez que se activa el
pulsador, se modifica el puntero pADC_result para tomar los valores del canal
apuntado por esta variable.

PRÁCTICA 19: RECEPCIÓN POR USART UTILIZANDO MÉTODO DE


POLLING
Realizar un programa que reciba un carácter hexadecimal del terminal y que el
valor lo muestre en binario en los LEDs de la placa Xpress. Utilizar el método de
recepción por polling

125
Descripción:
Comenzaremos a ver la recepción de datos desde la USART utilizando el
método de polling explicado en la clase, donde haremos uso nuevamente de
una maquina de estado para recibir los caracteres y no generar tareas
bloqueantes y poder ejecutar procesos en paralelo. Veremos cómo recibir un
dato y detectar un carácter de control que indique el final del mensaje, que en
este caso será el la tecla Enter. Si es posible, se debe configurar el terminal
para que envié el carácter de final de línea solamente con un CR(Carry Return)
o un LF(Line Feed) y no con ambas, para simplificar el código.

Periféricos Utilizados:
En esta práctica se utilizaran los pines GPIO para mostrar el valor binario del
carácter hexadecimal recibido y el modulo USART para comunicarse con una
PC vía USB.

Configuración MCC:

126
Código:
#include "mcc_generated_files/mcc.h"

typedef enum

127
{
REC_INIT_STATE,
REC_WAIT_STATE,
REC_PROCESS_STATE
} RECEIVE_STATE;

//Variables Global
char receive_data;

bool process_data = false;

//Prototipos
void StateMachine_ReceptionUSART(void);

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();
printf("Ingrese un valor entre 0 y F y presione Enter\r\n");
while (1)
{
// Add your application code
//Corro la maquina de estados para recibir caracter.
StateMachine_ReceptionUSART();

//Chequeo la recepcion de un mensaje


if(process_data)
{
//borro el flag
process_data = false;
//Chequeo si el dato es valido
if( receive_data < '0' || receive_data > 'F')
{
printf("\r\nValor erroneo, ingrese caracteres de 0 a F\r\n");
}
else
{
//al valor recibido en ascii lo convierto entero.
char led_data;
if(receive_data > '9')
//'A' es 65 en ASCII, por ello se resta 55, para que el valor de A = 10
led_data = receive_data - 55;

128
else
led_data = receive_data - '0';

//printf("led_data = %d\r\n", led_data);


//Cargo el valor en los LEDs de la placa
LED_LoadRegister(led_data);
}
}
}
}

Este el código de la función principal, en donde se envía un mensaje de


bienvenida indicando que se debe enviar un carácter hexadecimal, luego en el
loop principal se comienza a ejecutar la máquina de estado que recibe los
caracteres por USART, que lo veremos a continuación. Una vez recibido el
mensaje, este se guarda en el buffer receive_data con el valor en Hexadecimal
ingresado por el usuario. Este carácter recibido se verifica que se encuentra
dentro del rango de valores pedidos, si es asi, si el carácter es un numero de 0
a 9, se le resta el ASCII del número cero ‘0’, en cambio se es un valor entre A y
F, s le resta 55 en decimal (ya que el valor ASCII de ‘A’ es 65, menos los 55,
obtenemos 10 en decimal). El resultado de esas restas se envía a los LEDs para
mostrarlos.

Maquina de Estado de la Recepción:


void StateMachine_ReceptionUSART(void)
{
char data;
static RECEIVE_STATE rec_state = REC_INIT_STATE;
//Tarea que monitorea la llegada de un mensaje

switch(rec_state)
{
case REC_INIT_STATE:
{
rec_state = REC_WAIT_STATE;
}
case REC_WAIT_STATE:
{
//Si no llego ningun caracter salgo sin cambiar el estado
if(!EUSART_DataReady) break;
data = getch();
//Borro flag de recepcion
PIR3bits.RCIF=0;

129
//Imprimo caracter recibido
putch(data);
//Cambio de estado
rec_state = REC_PROCESS_STATE;
}
case REC_PROCESS_STATE:
{
if(data == '\n' || data == '\r')
{
//Activo Flag
process_data = true;
}
else
{
receive_data = data;
}
}
default:
rec_state = REC_INIT_STATE;
}
}

En el código anterior vemos la máquina de estado de la recepción de


caracteres por el puerto USART. La función define mediante una variable
estática rec_state cual es el estado actual. El estado inicial solamente realiza la
transición al estado de ESPERA de llegada de un carácter. Mientras
EUSART_DataReady sea falso, nos quedaremos en este estado, si es
verdadero, cambiamos al estado PROCESS, y en la transición almaceno el
carácter recibido y lo transmito para generar el eco. En el siguiente estado
verifico si el dato no es un final de línea, en ese caso, se recibió el carácter por
parte del usuario y se activa un flag indicando que el dato ya fue procesado.
Este flag se toma en la función principal para determinar la llegada de un
carácter.

PRÁCTICA 20: RECEPCIÓN DE COMANDOS POR USART


Generar un programa que realice la recepción de comandos del tipo LED1=ON
o LED2=OFF para encender o apagar los LEDs de la placa XPRESS. Para la
recepción de datos utilizar el método de interrupciones y para transmitir
también.

130
Descripción:
En esta práctica veremos cómo realizar la transmisión y la recepción de
caracteres utilizando las interrupciones del PIC. Además veremos cómo realizar
el procesamiento de los datos recibidos para poder desglosar los campos y
quedarnos con los comandos. Hay que destacar que en la configuración del
MCC podemos seleccionar un buffer generado por software en donde se
guardan os valores hasta que realizamos la lectura. El tamaño del buffer es
configurable y se realiza mediante el MCC. Una vez que terminamos de
configurar el modulo USART por interrupciones en el MCC, vamos a hacer uso
de las rutinas de interrupción que genera este programa, ya que estas son
suficientes para recibir y transmitir datos, sin necesidad de realizar alguna
modificación.

Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa y el modulo de comunicación USART.

131
Configuración del MCC

132
133
Código:
#include "mcc_generated_files/mcc.h"
#include <string.h>
/**
Section: Defines
*/
#define BUFFER_SIZE 20

/**
Section: Prototypes
*/
bool ProcesarDatos(char* buffer_datos);
int GetStatusLed(char* aux);
/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

134
// When using interrupts, you need to set the Global and Peripheral Interrupt Enable
bits
// Use the following macros to:

// Enable the Global Interrupts


INTERRUPT_GlobalInterruptEnable();

// Enable the Peripheral Interrupts


INTERRUPT_PeripheralInterruptEnable();

//Buffer de recepcion de datos


char receive_buffer[BUFFER_SIZE];
int buffer_count = 0;

printf("*-------Recepcion de Comandos por USART------*\r\n\r\n");


printf("Escriba un comando del tipo LED1=ON o LED2=OFF\r\n");
while (1)
{
// Add your application code
if(EUSART_DataReady)
{
//Guardar caracter
char data = getch();
if(data == '\r' || data == '\n')
{
//Fin del mensaje, termino el string
receive_buffer[buffer_count] = '\0';
//reinicio contador de caracteres
buffer_count = 0;
//Procesar datos
if(!ProcesarDatos(receive_buffer))
printf("\r\nComando no valido\r\n");
else
printf("\r\n");
}
else
{
receive_buffer[buffer_count++] = data;
putch(data);
}

}
}
}

135
Comenzaremos examinando el código de la función principal en donde se
habilitan las interrupciones globales y la de los perifericos. Una vez terminada
la configuración y activado las interrupciones, se comienza con el mensaje de
bienvenida y en el loop principal, se pregunta por el estado de la USART
consultando la constante EUSART_DataReady, provisto por la librería usart del
MCC. Cuando este devuelve true, indica que se recibió un carácter y por lo
tanto debemos leer el dato, si el dato es distinto al final de línea, se guarda en
un buffer. En cambio, si el dato recibido es un final de línea, entonces armo el
string, reseteo el contador de caracteres recibidos y realizo el procesamiento
de los datos.

Funciones que procesan los datos:


bool ProcesarDatos(char* buffer_datos)
{
char* aux;

//Divido el String
aux = strtok(buffer_datos, "=");
aux = strtok(NULL, "=");

if(aux == NULL)
return false;

if(strstr(buffer_datos,"LED1")!= NULL)
{
D2_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED2")!= NULL)
{
D3_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED3")!= NULL)
{
D4_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED4")!= NULL)
{
D5_LAT = GetStatusLed(aux);
}
else
{
return false;
}

136
return true;
}

int GetStatusLed(char* aux)


{
if(strstr(aux,"ON") != NULL)
return HIGH;
else if(strstr(aux,"OFF") != NULL)
return LOW;

return LOW;
}

Cuando se termina de recibir el dato completo por parte del usuario, se llama a
la función ProcesarDatos() el cual toma el string y lo psa por la función strtok().
Esta función toma como parámetro un token, en esta caso se le pasa el signo
igual (=), y la función se encarga de buscar este token por el string y en cada
uno de los lugares encontrados, le coloca el carácter nulo, destruyendo el
string original. La función devuelve un puntero al string inicial y en cada
llamado sucesivo en donde se le pasa como parámetro el puntero nulo (NULL)
la función devolverá un puntero al siguiente string que se genero en la
separación, y así sucesivamente. De esta manera se logra separar, por ejemplo,
el comando LED1=ON en dos string, por un lado LED1 y por el otro ON. Luego
de separarlos, el primer string se compara con la función strstr() para saber a
que LED se esta accediendo, o si no reconoce ningún comando, la función
devuelve false. Si se encuentra a que LED se dirige el comando, luego la
función GetStatusLed(), devuelve true si la palabra es “ON” o false si es
cualquier otra palabra. Luego, de acuerdo al resultado de esta función, se
realiza el encendido o apagado del LED correspondiente.

PRÁCTICA 21: USUARIO Y CONTRASEÑA EEPROM INTERNA


Lo que se pretende realizar en esta práctica es tomar el código realizado en la
practia anterior, en donde se podía controlar el encendido y apagado de los
LEDs mediante comandos por la USART, y se le quiere añadir una autenticación
inicial del tipo usuario y contraseña. De esta manera, al iniciar el programa, se
le debe pedir al usuario un nombre y una contraseña para continuar con la
ejecución del mismo. Los nombres de usuario y contraseña no deben tener
más de 20 caracteres y por defecto, es decir, si no se configuro todavía un
usuario, los valores deben ser ‘admin’ – ‘admin’.

Descripción:

137
Tomaremos el código realizado en la clase anterior para enviar comandos y le
agregaremos una validación inicial del tipo usuario-contraseña. Se debe
realizar primero una verificación inicial de la memoria, si este no tiene escrito
un nombre de usuario, se utilizara por defecto el par de nombres ‘admin-
admin’ de manera inicial. El programa no debe continuar con la ejecución
hasta que se hayan colocado correctamente los valores de usuario y
contraseña. Una vez que se supera esta validación, se ejecuta el programa
visto en la clase anterior donde se procesan comandos para encender los LEDs
individualmente. Para agregar la configuración de un usuario y una contraseña,
se debe agregar el comando “SETUP” desde el cual se desplegara en pantalla
una guía para configurar un nombre de usuario nuevo y una contraseña nueva.
Luego al pagar la placa y volver a encender, se debe ingresar los nuevos valores
de usuario y contraseña especificados anteriormente.

Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa, el uso de la USART para la comunicación con el usuario y la
memoria EEPROM interna para almacenar los datos de manera no volátil.

Configuración del MCC

138
139
Código:
#include "mcc_generated_files/mcc.h"
#include <string.h>
/*
Defines
*/
#define BUFFER_SIZE 21
#define SIZE_STRING_USER_PASS 21
#define ADDRESS_USER_EEPROM_INT 0x7000
#define ADDRESS_PASS_EEPROM_INT ADDRESS_USER_EEPROM_INT +
SIZE_STRING_USER_PASS + 1

/*
Prototypes
*/
void ReadMemory(char* string, uint16_t address);
void WriteMemory(char* string, uint16_t address);
void RecibirMensajeUSART(char * receive_buffer);
bool CheckUserPass(char* user, char* pass);
void ConfigUserPass(void);
bool ProcesarDatos(char* buffer_datos);
int GetStatusLed(char* aux);

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

// Enable the Global Interrupts


INTERRUPT_GlobalInterruptEnable();

// Enable the Peripheral Interrupts

140
INTERRUPT_PeripheralInterruptEnable();

//Defino strings para usuarios y password


char user[SIZE_STRING_USER_PASS];
char pass[SIZE_STRING_USER_PASS];

//Leer memoria EEPROM para obtener user y pass


ReadMemory(user, ADDRESS_USER_EEPROM_INT);
ReadMemory(pass, ADDRESS_PASS_EEPROM_INT);

if( strlen(user) == 0)
{
//No hay usuario definido, uso usuario Default
strcpy(user, "admin");
strcpy(pass, "admin");
}
//DEBUG
printf("**DEBUG** user:%s pass:%s\r\n", user, pass);
//Mensaje inicial
printf("*-------Recepcion de Comandos por USART------*\r\n\r\n");
//Nos quedamos en este loop hasta recibir al usuario correcto
while(!CheckUserPass(user, pass));

printf("Escriba un comando del tipo LED1=ON o LED2=OFF\r\n");


printf("Escriba SETUP para configurar usuario y contraseña\r\n");

//Buffer de recepcion de datos


char receive_buffer[BUFFER_SIZE];
int buffer_count = 0;

while (1)
{
// Add your application code
if(EUSART_DataReady)
{
//Guardar caracter
char data = getch();
if(data == '\r' || data == '\n')
{
//Fin del mensaje, termino el string
receive_buffer[buffer_count] = '\0';
//reinicio contador de caracteres
buffer_count = 0;
//Procesar datos
if(!ProcesarDatos(receive_buffer))
printf("\r\nComando no valido\r\n");

141
else
printf("\r\n");
}
else
{
receive_buffer[buffer_count++] = data;
putch(data);
}

}
}
}

La función principal comienza haciendo una lectura de los datos almacenados


en la memoria EEPROM interna. Luego se realiza un chequeo observando si
existe algún nombre de usuario que ya este configurado, si no lo está, es decir,
el tamaño del array de usuarios es cero, se utilizan los valores por defecto que
son el par admin-admin. Una vez obtenido el nombre de usuario y contraseña
se envía el mensaje de verificación en la pantalla, en donde la función
CheckUsersPass(); se queda esperando a que se coloquen los nombres de
usuario y contraseña correctos. Si los datos ingresados coinciden con el
almacenado en memoria, entonces la función entrega el valor True, en caso
contrario entrega el valor False.

Luego el bucle principal es igual al realizado en el ejercicio de comandos por


USART de la clase anterior en donde se espera un comando completo por
parte del usuario para procesarlo, pero con el agregado del comando SETUP,
que inicia la configuración del usuario y contraseña nuevos.

Funciones Auxiliares
void RecibirMensajeUSART(char * receive_buffer)
{
int buffer_count = 0;
char data;
//Comienzo con la recepcion de datos
do
{
while(!EUSART_DataReady);
//Guardar caracter
data = getch();
if(data == '\r' || data == '\n')
{
//Fin del mensaje, termino el string

142
receive_buffer[buffer_count] = '\0';
//reinicio contador de caracteres
buffer_count = 0;
}
else
{
receive_buffer[buffer_count++] = data;
putch(data);
}

}while(data != '\n' && data != '\r');

Esta función es la encargada de recibir un mensaje desde la USART y


almacenarlo en el buffer que se le pasa como referencia. Esta función es
bloqueante, es decir, que hasta que no se oprima enter en la pantalla, no se va
salir de la misma. La idea de esta función es utilizarla para la configuración de
un nuevo usuario y una nueva contraseña.

bool CheckUserPass(char* user, char* pass)


{
char user_receive[SIZE_STRING_USER_PASS];
char pass_receive[SIZE_STRING_USER_PASS];

printf("Ingrese nombre de usuario: ");


RecibirMensajeUSART(user_receive);
printf("\r\nIngrese contraseña: ");
RecibirMensajeUSART(pass_receive);

if(!strcmp(user, user_receive))
{
if(!strcmp(pass, pass_receive))
{
printf("\r\nUsuario y contraseña Correcta!\r\n");
return true;
}
else
{
printf("\r\nContraseña Incorrecta!\r\n");
return false;
}
}
else

143
{
printf("\r\nUsuario Incorrecto!\r\n");
return false;
}
}

Esta función realiza la secuencia de verificación de un usuario y contraseña, se


le pide al usuario que coloque su nombre y a continuación una contraseña
valida, esto se realiza mediante la función RecibirMensajeUSART() que es
bloqueante y esperar a recibir el nombre completo junto con el Enter y la
contraseña. Una vez ingresado los valores se verifican con los almacenados en
memoria para ejecutar la validación, si son correctos, la función devuelve True,
en caso contario devolverá False.

void ConfigUserPass(void)
{
char user_receive[SIZE_STRING_USER_PASS];
char pass_receive[SIZE_STRING_USER_PASS];
char data;

printf("\r\nConfiguracion de nuevo usuario\r\nIngrese nuevo nombre de usuario: ");


RecibirMensajeUSART(user_receive);
printf("\r\nIngrese nueva contraseña: ");
RecibirMensajeUSART(pass_receive);
printf("\r\nVa a configurar el nuevo usuario: %s y la contraseña: %s\r\n",
user_receive, pass_receive);
printf("Desea Continuar? Presione Y para aceptar, cualquier otra tecla para
cancelar:");
data = getch();
if(data == 'Y' || data == 'y')
{
//Guardamos en memoria interna
WriteMemory(user_receive, ADDRESS_USER_EEPROM_INT);
WriteMemory(pass_receive, ADDRESS_PASS_EEPROM_INT);
printf("\r\nConfigurado nuevo Usuario\r\n");
}
else
{
printf("\r\nNo se configuro el nuevo usuario\r\n");
}

}
Esta es la función encargada de tomar el nuevo nombre de usuario y
contraseña y guardarla en la memoria EEPROM. Mediante simples mensajes se
va guiando al usuario a configurar un usuario y una contraseña nueva. Una vez

144
ingresado ambos valores, se consulta si se desea ejecutar la acción de
guardarlo en memoria oprimiendo la tecla “Y” en la PC. Si es así, se inicia la
secuencia de escritura en la EEPROM interna, donde se pasa los strings de
usuario y contraseña a la función WriteMemory()

//Funcion encargada de Leer de la memoria no volatil los datos y almacenarlos


//en un string
void ReadMemory(char* string, uint16_t address)
{
int indice = -1;
char data;

do
{
//incrmento indice, el primer valor sera 0
indice++;
//Utilizo funcion de lectura por byte de la EEPROM interna
data = DATAEE_ReadByte(address + indice);
string[indice]= data;
}while((data != 0)&& (data != 0xFF) );

//Si el ultimo dato no es el nulo, finalizo el string con este valor


if(string[indice] != 0)
string[indice] = '\0'; //Finalizo el string con el caracter nulo
}

//Funcion que escribe un string en la memoria


void WriteMemory(char* string, uint16_t address)
{
int indice = -1;
//Si el string esta vacio o es nulo, salimos de la funcion sin escribir ningun caracter
if((string[0] == 0) || (string == NULL))
return;

do
{
indice++;
//Utilizo funcion de escritura por byte de la EEPROM interna
DATAEE_WriteByte(address + indice, string[indice]);
//copio tambien el caracter nulo en la memoria EEPROM
}while(string[indice]);
}

145
Las funciones ReadMemory() y WriteMemory() son las encargadas de leer un
String y de escribir un String en la memoria EEPROM interna. Para leerlo hacen
uso de las funciones de la librería del MCC para lectura y escritura de un byte
en la EEPROM, las funciones son DATAEE_ReadByte() y DATEE_WriteByte().
Cabe aclarar que la función de escritura implementada copia el carácter nulo
en la memoria y la función de lectura devuelve el string con el carácter nulo al
final.

bool ProcesarDatos(char* buffer_datos)


{
char* aux;
//Agregado de nuevo comando
if(strstr(buffer_datos,"SETUP")!= NULL)
{
ConfigUserPass();
return true;
}

//Divido el String
aux = strtok(buffer_datos, "=");
aux = strtok(NULL, "=");
if(aux == NULL)
return false;

if(strstr(buffer_datos,"LED1")!= NULL)
{
D2_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED2")!= NULL)
{
D3_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED3")!= NULL)
{
D4_LAT = GetStatusLed(aux);
}
else if(strstr(buffer_datos,"LED4")!= NULL)
{
D5_LAT = GetStatusLed(aux);
}
else
{
return false;
}

146
return true;
}

int GetStatusLed(char* aux)


{
if(strstr(aux,"ON") != NULL)
return HIGH;
else if(strstr(aux,"OFF") != NULL)
return LOW;

return LOW;
}

Estas son las funciones que procesan los comandos que envía el usuario una
vez pasado la verificación inicial de usuario. La función es exactamente igual a
la vista en la práctica pasada, pero con el agregado de procesar la palabra
SETUP, en cuyo caso ejecutará la rutina ConfigUserPass() para agregar un
nuevo usuario y contraseña.

PRÁCTICA 22: SENSOR DE PULSO CARDIACO


En esta práctica veremos cómo configurar el conversor ADC y el timer para
realizar sucesivas muestras del sensor de pulso cardiaco y poder calcular el
tiempo entre picos y las pulsaciones Cardiacas.

Descripción
Para la realización de esta práctica haremos uso del conversor ADC trabajando
en modo básico para tomar muestras del sensor analógico. Las muestras
deben ser periódicas, y se realizaran cada dos milisegundos, por lo que se hará
uso del timer 1 para generar interrupciones periódicas. Cada muestra sucesiva
del sensor se almacena en un array y se detecta el pico de la señal, al detectar
donde se producen los picos, se calcula el tiempo que transcurre entre estos
picos, y mediante una simple cuenta, se obtienen las pulsaciones por minuto.

Periféricos Utilizados:
En esta práctica se utilizaran el conversor ADC en el modo básico y el Timer 1
para generar retardos periódicos cada 2mSegundos. También se utilizara el
modulo de interrupciones.

147
148
149
150
Código:
#include "mcc_generated_files/mcc.h"

// Variables
extern volatile int BPM; // int that holds raw Analog in 0. updated every 2mS
extern volatile int IBI;
extern volatile bool QS; // becomes true when Pic finds a beat.

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

// When using interrupts, you need to set the Global and Peripheral Interrupt Enable
bits
// Use the following macros to:

// Enable the Global Interrupts


INTERRUPT_GlobalInterruptEnable();

// Enable the Peripheral Interrupts


INTERRUPT_PeripheralInterruptEnable();

printf("----- Sensor de Pulso cardiaco ----\r\n\r\n");

while (1)
{
// Add your application code
//printf("loop");
while(QS == false);

// A Heartbeat Was Found


// BPM and IBI have been Determined
// Quantified Self "QS" true when pic finds a heartbeat
printf("Valor del Pulso cardiaco BPM: %i\r\n", BPM);
printf("Tiempo entre picos IBI: %i\r\n", IBI);

__delay_ms(1000);
//D4_Toggle();
}
}

151
En el archivo main.c se inicia declarando algunas variables externas, estas
variables son globales y están definidas en el archivo tmr1.c, que maneja las
interruciones del Timer. Estas variables almacenan el pulso cardiaco (BPM) el
intervalo de tiempo entre picos (IBI) y la variable que indica que ya están los
datos disponibles (QS). Luego en el Loop Principal, simplemente se pregunta
cada 1 segundo si los datos están disponibles y los imprime en pantalla.

Rutina de interrupción del Timer 1


#include <xc.h>
#include "tmr1.h"
#include "adcc.h"
#include <stdio.h>

// Variables

// Volatile Variables, used in the interrupt service routine!


volatile int BPM; // int that holds raw Analog in 0. updated every 2mS
volatile int Signal; // holds the incoming raw data
volatile int IBI = 600; // int that holds the time interval between beats! Must be
seeded!
volatile bool Pulse = false; // "True" when User's live heartbeat is detected. "False"
when not a "live beat".
volatile bool QS = false; // becomes true when Arduoino finds a beat.

volatile int rate[10]; // array to hold last ten IBI values


volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P =512; // used to find peak in pulse wave, seeded
volatile int T = 512; // used to find trough in pulse wave, seeded
volatile int thresh = 525; // used to find instant moment of heart beat, seeded
volatile int amp = 100; // used to hold amplitude of pulse waveform, seeded
volatile bool firstBeat = true; // used to seed rate array so we startup with
reasonable BPM
volatile bool secondBeat = false; // used to seed rate array so we startup with
reasonable BPM

void TMR1_DefaultInterruptHandler(void){
// add your TMR1 interrupt custom code
// or set custom function using TMR1_SetInterruptHandler()
INTCONbits.GIE = 0; // disable interrupts while we do this
TMR1_StopTimer();
TMR1_Reload();
//TMR1_StartTimer();
Signal = ADCC_GetSingleConversion(channel_pulse_heart);

152
// read the Pulse Sensor
sampleCounter += 8;
// keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime;
// monitor the time since the last beat to avoid noise

// find the peak and trough of the pulse wave


if(Signal < thresh && N > (IBI/5)*3){
// avoid dichrotic noise by waiting 3/5 of last IBI
if (Signal < T){ // T is the trough
T = Signal;
// keep track of lowest point in pulse wave
}
}

if(Signal > thresh && Signal > P){


// thresh condition helps avoid noise
P = Signal; // P is the peak
}
// keep track of highest point in pulse wave

// NOW IT'S TIME TO LOOK FOR THE HEART BEAT


// signal surges up in value every time there is a pulse
if (N > 250)
{ // avoid high frequency noise
//printf("N>250\r\n");
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
Pulse = true;
// set the Pulse flag when we think there is a pulse
IBI = sampleCounter - lastBeatTime;
// measure time between beats in mS
lastBeatTime = sampleCounter;
// keep track of time for next pulse

//printf("IBI:%i-N:%i\r\n",IBI, N);

if(secondBeat){
// if this is the second beat, if secondBeat == TRUE
secondBeat = false; // clear secondBeat flag
for(int i=0; i<=9; i++){
// seed the running total to get a realisitic BPM at startup
rate[i] = IBI;
}
}

if(firstBeat){

153
// if it's the first time we found a beat, if firstBeat == TRUE
firstBeat = false; // clear firstBeat flag
secondBeat = true; // set the second beat flag
INTCONbits.GIE = 1; // enable interrupts again

TMR1_StartTimer();
return; // IBI value is unreliable so discard it
}

// keep a running total of the last 10 IBI values


int runningTotal = 0; // clear the runningTotal variable

for(int i=0; i<=8; i++){


// shift data in the rate array
rate[i] = rate[i+1];
// and drop the oldest IBI value
runningTotal += rate[i];
// add up the 9 oldest IBI values
}

rate[9] = IBI; // add the latest IBI to the rate array


runningTotal += rate[9];
// add the latest IBI to runningTotal
runningTotal /= 10;
// average the last 10 IBI values
BPM = 60000/runningTotal;
// how many beats can fit into a minute? that's BPM!
QS = true; // set Quantified Self flag
// QS FLAG IS NOT CLEARED INSIDE THIS ISR
}
}

if (Signal < thresh && Pulse == true){


// when the values are going down, the beat is over
Pulse = false;
// reset the Pulse flag so we can do it again
amp = P - T; // get amplitude of the pulse wave
thresh = amp/2 + T; // set thresh at 50% of the amplitude
P = thresh; // reset these for next time
T = thresh;
//printf("no pulse\r\n");
}

if (N > 2500){ // if 2.5 seconds go by without a beat


thresh = 512; // set thresh default
P = 512; // set P default

154
T = 512; // set T default
lastBeatTime = sampleCounter;
// bring the lastBeatTime up to date
firstBeat = true; // set these to avoid noise
secondBeat = false; // when we get the heartbeat back
}

INTCONbits.GIE = 1; // enable interrupts when youre done!


TMR1_StartTimer();
}

Esta es la rutina de interrupción del Timer 1, el cual se ejecuta cada 2


milisegundos. Esta rutina comienza deshabilitando cualquier interrupción, y
luego se inicia la conversión del ADC, en el canal del sensor de pulso. El valor
de la conversión es almacenada en la variable Signal y luego se ejecutan un
algoritmo para obtener el pico de la señal y en cada uno de estos picos se va
tomando el tiempo en el que ocurre, de esta manera se obtiene el intervalo de
tiempo entre picos. Estos valores son almacenados en un array de 10
posiciones, para obtener el valor medio. Con este parámetro, ya se puede
calcular el BPM, calculando cuantos picos se pueden tener por minuto. Una vez
que se obtienen estos valores, se habilita el flag QS para que la rutina principal
pueda actualizar los nuevos datos.

PROYECTO INTEGRADOR

Introducción:
Haremos uso de los conocimientos adquiridos durante el curso para realizar un
sistema de monitoreo de temperatura inteligente utilizando el dispositivo
EMC1001, el cual nos permite no solo monitorear la temperatura de un
equipo, sino que además podemos configurarles valores de temperaturas
Limites con la cual accionar algún sistema de refrigeración si existe algún
sobrecalentamiento.

Proyecto Final: Sistema de Monitoreo de Temperatura


Lo que se pretende realizar en esta práctica es un sistema de Monitoreo de
temperatura utilizando el sensor EMC1001. También el sistema debe poder

155
configurar en valor de Temperatura Limite, el cual si es superado, debe
activarse un sistema de refrigeración, que en este caso será emulado por un
LED.

Descripción:
Utilizaremos los recursos que provee el sensor de temperatura EMC1001, el
cual se comunica por el protocolo de comunicación serie SMBus. Con este
dispositivo no solo podemos obtener el valor actual de la temperatura, sino
que también podemos configurarle un valor de temperatura limite, en el cual,
si la medición supera este valor, se activara el pin de ALARM-/THERM el cual
puede utilizarse para encender un sistema de refrigeración para controlar la
temperatura del dispositivo. Para la práctica vamos a detectar si se activo esta
alarma mediante el sensado del estado en el pin RA7 del PIC e indicarlo con un
LED el estado de la activación. Para detectar el cambio de estado del pin
ALARM-/THERM, vamos a configurar una interrupción por cambio de estado
(IOC) en el pin RA7. Las comunicaciones con el modulo EMC1001 serán por
medio del protocolo SMBus, que es muy similar al I²C, y cuya dirección de
esclavo es el valor H38. Este valor es sacado de la hoja de datos junto con el
valor de la resistencia de pull-up en el pin ALARM-/THERM. Mediante lecturas
vamos a leer el estado del conversor, cabe destacar que el código de lectura
del estado lo obtuvimos desde un código fuente en los ejemplos realizados por
la comunidad de Microchip para el MPLABXpress. También agregaremos la
posibilidad de configurar el valor de temperatura limite, llamado THERM Limit
y su valor de hysteresis para la activación y desactivación de la alarma de
temperatura. En la siguiente figura se puede ver el comportamiento del pin
ALARM-/THERM cuando la temperatura supera el umbral limite y el
funcionamiento de la hysteresis.

156
Periféricos utilizados:
Para esta práctica se utilizaran los pines GPIO para activar el encendido de los
LEDs de la placa y poder detectar el cambio de estado en el pin ALARM-
/THERM del modulo, además se utilizara la USART para la interfaz de ususario
y el modulo MSSP en modo I²C para la comunicación con el EMC1001.

Configuración del MCC

157
158
159
160
Código:
#include "mcc_generated_files/mcc.h"
#include <stdlib.h>

uint16_t emc_1001_address;

#define SIZE_STRING_BUFFER 20

#define EMC1001_20K_ADDRESS 0x38 // slave device address

// EMC1001 registers
#define TEMP_HI 0x00 // temperature value high byte
#define TEMP_LO 0x02 // low byte containing 1/4 deg fraction
#define TEMP_HIGH_LIMIT_H 0x05
#define TEMP_HIGH_LIMIT_L 0x06
#define TEMP_LOW_LIMIT_H 0x07
#define TEMP_LOW_LIMIT_L 0x08
#define THERM_LIMIT 0x20
#define THERM_HYSTERESIS 0x21

161
bool EMC1001_Read(uint8_t reg, uint8_t *pData);
bool EMC1001_Write(uint8_t reg, uint8_t data);
//Menu
void ImprimirMenu(void);
void ImprimirEstado(void);
void MonitorearTemperatura(void);
void ConfigThermLimit(void);
void RecibirMensajeUSART(char * receive_buffer);

void main(void)
{
SYSTEM_Initialize();
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();

char data;
emc_1001_address = EMC1001_20K_ADDRESS;

printf("\r\n*-------Sistema de monitoreo de Temperatura v1.0------*\r\n");

while (1)
{
ImprimirMenu();
data = getch();

switch(data)
{
case '1':
ImprimirEstado();
break;
case '2':
ConfigThermLimit();
break;
case '3':
MonitorearTemperatura();
break;
default:
printf("Opcion Incorrecta\r\n");
}
}
}
El archivo fuente del programa principal comienza con algunas definiciones
útiles para el desarrollo del programa, como la dirección del EMC1001 y los
registros del modulo. En la función principal se comienza asignando la
dirección del modulo EMC1001 en una variable global. Luego en el bucle

162
principal se imprime el menú de opciones y se espera, mediante la función
getch(), a que el usuario coloque una opción, luego esta se procesa mediante
el bloque condicional switch. En cada una de las opciones se ejecutan las
funciones correspondientes y una vez finalizado, se vuelve a imprimir el menú.

Funciones Auxiliares
void ImprimirMenu(void)
{
printf("\r\n*MENU*\r\n\r\nPresione una opcion:\r\n1-Imprimir Estado\r\n2-Setear
Temperatura Limite\r\n3-Monitor de Temperatura\r\n");
}

void ImprimirEstado(void)
{
uint8_t data;
int8_t temp;
uint8_t templo;

//printf("\x0C"); // comment out if terminal does not support Form Feed


printf("----------------------------------------------------------");
printf("\r\nTemperature Sensor Demo v1.01\n\r");
printf("EMC1001 address 0x%X\n\r",emc_1001_address );
if (EMC1001_Read(0xfd, &data)) printf("Product ID: EMC1001%s\n\r", data ? "-1" :
"");
if (EMC1001_Read(0xfe, &data)) printf("Manufacturer ID: 0x%X\n\r", data);
if (EMC1001_Read(0xff, &data)) printf("Revision : %d\n\r", data);

if (EMC1001_Read(TEMP_HI, (uint8_t*)&temp)) {
EMC1001_Read(TEMP_LO, &templo); // get lsb
templo = templo >> 6;
if (temp < 0) templo = 3-templo; // complement to 1 if T negative
printf("\n\rThe temperature is: %d.%d C\n\r", temp, templo*25);
}

if (EMC1001_Read(4, &data)) printf("\n\rThe Conversion rate is: %x\n\r", data);


if (EMC1001_Read(5, &data)) printf("The high limit is: %d C\n\r", data);
if (EMC1001_Read(7, &data)) printf("The low limit is: %d C\n\r", data);
if (EMC1001_Read(THERM_LIMIT, &data)) printf("The THERM LIMIT is: %d C\n\r",
data);
if (EMC1001_Read(THERM_HYSTERESIS, &data)) printf("The THERM HYSTERESIS is:
%d C\n\r", data);
printf("----------------------------------------------------------");
}

163
La primera función simplemente imprime el menú por la USART, la segunda, es
una función tomada de un ejemplo que se encuentra en el MPLABXpress, el
cual imprime en pantalla el estado general del modulo EMC1001, utilizando la
función de lectura del modulo.

void MonitorearTemperatura(void)
{
char data = '0';
int8_t temp;
uint8_t templo;

printf("\r\nMonitor de Temperatura.\r\nPresione Q para salir\r\n\r\n");

do
{
if (EMC1001_Read(TEMP_HI, (uint8_t*)&temp)) {
EMC1001_Read(TEMP_LO, &templo); // get lsb
templo = templo >> 6;
if (temp < 0) templo = 3-templo; // complement to 1 if T negative
printf("\rThe temperature is: %d.%d C ", temp, templo*25);
}
__delay_ms(1000);
if(EUSART_DataReady)
{
data = getch();
}
} while (data != 'Q' && data != 'q');

Esta función realiza la impresión del valor de la temperatura en la pantalla


cada un segundo. La lectura se hace sobre los dos registros que forman el
resultado, el byte alto tiene la parte entera de la conversión y los dos bits más
significativos de la parte baja del resultado almacena, con precisión de 0,25°C ,
la parte decimal. Ambos valores luego son mostrados en pantalla, separados
por el punto. Esto se repite cada 1 segundo y a la vez se pregunta si se quiere
salir del modo de monitoreo, para ello, se debe oprimir la tecla Q. Cuando se
detecta que la letra Q se oprimió, se sale del bucle que imprime el monitor de
temperatura.

void ConfigThermLimit(void)
{
char buffer[SIZE_STRING_BUFFER];

164
char data;

printf("\r\nConfiguracion Limite de Temperatura\r\nIngrese un nuevo valor de


Temperatura Limite entre 0 y 85ºC: ");
RecibirMensajeUSART(buffer);
int limit = atoi(buffer);
printf("\r\nIngrese un nuevo valor de hysteresis: ");
RecibirMensajeUSART(buffer);
int hysteresis = atoi(buffer);
printf("\r\nVa a configurar los valores THERM LIMIT= %i y THERM Hysteresis=
%i\r\n", limit, hysteresis);
printf("Desea Continuar? Presione Y para aceptar, cualquier otra tecla para
cancelar:");
data = getch();
if(data == 'Y' || data == 'y')
{
//Guardamos en memoria interna

EMC1001_Write(THERM_LIMIT, (uint8_t)limit);
EMC1001_Write(THERM_HYSTERESIS, (uint8_t)hysteresis);

printf("\r\nConfigurado nuevos Valores\r\n");


}
else
{
printf("\r\nNo se configuraron los nuevos valores\r\n");
}
}

Esta es la función encargada de tomar el nuevo valor del límite superior,


THERM Limit, y la histéresis y configurarlo en el modulo EMC1001. Mediante
simples mensajes se va guiando al usuario a configurar un nuevo límite de
temperatura y una histéresis nueva. Una vez ingresado ambos valores, se
consulta si se desea ejecutar la acción de configurarlo en el modulo memoria
oprimiendo la tecla “Y” en la PC. Si es así, se inicia la secuencia de escritura en
el modulo EMC1001 mediante la función de escritura del modulo
EMC1001_Write().

bool EMC1001_Read(uint8_t reg, uint8_t *pData)


{
I2C2_MESSAGE_STATUS status = I2C2_MESSAGE_PENDING;
static I2C2_TRANSACTION_REQUEST_BLOCK trb[2];

I2C2_MasterWriteTRBBuild(&trb[0], &reg, 1, emc_1001_address);

165
I2C2_MasterReadTRBBuild(&trb[1], pData, 1, emc_1001_address);
I2C2_MasterTRBInsert(2, &trb[0], &status);

while(status == I2C2_MESSAGE_PENDING); // blocking

return (status == I2C2_MESSAGE_COMPLETE);


}

bool EMC1001_Write(uint8_t reg, uint8_t data)


{
I2C2_MESSAGE_STATUS status = I2C2_MESSAGE_PENDING;
//static I2C2_TRANSACTION_REQUEST_BLOCK trb[2];

uint8_t send_data[3];

send_data[0] = reg;
send_data[1] = data;

I2C2_MasterWrite(send_data, 2, emc_1001_address, &status);


while(status == I2C2_MESSAGE_PENDING); // blocking
return (status == I2C2_MESSAGE_COMPLETE);
}

Las funciones EMC1001_Read() y EMC1001_Write() son las encargadas de leer


y escribir un registro en el sensor de temperatura EMC1001. La secuencia de
lectura se realiza mediante la escritura de la dirección del registro en el
modulo EMC y luego se realiza la lectura. Las funciones de escritura y lectura
de la función EMC1001_Read() se realizan mediante un set de funciones que
simplemente almacenan los datos a enviar en una estructura en C, la que la
librería del I²C llaman Transaction Request Block (TRB). De esta manera se
pueden almacenar varios comandos a enviar por el puerto de comunicaciones,
y esto lo realiza la función I2C2_MasterTRBInsert() que toma todos los bloques
TRB y los envía al modulo EMC1001. Cuando finalmente envía los comandos,
se espera a que el bloque MSSP los termine de procesar.

Para la escritura la secuencia es similar, simplemente se envía la dirección del


registro a modificar y el nuevo valor, esto se realiza con la función
I2C2_MasterWrite() de la librería del MCC y se espera a que la transacción
finalice. Los datos a enviarse se guardan en un array de dos posiciones que
contiene la dirección del registro y el nuevo valor a configurar.

166
PROYECTOS CON PLACAS CLICK

En esta sección veremos prácticas dedicadas al uso de placas externas que


pueden extender las funcionalidades de la placa Xpress. En particular veremos
el uso de las placas “Click” de la empresa mikroelectronika, las cuales
implementa el bus de conexión “micro BUS” desarrollada por esta misma
empresa.

A continuación se lista las placas que usaremos en este libro:

- Placa Weather Click: Sensor de humedad, presión y temperatura


- Placa 6DOF IMU Click: Sensor de aceleración y giroscopio.

WEATHER CLICK
La placa Weather Click está diseñada para obtener distintos parámetros
ambientales que indican el estado del clima. Esta placa cuenta con el sensor
BME280 del fabricante Bosch y permite realizar mediciones de presión,
humedad y temperatura. Está diseñado para trabajar con bajo consumo y con
largos periodos de estabilidad, trabaja con una tensión de alimentación de
3,3V. El integrado es totalmente digital, los resultados de las mediciones de
humedad, presión y temperatura son almacenados en registros internos, junto
con otros registros de configuraciones. El modo de comunicarse con este
sensor es utilizando el protocolo SPI o el I2C.

Descripción general
El uso de este sensor es ideal en aplicaciones que requieran el estado del clima
en un determinado ambiente utilizando los parámetros de humedad, presión y
temperatura. Las posibles aplicaciones se encuentran en la automatización de
los hogares inteligentes, in-door navigation, fitness o como información
suplementaria en las estaciones de GPS.

El sensor de humedad provee una medición de alta exactitud y en un corto


tiempo de respuesta. Lo mismo para el sensor de presión, el cual tiene una alta
exactitud en la medición absoluta de la presión barométrica en un ambiente y

167
trabaja sobre un gran rango de temperaturas. El sensor de temperatura cuenta
además con una alta resolución e inmunidad a los ruidos. La salida de
temperatura se utiliza para compensar los valores de humedad y presión o
para registrar la temperatura ambiente.

El tiempo de respuesta a la medición de la humedad es de alrededor de 1


segundo y cuenta con tres modos de operación para disminuir el consumo,
estos modos son:

- Modo Sleep.
- Modo Normal.
- Modo Forzado manual.

En el modo Sleep, el modulo no realiza mediciones, pero es el modo en que


menos consumo tiene. En el modo normal, las mediciones se realizan
periódicamente y en el modo forzado, se le indica al modulo el momento en
que debe iniciar una única medición.

Pin out de la placa Weather Click:


En la siguiente tabla se muestra el diagrama de pines del conector microbús.

Descripción Pin Pin Descripción

NC 1 AN PW 16 NC
M
NC 2 RST INT 15 NC
Chip Select CS 3 CS TX 14 NC
SPI Clock SCK 4 SCK RX 13 NC
SPI Master Input Slave MIS 5 MISO SCL 12 SCL I2C Clock
Output O
SPI Master Output MOS 6 MOSI SDA 11 SDA I2C Data
Slave Input I
Power supply 3,3V 7 3,3V 5V 10 NC
Ground GND 8 GND GND 9 GND Ground

Lectura de los Datos


Una vez que se termina de realizar la medición de las magnitudes a medir, los
resultados son almacenados en diferentes registros internos del sensor. Se

168
recomienda que la lectura de todos los valores se realice en un solo comando
de lectura o como lo indica el fabricante, realizar una lectura del tipo “burst”,
ya que de esta manera nos aseguramos que no ocurran cambios en los
registros durante un proceso de lectura. Además es posible agregar en las
mediciones de la presión y de la temperatura un filtro IIR a la salida, utilizado
para quitar las fluctuaciones que pueden aparecer en un cuarto cerrado, por
ejemplo, donde el cierre o apertura de una puerta puede generar una mínima
modificación en la lectura de la presión. El fabricante del sensor aconseja
realizar una compensación a los valores medidos por el mismo, para ello se
hace uso de una fórmula para cada uno de las magnitudes a medir junto con
un conjunto de valores que son almacenados en una memoria no volátil en el
chip y que son valores ajustados de fabrica. Para dispositivos de 8 bits, las
formulas son las siguientes:

Temperatura
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
BME280_S32_t t_fine;
BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T)
{
BME280_S32_t var1, var2, T;
var1 = ((((adc_T>>3) – ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11;
var2 = (((((adc_T>>4) – ((BME280_S32_t)dig_T1)) * ((adc_T>>4) – ((BME280_S32_t)dig_T1))) >> 12) *
((BME280_S32_t)dig_T3)) >> 14;
t_fine = var1 + var2;
T = (t_fine * 5 + 128) >> 8;
return T;
}

Presión
// Returns pressure in Pa as unsigned 32 bit integer. Output value of “96386” equals 96386 Pa = 963.86 hPa
BME280_U32_t BME280_compensate_P_int32(BME280_S32_t adc_P)
{
BME280_S32_t var1, var2;
BME280_U32_t p;
var1 = (((BME280_S32_t)t_fine)>>1) – (BME280_S32_t)64000;
var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((BME280_S32_t)dig_P6);
var2 = var2 + ((var1*((BME280_S32_t)dig_P5))<<1);
var2 = (var2>>2)+(((BME280_S32_t)dig_P4)<<16);
var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((BME280_S32_t)dig_P2) * var1)>>1))>>18;
var1 =((((32768+var1))*((BME280_S32_t)dig_P1))>>15);
if (var1 == 0)
{
return 0; // avoid exception caused by division by zero
}

169
p = (((BME280_U32_t)(((BME280_S32_t)1048576)-adc_P)-(var2>>12)))*3125;
if (p < 0x80000000)
{
p = (p << 1) / ((BME280_U32_t)var1);
}
else
{
p = (p / (BME280_U32_t)var1) * 2;
}
var1 = (((BME280_S32_t)dig_P9) * ((BME280_S32_t)(((p>>3) * (p>>3))>>13)))>>12;
var2 = (((BME280_S32_t)(p>>2)) * ((BME280_S32_t)dig_P8))>>13;
p = (BME280_U32_t)((BME280_S32_t)p + ((var1 + var2 + dig_P7) >> 4));
return p;
}

Humedad
// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46.333 %RH
BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H)
{
BME280_S32_t v_x1_u32r;
v_x1_u32r = (t_fine – ((BME280_S32_t)76800));
v_x1_u32r = (((((adc_H << 14) – (((BME280_S32_t)dig_H4) << 20) – (((BME280_S32_t)dig_H5) * v_x1_u32r))
+
((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r *
(BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) *
((BME280_S32_t)dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r – (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >>
4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
return (BME280_U32_t)(v_x1_u32r>>12);
}

Interfaz de comunicación
El sensor BME280 soporta tanto la interfaz digital SPI como el I2C. En ambos
protocolo, el sensor actuara como el dispositivo esclavo. La interfaz SPI soporta
los modos ‘00’ (CPOL = CPHA = ‘0’) y el modo ‘11’ (CPOL = CPHA = ‘1’) ya sea
utilizando 4 o 3 hilos en la comunicación. La interfaz I2C soporta las velocidades
Standart, Fast y High Speed.

Ambas interfaces soportan las siguientes transacciones:

- Escritura de un Byte Simple


- Escritura de múltiples Bytes (se envía pares de Registro – Valor)
- Lectura de un Byte Simple

170
- Lectura de múltiples Bytes (se envía una Dirección Base y luego se
autoincrementa para la lectura)

La selección del tipo de interfaz de comunicación en el sensor y el host


maestro se realiza mediante el estado del pin CSB (chip select). Si el pin se
conecta a VDD, la interfaz será I2C, en cambio si esta puesto a masa, la interfaz
activa será el SPI.

La placa Weather cuenta con unos Jumpers que permiten seleccionar el tipo de
comunicación, I2C o SPI. Por defecto, los jumpers están colocados para
trabajar en I2C, por lo que este protocolo será utilizado en las prácticas de este
libro.

Interfaz I2C
La interfaz esclava I2C es compatible con la especificación de Philips versión
2.1, soportando los modos de comunicación standart, fast y high speed. Los 7-
bit de dirección del dispositivo son 111011x, donde los 6 bits más significativos
son fijos y el ultimo bit esta dado por el estado del pin SDO y que además
puede ser cambiado durante la operación del dispositivo. Si se conecta el pin
SDO a GND, la dirección esclava del dispositivo será 1110110 (0x76); en
cambio, si el pin es conectado a VDD, la dirección resultante será 1110111
(0x77). Los pines utilizados en la interfaz I2C son los siguientes:

- SCK: serial clock (SCL)


- SDI: data (SDA)
- SDO: Bit menos significativo de la dirección esclava (GND = ‘0’, Vdd =
‘1’)

El pin CSB debe estar conectado a VDD para seleccionar la interfaz I2C. En la
hoja de datos del BME280 se puede visualizar el mapa de memoria de los
registros del sensor.

PRACTICA 24: OBTENER VALORES DE HUMEDAD, PRESIÓN Y


TEMPERATURA DE LA PLACA WEATHER CLICK
Realizar un proyecto en donde se utilice la placa Weather para medir la
humedad, presión y temperatura de un recinto y mostrarlo por USART
utilizando la placa MPLAB Xpress.

171
Descripción
Ya vimos cuales son las características y modo de operaciones con las que se
puede configurar el sensor BME280, el cual utilizaremos en esta práctica para
medir la humedad, presión y temperatura de un recinto. En cuanto a las
necesidades de Hardware para esta práctica, vamos a utilizar la placa MPLAB
Xpress junto con la placa “Weather Click” de la empresa Mikroelectonika. La
placa Weather está provista de unos jumpers que permiten seleccionar la
interfaz de comunicación entre el sensor y el microcontrolador, por defecto,
estos jumpers están configurados para trabajar con la interfaz I2C.

Periféricos utilizados
La comunicación entre el sensor y el microcontrolador es a trabes de la
interfaz I2C, por lo que haremos uso de esta interfaz de comunicación y
realizaremos su configuración tal como lo vimos en prácticas anteriores. Para
mostrar los resultados de las magnitudes que nos entrega el sensor haremos
uso de la comunicación USART y mostrar los datos en la PC.

Configuración del MCC.


Comenzaremos la configuración de nuestro dispositivo abriendo el MPLAB
Code Configurator y dentro de la ventana “Project Resources” vamos a
configurar el oscilador interno para que trabaje a 4MHz junto con el divisor de
Clock en 4, para un reloj del sistema trabajando a 1MHz, igual que en las
prácticas anteriores, como lo muestra la siguiente figura.

172
En la versión 3.45.1 del MPLAB Code Configurator se agregan como soporte
algunas de las placas Click de la empresa MikroElectronika, en particular, se
agrega el soporte a la placa Weather Click. Para agregarla a nuestro proyecto,
se debe recorrer la ventana “Device Resources” ir a la sección “Mikro-E Clicks”
y buscar entre las placas “Sensors” la que vamos a utilizar, la placa “Weather”,
una vez encontrada, hacer doble click sobre ella para agregarla a la ventana de
Recursos del proyecto.

173
Una vez agregado la librería de la placa Weather, se agregaran los códigos
fuentes que manejan la los registros y la comunicación I2C con la placa. En
todos los casos, dejar las configuraciones por defecto, pero activar en la
sección “Example” el casillero que dice “Generate Example”.

174
175
Por último agregar el módulo USART para la comunicación con la PC y poder
mostrar los resultados en pantalla.

De acuerdo a la distribución de pines que tiene la placa Weather para su


comunicación I2C y los pines de comunicación USART, en la ventana PIN
Module se debe tener la siguiente configuración.

176
Una vez que terminamos la configuración hacemos click en “Generate” dentro
de la ventana de recursos del proyecto y el MCC generará los siguientes
códigos fuentes que controlaran el sensor BME280 de la placa Weather.

Código principal
#include "mcc_generated_files/mcc.h"
#include "mcc_generated_files/bme280.h"
#include "mcc_generated_files/weather_example.h"

/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();

//Mensaje de Bienvenida
printf("----- Weather Click -----\r\n");

//Valor del Chip_ID


uint8_t chip_id;
chip_id = BME280_getID();

177
printf("BME280 Chip ID: %i\r\n", chip_id);

while (1)
{
// Add your application code
__delay_ms(1000);
Weather_example();
}
}

Gracias a la utilización del ejemplo de la libraría Weather, el código principal


se vuelve tan simple como el que se muestra. Con estas librerías, el tiempo de
desarrollo para estos sensores se vuelve muy cortos, permitiendo que los
productos salgan rápidos al mercado. El código principal comienza inicializando
los periféricos que vamos a utilizar en el PIC, que son el uso del oscilador
interno, los pines GPIO y el modulo USART. La configuración del módulo MSSP
para que actúe como I2C se realiza una vez que se inicia la comunicación con el
sensor BME280. La primer sentencia de código es la de escribir un mensaje de
bienvenida, luego buscaremos mostrar en pantalla el identificador del CHIP
BME280, que según las hojas de datos tiene el valor 0x60 en hexadecimal o 96
en valor decimal. La lectura de este CHIP_ID se realiza con la función
BME280_getID() de la librería bme280.h y nos devolverá el valor de
identificación del Chip en un entero. Obtener el CHIP_ID es de utilidad para
verificar la comunicación entre el PIC y el sensor BME280, ya que si leemos un
valor distinto de 96 en decimal, debemos revisar los parámetros de la
comunicación.

Una vez verificada la comunicación con el sensor y de mostrar el ID en pantalla,


se comienza con el monitoreo de los valores entregados por el sensor, estos
son la humedad, presión y temperatura. Para ello haremos uso de la rutina
Weather_Example() la cual se encargará de obtener los resultados de la
medición y los mostrará en pantalla. Esta rutina se encuentra definida en el
archivo “weather_example.c” y es llamada una vez por segundo. A
continuación mostraremos en detalle cómo trabaja este método.

Funciones Auxiliares
void Weather_example(void) {
// Read the sensor data registers
Weather_readSensors();

178
// Return the compensated measurement readings of each sensor
printf("T = %i C \t", (int8_t) Weather_getTemperatureDegC());
printf("P = %u kPa \t", (uint8_t) Weather_getPressureKPa());
printf("H = %u RH \n\r", (uint8_t) Weather_getHumidityRH());
}

La rutina Weather_example() está provista por la librería del MCC para la placa
Weather, y se define su código en el archivo weather_example.c. La rutina
comienzo realizando el proceso de medición, llamando al método
Weather_readSensors() el cual inicia de manera forzada una medición en el
sensor, espera a que este termine y almacena los resultados en variables
utilizadas por la librería. Luego los valores son mostrados por USART utilizando
a función printf() y tomando los valores, ya compensados, de la temperatura,
presión y humedad. Cada uno de las funciones
Weather_getTemepratureDegC(), Weather_getPressureKPa() y
Weather_getHumidityRH() devuelven los valores, ya compensados de cada una
de las magnitudes. Debido a que el micro solo tiene un procesador de 8 bits,
los resultados obtenidos se manejan como punto fijo de 32 bits, tal como se
indico en la sección de compensación de los resultados obtenidos por el
sensor.

6 DOF IMU 2
Esta placa Click está diseñada para medir la aceleración en tres ejes y los
valores de giroscopio, también en sus tres ejes. Cuenta con el Sensor BMI160
de la empresa Bosch de bajo consumo para ser utilizado como una unidad de
medición inercial (IMU en sus siglas en ingles). Los IMU son utilizados en
sistemas de navegación inercial y funcionan detectando la aceleración en uno
o más ejes y los atributos rotacionales, usando giroscopios en uno o más ejes.
El sensor BMI160 cuenta con una unidad administradora de potencia (PMU)
para una eficiente administración de la energía y de los estados de bajo
consumo para aumentar el rendimiento de baterías. Se alimenta con 3,3V y se
comunica con el procesador principal utilizando los protocolos de
comunicación SPI o I2C.

Descripción General
El sensor BMI160 es una unidad de medición inercial conformado por un
acelerómetro low-g de 3 ejes y un giroscopio de bajo consumo de 3 ejes,
formando una unidad de medición de 6 ejes, que puede ser extendido a 9, ya

179
que cuenta con una segunda interfaz que puede ser conectado a un
magnetómetro externo de 3 ejes. Este sensor está diseñado para ser utilizado
en tablets, teléfonos móviles, control remoto, juegos y en juguetes. En el modo
normal de funcionamiento puede llegar a consumir unos 925µA y trabaja a una
tensión de 3,3V resultando útil en sistemas alimentados por batería. Cuenta
con FIFO de 1024 posiciones que permite almacenar los últimos valores.
Además cuenta con un sistema de interrupciones que permite detectar gestos
basados en el movimiento y de reconocimiento del contexto. Mediante el
módulo de interrupción es posible detectar si el sistema está en movimiento o
no, si se realizo un toque o un doble toque, detección de la orientación, caída
libre o detectar algún evento de choque. Existen tres modos de operación
soportados para el acelerómetro y el giroscopio:

Acelerómetro:

- Modo Normal: El chip opera normalmente


- Modo Bajo consumo: Intercala estados entre modo normal y
suspendido.
- Modo suspendido: No se toman muestras y los datos son retenidos.

Giroscopio

- Modo Normal: El chip opera normalmente


- Modo Suspendido: No se toman muestras y los datos son retenidos.
- Modo de arranque rápido: El tiempo de arranque al modo normal es
menor a los 10 ms.

Pin out de la placa 6 DOF IMU 2 Click:


En la siguiente tabla se muestra el diagrama de pines del conector microbús.

Descripción Pin Pin Descrip


ción

NC 1 AN PW 16 NC
M
NC 2 RST INT 15 INT Interrup
ción
Chip Select CS 3 CS TX 14 NC
SPI Clock SCK 4 SCK RX 13 NC
SPI Master Input Slave MISO 5 MISO SCL 12 SCL I2C

180
Output Clock
SPI Master Output MOSI 6 MOSI SDA 11 SDA I2C Data
Slave Input
Power supply 3,3V 7 3,3V 5V 10 NC
Ground GND 8 GND GND 9 GND Ground

Procesamiento de datos en el acelerómetro


La toma de datos por parte del sensor de aceleración puede trabajar en dos
modos, en modo normal o el modo de bajo consumo. Mediante el registro de
configuración (0x40) ACC_CONF se puede indicar en qué modo trabajará la
obtención de las muestras del sensor de aceleración. En el modo normal, la
adquisición de los datos se realiza en puntos equidistantes del tiempo
definidos por el Data Rate de salida (valor del acc_odr en el registro de
configuración). El Data Rate puede seleccionarse entre ocho distintos valores
comprendidos entre 12,5Hz y 1600Hz, pero a su vez estos Data Rate pueden
duplicarse (modo OSR2) o cuadruplicarse (modo OSR4) pero se puede llegar a
perder precisión. En el modo bajo consumo, el tiempo de muestreo entre cada
muestra se divide en un tiempo que trabajará en modo normal y en el otro,
estará en modo suspendido, el tiempo o Duty Cycle dependerá del Data Rate
que se configure en este modo, que va entre 0,78Hz hasta 1600Hz.

Procesamiento de datos en el giroscopio


La toma de datos por parte del sensor de giroscopio solo puede trabajar en
modo normal. Mediante el registro de configuración (0x42) GYR_CONF se
puede indicar en qué modo trabajará la obtención de las muestras del sensor
de aceleración, los cuales pueden ser el modo normal, OSR2 y OSR4. En el
modo normal, la adquisición de los datos se realiza en puntos equidistantes del
tiempo definidos por el Data Rate de salida (valor del gyr_odr en el registro de
configuración). El Data Rate puede seleccionarse entre ocho distintos valores
comprendidos entre 25 y 3200Hz. El Data Rate puede llegar a duplicarse (modo
OSR2) o cuadruplicarse (modo OSR4) pero se puede llegar a perder precisión.

FIFO
El sensor BMI160 cuenta con una FIFO interna que permite almacenar datos
que no fueron leídos, evitando que estos se pierdan si se trabaja en sistemas
de tiempo real. La FIFO tiene un tamaño de 1024bytes y sirve para alojar tanto
datos del acelerómetro como del giroscopio. Si se agrega un sensor

181
geomagnético externo, la FIFO soporta la capacidad de almacenar los datos de
ese sensor también.

Controlador de Interrupciones
Existen dos pines de interrupciones que pueden ser utilizado para señalizar
cualquiera de las trece fuentes de interrupciones que posee el sensor. La salida
de estas interrupciones pueden ser programadas por parte del usuario.

Las fuentes de interrupciones del acelerómetro son:

- Any-motion detection: Se activa si el valor absoluto del acelerador


pasa de un determinado valor de umbral
- Significant Motion: Se activa por cambio de lugar por parte del
usuario.
- Step Detector: Se activa ante la detección de un simple paso.
- Tap Sensing: detecta un simple toque o un doble toque.
- Orientation reconigtion: Reconoce un cambio en la orientación del
sensor.
- Flat Detection: Se dispara cuando se reconoce que el sensor esta de
forma horizontal a la tierra.
- Low-g/ Free Fall Detection: Se dispara si existe poca aceleración
(Caída Libre)
- High-g Detection: Se dispara si aparece una alta aceleración(Evento de
choque)
- Slow Motion Alert/No motion interrupt: Detecta si el dispositivo esta
en reposo.

Interrupciones del Giroscopio.

- PMU Trigger: Se dispara si el modulo de potencia entra en modo sleep


o se despierta, se lo utiliza para saber si no hubo movimientos o si lo
hubo por parte del giroscopio.

Interrupciones comunes para ambos sensores

- Data Ready: Se dispara cuando se detecta el resultado de una


conversión.
- FIFO Interrupts: Se dispara cuando la FIFO se llena o pasa de una
determinada marca.

182
Interfaz de comunicación
El sensor BMI160 soporta tanto la interfaz digital SPI como el I2C como
interface de comunicación primaria con el host y cuenta con una segunda
interfaz para un sensor externo. En la interfaz primaria, en ambos protocolo, el
sensor actuará como el dispositivo esclavo. La interfaz SPI soporta los modos
‘00’ (CPOL = CPHA = ‘0’) y el modo ‘11’ (CPOL = CPHA = ‘1’) ya sea utilizando 4 o
3 hilos en la comunicación. La interfaz I2C soporta las velocidades Standart,
Fast y High Speed.

Ambas interfaces soportan las siguientes transacciones:

- Escritura de un Byte Simple


- Escritura de múltiples Bytes (se envía pares de Registro – Valor)
- Lectura de un Byte Simple
- Lectura de múltiples Bytes (se envía una Dirección Base y luego se
autoincrementa para la lectura)

La selección del tipo de interfaz de comunicación en el sensor y el host


maestro se realiza mediante el estado del pin CSB (chip select). Si el pin se
conecta a VDD, la interfaz será I2C, en cambio si esta puesto a masa, la interfaz
activa será el SPI.

La placa 6DOF IMU 2 cuenta con unos Jumpers que permiten seleccionar el
tipo de comunicación, I2C o SPI. Por defecto, los jumpers están colocados para
trabajar en I2C, por lo que este protocolo será utilizado en las prácticas de este
libro.

Interfaz I2C
La interfaz esclava I2C es compatible con la especificación de Philips versión
2.1, soportando los modos de comunicación standart, fast y high speed. Los 7-
bit de dirección del dispositivo son 110100x, donde los 6 bits más significativos
son fijos y el ultimo bit esta dado por el estado del pin SDO y que además
puede ser cambiado durante la operación del dispositivo. Si se conecta el pin
SDO a GND, la dirección esclava del dispositivo será 1101000 (0x68); en
cambio, si el pin es conectado a VDD, la dirección resultante será 1101001
(0x69). Los pines utilizados en la interfaz I2C son los siguientes:

- SCK: serial clock (SCL)

183
- SDI: data (SDA)
- SDO: Bit menos significativo de la dirección esclava (GND = ‘0’, Vdd =
‘1’)

El pin CSB debe estar conectado a VDD para seleccionar la interfaz I2C. En la
hoja de datos del BMI160 se puede visualizar el mapa de memoria de los
registros del sensor.

PRACTICA 25: OBTENER VALORES DE ACELERACIÓN Y VELOCIDAD


ANGULAR DEL DISPOSITIVO.
Realizar un proyecto en donde se utilice la placa 6 DOF IMU 2 para medir la
aceleración y la velocidad angular de un dispositivo y mostrarlo por USART
utilizando la placa MPLAB Xpress.

Descripción
Ya vimos cuales son las características y modo de operaciones con las que se
puede configurar el sensor BMI160, el cual utilizaremos en esta práctica para
medir la aceleración y velocidad angular de un dispositivo. En cuanto a las
necesidades de Hardware para esta práctica, vamos a utilizar la placa MPLAB
Xpress junto con la placa “6DOF IMU 2 Click” de la empresa Mikroelectonika.
La placa está provista de unos jumpers que permiten seleccionar la interfaz de
comunicación entre el sensor y el microcontrolador, por defecto, estos
jumpers están configurados para trabajar con la interfaz I2C. El
microcontrolador actuará como el maestro de la comunicación y la dirección
del esclavo, en este caso el sensor BMI160, será 0x68 en hexadecimal, ya que
el pin SDO viene conectado a masa por defecto en la placa Click.

Periféricos utilizados
La comunicación entre el sensor y el microcontrolador es a trabes de la
interfaz I2C, por lo que haremos uso de esta interfaz de comunicación y
realizaremos su configuración tal como lo vimos en prácticas anteriores. Para
mostrar los resultados de las magnitudes que nos entrega el sensor haremos
uso de la comunicación USART y mostrar los datos en la PC.

Configuración del MCC.


Comenzaremos la configuración de nuestro dispositivo abriendo el MPLAB
Code Configurator y dentro de la ventana “Project Resources” vamos a
configurar el oscilador interno para que trabaje a 4MHz junto con el divisor de

184
Clock en 4, para un reloj del sistema trabajando a 1MHz, igual que en las
prácticas anteriores, como lo muestra la siguiente figura.

Agregamos la interfaz de comunicación I2C y lo configuramos como muestra la


siguiente ventana.

Por último agregar el módulo USART para la comunicación con la PC y poder


mostrar los resultados en pantalla.

185
De acuerdo a la distribución de pines que tiene la placa 6 DOF IMU 2 para su
comunicación I2C y los pines de comunicación USART, en la ventana PIN
Module se debe tener la siguiente configuración.

Una vez que terminamos la configuración hacemos click en “Generate” dentro


de la ventana de recursos del proyecto y el MCC generará los códigos fuentes
para controlar el modulo MSSP1 en modo I2C y la comunicación con la PC con

186
la EUSART. Hasta la presente edición del libro, todavía no se han agregado en
el MPLAB Code Configurator una librería de control para la placa 6 DOF IMU 2,
como si lo tiene la placa Weather. Pero el fabricante de las placas nos provee
de ejemplos como para poder utilizarlo, de allí se pudo modificar la librería de
control del sensor BMI160 para que pueda utilizarse con el compilador XC8 y
que será provisto durante el curso como material para que los alumnos
puedan utilizarlo y controlar el sensor mediante un set de funciones.

Código Fuentes para el control del sensor BMI160


En la pagina del fabricante de la placa 6 DOF IMU 2 vamos a encontrar algunos
recursos para poder utilizar el sensor BMI160 en nuestros desarrollos. Allí por
ejemplo encontraremos un esquemático de la placa, las hojas de datos del
sensor y lo que más nos interesa a nosotros, un código de ejemplo para
utilizarlos en nuestra plataforma. El ejemplo que provee el fabricante de la
placa contiene una librería con un set de funciones que permiten realizar
varias operaciones sobre los registros del sensor BMI160. La librería está
escrita en C y es necesario realizarle algunas modificaciones para adaptarlo a la
plataforma y el compilador que utilizamos. Los archivos que integran esta
librería son:

- 6dof_hw.c/h: Este archivo fuente contiene un amplio set de funciones


que permiten configurar y operar con el sensor BMI160.
- 6dof_hal.c/h: Este archivo fuente nos sirve como una capa de conexión
entre la librería 6dof_hw.h y la capa de drivers del I2C.

Para poder utilizar esta librería en el MLAB Xpress, debemos modificar la


definición de tres funciones que se encuentran en el archivo 6dof_hal.h. Las
funciones a modificar son las siguientes:

- dof6_hal_init(): Solo almacena en una variable la dirección esclavo del


dispositivo
- dof6_hal_write(): Puede escribir sobre uno más registros en el sensor
BMI160
- dof6_hal_read(): Puede leer sobre uno o más registros del sensor
BMI160

187
Estas son las tres funciones que debemos modificar para que la librería
funcione en nuestro ejemplo. Luego se deben agregar estos códigos fuentes al
proyecto, por lo que quedaría de la siguiente manera:

Código principal:
#include "mcc_generated_files/mcc.h"
#include "mcc_generated_files/6dof_hal.h"
#include "mcc_generated_files/6dof_hw.h"
/*
Prototypes
*/
void display_data();
/*
Main application
*/
void main(void)
{
// initialize the device
SYSTEM_Initialize();
//Mensaje de Bienvenida
printf("----- 6DOF IMU 2 Click -----\r\n");

188
//Valor del Chip_ID para test
uint8_t chip_id;
dof6_hal_init(BMI160_I2C_ADDRESS );
chip_id = dof6_get_chip_id();
printf("BMI160 Chip ID: %i\r\n", chip_id);

//Configuración inicial del Chip


//dof6_hal_init(BMI160_I2C_ADDRESS );
dof6_sensor_config(accel_normal , gyro_normal);
while (1)
{
// Add your application code
display_data();
__delay_ms(1000);
}
}

En la función principal vamos a comenzar llamando a la función que configura


e inicializa los periféricos y los pines del microcontrolador, llamamos a la
función SYSTEM_Initialize(). Luego de configurar todos los periféricos, el
programa comienza imprimiendo por USART un mensaje de bienvenida y
mostrando cual sería el CHIP_ID del sensor. Recordemos que esta primera
operación sobre el sensor de leer el CHIP_ID del mismo, es utilizada para
verificar los parámetros de comunicación entre el microcontrolador y el sensor
BMI160. EL valor del CHIP_ID es de 209 en entero decimal. Luego de verificar la
conexión entre el PIC y el sensor y mostrar el CHIP_ID en pantalla, se continúa
con la configuración del sensor para realizar las mediciones de la aceleración y
del giroscopio en modo normal. Luego entramos en un loop infinito,
obteniendo el valor de los resultados del sensor e imprimiéndolos en pantalla.
Esto lo realizamos por medio de la función display_data().

Funciones Auxiliares
void display_data()
{
float acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z;

acc_x=dof6_get_acc_x();
acc_y=dof6_get_acc_y();
acc_z=dof6_get_acc_z();
gyro_x=dof6_get_gyr_x();
gyro_y=dof6_get_gyr_y();
gyro_z=dof6_get_gyr_z();

189
printf("acc x = %i \t gyro x: %i \r\n", (int8_t) acc_x, (int8_t) gyro_x);
printf("acc y = %i \t gyro y: %i \r\n", (int8_t) acc_y, (int8_t) gyro_y);
printf("acc z = %i \t gyro z: %i \r\n", (int8_t) acc_z, (int8_t) gyro_z);

Esta es la función display_data() la cual se encarga de obtener del sensor


BMI160 los valores de aceleración y del giroscopio de cada uno de los ejes que
puede medir el dispositivo. Para leer el valor del registro, se utiliza el método
dof6_get_acc_x() para la aceleración en cada uno de sus ejes y lo mismo para
el giroscopio con los métodos dof6_get_gyr_x(). Una vez que almacena los
punto flotantes de 32 bits, la misma función lo toma y los imprime en pantalla
mediante la función printf().

Una vez escrito todo el código y agregado las librerías modificadas para esta
plataforma, se puede compilar el proyecto y cargarlo en la placa MPLAB
Xpress. Una vez que arranca el programa, se comienza a escribir en pantalla el
valor de las aceleraciones y el giroscopio.

190
191
Se terminó de imprimir
en Agosto de 2018
en Rolta, (4865-7337),
Ecuador 334, Buenos Aires.
www.rolta.com.ar

192

También podría gustarte