神农鼎 发表于 2026-6-18 10:10:32

帮训练 STC 【AI 助手】一次能将 PWM / 串口 搞对,通知 STC 【AI 助手】他错的原因

帮训练 STC 【AI 助手】一次能将 PWM / 串口 搞对,
通知 STC 【AI 助手】他错的原因,帮他前进

STC8H1K08-36I-TSSOP20, PWM应用,
根据接收到的串口命令,
调整PWM输出的占空比和频率的PWM通用演示程序,
要方便修改,使用 KEIL C51编译器




















神农鼎 发表于 2026-6-18 10:22:39















ercircle 发表于 2026-6-18 10:43:48


结论: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");
}效果测试:





修改占空比:


修改频率:


ercircle 发表于 2026-6-18 11:01:28



结论: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");
}


ercircle 发表于 2026-6-18 11:09:01



结论:同样的问题,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");
}

神农鼎 发表于 2026-6-18 11:35:48

请问楼上,您 2个问题,
STC【AI助手】,第几次搞对了 ?
还是有时对,有时错 ?
可能的原因 ?

帮提供思路

ercircle 发表于 2026-6-18 11:50:47

神农鼎 发表于 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没有保留上一次对话的经验,并且这次首次输出的代码编译不通过。


patch1582 发表于 2026-6-18 14:35:30

这个好玩

我心飞扬 发表于 2026-6-19 13:07:01

ercircle 发表于 2026-6-18 11:09
结论:同样的问题,AI没有保留上一次对话的经验,并且这次首次输出的代码编译不通过。
================= ...

使用中也发现与你类似的问题。
同样的问题多问几次展示的内容有变化。
还有:
1、登录论坛的用户提问问题历史记录最好能保留。
2、回答结果、产生的源代码等能压缩后一键下载,这样方便。

狂热主宰 发表于 2026-6-20 08:46:57

我心飞扬 发表于 2026-6-19 13:07
使用中也发现与你类似的问题。
同样的问题多问几次展示的内容有变化。
还有:


保留历史对话
下次还可以翻阅
无需重复提问消耗token
页: [1] 2
查看完整版本: 帮训练 STC 【AI 助手】一次能将 PWM / 串口 搞对,通知 STC 【AI 助手】他错的原因