用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;
}
你提出的这个问题涉及 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]