Bo mạch tối thiểu STC12C5A60S2/Quạt điều khiển nhiệt độ 8051, tích hợp sẵn DS18B20 và TM1650 + đèn 7 đoạn 4 chữ số.
Nhóm trao đổi kỹ thuật điện tử/vi điều khiển: 2169025065
Giới thiệu dự án
Đồ án môn học vi điều khiển: làm quạt thông minh điều khiển theo nhiệt độ. Khi nhiệt độ đạt ngưỡng dưới thì bật quạt; nằm giữa ngưỡng trên-dưới thì điều chỉnh tốc độ quạt theo tỷ lệ PWM; vượt ngưỡng trên thì chạy tối đa.
Sử dụng vi điều khiển STC12C5A60S2, cảm biến nhiệt độ DS18B20, đèn 7 đoạn do chip TM1650 điều khiển, giao tiếp I²C với TM1650.
Dự án mở này có thể dùng luôn như bo tối thiểu cho STC12C5A60S2, toàn bộ chân I/O đã được đưa ra ngoài.
Giới thiệu STC12C5A60S2
Dòng STC12C5A60S2 là vi điều khiển 1 chu kồ/1 chu kỳ máy (1T) do Macrochip sản xuất. Là thế hệ 8051 mới tốc độ cao/tiêu thụ thấp/kháng nhiễu mạnh, mã lệnh hoàn toàn tương thích 8051 truyền thống nhưng nhanh hơn 8-12 lần. Tích hợp sẵn mạch reset chuyên dụng MAX810, 2 kênh PWM, 8 kênh ADC 10-bit tốc độ cao (250 kS/s), phù hợp điều khiển động cơ, môi trường nhiễu mạnh.
TM1650
TM1650 là mạch chuyên dụng điều khiển LED (đèn 7 đoạn) có giao diện quét phím. Bên trong tích hợp MCU giao tiếp số, bộ khóa dữ liệu, trình điều khiển LED, quét phím, điều chỉnh độ sáng. TM1650 ổn định, độ tin cậy cao, kháng nhiễu tốt, có thể hoạt động 24/7.
- Hai chế độ hiển thị: 8 đoạn × 4 chữ số hoặc 7 đoạn × 4 chữ số
- Hỗ trợ 28 phím đơn (7×4 bit) và 4 phím tổ hợp
- 8 mức độ sáng
- Dòng đoạn >25 mA, dòng vị trí >150 mA
- Giao tiếp nhanh 2 dây (CLK, DAT)
- Dao động: RC nội
- Mạch reset nguồn nội
- Bộ khóa dữ liệu nội
- Điện áp 3-5,5 V
- Kháng nhiễu mạnh
- Đóng gói DIP16/SOP16
Ảnh thực tế
Sơ đồ nguyên lý
PCB
Lớp trên:
Lớp dưới:
Link mua linh kiện
- Mẫu điện trở 0805: https://s.click.taobao.com/oWjIgGu
- Mẫu tụ 0805: https://s.click.taobao.com/r9ea1Hu
- Đèn 7 đoạn chung âm: https://u.jd.com/1ir7YWC
- Chip TM1650: https://s.click.taobao.com/pYveTFu
- Chip DS18B20: https://s.click.taobao.com/zaNQqFu
Khuyến nghị mua linh kiện tại LCSC, đăng ký ưu đãi: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
Mã nguồn & tài liệu
Link tải toàn bộ project và datasheet: https://url.zeruns.com/AkHGU Mã giải nén: 6gzf
Link mở trên nền tảng LCSC: https://url.zeruns.com/46y43
main.c
#include <STC12C5A60S2.H>
#include<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;
// Định nghĩa mảng hiển thị của 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、-//không có dấu thập phân
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、-//có dấu thập phân
unsigned char code dig3[3] = {0x76, 0x38, 0x40}; // H、L、-
// Định nghĩa biến đếm
unsigned int count = 0, count1 = 0; // Giá trị đếm
// Giá trị giới hạn trên và dưới của nhiệt độ
uint8_t H_Set = 50;
uint8_t L_Set = 25;
// Định nghĩa kiểu biến enum cho chế độ hiển thị
typedef enum
{
H_mode = 0, // Thiết lập nhiệt độ giới hạn trên
L_mode, // Thiết lập nhiệt độ giới hạn dưới
T_mode, // Hiển thị nhiệt độ
} Display_MODE;
Display_MODE Display_mode = T_mode;
uint16_t temp;
// Khởi tạo bộ định thời/bộ đếm
void Timer_Init()
{
EA = 1; // Cho phép ngắt toàn cục
AUXR |= 0x80; // Chế độ 1T cho timer 0
TMOD &= 0xF0; // Xóa 4 bit thấp, đặt chế độ bộ đếm 16 bit
TMOD |= 0x01; // Đặt 4 bit cao là chế độ định thời 0
TL0 = 0xCD; // Đặt giá trị định thời ban đầu
TH0 = 0xD4; // Đặt giá trị định thời ban đầu
TF0 = 0; // Xóa cờ TF0
TR0 = 1; // Bắt đầu đếm timer 0
ET0 = 1; // Cho phép ngắt timer 0
AUXR &= 0xBF; // Chế độ 12T cho timer 1
TMOD &= 0x0F; // Đặt chế độ định thời
TMOD |= 0x10; // Đặt chế độ định thời
TL1 = 0x00; // Đặt giá trị định thời ban đầu
TH1 = 0xB8; // Đặt giá trị định thời ban đầu
TF1 = 0; // Xóa cờ TF1
TR1 = 1; // Bắt đầu đếm timer 1
ET1 = 1; // Cho phép ngắt timer 1
}
// Hàm phục vụ ngắt bộ định thời/bộ đếm 0
void Timer0_Isr(void) interrupt 1
{
TL0 = 0xCD; // Đặt giá trị định thời ban đầu
TH0 = 0xD4; // Đặt giá trị định thời ban đầu
count++; // Mỗi 1 ms tăng biến đếm lên 1
count1++;
}
void Timer1_Isr(void) interrupt 3
{
TL1 = 0x00; // Đặt giá trị định thời ban đầu
TH1 = 0xB8; // Đặt giá trị định thời ban đầu
key_status_check(0, KEY1);
key_status_check(1, KEY2);
key_status_check(2, KEY3);
key_status_check(3, KEY4);
}
void PWMInit()
{
// Cấu hình PWM
CCON = 0; // Khởi tạo thanh ghi điều khiển PCA
// Dừng PCA timer
// Xóa cờ CF
// Xóa tất cả cờ ngắt module
CL = 0; // Reset PCA base timer
CH = 0;
CMOD = 0x00; // Đặt clock PCA timer là tần số dao động/12, vô hiệu ngắt tràn PCA
CCAP0H = CCAP0L = 0x80; // PWM0 xuất ra xung vuông chu kỳ 50%
CCAPM0 = 0x42; // Bật chức năng so sánh, bật PWM0
AUXR1 |= 0x40; // Chuyển xuất PWM sang P4
CR = 1; // PCA timer bắt đầu chạy
}
// https://blog.zeruns.com
void SetPwmDutyCycle(unsigned char dutyCycle)
{
// dutyCycle có thể từ 0 đến 100, biểu thị tỷ lệ 0% đến 100%.
unsigned char newValue = ((100 - dutyCycle) * 255) / 100;
CCAP0H = CCAP0L = newValue; // Cập nhật CCAP0L, thay đổi tỷ lệ xung PWM
}
// Hàm main
void main()
{
TM_WrCmd(0x21); // Đặt TM1650 chế độ 8 đoạn×4 chữ số, bật hiển thị, độ sáng mức 2
Timer_Init(); // Khởi tạo bộ định thời
Key_Init(); // Khởi tạo trạng thái phím
P4M0 = 0x04; // Đặt P4.2 là đầu ra push-pull
P4M1 = 0x00;
PWMInit(); // Khởi tạo PWM
SetPwmDutyCycle(0); // Đặt tỷ lệ xung PWM là 0
temp = GetTemp();
// https://blog.zeruns.com
while (1) // Vòng lặp vô hạn, lặp lại các thao tác sau
{
if (count >= 100) // Mỗi 100 ms thực hiện một lần
{
count = 0;
temp = GetTemp(); // Đọc nhiệt độ
if (Display_mode == T_mode) // Chế độ hiển thị nhiệt độ
{
TM_WrDat(0x68, dig1[temp / 1000]); // Ghi dữ liệu hiển thị chữ số thứ 1
TM_WrDat(0x6a, dig2[temp / 100 % 10]); // Ghi dữ liệu hiển thị chữ số thứ 2
TM_WrDat(0x6c, dig1[temp / 10 % 10]); // Ghi dữ liệu hiển thị chữ số thứ 3
TM_WrDat(0x6e, dig1[temp % 10]); // Ghi dữ liệu hiển thị chữ số thứ 4
}
if (Display_mode == H_mode) // Thiết lập nhiệt độ giới hạn trên
{
TM_WrDat(0x68, dig3[0]); // Chữ số thứ 1 hiển thị H
TM_WrDat(0x6a, dig3[2]); // Chữ số thứ 2 hiển thị -
TM_WrDat(0x6c, dig1[H_Set / 10]); // Ghi dữ liệu hiển thị chữ số thứ 3
TM_WrDat(0x6e, dig1[H_Set % 10]); // Ghi dữ liệu hiển thị chữ số thứ 4
}
else if (Display_mode == L_mode) // Thiết lập nhiệt độ giới hạn dưới
{
TM_WrDat(0x68, dig3[1]); // Chữ số thứ 1 hiển thị L
TM_WrDat(0x6a, dig3[2]); // Chữ số thứ 2 hiển thị -
TM_WrDat(0x6c, dig1[L_Set / 10]); // Ghi dữ liệu hiển thị chữ số thứ 3
TM_WrDat(0x6e, dig1[L_Set % 10]); // Ghi dữ liệu hiển thị chữ số thứ 4
}
if (temp / 100 >= L_Set && temp / 100 < H_Set) // Khi nhiệt độ nằm giữa giới hạn dưới và trên, đặt tỷ lệ xung PWM cho quạt theo nhiệt độ
{
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) // Khi nhiệt độ lớn hơn giới hạn trên, quạt chạy hết tốc độ
{
SetPwmDutyCycle(100); // Đặt tỷ lệ xung 100%
}
else if (temp / 100 < L_Set) // Khi nhiệt độ nhỏ hơn giới hạn dưới, quạt tắt
{
SetPwmDutyCycle(0);
}
LED2 = ~LED2;
}
if (count1 >= 500) // Mỗi 500 ms thực hiện một lần
{
count1 = 0;
LED3 = ~LED3;
}
if (key[0] == 1) // SW3 chuyển chế độ
{
if (Display_mode != 2)
{
Display_mode++;
}
else
{
Display_mode = 0;
}
key[0] = 0;
}
if (key[1] == 1) // SW4 phím lên
{
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 phím xuống
{
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
// Định nghĩa chân TM1650
sbit SCL_T = P2 ^ 0; // Xung clock nối tiếp
sbit SDA_T = P2 ^ 1; // Dữ liệu nối tiếp
// Định nghĩa hàm trễ
void Delay5us_TM() //@11.0592MHz
{
unsigned char i;
_nop_();
_nop_();
_nop_();
i = 10;
while (--i)
;
}
void Delay1us_TM() //@11.0592MHz
{
_nop_();
}
// Bit bắt đầu TM1650
void TM_Start()
{
SCL_T = 1;
SDA_T = 1;
Delay5us_TM();
SDA_T = 0;
}
// Bit kết thúc TM1650
void TM_Stop()
{
SCL_T = 1;
SDA_T = 0;
Delay5us_TM();
SDA_T = 1;
}
// Tín hiệu ACK 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;
}
// Viết một byte qua 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;
}
}
// Viết dữ liệu TM1650
void TM_WrDat(unsigned char add, unsigned char dat)
{
TM_Start();
Write_TM_Byte(add); // Địa chỉ bộ nhớ hiển thị
TM_Ack();
Write_TM_Byte(dat); // Dữ liệu hiển thị
TM_Ack();
TM_Stop();
}
// Viết lệnh TM1650
void TM_WrCmd(unsigned char Bri)
{
TM_Start();
Write_TM_Byte(0x48); // Chế độ hiển thị
TM_Ack();
Write_TM_Byte(Bri); // Điều khiển độ sáng
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
// Chân dữ liệu DS18B20
sbit DS = P2 ^ 2;
// Hàm trễ, đơn vị micro giây
void delay_us(uchar us)
{
while (us--)
{
_nop_();
}
}
// https://blog.zeruns.com
// Khởi tạo DS18B20, trả về 0 nếu thành công, 1 nếu thất bại
bit DS18B20_Init()
{
bit i;
DS = 1; // Giải phóng bus
_nop_();
DS = 0; // Kéo bus xuống thấp ít nhất 480us, reset DS18B20
delay_us(480);
DS = 1; // Giải phóng bus
delay_us(20); // Chờ 15~60us
i = DS; // Đọc tín hiệu hiện diện của DS18B20, 0 = có mặt, 1 = không có mặt
delay_us(70); // Chờ 60~240us
DS = 1; // Giải phóng bus
_nop_();
_nop_();
return (i);
}
// Ghi một byte vào DS18B20
void DSWriteByte(uchar dat)
{
uchar i;
for (i = 0; i < 8; i++)
{
DS = 0; // Kéo bus xuống tạo chu kỳ ghi
_nop_();
_nop_();
DS = dat & 0x01; // Ghi bit thấp nhất
delay_us(60); // Duy trì chu kỳ ghi ít nhất 60us
DS = 1; // Giải phóng bus
_nop_();
_nop_();
dat >>= 1; // Dịch phải 1 bit, chuẩn bị ghi bit tiếp
}
}
// Đọc một byte từ DS18B20
uchar DSReadByte()
{
uchar i, dat, j;
for (i = 0; i < 8; i++)
{
DS = 0; // Kéo bus xuống tạo chu kỳ đọc
_nop_();
_nop_();
DS = 1; // Giải phóng bus
_nop_();
_nop_();
j = DS; // Đọc bit thấp nhất
delay_us(60); // Duy trì chu kỳ đọc ít nhất 60us
DS = 1; // Giải phóng bus
_nop_();
_nop_();
dat = (j << 7) | (dat >> 1); // Dịch trái 1 bit, lưu bit vừa đọc vào MSB của dat
}
return (dat);
}
// Lấy dữ liệu nhiệt độ DS18B20, giá trị trả về = nhiệt độ ×100, đơn vị độ C
int GetTemp()
{
uchar L, H;
int temp;
DS18B20_Init(); // Khởi tạo DS18B20
DSWriteByte(0xcc); // Gửi lệnh bỏ qua ROM, bỏ qua bước khớp địa chỉ
DSWriteByte(0x44); // Gửi lệnh chuyển đổi nhiệt độ, bắt đầu đo và lưu kết quả vào bộ nhớ tạm
DS18B20_Init(); // Khởi tạo DS18B20
DSWriteByte(0xcc); // Gửi lệnh bỏ qua ROM
DSWriteByte(0xbe); // Gửi lệnh đọc bộ nhớ tạm, chuẩn bị đọc dữ liệu nhiệt độ
L = DSReadByte(); // Đọc byte thấp
H = DSReadByte(); // Đọc byte cao
temp = H;
temp <<= 8;
temp |= L; // Ghép byte cao-thấp thành số 16 bit, mỗi 4 bit tương ứng 1 chữ số hexa, ví dụ 0000010110100000 = 01A0H
temp = temp * 0.0625 * 100 + 0.5; // Chuyển số nhị phân sang thập phân, nhân độ phân giải (mặc định 0,0625 °C), nhân 10 để giữ 1 chữ số thập phân và làm tròn
return (temp); // Trả về dữ liệu nhiệt độ, ví dụ 25,6 °C ứng với 256
}
DS18B20.h
#ifndef __DS18B20_H_
#define __DS18B20_H_
bit DS18B20_Init();
int GetTemp();
#endif
Key.c
#include "Key.h"
// Định nghĩa kiểu enum trạng thái phím
typedef enum
{
KS_RELEASE = 0, // Phím nhả
KS_SHAKE, // Nhấp nháy
KS_PRESS, // Giữ ổn định
} KEY_STATUS;
// Trạng thái kết thúc vòng lặp hiện tại (máy trạng thái)
#define g_keyStatus 0
// Trạng thái hiện tại (sau mỗi vòng lặp đồng bộ với g_keyStatus)
#define g_nowKeyStatus 1
// Trạng thái trước (dùng để phân biệt nguồn gốc trạng thái)
#define g_lastKeyStatus 2
uint8_t KEY_Status[4][3]; // Lưu trạng thái từng phím
uint8_t key[4]; // Lưu phím đã ổn định, 1 = đã nhấn, 0 = chưa nhấn
// 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;
} // Máy trạng thái phím khởi tạo ở trạng thái nhả
KEY1 = KEY2 = KEY3 = KEY4 = 1;
}
// Chương trình máy trạng thái phím
void key_status_check(uint8_t key_num, uint8_t KEY)
{
switch (KEY_Status[key_num][g_keyStatus])
{
// Phím đang nhả (trạng thái ban đầu)
case KS_RELEASE:
{
// Phát hiện mức thấp, tiến hành chống rung
if (KEY == 0)
{
KEY_Status[key_num][g_keyStatus] = KS_SHAKE;
}
}
break;
// Nhấp nháy
case KS_SHAKE:
{
if (KEY == 1)
{
KEY_Status[key_num][g_keyStatus] = KS_RELEASE;
}
else
{
KEY_Status[key_num][g_keyStatus] = KS_PRESS;
}
}
break;
// Giữ ổn định
case KS_PRESS:
{
// Phát hiện mức cao, tiến hành chống rung
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])
{
// Trạng thái hiện tại là nhả và trạng thái trước là nhấn
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>
/*Định nghĩa IO phím*/
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]; // Lưu trạng thái từng phím
extern uint8_t key[4]; // Lưu phím đã ổn định, 1 = đã nhấn, 0 = chưa nhấn
void Key_Init(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
#endif
Các dự án mã nguồn mở khác đề xuất
- Vẽ mạch tối thiểu MSP430F149 và mở nguồn: https://blog.zeruns.com/archives/713.html
- STM32F030C8T6 mạch tối thiểu và đèn chạy (sơ đồ nguyên lý & PCB): https://blog.zeruns.com/archives/715.html
- Mô-đun nguồn DCDC hạ áp đồng bộ SY8205 (sơ đồ & PCB): https://blog.zeruns.com/archives/717.html
- Đề thi điện toàn quốc 2011 – Hệ thống cấp nguồn mô-đun song song: https://blog.zeruns.com/archives/718.html
- Đề nguồn 2007: mô-đun DCDC nâng áp 30–36 V (UC3843): https://oshwhub.com/zeruns/36v-sheng-ya-dcdc-mo-kuai-uc3842
Đề xuất đọc thêm
- VPS/cloud server giá rẻ, hiệu năng cao: https://blog.vpszj.cn/archives/41.html
- Cách dựng blog cá nhân: https://blog.zeruns.com/archives/218.html
- Hướng dẫn cài server Minecraft: https://blog.zeruns.com/tag/mc/
- STM32 đọc cảm biến nhiệt độ/độ ẩm SHT3x: https://blog.zeruns.com/archives/700.html
- Dùng VSCode thay Keil lập trình STM32 và 51: https://blog.zeruns.com/archives/690.html
- Đánh giá VPS Zhidian Nhật Bản – 1C/1G 10 Mbps 5 Gbps DDoS chỉ 29,4 ¥/tháng: https://blog.vpszj.cn/archives/1749.html
- Đánh giá server áp Gold 6146 YuYun – Suqian: https://blog.vpszj.cn/archives/1725.html




