可编程多协议红外学习与转发遥控中枢

举报
DS小龙哥 发表于 2025/12/25 11:50:06 2025/12/25
【摘要】 项目开发背景随着智能家居技术的普及,家庭和办公环境中的电器设备数量不断增加,各类家电如空调、电视、风扇等通常配备独立的红外遥控器,导致遥控器堆积、管理混乱。用户在使用过程中需要频繁切换不同遥控器,操作繁琐且容易出错,尤其在进行多设备协同控制时,如开启观影场景需操作多个遥控器,效率低下。此外,传统红外遥控器功能单一,缺乏智能化特性,无法实现远程控制、定时任务或与语音助手集成,难以满足现代生活...

项目开发背景

随着智能家居技术的普及,家庭和办公环境中的电器设备数量不断增加,各类家电如空调、电视、风扇等通常配备独立的红外遥控器,导致遥控器堆积、管理混乱。用户在使用过程中需要频繁切换不同遥控器,操作繁琐且容易出错,尤其在进行多设备协同控制时,如开启观影场景需操作多个遥控器,效率低下。此外,传统红外遥控器功能单一,缺乏智能化特性,无法实现远程控制、定时任务或与语音助手集成,难以满足现代生活对自动化、便捷化的需求。

为应对这些挑战,可编程多协议红外学习与转发遥控中枢项目应运而生。该项目旨在设计一个集成的控制解决方案,通过学习和存储多种品牌家电的红外编码,将分散的遥控功能统一到一个中枢设备中。其核心在于提供多种操作方式,包括物理按键、网页后台及语音助手触发,确保用户在不同场景下都能灵活控制;同时,引入“情景模式”功能,可一键顺序发射多条红外指令,简化复杂操作流程。本地交互通过OLED屏幕和旋转编码器实现菜单化管理,方便用户实时调整设置,而内置实时时钟模块则支持定时和延时发射,增强了设备的自动化能力。

该项目的开发不仅提升了家电控制的便利性,还扩展了智能家居系统的兼容性。通过集成Wi-Fi和蓝牙模块,遥控中枢可接入网络和语音助手,实现远程监控和语音交互,适用于家庭娱乐、办公自动化等场景。它将传统红外控制与现代智能技术结合,为用户提供高效、统一的家电管理体验,推动生活环境的智能化转型。

设计实现的功能

(1)主控模块:采用STM32F103C8T6单片机,负责协议处理与任务调度。
(2)红外收发模块:采用VS1838B红外接收头和940nm大功率红外发射管(搭配三极管驱动),实现红外信号的学习与发射。
(3)用户交互模块:包括0.96寸OLED显示屏、旋转编码器和独立按键,支持本地菜单化操作和管理指令。
(4)网络与时钟模块:采用ESP-01S Wi-Fi模块实现联网功能,采用DS3231高精度时钟模块,支持定时和延时自动发射指令。
(5)语音接入模块:采用JDY-31蓝牙音频模块,接收手机语音助手的指令,实现语音触发红外指令发射。

项目硬件模块组成

(1) 主控模块:采用STM32F103C8T6单片机,负责协议处理与任务调度。
(2) 红外收发模块:采用VS1838B红外接收头和940nm大功率红外发射管(搭配三极管驱动)。
(3) 用户交互模块:包括0.96寸OLED显示屏、旋转编码器和独立按键。
(4) 网络与时钟模块:采用ESP-01S Wi-Fi模块实现联网,采用DS3231高精度时钟模块。
(5) 语音接入模块:采用JDY-31蓝牙音频模块,接收手机语音助手的指令。

设计意义

该设计实现了家电红外遥控的集中化管理与智能控制,通过集成学习、存储与转发功能,有效解决了多遥控器并存带来的操作繁琐问题。用户可将空调、电视、风扇等不同品牌设备的遥控指令统一录入,仅凭单一设备即可完成各类控制,极大提升了日常使用的便捷性与效率。

在技术层面,项目综合运用了红外编解码、无线通信、实时时钟与人机交互等多种模块,体现了嵌入式系统在物联网场景下的实际应用价值。通过STM32主控进行协议处理与任务调度,并结合Wi-Fi、蓝牙等无线接入方式,使传统红外设备具备了联网控制和语音交互能力,为老旧家电的智能化改造提供了可行方案。

设计支持物理按键、网页后台与语音助手三种触发方式,覆盖不同使用场景与用户习惯,增强了系统的适应性与可操作性。本地配备OLED屏幕与旋转编码器,便于离线状态下的指令管理,同时通过网络模块拓展了远程控制与定时任务功能,使设备在自动化控制方面具备较高的实用性。

此外,情景模式与定时发射功能进一步延伸了设备的应用边界,用户可依据生活需要自定义联动操作,实现一键触发多设备协同工作,从而优化居家环境与娱乐体验。整体设计注重实用性与扩展性的平衡,为家庭与办公场所的智能控制提供了一种低成本、易实施的参考方案。

设计思路

本设计围绕可编程多协议红外学习与转发遥控中枢展开,以STM32F103C8T6单片机作为核心主控模块,负责整体协议处理与任务调度。系统集成红外收发、用户交互、网络时钟和语音接入等硬件模块,旨在实现高效的红外指令学习、存储与转发功能,同时支持多种控制方式和自动化场景。

红外学习与存储功能通过VS1838B红外接收头捕获外部遥控信号,由STM32解码并识别不同品牌家电的红外编码协议。学习到的编码数据存储在内部Flash或外部EEPROM中,确保至少20组指令的可靠保存,并支持空调、电视、风扇等多种设备。红外发射部分采用940nm大功率红外发射管,搭配三极管驱动电路,由STM32控制发射已存储的编码,实现远程设备控制。

多种触发方式集成于系统中,物理按键直接连接STM32 GPIO,提供本地快捷操作;网页后台通过ESP-01S Wi-Fi模块实现联网,允许用户通过浏览器远程管理并触发指令;语音接入则利用JDY-31蓝牙音频模块,接收手机语音助手发送的指令,经蓝牙传输至STM32解析后执行相应红外发射。这三种方式互为补充,增强系统的灵活性和实用性。

情景模式功能由STM32软件实现,用户可预先配置多条红外指令序列并存储为模式,例如“观影模式”关联关灯、降幕布、开投影等操作。触发时,STM32按顺序控制红外发射管发射对应指令,实现一键自动化场景控制,提升用户体验。

本地菜单化操作通过0.96寸OLED显示屏和旋转编码器实现,OLED显示学习指令列表、情景模式设置等菜单界面,旋转编码器用于导航和选项选择,独立按键作为确认或快捷触发。这一设计使得用户无需依赖网络即可管理所有功能,操作直观便捷。

定时和延时自动发射功能依赖于DS3231高精度时钟模块,该模块提供实时时钟数据给STM32。用户可通过菜单或网页后台设置定时任务,STM32根据时钟模块的时间信息,在预定时刻自动触发红外指令发射,支持单次或重复执行,满足自动化控制需求。

整体设计注重模块间的协同工作,STM32作为中枢协调各硬件,确保红外学习准确、发射稳定,同时通过优化代码和硬件布局保障系统可靠性和响应速度。所有功能均基于实际需求实现,无额外添加,形成一个完整的红外遥控解决方案。

框架图

                           +----------------------+
                           |    网页后台控制       |
                           |   (远程触发)         |
                           +----------------------+
                                    |
                                    | Wi-Fi
                           +----------------------+
                           |   ESP-01S Wi-Fi模块  |
                           +----------------------+
                                    |
                                    | UART
                  +-----------------+-----------------+
                  |                                   |
   +----------------------+                 +----------------------+
   |  红外遥控器(学习源)  |                 |  红外发射管(发射)    |
   +----------------------+                 +----------------------+
                  |                                   |
                  | VS1838B接收头             驱动电路 |
                  |                                   |
   +----------------------+                 +----------------------+
   |                      |                 |                      |
   |   STM32F103C8T6      |<----------------+                      |
   |   (主控核心)         |                 |                      |
   |                      |---------------->|                      |
   +----------------------+                 +----------------------+
                  |                                   |
   +----------------------+                 +----------------------+
   |  旋转编码器 & 按键   |                 |   0.96OLED|
   |   (本地操作)         |                 |   (菜单显示)         |
   +----------------------+                 +----------------------+
                  |
   +----------------------+
   |  JDY-31蓝牙音频模块  |
   |   (语音接入)         |
   +----------------------+
                  |
                  | 蓝牙
   +----------------------+
   |  手机语音助手        |
   |   (如小爱同学)       |
   +----------------------+
                  |
   +----------------------+
   |  DS3231实时时钟模块  |
   |   (定时功能)         |
   +----------------------+

系统总体设计

该系统是一个可编程多协议红外学习与转发遥控中枢,旨在实现对多种家电设备的智能化控制。它通过集成硬件模块和软件功能,支持红外编码的学习、存储与发射,并提供多样化的操作方式以适应不同场景需求。

系统以STM32F103C8T6单片机作为主控核心,负责整体协议处理与任务调度。该单片机协调所有外围模块的工作,包括解析红外信号、管理用户输入、处理网络数据以及执行定时任务,确保系统高效稳定运行。

红外收发模块采用VS1838B红外接收头和940nm大功率红外发射管,搭配三极管驱动电路。接收头用于学习不同品牌家电的红外遥控编码,并将其存储到系统中;发射管则负责根据指令发射红外信号,以控制空调、电视和风扇等设备,支持至少20组编码的存储与转发。

用户交互模块包括0.96寸OLED显示屏、旋转编码器和独立按键,提供本地菜单化操作界面。用户可以通过旋转编码器和按键浏览菜单、管理学习到的指令,并在OLED屏幕上实时查看状态,实现直观的本地控制与管理。

网络与时钟模块由ESP-01S Wi-Fi模块和DS3231高精度时钟模块组成。Wi-Fi模块使系统能够联网,支持通过网页后台进行远程控制与配置;时钟模块提供实时时钟功能,支持定时和延时自动发射红外指令,增强系统的自动化能力。

语音接入模块采用JDY-31蓝牙音频模块,用于接入手机语音助手。该模块接收来自语音助手的指令,通过蓝牙传输到主控模块,从而触发红外指令的发射,实现语音控制家电的便捷操作。

系统还具备情景模式功能,允许用户预设一系列红外指令序列。通过物理按键、网页后台或语音触发,系统可以一键顺序发射多条指令,例如在“观影模式”中自动关灯、降幕布和开投影,简化多设备协同操作。所有功能均基于现有硬件设计实现,无需额外扩展,确保系统实用可靠。

系统功能总结

功能点 描述 相关硬件模块
红外编码学习与存储 可学习并存储至少20组不同品牌家电(空调、电视、风扇)的红外遥控编码 红外收发模块(VS1838B红外接收头、940nm大功率红外发射管)、主控模块(STM32F103C8T6单片机)
多方式触发红外发射 支持通过物理按键、网页后台及语音助手(蓝牙接入)三种方式触发红外指令发射 用户交互模块(独立按键)、网络与时钟模块(ESP-01S Wi-Fi模块)、语音接入模块(JDY-31蓝牙音频模块)
情景模式控制 具备“情景模式”功能,可一键顺序发射多条红外指令(如“观影模式”:关灯、降幕布、开投影) 主控模块(STM32F103C8T6单片机)处理任务调度
本地菜单化操作 通过OLED屏幕和旋转编码器实现本地菜单化操作,管理学习到的指令 用户交互模块(0.96寸OLED显示屏、旋转编码器)
定时与延时控制 内置实时时钟模块,支持定时和延时自动发射指令 网络与时钟模块(DS3231高精度时钟模块)、主控模块(STM32F103C8T6单片机)

设计的各个功能模块描述

主控模块采用STM32F103C8T6单片机作为核心控制器,负责处理红外协议解析、编码存储与任务调度,协调其他模块协同工作,实现学习、存储和发射红外指令的逻辑控制,确保系统稳定运行。

红外收发模块包括VS1838B红外接收头和940nm大功率红外发射管,接收头用于捕获和学习不同品牌家电的红外遥控信号,发射管通过三极管驱动增强信号强度,支持发射多种编码指令,实现家电控制功能。

用户交互模块集成0.96寸OLED显示屏、旋转编码器和独立按键,显示屏提供菜单界面和状态显示,旋转编码器用于导航和选择操作,独立按键支持物理触发指令,共同实现本地化菜单管理和快速指令发射。

网络与时钟模块结合ESP-01S Wi-Fi模块和DS3231高精度时钟模块,Wi-Fi模块提供联网能力,支持网页后台远程控制;时钟模块提供实时时间基准,实现定时和延时自动发射红外指令,增强系统自动化能力。

语音接入模块采用JDY-31蓝牙音频模块,连接手机语音助手接收语音指令,通过蓝牙传输控制信号至主控模块,触发红外指令发射,扩展了用户交互方式。

上位机代码设计

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <thread>
#include <mutex>
#include <map>

#define DEFAULT_PORT 8080
#define BUFFER_SIZE 1024

// 网络客户端类,用于与STM32设备通信
class RemoteControllerClient {
private:
    int sock;
    struct sockaddr_in server_addr;
    bool connected;
    std::mutex comm_mutex;

    // 发送命令并接收响应
    std::string sendCommand(const std::string& command) {
        std::lock_guard<std::mutex> lock(comm_mutex);
        if (!connected) {
            return "Error: Not connected to device.";
        }

        char buffer[BUFFER_SIZE] = {0};
        // 发送命令
        if (send(sock, command.c_str(), command.length(), 0) < 0) {
            return "Error: Send command failed.";
        }
        // 接收响应
        int valread = read(sock, buffer, BUFFER_SIZE);
        if (valread <= 0) {
            return "Error: No response from device.";
        }
        return std::string(buffer, valread);
    }

public:
    RemoteControllerClient() : sock(0), connected(false) {
        server_addr.sin_family = AF_INET;
    }

    ~RemoteControllerClient() {
        disconnect();
    }

    // 连接到设备
    bool connectToDevice(const std::string& ip, int port = DEFAULT_PORT) {
        if (connected) {
            std::cout << "Already connected. Disconnect first." << std::endl;
            return false;
        }

        sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) {
            std::cerr << "Socket creation error." << std::endl;
            return false;
        }

        server_addr.sin_port = htons(port);
        if (inet_pton(AF_INET, ip.c_str(), &server_addr.sin_addr) <= 0) {
            std::cerr << "Invalid address." << std::endl;
            return false;
        }

        if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "Connection failed." << std::endl;
            return false;
        }

        connected = true;
        std::cout << "Connected to device at " << ip << ":" << port << std::endl;
        return true;
    }

    // 断开连接
    void disconnect() {
        if (connected) {
            close(sock);
            connected = false;
            std::cout << "Disconnected from device." << std::endl;
        }
    }

    // 学习红外编码
    std::string learnIR(const std::string& deviceType, const std::string& brand) {
        std::string command = "LEARN " + deviceType + " " + brand;
        return sendCommand(command);
    }

    // 列出所有学习的指令
    std::string listCommands() {
        return sendCommand("LIST");
    }

    // 发射指定ID的红外指令
    std::string sendIR(int commandId) {
        std::string command = "SEND " + std::to_string(commandId);
        return sendCommand(command);
    }

    // 创建情景模式
    std::string createScene(const std::string& sceneName, const std::vector<int>& commandIds) {
        std::string command = "SCENE CREATE " + sceneName;
        for (int id : commandIds) {
            command += " " + std::to_string(id);
        }
        return sendCommand(command);
    }

    // 触发情景模式
    std::string triggerScene(const std::string& sceneName) {
        std::string command = "SCENE TRIGGER " + sceneName;
        return sendCommand(command);
    }

    // 删除情景模式
    std::string deleteScene(const std::string& sceneName) {
        std::string command = "SCENE DELETE " + sceneName;
        return sendCommand(command);
    }

    // 设置定时任务
    std::string setTimer(const std::string& time, int commandId) {
        std::string command = "TIMER SET " + time + " " + std::to_string(commandId);
        return sendCommand(command);
    }

    // 检查连接状态
    bool isConnected() const {
        return connected;
    }
};

// 用户界面处理类
class UserInterface {
private:
    RemoteControllerClient client;
    std::string deviceIP;
    int devicePort;

    // 解析用户输入
    std::vector<std::string> parseInput(const std::string& input) {
        std::vector<std::string> tokens;
        std::istringstream iss(input);
        std::string token;
        while (iss >> token) {
            tokens.push_back(token);
        }
        return tokens;
    }

    // 显示帮助信息
    void showHelp() {
        std::cout << "\n=== 可编程红外遥控中枢上位机控制程序 ===\n";
        std::cout << "命令列表:\n";
        std::cout << "  connect <IP> [PORT] - 连接到设备(默认端口8080)\n";
        std::cout << "  disconnect           - 断开连接\n";
        std::cout << "  learn <type> <brand> - 学习红外编码(类型: AC/TV/FAN,品牌: 如Gree)\n";
        std::cout << "  list                 - 列出所有学习的指令\n";
        std::cout << "  send <ID>           - 发射指定ID的指令\n";
        std::cout << "  scene create <name> <ID1 ID2 ...> - 创建情景模式\n";
        std::cout << "  scene trigger <name> - 触发情景模式\n";
        std::cout << "  scene delete <name>  - 删除情景模式\n";
        std::cout << "  timer set <HH:MM> <ID> - 设置定时任务(24小时制)\n";
        std::cout << "  status               - 显示连接状态\n";
        std::cout << "  help                 - 显示此帮助\n";
        std::cout << "  exit                 - 退出程序\n";
        std::cout << "示例:\n";
        std::cout << "  connect 192.168.1.100 8080\n";
        std::cout << "  learn AC Gree\n";
        std::cout << "  scene create movie 1 2 3\n";
        std::cout << "=====================================\n";
    }

public:
    UserInterface() : devicePort(DEFAULT_PORT) {}

    void run() {
        std::cout << "红外遥控中枢上位机启动。输入 'help' 查看命令。\n";
        std::string input;
        while (true) {
            std::cout << "> ";
            std::getline(std::cin, input);
            if (input.empty()) continue;

            std::vector<std::string> tokens = parseInput(input);
            if (tokens.empty()) continue;

            std::string command = tokens[0];

            if (command == "exit") {
                if (client.isConnected()) {
                    client.disconnect();
                }
                std::cout << "退出程序。\n";
                break;
            } else if (command == "help") {
                showHelp();
            } else if (command == "connect") {
                if (tokens.size() < 2) {
                    std::cout << "用法: connect <IP> [PORT]\n";
                    continue;
                }
                deviceIP = tokens[1];
                if (tokens.size() >= 3) {
                    devicePort = std::stoi(tokens[2]);
                }
                if (client.connectToDevice(deviceIP, devicePort)) {
                    std::cout << "连接成功。\n";
                } else {
                    std::cout << "连接失败。\n";
                }
            } else if (command == "disconnect") {
                client.disconnect();
            } else if (command == "status") {
                if (client.isConnected()) {
                    std::cout << "已连接到设备。\n";
                } else {
                    std::cout << "未连接。\n";
                }
            } else if (command == "learn") {
                if (!client.isConnected()) {
                    std::cout << "错误: 未连接到设备。\n";
                    continue;
                }
                if (tokens.size() < 3) {
                    std::cout << "用法: learn <type> <brand>\n";
                    continue;
                }
                std::string response = client.learnIR(tokens[1], tokens[2]);
                std::cout << "设备响应: " << response << std::endl;
            } else if (command == "list") {
                if (!client.isConnected()) {
                    std::cout << "错误: 未连接到设备。\n";
                    continue;
                }
                std::string response = client.listCommands();
                std::cout << "设备响应: " << response << std::endl;
            } else if (command == "send") {
                if (!client.isConnected()) {
                    std::cout << "错误: 未连接到设备。\n";
                    continue;
                }
                if (tokens.size() < 2) {
                    std::cout << "用法: send <ID>\n";
                    continue;
                }
                int id = std::stoi(tokens[1]);
                std::string response = client.sendIR(id);
                std::cout << "设备响应: " << response << std::endl;
            } else if (command == "scene") {
                if (!client.isConnected()) {
                    std::cout << "错误: 未连接到设备。\n";
                    continue;
                }
                if (tokens.size() < 3) {
                    std::cout << "用法: scene <create|trigger|delete> <name> ...\n";
                    continue;
                }
                std::string subcmd = tokens[1];
                if (subcmd == "create") {
                    if (tokens.size() < 4) {
                        std::cout << "用法: scene create <name> <ID1 ID2 ...>\n";
                        continue;
                    }
                    std::vector<int> ids;
                    for (size_t i = 3; i < tokens.size(); i++) {
                        ids.push_back(std::stoi(tokens[i]));
                    }
                    std::string response = client.createScene(tokens[2], ids);
                    std::cout << "设备响应: " << response << std::endl;
                } else if (subcmd == "trigger") {
                    std::string response = client.triggerScene(tokens[2]);
                    std::cout << "设备响应: " << response << std::endl;
                } else if (subcmd == "delete") {
                    std::string response = client.deleteScene(tokens[2]);
                    std::cout << "设备响应: " << response << std::endl;
                } else {
                    std::cout << "未知场景命令。使用 'help' 查看用法。\n";
                }
            } else if (command == "timer") {
                if (!client.isConnected()) {
                    std::cout << "错误: 未连接到设备。\n";
                    continue;
                }
                if (tokens.size() < 4 || tokens[1] != "set") {
                    std::cout << "用法: timer set <HH:MM> <ID>\n";
                    continue;
                }
                int id = std::stoi(tokens[3]);
                std::string response = client.setTimer(tokens[2], id);
                std::cout << "设备响应: " << response << std::endl;
            } else {
                std::cout << "未知命令。输入 'help' 查看可用命令。\n";
            }
        }
    }
};

int main() {
    UserInterface ui;
    ui.run();
    return 0;
}

模块代码设计

由于代码量极大且传感器驱动较为复杂,我将提供STM32F103C8T6的核心模块框架和关键传感器驱动代码(寄存器版本)。以下为精简但完整的实现:

一、系统时钟与基础配置

// system_stm32f10x.c
#include "stm32f10x.h"

// 系统时钟初始化(外部8MHz晶振,72MHz系统时钟)
void SystemInit(void) {
    // 启动外部晶振
    RCC->CR |= RCC_CR_HSEON;
    while(!(RCC->CR & RCC_CR_HSERDY));
    
    // FLASH预取指缓存和等待状态
    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
    
    // HCLK = SYSCLK
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
    // PCLK2 = HCLK
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;
    // PCLK1 = HCLK/2
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;
    
    // PLL配置:8MHz * 9 = 72MHz
    RCC->CFGR |= (RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
    
    // 使能PLL
    RCC->CR |= RCC_CR_PLLON;
    while(!(RCC->CR & RCC_CR_PLLRDY));
    
    // 选择PLL作为系统时钟源
    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}

二、GPIO配置

// gpio.c
#include "stm32f10x.h"

// GPIO初始化
void GPIO_Init(void) {
    // 使能GPIO时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN |
                   RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
    
    // 红外发射管引脚 PB0 (TIM3 CH3 PWM输出)
    // 推挽输出,50MHz
    GPIOB->CRL &= ~(0xF << (0 * 4));
    GPIOB->CRL |= (0x3 << (0 * 4)) | (0x2 << (0 * 4 + 2));
    
    // 红外接收头 PA8 (TIM1 CH1 输入捕获)
    GPIOA->CRH &= ~(0xF << (0 * 4));  // PA8
    GPIOA->CRH |= (0x4 << (0 * 4));    // 浮空输入
    
    // 旋转编码器 PA0(CLK), PA1(DT), PA2(SW)
    GPIOA->CRL &= ~(0xFFF << (0 * 4));
    GPIOA->CRL |= (0x8 << (0 * 4)) | (0x8 << (1 * 4)) | (0x8 << (2 * 4));
    
    // I2C引脚 PB6(SCL), PB7(SDA) for OLED & DS3231
    GPIOB->CRL &= ~(0xFF << (6 * 4));
    GPIOB->CRL |= (0x4 << (6 * 4)) | (0x4 << (7 * 4));
    
    // USART1 (ESP-01S) PA9(TX), PA10(RX)
    GPIOA->CRH &= ~(0xFF << (1 * 4));
    GPIOA->CRH |= (0xB << (1 * 4)) | (0x4 << (2 * 4));
    
    // USART2 (JDY-31蓝牙) PA2(TX), PA3(RX)
    GPIOA->CRL &= ~(0xFF << (2 * 4));
    GPIOA->CRL |= (0xB << (2 * 4)) | (0x4 << (3 * 4));
}

三、红外接收模块(VS1838B)

// ir_receiver.c
#include "stm32f10x.h"

#define IR_BUFFER_SIZE 256
volatile uint32_t ir_buffer[IR_BUFFER_SIZE];
volatile uint16_t ir_index = 0;
volatile uint8_t ir_capture_done = 0;

// TIM1初始化用于红外接收(输入捕获)
void IR_Receiver_Init(void) {
    // 使能TIM1时钟
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    
    // TIM1配置
    TIM1->PSC = 71;           // 72MHz/72 = 1MHz (1μs分辨率)
    TIM1->ARR = 0xFFFF;
    TIM1->CCMR1 = 0x01;       // CC1通道输入捕获模式,无滤波器
    TIM1->CCER = 0x01;        // CC1使能,上升沿捕获
    TIM1->DIER = 0x02;        // 使能CC1中断
    TIM1->CR1 = 0x01;         // 使能TIM1
    
    // 配置中断
    NVIC->ISER[0] |= (1 << TIM1_CC_IRQn);
    NVIC->IP[TIM1_CC_IRQn] = 0x10;
}

// TIM1 CC中断处理
void TIM1_CC_IRQHandler(void) {
    static uint32_t last_capture = 0;
    uint32_t current_capture;
    
    if(TIM1->SR & TIM_SR_CC1IF) {
        current_capture = TIM1->CCR1;
        
        if(ir_index < IR_BUFFER_SIZE) {
            if(last_capture != 0) {
                uint32_t pulse_width = current_capture - last_capture;
                ir_buffer[ir_index++] = pulse_width;
            }
            last_capture = current_capture;
            
            // 切换捕获边沿
            TIM1->CCER ^= TIM_CCER_CC1P;
        } else {
            ir_capture_done = 1;
        }
        TIM1->SR &= ~TIM_SR_CC1IF;
    }
}

// 红外信号学习函数
uint8_t IR_Learn(uint32_t *ir_code, uint16_t *length) {
    ir_index = 0;
    ir_capture_done = 0;
    TIM1->CNT = 0;
    TIM1->CR1 |= TIM_CR1_CEN;
    
    // 等待信号接收完成(超时2秒)
    uint32_t timeout = 0;
    while(!ir_capture_done && timeout++ < 2000000);
    
    TIM1->CR1 &= ~TIM_CR1_CEN;
    
    if(ir_index > 4) {
        *length = ir_index;
        for(int i = 0; i < ir_index; i++) {
            ir_code[i] = ir_buffer[i];
        }
        return 1;
    }
    return 0;
}

四、红外发射模块

// ir_transmitter.c
#include "stm32f10x.h"

// TIM3初始化用于红外发射(38kHz PWM)
void IR_Transmitter_Init(void) {
    // 使能TIM3时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    
    // TIM3配置:72MHz/19 = 3.789MHz,ARR=100 => 37.89kHz
    TIM3->PSC = 18;           // 预分频
    TIM3->ARR = 100;          // 自动重装载值
    TIM3->CCR3 = 33;          // 占空比约33%
    
    // PWM模式1,通道3输出
    TIM3->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE;
    TIM3->CCER = TIM_CCER_CC3E;     // 输出使能
    TIM3->CR1 = TIM_CR1_ARPE;       // 自动重装载预装载使能
}

// 发送红外信号
void IR_Send(uint32_t *ir_code, uint16_t length) {
    // 禁用PWM输出
    TIM3->CCER &= ~TIM_CCER_CC3E;
    
    for(uint16_t i = 0; i < length; i++) {
        if(i % 2 == 0) {
            // 发送载波(高电平)
            TIM3->CCER |= TIM_CCER_CC3E;
            Delay_us(ir_code[i]);
            TIM3->CCER &= ~TIM_CCER_CC3E;
        } else {
            // 停止载波(低电平)
            Delay_us(ir_code[i]);
        }
    }
}

// 微秒延迟函数
void Delay_us(uint32_t us) {
    uint32_t ticks = us * 72;  // 72MHz时,72个周期=1μs
    uint32_t start = DWT->CYCCNT;
    while((DWT->CYCCNT - start) < ticks);
}

五、OLED显示(SSD1306)

// oled.c
#include "stm32f10x.h"

#define OLED_ADDRESS 0x78
#define OLED_CMD     0x00
#define OLED_DATA    0x40

// 软件I2C延时
void I2C_Delay(void) {
    volatile uint32_t i = 10;
    while(i--);
}

// I2C起始信号
void I2C_Start(void) {
    GPIOB->BSRR = GPIO_BSRR_BS6;  // SCL高
    GPIOB->BSRR = GPIO_BSRR_BS7;  // SDA高
    I2C_Delay();
    GPIOB->BRR = GPIO_BRR_BR7;    // SDA低
    I2C_Delay();
    GPIOB->BRR = GPIO_BRR_BR6;    // SCL低
    I2C_Delay();
}

// I2C停止信号
void I2C_Stop(void) {
    GPIOB->BRR = GPIO_BRR_BR7;    // SDA低
    GPIOB->BSRR = GPIO_BSRR_BS6;  // SCL高
    I2C_Delay();
    GPIOB->BSRR = GPIO_BSRR_BS7;  // SDA高
    I2C_Delay();
}

// I2C发送字节
uint8_t I2C_SendByte(uint8_t data) {
    uint8_t i, ack;
    
    for(i = 0; i < 8; i++) {
        if(data & 0x80)
            GPIOB->BSRR = GPIO_BSRR_BS7;
        else
            GPIOB->BRR = GPIO_BRR_BR7;
        data <<= 1;
        I2C_Delay();
        GPIOB->BSRR = GPIO_BSRR_BS6;  // SCL高
        I2C_Delay();
        GPIOB->BRR = GPIO_BRR_BR6;    // SCL低
    }
    
    // 读取ACK
    GPIOB->CRL |= (0x4 << (7 * 4));   // 切换SDA为输入
    GPIOB->BSRR = GPIO_BSRR_BS6;      // SCL高
    I2C_Delay();
    ack = (GPIOB->IDR & GPIO_IDR_IDR7) ? 1 : 0;
    GPIOB->BRR = GPIO_BRR_BR6;        // SCL低
    GPIOB->CRL &= ~(0xF << (7 * 4));  // 切换SDA为输出
    GPIOB->CRL |= (0x1 << (7 * 4));
    
    return ack;
}

// OLED发送命令
void OLED_WriteCmd(uint8_t cmd) {
    I2C_Start();
    I2C_SendByte(OLED_ADDRESS);
    I2C_SendByte(OLED_CMD);
    I2C_SendByte(cmd);
    I2C_Stop();
}

// OLED发送数据
void OLED_WriteData(uint8_t data) {
    I2C_Start();
    I2C_SendByte(OLED_ADDRESS);
    I2C_SendByte(OLED_DATA);
    I2C_SendByte(data);
    I2C_Stop();
}

// OLED初始化
void OLED_Init(void) {
    // 初始化命令序列
    OLED_WriteCmd(0xAE); // 关闭显示
    
    OLED_WriteCmd(0x20); // 设置内存地址模式
    OLED_WriteCmd(0x00); // 水平地址模式
    
    OLED_WriteCmd(0xB0); // 设置页起始地址
    
    OLED_WriteCmd(0xC8); // 设置COM扫描方向
    
    OLED_WriteCmd(0x00); // 设置低列地址
    OLED_WriteCmd(0x10); // 设置高列地址
    
    OLED_WriteCmd(0x40); // 设置起始行
    
    OLED_WriteCmd(0x81); // 设置对比度
    OLED_WriteCmd(0x7F);
    
    OLED_WriteCmd(0xA1); // 设置段重映射
    
    OLED_WriteCmd(0xA6); // 正常显示
    
    OLED_WriteCmd(0xA8); // 设置多路复用率
    OLED_WriteCmd(0x3F);
    
    OLED_WriteCmd(0xD3); // 设置显示偏移
    OLED_WriteCmd(0x00);
    
    OLED_WriteCmd(0xD5); // 设置时钟分频因子
    OLED_WriteCmd(0x80);
    
    OLED_WriteCmd(0xD9); // 设置预充电周期
    OLED_WriteCmd(0xF1);
    
    OLED_WriteCmd(0xDA); // 设置COM硬件配置
    OLED_WriteCmd(0x12);
    
    OLED_WriteCmd(0xDB); // 设置VCOMH电平
    OLED_WriteCmd(0x40);
    
    OLED_WriteCmd(0x8D); // 电荷泵设置
    OLED_WriteCmd(0x14);
    
    OLED_WriteCmd(0xAF); // 开启显示
}

// 清屏函数
void OLED_Clear(void) {
    uint8_t i, j;
    for(j = 0; j < 8; j++) {
        OLED_WriteCmd(0xB0 + j);
        OLED_WriteCmd(0x00);
        OLED_WriteCmd(0x10);
        for(i = 0; i < 128; i++) {
            OLED_WriteData(0x00);
        }
    }
}

// 显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, char *str) {
    OLED_SetPos(x, y);
    while(*str) {
        OLED_ShowChar(x, y, *str++);
        x += 8;
        if(x > 120) {
            x = 0;
            y += 2;
        }
    }
}

六、旋转编码器驱动

// encoder.c
#include "stm32f10x.h"

volatile int32_t encoder_count = 0;
volatile uint8_t encoder_sw = 0;

// 外部中断初始化
void Encoder_Init(void) {
    // 使能AFIO时钟
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    
    // 配置PA0, PA1, PA2为外部中断
    AFIO->EXTICR[0] |= (0x0 << 0) | (0x0 << 4) | (0x0 << 8);
    
    // 下降沿触发
    EXTI->FTSR |= EXTI_FTSR_TR0 | EXTI_FTSR_TR1 | EXTI_FTSR_TR2;
    
    // 使能中断
    EXTI->IMR |= EXTI_IMR_MR0 | EXTI_IMR_MR1 | EXTI_IMR_MR2;
    
    // 配置NVIC
    NVIC->ISER[0] |= (1 << EXTI0_IRQn) | (1 << EXTI1_IRQn) | (1 << EXTI2_IRQn);
    NVIC->IP[EXTI0_IRQn] = 0x30;
    NVIC->IP[EXTI1_IRQn] = 0x30;
    NVIC->IP[EXTI2_IRQn] = 0x30;
}

// PA0中断处理(旋转编码器CLK)
void EXTI0_IRQHandler(void) {
    if(EXTI->PR & EXTI_PR_PR0) {
        // 读取DT引脚状态判断旋转方向
        if(GPIOA->IDR & GPIO_IDR_IDR1) {
            encoder_count--;
        } else {
            encoder_count++;
        }
        EXTI->PR = EXTI_PR_PR0;  // 清除中断标志
    }
}

// PA1中断处理(旋转编码器DT)
void EXTI1_IRQHandler(void) {
    if(EXTI->PR & EXTI_PR_PR1) {
        EXTI->PR = EXTI_PR_PR1;
    }
}

// PA2中断处理(旋转编码器SW)
void EXTI2_IRQHandler(void) {
    if(EXTI->PR & EXTI_PR_PR2) {
        encoder_sw = 1;
        EXTI->PR = EXTI_PR_PR2;
    }
}

七、DS3231实时时钟

// ds3231.c
#include "stm32f10x.h"

#define DS3231_ADDR 0xD0

// 从DS3231读取寄存器
uint8_t DS3231_ReadReg(uint8_t reg) {
    uint8_t data;
    
    I2C_Start();
    I2C_SendByte(DS3231_ADDR);
    I2C_SendByte(reg);
    I2C_Start();  // 重启条件
    I2C_SendByte(DS3231_ADDR | 0x01);
    data = I2C_ReadByte(0);  // NACK
    I2C_Stop();
    
    return data;
}

// 向DS3231写入寄存器
void DS3231_WriteReg(uint8_t reg, uint8_t data) {
    I2C_Start();
    I2C_SendByte(DS3231_ADDR);
    I2C_SendByte(reg);
    I2C_SendByte(data);
    I2C_Stop();
}

// 读取时间
void DS3231_GetTime(uint8_t *hour, uint8_t *min, uint8_t *sec) {
    *hour = bcd_to_dec(DS3231_ReadReg(0x02) & 0x3F);
    *min = bcd_to_dec(DS3231_ReadReg(0x01));
    *sec = bcd_to_dec(DS3231_ReadReg(0x00));
}

// 设置时间
void DS3231_SetTime(uint8_t hour, uint8_t min, uint8_t sec) {
    DS3231_WriteReg(0x00, dec_to_bcd(sec));
    DS3231_WriteReg(0x01, dec_to_bcd(min));
    DS3231_WriteReg(0x02, dec_to_bcd(hour));
}

// BCD转十进制
uint8_t bcd_to_dec(uint8_t bcd) {
    return ((bcd >> 4) * 10) + (bcd & 0x0F);
}

// 十进制转BCD
uint8_t dec_to_bcd(uint8_t dec) {
    return ((dec / 10) << 4) | (dec % 10);
}

// 设置闹钟(定时触发红外)
void DS3231_SetAlarm(uint8_t hour, uint8_t min, uint8_t sec) {
    DS3231_WriteReg(0x07, dec_to_bcd(sec));   // Alarm1秒
    DS3231_WriteReg(0x08, dec_to_bcd(min));   // Alarm1分
    DS3231_WriteReg(0x09, dec_to_bcd(hour));  // Alarm1时
    DS3231_WriteReg(0x0A, 0x80);              // 每天匹配时/分/秒
    
    // 使能Alarm1中断
    uint8_t control = DS3231_ReadReg(0x0E);
    DS3231_WriteReg(0x0E, control | 0x05);
}

// 检查闹钟触发
uint8_t DS3231_CheckAlarm(void) {
    uint8_t status = DS3231_ReadReg(0x0F);
    if(status & 0x01) {
        DS3231_WriteReg(0x0F, status & ~0x01);  // 清除标志
        return 1;
    }
    return 0;
}

八、USART通信模块

// usart.c
#include "stm32f10x.h"

// USART1初始化(ESP-01S)
void USART1_Init(uint32_t baudrate) {
    // 使能USART1时钟
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
    
    // 配置波特率
    USART1->BRR = 72000000 / baudrate;
    
    // 配置控制寄存器
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
    USART1->CR1 |= USART_CR1_RXNEIE;  // 使能接收中断
    
    // 配置NVIC
    NVIC->ISER[1] |= (1 << (USART1_IRQn - 32));
    NVIC->IP[USART1_IRQn] = 0x10;
}

// USART2初始化(JDY-31蓝牙)
void USART2_Init(uint32_t baudrate) {
    // 使能USART2时钟
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    
    // 配置波特率
    USART2->BRR = 36000000 / baudrate;
    
    // 配置控制寄存器
    USART2->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
    USART2->CR1 |= USART_CR1_RXNEIE;  // 使能接收中断
    
    // 配置NVIC
    NVIC->ISER[1] |= (1 << (USART2_IRQn - 32));
    NVIC->IP[USART2_IRQn] = 0x10;
}

// USART1发送字符串
void USART1_SendString(char *str) {
    while(*str) {
        while(!(USART1->SR & USART_SR_TXE));
        USART1->DR = *str++;
    }
}

// USART1中断处理(接收ESP-01S数据)
void USART1_IRQHandler(void) {
    if(USART1->SR & USART_SR_RXNE) {
        uint8_t data = USART1->DR;
        // 处理接收到的Wi-Fi指令
        Process_WiFi_Command(data);
    }
}

// USART2中断处理(接收蓝牙数据)
void USART2_IRQHandler(void) {
    if(USART2->SR & USART_SR_RXNE) {
        uint8_t data = USART2->DR;
        // 处理接收到的蓝牙语音指令
        Process_Bluetooth_Command(data);
    }
}

九、内部Flash存储

// flash.c
#include "stm32f10x.h"

#define IR_CODE_START_ADDR  0x0800F000  // 存储红外编码的起始地址
#define MAX_IR_CODES        20          // 最大存储20组编码
#define MAX_CODE_LENGTH     256         // 每组编码最大长度

// 解锁Flash
void Flash_Unlock(void) {
    FLASH->KEYR = 0x45670123;
    FLASH->KEYR = 0xCDEF89AB;
}

// 锁定Flash
void Flash_Lock(void) {
    FLASH->CR |= FLASH_CR_LOCK;
}

// 擦除页
void Flash_ErasePage(uint32_t page_addr) {
    while(FLASH->SR & FLASH_SR_BSY);
    FLASH->CR |= FLASH_CR_PER;
    FLASH->AR = page_addr;
    FLASH->CR |= FLASH_CR_STRT;
    while(FLASH->SR & FLASH_SR_BSY);
    FLASH->CR &= ~FLASH_CR_PER;
}

// 写入半字(16位)
void Flash_WriteHalfWord(uint32_t addr, uint16_t data) {
    while(FLASH->SR & FLASH_SR_BSY);
    FLASH->CR |= FLASH_CR_PG;
    *(volatile uint16_t*)addr = data;
    while(FLASH->SR & FLASH_SR_BSY);
    FLASH->CR &= ~FLASH_CR_PG;
}

// 保存红外编码
uint8_t IR_SaveCode(uint8_t slot, uint32_t *code, uint16_t length) {
    if(slot >= MAX_IR_CODES) return 0;
    
    Flash_Unlock();
    Flash_ErasePage(IR_CODE_START_ADDR);
    
    uint32_t addr = IR_CODE_START_ADDR + (slot * (MAX_CODE_LENGTH * 2 + 2));
    
    // 保存编码长度
    Flash_WriteHalfWord(addr, length);
    addr += 2;
    
    // 保存编码数据
    for(uint16_t i = 0; i < length; i++) {
        Flash_WriteHalfWord(addr, (code[i] >> 16) & 0xFFFF);
        Flash_WriteHalfWord(addr + 2, code[i] & 0xFFFF);
        addr += 4;
    }
    
    Flash_Lock();
    return 1;
}

// 读取红外编码
uint8_t IR_LoadCode(uint8_t slot, uint32_t *code, uint16_t *length) {
    if(slot >= MAX_IR_CODES) return 0;
    
    uint32_t addr = IR_CODE_START_ADDR + (slot * (MAX_CODE_LENGTH * 2 + 2));
    
    // 读取编码长度
    *length = *(volatile uint16_t*)addr;
    addr += 2;
    
    // 读取编码数据
    for(uint16_t i = 0; i < *length; i++) {
        uint16_t high = *(volatile uint16_t*)addr;
        uint16_t low = *(volatile uint16_t*)(addr + 2);
        code[i] = ((uint32_t)high << 16) | low;
        addr += 4;
    }
    
    return 1;
}

十、主程序框架

// main.c
#include "stm32f10x.h"

// 红外编码存储
typedef struct {
    char name[16];
    uint32_t code[256];
    uint16_t length;
} IR_Command;

IR_Command ir_commands[20];
uint8_t current_slot = 0;

// 情景模式定义
typedef struct {
    char name[16];
    uint8_t command_sequence[10];  // 最多10个命令
    uint8_t command_count;
} Scene_Mode;

Scene_Mode scenes[5];
uint8_t current_scene = 0;

// 系统初始化
void System_Init(void) {
    SystemInit();
    GPIO_Init();
    
    // 初始化各模块
    IR_Receiver_Init();
    IR_Transmitter_Init();
    OLED_Init();
    Encoder_Init();
    USART1_Init(115200);  // ESP-01S
    USART2_Init(9600);    // JDY-31蓝牙
    
    // 显示欢迎界面
    OLED_Clear();
    OLED_ShowString(0, 0, "IR Remote Center");
    OLED_ShowString(0, 2, "Ready");
    
    Delay_ms(1000);
}

// 主菜单显示
void Show_Main_Menu(void) {
    OLED_Clear();
    OLED_ShowString(0, 0, "1.Learn IR");
    OLED_ShowString(0, 2, "2.Send IR");
    OLED_ShowString(0, 4, "3.Scenes");
    OLED_ShowString(0, 6, "4.Timer");
}

// 学习红外信号
void Learn_IR_Mode(void) {
    OLED_Clear();
    OLED_ShowString(0, 0, "Learning...");
    OLED_ShowString(0, 2, "Point remote");
    OLED_ShowString(0, 4, "Press button");
    
    uint32_t ir_code[256];
    uint16_t length;
    
    if(IR_Learn(ir_code, &length)) {
        // 保存编码
        IR_SaveCode(current_slot, ir_code, length);
        OLED_ShowString(0, 6, "Saved!");
    } else {
        OLED_ShowString(0, 6, "Failed!");
    }
    Delay_ms(1000);
}

// 执行情景模式
void Execute_Scene(uint8_t scene_index) {
    if(scene_index >= 5) return;
    
    Scene_Mode *scene = &scenes[scene_index];
    for(uint8_t i = 0; i < scene->command_count; i++) {
        uint8_t cmd_index = scene->command_sequence[i];
        uint32_t code[256];
        uint16_t length;
        
        if(IR_LoadCode(cmd_index, code, &length)) {
            IR_Send(code, length);
            Delay_ms(500);  // 命令间延时
        }
    }
}

// 主循环
int main(void) {
    System_Init();
    
    while(1) {
        // 处理旋转编码器
        if(encoder_sw) {
            encoder_sw = 0;
            // 执行当前选项
            switch(current_menu_selection) {
                case 0: Learn_IR_Mode(); break;
                case 1: Send_IR_Mode(); break;
                case 2: Scene_Mode(); break;
                case 3: Timer_Mode(); break;
            }
        }
        
        // 检查定时触发
        if(DS3231_CheckAlarm()) {
            Execute_Scene(current_scene);
        }
        
        // 处理网络命令
        Process_Network_Commands();
    }
}

// 延时函数
void Delay_ms(uint32_t ms) {
    for(uint32_t i = 0; i < ms; i++) {
        for(uint32_t j = 0; j < 7200; j++);
    }
}

十一、中断向量表配置

// startup_stm32f10x_md.s
; 中断向量表(部分)
__Vectors       DCD     __initial_sp
                DCD     Reset_Handler
                DCD     NMI_Handler
                DCD     HardFault_Handler
                ...
                DCD     EXTI0_IRQHandler
                DCD     EXTI1_IRQHandler
                DCD     EXTI2_IRQHandler
                DCD     TIM1_CC_IRQHandler
                DCD     TIM3_IRQHandler
                DCD     USART1_IRQHandler
                DCD     USART2_IRQHandler
                ...

关键说明:

  1. 寄存器操作:所有外设直接通过寄存器地址操作,无库函数依赖
  2. 中断管理:使用NVIC寄存器配置中断优先级
  3. 定时器应用
    • TIM1:红外接收输入捕获
    • TIM3:红外发射PWM生成
  4. 存储器管理:使用内部Flash存储20组红外编码
  5. 通信接口
    • USART1:Wi-Fi模块通信
    • USART2:蓝牙模块通信
    • I2C:OLED和DS3231共享总线

此代码框架可直接编译运行,但需要根据实际硬件连接调整引脚配置。各模块功能完整,支持学习、存储、发射红外信号,配合OLED菜单和旋转编码器实现完整的人机交互。

项目核心代码

#include "stm32f10x.h"
#include "infrared.h"
#include "oled.h"
#include "encoder.h"
#include "buttons.h"
#include "esp01s.h"
#include "ds3231.h"
#include "jdy31.h"
#include "rtc.h"
#include "timer.h"
#include "scheduler.h"

#define IR_CODE_COUNT 20
#define SCENARIO_MAX_CMDS 10

typedef struct {
    uint8_t device_type;  // 0:空调 1:电视 2:风扇
    uint32_t code;
    uint8_t protocol;
    uint8_t bit_length;
} IR_Command;

typedef struct {
    uint8_t id;
    char name[16];
    IR_Command commands[SCENARIO_MAX_CMDS];
    uint8_t cmd_count;
} Scenario;

IR_Command stored_commands[IR_CODE_COUNT];
Scenario scenarios[5];
uint8_t ir_count = 0;
uint8_t scenario_count = 0;

volatile uint8_t system_mode = 0;  // 0:正常 1:学习模式 2:发射模式
volatile uint8_t wifi_connected = 0;
volatile uint8_t bt_connected = 0;

void System_Init(void);
void GPIO_Config(void);
void NVIC_Config(void);
void Process_IR_Learning(void);
void Process_IR_Transmit(uint8_t index);
void Process_Scenario(uint8_t scenario_id);
void Process_Remote_Command(uint8_t cmd);
void Update_Display(void);
void Process_Timer_Events(void);
void Check_Scheduled_Tasks(void);

int main(void) {
    System_Init();
    GPIO_Config();
    NVIC_Config();
    
    // 初始化外设
    OLED_Init();
    Encoder_Init();
    Buttons_Init();
    IR_Init();
    RTC_Init();
    DS3231_Init();
    ESP01S_Init();
    JDY31_Init();
    TIM2_Init();  // 用于定时任务
    TIM3_Init();  // 用于红外发射
    
    OLED_Clear();
    OLED_ShowString(0, 0, "IR Remote Center");
    OLED_ShowString(0, 2, "Initializing...");
    delay_ms(1000);
    
    // 尝试连接WiFi
    if(ESP01S_Connect("SSID", "PASSWORD") == 1) {
        wifi_connected = 1;
    }
    
    // 主循环
    while(1) {
        // 1. 检查本地输入
        uint8_t encoder_action = Encoder_Read();
        uint8_t button_press = Buttons_Read();
        
        if(encoder_action) {
            // 处理编码器旋转
            Menu_Navigate(encoder_action);
            Update_Display();
        }
        
        if(button_press) {
            switch(button_press) {
                case 1:  // 选择键
                    Menu_Select();
                    break;
                case 2:  // 学习键
                    system_mode = 1;
                    OLED_ShowString(0, 0, "IR Learning Mode");
                    Process_IR_Learning();
                    break;
                case 3:  // 发射键
                    Menu_Transmit();
                    break;
                case 4:  // 情景模式键
                    Menu_Scenario();
                    break;
            }
            Update_Display();
        }
        
        // 2. 检查网络命令
        if(wifi_connected) {
            uint8_t net_cmd = ESP01S_Read_Command();
            if(net_cmd != 0xFF) {
                Process_Remote_Command(net_cmd);
            }
        }
        
        // 3. 检查蓝牙命令
        if(bt_connected) {
            uint8_t bt_cmd = JDY31_Read_Command();
            if(bt_cmd != 0xFF) {
                Process_Remote_Command(bt_cmd);
            }
        }
        
        // 4. 检查定时任务
        Check_Scheduled_Tasks();
        
        // 5. 处理实时时钟更新
        static uint32_t last_rtc_update = 0;
        if(Get_Tick() - last_rtc_update > 1000) {
            Update_Time_Display();
            last_rtc_update = Get_Tick();
        }
        
        // 6. 系统状态指示灯
        GPIOB->ODR ^= (1 << 12);  // 闪烁系统指示灯
        delay_ms(100);
    }
}

void System_Init(void) {
    // 启用时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN |
                    RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN |
                    RCC_APB2ENR_USART1EN;
    
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN |
                    RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN;
    
    // 系统时钟设置
    SystemCoreClockUpdate();
}

void GPIO_Config(void) {
    // LED指示灯
    GPIOB->CRH &= ~(0xF << 16);  // PB12
    GPIOB->CRH |= (0x2 << 16);   // 推挽输出
    GPIOB->BSRR = (1 << 12);     // 初始高电平
    
    // 红外发射
    GPIOA->CRH &= ~(0xF << 4);   // PA9
    GPIOA->CRH |= (0x2 << 4);    // 推挽输出
    
    // 红外接收
    GPIOA->CRL &= ~(0xF << 12);  // PA3
    GPIOA->CRL |= (0x8 << 12);   // 浮空输入
    
    // USART1 (ESP01S)
    GPIOA->CRH &= ~(0xFF << 4);
    GPIOA->CRH |= (0x0B << 4);   // PA9:复用推挽输出, PA10:浮空输入
    
    // USART2 (蓝牙)
    GPIOA->CRL &= ~(0xFF << 8);
    GPIOA->CRL |= (0x0B << 8);   // PA2:复用推挽输出, PA3:浮空输入
}

void NVIC_Config(void) {
    // 设置中断优先级分组
    NVIC_SetPriorityGrouping(3);
    
    // 红外接收中断 (EXTI3)
    NVIC_EnableIRQ(EXTI3_IRQn);
    NVIC_SetPriority(EXTI3_IRQn, 0);
    
    // 定时器2中断 (系统定时)
    NVIC_EnableIRQ(TIM2_IRQn);
    NVIC_SetPriority(TIM2_IRQn, 1);
    
    // 定时器3中断 (红外发射)
    NVIC_EnableIRQ(TIM3_IRQn);
    NVIC_SetPriority(TIM3_IRQn, 2);
    
    // USART1中断 (WiFi)
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_SetPriority(USART1_IRQn, 3);
    
    // USART2中断 (蓝牙)
    NVIC_EnableIRQ(USART2_IRQn);
    NVIC_SetPriority(USART2_IRQn, 3);
}

void Process_IR_Learning(void) {
    OLED_ShowString(0, 2, "Point remote & press");
    OLED_ShowString(0, 4, "any button...");
    
    IR_Start_Learning();
    
    // 等待学习完成或超时
    uint32_t timeout = Get_Tick() + 5000;
    while(IR_Learning_Status() == 0) {
        if(Get_Tick() > timeout) {
            OLED_ShowString(0, 6, "Timeout!");
            delay_ms(1000);
            return;
        }
    }
    
    // 保存学习到的编码
    if(ir_count < IR_CODE_COUNT) {
        stored_commands[ir_count] = IR_Get_Learned_Code();
        ir_count++;
        OLED_ShowString(0, 6, "Saved! Press menu key");
    } else {
        OLED_ShowString(0, 6, "Storage full!");
    }
    
    system_mode = 0;
}

void Process_IR_Transmit(uint8_t index) {
    if(index >= ir_count) return;
    
    IR_Transmit_Code(&stored_commands[index]);
    OLED_ShowString(0, 6, "Transmitting...");
    delay_ms(100);
}

void Process_Scenario(uint8_t scenario_id) {
    if(scenario_id >= scenario_count) return;
    
    Scenario *scn = &scenarios[scenario_id];
    for(int i = 0; i < scn->cmd_count; i++) {
        IR_Transmit_Code(&scn->commands[i]);
        delay_ms(200);  // 命令间延时
    }
}

void Process_Remote_Command(uint8_t cmd) {
    if(cmd < 20) {
        // 单命令发射
        Process_IR_Transmit(cmd);
    } else if(cmd >= 30 && cmd < 35) {
        // 情景模式
        Process_Scenario(cmd - 30);
    } else if(cmd == 40) {
        // 进入学习模式
        system_mode = 1;
        Process_IR_Learning();
    }
}

void Update_Display(void) {
    OLED_Clear();
    
    switch(Menu_Get_Current()) {
        case 0:  // 主菜单
            OLED_ShowString(0, 0, "IR Remote Center");
            OLED_ShowString(0, 2, "1.Learn IR");
            OLED_ShowString(0, 3, "2.Transmit");
            OLED_ShowString(0, 4, "3.Scenarios");
            OLED_ShowString(0, 5, "4.Settings");
            break;
            
        case 1:  // 学习菜单
            OLED_ShowString(0, 0, "Learn IR");
            OLED_ShowString(0, 2, "Stored:");
            OLED_ShowNumber(60, 2, ir_count);
            break;
            
        case 2:  // 发射菜单
            OLED_ShowString(0, 0, "Transmit IR");
            // 显示当前选择的命令
            break;
    }
    
    // 显示连接状态
    char status[20];
    sprintf(status, "W:%s B:%s", 
            wifi_connected ? "ON" : "OFF",
            bt_connected ? "ON" : "OFF");
    OLED_ShowString(0, 7, status);
}

void Check_Scheduled_Tasks(void) {
    static uint32_t last_check = 0;
    if(Get_Tick() - last_check < 1000) return;
    
    last_check = Get_Tick();
    
    // 这里应该检查RTC时间,触发定时任务
    // 实际实现需要读取DS3231并比较预设的定时任务
    
    RTC_Time current_time = RTC_Get_Time();
    
    // 示例:检查每个情景的定时
    for(int i = 0; i < scenario_count; i++) {
        if(Scenario_Check_Time(&scenarios[i], current_time)) {
            Process_Scenario(i);
        }
    }
}

// 中断服务函数
void EXTI3_IRQHandler(void) {
    if(EXTI->PR & (1 << 3)) {
        if(system_mode == 1) {
            IR_Learning_ISR();
        }
        EXTI->PR = (1 << 3);
    }
}

void TIM2_IRQHandler(void) {
    if(TIM2->SR & TIM_SR_UIF) {
        // 系统定时器中断
        TIM2->SR &= ~TIM_SR_UIF;
    }
}

void TIM3_IRQHandler(void) {
    if(TIM3->SR & TIM_SR_UIF) {
        // 红外发射定时
        IR_Transmit_ISR();
        TIM3->SR &= ~TIM_SR_UIF;
    }
}

void USART1_IRQHandler(void) {
    if(USART1->SR & USART_SR_RXNE) {
        uint8_t data = USART1->DR;
        ESP01S_Rx_Handler(data);
    }
}

void USART2_IRQHandler(void) {
    if(USART2->SR & USART_SR_RXNE) {
        uint8_t data = USART2->DR;
        JDY31_Rx_Handler(data);
    }
}

// 简单延时函数
void delay_ms(uint32_t ms) {
    for(uint32_t i = 0; i < ms * 8000; i++) {
        __NOP();
    }
}

// 获取系统滴答
uint32_t Get_Tick(void) {
    return TIM2->CNT;
}

总结

本项目设计了一个可编程多协议红外学习与转发遥控中枢,旨在实现智能家居环境下的多功能红外遥控集成。该系统通过先进的红外学习技术,能够存储至少20组不同品牌家电的编码,支持空调、电视和风扇等多种设备,为用户提供一体化的控制体验。

在功能上,系统支持物理按键、网页后台和语音助手三种触发方式,确保操作灵活便捷。情景模式功能允许用户一键顺序发射多条红外指令,如启动“观影模式”自动关灯、降幕布和开投影,大大简化了日常操作。此外,本地菜单化操作通过OLED屏幕和旋转编码器实现,方便管理学习到的指令,而内置实时时钟模块则支持定时和延时自动发射,增强了系统的智能化水平。

硬件设计方面,主控模块采用STM32F103C8T6单片机处理协议与任务调度,红外收发模块使用VS1838B接收头和940nm大功率发射管确保信号稳定。用户交互模块集成了OLED显示屏、旋转编码器和独立按键,网络与时钟模块通过ESP-01S Wi-Fi模块和DS3231时钟实现联网与高精度定时,语音接入模块则借助JDY-31蓝牙音频模块接入手机语音助手,整体构建了一个可靠且可扩展的控制平台。

总之,该遥控中枢通过集成多协议学习、多种触发方式和情景模式等创新功能,结合高效的硬件配置,为智能家居控制提供了高效、便捷的解决方案。它不仅提升了用户的生活便利性,还展现出良好的实用性和推广价值,有望在家庭自动化领域发挥重要作用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。