Implementación de Firmware para Sensor HS3003 y Pantalla OLED

Control de Sensores de Humedad y Temperatura HS3003 con Pantalla OLED

Este artículo detalla la implementación del firmware para interactuar con un sensor digital de humedad y temperatura HS3003, y cómo visualizar los datos obtenidos en una pantalla OLED de 0.96 pulgadas. El código se desarrolla utilizando funciones de una biblitoeca estándar de microcontroladores, empleando el protocolo I2C para la comunicación con ambos dispositivos periféricos.

Sensor de Humedad y Temperatura HS3003

Descripción General

El HS3003 es un sensor digital diseñado para la medición precisa de la humedad relativa y la temperatura. Ofrece valores completamente calibrados de estos parámetros a través de una interfaz I2C estándar, incorporando calibración y compensación internas que eliminan la necesidad de ajustes adicionales por parte del usuario. Sus características incluyen alta precisión, un tiempo de respuesta rápido y una tecnología de sensor MEMS con protección propietaria que garantiza una alta fiabilidad y estabilidad a largo plazo.

Especificaciones de Operación

  • Los sensores de la serie HS300x están optimizados para operar de manera óptima en rangos de temperatura de 10°C a 50°C y humedad relativa del 20% al 80%. Una operación prolongada fuera de estas condiciones, especialmente a niveles de humedad elevados, puede provocar un desvío temporal en las lceturas. En la mayoría de los casos, este desvío es temporal y se corrige gradualmente una vez que el sensor regresa a sus condiciones ambientales normales.
  • La dirección I2C de 7 bits por defecto para los dispositivos HS300x es 0x44. El sensor solo responderá a esta dirección.

Inicio de Funcionamiento del Sensor

Cuando el HS3003 sale de fábrica, está configurado para operar en un modo de bajo consumo (sleep mode). En este modo, el sensor espera una orden del microcontrolador anfitrión antes de iniciar una medición. Solo realiza una conversión al recibir el comando de Solicitud de Medición (MR), permaneciendo en un estado de bajo consumo el resto del tiempo. Para iniciar una medición, se envía una transacción I2C con la dirección del dispositivo y el bit de escritura, lo que actúa como el comando de inicio de medición.


/**
 * @brief Inicia la comunicación y solicita una medición al sensor HS3003.
 *        Envía la dirección del dispositivo con el bit de escritura para iniciar el muestreo.
 * @param direccion_i2c_escritura Dirección I2C de 8 bits (7 bits de dirección + R/W bit en 0).
 * @return 0 si la operación es exitosa, -1 si hay un error (ej. falta de ACK).
 */
int8_t HS3003_SolicitarMedicion(uint8_t direccion_i2c_escritura)
{   
    // Estas funciones (I2C_Master_Start, I2C_Master_SendByte, etc.)
    // deben ser implementadas en la capa de abstracción de I2C (software o hardware)
    // Se asume que I2C_Master_Start(), I2C_Master_SendByte(), I2C_Master_WaitAck(), I2C_Master_Stop() están definidas externamente.
    I2C_Master_Start();  	
    I2C_Master_SendByte(direccion_i2c_escritura);  
    if(I2C_Master_WaitAck()) {
        I2C_Master_Stop();
        return -1; // Error: El dispositivo no envió ACK
    }
    I2C_Master_Stop();
    return 0; // Solicitud de medición enviada con éxito
}

Para activar el sensor y soilcitar una medición, se invoca HS3003_SolicitarMedicion(0x88); (donde 0x88 es la dirección de 7 bits 0x44 desplazada un bit a la izquierda y con el bit de escritura/lectura en 0).

Proceso de Lectura de Datos

Después de solicitar una medición, el microcontrolador debe esperar un tiempo suficiente para que el sensor complete la conversión. Para una precisión de 14 bits (la configuración por defecto), se requiere aproximadamente 33 ms. Tras este retardo, se inicia una transacción de lectura I2C. Los datos devueltos constan de 4 bytes. Los dos bits más significativos (MSB) del primer byte indican el estado de los datos: '0' para datos válidos y completos, '1' para datos obsoletos o no finalizados. Los siguientes 14 bits representan la humedad y los últimos 14 bits la temperatura.


// Estructura para almacenar los valores de humedad y temperatura ya convertidos
typedef struct {
    double humedad_relativa;  // Valor en %HR
    double temperatura_celsius; // Valor en °C
} s_SensorData_HS300x;

// Definiciones para el procesamiento de datos brutos del HS300x
#define HS300X_ADDR_7BIT           (0x44U)
#define HS300X_STATUS_VALID        (0x00U) // Estado: Datos válidos
#define HS300X_STATUS_STALE        (0x01U) // Estado: Datos obsoletos/no válidos

// Máscaras y posiciones para la extracción de bits de un valor de 32 bits
#define HS300X_MASK_STATUS         (0xC0000000U)
#define HS300X_POS_STATUS          (30U)
#define HS300X_MASK_HUMIDITY       (0x3FFF0000U)
#define HS300X_POS_HUMIDITY        (16U)
#define HS300X_MASK_TEMPERATURE    (0x0000FFFCU)
#define HS300X_POS_TEMPERATURE     (2U)

// Factor de escala para la conversión de datos (2^14 - 1 = 16383)
#define HS300X_CALC_FACTOR         (16383U)

/**
 * @brief Convierte los datos brutos recibidos del sensor HS300x a valores de humedad y temperatura.
 * @param raw_data Valor de 32 bits que contiene los datos brutos del sensor.
 * @param converted_result Puntero a la estructura s_SensorData_HS300x para almacenar el resultado.
 * @return 1 si la conversión es exitosa (datos válidos), 0 en caso contrario (datos no válidos).
 */
static uint8_t HS300x_ConvertirValores(uint32_t raw_data, s_SensorData_HS300x *converted_result)
{
    uint32_t humedad_cruda;
    uint32_t temperatura_cruda;
    uint32_t estado_lectura;
    
    // Extraer el bit de estado (bits 31 y 30)
    estado_lectura = (raw_data & HS300X_MASK_STATUS) >> HS300X_POS_STATUS;

    if (estado_lectura == HS300X_STATUS_VALID)
    {
        // Extraer los valores brutos de humedad (bits 29 a 16) y temperatura (bits 13 a 2)
        humedad_cruda = (raw_data & HS300X_MASK_HUMIDITY) >> HS300X_POS_HUMIDITY;
        temperatura_cruda = (raw_data & HS300X_MASK_TEMPERATURE) >> HS300X_POS_TEMPERATURE;

        // Aplicar fórmulas de conversión basadas en la hoja de datos
        // Humedad: (humedad_cruda / (2^14 - 1)) * 100%
        converted_result->humedad_relativa = (double)humedad_cruda / (double)(HS300X_CALC_FACTOR) * 100.0;
        // Temperatura: (temperatura_cruda / (2^14 - 1)) * 165 - 40 °C
        converted_result->temperatura_celsius = (double)temperatura_cruda / (double)(HS300X_CALC_FACTOR) * 165.0 - 40.0;
        
        return 1; // Conversión exitosa
    }
    else
    {
        return 0; // Datos inválidos o no listos
    }
}

/**
 * @brief Realiza la lectura completa de datos del sensor HS300x y los procesa.
 * @return Una estructura s_SensorData_HS300x con los valores de humedad y temperatura.
 */
s_SensorData_HS300x HS300x_ObtenerMediciones(void)
{
    uint8_t buffer_rx[4]; // Buffer para los 4 bytes de datos recibidos
    uint32_t datos_concatenados = 0;
    s_SensorData_HS300x mediciones_procesadas;
    
    // Se asume la existencia de una función de lectura I2C de bajo nivel:
    // HS3003_IIC_Read_Data(direccion_lectura_8bit, buffer_destino, num_bytes)
    // 0x89 es la dirección de 7 bits 0x44 con el bit de lectura (0x44 << 1 | 1)
    HS3003_IIC_Read_Data(0x89, buffer_rx, 4);

    // Concatenar los 4 bytes recibidos en un único valor de 32 bits
    datos_concatenados = ((uint32_t)buffer_rx[0] << 24U) | 
                         ((uint32_t)buffer_rx[1] << 16U) | 
                         ((uint32_t)buffer_rx[2] << 8U)  | 
                         ((uint32_t)buffer_rx[3]);
 
    // Convertir y almacenar los datos
    (void)HS300x_ConvertirValores(datos_concatenados, &mediciones_procesadas);

    return mediciones_procesadas;
}

Una función para gestionar el ciclo de lectura y visualización podría ser la siguiente:


// Variable global para almacenar las mediciones actuales del sensor
extern s_SensorData_HS300x g_mediciones_ht; 
extern void ActualizarPantallaOLED(void); // Prototipo de la función de visualización
extern void delay_ms(uint32_t ms); // Se asume la existencia de una función de retardo

/**
 * @brief Ejecuta un ciclo completo de adquisición de datos del HS3003 y actualización del OLED.
 */
void Ciclo_Adquisicion_HS3003_OLED(void)
{
    HS3003_SolicitarMedicion(0x88); // Inicia una nueva medición
    delay_ms(40);                   // Espera a que el sensor complete la conversión (mínimo 33ms)
    g_mediciones_ht = HS300x_ObtenerMediciones(); // Lee y procesa los datos
    ActualizarPantallaOLED();       // Muestra los datos actualizados en el OLED
}

Control de Pantalla OLED

Para la visualización, se emplea una pantalla OLED de 0.96 pulgadas que utiliza una interfaz I2C de 4 hilos. A diferencia de una posible implementación I2C por software para el sensor HS3003, la pantalla OLED se controla mediante el periférico I2C de hardware del microcontrolador (en este caso, I2C1 de un STM32F103C8T6) para optimizar el rendimiento y la estabilidad.

Configuración del I2C de Hardware para OLED

La inicialización del I2C de hardware implica configurar los pines GPIO correspondientes y el módulo I2C del microcontrolador. Para el STM32F103C8T6, los pines PB6 (SCL) y PB7 (SDA) se configuran en modo de salida Open-Drain con función alternativa, un requisito fundamental para el protocolo I2C.


#include "oled_driver.h" // Prototipos para funciones del OLED
#include "utility_delay.h" // Se asume la existencia de esta librería para delay_ms
#include "font_data.h"     // Contiene las matrices de datos de fuentes (ej. F6x8, F8X16, F16x16)
#include <stdio.h>
#include <string.h>

// Dirección I2C del esclavo OLED (por defecto 0x78)
#define OLED_DIRECCION_ESCLAVO 0x78

// Variable global para las mediciones del sensor HS3003
s_SensorData_HS300x g_mediciones_ht; 

/**
 * @brief Configura el periférico I2C1 de hardware y los pines GPIO asociados para la pantalla OLED.
 */
void ConfigurarI2C_OLED(void)
{
    I2C_InitTypeDef  parametros_i2c;
    GPIO_InitTypeDef parametros_gpio; 

    // Habilitar los relojes de los periféricos
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // Configuración de los pines GPIO PB6 (SCL) y PB7 (SDA)
    parametros_gpio.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
    parametros_gpio.GPIO_Speed = GPIO_Speed_50MHz;
    parametros_gpio.GPIO_Mode = GPIO_Mode_AF_OD; // I2C requiere salida Open-Drain en modo función alternativa
    GPIO_Init(GPIOB, &parametros_gpio);

    // Reinicializar el módulo I2C1
    I2C_DeInit(I2C1);

    // Configuración del I2C1 como maestro
    parametros_i2c.I2C_Mode = I2C_Mode_I2C;
    parametros_i2c.I2C_DutyCycle = I2C_DutyCycle_2; // Ciclo de trabajo del reloj (50%)
    parametros_i2c.I2C_OwnAddress1 = 0x30; // Dirección de 7 bits del maestro (arbitraria, no usada por el esclavo)
    parametros_i2c.I2C_Ack = I2C_Ack_Enable; // Habilitar ACK
    parametros_i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    parametros_i2c.I2C_ClockSpeed = 400000; // Frecuencia del reloj I2C: 400 KHz (Modo Rápido)

    // Habilitar e inicializar el periférico I2C1
    I2C_Cmd(I2C1, ENABLE);
    I2C_Init(I2C1, &parametros_i2c);
}

/**
 * @brief Envía un byte de datos al OLED a través de I2C.
 * @param control_byte Byte de control (0x00 para comando, 0x40 para datos).
 * @param data_to_send Byte de datos a enviar.
 */
void EscribirByteOLED_I2C(uint8_t control_byte, uint8_t data_to_send)
{
    // Esperar a que el bus I2C esté desocupado
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
    
    // Generar condición de START
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // Esperar EV5

    // Enviar dirección del esclavo (OLED) con bit de escritura
    I2C_Send7bitAddress(I2C1, OLED_DIRECCION_ESCLAVO, I2C_Direction_Transmitter);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // Esperar EV6

    // Enviar byte de control (Comando/Datos)
    I2C_SendData(I2C1, control_byte);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // Esperar EV8

    // Enviar el byte de datos
    I2C_SendData(I2C1, data_to_send);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // Esperar EV8
    
    // Generar condición de STOP
    I2C_GenerateSTOP(I2C1, ENABLE);
}

/**
 * @brief Envía un comando a la pantalla OLED.
 * @param comando El byte del comando a enviar.
 */
void EnviarComandoOLED(unsigned char comando)
{
    EscribirByteOLED_I2C(0x00, comando); // Byte de control 0x00 para comandos
}

/**
 * @brief Envía un byte de datos a la pantalla OLED.
 * @param dato El byte de datos a enviar.
 */
void EnviarDatoOLED(unsigned char dato)
{
    EscribirByteOLED_I2C(0x40, dato); // Byte de control 0x40 para datos
}

/**
 * @brief Inicializa la pantalla OLED con la secuencia de comandos necesaria.
 */
void InicializarPantallaOLED(void)
{
    ConfigurarI2C_OLED();
    delay_ms(100); // Retardo esencial después del encendido

    // Secuencia de inicialización de comandos del controlador SSD1306
    EnviarComandoOLED(0xAE); // Desactivar pantalla
    EnviarComandoOLED(0x20); // Establecer modo de direccionamiento de memoria
    EnviarComandoOLED(0x10); // Modo de direccionamiento por páginas (RESET)
    EnviarComandoOLED(0xB0); // Establecer dirección de inicio de página (0-7)
    EnviarComandoOLED(0xC8); // Establecer dirección de escaneo de salida COM
    EnviarComandoOLED(0x00); // Establecer dirección de columna baja
    EnviarComandoOLED(0x10); // Establecer dirección de columna alta
    EnviarComandoOLED(0x40); // Establecer línea de inicio de visualización
    EnviarComandoOLED(0x81); // Establecer registro de control de contraste
    EnviarComandoOLED(0xFF); // Nivel de contraste (0x00-0xFF, FF es máximo)
    EnviarComandoOLED(0xA1); // Re-mapear segmentos (0 a 127)
    EnviarComandoOLED(0xA6); // Visualización normal (no invertida)
    EnviarComandoOLED(0xA8); // Establecer relación de multiplexación (1 a 64)
    EnviarComandoOLED(0x3F); // 1/64 de ciclo de trabajo
    EnviarComandoOLED(0xA4); // Salida sigue el contenido de la RAM (no ignora)
    EnviarComandoOLED(0xD3); // Establecer desplazamiento de visualización
    EnviarComandoOLED(0x00); // Sin desplazamiento
    EnviarComandoOLED(0xD5); // Establecer relación de división de reloj/frecuencia del oscilador
    EnviarComandoOLED(0xF0); // Establecer la relación de división
    EnviarComandoOLED(0xD9); // Establecer período de precarga
    EnviarComandoOLED(0x22); //
    EnviarComandoOLED(0xDA); // Establecer configuración de hardware de pines COM
    EnviarComandoOLED(0x12); //
    EnviarComandoOLED(0xDB); // Establecer VCOMH
    EnviarComandoOLED(0x20); // VCOMH = 0.77xVcc
    EnviarComandoOLED(0x8D); // Habilitar bomba de carga
    EnviarComandoOLED(0x14); //
    EnviarComandoOLED(0xAF); // Encender panel OLED
    LimpiarPantallaOLED();   // Limpiar el contenido de la pantalla
}

/**
 * @brief Establece la posición de inicio para la escritura en el OLED.
 * @param columna Posición X (0-127).
 * @param pagina Posición Y (0-7, cada página es 8 píxeles de altura).
 */
void EstablecerCursorOLED(unsigned char columna, unsigned char pagina) 
{ 
    EnviarComandoOLED(0xB0 + pagina);             // Establecer dirección de página
    EnviarComandoOLED(((columna & 0xF0) >> 4) | 0x10); // Establecer dirección de columna alta
    EnviarComandoOLED((columna & 0x0F) | 0x01);        // Establecer dirección de columna baja
}

/**
 * @brief Rellena toda la pantalla OLED con un patrón de datos específico.
 * @param patron_datos El byte a usar para rellenar la pantalla.
 */
void RellenarAreaOLED(unsigned char patron_datos)
{
    unsigned char m, n;
    for(m = 0; m < 8; m++) // Iterar por las 8 páginas de la pantalla
    {
        EnviarComandoOLED(0xB0 + m); // Seleccionar la página actual
        EnviarComandoOLED(0x00);     // Columna inicial baja (0)
        EnviarComandoOLED(0x10);     // Columna inicial alta (0)
        for(n = 0; n < 128; n++)     // Iterar por las 128 columnas de la página
            EnviarDatoOLED(patron_datos);
    }
}

/**
 * @brief Limpia la pantalla OLED, estableciendo todos los píxeles a OFF.
 */
void LimpiarPantallaOLED(void)
{
    RellenarAreaOLED(0x00);
}

/**
 * @brief Activa la pantalla OLED desde el modo de bajo consumo.
 */
void ActivarPantallaOLED(void)
{
    EnviarComandoOLED(0X8D);  // Comando para el generador de bomba de carga
    EnviarComandoOLED(0X14);  // Habilitar bomba de carga
    EnviarComandoOLED(0XAF);  // Encender pantalla OLED
}

/**
 * @brief Pone la pantalla OLED en modo de bajo consumo para reducir el consumo de energía.
 */
void DesactivarPantallaOLED(void)
{
    EnviarComandoOLED(0X8D);  // Comando para el generador de bomba de carga
    EnviarComandoOLED(0X10);  // Deshabilitar bomba de carga
    EnviarComandoOLED(0XAE);  // Apagar pantalla OLED (modo de reposo)
}

/**
 * @brief Muestra una cadena de caracteres ASCII en la pantalla OLED.
 * @param x Coordenada X de inicio (0-127).
 * @param y Coordenada Y de inicio (0-7, página).
 * @param texto_str Puntero a la cadena de caracteres a mostrar.
 * @param tam_fuente Tamaño de la fuente (1 para 6x8, 2 para 8x16).
 * @note Requiere que las fuentes 'F6x8' y 'F8X16' estén definidas en 'font_data.h'.
 */
void MostrarTextoOLED(unsigned char x, unsigned char y, const char texto_str[], unsigned char tam_fuente)
{
    unsigned char char_index = 0, i = 0, j = 0;
    switch(tam_fuente)
    {
        case 1: // Fuente 6x8
        {
            while(texto_str[j] != '\0')
            {
                char_index = texto_str[j] - 32; // Ajuste para el código ASCII (ASCII 32 es espacio)
                if(x > 126) { x = 0; y++; }     // Si excede el ancho, salta a la siguiente línea/página
                EstablecerCursorOLED(x, y);
                for(i = 0; i < 6; i++) { EnviarDatoOLED(F6x8[char_index][i]); }
                x += 6;
                j++;
            }
        } break;
        case 2: // Fuente 8x16
        {
            while(texto_str[j] != '\0')
            {
                char_index = texto_str[j] - 32; // Ajuste para el código ASCII
                if(x > 120) { x = 0; y++; }     // Si excede el ancho, salta a la siguiente línea/página
                EstablecerCursorOLED(x, y);
                for(i = 0; i < 8; i++) { EnviarDatoOLED(F8X16[char_index*16 + i]); }
                EstablecerCursorOLED(x, y + 1); // La fuente 8x16 ocupa 2 páginas (16 píxeles de alto)
                for(i = 0; i < 8; i++) { EnviarDatoOLED(F8X16[char_index*16 + i + 8]); }
                x += 8;
                j++;
            }
        } break;
    }
}

/**
 * @brief Muestra un carácter chino de 16x16 puntos en la pantalla OLED.
 * @param x Coordenada X de inicio (0-127).
 * @param y Coordenada Y de inicio (0-7, página).
 * @param indice Índice del carácter chino en la matriz de la fuente F16x16.
 * @note Requiere que 'F16x16' esté definido en 'font_data.h'.
 */
void MostrarCaracterChinoOLED(unsigned char x, unsigned char y, unsigned char indice)
{
    unsigned char byte_count;
    unsigned int  data_offset = 32 * indice; // Cada carácter 16x16 ocupa 32 bytes (16*16/8)
    EstablecerCursorOLED(x , y);
    for(byte_count = 0; byte_count < 16; byte_count++)
    {
        EnviarDatoOLED(F16x16[data_offset + byte_count]);
    }
    EstablecerCursorOLED(x, y + 1); // La segunda fila de la fuente 16x16
    for(byte_count = 0; byte_count < 16; byte_count++)
    {
        EnviarDatoOLED(F16x16[data_offset + 16 + byte_count]);
    }
}

/**
 * @brief Dibuja un mapa de bits (BMP) en una región específica de la pantalla OLED.
 * @param x0 Columna de inicio.
 * @param y0 Página de inicio.
 * @param x1 Columna final (exclusiva).
 * @param y1 Página final (exclusiva, en términos de filas de píxeles, se convertirá a páginas).
 * @param bmp_data Puntero a la matriz de datos del BMP.
 */
void DibujarMapaBitsOLED(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, const unsigned char bmp_data[])
{
    unsigned int data_idx = 0;
    unsigned char current_page, current_col;

    // Calcular el número de páginas verticales que abarca el BMP
    unsigned char num_vertical_pages = (y1 % 8 == 0) ? (y1 / 8) : (y1 / 8 + 1);
    
    for(current_page = y0; current_page < num_vertical_pages; current_page++)
    {
        EstablecerCursorOLED(x0, current_page);
        for(current_col = x0; current_col < x1; current_col++)
        {
            EnviarDatoOLED(bmp_data[data_idx++]);
        }
    }
}

char buffer_temperatura[20] = {};
char buffer_humedad[20] = {};
const char simbolo_celsius[] = "C";
const char simbolo_porcentaje[] = "%";

/**
 * @brief Actualiza la visualización en la pantalla OLED con los datos de temperatura y humedad.
 *        Asume que 'g_mediciones_ht' contiene los datos más recientes.
 */
void ActualizarPantallaOLED(void)
{
    // Limpiar buffers antes de formatear
    memset(buffer_temperatura, 0, sizeof(buffer_temperatura));
    memset(buffer_humedad, 0, sizeof(buffer_humedad));

    // Formatear la cadena de temperatura
    sprintf(buffer_temperatura, "%.2f", g_mediciones_ht.temperatura_celsius);
    strncat(buffer_temperatura, simbolo_celsius, sizeof(buffer_temperatura) - strlen(buffer_temperatura) - 1);

    // Formatear la cadena de humedad
    sprintf(buffer_humedad, "%.2f", g_mediciones_ht.humedad_relativa);
    strncat(buffer_humedad, simbolo_porcentaje, sizeof(buffer_humedad) - strlen(buffer_humedad) - 1);
    
    // Limpiar la pantalla para una actualización completa (puede optimizarse para solo actualizar áreas)
    LimpiarPantallaOLED(); 

    // Mostrar un título o encabezado
    MostrarTextoOLED(0, 0, "DATOS AMBIENTALES:", 1); // Fuente 6x8, en la primera página

    // Mostrar la temperatura actual
    MostrarTextoOLED(0, 2, "Temperatura:", 2);      // Fuente 8x16, en la página 2
    MostrarTextoOLED(72, 2, buffer_temperatura, 2); // Valor de temperatura

    // Mostrar la humedad actual
    MostrarTextoOLED(0, 4, "Humedad:", 2);          // Fuente 8x16, en la página 4
    MostrarTextoOLED(72, 4, buffer_humedad, 2);     // Valor de humedad

    // Nota: Si se desea mostrar caracteres chinos con índices específicos, se usaría:
    // MostrarCaracterChinoOLED(16,0,2); // Ejemplo si los caracteres están definidos y son relevantes en font_data.h
}

Etiquetas: HS3003 OLED I2C STM32 EmbeddedSystems

Publicado el 8-20 17:36