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

其他开源项目推荐

推荐阅读

最后修改:2023 年 06 月 09 日
如果您觉得我的文章有帮助,请随意赞赏,赞赏有助于激发博主的热情,感谢!