Translate

miércoles, 27 de junio de 2012

Aplicación: Audio con PWM

Una aplicación inmediata (y más interesante que los LED de colores del programa anterior) de la modulación PWM es intentar reproducir un archivo de audio digital tipo WAV usando el módulo PWM del PIC como un DAC (Digital-Analog Converter) incorporado.

Obviamente el principal problema es dónde guarda el PIC el audio a reproducir.  En la memoria de un PIC pueden caber unos 1000/2000 bytes, lo que a un ritmo de típico de 8 o 10 KHz solo da para una fracción de segundo. Lo que vamos a hacer en esta aplicación es recibir los datos a través del puerto serie. Si ajustamos la velocidad del puerto serie a 115200, teniendo en cuenta que para cada byte necesitamos mandar 10 bits (datos + start + stop) esto se traduce a un ritmo de unos 11500 bytes/segundo, muy cercano a 11025 que es una típica frecuencia de muestreo de archivos de audio. Según lleguen los datos por el puerto serie los usaremos para modificar el ciclo de trabajo del módulo PWM que previamente habremos configurado.

A la izquierda tenemos una foto con el hardware usado. A la derecha, un video donde pueden oírse el resultado,  junto con una captura de la señal PWM en el osciloscopio:

                         

Código asociado a esta entrada:  PWM_audio_serial.c
Proyecto C18 completo:  proyecto.rar

Modulación PWM (Pulse Width Modulation)

Nuestro controlador (digital) eventualmente tendrá que interactuar con el mundo exterior (analógico). Al margen de usar dispositivos externos (convertidor digital analógico o DAC) muchos microcontroladores cuentan con un módulo PWM (Pulse Width Modulation) que puede usarse (con ciertas limitaciones) para mandar "ordenes" analógicas.

En este tutorial describiremos en que consiste la modulación por ancho de pulsos (PWM), veremos las rutinas disponibles en C18 y, como hacemos habitualmente, describiremos los registros asociados y su funcionalidad.

Aprovecharemos los conocimientos adquiridos para escribir una rutina para inicializar el módulo PWM especificando la frecuencia deseada y escribiremos un pequeño programa para usar los dos módulos PWM de un PIC para crear transiciones de colores en un LED bicolor.

En una entrada posterior aplicaremos lo que hemos aprendido a una aplicación más interesante usando el módulo PWM como conversor DAC para un archivo de audio.

Archivos de código asociados a esta entrada: pwm1.c y pwm2.c

--------------------------------------------------------------------------------------------

viernes, 22 de junio de 2012

Decodificador de un mando a distancia


Vamos a escribir una aplicación que junta los tres temas sobre los que hemos hablado hasta ahora: timers, interrupciones y UART.  El objetivo es detectar los pulsos y medir sus tiempos de un mando a distancia por infrarrojos, obteniendo un volcado por el puerto serie similar a este:


Esta información la podremos usar para determinar qué protocolo se está usando. Posteriormente, una vez que sabemos que protocolo usa nuestro mando será simple escribir otro programa que decodifique o emule los pulsos. Esto puede ser muy útil en muchos proyectos para usar un mando por infrarrojos para enviar órdenes a nuestro sistema.

Codigo asociado a esta entrada: IR_timing.c

Comunicaciones UART con paridad

En muchos compiladores las rutinas del puerto serie asumen los valores por defecto de comunicación con 8 bits de datos y sin paridad.

Vamos a ver como, con los conocimientos adquiridos, es muy sencillo establecer una comunicación serie usando por ejemplo paridad. esto puede ser muy útil si tenemos que interactuar con un periférico que sólo soporte este tipo de comunicaciones.

Código asociado a esta entrada: uart_paridad.c

Comunicaciones UART con interrupciones

En el tutorial anterior hemos visto que lo que un compilador hace al escribir/leer de la UART no es muy complicado. Esencialmente se reduce a leer/escribir de un par de registros (TXREG, RCREG) verificando previamente que están libres (caso de TXREG) o que sus contenidos se han renovado (caso de RCREG).

Aprovecharemos estos conocimientos para implementar un programa de transmisión/recepción de datos más eficaz, usando las interrupciones asociadas a la UART, tanto de recepción (RX, activada al recibir un byte) como de transmisión (TX, activada cuando la UART está libre para mandar otro byte).

Asimismo usaremos dos buffers de mayor capacidad, uno de recepción (donde la interrupción RX depositará los datos recibidos) y otro de transmisión (de donde la interrupción de TX cogerá los datos a transmitir).

Veremos como la implementación de estas técnicas puede llevarnos a drásticas reducciones de la ocupación del PIC, liberándolo para otras tareas. 

También veremos como nuestra implementación con buffers e interrupciones puede configurarse como la salida por defecto stdout, pudiendo ser usada directamente por rutinas como printf, puts, etc. de forma transparente para el usuariuo.

Código asociado a esta entrada:  uart3_int.c

Comunicaciones puerto serie (UART)

Vamos a dedicar un par de entradas a examinar con cierto detalle el funcionamiento de las comunicaciones asíncronas (UART, o lo que habitualmente se conoce como el puerto serie) de la familia PIC18.

Nos centraremos en el software, dejando de lado el aspecto hardware, sobre todo la conversión de niveles si la comunicación es entre un PIC y el PC o entre dos microcontroladores con diferentes voltajes (p.e. 3.3V y 5V). En mis pruebas he usado el conversor de niveles integrado en la placa EasyPIC6. En general habrá que usar un conversor de niveles basado en el Max232 o similar. Una búsqueda de RS232 MAX232 TTL en ebay nos mostrará varias posibilidades a partir de unos $5.

En primer lugar presentaremos las funciones básicas que la mayoría de los compiladores tienen para abrir el puerto serie, enviar/recibir, etc. Luego escribiremos nuestras propias rutinas. El objetivo no es prescindir de las rutinas del compilador, sino entender los fundamentos para ser capaces (cuando sea necesario) de saltarnos las limitaciones de un compilador en particular.

También escribiremos una función que nos permita configurar el puerto serie a partir de la velocidad en baudios, sin tener que calcular y programar los registros asociados.

Código asociado a esta entrada:  uart0.c  uart1.c 

Uso de temporizadores (timers)

La mayoría de los microcontroladores tienen uno o varios timers. Serán muy útiles para medir el tiempo que ha pasado entre dos eventos, establecer tareas para ejecutarse a intervalos regulares, etc. Dependendo del modelo los PICs cuentan con un número variable de timers.

Vamos a describir con cierto detalle el timer 0 (TMR0). Los demás son muy similares, aunque siempre conviene mirarse el manual correspondiente, sobre todo porque algunos de ellos están asociados a otras tareas y puede que no estén disponibles para su uso general si se están usando ciertos periféricos.

Describiremos los registros asociados para configurar los temporizadores. El compilador C18 tiene disponibles varias rutinas para configurar y operar con los timers de una forma bastante cómoda. Sin embargo, la idea es ser capaces por nuestra cuenta de reproducir dichas rutinas lo que puede ser importante si lo que queremos hacer no se puede hacer exactamente con las rutinas suministradas. Como siempre es una cuestión de usar lo más conveniente en cada caso (pero para ello hay que conocer las posibilidades).

La configuración de los timers está basada en ciclos del oscilador.  Escribiremos alguna rutina que nos permita especificar un tiempo en usec o msec conocida la frecuencia de oscilador usado.

Código asociado a esta entrada: tipos.h  timer_1.c timer_2.c

Niveles de prioridad en interrupciones

En cualquier aplicación tendremos que manejar varias fuentes de interrupciones.  El problema es que, en principio, una interrupción no puede ser a su vez interrumpida por otra. En este primer ejemplo, mientras se este procesando una interrupción, aunque se produzca otra, tendrá que esperar. Esta es una de las razones por las que un código de interrupción debe ser lo más reducido posible.

No es difícil pensar una situación donde esto pueda suponer un problema. Imaginad que una de las interrupciones es critica e impredecible (p.e. contar el número de pulsos que llegan a un pin), mientras que la otra, que se ejecuta cada cierto tiempo, está asociada a una tarea menos importante  (p.e. refrescar una pantalla con el número de pulsos recibidos). El problema es que si los pulsos llegan muy rápidamente es posible que algunos lleguen mientras estamos en la segunda tarea. Como dicha tarea no se interrumpirá (ella misma es una interrupción) algunos pulsos no serán contados y perderemos dicha información.  La solución será usar prioridades en las interrupciones.

La familia PIC18 introdujo una importante novedad respecto a los PIC16 en el sentido de que es posible definir 2 niveles de prioridad en las interrupciones. Una interrupción de alto nivel puede interrumpir a otra de bajo nivel, pero lo contrario no sucederá. Presentaremos como configurar un PIC en este modo de prioridades y usaremos lo aprendido para escribir un sencillo programa que solucione el problema anterior.

Archivos de código asociado a esta entrada: test_int2.c  test_low_high_ok.c

Interrupciones (conceptos básicos)

Una interrupción es un aviso provocado por un módulo del PIC, por un cambio en el estado de un pin o un recordatorio de que ha pasado un cierto tiempo. Como su nombre indica este aviso interrumpirá la tarea que se este haciendo en ese momento y pasaremos a ejecutar una rutina de servicio o gestión de la interrupción.

Veremos un repaso de los bits y registros de control asociados a las diferentes interrupciones, como habilitarlas y como escribir rutinas de servicio (ISR). Crearemos definiciones (#define) que nos permitirán operar con las interrupciones sin tener que recordar los bits/registros asociados, a la vez que facilitarán la tarea de portar nuestro programa a otro compilador y/o microcontrolador. 

Es importante familiarizarse con el manejo de interrupciones, ya que nos evita poder manejar muchos tipos de eventos sin estar pendientes de ello. En sucesivos tutoriales veremos como el uso de interrupciones nos permite aprovechar de forma mucho más eficiente los recursos del PIC.

Archivos de código asociado a esta entrada:  int_defs.h   test_int.c 

jueves, 21 de junio de 2012

Introducción al BLOG

A raíz de la preparación de un curso, he ido acumulando cierto material sobre programación de microcontroladores PIC. Mi idea ahora es pulir dicho material y publicarlo, en forma de tutoriales, en este blog.  En el material asumiremos un conocimiento básico de los microcontroladores (no veremos como encender y apagar un LED) y está enfocado a programación en C.

Los compiladores que he usado al preparar el material han sido el C18 de Microchip y el MikroC Pro de MikroElektronika. De cara a estos tutoriales me he centrado en el C18 dado su caracter gratuito (en su versión de evaluación), aunque la práctica totalidad de los programas ejemplos presentados pueden compilarse dentro del límite de 2K words de la versión demo de MikroC Pro.

De todas formas el compilador usado no es tan importante ya que la idea de los tutoriales es entender que está pasando por debajo, de forma que no tengamos que depender de las rutinas de un compilador y que podamos reproducirlas por nuestra cuenta si decidimos migrar a otro.

En cuanto al hardware he usado fundamentalmente la placa de desarrollo EasyPIC6 de Mikroelektronica. Esencialmente consiste en un programador a través de USB + varios periféricos ya conectados (o conectables). Además de poder ver el estado de todos los pines a través de LEDs, contamos con un conversor de niveles MAX232, conector para LCD, un duplicador de puertos y alguna cosa más. Nada que no se pueda montar por separado gastándonos 10 euros. 

Los tutoriales alternarán entre descripciones de los diferentes módulos del PIC, explicaciones de protocolos o formatos de posibles periféricos y aplicaciones más o menos interesantes.