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

Минимальная системная плата STC12C5A60S2 / 51-микроконтроллер с индикацией температуры и управлением вентилятором по температуре. На плате установлены DS18B20 и TM1650 + 4-разрядный семисегментный индикатор.

Группа технического обмена по электронике/микроконтроллерам: 2169025065

Описание проекта

Курсовой проект по микроконтроллерам: «интеллектуальный вентилятор с терморегулированием». При достижении нижнего порога температуры вентилятор включается; при температуре между нижним и верхним порогом скорость регулируется ШИМ; при превышении верхнего порога вентилятор работает на максимальной скорости.
Используется микроконтроллер STC12C5A60S2, датчик температуры DS18B20, индикатор управляется по I²C через чип TM1650.
Плату можно использовать как минимальную систему STC12C5A60S2 — все выводы вынесены наружу.

Кратко о STC12C5A60S2

Серия STC12C5A60S2 — это 8051-совместимые однотактные (1T) микроконтроллеры компании STC. Работают в 8–12 раз быстрее классического 8051, обладают низким энергопотреблением и высокой помехоустойчивостью. Встроены: MAX810-схема сброса, 2 канала ШИМ, 8-канальный 10-бит АЦП (250 квыб/с), оптимизированы под управление двигателями и работу в сильных помехах.

TM1650

Специализированный драйвер LED-дисплея с функцией сканирования клавиатуры. Содержит цифровой интерфейс, регистры хранения, драйвы сегментов и разрядов, регулировку яркости, встроенное RC-генератор и схему сброса. Подходит для круглосуточной работы.

  • Режимы: 8 сегментов × 4 разряда или 7 сегментов × 4 разряда
  • Поддержка одиночных (28) и комбинированных (4) клавиш
  • 8 уровней яркости
  • Ток сегмента > 25 мА, ток разряда > 150 мА
  • 2-проводной высокоскоростной интерфейс (CLK, DAT)
  • Встроенный RC-генератор и POR
  • Питание 3…5,5 В
  • DIP16 и SOP16 корпуса

Фотографии платы


Схема

PCB

Верхний слой:

Нижний слой:

Где купить компоненты

Рекомендуемый магазин — LCSC (регистрация со скидкой): https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Прошивка и документация

Полный проект и даташиты: https://url.zeruns.com/AkHGU Код доступа: 6gzf
Открытый проект на LCSC: https://url.zeruns.com/46y43

main.c

#include <STC12C5A60S2.H>
#inклюет<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;

// Определение массива отображения 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,- // без десятичной точки
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,- // с десятичной точкой
unsigned char code dig3[3] = {0x76, 0x38, 0x40};                                                  // H, L, -

// Определение счётных переменных
unsigned int count = 0, count1 = 0; // значения счётчиков

// Верхний и нижний пределы температуры
uint8_t H_Set = 50;
uint8_t L_Set = 25;

// Определение перечисления режимов отображения
typedef enum
{
    H_mode = 0, // установка верхнего предела температуры
    L_mode,     // установка нижнего предела температуры
    T_mode,     // отображение температуры
} Display_MODE;

Display_MODE Display_mode = T_mode;

uint16_t temp;

// Инициализация таймера/счётчика
void Timer_Init()
{
    EA = 1;       // разрешить глобальные прерывания
    AUXR |= 0x80; // таймер0 в режиме 1T
    TMOD &= 0xF0; // очистить младшие 4 бита, установить 16-битный режим счётчика
    TMOD |= 0x01; // установить старшие 4 бита в режим таймера 0
    TL0 = 0xCD;   // установить начальное значение
    TH0 = 0xD4;   // установить начальное значение
    TF0 = 0;      // сбросить флаг TF0
    TR0 = 1;      // запустить таймер0
    ET0 = 1;      // разрешить прерывание таймера0

    AUXR &= 0xBF; // таймер1 в режиме 12T
    TMOD &= 0x0F; // установить режим таймера
    TMOD |= 0x10; // установить режим таймера
    TL1 = 0x00;   // установить начальное значение
    TH1 = 0xB8;   // установить начальное значение
    TF1 = 0;      // сбросить флаг TF1
    TR1 = 1;      // запустить таймер1
    ET1 = 1;      // разрешить прерывание таймера1
}

// Обработчик прерывания таймера/счётчика0
void Timer0_Isr(void) interrupt 1
{
    TL0 = 0xCD; // установить начальное значение
    TH0 = 0xD4; // установить начальное значение
    count++;    // каждую 1 мс увеличиваем счётчик
    count1++;
}

void Timer1_Isr(void) interrupt 3
{
    TL1 = 0x00; // установить начальное значение
    TH1 = 0xB8; // установить начальное значение
    key_status_check(0, KEY1);
    key_status_check(1, KEY2);
    key_status_check(2, KEY3);
    key_status_check(3, KEY4);
}

void PWMInit()
{
    // конфигурация ШИМ
    CCON = 0; // инициализировать регистр управления PCA
              // остановить таймер PCA
              // сбросить флаг CF
              // сбросить все флаги прерываний модулей
    CL = 0;   // сбросить базовый таймер PCA
    CH = 0;
    CMOD = 0x00;            // установить такт PCA на частоту кварца/12, запретить прерывание переполнения
    CCAP0H = CCAP0L = 0x80; // вывод PWM0: прямоугольный сигнал с коэффициентом заполнения 50%
    CCAPM0 = 0x42;          // включить компаратор и PWM0
    AUXR1 |= 0x40;          // переключить вывод ШИМ на P4

    CR = 1; // запустить таймер PCA
}
// https://blog.zeruns.com
void SetPwmDutyCycle(unsigned char dutyCycle)
{
    // dutyCycle может быть от 0 до 100, означает 0%-100% коэффициента заполнения
    unsigned char newValue = ((100 - dutyCycle) * 255) / 100;
    CCAP0H = CCAP0L = newValue; // обновить CCAP0L, изменив коэффициент заполнения
}

// Главная функция
void main()
{
    TM_WrCmd(0x21); // установить TM1650 в режим 8 сегментов × 4 разряда, включить дисплей, яркость 2
    Timer_Init();   // инициализировать таймер
    Key_Init();     // инициализировать конечный автомат кнопок
    P4M0 = 0x04;    // установить P4.2 как push-pull выход
    P4M1 = 0x00;
    PWMInit();          // инициализировать ШИМ
    SetPwmDutyCycle(0); // установить коэффициент заполнения 0
    temp = GetTemp();
// https://blog.zeruns.com
    while (1) // бесконечный цикл
    {
        if (count >= 100) // каждые 100 мс
        {
            count = 0;
            temp = GetTemp();           // считать температуру
            if (Display_mode == T_mode) // режим отображения температуры
            {
                TM_WrDat(0x68, dig1[temp / 1000]);     // данные для 1-го разряда
                TM_WrDat(0x6a, dig2[temp / 100 % 10]); // данные для 2-го разряда
                TM_WrDat(0x6c, dig1[temp / 10 % 10]);  // данные для 3-го разряда
                TM_WrDat(0x6e, dig1[temp % 10]);       // данные для 4-го разряда
            }
            if (Display_mode == H_mode) // установка верхнего предела
            {
                TM_WrDat(0x68, dig3[0]);          // на 1-м разряде H
                TM_WrDat(0x6a, dig3[2]);          // на 2-м разряде -
                TM_WrDat(0x6c, dig1[H_Set / 10]); // данные для 3-го разряда
                TM_WrDat(0x6e, dig1[H_Set % 10]); // данные для 4-го разряда
            }
            else if (Display_mode == L_mode) // установка нижнего предела
            {
                TM_WrDat(0x68, dig3[1]);          // на 1-м разряде L
                TM_WrDat(0x6a, dig3[2]);          // на 2-м разряде -
                TM_WrDat(0x6c, dig1[L_Set / 10]); // данные для 3-го разряда
                TM_WrDat(0x6e, dig1[L_Set % 10]); // данные для 4-го разряда
            }

            if (temp / 100 >= L_Set && temp / 100 < H_Set) // если температура между нижним и верхним пределом, регулировать ШИМ вентилятора
            {
                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) // при превышении верхнего предела вентилятор на полной скорости
            {
                SetPwmDutyCycle(100); // коэффициент заполнения 100%
            }
            else if (temp / 100 < L_Set) // при температуре ниже нижнего предела вентилятор выключен
            {
                SetPwmDutyCycle(0);
            }

            LED2 = ~LED2;
        }
        if (count1 >= 500) // каждые 500 мс
        {
            count1 = 0;
            LED3 = ~LED3;
        }
        if (key[0] == 1) // SW3 переключение режима
        {
            if (Display_mode != 2)
            {
                Display_mode++;
            }
            else
            {
                Display_mode = 0;
            }
            key[0] = 0;
        }
        if (key[1] == 1) // SW4 кнопка вверх
        {
            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 кнопка вниз
        {
            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

// Определение выводов TM1650
sbit SCL_T = P2 ^ 0; // тактовый сигнал
sbit SDA_T = P2 ^ 1; // данные

// Функция задержки
void Delay5us_TM() //@11.0592MHz
{
    unsigned char i;

    _nop_();
    _nop_();
    _nop_();
    i = 10;
    while (--i)
        ;
}
void Delay1us_TM() //@11.0592MHz
{
    _nop_();
}

// Стартовый бит TM1650
void TM_Start()
{
    SCL_T = 1;
    SDA_T = 1;
    Delay5us_TM();
    SDA_T = 0;
}

// Стоп-бит TM1650
void TM_Stop()
{
    SCL_T = 1;
    SDA_T = 0;
    Delay5us_TM();
    SDA_T = 1;
}

// Сигнал подтверждения 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;
}

// Записать байт в шину
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;
    }
}

// Записать данные в TM1650
void TM_WrDat(unsigned char add, unsigned char dat)
{
    TM_Start();
    Write_TM_Byte(add); // адрес памяти дисплея
    TM_Ack();
    Write_TM_Byte(dat); // данные для отображения
    TM_Ack();
    TM_Stop();
}

// Записать команду в TM1650
void TM_WrCmd(unsigned char Bri)
{
    TM_Start();
    Write_TM_Byte(0x48); // режим дисплея
    TM_Ack();
    Write_TM_Byte(Bri); // управление яркостью
    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

// Пин данных DS18B20
sbit DS = P2 ^ 2;

// Функция задержки, микросекунды
void delay_us(uchar us)
{
    while (us--)
    {
        _nop_();
    }
}

// https://blog.zeruns.com

// Инициализация DS18B20, возвращает 0 — успех, 1 — ошибка
bit DS18B20_Init()
{
    bit i;
    DS = 1; // Освободить шину
    _nop_();
    DS = 0; // Установить низкий уровень ≥480 мкс, сброс DS18B20
    delay_us(480);
    DS = 1;       // Освободить шину
    delay_us(20); // Подождать 15–60 мкс
    i = DS;       // Считать сигнал присутствия: 0 — датчик есть, 1 — нет
    delay_us(70); // Подождать 60–240 мкс
    DS = 1;       // Освободить шину
    _nop_();
    _nop_();
    return (i);
}

// Записать байт в DS18B20
void DSWriteByte(uchar dat)
{
    uchar i;
    for (i = 0; i < 8; i++)
    {
        DS = 0; // Начать временной слот
        _nop_();
        _nop_();
        DS = dat & 0x01; // Записать младший бит
        delay_us(60);    // Удерживать ≥60 мкс
        DS = 1;          // Освободить шину
        _nop_();
        _nop_();
        dat >>= 1; // Сдвинуть байт
    }
}

// Прочитать байт из DS18B20
uchar DSReadByte()
{
    uchar i, dat, j;
    for (i = 0; i < 8; i++)
    {
        DS = 0; // Начать временной слот
        _nop_();
        _nop_();
        DS = 1; // Освободить шину
        _nop_();
        _nop_();
        j = DS;       // Считать младший бит
        delay_us(60); // Удерживать ≥60 мкс
        DS = 1;       // Освободить шину
        _nop_();
        _nop_();
        dat = (j << 7) | (dat >> 1); // Сдвинуть данные
    }
    return (dat);
}

// Получить температуру, возвращает значение ×100 (°C)
int GetTemp()
{
    uchar L, H;
    int temp;
    DS18B20_Init();    // Инициализация
    DSWriteByte(0xcc); // Пропустить ROM
    DSWriteByte(0x44); // Начать преобразование
    DS18B20_Init();    // Инициализация
    DSWriteByte(0xcc); // Пропустить ROM
    DSWriteByte(0xbe); // Читать SCRATCHPAD
    L = DSReadByte();  // Младший байт
    H = DSReadByte();  // Старший байт
    temp = H;
    temp <<= 8;
    temp |= L;                        // Объединить
    temp = temp * 0.0625 * 100 + 0.5; // Перевести в °C×100
    return (temp);                    // Например, 25.6 °C → 256
}

DS18B20.h

#ifndef __DS18B20_H_
#define __DS18B20_H_

bit DS18B20_Init();
int GetTemp();

#endif

Key.c

#include "Key.h"

// Перечисление состояний кнопки
typedef enum
{
	KS_RELEASE = 0, // Отпущена
	KS_SHAKE,		// Дребезг
	KS_PRESS,		// Устойчиво нажата
} KEY_STATUS;

// Индексы в массиве состояний
#define g_keyStatus 0
#define g_nowKeyStatus 1
#define g_lastKeyStatus 2

uint8_t KEY_Status[4][3]; // Состояния кнопок
uint8_t key[4];			  // Флаг устойчивого нажатия (1 — нажата, 0 — нет)

// 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;
}

// Конечный автомат кнопки
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>

/* Пины кнопок */
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]; // Состояния кнопок
extern uint8_t key[4];           // Флаг нажатия

void Key_Init(void);
void key_status_check(uint8_t key_num, uint8_t KEY);

#endif

Другие открытые проекты

Рекомендуем к прочтению