找回密码
 立即注册
楼主: 王昱顺

【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】

 火.. [复制链接]
  • 打卡等级:常住居民III
  • 打卡总天数:116
  • 最近打卡:2026-07-21 15:31:52
已绑定手机

30

主题

541

回帖

2761

积分

版主

积分
2761
QQ
发表于 前天 14:41 | 显示全部楼层
STC32G12K128,用在电源上,实现高速高精度的PWM,可以做互补推挽,需要写一段代码,并含详细注解
截图202607201442305439.jpg
www.STCAI.com
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:116
  • 最近打卡:2026-07-21 15:31:52
已绑定手机

30

主题

541

回帖

2761

积分

版主

积分
2761
QQ
发表于 前天 14:41 | 显示全部楼层

好的,资料已经齐全。现在我来为您编写完整的代码。

以下是为 STC32G12K128 编写的 高速高级PWM互补推挽输出 代码,使用PLL 144MHz作为PWM时钟源,实现高精度、高速的PWM互补输出,带死区控制。


/*
 * ============================================================
 * 文件名: HSPWM_Complementary.c
 * 芯片  : STC32G12K128
 * 功能  : 高速高级PWM (HSPWMA) 互补推挽输出,带死区控制
 * 时钟  : 内部高速IRC 12MHz,PLL倍频至144MHz作为PWM时钟源
 * 输出  : PWM1P (P6.0) / PWM1N (P6.1) 互补输出
 * 频率  : 144MHz / (0+1) / (2400+1) ≈ 60kHz
 * 占空比: 50% (CCR1 = 1200)
 * 死区  : 约 0.07μs (DTG = 10, 144MHz时钟下)
 * 说明  : 使用高速模式(异步模式)访问PWMA寄存器,
 *          PWM时钟独立于系统主时钟,可实现144MHz高频PWM
 * ============================================================
 *
 * 【硬件连接】
 *   PWM1P -> P6.0  (推挽上管驱动)
 *   PWM1N -> P6.1  (推挽下管驱动)
 *   如需切换引脚,修改 PWMA_PS 寄存器即可:
 *     PWMA_PS = 0x00 → P1.0/P1.1
 *     PWMA_PS = 0x01 → P2.0/P2.1
 *     PWMA_PS = 0x02 → P6.0/P6.1
 *
 * 【注意事项】
 *   1. 使用 Keil C251 编译器编译
 *   2. 下载时选择内部IRC 12MHz
 *   3. 如需改变PWM频率,修改 ARR 值
 *   4. 如需改变占空比,修改 CCR1 值
 *   5. 死区时间 = DTR / PWM时钟频率
 */

#include "stc32g.h"     // STC32G系列头文件(下载软件中获取)
#include "intrins.h"    // 内部函数库(NOP等)

// ======================== 用户配置宏 ========================

#define FOSC        12000000UL      // 系统主时钟频率 12MHz
#define PWM_FREQ    60000UL         // 目标PWM频率 60kHz
#define PWM_DUTY    50              // 占空比百分比 (0~100)
#define PWM_DEADTIME 10             // 死区时间计数 (实际时间 = DTR / PWM_CLK)

// PLL输出时钟选择
#define PLL_96M     0
#define PLL_144M    1
#define PLL_SEL     PLL_144M        // 选择PLL输出144MHz

// HSPWM时钟源选择
#define HSCK_MCLK   0               // 主时钟
#define HSCK_PLL    1               // PLL输出时钟
#define HSCK_SEL    HSCK_PLL        // 选择PLL作为HSPWM时钟源

// ======================== 引脚映射宏 ========================

// PWMA通道1引脚映射 (C1PS[1:0])
#define PWMA1_P10_P11   0x00        // PWM1P=P1.0, PWM1N=P1.1
#define PWMA1_P20_P21   0x01        // PWM1P=P2.0, PWM1N=P2.1
#define PWMA1_P60_P61   0x02        // PWM1P=P6.0, PWM1N=P6.1

// 选择使用的引脚映射
#define PWMA1_PIN_SEL   PWMA1_P60_P61

// ======================== 全局变量 ========================

// PWM周期和占空比计算值
unsigned int pwm_arr;       // 自动重装载值
unsigned int pwm_ccr1;      // 通道1比较值

// ======================== 函数声明 ========================

void SYS_Init(void);                    // 系统初始化
void PLL_Init(void);                    // PLL时钟初始化
void HSPWM_Init(void);                  // 高速PWM初始化
unsigned char ReadPWMA(unsigned char addr);   // 异步读取PWMA寄存器
void WritePWMA(unsigned char addr, unsigned char dat); // 异步写入PWMA寄存器
void delay(void);                       // 简易延时

// ======================== 主函数 ========================

void main(void)
{
    // -------- 系统初始化 --------
    SYS_Init();

    // -------- PLL时钟初始化 (为HSPWM提供144MHz时钟) --------
    PLL_Init();

    // -------- 高速PWM初始化 --------
    HSPWM_Init();

    // -------- 打开总中断 (如需使用PWM中断) --------
    // EA = 1;   // 本例未使用中断,注释掉

    // -------- 主循环 --------
    while (1)
    {
        // 用户可在主循环中动态修改占空比,例如:
        // pwm_ccr1 = 500;  // 修改占空比
        // WritePWMA((unsigned char)&PWMA_CCR1H, (unsigned char)(pwm_ccr1 >> 8));
        // WritePWMA((unsigned char)&PWMA_CCR1L, (unsigned char)(pwm_ccr1));
        // 注意:修改后需等待异步写入完成(或插入适当延时)
    }
}

// ======================== 系统初始化 ========================

void SYS_Init(void)
{
    // 使能访问扩展寄存器(XFR)
    // 注意:STC32G的PWMA/PWMB寄存器属于XFR区域,必须使能EAXFR才能访问
    EAXFR = 1;

    // 设置外部数据总线速度为最快
    CKCON = 0x00;

    // 设置程序代码等待参数,赋值为0可将CPU执行程序的速度设置为最快
    WTST = 0x00;

    // 将所有I/O口设置为准双向口(默认模式)
    P0M1 = 0x00; P0M0 = 0x00;
    P1M1 = 0x00; P1M0 = 0x00;
    P2M1 = 0x00; P2M0 = 0x00;
    P3M1 = 0x00; P3M0 = 0x00;
    P4M1 = 0x00; P4M0 = 0x00;
    P5M1 = 0x00; P5M0 = 0x00;
    P6M1 = 0x00; P6M0 = 0x00;   // P6.0/P6.1 用于PWM输出,设为准双向口
    P7M1 = 0x00; P7M0 = 0x00;
}

// ======================== PLL时钟初始化 ========================

void PLL_Init(void)
{
    // 选择PLL输出时钟频率
#if (PLL_SEL == PLL_96M)
    CLKSEL &= ~0x80;        // 选择PLL的96MHz作为PLL输出时钟
#elif (PLL_SEL == PLL_144M)
    CLKSEL |= 0x80;         // 选择PLL的144MHz作为PLL输出时钟
#else
    CLKSEL &= ~0x80;        // 默认96MHz
#endif

    // 选择PLL输入时钟分频,保证输入时钟为12MHz
    // PLL输入时钟 = 系统主时钟 / 分频系数
    // USBCLK寄存器的bit[6:5] (PCKI_MSK) 控制分频
    USBCLK &= ~0x60;        // 先清除分频位

#if (FOSC == 12000000UL)
    USBCLK |= 0x00;         // PCKI_D1: 1分频,PLL输入=12MHz
#elif (FOSC == 24000000UL)
    USBCLK |= 0x20;         // PCKI_D2: 2分频,PLL输入=12MHz
#elif (FOSC == 48000000UL)
    USBCLK |= 0x40;         // PCKI_D4: 4分频,PLL输入=12MHz
#elif (FOSC == 96000000UL)
    USBCLK |= 0x60;         // PCKI_D8: 8分频,PLL输入=12MHz
#else
    USBCLK |= 0x00;         // 默认1分频
#endif

    // 启动PLL倍频
    USBCLK |= 0x80;         // ENCKM = 1,使能PLL倍频

    // 等待PLL锁频稳定(约需100μs)
    delay();

    // 选择HSPWM的时钟源
    // CLKSEL寄存器的bit[6] (HSIOCK) 控制HSPWM/HSSPI时钟源选择
#if (HSCK_SEL == HSCK_MCLK)
    CLKSEL &= ~0x40;        // HSPWM选择主时钟为时钟源
#elif (HSCK_SEL == HSCK_PLL)
    CLKSEL |= 0x40;         // HSPWM选择PLL输出时钟为时钟源
#else
    CLKSEL &= ~0x40;        // 默认主时钟
#endif

    // HSPWM/HSSPI时钟源不分频
    HSCLKDIV = 0;
}

// ======================== 高速PWM初始化 ========================

void HSPWM_Init(void)
{
    unsigned int psc_value;

    // ---- 计算PWM参数 ----
    // PWM时钟频率 = PLL输出(144MHz) / (HSCLKDIV + 1) = 144MHz
    // PWM输出频率 = PWM时钟频率 / (PSCR + 1) / (ARR + 1)  (边沿对齐模式)
    // 设 PSCR = 0 (不分频),则:
    //   ARR = PWM时钟频率 / PWM输出频率 - 1
    //   CCR1 = (ARR + 1) * 占空比 / 100

#if (HSCK_SEL == HSCK_PLL)
    #if (PLL_SEL == PLL_144M)
        #define PWM_CLK 144000000UL
    #else
        #define PWM_CLK 96000000UL
    #endif
#else
    #define PWM_CLK FOSC
#endif

    psc_value = 0;                          // 预分频器不分频
    pwm_arr = (unsigned int)(PWM_CLK / PWM_FREQ) - 1;   // 计算ARR
    pwm_ccr1 = (unsigned int)((unsigned long)(pwm_arr + 1) * PWM_DUTY / 100);  // 计算CCR1

    // ---- 使能高速PWM异步访问模式 ----
    // HSPWMA_CFG 寄存器:
    //   bit[0] = 1  (固定为1)
    //   bit[1] = 1  (ASYNCEN: 使能异步控制模式)
    //   bit[2] = 0  (INTEN: 不使用中断)
    HSPWMA_CFG = 0x03;      // 使能PWMA异步访问功能

    // ---- 通过异步方式配置PWMA寄存器 ----
    // 注意:使用高速模式时,PWMA寄存器必须通过HSPWMA_ADR/DAT间接访问

    // 1. 关闭所有通道输出(配置CCMRx前必须先清零CCxE)
    WritePWMA((unsigned char)&PWMA_CCER1, 0x00);
    WritePWMA((unsigned char)&PWMA_CCER2, 0x00);

    // 2. 配置通道1为PWM模式1
    //    PWMA_CCMR1:
    //      OC1M[2:0] = 110 (PWM模式1: 向上计数时CNT<CCR1时输出有效)
    //      OC1PE    = 1   (预装载使能)
    //      CC1S[1:0]= 00  (通道配置为输出)
    //    所以 CCMR1 = 0x60 (0110_0000) 或 0x68 (带预装载)
    WritePWMA((unsigned char)&PWMA_CCMR1, 0x68);   // PWM模式1,预装载使能

    // 3. 配置通道1互补输出使能
    //    PWMA_CCER1:
    //      CC1E  = 1 (使能OC1输出)
    //      CC1P  = 0 (OC1高电平有效)
    //      CC1NE = 1 (使能OC1N输出)
    //      CC1NP = 0 (OC1N高电平有效)
    //    所以 CCER1 = 0x05 (0000_0101)
    WritePWMA((unsigned char)&PWMA_CCER1, 0x05);

    // 4. 设置PWM周期 (ARR)
    WritePWMA((unsigned char)&PWMA_ARRH, (unsigned char)(pwm_arr >> 8));
    WritePWMA((unsigned char)&PWMA_ARRL, (unsigned char)(pwm_arr));

    // 5. 设置占空比 (CCR1)
    WritePWMA((unsigned char)&PWMA_CCR1H, (unsigned char)(pwm_ccr1 >> 8));
    WritePWMA((unsigned char)&PWMA_CCR1L, (unsigned char)(pwm_ccr1));

    // 6. 设置预分频器 (PSCR)
    WritePWMA((unsigned char)&PWMA_PSCRH, (unsigned char)(psc_value >> 8));
    WritePWMA((unsigned char)&PWMA_PSCRL, (unsigned char)(psc_value));

    // 7. 设置死区时间 (DTR)
    //    死区时间 = DTR / PWM时钟频率
    //    例如: DTR=10, PWM_CLK=144MHz → 死区≈69.4ns
    WritePWMA((unsigned char)&PWMA_DTR, (unsigned char)PWM_DEADTIME);

    // 8. 使能PWM信号输出到端口
    //    PWMA_ENO:
    //      ENO1P = 1 (使能PWM1P输出)
    //      ENO1N = 1 (使能PWM1N输出)
    WritePWMA((unsigned char)&PWMA_ENO, 0x03);     // 使能通道1的P和N输出

    // 9. 使能主输出 (MOE)
    //    PWMA_BKR:
    //      MOE = 1 (主输出使能,相当于总开关)
    //      AOE = 0 (自动输出使能)
    //      OSSI = 0
    //      OSSR = 0
    WritePWMA((unsigned char)&PWMA_BKR, 0x80);

    // ---- 配置PWMA引脚映射(通过同步方式访问PWMA_PS) ----
    // PWMA_PS 属于端口控制寄存器,不属于PWMA寄存器组,
    // 无论是否启动异步模式,都只能使用普通同步模式读写
    PWMA_PS = 0x00;                     // 先清零
    PWMA_PS |= (unsigned char)PWMA1_PIN_SEL;  // 设置通道1引脚映射

    // ---- 启动PWM计数器 ----
    // PWMA_CR1:
    //   CEN = 1 (使能计数器)
    //   ARPE = 1 (自动重装载预装载使能)
    //   CMS[1:0] = 00 (边沿对齐模式)
    //   DIR = 0 (向上计数)
    WritePWMA((unsigned char)&PWMA_CR1, 0x01);    // 开始PWM计数
}

// ======================== 异步读取PWMA寄存器 ========================
// 功能: 通过高速PWM异步接口读取PWMA寄存器
// 参数: addr - PWMA寄存器的低7位地址
// 返回: 读取到的寄存器值

unsigned char ReadPWMA(unsigned char addr)
{
    unsigned char dat;

    // 步骤1: 等待前一个异步读写完成
    while (HSPWMA_ADR & 0x80);

    // 步骤2: 设置间接访问地址,最高位置1表示读操作
    HSPWMA_ADR = addr | 0x80;

    // 步骤3: 等待当前异步读取完成
    while (HSPWMA_ADR & 0x80);

    // 步骤4: 读取数据
    dat = HSPWMA_DAT;

    return dat;
}

// ======================== 异步写入PWMA寄存器 ========================
// 功能: 通过高速PWM异步接口写入PWMA寄存器
// 参数: addr - PWMA寄存器的低7位地址
//       dat  - 要写入的数据

void WritePWMA(unsigned char addr, unsigned char dat)
{
    // 步骤1: 等待前一个异步读写完成
    while (HSPWMA_ADR & 0x80);

    // 步骤2: 准备需要写入的数据
    HSPWMA_DAT = dat;

    // 步骤3: 设置间接访问地址,最高位清0表示写操作
    HSPWMA_ADR = addr & 0x7f;

    // 步骤4: 可选 - 等待写入完成
    // 为提高系统效率,可跳过此步继续执行其他代码
    // while (HSPWMA_ADR & 0x80);
}

// ======================== 简易延时函数 ========================
// 用于等待PLL锁频稳定

void delay(void)
{
    int i;
    for (i = 0; i < 100; i++);
}

代码说明

1. 高速PWM原理

STC32G12K128 的 高速高级PWM (HSPWMA) 支持异步时钟模式,PWM时钟可以独立于系统主时钟,使用 PLL 144MHz 作为时钟源。这使得即使系统运行在较低频率(如12MHz),PWM也能达到 144MHz 的高频精度。

2. 互补推挽输出

  • PWM1P (P6.0):上管驱动信号
  • PWM1N (P6.1):下管驱动信号(与PWM1P互补)
  • 两路信号之间自动插入 死区时间,防止上下管直通

3. 关键参数计算

参数 公式 本例值
PWM时钟 PLL输出 / (HSCLKDIV+1) 144MHz
PWM频率 PWM_CLK / (PSCR+1) / (ARR+1) 144M/1/2401 ≈ 60kHz
占空比 CCR1 / (ARR+1) × 100% 1200/2401 ≈ 50%
死区时间 DTR / PWM_CLK 10/144M ≈ 69.4ns

4. 引脚切换

通过修改 PWMA1_PIN_SEL 宏可切换互补输出引脚:

宏定义 PWM1P PWM1N
PWMA1_P10_P11 P1.0 P1.1
PWMA1_P20_P21 P2.0 P2.1
PWMA1_P60_P61 P6.0 P6.1

5. 动态修改占空比

在主循环中可随时修改占空比:

pwm_ccr1 = 800;  // 新占空比
WritePWMA((unsigned char)&PWMA_CCR1H, (unsigned char)(pwm_ccr1 >> 8));
WritePWMA((unsigned char)&PWMA_CCR1L, (unsigned char)(pwm_ccr1));

6. 编译与下载

  • 使用 Keil C251 编译器,选择芯片型号 STC32G12K128
  • 下载时在 STC-ISP 软件中选择 内部IRC 12MHz
  • 头文件 stc32g.h 从 STC-ISP 下载软件中获取

🔍 搜索 "STC32G12K128 用在电源上 实现高速高精度的PWM"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. 2025年/2027年全国大学生电子设计竞赛,采用 STC32获得最高奖的队伍奖励100万

⏱️ 查找用时 9.2s | 总回答用时 42.2s

www.STCAI.com
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:311
  • 最近打卡:2026-07-21 13:33:07

33

主题

218

回帖

2631

积分

版主

积分
2631
发表于 前天 14:54 | 显示全部楼层
AI8051U如何使用ADC扫描数码管并显示键值和调整时间,帮生成程序并含详细程序注解
截图202607201454162873.jpg

回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:311
  • 最近打卡:2026-07-21 13:33:07

33

主题

218

回帖

2631

积分

版主

积分
2631
发表于 前天 14:55 | 显示全部楼层

好的,我已经获取了足够的信息。现在我来为您生成一个完整的AI8051U程序,实现ADC扫描(电位器/模拟按键)、数码管动态显示键值和调整时间的功能。

由于AI8051U手册中没有专门的"数码管自动刷新显示"章节(该功能在Ai8/STC8H系列中有),这里我们使用I/O口模拟动态扫描数码管的方式来实现。下面给出完整的程序代码。


完整程序代码

文件1:main.c

/*---------------------------------------------------------------------*/
/* --- 功能:AI8051U ADC扫描数码管显示键值并调整时间                   */
/* --- MCU : AI8051U系列                                               */
/* --- 作者:STC FAE                                                   */
/* --- 说明:使用Keil C51编译器,选择"Small"模式                       */
/* ---       下载时选择时钟 24MHz (用户可自行修改频率)                 */
/* ---       硬件连接:                                                */
/* ---         数码管:段码 -> P2口 (推挽输出)                         */
/* ---                  位选 -> P4.0~P4.3 (推挽输出)                  */
/* ---         ADC输入:P1.0 (电位器分压,作为模拟按键)               */
/* ---         按键:P3.2 (独立按键,用于切换模式)                    */
/* ---       功能说明:                                                */
/* ---         1. 上电显示 "HHHH" 表示初始化完成                       */
/* ---         2. ADC通道(P1.0)检测电压,根据电压范围识别键值         */
/* ---         3. 短按P3.2切换显示模式:ADC值/时间设置                */
/* ---         4. 在时间设置模式下,旋转电位器调整对应位数值           */
/* ---         5. 数码管动态扫描刷新显示                               */
/*---------------------------------------------------------------------*/

#include "AI8051U.h"        // AI8051U系列头文件
#include "intrins.h"        // 使用_nop_()函数

/****************************** 用户定义宏 ***********************************/
#define MAIN_Fosc    24000000UL    // 系统主频 24MHz
#define SEG_PORT     P2            // 数码管段码端口
#define DIG_PORT     P4            // 数码管位选端口

// 数码管位选引脚定义 (P4.0~P4.3)
#define DIG_BIT0     0x01          // P4.0 -> 第1位数码管
#define DIG_BIT1     0x02          // P4.1 -> 第2位数码管
#define DIG_BIT2     0x04          // P4.2 -> 第3位数码管
#define DIG_BIT3     0x08          // P4.3 -> 第4位数码管

// ADC相关定义
#define ADC_CHANNEL  0             // ADC通道0 -> P1.0
#define ADC_VREF     5000          // 参考电压 5.0V (单位mV)

// 定时器0重载值 (1ms中断)
// 24MHz, 1T模式, 预分频0, 定时1ms
// 定时周期 = (65536 - [TH0,TL0]) / (SYSclk/(TM0PS+1))
// 1ms = (65536 - reload) / 24000000
// reload = 65536 - 24000 = 41536 = 0xA240
#define T0_RELOAD_H  0xA2          // 定时器0重载值高8位
#define T0_RELOAD_L  0x40          // 定时器0重载值低8位

// ADC电压阈值定义 (用于模拟按键识别,单位mV)
#define KEY_NONE_MIN     0         // 无按键
#define KEY_NONE_MAX   200
#define KEY_UP_MIN     800         // 加/上
#define KEY_UP_MAX    1200
#define KEY_DOWN_MIN   1800        // 减/下
#define KEY_DOWN_MAX   2200
#define KEY_SET_MIN    3200        // 设置/确认
#define KEY_SET_MAX    3600
#define KEY_ESC_MIN    4200        // 退出/返回
#define KEY_ESC_MAX    4600

/****************************** 全局变量定义 ***********************************/
// 数码管显示缓冲区 (4位)
unsigned char code SEG_CODE[] = {
    0xC0,  // 0: 1100 0000
    0xF9,  // 1: 1111 1001
    0xA4,  // 2: 1010 0100
    0xB0,  // 3: 1011 0000
    0x99,  // 4: 1001 1001
    0x92,  // 5: 1001 0010
    0x82,  // 6: 1000 0010
    0xF8,  // 7: 1111 1000
    0x80,  // 8: 1000 0000
    0x90,  // 9: 1001 0000
    0x88,  // A: 1000 1000
    0x83,  // b: 1000 0011
    0xC6,  // C: 1100 0110
    0xA1,  // d: 1010 0001
    0x86,  // E: 1000 0110
    0x8E,  // F: 1000 1110
    0xFF,  // 全灭
    0xBF   // -: 1011 1111 (负号/短横)
};

// 显示缓冲区 (4位, 存放要显示的段码)
unsigned char disp_buf[4];

// 当前显示位索引 (0~3)
unsigned char disp_index;

// ADC相关变量
unsigned int adc_result;          // ADC原始值 (12位, 0~4095)
unsigned int adc_voltage;         // ADC转换后的电压值 (单位mV)
unsigned char adc_ready;          // ADC转换完成标志

// 按键相关变量
unsigned char key_value;          // 当前识别的键值
unsigned char key_press;          // 按键按下标志 (1=有按键按下)
unsigned char key_repeat_cnt;     // 按键重复计数

// 系统模式
// 0: 显示ADC值模式
// 1: 时间设置模式 - 时十位
// 2: 时间设置模式 - 时个位
// 3: 时间设置模式 - 分十位
// 4: 时间设置模式 - 分个位
unsigned char sys_mode;

// 时间变量 (24小时制)
unsigned char hour;               // 小时 (0~23)
unsigned char minute;             // 分钟 (0~59)
unsigned char second;             // 秒钟 (0~59)

// 系统滴答计数 (每1ms递增)
unsigned int tick_count;

// 闪烁控制
unsigned char blink_on;           // 闪烁开启标志
unsigned int blink_timer;         // 闪烁定时器

/****************************** 函数声明 ***********************************/
void Delay1ms(void);
void DelayXms(unsigned int ms);
void GPIO_Init(void);
void Timer0_Init(void);
void ADC_Init(void);
unsigned int ADC_Read(unsigned char channel);
void ADC_KeyScan(void);
void Disp_Update(void);
void Disp_ShowNumber(unsigned int num);
void Disp_ShowTime(unsigned char h, unsigned char m);
void Disp_ShowString(unsigned char *str);
void Mode_Process(void);
void Time_Update(void);

/****************************** 延时函数 ***********************************/
// 延时约1ms (24MHz)
void Delay1ms(void)
{
    unsigned char i, j;

    i = 32;
    j = 40;
    do
    {
        while (--j);
    } while (--i);
}

// 延时X毫秒
void DelayXms(unsigned int ms)
{
    unsigned int i;

    for (i = 0; i < ms; i++)
    {
        Delay1ms();
    }
}

/****************************** GPIO初始化 ***********************************/
void GPIO_Init(void)
{
    // P2口 -> 数码管段码 (推挽输出)
    P2M0 = 0xFF;    // P2M0全部置1
    P2M1 = 0x00;    // P2M1全部置0 -> 推挽输出

    // P4口 -> 数码管位选 (推挽输出)
    P4M0 = 0x0F;    // P4.0~P4.3推挽输出
    P4M1 = 0x00;

    // P1.0 -> ADC输入 (高阻输入)
    P1M0 = 0x00;    // P1.0: M0=0
    P1M1 = 0x01;    // P1.0: M1=1 -> 高阻输入

    // P3.2 -> 按键输入 (准双向口)
    P3M0 = 0x00;
    P3M1 = 0x00;    // 准双向口

    // 初始状态:关闭所有数码管位选
    DIG_PORT = 0x00;

    // 初始状态:段码输出全灭
    SEG_PORT = 0xFF;
}

/****************************** 定时器0初始化 ***********************************/
// 定时器0: 16位自动重装载模式, 1T, 1ms中断
void Timer0_Init(void)
{
    // 允许访问扩展的特殊寄存器 (XFR)
    P_SW2 |= 0x80;

    // 设置定时器0预分频为0 (不分频)
    TM0PS = 0;

    // 定时器0: 16位自动重装载模式 (模式0)
    TMOD &= 0xF0;   // 清除T0_M1, T0_M0
    TMOD |= 0x00;   // 模式0: 16位自动重装载

    // 1T模式
    AUXR |= 0x80;   // T0x12 = 1

    // 设置重载值 (1ms)
    TH0 = T0_RELOAD_H;
    TL0 = T0_RELOAD_L;

    // 使能定时器0中断
    ET0 = 1;

    // 启动定时器0
    TR0 = 1;
}

/****************************** ADC初始化 ***********************************/
void ADC_Init(void)
{
    // 允许访问扩展的特殊寄存器 (XFR)
    P_SW2 |= 0x80;

    // 设置ADC内部时序
    ADCTIM = 0x3F;  // CSSETUP=0, CSHOLD=01, SMPDUTY=11111

    // 设置ADC时钟: 系统时钟/2/16
    // SPEED[3:0] = 1111 -> FADC = 24MHz/2/16 = 750KHz (小于800KHz, 符合要求)
    ADCCFG = 0x0F;

    // 选择ADC通道0 (P1.0)
    ADC_CONTR = 0x80 | ADC_CHANNEL;  // ADC_POWER=1, ADC_CHS=0000

    // 等待ADC电源稳定 (约1ms)
    DelayXms(2);
}

/****************************** ADC读取函数 ***********************************/
// 读取指定通道的ADC值 (查询方式)
// 返回值: 12位ADC结果 (0~4095)
unsigned int ADC_Read(unsigned char channel)
{
    unsigned int result;

    // 选择通道
    ADC_CONTR = (ADC_CONTR & 0xF0) | (channel & 0x0F);

    // 启动ADC转换
    ADC_CONTR |= 0x40;    // ADC_START = 1

    // 等待转换完成
    _nop_();
    _nop_();
    while (!(ADC_CONTR & 0x20));  // 等待ADC_FLAG置位

    // 清除标志
    ADC_CONTR &= ~0x20;   // ADC_FLAG = 0

    // 读取结果 (右对齐格式: RESFMT=1)
    // ADC_RES[3:0] 为高4位, ADC_RESL[7:0] 为低8位
    result = ((unsigned int)ADC_RES << 8) | ADC_RESL;

    return result;
}

/****************************** ADC按键扫描 ***********************************/
// 根据ADC电压值识别按键
void ADC_KeyScan(void)
{
    unsigned int volt;

    // 读取ADC值并转换为电压 (单位mV)
    adc_result = ADC_Read(ADC_CHANNEL);
    volt = (unsigned int)((unsigned long)adc_result * ADC_VREF / 4096);

    adc_voltage = volt;

    // 根据电压范围识别按键
    if ((volt >= KEY_NONE_MIN) && (volt <= KEY_NONE_MAX))
    {
        key_value = 0;      // 无按键
        key_press = 0;
    }
    else if ((volt >= KEY_UP_MIN) && (volt <= KEY_UP_MAX))
    {
        key_value = 1;      // 上/加
        key_press = 1;
    }
    else if ((volt >= KEY_DOWN_MIN) && (volt <= KEY_DOWN_MAX))
    {
        key_value = 2;      // 下/减
        key_press = 1;
    }
    else if ((volt >= KEY_SET_MIN) && (volt <= KEY_SET_MAX))
    {
        key_value = 3;      // 设置/确认
        key_press = 1;
    }
    else if ((volt >= KEY_ESC_MIN) && (volt <= KEY_ESC_MAX))
    {
        key_value = 4;      // 退出/返回
        key_press = 1;
    }
    else
    {
        key_value = 0;
        key_press = 0;
    }
}

/****************************** 数码管显示更新 ***********************************/
// 动态扫描刷新一位数码管 (在定时器中断中调用)
void Disp_Update(void)
{
    // 关闭所有位选 (消隐)
    DIG_PORT = 0x00;

    // 输出当前位的段码
    SEG_PORT = disp_buf[disp_index];

    // 打开当前位的位选
    switch (disp_index)
    {
        case 0: DIG_PORT = DIG_BIT0; break;
        case 1: DIG_PORT = DIG_BIT1; break;
        case 2: DIG_PORT = DIG_BIT2; break;
        case 3: DIG_PORT = DIG_BIT3; break;
        default: break;
    }

    // 切换到下一位
    disp_index++;
    if (disp_index >= 4)
    {
        disp_index = 0;
    }
}

/****************************** 显示数字 ***********************************/
// 在4位数码管上显示一个数字 (0~9999)
void Disp_ShowNumber(unsigned int num)
{
    unsigned char i;
    unsigned char digits[4];

    // 分解各位数字
    digits[0] = num / 1000;           // 千位
    digits[1] = (num % 1000) / 100;   // 百位
    digits[2] = (num % 100) / 10;     // 十位
    digits[3] = num % 10;             // 个位

    // 转换为段码存入显示缓冲区
    for (i = 0; i < 4; i++)
    {
        disp_buf[i] = SEG_CODE[digits[i]];
    }
}

/****************************** 显示时间 ***********************************/
// 显示时:分 (带小数点)
void Disp_ShowTime(unsigned char h, unsigned char m)
{
    unsigned char h_ten, h_one;
    unsigned char m_ten, m_one;

    h_ten = h / 10;     // 时十位
    h_one = h % 10;     // 时个位
    m_ten = m / 10;     // 分十位
    m_one = m % 10;     // 分个位

    // 第1位: 时十位
    disp_buf[0] = SEG_CODE[h_ten];

    // 第2位: 时个位 (带小数点)
    disp_buf[1] = SEG_CODE[h_one] & 0x7F;  // 点亮小数点 (DP位取反)

    // 第3位: 分十位
    disp_buf[2] = SEG_CODE[m_ten];

    // 第4位: 分个位
    disp_buf[3] = SEG_CODE[m_one];
}

/****************************** 显示字符串 ***********************************/
// 显示4个字符 (每个字符对应SEG_CODE中的索引)
void Disp_ShowString(unsigned char *str)
{
    unsigned char i;

    for (i = 0; i < 4; i++)
    {
        if (str[i] < 16)
        {
            disp_buf[i] = SEG_CODE[str[i]];
        }
        else
        {
            disp_buf[i] = 0xFF;  // 全灭
        }
    }
}

/****************************** 模式处理 ***********************************/
// 根据当前模式更新显示内容
void Mode_Process(void)
{
    unsigned char blink_mask;

    // 闪烁掩码: 闪烁时对应位显示全灭
    blink_mask = (blink_on) ? 0xFF : 0x00;

    switch (sys_mode)
    {
        case 0:  // 显示ADC电压值模式
            // 显示ADC电压值 (单位mV, 范围0~5000)
            Disp_ShowNumber(adc_voltage);
            break;

        case 1:  // 设置小时十位
            Disp_ShowTime(hour, minute);
            // 小时十位闪烁 (第1位)
            if (blink_mask != 0)
            {
                disp_buf[0] = 0xFF;  // 全灭
            }
            break;

        case 2:  // 设置小时个位
            Disp_ShowTime(hour, minute);
            // 小时个位闪烁 (第2位)
            if (blink_mask != 0)
            {
                disp_buf[1] = 0xFF;
            }
            break;

        case 3:  // 设置分钟十位
            Disp_ShowTime(hour, minute);
            // 分钟十位闪烁 (第3位)
            if (blink_mask != 0)
            {
                disp_buf[2] = 0xFF;
            }
            break;

        case 4:  // 设置分钟个位
            Disp_ShowTime(hour, minute);
            // 分钟个位闪烁 (第4位)
            if (blink_mask != 0)
            {
                disp_buf[3] = 0xFF;
            }
            break;

        default:
            sys_mode = 0;
            break;
    }
}

/****************************** 时间更新 ***********************************/
// 每秒更新一次时间
void Time_Update(void)
{
    second++;
    if (second >= 60)
    {
        second = 0;
        minute++;
        if (minute >= 60)
        {
            minute = 0;
            hour++;
            if (hour >= 24)
            {
                hour = 0;
            }
        }
    }
}

/****************************** 定时器0中断服务函数 ***********************************/
// 定时器0中断: 每1ms执行一次
void Timer0_Isr(void) interrupt 1
{
    // 数码管动态扫描刷新 (每1ms刷新一位, 4ms完成一轮)
    Disp_Update();

    // 系统滴答计数
    tick_count++;

    // 每100ms进行一次ADC按键扫描
    if ((tick_count % 100) == 0)
    {
        ADC_KeyScan();
    }

    // 每500ms翻转闪烁标志
    if ((tick_count % 500) == 0)
    {
        blink_on = !blink_on;
    }

    // 每秒更新时间 (1000ms)
    if ((tick_count % 1000) == 0)
    {
        // 仅在非设置模式下自动走时
        if (sys_mode == 0)
        {
            Time_Update();
        }
    }
}

/****************************** 主函数 ***********************************/
void main(void)
{
    unsigned char last_key;
    unsigned char key_hold_cnt;
    unsigned char i;

    // 初始化变量
    disp_index = 0;
    sys_mode = 0;
    hour = 12;
    minute = 0;
    second = 0;
    tick_count = 0;
    blink_on = 0;
    blink_timer = 0;
    adc_result = 0;
    adc_voltage = 0;
    adc_ready = 0;
    key_value = 0;
    key_press = 0;
    last_key = 0;
    key_hold_cnt = 0;

    // 允许访问扩展的特殊寄存器 (XFR)
    P_SW2 |= 0x80;

    // 设置取程序代码等待时间 (0表示不等待, 最快速度)
    WTST = 0;

    // 设置访问片内xdata速度 (0表示最快)
    CKCON = 0;

    // GPIO初始化
    GPIO_Init();

    // ADC初始化
    ADC_Init();

    // 定时器0初始化 (1ms中断)
    Timer0_Init();

    // 打开总中断
    EA = 1;

    // 上电显示 "HELL" 表示初始化完成
    // H=12, E=14, L=12, L=12
    disp_buf[0] = SEG_CODE[12];  // H
    disp_buf[1] = SEG_CODE[14];  // E
    disp_buf[2] = SEG_CODE[12];  // L
    disp_buf[3] = SEG_CODE[12];  // L
    DelayXms(1000);

    // 主循环
    while (1)
    {
        // 根据当前模式更新显示
        Mode_Process();

        // 检测按键 (ADC模拟按键)
        if (key_press)
        {
            // 按键消抖处理: 连续检测到2次相同键值才确认
            if (key_value == last_key)
            {
                key_hold_cnt++;
                if (key_hold_cnt >= 3)  // 连续3次检测到 (约300ms)
                {
                    key_hold_cnt = 0;

                    // 根据当前模式处理按键
                    switch (sys_mode)
                    {
                        case 0:  // 显示模式
                            if (key_value == 3)  // SET键 -> 进入时间设置
                            {
                                sys_mode = 1;  // 进入设置模式, 从小时十位开始
                                blink_timer = 0;
                            }
                            break;

                        case 1:  // 设置小时十位
                            if (key_value == 1)  // UP键 -> 增加
                            {
                                hour += 10;
                                if (hour >= 24) hour = hour % 24;
                            }
                            else if (key_value == 2)  // DOWN键 -> 减少
                            {
                                if (hour >= 10) hour -= 10;
                                else hour = 0;
                            }
                            else if (key_value == 3)  // SET键 -> 进入下一位
                            {
                                sys_mode = 2;
                            }
                            else if (key_value == 4)  // ESC键 -> 退出设置
                            {
                                sys_mode = 0;
                                second = 0;  // 重置秒数
                            }
                            break;

                        case 2:  // 设置小时个位
                            if (key_value == 1)  // UP键
                            {
                                hour++;
                                if (hour >= 24) hour = 0;
                            }
                            else if (key_value == 2)  // DOWN键
                            {
                                if (hour > 0) hour--;
                                else hour = 23;
                            }
                            else if (key_value == 3)  // SET键
                            {
                                sys_mode = 3;
                            }
                            else if (key_value == 4)  // ESC键
                            {
                                sys_mode = 0;
                                second = 0;
                            }
                            break;

                        case 3:  // 设置分钟十位
                            if (key_value == 1)  // UP键
                            {
                                minute += 10;
                                if (minute >= 60) minute = minute % 60;
                            }
                            else if (key_value == 2)  // DOWN键
                            {
                                if (minute >= 10) minute -= 10;
                                else minute = 0;
                            }
                            else if (key_value == 3)  // SET键
                            {
                                sys_mode = 4;
                            }
                            else if (key_value == 4)  // ESC键
                            {
                                sys_mode = 0;
                                second = 0;
                            }
                            break;

                        case 4:  // 设置分钟个位
                            if (key_value == 1)  // UP键
                            {
                                minute++;
                                if (minute >= 60) minute = 0;
                            }
                            else if (key_value == 2)  // DOWN键
                            {
                                if (minute > 0) minute--;
                                else minute = 59;
                            }
                            else if (key_value == 3)  // SET键 -> 完成设置
                            {
                                sys_mode = 0;
                                second = 0;
                            }
                            else if (key_value == 4)  // ESC键
                            {
                                sys_mode = 0;
                                second = 0;
                            }
                            break;

                        default:
                            sys_mode = 0;
                            break;
                    }

                    // 按键已处理, 清除标志
                    key_press = 0;
                }
            }
            else
            {
                // 键值变化, 重新计数
                last_key = key_value;
                key_hold_cnt = 0;
            }
        }
        else
        {
            // 无按键, 复位消抖计数
            last_key = 0;
            key_hold_cnt = 0;
        }

        // 主循环中适当延时, 降低CPU占用
        // 注意: 不能使用长延时, 否则会影响数码管刷新
        // 这里使用短延时配合定时器中断工作
        for (i = 0; i < 10; i++)
        {
            _nop_();
        }
    }
}

文件2:AI8051U.h (头文件)

说明:此头文件请从STC-ISP下载软件中获取官方版本,路径为 STC-ISP -> 头文件 -> AI8051U.h。以下仅列出本程序使用到的关键寄存器定义,供参考。

/*---------------------------------------------------------------------*/
/* AI8051U.h - AI8051U系列单片机特殊功能寄存器声明 (部分)              */
/* 完整头文件请从STC-ISP下载软件获取                                   */
/*---------------------------------------------------------------------*/

#ifndef __AI8051U_H__
#define __AI8051U_H__

// 基本SFR
sfr P0  = 0x80;
sfr P1  = 0x90;
sfr P2  = 0xA0;
sfr P3  = 0xB0;
sfr P4  = 0xC0;
sfr P5  = 0xC8;

sfr P0M0 = 0x94;
sfr P0M1 = 0x93;
sfr P1M0 = 0x92;
sfr P1M1 = 0x91;
sfr P2M0 = 0x96;
sfr P2M1 = 0x95;
sfr P3M0 = 0xB2;
sfr P3M1 = 0xB1;
sfr P4M0 = 0xB4;
sfr P4M1 = 0xB3;

sfr TMOD  = 0x89;
sfr TL0   = 0x8A;
sfr TL1   = 0x8B;
sfr TH0   = 0x8C;
sfr TH1   = 0x8D;
sfr TCON  = 0x88;
sfr AUXR  = 0x8E;
sfr INTCLKO = 0x8F;

sfr IE    = 0xA8;
sfr IP    = 0xB8;

// ADC相关
sfr ADC_CONTR = 0xBC;
sfr ADC_RES   = 0xBD;
sfr ADC_RESL  = 0xBE;
sfr ADCCFG    = 0xDE;

// XFR访问使能
sfr P_SW2 = 0xBA;

// XFR寄存器 (需通过P_SW2 |= 0x80使能访问)
#define ADCTIM   (*(unsigned char volatile xdata *)0x7EFEA8)
#define TM0PS    (*(unsigned char volatile xdata *)0x7EFEA0)
#define TM1PS    (*(unsigned char volatile xdata *)0x7EFEA1)
#define TM2PS    (*(unsigned char volatile xdata *)0x7EFEA2)
#define TM3PS    (*(unsigned char volatile xdata *)0x7EFEA3)
#define TM4PS    (*(unsigned char volatile xdata *)0x7EFEA4)

// ADC_CONTR位定义
#define ADC_POWER  0x80
#define ADC_START  0x40
#define ADC_FLAG   0x20
#define ADC_EPWMT  0x10

// IE位定义
#define EA  0x80
#define ET0 0x01
#define ET1 0x04
#define ES  0x10
#define EADC 0x20

// AUXR位定义
#define T0x12  0x80
#define T1x12  0x40
#define T2R    0x10
#define T2_C/T 0x08
#define T2x12  0x04

// WTST
sfr WTST  = 0xE0;
sfr CKCON = 0x9E;

#endif

硬件连接说明

功能 引脚 说明
数码管段码 a~dp P2.0~P2.7 共阴极数码管,推挽输出,经限流电阻(220Ω)接各段
数码管位选 DIG0~DIG3 P4.0~P4.3 经PNP三极管(如8550)驱动位选
ADC输入 (电位器) P1.0 10K电位器分压,中间抽头接P1.0
独立按键 (可选) P3.2 外部按键,按下为低电平

电位器分压与键值对应关系 (VCC=5V):

电压范围 键值 功能
0~200mV 无按键 -
800~1200mV 1 UP/加
1800~2200mV 2 DOWN/减
3200~3600mV 3 SET/确认
4200~4600mV 4 ESC/退出

操作说明

  1. 上电:显示 "HELL" 约1秒后进入ADC电压显示模式
  2. 显示ADC值模式:4位数码管显示当前P1.0引脚电压值(单位mV)
  3. 进入设置:旋转电位器使电压进入SET范围(约3.2V~3.6V),进入时间设置模式
  4. 调整时间
    • 在设置模式下,当前调整的位会闪烁
    • 旋转电位器到UP范围(约0.8V~1.2V)增加数值
    • 旋转到DOWN范围(约1.8V~2.2V)减少数值
    • SET键(约3.2V~3.6V)进入下一位设置
    • ESC键(约4.2V~4.6V)退出设置
  5. 设置顺序:时十位 → 时个位 → 分十位 → 分个位 → 完成设置

程序关键点说明

  1. ADC配置:参考手册"ADC模数转换"章节,使用查询方式读取,右对齐格式
  2. 定时器:使用定时器0产生1ms中断,用于数码管动态扫描和系统计时
  3. 数码管驱动:采用动态扫描方式,每1ms刷新一位,4ms完成一轮刷新,人眼无闪烁感
  4. 按键消抖:连续3次(约300ms)检测到相同键值才确认按键有效
  5. 闪烁效果:在设置模式下,当前调整位以500ms周期闪烁提示

如需调整ADC阈值或修改引脚分配,请修改程序开头的宏定义部分即可。


🔍 搜索 "AI8051U如何使用ADC扫描数码管并显示键值和调整时间 帮生成程序并含详细程序注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. 在用stc8h8k64u的p1口做矩阵键盘时为什么9一直识别成4
  3. 用 STC8H8K64U的P1口做矩阵键盘识别有问题

⏱️ 查找用时 19.9s | 总回答用时 74.5s

回复

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:84
  • 最近打卡:2026-07-21 14:53:00

29

主题

182

回帖

1973

积分

版主

积分
1973
发表于 前天 14:59 | 显示全部楼层

STC32G8K64-33A-LQFP48设计一个汽车氛围灯项目,提供范例程序并含详细注解
截图202607201459138260.jpg
回复

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:84
  • 最近打卡:2026-07-21 14:53:00

29

主题

182

回帖

1973

积分

版主

积分
1973
发表于 前天 15:00 | 显示全部楼层

好的,我已经获取了足够的信息。现在让我来编写完整的汽车氛围灯项目代码。

根据手册信息,STC32G8K64-33A-LQFP48 的资源分配如下:

硬件资源规划:

  • PWMA(PWM1P/P1.0):RGB LED 红色通道(强推挽输出)
  • PWMA(PWM2P/P1.2):RGB LED 绿色通道(强推挽输出)
  • PWMA(PWM3P/P1.4):RGB LED 蓝色通道(强推挽输出)
  • UART1(P3.0/P3.1):与车身CAN/LIN模块通信(LIN协议或自定义协议)
  • ADC0(P1.0):可复用为按键/触摸检测(或独立按键)
  • P3.2:模式切换按键(INT0)
  • 系统时钟:22.1184MHz(内部IRC)

现在开始编写完整的项目代码:

/******************************************************************************
 * 项目名称:STC32G8K64-33A 汽车氛围灯控制器
 * 芯片型号:STC32G8K64-33A-LQFP48
 * 开发环境:Keil C251
 * 系统时钟:22.1184MHz(内部IRC)
 * 
 * 功能说明:
 *   1. 通过UART1接收LIN总线/自定义协议命令,控制RGB氛围灯
 *   2. 支持多种灯光模式:静态颜色、呼吸渐变、流水灯、音乐律动
 *   3. 使用PWMA高级PWM输出三路RGB PWM信号(16位精度)
 *   4. 支持P3.2按键切换灯光模式
 *   5. 支持通过ADC读取外部调色电位器或触摸按键
 *   6. 支持CAN/LIN总线通信(预留接口)
 * 
 * 硬件连接:
 *   PWM1P (P1.0) —— R通道(红色LED阳极,经限流电阻)
 *   PWM2P (P1.2) —— G通道(绿色LED阳极,经限流电阻)
 *   PWM3P (P1.4) —— B通道(蓝色LED阳极,经限流电阻)
 *   UART1_TXD (P3.1) —— LIN总线收发器TXD
 *   UART1_RXD (P3.0) —— LIN总线收发器RXD
 *   P3.2 —— 模式切换按键(按下为低电平)
 *   P1.6 —— ADC输入(可选调色电位器/触摸)
 * 
 * 通信协议(自定义简易LIN风格):
 *   主机发送:0x55(同步)+ ID + DATA[0~7] + Checksum
 *   命令格式:
 *     ID=0x01: 设置RGB颜色  DATA[0]=R, DATA[1]=G, DATA[2]=B
 *     ID=0x02: 设置模式     DATA[0]=模式编号
 *     ID=0x03: 设置亮度     DATA[0]=亮度值(0~100)
 *     ID=0x04: 设置速度     DATA[0]=速度值(0~100)
 * 
 * 注意事项:
 *   1. 除P3.0/P3.1外,所有I/O上电后为高阻输入,使用前必须设置模式
 *   2. I/O驱动LED时使用强推挽模式高电平输出,不要使用低电平灌电流
 *   3. 操作XFR寄存器前必须设置 P_SW2 |= 0x80
 *   4. 本代码使用Keil C251编译,适用于STC32G系列
 ******************************************************************************/

#include "stc32g.h"        // STC32G系列头文件(从ISP软件获取)
#include "intrins.h"       // 内嵌函数(_nop_等)

/* ========================== 宏定义 ========================== */
#define FOSC                22118400UL      // 系统时钟频率 22.1184MHz
#define BRT_UART1           (65536 - (FOSC / 115200 + 2) / 4)  // UART1波特率115200
                                            // 加2实现四舍五入

/* PWM频率与占空比相关 */
#define PWM_FREQ            20000UL         // PWM频率20KHz(避免人耳可闻噪声)
#define PWM_PERIOD          (FOSC / PWM_FREQ)  // PWM周期计数值
                                            // 22.1184MHz/20KHz ≈ 1106

/* 灯光模式枚举 */
#define MODE_STATIC         0               // 静态颜色模式
#define MODE_BREATH         1               // 呼吸渐变模式
#define MODE_FLOW           2               // 流水灯模式
#define MODE_MUSIC          3               // 音乐律动模式(预留)
#define MODE_MAX            3               // 最大模式编号

/* 亮度范围 */
#define BRIGHTNESS_MIN      0
#define BRIGHTNESS_MAX      100

/* 按键消抖时间 */
#define KEY_DEBOUNCE_MS     20

/* 缓冲区大小 */
#define UART1_RX_BUF_SIZE   16

/* ========================== 数据类型定义 ========================== */
typedef unsigned char   u8;
typedef unsigned int    u16;
typedef unsigned long   u32;

/* ========================== 全局变量 ========================== */

/* ---- RGB颜色变量 ---- */
u8 g_red;                   // 红色通道值 0~255
u8 g_green;                 // 绿色通道值 0~255
u8 g_blue;                  // 蓝色通道值 0~255
u8 g_brightness;            // 整体亮度 0~100(百分比)

/* ---- 模式变量 ---- */
u8 g_mode;                  // 当前灯光模式
u8 g_speed;                 // 渐变/流水速度 0~100
u8 g_mode_change_flag;      // 模式改变标志(主循环检测)

/* ---- 呼吸渐变变量 ---- */
u16 g_breath_step;          // 呼吸渐变步进计数器
u8  g_breath_direction;     // 呼吸方向:0=变亮,1=变暗
u8  g_breath_value;         // 当前呼吸亮度值 0~255

/* ---- 流水灯变量 ---- */
u8  g_flow_index;           // 流水灯当前索引
u16 g_flow_timer;           // 流水灯定时计数器

/* ---- UART1通信变量 ---- */
u8  g_uart1_rx_buf[UART1_RX_BUF_SIZE];  // 接收缓冲区
u8  g_uart1_rx_cnt;         // 接收字节计数
u8  g_uart1_rx_timeout;     // 接收超时计数
bit g_uart1_busy;           // 发送忙标志
bit g_uart1_rx_complete;    // 一帧接收完成标志

/* ---- 按键变量 ---- */
bit g_key_press;            // 按键按下标志
u8  g_key_timer;            // 按键消抖计时

/* ---- 系统定时器变量 ---- */
bit g_1ms_flag;             // 1ms定时标志
u16 g_sys_tick;             // 系统滴答计数(每1ms+1)

/* ========================== 函数声明 ========================== */
void System_Init(void);
void GPIO_Init(void);
void PWMA_Init(void);
void UART1_Init(void);
void Timer0_Init(void);
void ADC_Init(void);

void PWMA_SetRGB(u8 red, u8 green, u8 blue);
void PWMA_SetBrightness(u8 percent);

void Mode_Update(void);
void Mode_Static(void);
void Mode_Breath(void);
void Mode_Flow(void);

void UART1_SendByte(u8 dat);
void UART1_SendString(u8 *p);
void UART1_ProcessCommand(void);

void Key_Scan(void);
void delay_ms(u16 ms);

/* ========================== 主函数 ========================== */
void main(void)
{
    /* ---- 系统初始化 ---- */
    System_Init();          // 系统基本配置(时钟、等待周期等)
    GPIO_Init();            // I/O口初始化
    PWMA_Init();            // 高级PWM初始化(RGB三通道)
    UART1_Init();           // UART1初始化(LIN总线通信)
    Timer0_Init();          // 定时器0初始化(1ms系统滴答)
    ADC_Init();             // ADC初始化(预留,用于调色电位器)

    /* ---- 默认参数 ---- */
    g_red = 255;            // 默认红色
    g_green = 128;          // 默认绿色(混合成暖色)
    g_blue = 0;             // 默认蓝色
    g_brightness = 50;      // 默认亮度50%
    g_mode = MODE_STATIC;   // 默认静态模式
    g_speed = 50;           // 默认速度50

    g_breath_step = 0;
    g_breath_direction = 0;
    g_breath_value = 0;
    g_flow_index = 0;
    g_flow_timer = 0;

    /* ---- 使能中断 ---- */
    ES = 1;                 // 使能UART1中断
    ET0 = 1;                // 使能定时器0中断
    EA = 1;                 // 打开总中断

    /* ---- 发送启动信息 ---- */
    UART1_SendString("Ambient Light Controller Ready!\r\n");

    /* ======================== 主循环 ======================== */
    while (1)
    {
        /* ---- 1ms定时任务 ---- */
        if (g_1ms_flag)
        {
            g_1ms_flag = 0;
            g_sys_tick++;

            /* 按键扫描(每1ms执行一次) */
            Key_Scan();

            /* UART1接收超时检测 */
            if (g_uart1_rx_timeout > 0)
            {
                g_uart1_rx_timeout--;
                if (g_uart1_rx_timeout == 0)
                {
                    g_uart1_rx_complete = 1;  // 接收超时,认为一帧结束
                }
            }
        }

        /* ---- 处理UART1接收到的命令 ---- */
        if (g_uart1_rx_complete)
        {
            g_uart1_rx_complete = 0;
            UART1_ProcessCommand();  // 解析并执行命令
        }

        /* ---- 模式切换处理 ---- */
        if (g_mode_change_flag)
        {
            g_mode_change_flag = 0;
            /* 模式切换时重置相关变量 */
            g_breath_step = 0;
            g_breath_direction = 0;
            g_flow_index = 0;
            g_flow_timer = 0;
        }

        /* ---- 根据当前模式更新灯光 ---- */
        Mode_Update();

        /* ---- 简单延时,避免主循环过快 ---- */
        delay_ms(1);
    }
}

/* ========================== 系统初始化 ========================== */
void System_Init(void)
{
    /* 使能访问扩展寄存器(XFR),操作PWMA等寄存器前必须设置 */
    P_SW2 |= 0x80;          // EAXFR = 1

    /* 设置程序代码等待参数,赋值为0可将CPU执行速度设置为最快 */
    WTST = 0x00;

    /* 设置外部数据总线速度为最快 */
    CKCON = 0x00;
}

/* ========================== GPIO初始化 ========================== */
void GPIO_Init(void)
{
    /* 将所有I/O口初始化为准双向口(除P3.0/P3.1默认准双向外,其余上电为高阻) */
    P0M0 = 0x00;  P0M1 = 0x00;
    P1M0 = 0x00;  P1M1 = 0x00;
    P2M0 = 0x00;  P2M1 = 0x00;
    P3M0 = 0x00;  P3M1 = 0x00;
    P4M0 = 0x00;  P4M1 = 0x00;
    P5M0 = 0x00;  P5M1 = 0x00;

    /* ---- PWM输出引脚:设置为强推挽输出 ---- */
    /* PWM1P -> P1.0 (R通道) */
    /* PWM2P -> P1.2 (G通道) */
    /* PWM3P -> P1.4 (B通道) */
    /* 强推挽模式:PxM0=1, PxM1=0 */
    P1M0 |= 0x15;           // 置位P1.0、P1.2、P1.4为强推挽输出
    P1M1 &= ~0x15;          // 对应位清零

    /* ---- P3.2按键输入:设置为高阻输入 ---- */
    /* 高阻输入模式:PxM0=0, PxM1=1 */
    P3M0 &= ~0x04;          // P3.2对应位清零
    P3M1 |= 0x04;           // P3.2对应位置1

    /* ---- P1.6 ADC输入:设置为高阻输入(预留) ---- */
    P1M0 &= ~0x40;          // P1.6对应位清零
    P1M1 |= 0x40;           // P1.6对应位置1
}

/* ========================== PWMA高级PWM初始化 ========================== */
/*
 * 使用PWMA第一组PWM定时器,输出三路PWM:
 *   PWM1P (P1.0) -> 红色通道
 *   PWM2P (P1.2) -> 绿色通道
 *   PWM3P (P1.4) -> 蓝色通道
 *
 * 配置为20KHz频率,16位精度
 * 时钟源 = 系统时钟 / (PSCR + 1)
 * PWM频率 = 时钟源 / (ARR + 1)
 * 占空比 = CCR / (ARR + 1)
 */
void PWMA_Init(void)
{
    u16 psc_value;
    u16 arr_value;

    /* 计算预分频和自动重装值 */
    /* 目标PWM频率 = 20KHz */
    /* 设预分频PSCR=0,则时钟 = FOSC/1 = 22.1184MHz */
    /* ARR = 22.1184MHz / 20KHz - 1 = 1105 */
    psc_value = 0;          // 预分频值(PSCR = 0,不分频)
    arr_value = PWM_PERIOD - 1;  // 自动重装值

    /* ---- 关闭PWMA使能,先配置寄存器 ---- */
    PWMA_CR1 = 0x00;        // 关闭计数器

    /* ---- 设置预分频器 ---- */
    PWMA_PSCRH = (u8)(psc_value >> 8);   // 预分频高8位
    PWMA_PSCRL = (u8)(psc_value);        // 预分频低8位

    /* ---- 设置自动重装寄存器(决定PWM周期) ---- */
    PWMA_ARRH = (u8)(arr_value >> 8);    // 自动重装高8位
    PWMA_ARRL = (u8)(arr_value);         // 自动重装低8位

    /* ---- 设置初始占空比为0(所有通道关闭) ---- */
    /* 通道1:PWM1P (R) */
    PWMA_CCR1H = 0;
    PWMA_CCR1L = 0;
    /* 通道2:PWM2P (G) */
    PWMA_CCR2H = 0;
    PWMA_CCR2L = 0;
    /* 通道3:PWM3P (B) */
    PWMA_CCR3H = 0;
    PWMA_CCR3L = 0;

    /* ---- 配置捕获/比较模式寄存器 ---- */
    /* CC1S=00(输出模式), OC1M=110(PWM模式1,向上计数时低电平有效) */
    /* PWM模式1:CNT < CCR时输出有效电平,CNT >= CCR时输出无效电平 */
    PWMA_CCMR1 = 0x60;      // 通道1:PWM模式1,输出
    PWMA_CCMR2 = 0x60;      // 通道2:PWM模式1,输出
    PWMA_CCMR3 = 0x60;      // 通道3:PWM模式1,输出

    /* ---- 配置捕获/比较使能寄存器 ---- */
    /* CC1E=1(使能通道1输出), CC2E=1, CC3E=1 */
    PWMA_CCER1 = 0x00;      // 先清零
    PWMA_CCER1 |= 0x01;     // CC1E=1,使能PWM1P输出(CC1P=0,高电平有效)
    PWMA_CCER1 |= 0x10;     // CC2E=1,使能PWM2P输出(CC2P=0,高电平有效)
    PWMA_CCER2 = 0x00;      // 先清零
    PWMA_CCER2 |= 0x01;     // CC3E=1,使能PWM3P输出(CC3P=0,高电平有效)

    /* ---- 配置刹车和死区寄存器(不使用死区) ---- */
    PWMA_BKR = 0x80;        // MOE=1,使能主输出

    /* ---- 配置自动重装预装载使能 ---- */
    PWMA_CR1 |= 0x01;       // CEN=1,使能计数器
}

/* ========================== 设置RGB PWM占空比 ========================== */
/*
 * 参数:red, green, blue 范围 0~255
 * 实际占空比 = (value / 255) * (PWM_PERIOD)
 * 即 CCR = (u16)((u32)value * PWM_PERIOD / 256)
 * 注意:由于PWM_PERIOD=1106,乘以255后约282030,在u32范围内
 */
void PWMA_SetRGB(u8 red, u8 green, u8 blue)
{
    u16 ccr1, ccr2, ccr3;
    u32 tmp;

    /* 计算红色通道CCR值 */
    tmp = (u32)red * PWM_PERIOD;
    ccr1 = (u16)(tmp / 256);        // 除以256相当于>>8,但保留精度

    /* 计算绿色通道CCR值 */
    tmp = (u32)green * PWM_PERIOD;
    ccr2 = (u16)(tmp / 256);

    /* 计算蓝色通道CCR值 */
    tmp = (u32)blue * PWM_PERIOD;
    ccr3 = (u16)(tmp / 256);

    /* 写入捕获/比较寄存器 */
    PWMA_CCR1H = (u8)(ccr1 >> 8);
    PWMA_CCR1L = (u8)(ccr1);
    PWMA_CCR2H = (u8)(ccr2 >> 8);
    PWMA_CCR2L = (u8)(ccr2);
    PWMA_CCR3H = (u8)(ccr3 >> 8);
    PWMA_CCR3L = (u8)(ccr3);
}

/* ========================== 设置整体亮度 ========================== */
/*
 * 参数:percent 亮度百分比 0~100
 * 将当前RGB值按百分比缩放后输出
 */
void PWMA_SetBrightness(u8 percent)
{
    u8 r, g, b;
    u16 tmp;

    if (percent > 100) percent = 100;

    /* 按百分比缩放RGB值 */
    tmp = (u16)g_red * percent;
    r = (u8)(tmp / 100);

    tmp = (u16)g_green * percent;
    g = (u8)(tmp / 100);

    tmp = (u16)g_blue * percent;
    b = (u8)(tmp / 100);

    PWMA_SetRGB(r, g, b);
}

/* ========================== UART1初始化 ========================== */
/*
 * 使用定时器2作为波特率发生器
 * 波特率 = FOSC / 4 / (65536 - T2重装值)
 * 115200 = 22118400 / 4 / (65536 - BRT)
 * BRT = 65536 - 22118400/4/115200 = 65536 - 48 = 65488 = 0xFFD0
 */
void UART1_Init(void)
{
    /* ---- 配置UART1为模式1(8位数据,可变波特率) ---- */
    SCON = 0x50;            // SM0=0, SM1=1(模式1), REN=1(允许接收)

    /* ---- 选择定时器2作为波特率发生器 ---- */
    AUXR |= 0x01;           // S1BRT=1,选择定时器2

    /* ---- 配置定时器2为1T模式,设置重装值 ---- */
    T2L = (u8)BRT_UART1;           // 定时器2低8位
    T2H = (u8)(BRT_UART1 >> 8);    // 定时器2高8位
    AUXR |= 0x10;           // T2x12=1,1T模式
    AUXR |= 0x04;           // T2R=1,启动定时器2

    /* ---- 初始化变量 ---- */
    g_uart1_busy = 0;
    g_uart1_rx_cnt = 0;
    g_uart1_rx_timeout = 0;
    g_uart1_rx_complete = 0;
}

/* ========================== UART1发送一个字节 ========================== */
void UART1_SendByte(u8 dat)
{
    while (g_uart1_busy);   // 等待上次发送完成
    g_uart1_busy = 1;       // 设置忙标志
    SBUF = dat;             // 写入发送缓冲区,启动发送
}

/* ========================== UART1发送字符串 ========================== */
void UART1_SendString(u8 *p)
{
    while (*p)
    {
        UART1_SendByte(*p++);
    }
}

/* ========================== UART1中断服务函数 ========================== */
void UART1_ISR(void) interrupt 4
{
    if (TI)                 // 发送中断
    {
        TI = 0;             // 清除发送中断标志
        g_uart1_busy = 0;   // 清除发送忙标志
    }

    if (RI)                 // 接收中断
    {
        RI = 0;             // 清除接收中断标志

        /* 将接收到的数据存入缓冲区 */
        if (g_uart1_rx_cnt < UART1_RX_BUF_SIZE)
        {
            g_uart1_rx_buf[g_uart1_rx_cnt++] = SBUF;
        }

        /* 重置超时计数器(约10ms超时判断一帧结束) */
        g_uart1_rx_timeout = 10;
    }
}

/* ========================== UART1命令处理 ========================== */
/*
 * 自定义简易协议格式:
 *   帧头(0x55) + ID(1字节) + DATA(0~8字节) + Checksum(1字节)
 *   Checksum = 0xFF - (ID + DATA[0] + ... + DATA[n-1])
 *
 * 命令定义:
 *   ID=0x01: 设置RGB颜色  DATA[0]=R, DATA[1]=G, DATA[2]=B
 *   ID=0x02: 设置模式     DATA[0]=模式编号
 *   ID=0x03: 设置亮度     DATA[0]=亮度值(0~100)
 *   ID=0x04: 设置速度     DATA[0]=速度值(0~100)
 */
void UART1_ProcessCommand(void)
{
    u8 i;
    u8 id;
    u8 checksum;
    u8 calc_checksum;
    u8 data_len;

    /* 至少需要3字节:帧头 + ID + Checksum */
    if (g_uart1_rx_cnt < 3)
    {
        g_uart1_rx_cnt = 0;
        return;
    }

    /* 检查帧头 */
    if (g_uart1_rx_buf[0] != 0x55)
    {
        g_uart1_rx_cnt = 0;
        return;
    }

    /* 提取ID */
    id = g_uart1_rx_buf[1];

    /* 数据长度 = 总长度 - 帧头(1) - ID(1) - Checksum(1) */
    data_len = g_uart1_rx_cnt - 3;

    /* 计算校验和 */
    calc_checksum = id;
    for (i = 0; i < data_len; i++)
    {
        calc_checksum += g_uart1_rx_buf[2 + i];
    }
    calc_checksum = 0xFF - calc_checksum;

    /* 校验和检查 */
    checksum = g_uart1_rx_buf[g_uart1_rx_cnt - 1];
    if (calc_checksum != checksum)
    {
        /* 校验失败,发送错误响应 */
        UART1_SendByte(0x55);
        UART1_SendByte(0xFF);       // 错误响应ID
        UART1_SendByte(0x01);       // 错误码:校验失败
        UART1_SendByte(0xFF - 0xFF - 0x01);  // Checksum
        g_uart1_rx_cnt = 0;
        return;
    }

    /* ---- 根据ID执行命令 ---- */
    switch (id)
    {
        case 0x01:  /* 设置RGB颜色 */
            if (data_len >= 3)
            {
                g_red   = g_uart1_rx_buf[2];  // R值
                g_green = g_uart1_rx_buf[3];  // G值
                g_blue  = g_uart1_rx_buf[4];  // B值
                /* 立即更新灯光 */
                PWMA_SetBrightness(g_brightness);
                /* 发送响应 */
                UART1_SendByte(0x55);
                UART1_SendByte(0x81);       // 响应ID = 0x80 | 命令ID
                UART1_SendByte(0x00);       // 成功
                UART1_SendByte(0xFF - 0x81 - 0x00);
            }
            break;

        case 0x02:  /* 设置模式 */
            if (data_len >= 1)
            {
                g_mode = g_uart1_rx_buf[2];
                if (g_mode > MODE_MAX) g_mode = MODE_MAX;
                g_mode_change_flag = 1;
                /* 发送响应 */
                UART1_SendByte(0x55);
                UART1_SendByte(0x82);
                UART1_SendByte(0x00);
                UART1_SendByte(0xFF - 0x82 - 0x00);
            }
            break;

        case 0x03:  /* 设置亮度 */
            if (data_len >= 1)
            {
                g_brightness = g_uart1_rx_buf[2];
                if (g_brightness > 100) g_brightness = 100;
                PWMA_SetBrightness(g_brightness);
                /* 发送响应 */
                UART1_SendByte(0x55);
                UART1_SendByte(0x83);
                UART1_SendByte(0x00);
                UART1_SendByte(0xFF - 0x83 - 0x00);
            }
            break;

        case 0x04:  /* 设置速度 */
            if (data_len >= 1)
            {
                g_speed = g_uart1_rx_buf[2];
                if (g_speed > 100) g_speed = 100;
                /* 发送响应 */
                UART1_SendByte(0x55);
                UART1_SendByte(0x84);
                UART1_SendByte(0x00);
                UART1_SendByte(0xFF - 0x84 - 0x00);
            }
            break;

        default:
            /* 未知命令,发送错误响应 */
            UART1_SendByte(0x55);
            UART1_SendByte(0xFF);
            UART1_SendByte(0x02);       // 错误码:未知命令
            UART1_SendByte(0xFF - 0xFF - 0x02);
            break;
    }

    /* 清空接收缓冲区 */
    g_uart1_rx_cnt = 0;
}

/* ========================== 定时器0初始化(1ms系统滴答) ========================== */
void Timer0_Init(void)
{
    u16 reload_value;

    /* 定时器0作为16位自动重装载定时器 */
    /* 1T模式,计数频率 = FOSC */
    /* 定时时间 = (65536 - 重装值) / FOSC */
    /* 1ms = (65536 - reload) / 22118400 */
    /* reload = 65536 - 22118400 / 1000 = 65536 - 22118 = 43418 = 0xA99A */
    reload_value = 65536 - (u16)(FOSC / 1000);

    TMOD &= 0xF0;           // 清零T0的M1M0位
    TMOD |= 0x00;           // 模式0:16位自动重装载(实际上T0模式0是13位,需用模式1)
                            // 更正:使用模式1(16位定时器)
    TMOD &= 0xF0;
    TMOD |= 0x01;           // T0模式1:16位定时器

    T0x12 = 1;              // T0x12=1,1T模式
    TH0 = (u8)(reload_value >> 8);
    TL0 = (u8)(reload_value);
    TR0 = 1;                // 启动定时器0
}

/* ========================== 定时器0中断服务函数 ========================== */
void Timer0_ISR(void) interrupt 1
{
    /* 定时器0是16位模式,需要软件重装 */
    /* 实际上模式1不是自动重装载,需要手动重装 */
    /* 但STC32G的T0有自动重装载功能,使用TMOD的GATE位... */
    /* 更可靠的方式:使用模式0(16位自动重装载) */
    /* 修正:使用TMOD &= 0xF0; TMOD |= 0x00; 即模式0 */
    /* 模式0是16位自动重装载定时器 */
  
    /* 这里使用模式0,硬件自动重装,只需置标志 */
    g_1ms_flag = 1;
}

/* ========================== ADC初始化(预留) ========================== */
void ADC_Init(void)
{
    /* 配置ADC时序 */
    P_SW2 |= 0x80;          // 确保EAXFR=1
    ADCTIM = 0x3F;          // 设置ADC内部时序(CSSETUP=0, CSHOLD=01, SMPDUTY=11111)
  
    /* 配置ADC时钟:系统时钟/2/16 */
    /* FADC = FOSC / 2 / (SPEED+1) = 22.1184MHz / 2 / 16 ≈ 691KHz */
    ADCCFG = 0x0F;          // RESFMT=0(左对齐), SPEED=1111(/16)
  
    /* 使能ADC电源 */
    ADC_POWER = 1;          // 打开ADC电源
  
    /* 注意:打开ADC电源后需等待约1ms再启动转换 */
}

/* ========================== 按键扫描 ========================== */
/*
 * P3.2按键检测,每1ms调用一次
 * 按下为低电平,松开为高电平
 * 检测到按下后切换灯光模式
 */
void Key_Scan(void)
{
    static u8 key_debounce_cnt = 0;
    static bit key_last_state = 1;   // 上次状态,默认高电平(松开)
    static bit key_press_processed = 0;  // 按键按下是否已处理

    /* 读取当前按键状态 */
    bit key_current = (P32 != 0);    // 1=松开,0=按下

    /* 消抖处理 */
    if (key_current != key_last_state)
    {
        key_debounce_cnt++;
        if (key_debounce_cnt >= KEY_DEBOUNCE_MS)
        {
            key_debounce_cnt = 0;
            key_last_state = key_current;

            /* 检测到按下动作(从松开变为按下) */
            if (key_current == 0)   // 按下
            {
                if (!key_press_processed)
                {
                    key_press_processed = 1;

                    /* 切换模式 */
                    g_mode++;
                    if (g_mode > MODE_MAX) g_mode = 0;
                    g_mode_change_flag = 1;

                    /* 通过UART1发送模式切换信息 */
                    UART1_SendString("Mode: ");
                    UART1_SendByte(g_mode + '0');
                    UART1_SendString("\r\n");
                }
            }
            else                    // 松开
            {
                key_press_processed = 0;
            }
        }
    }
    else
    {
        key_debounce_cnt = 0;
    }
}

/* ========================== 模式更新 ========================== */
void Mode_Update(void)
{
    switch (g_mode)
    {
        case MODE_STATIC:
            Mode_Static();
            break;
        case MODE_BREATH:
            Mode_Breath();
            break;
        case MODE_FLOW:
            Mode_Flow();
            break;
        case MODE_MUSIC:
            /* 音乐律动模式(预留),暂用静态代替 */
            Mode_Static();
            break;
        default:
            Mode_Static();
            break;
    }
}

/* ========================== 静态颜色模式 ========================== */
void Mode_Static(void)
{
    /* 静态模式:直接输出设定的RGB颜色 */
    /* 已在命令处理或初始化时设置,无需额外操作 */
    /* 但需要确保亮度正确 */
    PWMA_SetBrightness(g_brightness);
}

/* ========================== 呼吸渐变模式 ========================== */
/*
 * 呼吸效果:在设定的颜色基础上,整体亮度做正弦波渐变
 * 使用查表法或线性渐变实现
 * 速度控制:g_speed影响渐变步进间隔
 */
void Mode_Breath(void)
{
    static u16 breath_counter = 0;
    u16 speed_factor;
    u8 breath_brightness;
    u16 tmp;

    /* 速度因子:速度越快,步进越大 */
    /* g_speed范围0~100,映射到步进间隔1~20 */
    speed_factor = 21 - (u16)g_speed * 20 / 100;
    if (speed_factor < 1) speed_factor = 1;

    breath_counter++;
    if (breath_counter >= speed_factor)
    {
        breath_counter = 0;

        /* 更新呼吸步进 */
        if (g_breath_direction == 0)    // 变亮
        {
            g_breath_step++;
            if (g_breath_step >= 255)
            {
                g_breath_step = 255;
                g_breath_direction = 1;  // 切换为变暗
            }
        }
        else                            // 变暗
        {
            if (g_breath_step > 0)
            {
                g_breath_step--;
            }
            else
            {
                g_breath_step = 0;
                g_breath_direction = 0;  // 切换为变亮
            }
        }

        /* 计算当前呼吸亮度值(0~255) */
        g_breath_value = (u8)g_breath_step;

        /* 将呼吸亮度叠加到当前颜色上 */
        tmp = (u16)g_red * g_breath_value / 255;
        if (tmp > 255) tmp = 255;
        breath_brightness = (u8)tmp;

        tmp = (u16)g_green * g_breath_value / 255;
        if (tmp > 255) tmp = 255;

        tmp = (u16)g_blue * g_breath_value / 255;
        if (tmp > 255) tmp = 255;

        /* 应用亮度百分比 */
        PWMA_SetBrightness(g_brightness);
    }
}

/* ========================== 流水灯模式 ========================== */
/*
 * 流水效果:RGB三通道依次亮起,形成流水效果
 * 速度控制:g_speed影响流水速度
 */
void Mode_Flow(void)
{
    static u16 flow_counter = 0;
    u16 speed_factor;
    u8 r, g, b;

    /* 速度因子 */
    speed_factor = 51 - (u16)g_speed * 50 / 100;
    if (speed_factor < 1) speed_factor = 1;

    flow_counter++;
    if (flow_counter >= speed_factor)
    {
        flow_counter = 0;

        /* 流水索引递增 */
        g_flow_index++;
        if (g_flow_index >= 3) g_flow_index = 0;

        /* 根据流水索引设置RGB输出 */
        switch (g_flow_index)
        {
            case 0:  /* 红色 */
                r = g_red;
                g = 0;
                b = 0;
                break;
            case 1:  /* 绿色 */
                r = 0;
                g = g_green;
                b = 0;
                break;
            case 2:  /* 蓝色 */
                r = 0;
                g = 0;
                b = g_blue;
                break;
            default:
                r = g_red;
                g = g_green;
                b = g_blue;
                break;
        }

        /* 应用亮度 */
        {
            u16 tmp_r, tmp_g, tmp_b;
            tmp_r = (u16)r * g_brightness / 100;
            tmp_g = (u16)g * g_brightness / 100;
            tmp_b = (u16)b * g_brightness / 100;
            PWMA_SetRGB((u8)tmp_r, (u8)tmp_g, (u8)tmp_b);
        }
    }
}

/* ========================== 毫秒延时函数 ========================== */
void delay_ms(u16 ms)
{
    u16 i, j;
    for (i = 0; i < ms; i++)
    {
        /* 粗略延时约1ms(22.1184MHz下约循环18432次) */
        for (j = 0; j < 1843; j++)
        {
            _nop_();
        }
    }
}

项目说明

一、硬件设计要点

1. RGB LED驱动电路(建议)

由于STC32G8K64的I/O口驱动能力有限(建议总电流不超过60mA),驱动RGB LED时请按以下方式设计:

VCC(5V/3.3V)
    │
    ├─── 限流电阻R_R ──── RGB_R阳极 ─── RGB_R阴极 ──── P1.0(PWM1P)
    ├─── 限流电阻R_G ──── RGB_G阳极 ─── RGB_G阴极 ──── P1.2(PWM2P)
    └─── 限流电阻R_B ──── RGB_B阳极 ─── RGB_B阴极 ──── P1.4(PWM3P)

重要:根据手册建议,I/O驱动LED时应使用强推挽模式高电平输出,不要使用低电平灌电流方式,以避免GND被抬高影响ADC精度。

2. LIN总线接口(UART1)

P3.1(TXD) ──── LIN收发器(TXD) ──── LIN总线
P3.0(RXD) ──── LIN收发器(RXD) ──── LIN总线

STC32G8K64的USART1支持硬件LIN模式,可通过设置 USARTCR1 |= 0x80(LINEN=1)使能。

3. 电源与去耦

  • Vcc和AVcc应连接在一起,并加10μF+0.1μF去耦电容
  • ADC_VRef+不能浮空,必须接Vcc或外部参考源

二、软件架构说明

模块 功能 使用资源
PWMA RGB三通道PWM输出(20KHz) PWM1P(P1.0), PWM2P(P1.2), PWM3P(P1.4)
UART1 LIN总线/自定义协议通信 P3.0(RxD), P3.1(TxD)
Timer0 1ms系统滴答定时器 定时器0
GPIO 按键输入 P3.2(INT0)
ADC 预留调色/触摸检测 P1.6(ADC6)

三、通信协议

本程序实现了自定义简易协议,格式如下:

字节 内容 说明
0 0x55 帧头
1 ID 命令标识
2~N-1 DATA 数据区
N Checksum 校验和 = 0xFF - (ID + DATA累加和)

支持的命令:

ID 功能 数据格式
0x01 设置RGB颜色 R, G, B (各0~255)
0x02 设置模式 模式编号 (0~3)
0x03 设置亮度 亮度值 (0~100)
0x04 设置速度 速度值 (0~100)

四、使用说明

  1. 编译环境:使用Keil C251编译器,选择芯片型号 STC32G8K64
  2. 头文件:从 STC-ISP 下载软件 获取 stc32g.h
  3. 下载设置:使用STC-ISP软件,选择内部IRC时钟 22.1184MHz
  4. 调试:通过UART1(P3.0/P3.1)连接串口助手,波特率115200,可查看调试信息

五、扩展建议

  1. CAN总线:STC32G8K64支持2路CAN总线,可连接车身CAN网络获取车速、车门状态等信息实现联动
  2. LIN从机模式:设置 USARTCR5 |= 0x20(SLVEN=1)可使能LIN从机模式,配合自动同步功能
  3. 音乐律动:可通过ADC采集音频信号,或通过CAN/LIN接收音乐节奏信息,实现灯光随音乐律动
  4. 触摸按键:可使用P3.2或ADC通道实现触摸感应调色

🔍 搜索 "STC32G8K64-33A-LQFP48设计一个汽车氛围灯项目 提供范例程序并含详细注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. STC单片机在玻璃变色控制器中的应用,已推荐 STC32G8K64-33A-LQFP48
  3. STC单片机在摩托车双向快排的应用 STC32G8K64-33A-LQFP48

⏱️ 查找用时 16.8s | 总回答用时 87.5s

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:55
  • 最近打卡:2026-07-21 15:11:12

21

主题

200

回帖

2092

积分

版主

积分
2092
发表于 前天 15:23 | 显示全部楼层
STC8H8K64U-45I-LQFP64设计一个智能锁项目,提供范例程序并含详细注解
截图202607201528411391.jpg
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:55
  • 最近打卡:2026-07-21 15:11:12

21

主题

200

回帖

2092

积分

版主

积分
2092
发表于 前天 15:23 | 显示全部楼层

好的,我已经获取了足够的手册资料。现在让我来编写一个完整的智能锁项目范例程序。

根据STC8H8K64U-45I-LQFP64的芯片特性,我将设计一个功能完整的智能锁项目,包含以下功能模块:

  1. 矩阵键盘输入(密码输入)
  2. OLED显示(I²C接口)
  3. 电磁锁控制(继电器/PWM)
  4. EEPROM存储(密码存储)
  5. 串口调试(串口1)
  6. 蜂鸣器提示
  7. LED指示灯

以下是完整的项目代码:


STC8H8K64U 智能锁项目范例程序

文件1:main.c - 主程序

/*
 * ============================================================
 * 项目名称:STC8H8K64U 智能锁控制系统
 * 芯片型号:STC8H8K64U-45I-LQFP64
 * 工作频率:11.0592MHz(内部IRC)
 * 功能说明:
 *   1. 4x4矩阵键盘输入密码
 *   2. OLED 128x64 I2C显示
 *   3. 电磁锁控制(继电器输出)
 *   4. EEPROM存储密码(支持修改)
 *   5. 串口1调试输出(115200bps)
 *   6. 蜂鸣器提示音
 *   7. LED状态指示
 *   
 * 硬件连接(LQFP64封装):
 *   P3.0(RxD) - 串口1接收(调试用)
 *   P3.1(TxD) - 串口1发送(调试用)
 *   P1.4(SDA) - OLED I2C数据线
 *   P1.5(SCL) - OLED I2C时钟线
 *   P3.2      - 蜂鸣器(推挽输出)
 *   P3.3      - 电磁锁控制继电器(推挽输出)
 *   P3.4      - LED指示灯(推挽输出)
 *   P0.0~P0.3 - 矩阵键盘行(行扫描输出,推挽)
 *   P0.4~P0.7 - 矩阵键盘列(列检测输入,高阻)
 *   P3.2      - 也作为USB下载按键(上电时P3.2接地进入下载模式)
 *   
 * 使用说明:
 *   1. 使用STC-ISP软件下载,选择芯片STC8H8K64U
 *   2. 主频选择11.0592MHz
 *   3. 默认密码:123456
 *   4. 串口波特率:115200,8,n,1
 *   5. 按'*'键清除输入,按'#'键确认密码
 *   6. 输入正确密码后按'#'开锁,按'0'+'#'+新密码+'#'修改密码
 * ============================================================
 */

#include "stc8h.h"
#include "intrins.h"

/* ======================== 系统参数定义 ======================== */
#define FOSC            11059200UL      // 系统主频 11.0592MHz
#define BAUD            115200          // 串口波特率
#define BRT             (65536 - (FOSC / BAUD + 2) / 4)  // 定时器重装值

/* ======================== I/O口引脚定义 ======================== */
// 矩阵键盘 - 行扫描输出(推挽输出)
#define KEY_ROW1        P00     // P0.0 - 行1
#define KEY_ROW2        P01     // P0.1 - 行2
#define KEY_ROW3        P02     // P0.2 - 行3
#define KEY_ROW4        P03     // P0.3 - 行4

// 矩阵键盘 - 列检测输入(高阻输入)
#define KEY_COL1        P04     // P0.4 - 列1
#define KEY_COL2        P05     // P0.5 - 列2
#define KEY_COL3        P06     // P0.6 - 列3
#define KEY_COL4        P07     // P0.7 - 列4

// 输出控制
#define BUZZER          P32     // P3.2 - 蜂鸣器(低电平有效)
#define LOCK_RELAY      P33     // P3.3 - 电磁锁继电器(高电平开锁)
#define LED_INDICATOR   P34     // P3.4 - LED指示灯(高电平亮)

/* ======================== 密码相关定义 ======================== */
#define PASSWORD_LEN    6               // 密码长度
#define EEPROM_ADDR     0x0400          // EEPROM存储起始地址(扇区2)
#define MAX_RETRY       5               // 最大错误重试次数
#define LOCK_OPEN_TIME  3000            // 开锁保持时间(ms)
#define LOCK_TIMEOUT    30000           // 错误锁定时间(ms)

/* ======================== OLED I2C命令 ======================== */
#define OLED_ADDR       0x78            // OLED I2C地址(7位地址0x3C左移1位)
#define OLED_CMD        0x00            // 命令模式
#define OLED_DATA       0x40            // 数据模式

/* ======================== 全局变量 ======================== */
unsigned char xdata g_password[PASSWORD_LEN];   // 当前密码
unsigned char xdata g_input_buf[PASSWORD_LEN];  // 输入缓冲区
unsigned char g_input_len;                      // 当前输入长度
unsigned char g_retry_count;                    // 错误重试计数
unsigned int  g_lock_timer;                     // 锁定计时
unsigned char g_sys_state;                      // 系统状态
unsigned char g_key_value;                      // 按键值
bit g_key_pressed;                              // 按键按下标志
bit g_buzzer_busy;                              // 蜂鸣器忙标志
bit g_uart1_busy;                               // 串口发送忙标志

/* ======================== 系统状态枚举 ======================== */
#define SYS_IDLE        0       // 空闲状态
#define SYS_INPUT       1       // 密码输入中
#define SYS_OPEN        2       // 已开锁
#define SYS_LOCKED      3       // 锁定状态
#define SYS_CHGPWD      4       // 修改密码中

/* ======================== 键盘键值映射 ======================== */
// 4x4矩阵键盘布局:
//     Col1  Col2  Col3  Col4
// R1:  '1'   '2'   '3'   'A'
// R2:  '4'   '5'   '6'   'B'
// R3:  '7'   '8'   '9'   'C'
// R4:  '*'   '0'   '#'   'D'

unsigned char code key_map[4][4] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};

/* ======================== 函数声明 ======================== */
void delay_ms(unsigned int ms);
void delay_us(unsigned int us);
void System_Init(void);
void GPIO_Init(void);
void UART1_Init(void);
void Timer0_Init(void);
void I2C_Init(void);
void OLED_Init(void);
void OLED_WriteCmd(unsigned char cmd);
void OLED_WriteData(unsigned char dat);
void OLED_SetCursor(unsigned char x, unsigned char y);
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch);
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str);
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len);
void OLED_Clear(void);
void OLED_Display_Status(void);
unsigned char Key_Scan(void);
void Buzzer_Beep(unsigned int duration);
void Lock_Open(void);
void Lock_Close(void);
void EEPROM_Write_Password(unsigned char *pwd);
void EEPROM_Read_Password(unsigned char *pwd);
void UART1_SendByte(unsigned char dat);
void UART1_SendString(unsigned char *str);
void UART1_SendNum(unsigned int num);
void Process_Key(unsigned char key);
int  Check_Password(unsigned char *input, unsigned char *stored);
void Save_New_Password(unsigned char *new_pwd);

/* ======================== 延时函数 ======================== */
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for (i = 0; i < ms; i++)
    {
        for (j = 0; j < (FOSC / 12000); j++);
    }
}

void delay_us(unsigned int us)
{
    unsigned int i;
    for (i = 0; i < us; i++)
    {
        _nop_();
        _nop_();
    }
}

/* ======================== GPIO初始化 ======================== */
void GPIO_Init(void)
{
    // P0口:P0.0~P0.3推挽输出(行扫描),P0.4~P0.7高阻输入(列检测)
    P0M0 = 0x0F;    // P0.0~P0.3推挽,P0.4~P0.7由M1决定
    P0M1 = 0xF0;    // P0.4~P0.7高阻输入
  
    // P1口:P1.4(SDA)和P1.5(SCL)开漏模式(I2C)
    P1M0 = 0x00;
    P1M1 = 0x30;    // P1.4,P1.5开漏模式
  
    // P3口:P3.2蜂鸣器推挽,P3.3继电器推挽,P3.4LED推挽
    // P3.0,P3.1由串口控制(准双向口默认)
    P3M0 = 0x1C;    // P3.2,P3.3,P3.4推挽输出
    P3M1 = 0x00;
  
    // 初始状态:所有输出置为安全状态
    KEY_ROW1 = 0;
    KEY_ROW2 = 0;
    KEY_ROW3 = 0;
    KEY_ROW4 = 0;
    BUZZER = 1;         // 蜂鸣器高电平(不响)
    LOCK_RELAY = 0;     // 电磁锁关闭
    LED_INDICATOR = 0;  // LED熄灭
}

/* ======================== 串口1初始化 ======================== */
void UART1_Init(void)
{
    SCON = 0x50;        // 模式1:8位可变波特率,允许接收
    TMOD &= 0x0F;       // 定时器1模式0(16位自动重载)
    TMOD |= 0x00;
    AUXR |= 0x40;       // 定时器1 1T模式
    AUXR &= ~0x01;      // 串口1选择定时器1做波特率发生器
    TH1 = (unsigned char)(BRT >> 8);
    TL1 = (unsigned char)BRT;
    TR1 = 1;            // 启动定时器1
    ES = 1;             // 使能串口1中断
    REN = 1;            // 允许接收
    g_uart1_busy = 0;
}

/* ======================== 定时器0初始化(1ms定时) ======================== */
void Timer0_Init(void)
{
    AUXR |= 0x80;       // 定时器0 1T模式
    TMOD &= 0xF0;       // 定时器0模式0(16位自动重载)
    TMOD |= 0x00;
    // 1ms定时:TH0/TL0 = 65536 - FOSC/1000
    // 11.0592MHz / 1000 = 11059.2,取整
    TH0 = (unsigned char)((65536 - FOSC / 1000) >> 8);
    TL0 = (unsigned char)(65536 - FOSC / 1000);
    ET0 = 1;            // 使能定时器0中断
    TR0 = 1;            // 启动定时器0
}

/* ======================== I2C总线延时 ======================== */
void I2C_Delay(void)
{
    _nop_(); _nop_(); _nop_(); _nop_();
}

/* ======================== I2C起始信号 ======================== */
void I2C_Start(void)
{
    P1M1 &= ~0x30;      // 临时切换为推挽输出
    P1M0 |= 0x30;
    SDA = 1;
    SCL = 1;
    I2C_Delay();
    SDA = 0;
    I2C_Delay();
    SCL = 0;
    P1M0 &= ~0x30;      // 恢复开漏模式
    P1M1 |= 0x30;
}

/* ======================== I2C停止信号 ======================== */
void I2C_Stop(void)
{
    P1M1 &= ~0x30;
    P1M0 |= 0x30;
    SDA = 0;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    SDA = 1;
    I2C_Delay();
    P1M0 &= ~0x30;
    P1M1 |= 0x30;
}

/* ======================== I2C发送一个字节 ======================== */
bit I2C_SendByte(unsigned char dat)
{
    unsigned char i;
    bit ack;
  
    P1M1 &= ~0x30;
    P1M0 |= 0x30;
  
    for (i = 0; i < 8; i++)
    {
        if (dat & 0x80)
            SDA = 1;
        else
            SDA = 0;
        I2C_Delay();
        SCL = 1;
        I2C_Delay();
        SCL = 0;
        dat <<= 1;
    }
  
    // 释放SDA,接收应答
    P1M0 &= ~0x30;
    P1M1 |= 0x30;
    SDA = 1;
    I2C_Delay();
    SCL = 1;
    I2C_Delay();
    ack = SDA;
    SCL = 0;
  
    return ack;
}

/* ======================== OLED写命令 ======================== */
void OLED_WriteCmd(unsigned char cmd)
{
    I2C_Start();
    I2C_SendByte(OLED_ADDR);
    I2C_SendByte(OLED_CMD);
    I2C_SendByte(cmd);
    I2C_Stop();
}

/* ======================== OLED写数据 ======================== */
void OLED_WriteData(unsigned char dat)
{
    I2C_Start();
    I2C_SendByte(OLED_ADDR);
    I2C_SendByte(OLED_DATA);
    I2C_SendByte(dat);
    I2C_Stop();
}

/* ======================== OLED初始化 ======================== */
void OLED_Init(void)
{
    delay_ms(100);      // 等待OLED上电稳定
  
    OLED_WriteCmd(0xAE);    // 关闭显示
    OLED_WriteCmd(0xD5);    // 设置显示时钟分频
    OLED_WriteCmd(0x80);
    OLED_WriteCmd(0xA8);    // 设置多路复用比
    OLED_WriteCmd(0x3F);    // 64行
    OLED_WriteCmd(0xD3);    // 设置显示偏移
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0x40);    // 设置显示起始行
    OLED_WriteCmd(0x8D);    // 电荷泵设置
    OLED_WriteCmd(0x14);    // 使能电荷泵
    OLED_WriteCmd(0x20);    // 设置内存地址模式
    OLED_WriteCmd(0x00);    // 水平寻址模式
    OLED_WriteCmd(0xA1);    // 段重映射(左右翻转)
    OLED_WriteCmd(0xC8);    // COM扫描方向(上下翻转)
    OLED_WriteCmd(0xDA);    // COM引脚配置
    OLED_WriteCmd(0x12);
    OLED_WriteCmd(0x81);    // 对比度设置
    OLED_WriteCmd(0xCF);
    OLED_WriteCmd(0xD9);    // 预充电周期
    OLED_WriteCmd(0xF1);
    OLED_WriteCmd(0xDB);    // VCOMH取消选择级别
    OLED_WriteCmd(0x40);
    OLED_WriteCmd(0xA4);    // 全局显示开启(正常显示)
    OLED_WriteCmd(0xA6);    // 正常显示(非反色)
    OLED_WriteCmd(0xAF);    // 开启显示
  
    OLED_Clear();
}

/* ======================== OLED清屏 ======================== */
void OLED_Clear(void)
{
    unsigned char i, j;
    for (i = 0; i < 8; i++)     // 8页
    {
        OLED_WriteCmd(0xB0 + i);    // 设置页地址
        OLED_WriteCmd(0x00);        // 设置列低地址
        OLED_WriteCmd(0x10);        // 设置列高地址
        for (j = 0; j < 128; j++)   // 128列
        {
            OLED_WriteData(0x00);
        }
    }
}

/* ======================== OLED设置光标 ======================== */
void OLED_SetCursor(unsigned char x, unsigned char y)
{
    // x: 0~127, y: 0~7(页)
    OLED_WriteCmd(0xB0 + y);
    OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10);
    OLED_WriteCmd(x & 0x0F);
}

/* ======================== OLED显示一个字符(6x8字体) ======================== */
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch)
{
    unsigned char i;
    unsigned char code *font6x8 = {
        // 标准ASCII 6x8字体表(仅包含可打印字符 0x20~0x7E)
        // 此处为简化,使用内置字库,实际项目中可包含完整字库
    };
  
    // 由于字库较大,此处使用简化方式:直接显示字符的ASCII码
    // 实际项目中请包含完整的6x8 ASCII字库
    OLED_SetCursor(x, y);
    for (i = 0; i < 6; i++)
    {
        // 这里需要完整的字库数据,为简化示例,直接写0
        // 实际使用时请从STC官方例程中复制字库数组
        OLED_WriteData(0x00);
    }
}

/* ======================== OLED显示字符串 ======================== */
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str)
{
    while (*str != '\0')
    {
        OLED_ShowChar(x, y, *str);
        x += 6;
        if (x > 122)    // 换行
        {
            x = 0;
            y++;
        }
        str++;
    }
}

/* ======================== OLED显示数字 ======================== */
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len)
{
    unsigned char i;
    unsigned char show[6];
  
    for (i = 0; i < len; i++)
    {
        show[len - 1 - i] = (num % 10) + '0';
        num /= 10;
    }
    for (i = 0; i < len; i++)
    {
        OLED_ShowChar(x + i * 6, y, show[i]);
    }
}

/* ======================== OLED显示系统状态 ======================== */
void OLED_Display_Status(void)
{
    unsigned char i;
    char buf[20];
  
    OLED_Clear();
  
    // 第一行:标题
    OLED_ShowString(0, 0, "STC Smart Lock");
  
    // 第二行:系统状态
    switch (g_sys_state)
    {
        case SYS_IDLE:
            OLED_ShowString(0, 2, "Status: Ready");
            break;
        case SYS_INPUT:
            OLED_ShowString(0, 2, "Enter Password:");
            // 显示已输入的密码位数(用*号代替)
            for (i = 0; i < g_input_len; i++)
            {
                OLED_ShowChar(i * 6, 3, '*');
            }
            break;
        case SYS_OPEN:
            OLED_ShowString(0, 2, "** UNLOCKED **");
            break;
        case SYS_LOCKED:
            OLED_ShowString(0, 2, "!! LOCKED !!");
            break;
        case SYS_CHGPWD:
            OLED_ShowString(0, 2, "Change Password");
            break;
        default:
            break;
    }
  
    // 第四行:提示信息
    OLED_ShowString(0, 5, "#=OK *=Clr");
}

/* ======================== 矩阵键盘扫描 ======================== */
unsigned char Key_Scan(void)
{
    unsigned char row, col;
    unsigned char key_val = 0;
  
    // 逐行扫描
    for (row = 0; row < 4; row++)
    {
        // 所有行置低
        KEY_ROW1 = 0;
        KEY_ROW2 = 0;
        KEY_ROW3 = 0;
        KEY_ROW4 = 0;
      
        // 当前行置高
        switch (row)
        {
            case 0: KEY_ROW1 = 1; break;
            case 1: KEY_ROW2 = 1; break;
            case 2: KEY_ROW3 = 1; break;
            case 3: KEY_ROW4 = 1; break;
        }
      
        delay_us(10);   // 等待电平稳定
      
        // 检测列
        if (KEY_COL1) { col = 0; key_val = key_map[row][col]; break; }
        if (KEY_COL2) { col = 1; key_val = key_map[row][col]; break; }
        if (KEY_COL3) { col = 2; key_val = key_map[row][col]; break; }
        if (KEY_COL4) { col = 3; key_val = key_map[row][col]; break; }
    }
  
    // 恢复所有行置低
    KEY_ROW1 = 0;
    KEY_ROW2 = 0;
    KEY_ROW3 = 0;
    KEY_ROW4 = 0;
  
    return key_val;
}

/* ======================== 蜂鸣器控制 ======================== */
void Buzzer_Beep(unsigned int duration)
{
    BUZZER = 0;             // 蜂鸣器响(低电平有效)
    g_buzzer_busy = 1;
    // 使用定时器计时,在主循环中关闭
    // 这里简单使用延时(注意:延时期间会阻塞)
    delay_ms(duration);
    BUZZER = 1;             // 蜂鸣器关闭
    g_buzzer_busy = 0;
}

/* ======================== 电磁锁控制 ======================== */
void Lock_Open(void)
{
    LOCK_RELAY = 1;         // 开锁
    LED_INDICATOR = 1;      // LED亮
    g_sys_state = SYS_OPEN;
    OLED_Display_Status();
    UART1_SendString("Lock Opened\r\n");
    Buzzer_Beep(200);       // 短鸣提示
}

void Lock_Close(void)
{
    LOCK_RELAY = 0;         // 关锁
    LED_INDICATOR = 0;      // LED灭
    g_sys_state = SYS_IDLE;
    OLED_Display_Status();
    UART1_SendString("Lock Closed\r\n");
}

/* ======================== EEPROM操作 ======================== */
void IapIdle(void)
{
    IAP_CONTR = 0;          // 关闭IAP功能
    IAP_CMD = 0;            // 清除命令寄存器
    IAP_TRIG = 0;           // 清除触发寄存器
    IAP_ADDRH = 0x80;       // 将地址设置到非IAP区域
    IAP_ADDRL = 0;
}

unsigned char IapRead(unsigned int addr)
{
    unsigned char dat;
  
    EA = 0;                 // 关闭总中断
    IAP_CONTR = 0x80;       // 使能IAP
    IAP_TPS = 11;           // 设置等待参数(11MHz)
    IAP_CMD = 1;            // 设置IAP读命令
    IAP_ADDRL = (unsigned char)addr;
    IAP_ADDRH = (unsigned char)(addr >> 8);
    IAP_TRIG = 0x5A;        // 写触发命令
    IAP_TRIG = 0xA5;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    dat = IAP_DATA;         // 读取数据
    IapIdle();
    EA = 1;
  
    return dat;
}

void IapProgram(unsigned int addr, unsigned char dat)
{
    EA = 0;
    IAP_CONTR = 0x80;
    IAP_TPS = 11;
    IAP_CMD = 2;            // 写命令
    IAP_ADDRL = (unsigned char)addr;
    IAP_ADDRH = (unsigned char)(addr >> 8);
    IAP_DATA = dat;
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    IapIdle();
    EA = 1;
}

void IapErase(unsigned int addr)
{
    EA = 0;
    IAP_CONTR = 0x80;
    IAP_TPS = 11;
    IAP_CMD = 3;            // 擦除命令
    IAP_ADDRL = (unsigned char)addr;
    IAP_ADDRH = (unsigned char)(addr >> 8);
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    IapIdle();
    EA = 1;
}

/* ======================== 从EEPROM读取密码 ======================== */
void EEPROM_Read_Password(unsigned char *pwd)
{
    unsigned char i;
    unsigned int addr = EEPROM_ADDR;
  
    for (i = 0; i < PASSWORD_LEN; i++)
    {
        pwd[i] = IapRead(addr + i);
    }
}

/* ======================== 写入密码到EEPROM ======================== */
void EEPROM_Write_Password(unsigned char *pwd)
{
    unsigned char i;
    unsigned int addr = EEPROM_ADDR;
  
    // 先擦除整个扇区(512字节)
    IapErase(EEPROM_ADDR);
  
    // 写入密码
    for (i = 0; i < PASSWORD_LEN; i++)
    {
        IapProgram(addr + i, pwd[i]);
    }
}

/* ======================== 串口发送一个字节 ======================== */
void UART1_SendByte(unsigned char dat)
{
    while (g_uart1_busy);
    g_uart1_busy = 1;
    SBUF = dat;
}

/* ======================== 串口发送字符串 ======================== */
void UART1_SendString(unsigned char *str)
{
    while (*str != '\0')
    {
        UART1_SendByte(*str);
        str++;
    }
}

/* ======================== 串口发送数字(十进制) ======================== */
void UART1_SendNum(unsigned int num)
{
    unsigned char buf[6];
    unsigned char i = 0;
  
    if (num == 0)
    {
        UART1_SendByte('0');
        return;
    }
  
    while (num > 0)
    {
        buf[i++] = (num % 10) + '0';
        num /= 10;
    }
  
    while (i > 0)
    {
        UART1_SendByte(buf[--i]);
    }
}

/* ======================== 密码比较 ======================== */
int Check_Password(unsigned char *input, unsigned char *stored)
{
    unsigned char i;
  
    for (i = 0; i < PASSWORD_LEN; i++)
    {
        if (input[i] != stored[i])
        {
            return 0;   // 不匹配
        }
    }
    return 1;           // 匹配
}

/* ======================== 保存新密码 ======================== */
void Save_New_Password(unsigned char *new_pwd)
{
    unsigned char i;
  
    for (i = 0; i < PASSWORD_LEN; i++)
    {
        g_password[i] = new_pwd[i];
    }
  
    EEPROM_Write_Password(g_password);
    UART1_SendString("Password changed!\r\n");
}

/* ======================== 按键处理 ======================== */
void Process_Key(unsigned char key)
{
    static unsigned char chgpwd_step = 0;
    static unsigned char xdata new_pwd[PASSWORD_LEN];
    static unsigned char xdata confirm_pwd[PASSWORD_LEN];
  
    // 如果系统处于锁定状态,不处理任何按键
    if (g_sys_state == SYS_LOCKED)
    {
        UART1_SendString("System locked!\r\n");
        return;
    }
  
    switch (key)
    {
        case '*':   // 清除键
            g_input_len = 0;
            g_sys_state = SYS_IDLE;
            chgpwd_step = 0;
            OLED_Display_Status();
            UART1_SendString("Input cleared\r\n");
            break;
          
        case '#':   // 确认键
            if (g_sys_state == SYS_CHGPWD)
            {
                // 修改密码流程
                if (chgpwd_step == 0)
                {
                    // 第一步:验证旧密码
                    if (g_input_len == PASSWORD_LEN && 
                        Check_Password(g_input_buf, g_password))
                    {
                        chgpwd_step = 1;
                        g_input_len = 0;
                        OLED_ShowString(0, 3, "New Pwd: ");
                        UART1_SendString("Enter new password:\r\n");
                    }
                    else
                    {
                        UART1_SendString("Old password error!\r\n");
                        Buzzer_Beep(500);
                        g_input_len = 0;
                        g_sys_state = SYS_IDLE;
                        chgpwd_step = 0;
                        OLED_Display_Status();
                    }
                }
                else if (chgpwd_step == 1)
                {
                    // 第二步:输入新密码
                    if (g_input_len == PASSWORD_LEN)
                    {
                        unsigned char i;
                        for (i = 0; i < PASSWORD_LEN; i++)
                        {
                            new_pwd[i] = g_input_buf[i];
                        }
                        chgpwd_step = 2;
                        g_input_len = 0;
                        OLED_ShowString(0, 4, "Confirm: ");
                        UART1_SendString("Confirm new password:\r\n");
                    }
                    else
                    {
                        UART1_SendString("Password too short!\r\n");
                        g_input_len = 0;
                    }
                }
                else if (chgpwd_step == 2)
                {
                    // 第三步:确认新密码
                    if (g_input_len == PASSWORD_LEN && 
                        Check_Password(g_input_buf, new_pwd))
                    {
                        Save_New_Password(new_pwd);
                        Buzzer_Beep(100);
                        g_sys_state = SYS_IDLE;
                        chgpwd_step = 0;
                        g_input_len = 0;
                        OLED_Display_Status();
                    }
                    else
                    {
                        UART1_SendString("Confirm failed!\r\n");
                        Buzzer_Beep(500);
                        g_sys_state = SYS_IDLE;
                        chgpwd_step = 0;
                        g_input_len = 0;
                        OLED_Display_Status();
                    }
                }
            }
            else
            {
                // 正常开锁流程
                if (g_input_len == PASSWORD_LEN)
                {
                    if (Check_Password(g_input_buf, g_password))
                    {
                        // 密码正确
                        g_retry_count = 0;
                        Lock_Open();
                        // 开锁保持一段时间后自动关锁
                        // 在主循环中处理
                    }
                    else
                    {
                        // 密码错误
                        g_retry_count++;
                        UART1_SendString("Wrong password! Retry: ");
                        UART1_SendNum(g_retry_count);
                        UART1_SendString("\r\n");
                        Buzzer_Beep(300);
                      
                        if (g_retry_count >= MAX_RETRY)
                        {
                            // 超过最大重试次数,锁定系统
                            g_sys_state = SYS_LOCKED;
                            g_lock_timer = LOCK_TIMEOUT;
                            OLED_Display_Status();
                            UART1_SendString("System locked for 30s!\r\n");
                        }
                      
                        g_input_len = 0;
                        OLED_Display_Status();
                    }
                }
                else
                {
                    UART1_SendString("Password too short!\r\n");
                    g_input_len = 0;
                }
            }
            break;
          
        case '0':   // 特殊功能:进入修改密码模式
            if (g_sys_state == SYS_IDLE)
            {
                g_sys_state = SYS_CHGPWD;
                chgpwd_step = 0;
                g_input_len = 0;
                OLED_Display_Status();
                OLED_ShowString(0, 3, "Old Pwd: ");
                UART1_SendString("Enter old password:\r\n");
            }
            else if (g_sys_state == SYS_INPUT || g_sys_state == SYS_CHGPWD)
            {
                // 数字键输入
                if (g_input_len < PASSWORD_LEN)
                {
                    g_input_buf[g_input_len++] = key;
                    OLED_Display_Status();
                }
            }
            break;
          
        case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            // 数字键输入
            if (g_sys_state == SYS_IDLE)
            {
                g_sys_state = SYS_INPUT;
                g_input_len = 0;
            }
          
            if (g_sys_state == SYS_INPUT || g_sys_state == SYS_CHGPWD)
            {
                if (g_input_len < PASSWORD_LEN)
                {
                    g_input_buf[g_input_len++] = key;
                    OLED_Display_Status();
                }
            }
            break;
          
        default:
            break;
    }
}

/* ======================== 系统初始化 ======================== */
void System_Init(void)
{
    unsigned char i;
  
    // 使能访问XFR
    P_SW2 |= 0x80;
  
    // 初始化I/O口
    GPIO_Init();
  
    // 初始化串口
    UART1_Init();
  
    // 初始化定时器
    Timer0_Init();
  
    // 初始化I2C和OLED
    I2C_Init();
    OLED_Init();
  
    // 从EEPROM读取密码
    EEPROM_Read_Password(g_password);
  
    // 检查EEPROM是否为空(首次使用)
    // 如果密码全为0xFF,说明是首次使用,设置默认密码
    for (i = 0; i < PASSWORD_LEN; i++)
    {
        if (g_password[i] != 0xFF)
            break;
    }
  
    if (i == PASSWORD_LEN)
    {
        // 首次使用,设置默认密码 "123456"
        g_password[0] = '1';
        g_password[1] = '2';
        g_password[2] = '3';
        g_password[3] = '4';
        g_password[4] = '5';
        g_password[5] = '6';
        EEPROM_Write_Password(g_password);
        UART1_SendString("Default password set: 123456\r\n");
    }
  
    // 初始化全局变量
    g_input_len = 0;
    g_retry_count = 0;
    g_sys_state = SYS_IDLE;
    g_key_pressed = 0;
    g_buzzer_busy = 0;
  
    // 显示初始界面
    OLED_Display_Status();
  
    // 串口输出启动信息
    UART1_SendString("\r\n================================\r\n");
    UART1_SendString("STC8H8K64U Smart Lock System\r\n");
    UART1_SendString("Freq: 11.0592MHz\r\n");
    UART1_SendString("Baud: 115200\r\n");
    UART1_SendString("================================\r\n");
    UART1_SendString("System Ready!\r\n");
    UART1_SendString("Enter password and press # to unlock\r\n");
    UART1_SendString("Press * to clear, 0+# to change pwd\r\n");
  
    // 蜂鸣器提示音
    Buzzer_Beep(100);
    delay_ms(100);
    Buzzer_Beep(100);
}

/* ======================== I2C初始化 ======================== */
void I2C_Init(void)
{
    // I2C引脚已在GPIO_Init中配置为开漏模式
    // 设置内部上拉电阻使能
    P1PU |= 0x30;   // P1.4,P1.5使能内部4K上拉
}

/* ======================== 主函数 ======================== */
void main(void)
{
    unsigned char key;
    unsigned int open_timer = 0;
  
    // 系统初始化
    System_Init();
  
    // 打开总中断
    EA = 1;
  
    // 主循环
    while (1)
    {
        // 1. 键盘扫描(非阻塞)
        key = Key_Scan();
        if (key != 0)
        {
            // 检测到按键
            if (!g_key_pressed)
            {
                g_key_pressed = 1;
                g_key_value = key;
              
                // 蜂鸣器短鸣反馈
                BUZZER = 0;
                delay_ms(50);
                BUZZER = 1;
              
                // 处理按键
                Process_Key(key);
            }
        }
        else
        {
            g_key_pressed = 0;
        }
      
        // 2. 开锁计时处理
        if (g_sys_state == SYS_OPEN)
        {
            open_timer++;
            if (open_timer >= LOCK_OPEN_TIME)   // 约3秒(1ms*3000)
            {
                open_timer = 0;
                Lock_Close();
            }
        }
        else
        {
            open_timer = 0;
        }
      
        // 3. 锁定计时处理(在定时器中断中处理)
        // 锁定状态在主循环中不做额外操作
      
        // 4. 延时约1ms(由定时器0控制节奏)
        delay_ms(1);
    }
}

/* ======================== 定时器0中断服务函数(1ms周期) ======================== */
void Timer0_Isr(void) interrupt 1
{
    // 锁定计时
    if (g_sys_state == SYS_LOCKED)
    {
        if (g_lock_timer > 0)
        {
            g_lock_timer--;
            if (g_lock_timer == 0)
            {
                // 锁定时间到,解锁
                g_retry_count = 0;
                g_sys_state = SYS_IDLE;
                OLED_Display_Status();
                UART1_SendString("System unlocked\r\n");
            }
        }
    }
}

/* ======================== 串口1中断服务函数 ======================== */
void UART1_Isr(void) interrupt 4
{
    if (TI)
    {
        TI = 0;
        g_uart1_busy = 0;
    }
  
    if (RI)
    {
        RI = 0;
        // 串口接收数据(可用于远程控制)
        // 此处可根据需要扩展远程开锁功能
    }
}

文件2:stc8h.h - 头文件(STC官方提供)

注意stc8h.h 头文件请从STC官方ISP软件安装目录下的 STC-ISP\STC8H.H 获取,或从STC官网下载。该头文件包含了STC8H8K64U所有特殊功能寄存器的定义。


项目说明

一、硬件资源分配(LQFP64封装)

引脚 功能 说明
P3.0/RxD 串口1接收 调试串口接收
P3.1/TxD 串口1发送 调试串口发送(115200bps)
P1.4/SDA I²C数据线 OLED显示屏数据
P1.5/SCL I²C时钟线 OLED显示屏时钟
P3.2 蜂鸣器输出 低电平有效,带三极管驱动
P3.3 电磁锁继电器 高电平开锁,驱动继电器模块
P3.4 LED指示灯 高电平点亮,状态指示
P0.0~P0.3 键盘行输出 推挽输出,行扫描信号
P0.4~P0.7 键盘列输入 高阻输入,列检测信号

二、功能操作说明

  1. 密码输入:按数字键输入密码(6位),按 # 确认
  2. 清除输入:按 * 清除当前输入
  3. 修改密码:按 0 + # 进入修改密码模式,按提示操作
  4. 错误锁定:连续5次密码错误,系统锁定30秒
  5. 自动关锁:开锁后3秒自动关锁
  6. 串口调试:通过串口1(115200bps)查看系统运行状态

三、编译说明

  1. 使用 Keil C51 编译器
  2. 芯片选择:STC8H8K64U
  3. 晶振频率:11.0592MHz
  4. 代码优化级别:建议 8(REGPARM)
  5. 需要将 stc8h.h 头文件放置于项目目录或Keil包含路径中

四、下载说明

  1. 打开 STC-ISP 下载软件
  2. 选择芯片型号:STC8H8K64U
  3. 设置IRC频率:11.0592MHz
  4. 连接硬件:
    • P3.0 → USB转串口 TXD
    • P3.1 → USB转串口 RXD
    • GND → GND
  5. 按下P3.2按键(接地),重新上电
  6. 点击"下载/编程"按钮

注意:本程序为功能完整的智能锁范例,OLED显示部分使用了简化的字符显示函数。在实际产品中,建议从STC官方例程中复制完整的6x8 ASCII字库数组,以获得完整的字符显示效果。如需更丰富的显示功能(如中文显示、图标等),可进一步扩展OLED驱动。


🔍 搜索 "STC8H8K64U 智能锁 范例 注解"

📌 相关论坛讨论

  1. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  2. STC单片机在智能锁中的应用, STC8H8K64U-45I-LQFP48
  3. 求助8A8K64S4A12-28I-LQFP64 代换问题

⏱️ 查找用时 17.4s | 总回答用时 83.9s

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:57
  • 最近打卡:2026-07-21 14:54:59
已绑定手机

22

主题

81

回帖

796

积分

版主

积分
796
发表于 前天 15:28 | 显示全部楼层
用STC8H1K08-36I-TSSOP20自动编写超声波眼镜清洗机控制的程序,生成完整演示程序,并含详细程序注解

截图202607201527562547.jpg

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:57
  • 最近打卡:2026-07-21 14:54:59
已绑定手机

22

主题

81

回帖

796

积分

版主

积分
796
发表于 前天 15:28 | 显示全部楼层

好的,我已经查阅了STC8H1K08的完整手册资料。现在为您编写完整的超声波眼镜清洗机控制程序。


STC8H1K08-36I-TSSOP20 超声波眼镜清洗机控制程序

一、硬件资源分配(TSSOP20封装)

根据手册中TSSOP20管脚图(章节"管脚图,TSSOP20,最小系统,通用USB转串口下载/仿真线路"),引脚分配如下:

引脚 功能 说明
P3.0/RxD 串口1接收 调试/通信
P3.1/TxD 串口1发送 调试/通信
P3.2/INT0 启动按键输入(低电平有效) 启动清洗
P3.3/INT1 停止按键输入(低电平有效) 停止清洗
P5.4/nRST 复位(做I/O时注意上电6.5ms高电平) 复位脚
P1.0/PWM1P 超声波振荡器PWM输出 驱动超声波换能器
P1.1/PWM1N 超声波互补PWM输出 驱动超声波换能器
P1.2/PWM2P 加热继电器控制 加热控制
P1.3/PWM2N 备用/蜂鸣器 提示音
P1.4/PWM3P LED指示灯-运行 运行指示
P1.5/PWM3N LED指示灯-加热 加热指示
P1.6/RxD_3 水位检测ADC输入 ADC通道
P1.7/TxD_3 NTC温度检测ADC输入 ADC通道
P3.4/T0 定时器0外部计数(备用) 备用
P3.5/T1 定时器1外部计数(备用) 备用
P3.6/INT2 门开关检测 安全检测
P3.7/INT3 过流保护检测 保护检测

二、完整程序代码

/*
 * ============================================================
 * 超声波眼镜清洗机控制程序
 * 主控芯片: STC8H1K08-36I-TSSOP20
 * 编译器: Keil C51
 * 功能说明:
 *   1. 超声波振荡驱动 (40kHz PWM互补输出)
 *   2. 加热控制 (恒温40~50°C)
 *   3. 水位检测 (ADC)
 *   4. 温度检测 (NTC热敏电阻 ADC)
 *   5. 定时清洗 (3/5/10分钟可选)
 *   6. 按键控制 (启动/停止)
 *   7. 状态指示 (LED)
 *   8. 安全保护 (门开关/过流)
 *   9. 串口调试输出
 *
 * 硬件连接:
 *   P1.0/PWM1P - 超声波驱动正端 (推挽输出)
 *   P1.1/PWM1N - 超声波驱动负端 (推挽输出)
 *   P1.2       - 加热继电器控制 (推挽输出)
 *   P1.3       - 蜂鸣器 (推挽输出)
 *   P1.4       - 运行指示灯 (推挽输出)
 *   P1.5       - 加热指示灯 (推挽输出)
 *   P1.6       - 水位检测 (ADC输入, 高阻)
 *   P1.7       - NTC温度检测 (ADC输入, 高阻)
 *   P3.2       - 启动按键 (输入, 上拉)
 *   P3.3       - 停止按键 (输入, 上拉)
 *   P3.6       - 门开关检测 (输入, 上拉)
 *   P3.7       - 过流保护检测 (输入, 上拉)
 *   P3.0/P3.1  - 串口调试 (115200bps)
 *
 * 使用说明:
 *   1. 在ISP下载软件中选择内部IRC时钟为 24MHz
 *   2. 下载时硬件选项: 允许低压复位(禁止低压中断) 勾选去掉
 *   3. P5.4做普通I/O时, 上电6.5ms内为高电平, 注意外部电路
 * ============================================================
 */

#include "stc8h.h"
#include "intrins.h"

/* ======================== 宏定义 ======================== */

#define MAIN_Fosc        24000000UL    // 系统主频 24MHz

// 定时器重载值定义
#define T0_1MS_RELOAD    (65536UL - (MAIN_Fosc / 12 / 1000))  // 定时器0 1ms定时 (12T模式)
#define T1_1MS_RELOAD    (65536UL - (MAIN_Fosc / 12 / 1000))  // 定时器1 1ms定时 (12T模式)

// 超声波PWM参数 (目标频率40kHz)
// PWM频率 = SYSclk / ((PSCR+1) * (ARR+1))
// 40kHz = 24MHz / ((PSCR+1) * (ARR+1))
// 取 PSCR=0, ARR=599, 则 24MHz/(1*600)=40kHz
#define ULTRASONIC_PSCR  0             // 预分频 (PSCR+1=1)
#define ULTRASONIC_ARR   599           // 自动重载值 (ARR+1=600)
#define ULTRASONIC_CCR   300           // 占空比50% (CCR=300)

// ADC通道定义
#define ADC_CH_WATER     6             // P1.6 水位检测通道
#define ADC_CH_TEMP      7             // P1.7 温度检测通道

// 温度阈值 (NTC 10K 3950, 分压电阻10K)
// ADC值 = 1024 * Rntc / (Rntc + 10K)
// 40°C时 NTC≈5.8K, ADC≈376
// 50°C时 NTC≈4.0K, ADC≈293
#define TEMP_40C_ADC     376           // 40°C对应ADC值
#define TEMP_50C_ADC     293           // 50°C对应ADC值

// 水位阈值
#define WATER_LOW_ADC    100           // 水位过低阈值 (无水时ADC接近0)
#define WATER_OK_ADC     300           // 水位正常阈值

// 清洗时间选项 (分钟)
#define TIME_3MIN        3
#define TIME_5MIN        5
#define TIME_10MIN       10

// 系统状态定义
#define STATE_IDLE       0             // 空闲
#define STATE_RUNNING    1             // 运行中
#define STATE_HEATING    2             // 加热中
#define STATE_ERROR      3             // 错误

// 错误标志位
#define ERR_NO_WATER     0x01          // 缺水
#define ERR_OVER_TEMP    0x02          // 超温
#define ERR_DOOR_OPEN    0x04          // 门打开
#define ERR_OVER_CURRENT 0x08          // 过流

// I/O口位定义
sbit KEY_START  = P3^2;               // 启动按键
sbit KEY_STOP   = P3^3;               // 停止按键
sbit DOOR_SW    = P3^6;               // 门开关 (0=关闭, 1=打开)
sbit OVER_CURR  = P3^7;               // 过流检测 (0=正常, 1=过流)

sbit HEAT_RELAY = P1^2;               // 加热继电器
sbit BUZZER     = P1^3;               // 蜂鸣器
sbit LED_RUN    = P1^4;               // 运行指示灯
sbit LED_HEAT   = P1^5;               // 加热指示灯

/* ======================== 全局变量 ======================== */

unsigned char g_SystemState;          // 系统状态
unsigned char g_ErrorFlags;           // 错误标志
unsigned char g_WorkTime;             // 工作计时 (秒)
unsigned char g_WorkTimeSet;          // 设定工作时间 (分钟)
unsigned int  g_HeatTimeCount;        // 加热计时 (秒)
unsigned char g_BeepCount;            // 蜂鸣计数
unsigned char g_KeyDebounce;          // 按键消抖计数
unsigned char g_1msFlag;              // 1ms标志
unsigned char g_100msFlag;            // 100ms标志
unsigned char g_1sFlag;               // 1s标志
unsigned int  g_100msCounter;         // 100ms计数器
unsigned int  g_1sCounter;            // 1s计数器

unsigned int  g_AdcWaterValue;        // 水位ADC值
unsigned int  g_AdcTempValue;         // 温度ADC值
unsigned char g_AdcChannel;           // 当前ADC通道

/* ======================== 函数声明 ======================== */

void System_Init(void);
void GPIO_Init(void);
void Timer0_Init(void);
void Timer1_Init(void);
void PWMA_Init(void);
void ADC_Init(void);
void UART1_Init(void);

void Key_Scan(void);
void State_Machine(void);
void Safety_Check(void);
void Ultrasonic_Control(unsigned char on);
void Heat_Control(unsigned char on);
void Beep_Control(unsigned char on);
void LED_Update(void);
void ADC_Start(unsigned char channel);
unsigned int ADC_GetResult(void);

void Delay1ms(unsigned int ms);
void UART1_SendByte(unsigned char dat);
void UART1_SendString(unsigned char *str);
void UART1_SendHex(unsigned char dat);

/* ======================== 主函数 ======================== */

void main(void)
{
    unsigned char i;

    // 系统初始化
    System_Init();

    // 初始状态: 空闲
    g_SystemState = STATE_IDLE;
    g_ErrorFlags = 0;
    g_WorkTime = 0;
    g_WorkTimeSet = TIME_5MIN;        // 默认5分钟
    g_HeatTimeCount = 0;
    g_BeepCount = 0;
    g_KeyDebounce = 0;
    g_AdcChannel = ADC_CH_WATER;      // 先测水位

    // 关闭所有输出
    Ultrasonic_Control(0);
    Heat_Control(0);
    Beep_Control(0);
    LED_RUN = 0;
    LED_HEAT = 0;

    // 启动ADC首次转换
    ADC_Start(g_AdcChannel);

    // 启动定时器
    TR0 = 1;                          // 启动定时器0
    TR1 = 1;                          // 启动定时器1

    EA = 1;                           // 开总中断

    // 串口输出启动信息
    UART1_SendString("Ultrasonic Cleaner v1.0\r\n");
    UART1_SendString("System Ready.\r\n");

    // 开机提示音
    Beep_Control(1);
    for (i = 0; i < 100; i++) Delay1ms(1);
    Beep_Control(0);

    /* ==================== 主循环 ==================== */
    while (1)
    {
        // 1ms定时任务
        if (g_1msFlag)
        {
            g_1msFlag = 0;

            // 按键扫描 (每1ms扫描一次)
            Key_Scan();

            // 100ms定时
            g_100msCounter++;
            if (g_100msCounter >= 100)
            {
                g_100msCounter = 0;
                g_100msFlag = 1;
            }

            // 1s定时
            g_1sCounter++;
            if (g_1sCounter >= 1000)
            {
                g_1sCounter = 0;
                g_1sFlag = 1;
            }
        }

        // 100ms任务
        if (g_100msFlag)
        {
            g_100msFlag = 0;

            // 安全检测
            Safety_Check();

            // LED状态更新
            LED_Update();

            // 蜂鸣器控制
            if (g_BeepCount > 0)
            {
                g_BeepCount--;
                if (g_BeepCount == 0) Beep_Control(0);
            }
        }

        // 1s任务
        if (g_1sFlag)
        {
            g_1sFlag = 0;

            // 状态机处理
            State_Machine();

            // 串口输出状态 (每5秒输出一次)
            if ((g_WorkTime % 5) == 0)
            {
                UART1_SendString("State:");
                UART1_SendHex(g_SystemState);
                UART1_SendString(" Time:");
                UART1_SendHex(g_WorkTime);
                UART1_SendString("s Water:");
                UART1_SendHex((unsigned char)(g_AdcWaterValue >> 2));
                UART1_SendString(" Temp:");
                UART1_SendHex((unsigned char)(g_AdcTempValue >> 2));
                UART1_SendString("\r\n");
            }
        }

        // ADC结果处理 (在ADC中断中完成, 这里只做轮询)
        // ADC转换由定时器1触发, 交替测量水位和温度
    }
}

/* ======================== 系统初始化 ======================== */

void System_Init(void)
{
    P_SW2 |= 0x80;                    // 使能访问XFR (扩展寄存器)

    GPIO_Init();                      // I/O口初始化
    Timer0_Init();                    // 定时器0初始化 (1ms系统节拍)
    Timer1_Init();                    // 定时器1初始化 (ADC触发)
    PWMA_Init();                      // 高级PWM初始化 (超声波驱动)
    ADC_Init();                       // ADC初始化
    UART1_Init();                     // 串口1初始化 (调试)

    // 设置省电模式唤醒参数
    IRCDB = 0x10;                     // 内部高速IRC唤醒等待时钟数
    IAP_TPS = 24;                     // 设置IAP等待参数 (24MHz)
}

/* ======================== GPIO初始化 ======================== */

void GPIO_Init(void)
{
    // P1口配置
    // P1.0/P1.1: PWM输出 (由PWMA硬件控制, 无需配置)
    // P1.2: 推挽输出 (加热继电器)
    // P1.3: 推挽输出 (蜂鸣器)
    // P1.4: 推挽输出 (运行LED)
    // P1.5: 推挽输出 (加热LED)
    // P1.6: 高阻输入 (ADC水位)
    // P1.7: 高阻输入 (ADC温度)
    P1M1 = 0xC0;                      // P1.6,P1.7高阻
    P1M0 = 0x3C;                      // P1.2-P1.5推挽输出

    // P3口配置
    // P3.0: 准双向 (串口RxD)
    // P3.1: 推挽输出 (串口TxD)
    // P3.2: 准双向 (启动按键, 内部上拉)
    // P3.3: 准双向 (停止按键, 内部上拉)
    // P3.6: 准双向 (门开关)
    // P3.7: 准双向 (过流检测)
    P3M1 = 0x00;
    P3M0 = 0x02;                      // P3.1推挽输出

    // P5口配置
    // P5.4: 准双向 (复位脚做I/O, 注意上电6.5ms高电平)
    P5M1 = 0x00;
    P5M0 = 0x00;

    // 初始输出电平
    HEAT_RELAY = 0;                   // 关闭加热
    BUZZER = 0;                       // 关闭蜂鸣器
    LED_RUN = 0;                      // 关闭运行灯
    LED_HEAT = 0;                     // 关闭加热灯
}

/* ======================== 定时器0初始化 (1ms系统节拍) ======================== */

void Timer0_Init(void)
{
    // 定时器0: 16位自动重载模式, 12T模式
    // 定时周期 = (65536 - 重载值) * 12 / SYSclk
    // 1ms = (65536 - 重载值) * 12 / 24MHz
    // 重载值 = 65536 - 24000000/12/1000 = 65536 - 2000 = 63536 = 0xF830
    TMOD = 0x00;                      // T0: 16位自动重载模式
    AUXR &= ~0x80;                    // T0: 12T模式
    TL0 = (unsigned char)(T0_1MS_RELOAD);
    TH0 = (unsigned char)(T0_1MS_RELOAD >> 8);
    ET0 = 1;                          // 使能T0中断
    // TR0 = 1; 在主函数中启动
}

/* ======================== 定时器1初始化 (ADC触发) ======================== */

void Timer1_Init(void)
{
    // 定时器1: 16位自动重载模式, 12T模式
    // 定时周期 100ms = (65536 - 重载值) * 12 / 24MHz
    // 重载值 = 65536 - 24000000/12/10 = 65536 - 200000 = 需要24位...
    // 使用1T模式, 定时100ms
    // 重载值 = 65536 - 24000000/10 = 65536 - 2400000 = 负数, 需要分频
    // 使用预分频器 TM1PS = 99 (100分频)
    // 定时器时钟 = 24MHz / (99+1) = 240kHz
    // 100ms = (65536 - 重载值) / 240kHz
    // 重载值 = 65536 - 240000 = 负数...
    // 改用定时器2来实现ADC触发
    // 这里定时器1作为备用, 实际ADC由定时器2触发

    // 简化: 定时器1配置为1ms定时, 用于软件计时
    TMOD &= 0x0F;                     // 保留T0设置, 清除T1设置
    // T1已由TMOD=0x00设置为16位自动重载
    AUXR &= ~0x40;                    // T1: 12T模式
    TL1 = (unsigned char)(T1_1MS_RELOAD);
    TH1 = (unsigned char)(T1_1MS_RELOAD >> 8);
    ET1 = 1;                          // 使能T1中断
    // TR1 = 1; 在主函数中启动
}

/* ======================== 高级PWM初始化 (超声波40kHz驱动) ======================== */

void PWMA_Init(void)
{
    // 使用PWMA输出40kHz互补PWM驱动超声波换能器
    // PWM1P -> P1.0, PWM1N -> P1.1
    // 边沿对齐模式, 向上计数
    // PWM频率 = SYSclk / ((PSCR+1) * (ARR+1))
    // 40kHz = 24MHz / (1 * 600) -> PSCR=0, ARR=599

    // 配置PWM输出引脚
    PWMA_PS = 0x00;                   // C1PS=00: PWM1P->P1.0, PWM1N->P1.1

    // 关闭通道, 先配置模式
    PWMA_CCER1 = 0x00;                // 关闭所有通道

    // 配置通道1为PWM模式1, 预装载使能
    PWMA_CCMR1 = 0x68;                // OC1M=110(PWM模式1), OC1PE=1(预装载)

    // 设置周期和占空比
    PWMA_PSCRH = (unsigned char)(ULTRASONIC_PSCR >> 8);
    PWMA_PSCRL = (unsigned char)(ULTRASONIC_PSCR);
    PWMA_ARRH  = (unsigned char)(ULTRASONIC_ARR >> 8);
    PWMA_ARRL  = (unsigned char)(ULTRASONIC_ARR);
    PWMA_CCR1H = (unsigned char)(ULTRASONIC_CCR >> 8);
    PWMA_CCR1L = (unsigned char)(ULTRASONIC_CCR);

    // 设置死区时间 (防止上下管直通)
    // 死区时间 = DTR * tCK_PSC = DTR / 24MHz
    // 取 DTR=24, 死区=1us
    PWMA_DTR = 24;

    // 使能互补输出: PWM1P和PWM1N
    // CC1E=1 (使能PWM1P), CC1NE=1 (使能PWM1N)
    // CC1P=0 (高电平有效), CC1NP=0 (高电平有效)
    PWMA_CCER1 = 0x05;                // CC1E=1, CC1NE=1, 高有效

    // 使能PWM输出到引脚
    PWMA_ENO = 0x03;                  // ENO1P=1, ENO1N=1

    // 使能主输出 (MOE=1)
    PWMA_BKR = 0x80;

    // 注意: 先不启动计数器, 由状态机控制启停
    // PWMA_CR1 = 0x01;  // 启动计数器
}

/* ======================== ADC初始化 ======================== */

void ADC_Init(void)
{
    // ADC时钟 = SYSclk / 2 / (ADCCFG[3:0]+1)
    // 取 ADCCFG=15, ADC时钟 = 24MHz/2/16 = 750kHz
    ADCCFG = 0x0F;                    // 右对齐, ADC时钟=750kHz

    // ADC控制寄存器
    // ADC_POWER=1, ADC_START=0, ADC_FLAG=0, ADC_EPWMT=0
    // ADC_CHS=0 (先选通道0)
    ADC_CONTR = 0x80;                 // 开启ADC电源, 不启动转换

    // 设置ADC时序
    P_SW2 |= 0x80;                    // 确保XFR使能
    // ADCTIM 使用默认值即可

    EADC = 1;                         // 使能ADC中断
}

/* ======================== 串口1初始化 (115200bps) ======================== */

void UART1_Init(void)
{
    // 使用定时器2作为波特率发生器
    // 波特率 = SYSclk / 4 / (65536 - 重载值)
    // 115200 = 24000000 / 4 / (65536 - 重载值)
    // 重载值 = 65536 - 24000000/4/115200 = 65536 - 52 = 65484 = 0xFFCC
    SCON = 0x50;                      // 模式1, 8位UART, 允许接收
    AUXR |= 0x01;                     // 串口1选择定时器2为波特率发生器
    AUXR |= 0x04;                     // 定时器2开始运行
    T2L = 0xCC;                       // 波特率重载值低字节
    T2H = 0xFF;                       // 波特率重载值高字节
    ES = 1;                           // 使能串口1中断
}

/* ======================== 按键扫描 ======================== */

void Key_Scan(void)
{
    static unsigned char key_start_cnt = 0;
    static unsigned char key_stop_cnt = 0;
    static unsigned char key_start_ok = 0;
    static unsigned char key_stop_ok = 0;

    // 启动按键检测 (低电平有效, 消抖20ms)
    if (KEY_START == 0)
    {
        key_start_cnt++;
        if (key_start_cnt >= 20)      // 20ms消抖
        {
            if (!key_start_ok)
            {
                key_start_ok = 1;
                // 启动按键按下
                if (g_SystemState == STATE_IDLE)
                {
                    // 空闲状态 -> 启动清洗
                    if ((g_ErrorFlags & ERR_NO_WATER) == 0)
                    {
                        g_SystemState = STATE_RUNNING;
                        g_WorkTime = 0;
                        g_HeatTimeCount = 0;
                        Ultrasonic_Control(1);
                        UART1_SendString("Start Cleaning.\r\n");
                    }
                    else
                    {
                        // 缺水, 不能启动
                        Beep_Control(1);
                        g_BeepCount = 50;   // 蜂鸣500ms
                        UART1_SendString("No Water!\r\n");
                    }
                }
            }
        }
    }
    else
    {
        key_start_cnt = 0;
        key_start_ok = 0;
    }

    // 停止按键检测 (低电平有效)
    if (KEY_STOP == 0)
    {
        key_stop_cnt++;
        if (key_stop_cnt >= 20)       // 20ms消抖
        {
            if (!key_stop_ok)
            {
                key_stop_ok = 1;
                // 停止按键按下
                if (g_SystemState == STATE_RUNNING || g_SystemState == STATE_HEATING)
                {
                    g_SystemState = STATE_IDLE;
                    Ultrasonic_Control(0);
                    Heat_Control(0);
                    UART1_SendString("Stop Cleaning.\r\n");
                    // 提示音
                    Beep_Control(1);
                    g_BeepCount = 30;
                }
            }
        }
    }
    else
    {
        key_stop_cnt = 0;
        key_stop_ok = 0;
    }
}

/* ======================== 安全检测 ======================== */

void Safety_Check(void)
{
    unsigned char err = 0;

    // 水位检测
    if (g_AdcWaterValue < WATER_LOW_ADC)
    {
        err |= ERR_NO_WATER;
    }

    // 温度检测 (超过60°C报警)
    if (g_AdcTempValue < 200)         // ADC<200 约对应 >60°C
    {
        err |= ERR_OVER_TEMP;
    }

    // 门开关检测 (0=关闭, 1=打开)
    if (DOOR_SW == 1)
    {
        err |= ERR_DOOR_OPEN;
    }

    // 过流检测 (0=正常, 1=过流)
    if (OVER_CURR == 1)
    {
        err |= ERR_OVER_CURRENT;
    }

    // 更新错误标志
    if (err != g_ErrorFlags)
    {
        g_ErrorFlags = err;

        if (err != 0)
        {
            // 有错误发生
            UART1_SendString("Error:");
            UART1_SendHex(err);
            UART1_SendString("\r\n");

            // 如果正在运行, 停止
            if (g_SystemState == STATE_RUNNING || g_SystemState == STATE_HEATING)
            {
                g_SystemState = STATE_ERROR;
                Ultrasonic_Control(0);
                Heat_Control(0);
                Beep_Control(1);
                g_BeepCount = 200;    // 蜂鸣2秒
            }
        }
        else
        {
            // 错误恢复
            if (g_SystemState == STATE_ERROR)
            {
                g_SystemState = STATE_IDLE;
                UART1_SendString("Error Cleared.\r\n");
            }
        }
    }
}

/* ======================== 状态机 ======================== */

void State_Machine(void)
{
    switch (g_SystemState)
    {
    case STATE_IDLE:
        // 空闲状态, 不做任何操作
        break;

    case STATE_RUNNING:
        // 运行状态
        g_WorkTime++;

        // 检查是否需要加热 (前30秒加热)
        if (g_WorkTime <= 30)
        {
            // 检查温度是否低于40°C
            if (g_AdcTempValue > TEMP_40C_ADC)
            {
                Heat_Control(1);      // 开启加热
                g_SystemState = STATE_HEATING;
                UART1_SendString("Heating On.\r\n");
            }
        }

        // 检查是否到达设定时间
        if (g_WorkTime >= (unsigned int)(g_WorkTimeSet) * 60)
        {
            // 清洗完成
            g_SystemState = STATE_IDLE;
            Ultrasonic_Control(0);
            Heat_Control(0);
            UART1_SendString("Cleaning Complete.\r\n");

            // 完成提示音 (响3次)
            Beep_Control(1);
            g_BeepCount = 30;         // 300ms
            // 后续蜂鸣由主循环处理
        }
        break;

    case STATE_HEATING:
        // 加热状态
        g_WorkTime++;
        g_HeatTimeCount++;

        // 检查温度是否达到50°C
        if (g_AdcTempValue < TEMP_50C_ADC)
        {
            Heat_Control(0);          // 关闭加热
            g_SystemState = STATE_RUNNING;
            UART1_SendString("Heating Off.\r\n");
        }

        // 加热超时保护 (超过120秒强制关闭加热)
        if (g_HeatTimeCount > 120)
        {
            Heat_Control(0);
            g_SystemState = STATE_RUNNING;
            UART1_SendString("Heat Timeout.\r\n");
        }

        // 检查是否到达设定时间
        if (g_WorkTime >= (unsigned int)(g_WorkTimeSet) * 60)
        {
            g_SystemState = STATE_IDLE;
            Ultrasonic_Control(0);
            Heat_Control(0);
            UART1_SendString("Cleaning Complete.\r\n");
            Beep_Control(1);
            g_BeepCount = 30;
        }
        break;

    case STATE_ERROR:
        // 错误状态, 等待错误清除
        if (g_ErrorFlags == 0)
        {
            g_SystemState = STATE_IDLE;
            UART1_SendString("Recovery.\r\n");
        }
        break;

    default:
        g_SystemState = STATE_IDLE;
        break;
    }
}

/* ======================== 超声波控制 ======================== */

void Ultrasonic_Control(unsigned char on)
{
    if (on)
    {
        // 启动PWM输出
        PWMA_CR1 |= 0x01;             // CEN=1, 启动计数器
    }
    else
    {
        // 停止PWM输出
        PWMA_CR1 &= ~0x01;            // CEN=0, 停止计数器
        // 将输出引脚置为低电平
        P1M0 &= ~0x03;                // P1.0,P1.1改为准双向
        P1M1 &= ~0x03;
        P10 = 0;
        P11 = 0;
    }
}

/* ======================== 加热控制 ======================== */

void Heat_Control(unsigned char on)
{
    HEAT_RELAY = (on) ? 1 : 0;
    LED_HEAT = (on) ? 1 : 0;
}

/* ======================== 蜂鸣器控制 ======================== */

void Beep_Control(unsigned char on)
{
    BUZZER = (on) ? 1 : 0;
}

/* ======================== LED更新 ======================== */

void LED_Update(void)
{
    static unsigned char blink_cnt = 0;

    blink_cnt++;
    if (blink_cnt >= 5) blink_cnt = 0;  // 500ms周期

    switch (g_SystemState)
    {
    case STATE_IDLE:
        LED_RUN = 0;                  // 灭
        break;

    case STATE_RUNNING:
        LED_RUN = 1;                  // 常亮
        break;

    case STATE_HEATING:
        LED_RUN = (blink_cnt < 3) ? 1 : 0;  // 快闪
        break;

    case STATE_ERROR:
        LED_RUN = (blink_cnt < 1) ? 1 : 0;  // 慢闪
        break;

    default:
        LED_RUN = 0;
        break;
    }
}

/* ======================== ADC操作 ======================== */

void ADC_Start(unsigned char channel)
{
    ADC_CONTR = 0x80 | 0x40 | channel;   // ADC_POWER=1, ADC_START=1, 选择通道
}

unsigned int ADC_GetResult(void)
{
    unsigned int result;
    result = (unsigned int)(ADC_RES) << 8;
    result |= ADC_RESL;
    return result;
}

/* ======================== 延时函数 ======================== */

void Delay1ms(unsigned int ms)
{
    unsigned int i, j;
    for (i = 0; i < ms; i++)
    {
        // 24MHz下, 简单循环约1ms
        for (j = 0; j < 2000; j++)
        {
            _nop_();
        }
    }
}

/* ======================== 串口发送函数 ======================== */

void UART1_SendByte(unsigned char dat)
{
    SBUF = dat;
    while (TI == 0);                  // 等待发送完成
    TI = 0;                           // 清除标志
}

void UART1_SendString(unsigned char *str)
{
    while (*str != '\0')
    {
        UART1_SendByte(*str);
        str++;
    }
}

void UART1_SendHex(unsigned char dat)
{
    unsigned char hex_char;

    // 高4位
    hex_char = (dat >> 4) & 0x0F;
    if (hex_char < 10)
        UART1_SendByte(hex_char + '0');
    else
        UART1_SendByte(hex_char - 10 + 'A');

    // 低4位
    hex_char = dat & 0x0F;
    if (hex_char < 10)
        UART1_SendByte(hex_char + '0');
    else
        UART1_SendByte(hex_char - 10 + 'A');
}

/* ======================== 定时器0中断服务 (1ms系统节拍) ======================== */

void Timer0_ISR(void) interrupt 1
{
    g_1msFlag = 1;
}

/* ======================== 定时器1中断服务 (ADC触发) ======================== */

void Timer1_ISR(void) interrupt 3
{
    // 每1ms切换一次ADC通道, 交替测量水位和温度
    static unsigned char adc_ch_cnt = 0;

    adc_ch_cnt++;
    if (adc_ch_cnt >= 100)            // 每100ms切换一次
    {
        adc_ch_cnt = 0;
        if (g_AdcChannel == ADC_CH_WATER)
        {
            g_AdcChannel = ADC_CH_TEMP;
        }
        else
        {
            g_AdcChannel = ADC_CH_WATER;
        }
        ADC_Start(g_AdcChannel);
    }
}

/* ======================== ADC中断服务 ======================== */

void ADC_ISR(void) interrupt 5
{
    unsigned int adc_value;

    ADC_CONTR &= ~0x20;               // 清除ADC_FLAG

    adc_value = ADC_GetResult();

    // 根据当前通道保存结果
    if (g_AdcChannel == ADC_CH_WATER)
    {
        // 简单滤波: 取最近4次平均值
        static unsigned int water_buf[4] = {0,0,0,0};
        static unsigned char water_idx = 0;
        unsigned char i;
        unsigned long sum = 0;

        water_buf[water_idx] = adc_value;
        water_idx++;
        if (water_idx >= 4) water_idx = 0;

        for (i = 0; i < 4; i++) sum += water_buf[i];
        g_AdcWaterValue = (unsigned int)(sum >> 2);
    }
    else
    {
        // 温度滤波
        static unsigned int temp_buf[4] = {0,0,0,0};
        static unsigned char temp_idx = 0;
        unsigned char i;
        unsigned long sum = 0;

        temp_buf[temp_idx] = adc_value;
        temp_idx++;
        if (temp_idx >= 4) temp_idx = 0;

        for (i = 0; i < 4; i++) sum += temp_buf[i];
        g_AdcTempValue = (unsigned int)(sum >> 2);
    }
}

/* ======================== 串口1中断服务 ======================== */

void UART1_ISR(void) interrupt 4
{
    unsigned char dat;

    if (RI)
    {
        RI = 0;                       // 清除接收中断标志
        dat = SBUF;                   // 读取接收数据

        // 简单的串口命令解析
        // '1' - 启动
        // '2' - 停止
        // '3' - 设置3分钟
        // '4' - 设置5分钟
        // '5' - 设置10分钟
        // 's' - 查询状态
        switch (dat)
        {
        case '1':
            if (g_SystemState == STATE_IDLE)
            {
                if ((g_ErrorFlags & ERR_NO_WATER) == 0)
                {
                    g_SystemState = STATE_RUNNING;
                    g_WorkTime = 0;
                    g_HeatTimeCount = 0;
                    Ultrasonic_Control(1);
                    UART1_SendString("Start.\r\n");
                }
                else
                {
                    UART1_SendString("No Water!\r\n");
                }
            }
            break;

        case '2':
            if (g_SystemState == STATE_RUNNING || g_SystemState == STATE_HEATING)
            {
                g_SystemState = STATE_IDLE;
                Ultrasonic_Control(0);
                Heat_Control(0);
                UART1_SendString("Stop.\r\n");
            }
            break;

        case '3':
            g_WorkTimeSet = TIME_3MIN;
            UART1_SendString("Set 3min.\r\n");
            break;

        case '4':
            g_WorkTimeSet = TIME_5MIN;
            UART1_SendString("Set 5min.\r\n");
            break;

        case '5':
            g_WorkTimeSet = TIME_10MIN;
            UART1_SendString("Set 10min.\r\n");
            break;

        case 's':
            UART1_SendString("State:");
            UART1_SendHex(g_SystemState);
            UART1_SendString(" Time:");
            UART1_SendHex(g_WorkTime);
            UART1_SendString("s Set:");
            UART1_SendHex(g_WorkTimeSet);
            UART1_SendString("min Err:");
            UART1_SendHex(g_ErrorFlags);
            UART1_SendString("\r\n");
            break;

        default:
            break;
        }
    }

    if (TI)
    {
        TI = 0;                       // 清除发送中断标志
    }
}

三、程序说明

1. 系统时钟

  • 使用内部高精度IRC,在ISP下载时设置为 24MHz
  • 定时器0/1使用12T模式,产生1ms系统节拍

2. 超声波驱动

  • 使用 PWMA高级PWM 产生40kHz互补PWM信号
  • PWM1P(P1.0)和PWM1N(P1.1)输出带死区控制的互补波形
  • 死区时间约1μs,防止上下管直通
  • 占空比50%,频率40kHz

3. 温度控制

  • 使用NTC热敏电阻(10K, B=3950) + 10K分压电阻
  • 通过ADC检测电压,换算温度
  • 加热目标:40°C~50°C恒温区间
  • 加热超时保护:120秒

4. 水位检测

  • 使用电极式水位传感器
  • ADC检测水位,低于阈值报警并禁止启动

5. 安全保护

  • 缺水保护:水位过低时停止工作
  • 超温保护:温度超过60°C时停止
  • 门开关保护:门打开时停止工作
  • 过流保护:检测到过流时停止工作

6. 清洗时间

  • 默认5分钟
  • 可通过串口命令设置为3/5/10分钟

7. 串口调试

  • 波特率115200bps
  • 支持命令控制:启动/停止/设置时间/查询状态

四、使用步骤

  1. 打开 STC-ISP 下载软件
  2. 选择芯片型号:STC8H1K08
  3. 设置内部IRC频率:24MHz
  4. 硬件选项:取消勾选"允许低压复位(禁止低压中断)"
  5. 编译本程序,生成HEX文件
  6. 通过串口下载到芯片

如需进一步调整参数或有其他问题,请随时提出!


🔍 搜索 "STC8H1K08 超声波清洗机 控制程序 演示程序 注解"

📌 相关论坛讨论

  1. STC单片机在眼镜清洗机方面的应用
  2. 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
  3. STC单片机在超声波清洗机 中应用,STC8H1K08-36I-TSSOP20

⏱️ 查找用时 11.1s | 总回答用时 80.0s

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 10:41 , Processed in 0.096816 second(s), 92 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表