CCS - Punteros
<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!. | |||||||||
|
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> |
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> |
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> |
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> |
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> |
![]() |
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.
|
![]() |
|
CCS - Punteros
| |||||||||
IntroducciónUna 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. ¿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:
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:
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:
![]()
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);
}
![]()
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:
![]()
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)
Probando punteros, primera parteSiempre 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:
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);
}
*p así es como se debe declarar. si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la ventana LOCAL:
![]()
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:
![]()
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)
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);
}
![]()
#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);
}
![]()
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);
}
![]() 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);
}
![]()
![]()
Probando punteros, segunda parteAnalizando 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
}
![]()
![]()
![]()
Vamos a modificar este ejemplo pero usando float (4 bytes) y colocando el GPR en la dirección 0xAF
![]()
![]()
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 ... ![]()
![]()
Punteros en funcionesTodo 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);
}
![]() ![]()
¿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 ArraysComo 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);
}
![]()
![]()
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);
}
![]()
![]()
![]()
Arreglando el programa con *(p+t)![]()
![]()
*(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)
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);
}
![]()
![]() Temas relacionadosPuedes 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
| |||||||||
|
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. |






























