STM32 software I2C lectura de datos del sensor de temperatura y humedad AM2320

STM32 lee datos del sensor de temperatura y humedad AM2320 mediante I2C por software y los muestra en una pantalla OLED de 0,96 pulgadas.

Utilizo la placa STM32F103C8T6 y el programa está escrito con la biblioteca estándar de ST.

STM32 lee el sensor SHTC3 por I2C hardware: https://blog.zeruns.com/archives/692.html
STM32 lee datos del sensor AHT10: https://blog.zeruns.com/archives/693.html

Grupo de discusión técnica de electrónía/microcontroladores: 2169025065

Resultado visual

Introducción al protocolo I2C

El protocolo de comunicación I2C (Inter-Integrated Circuit) fue desarrollado por Philips. Gracias a su bajo número de pines, implementación hardware sencilla, gran escalabilidad y a que no requiere circuitos externos de transcepción como USART o CAN (esos chips de conversión de niveles), hoy se emplea ampliamente para la comunicación entre múltiples circuitos integrados dentro de un sistema.

I2C solo dispone de un bus de datos SDA (Serial Data Line), por lo que los bits se envían de uno en uno; es una comunicación serie y semidúplex.

Comunicación semidúplex: permite transmisión bidireccional, pero no simultánea; debe alternarse. Se puede ver como una comunicación simplex con dirección conmutable: en un instante solo puede haber una dirección de tráfico y basta con una línea de datos.
Dividimos el protocolo I2C en capa física y capa de protocolo: la física define características mecánicas y eléctricas (hardware), garantizando la transmisión primaria por el medio; la de protocolo regula la lógica de comunicación, unificando los formatos de empaquetado y desempaquetado entre emisor y receptor (software).

Capa física de I2C

Conexión típica entre dispositivos I2C

  1. Es un bus multi-dispositivo. “Bus” significa que varios dispositivos comparten la misma línea de señales. En un bus I2C pueden conectarse varios maestros y varios esclavos.
  2. Solo se usan dos líneas: SDA (datos) y SCL (reloj). SDA transporta la información y SCL sincroniza la transmisión.
  3. El bus se conecta a la alimentación mediante resistencias de pull-up. Cuando un dispositivo I2C está inactivo pasa a estado de alta impedancia; si todos están inactivos, las resistencias de pull-up llevan el bus a nivel alto.

Durante la comunicación I2C los pines GPIO del microcontrolador deben configurarse como salida open-drain; de lo contrario puede producirse un cortocircuito.

Más información sobre I2C en STM32: https://url.zeruns.com/JC0Ah
Aquí no lo detallo.

Sensor AM2320

Introducción

AM2320 es un sensor digital de temperatura y humedad con salida calibrada. Emplea tecnología específica de adquisición para alta fiabilidad y estabilidad a largo plazo. Integra un elemento capacitivo para humedad, un sensor de temperatura de alta precisión y un microprocesador de alto rendimiento. Ofrece excelente calidad, respuesta ultrarrápida, fuerte inmunidad a interferencias y gran relación calidad-precio. Permite comunicación Single-Bus o I2C estándar. La interfaz Single-Bus simplifica la integración; su pequeño tamaño y bajo consumo permiten distancias de más de 20 m. En modo I2C se conecta directamente al bus sin cableado adicional. Ambos modos entregan valores de humedad y temperatura compensados, junto con CRC; no requiere cálculos posteriores ni compensación de humedad respecto a temperatura. El usuario puede cambiar libremente entre modos. Disponible en encapsulado de 4 pines; se ofrecen versiones especiales bajo pedido.

Descarga del datasheet AM2320: https://url.zeruns.com/74o6F



Del datasheet se extrae:

  • Rango de temperatura: –40 °C ∼ 80 °C
  • Error de temperatura: ±0,5 °C
  • Rango de humedad: 0 % ∼ 99,9 %
  • Error de humedad: ±3 %
  • Voltaje de trabajo: 3,1 V ∼ 5,5 V
  • Medios de comunicación: I2C o Single-Bus
  • Frecuencia de reloj: ≤ 100 kHz

Dirección del dispositivo y comandos de lectura/escritura

En la práctica, la dirección de AM2320 se combina con el bit de dirección/lectura en un solo byte: los 7 bits superiores son la dirección del dispositivo; el bit 0 indica operación (0 = escritura, 1 = lectura).

Para escribir: tras el START se envía “1011 1000” = 0xB8.
Para leer: se envía “1011 1001” = 0xB9.
En resumen: 0xB8 = escritura, 0xB9 = lectura.

Lectura de datos de temperatura y humedad




Un ciclo de lectura consta de:

  1. Despertar al sensor
  2. Enviar orden de lectura
  3. Leer datos devueltos

Resumen:

  1. Despertar: START + 0xB8 + espera (> 800 µs) + STOP
  2. Orden de lectura: START + 0xB8 (SLA) + 0x03 (código función) + 0x00 (dirección inicial) + 0x04 (nº de registros) + STOP
  3. Recibir datos: enviar 0xB9 y leer 8 bytes: longitud + Hum_H + Hum_L + Temp_H + Temp_L + CRC_L + CRC_H
  4. Procesar los datos recibidos.

Cálculo de los datos

Según el datasheet:


Ejemplo:
Humedad leída = 0x01F4 → 500 decimal → 500 / 10 = 50,0 %
Temperatura leída = 0x00FA → 250 decimal → 250 / 10 = 25,0 °C

Componentes necesarios

  • Placa mínima STM32: [https://s.click.taobao.com/M2LgRPu](https://s.click.taobao.com/t?e=m%3D2%26s%3DIlfejTURsNccQipKwQzePOeEDrYVVa64Dne87AjQPk9yINtkUhsv0F7wsYq2ONVgnPBttjfRAZZwhoUbHqff68jZ753```c
    #include “stm32f10x.h” // Device header
    #include “Delay.h”
    #include “OLED.h”
    #include “AM2320.h”
    #include “IWDG.h”

int main(void)
{
IWDG_Configuration(); // Inicializar el perro guardian
AM2320_I2C_Init();
OLED_Init();

OLED_ShowString(1, 1, "T:");
OLED_ShowString(2, 1, "H:");

uint16_t i = 0;
uint16_t err_count = 0;

while (1)
{
	OLED_ShowNum(4, 1, i, 5);
	float Temp, Hum; // Declarar variables para almacenar datos de temperatura y humedad

	if (ReadAM2320(&Hum, &Temp)) // Leer datos de temperatura y humedad
	{
		if (Temp >= 0)
		{
			char String[10];
			sprintf(String, "+%.2fC", Temp); // Formatear cadena de salida a variable string
			OLED_ShowString(1, 3, String);	 // Mostrar temperatura

			sprintf(String, " %.2f%%", Hum); // Formatear cadena de salida a variable string
			OLED_ShowString(2, 3, String);	 // Mostrar humedad
		}
		else
		{
			char String[10];
			sprintf(String, "-%.2fC", Temp); // Formatear cadena de salida a variable string
			OLED_ShowString(1, 3, String);	 // Mostrar temperatura

			sprintf(String, " %.2f%%", Hum); // Formatear cadena de salida a variable string
			OLED_ShowString(2, 3, String);	 // Mostrar humedad
		}
	}
	else
	{
		err_count++;
		OLED_ShowNum(3, 1, err_count, 5); // Mostrar contador de errores
	}
	Delay_ms(100);
	i++;
	if (i >= 99999)
		i = 0;
	if (err_count >= 99999)
		err_count = 0;
	IWDG_FeedDog(); // Alimentar al perro (perro guardian, si no se alimenta en más de 1 segundo se reinicia automáticamente)
}
// blog.zeruns.com

}


### AM2320.c

```c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

/*
Autor del blog: https://blog.zeruns.com
WeChat oficial: zeruns-gzh
Página de inicio de Bilibili: https://space.bilibili.com/8320520
*/

/*Dirección AM2320*/
#define AM2320_ADDRESS 0xB8

/*Configuración de pines*/
#define AM2320_SCL GPIO_Pin_12
#define AM2320_SDA GPIO_Pin_13
#define AM2320_W_SCL(x) GPIO_WriteBit(GPIOB, AM2320_SCL, (BitAction)(x))
#define AM2320_W_SDA(x) GPIO_WriteBit(GPIOB, AM2320_SDA, (BitAction)(x))
#define AM2320_R_SDA() GPIO_ReadInputDataBit(GPIOB, AM2320_SDA)
#define AM2320_R_SCL() GPIO_ReadInputDataBit(GPIOB, AM2320_SCL)
/*Cuando el GPIO de STM32 está configurado en modo de salida de drenaje abierto, aún puede leer
el registro de datos de entrada del GPIO para obtener el nivel de entrada externo del pin, es decir, tiene la función del modo de entrada flotante*/

/**
 * @brief  Cálculo de verificación CRC
 * @param  *ptr Datos de bytes a calcular (almacenados como variable de array)
 * @param  len  Cantidad de bytes a calcular (longitud del array)
 * @retval Código de verificación CRC
 */
unsigned short CRC16(unsigned char *ptr, unsigned char len)
{
	unsigned short crc = 0xFFFF;
	unsigned char i;
	while (len--)
	{
		crc ^= *ptr++;
		for (i = 0; i < 8; i++)
		{
			if (crc & 0x01)
			{
				crc >>= 1;
				crc ^= 0xA001;
			}
			else
			{
				crc >>= 1;
			}
		}
	}
	return crc;
}

/**
 * @brief  Inicio I2C
 * @param  Ninguno
 * @retval Ninguno
 */
void AM2320_I2C_Start(void)
{
	AM2320_W_SDA(1);
	Delay_us(2); // Retraso de 2 microsegundos
	AM2320_W_SCL(1);
	Delay_us(4);
	AM2320_W_SDA(0);
	Delay_us(3);
	AM2320_W_SCL(0);
	Delay_us(5);
}

/**
 * @brief  Parada I2C
 * @param  Ninguno
 * @retval Ninguno
 */
void AM2320_I2C_Stop(void)
{
	AM2320_W_SDA(0);
	Delay_us(3);
	AM2320_W_SCL(1);
	Delay_us(4);
	AM2320_W_SDA(1);
	Delay_us(4);
}

/**
 * @brief  I2C envía un byte
 * @param  Byte Byte a enviar
 * @retval Ninguno
 */
void AM2320_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		AM2320_W_SDA((Byte << i) & 0x80);
		AM2320_W_SCL(1);
		Delay_us(4);
		AM2320_W_SCL(0);
		Delay_us(5);
	}
	AM2320_W_SDA(1); // Liberar bus SDA
}

/**
 * @brief  Esperar señal de respuesta
 * @param  Ninguno
 * @retval 1-Señal de no respuesta, 0-Señal de respuesta
 */
uint8_t WaitAck(void)
{
	uint8_t ret;

	AM2320_W_SCL(1);
	Delay_us(4);
	if (AM2320_R_SDA())
	{
		ret = 1;
	}
	else
	{
		ret = 0;
	}
	AM2320_W_SCL(0);
	Delay_us(5);
	return ret;
}

/**
 * @brief  I2C lee un byte
 * @param  NACK 1-Señal de no respuesta, 0-Señal de respuesta
 * @retval Datos del byte leído
 */
uint8_t AM2320_I2C_ReadByte(uint8_t NACK)
{
	uint8_t i, Byte = 0;
	AM2320_W_SDA(1); // Liberar bus SDA
	for (i = 0; i < 8; i++)
	{
		AM2320_W_SCL(1);
		Delay_us(4);
		Byte = Byte | (AM2320_R_SDA() << (7 - i));
		AM2320_W_SCL(0);
		Delay_us(5);
	}
	AM2320_W_SDA(NACK); // Enviar señal de respuesta/no respuesta
	AM2320_W_SCL(1);
	Delay_us(4);
	AM2320_W_SCL(0);
	Delay_us(5);
	AM2320_W_SDA(1); // Liberar bus SDA
	return Byte;
}

/*Despertar sensor*/
void AM2320_Wake(void)
{
	AM2320_I2C_Start();
	AM2320_I2C_SendByte(AM2320_ADDRESS);
	WaitAck();
	Delay_us(1000); // Retraso de 1000 microsegundos
	AM2320_I2C_Stop();
}

/*Inicialización de pines*/
void AM2320_I2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Habilitar reloj GPIOB

	GPIO_InitTypeDef GPIO_InitStructure;				 // Definir estructura para configurar GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // Modo de salida de drenaje abierto
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = AM2320_SCL;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = AM2320_SDA;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	AM2320_W_SCL(1);
	AM2320_W_SDA(1);
	AM2320_Wake(); // Despertar sensor
}

/**
 * @brief  Leer datos AM2320
 * @param  *Hum Humedad
 * @param  *Temp Temperatura
 * @retval 1 - Lectura exitosa; 0 - Lectura fallida
 */
uint8_t ReadAM2320(float *Hum, float *Temp)
{
	uint8_t Data[8];

	AM2320_I2C_Start(); // Enviar señal de inicio
	AM2320_I2C_SendByte(AM2320_ADDRESS);
	if (WaitAck()) // Verificar señal de respuesta
	{
		AM2320_I2C_Stop(); // Enviar señal de parada
		Delay_us(50);
		// Intentar leer nuevamente
		AM2320_I2C_Start(); // Enviar señal de inicio
		AM2320_I2C_SendByte(AM2320_ADDRESS);
		if (WaitAck()) // Verificar señal de respuesta
		{
			Delay_us(20);
			AM2320_I2C_Stop(); // Enviar señal de parada
			return 0;
		}
		else
		{
			Delay_us(20);// Aquí AM2320 baja SCL por un tiempo que causa errores en los datos enviados, así que se retrasa 20 microsegundos hasta que AM2320 libere SCL y continúe
			AM2320_I2C_SendByte(0x03); // Enviar código de función
			WaitAck();				   // Esperar señal de respuesta
			AM2320_I2C_SendByte(0x00); // Enviar dirección inicial del registro a leer
			WaitAck();				   // Esperar señal de respuesta
			AM2320_I2C_SendByte(0x04); // Enviar longitud del registro a leer
			WaitAck();				   // Esperar señal de respuesta
			Delay_us(20);// Aquí AM2320 baja SCL por un tiempo que causa fallo en el envío de la señal de parada, así que se retrasa 20 microsegundos hasta que AM2320 libere SCL y continúe
			AM2320_I2C_Stop();		   // Enviar señal de parada
		}
	}
	else
	{
		Delay_us(20);// Aquí AM2320 baja SCL por un tiempo que causa errores en los datos enviados, así que se retrasa 20 microsegundos hasta que AM2320 libere SCL y continúe
		AM2320_I2C_SendByte(0x03); // Enviar código de función
		WaitAck();				   // Esperar señal de respuesta
		AM2320_I2C_SendByte(0x00); // Enviar dirección inicial del registro a leer
		WaitAck();				   // Esperar señal de respuesta
		AM2320_I2C_SendByte(0x04); // Enviar longitud del registro a leer
		WaitAck();				   // Esperar señal de respuesta
		Delay_us(20);// Aquí AM2320 baja SCL por un tiempo que causa fallo en el envío de la señal de parada, así que se retrasa 20 microsegundos hasta que AM2320 libere SCL y continúe
		AM2320_I2C_Stop();		   // Enviar señal de parada
	}

	Delay_ms(2); // Retraso de 2 milisegundos

	AM2320_I2C_Start();
	AM2320_I2C_SendByte(AM2320_ADDRESS | 0x01); // Enviar instrucción de lectura
	WaitAck();
	Delay_us(35);
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		if (i != 7)
		{
			Data[i] = AM2320_I2C_ReadByte(0);
		}
		else
		{
			Data[i] = AM2320_I2C_ReadByte(1); // Enviar señal de no respuesta al leer el último byte
		}
	}
	AM2320_I2C_Stop();

	if (CRC16(Data, 6) == (Data[6] | (Data[7] << 8))) // Verificar datos
	{
		*Hum = ((((uint16_t)Data[2]) << 8) | Data[3]) / 10.0; // Calcular datos de humedad
		if (Data[4] >> 7)									  // Verificar si el valor de temperatura es negativo
		{
			*Temp = ((((uint16_t)(Data[4] && 0x7F) << 8)) | Data[5]) / -10.0; // Calcular temperatura negativa
		}
		else
		{
			*Temp = ((((uint16_t)Data[4]) << 8) | Data[5]) / 10.0; // Calcular temperatura positiva
		}
		return 1;
	}
	return 0;
}
```### OLED.c

```c
#include "stm32f10x.h"
#include "OLED_Font.h"

/*Configuración de pines*/
#define OLED_SCL GPIO_Pin_12
#define OLED_SDA GPIO_Pin_13
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, OLED_SCL, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, OLED_SDA, (BitAction)(x))

/*Inicialización de pines*/
void OLED_I2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = OLED_SCL;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = OLED_SDA;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
 * @brief  Inicio I2C
 * @param  Ninguno
 * @retval Ninguno
 */
void OLED_I2C_Start(void)
{
	OLED_W_SDA(1);
	OLED_W_SCL(1);
	OLED_W_SDA(0);
	OLED_W_SCL(0);
}

/**
 * @brief  Parada I2C
 * @param  Ninguno
 * @retval Ninguno
 */
void OLED_I2C_Stop(void)
{
	OLED_W_SDA(0);
	OLED_W_SCL(1);
	OLED_W_SDA(1);
}

/**
 * @brief  I2C envía un byte
 * @param  Byte byte a enviar
 * @retval Ninguno
 */
void OLED_I2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		OLED_W_SDA(Byte & (0x80 >> i));
		OLED_W_SCL(1);
		OLED_W_SCL(0);
	}
	OLED_W_SDA(1); //liberar el bus SDA
	OLED_W_SCL(1); //pulso de reloj adicional, sin procesar señal ACK
	OLED_W_SCL(0);
}

/**
 * @brief  OLED escribe comando
 * @param  Command comando a escribir
 * @retval Ninguno
 */
void OLED_WriteCommand(uint8_t Command)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78); //dirección del esclavo
	OLED_I2C_SendByte(0x00); //escribir comando
	OLED_I2C_SendByte(Command);
	OLED_I2C_Stop();
}

/**
 * @brief  OLED escribe dato
 * @param  Data dato a escribir
 * @retval Ninguno
 */
void OLED_WriteData(uint8_t Data)
{
	OLED_I2C_Start();
	OLED_I2C_SendByte(0x78); //dirección del esclavo
	OLED_I2C_SendByte(0x40); //escribir dato
	OLED_I2C_SendByte(Data);
	OLED_I2C_Stop();
}

/**
 * @brief  OLED establece posición del cursor
 * @param  Y coordenada hacia abajo desde la esquina superior izquierda, rango: 0~7
 * @param  X coordenada hacia la derecha desde la esquina superior izquierda, rango: 0~127
 * @retval Ninguno
 */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_WriteCommand(0xB0 | Y);			 //establecer posición Y
	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //establecer 4 bits bajos de X
	OLED_WriteCommand(0x00 | (X & 0x0F));		 //establecer 4 bits altos de X
}

/**
 * @brief  OLED limpia pantalla
 * @param  Ninguno
 * @retval Ninguno
 */
void OLED_Clear(void)
{
	uint8_t i, j;
	for (j = 0; j < 8; j++)
	{
		OLED_SetCursor(j, 0);
		for (i = 0; i < 128; i++)
		{
			OLED_WriteData(0x00);
		}
	}
}

/**
 * @brief  OLED limpieza parcial
 * @param  Line posición de línea, rango: 1~4
 * @param  start posición inicial de columna, rango: 1~16
 * @param  end posición final de columna, rango: 1~16
 * @retval Ninguno
 */
void OLED_Clear_Part(uint8_t Line, uint8_t start, uint8_t end)
{
	uint8_t i, Column;
	for (Column = start; Column <= end; Column++)
	{
		OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //establecer cursor en mitad superior
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00); //mostrar mitad superior
		}
		OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //establecer cursor en mitad inferior
		for (i = 0; i < 8; i++)
		{
			OLED_WriteData(0x00); //mostrar mitad inferior
		}
	}
}

/**
 * @brief  OLED muestra un carácter
 * @param  Line posición de línea, rango: 1~4
 * @param  Column posición de columna, rango: 1~16
 * @param  Char carácter a mostrar, rango: caracteres ASCII visibles
 * @retval Ninguno
 */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
	uint8_t i;
	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //establecer cursor en mitad superior
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i]); //mostrar mitad superior
	}
	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //establecer cursor en mitad inferior
	for (i = 0; i < 8; i++)
	{
		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //mostrar mitad inferior
	}
}

/**
 * @brief  OLED muestra cadena
 * @param  Line posición inicial de línea, rango: 1~4
 * @param  Column posición inicial de columna, rango: 1~16
 * @param  String cadena a mostrar, rango: caracteres ASCII visibles
 * @retval Ninguno
 */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i++)
	{
		OLED_ShowChar(Line, Column + i, String[i]);
	}
}

/**
 * @brief  Función potencia OLED
 * @retval Devuelve X elevado a Y
 */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}

/**
 * @brief  OLED muestra número (decimal, positivo)
 * @param  Line posición inicial de línea, rango: 1~4
 * @param  Column posición inicial de columna, rango: 1~16
 * @param  Number número a mostrar, rango: 0~4294967295
 * @param  Length longitud del número a mostrar, rango: 1~10
 * @retval Ninguno
 */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
 * @brief  OLED muestra número (decimal, con signo)
 * @param  Line posición inicial de línea, rango: 1~4
 * @param  Column posición inicial de columna, rango: 1~16
 * @param  Number número a mostrar, rango: -2147483648~2147483647
 * @param  Length longitud del número a mostrar, rango: 1~10
 * @retval Ninguno
 */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
	uint8_t i;
	uint32_t Number1;
	if (Number >= 0)
	{
		OLED_ShowChar(Line, Column, '+');
		Number1 = Number;
	}
	else
	{
		OLED_ShowChar(Line, Column, '-');
		Number1 = -Number;
	}
	for (i = 0; i < Length; i++)
	{
		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/**
 * @brief  OLED muestra número (hexadecimal, positivo)
 * @param  Line posición inicial de línea, rango: 1~4
 * @param  Column posición inicial de columna, rango: 1~16
 * @param  Number número a mostrar, rango: 0~0xFFFFFFFF
 * @param  Length longitud del número a mostrar, rango: 1~8
 * @retval Ninguno
 */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i, SingleNumber;
	for (i = 0; i < Length; i++)
	{
		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
		if (SingleNumber < 10)
		{
			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
		}
		else
		{
			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
		}
	}
}

/**
 * @brief  OLED muestra número (binario, positivo)
 * @param  Line posición inicial de línea, rango: 1~4
 * @param  Column posición inicial de columna, rango: 1~16
 * @param  Number número a mostrar, rango: 0~1111 1111 1111 1111
 * @param  Length longitud del número a mostrar, rango: 1~16
 * @retval Ninguno
 */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i++)
	{
		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
	}
}

/**
 * @brief  OLED inicialización
 * @param  Ninguno
 * @retval Ninguno
 */
void OLED_Init(void)
{
	uint32_t i, j;

	for (i = 0; i < 1000; i++) //retardo al encender
	{
		for (j = 0; j < 1000; j++)
			;
	}

	OLED_I2C_Init(); //inicializar puertos

	OLED_WriteCommand(0xAE); //apagar display

	OLED_WriteCommand(0xD5); //establecer divisor de reloj/frecuencia del oscilador
	OLED_WriteCommand(0x80);

	OLED_WriteCommand(0xA8); //establecer multiplex ratio
	OLED_WriteCommand(0x3F);

	OLED_WriteCommand(0xD3); //establecer offset de display
	OLED_WriteCommand(0x00);

	OLED_WriteCommand(0x40); //establecer línea de inicio del display

	OLED_WriteCommand(0xA1); //establecer orientación izquierda-derecha, 0xA1 normal 0xA0 invertido

	OLED_WriteCommand(0xC8); //establecer orientación arriba-abajo, 0xC8 normal 0xC0 invertido

	OLED_WriteCommand(0xDA); //configuración de pines COM
	OLED_WriteCommand(0x12);

	OLED_WriteCommand(0x81); //establecer control de contraste
	OLED_WriteCommand(0xCF);

	OLED_WriteCommand(0xD9); //establecer período de precarga
	OLED_WriteCommand(0xF1);

	OLED_WriteCommand(0xDB); //establecer nivel VCOMH deselect
	OLED_WriteCommand(0x30);

	OLED_WriteCommand(0xA4); //activar display completo

	OLED_WriteCommand(0xA6); //establecer display normal/invertido

	OLED_WriteCommand(0x8D); //establecer charge pump
	OLED_WriteCommand(0x14);

	OLED_WriteCommand(0xAF); //encender display

	OLED_Clear(); // limpiar OLED
}

Diagrama de temporización

Diagrama de temporización real del envío de instrucciones y lectura de datos del AM2320.

Recomendaciones de lectura

1 me gusta