STC12C5A60S2 placa mínima / Ventilador controlado por temperatura con microcontrolador 51, incorpora DS18B20 y TM1650 + tubo de 4 dígitos.
Grupo de intercambio técnico de electrónica/microcontroladores: 2169025065
Introducción al proyecto
Diseño de curso de microcontroladores: se requiere un ventilador inteligente controlado por temperatura. El ventilador se enciende al alcanzar la temperatura mínima configurada; entre los valores mínimo y máximo se regula el duty-cycle del PWM; por encima del máximo el ventilador gira a máxima velocidad.
Se utiliza el microcontrolador STC12C5A60S2, el sensor de temperatura DS18B20 y un display de 4 dígitos controlado por el chip TM1650 mediante I²C.
Este proyecto open-source puede usarse directamente como placa mínima del STC12C5A60S2: todos los pines de E/S están disponibles.
Introducción al STC12C5A60S2
La familia STC12C5A60S2 es un microcontrolador de ciclo único por máquina (1T) fabricado por STC. Es un nuevo 8051 de alta velocidad, bajo consumo y alta inmunidad a interferencias, totalmente compatible en código de instrucciones con el 8051 tradicional, pero 8-12 veces más rápido. Integra internamente el circuito de reset dedicado MAX810, 2 canales PWM, 8 canales de ADC de 10 bits a 250 kS/s, orientado al control de motores y ambientes con fuertes interferencias.
TM1650
TM1650 es un circuito dedicado para controlar displays LED (diodos emisores) con interfaz de escaneo de teclado. Integra interfaz digital MCU, registro de datos, driver LED, escaneo de teclado y regulación de brillo. Es estable, fiable y de gran inmunidad a interferencias, apto para funcionamiento continuo 24 h.
- Dos modos de visualización: 8 segmentos × 4 dígitos o 7 segmentos × 4 dígitos
- Soporta hasta 28 teclas individuales (7×4 bits) y 4 teclas combinadas
- 8 niveles de brillo ajustables
- Corriente de segmento >25 mA, corriente de dígito >150 mA
- Interfaz serie de 2 hilos rápida (CLK, DAT)
- Oscilador interno RC
- Reset por encendido integrado
- Registro de datos integrado
- Voltaje de alimentación 3-5,5 V
- Alta inmunidad a interferencias
- Disponible en encapsulado DIP16 y SOP16
Fotos del producto
Esquemático
PCB
Capa superior:
Capa inferior:
Enlaces de compra de componentes
- Kit de muestra de resistencias 0805: https://s.click.taobao.com/oWjIgGu
- Kit de muestra de condensadores 0805: https://s.click.taobao.com/r9ea1Hu
- Display de 4 dígitos cátodo común: https://u.jd.com/1ir7YWC
- Chip TM1650: https://s.click.taobao.com/pYveTFu
- Sensor DS18B20: https://s.click.taobao.com/zaNQqFu
Se recomienda comprar componentes en LCSC; enlace de registro con descuento: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
Código y documentación
Enlace de descarga del proyecto completo y datasheets: https://url.zeruns.com/AkHGU Código de extracción: 6gzf
Enlace del proyecto en plataforma LCSC Open Source: https://url.zeruns.com/46y43
main.c
#include <STC12C5A60S2.H>
#in```clude<intrins.h>
#include "TM1650.h"
#include "DS18B20.h"
#include "Key.h"
sbit LED2 = P2 ^ 3;
sbit LED3 = P2 ^ 4;
sbit LED4 = P2 ^ 5;
sbit FAN = P4 ^ 2;
// Definir array de visualización del TM1650
unsigned char code dig1[11] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40}; // 0,1,2,3,4,5,6,7,8,9,- // sin punto decimal
unsigned char code dig2[11] = {0xbf, 0x86, 0xdb, 0xcf, 0xe6, 0xed, 0xfd, 0x87, 0xff, 0xef, 0x40}; // 0,1,2,3,4,5,6,7,8,9,- // con punto decimal
unsigned char code dig3[3] = {0x76, 0x38, 0x40}; // H, L, -
// Definir variables de contador
unsigned int count = 0, count1 = 0; // valores de contador
// valores límite de temperatura
uint8_t H_Set = 50;
uint8_t L_Set = 25;
// Definir tipo de variable enumerada para el modo de visualización
typedef enum
{
H_mode = 0, // ajuste de temperatura límite superior
L_mode, // ajuste de temperatura límite inferior
T_mode, // visualización de temperatura
} Display_MODE;
Display_MODE Display_mode = T_mode;
uint16_t temp;
// Inicialización del temporizador/contador
void Timer_Init()
{
EA = 1; // habilitar interrupción global
AUXR |= 0x80; // temporizador 0 en modo reloj 1T
TMOD &= 0xF0; // limpiar los 4 bits bajos, configurar como contador de 16 bits
TMOD |= 0x01; // configurar los 4 bits altos como temporizador modo 0
TL0 = 0xCD; // establecer valor inicial de temporización
TH0 = 0xD4; // establecer valor inicial de temporización
TF0 = 0; // limpiar bandera TF0
TR0 = 1; // iniciar temporizador 0
ET0 = 1; // habilitar interrupción del temporizador 0
AUXR &= 0xBF; // temporizador 1 en modo reloj 12T
TMOD &= 0x0F; // configurar modo del temporizador
TMOD |= 0x10; // configurar modo del temporizador
TL1 = 0x00; // establecer valor inicial de temporización
TH1 = 0xB8; // establecer valor inicial de temporización
TF1 = 0; // limpiar bandera TF1
TR1 = 1; // iniciar temporizador 1
ET1 = 1; // habilitar interrupción del temporizador 1
}
// Rutina de servicio de interrupción del temporizador/contador 0
void Timer0_Isr(void) interrupt 1
{
TL0 = 0xCD; // establecer valor inicial de temporización
TH0 = 0xD4; // establecer valor inicial de temporización
count++; // incrementar contador cada 1 ms
count1++;
}
void Timer1_Isr(void) interrupt 3
{
TL1 = 0x00; // establecer valor inicial de temporización
TH1 = 0xB8; // establecer valor inicial de temporización
key_status_check(0, KEY1);
key_status_check(1, KEY2);
key_status_check(2, KEY3);
key_status_check(3, KEY4);
}
void PWMInit()
{
// configurar PWM
CCON = 0; // registro de control PCA inicial
// detener temporizador PCA
// limpiar bandera CF
// limpiar todas las banderas de interrupción del módulo
CL = 0; // reiniciar temporizador base PCA
CH = 0;
CMOD = 0x00; // configurar reloj del temporizador PCA a frecuencia de cristal/12, deshabilitar interrupción de desbordamiento del contador PCA
CCAP0H = CCAP0L = 0x80; // puerto PWM0 genera onda cuadrada con ciclo de trabajo 50%
CCAPM0 = 0x42; // habilitar función comparadora, habilitar PWM0
AUXR1 |= 0x40; // cambiar salida PWM al puerto P4
CR = 1; // iniciar temporizador PCA
}
// https://blog.zeruns.com
void SetPwmDutyCycle(unsigned char dutyCycle)
{
// dutyCycle puede estar entre 0 y 100, representando 0% a 100% del ciclo de trabajo.
unsigned char newValue = ((100 - dutyCycle) * 255) / 100;
CCAP0H = CCAP0L = newValue; // actualizar CCAP0L para cambiar el ciclo de trabajo de la señal PWM
}
// función principal
void main()
{
TM_WrCmd(0x21); // configurar TM1650 a modo 8 segmentos × 4 dígitos, encender visualización, brillo nivel 2
Timer_Init(); // inicializar temporizador
Key_Init(); // inicializar máquina de estados del teclado
P4M0 = 0x04; // configurar P4.2 como salida push-pull
P4M1 = 0x00;
PWMInit(); // inicializar PWM
SetPwmDutyCycle(0); // establecer ciclo de trabajo PWM a 0
temp = GetTemp();
// https://blog.zeruns.com
while (1) // bucle infinito, ejecutar repetidamente las siguientes operaciones
{
if (count >= 100) // cada 100 ms
{
count = 0;
temp = GetTemp(); // leer temperatura
if (Display_mode == T_mode) // modo visualización temperatura
{
TM_WrDat(0x68, dig1[temp / 1000]); // escribir datos de visualización en el dígito 1
TM_WrDat(0x6a, dig2[temp / 100 % 10]); // escribir datos de visualización en el dígito 2
TM_WrDat(0x6c, dig1[temp / 10 % 10]); // escribir datos de visualización en el dígito 3
TM_WrDat(0x6e, dig1[temp % 10]); // escribir datos de visualización en el dígito 4
}
if (Display_mode == H_mode) // ajuste límite superior
{
TM_WrDat(0x68, dig3[0]); // dígito 1 muestra H
TM_WrDat(0x6a, dig3[2]); // dígito 2 muestra -
TM_WrDat(0x6c, dig1[H_Set / 10]); // escribir datos del dígito 3
TM_WrDat(0x6e, dig1[H_Set % 10]); // escribir datos del dígito 4
}
else if (Display_mode == L_mode) // ajuste límite inferior
{
TM_WrDat(0x68, dig3[1]); // dígito 1 muestra L
TM_WrDat(0x6a, dig3[2]); // dígito 2 muestra -
TM_WrDat(0x6c, dig1[L_Set / 10]); // escribir datos del dígito 3
TM_WrDat(0x6e, dig1[L_Set % 10]); // escribir datos del dígito 4
}
if (temp / 100 >= L_Set && temp / 100 < H_Set) // cuando la temperatura está entre el límite inferior y superior, ajustar ciclo de trabajo del ventilador según temperatura
{
uint8_t pwm_set = (uint8_t)((temp / 100.0 - (float)L_Set) / ((H_Set - L_Set) / 55.0) + 45.0 + 0.5);
SetPwmDutyCycle(pwm_set);
}
else if (temp / 100 >= H_Set) // cuando la temperatura supera el límite superior, ventilador a máxima velocidad
{
SetPwmDutyCycle(100); // establecer ciclo de trabajo al 100%
}
else if (temp / 100 < L_Set) // cuando la temperatura está por debajo del límite inferior, apagar ventilador
{
SetPwmDutyCycle(0);
}
LED2 = ~LED2;
}
if (count1 >= 500) // cada 500 ms
{
count1 = 0;
LED3 = ~LED3;
}
if (key[0] == 1) // SW3 cambio de modo
{
if (Display_mode != 2)
{
Display_mode++;
}
else
{
Display_mode = 0;
}
key[0] = 0;
}
if (key[1] == 1) // SW4 tecla arriba
{
if (Display_mode == H_mode)
{
if (H_Set < 99)
{
H_Set++;
}
}
else if (Display_mode == L_mode)
{
if (L_Set < 99)
{
L_Set++;
}
}
key[1] = 0;
}
if (key[2] == 1) // SW5 tecla abajo
{
if (Display_mode == H_mode)
{
if (H_Set > 0)
{
H_Set--;
}
}
else if (Display_mode == L_mode)
{
if (L_Set > 0)
{
L_Set--;
}
}
key[2] = 0;
}
LED4 = ~LED4;
}
}
TM1650.c
#include "TM1650.h"
#include <STC12C5A60S2.H>
#include <intrins.h>
// https://blog.zeruns.com
// Definir pines del TM1650
sbit SCL_T = P2 ^ 0; // reloj en serie
sbit SDA_T = P2 ^ 1; // datos en serie
// Definir función de retardo
void Delay5us_TM() //@11.0592MHz
{
unsigned char i;
_nop_();
_nop_();
_nop_();
i = 10;
while (--i)
;
}
void Delay1us_TM() //@11.0592MHz
{
_nop_();
}
// Bit de inicio TM1650
void TM_Start()
{
SCL_T = 1;
SDA_T = 1;
Delay5us_TM();
SDA_T = 0;
}
// Bit de parada TM1650
void TM_Stop()
{
SCL_T = 1;
SDA_T = 0;
Delay5us_TM();
SDA_T = 1;
}
// Señal de reconocimiento TM1650
void TM_Ack()
{
unsigned char timeout = 1;
SCL_T = 1;
Delay5us_TM();
SCL_T = 0;
while ((SDA_T) && (timeout <= 100))
{
timeout++;
}
Delay5us_TM();
SCL_T = 0;
}
// Escribir un byte por el bus
void Write_TM_Byte(unsigned char TM_Byte)
{
unsigned char i;
SCL_T = 0;
Delay1us_TM();
for (i = 0; i < 8; i++)
{
if (TM_Byte & 0x80)
SDA_T = 1;
else
SDA_T = 0;
SCL_T = 0;
Delay5us_TM();
SCL_T = 1;
Delay5us_TM();
SCL_T = 0;
TM_Byte <<= 1;
}
}
// Escribir datos TM1650
void TM_WrDat(unsigned char add, unsigned char dat)
{
TM_Start();
Write_TM_Byte(add); // dirección de memoria de visualización
TM_Ack();
Write_TM_Byte(dat); // dato de visualización
TM_Ack();
TM_Stop();
}
// Escribir comando TM1650
void TM_WrCmd(unsigned char Bri)
{
TM_Start();
Write_TM_Byte(0x48); // modo de visualización
TM_Ack();
Write_TM_Byte(Bri); // control de brillo
TM_Ack();
TM_Stop();
}
TM1650.h
#ifndef __TM1650_H_
#define __TM1650_H_
void TM_WrDat(unsigned char add, unsigned char dat);
void TM_WrCmd(unsigned char Bri);
#endif
DS18B20.c
#include "DS18B20.h"
#include <STC12C5A60S2.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
// Pin de datos del DS18B20
sbit DS = P2 ^ 2;
// Función de retardo en microsegundos
void delay_us(uchar us)
{
while (us--)
{
_nop_();
}
}
// https://blog.zeruns.com
// Inicializa el DS18B20; devuelve 0 si tiene éxito, 1 si falla
bit DS18B20_Init()
{
bit i;
DS = 1; // Libera el bus
_nop_();
DS = 0; // Baja el bus al menos 480 µs para resetear el DS18B20
delay_us(480);
DS = 1; // Libera el bus
delay_us(20); // Espera 15-60 µs
i = DS; // Lee la señal de presencia: 0 = presente, 1 = no presente
delay_us(70); // Espera 60-240 µs
DS = 1; // Libera el bus
_nop_();
_nop_();
return (i);
}
// Escribe un byte al DS18B20
void DSWriteByte(uchar dat)
{
uchar i;
for (i = 0; i < 8; i++)
{
DS = 0; // Baja el bus para generar el time-slot de escritura
_nop_();
_nop_();
DS = dat & 0x01; // Escribe el bit menos significativo
delay_us(60); // Mantiene el time-slot al menos 60 µs
DS = 1; // Libera el bus
_nop_();
_nop_();
dat >>= 1; // Desplaza un lugar para el siguiente bit
}
}
// Lee un byte del DS18B20
uchar DSReadByte()
{
uchar i, dat, j;
for (i = 0; i < 8; i++)
{
DS = 0; // Baja el bus para generar el time-slot de lectura
_nop_();
_nop_();
DS = 1; // Libera el bus
_nop_();
_nop_();
j = DS; // Lee el bit menos significativo
delay_us(60); // Mantiene el time-slot al menos 60 µs
DS = 1; // Libera el bus
_nop_();
_nop_();
dat = (j << 7) | (dat >> 1); // Inserta el bit leído en la posición más alta
}
return (dat);
}
// Obtiene la temperatura del DS18B20; devuelve el valor multiplicado por 100 (ºC)
int GetTemp()
{
uchar L, H;
int temp;
DS18B20_Init(); // Inicializa el DS18B20
DSWriteByte(0xcc); // Salta ROM
DSWriteByte(0x44); // Inicia conversión de temperatura
DS18B20_Init(); // Reinicializa
DSWriteByte(0xcc); // Salta ROM
DSWriteByte(0xbe); // Lee scratchpad
L = DSReadByte(); // Byte bajo
H = DSReadByte(); // Byte alto
temp = H;
temp <<= 8;
temp |= L; // Combina bytes
temp = temp * 0.0625 * 100 + 0.5; // Convierte a ºC×100 y redondea
return (temp); // Ej: 25.6 ºC → 256
}
DS18B20.h
#ifndef __DS18B20_H_
#define __DS18B20_H_
bit DS18B20_Init();
int GetTemp();
#endif
Key.c
#include "Key.h"
// Enumeración de estados del pulsador
typedef enum
{
KS_RELEASE = 0, // Suelto
KS_SHAKE, // Rebote
KS_PRESS, // Estable pulsado
} KEY_STATUS;
// Índices para la máquina de estados
#define g_keyStatus 0
#define g_nowKeyStatus 1
#define g_lastKeyStatus 2
uint8_t KEY_Status[4][3]; // Estado de cada pulsador
uint8_t key[4]; // 1 = pulsador estable pulsado, 0 = no pulsado
// https://blog.zeruns.com
void Key_Init(void)
{
uint8_t i;
for (i = 0; i < 4; i++)
{
KEY_Status[i][g_keyStatus] = KS_RELEASE;
KEY_Status[i][g_nowKeyStatus] = KS_RELEASE;
KEY_Status[i][g_lastKeyStatus] = KS_RELEASE;
key[i] = 0;
}
KEY1 = KEY2 = KEY3 = KEY4 = 1;
}
// Máquina de estados del pulsador
void key_status_check(uint8_t key_num, uint8_t KEY)
{
switch (KEY_Status[key_num][g_keyStatus])
{
case KS_RELEASE:
if (KEY == 0)
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
break;
case KS_SHAKE:
if (KEY == 1)
KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
else
KEY_Status[key_num][g_keyStatus] = KS_PRESS;
break;
case KS_PRESS:
if (KEY == 1)
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
break;
default:
break;
}
if (KEY_Status[key_num][g_keyStatus] != KEY_Status[key_num][g_nowKeyStatus])
{
if ((KEY_Status[key_num][g_keyStatus] == KS_RELEASE) &&
(KEY_Status[key_num][g_lastKeyStatus] == KS_PRESS))
{
key[key_num] = 1;
}
KEY_Status[key_num][g_lastKeyStatus] = KEY_Status[key_num][g_nowKeyStatus];
KEY_Status[key_num][g_nowKeyStatus] = KEY_Status[key_num][g_keyStatus];
}
}
Key.h
#ifndef __KEY_H
#define __KEY_H
#include <STC12C5A60S2.H>
/* Definición de pines de pulsadores */
sbit KEY1 = P3 ^ 2;
sbit KEY2 = P3 ^ 3;
sbit KEY3 = P3 ^ 4;
sbit KEY4 = P3 ^ 5;
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
extern uint8_t KEY_Status[4][3]; // Estado de cada pulsador
extern uint8_t key[4]; // 1 = pulsador estable pulsado
void Key_Init(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
#endif
Otros proyectos open-source recomendados
- Diseñé una placa mínima para MSP430F149 y la liberé: https://blog.zeruns.com/archives/713.html
- Placa mínima STM32F030C8T6 y luces de desplazamiento (esquemático y PCB): https://blog.zeruns.com/archives/715.html
- Módulo de alimentación DCDC reductora ajustable SY8205 (esquemático y PCB): https://blog.zeruns.com/archives/717.html
- Concurso Nacional de Electrónica 2011 – Sistema de alimentación con módulos de fuente conmutada en paralelo: https://blog.zeruns.com/archives/718.html
- Concurso de fuentes 2007: módulo DCDC elevador 30-36 V (UC3843): https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
Recomendaciones de lectura
- VPS/cloud de alta relación calidad-precio y económicos: https://blog.vpszj.cn/archives/41.html
- Cómo crear un blog personal: https://blog.zeruns.com/archives/218.html
- Tutorial para montar un servidor de Minecraft: https://blog.zeruns.com/tag/mc/
- STM32 lee sensores de temperatura/humedad serie SHT3x: https://blog.zeruns.com/archives/700.html
- Usar VSCode en lugar de Keil para desarrollar con STM32 y 51: https://blog.zeruns.com/archives/690.html
- Evaluación de rendimiento del VPS japonés de Zhiyun, 1C/1G 10 Mbps 5 G DDoS por solo 29.4 ¥/mes: https://blog.vpszj.cn/archives/1749.html
- Evaluación de rendimiento del servidor cloud de alta protección Gold 6146 en Suqian de Yuyun: https://blog.vpszj.cn/archives/1725.html




