Mientras veo bocetos que otras personas han escrito, ocasionalmente encuentro un código que se parece a esto:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
Todo lo que sé es que tiene algo que ver con el tiempo / temporizadores (creo). ¿Cómo puedo descifrar y crear un código como este? ¿Cuáles son TCCR1A
, TCCR1B
, TCNT1
, CS12
, TIMSK1
, y TOIE1
?
timers
programming
El chico con el sombrero
fuente
fuente
Respuestas:
Esto no tiene un aspecto extraño. Así es como se ve el código MCU normal.
Lo que tienes aquí es un ejemplo del concepto de periféricos mapeados en memoria . Básicamente, el hardware de MCU tiene ubicaciones especiales en el espacio de direcciones SRAM de la MCU asignada. Si escribe en estas direcciones, los bits del byte escrito en la dirección n controlan el comportamiento del periférico m .
Básicamente, ciertos bancos de memoria tienen literalmente pequeños cables que van desde la celda SRAM al hardware. Si escribe un "1" en este bit en ese byte, establece esta celda SRAM en un valor lógico alto, que luego activa alguna parte del hardware.
Si observa los encabezados de la MCU, existen grandes tablas de asignaciones de direcciones de palabras clave <->. Así es como
TCCR1B
se resuelven cosas como etc. en tiempo de compilación.Este mecanismo de mapeo de memoria es extremadamente utilizado en MCU. La MCU ATmega en el arduino lo usa, al igual que las series MCU PIC, ARM, MSP430, STM32 y STM8, así como muchas MCU con las que no estoy familiarizado de inmediato.
El código Arduino es algo extraño, con funciones que acceden a los registros de control de MCU indirectamente. Si bien esto tiene un aspecto algo "más agradable", también es mucho más lento y utiliza mucho más espacio en el programa.
Todas las constantes misteriosas se describen con gran detalle en la hoja de datos ATmega328P , que realmente debería leer si está interesado en hacer algo más que pasadores de palanca en un arduino.
Seleccione extractos de la hoja de datos vinculada anteriormente:
Así, por ejemplo,
TIMSK1 |= (1 << TOIE1);
establece el bitTOIE1
enTIMSK1
. Esto se logra desplazando el binario 1 (0b00000001
) a la izquierda porTOIE1
bits, yTOIE1
se define en un archivo de encabezado como 0. Esto luego se OR en bits en el valor actual deTIMSK1
, que efectivamente establece este bit alto.Mirando la documentación para el bit 0 de
TIMSK1
, podemos ver que se describe comoTodas las otras líneas deben interpretarse de la misma manera.
Algunas notas:
También puede ver cosas como
TIMSK1 |= _BV(TOIE1);
._BV()
es una macro comúnmente utilizada originalmente de la implementación AVR libc ._BV(TOIE1)
es funcionalmente idéntico a(1 << TOIE1)
, con el beneficio de una mejor legibilidad.Además, también puede ver líneas como:
TIMSK1 &= ~(1 << TOIE1);
oTIMSK1 &= ~_BV(TOIE1);
. Esto tiene la función opuesta deTIMSK1 |= _BV(TOIE1);
, en el que se desarma el bitTOIE1
enTIMSK1
. Esto se logra tomando la máscara de bits producida por_BV(TOIE1)
, realizando una operación NOT bit a bit (~
), y luego ANDTIMSK1
por este valor NOTed (que es 0b11111110).Tenga en cuenta que en todos estos casos, el valor de cosas como
(1 << TOIE1)
o_BV(TOIE1)
se resuelven por completo en tiempo de compilación , por lo que se reducen funcionalmente a una constante simple y, por lo tanto, no requieren tiempo de ejecución para calcular en tiempo de ejecución.El código escrito correctamente generalmente tendrá comentarios en línea con el código que detalla lo que los registros asignados deben hacer. Aquí hay una rutina de SPI suave bastante simple que escribí recientemente:
PORTC
es el registro que controla el valor de los pines de salida dentroPORTC
del ATmega328P.PINC
es el registro donde están disponibles los valores de entrada dePORTC
. Básicamente, cosas como esta son las que ocurren internamente cuando usas las funcionesdigitalWrite
odigitalRead
. Sin embargo, hay una operación de búsqueda que convierte los "números de pin" de arduino en números de pin de hardware reales, lo que lleva a algún lugar en el ámbito de los 50 ciclos de reloj. Como probablemente pueda adivinar, si está tratando de ir rápido, desperdiciar 50 ciclos de reloj en una operación que solo debería requerir 1 es un poco ridículo.La función anterior probablemente tome algún lugar en el ámbito de 100-200 ciclos de reloj para transferir 8 bits. Esto implica 24 escrituras pin y 8 lecturas. Esto es mucho, muchas veces más rápido que usar las
digital{stuff}
funciones.fuente
TCCR1A
es el temporizador / contador 1 registro de control ATCCR1B
es el registro de control del temporizador / contador 1 BTCNT1
es el valor del contador del temporizador / contador 1CS12
es el bit de selección del tercer reloj para el temporizador / contador 1TIMSK1
es el registro de máscara de interrupción del temporizador / contador 1TOIE1
es el temporizador / contador 1 habilitación de interrupción de desbordamientoEntonces, el código habilita el temporizador / contador 1 a 62.5 kHz y establece el valor en 34286. Luego habilita la interrupción por desbordamiento, de modo que cuando alcanza 65535, activará la función de interrupción, probablemente etiquetada como
ISR(timer0_overflow_vect)
fuente
CS12 tiene un valor de 2 ya que representa el bit 2 del registro TCCR1B.
(1 << CS12) toma el valor 1 (0b00000001) y lo desplaza 2 veces hacia la izquierda para obtener (0b00000100). El orden de las operaciones dicta que las cosas en () sucedan primero, por lo que esto se hace antes de que se evalúe "| =".
(1 << CS10) toma el valor 1 (0b00000001) y lo desplaza a la izquierda 0 veces para obtener (0b00000001). El orden de las operaciones dicta que las cosas en () sucedan primero, por lo que esto se hace antes de que se evalúe "| =".
Entonces ahora obtenemos TCCR1B | = 0b00000101, que es lo mismo que TCCR1B = TCCR1B | 0b00000101.
Desde "|" es "OR", todos los bits que no sean CS12 en TCCR1B no se ven afectados.
fuente