Velocidad de ejecución de instrucciones (Solucionado)

Hace tres meses que ando enredando con el arduino. Hoy he medido la velocidad máxima de conmutación de una de sus salidas digitales mediante un osciloscopio con este sketch:

int ledPin =  12;  
void setup()  
{                
  pinMode(ledPin, OUTPUT);    
}
void loop()                    
{
  digitalWrite(ledPin, HIGH);  
  digitalWrite(ledPin, LOW);  
}

Ya que el micro que lleva, un atmega168, trabaja a 16MHz y utiliza un ciclo por instrucción según el datasheet, pensé que la frecuencia medida en el pin 12 sería de 8MHz pero sólo tengo 125KHz.
Casualmente 125KHz x 64 = 8MHz
He seguido consultando el datasheet y he visto que tiene unos "fuse", el equivalente a la palabra de configuración de los PICs. Hay unos bits CLKPS3..0, que se supone configuran un prescaler aplicado al reloj. ¿Podría ser que el prescaler viene configurado a 64 DEC?.
¿Como puedo acceder a ese dato desde la pantalla Arduino?
El arduino es el que vende Elektor, un friduino igual que el Diecimila pero con el chip de soldadura superficial.

Gracias

Ten en cuenta que es una llamada a una función, que ya de por si mete un overhead, más luego ejecutar todo el código de dentro de la función que no son dos líneas. En la carpeta del IDE de Arduino tienes el código fuente disponible para ver que hace la función.

Si quieres algo un pelín más rápido puedes probar con este código:

int ledPin =  12;

void setup()  
{                
  pinMode(ledPin, OUTPUT);    
}
void loop()                    
{
  for(;;)
  {
  //  digitalWrite(ledPin, HIGH);
    PORTB = 0b00100000;

  //  digitalWrite(ledPin, LOW);  
     PORTB = 0b00000000;
  }
}

Deberías conseguir una señal de 4MHz con una relación 1/4 entre el tiempo en 1 y 0.

Creo que sacaron hace poco la función DigitalWriteFast, pero todavía no le he echado un vistazo, puede ser interesante

Con lo que dice FM, si copias varias veces las líneas de PORTB (15 o 20c&P), tendrías cerca de los 8MHz (para 2 instrucciones, o sea, los 16MHZ), te saltarías varios overheads de las subrutinas, como dice Chiva.

Es una cuestión de gestionar recursos, si te interesa código muy eficiente, deberias repetir el encender led apagar led millones de veces, para evitar saltos de memoria (que es uno de los factores que hace que no te de la frecuencia del reloj), pero necesitarías una memoria bastante grande.
Por otro lado, si sacrificas un poco de eficiencia en beneficio de ahorrar líneas de código, puedes meter problemas más complejos, a costa de perder un poco de rendimiento.
El truco consiste en jugar con estos dos factores :grin:

copachino, creo que no se está hablando del ADC....

Por cierto, como curiosidad,con una sola instrucción podrías hacer un toggle de un pin (copio del datasheet). Lo digo para que el duty sea 50%:
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn. Note that the SBI instruction can be used to toggle one single bit in a port.

No todas las instrucciones son de 1 ciclo, por ejemplo SBI son 2 ciclos.

Con el código que he metido, el duty que tengo es prácticamente del 50%, luego tarda lo mismo en poner el puerto a 0 que en finalizar la funcion loop(), volver al principio de la función y poner el puerto a 1.
La cuestión es: teniendo una frecuencia de reloj de 16MHz y una instrucción por ciclo de relog, ¿cómo es que no puedo conmutar un puerto a más de 125KHz?. Si puedo, el lunes lo repetiré con un pic 16F84.

=(

troglodita, vuelve a releer los post y las explicaciones que te han dado. Como bien te han dicho, la función digitalWrite por detrás hace muchas cosas, no es la más "óptima". Te han puesto ejemplos de como hacerlo directamente manipulando el puerto. Y repito una vez más, no todas las instrucciones son de 1 ciclo.

:sweat_smile: vamos a sudar tinta! Si no lees las respuestas que se publican en respuesta a tu duda, va a ser complicadillo...

La funcion digitalWrite del entorno Arduino es un "mojón" ya que hace muchas cosas en la trastienda para simplemente cambiar un bit de un registro del micro.

Vamos a ver...
digitalWrite(pin, value); es una FUNCIÓN. Las funciones implican un salto en la memoria del microcontrolador donde está el programa, es como si estás leyendo un libro y te dice "ver capítulo anterior", tienes que saltar unas páginas para volver atrás, se pierde algo de tiempo. De hecho, la función digitalWrite(); incluye llamadas a funciones;

void digitalWrite(uint8_t pin, uint8_t val)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *out;

	if (port == NOT_A_PIN) return;

	// If the pin that support PWM output, we need to turn it off
	// before doing a digital write.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

	out = portOutputRegister(port);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;
}

14 lineas más las que añadir las 6 llamadas a funciones que implementa y los saltos condicionales (los if). A esto súmale el doble (porque tienes que encender y apagar el led). Con esta función pierdes un montón de rendimiento.

PORTB= xxxxxxx; es una INSTRUCCIÓN. Las instrucciones, no todas son de un ciclo, en este caso sí lo es. Esta vendría a equivaler en ASM;

 ldi PORTB, xxxxxxx

Pero para que haya frecuencia, se necesitan ciclos, los ciclos comprenden una subida y una bajada de señal, en este caso, encender un led, apagar un led. Por tanto necesitas 2 instrucciones para que el led parpadee y puedas tomar frecuencias, pero ten en cuenta que leerás la mitad de la frecuencia del reloj, porque usas 2 instrucciones.

prueba a tomar la frecuencia con el código como te comentaba en mi post anterior, pon en loop:

PORTB = 0b00000000;
PORTB = 0b11111111;

Como unas 50 veces y toma tiempos. Te dará muy aproximado a 8MH, porque son 2 instrucciones.

Perdón. Hasta ahora había programado los PICs directamente en ensamblador y pensé que en arduino la instrucción digitalWrite(ledPin, HIGH) se traducía en una sola instrucción en código máquina. :~

Esto ya es otra cosa.
He cargado este código:

int testPin =  12;    

void setup()   {                
  // initialize the digital pin as an output:
  pinMode(testPin, OUTPUT);     
}

void loop()                     
{
PORTB = 0b00000000;
PORTB = 0b00010000;
PORTB = 0b00000000;
PORTB = 0b00010000;
}

Y ahora sí que el arduino tarda en desactivar y activar el puerto (dos instrucciones) 125 nseg (nanosegundos) que corresponde a una frecuencia de 8 MHz.

Tarda 62,5 nseg (nanosegundos) en ejecutar una instrucción,que corresponde a una frecuencia de 16 MHz.
Desde que acaba de ejecutar la última instrucción y vuelve a la primera del bucle gasta 10 ciclos de reloj, 625 nseg (nanosegundos).
Se ve un rizado a lo largo de la señal de 32MHz, el doble del oscilador, no sé si es normal o nó. Tambien he comprobado que en los otros puertos que tendrían que estar a 0 se cuela un poco de la señal del puerto 12.

Gracias a todos por la ayuda. Me he leido varios manuales del arduino y ninguno hablaba de la gran diferencia que hay entre usar "digitalWrite(ledPin, HIGH)" y "PORTB 0b00010000" .

Depende de que placa sea, si es una Arduino oficial, el rizado que tiene es peor de lo que imaginaba. Si es un clon pues a saber, ...

Si la contrastsmos con la vinciDuino, parece que tienes a la filarmónica de Viena tocando dentro de esa placa, voy a ver si publico una imagen para comparar.

Hola fm.

He probado tu código y es más rapido, ya que el bucle for tarda sólo 2 ciclos de reloj en retornar que la funcion loop que tarda 10.

int testPin =  12;

void setup()  
{                
  pinMode(testPin, OUTPUT);    
}
void loop()                    
{
  for(;;)
  {
    PORTB = 0b00010000;
    PORTB = 0b00000000;
    PORTB = 0b00010000;
    PORTB = 0b00000000;
    PORTB = 0b00010000;
    PORTB = 0b00000000;
  }
}

PD. Leí en las normas del foro que cuando un hilo estaba solucionado había que cambiar el título del hilo añadiendo (Solucionado) pero no he visto cómo hacerlo.

OK, perfecto. Es más rápido porque no estás volviendo del loop y volviendo a entrar. Con lo cual te ahorras cargar en la pila información y ejecutar un salto.

Aquí te dejo las imágenes que he tomado del rizado que se ve en la vinciDuino. Como ves es una señal muy limpia. Por la escala que he visto de la tuya es de casi 300mV!



Por cierto, qué osciloscopio más apañado que tienes!

Os ha quedado un post muy interesante me lo guardo para mis alumnos.

Para poner solucionado tienes que editar el primer post del hilo y escribir solucionado en el titulo. Salu2

ionhs:
Os ha quedado un post muy interesante me lo guardo para mis alumnos.
Para poner solucionado tienes que editar el primer post del hilo y escribir solucionado en el titulo.

Gracias, ionhs.