数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 704|回复: 25

[AVR] 祖国76岁华诞之际,继续炒炒QUICK963的叛逆之路

[复制链接]
发表于 2025-9-30 20:06:12 | 显示全部楼层 |阅读模式
1949年10月1日,中华人民共和国中央人民政府成立。教员的声音至今令人热血沸腾。
今年,当中秋的月光与国庆的华彩相互交织,我们即将迎来祖国76岁华诞。
金风漫卷五星红,七六春秋伟业隆。踏浪乘风新程启,复兴歌里颂苍穹。
继续炒炒QUICK963的叛逆之路,祝祖国生日快乐!
不多唠叨,主要分四部分汇报:
一是确定数码管的显示方式
二是硬件使用的加热方式
三是PID算法
四是整体调试
用两层,第二层为简陋的程序,请老鸟勿喷哈。

一、确定数码管的显示方式
码管有1位、2位、3位等等,又分为共阴极和共阳极两种封装,多用共阴极。
具体LED数码管的原理请移步:
岁末一帖,给小数码管做个大手术,并了解数码管基本结构原理
https://www.mydigit.cn/thread-433928-1-1.html
其中第二部分有相关介绍。
要想点亮数码管并显示“字”,就要知道使用的是什么类型的数码管,
以8段共阴极为例:公共的阴极接低电平,8个段只有接高电平的才能点亮,
要显示数字“1”,就是点亮B、C两段,
当以正序排列时:数码管的8段对应一个字节就是0b00000110(0x06),
其中bit0对应A段、bit1对应B段......bit7对应DP段(小数点)。
而当以逆序排列时:数码管的8段对应一个字节就是0b01100000(0x60),
其中bit0对应DP段、bit1对应G段......bit7对应A段。
两种编码关系如下:

总结如下:


对照这个963焊台的电路:


使用的是3位共阳极编码,根据硬件,定义如下:

  1. // 数码管段选引脚定义 (共阳极,低电平有效)
  2. #define SEG_A   PB6   // 数码管A段
  3. #define SEG_B   PD0   // 数码管B段
  4. #define SEG_C   PD2   // 数码管C段
  5. #define SEG_D   PD4   // 数码管D段
  6. #define SEG_E   PD5   // 数码管E段
  7. #define SEG_F   PB7   // 数码管F段
  8. #define SEG_G   PD1   // 数码管G段
  9. #define SEG_DP  PD3   // 数码管小数点
  10. // 数码管位选引脚定义 (共阳极,低电平有效)
  11. #define DIGIT_1 PC1   // 第一位数字(百位)
  12. #define DIGIT_2 PC4   // 第二位数字(十位)
  13. #define DIGIT_3 PC5   // 第三位数字(个位)

  14. 段位编码用正序:
  15. // 数码管显示字符编码 (共阳极, 低电平有效)
  16. // 数码管段位顺序:DP G F E D C B A
  17. const uint8_t seg_pattern[] = {
  18.     // 0     1     2     3     4     5     6     7     8     9
  19.     0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
  20. };

  21. 每一段是否点亮由下面代码完成:
  22. // 数码管段选信号(共阳极,低电平点亮)
  23.     // PORTB处理:A段(PB6)和F段(PB7)
  24.     if(!(seg & 0x01)) PORTB &= ~(1 << SEG_A);  // A段点亮
  25.     else PORTB |= (1 << SEG_A);               // A段熄灭
  26.     if(!(seg & 0x20)) PORTB &= ~(1 << SEG_F);  // F段点亮
  27.     else PORTB |= (1 << SEG_F);               // F段熄灭
  28.     // PORTD处理:B,C,D,E,G,DP段
  29.     if(!(seg & 0x02)) PORTD &= ~(1 << SEG_B);  // B段点亮
  30.     else PORTD |= (1 << SEG_B);               // B段熄灭
  31.     if(!(seg & 0x04)) PORTD &= ~(1 << SEG_C);  // C段点亮
  32.     else PORTD |= (1 << SEG_C);               // C段熄灭
  33.     if(!(seg & 0x08)) PORTD &= ~(1 << SEG_D);  // D段点亮
  34.     else PORTD |= (1 << SEG_D);               // D段熄灭
  35.     if(!(seg & 0x10)) PORTD &= ~(1 << SEG_E);  // E段点亮
  36.     else PORTD |= (1 << SEG_E);               // E段熄灭
  37.     if(!(seg & 0x40)) PORTD &= ~(1 << SEG_G);  // G段点亮
  38.     else PORTD |= (1 << SEG_G);               // G段熄灭
  39.     if(!(seg & 0x80)) PORTD &= ~(1 << SEG_DP); // DP段点亮
  40. <font size="2">    else PORTD |= (1 << SEG_DP);              // DP段熄灭</font>
复制代码
  1. // 共阳数码管关闭所有位选(共阳极,低电平关闭)
  2.     PORTC &= ~((1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3));
复制代码


二、硬件使用的加热方式交流过零触发
为了减小干扰,原来的硬件是使用了可控硅控制加热的,
加热控制信号由MCU的13脚PB1输出,控制非零交叉双可控硅耦合器MOC3021的1脚,MOC3021的2脚接地,
MOC3021的6脚和4脚分别接双向可控硅BTA16的2脚和3脚,其导通可以控制30V交流电给发热芯加热。
这种过零触发方式,只在交流电过零点开启可控硅,下一个过零点可控硅自己关闭(可控硅会导通整个半波)。
由于市电是50Hz,即每20ms一个周期(每个半波为10ms,过零信号在每个半波的开始处产生)。

可以理解为通过控制导通周期数(或半波数)来调节功率。
通过控制在一定数量的半波中,导通的个数来调节功率。
比如在100个半波中,通过控制60个半波导通,最后得到了60%的功率。
需要的加热控制方式可以通过“累加器”算法,使其均匀分布在100个半波中。
根据电路实际情况,

在程序中设定

// 其他引脚定义
#define ZERO_CROSS  PD6   // 交流过零检测输入
#define HEATER_CTL  PB1   // 加热控制输出(高电平有效)

// 外部中断初始化(过零检测)
void init_ext_int(void) {
    // 使用PCINT22(PD6)用于过零检测
    PCICR |= (1 << PCIE2);               // 启用PCINT2组中断(PD0-PD7)
    PCMSK2 |= (1 << PCINT22);            // 启用PD6的引脚变化中断
}



三、控制过程
控制过程就是:采样、计算、输出控制脉冲、再采样、计算……
(一)实际温度采样
如下图

采用ADC7脚进行温度采样
相关的程序
// 程序中ADC通道定义
#define TEMP_ADC   7      // ADC7 - 温度传感器输入

// ADC初始化函数
void init_adc(void) {
    // 使用外部AREF作为参考电压,选择ADC7通道,右对齐模式
    ADMUX = (0 << REFS1) | (0 << REFS0) | (0 << ADLAR) | (TEMP_ADC & 0x0F);
   
    // 使能ADC,预分频128(8MHz/128=62.5kHz)
    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
   
    // 第一次转换丢弃,确保ADC稳定
    ADCSRA |= (1 << ADSC);
    while(ADCSRA & (1 << ADSC));
}

// 读取ADC值并转换为mV
uint16_t read_adc(uint8_t channel) {
    // 选择通道,保持参考电压设置不变
    ADMUX = (0 << REFS1) | (0 << REFS0) | (0 << ADLAR) | (channel & 0x0F);
   
    // 开始转换
    ADCSRA |= (1 << ADSC);
   
    // 等待转换完成
    while (ADCSRA & (1 << ADSC));
   
    // 返回10位ADC值转换的mV值
    // 右对齐时,ADC值 = ADCL(低8位) + (ADCH << 8)(高2位)
    return ADC; // ADC寄存器自动拼接ADCL和ADCH(右对齐)
}

// 读取温度传感器值
void read_temp(void) {
    static uint32_t last_temp_read = 0;
    uint32_t current_time = millis();
   
    // 定时读取温度(200ms一次)
    if (current_time - last_temp_read >= TEMP_UPDATE_TIME) {
        last_temp_read = current_time;
        
        // 读取温度传感器的mV值
        uint16_t adc_value = read_adc(TEMP_ADC);
               
        // 计算电压(mV):参考电压2500mV,10位分辨率
        uint32_t voltage = (adc_value * 2500UL) / 1024;
        
        // 检查热耦故障(超过2.2V)
        if(voltage > 2200) {
            error_state = 1;  // 1表示热耦故障
            return;
        }
        
        // 转换为温度值
        actual_temp = mv_to_temp(voltage);

        // 温度更新后立即触发PID计算(同步)
        calculate_pid();  
    }
}

(二)PID计算
一般对温度控制使用PWM方式,PID计算结果是占比,从0%到100%。
本电路使用的是可控硅,也可以用PID的结果采取移相控制,但,
还是采用了与原来相类似的方式:过零触发方式。
PID算法出将转换为在一定时间内(比如1秒)需要导通的半波数。
50Hz交流电,每秒钟有100个半波。如果我们以1秒为周期,则最大功率可视为100个半波全部导通,功率为100%;
如果导通50个,则为50%。
因此,可以将PID的输出(0-100%)转换为每100个半波中导通的个数。
但是,为了响应更快,我们可以将控制周期缩短,比如0.2秒(20个半波),
这样每0.2秒(200ms)计算一次PID,并决定在接下来的20个半波中导通几个。
基本的思路:
协调同步:温度采样(200ms)→ 立即PID 计算 → 加热控制(200ms 小周期),形成闭环。
响应速度:1 秒内更新 5 次加热策略(PID),对温度变化的响应更快。
减少滞后:小周期(20 半波)控制减少的滞后误差,更接近实时调节。

// PID控制计算
void calculate_pid(void) {
   
    // 计算误差
    float error = set_temp - actual_temp;
   
    // 实际温度高于设定温度,停止加热并重置积分
    if (error < 0) {
        required_heat_cycles = 0;
        integral = 0;  // 重置积分项,防止积分饱和
        last_error = error;
        return;
    }
   
    // 温差较大时全功率加热,启动时的强加热阶段
    if (error > 100) {
        required_heat_cycles = MAX_HEAT_CYCLES;
        integral = 0;
        last_error = error;
        return;
    }
   
    // 积分项计算(仅误差为正时累加,防止积分饱和)
    if (error > 0) {
        integral += error;
        // 限制积分范围
        if (integral > 500) integral = 500;
        if (integral < -500) integral = -500;
    } else {
        integral = 0;  // 当误差为负时重置积分项
    }
   
    // 微分项计算(误差变化率)
    float derivative = (error - last_error);
    last_error = error;

    derivative = -derivative;  // 反转符号,抑制超调
   
    // PID输出计算
    float p_term = Kp * error;
    float i_term = Ki * integral;
    float d_term = Kd * derivative;
    float output = p_term + i_term + d_term;
   
    // 限制输出范围
    if (output < 0) output = 0;
    if (output > 100) output = 100;
   
    required_heat_cycles = (uint8_t)output;

    // 计算当前小周期(20半波)的加热数:按比例分配到20个半波中
    required_heat_cycles = (required_heat_cycles * HEAT_SUB_CYCLE) / HEAT_FULL_CYCLE;
}


加热信号的输出,由过零中断控制PB1输出,
  1. // 过零检测中断服务程序
  2. ISR(PCINT2_vect) {
  3.     // 检查PD6引脚状态变化(过零检测)
  4.     static uint8_t last_pd6_state = 0;
  5.     uint8_t current_pd6_state = PIND & (1 << ZERO_CROSS);
  6.    
  7.     // 仅处理状态变化的情况
  8.     if (current_pd6_state == last_pd6_state) return;
  9.     last_pd6_state = current_pd6_state;
  10.    
  11.     uint32_t current_time = micros();
  12.     // 过零检测去抖(避免高频干扰)
  13.     if (current_time - last_zero_cross_time < ZERO_CROSS_DEBOUNCE) return;
  14.     last_zero_cross_time = current_time;

  15.         // 如果要求加热周期数为0,直接返回
  16.         if (required_heat_cycles == 0) {
  17.             heat_accumulator = 0;  // 重置累加器
  18.             return;
  19.         }
  20.         
  21.         // 均匀分布算法控制加热
  22.         // 累加器算法:每次过零时累加所需加热功率
  23.         heat_accumulator += required_heat_cycles;

  24.         // 当累加器达到或超过20时,触发加热并减去20
  25.         if (heat_accumulator >= 20) {
  26.             heat_accumulator -= 20;
  27.             // 触发加热(输出200us脉冲)
  28.             PORTB |= (1 << HEATER_CTL);
  29.             _delay_us(200);  // 触发脉冲宽度
  30.             PORTB &= ~(1 << HEATER_CTL);
  31.         }
  32. }
复制代码


四、整体调试

整个的调试主要是以下三个方面:
一是调试LED数码管的编码及显示是否正确
二是调试控制逻辑能否实现
三是PID参数的调整
调试可以两步走:
(一)用仿真对数码管及整体逻辑检查调试

仿真调试程序的逻辑功能
为了能快速检查程序中的逻辑正确性,先用仿真调试。

如果数码管显示不正确,可以在仿真中快速发现并修正。
用示波器可以观察加热信号在交流过零时同步输出。

当实际温度与设定温度(280度)相关较大时,
全功率加热,每个过零点都输出加热信号,且能点亮最后一位小数点。

放大波形可见输出与过零点是同步的。

当实际温度快达到设定定温度时,进入间歇加热状态。

当温度达到设定温度时,停止加热,没有加热信号输出,
最后一位小数点也熄灭。

程序的基本加热逻辑功能正常。
也可以测试按键及振动传感器的工作情况,
以及传感器故障、发热体故障等。




(二)PID算法调试
这是必须在实际硬件环境中进行的,
换ATMEGA168是前期完成的,

从电路板上引出相关测试点是必须的:

连接ASP下载器也是必须的:

先看下加热信号的同步及间歇工作情况
同步信号如下:

温差较大,全功率工作:

温差较小,出现间歇工作:

刚到达设定温度时:

1、大概理解一下 PID 参数的核心作用:
在调整前,需明确Kp(比例)、Ki(积分)、Kd(微分) 对系统的具体影响:
Kp(比例增益):主要影响系统的响应速度和稳态误差。
Kp 越大,响应越快,对偏差的纠正力度越强,但过大会导致超调(超过目标值)和震荡。
Ki(积分增益):主要用于消除稳态误差(系统稳定后实际值与目标值的偏差)。
Ki 越大,消除误差的速度越快,但过大会放大系统震荡,甚至导致不稳定。
Kd(微分增益):主要用于抑制超调和震荡,提前“预判”偏差的变化趋势(通过偏差的变化率),
从而抑制系统的剧烈波动。Kd 过大会导致系统对噪声敏感(微小扰动被放大),过小则无法有效抑制超调。
2、手动参数调整方法:
适合初学(乞丐)者,没有精密的测量设备,但直观可控。
手动调参遵循 “先比例,再积分,最后微分” 的顺序,逐步优化:
第一步:调比例(Kp)
先将 Ki=0,Kd=0,仅保留比例控制(纯 P 控制),逐渐增大 Kp,观察系统响应:
若响应太慢(实际值接近设定值的速度慢),增大 Kp;
若出现明显超调或持续震荡,减小 Kp。
目标:找到一个 “临界 Kp”,系统刚好出现轻微、衰减的震荡(即震荡幅度逐渐减小,最终稳定),
此时系统响应较快且超调可接受。
这是不同Kp时的曲线图:

第二步:调积分(Ki)
在第一步的 “临界”Kp 基础上,逐渐增大 Ki(从 0 开始,每次增加少量)。
观察系统:
若存在稳态误差(稳定后实际值与目标值有偏差),增大 Ki;
若震荡加剧(因积分累积放大了波动),减小 Ki。
目标:消除稳态误差,同时保持系统震荡在可接受范围,
一般 Ki 不宜过大,避免 “积分饱和”、输出因积分累积超出执行器范围。
当Kp=4时,不同Ki的曲线图:

当Kp=6时,不同Ki的曲线图:

第三步:调微分(Kd)
在 P、I 参数基础上,逐渐增大 Kd(从 0 开始,每次增加少量)。
观察系统:
若超调严重或震荡频繁,增大 Kd(抑制波动);
若系统响应变得迟钝(过度抑制)或对噪声敏感(如传感器微小波动导致输出剧烈变化),减小 Kd。
目标:进一步减小超调,加快系统稳定速度,同时避免过度灵敏。
当Kp=4.0、Ki=0.05时,不同Kd的曲线图:

当Kp=6.0、Ki=0.1时,不同Kd的曲线图:

最终选定PID的参数:
// PID控制参数(根据实际调整)
float Kp = 4.0;     // 比例系数 - 提高响应速度  5.0
float Ki = 0.05;     // 积分系数 - 减小稳态误差  0.05
float Kd = 2.0;     // 微分系数 - 抑制超调     2.0
float integral = 0;     // 积分项
float last_error = 0;   // 上一次误差值
uint32_t last_pid_time = 0;    // 上次PID计算时间




五、无标破焊台
目前老快克963变成了没有标识的了,外壳也修修补补不成样子了

程序的总体控制不是很好,还是有过冲,将就着用,主要还是消耗现有的手柄。

简单核对一下温度,当然,表也是炮灰


开到330度,焊一个双面电路板,带弹簧的,无压力。

楼下是程序,不感兴趣的可以返回了。
去楼下的先点个赞哈




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x

打赏

参与人数 13家元 +390 收起 理由
Google Earth + 30 優秀文章
moontree + 30 謝謝分享
kyhwhb + 30 優秀文章
潜隆 + 30 謝謝分享
小鼯 + 30 優秀文章

查看全部打赏

 楼主| 发表于 2025-9-30 20:07:15 | 显示全部楼层
本帖最后由 jf201006 于 2025-9-30 20:22 编辑

以下程序,带有调试的部分没有删除,请自行检查。
这是最小功能版,加减温度、故障检测及蜂鸣、休眠、软关机。
没有温度补偿及更换不同发热头(手柄)后的温度校准。
半桶水,注释已经很详细了,不答疑哈。
  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3. #include <util/delay.h>
  4. #include <math.h>

  5. // 数码管段选引脚定义 (共阳极,低电平有效)
  6. #define SEG_A   PB6   // 数码管A段
  7. #define SEG_B   PD0   // 数码管B段
  8. #define SEG_C   PD2   // 数码管C段
  9. #define SEG_D   PD4   // 数码管D段
  10. #define SEG_E   PD5   // 数码管E段
  11. #define SEG_F   PB7   // 数码管F段
  12. #define SEG_G   PD1   // 数码管G段
  13. #define SEG_DP  PD3   // 数码管小数点

  14. // 数码管位选引脚定义 (共阳极,低电平有效)
  15. #define DIGIT_1 PC1   // 第一位数字(百位)
  16. #define DIGIT_2 PC4   // 第二位数字(十位)
  17. #define DIGIT_3 PC5   // 第三位数字(个位)

  18. // 按键引脚定义 (上拉输入,低电平表示按下)
  19. #define KEY_DEC  PB3   // K1 - 减温度
  20. #define KEY_FUNC PB4   // K2 - 功能/快速设置
  21. #define KEY_INC  PB5   // K3 - 加温度
  22. #define KEY_RST  PC2   // S1 - 恢复出厂设置

  23. // 其他引脚定义
  24. #define VIB_SENSOR  PB0   // 振动传感器输入(高电平有效)
  25. #define ZERO_CROSS  PD6   // 交流过零检测输入
  26. #define HEATER_CTL  PB1   // 加热控制输出(高电平有效)
  27. #define BUZZER      PC0   // 蜂鸣器输出(低电平有效)

  28. // ADC通道定义
  29. #define TEMP_ADC   7      // ADC7 - 温度传感器输入

  30. // 默认参数设置
  31. #define DEFAULT_TEMP 280  // 默认温度280°C
  32. #define MIN_TEMP     150  // 最低温度150°C
  33. #define MAX_TEMP     500  // 最高温度500°C
  34. #define SLEEP_TEMP   200  // 休眠温度200°C

  35. // 温度与ADC采样值对应关系(两点校准)
  36. #define TEMP1       200   // 温度点1
  37. #define ADC1_MV     530   // 温度点1对应的mV值
  38. #define TEMP2       350   // 温度点2
  39. #define ADC2_MV     975   // 温度点2对应的mV值

  40. // 时间常数定义(ms)
  41. #define DEBOUNCE_TIME   20      // 按键去抖时间
  42. #define LONG_PRESS_TIME 500     // 长按时间阈值
  43. #define DISP_UPDATE_TIME 5      // 显示更新间隔
  44. #define SLEEP_TIME      600000  // 休眠触发时间(10分钟)
  45. #define SHUTDOWN_TIME   1800000 // 关机触发时间(30分钟)
  46. #define ERROR_BEEP_TIME 10000   // 错误提示间隔(10秒)
  47. #define ZERO_CROSS_DEBOUNCE 500 // 过零检测去抖时间(us)

  48. // 时间常数(基于10ms过零周期的整数倍)
  49. #define TEMP_UPDATE_TIME  200    // 温度采样间隔200ms(20×10ms)
  50. #define PID_UPDATE_TIME   200    // PID计算间隔=采样间隔(同步)
  51. #define HEAT_SUB_CYCLE    20     // 加热小周期(20个半波=200ms)
  52. #define HEAT_FULL_CYCLE   100    // 加热大周期(100个半波=1秒)

  53. // 全局变量:记录当前小周期内已加热的半波数
  54. volatile uint8_t current_sub_cycle_count = 0;
  55. volatile uint8_t heat_sub_cycles = 0;          // 每个小周期需加热的半波数

  56. // 过零控制参数
  57. #define MAX_HEAT_CYCLES 20     // 最大加热周期数(100个半波=1秒)现在用20半波
  58. #define MIN_HEAT_CYCLES 0       // 最小加热周期数

  59. // 加热故障检测参数
  60. #define HEATING_FAULT_TIME 40000    // 加热故障检测时间(40秒)
  61. #define HEATING_FAULT_DELTA 80      // 加热故障温度差(80°C)

  62. // 数码管显示字符编码 (共阳极, 低电平有效)
  63. // 段位顺序:DP G F E D C B A (bit7 ~ bit0)
  64. const uint8_t seg_pattern[] = {
  65.     // 0    1    2    3    4    5    6    7    8    9
  66.     0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,
  67.     // E    -    ' '   S     H
  68.     0x86, 0xBF, 0xFF, 0x92, 0x89
  69. };

  70. // 全局变量定义
  71. volatile uint16_t set_temp = DEFAULT_TEMP;    // 设定温度
  72. volatile uint16_t actual_temp = 0;           // 实际温度
  73. volatile uint16_t befor_sleep_temp = 0;      // 进入休眠前的温度
  74. volatile uint8_t display_value[3] = {0};     // 显示数值缓存
  75. volatile uint8_t digit_pos = 0;              // 当前显示位
  76. volatile uint8_t heating = 0;                // 加热状态(1:加热中)
  77. volatile uint8_t in_settings = 0;            // 设置模式标志
  78. volatile uint8_t quick_set_index = 0;        // 快速设置索引
  79. volatile uint32_t last_activity_time = 0;    // 最后活动时间
  80. volatile uint32_t sleep_start_time = 0;      // 进入休眠时间
  81. volatile uint8_t sleep_mode = 0;             // 休眠模式标志
  82. volatile uint8_t shutdown_mode = 0;          // 关机模式标志
  83. volatile uint8_t error_state = 0;            // 错误状态(0:正常)
  84. volatile uint32_t error_beep_time = 0;       // 错误提示时间
  85. volatile uint8_t blink_state = 0;            // 闪烁状态
  86. volatile uint32_t last_blink_time = 0;       // 最后闪烁时间

  87. // 加热故障检测变量
  88. volatile uint32_t heating_fault_start_time = 0;  // 开始检测加热故障的时间
  89. volatile uint8_t heating_fault_detected = 0;     // 加热故障检测标志

  90. // 过零控制变量
  91. volatile uint8_t zero_cross_detected = 0;    // 过零检测标志
  92. volatile uint32_t last_zero_cross_time = 0;  // 最后过零时间
  93. volatile uint8_t heat_cycles = 0;            // 当前加热周期数
  94. volatile uint8_t required_heat_cycles = 0;   // 需要加热的周期数

  95. // 均匀分布算法变量
  96. volatile int16_t heat_accumulator = 0;       // 加热累加器

  97. // 快速设置温度值
  98. const uint16_t quick_set_temps[] = {250, 330, 380};

  99. // PID控制参数(根据实际调整)
  100. float Kp = 4.0;     // 比例系数 - 提高响应速度  5.0
  101. float Ki = 0.05;     // 积分系数 - 减小稳态误差  0.05
  102. float Kd = 2.0;     // 微分系数 - 抑制超调     2.0
  103. float integral = 0;     // 积分项
  104. float last_error = 0;   // 上一次误差值
  105. uint32_t last_pid_time = 0;    // 上次PID计算时间

  106. // 按键长按处理变量
  107. volatile uint8_t key_long_press_active = 0;  // 长按激活标志
  108. volatile uint32_t key_long_press_time = 0;   // 长按计时
  109. volatile uint8_t key_long_press_id = 0;      // 长按按键ID

  110. // 函数声明
  111. void init_io(void);            // IO初始化
  112. void init_adc(void);           // ADC初始化
  113. void init_timers(void);        // 定时器初始化
  114. void init_ext_int(void);       // 外部中断初始化(过零检测)
  115. void update_display(void);     // 更新显示
  116. void read_temp(void);          // 读取温度
  117. void check_buttons(void);      // 检查按键
  118. void heater_control(void);     // 加热控制(过零触发)
  119. void check_sleep(void);        // 检查休眠状态
  120. void handle_errors(void);      // 处理错误状态
  121. void beep(uint16_t duration, uint16_t frequency); // 蜂鸣控制(时间,频率)
  122. uint16_t read_adc(uint8_t channel);  // 读取ADC的 MV值
  123. uint16_t mv_to_temp(uint16_t mv);    // 将MV值转换为温度值
  124. void calculate_pid(void);      // PID计算
  125. uint32_t millis(void);         // 获取当前时间(ms)
  126. uint32_t micros(void);         // 获取当前时间(us)
  127. void set_digit(uint8_t digit, uint8_t value, uint8_t dp); // 设置数码管显示
  128. void handle_long_press(void);  // 处理长按按键
  129. void check_heating_fault(void); // 检查加热故障

  130. int main(void) {
  131.     // 初始化系统
  132.     init_io();        // IO初始化
  133.     init_adc();       // ADC初始化
  134.     init_timers();    // 定时器初始化
  135.     init_ext_int();   // 外部中断初始化(过零检测)
  136.    
  137.     sei();            // 启用全局中断
  138.    
  139.     // 初始化时间戳
  140.     last_activity_time = millis();
  141.     last_pid_time = millis();
  142.     befor_sleep_temp = DEFAULT_TEMP;
  143.    
  144.     // 主循环
  145.     while(1) {
  146.         check_buttons();         // 检查按键输入
  147.         read_temp();             // 读取温度传感器
  148.         calculate_pid();         // 计算PID输出
  149.         heater_control();        // 加热控制
  150.         check_sleep();           // 检查休眠状态
  151.         handle_errors();         // 处理错误状态
  152.         check_heating_fault();   // 检查加热故障
  153.         update_display();        // 更新数码管显示
  154.         
  155.         _delay_ms(5);            // 短延时,降低CPU占用
  156.     }
  157. }

  158. // IO初始化函数
  159. void init_io(void) {
  160.     // 设置数码管段选引脚为输出
  161.     DDRB |= (1 << SEG_A) | (1 << SEG_F) | (1 << HEATER_CTL);
  162.     DDRD |= (1 << SEG_B) | (1 << SEG_C) | (1 << SEG_D) |
  163.             (1 << SEG_E) | (1 << SEG_G) | (1 << SEG_DP);
  164.    
  165.     // 设置数码管位选引脚为输出
  166.     DDRC |= (1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3) | (1 << BUZZER);
  167.    
  168.     // 设置按键引脚为输入,启用上拉电阻
  169.     DDRB &= ~((1 << KEY_DEC) | (1 << KEY_FUNC) | (1 << KEY_INC));
  170.     PORTB |= (1 << KEY_DEC) | (1 << KEY_FUNC) | (1 << KEY_INC);
  171.    
  172.     DDRC &= ~(1 << KEY_RST);
  173.     PORTC |= (1 << KEY_RST);
  174.    
  175.     // 设置振动传感器引脚为输入,启用上拉电阻
  176.     DDRB &= ~(1 << VIB_SENSOR);
  177.     PORTB |= (1 << VIB_SENSOR);
  178.    
  179.     // 设置过零检测引脚为输入
  180.     DDRD &= ~(1 << ZERO_CROSS);
  181.     PORTD |= (1 << ZERO_CROSS); // 启用上拉电阻
  182.    
  183.     // 初始化加热控制引脚(关闭状态)
  184.     PORTB &= ~(1 << HEATER_CTL);
  185.    
  186.     // 初始化蜂鸣器引脚(关闭状态)
  187.     PORTC |= (1 << BUZZER);
  188.    
  189.     // 初始化显示 - 关闭所有数码管
  190.     PORTC &= ~((1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3));
  191. }

  192. // ADC初始化函数
  193. void init_adc(void) {
  194.     // 使用外部AREF作为参考电压,选择ADC7通道,右对齐模式
  195.     ADMUX = (0 << REFS1) | (0 << REFS0) | (0 << ADLAR) | (TEMP_ADC & 0x0F);
  196.    
  197.     // 使能ADC,预分频128(8MHz/128=62.5kHz)
  198.     ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
  199.    
  200.     // 第一次转换丢弃,确保ADC稳定
  201.     ADCSRA |= (1 << ADSC);
  202.     while(ADCSRA & (1 << ADSC));
  203. }

  204. // 定时器初始化函数
  205. void init_timers(void) {
  206.     // 定时器0 用于数码管扫描(CTC模式)
  207.     // 8MHz/64=125kHz,125kHz/100=1.25kHz(0.8ms中断)
  208.     TCCR0A = (1 << WGM01);               // CTC模式
  209.     TCCR0B = (1 << CS01) | (1 << CS00);  // 64分频
  210.     OCR0A = 100;                         // 比较匹配值 100计数 = 0.8ms
  211.     TIMSK0 = (1 << OCIE0A);              // 启用比较匹配中断
  212.    
  213.     // 定时器1 用于时间计数(1ms分辨率)
  214.     // 8MHz/64=125kHz,125kHz/125=1kHz(1ms中断)
  215.     TCCR1A = 0;
  216.     TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10); // CTC模式,64分频
  217.     OCR1A = 125;                         // 比较匹配值 125计数 = 1ms
  218.     TIMSK1 = (1 << OCIE1A);              // 启用比较匹配中断
  219. }

  220. // 外部中断初始化(过零检测)
  221. void init_ext_int(void) {
  222.     // 使用PCINT22(PD6)用于过零检测
  223.     PCICR |= (1 << PCIE2);               // 启用PCINT2组中断(PD0-PD7)
  224.     PCMSK2 |= (1 << PCINT22);            // 启用PD6的引脚变化中断
  225. }

  226. // 读取ADC值并转换为mV
  227. uint16_t read_adc(uint8_t channel) {
  228.     // 选择通道,保持参考电压设置不变
  229.     ADMUX = (0 << REFS1) | (0 << REFS0) | (0 << ADLAR) | (channel & 0x0F);
  230.    
  231.     // 开始转换
  232.     ADCSRA |= (1 << ADSC);
  233.    
  234.     // 等待转换完成
  235.     while (ADCSRA & (1 << ADSC));
  236.    
  237.     // 返回10位ADC值转换的mV值
  238.     // 右对齐时,ADC值 = ADCL(低8位) + (ADCH << 8)(高2位)
  239.     return ADC; // ADC寄存器自动拼接ADCL和ADCH(右对齐)
  240. }

  241. // 将mV值转换为温度值(线性插值)
  242. uint16_t mv_to_temp(uint16_t mv) {
  243.     // 已知两点: (ADC1_MV, TEMP1) 和 (ADC2_MV, TEMP2)
  244.     // 线性插值公式: temp = TEMP1 + (mv - ADC1_MV) * (TEMP2-TEMP1)/(ADC2_MV-ADC1_MV)
  245.     uint32_t delta_temp = TEMP2 - TEMP1;
  246.     uint32_t delta_mv = ADC2_MV - ADC1_MV;
  247.     uint16_t temp;

  248.     // 温度低于校准点范围时,线性外推
  249.     if (mv < ADC1_MV) {
  250.         temp = TEMP1 - (uint16_t)(((ADC1_MV - mv) * delta_temp) / delta_mv);
  251.     }
  252.     // 温度高于校准点范围时,线性外推
  253.     else {
  254.         temp = TEMP1 + (uint16_t)(((mv - ADC1_MV) * delta_temp) / delta_mv);
  255.     }

  256.     // 温度范围限制
  257.     if (temp < 0) return 0;     // 超过最低显示范围,显示000°C
  258.     if (temp > 999) return 999; // 超过最高显示范围,显示999°C
  259.    
  260.     return temp;
  261. }

  262. // 读取温度传感器值
  263. void read_temp(void) {
  264.     static uint32_t last_temp_read = 0;
  265.     uint32_t current_time = millis();
  266.    
  267.     // 定时读取温度(200ms一次)
  268.     if (current_time - last_temp_read >= TEMP_UPDATE_TIME) {
  269.         last_temp_read = current_time;
  270.         
  271.         // 读取温度传感器的mV值
  272.         uint16_t adc_value = read_adc(TEMP_ADC);
  273.                
  274.         // 计算电压(mV):参考电压2500mV,10位分辨率
  275.         uint32_t voltage = (adc_value * 2500UL) / 1024;
  276.         
  277.         // 检查热耦故障(超过2.2V)
  278.         if(voltage > 2200) {
  279.             error_state = 1;  // 1表示热耦故障
  280.             return;
  281.         }
  282.         
  283.         // 转换为温度值
  284.         actual_temp = mv_to_temp(voltage);
  285.         // 调试:如果需要显示电压值,可以取消下面的注释
  286.         // actual_temp = voltage / 10; // 显示电压的前三位数字

  287.         // 温度更新后立即触发PID计算(同步)
  288.         calculate_pid();  
  289.     }
  290. }

  291. // 检查加热故障
  292. void check_heating_fault(void) {
  293.     // 错误或关机状态不检测
  294.     if (error_state || shutdown_mode) return;
  295.    
  296.     uint32_t current_time = millis();
  297.    
  298.     // 计算当前温度与设定温度的差值
  299.     int16_t temp_difference = abs(set_temp - actual_temp);
  300.    
  301.     // 温差大于阈值时开始计时
  302.     if (temp_difference > HEATING_FAULT_DELTA) {
  303.         if (heating_fault_start_time == 0) {
  304.             // 第一次检测到温差大于80度,记录开始时间
  305.             heating_fault_start_time = current_time;
  306.         } else {
  307.             // 检查是否持续超过故障检测时间是否持续了40秒
  308.             if (current_time - heating_fault_start_time > HEATING_FAULT_TIME) {
  309.                 // 持续40秒温差仍然大于80度,触发加热故障报警
  310.                 error_state = 2;  // 2表示加热故障
  311.                 heating_fault_detected = 1;
  312.             }
  313.         }
  314.     } else {
  315.         // 温差正常(小于80度),重置计时器
  316.         heating_fault_start_time = 0;
  317.         heating_fault_detected = 0;
  318.     }
  319. }

  320. // PID控制计算
  321. void calculate_pid(void) {
  322.     // 错误或关机状态,停止加热
  323.     if (error_state || shutdown_mode) {
  324.         integral = 0;
  325.         last_error = 0;  //上一次误差值
  326.         required_heat_cycles = 0;
  327.         return;
  328.     }
  329.    
  330.     // 计算误差
  331.     float error = set_temp - actual_temp;
  332.    
  333.     // 实际温度高于设定温度,停止加热并重置积分
  334.     if (error < 0) {
  335.         required_heat_cycles = 0;
  336.         integral = 0;  // 重置积分项,防止积分饱和
  337.         last_error = error;
  338.         return;
  339.     }
  340.    
  341.     // 温差较大时全功率加热,启动时的强加热阶段
  342.     if (error > 100) {
  343.         required_heat_cycles = MAX_HEAT_CYCLES;
  344.         integral = 0;
  345.         last_error = error;
  346.         return;
  347.     }
  348.    
  349.     // 积分项计算(仅误差为正时累加,防止积分饱和)
  350.     if (error > 0) {
  351.         integral += error;
  352.         // 限制积分范围
  353.         if (integral > 500) integral = 500;
  354.         if (integral < -500) integral = -500;
  355.     } else {
  356.         integral = 0;  // 当误差为负时重置积分项
  357.     }
  358. /*   
  359.     // 微分项 - 使用实际温度的变化率而不是误差变化
  360.     // 这样可以避免在设定点变化时产生大的微分冲击
  361.     static float last_actual_temp = 0;
  362.     float derivative = (actual_temp - last_actual_temp);
  363.     last_actual_temp = actual_temp;
  364. */     
  365.     // 微分项计算(误差变化率)
  366.     float derivative = (error - last_error);
  367.     last_error = error;

  368.     derivative = -derivative;  // 反转符号,抑制超调
  369.    
  370.     // PID输出计算
  371.     float p_term = Kp * error;
  372.     float i_term = Ki * integral;
  373.     float d_term = Kd * derivative;
  374.     float output = p_term + i_term + d_term;
  375.    
  376.     // 限制输出范围
  377.     if (output < 0) output = 0;
  378.     if (output > 100) output = 100;
  379.    
  380.     required_heat_cycles = (uint8_t)output;

  381.     // 计算当前小周期(20半波)的加热数:按比例分配到20个半波中
  382.     // heat_sub_cycles = (required_heat_cycles * HEAT_SUB_CYCLE) / HEAT_FULL_CYCLE;
  383.     required_heat_cycles = (required_heat_cycles * HEAT_SUB_CYCLE) / HEAT_FULL_CYCLE;
  384. }

  385. // 加热控制(过零触发)
  386. void heater_control(void) {
  387.     // 错误或关机状态,停止加热
  388.     if (error_state || shutdown_mode) {
  389.         PORTB &= ~(1 << HEATER_CTL);
  390.         heating = 0;
  391.         return;
  392.     }
  393.    
  394.     // 休眠模式下维持休眠温度
  395.     if (sleep_mode && set_temp != SLEEP_TEMP) {
  396.         set_temp = SLEEP_TEMP;
  397.     }
  398.    
  399.     // 更新加热状,根据实际加热周期数确定
  400.     heating = (required_heat_cycles > 0);

  401.     // 过热保护:实际温度高于设定温度时强制停止加热
  402.     if (actual_temp > set_temp ) {
  403.         required_heat_cycles = MIN_HEAT_CYCLES;
  404.         heating = 0;
  405.         integral = 0;  // 重置积分项
  406.     }
  407.    
  408.     // 停止加热时重置故障检测计时器
  409.     if (!heating) {
  410.         heating_fault_start_time = 0; // 停止加热时重置故障检测计时器
  411.     }
  412. }

  413. // 处理按键长按
  414. void handle_long_press(void) {
  415.     if (!key_long_press_active) return;
  416.    
  417.     uint32_t current_time = millis();
  418.    
  419.     // 长按每150ms触发一次
  420.     if (current_time - key_long_press_time > 150) {
  421.         key_long_press_time = current_time;
  422.         
  423.         // 根据不同按键处理
  424.         switch(key_long_press_id) {
  425.             case 0:  // K1 - 快速减
  426.                 if (set_temp > MIN_TEMP) set_temp--;
  427.                 in_settings = 1;
  428.                 break;
  429.             case 2:  // K3 - 快速加
  430.                 if (set_temp < MAX_TEMP) set_temp++;
  431.                 in_settings = 1;
  432.                 break;
  433.             default:  // 其他按键取消长按
  434.                 key_long_press_active = 0;
  435.                 break;
  436.         }
  437.         
  438.         // 更新活动时间
  439.         last_activity_time = current_time;
  440.     }
  441. }

  442. // 检查按键状态
  443. void check_buttons(void) {
  444.     static uint8_t last_key_state[4] = {1, 1, 1, 1};  // 初始状态(未按下)
  445.     static uint32_t key_press_time[4] = {0, 0, 0, 0};  // 按键按下时间
  446.     uint8_t key_state[4];  // 当前按键状态
  447.    
  448.     handle_long_press();  // 处理长按
  449.    
  450.     // 读取按键状态(1表示按下)
  451.     key_state[0] = (PINB & (1 << KEY_DEC)) ? 0 : 1;   // K1 - 减
  452.     key_state[1] = (PINB & (1 << KEY_FUNC)) ? 0 : 1;  // K2 - 功能
  453.     key_state[2] = (PINB & (1 << KEY_INC)) ? 0 : 1;   // K3 - 加
  454.     key_state[3] = (PINC & (1 << KEY_RST)) ? 0 : 1;   // S1 - 复位
  455.    
  456.     // 处理每个按键
  457.     for (uint8_t i = 0; i < 4; i++) {
  458.         // 按键按下(上升沿)
  459.         if (key_state[i] && !last_key_state[i]) {
  460.             key_press_time[i] = millis();  // 记录按下时间
  461.             
  462.             // 唤醒休眠
  463.             if (sleep_mode) {
  464.                 sleep_mode = 0;
  465.                 set_temp = befor_sleep_temp;  // 恢复休眠前温度
  466.                 beep(100, 1000);  // 提示音
  467.             }
  468.             
  469.             // 更新活动时间
  470.             last_activity_time = millis();
  471.         }
  472.         
  473.         // 按键释放(下降沿)
  474.         if (!key_state[i] && last_key_state[i]) {
  475.             uint32_t press_duration = millis() - key_press_time[i];
  476.             
  477.             // 去抖处理
  478.             if (press_duration > DEBOUNCE_TIME) {
  479.                 // 短按处理
  480.                 if (press_duration < LONG_PRESS_TIME) {
  481.                     switch(i) {
  482.                         case 0:  // K1 - 减温度
  483.                             if (set_temp > MIN_TEMP) set_temp--;
  484.                             in_settings = 1;
  485.                             break;
  486.                         case 1:  // K2 - 功能切换
  487.                             quick_set_index = (quick_set_index + 1) % 3;
  488.                             set_temp = quick_set_temps[quick_set_index];
  489.                             in_settings = 1;
  490.                             break;
  491.                         case 2:  // K3 - 加温度
  492.                             if (set_temp < MAX_TEMP) set_temp++;
  493.                             in_settings = 1;
  494.                             break;
  495.                         case 3:  // S1 - 恢复默认
  496.                             set_temp = DEFAULT_TEMP;
  497.                             befor_sleep_temp = set_temp;
  498.                             break;
  499.                     }
  500.                     last_activity_time = millis();
  501.                 } else {  // 长按时,标记长按激活
  502.                     key_long_press_active = 1;
  503.                     key_long_press_id = i;
  504.                     key_long_press_time = millis();
  505.                 }
  506.             }
  507.             
  508.             // 释放当前长按的按键
  509.             // (只有当这个按键是当前长按的按键时才取消)
  510.             if (i == key_long_press_id) {
  511.                 key_long_press_active = 0;
  512.             }
  513.         }
  514.         
  515.         // 持续按下检测(用于长按触发)
  516.         if (key_state[i] && last_key_state[i]) {
  517.             uint32_t press_duration = millis() - key_press_time[i];

  518.             // 如果按键按下时间超过长按时间,并且还没有激活长按,则激活长按
  519.             if (press_duration > LONG_PRESS_TIME && !key_long_press_active) {
  520.                 key_long_press_active = 1;
  521.                 key_long_press_id = i;
  522.                 key_long_press_time = millis();
  523.             }
  524.         }
  525.         
  526.         last_key_state[i] = key_state[i];  // 更新按键状态
  527.     }
  528.    
  529.     // 设置模式1秒后自动退出
  530.     if (in_settings && (millis() - last_activity_time > 1000)) {
  531.         in_settings = 0;
  532.         befor_sleep_temp = set_temp;  // 保存设置
  533.     }
  534. }

  535. // 检查休眠和关机状态
  536. void check_sleep(void) {
  537.     uint32_t current_time = millis();
  538.    
  539.     // 检测振动传感器(唤醒功能)
  540.     static uint8_t last_vib_state = 1;
  541.     uint8_t vib_state = (PINB & (1 << VIB_SENSOR)) ? 0 : 1;
  542.    
  543.     if (vib_state && !last_vib_state) {  // 检测到振动
  544.         last_activity_time = current_time;
  545.         
  546.         // 唤醒休眠
  547.         if (sleep_mode) {
  548.             sleep_mode = 0;
  549.             set_temp = befor_sleep_temp;  // 恢复温度设置
  550.             beep(100, 1000);  // 提示音
  551.         }
  552.     }
  553.     last_vib_state = vib_state;
  554.    
  555.     // 检查进入休眠
  556.     if (!sleep_mode && !shutdown_mode && !error_state) {
  557.         if (current_time - last_activity_time > SLEEP_TIME) {
  558.             sleep_mode = 1;
  559.             sleep_start_time = current_time;
  560.             befor_sleep_temp = set_temp;  // 保存当前温度
  561.             set_temp = SLEEP_TEMP;  // 切换到休眠温度
  562.         }
  563.     }
  564.    
  565.     // 检查进入关机
  566.     if (sleep_mode && !shutdown_mode && !error_state) {
  567.         if (current_time - sleep_start_time > SHUTDOWN_TIME) {
  568.             shutdown_mode = 1;
  569.         }
  570.     }
  571. }

  572. // 错误状态处理
  573. void handle_errors(void) {
  574.     if (!error_state) return;
  575.    
  576.     uint32_t current_time = millis();
  577.    
  578.     // 定时蜂鸣提,10秒
  579.     if (current_time - error_beep_time > ERROR_BEEP_TIME) {
  580.         error_beep_time = current_time;
  581.         beep(600, 1500);  // 错误提示音
  582.     }
  583.    
  584.     // 错误状态下停止加热
  585.     required_heat_cycles = 0;
  586.     heating = 0;
  587.     PORTB &= ~(1 << HEATER_CTL);
  588. }

  589. // 蜂鸣器控制
  590. void beep(uint16_t duration, uint16_t frequency) {
  591.     if (frequency == 0) return;  // 避免除零错误
  592.    
  593.     // 计算周期和半周期
  594.     uint32_t period_us = 1000000UL / frequency;
  595.     uint32_t half_period_us = period_us / 2;
  596.    
  597.     uint32_t start_time = micros();
  598.     uint32_t end_time = start_time + (uint32_t)duration * 1000UL; // 总时长(us)
  599.     uint8_t buzzer_state = 0;  // 蜂鸣器状态:0为关,1为开
  600.     uint32_t last_toggle_time = start_time;
  601.    
  602.     // 生成指定频率的方波
  603.     while (micros() < end_time) {
  604.         uint32_t current_time = micros();

  605.         // 到达翻转时间时切换蜂鸣器状态
  606.         if (current_time - last_toggle_time >= half_period_us) {
  607.             if (buzzer_state) {
  608.                 PORTC |= (1 << BUZZER);  // 蜂鸣器关
  609.             } else {
  610.                 PORTC &= ~(1 << BUZZER);  // 蜂鸣器开
  611.             }
  612.             buzzer_state = !buzzer_state;
  613.             last_toggle_time = current_time;
  614.         }
  615.     }
  616.    
  617.     // 确保蜂鸣器最终关闭
  618.     PORTC |= (1 << BUZZER);
  619. }

  620. // 设置数码管显示
  621. void set_digit(uint8_t digit, uint8_t value, uint8_t dp) {
  622.     // 关闭所有位选
  623.     PORTC &= ~((1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3));
  624.    
  625.     // 获取段选码
  626.     uint8_t pattern = seg_pattern[value];
  627.    
  628.     // 设置段选信号(低电平有效)
  629.     if(!(pattern & 0x01)) PORTB &= ~(1 << SEG_A); else PORTB |= (1 << SEG_A);
  630.     if(!(pattern & 0x02)) PORTD &= ~(1 << SEG_B); else PORTD |= (1 << SEG_B);
  631.     if(!(pattern & 0x04)) PORTD &= ~(1 << SEG_C); else PORTD |= (1 << SEG_C);
  632.     if(!(pattern & 0x08)) PORTD &= ~(1 << SEG_D); else PORTD |= (1 << SEG_D);
  633.     if(!(pattern & 0x10)) PORTD &= ~(1 << SEG_E); else PORTD |= (1 << SEG_E);
  634.     if(!(pattern & 0x20)) PORTB &= ~(1 << SEG_F); else PORTB |= (1 << SEG_F);
  635.     if(!(pattern & 0x40)) PORTD &= ~(1 << SEG_G); else PORTD |= (1 << SEG_G);
  636.    
  637.     // 控制小数点
  638.     if (dp) {
  639.         PORTD &= ~(1 << SEG_DP);  // 点亮小数点
  640.     } else {
  641.         PORTD |= (1 << SEG_DP);   // 关闭小数点
  642.     }
  643.    
  644.     // 开启当前位选
  645.     switch(digit) {
  646.         case 0: PORTC |= (1 << DIGIT_1); break;
  647.         case 1: PORTC |= (1 << DIGIT_2); break;
  648.         case 2: PORTC |= (1 << DIGIT_3); break;
  649.     }
  650. }

  651. // 更新数码管显示
  652. void update_display(void) {
  653.     static uint32_t last_display_update = 0;
  654.     static uint32_t last_blink_update = 0;
  655.     static uint32_t last_temp_update = 0;
  656.     uint32_t current_time = millis();
  657.    
  658.     // 显示温度值更新频率(每秒2-3次)
  659.     if (current_time - last_temp_update >= 400) { // 400ms = 2.5次/秒
  660.         last_temp_update = current_time;
  661.         // 确定显示温度(设置模式显示设定值,否则显示实际值)
  662.         uint16_t display_temp = in_settings ? set_temp : actual_temp;
  663.         // 分解各位数字
  664.         display_value[0] = display_temp / 100;         // 百位
  665.         display_value[1] = (display_temp / 10) % 10;   // 十位
  666.         display_value[2] = display_temp % 10;          // 个位
  667.     }

  668.     // 显示更新频率控制(减少闪烁)
  669.     if (current_time - last_display_update < DISP_UPDATE_TIME) {
  670.         return;
  671.     }
  672.     last_display_update = current_time;
  673.    
  674.     // 错误状态显示
  675.     if (error_state) {
  676.         if (digit_pos == 0) {
  677.             // 显示"S"(传感器故障)或"H"(加热故障)
  678.             set_digit(0, (error_state == 1) ? 13 : 14, 0);
  679.         } else if (digit_pos == 1) {
  680.             set_digit(1, 11, 0);  // 显示"-"
  681.         } else {
  682.             set_digit(2, 10, 0);  // 显示"E"
  683.         }
  684.         digit_pos = (digit_pos + 1) % 3;
  685.         return;
  686.     }
  687.    
  688.     // 关机模式关闭显示
  689.     if (shutdown_mode) { // 关闭所有显示
  690.         PORTC &= ~((1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3));
  691.         return;
  692.     }
  693.    
  694.     // 休眠模式闪烁控制
  695.     if (sleep_mode) {
  696.         static uint32_t last_blink_update = 0;
  697.         if (current_time - last_blink_update > 500) { // 500ms闪烁周期
  698.             last_blink_update = current_time;
  699.             blink_state = !blink_state;
  700.         }
  701.         if (!blink_state) {  // 闪烁关闭阶段
  702.             PORTC &= ~((1 << DIGIT_1) | (1 << DIGIT_2) | (1 << DIGIT_3));
  703.             return;
  704.         }
  705.     }
  706.    
  707.     // 正常显示(个位小数点表示加热状态)
  708.     uint8_t dp = (digit_pos == 2 && heating) ? 1 : 0;
  709.     set_digit(digit_pos, display_value[digit_pos], dp); // 显示当前位
  710.     digit_pos = (digit_pos + 1) % 3;  // 切换到下一位
  711. }

  712. // 过零检测中断服务程序
  713. ISR(PCINT2_vect) {
  714.     // 检查PD6引脚状态变化(过零检测)
  715.     static uint8_t last_pd6_state = 0;
  716.     uint8_t current_pd6_state = PIND & (1 << ZERO_CROSS);
  717.    
  718.     // 仅处理状态变化的情况
  719.     if (current_pd6_state == last_pd6_state) return;
  720.     last_pd6_state = current_pd6_state;
  721.    
  722.     uint32_t current_time = micros();
  723.     // 过零检测去抖(避免高频干扰)
  724.     if (current_time - last_zero_cross_time < ZERO_CROSS_DEBOUNCE) return;
  725.     last_zero_cross_time = current_time;

  726.         // 如果要求加热周期数为0,直接返回
  727.         if (required_heat_cycles == 0) {
  728.             heat_accumulator = 0;  // 重置累加器
  729.             return;
  730.         }
  731.         
  732.         // 均匀分布算法控制加热
  733.         // 累加器算法:每次过零时累加所需加热功率
  734.         heat_accumulator += required_heat_cycles;

  735.         // 当累加器达到或超过20时,触发加热并减去20
  736.         if (heat_accumulator >= 20) {
  737.             heat_accumulator -= 20;
  738.             // 触发加热(输出200us脉冲)
  739.             PORTB |= (1 << HEATER_CTL);
  740.             _delay_us(200);  // 触发脉冲宽度
  741.             PORTB &= ~(1 << HEATER_CTL);
  742.         }

  743. /*    // 小周期计数逻辑(20个半波=200ms,与采样/PID周期对齐)
  744.     current_sub_cycle_count++;
  745.     if (current_sub_cycle_count >= HEAT_SUB_CYCLE) {
  746.         current_sub_cycle_count = 0;  // 小周期结束,重置计数
  747.     }

  748.     // 加热控制逻辑(基于小周期内的分配)
  749.     if (error_state || shutdown_mode || sleep_mode) {
  750.         // 异常状态下禁止加热
  751.         PORTB &= ~(1 << HEATER_CTL);
  752.         heat_accumulator = 0;
  753.         return;
  754.     }

  755.     // 当需要加热时,在小周期内的前N个半波触发加热
  756.     if (current_sub_cycle_count < heat_sub_cycles) {
  757.         PORTB |= (1 << HEATER_CTL);    // 触发加热
  758.         _delay_us(200);                // 保持触发信号(根据继电器特性调整)
  759.         PORTB &= ~(1 << HEATER_CTL);   // 关闭加热触发
  760.     } else {
  761.         PORTB &= ~(1 << HEATER_CTL);   // 本半波不加热
  762.     }
  763. */
  764. }

  765. // 定时器0中断服务程序 - 数码管扫描
  766. ISR(TIMER0_COMPA_vect) {
  767.     update_display();  // 定时刷新显示
  768. }

  769. // 定时器1中断服务程序 - 时间计数
  770. volatile uint32_t timer1_millis = 0;
  771. volatile uint32_t timer1_micros = 0;

  772. ISR(TIMER1_COMPA_vect) {
  773.     timer1_millis++;      // 毫秒计数
  774.     timer1_micros += 1000; // 微秒计数
  775. }

  776. // 获取当前时间(ms)
  777. uint32_t millis(void) {
  778.     uint32_t m;
  779.     cli();  // 关中断防止计数被修改
  780.     m = timer1_millis;
  781.     sei();  // 开中断
  782.     return m;
  783. }

  784. // 获取当前时间(us)
  785. uint32_t micros(void) {
  786.     uint32_t m;
  787.     cli();  // 关中断防止计数被修改
  788.     m = timer1_micros;
  789.     sei();  // 开中断
  790.     return m;
  791. }
复制代码
谢谢观赏!
双节同辉,家和国盛!
祝各位坛友及数码之家的全体工作人员双节快乐!

打赏

参与人数 5家元 +130 收起 理由
ljlun + 30 優秀文章
慕名而来 + 30 原創內容
兔包公 + 30 優秀文章
8139 + 30 優秀文章
zhongyidiy + 10 優秀文章

查看全部打赏

回复 支持 4 反对 0

使用道具 举报

发表于 2025-9-30 20:36:24 | 显示全部楼层
顶起来,第一楼我来坐

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-9-30 21:03:09 | 显示全部楼层
jf201006 发表于 2025-9-30 20:07
以下程序,带有调试的部分没有删除,请自行检查。
这是最小功能版,加减温度、故障检测及蜂鸣、休眠、软关 ...

支持下,软件硬件大牛

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-9-30 21:10:01 来自手机浏览器 | 显示全部楼层
软硬件大师!就温控理论原理、调试实战一应俱全!

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-9-30 21:20:20 | 显示全部楼层
本帖最后由 8139 于 2025-9-30 21:29 编辑

国庆节快乐!老哥要是揽到了DF5C推进剂压力贮罐焊接的活儿就明说吧,不要以魔改烙铁这么含蓄的方式表达

打赏

参与人数 1家元 +12 收起 理由
jf201006 + 12 精彩回帖

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-9-30 22:07:48 | 显示全部楼层
非常不错,软硬件结合。

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-9-30 22:29:30 来自手机浏览器 | 显示全部楼层
8139 发表于 2025-9-30 21:20
国庆节快乐!老哥要是揽到了DF5C推进剂压力贮罐焊接的活儿就明说吧,不要以魔改烙铁这么含蓄的方式表达 ...


这活儿得八级钳工才能干,我还是老老实实修理地球吧
回复 支持 反对

使用道具 举报

发表于 2025-9-30 22:45:39 | 显示全部楼层
楼主厉害,软硬件都是高手。

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-9-30 23:27:18 | 显示全部楼层
恒温电络铁?

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 歡迎探討 恒温电烙铁

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 08:01:09 | 显示全部楼层
建议楼主增加菜单功能可以更改PID参数,最好了

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 歡迎探討

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 08:39:36 来自手机浏览器 | 显示全部楼层
本帖最后由 yinjiudong 于 2025-10-1 08:44 编辑

文科生不懂这些,但看出来标题有点问题(说太明确发不出去啊)

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 歡迎探討

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 09:12:17 | 显示全部楼层
技术贴,还是先顶一下贴再慢慢看

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 09:37:17 | 显示全部楼层
技术强帖,先支持再学习

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 11:10:27 | 显示全部楼层
谢谢分享~进来学习一下,真的太强了

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 11:12:52 | 显示全部楼层
国庆节快乐!谢谢楼主分享!

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享 同乐

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 21:31:38 | 显示全部楼层
国庆快乐            

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享 同乐哈

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-1 23:28:04 | 显示全部楼层
虽然不懂,但觉得高大上,必须加分

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

发表于 2025-10-3 08:43:17 | 显示全部楼层
还有仿真编程啊,厉害

打赏

参与人数 1家元 +6 收起 理由
jf201006 + 6 謝謝分享

查看全部打赏

回复 支持 反对

使用道具 举报

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

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-11-17 06:27 , Processed in 0.998401 second(s), 32 queries , Gzip On, Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

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