找回密码
 立即注册
查看: 429|回复: 2

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

[复制链接]
  • 打卡等级:常住居民II
  • 打卡总天数:92
  • 最近打卡:2025-06-16 09:31:08
已绑定手机

4

主题

16

回帖

405

积分

中级会员

积分
405
发表于 2025-2-9 16:51:16 | 显示全部楼层 |阅读模式
最开始接触8G1K08是因为某九阳豆浆机通电无法进入工作模式,所以用STC8G1K08编写了最简单的定时程序,实现通电等待5秒,然后模拟短按1秒的动作,让豆浆机定时通电(智能插座)后自动煮豆浆。


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

  1. /*----------------------------------------------------------------------*/
  2. /* --- STC8G1K08A 按键动作,按键中断唤醒,定时器练习--------------------*/
  3. /* --- 1.PIN脚低电平触发                                                */
  4. /* --- 2.短按启动或关闭 LED_PIN,默认定时5秒                            */
  5. /* --- 3.长按3~9秒,进入设置模式,再短按时,记录长按~短按之间的时长   */
  6. /* ---   下次短按时,已最新设置的时长进行定时,定时结束前短按,退出定时 */
  7. /* ---   断电记忆上次的设置时长                                         */
  8. /* --- 4.长按10秒以上,释放按键时擦除ERPROM第1扇区                      */
  9. /*----------------------------------------------------------------------*/
  10. #include "STC8G.H"
  11. #define MAIN_Fosc       24000000L   // 定义主时钟
  12. #define PX0H            0x01  // 定义PX0H为IPH寄存器的第0位
  13. #define MCU_IDLE()          PCON |= 1   /* MCU 进入 IDLE 模式 */
  14. #define MCU_POWER_DOWN()    PCON |= 2   /* MCU 进入 睡眠 模式 */
  15. #define CTR_PIN P55  // 控制/动作指示灯
  16. #define LED_PIN P54  // LED连接P54 低电平触发,作为对外控制输出脚
  17. #define KEY_PIN P32  // 按键连接P32
  18. #define SHORT_PRESS_TIME 10  // 短按时间阈值 毫秒
  19. #define LONG_PRESS_TIME 3000 // 长按时间阈值 毫秒
  20. #define IDLE_TIME 30000      // 空闲时间阈值 毫秒
  21. #define KEEP_TIME 5000L      // 默认LED保持时长  毫秒
  22. #define RESET_TIME 10000     // 重置时间阈值 毫秒,长按以上,清除EEPROM
  23. #define EEPROM_ADDR 0x0000 // EEPROM存储地址(根据实际需求设置)
  24. #define IAP_STANDBY()   IAP_CMD = 0     //IAP空闲命令(禁止)
  25. #define IAP_READ()      IAP_CMD = 1     //IAP读出命令
  26. #define IAP_WRITE()     IAP_CMD = 2     //IAP写入命令
  27. #define IAP_ERASE()     IAP_CMD = 3     //IAP擦除命令
  28. #define IAP_ENABLE()    IAP_CONTR = 0x80; IAP_TPS = MAIN_Fosc / 1000000 //激活IAP操作
  29. #define IAP_DISABLE()   IAP_CONTR = 0; IAP_CMD = 0; IAP_TRIG = 0; IAP_ADDRH = 0xff; IAP_ADDRL = 0xff // 禁止IAP操作
  30. extern void          _nop_     (void);
  31. // 定义全局变量
  32. unsigned long msTicks = 0;           // 定时器计数 毫秒
  33. unsigned long led_keep_time = KEEP_TIME; // 默认LED保持时长5秒
  34. unsigned long led_on_time = 0;       // LED已点亮时间 毫秒
  35. unsigned long idle_time = 0;         // 空闲时间计数器 毫秒
  36. bit led_state = 0;                 // LED状态(0:熄灭,1:点亮)
  37. bit setting_mode = 0;              // 设置模式标志
  38. bit key_pressed = 0;               // 按键按下标志
  39. bit key_released = 1;              // 按键释放标志
  40. bit key_long_press = 0;            // 长按标志
  41. bit wakeup_status = 1;             // 运行标志(0:休眠,1:活跃)
  42. bit erase_mode = 0;                // 设置EEPROM擦除标志
  43. void Timer0_Init(void);            // 定时器0初始化
  44. void Int0_Init(void);              // 外部中断0初始化
  45. void PowerDownMode(void);          // 掉电模式
  46. void WakeUpFromPowerDown(void);    // 从掉电模式唤醒
  47. // 初始化I/O端口
  48. void InitPorts()
  49. {
  50.     // 初始化为准双向
  51.     P5M0 = 0x00; P5M1 = 0xcf;
  52.     P3M0 = 0x00; P3M1 = 0xf8;
  53. }
  54. // 初始化全局变量
  55. void InitGlobals()
  56. {
  57.     CTR_PIN = 1;
  58.     LED_PIN = 1;
  59.     KEY_PIN = 1;
  60.     msTicks = 0;
  61.     wakeup_status = 1;
  62.     idle_time = msTicks;
  63. }
  64. // 毫秒级延时函数
  65. void Delay_Ms(unsigned long ms) {
  66.     unsigned long delay_start = msTicks + ms;
  67.     while(delay_start > msTicks);
  68. }
  69. void Flash_LED(unsigned char i,unsigned int j) {
  70.     bit  CTR_PIN_status = CTR_PIN; // 保存控制指示灯的状态
  71.     do{
  72.         CTR_PIN = !CTR_PIN;
  73.         Delay_Ms(j);
  74.     }while(--i);
  75.     CTR_PIN = CTR_PIN_status;     // 恢复控制指示灯的状态
  76.     idle_time = msTicks;
  77. }
  78. // 触发EEPROM操作
  79. void EEPROM_Trig(void)
  80. {
  81.     F0 = EA;    //保存全局中断
  82.     EA = 0;     //禁止中断, 避免触发命令无效
  83.     IAP_TRIG = 0x5A;
  84.     IAP_TRIG = 0xA5;  //先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
  85.                       //送完A5H后,IAP命令立即被触发启动
  86.                       //CPU等待IAP完成后,才会继续执行程序。
  87.     _nop_();
  88.     _nop_();
  89.     EA = F0;    //恢复全局中断
  90.     _nop_();
  91. }
  92. // 写入EEPROM
  93. void EEPROM_Write(unsigned int addr, unsigned int dat) {
  94.     IAP_ENABLE();
  95.     IAP_WRITE();       // 设置IAP写命令
  96.     IAP_ADDRL = addr;  // 设置低地址
  97.     IAP_ADDRH = addr >> 8;  // 设置高地址
  98.     IAP_DATA = dat;    // 写入数据
  99.     EEPROM_Trig();     // 触发EEPROM操作
  100.     IAP_DISABLE();     // 关闭IAP功能
  101. }
  102. // 从EEPROM读取数据
  103. unsigned char EEPROM_Read(unsigned int addr) {
  104.     unsigned char dat;
  105.     IAP_ENABLE();
  106.     IAP_READ();        // 设置IAP读命令
  107.     IAP_ADDRL = addr;  // 设置低地址
  108.     IAP_ADDRH = addr >> 8;  // 设置高地址
  109.     EEPROM_Trig();     // 触发EEPROM操作
  110.     dat = IAP_DATA;    // 读取数据
  111.     IAP_DISABLE();     // 关闭IAP功能
  112.     return dat;
  113. }
  114. // 把指定地址的EEPROM扇区擦除
  115. void EEPROM_SectorErase(unsigned int addr)
  116. {
  117.     IAP_ENABLE();                       //设置等待时间,允许IAP操作,送一次就够
  118.     IAP_ERASE();                        //宏调用, 送扇区擦除命令,命令不需改变时,不需重新送命令
  119.                                           //只有扇区擦除,没有字节擦除,512字节/扇区。
  120.                                           //扇区中任意一个字节地址都是扇区地址。
  121.     IAP_ADDRH = addr / 256;             //送扇区地址高字节(地址需要改变时才需重新送地址)
  122.     IAP_ADDRL = addr % 256;             //送扇区地址低字节
  123.     EEPROM_Trig();                      //触发EEPROM操作
  124.     IAP_DISABLE();                      //禁止EEPROM操作
  125. }
  126. // 写入32位long类型数据到EEPROM
  127. void Write_Led_Keep_Time(unsigned long dat) {
  128.     unsigned char byte0, byte1, byte2, byte3;
  129.     // 将32位long类型数据分解为4个字节
  130.     byte0 = (dat >> 24) & 0xFF;  // 最高字节
  131.     byte1 = (dat >> 16) & 0xFF;  // 高次字节                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  132.     byte2 = (dat >> 8) & 0xFF;   // 低次字节
  133.     byte3 = dat & 0xFF;          // 最低字节
  134.     // 写入EEPROM
  135.     EEPROM_Write(EEPROM_ADDR, byte0);    // 写入最高字节
  136.     EEPROM_Write(EEPROM_ADDR + 1, byte1); // 写入高次字节
  137.     EEPROM_Write(EEPROM_ADDR + 2, byte2); // 写入低次字节
  138.     EEPROM_Write(EEPROM_ADDR + 3, byte3); // 写入最低字节
  139. }
  140. // 从EEPROM读取32位long类型数据
  141. int Read_Led_Keep_Time() {
  142.     unsigned char byte0, byte1, byte2, byte3;
  143.     // 从EEPROM读取4个字节
  144.     byte0 = EEPROM_Read(EEPROM_ADDR);    // 读取最高字节
  145.     byte1 = EEPROM_Read(EEPROM_ADDR + 1); // 读取高次字节
  146.     byte2 = EEPROM_Read(EEPROM_ADDR + 2); // 读取低次字节
  147.     byte3 = EEPROM_Read(EEPROM_ADDR + 3); // 读取最低字节
  148.     // 将4个字节组合成一个32位long类型数据
  149.     return ((long)byte0 << 24) | ((long)byte1 << 16) | ((long)byte2 << 8) | byte3;
  150. }
  151. // 主流程
  152. void main() {
  153.     InitPorts();    // 初始化I/O端口
  154.     Timer0_Init();  // 初始化定时器0
  155.     Int0_Init();    // 初始化外部中断0
  156.     InitGlobals();  // 初始化全局变量
  157.     Flash_LED(2,1000); // 开机完成提示
  158.    
  159.     // 从EEPROM读取保持时长数据并赋值给led_keep_time,否则保存默认值到EEPROM
  160.     led_keep_time = Read_Led_Keep_Time();
  161.     if (led_keep_time == 0xFFFFFFFF || led_keep_time < 2000L || led_keep_time > 300000L) { // 如果EEPROM未初始化或值不合规
  162.         led_keep_time = KEEP_TIME; // 使用默认值
  163.         Write_Led_Keep_Time(led_keep_time);  // 将定时默认值写入EEPROM
  164.         Flash_LED(6,1000);   //慢闪,保存成功      
  165.     }
  166.     while (1) {
  167.         if (key_released && key_pressed) { // 检测按键按下
  168.             key_pressed = 0; // 清除按键标志
  169.             if (erase_mode) { //重置模式
  170.                 erase_mode = 0; // 清除重置模式标志
  171.                 EEPROM_SectorErase(EEPROM_ADDR);
  172.                 Flash_LED(10,150);   //执行完成后闪烁提示         
  173.                 led_keep_time = KEEP_TIME;
  174.             }
  175.             else if (key_long_press) { // 长按按键
  176.                 key_long_press = 0;
  177.                 if (!setting_mode) { // 进入设置模式
  178.                     setting_mode = 1;
  179.                     led_state = 1; // 点亮LED
  180.                     LED_PIN = 0;
  181.                     led_on_time = msTicks; // 重置点亮时间
  182.                 }
  183.             }
  184.             else { // 短按按键
  185.                 if (setting_mode) { // 在设置模式中
  186.                     setting_mode = 0; // 退出设置模式
  187.                     led_state = 0; // 熄灭LED
  188.                     LED_PIN = 1;
  189.                     led_keep_time = msTicks - led_on_time;        // 保存点亮时长
  190.                     Write_Led_Keep_Time(led_keep_time);           // 将new_keep_time写入EEPROM
  191.                     Flash_LED(6,1000);   //慢闪,保存成功
  192.                 } else { // 不在设置模式
  193.                     if (led_state) { // 如果LED已点亮
  194.                         led_state = 0; // 熄灭LED
  195.                         LED_PIN = 1;
  196.                     } else { // 如果LED未点亮
  197.                         led_state = 1; // 点亮LED
  198.                         LED_PIN = 0;
  199.                         led_on_time = msTicks; // 重置点亮时间
  200.                     }
  201.                 }
  202.             }
  203.             idle_time = msTicks; // 重置空闲时间
  204.         }
  205.         
  206.         if (!setting_mode){ // 不在设置模式
  207.             if (led_state) { // LED点亮
  208.                 if (msTicks - led_on_time >= led_keep_time) { // 达到保持时长
  209.                     led_state = 0; // 熄灭LED            
  210.                     LED_PIN = 1;
  211.                     Flash_LED(6,200);   //执行完成后闪烁提示
  212.                 }
  213.             }else{ // LED熄灭
  214.                 if (msTicks - idle_time >= IDLE_TIME) { // 空闲时间超过30秒
  215.                     Flash_LED(10,100);   //执行完成后闪烁提示            
  216.                     PowerDownMode(); // 进入掉电模式
  217.                 }
  218.             }
  219.         }else if (led_state && (msTicks - led_on_time > 120000)){
  220.             setting_mode = 0; // 超时退出设置模式
  221.             led_state = 0;    // 熄灭LED
  222.             LED_PIN = 1;
  223.             Flash_LED(6,500);   //半慢闪2次,超时
  224.         }
  225.     }
  226. }
  227. // 定时器0初始化
  228. void Timer0_Init(void)      //1毫秒@24.000MHz
  229. {
  230.     AUXR |= 0x80;           //定时器时钟1T模式
  231.     TMOD &= 0xF0;           //设置定时器模式 16位自动重载
  232.     TL0 = 0x40;             //设置定时初始值
  233.     TH0 = 0xA2;             //设置定时初始值
  234.     TF0 = 0;                //清除TF0标志
  235.     TR0 = 1;                //定时器0开始计时
  236.     ET0 = 1;                //使能定时器0中断
  237. }
  238. // 定时器0中断服务程序
  239. void Timer0_Isr(void) interrupt 1 {
  240.     static unsigned long key_press_time = 0;   // 按键按下时间 毫秒
  241.     msTicks++;
  242.    
  243.     if (!KEY_PIN) { // 按键按下
  244.         key_press_time++;
  245.         CTR_PIN = 0;
  246.         key_released = 0;  // 设置按键状态为按下
  247.     } else { // 按键松开
  248.         if (key_press_time >= RESET_TIME) { // 长按 10秒及以上
  249.             erase_mode = 1;
  250.         }
  251.         else if (key_press_time >= LONG_PRESS_TIME && key_press_time < RESET_TIME) { // 长按 3秒 ~ 10秒
  252.             key_long_press = 1;
  253.             key_pressed = 1;
  254.         }
  255.         else if (key_press_time >= SHORT_PRESS_TIME && key_press_time < LONG_PRESS_TIME) { // 短按少于3秒
  256.             key_pressed = 1;
  257.         }
  258.         else {
  259.             key_long_press = 0;
  260.             key_pressed = 0;
  261.         }
  262.         if (!key_released) {  // 按键曾按下
  263.             CTR_PIN = 1;
  264.         }
  265.         key_released = 1;   // 重置按键状态为释放
  266.         key_press_time = 0; // 重置按键计时
  267.     }
  268. }
  269. // 外部中断0初始化
  270. void Int0_Init(void) {
  271.     IT0 = 1;         // 使能INT0下降沿中断
  272.     EX0 = 1;         // 使能INT0中断0
  273.     EA = 1;          // 使能中断总开关
  274. }
  275. // 掉电模式
  276. void PowerDownMode(void) {
  277.     LED_PIN = 1; // 确保输出高电平
  278.     CTR_PIN = 1;
  279.     KEY_PIN = 1;
  280.     wakeup_status = 0;
  281.     _nop_();
  282.     _nop_();
  283.     MCU_POWER_DOWN();  // MCU进入掉电模式(STC8G系列)
  284.     _nop_();
  285.     _nop_();
  286.     _nop_();
  287.     _nop_();
  288. }
  289. // 从掉电模式唤醒
  290. void WakeUpFromPowerDown(void) {
  291.     PCON &= 0xFD; // 清除掉电标志
  292.     msTicks = 0;
  293.     idle_time = msTicks; // 重置空闲时间
  294. }
  295. // 外部中断0服务程序(按键唤醒)
  296. void INT0_ISR(void) interrupt 0 {
  297.     if (!wakeup_status){       // 如果在休眠状态,就执行唤醒操作
  298.         wakeup_status = 1;
  299.         WakeUpFromPowerDown(); // 从掉电模式唤醒
  300.     }
  301. }
复制代码


回复

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:92
  • 最近打卡:2025-06-16 09:31:08
已绑定手机

4

主题

16

回帖

405

积分

中级会员

积分
405
发表于 2025-2-9 16:54:09 | 显示全部楼层
最后一次按键操作后,且无任务正在执行,待机超过30秒,自动进入掉电模式,达到最大化省电,按键按下时自动唤醒
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:92
  • 最近打卡:2025-06-16 09:31:08
已绑定手机

4

主题

16

回帖

405

积分

中级会员

积分
405
发表于 2025-2-10 14:15:04 | 显示全部楼层
继续更正,EEPROM中已有数据后,再次写入数据,需要先擦除,否则数据写入结果会异常,所以,要在Write_Led_Keep_Time(led_keep_time) 前面增加一行代码 EEPROM_SectorErase(EEPROM_ADDR);
回复 支持 反对

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-6-17 23:54 , Processed in 0.118256 second(s), 57 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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