Noticias:

Grupo en telegram, del foro de meteorología fácil: https://t.me/meteorologiafacil

Para mas detalles, puedes visitar el siguiente tema http://www.meteorologiafacil.com.ar/foros/index.php?topic=1608.0

Espero que les sea de mucha utilidad.

Menú Principal

Software, herramientas y ejemplos de programación para JALv2

Iniciado por David Met, Junio 03, 2011, 01:37:28 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

David Met

#15
011 - Conexión USB - Serial - Ejemplo 1

En esta oportunidad, vamos a ver como realizar una comunicación entre la PC y el PIC por el puerto USB.
JALv2 dispone de una rutina para emular un puerto serial. ¿Cómo es esto? pues la PC cree que se le conectó un dispositivo serial y no un dispositivo USB. Cabe aclarar que esto ocurre, no solo por la librería de JALv2 que vamos a utilizar, sino que también los drivers utilizados. Estos drivers se pueden descargar del presente mensaje que está adjunto.

La utilización es muy sencilla y fácil de implementar, pero hay que tener cuidado con los fuses del PIC ya que sino, el módulo USB del PIC no funcionará correctamente y causará más de un dolor de cabeza buscando la causa.

La configuración que aquí utilizamos, puede ser modificada pero seguramente habrá que adaptar el hardware y/o el software con el que trabajaremos.

A modo explicativo, diré que, el PIC trabaja con una frecuencia de 20Mhz. Como el USB necesita 96Mhz para trabajar, se utiliza una configuración determinada para multiplicar y dividir la frecuencia principal para llegar a los 96Mhz. Vemos como llegamos al valor indicado.
Primero tenemos:

pragma target PLLDIV        P5          -- divide by 5 - 20MHZ_INPUT

Aquí le indicamos al PIC, que tenemos una entrada de 20Mhz, para ello ajustamos PLLDIV con el valor 5 (P5). Ver en el data sheet Special Features of the CPU

Luego tenemos:

pragma target CPUDIV        P2          -- OSC1_OSC2_SRC_1_96MHZ_PLL_SRC_2

Con la configuración anterior (pragma target PLLDIV        P5) el PLL interno, llega a 96Mhz, pero el módulo USB necesita trabajar a 48Mhz, es por eso que se lo divide por dos y que se ve reflejado en la siguiente configuración:

pragma target USBPLL        F48MHZ      -- CLOCK_SRC_FROM_96MHZ_PLL_2

Y al final le indicamos al PIC que habilite el regulador 3,3V del USB:

pragma target VREGEN        ENABLED     -- USB voltage regulator

Con esta configuración, me ha andado de maravilla en una PC con UBUNTU, Win XP y Win 7. Vuelvo a aclarar que estoy utilizando el entrenador de Felixls y que al principio de este tema, se puede acceder al esquemático para utilizarlo y armarlo en forma gratuita.

Pero vamos a los que más nos interesa, el ejemplo propiamente dicho.
En el software, verán más configuraciones de los fuses que no vienen al cabo explicar, pero hay que prestar atención si se desea modificar este programa para otro uso.
Luego de configurar los fuses, en varias constantes guardo unos mensajes que voy a mostrar en la computadora. La forma que lo hago es la siguiente:

-- Constantes ------------------------------------------------------------------
const byte mensaje_1[] = "Hola a todos los usuarios de\n"
const byte mensaje_2[] = "Foro de Meteorologia Facil\n"
const byte mensaje_3[] = "Foro de Ucontrol\n"
const byte mensaje_4[] = "Foro de Todopic\n"
const byte mensaje_5[] = "Ejemplo para encender y apagar un led\n"
const byte mensaje_6[] = "en el puerto b\n"
const byte mensaje_7[] = "y lectura de los mismo bit\n"
const byte mensaje_8[] = "Ademas, el control de envio de los diferentes mensajes\n"
const byte dir_url_1[] = "www.meteorologiafacil.com.ar\n"
const byte dir_url_2[] = "www.ucontrol.com.ar\n"
const byte dir_url_3[] = "www.todopic.com.ar\n"

Si prestan atención, verán que tiene un patrón en su utilización de las constantes, por lo que voy a explicar el primero:

const byte mensaje_1[] = "Hola a todos los usuarios de\n"

const indica que lo que se va a guardar es una constante y como dice el nombre, no va a cambiar durante el funcionamiento del PIC. Cuando se intenta cambiar el valor de una constante, el compilador nos avisará con un warning o precaución.

byte ya conocemos que significa y quiere decir que el tamaño es de 8 bit.

mensaje_1 es el nombre de la constante

[] define una constante de array (serie) y como lo dejamos en blanco, el compilador lo asigna automáticamente dependiendo del contenido que le daremos.

= Le asigna el valor correspondiente. No confundir == que significa "es igual a"

"" Entre las comillas se escribe el valor que queremos guardar en la constante. En este caso Hola a todos los usuario de

/n Significa salto de línea.

Noten algo muy importante. Si le indicamos al compilador que la constante es del tipo byte y que guardamos la frase Hola a todos los usuario de, es evidente que no entra en un byte, ¿Qué es lo que está pasando? Al indicar un Array o serie, el compilador lo que hace es tomar tantas posiciones de memoria como caracteres haya. Por lo tanto, la frase que guardamos necesita 26 posiciones de memoria en forma consecutiva. Estas 26 posiciones de memoria, el compilador las identifica con el nombre mensaje_1 y cada posición de memoria el compilador le da el tamaño de 8 bit.

Para que se entienda un poco mejor, cuando asignamos una variable o constante de tamaño WORD (por dar un ejemplo) el compilador toma dos posiciones de memoria como si fuera una sola.

Una vez que asignamos las constante, inicializamos el módulo USB, para ello ejecutamos la función usb_serial_init() una sola vez. Es por eso que se encuentra fuera del bucle principal.

Dentro del bucle principal, o sea, todo lo que está dentro del forever loop, vemos una función. Esta función lo que hace es mantener viva la comunicación entre el PIC y la PC. usb_serial_flush() Para mantener viva la comunicación, esta función debe ser ejecutada entre 2 a 3 ms. Si está fuera de este tiempo, la PC no reconocerá el dispositivo conectado. Por lo tanto, si tu PC no reconoce al PIC, chequea que esta función se ejecute en menos de ese tiempo.

La función usb_cdc_line_status() nos devuelve el valor si la PC enumeró al PIC en forma correspondiente. Para saber eso, utilizamos la instrucción IF THEN.

Dentro de este IF, tenemos otro IF que chequea si se recibió algún dato por el puerto USB. La función que nos dice si se recibió algún dato y que nos da ese valor es usb_serial_read(). Notarán que está escrito así: usb_serial_read(ch) ch es una variable byte y lo que hace la función es guardarnos, en esa variable, el dato recibido.
Como es de esperar, si se recibió algún dato, queda adentro del IF y dentro de este IF, está el CASE OF. Este CASE, lo que hace es analizar el dato guardado en la variable ch y dirigirse al igual valor dentro del CASE. Me explico mejor, verán que está "A": "B": etc. Estas comillas indican que se debe leer como un valor ASCII, y si por ejemplo en ch se recibió "A", se va a ejecutar esta línea:

"A": pin_b0 = on      -- Encendemos Led en b0

Por lo tanto, si nosotros enviamos desde la PC el código ASCII de la A, cuando el pic reciba dicho dato, pondrá un 1 lógico en el bit 0 del puerto b encendiendo el led que está conectado ahí. Pero si luego se envía "B", se ejecutará la siguiente línea:

"B": pin_b0 = off      -- Apagamos Led en b0

Por lo que apagará el led ya que el pic pondrá un 0 lógico en el bit 0 del puerto b.

Otra parte interesante, es la posibilidad de que el PIC envíe datos a la PC y esto se hace con dos funciones parecidas:

usb_serial_write() y usb_serial_data()

El primero, está creado para enviar un dato de 8 bit, mientras que el segundo está creado para enviar un array.
La forma en que la utilizamos aquí es sencilla. Si enviamos los números del 0 al 7, nos enviará el código ASCII según el estado del puerto pedido. Por ejemplo:

"0":                      -- Mostrar el estado de b0.
   block
      if pin_b0 == 1 then
         usb_serial_write("A")      
      else
         usb_serial_write("B")
      end if
   end block

Cuando el CASE analice y ejecute el código anterior, el PC del pic analizará el estado del bit 0 del puerto b. Si el mismo está encendido (1 lógico) enviará el código ASCII A, por el contrario, si es 0 lógico, enviará el código ASCII B. Noten que del 1 al 7 se repite lo mismo pero que cambia la letra a enviar.
Antes de seguir con la última parte del programa, me gustaría aclarar porqué se utilizó el block. En "A", como era una sola línea de instrucción, no era necesario, pero aquí tenemos más de una línea y que encima tenemos un if para analizar el estado del bit correspondiente, entonces se hace imprescindible la utilización del block. Esto hace que JALv2 solo ejecute estas líneas de programación dentro del block cuando se ejecuta la línea "0".

Para finalizar, vemos como enviamos las frases. Se ha echo de una manera muy sencilla. Se le pide al pic que envíe ciertos mensajes cuando se envía el código ASCII de las letras en minúsculas y que van de la "a" hasta la "k".
En este caso se lo ha utilizado de la siguiente manera:

"a": print_string(usb_serial_data, mensaje_1)   -- Si se recibió "a", entregamos el primer mensaje.

print_string ejecuta la función usb_serial_data y envía caracter por caracter del mensaje_1. ¡Fácil! ¿Verdad?

Del lado de la computadora, utilicé el programa Tera TERM PRO. En windows 7, se puede cambiar el com a utilizar y yo he utilizado el com2 conectado al PIC.
La configuración del Tera TERM PRO es a 9600 baudios, 1 bit de parada, sin paridad. 8 bit de datos.

Esto es el resultado:

USB-Serial 1 con JALv2




Adjuntado está el archivo fuente utilizado en este ejemplo, más los drivers para windows 7




Volver al índice
Jesús dijo, yo soy el CAMINO, la VERDAD y la VIDA, nadie llega al PADRE si no es por mi.

David Met

#16
012 - Conexión USB - Serial - Ejemplo 2

En esta oportunidad, hemos modificado el ejemplo anterior y hemos agregado otra librería, ADC.jal. Esta librería, más a las que acompaña dicha librería, nos permite manejar el ADC del pic de una forma muy sencilla y rápida.

Para trabajar con esta librería, primero tenemos que configurar algunas constantes y variables para luego incluir la librería, inicializarla y por último, leer las entradas analógicas en el momento que se desee.

La manera en que se configura el módulo ADC es la siguiente:

const bit ADC_HIGH_RESOLUTION = low      -- Elegimos una resolución baja (8 bit)

Creamos una constante llamado ADC_HIGH_RESOLUTION y le damos el valor LOW, como es del tipo bit, solo alojará un 1 o un 0 lógico. Al colocarlo en LOW, elegimos una resolución de 8 bit, pero si lo configuramos en HIGH, la resolución será de 10 bit.

const byte ADC_NCHANNEL = 2            -- AN0 y AN1

En esta constante, especificamos la cantidad de canales que vamos a utilizar, en este caso son 2 AN0 y AN1. La librería está preparada para que se utilicen los canales en forma ascendente. Con esto quiero decir que, si deseamos usar un canal solo, tenemos que conectarlo a AN0, si deseamos utilizar otro canal, hay que modificar la librería. Al elegir dos canales, estoy obligado a utilizar AN0 y AN1.

const byte ADC_NVREF = ADC_NO_EXT_VREF   -- Sin tensión de referencia externa.

Y aquí lo que hacemos es indicarle a la librería, para no utilizar la tensión de referencia externa.
Luego de estas tres constante, podrán ver que inicializo el módulo ADC con la función adc_init()

Lo bueno de esta librería, que leer una entrada analógica es tan fácil que solo hay que hacerlo de la siguiente manera:

med_promedio_temp = adc_read(0)

med_promedio_temp es una variable que yo cree del tipo word. Recordemos que la resolución elegida, es de 8 bit, así que la manera en que escribí la instrucción, nos dará un warning el compilador a la hora de compilarlo debido a que tenemos una variable del tipo word y un registro del tipo byte. En el siguiente ejemplo, veremos como hacer para que no nos aparezca el warning y garantizar que el compilador haga la operación que nosotros queramos.

adc_read(0) es la función que nos permite leer la entrada analógica elegida con el número entre paréntesis, en este caso AN0. Cabe mencionar que esta función, toma en cuenta los tiempos necesarios entre una conversión y otra de acuerdo a nuestro crystal, en otras palabras, esta función nos devuelve el valor del ADC del PIC censado.

Veamos ahora, como hago el promedio del canal AN0:

        promedio = 8
   med_promedio = 0
   while promedio != 0 loop
      usb_serial_flush()                     -- Mantenemos viva la conección usb
      med_promedio_temp = adc_read(0)               -- Pasamos el valor del ADC AN0 a la variable med_promedio.
      usb_serial_flush()
      med_promedio = med_promedio + med_promedio_temp
      usb_serial_flush()
      promedio = promedio - 1
   end loop
   med_promedio = med_promedio / 8

Primero, vemos que a la variable promedio, le asignamos el valor decimal 8, esto nos dará la cantidad de veces que se repetirá el while. Dicho while analiza la siguiente operación lógica promedio != 0 esto es, mientras (while) promedio sea distinto de 0, ejecutamos todo lo que está dentro del loop. Sabiendo que promedio vale 8, las instrucciones que están dentro del loop se repetirá 8 veces.

Vemos que se repite la función usb_serial_flush(), como recordarán es para mantener viva la comunicación USB. En el siguiente ejemplo, gracias a Felixls (usuario de ucontrol) podremos evitar repetir tanta veces esta función.

El promedio es la sumatoria de la información de la cantidad de los datos obtenidos, dividido por esa misma cantidad. En este caso, tomamos 8 muestras que las sumamos entre si para luego dividirlas por 8. El valor máximo que podemos obtener por cada muestra, es de 255 y si a esto lo sumamos 8 veces, 255 * 8 = 2040 (7F8 en hexadecimal), como excede del tamaño byte, hemos configurado la variable como word (16 bit)

Antes de salir del loop, vemos que hay una instrucción promedio = promedio - 1, esto lo hacemos para decrementar en 1 dicha variable. Es muy importante que esté dentro del loop, porque sino, el while se ejecutará en forma infinita al nunca cumplir la condición dada.

Afuera del while, tenemos la división. Aquí también es importante que la división esté afuera del while, ya que si está adentro, la división se hará 8 veces dándonos un resultado erróneo.
Vale aclarar que, una vez echo la división, tendremos un número de 8 bit ((255 * 8) / 8 = 255

Al momento de enviar el dato por la función usb_serial_write(med_promedio), el compilador nos dará otro segundo warning ya que, la función trabaja hasta 8 bit (byte) y la variable med_promedio es word.

Por lo demás, ya se ha explicado en el ejemplo anterior.

Aquí está el video de como se comporta el programa:





Adjuntado está el archivo usb2.jal; el HEX listo para grabar al PIC y el esquemático de la entrenadora de Felixils que utilicé para armar la placa. Ahí figura como es la conexión del USB.




Volver al índice.
Jesús dijo, yo soy el CAMINO, la VERDAD y la VIDA, nadie llega al PADRE si no es por mi.