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
-
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.
-
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.
-
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:
- Enviar comando de activación (wake).
- Enviar comando de medición.
- Leer los datos obtenidos.
- Enviar comando de sueño.
Los comandos de activación y sueño figuran en el datasheet.
Resumen:
- Activar SHTC3: enviar dirección de escritura (0xE0), byte alto del comando wake (0x35), byte bajo (0x17).
- Esperar activación: el datasheet indica 240 µs máx; basta esperar algo más.
- Enviar comando de medición: dirección 0xE0 seguida de los bytes alto y bajo del comando deseado.
- 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.
- 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
- VPS/cloud de alto rendimiento y bajo costo: https://blog.vpszj.cn/archives/41.html
- Crea un servidor de túnel de red privada con panel Web NPS: https://blog.vpszj.cn/archives/748.html
- Tutorial de creación de sitios web en Linux: https://blog.vpszj.cn/archives/1094.html
- Tutorial de servidor Minecraft: https://blog.vpszj.cn/archives/tag/minecraft
- Medición de distancia ultrasónica con STM32 y HC-SR04: https://blog.zeruns.com/archives/680.html
- Configuración del entorno de desarrollo ESP8266 y demostración de proyectos: https://blog.zeruns.com/archives/526.html






