Saltar al contenido principal
LibreTexts Español

18.3: Cosas específicas del controlador

  • Page ID
    82189
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Pasando a otros archivos de encabezado, debemos recordar que hay decenas y docenas de modelos en una serie de procesadores determinada como el AVR. Cada uno de estos controladores tendrá diferentes capacidades de memoria, capacidades de IO y así sucesivamente, por lo que necesitamos distinguir cuál estamos usando mientras también tratamos de mantener el código lo más genérico posible. Normalmente, esto se hace creando archivos de encabezado específicos para cada controlador. Luego, el IDE te da la opción de seleccionar qué controlador estás usando y #define es un ID de controlador para él (consulta la lista en el IDE de Arduino en Herramientas>>Placa). Considera el siguiente trozo de avr/io.h:

    #ifndef _AVR_IO_H_
    #define _AVR_IO_H_
    
    #include <avr/sfr_defs.h>
    

    Encontramos una serie (enorme) de inclusiones condicionales, cada una buscando el símbolo de procesador predefinido establecido por el IDE de Arduino:

    #if defined (__AVR_AT94K__)
    #    include <avr/ioat94k.h>
    #elif defined (__AVR_AT43USB320__)
    #    include <avr/io43u32x.h>
    #elif defined (__AVR_AT43USB355__)
    #    include <avr/io43u35x.h>
    

    ... y así sucesivamente hasta llegar al ATMega 328P para el Arduino Uno:

    #elif defined (__AVR_ATmega328P__)
    #    include <avr/iom328p.h> 
    

    ... y continuamos hasta llegar al final:

    #elif defined (__AVR_ATxmega256A3B__)
    #    include <avr/iox256a3b.h>
    #else
    #    if !defined(__COMPILING_AVR_LIBC__)
    #        warning "device type not defined"
    #    endif
    #endif
    
    #include <avr/portpins.h>
    #include <avr/common.h>
    #include <avr/version.h>
    
    #endif /* _AVR_IO_H_ */
    

    Entonces, ¿qué hay en avr/iom328p.h preguntas? Esto incluye un montón de cosas que harán que nuestra vida de programación sea mucho más fácil, como definiciones de puertos, registros y bits. Vamos a estar viendo estos una y otra vez:

    #ifndef _AVR_IOM328P_H_
    #define _AVR_IOM328P_H_ 1
    /* Registers and associated bit numbers */
    

    Este es un puerto de entrada de 8 bits y bits asociados

    #define PINB _SFR_IO8(0x03)
    #define PINB0 0
    #define PINB1 1
    #define PINB2 2
    #define PINB3 3
    #define PINB4 4
    #define PINB5 5
    #define PINB6 6
    #define PINB7 7
    

    Este es un registro de dirección de datos de 8 bits y bits asociados

    #define DDRB _SFR_IO8(0x04)
    #define DDB0 0
    #define DDB1 1
    #define DDB2 2
    #define DDB3 3
    #define DDB4 4
    #define DDB5 5
    #define DDB6 6
    #define DDB7 7
    

    Este es un puerto de salida de 8 bits y bits asociados

    #define PORTB _SFR_IO8(0x05)
    #define PORTB0 0
    #define PORTB1 1
    #define PORTB2 2
    #define PORTB3 3
    #define PORTB4 4
    #define PORTB5 5
    #define PORTB6 6
    #define PORTB7 7
    

    ... y así sucesivamente para los puertos C y D. Ahora para convertidor analógico a digital (ADC) golosinas:

    #ifndef __ASSEMBLER__
    #define ADC     _SFR_MEM16(0x78)
    #endif
    #define ADCW    _SFR_MEM16(0x78)
    
    #define ADCL _SFR_MEM8(0x78)
    #define ADCL0 0
    #define ADCL1 1
    #define ADCL2 2
    #define ADCL3 3
    #define ADCL4 4
    #define ADCL5 5
    #define ADCL6 6
    #define ADCL7 7
    
    #define ADCH _SFR_MEM8(0x79)
    #define ADCH0 0
    #define ADCH1 1
    #define ADCH2 2
    #define ADCH3 3
    #define ADCH4 4
    #define ADCH5 5
    #define ADCH6 6
    #define ADCH7 7
    
    #define ADCSRA _SFR_MEM8(0x7A)
    #define ADPS0 0
    #define ADPS1 1
    #define ADPS2 2
    #define ADIE 3
    #define ADIF 4
    #define ADATE 5
    #define ADSC 6
    #define ADEN 7
    
    #define ADCSRB _SFR_MEM8(0x7B)
    #define ADTS0 0
    #define ADTS1 1
    #define ADTS2 2
    
    #define ACME 6
    
    #define ADMUX _SFR_MEM8(0x7C)
    #define MUX0 0
    #define MUX1 1
    #define MUX2 2
    #define MUX3 3
    #define ADLAR 5
    #define REFS0 6
    #define REFS1 7
    

    ... y así sucesivamente para el resto del archivo de cabecera. Bien, entonces, ¿qué es esta pepita de abajo?

    #define PORTB _SFR_IO8(0x05)
    

    La mayoría de los controladores se comunican mediante E/S mapeadas en memoria Es decir, los pines externos se escriben y leen desde ellos como si fueran ubicaciones de memoria ordinarias. Entonces, si quieres escribir en un puerto, simplemente puedes declarar una variable de puntero, establecer su valor en la dirección apropiada y luego manipularla según sea necesario. Para el ATMega 328P, la dirección del puerto IO B es 0x25. Podrías escribir lo siguiente si quisieras establecer el bit 5 alto:

    unsigned char *portB;
    
    portB = (unsigned char *)0x25; // cast required to keep compiler quiet
    *portB = *portB | 0x20;
    

    También puede usar la función de configuración de bits vista anteriormente, que es un poco más clara:

    bitSet( *portB, 5 );
    

    El problema aquí es que hay que declarar y usar la variable puntero que es un poco torpe. También está el negocio propenso a errores de la desreferenciación de puntero en la instrucción de asignación (el * frente a la variable que los programadores principiantes tienden a olvidar). Además, requiere que busques la dirección específica del puerto (y la cambies si usas otro procesador). Entonces, para hacer las cosas más genéricas colocamos las direcciones (o más típicamente, los desplazamientos de una dirección base) en el archivo de encabezado específico del procesador.

    El archivo de encabezado declara un artículo, PORTB, para nuestra conveniencia. Se define como _SFR_IO8 (0x05) pero ¿qué es esa función _SFR_IO8? En sfr_defs.h se define de la siguiente manera:

    #define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
    

    y __SFR_OFFSET se define como 0x20. En otras palabras, este ítem está 0x05 por encima de la dirección de desplazamiento base de 0x20, lo que significa que es la dirección 0x25 como vimos anteriormente. Pero, ¿qué carajo es _MMIO_BYTE ()? Eso se ve un poco raro a primera vista:

    #define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
    

    Esto es solo un elenco con un puntero de-referencia. Dice que este ítem es un puntero a un char sin signo que está siendo desreferenciado (des-referenciado por el primer * y no olvide el typedef anterior para el uint8_t). Al colocar todos estos elementos en archivos de encabezado, hemos logrado que la programación de E/S sea genérica al tiempo que eliminamos la necesidad de declaraciones de variables de puntero y la necesidad de des-referenciación de puntero. 1

    Por lo tanto, si queremos establecer el bit 5 alto, ahora solo podemos decir

    PORTB = PORTB | 0x20; // or more typically: PORTB |= 0x20;
    

    o

    bitSet( PORTB, 5 ); // note lack of “*” in both lines of code
    

    Todo el negocio sobre exactamente dónde se encuentra PORTB en el mapa de memoria nos está oculto y no habrá errores accidentales por dejar fuera el *. El mismo código funcionará con varios procesadores diferentes y, para colmo, es operacionalmente más eficiente que tratar también con punteros. En consecuencia, normalmente utilizaremos estos registros simbólicos y nombres de puertos en lugar de direcciones duras en trabajos futuros. Simplemente encantador. Tiempo para una merienda; algo frutal vez, algo que nos recuerda a las islas tropicales...


    1. Si te preguntas acerca de volátil, recuerda que es un modificador que indica que la variable puede ser cambiada por otro proceso (posiblemente una interrupción). Esto evitará optimizaciones de código ensamblador excesivamente agresivas por parte del compilador.

    This page titled 18.3: Cosas específicas del controlador is shared under a CC BY-NC-SA 4.0 license and was authored, remixed, and/or curated by James M. Fiore via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.