TODOPIC

Microcontroladores PIC => Almacén del Assembler => Mensaje iniciado por: Miquel_S en 21 de Marzo de 2015, 08:31:27

Título: Dudas con operacion aritmética
Publicado por: Miquel_S en 21 de Marzo de 2015, 08:31:27
Código: C
  1. #include <16F88.h>
  2. #device adc=8
  3. #FUSES NOWDT,INTRC_IO,MCLR,PUT,NOBROWNOUT,NOCPD,NOLVP,NOIESO,NODEBUG
  4.  
  5. //--- Constantes ---
  6. #define SERVO_1   PIN_A0   //linea de control por PWM para el servo
  7. #define ADC_SERVO     1    //canal adc que se usa para el potenciometro
  8. //------------------
  9.  
  10. #use delay(clock=4000000)
  11. #use standard_io(a)
  12. #use standard_io(b)
  13.  
  14. //--- Variables ---
  15. int1 flag5RTCC = TRUE;           //indica si se completaron 5 RTCC
  16. int1 flag50mS = FALSE;           //indica si ocurrio interrupcion de timer1
  17. int1 flagPulso_Alto = FALSE;     //indica si esta el pulso en alto
  18. int8 Posicion_Servo = 93;        //donde se posiciona el servo segun la lectura del adc
  19. int8 ContRTCC = 0;               //contador de RTCC
  20. //-----------------
  21.  
  22. //--- Funciones ---
  23. void Calcular_Posicion(void);
  24. //-----------------
  25.  
  26. #int_TIMER0
  27. void TIMER0_isr(void){        //timer que cuenta 20mS
  28. //Timer de 8 bits --> 256 = 1 RTCC
  29. //Pescaler de 1:16
  30. //1 tick cada 16 uS = 0.016mS
  31. //1 RTCC cada 4.0mS (256 - 6 Ticks)
  32. //5 RTCC = 20mS
  33.  
  34.    ContRTCC = ContRTCC + 1;
  35.    set_TIMER0(6);
  36.  
  37.    if(ContRTCC == 5){
  38.       flag5RTCC = TRUE;
  39.       ContRTCC=0;
  40.    }
  41. }
  42.  
  43. #int_TIMER1
  44. void TIMER1_isr(void) {       //timer que cuenta 50mS
  45. //Timer de 16 bits --> 65536 = 1 RTCC
  46. //Pescaler de 1:1
  47. //1 tick cada 1 uS
  48. //1 RTCC cada 50mS (65536 - 15536 Ticks)
  49.  
  50.    set_TIMER1(15535);
  51.    flag50mS = TRUE;
  52. }
  53.  
  54. void main(void) {
  55.    int8 ValTMR0;
  56.  
  57.    setup_adc_ports(sAN1|VSS_VDD);
  58.    setup_adc(ADC_CLOCK_INTERNAL);
  59.    setup_spi(FALSE);
  60.    setup_timer_0(RTCC_INTERNAL|RTCC_DIV_16);
  61.    setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
  62.    setup_timer_2(T2_DISABLED,0,1);
  63.    setup_comparator(NC_NC_NC_NC);
  64.    setup_vref(FALSE);
  65.    enable_interrupts(INT_TIMER1);
  66.    enable_interrupts(INT_TIMER0);
  67.    enable_interrupts(GLOBAL);
  68.    setup_oscillator(OSC_4MHZ|OSC_INTRC);
  69.  
  70.    set_adc_channel(ADC_SERVO);
  71.    
  72.         do {
  73. //>-----------------------Movimiento del servo--------------------------------->
  74.  
  75.          //--- Calcula posicion servo segun lectura adc ---
  76.          if(flag50mS == TRUE){
  77.             Calcular_Posicion();
  78.             flag50mS = FALSE;
  79.          }
  80.          //------------------------------------------------
  81.  
  82.          //--- Dispara pulso PWM cuando se completan 5 RTCC ---
  83.                 if(flag5RTCC == TRUE){
  84.                         flag5RTCC = FALSE;
  85.             output_high(SERVO_1);
  86.             flagPulso_Alto = TRUE;
  87.                 }
  88.          //----------------------------------------------------
  89.  
  90.          //--- Baja pulso PWM cuando corresponda ---
  91.                 valTMR0 = get_TIMER0() - 6;
  92.  
  93.                 if((flagPulso_Alto == TRUE) && (valTMR0 > Posicion_Servo)){  //baja el pulso en el tiempo correspondiente a Posicion
  94.             output_low(SERVO_1);
  95.             flagPulso_Alto = FALSE;
  96.          }
  97.          //-----------------------------------------
  98. //<----------------------------------------------------------------------------<
  99.  
  100.         } while(true);
  101. }
  102.  
  103. void Calcular_Posicion(void){
  104.    int16 ValorADC;
  105.    ValorADC = read_adc();
  106.    Posicion_Servo = 31 + (int16)((int16)(ValorADC * 124) / 255);
  107. }
Buenos días, estoy intentando comprender el funcionamiento de este código escrito por Marttyn en este foro y en la función
void Calcular_Posicion(void) esta la operación aritmética
Código: C
  1. Posicion_Servo = 31 + (int16)((int16)(ValorADC * 124) / 255);
pero no consigo comprender de donde sale en valor 31 y 124, por lo que he leído en su post convierte la señal analógica del potenciómetro en ticks para posicionar el servo.

Gracias.
Título: Re: Dudas con operacion aritmética
Publicado por: elgarbe en 21 de Marzo de 2015, 11:01:26
Para mi son constantes que permiten ajustar el Offset y el Span (0 y rango) de acuerdo a cada servo. No todos los servos estan centrados con el mismo ciclo del PWM y no todos tienen el mismo recorrido. Algunos responden desde 0.5mseg hasta 1.5mseg, pero otros responden a mas o menos.

Cada Tick del RTCC son 4useg. Dependiendo del servo, responde entre 0 y 2000useg como maximo. Entre 500useg y 1500useg generalmente.
El ADC va de 0 a 255. Entonces posiscion_servo puede devolver entre 31 y 155, que al multiplicarlo por 16useg nos da 496useg y 2480useg

Entonces con esas cte se regula el recorrido del servo.

saludos!
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 21 de Marzo de 2015, 18:02:14
Gracias elgarbe por molestarte en contestar, intentare entender lo que explicas.

Saludos!
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 21 de Marzo de 2015, 18:59:07
Creo que lo entiendo, voy a intentar realizar dicha operación en ASM, si es posible  :mrgreen:

Miquel_S
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 21 de Marzo de 2015, 20:00:07
Deberias hacerlo dividido 256 si lo vas a hacer en ASM, no vas a perder mucha precision.

Posicion_Servo = 31 + (int16)((int16)(ValorADC * 124) / 256);

o que es lo mismo que :

Posicion_Servo = 31 + (int16)((int16)(ValorADC * 31) / 64);

 que es lo mismo que:

Posicion_Servo = 31 ( 1 + ValorADC / 64);

Es decir rotar 6 veces sumarle 1 y multiplicarlo por 31.. este seria lo mas simple
Pero tendrias bastante erro ya que perderias todas las comas. Sino hace la anterior, multiplicalo por 31 y luego dividilo por 64 ( 2^6 )
Y si tiene un multiplicador por HW se hace mucho mas facil aun
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 21 de Marzo de 2015, 20:54:25
 ;-) Gracias KILLERJC se ara lo que se pueda, voy a intentarlo con la primera opción:

Código: C
  1. Posicion_Servo = 31 + (int16)((int16)(ValorADC * 124) / 256);
Título: Re: Dudas con operacion aritmética
Publicado por: elgarbe en 21 de Marzo de 2015, 21:18:15
A ver si lo puedo explicar mejor por si quedan dudas.
El servo para funcionar necesita un PWM con frecuencia de 50Hz (20mseg de período). Variando el tiempo en que esta en ON el PWM se varía la posicion del servo.
Los servos se centran cuando el Ton es de 1mseg. Los servos estan 180° antihorario cuando el Ton es 0mseg. Los servos estan 180° en sentido horario cuando el Ton es de 2mseg.
El tema es que no todos los servos pueden recorrer 180°. Lo comun es tener servos de 90° en cada sentido. Entonces se centran en 1mseg y responden entre 0.5mseg y 1.5mseg.
Lo que hay que conseguir es transformar las 255 cuentas del ADC en tiempo. En el programa han configurado el Tick del RTCC en 16useg:
Código: [Seleccionar]
#int_TIMER0
void TIMER0_isr(void){        //timer que cuenta 20mS
//Timer de 8 bits --> 256 = 1 RTCC
//Pescaler de 1:16
//1 tick cada 16 uS = 0.016mS

En el programa principal, cuando se detectan 5 overflow del RTCC (20mseg) se pone la salida al servo en 1. Para saber cuando hay que poner en 0 ese pin es que usa la funcion por la cual preguntas.
En esta parte del código:
Código: [Seleccionar]
         //--- Baja pulso PWM cuando corresponda ---
  valTMR0 = get_TIMER0() - 6;
 
  if((flagPulso_Alto == TRUE) && (valTMR0 > Posicion_Servo)){  //baja el pulso en el tiempo correspondiente a Posicion
           output_low(SERVO_1);
           flagPulso_Alto = FALSE;
Se obtiene el valor del Timer, que al multiplicarlo por 16useg se obtiene el tiempo en que viene estando la señal en 1. Luego se pregunta, si el valor del timer alcanzó el valor de la cuenta que saca en esa funcion. Si ya llegamos al tiempo puesto por el potenciometro se baja la señal y se sigue repitiendo el proceso.

Como el servo responde entre 0.5mse y 1.5 mseg, esa funcion debería retorna un número entre 500useg/16useg = 31 y 1500useg/16useg=93
O sea que cuando el ADC es 0 debería devolver 31 y cuando el ADC es 255 debería devolver 93.
Si haces (93-31) = 62 es el valor por el que yo creo debería multiplicar. En el ejemplo, multiplica por 124 y no estoy seguro porque, es como que llegaría hasta 2.5mseg, o sea mas de 180° en un sentido... probalo y si no te anda, o para un lado gira mas que para el otro es por ese número...

Saludos
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 21 de Marzo de 2015, 21:40:06
;-) Gracias KILLERJC se ara lo que se pueda, voy a intentarlo con la primera opción:

Código: C
  1. Posicion_Servo = 31 + (int16)((int16)(ValorADC * 124) / 256);

Me refiero que a la operacion es lo mismo la primera que la segunda, solo simplifique el 124/256 ( ya que ahora es potencia de 2 ) a 31/64

De esa forma multiplicas por menos, tenes un valor mas pequeño y ocupas menos instrucciones.

Citar
voy a intentar realizar dicha operación en ASM, si es posible

Y es por eso que lo digo.. Ademas en el peor caso:

1023 * 124 = 126852 = 17 bits
1023 * 31 = 31713 = 15 bits

Si lo vas a hacer en ASM y no tenes multiplicador por HW, deberias sumar y sumar eso ( 31 veces ). ocuparias 2 registros Y luego dividir es un borrar carry y 2 rotar ( 1 por cada registro ) a traves del carry a la derecha por cada division por 2.
Lo cual imagino que quedaria bastante corto, segun sea el micro que estes usando, sino pasa el micro y hacemos la funcion en ASM en un ratito.

EDIT: Por que estaba mirando el techo lo hice:
Por si tenes un PIC16F

Código: ASM
  1. ; Realiza esta operacion:
  2. ; 31 + ((ValorADC * 31) / 64)
  3. ;
  4. ; Entrada:  ADRESH y ADRESL ( justificado derecha )
  5. ;
  6. ; Salida:   POSIC_H:POSIC_L ( 16 bits )
  7. ;
  8. ; Registros usados: 5 (3 temporales) - POSIC_H , POSIC_L , TEMP_L , TEMP_H , TEMP
  9. ;
  10.  
  11. ; 31d = 1Fh = 0001 1111
  12. ; Como son todos 1 simplemente roto por esa cantidad y sumo
  13. ;
  14. ; Podia hacerlo de 4 formas a la multiplicacion:
  15. ;  - Sumar 31 veces el resultado del ADC (31 sumas de 16bits )
  16. ;  - Sumar 31 por cada unidad del resultado del ADC (x sumas de 16 bits)
  17. ;  - Realizar ((ADC * 2) * 3 ) * 5 + ADC = ADC * 31 , separo en simples rotaciones y sumas ( *2 = rotacion , *3 = rotacion y suma , *5 = 2 rotacion y suma )
  18. ;  - Sumar las rotaciones de cada 1 - Elegi esta ultima por ser la mas corta en programa aunque lleve un par de ciclos mas (4 sumas de 16bits + rotacion)
  19. ;
  20.  
  21.  
  22. POSICION
  23.         MOVLW   0x04
  24.         MOVWF   TEMP
  25.         MOVF    ADRESH,W
  26.         MOVWF   TEMP_H
  27.         MOVWF   POSIC_H
  28.         MOVF    ADRESH,W
  29.         MOVWF   TEMP_L
  30.         MOVWF   POSIC_L         ;Ya tengo 2^0 realizado
  31.  
  32. MULT
  33.         BCF     STATUS,C        ; Roto registro auxiliar
  34.         RLF     TEMP_L,F
  35.         RLF     TEMP_H,F
  36.         MOVF    TEMP_L,W
  37.         ADDWF   POSIC_L,F       ; Sumo, si hubo acarreo se lo sumo al registro superior
  38.         BTFSC   STATUS,C
  39.         INCF    POSIC_H,F
  40.         MOVF    TEMP_H,W
  41.         ADDWF   POSIC_H,F       ; Directamente sumo, ya que con 10 bits nunca superaria los 16bits aun asi tuviera 1023 o 0x3FF de entrada
  42.         DECFSZ  TEMP,F          ; Termino de multiplicar?
  43.         GOTO    MULT
  44.         MOVLW   0x06            ; Cargo mi division 2^6
  45.         MOVWF   TEMP
  46. DIVI
  47.         BCF     STATUS,C
  48.         RRF     POSIC_H,F
  49.         RRF     POSIC_L,F
  50.         DECFSZ  TEMP,F          ;Termine de dividir?
  51.         GOTO    DIVI
  52.         MOVLW   0x1F            ; Sumo los 31 = 0x1F
  53.         ADDWF   POSIC_L,F
  54.         BTFSC   STATUS,C
  55.         INCF    POSIC_H,F
  56.         RETURN

Tal ves me podria haber ahorrado 2 registros o 1 pero creo que se complicaba mas. Y registros auxiliares/temporales siempre hacen falta.. asi que podes reutilizarlos en otra funcion
Y el codigo ASM que pase se podria modificar para que sea un producto general de 8 * 16 bits
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 22 de Marzo de 2015, 06:51:16
Gracias a los dos por molestaros en ayudarme a aprender, con ustedes da gusto ir a clase, ahora no puedo pero esta tarde me pongo con ello y iré contando los resultados.

Nuevamente MUCHAS GRACIAS
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 22 de Marzo de 2015, 18:01:01
Hola, solo una cosa, estoy intentando comprender el código escrito por KILLERJC y me he encontrado con una errata creo que donde dice:
Código: ASM
  1. POSICION
  2. MOVLW   0x04
  3. MOVWF   TEMP
  4. MOVF    ADRESH,W
  5. MOVWF   TEMP_H
  6. MOVWF   POSIC_H
  7. MOVF    ADRESH,W
  8. MOVWF   TEMP_L
  9. MOVWF   POSIC_L   ;Ya tengo 2^0 realizado
el segundo ADRESH,W debería de ser ADRESL,W

Saludos!
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 22 de Marzo de 2015, 18:16:20
Si perdon, el codigo le simule y funciona, nomas que usaba 2 registros ADCL y ADCH cuando simulaba (asi antes de llamar a la funcion le cargaba los valores que queria para probarlo) pero luego busque y tome del datasheet ADRESH y copie y pegue solo para ponerlo aca. y me olvide cambiarle la H por la L xD

Empiezo por la multiplicacion:

31d = 0x1F = 0001 1111

si hago ADC * 0x1F
es lo mismo que hacer:

ADC * 0x01 + ADC * 0x02 + ADC * 0x04 + ADC * 0x08 + ADC * 0x10
o que es lo mismo que:
ADC * 2^0 + ADC * 2^1 + ADC * 2^2 + ADC * 2^3 + ADC * 2^4

Son rotaciones. ahi en posicion al guardar en POSIC_x ya estaria haciendo la primera, que es ADC * 2^0 = ADC
En TEMP_x ya tengo el valor del ADC tambien
Roto TEMP_x y ahora tengo el resultado de ADC * 2^1, entonces se lo sumo a lo que ya tenia y asi consecutivamente
Si se produce un carry en la suma como es de 16 bits y le sumo 1 a la parte alta. Una ves que hice todas las multiplicaciones TEMP queda en 0 y paso a la division
La division es simple tambien, solo rotar
Y cuando roto la cantidad suficiente de veces le sumo 31d ( y obviamente me fijo si hay carry ) y listo fin

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

Si se puede simplificar o hacer mas corto, pensandolo de otra forma que recien se me ocurre..
(El caso que hice con mi ASM es mas general, se puede extender a cualquier numero preguntando si bit esta en 0 o cosas asi, este es especifico del valor 31)

Multiplica todo por 32 ( 2^5 ) y restale ADC

ADC * 32 - ADC = ADC * 31

Constaria de 5 rotaciones, guardar y luego restar, esto haria que tal vez no necesites de los TEMP_x ( aunque si llega a variar el resultado del ADC entonces deberias de tener esos TEMP_x para que no varie de ninguna forma )
Tal ves mas corto el programa en tema de ciclos pero igual de largo

El codigo:

Código: ASM
  1. POSICION2
  2.     MOVLW       0x05
  3.     MOVWF       TEMP
  4.     MOVF        ADRESH,W
  5.     MOVWF       TEMP_H
  6.     MOVWF       POSIC_H
  7.     MOVF        ADRESL,W
  8.     MOVWF       TEMP_L
  9.     MOVWF       POSIC_L
  10.  
  11. MULT2
  12.     BCF         STATUS,C        ;Multiplicacion * 32
  13.     RLF         POSIC_L,F
  14.     RLF         POSIC_H,F
  15.     DECFSZ      TEMP,F
  16.     GOTO        MULT2
  17.     MOVF        TEMP_L,W        ; Comienzo del restado POSIC = POSIC - TEMP   (16 bits ambos )
  18.     SUBWF       POSIC_L
  19.     MOVF        TEMP_H,W
  20.     BTFSS       STATUS,C
  21.     INCFSZ      TEMP_H,W
  22.     SUBWF       POSIC_H         ; Fin del restado
  23.     MOVLW       0x06            ;Division
  24.     MOVWF       TEMP
  25. DIV2
  26.     BCF         STATUS,C
  27.     RRF         POSIC_H,F
  28.     RRF         POSIC_L,F
  29.     DECFSZ      TEMP,F
  30.     GOTO        DIV2
  31.     MOVLW       0x1F            ;Suma
  32.     ADDWF       POSIC_L,F
  33.     BTFSC       STATUS,C
  34.     INCF        POSIC_H,F
  35.     RETURN

La diferencia es que este usa 12 ciclos menos que el que pase primero. Este usa 84 ciclos

-----------

Con respecto a las rotaciones pongo un 0 siempre al comienzo en en flag de carry.
asi al rotar el byte superior, si tenia un 1 en el bit0, este queda en el carry, y cuando roto el inferior ese 1 viene del registro alto.
O cuando rotas a la izquierda es lo mismo, mi idea es multiplicarlo por 2 asi que no puede agregarse ningun 1 a la cola ( o parte baja del byte )

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

EDIT:

Se me siguen ocurriendo cosas Como:
31 + ADC * 31/64 =  31 + ADC * (32 - 1)/64 =
31 + 32*ADC/64 - ADC/64 = 31 + ADC/2 - ADC/64

ADC/2 = 1 rotacion
ADC/64 = 6 rotaciones a la derecha de 16 bits o 2 rotaciones a la derecha y tomar el nibble alto del byte bajo o 2 rotaciones a la izquierda y tomar el byte mayor como el byte menor total el otro es 0.

Código: ASM
  1. POSICION3
  2.     MOVF        ADRESH,W
  3.     MOVWF       TEMP_H
  4.     MOVWF       POSIC_H
  5.     MOVF        ADRESL,W
  6.     MOVWF       TEMP_L
  7.         MOVWF   POSIC_L
  8.     BCF         STATUS,C
  9.         RRF     POSIC_H,F
  10.         RRF     POSIC_L,F
  11.     BCF         STATUS,C
  12.         RLF     TEMP_L,F
  13.         RLF     TEMP_H,F
  14.     BCF         STATUS,C
  15.         RLF     TEMP_L,F
  16.         RLF     TEMP_H,W
  17.     MOVWF   TEMP_L
  18.     CLRF    TEMP_H
  19.     MOVF    TEMP_L,W
  20.     SUBWF   POSIC_L
  21.     MOVF   TEMP_H,W
  22.     BTFSS   STATUS,C
  23.     INCFSZ  TEMP_H,W
  24.     SUBWF   POSIC_H
  25.         MOVLW   0x1F
  26.         ADDWF   POSIC_L,F
  27.         BTFSC   STATUS,C
  28.         INCF    POSIC_H,F
  29.         RETURN

27 ciclos aproximadamente. y lo unico que noto es la diferencia en el resultado, segun algunas entradas el decimal cambia el valor, en algunos numeros que lo probe resulta como que le suma 1 al resultado comparandolo con el primer codigo,
aun asi mucho mas rapido. Se opdria mejorar aun mas:
No usar 2 TEMP_x ya que un rotacion mas a POSIC y guardarlo en W + ANDLW 0xF0 + guardar en un TEMP + SWAP (en ves de MOVF para la resta) y resta de 16 - 8 y no 16 -  16bits como esta realizado. Haria mas corto el programa y utilizaria 1 solo registro temporal.

Asi:

Código: ASM
  1. POSICION4
  2.     BCF         STATUS,C        ; Division por 2
  3.     RRF         ADRESH,W
  4.     MOVWF       POSIC_H
  5.     RRF         ADRESL,W
  6.     MOVWF       POSIC_L
  7.     RRF         POSIC_H,W        ; Divido nuevamente por 2, asi poner esos 2 bits del byteH en el byteL pero uso como destino W asi no sobreescribo nada
  8.     RRF         POSIC_L,W
  9.     ANDLW       0xF0                ;Tomo nibble alto del byteL (ADC/64) y guardo en registro temporal, queda en el parte alta
  10.     MOVWF       TEMP
  11.     SWAPF       TEMP,W            ; Lo paso al nibble bajo para que sea correcto antes de restar
  12.     SUBWF       POSIC_L
  13.     BTFSS       STATUS,C          ; Si la resta da 0 le resto uno al byteH
  14.     DECF        POSIC_H,F
  15.     MOVLW       0x1F                ; Sumo 31
  16.     ADDWF       POSIC_L,F
  17.     BTFSC       STATUS,C
  18.     INCF        POSIC_H,F
  19.     RETURN

17/19 ciclos, 1 solo registro temporal, 2 registros de salida

Tendras que ver cuanto es el error maximo que se comete por haber realizado tantos calculos y la sustitucion del 1023 por el 1024 , aunque no deberia ser MUY grande o lo importante creo que seria el valor maximo y minimo,
el minimo es de 31 (0x1F)
el maximo es de 526 (0x20E) para los 2 primeros codigos y 527 (0x20F) para este ultimo

PD: Jamas pense que esa funcion podria llegar a hacerse de 17/19 ciclos.
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 23 de Marzo de 2015, 05:03:04
 ((:-)) ((:-)) ((:-)) ((:-)) ((:-)) ((:-)) ((:-))
Ya tengo curro para lo que resta de semana para poder asimilar todo lo escrito.

MUCHAS GRACIAS KILLERJC
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 23 de Marzo de 2015, 10:11:50
Perdon Miguel ahora que reviso algo estuve haciendo mal.

Es imposible que de 526/527 como maximo cuando el maximo deberia ser 124+31 = 155

Algo hice muy mal, luego lo reviso cuando tenga mas tiempo

EDIT:

Ya se cual fue mi error... me base en un ADC de 10 bits (me equivoque al ver los int 16 de la formula), cuando en realidad la formula esta realizada para un ADC de 8 bits, es correcto el resultado si hubiera un valor de 10bits ahi, pero lo que importa es que cumpla exactamente con el valor del AD
Si el ADC es de 8 bits, se resume a:

Código: ASM
  1. POSICION5
  2.     BCF         STATUS,C        ; Division por 2
  3.     RRF         ADRES,W
  4.     MOVWF       POSIC
  5.     RRF         POSIC,W
  6.     ANDLW       0x30                ; Al dividirlo por 64 solo me importan los 2 bits mas significantes
  7.     MOVWF       TEMP
  8.     SWAPF       TEMP,W            ; Lo paso al nibble bajo para que sea correcto antes de restar
  9.     SUBWF       POSIC,F
  10.     MOVLW       0x1F                ; Sumo 31
  11.     ADDWF       POSIC,F
  12.     RETURN

10 ciclos, dando efectivamente como maximo 0x9B = 155 , con una entrada de 255
Ni siquiera vale la pena el llamado a la subrutina. 4 ciclos de los 10
Mas sorprendido aun, 10 intrucciones para hacer una multiplicacion + una division + una suma
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 23 de Marzo de 2015, 15:33:11
Gracias de nuevo, pero voy a intentar trabajar con los 10bits de resolución.

Saludos!
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 23 de Marzo de 2015, 18:21:39
Entonces se deberia de modificar la ecuacion a , hay un par de cambios con eso...

31 + (int16)((int16)(ValorADC * 124) / 1024

EDIT:

Es tan asurdo que si queres hacer este cambio solo necesitas cambiar 2 cosas, justificado izquierdo y usar solo el ADRESH xD y el codigo de 8 bits funciona perfecto
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 23 de Marzo de 2015, 18:43:53
Si, esto de modificar la ecuación lo tenia en cuenta deja que lo pruebe y si no lo consigo pregunto.

Saludos!
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 24 de Marzo de 2015, 18:48:04
Hay una cosa la cual no la tengo clara, en el siguiente código realizado por KILLERJC el cual entiendo lo que hace cada instruccion
Código: ASM
  1. ;----------------------------------------------------------------------------------------
  2. ;   RUTINA PARA OPERACION ARITMETICA
  3. ;----------------------------------------------------------------------------------------
  4. POSICION
  5.             BCF              STATUS,C            ;Limpiamos el bit C del registro STATUS
  6.             RRF              ADRES,W             ;Rotamos a la derecha el valor de ADRES (Dividimos por 2)
  7.             MOVWF        POSIC                   ;ADRES --> POSIC
  8.             RRF              POSIC,W              ;Rotamos a la derecha el valor de POSIC (Dividimos por 2)
  9.             ANDLW        0x30                     ;w AND k --> w
  10.             MOVWF        TEMP                   ;W --> TEMP
  11.             SWAPF        TEMP,W               ;Los cuatro bits de más peso del registro TEMP se intercambian
  12.                                                              ;con los 4 bits de menos peso del mismo registro.
  13.             SUBWF        POSIC,F               ;Resta en complemento a dos el contenido del registro POSIC
  14.                                                              ;menos el contenido del registro W
  15.             MOVLW        0x1F                    ;Cargamos en W el valor 1F, 31 en decimal
  16.             ADDWF        POSIC,F             ;Sumamos el contenido de W al contenido del registro POSIC
  17.             RETURN
¿hay alguna lógica la cual yo no la veo para usar estos valores y operaciones de dividir, sumar, restar o por el contrario se trata de ir probando hasta conseguir el resultado esperado? lo pregunto porque aunque no tenga nada que ver ¿no es mas fácil por ejemplo sumar 20 + 20 que dividir 20 entre dos para después sumarle 30?

Perdón por mi ignorancia.

Gracias.
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 24 de Marzo de 2015, 19:12:29
Si, por supuesto que hay una logica detras todos los programas que pase. Todo parte de funcion matematica que pasaste, buscando distintas combinaciones.
Como ves en mis primeros programas podrias dividir el programa en 3 partes, multiplicacion por 31 , division por 64 y luego la suma (en el ultimo tambien pero no tan explicito), luego le buscamos un poco mas la vuelta para generar otra ecuacion, que fue la de reemplazar el 31 por (32 -1) en la formula y que es equivalente. Asi no era necesario multiplicar, sino solamente dividir por numeros potencia de 2. Incluso en el comentario de mi primer programa puse algunas cosas que se me ocurrieron:

Citar
; Podia hacerlo de 4 formas a la multiplicacion:
;  - Sumar 31 veces el resultado del ADC (31 sumas de 16bits )
;  - Sumar 31 por cada unidad del resultado del ADC (x sumas de 16 bits)
;  - Realizar ((ADC * 2) * 3 ) * 5 + ADC = ADC * 31 , separo en simples rotaciones y sumas ( *2 = rotacion , *3 = rotacion y suma , *5 = 2 rotacion y suma )
;  - Sumar las rotaciones de cada bit en 1 del multiplicador - Elegi esta ultima por ser la mas corta en programa aunque lleve un par de ciclos mas (4 sumas de 16bits + rotacion)

Luego se me ocurrio lo de hacer el (32-1) y ahi cambio y se simplifico mucho mas

La explico como funcionan o como es que llegas a esas operaciones...

Tanto para 8 bits de ADC (este ultimo era para la funcion que es 124/256 o 31/64 ojo) como para 10 bits ( este te la explico para cuando es 124/1024 = 31/256 =

8bits ADC:

Formula utilizada:
31 + (ADC * 124 ) / 256  (8 bits)
Esta formula como ya explique se puede escribir como 31 + ADC/2 - ADC/64
Tomando el 31 como (32 -1) , aplicando la propiedad distributiva y simplificando nuevamente, hasta aqui solo el manejo de simples ecuaciones.

ADC  = xxaa aaan       (Resultado del ADC)
ADC/2 = 0xxa aaaa     (Valor a conseguir)
ADC/64 =0000 00xx    (Valor a conseguir)

Lo primero que hago es rotar a la derecha ( dividir por 2) y guardo es decir ya tengo mi ADC/2 guardado, luego tengo que restarle ADC/64 si observas yo denote a los 2 bits mas significativos como XX que es lo unico que "sobrevive" a esa division
POSIC= ADC/2
Si roto una ves mas ADC/2 (sin importar el carry) este me quedaria: u0xx aaaa  ( u de indefinido segun el carry anterior, podria haberlo puesto a 0, es una instruccion mas y sin sentido ya que luego le hago un AND a ese valor)
Luego una AND con 0011 0000 quedandome  00xx 0000 y guardo en TEMP .
TEMP = ADC/64 * 16
Cuando voy a restar, realizo el cambio de nibble W = 0000 00xx = ADC/64 y resto.
POSIC = ADC/2 - ADC/64
Esto jamas me va a dar negativo por que si tengo cualquiera de los bits significativos en 1, ADC/2 va a ser mucho mayor que ADC/64 por eso quite la parte donde pregunto por el carry en la resta.
Luego sumo 31 o que es igual a 0x1F
POSIC = 31 + ADC/2 - ADC /64
Tampoco pregunto por si hay carry debido a 2 cosas, suponiendo el peor de los casos de ADC = 1111 1111 , al rotarlo queda 0111 1111 , ademas se le resta 3 y le tendrias que sumar casi 128 para que se produzca un overflow. Asi que jamas va a suceder.

10 bits ADC:

Funcion ( sigo manteniendo el maximo de 155 )
El mismo caso de antes, simplifico , tomo 31 como 32 -1 , distribuyo y simplifico nuevamente
31 + ADC * 124 / 1024  =  31 + ADC/8 - ADC/256
= 31 + ADC * 2-3 - ADC *2-8

Usando justificado derecha como veniamos haciendo quedaria:

ADCH = 0000 00xx    (Resultado del ADC)
ADCL = aaaa annn     (Resultado del ADC)

ADC/8 = 0000 0000 0xxa aaaa    (Valor a conseguir)
ADC/256 = 0000 0000 0000 00xx  (Valor a conseguir)

Si observas con el caso anterior la parte baja ADCL es igual que en el caso de 8 bits a pesar que cambian los divisores. Y solo trabajariamos con 8 bits
Cosas interesantes a notar. ADC/256 = ADCH (1 instrucciones) lo cual es super simple de sacar , pero para llegar a ADC/8 tengo que rotar 3 veces (7 instrucciones ya que son de 16bits 2 rotaciones), los bits marcados con nnn no "sobreviven"

Para simplificar se me ocurrio usar el justificado izquierda y observo que solo la parte alta es de mi interes, nuevamente denoto como nnn como los bits que no me interesan o que serian eliminados por la operacion

ADCHji = xxaa aaan
ADCLji = nn00 0000

ADCHji/2 = 0xxa aaaa    ( Lo mismo que ADC/8 del otro justificado, pero ahora con una sola rotacion - 3 instrucciones, consegui el mismo valor pero con menos instrucciones )
ADCHji/4 = u0xx aaaa    ( Igual que antes hago una AND con 0x30 y luego un SWAP antes de restar, equivalente a ADC/256 , este ya no es tan facil como antes que era ADCH sino que volvemos a lo mismo que hice con 8 bits - 3 instrucciones )

Aprovecho el justificado para que los bits esten en otro lugar y tratar de rotar lo menos posible.

Luego hago lo mismo sumo 31 nuevamente, se aplica todo lo demas siempre 0xxa aaaa va a ser mayor que 0000 00xx , por lo tanto no hay carry en la resta. Y tampoco va a excederse de 8 bits por que deberia sumarle en el peor de los casos un numero mayor a 128. Ademas seria imposible ya que el maximo dado por la ecuacion se encuentra en 155 ( 8 bits )

La ultima funcion esta bastante resumida. Si observas uno de los post que puse, cuando llegue con esta idea comenze rotando y necesitando cerca de 27 ciclos, luego con esta explicacion o rebuscado logre reducirlo aun mas

Citar
¿no es mas fácil por ejemplo sumar 20 + 20 que dividir 20 entre dos para después sumarle 30?

No entendi, podrias explicarlo con un ejemplo o una formula ?

EDIT:

Tu ejemplo pierde forma cuando supones esto:

x1 = A + A = 2A
y la otra es:
x2 = A/2 +30

Si A = 40,y uso esa misma logica : x1 = 80 y x2= 50
Para en el unico punto que se intersectan es con A = 20
x1 = x2
2A = A/2 +30
2A - A/2 = 30
3A/2 = 30
A = 30 * 2 / 3
A = 20

En fin no es la misma ecuacion. Y ya no se a donde te dirigias con esa pregunta...

Dio la suerte que esta funcion sea algo simple

Con respecto a los valores yo DEBO sumar 31, en el codigo que estamos haciendo te recuerdo la formula:

Código: C
  1. Posicion_Servo = 31 + (ValorADC * 124) / 256);

Lo unico rebuscado es la forma de multipicar y dividir aprvechandose que se esta en binario y con registros de 8 bits, la suma se hace normalmente.
Y en fin espero demostrar que no es/fue cuestion de prueba y error. O valores al azar.
Y que te sea util xD
Título: Re: Dudas con operacion aritmética
Publicado por: Miquel_S en 26 de Marzo de 2015, 14:51:29
Gracias de nuevo KILLERJG, ahora si que entiendo el proceso por completo, pasaba que había datos de la formula final que no sabia de donde salían. Ya puedo ir probando a escribir el código para el control del servo con potenciómetro.
Perdón por no haber dado las gracias antes pero quería tener claro lo de la explicación, lo digo porque en otro foro que muchos conocemos por tardar dos días en dar las gracias ya ni me contestaban si escribía.

Miquel_S
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 26 de Marzo de 2015, 17:27:23
Esta bien, por ahi no se posee tiempo, o algunos se dedican en sus tiempos libres a hacerlo.

Yo pienso que es peor que nadie te responda en 2 dias cuando creas un tema, que no que se tarden una semana en dar las gracias.
Igual me paso de gente que le contestas y luego no te responde mas xD
Título: Re: Dudas con operacion aritmética
Publicado por: elgarbe en 26 de Marzo de 2015, 20:31:29
Con semejante explicacion que diste en assembler como para no agradecer!!!!

sds.
Título: Re: Dudas con operacion aritmética
Publicado por: KILLERJC en 28 de Marzo de 2015, 00:22:05
Como todo esto comenzo en C y quisiera ver que tan bueno puede ser.
Ademas para expandir el conocimiento, y sacarme la duda xD
Decidi hacer algunos calculos y mirarlos con el stopwatch y descubri algunas cosas interesantes:

El codigo que hice fue este:

Código: C
  1. unsigned char ADC,RESULTADO;
  2.     unsigned int  ADCD,RESULTADOD;
  3.  
  4.     ADC = 0xFF;
  5.     ADCD = 0x3FF;
  6.  
  7.     while(1)
  8.     {
  9.     RESULTADO = 31 + ((int)ADC * 124)/256;
  10.     RESULTADO = 31 + ((int)ADC * 31)/64;
  11.     RESULTADO = 31 + (((int)ADC * 31) >> 6);
  12.     RESULTADO = 31 + ADC/2 - ADC/64;
  13.     RESULTADO = 31 + (ADC >> 1) - (ADC >> 6);
  14.     RESULTADO = 31 + (ADC - ADC/32)/2;
  15.     RESULTADO = 31 + ((ADC - (ADC >> 5) )>> 1);
  16.  
  17.     RESULTADOD = 31 + ((unsigned short long)ADCD * 124)/1024;
  18.     RESULTADOD = 31 + (ADCD * 31)/256;
  19.     RESULTADOD = 31 + ((ADCD * 31) >> 8);
  20.     RESULTADO = 31 + (ADCD >> 3) - (ADCD >> 8);
  21.     RESULTADOD = 31 + ADCD/8 - ADCD/256;
  22.     RESULTADOD = 31 + (ADCD >> 3) - (ADCD >> 8);
  23.     RESULTADOD = 31 + (ADCD - ADCD/32)/8;
  24.     RESULTADOD = 31 + ((ADCD - ADCD >> 5) >> 3);
  25.     }

Muchos parentesis sino las rotaciones las toma mal , todas las funciones dan el mismo resultado, aunque el maximo de algunas esta entre 154 y 155
Probe mas combinaciones tambien, pero con pobres resultados.

Los resultados fueron los siguientes:

Citar
Target halted. Stopwatch cycle count = 654 (654 µs)
Target halted. Stopwatch cycle count = 740 (740 µs)
Target halted. Stopwatch cycle count = 290 (290 µs)
Target halted. Stopwatch cycle count = 1195 (1,195 ms)
Target halted. Stopwatch cycle count = 54 (54 µs)
Target halted. Stopwatch cycle count = 1238 (1,238 ms)
Target halted. Stopwatch cycle count = 55 (55 µs)

Target halted. Stopwatch cycle count = 506 (506 µs)
Target halted. Stopwatch cycle count = 312 (312 µs)
Target halted. Stopwatch cycle count = 312 (312 µs)
Target halted. Stopwatch cycle count = 21 (21 µs)
Target halted. Stopwatch cycle count = 41 (41 µs)
Target halted. Stopwatch cycle count = 41 (41 µs)
Target halted. Stopwatch cycle count = 69 (69 µs)
Target halted. Stopwatch cycle count = 67 (67 µs)

Primero que nada es notar la diferencia entre ciclos al realizar un desplazamiento o una division, por ejemplo entre estos 4 equivalentes:

    RESULTADO = 31 + ADC/2 - ADC/64; ---------------------------------- 1195 ciclos (casi el doble que nuestra ecuacion original y uno pensaria que deberia ser mucho mas rapido por como es la formula y como se aplica al microcontrolador)
    RESULTADO = 31 + (ADC >> 1) - (ADC >> 6);------------------------ 54 ciclos     (lo mismo que arriba pero con dezplazamientos, uan decima de lo que lleva la ecuacion original)
    RESULTADO = 31 + (ADC - ADC/32)/2;--------------------------------- 1238 ciclos
    RESULTADO = 31 + ((ADC - (ADC >> 5) )>> 1);---------------------- 55 ciclos

Pero es de notarse unicamente en 8 bits, pero cuando hacemos lo mismo con variables de 16 bits obtenemos:

    RESULTADOD = 31 + ADCD/8 - ADCD/256;----------------------------- 41 ciclos
    RESULTADOD = 31 + (ADCD >> 3) - (ADCD >> 8 );-------------------- 41 ciclos
    RESULTADOD = 31 + (ADCD - ADCD/32)/8;----------------------------- 69 ciclos
    RESULTADOD = 31 + ((ADCD - ADCD >> 5) >> 3); ------------------- 67 ciclos

Otra cosa interesante.. 16 bits menos ciclos que 8 bits ... incluso hasta con desplazamientos

Y la diferencia notable de 8 vs 16 bits:

    RESULTADO = 31 + (ADC >> 1) - (ADC >> 6);------------------------ 54 ciclos (ADC variable 8 bits)
    RESULTADO = 31 + (ADCD >> 3) - (ADCD >> 8 ); -------------------- 21 ciclos (ADC variable 16 bits)

El claro ganador es:

    RESULTADO = 31 + (ADCD >> 3) - (ADCD >> 8 ); -------------------- 21 ciclos
    RESULTADO = 31 + ADCD/8 - ADCD/256; ------------------------------ 21 ciclos ( este lo simule despues pero usa la misma cantidad de ciclos )

Que le quita 20 ciclos por solo cambiar a donde se guarda por una variable de 8 bits. Y no esta para nada lejos del ASM, y no le deja nada que envidiar.

Conclusiones:
- Al momento de dividir aca es mejor rotar si es potencia de 2 o tal ves no xD
- Y una diferencia inexplicable entre 8 y 16bits ( cuando 16 bits deberia ser aquel que lleva mas ciclos ), hasta la ecuacion original el cual es casteada a una variable de 24bits lleva menos tiempo que la de 8 bits casteada a 16
 
Probe hacerlo con un justificado izquierda como hicimos en ASM, pero el simple hecho de rotarlo mas veces para conseguir el mismo resultado, hacia que tuviera muchos mas ciclos

Por ultimo estudiamos el ASM generado:

Código: Microchip Assembler
  1. MOVF ADCDH, W         ; Mueve los registros ADCDH:ADCDL a unos temporales que estan ubicados en 0x20 (__pcstackBANK0) y 0x21
  2. MOVWF 0x21
  3. MOVF ADCD, W
  4. MOVWF __pcstackBANK0                  ; Rotacion de 16 bits , 3 rotaciones , es decir dividirlo por 8 , ya mis registros temporales tienen el valor ADCD/8
  5. BCF STATUS, 0x0
  6. RRF 0x21, F
  7. RRF __pcstackBANK0, F
  8. BCF STATUS, 0x0
  9. RRF 0x21, F
  10. RRF __pcstackBANK0, F
  11. BCF STATUS, 0x0
  12. RRF 0x21, F
  13. RRF __pcstackBANK0, F
  14. MOVF ADCDH, W               ; Toma la parte superior 0x03
  15. XORLW 0xFF                     ; Y restamos! como ? usando el complemento a 2 de 0x03 y sumando
  16. ADDLW 0x1
  17. ADDWF __pcstackBANK0, W         ;Por ser sin signo no se pregunta por el carry. Y el compilador decide no ponerlo, W = ADCD/8 - ADCD/256
  18. ADDLW 0x1F                       ; Suma 31, interesante ver que no sale de W el resultado y las operaciones, , W = 31 + ADCD/8 - ADCD/256 ,  al haberlo ubicado como destino una varaible de 8 bits imagino que no le importa si en la suma existe un carry o no
  19. MOVWF 0x22                     ; Guarda el resultado en otro registro temporal que usa C
  20. MOVF 0x22, W                   ; Y procede al guardado en la variable en cuestion
  21. MOVWF RESULTADO

Tal ves se podrian haber ahorrado hasta 7 intrucciones, pero es bueno saber como lo hace

Resultado: Si te la rebuscas funciona como queres :D