liuxingyu 发表于 2025-9-20 14:06:27

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

请大佬看下程序,为啥运行程序为啥无法实现循环按键调光和记忆占空比,该如何解决?

#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_VALUE0x81   // 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;
}


国芯人工智能 发表于 2025-9-20 14:08:00

你提出的这个问题涉及 STC8G1K08A 单片机 的功能实现,主要包括以下两个方面:

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

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

一、问题分析

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

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

二、关键模块分析与建议

1. PWM配置
你使用的是定时器模拟 PWM,这是可以的,但要注意以下几点:

c
// 假设你使用的是定时器0,每2us触发一次,周期100次即200us

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

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

建议采用如下结构:

c
uint8t getkey(void) {
    static uint8t keystate = 0;
    if (KEYPIN == 0) {
      delayms(10); // 软件消抖
      if (KEYPIN == 0) {
            if (!keystate) {
                keystate = 1;
                return 1; // 有效按下
            }
      }
    } else {
      keystate = 0;
    }
    return 0;
}

然后在主循环中使用:

c
if (getkey()) {
    level = (level + 1) % TOTALLEVELS;
    savedutytoeeprom(level); // 更新后立即保存
}

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

建议使用如下 EEPROM 操作函数:

c
void IapIdle() {
    if (IAPCONTR & 0x80) {   // 如果正在执行IAP操作
      IAPCONTR = 0x00;       // 停止当前IAP操作
      IAPCMD = 0x00;         // 清空命令
      IAPTRIG = 0x00;      // 清除触发
      nop(); nop();
    }
}

void IapEraseSector(uint16t addr) {
    IapIdle();
    IAPADDRH = (uint8t)(addr >> 8);
    IAPADDRL = (uint8t)(addr & 0xFF);
    IAPCMD = 0x02;             // 擦除扇区
    IAPTRIG = 0x5A;
    IAPTRIG = 0xA5;
    nop();
    while (!(IAPCONTR & 0x80)); // 等待完成
}

void IapProgramByte(uint16t addr, uint8t dat) {
    IapIdle();
    IAPADDRH = (uint8t)(addr >> 8);
    IAPADDRL = (uint8t)(addr & 0xFF);
    IAPCMD = 0x01;             // 写入字节
    IAPDATA = dat;
    IAPTRIG = 0x5A;
    IAPTRIG = 0xA5;
    nop();
    while (!(IAPCONTR & 0x80)); // 等待完成
}

uint8t IapReadByte(uint16t addr) {
    IapIdle();
    IAPADDRH = (uint8t)(addr >> 8);
    IAPADDRL = (uint8t)(addr & 0xFF);
    IAPCMD = 0x00;             // 读取字节
    IAPTRIG = 0x5A;
    IAPTRIG = 0xA5;
    nop();
    while (!(IAPCONTR & 0x80)); // 等待完成
    return IAPDATA;
}

4. 占空比记忆逻辑
建议主程序中先读取 EEPROM:

c
uint8t loaddutylevel(void) {
    uint8t level = IapReadByte(EEPROMADDR);
    if (level < TOTALLEVELS && IapReadByte(EEPROMADDR + 1) == VALIDFLAG) {
      return level;
    } else {
      return 0; // 默认档位
    }
}

void savedutytoeeprom(uint8t level) {
    IapEraseSector(EEPROMADDR); // 擦除整个扇区
    IapProgramByte(EEPROMADDR, level);
    IapProgramByte(EEPROMADDR + 1, VALIDFLAG);
}

三、主程序结构建议

c
void main(void) {
    uint8t level;

    // 初始化
    PWMPIN = 0;
    initpwm(); // 初始化PWM或定时器

    // 读取上次保存的档位
    level = loaddutylevel();

    while (1) {
      if (getkey()) {
            level = (level + 1) % TOTALLEVELS;
            savedutytoeeprom(level);
      }

      // 设置当前档位PWM
      setpwmduty(level);
    }
}

四、其他建议

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

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

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

五、总结

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

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

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

祝你调试顺利!

[本文内容由国芯人工智能辅助生成,仅供参考]
页: [1]
查看完整版本: 用stc8g1k08A单片机断电带记忆当前占空比功能,在下次开机后执行上次关断的占空比