viernes, 16 de diciembre de 2011

Entradas en C

En este post vamos a explicar como leer los puertos del PIC, si ha entendido el post anterior de salidas sera muy fácil. 

Como sabemos los microcontroladores tienen pines que pueden ser usados como entrada o salida. En el post anterior hablamos que si el programador no configura los puertos y llama a una función que genera una salida el compilador entiende que debe configurar ese puerto o pin como salida. En el caso de las funciones de entrada pasa lo mismo.

Funciones orientadas a bit:

valor = input (pin);

Esta función lee el estado del pin que se apunta en el argumento, es decir dentro del paréntesis se coloca la dirección del pin de entrada a leer. Como ya vimos en el post anterior las direcciones de los pines están definidas en el archivo cabecera y no es necesario aprenderlas de memoria. Si queremos leer el estado del pin RB7 podemos escribir:

valor = input (PIN_B7);

La función lee el estado del pin RB7 y los guarda en la variable que esta a la izquierda del símbolo =. La variable valor es aconsejable que sea una variable tipo booleana. Es posible usar la función directamente en el argumento de otra función o en sentencias de decisión o bucle. Por ejemplo:


if (input (PIN_B7))
{
    output_high (pin_d1); // vuelve el pin RD1=1
}
else
{
output_low (pin_d1); // vuelve el pin RD1=0
}

El ejemplo de arriba lee una entrada en RB7 y si es 1 enciende el pin RD1, si es 0 apaga el pin RD1. Veremos las sentencias de decisión en los próximos posts. El if lee el valor que esta dentro de su paréntesis y si es 1 entonces se ejecuta lo que esta dentro de las llaves que le siguen al if. Si el valor en el paréntesis es 0 se ejecuta lo que esta dentro de las llaves que siguen a la sentencia else. Podemos hacer un código que haga lo mismo mucho mas corto empleando la función de salida orientada a bit:

output_bit ( pin, valor);
Podemos hacer que el argumento valor de la función de salida sea el estado del pin leído mediante la función input(). Esto lo podemos escribir así

output_bit ( pin_d1, input (PIN_B7));
Observe que cuando la función esta como argumento de otra función o de una sentencia de control no se coloca el ;.
Funciones orientadas a byte(puertos):
value = input_a()
value = input_b()
value = input_c()
value = input_d()
value = input_e()
value = input_f()
value = input_g()
value = input_h()
value = input_j()
value = input_k()
Las funciones mostradas arriba leen el valor de su respectivo puerto y lo guardan en la variable que esta a la izquierda del símbolo =. En el caso de el PIC16f877 solo aplican las funciones en naranja, porque solo posee esos puertos. La variable debe ser de 8 bits de ancho como por ejemplo una variable tipo char, las variables serán discutidas en los próximos posts. El resultado de estas funciones también se pueden usar como argumento de otras funciones o sentencias de decisión y bucles. Por ejemplo podemos hacer un código que lea todo el puerto B y si es 0 entonces coloque un 1 en el bit RD3:
if(input_b()==0)
{
 output_high (pin_d3); // enciende el pin RD3
}
else
{
 output_low(pin_d3); Apaga el pin RD3
 


El tema de las entradas y salidas es mas o  menos amplio y lo vamos a ir tratando poco a poco en el blog.

jueves, 8 de diciembre de 2011

Operadores básicos

Los operadores son símbolos que le indican al compilador que se debe llevar a cabo una operación especificada a un cierto numero de operandos. C ofrece una gran cantidad de operadores, nosotros en este post hablaremos de algunos básicos.

Operador asignación:
El operador asignación es representado por el símbolo igual (=) y su función es asignar el valor que esta a su derecha a la variable que esta a la izquierda. Por ejemplo, supongamos que queremos asignar el valor 5 a la variable Var:

Var=5;

También podemos usarlo para caracteres:

Letra='a';

Aritméticos:
Los operadores aritméticos nos permiten realizar operaciones aritméticas básicas. A continuación se muestran algunos:


Operador Símbolo Ejemplo
Suma + x+1
Resta - 3-a
Multiplicación * 2*2
división / x/y
Módulo % r%s

Por ejemplo si queremos sumar el valor de la variable A mas el de B y guardar el resultado en la variable R hacemos lo siguiente:

R=A+B;

También podemos hacer operaciones combinadas como la que sigue:

R=A+B*C;

El compilador sigue las reglas típicas de jerarquía en operaciones matemáticas. Primero resuelve el valor de la multiplicación B*C y luego este valor es sumado a A y guardado en R. También podemos emplear paréntesis.

R=(A+B)*C;

Así se resuelve la suma primero y el resultado de esta es multiplicado a C y guardado en R. En C se evalúan primero las operaciones dentro de paréntesis luego la multiplicación, división, modulo y de ultimo la suma y resta.

Comparación:
Estos son muy empleados para la toma de decisiones dentro del programa. Podemos comparar 2 valores para saber si son iguales, si uno es mayor que otro, entre otras posibilidades. Si la condición se cumple entregan un 1 lógico si no 0.Los operadores se muestran a continuación:


Operador Símbolo Ejemplo
Igual == x==y
Diferente != x!=y
Mayor que > x>y
Menor que < x<y
Mayor o igual que >= x>=y
Menor o igual que <= x<=y

Estos operadores suelen estar dentro de decisiones y bucles que serán explicados en un próximo post. Por ejemplo:


if(a>10)
{
    funcion_mayor(); // se llama a esta funcion si el valor de a es mayor a 10
}
else
{
    funcion_menor(); // se ejecuta esta funcion si el valor de a es menor o igual a 10
}


El anterior es un ejemplo muy simple de como se puede controlar la ejecución del programa empleando operadores de comparación e instrucciones de decisión.


Lógicos:
Los operadores lógicos en C son los siguientes:


Operador Símbolo
Not !
And &&
Or ||

El operador Not produce un verdadero si su operando es falso y un falso si el operando es verdadero. Es decir, invierte el valor del operando. Por ejemplo:

!(2<8)

En el ejemplo de arriba tenemos una expresión de comparación dentro de paréntesis que podemos comprobar es verdadera. Sin embargo luego hay un operador ! que provoca que el resultado definitivo de toda la expresión completa sea Falso ó 0.

El operador And (&&) produce un verdadero solo si ambos operandos son verdaderos. Por ejemplo:

(x>10)&&(z<5)

La expresión sera verdadera solo si x es mayor que 10 y z es menor que 5.

El operador Or (||) produce un verdadero si al menos uno de los dos operandos que le rodean es verdadero. Por ejemplo:

(x>10)||(z<5)

En la expresión anterior si x es mayor a 10 o z es menor que 5 el resultado sera verdadero. Si ambas condiciones se cumplen también se obtendrá un verdadero. La única posibilidad de que el resultado sea falso es que ambas condiciones sean falsas.

En el próximo post se aplicarán bastante los operadores aquí mencionados y servirá para comprender mejor los mismos. Hay mas operadores que serán abordados en futuros posts.

Página principal

martes, 29 de noviembre de 2011

Variables

Una variable es una porción de memoria en donde se puede almacenar un cierto tipo de dato (números, letras, etc). A este espacio reservado de memoria se le asigna un nombre el cual típicamente describe su propósito.

Las variables pueden tomar muchos valores mientras se ejecuta el programa, es decir pueden cambiarse en cualquier momento. Como la naturaleza de los datos que se manejan puede ser muy diversa existen varios tipos de variables diferentes.

Tipos

El compilador CCS tiene por defecto los siguientes:
  • short :
En CCS el tipo short es para guardar valores booleanos (o ó 1) y ocupa 1 bit de espacio de memoria.


  • Char:
Las variables tipo Char se emplean para guardar caracteres ASCII y ocupan un byte de memoria.

  • int:
Se usan para guardar números enteros comprendidos entre 0 y 255 y ocupan un byte de memoria

  • long:
Son variables para manejar enteros comprendidos entre 0 y 65535. El tipo long ocupa 2 bytes de memoria

  • long long:
El tipo long long se usa para almacenar variables enteras d gran tamaño, su rango es va desde 0 hasta 4294967295 y ocupan 32 bits de memoria o 4 bytes.

  • float
Este tipo se usa para guardar números reales con un rango comprendido entre -1.5 x 10^45 y 3.4 x 10^38. Este tipo permite trabajar con decimales y su espacio en la memoria es de 32 bits.

Existen otros tipos pero por ahora nos conformamos con describir estos básicos. Otro dato importante es  que las variables enteras pueden trabajar con números negativos si se emplea el calificador signed. Debido a que el signo ocupa un bit de espacio los rangos de operación cambian. A continuación escribimos los rangos para enteros con signo:


  • signed int:
Rango: -128 a 127

  • signed long:
Rango: -32768 a 32767
  • signed long long:
Rango: -2147483648 a 2147483647
Siguen ocupando la misma cantidad de memoria pero ahora poseen rangos diferentes.

Es importante emplear el tipo adecuado según la naturaleza de la variable y también según el rango que se necesita. Emplear variables muy sobre dimensionadas implica un desperdicio de memoria y en algunos casos mayor tiempo de ejecución del código.

Los rangos y espacio de memoria descritos aquí son específicamente para el compilador CCS. Pueden ser diferentes en otros compiladores. 

Declaración 

Como existen diversos tipos de variables debemos indicarle al compilador cual tipo estamos usando.La declaración de la variable consiste en escribir el tipo y luego el nombre de la misma terminando con un carácter ';'. Por ejemplo, si necesitamos guardar la edad de una persona podemos emplear un entero de tipo int. Este tipo se ajusta bien a la variable edad porque no necesitamos números decimales para representarla. También porque el rango de valores de edad humana esta entre 0 y poco mas de 100 lo que se ajusta dentro del rango de las variables tipo int (de 0 a 255) . Para declarar la variable edad escribimos lo siguiente:

int edad;


Si en cambio necesitamos guardar un carácter debemos usar una variable tipo char .Por ejemplo supongamos que queremos crear una variable llamada letra, podemos declararla asi:

char letra;

Como se puede ver declarar una variable es muy simple y todas siguen el mismo patrón. Otro detalle importante es el lugar donde se realiza la declaración en el programa. Podemos declarar variables al inicio del programa, sin que estén dentro de la función main o ninguna otra funcion. A las variables que se declaran fuera de las funciones se les denomina variables globales.

También se pueden declarar variables locales dentro de las funciones. La diferencia entre las variables globales y locales es que las primeras pueden ser accedidas desde cualquier parte del código mientras que las variables locales solo pueden ser accedidas dentro de la función en donde son declaradas. Las variables locales solo existen dentro de la función donde son declaradas y su valor es conservado mientras se esté dentro de la función. Cuando el flujo de programa sale de la función el espacio de memoria que ocupaban anteriormente las variables locales queda libre para ser usado por nuevas variables.

Inicialización:
La inicialización consiste en asignar un valor inicial a una variable que anteriormente fue declarada. También se puede realizar al mismo tiempo que se declara la variable. 


Para inicializar se escribe el nombre de la variable declarada seguido del símbolo igual '=' y luego el valor que se quiere asignar con un caracter ';' para terminar. Por ejemplo la variable edad:


int edad=25;

Arriba declaramos e inicializamos edad al mismo tiempo, si lo hacemos por separado:

int edad;
edad=25;


El funcionamiento del código será mismo en ambos casos.
El ejemplo anterior es bastante intuitivo porque se inicializa una variable con un valor de base decimal. Hay muchos casos en donde es mas conveniente usar base hexadecimal o binario. Para esto el compilador posee un formato específico para cada base. A continuación se describen algunos:

Decimal=10;
Hexadecimal=0x0A; 
binario=0b00001010;


Arriba se inicializaron 3 variables con el mismo valor numérico pero en diferente base. Cuando se usa la base hexadecimal se debe escribir antes del numero el prefijo 0x, si se usa base 2 o binario se escribe el prefijo 0b. El compilador considera el sistema de numeración usado por defecto el decimal así que no se necesitan prefijos que lo identifiquen.


Otro caso muy importante es el de inicializar una variable tipo char. Para esto se emplean las comillas simples ' '. Por ejemplo si queremos cargar en la variable "letra" el carácter A:

char letra='A';




Pagina principal

sábado, 12 de noviembre de 2011

Arquitectura

En este blog suponemos que el lector tiene un nivel de conocimiento básico de la arquitectura de un microcontrolador. Esto no es obligatorio pero es muy bueno porque entenderá mas fácilmente el contenido y estará mejor capacitado para desarrollar software en el futuro.

En caso de que el lector no conozca la arquitectura de los PIC puede revisar el siguiente enlace:

Introducción a los PIC


Página principal

viernes, 11 de noviembre de 2011

Salidas en C

En esta entrada vamos  a describir algunas funciones que son usadas para generar salidas en un microcontrolador, salidas de tipo discretas.

El compilador CCS ofrece funciones para salidas orientadas a bit o a puertos completos. En esta sección suponemos que el lector dispone de conocimiento básico de la arquitectura del microcontrolador. 


Funciones orientadas a bit:

output_high( pin );  
output_low ( pin );

Estas funciones fueron descritas en la entrada donde se hace el primer programa, sirven para volver un pin 1 ó 0. En el espacio entre paréntesis el programador debe colocar la dirección de memoria del pin que se quiere manejar. Realmente no es necesario conocer las posiciones de memoria puesto que el archivo de cabecera las define empleando la directiva #define

La directiva #define nombre valor crea una constante y el formato para usarlo es como se muestra. Primero se escribe la directiva, luego el nombre que se le va a dar la constante (texto verde) y finalmente el valor de la misma (texto rojo).  

El archivo 16f877.h define los pines así:

#define PIN_A0  40
#define PIN_A1  41
#define PIN_A2  42
#define PIN_A3  43
#define PIN_A4  44
#define PIN_A5  45

#define PIN_B0  48
#define PIN_B1  49
#define PIN_B2  50
#define PIN_B3  51
#define PIN_B4  52
#define PIN_B5  53
#define PIN_B6  54
#define PIN_B7  55

#define PIN_C0  56
#define PIN_C1  57
#define PIN_C2  58
#define PIN_C3  59
#define PIN_C4  60
#define PIN_C5  61
#define PIN_C6  62
#define PIN_C7  63

#define PIN_D0  64
#define PIN_D1  65
#define PIN_D2  66
#define PIN_D3  67
#define PIN_D4  68
#define PIN_D5  69
#define PIN_D6  70
#define PIN_D7  71

#define PIN_E0  72
#define PIN_E1  73
#define PIN_E2  74

De manera que si queremos volver un 1 el pin B7 escribimos:
output_high( PIN_B7 ); 
Mucho mas fácil que saber todas las posiciones de memoria.

Otra función orientada a bit es:

output_bit ( pin, valor);

Es parecida a las anteriores pero posee 2 argumentos dentro del paréntesis. El primero es el pin de salida sobre el cual la función tendrá efecto ( pin ). El segundo es el valor que le queremos asignar, debe ser 0 ó 1.

Funciones orientadas a puertos:

output_a (valor);
output_b (valor);
output_c (valor);
output_d (valor);
output_e (valor);

Con las funciones mostradas arriba podemos sacar valores por el puerto completo. Dentro del paréntesis debe ir un número entre 0 y 255. Ese "valor" se escribirá en el puerto en forma de un número binario.

Los puertos de los microcontroladores pueden ser configurados como entradas o salidas. Hay varias formas de configurar los puertos, si no se especifica su configuración (es decir si no le indicamos cuales pines serán configurados como entradas y cuales como salidas) el compilador los configura cada vez que se ejecuta una función de entrada/salida. Por ejemplo, si no especificamos configuración y llamamos a la función output_d(valor); el compilador entiende que queremos usar el puerto 'd' como salida.

Página principal

martes, 1 de noviembre de 2011

Temporizaciones en C

Las temporizaciones son de uso frecuente en la programación de sistemas embebidos, podría decir que la gran mayoría de las aplicaciones para PIC que uno se pueda imaginar necesitan algún tipo de rutinas de tiempo.
El compilador CCS ofrece una alternativa muy simple para crear retardos empleando la función delay_ms(tiempo);.
Cuando se ejecuta esta función el PIC se detiene por un tiempo determinado y  luego continua operando. El tiempo lo puede seleccionar el programador y se debe indicar en el argumento de la función (dentro del paréntesis, el espacio que dice tiempo), Se puede ingresar cualquier valor entre 0 y 65535. El numero indica el tiempo en mili segundos que durará el retardo.

La función no emplea los timers internos del PIC. Los retardos son generados mediante bucles, calculando el tiempo que el procesador se demora en ejecutar cada instrucción. No voy a profundizar mucho en como genera los retardos el compilador, si el lector ya ha programado en ensamblador debe tener una buena idea de como funciona. 

Ahora vamos a realizar otro código de ejemplo, usaremos el mismo circuito que describimos en la sección anterior Hola mundo

En este caso vamos a hacer oscilar la salida RD1 a una frecuencia de 1 Hz, podemos conectar un LED y observar que titila.

Para lograr lo anteriormente propuesto necesitamos escribir un código que realice los siguientes pasos:
  • Enciende RD1
  • Espera 0,5 seg
  • Apaga RD1
  • Espera 0,5 seg
Estos pasos se deben repetir de forma cíclica, a continuación mostramos el código ejemplo:

 
#include <16F877.h> // Archivo de cabecera propio del pic usado
#fuses XT,NOWDT,NOPROTECT,NOLVP // Bits de configuracion del pic
#use delay (clock = 4000000) // Le dice al compilador el valor 

// del cristal

void main() // Inicio de la funcion main
{
                           
    while(TRUE) // Ciclo infinito
    {
        output_high (pin_d1); // vuelve el pin RD1=1
        delay_ms(500);   // Retardo de 0,5 seg
        output_low (pin_d1); // vuelve el pin RD1=0
        delay_ms(500);
   // Retardo de 0,5 seg
     }                           
}


El código mostrado arriba es muy similar al anterior, podemos ver que se incluye el archivo cabecera y la directiva que se usa para la palabra de configuración (#fuses). Luego vemos el primer cambio respecto al programa anterior, la directiva 
 #use delay (clock = 4000000) , esta le indica al compilador a que velocidad esta trabajando el PIC, es decir la frecuencia de trabajo. El circuito esta conectado a un cristal 4 Mhz, de allí el porque del numero 4000000. Siempre que usemos la función delay_ms(); se debe agregar esta directiva al principio del programa.
Luego empieza la función main que es la que contiene el programa principal. Dentro del programa observamos un bucle while. El while se encarga de repetir un bloque de código mientras una condición sea verdadera. La sintaxis es como se muestra abajo:

while(Condición)
    {
     Instruccion1;

     Instruccion2;
     Instruccion3;
     }  

 La condición esta encerrada entre paréntesis, luego siguen un espacio entre llaves, en este se escribe el bloque de código a ejecutar. Las instrucciones se ejecutan en orden hasta llegar al final (desde la instrucción 1 hasta la 3). Cuando se ejecuta la ultima instrucción se verifica si la condición es verdadera, si es verdadera entonces se vuelve a ejecutar el bloque de código en orden, si no, se sale del bucle while. En las próximas entradas se estudiara a detalle el bucle while.

Para el código de ejemplo la condición del bucle while es TRUE (verdadero en inglés), lo que significa que el bucle se repetirá infinitamente.

Dentro del while hay 4 llamadas a funciones, la primera y la tercera fueron estudiadas en la entrada anterior. Estas encienden y apagan RD1 respectivamente.

También dentro del bloque de código del bucle while vemos 2 instrucciones usadas para generar retardos, delay_ms(500); al llamar la función la ejecución del código se detiene 500 mili segundos (500 mili segundos = 0.5 segundos) y luego se ejecuta la próxima instrucción.


El compilador CCS posee dentro de sus librerias una función similar a delay_ms(tiempo); que genera retardos en micro segundos en vez de generarlos en mili segundos. Esta es la función delay_us(tiempo); , se trabaja de la misma manera que la anterior.


Pagina principal