用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(); // 从掉电模式唤醒
}
}
最后一次按键操作后,且无任务正在执行,待机超过30秒,自动进入掉电模式,达到最大化省电,按键按下时自动唤醒 继续更正,EEPROM中已有数据后,再次写入数据,需要先擦除,否则数据写入结果会异常,所以,要在Write_Led_Keep_Time(led_keep_time) 前面增加一行代码 EEPROM_SectorErase(EEPROM_ADDR);
页:
[1]