aming_ou 发表于 2025-2-9 16:51:16

用8G1K08实现定时抽水(时长可配置且断电不丢失)

最开始接触8G1K08是因为某九阳豆浆机通电无法进入工作模式,所以用STC8G1K08编写了最简单的定时程序,实现通电等待5秒,然后模拟短按1秒的动作,让豆浆机定时通电(智能插座)后自动煮豆浆。


后来又对桶装水电动抽水器的开关打起了小注意,能不能按一次启动,然后定时若干秒后自动停止抽水呢? 这样就不用看着,防止烧水壶满出。如果固定定时时间,就要根据不同烧水壶的容积计算加水时间,再把定时之间写到代码中,显然这样不够灵活,在deepseek和kimi的加持下,经过多天反复编译测试,已初步实现,各位大佬帮忙看看,还有哪里可以再优化优化,精简或者补充的。

/*----------------------------------------------------------------------*/
/* --- STC8G1K08A 按键动作,按键中断唤醒,定时器练习--------------------*/
/* --- 1.PIN脚低电平触发                                                */
/* --- 2.短按启动或关闭 LED_PIN,默认定时5秒                            */
/* --- 3.长按3~9秒,进入设置模式,再短按时,记录长按~短按之间的时长   */
/* ---   下次短按时,已最新设置的时长进行定时,定时结束前短按,退出定时 */
/* ---   断电记忆上次的设置时长                                       */
/* --- 4.长按10秒以上,释放按键时擦除ERPROM第1扇区                      */
/*----------------------------------------------------------------------*/
#include "STC8G.H"

#define MAIN_Fosc       24000000L   // 定义主时钟
#define PX0H            0x01// 定义PX0H为IPH寄存器的第0位
#define MCU_IDLE()          PCON |= 1   /* MCU 进入 IDLE 模式 */
#define MCU_POWER_DOWN()    PCON |= 2   /* MCU 进入 睡眠 模式 */

#define CTR_PIN P55// 控制/动作指示灯
#define LED_PIN P54// LED连接P54 低电平触发,作为对外控制输出脚
#define KEY_PIN P32// 按键连接P32

#define SHORT_PRESS_TIME 10// 短按时间阈值 毫秒
#define LONG_PRESS_TIME 3000 // 长按时间阈值 毫秒
#define IDLE_TIME 30000      // 空闲时间阈值 毫秒
#define KEEP_TIME 5000L      // 默认LED保持时长毫秒
#define RESET_TIME 10000   // 重置时间阈值 毫秒,长按以上,清除EEPROM

#define EEPROM_ADDR 0x0000 // EEPROM存储地址(根据实际需求设置)
#define IAP_STANDBY()   IAP_CMD = 0   //IAP空闲命令(禁止)
#define IAP_READ()      IAP_CMD = 1   //IAP读出命令
#define IAP_WRITE()   IAP_CMD = 2   //IAP写入命令
#define IAP_ERASE()   IAP_CMD = 3   //IAP擦除命令
#define IAP_ENABLE()    IAP_CONTR = 0x80; IAP_TPS = MAIN_Fosc / 1000000 //激活IAP操作
#define IAP_DISABLE()   IAP_CONTR = 0; IAP_CMD = 0; IAP_TRIG = 0; IAP_ADDRH = 0xff; IAP_ADDRL = 0xff // 禁止IAP操作

extern void          _nop_   (void);

// 定义全局变量
unsigned long msTicks = 0;         // 定时器计数 毫秒
unsigned long led_keep_time = KEEP_TIME; // 默认LED保持时长5秒
unsigned long led_on_time = 0;       // LED已点亮时间 毫秒
unsigned long idle_time = 0;         // 空闲时间计数器 毫秒

bit led_state = 0;               // LED状态(0:熄灭,1:点亮)
bit setting_mode = 0;            // 设置模式标志
bit key_pressed = 0;               // 按键按下标志
bit key_released = 1;            // 按键释放标志
bit key_long_press = 0;            // 长按标志
bit wakeup_status = 1;             // 运行标志(0:休眠,1:活跃)
bit erase_mode = 0;                // 设置EEPROM擦除标志

void Timer0_Init(void);            // 定时器0初始化
void Int0_Init(void);            // 外部中断0初始化
void PowerDownMode(void);          // 掉电模式
void WakeUpFromPowerDown(void);    // 从掉电模式唤醒

// 初始化I/O端口
void InitPorts()
{
    // 初始化为准双向
    P5M0 = 0x00; P5M1 = 0xcf;
    P3M0 = 0x00; P3M1 = 0xf8;
}

// 初始化全局变量
void InitGlobals()
{
    CTR_PIN = 1;
    LED_PIN = 1;
    KEY_PIN = 1;
    msTicks = 0;
    wakeup_status = 1;
    idle_time = msTicks;
}

// 毫秒级延时函数
void Delay_Ms(unsigned long ms) {
    unsigned long delay_start = msTicks + ms;
    while(delay_start > msTicks);
}

void Flash_LED(unsigned char i,unsigned int j) {
    bitCTR_PIN_status = CTR_PIN; // 保存控制指示灯的状态
    do{
      CTR_PIN = !CTR_PIN;
      Delay_Ms(j);
    }while(--i);
    CTR_PIN = CTR_PIN_status;   // 恢复控制指示灯的状态
    idle_time = msTicks;
}

// 触发EEPROM操作
void EEPROM_Trig(void)
{
    F0 = EA;    //保存全局中断
    EA = 0;   //禁止中断, 避免触发命令无效
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;//先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
                      //送完A5H后,IAP命令立即被触发启动
                      //CPU等待IAP完成后,才会继续执行程序。
    _nop_();
    _nop_();
    EA = F0;    //恢复全局中断
    _nop_();
}

// 写入EEPROM
void EEPROM_Write(unsigned int addr, unsigned int dat) {
    IAP_ENABLE();
    IAP_WRITE();       // 设置IAP写命令
    IAP_ADDRL = addr;// 设置低地址
    IAP_ADDRH = addr >> 8;// 设置高地址
    IAP_DATA = dat;    // 写入数据
    EEPROM_Trig();   // 触发EEPROM操作
    IAP_DISABLE();   // 关闭IAP功能
}

// 从EEPROM读取数据
unsigned char EEPROM_Read(unsigned int addr) {
    unsigned char dat;
    IAP_ENABLE();
    IAP_READ();      // 设置IAP读命令
    IAP_ADDRL = addr;// 设置低地址
    IAP_ADDRH = addr >> 8;// 设置高地址
    EEPROM_Trig();   // 触发EEPROM操作
    dat = IAP_DATA;    // 读取数据
    IAP_DISABLE();   // 关闭IAP功能
    return dat;
}

// 把指定地址的EEPROM扇区擦除
void EEPROM_SectorErase(unsigned int addr)
{
    IAP_ENABLE();                     //设置等待时间,允许IAP操作,送一次就够
    IAP_ERASE();                        //宏调用, 送扇区擦除命令,命令不需改变时,不需重新送命令
                                          //只有扇区擦除,没有字节擦除,512字节/扇区。
                                          //扇区中任意一个字节地址都是扇区地址。
    IAP_ADDRH = addr / 256;             //送扇区地址高字节(地址需要改变时才需重新送地址)
    IAP_ADDRL = addr % 256;             //送扇区地址低字节
    EEPROM_Trig();                      //触发EEPROM操作
    IAP_DISABLE();                      //禁止EEPROM操作
}

// 写入32位long类型数据到EEPROM
void Write_Led_Keep_Time(unsigned long dat) {
    unsigned char byte0, byte1, byte2, byte3;

    // 将32位long类型数据分解为4个字节
    byte0 = (dat >> 24) & 0xFF;// 最高字节
    byte1 = (dat >> 16) & 0xFF;// 高次字节                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    byte2 = (dat >> 8) & 0xFF;   // 低次字节
    byte3 = dat & 0xFF;          // 最低字节

    // 写入EEPROM
    EEPROM_Write(EEPROM_ADDR, byte0);    // 写入最高字节
    EEPROM_Write(EEPROM_ADDR + 1, byte1); // 写入高次字节
    EEPROM_Write(EEPROM_ADDR + 2, byte2); // 写入低次字节
    EEPROM_Write(EEPROM_ADDR + 3, byte3); // 写入最低字节
}

// 从EEPROM读取32位long类型数据
int Read_Led_Keep_Time() {
    unsigned char byte0, byte1, byte2, byte3;

    // 从EEPROM读取4个字节
    byte0 = EEPROM_Read(EEPROM_ADDR);    // 读取最高字节
    byte1 = EEPROM_Read(EEPROM_ADDR + 1); // 读取高次字节
    byte2 = EEPROM_Read(EEPROM_ADDR + 2); // 读取低次字节
    byte3 = EEPROM_Read(EEPROM_ADDR + 3); // 读取最低字节

    // 将4个字节组合成一个32位long类型数据
    return ((long)byte0 << 24) | ((long)byte1 << 16) | ((long)byte2 << 8) | byte3;
}

// 主流程
void main() {
    InitPorts();    // 初始化I/O端口
    Timer0_Init();// 初始化定时器0
    Int0_Init();    // 初始化外部中断0
    InitGlobals();// 初始化全局变量
    Flash_LED(2,1000); // 开机完成提示
   
    // 从EEPROM读取保持时长数据并赋值给led_keep_time,否则保存默认值到EEPROM
    led_keep_time = Read_Led_Keep_Time();
    if (led_keep_time == 0xFFFFFFFF || led_keep_time < 2000L || led_keep_time > 300000L) { // 如果EEPROM未初始化或值不合规
      led_keep_time = KEEP_TIME; // 使用默认值
      Write_Led_Keep_Time(led_keep_time);// 将定时默认值写入EEPROM
      Flash_LED(6,1000);   //慢闪,保存成功      
    }

    while (1) {
      if (key_released && key_pressed) { // 检测按键按下
            key_pressed = 0; // 清除按键标志
            if (erase_mode) { //重置模式
                erase_mode = 0; // 清除重置模式标志
                EEPROM_SectorErase(EEPROM_ADDR);
                Flash_LED(10,150);   //执行完成后闪烁提示         
                led_keep_time = KEEP_TIME;
            }
            else if (key_long_press) { // 长按按键
                key_long_press = 0;
                if (!setting_mode) { // 进入设置模式
                  setting_mode = 1;
                  led_state = 1; // 点亮LED
                  LED_PIN = 0;
                  led_on_time = msTicks; // 重置点亮时间
                }
            }
            else { // 短按按键
                if (setting_mode) { // 在设置模式中
                  setting_mode = 0; // 退出设置模式
                  led_state = 0; // 熄灭LED
                  LED_PIN = 1;
                  led_keep_time = msTicks - led_on_time;      // 保存点亮时长
                  Write_Led_Keep_Time(led_keep_time);         // 将new_keep_time写入EEPROM
                  Flash_LED(6,1000);   //慢闪,保存成功
                } else { // 不在设置模式
                  if (led_state) { // 如果LED已点亮
                        led_state = 0; // 熄灭LED
                        LED_PIN = 1;
                  } else { // 如果LED未点亮
                        led_state = 1; // 点亮LED
                        LED_PIN = 0;
                        led_on_time = msTicks; // 重置点亮时间
                  }
                }
            }
            idle_time = msTicks; // 重置空闲时间
      }
      
      if (!setting_mode){ // 不在设置模式
            if (led_state) { // LED点亮
                if (msTicks - led_on_time >= led_keep_time) { // 达到保持时长
                  led_state = 0; // 熄灭LED            
                  LED_PIN = 1;
                  Flash_LED(6,200);   //执行完成后闪烁提示
                }
            }else{ // LED熄灭
                if (msTicks - idle_time >= IDLE_TIME) { // 空闲时间超过30秒
                  Flash_LED(10,100);   //执行完成后闪烁提示            
                  PowerDownMode(); // 进入掉电模式
                }
            }
      }else if (led_state && (msTicks - led_on_time > 120000)){
            setting_mode = 0; // 超时退出设置模式
            led_state = 0;    // 熄灭LED
            LED_PIN = 1;
            Flash_LED(6,500);   //半慢闪2次,超时
      }
    }
}

// 定时器0初始化
void Timer0_Init(void)      //1毫秒@24.000MHz
{
    AUXR |= 0x80;         //定时器时钟1T模式
    TMOD &= 0xF0;         //设置定时器模式 16位自动重载
    TL0 = 0x40;             //设置定时初始值
    TH0 = 0xA2;             //设置定时初始值
    TF0 = 0;                //清除TF0标志
    TR0 = 1;                //定时器0开始计时
    ET0 = 1;                //使能定时器0中断
}

// 定时器0中断服务程序
void Timer0_Isr(void) interrupt 1 {
    static unsigned long key_press_time = 0;   // 按键按下时间 毫秒
    msTicks++;
   
    if (!KEY_PIN) { // 按键按下
      key_press_time++;
      CTR_PIN = 0;
      key_released = 0;// 设置按键状态为按下
    } else { // 按键松开
      if (key_press_time >= RESET_TIME) { // 长按 10秒及以上
            erase_mode = 1;
      }
      else if (key_press_time >= LONG_PRESS_TIME && key_press_time < RESET_TIME) { // 长按 3秒 ~ 10秒
            key_long_press = 1;
            key_pressed = 1;
      }
      else if (key_press_time >= SHORT_PRESS_TIME && key_press_time < LONG_PRESS_TIME) { // 短按少于3秒
            key_pressed = 1;
      }
      else {
            key_long_press = 0;
            key_pressed = 0;
      }
      if (!key_released) {// 按键曾按下
            CTR_PIN = 1;
      }
      key_released = 1;   // 重置按键状态为释放
      key_press_time = 0; // 重置按键计时
    }
}

// 外部中断0初始化
void Int0_Init(void) {
    IT0 = 1;         // 使能INT0下降沿中断
    EX0 = 1;         // 使能INT0中断0
    EA = 1;          // 使能中断总开关
}


// 掉电模式
void PowerDownMode(void) {
    LED_PIN = 1; // 确保输出高电平
    CTR_PIN = 1;
    KEY_PIN = 1;
    wakeup_status = 0;
    _nop_();
    _nop_();
    MCU_POWER_DOWN();// MCU进入掉电模式(STC8G系列)
    _nop_();
    _nop_();
    _nop_();
    _nop_();
}

// 从掉电模式唤醒
void WakeUpFromPowerDown(void) {
    PCON &= 0xFD; // 清除掉电标志
    msTicks = 0;
    idle_time = msTicks; // 重置空闲时间
}

// 外部中断0服务程序(按键唤醒)
void INT0_ISR(void) interrupt 0 {
    if (!wakeup_status){       // 如果在休眠状态,就执行唤醒操作
      wakeup_status = 1;
      WakeUpFromPowerDown(); // 从掉电模式唤醒
    }
}


aming_ou 发表于 2025-2-9 16:54:09

最后一次按键操作后,且无任务正在执行,待机超过30秒,自动进入掉电模式,达到最大化省电,按键按下时自动唤醒

aming_ou 发表于 2025-2-10 14:15:04

继续更正,EEPROM中已有数据后,再次写入数据,需要先擦除,否则数据写入结果会异常,所以,要在Write_Led_Keep_Time(led_keep_time) 前面增加一行代码 EEPROM_SectorErase(EEPROM_ADDR);
页: [1]
查看完整版本: 用8G1K08实现定时抽水(时长可配置且断电不丢失)