STC12C5A60S2最小系统板/51单片机温度显示和温度控制风扇,板载有DS18B20和TM1650+4位数码管。
项目简介
单片机课课程设计,要做一个智能温控风扇,达到设定的下限温度值就开启风扇,温度在上限和下限之间就按比例输出PWM占空比控制风扇,超过上限值就风扇全速。
使用STC12C5A60S2单片机,温度传感器用的DS18B20,数码管用TM1650芯片驱动,通过I2C与TM1650通信。
这个开源项目也可以直接当STC12C5A60S2最小系统,所有IO口都引出了。
STC12C5A60S2简介
STC12C5A60S2系列单片机是宏晶科技生产的单时钟/机器周期(1T)的单片机。它是高速/低功耗/超强抗干扰的新一代8051单片机,指令代码完全兼容传统8051,但速度快8-12倍。内部集成MAX810专用复位电路,2路PWM,8路高速10位A/D转换(250K/S),针对电机控制,强干扰场合。
TM1650
TM1650 是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路。内部集成有MCU输入输出控制数字接口、数据锁存器、LED 驱动、键盘扫描、辉度调节等电路。TM1650 性能稳定、质量可靠、抗干扰能力强,可适用于24 小时长期连续工作的应用场合。
- 两种显示模式:8段×4位和7段×4位
- 支持单个按键7x4bit(28个按键)和组合按键(4个)
- 8级亮度可调
- 段驱动电流大于25mA,位驱动电流大于150mA
- 高速2线串行接口(CLK,DAT)
- 振荡方式:内置RC振荡
- 内置上电复位电路
- 内置数据锁存电路
- 支持3-5.5V电源电压
- 抗干扰能力强
- 提供DIP16及SOP16封装
实物图
原理图
PCB
顶层:
底层:
元件购买地址
元器件购买推荐立创商城,优惠注册链接:https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html
代码和资料
完整工程和各芯片数据手册的下载地址:https://url.zeruns.tech/AkHGU 提取码:6gzf
立创开源平台开源链接:https://url.zeruns.tech/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;
// 定义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; // 清零低四位,设置为16位计数器模式
TMOD |= 0x01; // 设置高四位为定时器模式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毫秒,计数值加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()
{
// 配置PWM
CCON = 0; // Initial PCA control register
// PCA timer stop running
// Clear CF flag
// Clear all module interrupt flag
CL = 0; // Reset PCA base timer
CH = 0;
CMOD = 0x00; // 设置PCA定时器时钟为晶振频率/12,禁用PCA计数器溢出中断
CCAP0H = CCAP0L = 0x80; // PWM0 port output 50% duty cycle square wave
CCAPM0 = 0x42; // 开启比较器功能,开启PWM0
AUXR1 |= 0x40; // 切换PWM输出IO到P4
CR = 1; // PCA timer start run
}
// https://blog.zeruns.tech
void SetPwmDutyCycle(unsigned char dutyCycle)
{
// dutyCycle的范围可以是0到100,表示0%至100%的占空比。
unsigned char newValue = ((100 - dutyCycle) * 255) / 100;
CCAP0H = CCAP0L = newValue; // 更新CCAP0L,从而改变PWM信号占空比
}
// 主函数
void main()
{
TM_WrCmd(0x21); // 设置TM1650为8段×4位模式,并打开显示,亮度为2级
Timer_Init(); // 初始化定时器
Key_Init(); // 初始化按键状态机
P4M0 = 0x04; // 设置P4.2为推挽输出
P4M1 = 0x00;
PWMInit(); // 初始化PWM
SetPwmDutyCycle(0); // 设置PWM占空比为0
temp = GetTemp();
// https://blog.zeruns.tech
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) // 当温度在下限和上限之间,根据温度设置风扇PWM占空比
{
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.tech
// 定义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.tech
// 初始化DS18B20,返回0表示成功,返回1表示失败
bit DS18B20_Init()
{
bit i;
DS = 1; // 释放总线
_nop_();
DS = 0; // 拉低总线至少480us,复位DS18B20
delay_us(480);
DS = 1; // 释放总线
delay_us(20); // 等待15~60us
i = DS; // 读取DS18B20的存在信号,0表示存在,1表示不存在
delay_us(70); // 等待60~240us
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); // 维持写时序至少60us
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); // 维持读时序至少60us
DS = 1; // 释放总线
_nop_();
_nop_();
dat = (j << 7) | (dat >> 1); // 左移一位,将读取的位存入dat的最高位
}
return (dat);
}
// 获取DS18B20的温度数据,返回值为温度乘以100的结果,单位为摄氏度
int GetTemp()
{
uchar L, H;
int temp;
DS18B20_Init(); // 初始化DS18B20
DSWriteByte(0xcc); // 发送跳过ROM指令,忽略地址匹配过程
DSWriteByte(0x44); // 发送温度转换指令,开始测量温度并将结果存入暂存器中
DS18B20_Init(); // 初始化DS18B20
DSWriteByte(0xcc); // 发送跳过ROM指令,忽略地址匹配过程
DSWriteByte(0xbe); // 发送读取暂存器指令,准备读取温度数据
L = DSReadByte(); // 先读取低字节数据
H = DSReadByte(); // 再读取高字节数据
temp = H;
temp <<= 8;
temp |= L; // 合并高低字节数据,得到16位二进制数,每4位对应一个十六进制数,如0000010110100000对应01A0H
temp = temp * 0.0625 * 100 + 0.5; // 将二进制数转换为十进制数,并乘以分辨率(默认为0.0625摄氏度),再乘以10以便保留一位小数,并四舍五入
return (temp); // 返回温度数据,如25.6摄氏度对应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
// 当前状态(每次循环后与g_keyStatus保持一致)
#define g_nowKeyStatus 1
// 上次状态(用于记录前一状态以区分状态的来源)
#define g_lastKeyStatus 2
uint8_t KEY_Status[4][3]; // 记录各按键状态
uint8_t key[4]; // 记录各按键是否稳定按下,1表示按键已按下,0表示按键没被按下
// https://blog.zeruns.tech
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>
/*定义按键IO*/
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]; // 记录各按键是否稳定按下,1表示按键已按下,0表示按键没被按下
void Key_Init(void);
void key_status_check(uint8_t key_num, uint8_t KEY);
#endif
其他开源项目推荐
推荐阅读