STC12C5A60S2最小系统板/51单片机温度显示和温度控制风扇

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

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

Recomendaciones de lectura