
Una vez me encontré con un excelente artículo ( tyk ), en el que el autor mostró claramente la diferencia entre usar las funciones de Arduino y trabajar con registros. Se han escrito muchos artículos, alabando a Arduino y argumentando que todo esto es frívolo y en general para los niños, por lo que no lo repetiremos, pero trataremos de averiguar qué causó los resultados obtenidos por el autor de ese artículo. Y, igual de importante, pensaremos en lo que podemos hacer. A cualquiera que esté interesado, le pregunto debajo del gato.
Parte 1 "Preguntas"
Citando al autor de este artículo:
Resulta una pérdida de rendimiento en este caso, 28 veces. Por supuesto, esto no significa que arduino funcione 28 veces más lento, pero creo que, por claridad, este es el mejor ejemplo de lo que a Arduino no le gusta.
Dado que el artículo acaba de comenzar, aún no lo entenderemos, pero ignoraremos la segunda oración y asumiremos que la velocidad del controlador es aproximadamente equivalente a la frecuencia de conmutación del pin. Es decir Nos enfrentamos a la tarea de hacer que el generador tenga la frecuencia más alta de lo que tenemos. Primero, veamos qué tan mal está todo.
Escribiremos un programa simple para arduino (de hecho, solo copie el parpadeo).
void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1);
Coser en el controlador. Como no tengo un osciloscopio, sino solo un analizador lógico chino, debe configurarse correctamente. La frecuencia máxima del analizador es de 24 MHz; por lo tanto, debe ecualizarse con la frecuencia del controlador, configurada en 16 MHz. Miramos ...

... por mucho tiempo. Estamos tratando de recordar de qué depende la velocidad del controlador, exactamente, la frecuencia. Buscamos en arduino.cc . La velocidad del reloj es de 16 MHz, y aquí tenemos 145,5 kHz. Que hacer Tratemos de resolverlo en la frente. En el mismo arduino.cc miramos el resto del tablero:
- Leonardo - no adecuado - también hay 16 MHz
- Mega - también - 16 MHz
- 101 - lo hará - 32MHz
- DEBIDO - Aún mejor - 84 MHz
Se puede suponer que si aumenta la frecuencia del controlador 2 veces, entonces la frecuencia de parpadeo del LED también aumentará 2 veces, y si es 5, entonces 5 veces.

No obtuvimos los resultados deseados. Y el generador es cada vez menos como un meandro. Pensamos más: ahora, probablemente, el idioma es malo. Parece que es con c, c ++, pero es difícil (de acuerdo con el efecto Dunning-Krueger , no podemos darnos cuenta de que ya estamos escribiendo en c ++), por lo tanto, estamos buscando alternativas. Una búsqueda breve nos lleva a BASCOM-AVR (no está mal dicho aquí ), póngalo, escriba el código:
$Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop
Obtenemos:

El resultado es mucho mejor, además, obtuvimos el meandro perfecto, pero ... ¿BÁSICO en 2018, en serio? Quizás dejemos esto en el pasado.
Parte 2 "Respuestas"
Parece que es hora de dejar de jugar al tonto y comenzar a entender (y también recordar si y ensamblador). Simplemente copie el código "útil" del artículo mencionado al principio en loop ().
Aquí, creo, se necesita una explicación: todo el código se escribirá en el proyecto arduino, pero en el entorno Atmel Studio 7.0 (hay un desensamblador conveniente allí), las capturas de pantalla serán de él.
void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON }
resultado:

Aqui esta! Casi lo que necesitas. Solo la forma no es particularmente similar al meandro y la frecuencia, aunque ya está más cerca, pero aún no es la misma. También intentamos acercarnos y encontrar huecos en la señal cada milisegundo.

Esto se debe a la activación de interrupciones del temporizador responsable de millis (). Entonces, lo que haremos es simplemente desconectarnos. Estamos buscando ISR (función de manejo de interrupciones). Encontramos:
ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; }
Un montón de código inútil para nosotros. Puede cambiar el modo de operación del temporizador o deshabilitar la interrupción, pero esto es innecesario para nuestros propósitos, así que simplemente deshabilite todas las interrupciones con el comando cli (). Solo mira nuestro código:
PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON
demasiados operadores, reduzca a una tarea.
PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON
Sí, y cambiar a loop () requiere muchos comandos, ya que esta es una función adicional en el ciclo principal.
int main(void) { init();
Así que solo haz un bucle sin fin en setup (). Obtenemos lo siguiente:
void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } }

61 ns es el máximo correspondiente a la frecuencia del controlador. ¿Es posible más rápido? Spoiler - no. Tratemos de entender por qué, para esto desmontamos nuestro código:

Como se puede ver en la pantalla, para escribir en el puerto 1 o 0, se gasta exactamente 1 ciclo de reloj, pero la transición no se puede completar en menos de un ciclo de reloj (RJMP se realiza en dos ciclos de reloj y, por ejemplo, JMP, en tres ) Y ya casi estamos allí: para obtener un meandro, debe aumentar el tiempo cuando 0 se da por dos medidas. Agregue para este dos ensamblador nop comandos que no hacen más que tomar 1 ciclo de reloj:
void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } }

Parte 3 "Conclusiones"
Desafortunadamente, todo lo que hicimos fue absolutamente inútil desde un punto de vista práctico, porque ya no podemos ejecutar ningún código. Además, en el 99,9% de los casos, las frecuencias de conmutación de puertos son suficientes para cualquier propósito. Sí, y si realmente necesitamos generar un meandro suave, puede tomar stm32 con dma o un chip temporizador externo como NE555. Este artículo es útil para comprender los dispositivos mega328p y arduino en general.
Sin embargo, escribir en los registros de valores de 8 bits PORTB = 0b11111111;
mucho más rápido que digitalWrite(13, 1);
pero tendrá que pagar por esto por la imposibilidad de transferir el código a otras juntas, porque los nombres de los registros pueden diferir.
Solo queda una pregunta: ¿por qué el uso de cálculos más rápidos no dio resultados? La respuesta es muy simple: en sistemas complejos, la frecuencia de gpio es menor que la frecuencia central. Pero cuánto más bajo y cómo configurarlo siempre se puede ver en la hoja de datos de un controlador específico.
La publicación citó artículos: