Registros separados para entrada y salida en microcontroladores

Estoy acostumbrado a usar microcontroladores PIC y solo tienen un registro para entrada y salida.

Ahora estoy estudiando MSP430 y veo que proporcionan registros separados para entrada y salida.

Entonces, ¿por qué es útil proporcionar registros separados para entrada y salida?

EDITAR

Estoy leyendo este libro: Conceptos básicos del microcontrolador MSP430

ingrese la descripción de la imagen aquí

Normalmente este libro compara MSP430 con la familia PIC16. PIC10/12/16 no tiene el registro LATx. Pero PIC16 mejorado tiene.

¿Estás hablando de PxIN PxOUT?
@jsolarski, Sí.

Respuestas (5)

No conozco el MSP430 en detalle, pero tengo mucha experiencia con PIC. Los PIC no tienen específicamente un registro separado para entrada y salida, pero muchos de ellos lo tienen en la práctica. El registro PORT contiene los estados inmediatos de los pines, para entrada y salida. El registro LAT contiene los últimos valores escritos, así que supongo que puede llamarlo registro de salida. Si usa PORT para entrada y LAT para salida, entonces tiene registros de entrada y salida separados. Simplemente ignore que PORT también podría usarse para la salida, con propiedades ligeramente diferentes a LAT.

Los PIC bajos del 16 en adelante no tienen registros LAT, solo el registro PORT. Por lo tanto, utiliza el mismo registro para la entrada y la salida. Eso no es gran cosa ya que leer y escribir son operaciones separadas.

Hay un problema con esto que a veces atrapa a la gente, y gran parte de la programación basada en la superstición ha evolucionado a su alrededor. El problema es que el registro PORT siempre refleja los estados reales de los pines . Esto puede sonar simple e inofensivo, pero puede tener problemas cuando el circuito externo mantiene el pin en el estado opuesto al que fue escrito. Tenga en cuenta que suficiente capacitancia en el pin hará esto, al menos por un tiempo.

Esto se convierte en un problema cuando realiza una operación de lectura, modificación y escritura en un registro de puerto poco antes de haber cambiado un pin de salida. Tomemos el caso realmente obvio de ORing 0 en el registro PORT. OR es una operación de lectura-modificación-escritura. La instrucción OR leerá el valor del registro existente, realizará el OR y luego volverá a escribir el resultado en el registro. Ahora imagine que la instrucción anterior escribió un nuevo valor en un pin de salida, pero ese pin aún no ha tenido tiempo de llegar a su nuevo estado. La parte de lectura de la instrucción OR lee el valor actual del registro PORT, que no es el valor más reciente escrito porque el pin no se ha movido a su nuevo estado. El OR con 0 no cambia nada, por lo que el estado anterior del registro PORT se vuelve a escribir, esencialmente cancelando la escritura anterior.

Ahora puede decir que ORing 0 en un registro PORT es una tontería. En la mayoría de los casos eso es cierto, pero eso fue solo para hacer un ejemplo obvio. Considere que las instrucciones BSF y BCF (bit set y bit clear) en realidad realizan una lectura-modificación-escritura en todo el registro del puerto. Considere la secuencia de instrucciones:

     puerto bankel
     puerto bsf, 1
     puerto bsf, 2

Supongamos que todos los pines del puerto B están configurados en salidas y todos están bajos para empezar. Después de la primera instrucción, RB1 comenzará a subir. Debido a la carga capacitiva, RB1 todavía está bajo y, por lo tanto, PORTB lee 0 cuando se recupera como parte de la segunda instrucción. El bit 2 ahora está establecido, por lo que el valor 4 se escribe en PORTB. RB1 ahora bajará nuevamente ya que se escribió 0 en ese bit. RB2 comenzará a subir. El resultado neto de esta secuencia de instrucciones podría ser que solo RB2 sea alto, no RB1 y RB2 como probablemente se pretendía.

El registro LAT se introdujo para evitar este problema. Contiene el último valor escrito, no el estado instantáneo real de los pines. Si esta secuencia de instrucciones se ejecutara en LATB en lugar de PORTB, tanto RB1 como RB2 se elevarían al final, independientemente de la lentitud con la que lleguen allí.

Entonces, ¿Qué haces? En un PIC 18 y superior, lea desde PORT y escriba a LAT, y no habrá problemas. En los otros PIC, evite cualquier operación de lectura, modificación y escritura en PORT hasta que sepa que todos los estados de los pines se han establecido. Algunas personas le dirán que siempre use una instantánea, modifíquela y luego escríbala en el registro PORT. Eso es solo programación vudú tonta, por supuesto. Conoces tu circuito. La mayoría de las veces, todo lo que se necesita es un solo NOP entre una escritura y cualquier lectura-modificación-escritura. Si el pin no puede llegar a su nuevo estado en al menos un ciclo de instrucción completo, lo más probable es que el circuito se arregle de todos modos. En casos raros, el registro de sombra puede ser útil, pero esos son casos realmente raros. En su mayoría, son solo una pérdida de ciclos, RAM y una cosa más para estropear, especialmente para el tipo de personas que siguen ciegamente reglas como "

Los registros de sombra pueden ser esenciales cuando se trata de salidas de colector abierto.

Algunos procesadores pueden realizar una lectura-modificación-escritura atómica en una dirección de memoria; algunos no pueden. En un ARM, por ejemplo, una instrucción puede leer o escribir una dirección de memoria, pero no ambas. Considere la secuencia:

  ldr r0,[r1] ; Suponga que R1 apunta al puerto
  o r0,#1
  cadena r0,[r1]

Ahora suponga que, entre el ldr y el str, ocurre una interrupción y ejecuta lo siguiente:

  ldr r2,[r1] ; Suponga que R1 apunta al puerto
  o r2,#2
  cadena r2,[r1]

La interrupción establecería el bit 1 (valor #2) del puerto, pero ese cambio no se reflejaría en la copia del código principal de r0. En consecuencia, cuando el código de la línea principal ejecuta la str r0,[r1]instrucción, el cambio de la interrupción se puede deshacer.

Varias CPU tienen diferentes formas de facilitar la actualización de puertos a través de la línea principal y el código de interrupción; muchos de ellos implican tener más registros de los que se necesitarían si no se considerara tal operación.

Hay un nivel de comodidad en la idea de que tiene un registro para escribir solo para salida. Y un registro que solo leerá para muestrear la entrada. Creo que la comodidad proviene de no volver a escribir en algunos de esos pines de entrada. También puede hacer que el puerto de entrada siempre refleje el estado del pin independientemente de la configuración de dirección. Con un solo registro, realiza lecturas, modificaciones y escrituras para cambiar los registros de salida, lo que supone que la lectura de un bit de salida refleja el estado de lo que escribió en ese bit, no el estado real del bit. Por lo tanto, puede administrar por separado la conducción de las salidas y el muestreo de la línea.

Si toma una muestra de varios proveedores, encontrará algunos métodos diferentes, algunos proveedores proporcionan todos los métodos, otros no. Un método es el que usted está acostumbrado. En algún lugar, define el estado de entrada/salida para cada bit, y en un lugar separado tiene un registro para entrada y salida. Los bits declarados de salida cuando se escriben bloquean lo que escribió e intentan controlar el pin de E/S de esa manera, cuando se leen, los bits de salida devuelven lo que escribió (no necesariamente el estado de la línea de E/S). Los bits de entrada siempre devuelven el estado del pin de E/S. Las lecturas, modificaciones y escrituras se utilizan para cambiar salidas individuales sin afectar a otras.

Otro método es el que acaba de descubrir, tener un registro de lectura/escritura solo para la salida y un registro separado esencialmente de solo lectura para la entrada. El registro de entrada puede reflejar el estado de la línea o no para las salidas, según la elección de diseño del proveedor, pero para las entradas se leen y enmascaran como lo haría con un único registro de entrada/salida. Para las escrituras, realiza la misma rutina de lectura, modificación y escritura para establecer bits individuales. O escriba todo el registro para buses, etc. Pero use un registro de salida de puerto separado.

Otro método, útil para E/S que es única o de pocos bits, pero no un puerto completo de 8 o más bits, es tener un puerto de salida establecido que registre que cualquier bit que esté establecido en el valor que escribe en el puerto provoca la salida. E/S (si está configurada como salida) para cambiar a uno. Y un registro de puerto de salida claro separado donde cualquier bit establecido cuando lo escribe (para bits que son salidas) BORRA el puerto de salida. Y un tercer registro cuando se lee devuelve el estado del pin de E/S, aquí nuevamente los pines de salida pueden ser el estado real o lo último que escribió en esa salida. Entonces, si el registro de dirección tiene un 1 para una salida y un 0 para una entrada y escribes un0x3a ese registro de dirección haciendo salidas bit/pin 0 y 1 y las demás entradas. Cuando escribe un 1 en el registro de salida establecido, cambiará la salida en el pin 0 a un voltaje alto. Cuando escribe a 0x2en el registro de salida clara, generará un voltaje cero en el pin de salida 1. Cuando escribe 0xFFen el registro de salida clara, todas las salidas declaradas de E/S se conducirán a cero voltios. Al principio se siente extraño escribir un uno para obtener una salida cero en una línea de E/S, pero te acostumbras bastante rápido cuando dejas de tener que leer, modificar y escribir, e incluso mejor, siempre escribes el número pin o los números escriben. 1<<3para borrar el pin 3 y 1<<3para configurar el pin 3.

Cada solución tiene sus pros y sus contras y ninguna de ellas es una clara ganadora. Para situaciones en las que desea un bus/puerto/salida paralelo de 8 bits que tenga un solo registro en el que escriba un byte que haga que los 8 pines de E/S reflejen el estado de cada bit (los ceros son cero voltios y los unos son el voltaje de suministro ). Para golpear un bus SPI o I2C o algo así, este último es ideal, escriba un 0x10digamos en un registro para afirmar un pin de salida único (sin perturbar ninguna otra salida) y escriba un 0x10en otro registro para borrar esa salida única alfiler. Puede haber momentos y configuraciones de E/S (digamos, un controlador débil con un pull-up) en los que desee leer el estado real de un pin de salida, no lo último que le escribió, pero poder escribir cosas en él.

Verá una tendencia con los proveedores, un proveedor a menudo/siempre usará un método en una familia de chips, a veces familias diferentes. Y a otro vendedor le gusta su método y lo usa para toda su familia. Una vez que haya utilizado uno de sus chips, su código a veces, y el conocimiento/experiencia es definitivamente, transferible de uno de sus productos a otro. Como desarrollador, desea sentirse cómodo con las soluciones de varios proveedores, en muchos casos, la configuración y el borrado de las salidas y el muestreo de las entradas es la parte trivial, la configuración para habilitar un puerto y/o pin convirtiéndolo en una entrada o salida. , tirado hacia arriba o no, colector abierto o no, etc., puede ser difícil en el mejor de los casos. Al implementar líneas de datos bidireccionales, por ejemplo (I2C, por ejemplo), debe saber que, para algunos proveedores, lo configura como algo extraído que puede leer/escribir, a veces lo convierte en una salida impulsada cuando escribe, y lo convierte en una entrada extraída cuando lee. Y algunos lo conviertes en una salida impulsada cuando escribes, y una entrada de alta Z cuando lees y el pull-up tiene que estar en la línea fuera del chip en alguna parte.

De hecho, trabajé en hardware en el que no podía leer el estado de los pines de salida, tenía que realizar un seguimiento de ellos en el software y, esencialmente, el registro del puerto de salida era solo de escritura. Raro de ver en estos días, pero supongo que es otra solución que puede encontrar por ahí.

El Apple II tenía bastantes "interruptores suaves" de solo escritura que se encendían al acceder a una determinada dirección y se apagaban al acceder a otra (solo importaba la dirección, no los datos). Apple //e agregó direcciones de lectura para muchos de ellos, aunque las direcciones de lectura no tenían relación con las direcciones de escritura. Tener la partición en un universo de solo lectura y solo escritura puede parecer extraño, pero en la práctica no causa demasiadas dificultades. En los casos en que un registro de E/S se divide entre funciones, a algunas de las cuales se puede acceder desde las interrupciones mientras que a otras se accede desde el código principal...
... poder configurar y borrar algunos bits de un registro sin afectar a otros es una gran mejora con respecto a tener que hacer una lectura, modificación y escritura. Leer-modificar-escribir es particularmente horrible en los registros que pueden ser cambiados de forma asincrónica por eventos externos. ¿Por qué la gente diseña tales cosas?

Por lo general, hay varios registros si hay más de dos cosas que puede hacer un pin.

El Atmel AVR tiene tres registros para un puerto PIO: DDR, PORT y PIN. Cada bit corresponde al PIO con el mismo número. PIN siempre indica el nivel actual del pin, independientemente de su dirección. DDR establece la dirección de un pin: 0 significa entrada y 1 significa salida. PORT establece el nivel de la unidad (0 = GND, 1 = VCC) si el pin es una salida y el estado pullup (0 = sin pullup, 1 = pullup) para una entrada.

Otra cosa que es muy útil con varios registros: si tiene un registro por cosa que quiere hacer con un periférico, de modo que una escritura hace que algo suceda, y puede evitar leer-modificar-escribir, entonces puede evitar fácilmente las condiciones de carrera (es decir, algo interrumpe entre la lectura y la escritura de una lectura-modificación-escritura y cambia el registro) y mantiene el tamaño del código bajo (al no tener que desactivar las interrupciones antes de leer-modificar-escribir y volver a activarlas después).

El Atmel ATSAM3U (núcleo Cortex-M3) tiene más de 20 registros por puerto PIO. Ellos controlan:

  • Si el pin es PIO o periférico controlado
  • Habilitar salida
  • Activar filtro de entrada
  • Unidad de salida
  • Lectura del estado del pin
  • Interrumpir en el cambio
  • Salida push-pull o drenaje abierto
  • resistencia pull-up
  • Periférico A o B seleccionado
  • Configuración del filtro de fallos o antirrebote

La mayoría de estos ajustes están en grupos de tres registros:

  • Un registro de estado , de solo lectura, que muestra el estado de la función por pin,
  • un registro de habilitación , de solo escritura, donde escribir a 1en un bit establece el bit correspondiente en el registro de estado, y escribir a 0en un bit lo deja solo, y
  • un registro de desactivación , de solo escritura, que funciona igual que el registro de habilitación excepto que al escribir se 1 borra el bit correspondiente en el registro de estado.

Para ilustrar, supongamos que desea habilitar la salida en el pin 5, solo en el puerto A. En el AVR, tendría que escribir:

PORTA |= 0x20;

que lee PORTA, establece el bit 5 y lo vuelve a escribir. La arquitectura AVR tiene una instrucción especializada ( sbi) que hace esto en una instrucción para algunos registros, pero no todos ellos en los chips más grandes, por lo que debe tener cuidado. Si PORTA no estuviera cubierto por la sbiinstrucción, entonces tendría que escribir:

disable_interrupts();
PORTA |= 1 << 5;
enable_interrupts();

(Y para hacerlo realmente bien, tendría que guardar el estado al deshabilitar las interrupciones, de modo que no vuelva a habilitar accidentalmente las interrupciones si se las llama mientras están deshabilitadas).

Pero en el SAM3U, simplemente escribe:

PIOA.OER = 1 << 5;

y listo

(En realidad, es solo PIOA.OERen C++; en C, debe usar un #definenombre grande debido a la falta de control del espacio de nombres. O, si usa plantillas en C++, es , PIO::OutputPin<'A', 5>::Setup()que se convierte en el código anterior, pero se puede combinar con otro PIO configuraciones para configurar todos los registros PIO a la vez. Estoy divagando...)

Los registros múltiples le permiten usar más funciones con un pin. MSP430 tiene 9 registros por puerto Px que le permite configurarlo de la manera que lo necesite. Como usar las resistencias pullups y pulldown incorporadas cuando selecciona el pin como entrada. O configurando el Pin como salida y seleccionando Alto o bajo, o incluso seleccionando los periféricos integrados como salida de fuente de reloj (MCLK, SCLK, ACLK, ADC10, Timer_A).