STM32 lee datos del sensor de temperatura y humedad AHT10 mediante I2C por hardware y los muestra en una pantalla OLED de 0,96 pulgadas.
Utilizo el microcontrolador STM32F103C8T6 y el programa está escrito con la biblioteca estándar de ST.
STM32 lee el sensor de temperatura y humedad SHTC3 mediante I2C por hardware: https://blog.zeruns.com/archives/692.html
Grupo de tecnología de electrónica/microcontroladores: 2169025065
Resultado visual del proyecto
Introducción al protocolo I2C
El protocolo de comunicación I2C (Inter-Integrated Circuit) fue desarrollado por Philips. Debido a su bajo número de pines, implementación hardware simple, alta escalabilidad y la ausencia de dispositivos de transmisión/recepción externos como los necesarios en 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 en serie que transmite bit a bit; por tanto es una comunicación serie y semidúplex.
Comunicación semidúplex: permite comunicación bidireccional, pero no simultáneamente en ambas direcciones; debe alternarse por turnos. Se puede entender como una comunicación simplex con dirección variable: en un momento dado solo puede transmitirse en un sentido y basta con una línea de datos.
Para el protocolo I2C distinguimos capa física y capa de protocolo: la física define las características mecánicas y electrónicas (hardware) que garantizan la transmisión de datos brutos por el medio; la de protocolo regula la lógica de comunicación, estandarizando la forma de empaquetar y desempaquetar los datos entre emisor y receptor (software).
Capa física de I2C
Conexión típica entre dispositivos I2C
-
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 dispositivos, admitiendo varios maestros y varios esclavos.
-
Un bus I2C solo necesita dos líneas: una línea de datos bidireccional SDA (Serial Data Line) y una línea de reloj SCL (Serial Clock Line). La línea de datos transporta la información y la de reloj sincroniza la transmisión.
-
El bus se conecta a la alimentación mediante resistencias de pull-up. Cuando un dispositivo I2C está inactivo presenta alta impedancia; si todos los dispositivos están inactivos, las resistencias de pull-up llevan el bus al nivel alto.
Durante la comunicación I2C los pines GPIO del microcontrolador deben configurarse como salida drenador abierto; de lo contrario puede producirse un cortocircuito.
Para más información sobre I2C en STM32 y su uso, consulte este artículo: https://url.zeruns.com/JC0Ah
Aquí no lo explicaré en detalle.
Sensor de temperatura y humedad AHT10
Introducción
El AHT10 es un sensor de temperatura y humedad fabricado en China, barato, preciso y de pequeño tamaño.
Incluye un nuevo ASIC diseñado específicamente, un elemento capacitivo MEMS mejorado para medir humedad y un sensor de temperatura integrado estándar. Su rendimiento supera con creces la fiabilidad de la generación anterior, manteniéndose estable incluso en entornos hostiles.
Descarga del datasheet del AHT10: https://url.zeruns.com/EDEwF
Del datasheet se extrae:
- Rango de temperatura: –40 °C ‑ 85 °C
- Error de temperatura: ±0,3 °C
- Rango de humedad: 0 % ‑ 100 %
- Error de humedad: ±2 %
- Voltaje de trabajo: 1,8 V ‑ 3,6 V
- Método de comunicación: I2C
- Frecuencia de reloj: 100 kHz y 400 kHz
Dirección del dispositivo y comandos de lectura/escritura
Durante el uso real, la dirección del AHT10 debe combinarse con el bit de dirección de lectura/escritura para formar un byte completo que se envía tras el bit de inicio; el bit menos significativo indica la operación (lectura o escritura) y los 7 bits superiores son la dirección del dispositivo.
Para escribir datos o comandos: tras la señal de inicio se envía “0111 0000”, es decir, 0x70. Los 7 bits altos (“0111 000”) seleccionan el dispositivo y el bit bajo (“0”) indica escritura.
Para leer datos: tras el inicio se envía “0111 0001”, 0x71, donde el bit bajo (“1”) indica lectura.
En resumen: 0x70 para escritura, 0x71 para lectura. Con la I2C por hardware de STM32 basta con indicar 0x70; la biblioteca estándar gestiona el bit de lectura/escritura.
Lectura de datos de temperatura y humedad
Según el datasheet, un ciclo de medida consta de:
- Enviar comando de medida
- Esperar a que finalice la medida
- Leer los datos obtenidos
Resumen:
- Enviar comando de medida: primero dirección de escritura (0x70), luego comando de disparo (0xAC) y parámetros del comando (0x33 y 0x00).
- Esperar: el datasheet indica 75 ms; basta con esperar algo más.
- Recibir datos: enviar dirección de lectura (0x71) y leer 6 bytes consecutivos. El primero es el byte de estado; comprobar que el bit 3 (calibración) valga 1; si no, enviar comando de inicialización. Comprobar el bit 7 (ocupado/libre); si es 0 la medida ha terminado y se procede.
- Procesar los datos recibidos.
Cálculo de los datos
Según el datasheet del AHT10:
Ejemplo: si el valor de humedad leído es 0x0C6501, en decimal es 812289.
Entonces: humedad = 812289 × 100 / 1048576 = 77,46 %
Si el valor de temperatura es 0x056A00, decimal 354816.
Entonces: temperatura = (354816 × 200 / 1048576) − 50 = 17,67 °C
Componentes necesarios
Placa mínima STM32: https://s.click.taobao.com/bqMwZRu
Módulo AHT10: https://s.click.taobao.com/gIF09Ru
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
Código
Aquí solo muestro los tres archivos principales: main.c, AHT10.c y OLED.c. El resto está en el paquete comprimido del enlace.
Proyecto completo: https://url.zeruns.com/AHT10
SCL de los módulos AHT10 y OLED a PB6, SDA a PB7.
Usar VSCode en lugar de Keil para desarrollar con STM32 y 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 "AHT10.h"
uint16_t numlen(uint16_t num);
int main(void)
{
IWDG_Configuration(); // Inicializar watchdog
OLED_Init(); // Inicializar pantalla OLED
AHT10_Init(); // Inicializar AHT10
OLED_ShowString(1, 1, "T:");
OLED_ShowString(2, 1, "H:");
uint32_t a=0;
uint16_t err_count=0;
while (1)
{
a++;
OLED_ShowNum(3, 1, a, 9); // Contador para verificar que el programa sigue activo
if(a==999999999)a=0;
float Temp,Hum; // Variables para almacenar temperatura y humedad
/*
https://blog.zeruns.com
*/
if(ReadAHT10(&Hum,&Temp)) // Leer temperatura y humedad
{
if(Temp>=0)
{
char String[10];
sprintf(String, "+%.2fC", Temp); // Formatear cadena
OLED_ShowString(1, 3, String); // Mostrar temperatura
sprintf(String, " %.2f%%", Hum); // Formatear cadena
OLED_ShowString(2, 3, String); // Mostrar humedad
}else
{
char String[10];
sprintf(String, "-%.2fC", Temp); // Formatear cadena
OLED_ShowString(1, 3, String); // Mostrar temperatura
sprintf(String, " %.2f%%", Hum); // Formatear cadena
OLED_ShowString(2, 3, String); // Mostrar humedad
}
}
else
{
err_count++;
OLED_ShowNum(4,1, err_count, 5); // Mostrar contador de errores
}
Delay_ms(100); // Retardo 100 ms
IWDG_FeedDog(); // Alimentar watchdog (si transcurre más de 1 s sin hacerlo se reinicia el sistema)
}
}
```**AHT10.c**
```c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
/*Dirección AHT10*/
#define AHT10_ADDRESS 0x38<<1 //La dirección del esclavo es de 7 bits, el último bit indica la dirección de transmisión, por lo tanto se desplaza un bit a la izquierda
/*Configurar qué I2C se utiliza*/
#define I2Cx I2C1
/*
https://blog.zeruns.com
*/
/*Enviar señal de inicio*/
void AHT10_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 AHT10_I2C_STOP(){
I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
}
/**
* @brief Enviar 3 bytes de datos
* @param cmd byte de comando
* @param DATA0 primer parámetro
* @param DATA1 segundo parámetro
* @retval Ninguno
*/
void AHT10_WriteByte(uint8_t cmd, uint8_t DATA0, uint8_t DATA1)
{
AHT10_I2C_START(); //Enviar señal de inicio
I2C_Send7bitAddress(I2Cx, AHT10_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, cmd);//Enviar comando
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
I2C_SendData(I2Cx, DATA0);//Enviar los 8 bits altos del parámetro del comando
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
I2C_SendData(I2Cx, DATA1);//Enviar los 8 bits bajos del parámetro del comando
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
}
/**
* @brief Enviar comando para leer el estado del AHT10
* @retval byte de estado leído
*/
/*uint8_t AHT10_ReadStatus(void){
AHT10_I2C_START();//Enviar señal de inicio
I2C_Send7bitAddress(I2Cx,AHT10_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
while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Detectar evento EV7
I2C_AcknowledgeConfig(I2Cx, DISABLE); //Desactivar señal de respuesta
uint8_t status = I2C_ReceiveData(I2Cx);//Leer datos y devolver
AHT10_I2C_STOP(); //Enviar señal de parada
I2C_AcknowledgeConfig(I2Cx,ENABLE);//Volver a activar la señal de respuesta
return status;
}*/
/**
* @brief Leer datos
* @retval byte de datos leído
*/
uint8_t AHT10_ReadData(void)
{
while (!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));//Detectar evento EV7
return I2C_ReceiveData(I2Cx);//Leer datos y devolver
}
/*Reinicio por software del AHT10*/
void AHT10_SoftReset(void)
{
AHT10_I2C_START(); //Enviar señal de inicio
I2C_Send7bitAddress(I2Cx, AHT10_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, 0xBA);//Enviar comando de reinicio por software
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Detectar evento EV8
I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
Delay_ms(20);
}
/*Inicialización de pines*/
void AHT10_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); //Habilitar reloj de I2C1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//Habilitar reloj de GPIOB
/*I2C1 por hardware del chip STM32F103: 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 del periférico I2C a valores predeterminados
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 = 0x88; //Dirección I2C del host, si no se usa se puede escribir cualquier valor, no afecta
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //Habilitar bit de respuesta
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Establecer longitud de dirección a 7 bits
I2C_InitStructure.I2C_ClockSpeed = 400000; //Velocidad de transmisión I2C, 400K, verificar en el manual del chip la velocidad soportada.
I2C_Init(I2Cx, &I2C_InitStructure); //Inicializar I2C
I2C_Cmd(I2Cx, ENABLE); //Habilitar I2C
Delay_ms(20);//Retraso al encender
AHT10_WriteByte(0XE1,0X08,0x00);//Enviar instrucción de inicialización
Delay_ms(20);
}
/**
* @brief Leer datos del AHT10
* @param *Hum humedad
* @param *Temp temperatura
* @retval 1 - lectura exitosa; 0 - lectura fallida
*/
uint8_t ReadAHT10(float *Hum,float *Temp)
{
uint8_t Data[5];//Declarar variable para almacenar datos leídos
AHT10_WriteByte(0XAC,0X33,0x00);//Enviar instrucción para activar medición
Delay_ms(70); //Retraso de 70 ms esperando a que se complete la medición
AHT10_I2C_START();//Enviar señal de inicio
I2C_Send7bitAddress(I2Cx,AHT10_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
uint8_t i;
for(i=0;i<6;i++)//Ciclo 6 veces para leer 6 bytes de datos
{
if (i == 5) //Al leer el último byte, desactivar la señal de respuesta
{
I2C_AcknowledgeConfig(I2Cx, DISABLE); //Desactivar señal de respuesta
}
Data[i] = AHT10_ReadData(); //Leer datos
if (i == 5)
I2C_GenerateSTOP(I2Cx, ENABLE); //Enviar señal de parada
}
I2C_AcknowledgeConfig(I2Cx,ENABLE);//Volver a activar la señal de respuesta
if( (Data[0]&0x08) == 0 )//0x08(00001000) verificar si el bit 3 del byte de estado (bit de habilitación de calibración) es 0
{
AHT10_WriteByte(0XE1,0X08,0x00); //Enviar instrucción de inicialización
Delay_ms(20);
return 0;
}
else if( (Data[0]&0x80) == 0 )//0x80(10000000) verificar si el bit 7 del byte de estado (indicador de ocupado) es 0
{
uint32_t SRH = (Data[1]<<12) | (Data[2]<<4) | (Data[3]>>4); //Procesar datos de humedad
uint32_t ST = ((Data[3]&0x0f)<<16) | (Data[4]<<8) | Data[5];//Procesar datos de temperatura
*Hum = (SRH * 100.0) / 1024.0 / 1024; //Convertir datos de humedad según la fórmula del manual
*Temp = (ST * 200.0) / 1024.0 / 1024 - 50; //Convertir datos de temperatura según la fórmula del manual
return 1;
}
I2C_GenerateSTOP(I2Cx, ENABLE);//Enviar señal de parada
return 0;
}
/*
https://blog.zeruns.com
*/
```**OLED.c**
```c
#include "stm32f10x.h"
#include "OLED_Font.h"
/*Dirección de la pantalla OLED*/
#define OLED_ADDRESS 0x78
/*Configurar cuál I2C se usa*/
#define I2Cx I2C1
/*
https://blog.zeruns.com
*/
/*Inicialización de pines*/
void OLED_I2C_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE); //Habilitar reloj de I2C1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //Habilitar reloj de GPIOB
/*I2C por hardware del chip STM32F103: 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; //Modo de salida drenaje abierto, requiere resistencia de pull-up
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2Cx); //Restablecer registros 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 = 0x88; //Dirección I2C del maestro, si no se usa se puede poner cualquier valor
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //Habilitar bit de reconocimiento
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Dirección de 7 bits
I2C_InitStructure.I2C_ClockSpeed = 400000; //Velocidad de transmisión I2C, 400K, verificar en el manual del chip utilizado.
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 dato
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
//Enviar señal de parada
I2C_GenerateSTOP(I2Cx, ENABLE);
}
/**
* @brief Escribir comando en OLED
* @param Command comando a escribir
* @retval Ninguno
*/
void OLED_WriteCommand(unsigned char Command)//Escribir comando
{
I2C_WriteByte(0x00, Command);
}
/**
* @brief Escribir dato en OLED
* @param Data dato a escribir
* @retval Ninguno
*/
void OLED_WriteData(unsigned char Data)//Escribir dato
{
I2C_WriteByte(0x40, Data);
}
/**
* @brief Establecer posición del cursor en OLED
* @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 bits altos de X
OLED_WriteCommand(0x00 | (X & 0x0F)); //Establecer bits bajos de X
}
/**
* @brief Limpiar pantalla OLED
* @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 Limpiar parte de la pantalla OLED
* @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 parte superior
for (i = 0; i < 8; i++)
{
OLED_WriteData(0x00); //Mostrar parte superior
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //Establecer cursor en parte inferior
for (i = 0; i < 8; i++)
{
OLED_WriteData(0x00); //Mostrar parte inferior
}
}
}
/**
* @brief Mostrar un carácter en OLED
* @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 parte superior
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //Mostrar parte superior
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //Establecer cursor en parte inferior
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //Mostrar parte inferior
}
}
/**
* @brief Mostrar cadena en OLED
* @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 de potencia para OLED
* @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 Mostrar número en OLED (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 Mostrar número en OLED (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 Mostrar número en OLED (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 Mostrar número en OLED (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 Inicializar OLED
* @param Ninguno
* @retval Ninguno
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //Retardo tras encendido
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //Inicializar puertos
OLED_WriteCommand(0xAE); //Apagar pantalla
OLED_WriteCommand(0xD5); //Establecer división de reloj/frecuencia del oscilador
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //Establecer ratio de multiplexado
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //Establecer desplazamiento de pantalla
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //Establecer línea de inicio de pantalla
OLED_WriteCommand(0xA1); //Establecer dirección izquierda-derecha, 0xA1 normal 0xA0 invertido
OLED_WriteCommand(0xC8); //Establecer dirección arriba-abajo, 0xC8 normal 0xC0 invertido
OLED_WriteCommand(0xDA); //Configuración de pines COM
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //Control de contraste
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //Período de precarga
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //Nivel de deselección VCOMH
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //Encender/apagar pantalla completa
OLED_WriteCommand(0xA6); //Pantalla normal/invertida
OLED_WriteCommand(0x8D); //Bomba de carga
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //Encender pantalla
OLED_Clear(); //Limpiar pantalla
}
Recomendaciones de lectura
- Recomendación de VPS/Servidores en la nube de alta relación calidad-precio y económicos: https://blog.vpszj.cn/archives/41.html
- Usar NPS para construir un servidor de penetración de red interna con panel web: https://blog.vpszj.cn/archives/748.html
- Tutorial de construcción de sitios web en Linux, tutorial de creación de sitios: https://blog.vpszj.cn/archives/1094.html
- Tutorial de construcción de servidor de Minecraft: https://blog.vpszj.cn/archives/tag/minecraft
- Implementar función de medición de distancia por ultrasonido basada en STM32 y módulo HC-SR04: https://blog.zeruns.com/archives/680.html
- Configuración del entorno de desarrollo ESP8266 y demostración del proyecto: https://blog.zeruns.com/archives/526.html








