Autor Tema: Consejos sobre buena programacion en C  (Leído 7590 veces)

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

Desconectado elgarbe

  • Moderadores
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Consejos sobre buena programacion en C
« Respuesta #15 en: 11 de Febrero de 2016, 06:30:30 »
Otra cosa que estoy viendo y mucho son los CallBack's... Otra muy buena técnica para aplicar!

sds.
-
Leonardo Garberoglio

Desconectado elgarbe

  • Moderadores
  • PIC24H
  • *****
  • Mensajes: 2178
Re:Consejos sobre buena programacion en C
« Respuesta #16 en: 11 de Febrero de 2016, 06:42:33 »
-
Leonardo Garberoglio

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Consejos sobre buena programacion en C
« Respuesta #17 en: 11 de Febrero de 2016, 12:13:44 »
Lo que si no recomiendo es el #tip 13.

Es absurdo. Yo creo que una restas sucesivas que hubieran entrado en 3 lineas ( 2 for + 1 asignacion ) hubieran sido mas eficientes que lo propuesto por la persona.
Peeero esta en lo cierto del modulo y de la division. Aunque como dije nunca hace enfasis en una simple resta.

El cual venceria su benchmark:

AVR:  14,400 cycles
MSP430: 11,595 cycles

Desconectado fernandogomav

  • PIC10
  • *
  • Mensajes: 32
Re:Consejos sobre buena programacion en C
« Respuesta #18 en: 11 de Febrero de 2016, 21:43:39 »
Excelente post KILLERJC, justo estoy programando un menú con un teclado matricial y una LCD y estaba enredado tratando de descifrar lo de los punteros en un cogido que me pasaron =)

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Consejos sobre buena programacion en C
« Respuesta #19 en: 12 de Febrero de 2016, 03:24:36 »
Headers

A pedido de elgarbe pongo un par de headers de ejemplo y para que lo usariamos, tambien como toda persona tengo dudas y probando traigo algunas "curiosidades". Asi que no lo tomen como si fuera la biblia por que solamente es mi opinion. Pero no creo que difiera de varias personas.

¿ Que pueden ir en los headers ?

Realmente en los headers puede ir de todo. Incluso declaraciones de variables. Es decir:

Código: C
  1. int i=0;

Pero ¿ es lo correcto ?. Pensemos por un momento para que sirve un header. En nuestros archivos fuentes podemos:
  • Crear funciones
  • Declarar los prototipos a las funciones creadas
  • Utilizar #define para algunas macros / constantes
  • Poner un enum, struct, typedef
  • Declarar variables

¿ para que sirve el .h si todo lo podemos hacer desde el los archivos .c ?

Hay una sola cosa que en la lista parece no estar. Y es que si yo quisiera tener esos #define en varios archivos .c deberia copiarlos en todos, y peor aun, si necesitara cambiar 1 solo parametro deberia recorrerme todos los archivos para cambiarlo ( que seguro me olvidaria de alguno ). Entonces seria mejor crear un .h e incluirlos en todos.

Nuestros .h son archivos compartidos. Uno podria pensar equivocamente que el .h sirve como "nexo" a un archivo fuente. El mejor ejemplo es cuando creamos nuestras librerias. En el que ponemos todos los prototipos y defines que necesite nuestra libreria.c y para usar las funciones incorporamos ese .h a el main.c . Pero es posible crear un nexo entre las librerias sin usar un header.. Este simple ejemplo lo demuestra:


auxiliar.c
Código: C
  1. void funcion1(int x){
  2.    
  3.     int i,j;
  4.     for (i=0;i<10;i++)
  5.     {
  6.         j+=i;
  7.     }
  8. }

main.c
Código: C
  1. extern void funcion1(int x);
  2.  
  3. void main(void) {
  4.  
  5.     while(1)
  6.     {
  7.         funcion1(10);
  8.     }
  9. }

Incluso sin el extern funcionaria, esto por que los protipos a funciones vienen explicito el "extern". Y que deberia agregarse para que la persona que lo lea sepa que corresponde a otro archivo. Si es que ocurre el caso anterior que di el ejemplo.

Entonces no sirve de "nexo",ya que podemos crear un nexo sin un archivo header. Pero si para compartir datos que usemos en ambos lados. Supongamos ahora el mismo codigo pasa un puntero a una struct. Pero obviamente tengo que definir la estructura en ambos lados. Y por que soy bastante vago y no quiero escribir muchos "struct" tambien creo un tipo con el typedef. Tengo #defines y enums para usar en ambos archivos. Para este ejemplo cree 3 archivos:

auxheader.h
Código: C
  1. #ifndef AUXHEADER_H
  2. #define AUXHEADER_H
  3.  
  4.  
  5. #define START   0
  6. #define END     10
  7.  
  8. enum {  ESTADO_INICIAL = 0,
  9.         ESTADO_NUM2,
  10.         ESTADO_NUM3 };
  11.        
  12. struct myStruct {
  13.     char    *ptr;
  14.     int     large;
  15.     int     end;
  16. };
  17.  
  18. typedef struct myStruct  myS;
  19.  
  20. void funcion1(myS *segundaStruct, int valor1 , int valor2);
  21.  
  22. #endif  /* AUXHEADER_H */

En el header tengo varias cosas que voy a usar, entre ellas estan unos defines, un par de enum, la estructura, como dije el typedef y finalmente el prototipo. Como voy a usar TODO en ambos archivos entonces lo incluyo en ambos y asi sin necesidad de repetirlo en los .c puedo usarlos.

main.c
Código: C
  1. #include "auxheader.h"
  2.  
  3. void main(void) {
  4.    
  5.     myS nuevaStruct;
  6.    
  7.     nuevaStruct.end = ESTADO_INICIAL;
  8.     nuevaStruct.large = ESTADO_NUM3;
  9.    
  10.  
  11.     while(1)
  12.     {
  13.         funcion1(&nuevaStruct, START, END);
  14.     }
  15. }

auxiliar.c
Código: C
  1. #include "auxheader.h"
  2.  
  3. void funcion1(myS *segundaStruct, int valor1 , int valor2){
  4.    
  5.     int i,j;
  6.    
  7.     segundaStruct->end = ESTADO_NUM2;
  8.    
  9.     for (i=valor1;i<valor2;i++)
  10.     {
  11.         j+=i;
  12.     }
  13. }

A veces no es necesario compartir todo, podemos tener 10 funciones en nuestro auxiliar.c y tal ves solo queremos que los demas archivo tengan acceso a 2 funciones nomas. Entonces para eso podemos crear 2 headers. Uno para uso externo ( conteniendo esas 2 funciones y las definiciones necesarias ) y uno para uso interno ( conteniendo los prototipos /definiciones que se usan en ese archivo ).

¿Que ocurre si creamos una variable en nuestro header? ( yo tambien me lo preguntaba )

Como esto es nuevo para mi tambien, me refiero al jugar con los headers al punto de hacer algo "ilogico". Curiosamente esperaba un error. Ya que si incorporamos el header a 2 archivos es como si cada archivo tuviera su propia variable, o mejor dicho es como si declaramos 2 veces la variable con el mismo nombre, y pensaba que el linker iba a acusarme. Pero en XC8 funciono como si en un archivo hubiera dicho que estaba definido como externo y en el otro global, es decir ambas variables eran las mismas, en main.c cambiaba el valor y en auxiliar.c podia cambiarlo tambien. Interesante para probar y ver si se cumple en algun otro compilador.

Y como siempre ocurre, aparecen mas preguntas.. ¿Debo poner una variable en el header?

Personalmente pienso que no. Es mucho mas comodo para una persona ver la variable en cuestion en el .c que estar buscando en los .h cada una de las variables. Y si, no es algo raro de ver. Vi varias personas como se manejan unicamente con un solo archivo ( main.c ) en el que se vuelven super extensos, crean sus headers con todas las declaraciones de variables posibles para tratar de "ordenar" un poco las cosas. Entonces hay que tener 2 archivos abiertos para ir viendo cual variable hay para poder usarlas. Creo que pasa por una decision personal pero si vamos a legibilidad mejor seria en el .c . ( Aqui hablo de incluir el header solo en un solo archivo y no en varios )

Y para finalizar y dar un resumen sobre que poner y que NO poner en las cabeceras ( a mi recomendacion ) seria:

En un .h iria: Todo aquello que se desea compartir entre varios archivos para que los demas lo usen y que no ocupe espacio fisico.
  • typedef
  • Definiciones de estructuras, ojo definiciones nomas, decir cuales son sus elementos, nada de crear una estructura
  • Defines ya sea constantes o macros
  • enum
  • prototipos de funciones

Y recordar que se puede hacer 2 headers o mas (me refiero a uno privado y otro publico ). Para un mismo archivo.

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

Extern

Respecto a cuando usar extern. Preguntado por elgarbe tambien.
Pienso que el extern es bueno usarlo para no tener que modificar archivos a cada rato. Y espero explicarme con un codigo ejemplo.

Supongamos una liberia LCD 2x16. Si la libreria esta bien realizada tendriamos un port.c el cual contendria unas funciones tales como:

Código: C
  1. inline void LCDCsOn(void){
  2.         PORTBbits.RB0=1;
  3. };
  4.  
  5. inline void LCDRsOn(void){
  6.         PORTBbits.RB1=1;
  7. };
  8.  
  9. inline void LCDCsOff(void){
  10.         PORTBbits.RB0=0;
  11. };

De esta forma tendriamos que, si por alguna razon, quisieramos cambiar el microcontrolador y tal ves usar la API de un integrado de TI ( es lo unico que tengo a mano ) cambiariamos estas funciones

Código: C
  1. inline void LCDCsOff(void){
  2.         GPIOPinWrite(TFTPORT_CONT, CS , 0x00);
  3. };

Perfecto.. Nuestro modulo de LCD es independiente de nuestro microcontrolador. Solo debemos cambiar el port. Pero es fastidioso estar modificando el port.c a cada rato. Entonces creo un archivo para cada micro, portPIC.c y portTI.c . En el caso de incluirlo en el proyecto solo lo hago con el port que necesito.

Genial ahora tendria que crear un header con todas las definiciones e incluirlo en mi moduloLCD.c o ¿ mejor no ?

Si se ponen a pensar ese header tiene definiciones que solo le importarian al portTI o portPIC ( tal cual ven ahi que tengo TFTPORT_CONT y CS ), lo cual a mi moduloLCD.c no deberia importarle nada de eso. Tampoco podria crear un portPIC.h y portTI.h para incluirlo en mi moduloLCD.c, sino deberia estar modificandolo a cada rato si cambio entre un proyecto de PIC a uno de TI y viceversa, y por ser un modulo deberia servir para todos.

Existen 2 soluciones que se me ocurren ahora mismo:

- La primera seria crear un header con solo los prototipos ( supongamos LCDport.h ), entonces los portxxx.c deberian incorporar ambos headers ( el comun para el moduloLCD.c y el propio del port ).

- La segunda es la mejor, el portxx.c posee su propio .h ( con las definiciones que solo le importan a portxx.c ) , y luego en moduloLCD.c podria definirlo asi:

Código: C
  1. extern void LCDCsOn(void);
  2. extern void LCDCsOff(void);
  3. extern void LCDRsOn(void);

Y listo, ya no necesito usar algun include portxx.h, no me interesa y hace mas facil incluso la programacion de portxx.c. El archivo port sea cual sea que va a ser incorporado al proyecto deberia tener esas funciones, si no las tiene saltaria un error. Y ya con eso me libro de estar modificando moduloLCD.c, y solo deberia incluir un port o el otro segun este trabajando con PIC o el de TI. Todo lo demas se encargaria el linker.

Como resumen, el extern en una funcion lo usaria para no tener que modificando los archivos por unos pequeños cambios.
Otro ejemplo en el que vi usado extern en las funciones es el de cuando tenemos los vectores de interrupcion ( PIC16/18 no tiene :P ). Supongamos esto:

(obviamente esta incompleto el codigo, pero es para que se entienda)
Código: C
  1. void (*Vectores[6])(void) = {   // Vectores es un array de 6 punteros a funciones
  2.         Interrupt_Default,      // UART
  3.         Interrupt_Default,      // SPI
  4.         Interrupt_Default,      // I2C
  5.         Interrupt_Default,      // Timer0
  6.         Interrupt_Default,      // Timer1
  7.         Interrupt_Default}      // Timer2
  8.  
  9. void Interrupt_Default(void){
  10.         while(1);
  11. }

Este es nuestro archivo de vectores de interrupcion. Si nosotros quisieramos crear una funcion de interrupcion en otro archivo, podriamos crear un .h e incluirlo en ese archivo de vectores, entonces llega el momento en que uno no posee 1 solo archivo con funciones a agregar en los vectores, y llenamos de includes. Otra solucion es que podriamos simplemente usar extern. Si por ejemplo en interrupciones.c tengo la funcion UART_RXInt_Handler(), entonces me quedaria:

Código: C
  1. extern UART_RXInt_Handler(void);
  2.  
  3. void (*Vectores[6])(void) = {   // Vectores es un array de 6 punteros a funciones
  4.         UART_RXInt_Handler,     // UART
  5.         Interrupt_Default,      // SPI
  6.         Interrupt_Default,      // I2C
  7.         Interrupt_Default,      // Timer0
  8.         Interrupt_Default,      // Timer1
  9.         Interrupt_Default}      // Timer2
  10.  
  11. void Interrupt_Default(void){
  12.         while(1);
  13. }

Y no deberia preocuparme para nada de donde viene esa funcion, siempre y cuando corresponda el nombre/tipo/argumento.

Eso con respecto a las funciones. Con respecto a las variables ocurre algo parecido. Supongamos que tenemos una variable "global" definido en interrupcion.c y que queremos acceder a la misma desde nuestro main.c, esto podemos hacerlo como probamos antes, es decir creamos un header SOLO para esa definicion de la variable y la compartimos. 1 archivo enteramente dedicado a eso.. Algo bastante loco y que si uno se pone a pensar que puede tener mas de 1 variable global entre distintos archivos, lo cual llevaria a tener muchos .h para solamente este fin. Pero nuevamente podriamos hacer esto:

interrupcion.c
Código: C
  1. volatile int variable;

main.c
Código: C
  1. extern volatile int variable;

De esa forma le decimos al compilador que esa variable existe en otro lugar. Y que el linker luego va a unirlas.

Son los unicos casos en el que vi, y pienso que debe usarse el extern.

Desconectado Marttyn

  • Colaborador
  • PIC24H
  • *****
  • Mensajes: 1836
    • IDEAA
Re:Consejos sobre buena programacion en C
« Respuesta #20 en: 26 de Febrero de 2016, 13:46:23 »
Este hilo es genial!  :-/
Ayuda mucho a ver que hacemos mal y corregirlo. Por suerte yo lo hago todo bien  :D

Una duda que no termino de resolver es la estructura "externa" del programa, es decir, los .c y .h
Me gustaria saber cual es la forma correcta de estructurar todo. Yo actualmente guardo todo en un fichero main.c, y si veo que se hace muy largo, creo algun otro fichero .c con funciones. Incluso cuando hago librerias las guardo en .c  Nunca uso .h

Me acabo de comprar varios starter kits de microchip, donde muestran ejemplos, y la verdad que es IMPRESIONANTE la cantidad de ficheros que hay dentro del proyecto!! Y no he abierto las carpetas "system_config" porque ahi hay carpetas y ficheros infinitos! :D

* proyecto.png
(12.6 kB, 238x422 - visto 584 veces)


Me gustaria saber que se mete en un .h y que en un .c Tambien si hace falta un .h por cada .c que tenemos...

Tambien que deberiamos hacer para crear el codigo para que sea facilmente portable entre un PIC y otro?
Quizas crear ficheros (.c o .h ¿?) donde metamos los fuses, las interrupciones, la configuracion inicial... ¿?

En fin, de momento programo en CCS, pero estoy intentando pasar a X8, aunque supongo que esto sera "universal".
Si alguien puede aportar algo mas de luz, o derivarme a una web que expliquen esto seria fantastico  :g)
La gente ve las cosas que existen y se pregunta por qué.
Yo prefiero imaginar lo que no existe y preguntarme por qué no.

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re:Consejos sobre buena programacion en C
« Respuesta #21 en: 26 de Febrero de 2016, 14:46:25 »
Justo en el post anterior habla sobre los headers.

Sobre como organizarlos es el mismo problema cuando tenes que organizar una carpeta llena de archivos. En el que terminas dividiendo en mas carpetas y agrupando archivos que tengan que ver con esas carpetas, al menos eso en el "proyecto", no significa que esten asi separados en el disco.

Un ejemplo a lo que me refiero y asi lo ordeno yo.. ( uso Code Composer Studio basado en eclipse para el ARM de TI, no el CCS que usan para los PIC ), vas a ver solo incluidos muchos .c pero estan los .h que son el nexo entre los archivos para que se puedan llamar entre uno y otro.


* proyecto.png
(25.36 kB, 226x616 - visto 521 veces)


Si observas en la imagen asi lo tengo dividido yo al proyecto, hay unas cuantas carpetas de mas creadas por el doxygen que son ( latex y html )
Las creadas por el mismo compilador ( Debug, Release, Include, Binaries ) y finalmente las mias.

Que son Drivers , las cuales tengo todas mis librerias, si te fijas en el TFT320 ( 320x240 ) tengo una carpeta llamada port, lo agregue alli.
Tambien en Drivers tengo las librerias de FatFS, Touch del TFT, y lo perteneciente al TivaWare ( librerias de TI, algo asi como las plib de MCHP )

Por otro lado cree una carpeta llamada Configuracion, esto para almacenar todo lo que respecta a eso, ejemplos como el config del RTOS, tengo ahi definido algunos valores para poder trabajar con la placa de prueba.

Despues tengo creado una carpeta llamada "Menu" que lo que hace es el encargado de justamente manejar el menu en el TFT. menus.c y menus.h son la "libreria" se podria decir ( ya que es general ). Y luego poseo el archivo eventosmenu.c que son las acciones + graficos que debe crear propias al proyecto.

Finalmente 3 archivos mas:

interrupcion.c
Obviamente tiene todas las funciones de interrupcion en el caso que sean propias al proyecto y no sean pertenecientes a una libreria, por ejemplo suponete el Touch del TFT es por SPI, entonces la interrupcion de recepcion del SPI podria crearla directamente desde la libreria y solamente desde este archivo llamar a esa funcion ( handler ).

startup_ccs.c
Contiene los vectores de interrupcion/reset/excepciones.

Finalmente el main.c
Que uno pensaria que deberia ser largo, pero te encontras con esto:

Código: C
  1. int main(void) {
  2.  
  3.     //
  4.     // Configuracion del reloj
  5.     //
  6.         //  Seteo de frecuencia de cristal externo a 25 Mhz
  7.         //  Seteo de frecuencia de cristal externo a 25 Mhz
  8.         //  Seleccion del oscilador principal ( externo )
  9.         //  Activado del PLL ( desactivado por default ) y salida del PLL como reloj de sistema
  10.         //  Seteado de salida del VCO del PLL a 480 Mhz
  11.         //  Valor de frecuencia = 120Mhz
  12.  
  13.     g_ui32SysClock = ROM_SysCtlClockFreqSet(( SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000);
  14.  
  15.     ConfigPins();
  16.     ConfigureUART();
  17.  
  18.         vInitTFT();                                                             //Inicializo el TFT
  19.     vInitTouch();                                                       //Inicializo el touch
  20.     vMenuInit();
  21.  
  22.     while(1)
  23.         {
  24.                 vEjecutaEventosMenu();
  25.         }
  26. }

Que podria haber reducido mas en una simple funcion y listo.

Por eso el orden es perteneciente a cada uno. Yo separo en funciones a los archivos e intento dejar mi main lo mas simple posible, pero esto no quiere decir llamar a una funcion extremadamente extensa en otro archivo. Nuevamente por si no quedo claro es cuestion de cada uno. Personalmente me gusta asi, ya que si pienso en que algo funciona mal, puedo ir directamente al archivo que esta ejecutando eso. Supongamos que me funciona mal el TFT, voy directo a Drivers -> TFT, se que me funciona el funcionamiento del menu, voy a  Menu -> menu.c, si me funciona bien el comportamiento del menu, pero no dibujo correctamente, procedo Menu -> eventosmenu.c

Y no tener que estar navegando por un eterno main.c



Respecto a como hacer las librerias portables. Tenes que darte cuenta que lo evita que sean portables es el manejo de pines / registros propios del microcontrolador y esas cosas.
Ejemplo de un LCD 2x16, cuales son los problemas de portabilidad? Manejar los pines E,RS,RW, datos, los delays, o cualquier funcion perteneciente a una libreria de algun modulo. Lo demas es C y es comun para todos. Creas funciones "inline" en otro archivo ( supongamos portPIC.c ) y llamas a esas funciones desde tu libreria. De esa forma si queres cambiar de micro, solo necesitas usar otro port.c  Y tendrais tu libreria de forma portable entre distintos micros.



Como escribi antes ( en el post anterior ) en los .h no vas a poner nada que ocupe espacio fisico en el micro. Es decir no vas a poner funciones, no vas a poner variables, etc.
Por lo cual podes tener los fuses ( que son directivas de preprocesamiento ) , define , definiciones de struct, union ( ojo que dije definiciones y no crear la variable con el struct/union ese ), typedef, macros, etc.

Y por lo demas te sirve para llamar a funciones de los demas archivos.
« Última modificación: 26 de Febrero de 2016, 14:51:42 por KILLERJC »