目录
1.设计背景:
基于51单片机的电风扇设计是一种智能化的电风扇系统,旨在利用单片机的数字信号处理和可编程控制能力,实现电风扇的自动调速、定时开关等功能。该设计可以通过温度传感器、湿度传感器等环境监测模块获取实时环境数据,从而根据环境条件自动控制电风扇的转速和开关状态,提高使用者的舒适度和便利性。此外,该设计还具有功率调节、故障检测等功能,为用户提供更加智能、安全、高效的电风扇体验。
2.设计清单:
本次设计十分简单,仅仅需要以下4个器件,以直流电机模块为主
序号 |
器件名称 |
数量 |
1 |
STC89C52开发板 |
1 |
2 |
数码管模块 |
1 |
3 |
直流电机模块 |
1 |
4 |
独立按键 |
1 |
5 | LED模块 | 1 |
3.设计要求:
3个独立按键分别控制“自然风”、“睡眠风”、“常风”,(三者的区别是直流电机的停歇时间不同),并在数显管上显示出区别每种类型风可以根据按下独立按键次数分为4个档的风力调节。设计风扇的过热保护,即当风扇运行一段时间后,暂停10秒。
系统总体设计
该系统以51单片机为核心控制器,通过按键接收用户指令,控制直流电机(风扇)的运行模式与转速,并通过数码管和LED指示灯显示当前状态。其核心是利用PWM(脉冲宽度调制)技术对直流电机进行调速,并通过编程实现不同的风模式。
4.核心功能与硬件引脚定义
实现了以下核心功能,其硬件连接大致如下:
功能模块 |
引脚连接 |
说明 |
---|---|---|
电机控制 |
|
通过三极管或电机驱动芯片(如L298N )控制直流电机。 |
蜂鸣器报警 |
|
用于过热保护时的声光报警。 |
模式按键 |
|
三个独立按键,分别选择自然风、睡眠风、常风模式。 |
数码管显示 |
|
使用三位数码管(通过74HC138等芯片进行位选),显示模式和档位。 |
模式指示灯 |
|
三个LED灯,直观指示当前运行模式。注意: 与数码管位选引脚存在冲突! |
5.关键代码逻辑深度解析
1. 风模式与档位定义
#define STOP_MODE 0
#define NATURAL_MODE 1
#define SLEEP_MODE 2
#define NORMAL_MODE 3
这是系统的状态标识,用不同的数字代表不同的工作模式,便于后续在 switch-case
语句中进行判断和控制。
2. 全局变量系统
unsigned char WindMode = STOP_MODE; // 核心状态机
unsigned char WindGear = 0; // 当前档位 (0停止, 1-4档)
unsigned int OverheatTimer = 0; // 过热保护累加计时器
unsigned char PwmCount = 0; // PWM周期内的时间切片
unsigned int SystemTick = 0; // 系统“心跳”,用于各种计时
unsigned char KeyPressCount[3] = {0}; // 记录各按键按下次数以实现循环调档
这些全局变量是程序的灵魂,记录了系统运行的所有关键状态。
3. 主函数流程
void main(void)
{
System_Init(); // 硬件初始化
Timer0_Init(); // 定时器初始化,开启中断
while(1) // 主循环
{
Key_Scan(); // 扫描按键
Motor_Control(); // 控制电机
Smg_Display(...); // 刷新显示
}
}
单片机程序通常采用前后台系统结构。main()
中的 while(1)
循环是后台(后台任务),不断轮询和执行各项任务。定时器中断则是前台(紧急任务),它会打断后台任务,以精确的时间间隔执行关键操作(如生成PWM)。
4. 按键扫描函数 (Key_Scan
)
void Key_Scan(void)
{
static unsigned char key_lock[3] = {0}; // 按键锁标志,用于消抖
unsigned char i;
// 自然风模式按键处理
if (KEY_NATURAL == 0)
{
if (key_lock[0] == 0 && SystemTick > 20) // 消抖检测
{
key_lock[0] = 1;
KeyPressCount[0]++;
if (KeyPressCount[0] > 4) KeyPressCount[0] = 1; // 档位循环1-4
if (WindMode != NATURAL_MODE)
{
WindMode = NATURAL_MODE;
WindGear = KeyPressCount[0];
}
else
{
WindGear = KeyPressCount[0];
}
OverheatTimer = 0; // 重置过热计时器
SystemTick = 0;
}
}
else
{
key_lock[0] = 0;
}
// 睡眠风模式按键处理 (逻辑同自然风)
if (KEY_SLEEP == 0)
{
if (key_lock[1] == 0 && SystemTick > 20)
{
key_lock[1] = 1;
KeyPressCount[1]++;
if (KeyPressCount[1] > 4) KeyPressCount[1] = 1;
if (WindMode != SLEEP_MODE)
{
WindMode = SLEEP_MODE;
WindGear = KeyPressCount[1];
}
else
{
WindGear = KeyPressCount[1];
}
OverheatTimer = 0;
SystemTick = 0;
}
}
else
{
key_lock[1] = 0;
}
// 常风模式按键处理 (逻辑同自然风)
if (KEY_NORMAL == 0)
{
if (key_lock[2] == 0 && SystemTick > 20)
{
key_lock[2] = 1;
KeyPressCount[2]++;
if (KeyPressCount[2] > 4) KeyPressCount[2] = 1;
if (WindMode != NORMAL_MODE)
{
WindMode = NORMAL_MODE;
WindGear = KeyPressCount[2];
}
else
{
WindGear = KeyPressCount[2];
}
OverheatTimer = 0;
SystemTick = 0;
}
}
else
{
key_lock[2] = 0;
}
}
这个函数采用软件消抖和状态锁的机制来检测按键。
if (KEY_NATURAL == 0)
: 检测按键是否按下(低电平有效)。if (key_lock[0] == 0 && SystemTick > 20)
: 这是消抖和状态锁的关键逻辑。key_lock
确保一次按下只触发一次动作;SystemTick > 20
(即200ms) 是一种简单的延时消抖,确保按键状态稳定。KeyPressCount[0]++
: 每次有效按下,该模式对应的按键次数加1,实现1-4档的循环切换。- 它同时处理了模式切换和档位调节:如果按下的不是当前模式,则切换到新模式并置为1档;如果是当前模式,则只循环调整档位。
5. 电机控制函数 (Motor_Control
)
void Motor_Control(void)
{
static unsigned int NaturalWindTimer = 0;
static unsigned int SleepWindTimer = 0;
static bit MotorState = 0;
// 过热保护检测:运行超过设定时间(例如30秒)后暂停10秒
if (OverheatTimer > 1000) // 假设10ms中断一次,3000次即为30秒
{
MOTOR_PIN = 0;
BUZZER_PIN = 0; // 蜂鸣器报警
Delay_ms(10000); // 暂停10秒 (实际应用中建议用计时器非阻塞方式)
MOTOR_PIN = 1;
BUZZER_PIN = 1; // 停止报警
OverheatTimer = 0; // 重置过热计时器
}
// 根据模式和档位控制电机
switch (WindMode)
{
case STOP_MODE:
MOTOR_PIN = 0; // 电机停止
LED_NATURAL = 1;
LED_SLEEP = 1;
LED_NORMAL = 1;
break;
case NATURAL_MODE:
LED_NATURAL = 0;
LED_SLEEP = 1;
LED_NORMAL = 1;
// 自然风: 随机启停,模拟自然风效果 (此处用定时器简单模拟)
NaturalWindTimer++;
if (NaturalWindTimer < (200 * WindGear)) // 转动时间随风速增加
{
MOTOR_PIN = 1;
}
else if (NaturalWindTimer < (400 * WindGear))
{
MOTOR_PIN = 0;
}
else
{
NaturalWindTimer = 0;
}
break;
case SLEEP_MODE:
LED_NATURAL = 1;
LED_SLEEP = 0;
LED_NORMAL = 1;
// 睡眠风: 长时间停顿,轻柔风
SleepWindTimer++;
if (SleepWindTimer < (50 * WindGear)) // 转动时间较短
{
MOTOR_PIN = 1;
}
else if (SleepWindTimer < (600 * WindGear)) // 停止时间较长
{
MOTOR_PIN = 0;
}
else
{
SleepWindTimer = 0;
}
break;
case NORMAL_MODE:
LED_NATURAL = 1;
LED_SLEEP = 1;
LED_NORMAL = 0;
// 常风: 持续转动,PWM调速
if (PwmCount < (WindGear * 20)) // 占空比随风速增加 (20%, 40%, 60%, 80%)
{
MOTOR_PIN = 1;
}
else
{
MOTOR_PIN = 0;
}
break;
default:
break;
}
}
这是实现不同风模式的核心。
- 自然风 (NATURAL_MODE):通过
NaturalWindTimer
循环计数,在一个大周期内控制电机转动一段时间,停止一段时间,模拟自然风的不规则特性。转动和停止的时间均随风速档位 (WindGear
) 的增加而增加。 - 睡眠风 (SLEEP_MODE):逻辑同自然风,但转动时间非常短,停止时间非常长,从而产生极其轻柔的风感。
- 常风 (NORMAL_MODE):基于PWM的经典调速方式。
PwmCount
在0-99循环。if (PwmCount < (WindGear * 20))
意味着:- 1档:占空比20% (
PwmCount < 20
) - 2档:占空比40% (
PwmCount < 40
) - ...以此类推,占空比越大,电机平均电压越高,转速越快。
- 1档:占空比20% (
- 过热保护:当
OverheatTimer > 1000
(即运行10秒后),触发保护。 - 注意: 我此代码中使用
Delay_ms(10000);
会导致主循环阻塞10秒,这是一处重大缺陷。
6. 定时器中断服务程序 (Timer0_ISR
)
void Timer0_ISR(void) interrupt 1
{
TH0 = ...; TL0 = ...; // 重装初值,保证下次中断仍是10ms
SystemTick++; // 系统时基,用于按键消抖等计时
PwmCount++; // PWM计数器,用于生成PWM波形
if (PwmCount >= 100) PwmCount = 0; // 保持PWM周期为100份
if (WindMode != STOP_MODE) OverheatTimer++; // 风扇运行则开始过热计时
}
定时器中断是系统的心跳。每10ms进入一次,用于更新PWM和控制各种计时变量。重装初值是必须的,否则下次中断时间会错乱。
7. 数码管显示函数 (Smg_Display
)
void Smg_Display(unsigned char mode, unsigned char gear)
{
// 简化显示:第一位显示模式(N-自然, S-睡眠, C-常风), 第二位显示档位(1-4)
LSC=1;LSB=1;LSA=1;SMG_PORT = 0x00;Delay_ms(1); // 关闭所有位选
switch(mode)
{
case NATURAL_MODE:
SMG_PORT = 0x37; // 显示"N"
break;
case SLEEP_MODE:
SMG_PORT = 0x6D; // 显示"S"
break;
case NORMAL_MODE:
SMG_PORT = 0x39; // 显示"C"
break;
default:
SMG_PORT = 0x00; // 不显示
break;
}
LSC=1;LSB=1;LSA=1; // 第一位显示模式
Delay_ms(2);
SMG_PORT = 0x00;
if (gear > 0)
{
SMG_PORT = SmgCode[gear]; // 显示档位数字
}
else
{
SMG_PORT = 0x00; // 停止时档位不显示
}
LSC=1;LSB=1;LSA=0; // 第二位显示档位
Delay_ms(2);
SMG_PORT = 0x00;
}
该函数采用动态扫描的方式,依次点亮每一位数码管。
- 消影:先关闭所有位选 (
LSC=1;LSB=1;LSA=1;
),再段选送数据,最后再打开位选。这是防止在切换数字时产生“鬼影”的标准做法。 - 显示模式:第一位显示模式字符(N, S, C)。
- 显示档位:第二位显示档位数字(1-4)。
- 短暂延时:
Delay_ms(2)
让该位数码管点亮一段时间,利用人眼视觉暂留形成稳定显示。扫描速度不能太慢,否则会有闪烁感。
5.代码中的小问题
引脚冲突与定义问题:
- 冲突:
LED_SLEEP (P2^5)
与BUZZER_PIN (P2^5)
是同一个引脚。LED_NORMAL (P2^6)
和LSC (P2^4)
也与数码管位选引脚复用。这会导致功能紊乱。 - 建议:重新规划引脚定义,确保每个功能使用独立的IO口。例如,将蜂鸣器移到
P3^7
,LED指示灯移到P1
口或其他空闲引脚。
代码健壮性:
- 在
Key_Scan
函数中,每次有效按键后都重置了OverheatTimer = 0
。这意味着任何按键操作都会重置过热保护计时,这可能不是最理想的行为。可以考虑只在模式切换或档位变化时重置,或者单独设置一个“复位”按键来清零过热计时。
大家如果对此有所在意的可以自行针对性的进行完善,我在此就不优化了。
6.总结
该设计PWM的相关编程是重点,大家应着重学习此重点。
附总代码
#include <REGX52.H>
#include <INTRINS.H>
// 定义风的类型
#define STOP_MODE 0
#define NATURAL_MODE 1
#define SLEEP_MODE 2
#define NORMAL_MODE 3
// 电机控制引脚定义 (需根据实际硬件连接修改)
sbit MOTOR_PIN = P1^0; // 假设电机通过三极管或驱动芯片连接到P1.0
sbit BUZZER_PIN = P2^5; // 蜂鸣器引脚,用于报警提示
// 按键引脚定义 (需根据实际硬件连接修改)
sbit KEY_NATURAL = P3^1; // "自然风"模式按键
sbit KEY_SLEEP = P3^0; // "睡眠风"模式按键
sbit KEY_NORMAL = P3^2; // "常风"模式按键
// 数码管位选和段选定义 (需根据实际硬件连接修改,此处为示例)
#define SMG_PORT P0 // 数码管段选数据端口
sbit LSA = P2^2; // 数码管第一位位选
sbit LSB = P2^3; // 数码管第二位位选
sbit LSC = P2^4; // 数码管第三位位选
// 状态指示灯定义 (需根据实际硬件连接修改)
sbit LED_NATURAL = P2^4; // 自然风模式指示灯
sbit LED_SLEEP = P2^5; // 睡眠风模式指示灯
sbit LED_NORMAL = P2^6; // 常风模式指示灯
// 数码管显示码表 (共阴数码管 0-9)
unsigned char code SmgCode[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
// 全局变量声明
unsigned char WindMode = STOP_MODE; // 当前风模式, 默认为停止
unsigned char WindGear = 0; // 当前风力档位, 0表示停止
unsigned int OverheatTimer = 0; // 过热保护计时器
unsigned char PwmCount = 0; // PWM计数器
unsigned int SystemTick = 0; // 系统滴答计数器
unsigned char KeyPressCount[3] = {0}; // 记录各模式按键按下次数 [自然, 睡眠, 常风]
// 函数声明
void System_Init(void);
void Timer0_Init(void);
void Key_Scan(void);
void Motor_Control(void);
void Smg_Display(unsigned char mode, unsigned char gear);
void Delay_ms(unsigned int n);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
void main(void)
{
System_Init(); // 系统初始化
Timer0_Init(); // 定时器0初始化
while(1)
{
Key_Scan(); // 按键扫描
Motor_Control(); // 电机控制
Smg_Display(WindMode, WindGear); // 数码管显示
// 其他功能逻辑可以在此添加
}
}
/**
* @brief 系统初始化
* @param 无
* @retval 无
*/
void System_Init(void)
{
// 初始化IO口
MOTOR_PIN = 0; // 电机初始停止
BUZZER_PIN = 1; // 蜂鸣器初始不响
// 初始化状态指示灯
LED_NATURAL = 1;
LED_SLEEP = 1;
LED_NORMAL = 1;
// 初始化数码管(全部关闭)
SMG_PORT = 0x00;
}
/**
* @brief 定时器0初始化,用于产生PWM和系统计时
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD &= 0xF0; // 设置定时器0模式
TMOD |= 0x01; // 定时器0工作模式1, 16位定时器
TH0 = (65536 - 10000) / 256; // 10ms定时初值
TL0 = (65536 - 10000) % 256;
ET0 = 1; // 使能定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
}
/**
* @brief 按键扫描函数
* @param 无
* @retval 无
*/
void Key_Scan(void)
{
static unsigned char key_lock[3] = {0}; // 按键锁标志,用于消抖
unsigned char i;
// 自然风模式按键处理
if (KEY_NATURAL == 0)
{
if (key_lock[0] == 0 && SystemTick > 20) // 消抖检测
{
key_lock[0] = 1;
KeyPressCount[0]++;
if (KeyPressCount[0] > 4) KeyPressCount[0] = 1; // 档位循环1-4
if (WindMode != NATURAL_MODE)
{
WindMode = NATURAL_MODE;
WindGear = KeyPressCount[0];
}
else
{
WindGear = KeyPressCount[0];
}
OverheatTimer = 0; // 重置过热计时器
SystemTick = 0;
}
}
else
{
key_lock[0] = 0;
}
// 睡眠风模式按键处理 (逻辑同自然风)
if (KEY_SLEEP == 0)
{
if (key_lock[1] == 0 && SystemTick > 20)
{
key_lock[1] = 1;
KeyPressCount[1]++;
if (KeyPressCount[1] > 4) KeyPressCount[1] = 1;
if (WindMode != SLEEP_MODE)
{
WindMode = SLEEP_MODE;
WindGear = KeyPressCount[1];
}
else
{
WindGear = KeyPressCount[1];
}
OverheatTimer = 0;
SystemTick = 0;
}
}
else
{
key_lock[1] = 0;
}
// 常风模式按键处理 (逻辑同自然风)
if (KEY_NORMAL == 0)
{
if (key_lock[2] == 0 && SystemTick > 20)
{
key_lock[2] = 1;
KeyPressCount[2]++;
if (KeyPressCount[2] > 4) KeyPressCount[2] = 1;
if (WindMode != NORMAL_MODE)
{
WindMode = NORMAL_MODE;
WindGear = KeyPressCount[2];
}
else
{
WindGear = KeyPressCount[2];
}
OverheatTimer = 0;
SystemTick = 0;
}
}
else
{
key_lock[2] = 0;
}
}
/**
* @brief 电机控制函数
* @param 无
* @retval 无
*/
void Motor_Control(void)
{
static unsigned int NaturalWindTimer = 0;
static unsigned int SleepWindTimer = 0;
static bit MotorState = 0;
// 过热保护检测:运行超过设定时间(例如30秒)后暂停10秒
if (OverheatTimer > 1000) // 假设10ms中断一次,3000次即为30秒
{
MOTOR_PIN = 0;
BUZZER_PIN = 0; // 蜂鸣器报警
Delay_ms(10000); // 暂停10秒 (实际应用中建议用计时器非阻塞方式)
MOTOR_PIN = 1;
BUZZER_PIN = 1; // 停止报警
OverheatTimer = 0; // 重置过热计时器
}
// 根据模式和档位控制电机
switch (WindMode)
{
case STOP_MODE:
MOTOR_PIN = 0; // 电机停止
LED_NATURAL = 1;
LED_SLEEP = 1;
LED_NORMAL = 1;
break;
case NATURAL_MODE:
LED_NATURAL = 0;
LED_SLEEP = 1;
LED_NORMAL = 1;
// 自然风: 随机启停,模拟自然风效果 (此处用定时器简单模拟)
NaturalWindTimer++;
if (NaturalWindTimer < (200 * WindGear)) // 转动时间随风速增加
{
MOTOR_PIN = 1;
}
else if (NaturalWindTimer < (400 * WindGear))
{
MOTOR_PIN = 0;
}
else
{
NaturalWindTimer = 0;
}
break;
case SLEEP_MODE:
LED_NATURAL = 1;
LED_SLEEP = 0;
LED_NORMAL = 1;
// 睡眠风: 长时间停顿,轻柔风
SleepWindTimer++;
if (SleepWindTimer < (50 * WindGear)) // 转动时间较短
{
MOTOR_PIN = 1;
}
else if (SleepWindTimer < (600 * WindGear)) // 停止时间较长
{
MOTOR_PIN = 0;
}
else
{
SleepWindTimer = 0;
}
break;
case NORMAL_MODE:
LED_NATURAL = 1;
LED_SLEEP = 1;
LED_NORMAL = 0;
// 常风: 持续转动,PWM调速
if (PwmCount < (WindGear * 20)) // 占空比随风速增加 (20%, 40%, 60%, 80%)
{
MOTOR_PIN = 1;
}
else
{
MOTOR_PIN = 0;
}
break;
default:
break;
}
}
/**
* @brief 数码管显示函数
* @param mode: 当前模式
* @param gear: 当前档位
* @retval 无
*/
void Smg_Display(unsigned char mode, unsigned char gear)
{
// 简化显示:第一位显示模式(N-自然, S-睡眠, C-常风), 第二位显示档位(1-4)
LSC=1;LSB=1;LSA=1;SMG_PORT = 0x00;Delay_ms(1); // 关闭所有位选
switch(mode)
{
case NATURAL_MODE:
SMG_PORT = 0x37; // 显示"N"
break;
case SLEEP_MODE:
SMG_PORT = 0x6D; // 显示"S"
break;
case NORMAL_MODE:
SMG_PORT = 0x39; // 显示"C"
break;
default:
SMG_PORT = 0x00; // 不显示
break;
}
LSC=1;LSB=1;LSA=1; // 第一位显示模式
Delay_ms(2);
SMG_PORT = 0x00;
if (gear > 0)
{
SMG_PORT = SmgCode[gear]; // 显示档位数字
}
else
{
SMG_PORT = 0x00; // 停止时档位不显示
}
LSC=1;LSB=1;LSA=0; // 第二位显示档位
Delay_ms(2);
SMG_PORT = 0x00;
}
/**
* @brief 定时器0中断服务函数
* @param 无
* @retval 无
*/
void Timer0_ISR(void) interrupt 1
{
TH0 = (65536 - 10000) / 256; // 重新装载10ms初值
TL0 = (65536 - 10000) % 256;
SystemTick++; // 系统滴答计数器加1
PwmCount++; // PWM计数器加1
if (PwmCount >= 100) // PWM周期为100份
{
PwmCount = 0;
}
if (WindMode != STOP_MODE) // 如果风扇在运行
{
OverheatTimer++; // 过热保护计时器加1
}
}
/**
* @brief 毫秒级延时函数
* @param n: 延时毫秒数
* @retval 无
*/
void Delay_ms(unsigned int n)
{
unsigned int i, j;
for (i = n; i > 0; i--)
for (j = 112; j > 0; j--);
}
请大家点点关注和点赞,后面我一定会分享更多实用的项目的