Convertir lenguaje C a asembler

hay alguna manera o programa que convierta un codigo de programa en lenguaje C a asembler para pic?

gracias.. es urgente..
 
Lo habitual es es el compilador de C que uses genere codigo binario (fichero HEX) pero con un desensamblador obtienes el codigo en ensamblador.

Eso si, pierdes los nombres y los nemonicos de las variables, etiquetas,etc.
Y hay que interpretar bien lo que obtienes por que puede haber zonas que no sean codigo sino datos.
Puede ser dificil en algunos programas distinguir una cosa de otra pero con un poco de trabajo se puede conseguir el codigo fuente.
 
Si estás usando el MPLAB una vez que compilaste el programa podés ver el código assembler generado yendo a View->Disassembly listing. Aparecen los comentarios y las instrucciones assembler generadas por cada instrucción de C, y por cada archivo de código fuente C.
 
Buen día, hace poco comencé en la programación por lo que se muy poco acerca del lenguaje c, me encontré con cierto código que me pareció bastante genial para realizar, el hecho es que me gustaría pasarlo de lenguaje c a .asm o al .hex. Ya que se muy poco podrían explicarme como hacer esto?. Gracias.

Este es el código en C, que aun así no se si esta correcto. Derechos reservados a RobotyPic.

Código:
//////////////////////////////////////////////////////////////////////////////// 
//                                                                            // 
//                                  ROBOT                                     //
//                                                                            //
//    Robot avanza sin chocar con obstáculos y reacciona frente a presencia   //
//                         humana y fuentes de calor                          //
//                                                                            //
//                          (c) RobotyPic  2010                               //
//                                                                            // 
////////////////////////////////////////////////////////////////////////////////

#include <16F876A.h>
#fuses NOWDT, XT, NOPROTECT              
#use delay(clock=4000000)                //Frecuencia del cristal oscilador 4MHz
#use i2c(master, SCL=PIN_C3, SDA=PIN_C4)        //Configuración comunicación I2C

#byte PIR1=0x0C
#byte trisa=0x85
#byte porta=0x05
#byte trisb=0x86
#byte portb=0x06
#byte trisc=0x87
#byte portc=0x07

#define BIT_TEST_ON     output_high(PIN_C0)
#define BIT_TEST_OFF    output_low(PIN_C0)
#define AVANCE output_high(PIN_B1),output_high(PIN_B3),output_low(PIN_B2),output_low(PIN_B4)
#define RETROCESO       output_low(PIN_B1),output_low(PIN_B3),output_high(PIN_B2),output_high(PIN_B4)
#define PARO            output_low(PIN_B1),output_low(PIN_B3),output_low(PIN_B2),output_low(PIN_B4)
#define GIRO_DERECHA    output_high(PIN_B1),output_low(PIN_B3),output_low(PIN_B2),output_high(PIN_B4)
#define GIRO_IZQUIERDA  output_low(PIN_B1),output_high(PIN_B3),output_high(PIN_B2),output_low(PIN_B4)
#define BUMPER_DER      bit_test(porta,4)
#define BUMPER_IZQ      bit_test(porta,5)
#bit  ojo_derecho    =  PORTB.7   //Bit 7 puerto B ojo derecho
#bit  ojo_izquierdo  =  PORTB.6   //Bit 6 puerto B ojo izquierdo

/********************** Prototipos de las funciones ***************************/

void main (void);                //función principal
void pulso_test (void);          //da pulso y calcula distancia al obstáculo
void ccp2_int (void);            //mide pulso de eco ultrasonidos
void navegacion (void);          //avance y detección obstáculos por ultrasonidos
void bumper (void);              //avance y detección obstáculos por bumpers
void servo_tpa81 (void);         //establece posiciones de servos de los TPA81
void funcion_TPA81(void);        //reacción frente a estímulos de los TPA81
void lectura_tpa81(void);        //lectura valores tª de los TPA81 
void movimiento_TPA81(void);     //Avance o retroceso segun distancia al foco

/******************** Variables para lectura obstáculo ************************/

int1 nuevopulso=0;            //Entra otro pulso de lectura
int16 TFB=0,TFS=0,TF=0;       //Tiempo flancos
float AP=0.0;                 //Valor del pulso del obstáculo en microsegundos
int16 distancia=0;            //Valor distancia al obstáculo
int16 distancia_derecha=0;    //Valor distancia al obstáculo de la derecha
int16 distancia_izquierda=0;  //Valor distancia al obstáculo de la izquierda
int1 cambio=0;                //Control flanco subida o bajada del impulso leido

/********* Variables para lectura tª y control servos de los TPA81 ************/

int TPA81_h_ID    = 0xD0;        //Dirección I2C de acceso al TPA81
int TPA81_v_ID    = 0xD2;        //Dirección I2C de acceso al TPA81 vertical     
int b[10];                       //Buffer para datos lectura TPA81
int servo_h;                     //Posición del servo horizontal
int servo_v;                     //Posición del servo vertical
int i;                           //Posiciones para Buffer de datos temperatura  
int media_a;                     //El valor medio de b[2] b[3] y b[4]
int media_b;                     //El valor medio de b[7] b[8] y b[9]
int condicion;                   //Condición para girar sensor térmico
short int flag_calor_h=0;        //Con 1 se ha detectado fuente de calor
short int flag_calor_v=0;        //Con 1 se ha detectado fuente de calor
short int flag_presencia_h=0;    //Con 1 se ha detectado presencia
short int flag_presencia_v=0;    //Con 1 se ha detectado presencia
int flag_h=0;
int flag_v=0;
int repeticiones;                //Número de repeticiones de función

/******************************************************************************/
/********************* FUNCIÓN CONTROL DE LOS SERVOS **************************/
/*********** Control de las posiciones de los servos con tpa81 ****************/

void servo_tpa81 ( byte slaveID, byte servo_pos ) {
   i2c_start();                  //Comienzo de la comunicación I2C ...
   i2c_write(slaveID);           //...con la dirección del TPA81...
   i2c_write(0);            //...apuntando a la posición 0 del registro del TPA81
   i2c_write(servo_pos&0x1F);    //escribe posición del servo 
   i2c_stop ();                  //Finalización de la transmisión
}

/******************************************************************************/
/******************* FUNCIÓN DE LECTURA DEL SENSOR TPA81 **********************/
/************ Carga valores de temperatura leídos en el buffer b[] ************/

void lectura_tpa81( byte slaveID ) {
   for ( i=0; i<10; i++) {       //Contador de posiciones del buffer b[]
      i2c_start();               //Comienzo de la comunicación I2C ...
      i2c_write(slaveID);        //...con la dirección del TPA81...
      i2c_write(i);              //...apuntando a la dirección (i) del registro 
      i2c_start();               //Reinicio
      i2c_write(slaveID+1);      //Cambio a función de lectura
      b[i] =  i2c_read(0);       //Carga buffer b[] con datos leídos del TPA81
      i2c_stop ();               //Finalización de la transmisión
      delay_ms(10); 
   } 
}

/******************************************************************************/
/*********************** FUNCIÓN LECTURA DISTANCIA ****************************/
/** Da pulso test y dato obtenido por interrupción ccp2_int lo traduce a cms.**/

void pulso_test(){
   enable_interrupts(INT_CCP2); //Habilitación interrupción para medida de pulso eco
   BIT_TEST_ON;           //salida de pulso test durante 10us por Bit 0 puerto C
   delay_us(10);                
   BIT_TEST_OFF;
   
   while(nuevopulso==0)    //Espera a finalizar pulso eco (interrupción ccp2_int)
      {}
      
   if(nuevopulso==1){         //Finalizado el pulso eco se calcula su valor.
      TF=(TFB-TFS);           //Valor entre pulso de subida y bajada.
      AP=TF*1.0;             //Valor pulso leido en us(obstáculo) de 100u a 25ms
      AP = AP/58;             //Distancia del obstáculo en cm
      distancia =(int16)AP;   //paso de flotante a entero largo
      nuevopulso=0;           //Listo para recibir nuevo pulso
   }
   disable_interrupts(INT_CCP2);
}

/******************************************************************************/
/************************ FUNCIÓN RESPUESTA SEGÚN TPA81 ***********************/
/************ Reacciona frente a los estímulos del sensor TPA81 ***************/

void funcion_TPA81() {
      
      delay_ms(10);
      lectura_tpa81( TPA81_h_ID );  //Lectura valores temperatura horizontales

      condicion = ((b[1]+b[5]+b[6])/3);
        
      //obtine la media de entre b[2], b[3], b[4] y Tª ambiente
      media_a=(b[1]+b[2]+b[3]+b[4])/4;
      
      //obtiene la media de entre b[7], b[8], b[9] y Tª ambiente
      media_b=((b[1]+b[7]+b[8]+b[9])/4);
     
      //Comprueba si debe girar a la izquierda
      if (media_a > condicion){   //Si temp. a izquierda es mayor que central...
         if (media_a > media_b){    //...y además es mayor que la derecha...
               servo_h=servo_h+1;      //... entonces gira el servo una posición
               if (servo_h>=30) {      //Si servo llega al final de su giro...
                  servo_h=16;          //...lo deja en 0º ...
                  servo_tpa81 ( TPA81_h_ID, servo_h );
                  GIRO_IZQUIERDA;      //... y gira el cuerpo a la izquierda
                  delay_ms(800);
                  PARO;
               }
               else servo_tpa81 ( TPA81_h_ID, servo_h ); //Sino sólo gira el servo
          }
       }
        else {
         flag_h=flag_h+1;
         delay_ms(5);
       }
       
      //Comprueba si debe girar a la derecha
      if (media_b > condicion){      //Si temp. a derecha es mayor que central...
         if (media_b > media_a){
               servo_h=servo_h-1;    //... entonces gira el servo una posición
               if (servo_h<=1){      //Si servo llega al final de su giro...
                  servo_h=16;        //...lo deja en 0º ... 
                  servo_tpa81 (TPA81_h_ID, servo_h);
                  GIRO_DERECHA;      //... y gira el cuerpo a la derecha
                  delay_ms(800);
                  PARO;
               }
               else servo_tpa81 ( TPA81_h_ID, servo_h );  //Sino sólo gira el servo
         }
      }
      else {
         flag_h=flag_h+1;
         delay_ms(5);
      }
      
      //Comprueba si algun pixel horizontal mide temperatura > 28ºC (presencia)
      flag_presencia_h=0;                    //Reinicia el flag deteccion_h
         for (i=1; i<10; i++){
            if (b[i]>28) flag_presencia_h=1;
         }
            
      if (condicion >= (media_b&&media_a)) flag_calor_h=1;  //Flag calor_h=1 cuando detecta foco en el pixel central
      else flag_calor_h=0;
      
      lectura_tpa81( TPA81_v_ID );       //Lectura valores temperatura vertical

      condicion = ((b[1]+b[6])/2);
        
      //obtine la media de entre b[2], b[3], b[4] y Tª ambiente
      media_a=(b[1]+b[2]+b[3]+b[4]+b[5])/5;
      
      //obtiene la media de entre b[7], b[8], b[9] y Tª ambiente
      media_b=((b[1]+b[7]+b[8]+b[9])/4);
     
      //Comprueba si debe subir una posición
      if (media_a > condicion){    //Si temp. a izquierda es mayor que central...
         if (media_a > media_b){   //...y además es mayor que la derecha...
               servo_v=servo_v-1;  //... entonces gira el servo una posición
               if (servo_v<=5) servo_v=5;   //Si servo llega al final de su giro lo deja en misma posición 
               servo_tpa81 ( TPA81_v_ID, servo_v ); 
          }
       }
        else {
         flag_h=flag_v+1;
         delay_ms(5);
       }
       
      //Comprueba si la cabeza debe bajar una posición
      if (media_b > condicion){      //Si temp. a derecha es mayor que central...
         if (media_b > media_a){
               servo_v=servo_v+1;    //... entonces gira el servo una posición
               
               if (servo_v>=20) servo_v=20;       //Si servo llega al final de su giro lo deja en misma posición 
               servo_tpa81 ( TPA81_v_ID, servo_v );
          }
      } 
       else {
         flag_h=flag_h+1;
         delay_ms(5);
      }
      
      //Comprueba si algun pixel vertical mide temperatura > 28ºC (presencia)d
      flag_presencia_v=0;                    //Reinicia el flag deteccion_v
         for (i=1; i<10; i++){
            if (b[i]>26) flag_presencia_v=1;
         }    
      
      if (condicion >= (media_b&&media_a)) flag_calor_v=1;  //Flag calor_v=1 cuando detecta foco en el pixel central
      else flag_calor_v=0;
}

/******************************************************************************/
/************************ FUNCIÓN CALCULO DE LA DISTANCIA *********************/

#int_ccp2                           //LLamada por interrupción flanco en RC2
void ccp2_int(){
   if(cambio==0){                   //Si es flanco de subida...
      TFS=CCP_2;             //Carga  en valor flanco subida valor registro ccpr1
      setup_ccp2(CCP_CAPTURE_FE); //Configuración modo captura en flanco de bajada
      cambio=1;                     //Próximo flanco debe ser de bajada
   }
   else{                            //Si es flanco de bajada...
      TFB=CCP_2;             //Carga  en valor flanco bajada valor registro ccpr1
      setup_ccp2(CCP_CAPTURE_RE); //Configuración modo captura en flanco de subida
      cambio=0;                     //Próximo flanco debe ser de subida
      if(nuevopulso==0)             //Fin de pulso...
         nuevopulso=1;              //Pulso finalizado.
   }
}

/******************************************************************************/
/************************ FUNCIÓN LECTURA BUMPER ******************************/

void bumper(){
   while (!BUMPER_DER) GIRO_IZQUIERDA;       //Cuando detecte bumper derecho
   
   while (!BUMPER_IZQ) GIRO_DERECHA;         //Cuando detecte bumper izquierdo
      
   AVANCE;
}  

/******************************************************************************/
/********************* FUNCIÓN AVANCE - DETECCIÓN OBSTÁCULO *******************/

void navegacion(){
   delay_ms(500);          //Tiempo entre lecturas de distancia           
   pulso_test();
   if (distancia>30){      //Si distancia >30cm avanza
      AVANCE;
      Bumper();            //Comprueba obstáculos laterales en el avance
   }
   else{
      PARO;
      
      servo_h=1;                             //Posición servo -90º
      servo_tpa81 ( TPA81_h_ID, servo_h ); //Manda posición del servo a TPA81     
      delay_ms(700);                         //Tiempo servo en -90º
      pulso_test();
      distancia_derecha=distancia;
      servo_h=16;                            //Posición servo 0º
      servo_tpa81 ( TPA81_h_ID, servo_h);    //Manda posición del servo a TPA81
      delay_ms (300);                        //Tiempo servo 0º
      servo_h=30;                            //Posición servo +90º
      servo_tpa81 ( TPA81_h_ID, servo_h );   //Manda posición del servo a TPA81
      delay_ms(700);                         //Tiempo servo +90º
      pulso_test();
      distancia_izquierda=distancia;
      servo_h=16;                            //Posición servo 0º
      servo_tpa81 ( TPA81_h_ID, servo_h );   //Manda posición del servo a TPA81
      
      if (distancia_derecha>distancia_izquierda){
         GIRO_DERECHA;
         delay_ms(500);
      }
      else{
         GIRO_IZQUIERDA;
         delay_ms(500);
      }
   }
}

/******************************************************************************/
/*********************** FUNCIÓN MOVIMIENTO TPA81 *****************************/

void movimiento_TPA81 () {
      //Se mantendrá entre 1,5 m y 2,5 m de la fuente de calor
      if ((flag_calor_v && flag_calor_h)==1){
         
            pulso_test();                 //Comprueba distancia a la fuente
            delay_ms(300);
         
         if (distancia>130){ 
            //Antes de avanzar se posiciona frente a la fuente de calor tomando
            //como referencia el ángulo de giro del servo del cuello horizontal
            if (servo_h<14){
               GIRO_DERECHA; 
               delay_ms(((16-servo_h)*60));
               servo_h=16;                        //Deja el servo en posición 0º
               servo_tpa81 ( TPA81_h_ID, servo_h );
            }
            if (servo_h>18){
               GIRO_IZQUIERDA; 
               delay_ms(((servo_h-16)*60));
               servo_h=16;                        //Deja el servo en posición 0º
               servo_tpa81 ( TPA81_h_ID, servo_h );
            }
            AVANCE;                    //Con distancia a objeto > 1,3m avanza...
            while (distancia>130) {    //...hasta estar a 1,3m del objeto...     
               bumper();               //...usando bumpers    
               delay_ms(300);
               pulso_test();           //Revisa distancia a la fuente
            }
            PARO;                      //Acercado ya hasta 1\'3m, se para
         }               
         
         if (distancia<80){            //Si distancia a objeto < 0,8m ... 
            RETROCESO;                 // ...retrocede...
            while (distancia<80) {     // ...hasta estar a 0,8m del objeto
               pulso_test();           //Revisa distancia al objeto
               delay_ms(300);
            }
            PARO;                      //Separado ya a 0\'8m, se para
         }
          
         if (80<distancia<130) PARO;   //Entre 0,8m y 1,3m se para.
            
      }
}

/******************************************************************************/
/*************************** FUNCIÓN PRINCIPAL ********************************/

void main(){
   trisa=0xff;
   trisb=0x00;                      //Puerto B todo salidas
   trisc=0b00000100;                //Puerto C definición de entradas y salidas
   
   servo_h=16;                      //Posición 0º del servo horizontal
   servo_v=14;                      //Posición 0º del servo vertical
     
   PARO;                            //Inicia con el robot parado
  
   setup_timer_1(T1_INTERNAL);      //Configuración timer1 para lectura obstáculo
   setup_ccp2(CCP_CAPTURE_RE);   //Configuración modo captura en flanco de subida
   
   disable_interrupts(INT_TIMER1);
   disable_interrupts(INT_CCP2);  //Deshabilitación interrupción modo comparación
   enable_interrupts (GLOBAL);
   
   ojo_derecho=1;
   ojo_izquierdo=1;
   delay_ms(2000);                  //Estabilización en el arranque del sistema
   
   while (1){
   
         funcion_TPA81();        //Salto a la función del TPA 
         if (flag_presencia_v==1||flag_presencia_h==1) {  //Si detecta presencia...
            PARO;

           for(repeticiones=0;repeticiones<6;repeticiones++){//nºmovimientos para posicionar la cabeza
               funcion_TPA81();        //Salto a la función del TPA 
            }
            movimiento_TPA81();        //Avanzar o retroceder segun distancia
         }
         
         else {
            servo_v=((servo_v+14)/2);              //Posición servo v
            servo_tpa81 ( TPA81_v_ID, servo_v); //Manda posición del servo a TPA81
            delay_ms(10);
            servo_h=((servo_h+16)/2);
            servo_tpa81 ( TPA81_h_ID, servo_h);
            navegacion();                         //Salto a la función navegacion
         }
            
   }
}
...
 
pasar un programa C a ASM
no lo veo nada practico

ese programa es poco eficiente al darle una revisada rapida

pero no es malo esta muy grande
yo diria que debes estudiar bien el codigo para almenos hacer una modificacion
 
El propio compilador C tiene la opcion de.generar el listado Assembler que logra cuando compila, incluyendo las optimizaciones.
Pero desde ya te aviso que estas intentando ir a contramano del mundo. Mejor estudia C y aprende a hacer las cosas como se debe.
 
Pues si, es como decir, yo le voy a poner llantas cuadradas a la bicicleta y andar 30 Km. Se puede, pero no es lo más práctico.
 
Última edición por un moderador:
Estimado vaness543:

A diferencia de las opiniones de TRILO-BYTE y Dr. Zoidberg, yo te animo a que lo pases a ensamblador.

Empieza por lo que ha dicho Dr. Zoidberg: el propio compilador de C suele generar un código ensamblador en el mismo proceso de generación del hex. Estúdialo. Eso significa que primero debes aprender el ensamblador del PIC16F876A.

Mantén la hoja de datos del microcontrolador a mano, ya que lo necesitarás consultar a menudo.

El aprender ensamblador, además de C, te permitirá descubrir atajos que el compilador de C no vio, por lo que el código al final será más eficiente y rápido.

Pero... todo ese proceso llevará un tiempo (muchas horas) para, a lo mejor, ahorrar unos pocos bytes. Eso es lo que querían decir TRILO-BYTE y Dr. Zoidberg: lo más valioso es tu tiempo como programador. Y si el programa no "cabe" en el micro que vas a usar, por unos céntimos más puedes comprar otro micro con más memoria. O más rápido. Otro tema distinto es si estás obligado a usar cierto micro, o hay que hacer una rutina contando los ciclos de reloj (como el caso de cuando un microcontrolador se usa para generar las imágenes de sincronía de un TV RTC).

Para que veas un ejemplo: en un reciente curso de programación de Arduino, el programa en C nos generaba una solución de 220 bytes. Mirando el código en ensamblador, fuimos capaces de reducirlo a 48 bytes.

Aprender ensamblador frente a C, es como lo que hacen los correctores ortográficos: no leen palabras enteras como las demás personas, sino que tienen que leer "cada letra", para descubrir las faltas.
 
pues si saber algo de ensamblador es bueno por ejemplo hay ocaciones que hacemos porquerias
como :

variable= 2*2 + 4*4;

en binario se hace mucho codigo pues el micro en este caso un pic debe hacer uso de varios ciclos de reloj por que carece de instrucciones de multiplicacion aparte de la instruccion suma

podemos hacer uso de una funcion de rotacion para hacer la multiplicacion 2 y la instruccion logica OR para la suma

en este caso hacemos una reduccion de instrucciones a nivel ASM sin salirnos de lenguaje C
solo hay que ver que se esta generando en el .LST del compilador para ver si estamos haciendo algunas porquerias o no


otra cosa que genera porqueria en el codigo

la instruccion XPRINTF ya sea SPRINTF o PRINTF hacen mucho uso de instrucciones para jugar con cadenas de caracteres con formato hay ocaciones que queremos enviar algo a una LCD o a un puerto COM y se hace mucho uso del print si logramos quitarnos este mal habito reducimos drasticamente el numero de bytes en un micro de bajos recursos.

es cuestion de ir viendo que codigo es mas eficiente sin sacrificarnos al ensamblador propio del micro

pues es cuestion de compatibilidad un buen codigo C puede ser muy compatible de un PIC a un AVR o un freescale dependiendo de las habilidades de programacion de cada uno

pues si el codigo esta escrito directamente en ASM solo sera funcional para el micro que fue escrito y con algunas modificaciones puede ser migrado a un micro diferente pero de la misma familia
 
Eso me pareció interesante.
¿Tendrás alguna guía o algo donde explique los malos hábitos o maneras de ahorrar (uso de variables globales, etc.)?

Es que el PIC que uso se quedo un poco corto y me gustaría reducir el espacio.
También me doy cuenta que cuando paso a una función una estructura de muchos caracteres, ocupa como un 1% del PIC, y si llamo 10 veces a la función son 10% :eek:
Así que me interesa eso para ver que cosas hacer. (Sé que se pueden revisar las instrucciones en ensamblador, pero estoy seco en ensamblador, así que no sé que ocupa y que no)

"Más vale un código eficiente y mantenible en C, que uno deficiente en ensamblador."
 
Última edición por un moderador:
si se llama MISRA C al modo de programar

la idea es hacer codigo estandar es decir un codigo para un pic16fxxx puede funcionar en un frescale , avr ,PC ,etc.

se usan directivas propias del preprocesador e instrucciones que normalmente trae un CPU como operaciones logicas y funciones como rotar ,etc.

no es facil asimilar la idea pues muchos piden ayuda exclusiva para Arduino , otros se casan con CCS y unos dicen que 16f84a en ASM es lo mejor y de ahi no salen

yo diria que si uno ya sabe programar en C no seria buena idea retroceder para solucionar un problema, mas bien creo que si ya se tiene el conocimiento es mejor pulir ese conocimiento.

si hay ocaciones que uno se acaba la memoria RAM o ROM del micro , generalmente van de la mano cuando se hace un programa largo.

para ahorrar RAM y ciclos de reloj se puede quitar todos los if() haciendo preguntas y en su lugar usar switch() , es mas rapido el switch en terminos de ejecucion.

cuando se usan 2 if haciendo preguntas ligeramente diferentes es mejor usar if() else por que a nivel ASM se tienen las banderas del CPU que hace la pregunta verdadera o negativa que facilmente se puede traducir al conocido if else

aveces es mejor escribir varias veces

var=1;
var=2;
var=3;
var=4;


que

for(i=0; i!=4 ;i++)
{
var = i;
}


por que en el primer ejemplo
hacemos a niverl asm

movlw registro
movlw registro
movlw registro
movlw registro

que

hacer una subrutina con una pregunta condicionada y hacer un MOV


es mejor manipular los strings con formatos simples que usar printf por que printf nos consume mucho rom del micro

no son cosas que se aprenden en la escuela o en un libro, se aprenden cuando uno anda trabajando en un proyecto y se nos acaba la RAM o la ROM y debemos recurrir a siertas mañas
 
Eso me pareció interesante.
¿Tendrás alguna guía o algo donde explique los malos hábitos o maneras de ahorrar (uso de variables globales, etc.)?
Hoy en día los compiladores son muy buenos, y son capaces de detectar los casos de abuso de memoria, y ahorrarlos, pero no en todas las ocasiones.

Por la red encontrarás buenas prácticas para programar en C, pero se refieren a que debes escribir programas claros, entendibles. Solo en casos muy especiales tendrás que hacer engendros con el código para intentar optimizar memoria de programa y de datos.

No he encontrado documentos para programar los PIC, solo los AVR, pero te pueden dar una idea de cómo optimizar el código.

Es que el PIC que uso se quedo un poco corto y me gustaría reducir el espacio. También me doy cuenta que cuando paso a una función una estructura de muchos caracteres, ocupa como un 1% del PIC, y si llamo 10 veces a la función son 10% :eek:
Ese 10 % se debe referir a lo que ocupan las estructuras, no la llamada a la función. Entonces... el trabajo es ver si hay partes comunes en las estructuras que puedan reducir el consumo de memoria. O si se puede usar un procedimiento que pueda generar esa información (por ejemplo, descomprimir).

Así que me interesa eso para ver que cosas hacer. (Sé que se pueden revisar las instrucciones en ensamblador, pero estoy seco en ensamblador, así que no sé que ocupa y que no)
No pasa nada: lo haces en C, y se acabó. Pero si se te queda corto, pues la opción más sencilla es gastar unos pocos céntimos más y comprar un micro con más memoria.

"Más vale un código eficiente y mantenible en C, que uno deficiente en ensamblador."
El caso es que la programación en microcontrolador es curiosa: algunos pasos (como des/activar una línea, por ejemplo), ocupan lo mismo en C que en ensamblador (una línea), así que el esfuerzo en problemas sencillos es casi el mismo. Es en los problemas mayores donde vemos la ganancia de C (al manejar flotantes, las bibliotecas con las funciones para protocolos serie/paralelo ya hechas).

Hoy en día no es como cuando estaban los micros de 8 bits (ZX Spectrum, MSX) con su limitadísima memoria (1, 16, 32, 48, 64 KB). Un solo microcontrolador puede tener igual o varias veces esa misma memoria, y por un precio ridículo, así que no es necesario perder mucho tiempo con el ensamblador si hay espacio de sobra.

Aprender ensamblador, en cambio, te ayuda a entender más la máquina. De hecho, en la hojas de datos de los microcontroladores, todos los ejemplos están en ensamblador.

Un caso extremo, para que veas lo feo que se pueden volver las cosas.

Supongamos que tenemos que hacer un código que, pulsando un botón, el micro "aprenda" el tiempo que ese botón está pulsado. Y luego, cuando se pulse otro botón, encender una luz o un motor, durante exactamente ese tiempo.

Puede servir el ejercicio para enseñar a una máquina cuánto tiempo le lleva llenar un cubo o el tiempo necesario para que una herramienta desgaste un material, etc.

El problema se puede resolver con un código parecido a este:

PHP:
#define Led1Pin            PIN4
#define Pous1Pin        PIN2
#define Pous2Pin        PIN3
#define Pous(x)            ((PIND & (1<<(x))) != 0)
#define Pous1            Pous(Pous1Pin)
#define Pous2            Pous(Pous2Pin)
#define Pous1On            (Pous1 == LOW )
#define Pous1Off        (Pous1 == HIGH)
#define Pous2On            (Pous2 == LOW )
#define LedOn(x)        (PORTD |=  (1<<(x)))
#define LedOff(x)        (PORTD &= ~(1<<(x)))
#define Led1On            LedOn    (Led1Pin)
#define Led1Off            LedOff   (Led1Pin)

enum {espera, aprendiendo, llenando} estado = espera;

void setup() {
  DDRD = 0b11110010;

//   Serial.begin(115200);
}

unsigned long ahora;
unsigned long tiempo_llenado;

void loop() {
  switch (estado) {
    case espera:
      if (Pous1On) {
        estado = aprendiendo;
    ahora = millis();
    Led1On;
    delay(20);
//     Serial.println("aprendiendo");
      }
      if (Pous2On) {
      estado = llenando;
      ahora = millis();
      Led1On;
//       Serial.println("llenando");
      }
      break;
    case aprendiendo:
    if (Pous1Off) {
        estado = espera;
        tiempo_llenado = millis() - ahora;
        Led1Off;
//         Serial.println("espera");
    }
    break;
    case llenando:
    if (millis() >= ahora + tiempo_llenado) {
        estado = espera;
        Led1Off;
//         Serial.println("espera");
    }
    break;
  }
}
que es la solución "normal" que todos escogeríamos (una tabla de estados, y listo).

Pero... esta solución, al compilarla, ocupa muchas decenas de bytes de memoria de programa y datos. Si queremos una solución más "comprimida", debemos buscar un algoritmo que resuelva el problema de forma más breve. Y con instrucciones muy cercanas a la máquina.

Una posible solución es este arcano:
PHP:
// --------------------------------------------------------------------------------

static int _INCLUDE_ARDUINO_H_HERE_;

/**
 * Generate unsigned long (uint32_t) with specified bit(s) set.
 *
 * Supports setting up to 8 individual bits. Typically used to set up registers.
 * Note: Overrides Arduino.h homonym macro, adding multi-bits functionality.
 *
 * Examples:
 *     bit(0, 2)  ->  (1ul<<(0) | 1ul<<(2))  ->  (1 | 4)  ->  5
 *     bit(ADEN, ADIE, ADSC)  ->  (1ul<<(ADEN) | 1ul<<(ADIE) | 1ul<<(ADSC))
 */
#define bit(bits...)  (_bit_impl(args_nb(bits), bits))

/**
 * Get/set/clear/toggle bit(s).
 *
 * Typically used to access/modify registers, but works with variables too.
 * Supports multiple bits in one instruction. See bit() for details.
 *
 * Examples:
 *     get(PIND, SW1, SW2);              // get states of switches 1 and 2
 *     set(ADCSRA, ADEN, ADIE, ADSC);    // start ADC conversion + interrupt
 *     clr(ADCSRA, ADEN, ADIE);          // switch off ADC
 *     tgl(PORTD, LED1, LED2);           // toggle states of LEDs 1 and 2
 */
#define get(...)    _get(__VA_ARGS__)
#define set(...)    _set(__VA_ARGS__)
#define clr(...)    _clr(__VA_ARGS__)
#define tgl(...)    _tgl(__VA_ARGS__)

/**
 * Is specified bit(s) set/clear?
 *
 * If several bits are tested, all of them must be set/clear to yield 'true'.
 *
 * Examples:
 *     while (is_set(ADCSRA, ADSC));     // wait till ADC conversion is ready
 *     if (is_clr(PIND, SW1, SW2))       // both SW1 and SW2 are active (low)?
 */
#define is_set(...) _is_set(__VA_ARGS__)
#define is_clr(...) _is_clr(__VA_ARGS__)

/*
 * Implementations
 */
// Preprocessor voodoo: count number of macro variadic arguments (0 to 8)
#define args_nb(...)  _args_nb(,##__VA_ARGS__,)
#define _args_nb(...) __args_nb(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __args_nb(_E,_0,_1,_2,_3,_4,_5,_6,_7,_8, N, ...)  N
// Set bits implementation (steps 1 and 2)
#define _bit_impl(n, args...)    __bit_impl(n, args)
#define __bit_impl(n, args...)   __bit_##n(args)
// Set bits implementation (steps 3: bits 0 to 8)
#define __bit_0(none)            0
#define __bit_1(bit)             1ul<<(bit)
#define __bit_2(bit, next)       __bit_1(bit) | __bit_1(next)
#define __bit_3(bit, next...)    __bit_1(bit) | __bit_2(next)
#define __bit_4(bit, next...)    __bit_1(bit) | __bit_3(next)
#define __bit_5(bit, next...)    __bit_1(bit) | __bit_4(next)
#define __bit_6(bit, next...)    __bit_1(bit) | __bit_5(next)
#define __bit_7(bit, next...)    __bit_1(bit) | __bit_6(next)
#define __bit_8(bit, next...)    __bit_1(bit) | __bit_7(next)
// Get/set/clear/toggle register bit(s) implementations
#define _get(reg, bits...)       ((reg) & bit(bits))
#define _set(reg, bits...)       ((reg) |=  bit(bits))
#define _clr(reg, bits...)       ((reg) &= ~bit(bits))
#define _tgl(reg, bits...)       ((reg) ^=  bit(bits))
// Is bit set/clear implementations
#define _is_set(reg, bits...)    (get(reg, bits) == bit(bits))
#define _is_clr(reg, bits...)    (!get(reg, bits))

// --------------------------------------------------------------------------------

// LEDS (actives à 1)
#define L1     4
#define L1on   set(PORTD, L1);
#define L1off  clr(PORTD, L1);

// Boutons poussoirs (actifs à 0)
#define P1    PIND, 2
#define P2    PIND, 3
#define P1on    is_clr(P1)
#define P2on    is_clr(P2)
#define P1off    is_set(P1)
#define P2off    is_set(P2)

// #define getCnt() ({ \
//         __asm__ volatile ("" : "=r"(cnt)); \
//         cnt; \
// })

// compteur de temps
register uint8_t cnt   asm("r4");
register uint8_t duree asm("r5");            // durée de remplissage sauvegardée

int main() __attribute__ ((optimize("O3"
        ,"no-move-loop-invariants"            // cpse r5, r4; rjmp .-2  // Enables the loop invariant motion pass in the RTL loop optimizer. Enabled at level -O1
        ,"no-tree-loop-optimize"            //              rjmp .-4  // Perform loop optimizations on trees. This flag is enabled by default at -O and higher
//         ,"no-unsafe-loop-optimizations"
//         ,"no-reschedule-modulo-scheduled-loops"
//         ,"no-aggressive-loop-optimizations"
//         ,"no-gcse"
//         ,"no-rerun-cse-after-loop"
//         ,"no-prefetch-loop-arrays"
)));

int main() {

    set(DDRD, 4);            // L1 en sortie

    TCCR0B = bit(CS02, CS00);        // diviseur du timer0: clk/1024 (64µs)
    TIMSK0 = bit(TOIE0);        // enable timer0 overflow interrupt
    sei();

    while (1) {
    if (P1on) {            // P1? mesure du temps...
        L1on;
        while (P1on);        // attend que P1 soit relaché
        duree = cnt;        // sauvegarde la durée
    }
    if (P2on) {            // P2? remplissage...
        L1on;
                    // attend la durée sauvegardée
        while ( duree != cnt ) ;

//         while (duree != *((volatile uint8_t*)(0x0004))) ;
//          while (duree != getCnt());
//        asm ("cpse %0, %1" "\n\t" "rjmp .-4"::"r"(cnt), "r"(duree));
    }
    L1off;
    cnt = 0;            // réinitialise le compteur
    }
}

ISR(TIMER0_OVF_vect, ISR_NAKED) {    // tous les 16.384ms (256x64µs)
    cnt++;                // incrémente le compteur
    reti();
}

La clave está en esta línea:
PHP:
     while ( duree != cnt ) ;
Para nosotros está muy clara: es un bucle infinito en que esperamos que la variable cnt llegue al valor que almacena duree. Como la variable cnt se actualiza por la rutina de interrupción, pues ese bucle realmente no es infinito.

Pero... para el compilador ni siquiera es un bucle: analizando el recorrido de las variables por el código llega a la conclusión de que las dos variables siempre tienen el mismo valor, por lo que la condición nunca se cumple, por lo que, directamente, no traduce a ensamblador ese bucle. Sin más.

Y no vale con que le digamos al compilador que no, que cnt y duree son dos variables volatile que debe consultar en todo momento. Si las definimos de esa manera, el compilador sí que traducirá el bucle, pero hará TRAMPA: primero leerá el valor de cnt y lo guardará en un registro del procesador, y luego entrará en el bucle sin fin. Y esta vez, sí que será sin fin (ya que el registro que está usando no es el mismo que almacena el valor de cnt actualizado desde la interrupción).

A partir de ese momento, comienza una lucha entre programador y compilador, para ver cómo demonios podemos convencerle de que haga solo-lo-que-le-estamos-diciendo-que-haga, nada más.

Una primera solución es... destrozar el bucle y convertir cnt en una macro:
PHP:
 #define getCnt() ({ \
         __asm__ volatile ("" : "=r"(cnt)); \
         cnt; \
 })

# ...
          while (duree != getCnt());
Ahora sí que lo hace bien, pero... el código es horrible de feo.

Otra opción es... como los registros de los microcontroladores están mapeados en el mapa de direcciones, entonces ajustamos que la variable cnt esté en el registro r4, y en el bucle le decimos al compilador que por favor lea la dirección de memoria correspondiente a ese registro:
PHP:
register uint8_t cnt   asm("r4");

# ...
        while (duree != *((volatile uint8_t*)(0x0004))) ;
El código es... igual de horrible que el anterior, aunque esta vez se parece más a cuando tienes que programar un sistema de forma "muy directa". Pero fea de todas formas.

Otra opción es... escribirlo en puro ensamblador, en el propio lenguaje C:

PHP:
        asm ("cpse %0, %1" "\n\t" "rjmp .-4"::"r"(cnt), "r"(duree));
Quitamos el while() y lo sustituimos por esa macro, que crea las instrucciones en máquina necesarias para hacer la comparación (cpse) y el salto hacia atrás (rjmp .-4) mientras no se cumpla la condición.

La impresión que se queda es que el programador, en ese punto, no encontró otra solución que usar un calzador para meter ese bucle "a mano". Y queda la duda: "¿es que no hay otra forma?".

La solución final viene de ajustar, no el código, sino las propias opciones de compilación:
PHP:
register uint8_t cnt   asm("r4");
register uint8_t duree asm("r5");            // durée de remplissage sauvegardée

int main() __attribute__ ((optimize("O3"
        ,"no-move-loop-invariants"            // cpse r5, r4; rjmp .-2  // Enables the loop invariant motion pass in the RTL loop optimizer. Enabled at level -O1
        ,"no-tree-loop-optimize"            //              rjmp .-4  // Perform loop optimizations on trees. This flag is enabled by default at -O and higher
//         ,"no-unsafe-loop-optimizations"
//         ,"no-reschedule-modulo-scheduled-loops"
//         ,"no-aggressive-loop-optimizations"
//         ,"no-gcse"
//         ,"no-rerun-cse-after-loop"
//         ,"no-prefetch-loop-arrays"
)));

#...
             while ( duree != cnt ) ;
Después de mucho investigar, aprendes que puedes manipular las opciones de compilación desde el propio código en C.

La línea del "int main()..." contiene un __attribute__ que permite modificar esas opciones en tiempo de compilación. Lo que sigue es que le estamos diciendo que haga una optimización de tipo "O3", PERO además, le indicamos que añada dos opciones más: "no-move-loop-invariants" (para impedir que detecte invariaciones dentro de bucles), y "no-tree-loop-optimize" (que no optimice bucles de forma general).

Y el while queda como queremos... después de cuatro días de investigación.

¿Merece la pena perder cuatro días? Para tu empresa, desde luego que no. Pero para ti, sí (aprendes mucho, a la fuerza).
 
Atrás
Arriba