Минимальная системная плата 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
Верхний слой:
Нижний слой:
Где купить компоненты
- Набор резисторов 0805: https://s.click.taobao.com/oWjIgGu
- Набор конденсаторов 0805: https://s.click.taobao.com/r9ea1Hu
- Общекатодный индикатор: https://u.jd.com/1ir7YWC
- Микросхема TM1650: https://s.click.taobao.com/pYveTFu
- Датчик DS18B20: https://s.click.taobao.com/zaNQqFu
Рекомендуемый магазин — 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
Другие открытые проекты
- Нарисовал минимальную плату MSP430F149 и выложил в открытый доступ: https://blog.zeruns.com/archives/713.html
- Минимальная система STM32F030C8T6 и «бегущий огонь» (схема и PCB): https://blog.zeruns.com/archives/715.html
- SY8205 — синхронный понижающий DC-DC модуль (схема и PCB): https://blog.zeruns.com/archives/717.html
- Национальное соревнование 2011: параллельная система питания на базе модулей коммутационного источника: https://blog.zeruns.com/archives/718.html
- Задание 2007 года: повышающий DC-DC 30–36 В на UC3843: https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
Рекомендуем к прочтению
- Доступные VPS/облачные серверы с лучшим соотношением цена-качество: https://blog.vpszj.cn/archives/41.html
- Как создать личный блог: https://blog.zeruns.com/archives/218.html
- Руководство по созданию сервера Minecraft: https://blog.zeruns.com/tag/mc/
- STM32 + датчик SHT3x (температура/влажность): https://blog.zeruns.com/archives/700.html
- Заменяем Keil на VS Code для разработки под STM32 и 51-серию: https://blog.zeruns.com/archives/690.html
- Обзор японского VPS от Zhiyun: 1 ядро, 1 ГБ, 10 Мбит/с, 5 Гбит DDoS за 29.4 юаня/мес: https://blog.vpszj.cn/archives/1749.html
- Тест высокозащищённого облака YuYun на Gold 6146 в Суянь: https://blog.vpszj.cn/archives/1725.html




