CCS - Punteros

De Ucontrol
Saltar a: navegación, buscar

<keywords content="TTL 74Ls164N, electronica, circuito, pic, NE555, PIC BASIC, PIC SIMULATOR IDE, esquema, circuito impreso, proyecto, gratis, download, programa, CMOS, pin, e/s, i/o, ucontrol, PIC, 16F628a, 16f84a" /> <center>


Inicio Foro Revista uControl Circuiteca Microcontroladores Electrónica Básica Herramientas y Software Tutoriales Colaboradores Enlaces
¿Ya descargaste los ejemplares GRATUITOS de la Revista uControl? ¡No te los pierdas!.
<linkedimage>

wikipage=Dado electrónico con PIC tooltip=Dado electrónico con PIC img_src=Image:dado100.jpg img_width=150px img_alt=Dado electrónico con PIC </linkedimage>

Dado electrónico
<linkedimage>

wikipage=Como trucar un servo tooltip=Como trucar un servo img_src=Image:trucaservo150.jpg img_width=150px img_alt=Como trucar un servo </linkedimage>

¿Como trucar un servo?
<linkedimage>

wikipage=Comunicación inalámbrica entre PICs tooltip=Comunicación inalámbrica entre PICs img_src=Image:TXRX150.jpg img_width=150px img_alt=Comunicación inalámbrica entre PICs </linkedimage>

Comunicación inalámbrica
<linkedimage>

wikipage=CCS - Libreria de gráficos para GLCD K0108 tooltip=CCS - Libreria de gráficos para GLCD K0108 img_src=Image:GLCD-100.gif img_width=150px img_alt=CCS - Libreria de gráficos para GLCD K0108 </linkedimage>

Gráficos con CCS
<linkedimage>

wikipage=Funcionamiento de una matriz de LEDs tooltip=Funcionamiento de una matriz de LEDs img_src=Image:GNUxx.jpg img_width=150px img_alt=Funcionamiento de una matriz de LEDs </linkedimage>

Carteles de LEDs
Foroizq.jpg
Todos los articulos y proyectos de uControl tienen su lugar en el foro. Si tienes dudas o comentarios, busca o crea el hilo correspondiente, y tendrás una rapida respuesta.
Foroder.jpg
CCS - Punteros

Introducción

Programando en CCS.

Una de las caracteristicas mas interesantes de las diferentes versiones de C son los punteros. Por supuesto, CCS permite el manejo de punteros, con lo que nuestros progamas pueden aprovechar toda la potencia de esta herramienta.

El presente artículo fue escrito por Pedro (PalitroqueZ), un amigo de uControl. Su dirección de correo elecrónico es palitroquez@gmail.com.

Contenido

¿Qué es un puntero?

Un puntero es una variable cuya finalidad es almacenar números ENTEROS POSITIVOS. Estos números no son números al azar, son direcciones de la memoria que posee el hardware del microcontrolador (memoria de programa o RAM).

¿Para que pueden servir los punteros?

Esta es la pregunta que puede alborotar a mas de un programador de C. Sirve para muchísimas cosas:

  • Acceso a la memoria RAM del PIC.
  • Ahorrar memoria RAM.
  • Modificar más de una variable dentro de una función (y por consiguiente devolver mas de un valor)
  • En arreglos y cadenas strings (arrays, matrices) juega un papel importantísimo.
  • Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa).
  • En un ordenador se amplía el abanico de opciones.

Más abajo veremos detalladamente como hacer todo esto.

¿Como funcionan los punteros?

Para entender el uso de estas variables especiales hay que comprender bien un concepto:

Cuando se crea una variable en CCS (llamado registro en ensamblador), el compilador reserva un espacio de memoria cuyo tamaño varia de acuerdo al tipo de dato.

Como todo en el mundo electrónico/digital, está basado en 2 cosas:

  • El registro: es la casilla donde se almacena el dato.
  • La dirección del registro: es la posición en la memoria donde está alojado el registro.

así pues tenemos 2 elementos diferentes pero que se relacionan.

Conociendo la dirección del registro o variable y pudiéndolo manejar nos da un poderosa herramienta para agilizar/simplificar nuestros programas.

¿Como podemos acceder a la dirección de una variable?

En CCS se hace a través del operador &. Veamos un ejemplo:

Ejemplo1:

#include <18F4550.h>
#use delay(clock=4000000)
void main(){
  int t,k;
  t=5;
  k= &t;
  delay_cycles(1);
}

al simular en el MPLAB tenemos:


CCS-PUNTEROS-ima1.jpg


Cuando detenemos en delay_cycles(1) vemos que en k se guarda la dirección de la variable t, ¿y que guarda t? guarda el número 5. todo se realiza usando memoria RAM ó el registro de propósito general GPR.

Vamos a cambiar ligeramente el código. Usemos 3 variables tipo entero (int):

#include <18F4550.h>
#use delay(clock=4000000)
void main(){
  int k,l,m; 
  int t,u,v;
  t=0xfa; u=0xfb; v=0xfc;
  k= &t; l= &u;  m= &v;
  delay_cycles(1);
}


CCS-PUNTEROS-ima2.jpg


se repite lo mismo, el resultado de las direcciones en k, l y m son contiguas. Pero... ¿por que?

Para responder esta pregunta vamos a cambiar el código otra vez, declarando los 3 tipos de registros conocidos, int, long y float:

#include <18F4550.h>
#use delay(clock=4000000)
void main(){
  int k,l,m,n;
  int t;
  long u;
  float v;
  int z;
  t=0xfa; z=0xff; u=0xfffa; v=3.45000000;
  k= &t; 	l= &u; 	m= &v; n=&z;
  delay_cycles(1);
}

la simulación:


CCS-PUNTEROS-ima3.jpg


Observa que las direcciones de t, u y v saltan. ¿Por que?

Dependiendo del tipo de dato se consume >= 1 byte de memoria. En el caso de t es un entero, y los enteros ocupan 1 byte (0..255). u es un dato "entero largo", ocupa dos bytes (0..65535) v es un dato "coma flotante", con parte fraccionaria en el sistema decimal y toma 4 bytes de memoria (32 bits)

  • en t tenemos una dirección que ocupa un byte [0xA]
  • en u tenemos una dirección que ocupa 2 byte [0xB - 0xC]
  • en v tenemos una dirección que ocupa 4 bytes [0xD - 0x10]



Probando punteros, primera parte

Siempre que se declare una variable puntero, al momento de usarlo se debe especificar la dirección de apuntamiento de la variable normal, porque entonces no se puede guardar un dato sino sabemos donde lo vamos a guardar. (Es obvio pero es cierto)

Esto quiere decir que se le debe pasar el número por valor de la dirección de la variable normal. Recordemos que:

  • Pasar un dato por valor: se copia el dato de una variable a otra.
  • Pasar un dato por referencia: se mueve/modifica el dato en la misma variable.
  • Variable normal: la variable que normalmente usamos.
  • Variable puntero: es la variable especial que estamos estudiando.

Veamos un ejemplo sencillo usando punteros:

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  int k;      // variable normal
  int *p;     // la variable puntero
  k=0xfa;     // k <- 0xfa
  *p=0x5;      
  delay_cycles(1);
}


Dentro del código reconocemos de inmediato quien es el puntero: el que tiene el símbolo * debe ir antes de la letra p y sin separación:

*p así es como se debe declarar.

si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la ventana LOCAL:


CCS-PUNTEROS-ima4.jpg


pero... ¡no aparece nada en p! ¿Por que?

Es simple: porque no fijamos una dirección que apuntara p, y esto es muy importante saberlo, era lo que se decía al inicio de este artículo. Vamos a darle la dirección de k:

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  int k;      // variable normal
  int *p;     // la variable puntero
  p=&k;       // dirección de k copiada a p
  k=0xfa;     // k <- 0xfa
  *p=0x5;     // k <- 0x5
  delay_cycles(1);
}

el resultado:


CCS-PUNTEROS-ima5.jpg


Ahora si funciona correctamente el código. Si ven la línea:

  p=&k;       // dirección de k copiada a p

Podran ovservar que se usa el puntero sin el *. Esto significa que se guardará allí una dirección y el compilador lo interpreta de esa manera.

Y con esta línea:

  *p=0x5;     // k <- 0x5

se está modificando el contenido de k, (indirectamente)


Otro detalle a tomar en cuenta es que para apuntar cierto tipos de datos, es que se debe declarar al apuntador con el mismo tipo de datos:

int k;   // si queremos apuntar a k
int *p;  // p debe ser tipo int
char c; // si queremos apuntar a c
char *p // p debe ser tipo char

no quiere decir que el tipo de datos que contendrá el puntero sea de ese tipo de datos, el puntero siempre soportará números enteros positivos, en realidad esto ya es a nivel interno del compilador.

Un ejemplo más:

En este código hay 2 punteros y 3 variables normales:

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  int i;     // variable normal
  int *p;    // la variable puntero
  int j;
  int *q;
  int k;
  //
  p=&i;      // dirección de i copiada a p
  q=&j;
  //
  i=0xfa;    // i <- 0xfa
  j=0x11;
  k=0x22;
  //
  *p=0x5;    // i <- 0x5
  *q=0x33;
  delay_cycles(1); 
}


CCS-PUNTEROS-ima6.jpg


  • Entre i, p hay 1 byte -> i ocupa 1 byte.
  • Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.
  • Entre j, q hay 1 byte -> j ocupa 1 byte
  • Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.


Modificando el código para que i sea del tipo float:

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  float i;      // variable normal
  float *p;     // la variable puntero
  //
  long j;
  long *q;
  int k;
  //
  i=2.51;        // i <- 0xfa
  j=0x11;
  k=0x22;
  //
  p=&i;          // dirección de i copiada a p
  q=&j;
  //
  *p=3.99;       // i <- 0x5
  *q=0x33;
  delay_cycles(1);

}


CCS-PUNTEROS-ima7.jpg


  • Entre i, p hay 4 bytes -> i ocupa 4 bytes.
  • Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.
  • Entre j, q hay 2 bytes -> j ocupa 2 bytes.
  • Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.


En ambos casos a pesar que cambiamos el tipo de declaración de los punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el tamaño de un puntero es de 2 bytes. No confundir con el tipo de datos a direccionar, pues eso es otra cosa.

Vamos con otro ejemplo. Supongamos que i sea del tipo float (4 bytes) pero su apuntador lo declaramos como int (1 byte):

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  float i;      // variable normal
  int *p;       // la variable puntero
  long j;
  long *q;
  int k;
  //
  i=2.51;     // i <- 0xfa
  j=0x11;
  k=0x22;
  //
  p=&i;       // dirección de i copiada a p
  q=&j;
  //
  *p=3.99;    // i <- 0x5
  *q=0x33;
  delay_cycles(1);
}


CCS-PUNTEROS-ima8.jpg

Noten que el nuevo valor de i no corresponde con el valor que indirectamente le dimos con el apuntador.

¿Por que? Porque declaramos a ese apuntador como entero (int) y con ello le estamos diciendo al compilador que reserve para p 1 byte de dirección en vez de 4 bytes que son los que se necesitan y por eso ocurre ese truncamiento y da ese valor extraño.

Para corregir esto, se declara a p del MISMO tipo de dato de i:

#include <18F4550.h>
#use delay(clock=4000000)
//*******************************
void main(){
  float i;      // variable normal
  float *p;    // la variable puntero
  long j;
  long *q;
  int k;
  //
  i=2.51;    // i <- 0xfa
  j=0x11;
  k=0x22;
  //
  p=&i;     // dirección de i copiada a p
  q=&j;
  //
  *p=3.99;      // i <- 0x5
  *q=0x33;
  delay_cycles(1);
}


CCS-PUNTEROS-ima9.jpg


Aquí se lee que está correcto el resultado.


Nota: los punteros tiene un máximo de 2 bytes para almacenar direcciones y el CCS sigue la misma normativa. Hay una directiva llamada #device xxxxxxx


CCS-PUNTEROS-ima10.jpg


con 4 modos de selección: CCS2,CCS3,CCS4 y ANSI. Con CCS2 y CCS3 el tamaño (size) del puntero es de 1 byte en partes de 14, 16 bits y con CCS4 (modo por defecto) el size es de 2 bytes.



Probando punteros, segunda parte

Analizando nuevamente lo hablado referente al size de los punteros en CCS, y en un intento de explicar que el tipo de dato y el tamaño del apuntado son 2 cosas distintas, vamos hacer un ejemplo donde se verá claramente. Para ello vamos a usar una directiva llamada #locate, sobre la que la ayuda del compilador reza así:

#LOCATE works like #BYTE however in addition it prevents C from using the area

bueno esto quiere decir que la variable normal la puedo alojar en cualquier dirección de la RAM (dentro de ciertos limites). Algo así como si en ensamblador pusieramos:

variable_normal EQU 0xNNNN

Esto nos servirá porque sería como manipular el contenido de un puntero pero en tiempo de diseño


#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
int dato=0xaa;         // declaramos dato (GPR) y lo cargamos con 0xAA
#locate dato = 0xff 
// le decimos al compilador que dato estará en la dirección 0xFF 
//del área de registro de propósito general, traducido, en la RAM del PIC
void main(){
  int *p;  // declaramos un puntero como entero (igual que dato)
  int t;   // otra variable normal
  p=&dato;  // inicializamos al puntero
  *p=0xbb;  // dato <- 0xBB
  delay_cycles(1);  // un nop 
}


CCS-PUNTEROS-ima11.jpg


CCS-PUNTEROS-ima12.jpg


CCS-PUNTEROS-ima13.jpg


Fíjense que el puntero p ocupa 2 bytes a pesar que está declarado como int (1 byte).

Vamos a modificar este ejemplo pero usando float (4 bytes) y colocando el GPR en la dirección 0xAF


CCS-PUNTEROS-ima14.jpg


CCS-PUNTEROS-ima15.jpg


Observen que el puntero p se mantuvo en 2 bytes siendo éste declarado como float.

Supongamos un ejemplo para el PIC18F4550, en el que tenemos una memoria de datos que llega hasta 0x7FF (Pág. 66 de su hoja de datos). Para que funcione 0x7FF debe ser el 4 byte para un float, entonces


float dato=1.23456789;
#locate dato = 0x7FB
...
CCS-PUNTEROS-ima16.jpg


Si que funcionó. Pero, ¿que pasa si asignamos el dato a 0x800?


CCS-PUNTEROS-ima17.jpg


Allí vemos que el puntero se cargó bien, pero el MPLAB-SIM delata el desbordamiento, ¿Por que? Es que a partir de allí no hay memoria de datos y las direcciones se deberían leer como puros 0x0 a pesar que compiló bien, (similarmente en programas de computadoras pueden ocurrir los lazos infinitos popularmente llamado ‘se colgó la máquina’)

Punteros en funciones

Todo lo que hagamos en CCS se hace a través de funciones o procedimientos, desde el punto de vista matemático una función se define así:

Una función es una relación entre dos variables numéricas, habitualmente las denominamos x e y; a una de ellas la llamamos variable dependiente pues depende de los valores de la otra para su valor, suele ser la y; a la otra por tanto se la denomina variable independiente y suele ser la x. Pero además, para que una relación sea función, a cada valor de la variable independiente le corresponde uno o ningún valor de la variable dependiente, no le pueden corresponder dos o más valores.

Aplicándolo a la programación, significa que podemos tener varios argumentos o parámetros de entrada, pero solo tendremos un dato de salida. Y eso no es todo, en C una función pasa los argumentos por valor. ¿que quiere decir esto? Que cuando llamemos a la función y le pasemos el dato como argumento, ésta copiará ese dato en su propia función sin alterar la variable original. veamos un ejemplo:


#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
int mi_funcion(int argumento1, argumento2){
  delay_cycles(1);
  return (argumento1 + argumento2);
}
//*******************************
void main(){
  int k,l,resultado;
  k=5; L=2;
  resultado = mi_funcion(k,L);
  delay_cycles(1);
}
CCS-PUNTEROS-ima18.jpg
CCS-PUNTEROS-ima19.jpg


Noten que cuando llamo a mi_funcion, se copia el contenido de k -> argumento1 y L -> argumento2 , luego hace la suma y regresa un dato con el resultado de la suma. k y L se quedan con el mismo valor anterior.

¿y si queremos cambiar esas variables como se hace?

Bueno seguro que alguien llegará y colocará a k y L como globales y entonces así se puede modificar en cualquier lado. Pero si la variable es local, dentro de main(), no se puede modificar fuera de main()...a menos que usemos punteros. ¿y como se haría eso?

Simple: se haría pasando el argumento a la función como referencia, haciendo referencia a la dirección, es decir lo que se pasará a la función es la dirección de k, L entonces allí si se puede modificar a gusto. Un ejemplo:

#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
int mi_funcion(int argumento1, argumento2, *la_k, *la_L){
   delay_cycles(1);
   *la_k=0xFF; *la_L=0xAF;
   return (argumento1 + argumento2);
}
//*******************************
void main(){
   int k,l,resultado;
   k=5; l=2;
   resultado = mi_funcion(k,l,&k,&l);
   delay_cycles(1);
}

Punteros en Arrays

Como sabrán los arrays son arreglos de datos que van en direcciones consecutivas, es decir, uno detrás del otro. Un ejemplo de ello:

char cadena[7]={'T','o','d','o','P','i','c'};

Si lo probamos en un código:

#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
char cadena[7]={'T','o','d','o','P','i','c'};
void main(){
   char c;
   int t;
   for(t=0;t<7;t++){
      c=cadena[t];
      }
   delay_cycles(1);
}


CCS-PUNTEROS-ima20.jpg


CCS-PUNTEROS-ima21.jpg


Se pueden usar punteros en el ejemplo anterior. Veamos como:

  • Declarando un puntero como char:
char c, *p;

lo inicializamos (le damos la dirección del primer elemento del array):

p=&cadena[0];

luego hacemos un barrido de direcciones para tomar el contenido de cada elemento y guardarlo en c

for(t=0;t<7;t++){
   c= *p + t;
}

Pero ese programa tiene 2 errores y no funcionará:

El primer error es que según la precedencia del operador primero está el puntero y luego viene la suma, y así estaríamos sumando direcciones que varían, la solución es usar *(p+i)

¿Y que es eso de que varían? Pues que cuando se recorre el arrays con el puntero, este debe ir sumando direcciones, pero direcciones de números constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe acumular números enteros de 1 byte en 1 byte

Si el tipo de datos es long (entero largo) entonces el puntero debe ir sumando direcciones de 2 bytes en 2 bytes. ¿Porque digo esto? Es que p quedará fijo (la dirección) y el truco está en desplazar al puntero tantas posiciones sea el size del tipo de dato. Este sería el segundo error y la solución es la misma: *(p+i)

Vamos a cambiar ese ejemplo por números long para que se entienda

#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
long cadena[7]={1000,2000,3000,4000,5000,6000,7000};
void main(){
  long c, *p;
  int t;
  p=&cadena[0];
  for(t=0;t<7;t++){
     c= *p + t;
  }
  delay_cycles(1);
}
CCS-PUNTEROS-ima22.jpg


CCS-PUNTEROS-ima23.jpg


CCS-PUNTEROS-ima24.jpg


Fíjense que p queda inmutable, y lo que hace el programa es contenido[0] + t. ¡Grave error!

Arreglando el programa con *(p+t)

CCS-PUNTEROS-ima25.jpg


CCS-PUNTEROS-ima26.jpg


Con esto estamos garantizando que el puntero se moverá de 2 bytes en 2 bytes, es decir

*(p+t) =

0x5 + 0x2 (desplazamiento de 2 byte)-> dame el contenido de la dirección 0x5
0x5 + 1x2          "                -> dame el contenido de la dirección 0x7
0x5 + 2x2          "                -> dame el contenido de la dirección 0x9
0x5 + 3x2          "                -> dame el contenido de la dirección 0xA
...

Noten que la suma se realiza no intervalos de t sino en intervalos del ancho del tipo de dato. Pero...¿esto no es lo mismo que se hizo en el código del inicio del artículo?

O sea que ¿ c = cadena[t]; es igual a c = *(p + t) cuando p = &cadena[0]; ?

Pues si, acabamos de ver un array al desnudo, como funciona en realidad, no es mas que un puntero escondido a nuestra vista. Solo que para hacer fácil la programación el compilador lo acepta de esta manera. Si p es un puntero -> p = cadena (para el primer índice del arreglo) es totalmente válido, se acepta que cadena es un puntero constante, también se podría llamar un puntero nulo (ya que no se ve y tampoco se puede modificar).

Ejemplos validos:

cadena[0]  =   *cadena
cadena[2]  = *(cadena + 2)
cadena  = *(cadena + i)


Nota: el operador () es el primero que atiende el compilador, antes que al resto.

Acomodando el código original, el que tenía la cadena de caracteres:

#include <18F4550.h>
#use delay(clock=4000000)
//*********************************
char cadena[7]={'T','o','d','o','P','i','c'};
void main(){
   char c, *p;
   int t;
   p=cadena;
   for(t=0;t<7;t++){
      c=*(p+t); 
      }
   delay_cycles(1);
}


CCS-PUNTEROS-ima27.jpg


CCS-PUNTEROS-ima28.jpg

Temas relacionados

Puedes encontrar el resto de los temas que componene este tutorial sobre CCS en esta categoría.

Hay toda una colección de ejemplos sobre este tema. Puedes consultarlos para reforzar lo aprendido aquí.

Autor

Palitroquez.jpg Datos del Autor
Nombre: Pedro (PalitroqueZ)
email: palitroquez@gmail.com
Ver los artículos de este autor. Página con el perfil del autor.

</center>
Cclicence.png

Este contenido se rige por la licencia de Creative Commons "Licencia Creative Commons Atribución-No Comercial-Sin Obras Derivadas 3.0". Para más información, véase la licencia en su forma reducida y completa.

Herramientas personales