STM32, interfaz serie I2C. STM32, interfaz serie I2C Interfaz Stm32 descripción i2c continuación

A algunas personas les gustan los pasteles, a otras no.

La interfaz i2c está ampliamente extendida y utilizada. En stm32f4 hay hasta tres módulos que implementan este protocolo.
Naturalmente, con apoyo total todo este asunto.

Trabajar con el módulo es, en general, igual que en otros controladores: le das comandos, él los ejecuta y reporta el resultado:
I> Fuimos a INICIAR.
S> Ok, lo envié.
Yo> Genial, envía la dirección ahora. Así: 0xXX.
S> Ok, lo envié. Me dijeron que ACK. Vamonos.
I>Aún vivo, bien. Aquí está el número de registro: 0xYY, vamos.
S> Enviado, recibido ACK.
I>Ahora envíale los datos, aquí está el byte: 0xZZ.
S> Enviado, acepta más: ACK.
I> Que se joda, todavía no. Fueron a PARAR.
S>Está bien.

Y todo es aproximadamente con este espíritu.

EN este controlador Los pines i2c están dispersos por los puertos de esta manera:
PB6: I2C1_SCL
PB7: I2C1_SDA

PB8: I2C1_SCL
PB9: I2C1_SDA

PB10: I2C2_SCL
PB11: I2C2_SDA

PA8: I2C3_SCL
PC9: I2C3_SDA
En general, conviene fijarse en el pinout de los periféricos en la página 59.

Sorprendentemente, para trabajar con i2c necesitas todos sus registros, afortunadamente son pocos:
I2C_CR1- comandos al módulo para enviar comandos/estados y seleccionar modos de funcionamiento;
I2C_CR2- configurar DMA e indicar la frecuencia de funcionamiento del módulo (2-42 MHz);
I2C_OAR1- configurar la dirección del dispositivo (para esclavo), el tamaño de la dirección (7 o 10 bits);
I2C_OAR2- configurar la dirección del dispositivo (si hay dos direcciones);
I2C_DR- registro de datos;
I2C_SR1- registro de estado del módulo;
I2C_SR2- registro de estado (esclavo, debe leerse si los indicadores ADDR o STOPF están configurados en SR1);
I2C_CCR- configurar la velocidad de la interfaz;
I2C_TRISE- configurar tiempos de bordes.

Sin embargo, la mitad de ellos son del tipo “anótalo y olvídalo”.

La placa STM32F4-Discovery ya cuenta con un dispositivo I2C con el que podrás practicar: CS43L22, audio DAC. Está conectado a los pines PB6/PB9. Lo principal es no olvidarse de aplicar un nivel alto al pin PD4 (~RESET se encuentra allí), de lo contrario el DAC no responderá.

El procedimiento de configuración es aproximadamente el siguiente:
1 . Permitir el cronometrado de los puertos y del propio módulo.
Necesitamos los pines PB6/PB9, por lo que debemos configurar el bit 1 (GPIOBEN) en el registro RCC_AHB1ENR para habilitar el puerto.
Y configure el bit 21 (I2C1EN) en el registro RCC_APB1ENR para habilitar el módulo I2C. Para el segundo y tercer módulo, los números de bits son 22 y 23, respectivamente.
2 . A continuación, se configuran los pines: salida Oped Drain (GPIO->OTYPER), modo de función alternativa (GPIO->MODER) y número de función alternativa (GPIO->AFR).
Si lo desea, puede configurar un pull-up (GPIO->PUPDR), si no está en la placa (y se requiere un pull-up a la fuente de alimentación de ambas líneas de cualquier forma). El número para I2C es siempre el mismo: 4. Es bueno que haya un número separado para cada tipo de periférico.
3 . La frecuencia de reloj actual del periférico Fpclk1 (expresada en MHz) se indica en el registro CR2. Según tengo entendido, esto es necesario para calcular diferentes tiempos de protocolo.
Por cierto, deberían ser al menos dos para el modo normal y al menos cuatro para el modo rápido. Y si necesita una velocidad máxima de 400 kHz, también debe dividirse entre 10 (10, 20, 30, 40 MHz).
Frecuencia de reloj máxima permitida: 42 MHz.
4 . La velocidad de la interfaz se configura en el registro CCR y se selecciona el modo (normal/rápido).
El significado es: Tsck = CCR * 2 * Tpckl1, es decir el período SCK es proporcional al CCR (para el modo rápido todo es un poco más complicado, pero se describe en RM).
5 . Se ajusta el tiempo máximo del flanco ascendente en el registro TRISE. Para el modo estándar este tiempo es de 1 µs. En el registro es necesario escribir el número de ciclos de autobús que caben en este tiempo, más uno:
si el ciclo Tpclk1 dura 125 ns, entonces escriba (1000 ns / 125 ns) + 1 = 8 + 1 = 9.
6 . Opcionalmente se habilita la generación de señales de interrupción (error, estado y datos);
7 . El módulo se enciende: el indicador PE en el registro CR1 se establece en 1.

Entonces el módulo funciona como debería. Sólo necesita implementar el orden correcto de los comandos y verificar los resultados. Por ejemplo, una entrada de registro:
1 . Primero debe enviar START configurando una bandera con el mismo nombre en el registro CR1. Si todo está bien, después de un tiempo se establecerá la bandera SB en el registro SR1.
Me gustaría señalar un punto: si no hay pull-up en la línea (y están en 0), es posible que esta bandera no espere en absoluto.
2 . Si se recibe la bandera, enviamos la dirección. Para una dirección de siete bits, simplemente la escribimos en DR exactamente como estará en la línea (7 bits de dirección + bit de dirección). Para diez bits, un algoritmo más complejo.
Si el dispositivo responde a la dirección con ACK, entonces aparecerá el indicador ADDR en el registro SR1. De lo contrario, aparecerá el indicador AF (Reconocimiento de falla).
Si aparece ADDR, necesita leer el registro SR2. No tiene que mirar nada allí, solo la lectura secuencial de SR1 y SR2 restablece este indicador. Y mientras la bandera está configurada, el maestro mantiene SCL bajo, lo cual es útil si necesita pedirle al dispositivo remoto que espere antes de enviar datos.
Si todo está bien, el módulo cambiará al modo de recepción o transmisión de datos, dependiendo del bit menos significativo de la dirección enviada. Para escribir debe ser cero, para leer debe ser uno.
pero estamos mirando el registro, por lo que asumiremos que allí había un cero.
3 . A continuación enviamos la dirección del registro que nos interesa. De la misma forma anotándolo en DR. Después de la transmisión, se configuran los indicadores TXE (búfer de transmisión vacío) y BTF (transferencia completa).
4 . Luego vienen los datos que se pueden enviar mientras el dispositivo responde con un ACK. Si la respuesta es NACK, estos indicadores no se establecerán.
5 . Al finalizar la transferencia (o en caso de una condición inesperada), enviamos STOP: la bandera del mismo nombre se establece en el registro CR1.

Al leer, todo es igual. Cambia solo después de escribir la dirección de registro.
En lugar de escribir datos, se envía nuevamente START (reinicio) y la dirección se envía con el bit menos significativo establecido (signo de lectura).
El módulo esperará datos del dispositivo. Para animarlo a enviar los siguientes bytes, debe configurar el indicador ACK en CR1 antes de recibir (para que después de recibir el módulo envíe este mismo ACK).
Cuando te canses, quita la bandera, el dispositivo verá NACK y se quedará en silencio. Después de lo cual enviamos STOP de la forma habitual y nos alegramos de los datos recibidos.

Aquí está lo mismo en forma de código:
// Inicializa el módulo void i2c_Init(void) ( uint32_t Clock = 16000000UL; // Frecuencia de reloj del módulo (no se utiliza system_stm32f4xx.c) uint32_t Speed ​​​​= 100000UL; // 100 kHz // Habilita el reloj del puerto GPIOB RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // Configurar los pines PB6, PB9 // ¡Abrir drenaje! GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9; // ¡El pull-up es externo, por lo que no se puede configurar aquí! // si es necesario, ver el registro GPIOB->PUPDR // Número de la función GPIOB alternativa ->AFR &= ~(0x0FUL<< (6 * 4)); // 6 очистим GPIOB->AFR |= (0x04UL<< (6 * 4)); // В 6 запишем 4 GPIOB->AFR &= ~(0x0FUL<< ((9 - 8) * 4)); // 9 очистим GPIOB->AFR |= (0x04UL<< ((9 - 8) * 4)); // В 9 запишем 4 // Режим: альтернативная функция GPIOB->MODERNO &= ~((0x03UL<< (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим GPIOB->MODERADOR |= ((0x02UL<< (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2 // Включить тактирование модуля I2C1 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // En este punto, I2C debe estar apagado // Restablecer todo (SWRST == 1, restablecer) I2C1->CR1 = I2C_CR1_SWRST; // PE == 0, esto es lo principal I2C1->CR1 = 0; // Suponemos que estamos ejecutando desde RC (16 MHz) // No hay preescaladores en el sistema de reloj (todos 1) // De manera amistosa, deberíamos calcular todo esto a partir de // la frecuencia de reloj real del módulo I2C1->CR2 = Reloj / 1000000UL; // 16 MHz // Ajustar la frecuencia ( // Tclk = (1 / Fperiph); // Muslo = Tclk * CCR; // Tlow = Muslo; // Fi2c = 1 / CCR * 2; // CCR = Fperiph / ( Fi2c * 2); uint16_t Valor = (uint16_t)(Reloj / (Velocidad * 2)); // Valor mínimo: 4 if(Valor< 4) Value = 4; I2C1->RCC = Valor; ) // Establecemos el tiempo límite de subida // En modo estándar, este tiempo es 1000 ns // Simplemente sumamos uno a la frecuencia expresada en MHz (ver RM p. 604). I2C1->TRISE = (Reloj / 1000000UL) + 1; // Habilitar módulo I2C1->CR1 |= (I2C_CR1_PE); // Ahora puedes hacer algo) // Enviar un byte bool i2c_SendByte(uint8_t Dirección, uint8_t Registro, uint8_t Datos) ( if(!i2c_SendStart()) return false; // Dirección del chip if(!i2c_SendAddress(Address)) return i2c_SendStop (); // Registrar dirección if(!i2c_SendData(Register)) return i2c_SendStop(); // Datos if(!i2c_SendData(Data)) return i2c_SendStop(); // ¡Detener! i2c_SendStop(); return true; ) // Recibir byte bool i2c_ReceiveByte(uint8_t Dirección, uint8_t Registro, uint8_t * Datos) ( if(!i2c_SendStart()) return false; // Dirección del chip if(!i2c_SendAddress(Dirección)) return i2c_SendStop(); // Registrar dirección if(! i2c_SendData(Register)) return i2c_SendStop(); // Reiniciar if(!i2c_SendStart()) return false; // Dirección del chip (leer) if(!i2c_SendAddress(Address | 1)) return i2c_SendStop(); // Recibir un byte if(!i2c_ReceiveData(Data)) return i2c_SendStop(); // ¡Detente! i2c_SendStop(); return true; ) Uso: ( uint8_t ID = 0; i2c_Init(); // Suponemos que PD4 está configurado en un nivel alto y el DAC está funcionando (esto debe hacerse de alguna manera) // Envía un byte al dispositivo con la dirección 0x94, para registrar 0x00 con el valor 0x00. i2c_SendByte(0x94, 0x00, 0x00); // Recibe un byte del dispositivo con dirección 0x94 del registro 0x01 (ID) en el búfer de variables i2c_ReceiveByte(0x94, 0x01, &ID); )
Por supuesto, no puedes hacer esto excepto en un ejemplo de entrenamiento. La espera para que se complete la acción es demasiado larga para un controlador tan rápido.

(Guía para desarrolladores de microcontroladores de la familia HCS08)

Para controlar el módulo I2C, se utilizan 6 registros de funciones especiales:

  • IICC - primer registro de control del módulo I2C;
  • IICC2 - segundo registro de control del módulo I2C;
  • IICS: registro de estado del módulo I2C;
  • IICF: registro de velocidad en baudios del módulo I2C;
  • IICA - Registro de direcciones del módulo I2C;
  • IICD es el registro de datos del módulo I2C.

La MCU de la serie QE contiene 2 módulos I2C y, en consecuencia, dos registros de control de cada tipo. Por ejemplo, el primer registro de estado es IIC1S y el segundo registro de estado es IIC2S.

11.2.8.1. registro de control IICC

Para CA serie MK. AW, Dx, EL, GB, GT, JM, LC, QE. Q.G. SG, SH, SL
Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICC Lectura IICEN IICIE MST Texas Txak 0 0 0
Registro RSTA
Reiniciar 0 0 0 0 0 0 0 0
Descripción de los bits:
Nombre del bit Descripción Símbolo en lenguaje C
IICEN Bit de habilitación del módulo I2C:
0: el controlador I2C está deshabilitado;
1 - El controlador I2C está habilitado.
BIICEN
IICIE Bit de habilitación de interrupción del módulo desde I2C:
0: las interrupciones de solicitud I2C están deshabilitadas;
1: las interrupciones de solicitud I2C están habilitadas.
bHCIE
MST Bit de selección del modo de funcionamiento del controlador I2C:
0: el controlador I2C funciona en modo esclavo;
1 - El controlador I2C funciona en modo Maestro.
Cuando este bit cambia de 0 a 1, se genera un estado de Inicio. Por el contrario, cuando un bit cambia de 1 a 0, se genera una condición de parada.
bMST
Texas Bit de selección de dirección de transmisión en la línea de datos SDA:
0 — la línea funciona para entrada;
1 - la línea funciona para la salida.
bTX
Txak Bit de acuse de recibo en modo de recepción:
0: se genera un bit de confirmación después de recibir un byte;
1: se genera un bit de byte no reconocido.
Este bit controla la generación de un bit de confirmación después de recibir un byte de datos, independientemente de si es esclavo o maestro.
bTXAK
RSTA Si el módulo I2C está funcionando en modo maestro, escribir un 1 en este bit hace que se vuelva a generar el estado Inicio - "Reinicio". BRSTA

11.2.8.2. Registro de estado del IICS

Para la serie MK AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL.
Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICS Lectura FCT IAAS OCUPADO ARBL 0 SRW IICIF RXAK
Registro
Reiniciar 0 0 0 0 0 0 0 0
Descripción de los bits:
Nombre del bit Descripción Símbolo en lenguaje C
FCT Bit de finalización de intercambio. Se establece una vez finalizado el intercambio de un byte:
0 — intercambio no completado;
1 - intercambio completado.
El indicador TCF se pone a 0 cuando se lee el registro de datos IICD (en modo de recepción) o cuando se escribe el registro de datos IICD (en modo de transmisión).
bTCF
IAAS Bandera de dirección esclava. Configure si el dispositivo está operando en modo esclavo y la dirección transmitida en el mensaje maestro es igual a la dirección del esclavo, que se almacena en el registro de direcciones IICA.
La bandera se borra al escribir en el registro IICC.
bIAAS
OCUPADO Bandera de línea ocupada. Este indicador se establece si el módulo I2C ha reconocido el bit de inicio en la línea. La bandera se borra cuando el módulo detecta un bit de parada en la línea.
0: el bus I2C está libre;
1 - El bus I2C está ocupado.
bOCUPADO
ARBL Bandera de pérdida de arbitraje:
0 - sin violaciones en el funcionamiento del bus I2C;
1 – hay pérdida del arbitraje. El módulo I2C debe esperar un momento y luego comenzar la operación de transferencia nuevamente.
barra
SRW Bit de dirección de transmisión del esclavo. Este bit indica el estado del bit R/W en el campo de dirección:
0 - el esclavo acepta. El líder transmite al esclavo;
1 - el esclavo transmite. El líder recibe del esclavo.
bSRW
IICIF Bandera de solicitudes de interrupción sin servicio para el módulo I2C. Se establece en 1 si se establece una de las banderas: TCF, IAAS o ARBL.
0: sin interrupciones sin servicio;
1: hay interrupciones sin servicio.
La bandera se restablece escribiéndole 1.
bIICIF
RXAK Bit de reconocimiento maestro:
0: el esclavo confirmó la recepción de datos;
1: el esclavo no acusó recibo de datos.
Este bit refleja el estado del campo ASK en el diagrama de tiempos del intercambio.
bRXAK

11.2.8.3. Registro de Direcciones del IICA

Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICA Lectura DIRECCIÓN
Registro
Reiniciar 0 0 0 0 0 0 0 0

Este registro almacena la dirección esclava de 7 bits que el desarrollador asignó este dispositivo al desarrollar el sistema. Esta dirección se compara automáticamente con el código de dirección que el esclavo recibió en el campo de dirección del bus I2C. Si las direcciones coinciden, se establece el bit IAAS en el registro de estado IICS.

11.2.8.4. Registro de velocidad de baudios IICF

Para la serie MK AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICF Lectura MULT ICR
Registro
Reiniciar 0 0 0 0 0 0 0 0
Descripción de los bits:

Valores de los coeficientes SCL_DIV y SDA_HV

ISR SCL_DIV SDA_HV ISR SCL_DIV SDA_HV
0x00 20 7 0x20 160 17
0x01 22 7 0x21 192 17
0x02 24 8 0x22 224 33
0x03 26 8 0x23 256 33
0x04 28 9 0x24 288 49
0x05 30 9 0x25 320 49
0x06 34 10 0x26 384 65
0x07 40 10 0x27 480 65
0x08 28 7 0x28 320 33
0x09 32 7 0x29 384 33
0x0A 36 9 0x2A 448 65
0x0B 40 9 0x2B 512 65
0x0C 44 11 0x2C 576 97
0x0D 48 11 0x2D 640 97
0x0E 56 13 0x2E 768 129
0x0F 68 13 0x2F 960 129
0x10 48 9 0x30 640 65
0x11 56 9 0x31 768 65
0x12 64 13 0x32 896 129
0x13 72 13 0x33 1024 129
0x14 80 17 0x34 1152 193
0x15 88 17 0x35 1280 193
0x16 104 21 0x36 1536 257
0x17 128 21 0x37 1920 257
0x18 80 9 0x38 1280 129
0x19 96 9 0x39 1536 129
0x1A 112 17 0x3A 1792 257
0x1B 128 17 0x3B 2048 257
0x1C 144 25 0x3C 2304 385
0x1D 160 25 0x3D 2560 385
0x1E 192 33 0x3E 3072 513
0x1F 240 33 0x3F 3840 513

Este registro almacena dos campos de bits que determinan los parámetros de velocidad y sincronización del intercambio I2C. La frecuencia de las señales de sincronización está determinada por la fórmula:

El tiempo de establecimiento de datos SDA_hold_time en el bus I2C es el intervalo de tiempo entre el momento en que la señal SCL se establece en 0 y los datos en la línea SDA cambian. Asignado por el parámetro SDA_HV (SDA_Hold_Value) de la tabla para el factor ICR del registro de velocidad en baudios:

.

11.2.8.5. registro de datos IICD

Para la serie MK AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICD Lectura DATOS I2C
Registro
Reiniciar 0 0 0 0 0 0 0 0

Si el módulo I2C está funcionando en modo maestro, entonces una operación de escritura en este registro inicia la comunicación I2C (pero solo si el bit de dirección de comunicación en el registro de control IICC está configurado correctamente, es decir, TX = 1). El primer byte después del estado de inicio, que el programa escribe en el registro de datos, es interpretado por los esclavos como la dirección del dispositivo. Por tanto, el programa debe formar correctamente el contenido del primer byte. La operación de lectura de registro devuelve el último byte recibido a través de I2C. La operación de lectura del registro también inicia el inicio de la recepción del siguiente byte, pero solo si el bit de dirección de comunicación en el registro de control IICC está configurado correctamente, es decir, en TX = 0! Con TX = 1, una operación de lectura de registro no provocará que se reciba un nuevo byte a través de I2C desde el esclavo.

Si el módulo I2C está funcionando en modo esclavo, los datos escritos en este registro se transferirán a la línea SDA del bus I2C cuando el dispositivo maestro realice un ciclo de recepción desde este esclavo. La operación de lectura de registro devuelve el último byte recibido del maestro.

11.2.8.6. Registro de control IICC2

Para las series MK AC, Dx, EL, JM, QE, SG, SH, SL
Registro Modo D7 D6 D5 D4 D3 D2 D1 D0
IICC Lectura GCAEN ADEXO 0 0 0 AD10 AD9 AD8
Registro
Reiniciar 0 0 0 0 0 0 0 0

Primeros pasos con el compilador STM32 y mikroC para arquitectura ARM - Parte 4 - Conexión LCD basada en I2C, pcf8574 y HD4478

Me gustaría dedicar el siguiente artículo a trabajar con la interfaz i2c común, que se utiliza a menudo en varios microcircuitos conectados a un microcontrolador.

I2C es un bus que opera a través de dos conexiones físicas (además del cable común). Se escribe mucho sobre esto en Internet y hay buenos artículos en Wikipedia. Además, el algoritmo de funcionamiento del bus se describe muy claramente. En resumen, el bus es un bus síncrono de dos hilos. Pueden haber hasta 127 dispositivos en el bus al mismo tiempo (la dirección del dispositivo es de 7 bits, volveremos a esto más adelante). A continuación se muestra un diagrama típico para conectar dispositivos al bus i2c, con el MK como dispositivo maestro.


Para i2c, todos los dispositivos (tanto maestros como esclavos) usan salidas de drenaje abierto. En pocas palabras, SÓLO pueden atraer el neumático al SUELO. El alto nivel del bus está garantizado por resistencias pull-up. El valor de estas resistencias suele seleccionarse en el rango de 4,7 a 10 kOhm. i2c es bastante sensible a las líneas físicas que conectan los dispositivos, por lo que si se utiliza una conexión con una capacitancia grande (por ejemplo, un cable largo, delgado o blindado), la influencia de esta capacitancia puede "difuminar" los bordes de la señal e interferir con operación normal llantas. Cuanto más pequeña es la resistencia pull-up, menos influencia tiene esta capacitancia en las características de los bordes de la señal, pero MAYOR es la CARGA en los transistores de salida en las interfaces i2c. El valor de estas resistencias se selecciona para cada implementación específica, pero no deben ser inferiores a 2,2 kOhms; de lo contrario, simplemente puede quemar los transistores de salida en los dispositivos que funcionan con el bus.

El bus consta de dos líneas: SDA (línea de datos) y SCL (señal de reloj). cronometra el dispositivo maestro del bus, generalmente nuestro MK. Cuando SCL es alto, la información se lee del bus de datos. El estado del SDA solo se puede cambiar cuando la señal del reloj es baja.. Cuando SCL es alto, la señal en SDA cambia al generar señales COMENZAR (cuando SCL es alto, la señal en SDA cambia de alta a baja) y DETENER - cuando el nivel SCL es alto, la señal en SDA cambia de baja a alta).

Por otra parte, cabe decir que en i2c la dirección se especifica como un número de 7 bits. 8: el bit menos significativo indica la dirección de transferencia de datos 0 - significa que el esclavo transmitirá datos, 1 - recibirá.. Brevemente, el algoritmo para trabajar con i2c es el siguiente:

  • Alto nivel en SDA y SCL- el autobús es gratis, puedes empezar a trabajar
  • ascensores maestros SCL a 1, y cambia de estado S.D.A. de 1 a 0 - lo atrae al suelo - se forma una señal COMENZAR
  • El maestro transmite una dirección de esclavo de 7 bits con un bit de dirección (datos en S.D.A. se exhiben cuando SCL tirado al suelo y leído por el esclavo cuando se suelta). Si el esclavo no tiene tiempo de “agarrar” el bit anterior, atrae SCL al suelo, dejando claro al maestro que no es necesario cambiar el estado del bus de datos: "Todavía estoy leyendo el anterior". Una vez que el maestro ha liberado el neumático, comprueba ¿El esclavo la dejó ir?.
  • Después de transmitir 8 bits de la dirección, el maestro genera el noveno ciclo de reloj y libera el bus de datos. Si el esclavo escuchó su dirección y la aceptó, entonces presionará S.D.A. al suelo. Así se forma la señal. PREGUNTAR- aceptado, todo está bien. Si el esclavo no entiende nada, o simplemente no está allí, entonces no habrá nadie para presionar el neumático. el maestro esperará un tiempo de espera y comprenderá que no fue comprendido.
  • Después de transmitir la dirección, si hemos configurado la dirección de amo a esclavo(8 bits de la dirección son iguales a 1), luego el maestro transmite los datos al esclavo, sin olvidar verificar la presencia de PREGUNTAR desde el esclavo, esperando que el dispositivo esclavo procese la información entrante.
  • Cuando el maestro recibe datos del esclavo, el propio maestro genera una señal PREGUNTAR después de recibir cada byte, y el esclavo controla su presencia. El capitán no podrá enviar específicamente PREGUNTAR antes de enviar el comando DETENER, normalmente dejando claro al esclavo que no es necesario proporcionar más datos.
  • Si, después de enviar datos por el maestro (modo de escritura), es necesario leer datos del esclavo, entonces el maestro genera la señal nuevamente COMENZAR , enviando la dirección del esclavo con una bandera de lectura. (si antes del comando COMENZAR no fue transferido DETENER entonces se forma un equipo REANUDAR). Esto se utiliza para cambiar la dirección de la comunicación maestro-esclavo. Por ejemplo, pasamos la dirección del registro al esclavo y luego leemos datos de él).
  • Al finalizar el trabajo con el esclavo, el maestro genera una señal DETENER- con un nivel alto de señal de reloj, se forma una transición de bus de datos de 0 a 1.
El STM 32 tiene transceptores de bus i2c implementados por hardware. En un MK puede haber 2 o 3 módulos de este tipo, para configurarlos se utilizan registros especiales, descritos en la referencia del MK utilizado.

En MicroC, antes de utilizar i2c (así como cualquier periférico), se debe inicializar correctamente. Para ello utilizamos la siguiente función (Inicialización como maestra):

I2Cn_Init_Advanced(largo sin firmar: I2C_ClockSpeed, const Module_Struct *módulo);

  • norte- número del módulo utilizado, por ejemplo I2C1 o I2C2.
  • I2C_ClockSpeed- velocidad del bus, 100000 (100 kbs, modo estándar) o 400000 (400 kbs, modo rápido). El segundo es 4 veces más rápido, pero no todos los dispositivos lo soportan
  • *módulo- puntero a un módulo periférico, por ejemplo &_GPIO_MODULE_I2C1_PB67, no olvidemos aquí que Asistente de código (Ctrl-espacio ) ayuda mucho.
Primero comprobemos si el autobús está libre, existe una función para ello. I2Cn_Is_Idle(); devolviendo 1 si el autobús es gratuito y 0 si hay intercambio en él.

I2Cn_Inicio();
Dónde norte- número del módulo i2c usado de nuestro microcontrolador. La función devolverá 0 si hay un error en el bus y 1 si todo está bien.

Para transferir datos al esclavo utilizamos la función:

I2Cn_Write (dirección_esclava de caracteres sin firmar, carácter *buf sin firmar, recuento largo sin firmar, modo END_largo sin firmar);

  • norte- número del módulo utilizado
  • dirección_esclava- Dirección esclava de 7 bits.
  • *buf- un puntero a nuestros datos - un byte o una matriz de bytes.
  • contar- el número de bytes de datos transferidos.
  • END_modo- qué hacer después de transferir datos al esclavo, END_MODE_STOP - transmitir una señal DETENER, o END_MODE_RESTART enviar de nuevo COMENZAR, generando una señal REANUDAR y dejar claro al departamento que la sesión con él no ha terminado y ahora se leerán sus datos.
Para leer datos del esclavo, use la función:

I2Cn_Read(char Slave_address, char *ptrdata, recuento largo sin firmar, modo END_largo sin firmar);

  • norte- número del módulo utilizado
  • dirección_esclava- Dirección esclava de 7 bits.
  • *buf- un puntero a una variable o matriz en la que recibimos datos, escriba char o short int
  • contar- número de bytes de datos recibidos.
  • END_modo- qué hacer después de recibir datos del esclavo - END_MODE_STOP - transmitir una señal DETENER, o END_MODE_RESTART enviar una señal REANUDAR.
Intentemos conectar algo a nuestro MK. Para empezar: el popular microcircuito PCF8574(A), que es un expansor de puertos de entrada/salida controlados a través del bus i2c. Este chip contiene sólo un registro interno, que es su puerto físico de E/S. Es decir, si le pasa un byte, inmediatamente quedará expuesto a sus conclusiones. Si cuentas un byte de él (Transmitir COMENZAR dirección con bandera de lectura, señal RESERVAR, leer los datos y finalmente generar una señal DETENER) entonces reflejará los estados lógicos en sus salidas. Conectemos nuestro microcircuito de acuerdo con la hoja de datos:


La dirección del microcircuito se forma a partir del estado de los pines. A0, A1, A2. Para microcircuito PCF8574 la dirección será: 0100A0A1A2. (Por ejemplo, tenemos A0, A1, A2 en un nivel alto, por lo que la dirección de nuestro microcircuito será 0b0100 111 = 0x27). Para PCF8574A - 0111A0A1A2, que con nuestro diagrama de conexión dará la dirección 0b0111 111 = 0x3F. Si, digamos, A2 está conectado a tierra, entonces la dirección para PCF8574A voluntad 0x3B. En total, se pueden montar simultáneamente 16 microcircuitos en un bus i2c, 8 PCF8574A y PCF8574 cada uno.

Intentemos transferir algo, inicializar el bus i2c y transferir algo a nuestro PCF8574.

#define PCF8574A_ADDR 0x3F //Dirección de nuestro PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Genera la señal de INICIO I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Transfiere 1 byte de datos y genera el STOP señal) carácter PCF8574A_reg; // la variable que escribimos en PCF8574 void main() ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Inicia I2C delay_ms(25); // Espera un poco PCF8574A_reg.b0 = 0; // enciende el primer LED PCF8574A_reg.b1 = 1; // apaga el segundo LED mientras (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // invierte el estado de los LED I2C_PCF8574_WriteReg (PCF8574A_reg) ; // transferir datos a nuestro PCF8574 ) )
Compilamos y ejecutamos nuestro programa y vemos que nuestros LED parpadean alternativamente.
Conecté el cátodo de LED a nuestro PCF8574 por una razón. El caso es que cuando se suministra un 0 lógico a la salida, el microcircuito honestamente tira su salida a tierra, pero cuando se aplica un 1 lógico, lo conecta a + alimentación a través de una fuente de corriente de 100 μA. Es decir, no se puede obtener un 1 lógico "honesto" en la salida. Y no se puede encender un LED con 100 µA. Esto se hizo para configurar la salida PCF8574 en la entrada sin registros adicionales. Simplemente escribimos en el registro de salida 1 (esencialmente configurando los estados de los pines en Vdd) y simplemente podemos ponerlo en cortocircuito a tierra. La fuente actual no permitirá que la etapa de salida de nuestro expansor de E/S se "queme". Si la pierna se tira al suelo, entonces el potencial de tierra está en ella y se lee el lógico 0. Si la pierna se tira a +, entonces se lee el lógico 1. Por un lado, es simple, pero por el otro, Siempre debes recordar esto cuando trabajes con estos microcircuitos.


Intentemos leer el estado de los pines de nuestro chip expansor.

#define PCF8574A_ADDR 0x3F //Dirección de nuestro PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Genera la señal de INICIO I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Transfiere 1 byte de datos y genera el STOP señal) void I2C_PCF8574_ReadReg (unsigned char rData) ( I2C1_Start(); // Genera la señal START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Lee 1 byte de datos y genera la señal STOP) char PCF8574A_reg; //variable que escribimos en PCF8574 char PCF8574A_out; // la variable que leemos y PCF8574 char lad_state; //nuestro LED está encendido o apagado void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Inicia I2C delay_ms(25); // Espera un poco PCF8574A_reg.b0 = 0; // enciende el primer LED PCF8574A_reg.b1 = 1; // apaga el segundo LED PCF8574A_reg.b6 = 1; // Tira de los pines 6 y 7 para encender. PCF8574A_reg.b7 = 1; while (1) (delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg); // escribe datos a PCF8574 I2C_PCF8574_ReadReg (PCF8574 A_out); // leer desde PCF8574 si (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Si se presiona 1 botón (el sexto bit del byte leído de PCF8574 es 0, entonces enciende/apaga nuestro LED) if (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // similar para 2 botones y 2 LED) )
Ahora presionando los botones encendemos o apagamos nuestro LED. El microcircuito tiene otra salida. EN T. Se genera un pulso en él cada vez que cambia el estado de los pines de nuestro expansor de E/S. Conectándolo a la entrada de interrupción externa de nuestro MK (te diré cómo configurar interrupciones externas y cómo trabajar con ellas en uno de los siguientes artículos).

Usemos nuestro expansor de puerto para conectar una pantalla de caracteres a través de él. Hay muchos de estos, pero casi todos están construidos sobre la base de un chip controlador. HD44780 y sus clones. Por ejemplo, utilicé una pantalla LCD2004.


La hoja de datos correspondiente y el controlador HD44780 se pueden encontrar fácilmente en Internet. Conectemos nuestra pantalla al PCF8574 y la de ella, respectivamente, a nuestro STM32.

HD44780 utiliza una interfaz cerrada paralela. Los datos se transmiten mediante 8 (por ciclo de reloj) o 4 (por 2 ciclos de reloj) pulsos de puerta en el pin. mi. (leído por el controlador de pantalla en un flanco descendente, transición de 1 a 0). Conclusión R.S. indica si estamos enviando datos a nuestro display ( RS = 1) (los caracteres que debería mostrar son en realidad códigos ASCII) o el comando ( RS = 0). RW Indica la dirección de transferencia, escritura o lectura de datos. Generalmente escribimos datos en la pantalla, entonces ( LE=0). La resistencia R6 controla el contraste de la pantalla. No puede simplemente conectar la entrada de ajuste de contraste a tierra o a la corriente, de lo contrario no verá nada.. VT1 se utiliza para encender y apagar la retroiluminación de la pantalla según los comandos MK. MicroC tiene una biblioteca para trabajar con este tipo de pantallas a través de una interfaz paralela, pero normalmente es caro gastar 8 patas en una pantalla, por lo que casi siempre uso el PCF8574 para trabajar con este tipo de pantallas. (Si alguien está interesado, escribiré un artículo sobre cómo trabajar con pantallas basadas en HD44780 integradas en MicroC a través de una interfaz paralela). El protocolo de intercambio no es particularmente complicado (usaremos 4 líneas de datos y transferiremos información en 2 ciclos de reloj). se muestra claramente en el siguiente diagrama de tiempos:


Antes de transferir datos a nuestra pantalla, se deben inicializar pasando comandos de servicio. (descritos en la hoja de datos, aquí presentamos solo los más utilizados)

  • 0x28- comunicación con el indicador a través de 4 líneas
  • 0x0C- habilitar la salida de imágenes, deshabilitar la visualización del cursor
  • 0x0E- habilitar la salida de imágenes, habilitar la visualización del cursor
  • 0x01- borrar el indicador
  • 0x08- desactivar la salida de imagen
  • 0x06- después de que se muestra el símbolo, el cursor se mueve 1 lugar familiar
Dado que necesitaremos trabajar con este indicador con bastante frecuencia, crearemos una biblioteca de complementos. "i2c_lcd.h" . Para este propósito en Gerente de proyecto Archivos de encabezado y elige Agregar nuevo archivo . Creemos nuestro archivo de encabezado.

#define PCF8574A_ADDR 0x3F //Dirección de nuestro PCF8574 #define DB4 b4 // Correspondencia entre los pines del PCF8574 y el indicador #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 // control de retroiluminación #define displenth 20 // número de caracteres en nuestra línea de visualización static unsigned char BL_status; // variable que almacena el estado de la luz de fondo (encendido/apagado) void lcd_I2C_Init(void); // Pantalla y función de inicialización de PCF8574 void lcd_I2C_txt(char *pnt); // Muestra una línea de texto, el parámetro es un puntero a esta línea void lcd_I2C_int(int pnt); // Muestra el valor de una variable entera, el parámetro es el valor de salida void lcd_I2C_Goto(unsigned short row, unsigned short col); // mueve el cursor a la posición especificada, parámetros fila - línea (de 1 a 2 o 4 dependiendo de la pantalla) y col - (de 1 a displenth)) void lcd_I2C_cls(); // Borra la pantalla void lcd_I2C_backlight (estado int corto sin firmar); // Habilita (al transmitir 1 y deshabilita - al transmitir 0 la retroiluminación de la pantalla)
Ahora describamos nuestras funciones, nuevamente vamos a Gerente de proyecto haga clic derecho en la carpeta Fuentes y elige Agregar nuevo archivo . Crear un archivo "i2c_lcd.с" .

#include "i2c_lcd.h" //incluye nuestro archivo de encabezado char lcd_reg; //registro de almacenamiento temporal para datos enviados a PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //función para enviar datos vía i2c al chip PCF8574 ( I2C1_Start(); I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); ) void LCD_COMMAND (char com) / /función de enviar un comando a nuestra pantalla ( lcd_reg = 0; //escribe 0 en el registro temporal lcd_reg.BL = BL_status.b0; //configura el pin de retroiluminación de acuerdo con el valor de la variable que almacena la retroiluminación state lcd_reg.DB4 = com.b4; // establece los 4 bits más significativos de nuestro comando en el bus de datos del indicador lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg .EN = 1; //establece la salida estroboscópica a 1 I2C_PCF8574_WriteReg ( lcd_reg); //escribe en el registro PCF8574, enviando datos al indicador delay_us (300); //espera el tiempo de espera lcd_reg.EN = 0; // restablece el pulso estroboscópico a 0, el indicador lee los datos I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = com.b0; //lo mismo para los 4 menos significativos bits lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.ES = 1; I2C_PCF8574_WriteReg(lcd_reg); demora_us(300); lcd_reg.ES = 0; I2C_PCF8574_WriteReg(lcd_reg); demora_us(300); ) void LCD_CHAR (unsigned char com) //enviando datos (código de carácter ASCII) al indicador ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //enviando un carácter es diferente de enviar comandos configurando el bit RS en 1 lcd_reg.DB4 = com.b4; //establece los 4 bits más significativos en las entradas lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg) ; delay_us (300); lcd_reg.EN = 0; // restablece el pulso estroboscópico a 0, el indicador lee los datos I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg .BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //configura los 4 bits menos significativos en las entradas lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg ( lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); ) void lcd_I2C_Init(void) ( I2C1_Init_Advanced(400000, &_GPIO _MODULO_I2C1_PB67 ); //inicializamos nuestro módulo I2c en M ​​K delay_ms(200); lcd_Command(0x28); // Visualización en 4 bits por modo de reloj delay_ms (5); lcd_Command(0x08); //Deshabilita la salida de datos a la pantalla delay_ms (5); lcd_Command(0x01); //Borrar la pantalla delay_ms (5); lcd_Command(0x06); //Habilita el cambio automático del cursor después de mostrar el símbolo delay_ms (5); lcd_Command(0x0C); //Activa la visualización de información sin mostrar el cursor delay_ms (25); ) void lcd_I2C_txt(char *pnt) //Envía una cadena de caracteres a la pantalla ( unsigned short int i; //variable de índice de matriz de caracteres temporal char tmp_str; //matriz temporal de caracteres, longitud 1 mayor que la longitud de la pantalla línea, ya que la línea debe terminar сiv con un carácter ASCII NULO 0x00 strncpy(tmp_str, pnt, displenth); //copiar no más de los caracteres displenth de la cadena original a nuestra cadena temporal para (i=0; i Ahora conectemos la biblioteca recién creada al archivo con nuestra función principal:

#include "i2c_lcd.h" //incluye nuestro archivo de encabezado unsigned int i; //contador de variable temporal void main() ( lcd_I2C_Init(); //inicializa la pantalla lcd_I2C_backlight (1); //enciende la luz de fondo lcd_I2C_txt ("Hello habrahabr"); //muestra la línea while (1) ( delay_ms( 1000); lcd_I2C_Goto (2,1); //va al carácter 1 de la línea 2 lcd_i2c_int (i); //muestra el valor i++; //incrementa el contador) )

Si todo está ensamblado correctamente, deberíamos ver texto en el indicador y un contador que se incrementa cada segundo. En general, nada complicado :)

En el próximo artículo continuaremos entendiendo el protocolo i2c y los dispositivos que funcionan con él. Consideremos trabajar con la memoria EEPROM 24XX y el acelerómetro/giroscopio MPU6050.

Hoy hay un nuevo invitado en nuestra mesa de operaciones, se trata de un producto de Microchip, el expansor de puertos MCP23008-E. Esto está destinado (como su nombre lo indica) a aumentar el número de patas de E/S del microcontrolador si de repente se vuelven insuficientes. Por supuesto, si necesitamos las patas y las salidas, entonces podemos cogerlo y no preocuparnos por ello. Si necesita tramos de entrada, existe una solución basada en una lógica estricta. Si necesitamos tanto entradas como salidas y también un pull-up controlado para las entradas, entonces un expansor de puertos es quizás la solución más normal. En cuanto al precio del dispositivo, es muy modesto: alrededor de un dólar. En este artículo intentaré describir en detalle cómo controlar este microcircuito utilizando el microcontrolador AVR.

Primero, un poco sobre las características:

  • 8 pines de puerto independientes
  • Interfaz para comunicación con el mundo exterior - I2C (frecuencia hasta 1,7 MHz)
  • Pull-up personalizable para entradas
  • Puede sacudir la pierna cuando cambia el estado de ciertas entradas.
  • Tres entradas para configurar la dirección del microcircuito (puede colgar 8 dispositivos en un bus)
  • Tensión de funcionamiento de 1,8 a 5,5 voltios.
  • Bajo consumo de corriente
  • Amplia selección de gabinetes (PDIP/SOIC/SSOP/QFN)

Prefiero usar la interfaz I2C, me atrae con una pequeña cantidad de cableado :-) sin embargo, si necesitas una velocidad muy rápida (hasta 10 MHz), entonces necesitas usar la interfaz SPI que está presente en el MCP23S08. La diferencia entre MCP23S08 y MCP23008, según tengo entendido, está solo en la interfaz y en el número de patas para configurar la dirección del chip. ¿A quién le gusta algo más corto? El pinout del mikruhi está en la hoja de datos, no es interesante de ninguna manera y no se considerará aquí. Por tanto, pasemos inmediatamente a cómo empezar a trabajar con este dispositivo. Todo el trabajo se reduce a escribir y leer ciertos datos de los registros del microcircuito. Para mi alegría, no había muchos registros: sólo once. Escribir y leer datos de registros es muy sencillo, publica sobre ello. Es cierto que no hablamos de registros, pero el principio es el mismo. Ahora todo lo que queda es descubrir qué registros leer de qué y en qué registros escribir en qué. Por supuesto, la hoja de datos nos ayudará con esto. De la hoja de datos aprendemos que el microcircuito tiene los siguientes registros:

registro IODIR
Especifica la dirección en la que fluyen los datos. Si el bit correspondiente a un determinado tramo se establece en uno, entonces el tramo es una entrada. Si se pone a cero, salga. En resumen, este registro es análogo al DDRx en AVR (solo que en AVR 1 es una salida y 0 es una entrada).

registro IPOL
Si se establece un bit de registro, entonces se habilita la inversión de entrada para el tramo correspondiente. Esto significa que si aplica un tronco a la pierna. cero entonces del registro GPIO se considera uno y viceversa. La utilidad de esta característica es muy cuestionable.

Registro GPINTEN
Cada bit de este registro corresponde a un pin de puerto específico. Si el bit está configurado, entonces el pin del puerto correspondiente configurado como entrada puede causar una interrupción. Si se reinicia el bit, no importa lo que haga con el tramo de babor, no habrá interrupción. Las condiciones de interrupción las establecen los dos registros siguientes.

registro DEFVAL
El valor actual de los pines configurados para la entrada se compara constantemente con este registro. Si de repente el valor actual comienza a diferir del que hay en este registro, se produce una interrupción. En pocas palabras, si el bit está configurado, la interrupción se producirá cuando el nivel cambie de alto a bajo en el tramo correspondiente. Si se borra el bit, la interrupción se produce en un flanco ascendente.

registro INTCON
Cada bit de este registro corresponde a un pin de puerto específico. Si el bit está limpio, cualquier cambio en el nivel lógico al opuesto provoca una interrupción. Si el bit está activado, la aparición de la interrupción está influenciada por el registro DEFVAL (de lo contrario, se ignora por completo).

registro IOCON
Este es el registro de configuración. Consta de cuatro bits:
SEQOP— Los controles de bits abordan el aumento automático. Si está instalado, el aumento automático está deshabilitado; de lo contrario, está habilitado. Si lo apagamos, podemos leer el valor del mismo registro muy rápidamente debido a que no tenemos que transmitir su dirección cada vez. Si necesita leer rápidamente los 11 registros por turno, entonces debe habilitar el aumento automático. Cada vez que se lee un byte del registro, la dirección en sí aumentará en uno y no será necesario transmitirla.
DISSLW- quién sabe qué es esta parte. No importa cómo lo gire, todo sigue funcionando. Me alegraría que alguien me lo explicara.
HAEN— Bit de configuración de salida INT. Si está configurado, el pin se configura como un drenaje abierto, si el bit se reinicia, entonces el nivel activo en el tramo INT determina el bit INTPOL.
INTPOL— determina el nivel activo en el tramo INT. Si está configurado, el nivel activo es uno, de lo contrario cero.

todavía hay un poco HAEN pero no se utiliza en este chip (activa/desactiva los pines de direccionamiento de hardware en MCP23S08)

registro GPPU
Controla el pull-up de entrada. Si el bit está configurado, aparecerá una conexión a la fuente de alimentación a través de una resistencia de 100 kOhm en el pin correspondiente.

registro INTF
Registro de bandera de interrupción. Si el bit está establecido, esto significa que el tramo del puerto correspondiente provocó una interrupción. Por supuesto, las interrupciones para los tramos requeridos deben estar habilitadas en el registro GPINTEN.

registro INTCAP
Cuando ocurre una interrupción, todo el puerto se lee en este registro. Si hay más interrupciones después de esto, entonces el contenido de este registro no será sobrescrito por el nuevo valor hasta que lo leamos o GPIO.

Registro GPIO
Al leer datos del registro, leemos los niveles lógicos en los pines del puerto. Al registrar datos, establecemos niveles lógicos. Al escribir en este registro, los mismos datos se escriben automáticamente en el registro OLAT.

registro OLAT
Al escribir datos en este registro, enviamos los datos al puerto. Si lee datos desde allí, leerá lo que se escribió y no lo que realmente hay en las entradas del puerto. Para leer entradas usamos solo GPIO.

La descripción de los registros, en mi opinión, es bastante normal y no debería sorprender a nadie. Ahora, solo por diversión, escribamos una pequeña demostración que sondeará 4 botones conectados al expansor de puerto y, dependiendo de su estado, encenderá o apagará 4 LED correspondientes conectados al mismo expansor. El ejemplo será lo más sencillo posible, sin interrupciones. Simplemente leemos constantemente el estado de los botones y mostramos este estado en los LED. El diagrama de nuestro dispositivo se verá así:

El pin de interrupción no tiene que estar conectado, no lo necesitamos aquí, ¡pero sí se requiere el pull-up para la pierna de reinicio! Pasé mucho tiempo hasta que me di cuenta de que mi expansor de puerto se reiniciaba periódicamente por falta de ajuste. Ahora vayamos al código. Por supuesto que seguiremos escribiendo. Código sencillo:

Programa Rashiritel; const AdrR=%01000001; //Dirección del chip con el bit leído const AdrW=%01000000; //Dirección del chip con el registro de bits var r:byte; ///El procedimiento escribe datos de la variable Dat en el registro en la dirección Adr Procedimiento WriteReg(Dat,Adr:byte); Comenzar TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Write(dat); TWI_Parada(); Fin; ///La función devuelve el valor del registro en la dirección Adr Function ReadReg(Adr:byte):byte; var a:byte; Comenzar TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Inicio(); TWI_Write(AdrR); a:=TWI_Read(0); TWI_Parada(); resultado:=a; Fin; comenzar TWI_INIT(200000); ///Inicializando i2c WriteReg(%00001111,0x00); //Los 4 bits más bajos son entradas y los 4 bits restantes son salidas WriteReg(%00001111,0x06); //En pull-up para 4 entradas mientras es VERDADERO, comience r:=ReadReg(0x09); //Lee el estado de las entradas r:= NOT r; //Es necesario invertir los bits, de lo contrario los LED se encenderán cuando se suelte el botón r:= r shl 4; //Desplaza 4 bits a la izquierda... WriteReg(r,0x0A); //Muestra el estado de los botones end; fin.

Publicado el 26/10/2016

En el artículo anterior, analizamos el funcionamiento del STM32 con el bus I 2 C como Maestro. Es decir, él era el líder e interrogó al sensor. Ahora hagamos del STM32 un esclavo y respondamos a las solicitudes, es decir, él mismo funciona como un sensor. Asignaremos 255 bytes de memoria para registros con direcciones de 0 a 0xFF y permitiremos que el Maestro los escriba/lea. Y para que el ejemplo no sea tan simple, hagamos de nuestro STM32 un convertidor analógico a digital con una interfaz I 2 C. El ADC procesará 8 canales. El controlador entregará los resultados de las transformaciones al Maestro cuando lea los registros. Dado que el resultado de la conversión del ADC es de 12 bits, necesitamos 2 registros (2 bytes) para cada canal del ADC.

i2c_slave.h contiene configuraciones:

I2CSLAVE_ADDR– dirección de nuestro dispositivo;

ADC_ADDR_START– la dirección inicial de los registros responsables de los resultados de las conversiones ADC.

En archivo i2c_slave.c lo que más nos interesa son las funciones get_i2c1_ram Y conjunto_i2c1_ram. Función get_i2c1_ram es responsable de leer los datos de los registros. Devuelve datos de la dirección especificada, que se proporciona al Maestro. En nuestro caso, los datos se leen de la matriz. i2c1_ram, pero si el Maestro solicita direcciones de registro del rango asignado para los resultados del ADC, entonces se envían los datos de conversión del ADC.

get_i2c1_ram:

Uint8_t get_i2c1_ram(uint8_t adr) ( //Datos ADC si ((ADC_ADDR_START<= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }

Función conjunto_i2c1_ram– escribe los datos recibidos del Maestro en registros con la dirección especificada. En nuestro caso, los datos simplemente se escriben en una matriz. i2c1_ram. Pero esto es opcional. Puede, por ejemplo, agregar un cheque y, cuando un determinado número llegue a una determinada dirección, realizar algunas acciones. De esta forma puedes enviar diferentes comandos al microcontrolador.

conjunto_i2c1_ram:

Vacío set_i2c1_ram(uint8_t adr, uint8_t val) (i2c1_ram = val; retorno;)

La inicialización es bastante simple:

Int principal(void) ( SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) ( ) )

Primero configuramos la frecuencia máxima de funcionamiento del controlador. Se requiere velocidad máxima cuando es necesario evitar retrasos en el bus I 2 C. Luego iniciamos la operación ADC usando DMA. ACERCA DE . ACERCA DE . Y finalmente, inicializamos el bus I 2 C como Esclavo. Como puedes ver, nada complicado.

Ahora conectemos nuestro módulo STM32 a la Raspberry Pi. Conectemos potenciómetros a los canales ADC. Y leeremos los indicadores ADC de nuestro controlador. No olvide que para que funcione el bus I 2 C, es necesario instalar resistencias pull-up en cada línea del bus.

En la consola Raspberry, comprobemos si nuestro dispositivo es visible en el bus I 2 C (sobre eso):

I2cdetect -y 1

Como puede ver, la dirección del dispositivo 0x27, aunque especificamos 0x4E. Cuando tengas tiempo, piensa por qué sucedió esto.

Para leer de los registros del dispositivo I 2 C-Slave, ejecute el comando:

I2cget -y 1 0x27 0x00

Dónde:
0x27– dirección del dispositivo,
0x00– dirección de registro (0x00…0xFF).

Para escribir en los registros del dispositivo I 2 C-Slave, ejecute el comando:

I2cset -y 1 0x27 0xA0 0xDD

Delaware:
0x27– dirección del dispositivo,
0xA0– dirección de registro
0xDD-Datos de 8 bits (0x00…0xFF)

El comando anterior escribió el número 0xDD en el registro. 0xA0(Puedes escribir en los primeros 16 registros, pero no tiene sentido, pero están reservados para ADC). Ahora leamos:

I2cget -y 1 0x27 0xA0

Para simplificar el proceso de lectura de datos del canal ADC, escribí un script:

#!/usr/bin/env python importar smbus tiempo de importación bus = smbus.SMBus(1) dirección = 0x27 mientras (1): ADC = (); para i en el rango (0, 8): LBS = bus.read_byte_data(dirección, 0x00+i*2) MBS = bus.read_byte_data(dirección, 0x00+i*2+1) ADC[i] = MBS*256 + LBS imprimir ADC time.sleep(0.2)

Sondea y muestra los resultados de los 8 canales ADC en la consola.

De forma similar, puedes combinar varios microcontroladores. Uno de ellos debería ser Maestro(), el otro Esclavo.

¡Te deseo éxito!




Arriba