En esta lecci贸n, aprenderemos a trabajar directamente con los registros del microcontrolador. 驴Para qu茅? Creo que, en primer lugar, esto es necesario para comprender el c贸digo de otra persona y rehacerlo usted mismo, porque el trabajo directo con registros en bocetos de Internet es bastante com煤n.
Registros, bytes, bits.
Comencemos recordando d贸nde escribimos el c贸digo: Arduino IDE. A pesar de la gran cantidad de negativas no siempre justificadas hacia este programa, es bastante bueno. Adem谩s de un mont贸n de herramientas integradas y soporte de 芦repositorio禄 de terceros, el IDE de Arduino nos permite programar la placa en diferentes lenguajes de programaci贸n. Esta es una especie de convenci贸n, pero de hecho resulta en tres idiomas:
- C ++ , que aprendimos en las lecciones del sitio;
- Ensamblador (inserciones de ensamblador): trabajo directo con un microcontrolador, un lenguaje muy complejo;
- El 芦idioma禄 de los registros del microcontrolador. Llam茅moslo condicionalmente un lenguaje separado, aunque no lo es.
驴Qu茅 es un registro? Aqu铆 todo es bastante simple: se trata de bloques de RAM ultrarr谩pidos con un volumen de 1 byte ubicados junto al n煤cleo del Mc y los perif茅ricos. En el lado del programa, estas son variables globales ordinarias que se pueden leer y cambiar. Los registros del microcontrolador almacenan 芦configuraciones禄 para sus diversos perif茅ricos: temporizadores-contadores, puertos con pines, ADC, bus UART, I2C, SPI y otro hardware integrado en el MC. Al cambiar el registro, le damos un comando casi directo al microcontrolador sobre qu茅 hacer y c贸mo. Escribir en el registro toma 1 reloj, es decir, 0.0625 microsegundos ( a una frecuencia de reloj de 16 MHz ) – es MUY r谩pido. Los nombres de registro son fijos, se puede encontrar una lista completa con una descripci贸n detallada en la hoja de datos del microcontrolador ( hoja de datos oficial en ATmega328 – Arduino Nano / UNO / Mini). Trabajar con registros es muy dif铆cil si no los ha aprendido todos de memoria, porque sus nombres suelen ser abreviaturas ilegibles. Las llamadas funciones 芦Arduino禄 realmente funcionan con registros, dej谩ndonos una funci贸n adecuada, comprensible y legible. No tiene nada de sobrenatural.
驴Por qu茅 trabajar con registros directamente? Hay varios grandes beneficios:
- Velocidad de operaci贸n: la lectura / escritura de un registro se realiza lo m谩s r谩pido posible, lo que le permite acelerar el trabajo con el MC (por ejemplo, tirar de los pines manualmente en lugar de digitalWrite ()). La velocidad m谩xima de trabajo con los perif茅ricos del Mc no siempre es necesaria, por lo que si no necesita ahorrar unos microsegundos, ni siquiera tiene que meterse con los registros.
- Tama帽o de la memoria: el trabajo directo con registros le permite trabajar con perif茅ricos del Mc de la manera m谩s compacta posible, leyendo y escribiendo solo los bits necesarios, por lo tanto, el c贸digo espec铆fico escrito para sus tareas ocupar谩 menos espacio que las funciones y bibliotecas universales listas para usar de otra persona (por ejemplo, el mismo digitalWrite () o trabajar con Serial). Cuando trabajas con Arduino, casi nunca puedes preocuparte por esto, pero si haces un proyecto en ATTiny, tendr谩s que comprimir el c贸digo a un byte.
- Flexibilidad de personalizaci贸n: trabajar con el microcontrolador directamente usando registros le permite personalizar de manera muy flexible los perif茅ricos para sus tareas. 隆El hecho es que todas las bibliotecas Arduino existentes cubren un poco m谩s de la mitad de las capacidades del microcontrolador! Una gran cantidad de configuraciones y trucos 煤tiles no se describen en ninguna parte excepto en la hoja de datos, y para usarlos, debe poder leer esta hoja de datos y trabajar con registros. Lea sobre esto a continuaci贸n.
Entonces, desde el punto de vista del c贸digo, el registro es una variable que podemos leer y escribir. En los microcontroladores ATmega / ATtiny, los registros son de 8 bits, es decir, el registro es una variable del tipo byte, cambiandolo es posible configurar el funcionamiento del microcontrolador a un nivel bajo. Hasta donde sabemos, un byte es un n煤mero de 0 a 255, por lo que cada registro tiene 255 configuraciones. No, la l贸gica aqu铆 es completamente diferente: un byte tiene 8 bits, tomando valores 0 y 1, es decir, un registro almacena 8 configuraciones que se pueden activar / desactivar. As铆 es exactamente como funciona la configuraci贸n del microcontrolador a bajo nivel. Tomemos como ejemplo uno de los registros del temporizador 1, llamadoTCCR1B… Imagen de la hoja de datos en ATmega328p:
Registro TCCR1B, corresponde a un byte, consta de 8 bits. Casi todos tienen un nombre (excepto el quinto, en este Mc no est谩 usado). Lo que hace cada bit y registro se describe de la manera m谩s detallada en la hoja de datos del microcontrolador. Los nombres de todos los bits y registros se establecen (ocupados, reservados) en el compilador, es decir, no se pueden crear variables con los mismos nombres. Tampoco puede cambiar los valores de los bits, son constantes:
int WGM12; // resultar谩 en un error CS11 = 5; // resultar谩 en un error
隆Pero puedes leer el valor de un bit por su nombre! Adem谩s, el valor ser谩 igual a su n煤mero en el registro, contando desde la derecha. CS11 es igual a 1, WGM13 es igual a 4, ICNC1 es el 7 (consulte la tabla anterior). Creo que todo est谩 claro aqu铆: hay un registro (byte) que tiene un nombre 煤nico y consta de 8 bits, cada bit tambi茅n tiene un nombre 煤nico por el cual se puede obtener el valor de este bit en el byte de su registro. Queda por descubrir c贸mo usarlo todo.
Leer y escribir registros.
Hay varias formas de establecer bits en registros. Los consideraremos todos para que cuando te encuentres con uno de ellos, sepas qu茅 es y c贸mo funciona esta l铆nea de c贸digo. Absolutamente todo trabajo con registros consiste en poner los bits necesarios en el byte deseado (en el registro) al estado 0 o 1. Recomiendo leer la lecci贸n sobre operaciones de bits, en la que se analiza en detalle todo lo relacionado con la manipulaci贸n de bits.
Volvamos al registro del temporizador que mostr茅 arriba e intentemos configurarlo. La primera forma es establecer expl铆citamente todo el byte a la vez, con todos los unos y ceros. Puedes hacerlo as铆:
TCCR1B = 0b01010101
Por lo tanto, activamos y desactivamos los bits necesarios a la vez, de una sola vez. Como recordar谩 de la lecci贸n sobre tipos de datos y n煤meros, al microcontrolador no le importa en qu茅 sistema num茅rico trabaje, es decir 0b01010101 en nuestro sistema binario, en decimal ser谩 85, y en hexadecimal – 0x55. Y estas tres opciones son absolutamente iguales en cuanto al resultado:
TCCR1B = 0b01010101 ; TCCR1B = 85; TCCR1B = 0x55 ;
Solo necesita mirar el primero y comprender de inmediato qu茅 es y d贸nde. No se puede decir lo mismo de los otros dos. Muy a menudo, en los bocetos de otras personas se encuentra esta notaci贸n y no es muy c贸modo.
Con mucha m谩s frecuencia es necesario cambiar 芦deliberadamente禄 un bit en un byte, y aqu铆 las funciones l贸gicas (bit) y las macros vienen al rescate. Considere todas las opciones, en todas ellas BYTE es un registro de bytes, y BIT es el n煤mero de bit de la derecha. Es decir, BIT es un d铆gito del 0 al 7, o el nombre de un ese bit de la hoja de datos.
Poner el bit en 1 | Poner el bit a 0 | Descripci贸n |
BYTE | = ( 1 << BIT ) ; | BYTE & = ~ ( 1 << BIT ) ; | Usando bit shift << |
BYTE | = ( 2 ^ BIT ) ; | BYTE & = ~ ( 2 ^ BIT ) ; | Usamos 2 a la potencia <n煤mero de bit> (隆ejemplo que no funciona!) |
BYTE | = bit ( BIT ) ; | BYTE & = ~ bit ( BIT ) ; | Usando la macro bit () de Arduin , que reemplaza el cambio |
BYTE | = _BV ( BIT ) ; | BYTE & = ~ _BV ( BIT ) ; | Usamos la funci贸n incorporada _BV () , nuevamente un an谩logo de shift |
sbi ( BYTE, BIT ) ; | cbi ( BYTE, BIT ) ; | Usamos macros sbi y cbi comunes nuestras |
bitSet ( BYTE, BIT ) ; | bitClear ( BYTE, BIT ) ; | Usando las funciones de Arduino bitSet () y bitClear () |
Lo que quiero decir sobre las opciones enumeradas: todas son esencialmente iguales, es decir, la primera, simplemente envuelta en otras funciones y macros. El tiempo de ejecuci贸n de todas las opciones es el mismo, ya que Las funciones macro no realizan acciones innecesarias, sino que llevan todos los m茅todos al primero, con un cambio y | = y & =… Puede encontrar todos estos m茅todos en bocetos de Internet, esto es un hecho. Personalmente, me gusta m谩s el bitSet y bitClear de Arduino , porque tienen un nombre legible y se encuentran en la biblioteca de antemano. Sobre sbi () y cbi () – luego, para usarlos, debe crear macros al principio del documento:
#define cbi (sfr, bit) (_SFR_BYTE (sfr) & = ~ _BV (bit)) #define sbi (sfr, bit) (_SFR_BYTE (sfr) | = _BV (bit))
Y despu茅s de eso puedes usar sbi () y cbi ().
Veamos un ejemplo en el que simplemente manipulamos TCCR1B por diferentes caminos:
// para usar sbi y cbi #define cbi (sfr, bit) (_SFR_BYTE (sfr) & = ~ _BV (bit)) #define sbi (sfr, bit) (_SFR_BYTE (sfr) | = _BV (bit)) void setup () { TCCR1B = 0; // establecer el registro en cero bitSet ( TCCR1B, CS11 ) ; // activ贸 el bit # 1 cbi ( TCCR1B, CS11 ) ; // desactiv贸 el bit # 1 TCCR1B | = _BV ( 4 ) ; // activ贸 el bit # 4 TCCR1B | = ( 1 << WGM12 ) ; // activ贸 el bit # 3 TCCR1B & = ~ _BV ( WGM13 ) ; // desactiv贸 el bit # 4 bitClear ( TCCR1B, 3 ) ; // desactiv贸 el bit # 3 }
Tambi茅n puede agregar una opci贸n donde puede establecer varios bits en una l铆nea:
void setup () { TCCR1B = 0; // establecer el registro en cero // establecer bit 1, 3 y 4 (WGM13) TCCR1B | = _BV ( 1 ) | _BV ( 3 ) | _BV ( WGM13 ) ; }
Creo que todo est谩 claro aqu铆, ahora intentemos 芦apuntar禄y leer un bit de un registro:
Lee un bit | Descrici贸n |
( BYTE >> BIT ) y 1 | Manualmente a trav茅s del rotaci贸n |
bitRead ( BYTE, BIT ) | Funci贸n macro de Arduino |
Los dos m茅todos discutidos devuelven 0 o 1 seg煤n el estado del bit. Ejemplo:
void setup () { TCCR1B = 0; // poner el registro en cero bitSet ( TCCR1B, CS12 ) ; // activ贸 el bit # 2 Serial.begin(9600); // abre el puerto Serial.println(bitRead(TCCR1B, 2));// consigui贸 1 }
Para obtener a煤n m谩s ejemplos de trabajo con bits, consulte el tutorial anterior sobre operaciones con bits.
Registros de 16 bits.
Los Arduino (AVR) tambi茅n tienen registros duales de 16 bits, consisten en dos de 8 bits, por ejemplo, un ADC es un 芦registro dual禄. ADC comprende ADCH y ADCL o registro de temporizador ICR1 comprende ICR1H y ICR1L. Nuestro ADC es de 10 bits, pero los registros son de 8 bits, por lo que parte (8 bits) se almacena en un registro (ADCL), y el resto (2 bits) – en otro (ADCH). Vea c贸mo se ve en la tabla:
La pregunta es: 驴c贸mo podemos leer o cambiar este mismo n煤mero de 10 bits si est谩 dividido en dos registros diferentes? Es muy simple: trabajar con tales registros dobles est谩 integrado en el compilador avr-gcc y puede trabajar con ellos directamente como con una variable regular, por ejemplo:
int val = ADC; // lee el valor ICR1 = 1234; // escribe el valor
Por alguna raz贸n, esta notaci贸n rara vez se usa, quiz谩s por compatibilidad con otros compiladores. Muy a menudo, se encontrar谩 con esta opci贸n, en la que los valores est谩n 芦pegados禄 a trav茅s de rotaci贸n:
int val = ADCL + ( ADCH << 8 ) ; // posible opci贸n ADCL | (ADCH << 8), vea la lecci贸n sobre operaciones de bits
Necesita leer desde el registro inferior. Tan pronto como leemos el registro m谩s bajo (primero), desde el MC se bloquea completamente el acceso a todos los registros, hasta que lea el superior. Si lee primero el m谩s significativo, es posible que se pierda el valor del menos significativo.
Problema inverso: nuevamente hay un registro imaginario de 16 bits (que consta de dos de 8 bits), en el que debemos escribir valor. Por ejemplo doble registro ICR1H y ICR1L, aqu铆 est谩 la tabla:
El microcontrolador s贸lo puede funcionar con un byte, pero 驴c贸mo escribimos un n煤mero de dos bytes?. As铆: divida el n煤mero en dos bytes (usando funciones de Arduino highByte () y lowByte ()) y escriba estos bytes en los registros correspondientes. Ejemplo:
uint16_t val = 1500; // solo un n煤mero de tipo int ICR1H = highByte ( val ) ; // escribe byte alto ICR1L = lowByte ( val ) ; // escribe el byte menos significativo // leer los bytes de nuevo y pegarlos en int byte val_1 = ICR1L; byte val_2 = ICR1H; uint16_t valor = val_1 + ( val_2 << 8 ) ;
Necesita escribir primero el byte alto. Tan pronto como grabemos el byte menos significativo (el 煤ltimo), el MC 芦enclava禄 ambos registros en la memoria, respectivamente, si primero escribe el inferior, el superior ser谩 0, y la escritura posterior del anterior ser谩 ignorado.