站内搜索
发作品签到
红外懒人开关1.0
专业版

红外懒人开关1.0

简介

本项目是基于STC15W104 8051单片机的红外学习型电机控制懒人开关,专为智能家居改造等场景设计。支持NEC标准红外协议,可自定义学习任意遥控器按键,实现电机正反转交替控制。

简介:本项目是基于STC15W104 8051单片机的红外学习型电机控制懒人开关,专为智能家居改造等场景设计。支持NEC标准红外协议,可自定义学习任意遥控器按键,实现电机正反转交替控制。
复刻成本:20

开源协议

MIT License

创建时间:2026-03-26 15:34:47更新时间:2026-04-08 12:01:31

描述

视频链接:

火速制作中

项目简介

本项目是基于STC15W104 增强型8051单片机的红外学习型电机控制懒人开关,专为智能家居改造等场景设计。支持NEC标准红外协议,可自定义学习任意遥控器按键,实现电机正反转交替控制,内置3档运行时长切换功能。硬件采用STC15W104+L9110S电机驱动方案,外围电路极简、成本低廉、稳定可靠,源码完全开源,支持SDCC编译,可直接二次开发适配各类电机控制需求。

项目功能

  1. 🔧 红外学习功能:支持NEC协议红外遥控,可学习任意遥控器按键,自定义A/B两种电机动作(正转→反转/反转→正转)
  2. 🎛️ 按键交互:短按按键交替执行电机动作
  3. ⚙️ 档位切换:红外码0xE619FF00可循环切换3档电机运行时长(1/1.2/1.5倍基础时长)
  4. 🛡️ 防误触设计:红外学习必须先按学习码 → 再按按键确认,避免误触发
  5. 📊 调试支持:内置软件串口调试,可查看红外码、档位、学习状态等信息
  6. 🔧 开源可定制:完整原理图、PCB、源码开源,可灵活修改参数

项目参数

主控芯片:STC15W104(8051 内核,内置 IRC、EEPROM,无需外部晶振)
供电电压:14500电池输入/烧录排针处5V输入
电机驱动:L9110S H桥驱动,可驱动小型直流电机、减速电机
红外协议:标准 NEC 38kHz 红外接收
控制方式:红外遥控 + 单机按键双控制
运行档位:3 档可调(1 倍 / 1.2 倍 / 1.5 倍基础时长)
编译环境:SDCC 开源编译器(无需 Keil)

原理解析(硬件说明)

本项目硬件由电源电路、主控最小系统、电机驱动电路、红外接收电路、按键电路、调试串口六部分组成,整体设计极简、无冗余元件、适合 DIY 打样

软件代码

/*
 STC15W104 红外学习+电机控制
 逻辑:红外学习码 → 等待按键按下(5秒)→ 按下即进入学习 → 正常学习
*/
#ifdef __SDCC
#include <8051.h>
// 寄存器定义
__sfr __at (0x8E) AUXR;
__sfr __at (0x8F) INT_CLKO;
__sfr __at (0x97) CLK_DIV;
__sfr __at (0xB1) P3M1;
__sfr __at (0xB2) P3M0;
// IAP/EEPROM 寄存器
__sfr __at (0xC2) IAP_DATA;
__sfr __at (0xC3) IAP_ADDRH;
__sfr __at (0xC4) IAP_ADDRL;
__sfr __at (0xC5) IAP_CMD;
__sfr __at (0xC6) IAP_TRIG;
__sfr __at (0xC7) IAP_CONTR;
#endif

typedef unsigned char  uint8_t;
typedef unsigned int   uint16_t;
typedef unsigned long  uint32_t;

// 配置参数
#define DEBUG_EN            1
#define BASE_MOTOR_TIME     1000
#define KEY_DEBOUNCE_TIME    20
#define KEY_LEARN_TIME       3000
#define LEARN_TIMEOUT        5000
#define WAIT_KEY_TIMEOUT     5000   // 👈 延长到5秒,足够按按键
#define EEP_ADDR             0x0000

// 引脚定义
#define PIN_TX    0x02
#define PIN_IA    0x04
#define PIN_IB    0x08
#define PIN_BTN   0x10
#define PIN_IR    0x20

// 红外码
#define NEC_REPEAT_CODE        0xFFFFFFFFUL
#define DEFAULT_CODE_A         0xBA45FF00UL
#define DEFAULT_CODE_B         0xB946FF00UL
#define LEARN_A_TRIGGER        0xE916FF00UL
#define LEARN_B_TRIGGER        0xF20DFF00UL
#define MOTOR_GEAR_SWITCH      0xE619FF00UL

#define NEC_LEAD_LOW_MIN       7000
#define NEC_LEAD_LOW_MAX       12000
#define NEC_REPEAT_HIGH_MIN    1700
#define NEC_REPEAT_HIGH_MAX    2700
#define NEC_DATA_HIGH_MIN      3500
#define NEC_DATA_HIGH_MAX      6000
#define NEC_BIT_THRESHOLD      900

typedef enum { MOTOR_IDLE, MOTOR_RUN1, MOTOR_RUN2 } MotorState;
typedef enum { KEY_BIND_A=0, KEY_BIND_B=1 } KeyBindMode;

// 全局
uint8_t  learn_mode = 0;
uint32_t codeA = DEFAULT_CODE_A;
uint32_t codeB = DEFAULT_CODE_B;
KeyBindMode key_bind = KEY_BIND_A;
MotorState motor_state = MOTOR_IDLE;
uint16_t motor_timer=0, learn_timer=0;
uint8_t  key_now=1;
uint16_t key_debounce=0, key_long_timer=0;
uint8_t  key_trig=0;

uint8_t  motor_gear = 1;
uint16_t motor_run_time = BASE_MOTOR_TIME;
uint8_t  motor_alt_dir = 0;

// 学习等待
uint8_t  wait_key_learn = 0;
uint8_t  wait_learn_sel  = 0;
uint16_t wait_key_t      = 0;

// 函数声明
void IAP_Close(void);
void IAP_Erase(void);
uint8_t IAP_Read(uint16_t addr);
void IAP_Write(uint16_t addr, uint8_t dat);
void Key_Load(void);
void Key_Save(KeyBindMode mode);
void T0_WaitBit(void);
void UART_SendByte(uint8_t dat);
void UART_SendStr(const char *s);
void UART_SendHex8(uint8_t dat);
void UART_SendHex32(uint32_t dat);
uint16_t T0_MeasureLow(void);
uint16_t T0_MeasureHigh(void);
uint8_t NEC_ReadBit(void);
uint8_t NEC_TryRead(uint32_t *code);
void Motor_Forward(void);
void Motor_Reverse(void);
void Motor_Stop(void);
void Motor_Run_FWD_REV(void);
void Motor_Run_REV_FWD(void);
void Motor_Task(void);
void Key_Task(void);
void DelayMs(uint16_t ms);

// ===================== IAP =====================
void IAP_Close(void){ IAP_CONTR=0; IAP_CMD=0; IAP_TRIG=0; }
void IAP_Erase(void){
    EA=0; IAP_CONTR=0x80; IAP_CMD=3; IAP_ADDRH=0; IAP_ADDRL=0;
    IAP_TRIG=0x5A; IAP_TRIG=0xA5; while(IAP_TRIG); IAP_Close(); EA=1;
}
uint8_t IAP_Read(uint16_t addr){
    uint8_t d; EA=0; IAP_CONTR=0x80; IAP_CMD=1;
    IAP_ADDRH=addr>>8; IAP_ADDRL=addr&0xFF;
    IAP_TRIG=0x5A; IAP_TRIG=0xA5; d=IAP_DATA; IAP_Close(); EA=1; return d;
}
void IAP_Write(uint16_t addr, uint8_t dat){
    if(IAP_Read(addr)!=0xFF) IAP_Erase();
    EA=0; IAP_CONTR=0x80; IAP_CMD=2;
    IAP_ADDRH=addr>>8; IAP_ADDRL=addr&0xFF; IAP_DATA=dat;
    IAP_TRIG=0x5A; IAP_TRIG=0xA5; while(IAP_TRIG); IAP_Close(); EA=1;
}

void Key_Load(void){
    uint8_t v=IAP_Read(EEP_ADDR);
    key_bind=(v==0||v==1)?v:KEY_BIND_A;
    if(DEBUG_EN){ UART_SendStr("LOAD:"); UART_SendHex8(key_bind); UART_SendStr("\r\n"); }
}
void Key_Save(KeyBindMode m){
    key_bind=m; IAP_Write(EEP_ADDR,m);
    if(DEBUG_EN){ UART_SendStr("SAVED:"); UART_SendHex8(m); UART_SendStr("\r\n"); }
}

// ===================== 串口 =====================
void T0_WaitBit(void){
    uint16_t reload=65536UL-(11059200UL/12/2400);
    TR0=0;TF0=0; TL0=reload&0xFF; TH0=reload>>8; TR0=1;
    while(!TF0); TR0=0; TF0=0;
}
void UART_SendByte(uint8_t dat){
    uint8_t i; P3&=~PIN_TX; T0_WaitBit();
    for(i=0;i<8;i++){ dat&1?(P3|=PIN_TX):(P3&=~PIN_TX); dat>>=1; T0_WaitBit(); }
    P3|=PIN_TX; T0_WaitBit();
}
void UART_SendStr(const char *s){ if(!DEBUG_EN)return; while(*s)UART_SendByte(*s++); }
void UART_SendHex8(uint8_t dat){
    if(!DEBUG_EN)return; const char h[]="0123456789ABCDEF";
    UART_SendByte(h[dat>>4]);UART_SendByte(h[dat&0x0F]);
}
void UART_SendHex32(uint32_t dat){
    if(!DEBUG_EN)return;
    UART_SendHex8(dat>>24);UART_SendHex8(dat>>16);UART_SendHex8(dat>>8);UART_SendHex8(dat);
}

// ===================== 红外 =====================
uint16_t T0_MeasureLow(void){
    uint16_t cnt=0; if(P3&PIN_IR)return 0;
    TR0=0;TF0=0;TL0=0;TH0=0;TR0=1;
    while(!(P3&PIN_IR)){if(TF0){cnt+=65536;TF0=0;}}
    TR0=0; return cnt+(TH0<<8|TL0);
}
uint16_t T0_MeasureHigh(void){
    uint16_t cnt=0; if(!(P3&PIN_IR))return 0;
    TR0=0;TF0=0;TL0=0;TH0=0;TR0=1;
    while(P3&PIN_IR){if(TF0){cnt+=65536;TF0=0;}}
    TR0=0; return cnt+(TH0<<8|TL0);
}
uint8_t NEC_ReadBit(void){
    uint16_t c; while(!(P3&PIN_IR)); c=T0_MeasureHigh(); return c>900;
}
uint8_t NEC_TryRead(uint32_t *code){
    uint16_t l,h; uint32_t d=0; uint8_t i;
    if(P3&PIN_IR)return 0; l=T0_MeasureLow();
    if(l<7000||l>12000)return 0; h=T0_MeasureHigh();
    if(h>=1700&&h<=2700){*code=0xFFFFFFFFUL;return 2;}
    if(h<3500||h>6000)return 0;
    for(i=0;i<32;i++)if(NEC_ReadBit())d|=1UL<=motor_run_time){
        motor_timer=0;
        if(motor_state==MOTOR_RUN1){motor_state=MOTOR_RUN2;(P3&0x04)?Motor_Reverse():Motor_Forward();}
        else{motor_state=MOTOR_IDLE;Motor_Stop();}
    }
}

// ===================== 按键(修复版) =====================
void Key_Task(void){
    key_now=(P3&PIN_BTN)?1:0;

    // 🔥 等待学习按键:按下就确认,不超时、不卡
    if(wait_key_learn){
        if(key_now==0){ // 按键按下
            if(key_debounce=KEY_DEBOUNCE_TIME && key_long_timer3)motor_gear=1;
                    if(motor_gear==1)motor_run_time=BASE_MOTOR_TIME*1;
                    if(motor_gear==2)motor_run_time=BASE_MOTOR_TIME*1.5;
                    if(motor_gear==3)motor_run_time=BASE_MOTOR_TIME*1.8;
                    UART_SendStr("GEAR:");UART_SendHex8(motor_gear);UART_SendStr("\r\n");
                }
                // 👈 收到学习码,进入等待
                else if(ir==LEARN_A_TRIGGER){
                    wait_key_learn=1; wait_learn_sel=1; wait_key_t=0;
                    UART_SendStr("PRESS KEY TO LEARN A\r\n");
                }
                else if(ir==LEARN_B_TRIGGER){
                    wait_key_learn=1; wait_learn_sel=2; wait_key_t=0;
                    UART_SendStr("PRESS KEY TO LEARN B\r\n");
                }
                else if(ir==codeA)Motor_Run_FWD_REV();
                else if(ir==codeB)Motor_Run_REV_FWD();
            }
        }
        END:
        if(learn_mode && !(P3&PIN_IR)){
            ret=NEC_TryRead(&ir);
            if(ret==1 && ir!=0xFFFFFFFFUL){
                if(learn_mode==1){codeA=ir;UART_SendStr("A OK\r\n");}
                if(learn_mode==2){codeB=ir;UART_SendStr("B OK\r\n");}
                if(learn_mode==3){
                    if(ir==codeA)Key_Save(KEY_BIND_A);
                    if(ir==codeB)Key_Save(KEY_BIND_B);
                }
                learn_mode=0;UART_SendStr("EXIT\r\n");
            }
        }
    }
}

注意事项

此1.0设计中因考虑不周,PCB底部有焊盘裸露,通电时必须避免电机金属外壳与焊点接触!!!

实物图

{19073BDF-EB5F-4C2D-8811-0205CF046FEB}.png
E3A8D6E42E4C8D7E8AC9E38C968EF136.jpg

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

3D模型

序号文件名称下载次数
1
懒人开关外壳下3.STEP
2
2
懒人开关盖3.STEP
2

附件

序号文件名称下载次数
1
main.c
8
克隆工程
添加到专辑
0
0
分享
侵权投诉
知识产权声明&复刻说明

本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。

请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。

底部导航