EUR | USD

Caso de manipulación directa de puertos

A medida que la programación se vuelve cada vez más accesible a las masas, algunos bits de código básicos pero valiosos pueden quedar en el camino o tal vez olvidarse por completo, como la DPM (manipulación directa de puertos). Algunos programadores argumentan que estos comandos son difíciles de leer y pueden ser confusos para los neófitos, por lo que nunca deberían mezclarse con estructuras más familiares. ¿Por qué no utilizar todas las herramientas disponibles para que el código sea conciso y eficiente?

Por ejemplo, cada puerto de un microcontrolador de 8 bits requiere tres registros de 8 bits para controlar la actividad del puerto. Estos registros son el DDRx para la dirección de entrada/salida, el PORTx para el control de nivel lógico del pin y el PINx que contiene los estados de pin actuales del puerto. Para más detalles, veamos el puerto C. Durante la configuración, es posible que un programador configure los pines del puerto C como salidas con un estado inicial BAJO. Con frecuencia, se escribe un “para bucle” ordenado que requiere 3 o 4 líneas de código repetido que atraviesa los pines de cero a siete para obtener un total de 32 líneas de código escrito ejecutado. El uso de funciones de biblioteca dentro del bucle como pinMode() y digitalWrite() invoca más actividad “detrás de escena”, lo que genera muchos más códigos ejecutados.

En última instancia, los ocho bits de registro de dirección para el puerto C (DDRC) se configuran en ALTO y los 8 bits de registro de nivel lógico para el puerto C (PORTC) se configuran en BAJO. Se puede lograr exactamente la misma acción al configurar los bits en ALTO o BAJO con los siguientes comandos de registro de puerto directo:

DDRC = 0xFF; //Configurar los pines del puerto C como SALIDAS (en DDRD binario = 0b11111111;)

PORTC = 0x00; //Configurar los pines del puerto C en BAJO (en PORTC binario = 0b00000000;)

¿Qué tal si configuramos el puerto A como entradas?

DDRA = 0x00; //Configurar los pines del puerto A como ENTRADAS (en DDRA binario = 0b00000000;)

Además, por último, el puerto D como entrada y salida mixtas:

DDRD = 0x0F; //Configurar los cuatro bits superiores del puerto D como ENTRADAS y los inferiores como SALIDAS (en DDRD binario = 0b00001111);

El uso de un valor hexadecimal como 0xFF agrega una percepción interesante para comprender la asignación del valor del bit en el registro. Con “0x” como indicador hexadecimal, el primer número ‘F’ representa los cuatro bits superiores de un registro de 8 bits, mientras que el segundo número ‘F’ representa los cuatro bits inferiores.

Todas las combinaciones posibles de bits configurados en ALTO o BAJO se pueden representar mediante un (número) hexadecimal o literal binario. El uso de literales binarios es más visual, ya que los ocho bits se pueden ver a medida que se escribe el código. Cualquiera de los dos es aceptable si es compatible con el compilador. Sin embargo, el hexadecimal es más corto y se ve bien.

Nota: El uso de literales binarios no es un estándar universal en C/C++.

Avancemos un poco más.

Con los puertos A y C establecidos en la configuración, solo se requieren unas pocas líneas de código adicional que utilizan DPM en el bucle principal para leer dispositivos digitales y operar otros dispositivos digitales. En la práctica, dos motores paso a paso con interruptores de límite de recorrido controlados por una palanca de control se programan fácilmente con un número mínimo de comandos de registro y una tabla de búsqueda. La configuración del hardware para esta situación se muestra en la Figura 1.

Figura 1: Configuración del hardware del motor paso a paso/palanca de control con los puertos A y C del microcontrolador señalados. (Fuente de la imagen: Digi-Key Electronics)

Hardware:

También se conecta a los pines 0 a 3 del puerto A una sola palanca de control digital con sus cuatro contactos NA (normalmente abiertos) conectados a VCC. Las salidas se jalan hacia ABAJO en el microcontrolador. Cuando los contactos se cierran, los pines del puerto se ponen en ALTO. Los contactos representan el control ARRIBA, ABAJO, IZQUIERDA y DERECHA y están configurados para permitir que dos contactos adyacentes se activen al mismo tiempo, lo que da como resultado ocho posibles combinaciones de salida de interruptor. Una novena salida representa una DETENCIÓN TOTAL que se produce cuando la palanca de control está centrada y todos los contactos están abiertos.

También se conectan a los pines restantes del puerto A cuatro interruptores de límite con sus contactos NA conectados a tierra y corresponden a la configuración de la palanca de control ARRIBA, ABAJO, IZQUIERDA y DERECHA. Las salidas del interruptor se jalan hacia ARRIBA en el microcontrolador. Cuando los contactos se cierran, los pines van hacia ABAJO.

Las placas de control del motor paso a paso crean movimiento mecánico al alternar tres pines de control alto y bajo para facilitar la funcionalidad de paso, dirección y retención. Para este ejemplo, los ocho bits del puerto C tienen la intención de operar dos controladores, aunque no se usen dos pines.

Programación:

Con el objetivo de generar la salida correcta a los controladores, se utiliza una tabla de búsqueda para transmitir los cuatro bits de la palanca de control a los ocho bits del controlador. Una sola línea de código en el bucle principal activa la función ‘get_output()’ y pasa los contenidos del registro PINA a la función. El valor que devuelve la función se escribe directamente en el registro PORTC;

PORTC = get_output(PINA);

Dentro de la función, se accede a la tabla de búsqueda ‘lookup_output[ ] y se devuelve un valor indexado. Sin embargo, hay algo más dentro de esta función que involucra los interruptores de límite. El valor de índice de la tabla de búsqueda que se encuentra entre corchetes ‘lookup_output[ ]’ está representado por una expresión de desplazamiento y enmascaramiento de bits. La variable de índice resultante es la operación AND bit a bit y de los 4 bits superiores del registro de entrada (valores del interruptor de límite) y los cuatro bits inferiores (valores de la palanca de control). Si alguno de los contactos del interruptor de límite está cerrado y existe un valor cero en cualquiera de los cuatro bits superiores, se borra el bit inferior correspondiente.

Nota: Debido a que cuatro bits pueden representar 16 combinaciones de bits únicas, las siete posiciones de índice de la tabla de búsqueda que no se utilizan se transmiten a 0x00 para evitar errores.

Copyuint8_t get_output(uint8_t porta_val){ return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; }

Ejemplo:

La palanca de control, en la posición ARRIBA y a la DERECHA con todos los interruptores de límite abiertos, daría como resultado un valor de registro PINA binario de 0b11111001 (o 0xF9 hexadecimal) pasado a la función. La función pone a cero los cuatro bits superiores con una operación AND bit a bit de 0b0000001111 (0x0F) que da como resultado 0b00001001 (0x09) como valor de índice de la tabla de búsqueda. La comparación de este resultado con otra operación AND bit a bit y el valor de estado del interruptor de límite desplazado 0b00001111 (0x0F) deja el valor anterior sin cambios y devuelve 0b00001001 (0x09) como el valor de índice final que apunta a 0b00100001 (0x21) en la tabla de búsqueda. Esta es la transmisión del controlador paso a paso para ARRIBA y a la DERECHA. Ver Figura 2.

Figura 2: Interpretación del microcontrolador de las entradas del puerto A y del puerto C cuando la palanca de control está en la posición ARRIBA y a la DERECHA. (Fuente de la imagen: Digi-Key Electronics)

Si se hubiera alcanzado el interruptor de límite ARRIBA y se hubiera generado un bit cero, el valor desplazado sería 0b00000111 (0x07) en lugar de 0b00001111 (0x0F), lo que negaría el valor correspondiente de la palanca de control ARRIBA y produciría un valor de índice final de 0b00000001 (0x01) y no de 0b00001001 (0x09). El valor transmitido para 0b00000001 (0x01) es 0x26 en la tabla de búsqueda. Esta es la transmisión del controlador paso a paso solo para DERECHA. Ver Figura 3.

Figura 3: Interpretación del microcontrolador de las entradas del puerto A y del puerto C cuando la palanca de control está en la posición ARRIBA y a la DERECHA, mientras que se activa el interruptor de límite ARRIBA. (Fuente de la imagen: Digi-Key Electronics)

Conclusión

Ya sea que un programador adopte o no DPM como una herramienta viable para configurar, leer o escribir datos de puerto, minimizar el código es un buen motivador. Las mismas operaciones que utilizan funciones de biblioteca estándar requieren mucho más códigos y pueden ocupar una cantidad considerable de la memoria de un programa disponible. El siguiente código suministrado utiliza menos del 1% de la memoria del microcontrolador ATMEGA328P usado en las pruebas de banco. Comentar correctamente el código es clave para entender la funcionalidad de DPM en código escrito y facilita su uso a cualquier nivel de programador para depurar.

Ejemplos de hardware de Digi-Key:

Motores paso a paso: https://www.digikey.com/short/pdnfp4

Controladores paso a paso: https://www.digikey.com/short/pdnf4r

Palanca de control: https://www.digikey.com/short/pdnf57

Interruptores de límite: https://www.digikey.com/short/pdnfwm

Código de muestra:

Copyconst uint8_t lookup_output[16] = { 0x09, //Índice 0 Detención total. Aplicar corriente de retención 0x26, // Índice 1 Derecho 0x34, // Índice 2 Izquierdo 0x00, // Índice 3 Sin usar 0x36, // Índice 4 Abajo 0x0C, // Índice 5 Abajo/Derecha 0x31, // Índice 6 Abajo/Izquierda 0x00, // Índice 7 Sin usar 0x24, // Índice 8 Arriba 0x21, // Índice 9 Arriba/Derecha 0x0E, // Índice 10 Arriba/Izquierda 0x00, // Índice 11 Sin usar 0x00, // Índice 12 Sin usar 0x00, // Índice 13 Sin usar 0x00, // Índice 14 Sin usar 0x00 // Índice 15 Sin usar }; void setup() { //Configurar todos los bits de registro de dirección del puerto A como ENTRADAS; //Límites (arriba, abajo, derecha, izquierda) Palanca de control (arriba, abajo, derecha, izquierda) DDRA = 0x00; //Configurar todos los bits de registro de dirección del puerto C como SALIDAS; //Control de motor (Not Used, Mot_1, Dir_1, En_1, Not Used, Mot_2, Dir_2, En_2 DDRC = 0xFF; } void loop() { //Enviar los valores del puerto A a la función. Escribir el valor de retorno al puerto C. PORTC = get_output(PINA); } /***** Input Value Translation Function *******/ uint8_t get_output(uint8_t porta_val) { //Comparar el interruptor de límite y los valores de la palanca de control. Recuperar y regresar al valor transmitido.
    return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; } 

Información sobre el autor

Image of Don Johanneck

Don Johanneck, desarrollador de contenido técnico de Digi-Key Electronics, ha trabajado para esta empresa desde 2014. Fue promovido recientemente y su responsabilidad actual es de escribir las descripciones de videos y contenido sobre productos. Don obtuvo un título de Ciencias aplicadas en tecnología electrónica y sistemas automatizados en Northland Community & Technical College a través del programa de becas de Digi-Key. También disfruta del control por radio y restauración y experimentación con máquinas antiguas.

More posts by Don Johanneck