找回密码
 立即注册
查看: 156|回复: 1

用stc8g1k08A单片机断电带记忆当前占空比功能,在下次开机后执行上次关断的占空比

[复制链接]
  • 打卡等级:常住居民III
  • 打卡总天数:118
  • 最近打卡:2025-10-16 07:43:04
已绑定手机

1

主题

49

回帖

257

积分

中级会员

积分
257
发表于 2025-9-20 14:06:27 | 显示全部楼层 |阅读模式
请大佬看下程序,为啥运行程序为啥无法实现循环按键调光和记忆占空比,该如何解决?

#include "STC8G.h"
#include "intrins.h"

// 类型定义
typedef unsigned char   uint8_t;
typedef unsigned int    uint16_t;

// 引脚定义
sbit PWM_PIN = P5^4;    // PWM输出引脚:P5.4
sbit KEY_PIN = P3^1;    // 档位切换按键:P3.1

// 功能参数定义(24M主频,精确计算占空比)
#define PWM_PERIOD      100      // PWM周期(单位:2us),总周期200us(5kHz)
#define DUTY_LEVEL0     95       // 95%占空比 (95/100)
#define DUTY_LEVEL1     85       // 85%占空比 (85/100)
#define DUTY_LEVEL2     70       // 70%占空比 (70/100)
#define DUTY_LEVEL3     50       // 50%占空比 (50/100)
#define TOTAL_LEVELS    4        // 总档位数量

// EEPROM配置
#define EEPROM_ADDR     0x0000   // 存储地址
#define VALID_FLAG      0xAA     // 数据有效标志
#define IAP_WAIT_VALUE  0x81     // 24M主频IAP等待时间

// 全局变量
uint8_t currentLevel = 0;        // 当前档位(0-3)
uint8_t pwmDuty = DUTY_LEVEL0;   // 当前PWM占空比
uint16_t pwmCount = 0;           // PWM计数器
uint16_t keyDebounce = 0;        // 按键防抖计数器
bit keyPressed = 0;              // 按键按下标志
bit keyProcessed = 0;            // 按键处理完成标志
bit needSave = 0;                // 需要保存标志

// 微秒级延时(24M主频校准)
void DelayUs(uint16_t us)
{
    while(us--)
    {
        _nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();
    }
}

// 关闭IAP功能
void IAP_Disable(void)
{
    IAP_CONTR = 0;
    IAP_CMD = 0;
    IAP_ADDRH = 0;
    IAP_ADDRL = 0;
}

// 从EEPROM读取数据
uint8_t IAP_ReadByte(uint16_t addr)
{
    uint8_t dat;
    IAP_CONTR = IAP_WAIT_VALUE;
    IAP_CMD = 1;
    IAP_ADDRH = (uint8_t)(addr >> 8);
    IAP_ADDRL = (uint8_t)(addr & 0xFF);
    _nop_();_nop_();_nop_();_nop_();
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();_nop_();_nop_();_nop_();
    dat = IAP_DATA;
    IAP_Disable();
    return dat;
}

// 向EEPROM写入数据
void IAP_WriteByte(uint16_t addr, uint8_t dat)
{
    IAP_CONTR = IAP_WAIT_VALUE;
    IAP_CMD = 2;
    IAP_ADDRH = (uint8_t)(addr >> 8);
    IAP_ADDRL = (uint8_t)(addr & 0xFF);
    IAP_DATA = dat;
    _nop_();_nop_();_nop_();_nop_();
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();_nop_();_nop_();_nop_();
    IAP_Disable();
}

// 擦除EEPROM扇区
void IAP_EraseSector(uint16_t addr)
{
    IAP_CONTR = IAP_WAIT_VALUE;
    IAP_CMD = 3;
    IAP_ADDRH = (uint8_t)(addr >> 8);
    IAP_ADDRL = (uint8_t)(addr & 0xFF);
    _nop_();_nop_();_nop_();_nop_();
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();_nop_();_nop_();_nop_();
    IAP_Disable();
}

// 定时器0初始化(24M主频,2us中断)
void Timer0_Init(void)
{
    TMOD &= 0xF0;
    TMOD |= 0x01;
    // 24M主频下定时2us:机器周期0.5us,需4个机器周期
    TL0 = (65536 - 4) % 256;      // 低8位
    TH0 = (65536 - 4) / 256;      // 高8位
    ET0 = 1;                      // 使能定时器中断
    EA = 1;                       // 使能总中断
    TR0 = 1;                      // 启动定时器
}

// 更新PWM占空比(关键修复:明确档位对应关系)
void UpdatePwmDuty(void)
{
    // 强制验证档位范围
    if(currentLevel >= TOTAL_LEVELS)
    {
        currentLevel = 0;
    }

    // 清晰定义各档位占空比
    switch(currentLevel)
    {
        case 0:
            pwmDuty = DUTY_LEVEL0;  // 95%
            break;
        case 1:
            pwmDuty = DUTY_LEVEL1;  // 85%
            break;
        case 2:
            pwmDuty = DUTY_LEVEL2;  // 70%
            break;
        case 3:
            pwmDuty = DUTY_LEVEL3;  // 50%
            break;
    }
}

// 保存当前档位到EEPROM
void SaveCurrentLevel(void)
{
    if(needSave)
    {
        IAP_EraseSector(EEPROM_ADDR);
        DelayUs(2000);

        IAP_WriteByte(EEPROM_ADDR, currentLevel);
        DelayUs(2000);

        IAP_WriteByte(EEPROM_ADDR + 1, VALID_FLAG);
        DelayUs(2000);

        needSave = 0;
    }
}

// 读取保存的档位
void LoadSavedLevel(void)
{
    uint8_t savedLevel, flag;

    savedLevel = IAP_ReadByte(EEPROM_ADDR);
    flag = IAP_ReadByte(EEPROM_ADDR + 1);

    // 严格验证数据有效性
    if(flag == VALID_FLAG && savedLevel < TOTAL_LEVELS)
    {
        currentLevel = savedLevel;
    }
    else
    {
        currentLevel = 0;  // 默认从95%开始
    }

    UpdatePwmDuty();
}

// 初始化IO口
void IO_Init(void)
{
    // 配置P5.4为推挽输出
    P5M0 = 0x10;
    P5M1 = 0x00;

    // 配置P3.1为准双向输入(带内部上拉)
    P3M0 &= 0xFD;
    P3M1 |= 0x02;

    // 初始PWM输出为低电平
    PWM_PIN = 0;
}

// 主函数
void main(void)
{
    IO_Init();
    Timer0_Init();
    LoadSavedLevel();

    // 初始化计数器
    pwmCount = 0;

    while(1)
    {
        // 按键检测逻辑(完全重构,确保可靠切换)
        if(KEY_PIN == 0)  // 按键按下(低电平)
        {
            keyDebounce++;
            // 24M下,10000 * 2us = 20ms防抖
            if(keyDebounce >= 10000)
            {
                if(!keyPressed && !keyProcessed)
                {
                    keyPressed = 1;
                    // 档位切换核心逻辑(0→1→2→3→0)
                    currentLevel++;
                    if(currentLevel >= TOTAL_LEVELS)
                    {
                        currentLevel = 0;  // 循环回0档
                    }
                    UpdatePwmDuty();  // 立即更新占空比
                    needSave = 1;
                    keyProcessed = 1;  // 标记已处理
                }
            }
        }
        else  // 按键释放(高电平)
        {
            // 按键释放后重置标志
            if(keyPressed)
            {
                SaveCurrentLevel();  // 保存当前档位
                keyPressed = 0;
                keyProcessed = 0;
                keyDebounce = 0;
            }
            else
            {
                keyDebounce = 0;
            }
        }
    }
}

// 定时器0中断服务函数(生成PWM)
void Timer0_ISR(void) interrupt 1
{
    // 重装载2us定时初值
    TL0 = (65536 - 4) % 256;
    TH0 = (65536 - 4) / 256;

    // PWM计数
    pwmCount++;
    if(pwmCount >= PWM_PERIOD)
    {
        pwmCount = 0;
    }

    // PWM输出(确保占空比准确)
    PWM_PIN = (pwmCount < pwmDuty) ? 1 : 0;
}


回复

使用道具 举报 送花

3

主题

1880

回帖

608

积分

等待验证会员

积分
608
发表于 2025-9-20 14:08:00 | 显示全部楼层
你提出的这个问题涉及 STC8G1K08A 单片机 的功能实现,主要包括以下两个方面:

1. 使用 PWM 实现按键调光(占空比调节);
2. 断电后记忆当前占空比,并在下次上电时恢复该状态。

你的代码结构基本合理,但存在一些关键问题导致功能无法实现。下面我将从程序逻辑、EEPROM操作、按键检测、PWM配置等方面进行详细分析,并给出解决方案。

一、问题分析

1. 按键调光无法循环
原因可能在于按键检测逻辑不完整或未做消抖处理。
按键状态切换逻辑没有正确实现循环,可能导致档位“卡死”或无法递增/递减。

2. 占空比无法记忆
EEPROM 写入逻辑可能未正确执行或未调用。
读取 EEPROM 数据时未做校验(如标志位判断),导致读取无效数据或默认值。
EEPROM 写入后未延时或未等待写入完成,导致数据未写入成功。

二、关键模块分析与建议

1. PWM配置
你使用的是定时器模拟 PWM,这是可以的,但要注意以下几点:
  1. c
  2. // 假设你使用的是定时器0,每2us触发一次,周期100次即200us
复制代码

建议:
使用 PCA 或 PWM 模块(如果芯片支持)会更稳定。
若使用定时器模拟 PWM,需确保中断频率准确,且中断服务中正确控制 PWMPIN 的高低电平。

2. 按键检测逻辑
目前你未贴出完整按键检测代码,但常见错误包括:
未做按键消抖(硬件或软件);
未检测按键释放(按键按住会连续触发);
档位切换逻辑错误(如档位超过范围未处理);

建议采用如下结构:
  1. c
  2. uint8t getkey(void) {
  3.     static uint8t keystate = 0;
  4.     if (KEYPIN == 0) {
  5.         delayms(10); // 软件消抖
  6.         if (KEYPIN == 0) {
  7.             if (!keystate) {
  8.                 keystate = 1;
  9.                 return 1; // 有效按下
  10.             }
  11.         }
  12.     } else {
  13.         keystate = 0;
  14.     }
  15.     return 0;
  16. }
复制代码

然后在主循环中使用:
  1. c
  2. if (getkey()) {
  3.     level = (level + 1) % TOTALLEVELS;
  4.     savedutytoeeprom(level); // 更新后立即保存
  5. }
复制代码

3. EEPROM读写操作
STC8G 系列单片机支持 IAP 操作,可以对 EEPROM 进行读写。但需要注意:
写入前必须关闭中断;
写入完成后要等待写入完成标志;
必须先擦除再写入(如果写入的是新地址);
读取时应先判断标志位是否为有效值。

建议使用如下 EEPROM 操作函数:
  1. c
  2. void IapIdle() {
  3.     if (IAPCONTR & 0x80) {     // 如果正在执行IAP操作
  4.         IAPCONTR = 0x00;       // 停止当前IAP操作
  5.         IAPCMD = 0x00;         // 清空命令
  6.         IAPTRIG = 0x00;        // 清除触发
  7.         nop(); nop();
  8.     }
  9. }
  10. void IapEraseSector(uint16t addr) {
  11.     IapIdle();
  12.     IAPADDRH = (uint8t)(addr >> 8);
  13.     IAPADDRL = (uint8t)(addr & 0xFF);
  14.     IAPCMD = 0x02;             // 擦除扇区
  15.     IAPTRIG = 0x5A;
  16.     IAPTRIG = 0xA5;
  17.     nop();
  18.     while (!(IAPCONTR & 0x80)); // 等待完成
  19. }
  20. void IapProgramByte(uint16t addr, uint8t dat) {
  21.     IapIdle();
  22.     IAPADDRH = (uint8t)(addr >> 8);
  23.     IAPADDRL = (uint8t)(addr & 0xFF);
  24.     IAPCMD = 0x01;             // 写入字节
  25.     IAPDATA = dat;
  26.     IAPTRIG = 0x5A;
  27.     IAPTRIG = 0xA5;
  28.     nop();
  29.     while (!(IAPCONTR & 0x80)); // 等待完成
  30. }
  31. uint8t IapReadByte(uint16t addr) {
  32.     IapIdle();
  33.     IAPADDRH = (uint8t)(addr >> 8);
  34.     IAPADDRL = (uint8t)(addr & 0xFF);
  35.     IAPCMD = 0x00;             // 读取字节
  36.     IAPTRIG = 0x5A;
  37.     IAPTRIG = 0xA5;
  38.     nop();
  39.     while (!(IAPCONTR & 0x80)); // 等待完成
  40.     return IAPDATA;
  41. }
复制代码

4. 占空比记忆逻辑
建议主程序中先读取 EEPROM:
  1. c
  2. uint8t loaddutylevel(void) {
  3.     uint8t level = IapReadByte(EEPROMADDR);
  4.     if (level < TOTALLEVELS && IapReadByte(EEPROMADDR + 1) == VALIDFLAG) {
  5.         return level;
  6.     } else {
  7.         return 0; // 默认档位
  8.     }
  9. }
  10. void savedutytoeeprom(uint8t level) {
  11.     IapEraseSector(EEPROMADDR); // 擦除整个扇区
  12.     IapProgramByte(EEPROMADDR, level);
  13.     IapProgramByte(EEPROMADDR + 1, VALIDFLAG);
  14. }
复制代码

三、主程序结构建议
  1. c
  2. void main(void) {
  3.     uint8t level;
  4.     // 初始化
  5.     PWMPIN = 0;
  6.     initpwm(); // 初始化PWM或定时器
  7.     // 读取上次保存的档位
  8.     level = loaddutylevel();
  9.     while (1) {
  10.         if (getkey()) {
  11.             level = (level + 1) % TOTALLEVELS;
  12.             savedutytoeeprom(level);
  13.         }
  14.         // 设置当前档位PWM
  15.         setpwmduty(level);
  16.     }
  17. }
复制代码

四、其他建议

1. EEPROM 写入次数限制:STC8G 的 EEPROM 支持约 10 万次擦写,频繁写入会缩短寿命。建议只在档位变化时写入,而不是每次按键都写。

2. 电源稳定性和写入可靠性:断电瞬间写入 EEPROM 可能导致数据损坏。可以考虑加入掉电检测电路,在断电前进行保存。

3. 调试建议:
使用串口输出当前档位和 EEPROM 读取结果,方便调试;
检查 IAPCONTR、IAPCMD、IAPTRIG 等寄存器是否正确设置;
检查主频是否配置正确(24MHz);
检查 PWMPIN 是否配置为推挽输出。

五、总结

你程序的主要问题可能出在以下几点:

| 模块 | 问题 | 建议 |
|------|------|------|
| 按键检测 | 未消抖、未检测释放 | 增加消抖和按键释放判断 |
| EEPROM操作 | 未正确擦除或写入 | 使用完整IAP函数,加入标志位 |
| PWM控制 | 可能未正确配置 | 使用定时器或PCA模块 |
| 程序结构 | 逻辑不清晰 | 分模块处理,主循环调用 |

如需进一步帮助,欢迎提供完整代码或具体现象(如串口调试信息、LED状态等),我可以进一步分析。

祝你调试顺利!

[本文内容由国芯人工智能辅助生成,仅供参考]
回复

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-10-16 11:02 , Processed in 0.117704 second(s), 55 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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