帮训练 STC 【AI 助手】一次能将 PWM / 串口 搞对,通知 STC 【AI 助手】他错的原因
帮训练 STC 【AI 助手】一次能将 PWM / 串口 搞对,通知 STC 【AI 助手】他错的原因,帮他前进
STC8H1K08-36I-TSSOP20, PWM应用,
根据接收到的串口命令,
调整PWM输出的占空比和频率的PWM通用演示程序,
要方便修改,使用 KEIL C51编译器
结论:AI输出的代码编译运行一把通
======================
提问:
STC8H1K08-36I-TSSOP20, PWM应用,
根据接收到的串口命令,
调整PWM输出的占空比和频率的PWM通用演示程序
/******************************************************************************
* 文件名:main.c
* MCU:STC8H1K08-36I-TSSOP20
* 功能:通过串口命令动态调整PWM的频率和占空比
* 主频:11.0592MHz(内部IRC)
* PWM输出:P1.0 (PWMA 通道1,PWM1P)
* 串口:UART1, P3.0/P3.1, 115200-8-N1
*
* 命令格式(字符串,以 0x0D 或 0x0A 结尾):
* F=1000→ 设置频率为 1000 Hz(范围:10 ~ 500000)
* D=50 → 设置占空比为 50% (范围:0 ~ 100)
* ? → 查询当前频率和占空比
*
* 编译环境:Keil C51,需包含 stc8h.h 头文件
*****************************************************************************/
#include "stc8h.h"
#include "intrins.h"
// ========================== 用户可修改宏 ==================================
#define MAIN_Fosc 11059200L // 系统主频(Hz),可修改
#define UART_BAUD 115200 // 串口波特率
#define PWM_DEFAULT_FREQ1000 // 默认PWM频率(Hz)
#define PWM_DEFAULT_DUTY50 // 默认占空比(%)
// ========================================================================
// 波特率重装载值(T2做波特率发生器,4倍采样,1T模式)
// BRT = 65536 - SYSCLK / 4 / Baud
#define BRT_VAL(65536UL - (MAIN_Fosc / 4 / UART_BAUD))
// ---------- 类型定义 ----------
typedef unsigned charu8;
typedef unsigned int u16;
typedef unsigned longu32;
// ---------- 全局变量 ----------
volatile u8Rx_Ready; // 收到完整命令标志
volatile u8Rx_Buffer; // 接收缓冲区
volatile u8Rx_Index; // 当前接收位置
u32PWM_Freq= PWM_DEFAULT_FREQ;// 当前PWM频率(Hz)
u16PWM_Duty= PWM_DEFAULT_DUTY;// 当前占空比(0~100)
// ---------- 函数声明 ----------
void UART1_Init(void);
void PWM_Init(void);
void PWM_Update(u32 freq, u16 duty);
void Cmd_Parse(void);
void Print_String(u8 *p);
void Print_Num(u32 val);
void Print_Prompt(void);
// ============================ 主函数 ====================================
void main(void)
{
u8 i;
// 使能XFR访问(PWM寄存器在XFR区)
P_SW2 |= 0x80;
// 所有IO设置为准双向口
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
// 初始化外设
UART1_Init();
PWM_Init();
// 使能中断
ES = 1; // 串口中断
EA = 1; // 总中断
// 输出开机提示
Print_Prompt();
while(1)
{
if(Rx_Ready)
{
Rx_Ready = 0;
Cmd_Parse(); // 解析并执行命令
}
}
}
// ======================== 串口1初始化 ===================================
// 使用定时器T2作为波特率发生器,模式1(8位可变波特率)
void UART1_Init(void)
{
SCON = 0x50; // 模式1, 8位UART, REN使能接收
// 定时器2配置:1T模式,作为波特率发生器
AUXR &= ~0x10; // T2停止
AUXR &= ~0x08; // T2设为定时器
AUXR |=0x04; // T2设为1T模式
T2H = (u8)(BRT_VAL >> 8); // 装载波特率重装值
T2L = (u8)(BRT_VAL);
AUXR |=0x10; // T2启动
AUXR |=0x01; // 串口1选择T2做波特率发生器
Rx_Index = 0;
Rx_Ready = 0;
}
// ======================== PWM初始化 =====================================
// 使用 PWMA 通道1 (PWM1P),默认输出在 P1.0
// 边沿对齐,向上计数模式,PWM模式1
void PWM_Init(void)
{
// ---- 关闭所有通道,写CCMRx前必须先清零CCERx ----
PWMA_CCER1 = 0x00;
// ---- 配置通道1为PWM模式1 ----
// OC1M = 110 (PWM模式1: CNT<CCR时输出有效电平)
// OC1PE = 1 (预装载使能)
PWMA_CCMR1 = 0x68; // 0110 1000
// ---- 通道1使能,高电平有效 ----
PWMA_CCER1 = 0x01; // CC1E=1, CC1P=0
// ---- 使能 PWM1P 端口输出 ----
PWMA_ENO= 0x01; // ENO1P=1
// ---- 通道输出脚选择:默认 C1PS=00 → PWM1P = P1.0 ----
PWMA_PS&= ~0x03; // C1PS = 00
// ---- 设置初始频率和占空比 ----
PWM_Update(PWM_Freq, PWM_Duty);
// ---- 使能主输出,启动计数 ----
PWMA_BKR= 0x80; // MOE=1 主输出使能
PWMA_CR1 |= 0x01; // CEN=1 启动计数
}
// ======================== 更新PWM参数 ===================================
// freq: 目标频率(Hz) 范围:10 ~ 500000
// duty: 占空比百分比 范围:0 ~ 100
//
// 公式:
// 频率 = SYSCLK / (PSC+1) / (ARR+1)
// 占空比 = CCR / (ARR+1) * 100%
// 自动选择 PSC 使 ARR 不溢出16位
void PWM_Update(u32 freq, u16 duty)
{
u16 arr, ccr;
u16 psc;
if(freq == 0) freq = 1; // 防除零
// ---- 自动计算预分频 ----
// 先计算不分频(PSC=0)时的ARR
// ARR = SYSCLK / freq - 1
if(freq < (MAIN_Fosc / 65536UL))
{
// 不分频时ARR会超16位,需要分频
// 让ARR尽量大以保证精度
psc = (u16)(MAIN_Fosc / freq / 65536UL);
if(psc > 0) psc--;
arr = (u16)(MAIN_Fosc / freq / (psc + 1) - 1);
}
else
{
psc = 0;
arr = (u16)(MAIN_Fosc / freq - 1);
}
// 边界保护
if(arr > 65535) arr = 65535;
if(arr < 1) arr = 1;
// ---- 占空比计算 ----
if(duty == 0)
ccr = 0; // 0%:CCR=0
else if(duty >= 100)
ccr = arr + 1; // 100%:CCR > ARR
else
ccr = (u16)((u32)(arr + 1) * duty / 100);
// ---- 写入寄存器 ----
PWMA_PSCRH = (u8)(psc >> 8);
PWMA_PSCRL = (u8)(psc);
PWMA_ARRH= (u8)(arr >> 8);
PWMA_ARRL= (u8)(arr);
PWMA_CCR1H = (u8)(ccr >> 8);
PWMA_CCR1L = (u8)(ccr);
// 产生更新事件,使新值立即生效
PWMA_EGR |= 0x01; // UG=1
// 保存当前值
PWM_Freq = freq;
PWM_Duty = duty;
}
// ======================== 串口中断服务 ==================================
void UART1_ISR(void) interrupt 4
{
u8 dat;
if(RI)
{
RI = 0;
dat = SBUF;
// 回车或换行 → 结束一帧命令
if(dat == 0x0D || dat == 0x0A)
{
if(Rx_Index > 0)
{
Rx_Buffer = '\0';
Rx_Ready = 1;
Rx_Index = 0;
}
}
else
{
// 防止缓冲区溢出
if(Rx_Index < sizeof(Rx_Buffer) - 1)
{
Rx_Buffer = dat;
}
}
}
if(TI)
{
TI = 0;
}
}
// ======================== 命令解析与执行 ================================
void Cmd_Parse(void)
{
u8 *p = (u8 *)Rx_Buffer;
u32 val = 0;
// ---- 跳过前导空格 ----
while(*p == ' ') p++;
// ---- 查询命令 ----
if(*p == '?' || *p == 'H' || *p == 'h')
{
Print_String("\r\n--- PWM Status ---\r\n");
Print_String("Freq: ");
Print_Num(PWM_Freq);
Print_String(" Hz\r\n");
Print_String("Duty: ");
Print_Num(PWM_Duty);
Print_String(" %\r\n");
Print_String("CLK : ");
Print_Num(MAIN_Fosc);
Print_String(" Hz\r\n");
Print_String("------------------\r\n");
return;
}
// ---- 频率命令: F=xxxx ----
if((*p == 'F' || *p == 'f') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val >= 10 && val <= 500000) // 范围保护:10Hz ~ 500kHz
{
PWM_Update(val, PWM_Duty);
Print_String("\r\nFreq set to ");
Print_Num(val);
Print_String(" Hz\r\n");
}
else
{
Print_String("\r\nError: Freq out of range (10~500000)\r\n");
}
return;
}
// ---- 占空比命令: D=xx ----
if((*p == 'D' || *p == 'd') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val <= 100)
{
PWM_Update(PWM_Freq, (u16)val);
Print_String("\r\nDuty set to ");
Print_Num(val);
Print_String(" %\r\n");
}
else
{
Print_String("\r\nError: Duty out of range (0~100)\r\n");
}
return;
}
// ---- 未知命令 ----
Print_String("\r\nUnknown cmd. Usage: F=1000D=50?\r\n");
}
// ======================== 辅助输出函数 ==================================
void Print_String(u8 *p)
{
while(*p)
{
SBUF = *p++;
while(!TI);
TI = 0;
}
}
void Print_Num(u32 val)
{
u8 buf;
u8 i = 0;
if(val == 0)
{
SBUF = '0';
while(!TI); TI = 0;
return;
}
while(val > 0)
{
buf = (u8)(val % 10) + '0';
val /= 10;
}
while(i > 0)
{
i--;
SBUF = buf;
while(!TI); TI = 0;
}
}
void Print_Prompt(void)
{
Print_String("\r\n========================================\r\n");
Print_String(" STC8H1K08 PWM Ctrl via UART\r\n");
Print_String(" CLK: "); Print_Num(MAIN_Fosc);
Print_String(" Hz, BAUD: "); Print_Num(UART_BAUD);
Print_String("\r\n");
Print_String(" Commands:\r\n");
Print_String("F=1000set Freq (10~500000 Hz)\r\n");
Print_String("D=50 set Duty (0~100 %%)\r\n");
Print_String("? Query status\r\n");
Print_String("========================================\r\n");
}效果测试:
修改占空比:
修改频率:
结论:AI首次输出的代码编译正常,逻辑有点小问题,通过对话可让其纠正。
======================
难度提升,带死区设置的互补pwm输出,提问:
STC8H, PWM应用,PWM1P,1N输出互补PWM
根据接收到的串口命令,
调整PWM输出的占空比、频率、死区的PWM通用演示程序
编译下载默认:
更新死区时间:
设置100时串口提示大约18000 ns,实际测量只有9us
错误分析:
修正后:
/******************************************************************************
* 文件名:main.c
* MCU:STC8H1K08-36I-TSSOP20(兼容所有STC8H系列)
* 功能:通过串口命令动态调节互补PWM输出的频率、占空比和死区时间
* 主频:11.0592MHz(内部IRC,宏可修改)
* PWM输出:P1.0 (PWM1P) + P1.1 (PWM1N) 互补带死区
* 串口:UART1, P3.0/P3.1, 115200-8-N1
*
* 命令格式(以 0x0D 或 0x0A 结尾):
* F=1000→ 设置频率为 1000 Hz(10 ~ 500000)
* D=50 → 设置占空比为 50% (0 ~ 100)
* T=20 → 设置死区寄存器值 (0 ~ 255)
* ? → 查询当前频率、占空比、死区值及实际死区时间
*
* 编译环境:Keil C51,需包含 stc8h.h 头文件
*****************************************************************************/
#include "stc8h.h"
#include "intrins.h"
// ========================== 用户可修改宏 ==================================
#define MAIN_Fosc 11059200L // 系统主频(Hz)
#define UART_BAUD 115200 // 串口波特率
#define PWM_DEFAULT_FREQ1000 // 默认PWM频率(Hz)
#define PWM_DEFAULT_DUTY50 // 默认占空比(%)
#define PWM_DEFAULT_DTR 20 // 默认死区寄存器值 (0~255)
// ========================================================================
// 波特率重装载值(T2做波特率发生器,4倍采样,1T模式)
#define BRT_VAL(65536UL - (MAIN_Fosc / 4 / UART_BAUD))
// ---------- 类型定义 ----------
typedef unsigned charu8;
typedef unsigned int u16;
typedef unsigned longu32;
// ---------- 全局变量 ----------
volatile u8Rx_Ready; // 收到完整命令标志
volatile u8Rx_Buffer; // 接收缓冲区
volatile u8Rx_Index; // 当前接收位置
u32PWM_Freq= PWM_DEFAULT_FREQ;// 当前PWM频率(Hz)
u16PWM_Duty= PWM_DEFAULT_DUTY;// 当前占空比(0~100)
u16PWM_Psc = 0; // 当前预分频值(保存用于死区计算)
u16PWM_Arr = 0; // 当前自动重装载值
u8 PWM_Dtr = PWM_DEFAULT_DTR; // 当前死区寄存器值
// ---------- 函数声明 ----------
void UART1_Init(void);
void PWM_Init(void);
void PWM_Update(u32 freq, u16 duty, u8 dtr);
void Cmd_Parse(void);
void Print_String(u8 *p);
void Print_Num(u32 val);
void Print_Prompt(void);
u32Calc_DeadTime_ns(u8 dtr, u16 psc);
// ============================ 主函数 ====================================
void main(void)
{
// 使能XFR访问(PWM寄存器在XFR区)
P_SW2 |= 0x80;
// 所有IO设置为准双向口
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
// 初始化外设
UART1_Init();
PWM_Init();
// 使能中断
ES = 1; // 串口中断
EA = 1; // 总中断
// 输出开机提示
Print_Prompt();
while(1)
{
if(Rx_Ready)
{
Rx_Ready = 0;
Cmd_Parse(); // 解析并执行命令
}
}
}
// ======================== 串口1初始化 ===================================
// 使用定时器T2作为波特率发生器,模式1(8位可变波特率)
void UART1_Init(void)
{
SCON = 0x50; // 模式1, 8位UART, REN使能接收
// 定时器2配置:1T模式,作为波特率发生器
AUXR &= ~0x10; // T2停止
AUXR &= ~0x08; // T2设为定时器
AUXR |=0x04; // T2设为1T模式
T2H = (u8)(BRT_VAL >> 8); // 装载波特率重装值
T2L = (u8)(BRT_VAL);
AUXR |=0x10; // T2启动
AUXR |=0x01; // 串口1选择T2做波特率发生器
Rx_Index = 0;
Rx_Ready = 0;
}
// ======================== PWM初始化 =====================================
// 使用 PWMA 通道1,P1.0(PWM1P) + P1.1(PWM1N) 互补输出带死区
// 边沿对齐,向上计数模式,PWM模式1
void PWM_Init(void)
{
// ---- 关闭所有通道,写CCMRx前必须先清零CCERx ----
PWMA_CCER1 = 0x00;
// ---- 配置通道1为PWM模式1(带预装载) ----
PWMA_CCMR1 = 0x68; // 0110 1000
// ---- 通道1输出脚选择:C1PS=00 → PWM1P=P1.0, PWM1N=P1.1 ----
PWMA_PS&= ~0x03;
// ---- 使能 PWM1P 和 PWM1N 端口输出 ----
PWMA_ENO= 0x03; // ENO1P=1 (bit0), ENO1N=1 (bit1)
// ---- 使能互补输出,高电平有效 ----
// CC1E=1 (bit0), CC1P=0 (bit1)
// CC1NE=1 (bit2), CC1NP=0 (bit3)
PWMA_CCER1 = 0x05; // 0000 0101
// ---- 设置初始参数 ----
PWM_Update(PWM_Freq, PWM_Duty, PWM_Dtr);
// ---- 使能主输出,启动计数 ----
PWMA_BKR= 0x80; // MOE=1 主输出使能
PWMA_CR1 |= 0x01; // CEN=1 启动计数
}
// ======================== 更新PWM参数 ===================================
// freq: 目标频率(Hz) 范围:10 ~ 500000
// duty: 占空比百分比 范围:0 ~ 100
// dtr:死区寄存器值 范围:0 ~ 255
//
// 频率公式:freq = SYSCLK / (PSC+1) / (ARR+1)
// 自动选择 PSC 使 ARR 不溢出16位
void PWM_Update(u32 freq, u16 duty, u8 dtr)
{
u16 arr, ccr;
u16 psc;
if(freq == 0) freq = 1;
// ---- 自动计算预分频 ----
if(freq < (MAIN_Fosc / 65536UL))
{
psc = (u16)(MAIN_Fosc / freq / 65536UL);
if(psc > 0) psc--;
arr = (u16)(MAIN_Fosc / freq / (psc + 1) - 1);
}
else
{
psc = 0;
arr = (u16)(MAIN_Fosc / freq - 1);
}
if(arr > 65535) arr = 65535;
if(arr < 1) arr = 1;
// ---- 占空比计算 ----
if(duty == 0)
ccr = 0;
else if(duty >= 100)
ccr = arr + 1; // 100%:CCR > ARR
else
ccr = (u16)((u32)(arr + 1) * duty / 100);
// ---- 写入寄存器 ----
PWMA_PSCRH = (u8)(psc >> 8);
PWMA_PSCRL = (u8)(psc);
PWMA_ARRH= (u8)(arr >> 8);
PWMA_ARRL= (u8)(arr);
PWMA_CCR1H = (u8)(ccr >> 8);
PWMA_CCR1L = (u8)(ccr);
PWMA_DTR = dtr; // 死区寄存器
// 产生更新事件,使新值立即生效
PWMA_EGR |= 0x01; // UG=1
// 保存当前值
PWM_Freq = freq;
PWM_Duty = duty;
PWM_Dtr= dtr;
PWM_Psc= psc;
PWM_Arr= arr;
}
// ======================== 串口中断服务 ==================================
void UART1_ISR(void) interrupt 4
{
u8 dat;
if(RI)
{
RI = 0;
dat = SBUF;
// 回车或换行 → 结束一帧命令
if(dat == 0x0D || dat == 0x0A)
{
if(Rx_Index > 0)
{
Rx_Buffer = '\0';
Rx_Ready = 1;
Rx_Index = 0;
}
}
else
{
if(Rx_Index < sizeof(Rx_Buffer) - 1)
{
Rx_Buffer = dat;
}
}
}
if(TI)
{
TI = 0;
}
}
// ======================== 命令解析与执行 ================================
void Cmd_Parse(void)
{
u8 *p = (u8 *)Rx_Buffer;
u32 val = 0;
while(*p == ' ') p++;
// ---- 查询命令 ----
if(*p == '?' || *p == 'H' || *p == 'h')
{
u32 dt_ns;
Print_String("\r\n--- Complementary PWM Status ---\r\n");
Print_String("Freq : ");
Print_Num(PWM_Freq);
Print_String(" Hz\r\n");
Print_String("Duty : ");
Print_Num(PWM_Duty);
Print_String(" %\r\n");
Print_String("DTR: ");
Print_Num(PWM_Dtr);
Print_String(" (0x");
if(PWM_Dtr < 16) Print_String("0");
Print_Num(PWM_Dtr);
Print_String(")\r\n");
dt_ns = Calc_DeadTime_ns(PWM_Dtr, PWM_Psc);
Print_String("DeadT: ");
Print_Num(dt_ns);
Print_String(" ns\r\n");
Print_String("PSC: ");
Print_Num(PWM_Psc);
Print_String("\r\n");
Print_String("ARR: ");
Print_Num(PWM_Arr);
Print_String("\r\n");
Print_String("CLK: ");
Print_Num(MAIN_Fosc);
Print_String(" Hz\r\n");
Print_String("Output: P1.0(PWM1P) + P1.1(PWM1N) 互补\r\n");
Print_String("----------------------------------\r\n");
return;
}
// ---- 频率命令: F=xxxx ----
if((*p == 'F' || *p == 'f') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val >= 10 && val <= 500000)
{
PWM_Update(val, PWM_Duty, PWM_Dtr);
Print_String("\r\nFreq set to ");
Print_Num(val);
Print_String(" Hz\r\n");
}
else
{
Print_String("\r\nError: Freq out of range (10~500000)\r\n");
}
return;
}
// ---- 占空比命令: D=xx ----
if((*p == 'D' || *p == 'd') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val <= 100)
{
PWM_Update(PWM_Freq, (u16)val, PWM_Dtr);
Print_String("\r\nDuty set to ");
Print_Num(val);
Print_String(" %\r\n");
}
else
{
Print_String("\r\nError: Duty out of range (0~100)\r\n");
}
return;
}
// ---- 死区命令: T=xx ----
if((*p == 'T' || *p == 't') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val <= 255)
{
PWM_Update(PWM_Freq, PWM_Duty, (u8)val);
Print_String("\r\nDTR set to ");
Print_Num(val);
Print_String(" (0x");
if(val < 16) Print_String("0");
Print_Num(val);
Print_String(")\r\n");
{
u32 ns = Calc_DeadTime_ns((u8)val, PWM_Psc);
Print_String("DeadTime ~ ");
Print_Num(ns);
Print_String(" ns\r\n");
}
}
else
{
Print_String("\r\nError: DTR out of range (0~255)\r\n");
}
return;
}
// ---- 未知命令 ----
Print_String("\r\nUnknown cmd.\r\n");
Print_String("Usage: F=1000D=50T=20?\r\n");
}
// ======================== ★★★ 修正后的死区计算函数 ★★★ ===================
// 根据 PWMA_DTR 寄存器的分段规则计算实际死区时间
// tCK_PSC = (PSC + 1) / SYSCLK(单位:秒)
// 返回值为纳秒
//
// 手册 25.9.34 节分段规则:
// DTG=000~011 (DTR 0~127) : DT = DTR × tCK_PSC
// DTG=100~101 (DTR 128~191): DT = (64 + DTR) × 2 × tCK_PSC
// DTG=110 (DTR 192~223): DT = (32 + DTR) × 8 × tCK_PSC
// DTG=111 (DTR 224~255): DT = (32 + DTR) × 16 × tCK_PSC
u32 Calc_DeadTime_ns(u8 dtr, u16 psc)
{
u32 dt_cycles; // 死区占用的 tCK_PSC 周期数
u32 tck_ns; // tCK_PSC 的纳秒数
// 计算 tCK_PSC(纳秒)
// tCK_PSC = (psc+1) / SYSCLK 秒
// tck_ns= (psc+1) * 1,000,000,000 / SYSCLK
tck_ns = ((u32)(psc + 1) * 1000000000UL) / MAIN_Fosc;
// 判断 DTR 所在的分段范围(依据 bit7 和 bit6)
if(dtr < 128) // DTG=0 → 第一段
{
dt_cycles = dtr; // 0~127 个 tCK_PSC
}
else if(dtr < 192) // DTG=100~101 → 第二段
{
dt_cycles = (u32)(64 + (dtr & 0x7F)) * 2;// 128~254 个 tCK_PSC
}
else if(dtr < 224) // DTG=110 → 第三段
{
dt_cycles = (u32)(32 + (dtr & 0x3F)) * 8;// 256~504 个 tCK_PSC
}
else // DTG=111 → 第四段
{
dt_cycles = (u32)(32 + (dtr & 0x1F)) * 16; // 512~1008 个 tCK_PSC
}
return dt_cycles * tck_ns;
}
// ========================================================================
// ======================== 辅助输出函数 ==================================
void Print_String(u8 *p)
{
while(*p)
{
SBUF = *p++;
while(!TI);
TI = 0;
}
}
void Print_Num(u32 val)
{
u8 buf;
u8 i = 0;
if(val == 0)
{
SBUF = '0';
while(!TI); TI = 0;
return;
}
while(val > 0)
{
buf = (u8)(val % 10) + '0';
val /= 10;
}
while(i > 0)
{
i--;
SBUF = buf;
while(!TI); TI = 0;
}
}
void Print_Prompt(void)
{
Print_String("\r\n============================================\r\n");
Print_String(" STC8H Complementary PWM Ctrl via UART\r\n");
Print_String(" CLK: "); Print_Num(MAIN_Fosc);
Print_String(" Hz, BAUD: "); Print_Num(UART_BAUD);
Print_String("\r\n");
Print_String(" Output: P1.0(PWM1P) + P1.1(PWM1N)\r\n");
Print_String(" Commands:\r\n");
Print_String("F=1000set Freq (10~500000 Hz)\r\n");
Print_String("D=50 set Duty (0~100 %%)\r\n");
Print_String("T=20 set DeadTime Reg (0~255)\r\n");
Print_String("? Query all params\r\n");
Print_String("============================================\r\n");
}
结论:同样的问题,AI没有保留上一次对话的经验,并且这次首次输出的代码编译不通过。
======================
关闭当前网页,重新打开AI助手,提问相同问题:
STC8H, PWM应用,PWM1P,1N输出互补PWM
根据接收到的串口命令,
调整PWM输出的占空比、频率、死区的PWM通用演示程序
输出代码报错:
/******************************************************************************
* 文件名:main.c
* MCU:STC8H 系列(如 STC8H8K64U、STC8H1K08 等)
* 功能:PWM1P/PWM1N 互补输出,串口命令调频、调占空比、调死区
* 主频:11.0592MHz(内部IRC)
* PWM 输出:P1.0 (PWM1P) / P1.1 (PWM1N)
* 串口:UART1, P3.0/P3.1, 115200-8-N1
*
* 命令格式(字符串,以 0x0D 或 0x0A 结尾):
* F=1000→ 设置频率为 1000 Hz(范围 10~500000)
* D=50 → 设置占空比为 50% (范围 0~100)
* T=500 → 设置死区为 500 ns(范围 0~12700)
* ? → 查询当前参数
*
* 编译环境:Keil C51,需包含 stc8h.h 头文件
*****************************************************************************/
#include "stc8h.h"
#include "intrins.h"
// ========================== 用户可修改宏 ==================================
#define MAIN_Fosc 11059200UL // 系统主频(Hz)
#define UART_BAUD 115200 // 串口波特率
#define PWM_DEFAULT_FREQ 1000 // 默认频率(Hz)
#define PWM_DEFAULT_DUTY 50 // 默认占空比(%)
#define PWM_DEFAULT_DT_NS1000 // 默认死区(ns),1000ns = 1us
// PWM 引脚选择:C1PS
// 00 -> PWM1P=P1.0, PWM1N=P1.1
// 01 -> PWM1P=P2.0, PWM1N=P2.1
// 10 -> PWM1P=P6.0, PWM1N=P6.1
#define PWM_C1PS 0x00
// ========================================================================
// 波特率重装载值(T2做波特率发生器,4倍采样,1T模式)
// BRT = 65536 - SYSCLK / 4 / Baud
#define BRT_VAL(65536UL - (MAIN_Fosc / 4 / UART_BAUD))
// ---------- 类型定义 ----------
typedef unsigned charu8;
typedef unsigned int u16;
typedef unsigned longu32;
// ---------- 全局变量 ----------
volatile u8Rx_Ready; // 收到完整命令标志
volatile u8Rx_Buffer; // 接收缓冲区
volatile u8Rx_Index; // 当前接收位置
u32PWM_Freq = PWM_DEFAULT_FREQ; // 当前频率(Hz)
u16PWM_Duty = PWM_DEFAULT_DUTY; // 当前占空比(%)
u16PWM_DeadNS = PWM_DEFAULT_DT_NS;// 当前死区(ns)
u16PWM_PSC = 0; // 当前预分频值
u16PWM_ARR = 0; // 当前自动重装载值
// ---------- 函数声明 ----------
void UART1_Init(void);
void PWM_Init(void);
void PWM_Update(u32 freq, u16 duty);
void PWM_SetDeadTime_ns(u16 ns);
u8 PWM_CalcDTG(u32 ns, u32 tCK, u8 *pDTG);
void Cmd_Parse(void);
void Print_String(u8 *p);
void Print_Num(u32 val);
void Print_Prompt(void);
// ============================ 主函数 ====================================
void main(void)
{
// 使能XFR访问(PWM寄存器在XFR区)
P_SW2 |= 0x80;
// 所有IO设置为准双向口
P0M0 = 0x00; P0M1 = 0x00;
P1M0 = 0x00; P1M1 = 0x00;
P2M0 = 0x00; P2M1 = 0x00;
P3M0 = 0x00; P3M1 = 0x00;
P4M0 = 0x00; P4M1 = 0x00;
P5M0 = 0x00; P5M1 = 0x00;
// 初始化外设
UART1_Init();
PWM_Init();
// 设置默认死区
PWM_SetDeadTime_ns(PWM_DeadNS);
// 使能中断
ES = 1; // 串口中断
EA = 1; // 总中断
// 输出开机提示
Print_Prompt();
while(1)
{
if(Rx_Ready)
{
Rx_Ready = 0;
Cmd_Parse(); // 解析并执行命令
}
}
}
// ======================== 串口1初始化 ===================================
// 使用定时器T2作为波特率发生器,模式1(8位可变波特率)
void UART1_Init(void)
{
SCON = 0x50; // 模式1, 8位 UART, REN使能接收
// 定时器2配置:1T模式,作为波特率发生器
AUXR &= ~0x10; // T2停止
AUXR &= ~0x08; // T2设为定时器
AUXR |=0x04; // T2设为1T模式
T2H = (u8)(BRT_VAL >> 8); // 装载波特率重装值
T2L = (u8)(BRT_VAL);
AUXR |=0x10; // T2启动
AUXR |=0x01; // 串口1选择T2做波特率发生器
Rx_Index = 0;
Rx_Ready = 0;
}
// ======================== PWM初始化 =====================================
// PWMA 通道1 (PWM1P/PWM1N) 互补输出
// 边沿对齐,向上计数模式,PWM模式1
void PWM_Init(void)
{
// ---- 关闭通道1输出,才能配置CCMR1的CC1S位 ----
PWMA_CCER1 = 0x00;
// ---- 配置通道1为PWM模式1 ----
// OC1M = 110 (PWM模式1: CNT<CCR时输出有效电平)
// OC1PE = 1 (预装载使能)
// CC1S = 00(输出模式)
PWMA_CCMR1 = 0x68; // 0110 1000
// ---- 通道1引脚选择 ----
PWMA_PS = (PWMA_PS & ~0x03) | (PWM_C1PS & 0x03);
// ---- 使能 PWM1P 和 PWM1N 端口输出 ----
PWMA_ENO = 0x03; // ENO1P=1, ENO1N=1
// ---- 通道1互补输出使能,高电平有效 ----
// CC1E=1, CC1NE=1, CC1P=0, CC1NP=0
PWMA_CCER1 = 0x05; // 0000 0101 (CC1E|CC1NE)
// ---- 边沿对齐,向上计数,预装载使能 ----
PWMA_CR1 = 0x80; // ARPE=1 (自动重装载预装载)
// ---- 设置初始频率和占空比 ----
PWM_Update(PWM_Freq, PWM_Duty);
// ---- 使能主输出(总开关),启动计数 ----
PWMA_BKR = 0x80; // MOE=1 主输出使能
PWMA_CR1 |= 0x01; // CEN=1 启动计数器
}
// ======================== 更新PWM频率和占空比 ============================
// freq: 目标频率(Hz) 范围:10 ~ 500000
// duty: 占空比百分比 范围:0 ~ 100
//
// 公式:
// 频率 = SYSCLK / (PSC+1) / (ARR+1)
// 占空比 = CCR / (ARR+1) * 100%
void PWM_Update(u32 freq, u16 duty)
{
u16 arr, ccr;
u16 psc;
if(freq == 0) freq = 1; // 防除零
// ---- 自动计算预分频,使ARR尽量大以保证精度 ----
if(freq >= MAIN_Fosc)
{
psc = 0;
arr = 1;
}
else
{
// 目标:ARR 在 100~65535 之间
// 先估算不分频时的ARR
u32 tmp = MAIN_Fosc / freq;
if(tmp <= 65536UL)
{
psc = 0;
arr = (u16)(tmp - 1);
if(arr < 2) arr = 2;
}
else
{
// 需要分频,让ARR尽量大
psc = (u16)(tmp / 2000); // 粗略分频
if(psc > 65535) psc = 65535;
arr = (u16)(tmp / (psc + 1) - 1);
if(arr > 65535) { arr = 65535; psc = (u16)(tmp / (arr + 1) - 1); }
if(arr < 2) { arr = 2;psc = (u16)(tmp / (arr + 1) - 1); }
}
}
// 保存当前PSC和ARR,供死区计算使用
PWM_PSC = psc;
PWM_ARR = arr;
// ---- 占空比计算 ----
if(duty == 0)
ccr = 0; // 0%:CCR=0
else if(duty >= 100)
ccr = arr + 1; // 100%:CCR > ARR
else
ccr = (u16)((u32)(arr + 1) * duty / 100);
// ---- 写入寄存器(先写高8位,再写低8位) ----
PWMA_PSCRH = (u8)(psc >> 8);
PWMA_PSCRL = (u8)(psc);
PWMA_ARRH= (u8)(arr >> 8);
PWMA_ARRL= (u8)(arr);
PWMA_CCR1H = (u8)(ccr >> 8);
PWMA_CCR1L = (u8)(ccr);
// 产生更新事件,使新值立即生效
PWMA_EGR |= 0x01; // UG=1
// 保存当前值
PWM_Freq = freq;
PWM_Duty = duty;
// 频率改变后,重新计算死区(因为tCK_PSC变了)
PWM_SetDeadTime_ns(PWM_DeadNS);
}
// ======================== 设置死区时间(ns为单位)=======================
// 根据当前预分频,将用户输入的纳秒值转换为 DTR 寄存器值
void PWM_SetDeadTime_ns(u16 ns)
{
u32 tCK; // PWM时钟周期(ps),用皮秒避免浮点
u32 target_ps; // 目标死区(ps)
u8dtg = 0;
u8found;
if(ns == 0)
{
PWMA_DTR = 0x00; // 死区=0
PWM_DeadNS = 0;
return;
}
// tCK = 1 / (SYSCLK / (PSC+1)) = (PSC+1) / SYSCLK (秒)
// 转皮秒:tCK_ps = (u32)(PSC+1) * 1000000000000UL / SYSCLK
// 但为了不溢出,用微秒级计算:tCK_ns 精确到纳秒
// tCK (ns) = (u32)(PWM_PSC + 1) * 1000000000UL / MAIN_Fosc
// 但避免32位溢出,用下面方式:
// tCK_ns = (u32)(PWM_PSC + 1) * 1000 / (MAIN_Fosc / 1000000)
// 这样精度到纳秒级别,对于死区设置足够
{
u32 clk_mhz = MAIN_Fosc / 1000000UL; // 主频(MHz)
u32 tCK_ns;
if(clk_mhz == 0) clk_mhz = 1;
// tCK_ns = (PSC+1) * 1000 / clk_mhz
tCK_ns = (u32)(PWM_PSC + 1) * 1000UL / clk_mhz;
// 如果tCK_ns=0(高频情况),按1ns处理
if(tCK_ns == 0) tCK_ns = 1;
target_ps = (u32)ns * 1000UL; // 转皮秒
// ---- 尝试匹配最佳 DTG 值 ----
// DTG 分4种模式
// 模式1: DTG=000, dead = DTG * tCK
// 模式2: DTG=100, dead = (64 + DTG) * 2 * tCK
// 模式3: DTG=110, dead = (32 + DTG) * 8 * tCK
// 模式4: DTG=111, dead = (32 + DTG) * 16 * tCK
found = 0;
// 模式1:0~255 * tCK_ns
if(!found && ns <= (255UL * tCK_ns))
{
dtg = (u8)(ns / tCK_ns);
if(dtg > 0 && (ns - dtg * tCK_ns) > (tCK_ns / 2))
dtg++; // 四舍五入
// DTG 保持为 000
found = 1;
}
// 模式2:(64~191) * 2 * tCK_ns => 128~382 * tCK_ns
if(!found && ns <= (191UL * 2UL * tCK_ns))
{
u16 val = (u16)(ns / (2UL * tCK_ns));
if(val < 64) val = 64;
if(val > 191) val = 191;
dtg = (u8)(0x80 | (val - 64));// DTG=100
found = 1;
}
// 模式3:(32~63) * 8 * tCK_ns => 256~504 * tCK_ns
if(!found && ns <= (63UL * 8UL * tCK_ns))
{
u16 val = (u16)(ns / (8UL * tCK_ns));
if(val < 32) val = 32;
if(val > 63) val = 63;
dtg = (u8)(0xC0 | (val - 32));// DTG=110
found = 1;
}
// 模式4:(32~63) * 16 * tCK_ns => 512~1008 * tCK_ns
if(!found && ns <= (63UL * 16UL * tCK_ns))
{
u16 val = (u16)(ns / (16UL * tCK_ns));
if(val < 32) val = 32;
if(val > 63) val = 63;
dtg = (u8)(0xE0 | (val - 32));// DTG=111
found = 1;
}
if(!found)
{
// 超出范围,取最大值
dtg = 0xFF; // 模式4最大值:63*16*tCK_ns
// 计算实际可达的最大死区
// 上限在模式4:DTG=0xFF => (32+31)*16*tCK = 1008*tCK_ns
}
}
PWMA_DTR = dtg;
PWM_DeadNS = ns;
// 产生更新事件,使新死区生效
PWMA_EGR |= 0x01; // UG=1
}
// ======================== 串口中断服务 ==================================
void UART1_ISR(void) interrupt 4
{
u8 dat;
if(RI)
{
RI = 0;
dat = SBUF;
// 回车或换行 → 结束一帧命令
if(dat == 0x0D || dat == 0x0A)
{
if(Rx_Index > 0)
{
Rx_Buffer = '\0';
Rx_Ready = 1;
Rx_Index = 0;
}
}
else
{
// 防止缓冲区溢出
if(Rx_Index < sizeof(Rx_Buffer) - 1)
{
Rx_Buffer = dat;
}
}
}
if(TI)
{
TI = 0;
}
}
// ======================== 命令解析与执行 ================================
void Cmd_Parse(void)
{
u8 *p = (u8 *)Rx_Buffer;
u32 val = 0;
// ---- 跳过前导空格 ----
while(*p == ' ') p++;
// ---- 查询命令 ----
if(*p == '?' || *p == 'H' || *p == 'h')
{
Print_String("\r\n--- PWM Status ---\r\n");
Print_String("Freq : "); Print_Num(PWM_Freq);
Print_String(" Hz\r\n");
Print_String("Duty : "); Print_Num(PWM_Duty);
Print_String(" %\r\n");
Print_String("Dead : "); Print_Num(PWM_DeadNS);
Print_String(" ns\r\n");
Print_String("PSC: "); Print_Num(PWM_PSC);
Print_String("\r\n");
Print_String("ARR: "); Print_Num(PWM_ARR);
Print_String("\r\n");
Print_String("DTR: 0x");
{ u8 d = PWMA_DTR;
u8 hex = { (d>>4)&0x0F, d&0x0F };
for(u8 i=0;i<2;i++) {
if(hex<10) SBUF = hex+'0'; else SBUF = hex-10+'A';
while(!TI); TI=0;
}
}
Print_String("\r\nCLK: "); Print_Num(MAIN_Fosc);
Print_String(" Hz\r\n");
Print_String("------------------\r\n");
return;
}
// ---- 频率命令: F=xxxx ----
if((*p == 'F' || *p == 'f') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val >= 10 && val <= 500000) // 范围保护:10Hz ~ 500kHz
{
PWM_Update(val, PWM_Duty);
Print_String("\r\nFreq set to "); Print_Num(val);
Print_String(" Hz\r\n");
}
else
{
Print_String("\r\nError: Freq out of range (10~500000)\r\n");
}
return;
}
// ---- 占空比命令: D=xx ----
if((*p == 'D' || *p == 'd') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val <= 100)
{
PWM_Update(PWM_Freq, (u16)val);
Print_String("\r\nDuty set to "); Print_Num(val);
Print_String(" %\r\n");
}
else
{
Print_String("\r\nError: Duty out of range (0~100)\r\n");
}
return;
}
// ---- 死区命令: T=xxx (ns) ----
if((*p == 'T' || *p == 't') && *(p+1) == '=')
{
p += 2;
val = 0;
while(*p >= '0' && *p <= '9')
{
val = val * 10 + (*p - '0');
p++;
}
if(val <= 12700)
{
PWM_SetDeadTime_ns((u16)val);
Print_String("\r\nDeadTime set to "); Print_Num(val);
Print_String(" ns\r\n");
// 显示实际的DTR寄存器值
Print_String("DTR=0x");
{ u8 d = PWMA_DTR;
u8 hex = { (d>>4)&0x0F, d&0x0F };
for(u8 i=0;i<2;i++) {
if(hex<10) SBUF = hex+'0'; else SBUF = hex-10+'A';
while(!TI); TI=0;
}
}
Print_String("\r\n");
}
else
{
Print_String("\r\nError: DeadTime out of range (0~12700 ns)\r\n");
}
return;
}
// ---- 未知命令 ----
Print_String("\r\nUnknown cmd. Usage: F=1000D=50T=500?\r\n");
}
// ======================== 辅助输出函数 ==================================
void Print_String(u8 *p)
{
while(*p)
{
SBUF = *p++;
while(!TI);
TI = 0;
}
}
void Print_Num(u32 val)
{
u8 buf;
u8 i = 0;
if(val == 0)
{
SBUF = '0';
while(!TI); TI = 0;
return;
}
while(val > 0)
{
buf = (u8)(val % 10) + '0';
val /= 10;
}
while(i > 0)
{
i--;
SBUF = buf;
while(!TI); TI = 0;
}
}
void Print_Prompt(void)
{
Print_String("\r\n===========================================\r\n");
Print_String(" STC8H PWM1P/PWM1N Complementary Output\r\n");
Print_String(" CLK: "); Print_Num(MAIN_Fosc);
Print_String(" Hz, BAUD: "); Print_Num(UART_BAUD);
Print_String("\r\n");
Print_String(" PWM1P=");
switch(PWM_C1PS) {
case 0: Print_String("P1.0"); break;
case 1: Print_String("P2.0"); break;
case 2: Print_String("P6.0"); break;
default: Print_String("---"); break;
}
Print_String(", PWM1N=");
switch(PWM_C1PS) {
case 0: Print_String("P1.1"); break;
case 1: Print_String("P2.1"); break;
case 2: Print_String("P6.1"); break;
default: Print_String("---"); break;
}
Print_String("\r\n");
Print_String(" Commands:\r\n");
Print_String("F=1000set Freq (10~500000 Hz)\r\n");
Print_String("D=50 set Duty (0~100 %%)\r\n");
Print_String("T=500 set DeadTime (0~12700 ns)\r\n");
Print_String("? Query all params\r\n");
Print_String("===========================================\r\n");
}
请问楼上,您 2个问题,
STC【AI助手】,第几次搞对了 ?
还是有时对,有时错 ?
可能的原因 ?
帮提供思路
神农鼎 发表于 2026-6-18 11:35
请问楼上,您 2个问题,
STC【AI助手】,第几次搞对了 ?
还是有时对,有时错 ?
提问了三次,后两个重复问题:
1.STC8H1K08-36I-TSSOP20, PWM应用,根据接收到的串口命令,调整PWM输出的占空比和频率的PWM通用演示程序
AI输出的代码编译运行一次就对, 满分
2.STC8H, PWM应用,PWM1P,1N输出互补PWM,根据接收到的串口命令,调整PWM输出的占空比、频率、死区的PWM通用演示程序
AI首次输出的代码编译正常,逻辑有点小问题,通过对话可让其纠正。70分
3.关闭网页,重新打开,重复提问2
同样的问题,AI没有保留上一次对话的经验,并且这次首次输出的代码编译不通过。
这个好玩 ercircle 发表于 2026-6-18 11:09
结论:同样的问题,AI没有保留上一次对话的经验,并且这次首次输出的代码编译不通过。
================= ...
使用中也发现与你类似的问题。
同样的问题多问几次展示的内容有变化。
还有:
1、登录论坛的用户提问问题历史记录最好能保留。
2、回答结果、产生的源代码等能压缩后一键下载,这样方便。 我心飞扬 发表于 2026-6-19 13:07
使用中也发现与你类似的问题。
同样的问题多问几次展示的内容有变化。
还有:
保留历史对话
下次还可以翻阅
无需重复提问消耗token
页:
[1]
2