¿Alguien podría explicar este código de aspecto extraño, utilizado para configurar temporizadores?


10

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?



1
Descargue la hoja de datos "Completa" para su dispositivo desde el sitio web de Atmel y lea los capítulos sobre temporizadores. La hoja de datos es sorprendentemente buena para leer en mi opinión.
jippie

Respuestas:


15

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 TCCR1Bse 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:

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Así, por ejemplo, TIMSK1 |= (1 << TOIE1);establece el bit TOIE1en TIMSK1. Esto se logra desplazando el binario 1 ( 0b00000001) a la izquierda por TOIE1bits, y TOIE1se define en un archivo de encabezado como 0. Esto luego se OR en bits en el valor actual de TIMSK1, que efectivamente establece este bit alto.

Mirando la documentación para el bit 0 de TIMSK1, podemos ver que se describe como

Cuando este bit se escribe en uno, y se establece el indicador I en el Registro de estado (interrupciones habilitadas globalmente), se habilita la interrupción de desbordamiento del temporizador / contador1. El vector de interrupción correspondiente (consulte "Interrupciones" en la página 57) se ejecuta cuando se establece el indicador TOV1, ubicado en TIFR1.

Todas 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);o TIMSK1 &= ~_BV(TOIE1);. Esto tiene la función opuesta de TIMSK1 |= _BV(TOIE1);, en el que se desarma el bit TOIE1en TIMSK1. Esto se logra tomando la máscara de bits producida por _BV(TOIE1), realizando una operación NOT bit a bit ( ~), y luego AND TIMSK1por 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:

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCes el registro que controla el valor de los pines de salida dentro PORTCdel ATmega328P. PINCes el registro donde están disponibles los valores de entrada de PORTC. Básicamente, cosas como esta son las que ocurren internamente cuando usas las funciones digitalWriteo digitalRead. 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.


Tenga en cuenta que este código también debería funcionar con Atmega32u4 (usado en Leonardo) ya que contiene más temporizadores que ATmega328P.
jfpoilpret

1
@Ricardo: probablemente más del 90% del código incrustado de MCU pequeña utiliza la manipulación directa de registros. Hacer cosas con funciones de utilidad indirecta no es el modo común de manipular periféricos / periféricos. Hay algunos juegos de herramientas para abstraer el control de hardware (The Atmel ASF, por ejemplo), pero generalmente está escrito para compilar todo lo posible para reducir la sobrecarga de tiempo de ejecución, y casi invariablemente requiere comprender los periféricos al leer las hojas de datos.
Connor Wolf

1
Básicamente, las cosas arduino, al decir "aquí hay funciones que hacen X", sin molestarse realmente en hacer referencia a la documentación real o cómo el hardware está haciendo las cosas que hace, no es muy normal. Entiendo que su valor es una herramienta introductoria, pero a excepción de la creación rápida de prototipos, en realidad nunca se hace en entornos profesionales reales.
Connor Wolf

1
Para ser claros, lo que hace que el código arduino sea inusual para el firmware MCU incorporado no es exclusivo del código arduino, es una función del enfoque general. Básicamente, una vez que tiene una comprensión decente de la MCU real , hacer las cosas correctamente (por ejemplo, usar registros de hardware directamente) toma poco o ningún tiempo adicional. Como tal, si desea aprender el verdadero desarrollo de MCU, es mucho mejor simplemente sentarse y comprender lo que realmente está haciendo su MCU , en lugar de confiar en la abstracción de otra persona , que tiende a tener fugas.
Connor Wolf

1
Tenga en cuenta que puedo ser un poco cínico aquí, pero muchos de los comportamientos que veo en la comunidad arduino son anti-patrones de programación. Veo mucha programación de "copiar y pegar", el tratamiento de las bibliotecas como recuadros negros y solo prácticas generales de diseño deficientes en la comunidad en general. Por supuesto, soy bastante activo en el intercambio de pilas de EE. UU., Por lo que puedo tener una visión un tanto sesgada, ya que tengo algunas herramientas de moderador y, como tal, veo muchas de las preguntas cerradas. Definitivamente hay un sesgo en las preguntas de arduino que he visto allí para "decirme qué C&P solucionar", en lugar de "por qué esto no funciona".
Connor Wolf

3

TCCR1A es el temporizador / contador 1 registro de control A

TCCR1B es el registro de control del temporizador / contador 1 B

TCNT1 es el valor del contador del temporizador / contador 1

CS12 es el bit de selección del tercer reloj para el temporizador / contador 1

TIMSK1 es el registro de máscara de interrupción del temporizador / contador 1

TOIE1 es el temporizador / contador 1 habilitación de interrupción de desbordamiento

Entonces, 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)


1

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.