Entonces, como dice el título, necesito retrasos exactos, no demasiado largos, idealmente en el rango de 0 a 350 relojes de CPU, pero si algo funcionara en un rango más estrecho, el rango mínimo absoluto es de 20 a 127 relojes de CPU. Así que estos están por debajo o justo por encima de los retrasos de microsegundos (50MHz CPU clock), relativamente cortos de varios relojes a varias decenas de relojes. El problema con el sondeo de un temporizador es que la precisión resulta en un paso de 7 relojes como máximo, según la implementación, por ejemplo:
- while (! TF0) {} Mientras que, no, y operador de bits, todos juntos toman 7 relojes. Entonces, si llamo a cualquier cosa entre los relojes 15-21, se producirá un retraso de 21 relojes ...
- Uso de la interrupción del temporizador y el modo de parada de la CPU : proporciona buenos resultados para más de 50 relojes, probablemente depende de la condición actual de la CPU, por lo que a veces va más allá de 50 relojes, en el rango de 100 relojes debido a la interrupción y activación hasta la latencia, pero cualquier cosa debajo vuelve a ser plana 50 (o 100) relojes de CPU.
- Utilizando switch-case , con por ejemplo 30 entradas para 30 retrasos con 1 incremento de reloj, con un número diferente de NOPes como retardo, da como resultado una optimización del compilador que lo hace impredecible en términos de tiempo y, sobre todo, demasiado largo De nuevo, más de 100 relojes. Esto hace que el enfoque sea inutilizable.
- Estoy planeando probar la tabla de puntero a funciones con un número diferente de NOPes. Pero antes de intentarlo ya veo dos problemas en este enfoque: a. Requerirá mucha memoria y me queda 1k; segundo. la latencia de una función de vacío (vacío) dentro y fuera es de alrededor de 18 relojes, por lo que es muy ajustada para cumplir con el mínimo absoluto de 20 relojes que necesito ...
¿Cómo abordar este tipo de problema? ¿Alguna idea será más que bienvenida?
Por cierto, lo ejecuto en el microcontrolador C8051F38x de Silicon Labs, usando C51 y Keil para codificar y compilar si eso importa.
El código que surgió como una solución parcial, parece que sigue el mismo tiempo que el bucle en C, y la instrucción "djnz" toma 5-6 ciclos de CPU, en lugar de la hoja de datos que se indica 2/4.
ACC_save= ACC;
ACC = counter102;
P0b3 = 1; // Start the Pulse
#pragma ASM // Precice DELAY using assembler
clr C // ; 1 Clear Carry
rrc A // ; 1 C = 1 if odd
jnc even // ; 2 or 4 extra 2 cycles if branch taken (spoils cache)
nop // ; 1
nop // ; 1
clr C // ; 1
even:
subb A,#4 // ; 1
mov R7,A // ; 1
loop:
djnz R7, loop // ; supposed to be 2, but practically takes 5 to 6 cycles!
#pragma ENDASM'
P0b3 = 0; // Stop the Pulse
EDIT
Muchas gracias a todos por un gran aporte, no puedo imaginar que el flujo de ideas sea tan positivo y productivo. Así que mi profundo agradecimiento a todos los que contribuyeron, y contribuirá en el futuro. Entonces, después de sus valiosos aportes, y grandes ideas, se me ocurrió algo que me funciona, hasta cierto punto. El código está abajo:
void delay(unsigned char delay_time) {
switch (delay_time)
{case 8: goto Q08;
case 9: goto Q09;
case 10: goto Q10;
case 11: goto Q11;
case 12: goto Q12;
case 13: goto Q13;
case 14: goto Q14;
case 15: goto Q15;
case 16: goto Q16;
case 17: goto Q17;
case 18: goto Q18;
case 19: goto Q19;
case 20: goto Q20;
default : goto Q00; }
Q19: PORT_ACTIVE(1); // 2clk
Q17: PORT_ACTIVE(1); // 2clk
Q15: PORT_ACTIVE(1); // 2clk
Q13: PORT_ACTIVE(1); // 2clk
Q11: PORT_ACTIVE(1); // 2clk
Q09: PORT_ACTIVE(1); // 2clk
_nop_(); // 1clk
goto EXIT1; // Skip the Even delay part
Q20: PORT_ACTIVE(1); // 2clk
Q18: PORT_ACTIVE(1); // 2clk
Q16: PORT_ACTIVE(1); // 2clk
Q14: PORT_ACTIVE(1); // 2clk
Q12: PORT_ACTIVE(1); // 2clk
Q10: PORT_ACTIVE(1); // 2clk
Q08: PORT_ACTIVE(1); // 2clk
Q00: // 0clk
EXIT1:
return; // Exit from the function takes 7 clocks
} // END of function delay
// Continued execution after the delay function
PORT_ACTIVE(0); // 2clk
Por lo tanto, PORT_ACTIVE (x) es una función #define que activa el puerto de pulsos. Como tengo todo el tiempo que necesito antes de comenzar el pulso, pude aprovechar la mayor parte de los gastos generales relacionados con las decisiones antes de la activación real del puerto. Entonces, la instrucción de retorno es prácticamente la misma cantidad de tiempo, por lo que ahora puedo generar un pulso con un mínimo de 8 ciclos de ciclos de ancho y hasta 20 ciclos. Ahora lo estoy extendiendo hasta 100 relojes, a expensas de la memoria de almacenamiento disponible, por supuesto. Y, de hecho, esta solución es, de hecho, gracias a la idea de JimmyB de poner la activación del pulso en la función y no antes, y, por supuesto, gracias a las grandes ideas de TCROSLEY, de cómo manejar los impares y hasta los retrasos, es simplemente que pasar al ensamblaje no es realmente amigable para la experiencia de depuración, y el código hace mucho más que simples retrasos, por lo que prefiero quedarme en C.
Una nota más, es que tan pronto como terminé de celebrar una solución de trabajo, encontré el siguiente problema.
SEGUNDO PROBLEMA
Necesito ejecutar un segundo pulso de regreso a la primera con ancho independiente. Así que no hay sobrecarga para el segundo pulso, de lo contrario terminará con un ancho variable. Casi me vuelve a colocar en el lugar en el que estaba antes, ya que el segundo pulso se limita nuevamente al cuello de botella de 6 ciclos del bucle while, a menos que haya una manera de colocar la sobrecarga de ramificación del segundo pulso antes del primer pulso. . ¿Alguna idea sobre eso?