STM32 utiliza I2C por hardware para leer el sensor de temperatura y humedad SHTC3

STM32 utiliza IIC por hardware para leer datos del sensor de temperatura y humedad SHTC3 y mostrarlos en una pantalla OLED de 0,96 pulgadas.

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

Grupo de intercambio técnico de electrónica/MCU: 2169025065

Efecto de la implementación


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, buena escalabilidad y al no requerir circuitos transceptores externos como USART o CAN (esos chips de conversión de niveles), hoy se utiliza ampliamente para la comunicación entre múltiples circuitos integrados (IC) dentro de un sistema.

I2C solo dispone de un bus de datos SDA (Serial Data Line), un bus de datos serie que transmite bit a bit, por lo que pertenece a la comunicación serie y es semidúplex.

Comunicación semidúplex: permite comunicación bidireccional, pero no simultánea en ambos sentidos; debe alternarse. Se puede entender como una comunicación simplex con dirección conmutable: en un momento dado solo puede transmitirse en un sentido y basta con una línea de datos.
Respecto al protocolo I2C, se divide en capa física y capa de protocolo. La capa física define las características mecánicas y electrónicas del sistema de comunicación (parte hardware), garantizando la transmisión de datos brutos por el medio físico. La capa de protocolo regula la lógica de comunicación, unificando los criterios de empaquetado y desempaquetado entre emisor y receptor (nivel software).

Capa física de I2C

Conexión típica entre dispositivos I2C

  1. Es un bus que admite múltiples dispositivos. “Bus” significa que varios dispositivos comparten la misma línea de señal. En un bus I2C pueden conectarse varios dispositivos, soportando múltiples maestros y esclavos.

  2. Un bus I2C solo utiliza dos líneas: una línea de datos serie bidireccional SDA (Serial Data Line) y una línea de reloj serie SCL (Serial Clock Line). La línea de datos transmite la información y la de reloj sincroniza el envío y recepción.

  3. El bus se conecta a la alimentación mediante resistencias de pull-up. Cuando el dispositivo I2C está inactivo su salida pasa a alta impedancia; si todos los dispositivos están inactivos y en alta impedancia, 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.

Para más información y uso de I2C en STM32 puede consultar este artículo: https://url.zeruns.com/JC0Ah
Aquí no lo explicaré en detalle.

Sensor de temperatura y humedad SHTC3

Descarga del datasheet de SHTC3: https://url.zeruns.com/WpLDy

Del datasheet se extrae que el SHTC3 detecta temperatura y humedad,
Rango de temperatura: –40 °C ~ 125 °C
Rango de humedad: 0 % ~ 100 %
Voltaje de trabajo: 1,6 V ~ 3,6 V
Método de comunicación: i2c
Frecuencia de reloj: tres modos, 0–100 kHz, 0–400 kHz, 0–1000 kHz

Localizamos la información clave:

Dirección del dispositivo y comandos de lectura/escritura

En la práctica, la dirección del SHTC3 debe combinarse con el bit de dirección R/W en un solo byte; el bit menos significativo indica la dirección de lectura/escritura y los 7 bits superiores son la dirección del dispositivo.

Para escribir datos o comandos por I2C, tras la señal START se envía “1110 0000”, es decir 0xE0. Los 7 bits superiores “1110 000” seleccionan el dispositivo y el bit bajo “0” indica operación de escritura.

Para leer datos, tras START se envía “1110 0001”, 0xE1: los 7 bits superiores igual y el bit bajo “1” indica lectura.
En resumen: 0xE0 = escritura, 0xE1 = lectura. Con I2C por hardware de STM32 basta con introducir 0xE0; la biblioteca estándar gestiona el bit bajo.

Lectura de datos de temperatura y humedad

Según el comando elegido varía el orden de los datos y la opción Clock Stretching Enable/Disable.

Clock Stretching significa “estiramiento de reloj”. Si se usa el comando con Clock Stretching Enable, tras enviar el comando de medición el SHTC3, mientras mide, mantiene baja la línea SCL, impidiendo que el maestro envíe más comandos. Solo cuando termina la medición libera SCL.

Si se usa Clock Stretching Disable, durante la medición el SCL no se baja; si el maestro intenta enviar datos, el SHTC3 no responde y el maestro puede detectar por la falta de ACK si la medición ha finalizado.

El ciclo de medición consta de cuatro pasos:

  1. Enviar comando de activación (wake).
  2. Enviar comando de medición.
  3. Leer los datos obtenidos.
  4. Enviar comando de sueño.

Los comandos de activación y sueño figuran en el datasheet.

Resumen:

  1. Activar SHTC3: enviar dirección de escritura (0xE0), byte alto del comando wake (0x35), byte bajo (0x17).
  2. Esperar activación: el datasheet indica 240 µs máx; basta esperar algo más.
  3. Enviar comando de medición: dirección 0xE0 seguida de los bytes alto y bajo del comando deseado.
  4. Recibir datos: enviar dirección de lectura 0xE1 y leer 6 bytes. Si el comando devuelve primero temperatura, los bytes 1-2 son temperatura, byte 3 es CRC de temp; bytes 4-5 humedad, byte 6 CRC de humedad. Si el orden es al revés, los primeros 3 bytes son humedad y los últimos temperatura.
  5. Dormir: enviar dirección de escritura y comando de sueño.

Cálculo de los datos

Según el datasheet del SHTC3:

Ejemplo: si el valor de humedad leído es 0x6501 → 25 857 en decimal.
Humedad = 100 × 25 857 / 65 536 = 39,45 %
Si la temperatura es 0x6600 → 26 112 en decimal.
Temperatura = –45 + 175 × 26 112 / 65 536 = 24,72 °C

Componentes necesarios

Placa mínima STM32: https://s.click.taobao.com/bqMwZRu
Módulo SHTC3: https://s.click.taobao.com/WxACJRu
Módulo OLED: https://s.click.taobao.com/aNlvZRu
Cables Dupont: https://s.click.taobao.com/xAkAJRu
Protoboard: https://s.click.taobao.com/ShJAJRu
ST-LINK V2: https://s.click.taobao.com/C8ftZRu

Programa

Aquí solo muestro los tres archivos principales: main.c, shtc3.c y oled.c. El resto está en el paquete comprimido del enlace.

Proyecto completo: https://url.zeruns.com/EXCvo

Conecta SCL de los módulos SHTC3 y OLED a PB6, y SDA a PB7.

Usar VSCode en lugar de Keil para desarrollar con STM32 y MCU 51: https://blog.zeruns.com/archives/690.html

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "IWDG.h"
#include "SHTC3.h"

uint16_t numlen(uint16_t num);

int main(void)
{
	IWDG_Configuration();	// Inicializar watchdog
	OLED_Init();			// Inicializar pantalla OLED
	SHTC3_I2C_Init();		// Inicializar SHTC3
	
	OLED_ShowString(1, 1, "T:");
	OLED_ShowString(2, 1, "H:");
	OLED_ShowString(4, 1, "err_count:");

	uint32_t a=0;
	uint16_t err_count=0;
	
	while (1)
	{
		a++;
		OLED_ShowNum(3, 1, a, 9);
		if(a==999999999)a=0;

		float Temp,Hum;			// Variables para temperatura y humedad

		if(ReadSHTC3(&Hum,&Temp))	// Leer datos
		{
			if(Temp>=0)
			{
				char String[10];
				sprintf(String, "%.2fC", Temp);	// Formatear cadena
				OLED_ShowString(1, 3, String);	// Mostrar temperatura
				/*
				OLED_ShowNum(1,3, (uint8_t)Temp, numlen((uint8_t)Temp));// parte entera
				OLED_ShowChar(1, 3+numlen((uint8_t)Temp), '.');			// punto decimal
				OLED_ShowNum(1,3+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	// decimales
				OLED_ShowChar(1, 3+numlen((uint8_t)Temp)+1+2, 'C');		// símbolo
				*/
```sprintf(String, "%.2f%%", Hum);	//Formatear la salida de cadena a la variable String
				OLED_ShowString(2, 3, String);	//Mostrar humedad
				/*
				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//Mostrar parte entera de la humedad
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//Mostrar punto decimal
				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//Mostrar parte decimal de la humedad
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//Mostrar símbolo
				*/
			}else
			{
				char String[10];
				sprintf(String, "-%.2fC", Temp);//Formatear la salida de cadena a la variable String
				OLED_ShowString(1, 3, String);	//Mostrar temperatura
				/*
				OLED_ShowChar(1, 3, '-');			//Mostrar signo negativo
				OLED_ShowNum(1,3+1, (uint8_t)Temp, numlen((uint8_t)Temp));	//Mostrar parte entera de la temperatura
				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp), '.');			//Mostrar punto decimal
				OLED_ShowNum(1,3+1+numlen((uint8_t)Temp)+1, (uint8_t)(Temp*100)%100, 2);	//Mostrar parte decimal de la temperatura
				OLED_ShowChar(1, 3+1+numlen((uint8_t)Temp)+1+2, 'C');		//Mostrar símbolo
				*/

				sprintf(String, "%.2f%%", Hum);	//Formatear la salida de cadena a la variable String
				OLED_ShowString(2, 3, String);	//Mostrar humedad
				/*
				OLED_ShowNum(2,3, (uint8_t)Hum, numlen((uint8_t)Hum));	//Mostrar parte entera de la humedad
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum), '.');			//Mostrar punto decimal
				OLED_ShowNum(2,3+numlen((uint8_t)Hum)+1, (uint8_t)(Hum*100)%100, 2);	//Mostrar parte decimal de la humedad
				OLED_ShowChar(2, 3+numlen((uint8_t)Hum)+1+2, '%');			//Mostrar símbolo
				*/
			}
		}
		else
		{
			err_count++;
			OLED_ShowNum(4,11, err_count, numlen(err_count));	//Mostrar contador de errores
		}
	/*
	https://blog.zeruns.com
	*/

		Delay_ms(100);	//Retardo de 100 ms

		IWDG_FeedDog();	//Alimentar al perro (watchdog, si no se alimenta en más de 1 segundo se reinicia automáticamente)
	}
}

/**
  * @brief  Calcular la longitud de un entero
  * @param  num entero cuya longitud se calculará
  * @retval valor de la longitud
  */
uint16_t numlen(uint16_t num)
{
    uint16_t len = 0;        // Longitud inicial 0
    for(; num > 0; ++len)    // Verificar si num es mayor que 0, de lo contrario longitud +1
        num /= 10;	         // Usar división hasta que num sea menor que 1
    return len;              // Devolver el valor de la longitud
}

SHTC3.c

#include "stm32f10x.h"
#include "Delay.h"

/*Dirección SHTC3*/
#define SHTC3_ADDRESS 0xE0

/*Configurar qué I2C usar*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/**
  * @brief  Verificación CRC, polinomio CRC: x^8+x^5+x^4+1, es decir 0x31
  * @param  DAT datos a verificar
  * @retval código de verificación
  */
uint8_t SHTC3_CRC_CAL(uint16_t DAT)
{
	uint8_t i,t,temp;
	uint8_t CRC_BYTE;

	CRC_BYTE = 0xFF;
	temp = (DAT>>8) & 0xFF;

	for(t = 0; t < 2; t++)
	{
		CRC_BYTE ^= temp;
		for(i = 0;i < 8;i ++)
		{
			if(CRC_BYTE & 0x80)
			{
				CRC_BYTE <<= 1;
				CRC_BYTE ^= 0x31;
			}
			else
			{
				CRC_BYTE <<= 1;
			}
		}

		if(t == 0)
		{
			temp = DAT & 0xFF;
		}
	}

	return CRC_BYTE;
}

/*Enviar señal de inicio*/
void SHTC3_I2C_START(){
    while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));//Esperar a que el bus esté libre
	I2C_GenerateSTART(I2Cx, ENABLE);//Enviar señal de inicio
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//Detectar evento EV5
}

/*Enviar señal de parada*/
void SHTC3_I2C_STOP(){
    I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
}

/**
  * @brief  Enviar dos bytes de datos
  * @param  MSB 8 bits altos
  * @param  LSB 8 bits bajos
  * @retval ninguno
  */
void SHTC3_WriteByte(uint8_t MSB,uint8_t LSB)
{
	SHTC3_I2C_START();  //Enviar señal de inicio
	
	I2C_Send7bitAddress(I2Cx, SHTC3_ADDRESS, I2C_Direction_Transmitter);    //Enviar dirección de escritura del dispositivo
	while(I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);  //Detectar evento EV6

    I2C_SendData(I2Cx, MSB);//Enviar datos 8 bits altos
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
   
	I2C_SendData(I2Cx, LSB);//Enviar datos 8 bits bajos
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
	
	I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
	
}

/**
  * @brief  Leer datos
  * @retval datos leídos en bytes
  */
uint8_t SHTC3_ReadData()
{
    while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Detectar evento EV7
	return I2C_ReceiveData(I2Cx);//Leer y devolver datos
}

/*Reinicio por software SHTC3*/
void SHTC3_SoftReset(void)                    
{
    SHTC3_WriteByte(0x80,0x5D);    //Reiniciar SHTC3
}

/*Inicialización de pines*/
void SHTC3_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Habilitar reloj I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Habilitar reloj GPIOB
 
	/*STM32F103 hardware I2C1: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;               //Definir estructura para configurar GPIO
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//Configurar modo de salida como drenaje abierto, requiere resistencia de pull-up
	GPIO_Init(GPIOB, &GPIO_InitStructure);              //Inicializar GPIO
	
	I2C_DeInit(I2Cx);	//Restablecer registros de periférico I2C a valores por defecto
	I2C_InitTypeDef  I2C_InitStructure;                 //Definir estructura para configurar I2C
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Modo de trabajo
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Ciclo de trabajo del reloj, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Dirección I2C del host, si no se usa escribir cualquiera, sin efecto
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Habilitar bit de respuesta
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Configurar longitud de dirección 7 bits
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//Velocidad de transmisión I2C, 400K, consultar manual del chip para velocidades soportadas.	
	I2C_Init(I2Cx, &I2C_InitStructure);         //Inicializar I2C

	I2C_Cmd(I2Cx, ENABLE);  //Habilitar I2C

	SHTC3_WriteByte(0X35,0X17);//Despertar SHTC3
	
	Delay_us(200);
}

/**
  * @brief  Leer datos SHTC3
  * @param  *Hum humedad
  * @param  *Temp temperatura
  * @retval 1 - lectura exitosa; 0 - lectura fallida
  */
uint8_t ReadSHTC3(float *Hum,float *Temp)
{
    uint16_t HumData,TempData,HumCRC,TempCRC;//Declarar variables para almacenar datos leídos
    
    /*SHTC3_WriteByte(0X35,0X17);//Despertar SHTC3
	
	Delay_us(300);*/
    
    SHTC3_WriteByte(0X5C,0X24);//Enviar comando, leer primero datos de humedad, estiramiento de reloj (bajar línea SCL durante medición, ocupar el bus)
	
    SHTC3_I2C_START();//Enviar señal de inicio
    
	I2C_Send7bitAddress(I2Cx,SHTC3_ADDRESS,I2C_Direction_Receiver);//Enviar dirección de lectura del dispositivo
	
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);//Detectar evento EV6
	
    HumData = SHTC3_ReadData(); //Leer 8 bits altos de humedad
    HumData=HumData<<8;         //Desplazar 8 bits a la izquierda
	HumData |= SHTC3_ReadData();//Leer 8 bits bajos de humedad
    HumCRC = SHTC3_ReadData();  //Leer datos de verificación CRC de humedad

    TempData = SHTC3_ReadData();//Leer 8 bits altos de temperatura
    TempData=TempData<<8;       //Desplazar 8 bits a la izquierda
	TempData |= SHTC3_ReadData();//Leer 8 bits bajos de temperatura
    TempCRC = SHTC3_ReadData(); //Leer datos de verificación CRC de temperatura

    SHTC3_I2C_STOP();   //Enviar señal de parada
		
	//SHTC3_WriteByte(0XB0,0X98);//Enviar comando de suspensión

    if( SHTC3_CRC_CAL(HumData)==HumCRC && SHTC3_CRC_CAL(TempData)==TempCRC ){   //Verificar datos recibidos con CRC
       *Hum = (float)HumData*100/65536;        //Convertir datos binarios de 16 bits a datos decimales de humedad
       *Temp = (float)TempData*175/65536-45;   //Convertir datos binarios de 16 bits a datos decimales de temperatura
       return 1;
    }
    else{
        return 0;
    }
}

OLED.c

#include "stm32f10x.h"
#include "OLED_Font.h"

/*Dirección de pantalla OLED*/
#define OLED_ADDRESS 0x78

/*Configurar qué I2C usar*/
#define I2Cx I2C1

/*
https://blog.zeruns.com
*/

/*Inicialización de pines*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Habilitar reloj I2C1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Habilitar reloj GPIOB
 
	/*STM32F103 hardware I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;		//Configurar modo de salida como drenaje abierto, requiere resistencia de pull-up
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2Cx);	//Restablecer registros de periférico I2C a valores por defecto
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Modo de trabajo
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Ciclo de trabajo del reloj, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Dirección I2C del host, si no se usa escribir cualquiera, sin efecto
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Habilitar bit de respuesta
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Configurar longitud de dirección 7 bits
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//Velocidad de transmisión I2C, 400K, consultar manual del chip para velocidades soportadas.	
	I2C_Init(I2Cx, &I2C_InitStructure);

	I2C_Cmd(I2Cx, ENABLE);
}

void I2C_WriteByte(uint8_t addr,uint8_t data)
{
	
	while( I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
	
	//Enviar señal de inicio
	I2C_GenerateSTART(I2Cx, ENABLE);
	//Detectar evento EV5
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//Enviar dirección de escritura del dispositivo
	I2C_Send7bitAddress(I2Cx, OLED_ADDRESS, I2C_Direction_Transmitter);
	//Detectar evento EV6
	while( I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	
	//Enviar dirección interna del dispositivo a operar
	I2C_SendData(I2Cx, addr);
	//Detectar evento EV8_2
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_SendData(I2Cx, data);//Enviar datos
	while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	//Enviar señal de parada
	I2C_GenerateSTOP(I2Cx, ENABLE);
}
 
/**
  * @brief  Escribir comando OLED
  * @param  Command comando a escribir
  * @retval ninguno
  */
void OLED_WriteCommand(unsigned char Command)//Escribir comando
{
	I2C_WriteByte(0x00, Command);
}
 
``````c
/**
  * @brief  OLED escribe datos
  * @param  Data dato a escribir
  * @retval ninguno
*/
void OLED_WriteData(unsigned char Data)  // escribe datos
{
    I2C_WriteByte(0x40, Data);
}

/**
  * @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);                           // establece posición Y
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));           // establece nibble bajo de X
    OLED_WriteCommand(0x00 | (X & 0x0F));                  // establece nibble alto 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 columna inicial, rango: 1~16
  * @param  end columna final, 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);        // cursor en mitad superior
        for (i = 0; i < 8; i++)
        {
            OLED_WriteData(0x00);                                  // borra mitad superior
        }
        OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);    // cursor en mitad inferior
        for (i = 0; i < 8; i++)
        {
            OLED_WriteData(0x00);                                // borra mitad inferior
        }
    }
}

/**
  * @brief  OLED muestra un carácter
  * @param  Line línea, rango: 1~4
  * @param  Column columna, rango: 1~16
  * @param  Char carácter ASCII visible
  * @retval ninguno
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{       
    uint8_t i;
    OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);            // mitad superior
    for (i = 0; i < 8; i++)
    {
        OLED_WriteData(OLED_F8x16[Char - ' '][i]);               // muestra mitad superior
    }
    OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);        // mitad inferior
    for (i = 0; i < 8; i++)
    {
        OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);           // muestra mitad inferior
    }
}

/**
  * @brief  OLED muestra cadena
  * @param  Line línea inicial, rango: 1~4
  * @param  Column columna inicial, rango: 1~16
  * @param  String cadena ASCII visible
  * @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  OLED función potencia
  * @retval retorna 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 línea inicial, rango: 1~4
  * @param  Column columna inicial, rango: 1~16
  * @param  Number número a mostrar, rango: 0~4294967295
  * @param  Length longitud del número, 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 línea inicial, rango: 1~4
  * @param  Column columna inicial, rango: 1~16
  * @param  Number número a mostrar, rango: -2147483648~2147483647
  * @param  Length longitud, 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 línea inicial, rango: 1~4
  * @param  Column columna inicial, rango: 1~16
  * @param  Number número a mostrar, rango: 0~0xFFFFFFFF
  * @param  Length longitud, 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 línea inicial, rango: 1~4
  * @param  Column columna inicial, rango: 1~16
  * @param  Number número a mostrar, rango: 0~1111 1111 1111 1111
  * @param  Length longitud, 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();                    // inicializa puertos
    
    OLED_WriteCommand(0xAE);            // apaga display
    
    OLED_WriteCommand(0xD5);            // divisor reloj / frecuencia oscilador
    OLED_WriteCommand(0x80);
    
    OLED_WriteCommand(0xA8);            // multiplex ratio
    OLED_WriteCommand(0x3F);
    
    OLED_WriteCommand(0xD3);            // offset display
    OLED_WriteCommand(0x00);
    
    OLED_WriteCommand(0x40);            // start line display
    
    OLED_WriteCommand(0xA1);            // dirección SEG, 0xA1 normal 0xA0 invertido
    
    OLED_WriteCommand(0xC8);            // dirección COM, 0xC8 normal 0xC0 invertido

    OLED_WriteCommand(0xDA);            // configuración pines COM
    OLED_WriteCommand(0x12);
    
    OLED_WriteCommand(0x81);            // control contraste
    OLED_WriteCommand(0xCF);

    OLED_WriteCommand(0xD9);            // período pre-carga
    OLED_WriteCommand(0xF1);

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

    OLED_WriteCommand(0xA4);            // display completo on/off

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

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

    OLED_WriteCommand(0xAF);            // enciende display
        
    OLED_Clear();                       // limpia OLED
}

Parte del contenido consultado en estos dos artículos:

https://blog.csdn.net/mj475002864/article/details/114027993

https://blog.csdn.net/k666499436/article/details/124686559

Recomendados

1 me gusta