













Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Prepara tus exámenes
Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Prepara tus exámenes con los documentos que comparten otros estudiantes como tú en Docsity
Los mejores documentos en venta realizados por estudiantes que han terminado sus estudios
Estudia con lecciones y exámenes resueltos basados en los programas académicos de las mejores universidades
Responde a preguntas de exámenes reales y pon a prueba tu preparación
Consigue puntos base para descargar
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Comunidad
Pide ayuda a la comunidad y resuelve tus dudas de estudio
Descubre las mejores universidades de tu país según los usuarios de Docsity
Ebooks gratuitos
Descarga nuestras guías gratuitas sobre técnicas de estudio, métodos para controlar la ansiedad y consejos para la tesis preparadas por los tutores de Docsity
Manejo del I2C con el STM32 y el hal
Tipo: Apuntes
1 / 21
Esta página no es visible en la vista previa
¡No te pierdas las partes importantes!
Hoy en día incluso el PCB más simple contiene dos o más circuitos integrados digitales (IC), además del MCU principal, designados para tareas específicas. ADC y DAC, memorias EEPROM, sensores, puertos lógicos de E/S, relojes RTC, circuitos de RF y controladores LCD dedicados son sólo una pequeña lista de posibles CI especializados en una sola tarea. El diseño de la electrónica digital moderna se basa en el derecho selección (y programación) de poderosos, específicos y, la mayoría de las veces, baratos ICs para mezclar en el PCB final. Dependiendo de las características de estos CI, a menudo están diseñados para intercambiar mensajes y datos con un dispositivo programable (que normalmente es, pero no limitado a, un microcontrolador) de acuerdo con un protocolo de comunicación bien definido. Dos de los protocolos más utilizados para el intrabordo comunicaciones son el I²C y el SPI, ambos datan de principios de los 80, pero aún están muy difundidos en la industria electrónica, especialmente cuando la velocidad de comunicación no es un requisito estricto y es limitado a los límites del PCB¹. Casi todos los microcontroladores STM32 proporcionan periféricos de hardware dedicados capaces de comunicarse usando los protocolos I²C y SPI. Este capítulo es el primero de dos dedicados a este tema, y presenta brevemente el protocolo I²C y las API del CubeHAL relacionadas para programar este periférico. Si se está interesado en profundizar en el protocolo I²C, el UM10204 de NXP² proporciona la especificación más completa y actualizada.
El Circuito Inter-Integrado (alias I²C - se pronuncia I-cuadrado-C o muy raramente I-dos-C) es una especificación de hardware y protocolo desarrollado por la división de semiconductores de Philips (ahora NXP Semiconductors³) allá por 1982. Se trata de una especificación de bus serial orientado a 8 bits, multislave⁴, semidúplex, de un solo extremo, que utiliza sólo dos cables para interconectar un número determinado de dispositivos esclavos a un maestro. Hasta octubre de 2006, el desarrollo de los dispositivos basados en I²C estaba sujeto al pago de derechos de autor a Philips, pero esta limitación ha sido superada Figura 1: Una representación gráfica del bus I²C
Los dos cables que forman un bus I²C son líneas bidireccionales de drenaje abierto, llamadas Línea de Datos en Serie (SDA) y la Línea de Relojes en Serie (SCL) respectivamente (ver Figura 1). El protocolo I²C especifica que estos dos las líneas necesitan ser levantadas con resistencias. El tamaño de estas resistencias está directamente conectado con la la capacidad del bus y la velocidad de transmisión. Este documento de Texas Instruments⁶ proporciona la matemáticas necesarias para calcular el valor de las resistencias. Sin embargo, es bastante común utilizar resistencias con un valor cercano a 4.7KΩ.Ω. Los modernos microcontroladores, como los de STM32, permiten configurar las líneas GPIO como de drenaje abierto pull-up, habilitando resistencias internas pull-up. Es bastante común leer en la web que puedes usar pull-ups internos para tirar de las líneas I²C, evitando el uso de resistencias dedicadas. Sin embargo, en todos los dispositivos STM32 las resistencias de pull-up internas tienen un valor cercano a 20KΩ.Ω para evitar las fugas de energía no deseadas. Tal valor aumenta el tiempo que necesita el autobús para llegar a la En estado alto, reduciendo la velocidad de transmisión. Si la velocidad no es importante para su aplicación y si (muy importante) no está usando largos trazos entre la MCU y el IC (menos luego 2cm), entonces está bien usar resistencias internas de pull-up para un montón de aplicaciones. Pero, si tienen suficiente espacio en el PCB para colocar un par de resistencias, entonces se sugiere fuertemente para usar los externos y los dedicados. Lea con atención Los microcontroladores STM32F1 no proporcionan la capacidad de tirar de las líneas SDA y SCL. Sus GPIOs deben ser configurados como de drenaje abierto, y se necesitan dos resistencias externas para arrancar las líneas I²C. Siendo un protocolo basado en sólo dos cables, debería haber una forma de dirigir un dispositivo esclavo individual en el mismo bus. Por esta razón, I²C define que cada dispositivo esclavo proporciona una dirección esclava única para el bus⁷ dado. La dirección puede ser de 7 o 10 bits de ancho (esta última opción es bastante poco común). Las velocidades del bus I²C están bien definidas por la especificación del protocolo, aunque no es tan raro encontrar chips capaces de hablar con velocidades personalizadas (y a menudo difusas). Las velocidades de bus I²C comunes son las de 100kHz, también conocido como modo estándar, y el de 400kHz, conocido como modo rápido. Las recientes revisiones del estándar pueden funcionar a velocidades más rápidas (1MHz, conocido como modo rápido plus, y 3.4MHz, conocido como modo de alta velocidad, y 5MHz, conocido como modo ultra rápido). El protocolo I²C es un protocolo lo suficientemente simple como para que una MCU pueda "simular" un periférico I²C dedicado si no lo proporciona: esta técnica se llama "bit-banging" y se utiliza comúnmente en la realidad arquitecturas de 8 bits de bajo costo, que a veces no proporcionan una interfaz I²C dedicada para reducir cuenta de alfileres y/o costo del IC.
En el protocolo I²C todas las transacciones son siempre iniciadas y completadas por el maestro. Este es uno de los las pocas reglas de este protocolo de comunicación que hay que tener en cuenta al programar (y, sobre todo, depuración) dispositivos I²C. Todos los mensajes intercambiados a través del bus I²C se dividen en dos tipos de marco: un marco de dirección,
El marco de la dirección es siempre el primero en cualquier nueva secuencia de comunicación. Para una dirección de 7 bits, la dirección se marca primero con el bit más significativo (MSB), seguido de un bit R/W que indica si se trata de una operación de lectura (1) o de escritura (0) (véase la figura 2). Figura 3: La estructura del mensaje en caso de que se utilice el direccionamiento de 10 bits En un sistema de direccionamiento de 10 bits (véase la figura 3), se necesitan dos tramas para transmitir la dirección del esclavo. La primera trama consistirá en el código 1111 0XXD2 donde XX son los dos bits MSB de la dirección del esclavo de 10 bits y D es el bit R/W como se ha descrito anteriormente. El primer bit ACKΩ. de la trama será afirmado por todos los esclavos que coinciden con los dos primeros bits de la dirección. Al igual que con una transferencia normal de 7 bits, comienza otra transferencia inmediatamente, y esta transferencia contiene bits [7:0] de la dirección. En este punto, el esclavo con dirección debería responder con un poco de ACKΩ.. Si no lo hace, el modo de fallo es el mismo que el de un sistema de 7 bits. Obsérvese que los dispositivos de dirección de 10 bits pueden coexistir con los dispositivos de dirección de 7 bits, ya que la parte principal 11110 de la dirección no forma parte de ninguna dirección de 7 bits válida.
El ACKΩ. tiene lugar después de cada byte. El bit ACKΩ. permite al receptor señalar al transmisor que el byte se ha recibido con éxito y que se puede enviar otro byte. El maestro genera todos los pulsos de reloj en la línea SCL, incluyendo el noveno pulso de reloj de ACKΩ.. La señal de ACKΩ. se define de la siguiente manera: el transmisor libera la línea SDA durante el reconocimiento pulso de reloj para que el receptor pueda tirar de la línea SDA BAJO y se mantenga estable BAJO durante el período ALTO de este pulso de reloj. Cuando SDA permanece ALTO durante este noveno pulso de reloj, este se define como la señal de no reconocimiento (NACKΩ.). El maestro puede entonces generar un STOP condición para abortar la transferencia, o una condición de RESTART para iniciar una nueva transferencia. Hay cinco condiciones que conducen a la generación de un NACKΩ.:
Después de que se haya enviado el marco de la dirección, los datos pueden comenzar a ser transmitidos. El maestro simplemente continúa generando pulsos de reloj en el SCL a un intervalo regular, y los datos serán colocados en el SDA por ya sea el amo o el esclavo, dependiendo de si el bit R/W indicaba una operación de lectura o escritura. Normalmente, el primer o los dos primeros bytes contienen la dirección del registro esclavo para escribir/leer. Por ejemplo, para las EEPROMs I²C los dos primeros bytes que siguen a la trama de direcciones representan la dirección de la ubicación de la memoria involucrada en la transacción. Dependiendo del bit R/W, los sucesivos bytes son llenados por el maestro (si el bit R/W está en 1) o el esclavo (si el bit R/W está en 0). El número de tramas de datos es arbitrario, y la mayoría de los dispositivos esclavos autoincrementarán el registro interno, lo que significa que las lecturas o escrituras posteriores vendrán del siguiente registro en línea. Este modo también se llama modo secuencial o ráfaga (ver Figura 4) y es una forma de acelerar la velocidad de transferencia. Figura 4: Una transmisión en modo ráfaga en la que se intercambian múltiples bytes en una sola transacción
El protocolo I²C tiene esencialmente un simple patrón de comunicación: un maestro envía en el bus la dirección del dispositivo esclavo involucrado en la transacción; el bit R/W, que es el bit LSB en el byte de dirección del esclavo, establece la dirección del flujo de datos (de maestro a esclavo - W - o de esclavo a maestro - R) se envía un número de bytes, cada uno intercalado con un bit de ACKΩ., por uno de los dos pares según la dirección de transferencia, hasta que se produce una condición de STOP.
(por ejemplo, una EEPROM que no ha terminado de escribir en la memoria no volátil todavía y necesita terminarla antes de poder atender otras solicitudes). En este caso, algunos dispositivos esclavos ejecutarán lo que se conoce como estiramiento de reloj. En el estiramiento del reloj el esclavo pausa una transacción manteniendo la línea SCL BAJA. La transacción no puede continuar hasta que la línea se libera de nuevo en ALTA. El estiramiento del reloj es opcional y la mayoría de los dispositivos esclavos no incluyen un controlador SCL para que no puedan estirar el reloj (principalmente para simplificar la disposición del hardware del Interfaz I²C). Como descubriremos más adelante, un MCU STM32 configurado en modo esclavo I²C puede opcionalmente implementar el modo de estiramiento del reloj.
Según el tipo de familia y el paquete utilizado, los microcontroladores STM32 pueden proporcionar hasta cuatro periféricos I²C independientes. La tabla 1 resume la disponibilidad de los periféricos I²C en los MCU STM32 que equipan las dieciséis placas Nucleo que estamos considerando en este libro.
Tabla 1: Disponibilidad efectiva de periféricos I²C en las MCU que equipan las dieciséis placas Nucleo Para cada periférico I²C, y un MCU STM32 dado, la Tabla 1 muestra los pines correspondientes a SDA y las líneas SCL. Además, las filas más oscuras muestran alfileres alternativos que pueden ser usados durante la disposición de la tablero. Por ejemplo, dado el MCU STM32F401RE, podemos ver que el periférico I2C1 está mapeado a PB7 y PB6, pero PB9 y PB8 también se pueden usar como alfileres alternativos. Tenga en cuenta que el periférico I2C1 utiliza los mismos pines de E/S en todas las MCU STM32 con el paquete LQFP-64. Este es un ejemplo fundamental de la la compatibilidad pin a pin que ofrecen los microcontroladores STM32. Ahora estamos listos para ver cómo usar las API del CubeHAL para programar este periférico. 14.2 HAL_I2C Module Para programar el periférico I²C, el CubeHAL define la estructura C I2C_HandleTypeDef, que se define de la siguiente manera: typedef struct { I2C_TypeDef Instance; _/ I²C registers base address /_ I2C_InitTypeDef Init; _/ I²C communication parameters */_ uint8_t pBuffPtr; _/ Pointer to I²C transfer buffer /_ uint16_t XferSize; _/ I²C transfer size /_ __IO uint16_t XferCount; _/ I²C transfer counter */_ DMA_HandleTypeDef hdmatx; _/ I²C Tx DMA handle parameters */_ DMA_HandleTypeDef hdmarx; _/ I²C Rx DMA handle parameters /_ HAL_LockTypeDef Lock; _/ I²C locking object /_ __IO HAL_I2C_StateTypeDef State; _/ I²C communication state /_ __IO HAL_I2C_ModeTypeDef Mode; _/ I²C communication mode /_ __IO uint32_t ErrorCode; _/ I²C Error code /_ } I2C_HandleTypeDef; Analicemos los campos más importantes de esta estructura C. Instancia: es el puntero al descriptor I²C que vamos a usar. Por ejemplo, I2C1 es el descriptor del primer periférico I²C. Init: es una instancia de la estructura C I2C_InitTypeDef usada para configurar el periférico. Nosotros lo estudiará más a fondo dentro de un tiempo. pBuffPtr: apuntador al buffer interno utilizado para almacenar temporalmente los datos transferidos a y del periférico I²C. Se utiliza cuando el I²C funciona en modo de interrupción y no debe ser modificado a partir del código de usuario. hdmatx, hdmarx: apuntador a instancias de la estructura DMA_HandleTypeDef usada cuando el I²C El periférico funciona en modo DMA. La configuración del periférico I²C se realiza utilizando una instancia de la estructura C I2C_InitTypeDef, que se define de la siguiente manera: typedef struct { uint32_t ClockSpeed; _/ Specifies the clock frequency /_ uint32_t DutyCycle; _/ Specifies the I²C fast mode duty cycle. /_ uint32_t OwnAddress1; _/ Specifies the first device own address. /_ uint32_t OwnAddress2; _/ Specifies the second device own address if dual addressing_
diferentes entre los modos de I²C. No son ratios obligatorios que los periféricos de STM32 I²C deban mantener. Por ejemplo, tHIGH = 4s y tLOW = 6s sería una relación de 0,67, que sigue siendo compatible con los tiempos del modo estándar (100kHz) (porque tHIGH = 4s y tLOW > 4,7s, y su suma es igual a 10μs) en el que una señal está activa. Para cada velocidad del bus I²C, las). Los periféricos I²C de las MCU de STM32 definen los siguientes ciclos de trabajo (ratios). Para el modo estándar, el ratio está fijado en 1:1. Esto significa que tLOW = tHIGH = 5s. Para el modo rápido podemos usar dos ratios: 2:1 o 16:9. La relación 2: significa que 4μs) en el que una señal está activa. Para cada velocidad del bus I²C, las (=400kHz) se obtienen con tLOW = 2,66s y tHIGH = 1,33s y ambos valores son superiores a los reportados en la Tabla 2 (0.6μs) en el que una señal está activa. Para cada velocidad del bus I²C, las y 1.3μs) en el que una señal está activa. Para cada velocidad del bus I²C, las). Una relación de 16:9 significa que 4μs) en el que una señal está activa. Para cada velocidad del bus I²C, las se obtienen con tLOW = 2:56s y tHIGH = 1:44s y ambos valores son aún más altos que el reportado en la Tabla 2. ¿Cuándo usar una relación de 2:1 en lugar de la de 16:9 y viceversa? Depende de la frecuencia del reloj periférico (PCLKΩ.1). Una relación de 2:1 significa que los 400MHz se consiguen dividiendo la fuente del reloj por tres (1+2). Esto significa que el PCLKΩ. debe ser un múltiplo de 1,2MHz (400kHz * 3). Usar una proporción de 16:9 significa que dividimos el PCLKΩ.1 entre 25. Esto significa que podemos obtener la máxima frecuencia de bus I²C cuando el PCLKΩ.1 es un múltiplo de 10MHz (400kHz * 25). Por lo tanto, la selección correcta de los ciclos de trabajo depende de la velocidad efectiva del bus APB1, y de la frecuencia I²C SCL deseada (máxima). Es importante subrayar que, aunque la frecuencia SCL sea inferior a 400kHz (por ejemplo, utilizando una relación igual a 16:9 mientras que teniendo una frecuencia PCLKΩ.1 de 8MHz podemos alcanzar una velocidad máxima de comunicación igual a 360kHz) seguimos satisfaciendo los requisitos de la especificación de modo rápido I²C (400kHz son un límite superior). OwnAddress1, OwnAddress2: el periférico I²C de las MCU de STM32 puede utilizarse para desarrollar tanto los dispositivos I²C maestro y esclavo. Cuando se desarrollan dispositivos I²C esclavos, el campo OwnAddress1 permite para especificar la dirección del esclavo I²C: el periférico detecta automáticamente la dirección dada en el I²C, y activa automáticamente todos los eventos relacionados (por ejemplo, puede generar el correspondiente interrupción para que el código del firmware pueda iniciar una nueva transacción en el autobús). I²C periférico soporta el direccionamiento de 7 o 10 bits, así como el modo de direccionamiento dual de 7 bits: en este en caso de que podamos especificar dos direcciones de esclavo de 7 bits distintas, para que el dispositivo sea capaz de responder a solicitudes enviadas a ambas direcciones. AddressingMode: este campo puede asumir los valores I2C_ADDRESSINGMODE_7BIT o I2C_ADDRESSINGMODE_ 10BIT para especificar el modo de direccionamiento de 7 o 10 bits respectivamente. DualAddressMode: este campo puede asumir los valores I2C_DUALADDRESS_ENABLE o I2C_DUALADDRESS_ DISABLE para activar/desactivar el modo de direccionamiento dual de 7 bits. GeneralCallMode: la llamada general es una especie de dirección de emisión en el protocolo I²C. Una dirección esclava especial de I²C, 0x0000 000, se utiliza para enviar un mensaje a todos los dispositivos del mismo bus. La llamada general es una característica opcional y, al establecer este campo en el valor I2C_GENERALCALL_ENABLE, el periférico I²C generará eventos cuando la dirección de la llamada general sea igualada. No trataremos este modo en este libro. NoStretchMode: este campo, que puede asumir los valores I2C_NOSTRETCH_ENABLE o I2C_- NOSTRETCH_DISABLE se utiliza para desactivar/habilitar el modo opcional de estiramiento del
reloj (ten en cuenta que al configurarlo como I2C_NOSTRETCH_ENABLE se desactiva el modo de estiramiento del reloj). Para obtener más información sobre este modo I²C opcional, consulte el UM10204 de NXP y el manual de referencia de su MCU. Como de costumbre, para configurar el periférico I²C utilizamos la función: HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c); que acepta un puntero a una instancia del I2C_HandleTypeDef visto antes.
Ahora vamos a analizar las principales rutinas proporcionadas por el CubeHAL para usar el periférico I²C en modo maestro. Para realizar una transacción a través del bus I²C en modo de escritura, el CubeHAL proporciona la función: HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); donde: hi2c: es el puntero a una instancia de la estructura I2C_HandleTypeDef vista anteriormente, que identifica el periférico I²C; DevAddress: es la dirección del dispositivo esclavo, que puede ser de 7 o 10 bits de largo dependiendo del IC específico; pData: es el puntero de un array, con una longitud igual al parámetro Size, que contiene la secuencia de bytes que vamos a transmitir; Timeout: representa el tiempo máximo, expresado en milisegundos, que estamos dispuestos a esperar para la finalización de la transmisión. Si la transmisión no se completa en el tiempo de espera especificado, la función aborta y devuelve el valor HAL_TIMEOUT; en caso contrario, devuelve el valor HAL_OKΩ. si no se producen otros errores. Además, podemos pasar un tiempo de espera igual a HAL_MAX_DELAY (0xFFFF FFFF) para esperar indefinidamente la finalización de la transmisión. Para realizar una transacción en modo de lectura podemos utilizar, en cambio, la siguiente función: HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); Las dos funciones anteriores realizan la transacción en modo de votación. Para las transacciones basadas en la interrupción, podemos usar las funciones: HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
k() ler() (^) de STOP y se ha abortado la transacción I²C (periférico que funciona tanto en modo maestro como en modo esclavo). Tabla 3: Llamadas disponibles del CubeHAL cuando un periférico I²C funciona en modo de interrupción o DMA Finalmente, las funciones: HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); permiten realizar transacciones I²C utilizando DMA. Para hacer ejemplos de trabajo completos y completos necesitamos un dispositivo externo capaz de interactuar a través del bus I²C, ya que las placas de Nucleo no proporcionan tales periféricos. Por esta razón usaremos un dispositivo externo Memoria EEPROM: el 24LCxx. Esta es una familia muy popular de EEPROMs seriales, que se han convertido en una especie de estándar en la industria electrónica. Son baratas (cuestan normalmente unas pocas decenas de céntimos), se producen en varios paquetes (que van desde los "viejos" paquetes THT P-DIP, hasta los modernos y compactos WLCP), proporcionan una retención de datos durante más de 200 años y las páginas individuales pueden ser borradas más de 1 millón de veces. Además, muchos fabricantes de silicio tienen sus propias versiones compatibles (ST también proporciona su propio conjunto de EEPROMs compatibles con 24LCxx). Estas memorias tienen la misma popularidad que 555 temporizadores, y apuesto a que sobrevivirán durante muchos años a la innovación tecnológica. Nuestros ejemplos se basarán en el modelo 24LC64, que es una EEPROM de 64KΩ.bits (esto significa que la la memoria es capaz de almacenar 8KΩ.b o, si lo prefiere, 8192 bytes). El pinout de la versión PDIP-8 se muestra en la figura 6. A0, A1 y A2 se utilizan para establecer los bits LSB de la dirección I²C, como se muestra en la Figura 7: si uno de esos pines está atado al suelo, entonces el bit
correspondiente se pone a 0; si está atado a VDD, entonces el se ajusta a 1. Si los tres pines están atados al suelo, entonces la dirección I²C corresponde a 0xA0. La clavija WP es la clavija de protección contra escritura: si se ata al suelo, podemos escribir dentro de las células de memoria individuales. Por el contrario, si se conecta a VDD, las operaciones de escritura no tienen efectos. Como el periférico I2C1 está mapeado a los mismos pines en todas las placas Nucleo, la figura 8 muestra la forma correcta de conectar una EEPROM 24LCxx al conector Arduino en las dieciséis placas Nucleo. Lea con atención Los microcontroladores STM32F1 no proporcionan la capacidad de tirar de las líneas SDA y SCL. Sus GPIOs deben ser configurados como de drenaje abierto. Por lo tanto, tienes que añadir dos resistencias adicionales a las líneas I²C pull-up. Algo entre 4KΩ. y 10KΩ. es un valor probado. Como se dijo antes, una EEPROM de 64KΩ.bits tiene 8192 direcciones, que van desde 0x0000 hasta 0x1FFF. La escritura de un byte individual se realiza enviando por el bus I²C la dirección de la EEPROM, la mitad superior de la dirección de la memoria seguida de la mitad inferior, y el valor a almacenar en esa celda, cerrando la transacción con una condición STOP. Asumiendo que queremos almacenar el valor 0x4C dentro de la ubicación de memoria 0x320, entonces la Figura 9 muestra la secuencia correcta de la transacción. La dirección 0x320 se divide en dos partes: la parte superior, igual a 0x3 se transmite primero, y la parte inferior igual a 0x20 se
Finalmente estamos listos para organizar un ejemplo completo. Crearemos dos funciones simples, llamadas Read_From_24LCxx() y Write_To_24LCxx() que permiten escribir/leer datos de una memoria 24LCxx, usando el CubeHAL. Luego probaremos estas rutinas simplemente almacenando una cadena dentro de la EEPROM, y luego leyéndola de nuevo: si la cadena original es igual a la leída de la EEPROM, 14 int main( void ) { 15 const char wmsg[] = "We love STM32!"; 16 char rmsg[ 20 ]; 17 18 HAL_Init(); 19 Nucleo_BSP_Init(); 20 21 MX_I2C1_Init(); 22 23 Write_To_24LCxx(&hi2c1, 0xA0, 0x1AAA, ( uint8_t *)wmsg, strlen(wmsg)+1); 24 Read_From_24LCxx(&hi2c1, 0xA0, 0x1AAA, ( uint8_t )rmsg, strlen(wmsg)+1); 25 26 if (strcmp(wmsg, rmsg) == 0) { 27 while ( 1 ) { 28 HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); 29 HAL_Delay( 100 ); 30 } 31 } 32 33 while ( 1 ); 34 } 35 36 _/ I2C1 init function /_ 37 static void MX_I2C1_Init( void ) { 38 GPIO_InitTypeDef GPIO_InitStruct; 39 40 _/ Peripheral clock enable */_ 41 __HAL_RCC_I2C1_CLK_ENABLE(); 42 43 hi2c1.Instance = I2C1; 44 hi2c1.Init.ClockSpeed = 100000; 45 hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; 46 hi2c1.Init.OwnAddress1 = 0x0; 47 hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; 48 hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; 49 hi2c1.Init.OwnAddress2 = 0; 50 hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; 51 hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; 52 53 GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; 54 GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; 55 GPIO_InitStruct.Pull = GPIO_PULLUP; 56 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; 57 GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; 58 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); 59 60 HAL_I2C_Init(&hi2c1); 61 }
Analicemos el fragmento de código anterior a partir de la rutina MX_I2C1_Init(). Comienza a habilitar el reloj periférico de I2C1, para que podamos programar sus registros. Luego fijamos la velocidad del bus (100kHz en nuestro caso - el ajuste del ciclo de servicio se ignora en este caso, porque el ciclo de servicio se fija en 1:1 cuando el bus funciona a velocidades inferiores o iguales a 100kHz). Luego configuramos los pines PB8 y PB9 para que actúen como líneas SCL y SDA respectivamente. La rutina principal es muy simple: almacena la cadena "¡We love STM32!" en la memoria 0x1AAA. la cadena se lee de nuevo desde la EEPROM y se compara con la original. Necesitamos para explicar por qué estamos almacenando y leyendo un buffer con una longitud igual a strlen(wmsg) +1. Este porque las rutinas C strlen() devuelven la longitud de la cadena saltándose el terminador de la cadena char ('\0'). Sin almacenar este carácter, y luego leerlo de nuevo de la EEPROM, el strcmp() en la línea 26 no sería capaz de calcular la longitud exacta de la cuerda. 63 HAL_StatusTypeDef Read_From_24LCxx(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t
64 MemAddress, uint8_t pData, uint16_t len) { 65 HAL_StatusTypeDef returnValue; 66 uint8_t addr[ 2 ]; 67 68 _/ We compute the MSB and LSB parts of the memory address /_ 69 addr[ 0 ] = ( uint8_t ) ((MemAddress & 0xFF00) >> 8); 70 addr[ 1 ] = ( uint8_t ) (MemAddress & 0xFF); 71 72 _/ First we send the memory location address where start reading data /_ 73 returnValue = HAL_I2C_Master_Transmit(hi2c, DevAddress, addr, 2 , HAL_MAX_DELAY); 74 if (returnValue != HAL_OK) 75 return returnValue; 76 77 _/ Next we can retrieve the data from EEPROM */_ 78 returnValue = HAL_I2C_Master_Receive(hi2c, DevAddress, pData, len, HAL_MAX_DELAY); 79 80 return returnValue; 81 } 82 83 HAL_StatusTypeDef Write_To_24LCxx(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t M
84 emAddress, uint8_t pData, uint16_t len) { 85 HAL_StatusTypeDef returnValue; 86 uint8_t data; 87 88 _/ First we allocate a temporary buffer to store the destination memory_ 89 _ address and the data to store /_ 90 data = ( uint8_t )malloc( sizeof ( uint8_t )(len+2)); 91 92 _/ We compute the MSB and LSB parts of the memory address /_ 93 data[ 0 ] = ( uint8_t ) ((MemAddress & 0xFF00) >> 8); 94 data[ 1 ] = ( uint8_t ) (MemAddress & 0xFF); 95 96 _/ And copy the content of the pData array in the temporary buffer /_ 97 memcpy(data+2, pData, len); 98 99 _/ We are now ready to transfer the buffer over the I2C bus */_ 100 returnValue = HAL_I2C_Master_Transmit(hi2c, DevAddress, data, len + 2, HAL_MAX_DELAY); 101 if (returnValue != HAL_OK)