AI芯启强 发表于 2026-7-2 09:24:50

<p>现在信息已经足够充分了。我来编写完整的演示程序。</p>
<hr />
<p>以下是针对 <strong>STC8G1K08</strong> 的完整演示程序,使用 Keil C51 编译器。程序实现了您要求的三大功能:</p>
<pre><code class="language-c">/*************************************************************
* STC8G1K08 综合演示程序
*
* 功能:
*   1. 串口指令开启/关闭 ADC 采样(ADC_ON / ADC_OFF)
*   2. 串口修改 PWM 输出频率(PWM 1000 表示1kHz)
*   3. 定时自动上报 2 路输入信号测量值(周期、占空比)
*
* 硬件连接(默认引脚分配):
*   - 串口1: P3.0(RxD), P3.1(TxD)@ 115200bps
*   - PWM输出: PWM0_0 (P1.4) —— 输出可调PWM
*   - 输入捕获1: PWM1_0 (P2.0) —— 测量外部信号1
*   - 输入捕获2: PWM2_0 (P1.0) —— 测量外部信号2
*   - ADC通道: ADC0 (P1.1)
*
* 串口命令(字符串+回车 \r\n):
*   &quot;ADC_ON\r\n&quot;   —— 开启ADC自动采样(每秒上报一次)
*   &quot;ADC_OFF\r\n&quot;—— 关闭ADC采样
*   &quot;PWM 1000\r\n&quot; —— 设置PWM输出频率为1000Hz(范围1~24000000)
*
* 自动上报格式:
*   ADC: 512 (1.25V)
*   CAP1: T=1000us F=1000Hz D=50.0%
*   CAP2: T=2000us F=500Hz D=25.0%
*
* 系统时钟: 24MHz (内部IRC)
*************************************************************/

#include &quot;STC8G.h&quot;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

/*************** 用户配置宏 ***************/
#define SYS_CLK         24000000UL      // 系统时钟 24MHz
#define BAUD_RATE       115200          // 串口波特率

// 定时器3定时周期(用于自动上报)
#define REPORT_INTERVAL_MS1000      // 1秒上报一次

// PWM输出参数
#define PWM0_CLK      SYS_CLK         // PWM0时钟 = 系统时钟
#define PWM0_PERIOD_MIN   10          // 最小周期值
#define PWM0_PERIOD_MAX   32767       // 最大周期值(15位)

/*************** 全局变量 ***************/
// 串口接收
volatile uint8_tUART1_RxBuf;
volatile uint8_tUART1_RxCnt = 0;
volatile uint8_tUART1_RxFlag = 0;

// ADC控制
volatile uint8_tADC_Enable = 0;       // 0=关闭, 1=开启
volatile uint16_t ADC_Result = 0;
volatile uint8_tADC_NewData = 0;

// 输入捕获结果(单位:系统时钟周期数)
volatile uint16_t Cap1_Period = 0;
volatile uint16_t Cap1_HighTime = 0;
volatile uint8_tCap1_Ready = 0;

volatile uint16_t Cap2_Period = 0;
volatile uint16_t Cap2_HighTime = 0;
volatile uint8_tCap2_Ready = 0;

// 捕获状态机
volatile uint16_t Cap1_RiseTime = 0;
volatile uint8_tCap1_State = 0;       // 0=等待上升沿, 1=等待下降沿, 2=等待下一个上升沿
volatile uint16_t Cap2_RiseTime = 0;
volatile uint8_tCap2_State = 0;

// 定时上报标志
volatile uint8_tReport_Tick = 0;      // 由定时器3每秒置1

/*************** 函数声明 ***************/
void System_Init(void);
void UART1_Init(void);
void ADC_Init(void);
void Timer3_Init(void);
void PWM0_Output_Init(void);
void PWM1_Capture_Init(void);
void PWM2_Capture_Init(void);

void UART1_SendByte(uint8_t dat);
void UART1_SendString(uint8_t *str);
void UART1_SendDec16(uint16_t val);
void UART1_SendDec32(uint32_t val);

void Set_PWM0_Freq(uint16_t freq_hz);
void Start_ADC(void);
void Process_Command(void);
void Report_All(void);

/*************** 主函数 ***************/
void main(void)
{
    System_Init();
    UART1_Init();
    ADC_Init();
    Timer3_Init();
    PWM0_Output_Init();
    PWM1_Capture_Init();
    PWM2_Capture_Init();

    EA = 1;   // 开启总中断

    UART1_SendString(&quot;STC8G1K08 Demo Ready.\r\n&quot;);
    UART1_SendString(&quot;Commands: ADC_ON, ADC_OFF, PWM xxxx\r\n&quot;);

    while(1)
    {
      // 处理串口命令
      if(UART1_RxFlag)
      {
            Process_Command();
            UART1_RxFlag = 0;
            UART1_RxCnt = 0;
      }

      // 定时自动上报
      if(Report_Tick)
      {
            Report_Tick = 0;
            Report_All();
      }
    }
}

/*************** 系统初始化 ***************/
void System_Init(void)
{
    // 使能访问XFR寄存器(PWM等扩展寄存器需要)
    P_SW2 |= 0x80;      // EAXFR = 1
}

/*************** 串口1初始化 ***************/
void UART1_Init(void)
{
    // 使用定时器2作为波特率发生器,1T模式
    // 24MHz / 4 / 115200 = 52.08 → 取52
    // 实际波特率 = 24000000 / 4 / (65536 - 65484) ≈ 115384 (误差0.16%)
    uint16_t reload = 65536 - (SYS_CLK / 4 / BAUD_RATE);

    AUXR &amp;= ~0x01;          // S1ST2 = 0
    AUXR |= 0x01;         // S1ST2 = 1,选择定时器2做波特率发生器

    T2L = (uint8_t)(reload);
    T2H = (uint8_t)(reload &gt;&gt; 8);
    AUXR |= 0x10;         // T2R = 1,启动定时器2

    SCON = 0x50;            // 模式1(8位UART),REN=1允许接收
    ES = 1;               // 使能串口1中断
}

/*************** ADC初始化 ***************/
void ADC_Init(void)
{
    // ADC通道0 (P1.1)
    // ADC时钟 = SYS_CLK / 2 / 16 = 24MHz/2/16 = 750kHz
    ADCCFG = 0x20;          // 右对齐,ADC时钟 = SYS_CLK/2/16
    ADC_CONTR = 0x80;       // ADC_POWER=1,开启ADC电源
    // 等待ADC电源稳定(简单延时)
    uint16_t i;
    for(i = 0; i &lt; 1000; i++);
}

/*************** 定时器3初始化(1秒定时) ***************/
void Timer3_Init(void)
{
    // 定时器3,1T模式,16位自动重载
    // 定时周期 = (65536 - reload) / SYS_CLK
    // 1秒 = (65536 - reload) / 24000000
    // reload = 65536 - 24000000 = 41536 = 0xA240
    // 但16位最大只能定时约2.73ms,所以需要分频
    // 使用预分频器 TM3PS,先分频再定时
    // 设 TM3PS = 239 (分频240倍),则定时器时钟 = 24MHz/240 = 100kHz
    // 1秒 = (65536 - reload) / 100000
    // reload = 65536 - 100000... 仍然超出16位范围
    // 改用每10ms中断一次,累计100次 = 1秒

    // 定时器3时钟 = SYS_CLK / (TM3PS + 1) = 24MHz / 240 = 100kHz
    // 10ms = 1000个时钟周期
    // reload = 65536 - 1000 = 64536 = 0xFC18

    TM3PS = 239;            // 预分频240倍,定时器时钟=100kHz
    T3L = 0x18;             // 低字节
    T3H = 0xFC;             // 高字节,重载值=0xFC18

    T4T3M |= 0x08;          // T3R = 1,启动定时器3
    IE2 |= 0x20;            // ET3 = 1,使能定时器3中断
}

/*************** PWM0输出初始化 ***************/
void PWM0_Output_Init(void)
{
    // PWM0组,通道0 (PWM0_0, 默认P1.4)
    // 默认输出: 1kHz, 50%占空比

    uint16_t period = PWM0_CLK / 1000;// 1kHz周期计数值
    uint16_t t2 = period / 2;         // 50%占空比

    // 使能PWM0组
    PWMSET = 0x01;          // ENPWM0 = 1

    // 设置PWM0周期
    PWM0CH = (uint8_t)((period &gt;&gt; 8) &amp; 0x7F);
    PWM0CL = (uint8_t)(period);

    // 设置PWM0时钟: 不分频
    PWM0CKS = 0x00;         // SELT2=0, PWM_PS=0 → SYSclk/1

    // 设置通道0的T1和T2
    // T1=0: 计数值到0时输出低电平
    // T2=period/2: 计数值到period/2时输出高电平
    PWM00T1H = 0x00;
    PWM00T1L = 0x00;
    PWM00T2H = (uint8_t)((t2 &gt;&gt; 8) &amp; 0x7F);
    PWM00T2L = (uint8_t)(t2);

    // 配置通道0: 使能输出,初始低电平
    PWM00CR = 0x80;         // ENO=1, INI=0

    // 启动PWM0计数
    PWMCFG01 |= 0x01;       // PWM0CEN = 1
}

/*************** PWM1输入捕获初始化 ***************/
void PWM1_Capture_Init(void)
{
    // PWM1组,通道0 (PWM1_0, 默认P2.0)
    // 测量外部信号1的周期和占空比
    // 使用T2中断(上升沿)和T1中断(下降沿)

    // 使能PWM1组
    PWMSET |= 0x02;         // ENPWM1 = 1

    // 设置PWM1周期为最大值(15位)
    PWM1CH = 0x7F;
    PWM1CL = 0xFF;          // 周期=32767

    // 设置PWM1时钟: 不分频
    PWM1CKS = 0x00;         // SYSclk/1

    // 通道0的T1/T2值不重要,我们只使用中断功能
    PWM10T1H = 0x00;
    PWM10T1L = 0x00;
    PWM10T2H = 0x00;
    PWM10T2L = 0x00;

    // 配置通道0: 使能T1和T2中断,不使能输出
    // ENT2I=1 (T2匹配中断,对应上升沿)
    // ENT1I=1 (T1匹配中断,对应下降沿)
    PWM10CR = 0x06;         // ENT2I=1, ENT1I=1, ENO=0

    // 启动PWM1计数
    PWMCFG23 |= 0x01;       // PWM1CEN = 1 (在PWMCFG23的bit0)
}

/*************** PWM2输入捕获初始化 ***************/
void PWM2_Capture_Init(void)
{
    // PWM2组,通道0 (PWM2_0, 默认P1.0)
    // 测量外部信号2的周期和占空比

    // 使能PWM2组
    PWMSET |= 0x04;         // ENPWM2 = 1

    // 设置PWM2周期为最大值
    PWM2CH = 0x7F;
    PWM2CL = 0xFF;

    // 设置PWM2时钟: 不分频
    PWM2CKS = 0x00;

    // 通道0的T1/T2
    PWM20T1H = 0x00;
    PWM20T1L = 0x00;
    PWM20T2H = 0x00;
    PWM20T2L = 0x00;

    // 配置通道0: 使能T1和T2中断
    PWM20CR = 0x06;         // ENT2I=1, ENT1I=1, ENO=0

    // 启动PWM2计数
    PWMCFG23 |= 0x04;       // PWM2CEN = 1 (在PWMCFG23的bit2)
}

/*************** 设置PWM0输出频率 ***************/
void Set_PWM0_Freq(uint16_t freq_hz)
{
    uint16_t period;
    uint16_t t2;

    // 限制频率范围
    if(freq_hz == 0) freq_hz = 1;
    if(freq_hz &gt; (PWM0_CLK / PWM0_PERIOD_MIN))
      freq_hz = PWM0_CLK / PWM0_PERIOD_MIN;

    // 计算周期计数值
    period = PWM0_CLK / freq_hz;
    if(period &gt; PWM0_PERIOD_MAX) period = PWM0_PERIOD_MAX;
    if(period &lt; PWM0_PERIOD_MIN) period = PWM0_PERIOD_MIN;

    // 保持50%占空比
    t2 = period / 2;

    // 停止PWM0
    PWMCFG01 &amp;= ~0x01;      // PWM0CEN = 0

    // 更新周期
    PWM0CH = (uint8_t)((period &gt;&gt; 8) &amp; 0x7F);
    PWM0CL = (uint8_t)(period);

    // 更新占空比
    PWM00T1H = 0x00;
    PWM00T1L = 0x00;
    PWM00T2H = (uint8_t)((t2 &gt;&gt; 8) &amp; 0x7F);
    PWM00T2L = (uint8_t)(t2);

    // 重新启动PWM0
    PWMCFG01 |= 0x01;       // PWM0CEN = 1
}

/*************** 启动ADC转换 ***************/
void Start_ADC(void)
{
    ADC_CONTR &amp;= ~0x0F;   // 清除通道选择
    ADC_CONTR |= 0x00;      // 选择通道0 (ADC0/P1.1)
    ADC_CONTR |= 0x40;      // ADC_START = 1,启动转换
}

/*************** 串口发送单字节 ***************/
void UART1_SendByte(uint8_t dat)
{
    SBUF = dat;
    while(!TI);
    TI = 0;
}

/*************** 串口发送字符串 ***************/
void UART1_SendString(uint8_t *str)
{
    while(*str)
    {
      UART1_SendByte(*str++);
    }
}

/*************** 串口发送16位十进制数 ***************/
void UART1_SendDec16(uint16_t val)
{
    uint8_t buf;
    uint8_t i = 0;

    if(val == 0)
    {
      UART1_SendByte('0');
      return;
    }

    while(val &gt; 0)
    {
      buf = (uint8_t)(val % 10) + '0';
      val /= 10;
    }

    while(i &gt; 0)
    {
      UART1_SendByte(buf[--i]);
    }
}

/*************** 串口发送32位十进制数 ***************/
void UART1_SendDec32(uint32_t val)
{
    uint8_t buf;
    uint8_t i = 0;

    if(val == 0)
    {
      UART1_SendByte('0');
      return;
    }

    while(val &gt; 0)
    {
      buf = (uint8_t)(val % 10) + '0';
      val /= 10;
    }

    while(i &gt; 0)
    {
      UART1_SendByte(buf[--i]);
    }
}

/*************** 处理串口命令 ***************/
void Process_Command(void)
{
    uint8_t *cmd = (uint8_t *)UART1_RxBuf;

    // 命令1: ADC_ON - 开启ADC自动采样
    if(strcmp((char *)cmd, &quot;ADC_ON&quot;) == 0)
    {
      ADC_Enable = 1;
      UART1_SendString(&quot;ADC sampling ON\r\n&quot;);
      return;
    }

    // 命令2: ADC_OFF - 关闭ADC采样
    if(strcmp((char *)cmd, &quot;ADC_OFF&quot;) == 0)
    {
      ADC_Enable = 0;
      UART1_SendString(&quot;ADC sampling OFF\r\n&quot;);
      return;
    }

    // 命令3: PWM xxxx - 设置PWM频率
    if(memcmp(cmd, &quot;PWM &quot;, 4) == 0)
    {
      uint8_t *p = cmd + 4;
      uint16_t freq = 0;

      while(*p &gt;= '0' &amp;&amp; *p &lt;= '9')
      {
            freq = freq * 10 + (*p - '0');
            p++;
      }

      if(freq &gt;= 1 &amp;&amp; freq &lt;= 2400000)    // 限制合理范围
      {
            Set_PWM0_Freq(freq);

            UART1_SendString(&quot;PWM Freq Set: &quot;);
            UART1_SendDec16(freq);
            UART1_SendString(&quot; Hz\r\n&quot;);
      }
      else
      {
            UART1_SendString(&quot;Invalid freq! Range: 1~2400000\r\n&quot;);
      }
      return;
    }

    // 未知命令
    UART1_SendString(&quot;Unknown cmd! Use: ADC_ON, ADC_OFF, PWM xxxx\r\n&quot;);
}

/*************** 自动上报所有测量值 ***************/
void Report_All(void)
{
    // --- 上报ADC值 ---
    if(ADC_Enable &amp;&amp; ADC_NewData)
    {
      ADC_NewData = 0;

      // 计算电压值(假设VCC=5.0V参考,10位ADC)
      uint16_t mv = (uint32_t)ADC_Result * 5000 / 1024;

      UART1_SendString(&quot;ADC: &quot;);
      UART1_SendDec16(ADC_Result);
      UART1_SendString(&quot; (&quot;);
      UART1_SendDec16(mv / 1000);
      UART1_SendByte('.');
      uint16_t frac = mv % 1000;
      if(frac &lt; 100) UART1_SendByte('0');
      if(frac &lt; 10) UART1_SendByte('0');
      UART1_SendDec16(frac);
      UART1_SendString(&quot;V)\r\n&quot;);
    }

    // --- 上报输入捕获1 ---
    if(Cap1_Ready)
    {
      // 周期(us) = Cap1_Period * 1000000 / SYS_CLK
      uint32_t period_us = (uint32_t)Cap1_Period * 1000000UL / SYS_CLK;
      uint32_t freq_hz = 1000000UL / (period_us ? period_us : 1);
      uint16_t duty_pct = (Cap1_Period ?
            (uint16_t)((uint32_t)Cap1_HighTime * 1000 / Cap1_Period) : 0);

      UART1_SendString(&quot;CAP1: T=&quot;);
      UART1_SendDec32(period_us);
      UART1_SendString(&quot;us F=&quot;);
      UART1_SendDec32(freq_hz);
      UART1_SendString(&quot;Hz D=&quot;);
      UART1_SendDec16(duty_pct / 10);
      UART1_SendByte('.');
      UART1_SendDec16(duty_pct % 10);
      UART1_SendString(&quot;%\r\n&quot;);
    }

    // --- 上报输入捕获2 ---
    if(Cap2_Ready)
    {
      uint32_t period_us = (uint32_t)Cap2_Period * 1000000UL / SYS_CLK;
      uint32_t freq_hz = 1000000UL / (period_us ? period_us : 1);
      uint16_t duty_pct = (Cap2_Period ?
            (uint16_t)((uint32_t)Cap2_HighTime * 1000 / Cap2_Period) : 0);

      UART1_SendString(&quot;CAP2: T=&quot;);
      UART1_SendDec32(period_us);
      UART1_SendString(&quot;us F=&quot;);
      UART1_SendDec32(freq_hz);
      UART1_SendString(&quot;Hz D=&quot;);
      UART1_SendDec16(duty_pct / 10);
      UART1_SendByte('.');
      UART1_SendDec16(duty_pct % 10);
      UART1_SendString(&quot;%\r\n&quot;);
    }
}

/*************** 串口1中断服务程序 ***************/
void UART1_ISR(void) interrupt 4
{
    if(RI)
    {
      RI = 0;
      uint8_t dat = SBUF;

      // 回车或换行结束
      if(dat == '\r' || dat == '\n')
      {
            if(UART1_RxCnt &gt; 0)
            {
                UART1_RxBuf = '\0';
                UART1_RxFlag = 1;
            }
      }
      else if(UART1_RxCnt &lt; sizeof(UART1_RxBuf) - 1)
      {
            UART1_RxBuf = dat;
      }
    }
    if(TI)
    {
      TI = 0;
    }
}

/*************** ADC中断服务程序 ***************/
void ADC_ISR(void) interrupt 5
{
    if(ADC_CONTR &amp; 0x20)    // ADC_FLAG
    {
      ADC_CONTR &amp;= ~0x20; // 清除标志

      // 读取ADC结果(右对齐模式)
      ADC_Result = ((uint16_t)ADC_RES &lt;&lt; 8) | ADC_RESL;
      ADC_NewData = 1;
    }
}

/*************** 定时器3中断服务程序(10ms周期) ***************/
void Timer3_ISR(void) interrupt 19
{
    // 清除中断标志(读T3H/T3L自动清除,或通过AUXINTIF)
    // 这里使用读T3H/T3L来清除标志
    uint8_t tmp;
    tmp = T3H;
    tmp = T3L;

    // 每100次(1秒)置上报标志
    static uint8_t cnt = 0;
    cnt++;
    if(cnt &gt;= 100)
    {
      cnt = 0;
      Report_Tick = 1;

      // 如果ADC开启,触发一次ADC转换
      if(ADC_Enable)
      {
            Start_ADC();
      }
    }
}

/*************** PWM1输入捕获中断服务程序 ***************/
void PWM1_ISR(void) interrupt 22
{
    uint16_t current_cnt;

    // 读取PWM1当前计数器值
    current_cnt = ((uint16_t)(PWM1CH &amp; 0x7F) &lt;&lt; 8) | PWM1CL;

    // 检查T2中断(上升沿触发点)
    if(PWM1IF &amp; 0x02)       // C1IF (通道0的T2匹配中断)
    {
      PWM1IF &amp;= ~0x02;    // 清除标志

      switch(Cap1_State)
      {
            case 0: // 等待上升沿 → 记录上升沿时间
                Cap1_RiseTime = current_cnt;
                Cap1_State = 1;
                break;

            case 1: // 等待下降沿 → 计算高电平时间
                if(current_cnt &gt;= Cap1_RiseTime)
                  Cap1_HighTime = current_cnt - Cap1_RiseTime;
                else
                  Cap1_HighTime = (32767 - Cap1_RiseTime) + current_cnt;
                Cap1_State = 2;
                break;

            case 2: // 等待下一个上升沿 → 计算周期
                if(current_cnt &gt;= Cap1_RiseTime)
                  Cap1_Period = current_cnt - Cap1_RiseTime;
                else
                  Cap1_Period = (32767 - Cap1_RiseTime) + current_cnt;
                Cap1_RiseTime = current_cnt;
                Cap1_Ready = 1;
                Cap1_State = 1;   // 回到等待下降沿
                break;
      }
    }

    // 检查T1中断(下降沿触发点)
    if(PWM1IF &amp; 0x01)       // C0IF (通道0的T1匹配中断)
    {
      PWM1IF &amp;= ~0x01;    // 清除标志
      // 下降沿 - 状态机已在上升沿处理中管理
    }
}

/*************** PWM2输入捕获中断服务程序 ***************/
void PWM2_ISR(void) interrupt 23
{
    uint16_t current_cnt;

    // 读取PWM2当前计数器值
    current_cnt = ((uint16_t)(PWM2CH &amp; 0x7F) &lt;&lt; 8) | PWM2CL;

    // 检查T2中断(上升沿触发点)
    if(PWM2IF &amp; 0x02)       // C1IF
    {
      PWM2IF &amp;= ~0x02;

      switch(Cap2_State)
      {
            case 0:
                Cap2_RiseTime = current_cnt;
                Cap2_State = 1;
                break;

            case 1:
                if(current_cnt &gt;= Cap2_RiseTime)
                  Cap2_HighTime = current_cnt - Cap2_RiseTime;
                else
                  Cap2_HighTime = (32767 - Cap2_RiseTime) + current_cnt;
                Cap2_State = 2;
                break;

            case 2:
                if(current_cnt &gt;= Cap2_RiseTime)
                  Cap2_Period = current_cnt - Cap2_RiseTime;
                else
                  Cap2_Period = (32767 - Cap2_RiseTime) + current_cnt;
                Cap2_RiseTime = current_cnt;
                Cap2_Ready = 1;
                Cap2_State = 1;
                break;
      }
    }

    if(PWM2IF &amp; 0x01)       // C0IF
    {
      PWM2IF &amp;= ~0x01;
    }
}
</code></pre>
<hr />
<h2>程序说明</h2>
<h3>功能1:串口指令开启/关闭 ADC 采样</h3>
<table>
<thead>
<tr>
<th align="left">命令</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>ADC_ON\r\n</code></td>
<td align="left">开启ADC自动采样,定时器3每1秒触发一次ADC转换</td>
</tr>
<tr>
<td align="left"><code>ADC_OFF\r\n</code></td>
<td align="left">关闭ADC采样</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>ADC通道</strong>:ADC0(P1.1),10位分辨率</li>
<li><strong>参考电压</strong>:VCC(假设5.0V)</li>
<li><strong>上报格式</strong>:<code>ADC: 512 (2.50V)</code></li>
</ul>
<h3>功能2:串口修改 PWM 输出频率</h3>
<table>
<thead>
<tr>
<th align="left">命令</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>PWM 1000\r\n</code></td>
<td align="left">设置PWM输出频率为1000Hz</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>PWM输出引脚</strong>:PWM0_0(P1.4)</li>
<li><strong>频率范围</strong>:约 732Hz ~ 2.4MHz(受15位PWM计数器限制)</li>
<li><strong>占空比</strong>:固定50%(可自行修改 <code>Set_PWM0_Freq</code> 函数增加占空比参数)</li>
</ul>
<h3>功能3:定时自动上报 2 路输入信号测量值</h3>
<ul>
<li><strong>上报周期</strong>:每1秒自动上报一次</li>
<li><strong>输入通道</strong>:
<ul>
<li>通道1:PWM1_0(P2.0)</li>
<li>通道2:PWM2_0(P1.0)</li>
</ul>
</li>
<li><strong>上报格式</strong>:
<pre><code>CAP1: T=1000us F=1000Hz D=50.0%
CAP2: T=2000us F=500Hz D=25.0%
</code></pre>
</li>
<li><strong>测量原理</strong>:利用PWM模块的T1/T2触发点中断,通过状态机记录上升沿和下降沿的时间戳,计算周期和高电平时间</li>
</ul>
<h3>中断向量表</h3>
<table>
<thead>
<tr>
<th align="left">中断源</th>
<th align="center">中断号</th>
<th align="left">用途</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">串口1</td>
<td align="center">4</td>
<td align="left">接收串口命令</td>
</tr>
<tr>
<td align="left">ADC</td>
<td align="center">5</td>
<td align="left">ADC转换完成</td>
</tr>
<tr>
<td align="left">定时器3</td>
<td align="center">19</td>
<td align="left">10ms定时,累计1秒触发上报和ADC</td>
</tr>
<tr>
<td align="left">PWM1</td>
<td align="center">22</td>
<td align="left">输入捕获通道1</td>
</tr>
<tr>
<td align="left">PWM2</td>
<td align="center">23</td>
<td align="left">输入捕获通道2</td>
</tr>
</tbody>
</table>
<h3>注意事项</h3>
<ol>
<li><strong>系统时钟</strong>:基于 <strong>24MHz 内部IRC</strong>,若使用其他频率需调整波特率重载值和定时器3参数</li>
<li><strong>引脚分配</strong>:可根据实际PCB通过 <code>P_SW1</code>、<code>P_SW2</code> 寄存器重映射串口引脚</li>
<li><strong>输入捕获频率范围</strong>:受15位PWM计数器最大值32767限制,可测量的最大周期约 <code>32767/24MHz ≈ 1.365ms</code>(约732Hz),更低频率需降低PWM时钟分频</li>
<li><strong>ADC参考电压</strong>:程序假设VCC=5.0V,若实际VCC不同需修改 <code>Report_All</code> 中的电压换算系数</li>
</ol>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC8G1K08%20%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%20%E8%A6%81%E5%8C%85%E5%90%AB%E4%B8%8B%E9%9D%A2%E8%BF%99%E4%BA%9B%0A1.%E7%BB%BC%E5%90%88%E4%BE%8B%E7%A8%8B%EF%BC%9A%E4%B8%B2%E5%8F%A3%E6%8C%87%E4%BB%A4%E5%BC%80%E5%90%AF%20%2F%20%E5%85%B3&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC【AI 助手】帮 STC8G1K08 自动写一个完整的演示程序 要包含下面这些<br />
1.综合例程:串口指令开启 / 关&quot;</a></p>
<p>⏱️ 查找用时 7.4s | 总回答用时 55.9s</p>

8051启蒙者 发表于 2026-7-2 09:25:00

基于 STC8G2K64S4 编写完整工程示例代码,要求实现:
1,串口查询模式接收指令,调用硬件 I2C 读取外部温感芯片数据,串口回传温度;
2,多路独立硬件 PWM,串口下发档位动态调节 RGB 三色 LED 亮度;
3,开机加载 EEPROM 内存储的 RGB 灯光参数,调整灯光后自动保存参数掉电不丢失;
4,内置硬件看门狗定时喂狗,死机复位后串口上报复位状态。





8051启蒙者 发表于 2026-7-2 09:25:37

<p>好的,基于已获取的手册数据和论坛信息,我为您整理出完整的工程示例代码。由于看门狗章节在 STC8G 手册中未直接检索到详细寄存器定义,我将基于 STC 系列通用的看门狗寄存器(WDT_CONTR)标准定义来编写。</p>
<hr />
<h2>STC8G2K64S4 综合工程示例代码</h2>
<h3>文件结构</h3>
<pre><code>Project/
├── main.c          // 主程序
├── config.h      // 配置头文件
├── uart.h / uart.c // 串口驱动
├── i2c.h / i2c.c   // 硬件I2C驱动
├── pwm.h / pwm.c   // PWM驱动
├── eeprom.h / eeprom.c // EEPROM驱动
└── wdt.h / wdt.c   // 看门狗驱动
</code></pre>
<hr />
<h3>1. config.h — 系统配置</h3>
<pre><code class="language-c">#ifndef __CONFIG_H__
#define __CONFIG_H__

#include &quot;reg51.h&quot;
#include &quot;intrins.h&quot;

#define MAIN_Fosc      24000000L   // 系统主频 24MHz
#define Baudrate1      115200L   // 串口波特率

// 数据类型重定义
typedef unsigned char   u8;
typedef unsigned int    u16;
typedef unsigned long   u32;

// 端口操作宏
#define Pn_push_pull(n)      PnM1 &amp;= ~(0x01 &lt;&lt; (n)), PnM0 |= (0x01 &lt;&lt; (n))
#define Pn_open_drain(n)   PnM1 |= (0x01 &lt;&lt; (n)), PnM0 |= (0x01 &lt;&lt; (n))
#define Pn_high_imp(n)       PnM1 |= (0x01 &lt;&lt; (n)), PnM0 &amp;= ~(0x01 &lt;&lt; (n))
#define Pn_bidirectional(n)PnM1 &amp;= ~(0x01 &lt;&lt; (n)), PnM0 &amp;= ~(0x01 &lt;&lt; (n))

// 使能访问XFR
#define EAXSFR()    P_SW2 |= 0x80
#define DXSFR()   P_SW2 &amp;= ~0x80

// 温感芯片I2C地址 (以LM75A为例,7位地址0x48)
#define TEMP_SENSOR_ADDR    0x48

// RGB PWM通道定义 (使用PWM0组)
#define PWM_R_CH    0   // PWM00 - 红色
#define PWM_G_CH    1   // PWM01 - 绿色
#define PWM_B_CH    2   // PWM02 - 蓝色

// EEPROM存储地址
#define EE_RGB_ADDR   0x0000// RGB参数存储起始地址(3字节)
#define EE_MAGIC_ADDR   0x0003// 校验标志地址(1字节)
#define EE_MAGIC_VAL    0xA5    // 校验值

// 看门狗超时时间 (约2.7s @ 24MHz, 预分频256)
#define WDT_TIMEOUT_MS2700

#endif
</code></pre>
<hr />
<h3>2. uart.h / uart.c — 串口驱动(查询模式)</h3>
<pre><code class="language-c">// uart.h
#ifndef __UART_H__
#define __UART_H__

void UART1_Init(void);
void UART1_SendByte(u8 dat);
void UART1_SendString(u8 *p);
u8   UART1_ReceiveByte(u8 *pdat);

#endif
</code></pre>
<pre><code class="language-c">// uart.c
#include &quot;config.h&quot;

sfr P_SW1 = 0xA2;
sfr AUXR= 0x8E;
sfr T2H   = 0xD6;
sfr T2L   = 0xD7;

// 波特率重装值: 65536 - Fosc/4/Baud
#define BRT (65536 - MAIN_Fosc / 4 / Baudrate1)

void UART1_Init(void)
{
    // 使用定时器2做波特率发生器
    P_SW1 &amp;= 0x3F;          // 串口1选择P3.0/P3.1
    P3M0 &amp;= ~0x03;
    P3M1 &amp;= ~0x03;          // P3.0/P3.1 准双向口

    SCON = 0x50;            // 模式1, 8位UART, 允许接收
    AUXR = 0x14;            // T2为1T模式, 启动T2
    T2L= BRT;
    T2H= BRT &gt;&gt; 8;
    AUXR |= 0x01;         // 串口1选择定时器2
}

void UART1_SendByte(u8 dat)
{
    SBUF = dat;
    while(!TI);             // 查询等待发送完成
    TI = 0;
}

void UART1_SendString(u8 *p)
{
    while(*p)
    {
      UART1_SendByte(*p++);
    }
}

// 查询模式接收,返回1表示收到数据,0表示未收到
u8 UART1_ReceiveByte(u8 *pdat)
{
    if(RI)
    {
      RI = 0;
      *pdat = SBUF;
      return 1;
    }
    return 0;
}
</code></pre>
<hr />
<h3>3. i2c.h / i2c.c — 硬件I2C驱动</h3>
<pre><code class="language-c">// i2c.h
#ifndef __I2C_H__
#define __I2C_H__

void I2C_Init(void);
u8   I2C_ReadByte(u8 dev_addr, u8 reg_addr);
void I2C_WriteByte(u8 dev_addr, u8 reg_addr, u8 dat);
u8   I2C_ReadTemp(void);   // 读取温度传感器(以LM75A为例)

#endif
</code></pre>
<pre><code class="language-c">// i2c.c
#include &quot;config.h&quot;

// I2C寄存器定义 (XFR空间)
sfr P_SW2 = 0xBA;

#define I2CCFG   (*(volatile u8 xdata *)0xFE80)
#define I2CMSCR(*(volatile u8 xdata *)0xFE81)
#define I2CMSST(*(volatile u8 xdata *)0xFE82)
#define I2CSLCR(*(volatile u8 xdata *)0xFE83)
#define I2CSLST(*(volatile u8 xdata *)0xFE84)
#define I2CSLADR (*(volatile u8 xdata *)0xFE85)
#define I2CTXD   (*(volatile u8 xdata *)0xFE86)
#define I2CRXD   (*(volatile u8 xdata *)0xFE87)
#define I2CMSAUX (*(volatile u8 xdata *)0xFE88)

// I2C主机命令
#define I2C_IDLE      0x00
#define I2C_START       0x01
#define I2C_SEND_DATA   0x02
#define I2C_RECV_ACK    0x03
#define I2C_RECV_DATA   0x04
#define I2C_SEND_ACK    0x05
#define I2C_SEND_NAK    0x06
#define I2C_STOP      0x07

// 等待I2C操作完成
void I2C_Wait(void)
{
    while(!(I2CMSST &amp; 0x40));   // 等待MSIF=1
    I2CMSST &amp;= ~0x40;         // 清除中断标志
}

void I2C_Init(void)
{
    EAXSFR();                   // 使能访问XFR
    I2CCFG = 0x80 | 0x20;       // ENI2C=1, MSSL=1, MSSPEED=32 -&gt; 约400kHz@24MHz
    // I2C引脚选择: P_SW2的I2C_S默认00 -&gt; P1.5(SCL), P1.4(SDA)
    P1M0 &amp;= ~0x30;
    P1M1 &amp;= ~0x30;            // 准双向口(开漏需外接上拉)
    DXSFR();
}

// 主机发送起始信号+从机地址(写)
void I2C_StartWrite(u8 dev_addr)
{
    EAXSFR();
    I2CMSCR = I2C_START;      // 发送START
    I2C_Wait();
    I2CTXD = (dev_addr &lt;&lt; 1) | 0; // 地址+写
    I2CMSCR = I2C_SEND_DATA;
    I2C_Wait();
    DXSFR();
}

// 主机发送起始信号+从机地址(读)
void I2C_StartRead(u8 dev_addr)
{
    EAXSFR();
    I2CMSCR = I2C_START;
    I2C_Wait();
    I2CTXD = (dev_addr &lt;&lt; 1) | 1; // 地址+读
    I2CMSCR = I2C_SEND_DATA;
    I2C_Wait();
    DXSFR();
}

void I2C_Stop(void)
{
    EAXSFR();
    I2CMSCR = I2C_STOP;
    I2C_Wait();
    DXSFR();
}

// 读取温度传感器LM75A (寄存器0x00为温度寄存器)
u8 I2C_ReadTemp(void)
{
    u8 temp_h;

    // 写寄存器指针
    I2C_StartWrite(TEMP_SENSOR_ADDR);
    EAXSFR();
    I2CTXD = 0x00;            // 温度寄存器地址
    I2CMSCR = I2C_SEND_DATA;
    I2C_Wait();
    DXSFR();

    // 重复起始+读
    I2C_StartRead(TEMP_SENSOR_ADDR);
    EAXSFR();
    I2CMSCR = I2C_RECV_DATA;    // 接收数据
    I2C_Wait();
    temp_h = I2CRXD;            // 读温度高字节(整数部分)
    I2CMSCR = I2C_SEND_NAK;   // 发送NAK(只读1字节)
    I2C_Wait();
    DXSFR();

    I2C_Stop();
    return temp_h;            // 返回温度整数部分(°C)
}
</code></pre>
<hr />
<h3>4. pwm.h / pwm.c — 增强型PWM驱动(RGB三路独立)</h3>
<pre><code class="language-c">// pwm.h
#ifndef __PWM_H__
#define __PWM_H__

typedef struct {
    u8 r;   // 红色亮度 0~255
    u8 g;   // 绿色亮度 0~255
    u8 b;   // 蓝色亮度 0~255
} RGB_TypeDef;

extern RGB_TypeDef g_rgb;

void PWM_Init(void);
void PWM_SetRGB(u8 r, u8 g, u8 b);
void PWM_SetDuty(u8 ch, u16 duty);

#endif
</code></pre>
<pre><code class="language-c">// pwm.c
#include &quot;config.h&quot;

sfr P_SW2 = 0xBA;
RGB_TypeDef g_rgb = {0, 0, 0};// 当前RGB值

// PWM0组寄存器 (XFR空间)
#define PWMSET      (*(volatile u8 xdata *)0x00F1)
#define PWMCFG01    (*(volatile u8 xdata *)0x00F6)

#define PWM0CH      (*(volatile u8 xdata *)0xFF00)
#define PWM0CL      (*(volatile u8 xdata *)0xFF01)
#define PWM0CKS   (*(volatile u8 xdata *)0xFF02)

// PWM00 ~ PWM02 的T1/T2寄存器
#define PWM00T1H    (*(volatile u8 xdata *)0xFF10)
#define PWM00T1L    (*(volatile u8 xdata *)0xFF11)
#define PWM00T2H    (*(volatile u8 xdata *)0xFF12)
#define PWM00T2L    (*(volatile u8 xdata *)0xFF13)
#define PWM00CR   (*(volatile u8 xdata *)0xFF14)

#define PWM01T1H    (*(volatile u8 xdata *)0xFF18)
#define PWM01T1L    (*(volatile u8 xdata *)0xFF19)
#define PWM01T2H    (*(volatile u8 xdata *)0xFF1A)
#define PWM01T2L    (*(volatile u8 xdata *)0xFF1B)
#define PWM01CR   (*(volatile u8 xdata *)0xFF1C)

#define PWM02T1H    (*(volatile u8 xdata *)0xFF20)
#define PWM02T1L    (*(volatile u8 xdata *)0xFF21)
#define PWM02T2H    (*(volatile u8 xdata *)0xFF22)
#define PWM02T2L    (*(volatile u8 xdata *)0xFF23)
#define PWM02CR   (*(volatile u8 xdata *)0xFF24)

// PWM周期 = 1000 (约24kHz @ 24MHz, 预分频1)
#define PWM_PERIOD1000

void PWM_Init(void)
{
    u16 period = PWM_PERIOD;

    EAXSFR();   // 使能访问XFR

    // 配置PWM0组时钟: 系统时钟不分频
    PWM0CKS = 0x00;             // SELT2=0, PWM_PS=0 -&gt; SYSclk/1

    // 设置PWM周期
    PWM0CH = (u8)(period &gt;&gt; 8);
    PWM0CL = (u8)(period);

    // --- 配置PWM00 (红色) ---
    // 初始电平: 低 (INI=0), 输出使能 (ENO=1)
    PWM00CR = 0x80;             // ENO=1, INI=0
    // T1=0 (计数值到0时输出低), T2=period/2 (初始占空比50%)
    PWM00T1H = 0;
    PWM00T1L = 0;
    PWM00T2H = (u8)((period/2) &gt;&gt; 8);
    PWM00T2L = (u8)(period/2);

    // --- 配置PWM01 (绿色) ---
    PWM01CR = 0x80;
    PWM01T1H = 0;
    PWM01T1L = 0;
    PWM01T2H = (u8)((period/2) &gt;&gt; 8);
    PWM01T2L = (u8)(period/2);

    // --- 配置PWM02 (蓝色) ---
    PWM02CR = 0x80;
    PWM02T1H = 0;
    PWM02T1L = 0;
    PWM02T2H = (u8)((period/2) &gt;&gt; 8);
    PWM02T2L = (u8)(period/2);

    // 使能PWM0组
    PWMSET |= 0x01;             // ENPWM0=1
    PWMCFG01 |= 0x01;         // PWM0CEN=1, 开始计数

    DXSFR();

    // 设置PWM输出引脚为准双向口
    // PWM00~PWM02 默认引脚: 查手册PWM0组引脚映射
    // 以PWM00=P1.0, PWM01=P1.1, PWM02=P1.2为例
    P1M0 &amp;= ~0x07;
    P1M1 &amp;= ~0x07;
}

// 设置单路占空比 (0~PWM_PERIOD)
void PWM_SetDuty(u8 ch, u16 duty)
{
    if(duty &gt; PWM_PERIOD) duty = PWM_PERIOD;

    EAXSFR();
    switch(ch)
    {
      case 0: // 红色
            PWM00T2H = (u8)(duty &gt;&gt; 8);
            PWM00T2L = (u8)(duty);
            break;
      case 1: // 绿色
            PWM01T2H = (u8)(duty &gt;&gt; 8);
            PWM01T2L = (u8)(duty);
            break;
      case 2: // 蓝色
            PWM02T2H = (u8)(duty &gt;&gt; 8);
            PWM02T2L = (u8)(duty);
            break;
    }
    DXSFR();
}

// 设置RGB亮度 (0~255)
void PWM_SetRGB(u8 r, u8 g, u8 b)
{
    // 将0~255映射到0~PWM_PERIOD
    u16 dr = (u16)r * PWM_PERIOD / 255;
    u16 dg = (u16)g * PWM_PERIOD / 255;
    u16 db = (u16)b * PWM_PERIOD / 255;

    PWM_SetDuty(0, dr);
    PWM_SetDuty(1, dg);
    PWM_SetDuty(2, db);

    g_rgb.r = r;
    g_rgb.g = g;
    g_rgb.b = b;
}
</code></pre>
<hr />
<h3>5. eeprom.h / eeprom.c — EEPROM驱动</h3>
<pre><code class="language-c">// eeprom.h
#ifndef __EEPROM_H__
#define __EEPROM_H__

void EEPROM_ReadN(u16 addr, u8 *buf, u16 len);
void EEPROM_WriteN(u16 addr, u8 *buf, u16 len);
void EEPROM_EraseSector(u16 addr);
void EEPROM_SaveRGB(RGB_TypeDef *prgb);
u8   EEPROM_LoadRGB(RGB_TypeDef *prgb);

#endif
</code></pre>
<pre><code class="language-c">// eeprom.c
#include &quot;config.h&quot;

sfr IAP_DATA= 0xC2;
sfr IAP_ADDRH = 0xC3;
sfr IAP_ADDRL = 0xC4;
sfr IAP_CMD   = 0xC5;
sfr IAP_TRIG= 0xC6;
sfr IAP_CONTR = 0xC7;
sfr IAP_TPS   = 0xF5;

void IAP_Idle(void)
{
    IAP_CONTR = 0;
    IAP_CMD   = 0;
    IAP_TRIG= 0;
    IAP_ADDRH = 0x80;
    IAP_ADDRL = 0;
}

void EEPROM_ReadN(u16 addr, u8 *buf, u16 len)
{
    u16 i;
    EA = 0;
    IAP_CONTR = 0x80;               // 使能IAP
    IAP_TPS = MAIN_Fosc / 1000000;// 设置等待参数
    IAP_CMD = 1;                  // 读命令
    for(i = 0; i &lt; len; i++)
    {
      IAP_ADDRH = (u8)(addr &gt;&gt; 8);
      IAP_ADDRL = (u8)(addr);
      IAP_TRIG = 0x5A;
      IAP_TRIG = 0xA5;
      _nop_();
      buf = IAP_DATA;
      addr++;
    }
    IAP_Idle();
    EA = 1;
}

void EEPROM_WriteN(u16 addr, u8 *buf, u16 len)
{
    u16 i;
    EA = 0;
    IAP_CONTR = 0x80;
    IAP_TPS = MAIN_Fosc / 1000000;
    IAP_CMD = 2;                  // 写命令
    for(i = 0; i &lt; len; i++)
    {
      IAP_ADDRH = (u8)(addr &gt;&gt; 8);
      IAP_ADDRL = (u8)(addr);
      IAP_DATA = buf;
      IAP_TRIG = 0x5A;
      IAP_TRIG = 0xA5;
      _nop_();
      addr++;
    }
    IAP_Idle();
    EA = 1;
}

void EEPROM_EraseSector(u16 addr)
{
    EA = 0;
    IAP_CONTR = 0x80;
    IAP_TPS = MAIN_Fosc / 1000000;
    IAP_CMD = 3;                  // 擦除命令
    IAP_ADDRH = (u8)(addr &gt;&gt; 8);
    IAP_ADDRL = (u8)(addr);
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();
    IAP_Idle();
    EA = 1;
}

// 保存RGB参数到EEPROM
void EEPROM_SaveRGB(RGB_TypeDef *prgb)
{
    u8 buf;
    buf = prgb-&gt;r;
    buf = prgb-&gt;g;
    buf = prgb-&gt;b;
    buf = EE_MAGIC_VAL;          // 校验标志

    EEPROM_EraseSector(EE_RGB_ADDR);
    EEPROM_WriteN(EE_RGB_ADDR, buf, 4);
}

// 从EEPROM加载RGB参数,返回1成功,0失败
u8 EEPROM_LoadRGB(RGB_TypeDef *prgb)
{
    u8 buf;
    EEPROM_ReadN(EE_RGB_ADDR, buf, 4);

    if(buf == EE_MAGIC_VAL)      // 校验通过
    {
      prgb-&gt;r = buf;
      prgb-&gt;g = buf;
      prgb-&gt;b = buf;
      return 1;
    }
    return 0;                     // 数据无效
}
</code></pre>
<hr />
<h3>6. wdt.h / wdt.c — 看门狗驱动</h3>
<pre><code class="language-c">// wdt.h
#ifndef __WDT_H__
#define __WDT_H__

void WDT_Init(u16 timeout_ms);
void WDT_Feed(void);
u8   WDT_GetResetFlag(void);
void WDT_ClearResetFlag(void);

#endif
</code></pre>
<pre><code class="language-c">// wdt.c
#include &quot;config.h&quot;

// 看门狗控制寄存器 (STC8G系列通用)
sfr WDT_CONTR = 0xC1;

// WDT_CONTR 位定义:
// B7: WDT_FLAG (复位标志, 需软件清零)
// B5: EN_WDT   (看门狗使能)
// B4: CLR_WDT(喂狗, 写1清除计数器)
// B3: IDLE_WDT (空闲模式是否计数)
// B2-B0: PS (预分频)

// 预分频值: PS
// 000: 2^0=1   -&gt; 约 10.7ms @24MHz
// 001: 2^1=2   -&gt; 约 21.3ms
// 010: 2^2=4   -&gt; 约 42.7ms
// 011: 2^3=8   -&gt; 约 85.3ms
// 100: 2^4=16-&gt; 约 170.7ms
// 101: 2^5=32-&gt; 约 341.3ms
// 110: 2^6=64-&gt; 约 682.7ms
// 111: 2^7=128 -&gt; 约 1.37s

// 注: 实际超时时间 = 12个系统时钟周期 * 预分频 * 2^15
// 约 = (12/24MHz) * 2^PS * 32768

void WDT_Init(u16 timeout_ms)
{
    u8 ps = 7;// 默认最大预分频
    // 根据timeout_ms选择预分频 (简化处理)
    if(timeout_ms &lt; 20)      ps = 0;
    else if(timeout_ms &lt; 40) ps = 1;
    else if(timeout_ms &lt; 80) ps = 2;
    else if(timeout_ms &lt; 160) ps = 3;
    else if(timeout_ms &lt; 320) ps = 4;
    else if(timeout_ms &lt; 640) ps = 5;
    else if(timeout_ms &lt; 1280) ps = 6;
    else ps = 7;

    WDT_CONTR = 0x20 | (ps &amp; 0x07);// EN_WDT=1, 设置预分频
    WDT_Feed();                     // 首次喂狗
}

void WDT_Feed(void)
{
    WDT_CONTR |= 0x10;// CLR_WDT=1, 清除计数器
}

u8 WDT_GetResetFlag(void)
{
    return (WDT_CONTR &amp; 0x80) ? 1 : 0;// 读取WDT_FLAG
}

void WDT_ClearResetFlag(void)
{
    WDT_CONTR &amp;= ~0x80;// 清除WDT_FLAG
}
</code></pre>
<hr />
<h3>7. main.c — 主程序</h3>
<pre><code class="language-c">#include &quot;config.h&quot;
#include &quot;uart.h&quot;
#include &quot;i2c.h&quot;
#include &quot;pwm.h&quot;
#include &quot;eeprom.h&quot;
#include &quot;wdt.h&quot;

// 串口命令缓冲区
u8 g_cmd_buf;
u8 g_cmd_idx = 0;

// 协议定义:
// &quot;T&quot; -&gt; 读取温度并回传
// &quot;Rxxx&quot; -&gt; 设置红色亮度 (xxx=0~255)
// &quot;Gxxx&quot; -&gt; 设置绿色亮度
// &quot;Bxxx&quot; -&gt; 设置蓝色亮度
// &quot;S&quot;    -&gt; 保存当前RGB到EEPROM

void ReportResetStatus(void)
{
    if(WDT_GetResetFlag())
    {
      UART1_SendString(&quot;System Reset: Watchdog Timeout!\r\n&quot;);
      WDT_ClearResetFlag();
    }
    else
    {
      UART1_SendString(&quot;System Reset: Power-On / Manual Reset\r\n&quot;);
    }
}

void ProcessCommand(void)
{
    u8 val = 0;
    u8 i;

    if(g_cmd_buf == 'T' || g_cmd_buf == 't')
    {
      // 读取温度
      u8 temp = I2C_ReadTemp();
      UART1_SendString(&quot;Temperature: &quot;);
      // 将温度值转为十进制字符串发送
      UART1_SendByte('0' + temp / 100);
      UART1_SendByte('0' + (temp / 10) % 10);
      UART1_SendByte('0' + temp % 10);
      UART1_SendString(&quot; C\r\n&quot;);
    }
    else if(g_cmd_buf == 'R' || g_cmd_buf == 'r')
    {
      // 解析数值
      val = 0;
      for(i = 1; i &lt; g_cmd_idx &amp;&amp; g_cmd_buf &gt;= '0' &amp;&amp; g_cmd_buf &lt;= '9'; i++)
            val = val * 10 + (g_cmd_buf - '0');
      PWM_SetRGB(val, g_rgb.g, g_rgb.b);
      UART1_SendString(&quot;Red set to: &quot;);
      UART1_SendByte('0' + val / 100);
      UART1_SendByte('0' + (val / 10) % 10);
      UART1_SendByte('0' + val % 10);
      UART1_SendString(&quot;\r\n&quot;);
      // 自动保存
      EEPROM_SaveRGB(&amp;g_rgb);
      UART1_SendString(&quot;RGB saved to EEPROM\r\n&quot;);
    }
    else if(g_cmd_buf == 'G' || g_cmd_buf == 'g')
    {
      val = 0;
      for(i = 1; i &lt; g_cmd_idx &amp;&amp; g_cmd_buf &gt;= '0' &amp;&amp; g_cmd_buf &lt;= '9'; i++)
            val = val * 10 + (g_cmd_buf - '0');
      PWM_SetRGB(g_rgb.r, val, g_rgb.b);
      UART1_SendString(&quot;Green set to: &quot;);
      UART1_SendByte('0' + val / 100);
      UART1_SendByte('0' + (val / 10) % 10);
      UART1_SendByte('0' + val % 10);
      UART1_SendString(&quot;\r\n&quot;);
      EEPROM_SaveRGB(&amp;g_rgb);
      UART1_SendString(&quot;RGB saved to EEPROM\r\n&quot;);
    }
    else if(g_cmd_buf == 'B' || g_cmd_buf == 'b')
    {
      val = 0;
      for(i = 1; i &lt; g_cmd_idx &amp;&amp; g_cmd_buf &gt;= '0' &amp;&amp; g_cmd_buf &lt;= '9'; i++)
            val = val * 10 + (g_cmd_buf - '0');
      PWM_SetRGB(g_rgb.r, g_rgb.g, val);
      UART1_SendString(&quot;Blue set to: &quot;);
      UART1_SendByte('0' + val / 100);
      UART1_SendByte('0' + (val / 10) % 10);
      UART1_SendByte('0' + val % 10);
      UART1_SendString(&quot;\r\n&quot;);
      EEPROM_SaveRGB(&amp;g_rgb);
      UART1_SendString(&quot;RGB saved to EEPROM\r\n&quot;);
    }
    else if(g_cmd_buf == 'S' || g_cmd_buf == 's')
    {
      EEPROM_SaveRGB(&amp;g_rgb);
      UART1_SendString(&quot;RGB saved to EEPROM\r\n&quot;);
    }
    else
    {
      UART1_SendString(&quot;Unknown cmd. Usage: T / Rxxx / Gxxx / Bxxx / S\r\n&quot;);
    }
}

void main(void)
{
    u8 dat;
    u16 wdt_count = 0;

    // 初始化各模块
    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();
    I2C_Init();
    PWM_Init();

    // 从EEPROM加载RGB参数
    if(!EEPROM_LoadRGB(&amp;g_rgb))
    {
      // 首次运行或数据无效,使用默认值(全灭)
      g_rgb.r = 0;
      g_rgb.g = 0;
      g_rgb.b = 0;
      UART1_SendString(&quot;EEPROM data invalid, use default (off)\r\n&quot;);
    }
    else
    {
      UART1_SendString(&quot;RGB loaded from EEPROM\r\n&quot;);
    }
    // 应用加载的RGB值
    PWM_SetRGB(g_rgb.r, g_rgb.g, g_rgb.b);

    // 初始化看门狗 (约2.7s超时)
    WDT_Init(WDT_TIMEOUT_MS);

    // 上报复位状态
    ReportResetStatus();

    UART1_SendString(&quot;STC8G2K64S4 Demo Ready\r\n&quot;);
    UART1_SendString(&quot;Commands: T / Rxxx / Gxxx / Bxxx / S\r\n&quot;);

    while(1)
    {
      // ---- 串口查询接收 ----
      if(UART1_ReceiveByte(&amp;dat))
      {
            if(dat == '\r' || dat == '\n')
            {
                if(g_cmd_idx &gt; 0)
                {
                  g_cmd_buf = '\0';
                  ProcessCommand();
                  g_cmd_idx = 0;
                }
            }
            else if(g_cmd_idx &lt; sizeof(g_cmd_buf) - 1)
            {
                g_cmd_buf = dat;
            }
      }

      // ---- 喂狗 (主循环每轮喂一次) ----
      wdt_count++;
      if(wdt_count &gt;= 1000)   // 约每1ms喂一次(粗略)
      {
            WDT_Feed();
            wdt_count = 0;
      }
    }
}
</code></pre>
<hr />
<h3>使用说明</h3>
<p><strong>硬件连接:</strong></p>
<table>
<thead>
<tr>
<th align="center">功能</th>
<th align="center">引脚</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">串口1 TXD</td>
<td align="center">P3.1</td>
<td align="center">接USB转串口RX</td>
</tr>
<tr>
<td align="center">串口1 RXD</td>
<td align="center">P3.0</td>
<td align="center">接USB转串口TX</td>
</tr>
<tr>
<td align="center">I2C SCL</td>
<td align="center">P1.5</td>
<td align="center">接温感芯片SCL(需上拉)</td>
</tr>
<tr>
<td align="center">I2C SDA</td>
<td align="center">P1.4</td>
<td align="center">接温感芯片SDA(需上拉)</td>
</tr>
<tr>
<td align="center">PWM00 (R)</td>
<td align="center">P1.0</td>
<td align="center">接RGB-LED红色(串联电阻)</td>
</tr>
<tr>
<td align="center">PWM01 (G)</td>
<td align="center">P1.1</td>
<td align="center">接RGB-LED绿色(串联电阻)</td>
</tr>
<tr>
<td align="center">PWM02 (B)</td>
<td align="center">P1.2</td>
<td align="center">接RGB-LED蓝色(串联电阻)</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意:</strong> PWM输出引脚映射以实际封装为准,请查阅 STC8G2K64S4 数据手册&quot;功能脚切换&quot;章节确认 PWM0 组的具体引脚。上述代码中 PWM00~PWM02 的默认引脚需根据手册实际映射调整。</p>
</blockquote>
<p><strong>串口命令(115200,8,N,1):</strong></p>
<table>
<thead>
<tr>
<th align="center">命令</th>
<th align="center">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><code>T</code></td>
<td align="center">读取温度并回传</td>
</tr>
<tr>
<td align="center"><code>R128</code></td>
<td align="center">设置红色亮度为128</td>
</tr>
<tr>
<td align="center"><code>G64</code></td>
<td align="center">设置绿色亮度为64</td>
</tr>
<tr>
<td align="center"><code>B192</code></td>
<td align="center">设置蓝色亮度为192</td>
</tr>
<tr>
<td align="center"><code>S</code></td>
<td align="center">手动保存当前RGB到EEPROM</td>
</tr>
</tbody>
</table>
<p><strong>功能说明:</strong></p>
<ol>
<li><strong>温度读取</strong>:通过硬件I2C读取外部温感芯片(示例为LM75A,地址0x48),串口回传温度值</li>
<li><strong>RGB调光</strong>:串口下发档位值(0~255),通过3路独立硬件PWM调节RGB三色LED亮度</li>
<li><strong>参数保存</strong>:开机自动从EEPROM加载RGB参数,每次调整后自动保存,掉电不丢失</li>
<li><strong>看门狗复位</strong>:硬件看门狗定时喂狗,死机后自动复位,复位后串口上报复位原因</li>
</ol>
<p><strong>信息来源:</strong></p>
<ul>
<li>串口配置:STC8G手册&quot;串口通信&quot;章节</li>
<li>硬件I2C:STC8G手册&quot;I²C总线&quot;章节(寄存器I2CCFG/I2CMSCR/I2CMSST等)</li>
<li>增强型PWM:STC8G手册&quot;精度可达15位的增强型PWM&quot;章节</li>
<li>EEPROM操作:STC8G手册&quot;IAP/EEPROM/DATA-FLASH&quot;章节(含完整C代码例程)</li>
<li>看门狗:基于STC系列通用WDT_CONTR寄存器标准定义</li>
</ul>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8G2K64S4%20%E4%B8%B2%E5%8F%A3%E6%9F%A5%E8%AF%A2%E6%A8%A1%E5%BC%8F%20%E6%8E%A5%E6%94%B6%E6%8C%87%E4%BB%A4%20%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8G2K64S4 串口查询模式 接收指令 示例代码&quot;</a></p>
<p>⏱️ 查找用时 25.5s | 总回答用时 85.8s</p>

芯未来 发表于 2026-7-2 09:25:56

STC【AI 助手】帮 STC32G12K128自动写一个完整的演示程序,要包含下面这些:
串口DMA发送,不使用DMA接收

芯未来 发表于 2026-7-2 09:26:10

<p>现在信息已经足够充分了。我来编写一个完整的演示程序。</p>
<hr />
<h2>完整的 STC32G12K128 串口 DMA 发送演示程序</h2>
<p>以下是一个可直接编译运行的完整工程,演示了串口1通过 DMA 发送数据(不使用 DMA 接收),同时配合定时器周期性触发发送。</p>
<h3>文件清单</h3>
<p>本程序为单文件 <code>main.c</code>,在 Keil C251 中新建项目,选择 <strong>STC32G12K128 Series</strong>,按以下设置:</p>
<ul>
<li><strong>Target → CPU Mode</strong>:Source (251 Native)</li>
<li><strong>Target → Memory Model</strong>:XSmall</li>
<li><strong>Target → Code Rom Size</strong>:Large(代码小于64K时)</li>
<li><strong>Output</strong>:勾选 Create HEX File,HEX-80</li>
<li><strong>L251 Misc → Misc controls</strong>:输入 <code>REMOVEUNUSED</code></li>
</ul>
<h3>完整代码</h3>
<pre><code class="language-c">/*------------------------------------------------------------------*/
/* --- STC32G12K128 串口DMA发送演示程序 (不使用DMA接收)            */
/*                                                                  */
/* 功能:串口1通过DMA方式周期性发送数据,不使用DMA接收            */
/*       定时器0每500ms触发一次DMA发送                              */
/*       发送完成后通过P6.0 LED闪烁指示                           */
/*                                                                  */
/* 晶振/时钟:内部IRC @ 40MHz                                       */
/* 串口1引脚:P3.0(RxD), P3.1(TxD)                                 */
/* 波特率:115200, 8N1                                              */
/*                                                                  */
/* 说明:缓冲区必须定义在 xdata 区                                  */
/*       DMA传输字节数 = 实际字节数 - 1                           */
/*       不使用DMA接收时,完全不涉及UR1R_DMA相关寄存器            */
/*------------------------------------------------------------------*/

#include &quot;stc32g.h&quot;

// 主时钟频率定义
#define MAIN_Fosc       40000000UL      // 内部IRC频率 40MHz

// 波特率计算(定时器2做波特率发生器,1T模式)
#define BAUD            115200UL
#define T2LOAD          (65536UL - (MAIN_Fosc / 4 / BAUD))

// 发送缓冲区大小
#define TX_BUF_SIZE   64

// 发送缓冲区(必须在 xdata 区!)
uint8_t xdata TxBuf;

// 发送计数
volatile uint16_t tx_counter = 0;

// LED指示
#define LED_P6_0      P60

// 函数声明
void SYS_Init(void);
void UART1_Init(void);
void UART1_DMA_Send(uint16_t size, uint8_t xdata *buf);
void UART1_DMA_WaitDone(void);
void Timer0_Init(void);
void delay_ms(uint16_t ms);

/*------------------------------------------------------------------*/
/* 主函数                                                            */
/*------------------------------------------------------------------*/
void main(void)
{
    uint16_t len;
    uint16_t i;

    SYS_Init();               // 系统初始化
    UART1_Init();               // 串口1初始化
    Timer0_Init();            // 定时器0初始化(用于周期性触发DMA发送)

    EA = 1;                     // 开启总中断

    // 准备初始发送数据
    for(i = 0; i &lt; TX_BUF_SIZE; i++)
    {
      TxBuf = i;
    }

    while(1)
    {
      // 主循环中不做任何事,DMA发送由定时器中断触发
      // 也可以在这里通过查询方式随时调用 UART1_DMA_Send()
      NOP();
    }
}

/*------------------------------------------------------------------*/
/* 系统初始化                                                      */
/*------------------------------------------------------------------*/
void SYS_Init(void)
{
    EAXFR = 1;                  // 允许访问扩展特殊功能寄存器(XFR)
    WTST = 0;                   // 程序代码等待时间设为0,最快速度
    CKCON = 1;                  // 访问xdata设为3T/4T,避免DMA与CPU总线竞争
                              // (手册建议:DMA使用时CKCON设为1可完美解决仲裁问题)

    // 设置P6.0为准双向口(LED指示)
    P6M0 = 0x00;
    P6M1 = 0x00;
    LED_P6_0 = 1;               // 初始熄灭(假设低电平点亮)
}

/*------------------------------------------------------------------*/
/* 串口1初始化(不使用DMA接收)                                    */
/* 使用定时器2做波特率发生器,115200bps @ 40MHz                      */
/*------------------------------------------------------------------*/
void UART1_Init(void)
{
    // 设置P3.0/P3.1为准双向口(串口功能)
    P3M0 &amp;= ~0x03;
    P3M1 &amp;= ~0x03;

    // 串口1引脚选择:默认P3.0(RxD), P3.1(TxD)
    // P_SW1 &amp;= ~0xC0;   // 默认就是,无需额外设置

    // 串口1配置:模式1(8位UART),允许接收位可置1或0
    // 注意:我们只做DMA发送,不接收,REN可以置0
    SCON = 0x40;                // 模式1, 8位UART, REN=0(不接收)
    // 如果后续需要查询方式接收,可将REN置1,但DMA接收仍不使用
    // SCON = 0x50;            // REN=1 允许接收,但接收仍用查询/中断方式

    // 定时器2做波特率发生器
    T2R = 0;                  // 停止定时器2
    T2x12 = 1;                  // 1T模式
    T2_CT = 0;                  // 定时器模式
    T2CLKO = 0;               // 不输出时钟

    // 设置波特率重载值
    T2L = (uint8_t)(T2LOAD);
    T2H = (uint8_t)(T2LOAD &gt;&gt; 8);

    // 选择定时器2作为串口1波特率发生器
    S1BRT = 1;

    T2R = 1;                  // 启动定时器2

    // 注意:不使用DMA接收时,不要使能UR1R_DMA的任何寄存器
    // 也不需要对DMA_UR1R_* 做任何操作
}

/*------------------------------------------------------------------*/
/* 串口1 DMA发送函数                                                */
/* 参数:size - 要发送的字节数                                       */
/*      buf- 发送缓冲区指针(必须是 xdata 类型)                  */
/* 说明:本函数启动DMA发送后立即返回,不等待完成                     */
/*       如需等待完成,请调用 UART1_DMA_WaitDone()                   */
/*------------------------------------------------------------------*/
void UART1_DMA_Send(uint16_t size, uint8_t xdata *buf)
{
    uint16_t dma_size;

    if(size == 0) return;

    // DMA传输字节数 = 实际字节数 - 1(手册明确规定)
    dma_size = size - 1;

    // 配置寄存器:关闭DMA中断,查询方式
    DMA_UR1T_CFG = 0x00;

    // 清除状态标志
    DMA_UR1T_STA = 0x00;

    // 设置传输总字节数
    DMA_UR1T_AMT= (uint8_t)(dma_size);      // 低8位
    DMA_UR1T_AMTH = (uint8_t)(dma_size &gt;&gt; 8);   // 高8位

    // 设置发送缓冲区地址(注意:地址必须是 xdata 类型变量的地址)
    DMA_UR1T_TXAL = (uint8_t)((uint16_t)buf);          // 低8位地址
    DMA_UR1T_TXAH = (uint8_t)((uint16_t)buf &gt;&gt; 8);   // 高8位地址

    // 使能DMA并触发发送:ENUR1T=1, TRIG=1
    DMA_UR1T_CR = 0xC0;
}

/*------------------------------------------------------------------*/
/* 等待串口1 DMA发送完成(查询方式)                                  */
/*------------------------------------------------------------------*/
void UART1_DMA_WaitDone(void)
{
    // 查询UR1TIF标志位
    while(!(DMA_UR1T_STA &amp; 0x01));

    // 清除完成标志
    DMA_UR1T_STA = 0x00;

    // 关闭DMA(可选,下次发送时会重新使能)
    DMA_UR1T_CR = 0x00;
}

/*------------------------------------------------------------------*/
/* 定时器0初始化:每500ms产生中断,触发DMA发送                        */
/* 使用16位自动重装载模式,12T分频                                 */
/*------------------------------------------------------------------*/
void Timer0_Init(void)
{
    // 定时器0计算:40MHz / 12 = 3.333MHz, 500ms = 1,666,667 个计数
    // 16位定时器最大65536,需要分多次中断或使用更短周期
    // 这里设置为10ms中断一次,在主循环中累计50次触发发送
    // 10ms = 33333 计数
    uint16_t timer_reload;

    // 10ms @ 40MHz, 12T模式
    // 计数 = 40000000 / 12 * 0.01 = 33333
    timer_reload = 65536UL - (MAIN_Fosc / 12 / 100);

    TM0PS = 0;                  // 定时器0预分频,0表示不分频
    T0x12 = 0;                  // 12T模式
    T0_CT = 0;                  // 定时器模式
    T0CLKO = 0;               // 不输出时钟

    // 设置重载值
    T0H = (uint8_t)(timer_reload &gt;&gt; 8);
    T0L = (uint8_t)(timer_reload);

    // 设置自动重装载
    T0_C/T = 0;               // 定时器
    T0_GATE = 0;                // 不门控

    ET0 = 1;                  // 使能定时器0中断
    T0R = 1;                  // 启动定时器0
}

/*------------------------------------------------------------------*/
/* 定时器0中断服务函数                                                */
/* 每10ms中断一次,累计50次(500ms)触发一次DMA发送                   */
/*------------------------------------------------------------------*/
void Timer0_ISR(void) interrupt 1
{
    static uint16_t tick_count = 0;
    uint16_t i;

    tick_count++;

    if(tick_count &gt;= 50)      // 500ms到
    {
      tick_count = 0;

      // 更新发送数据(演示用:发送递增的计数和固定字符串)
      tx_counter++;

      // 填充发送缓冲区:前4字节为计数值(大端格式)
      TxBuf = (uint8_t)(tx_counter &gt;&gt; 8);   // 高字节
      TxBuf = (uint8_t)(tx_counter);         // 低字节
      TxBuf = 'D';
      TxBuf = 'M';
      TxBuf = 'A';
      TxBuf = ':';
      TxBuf = 'O';
      TxBuf = 'K';
      TxBuf = '\r';
      TxBuf = '\n';

      // 填充递增序列用于观察
      for(i = 10; i &lt; 20; i++)
      {
            TxBuf = (uint8_t)(i - 10 + '0');
      }
      TxBuf = '\r';
      TxBuf = '\n';

      // 启动DMA发送22个字节
      // 注意:DMA发送是异步的,启动后CPU可以继续做其他事
      UART1_DMA_Send(22, TxBuf);

      // 如果希望等待发送完成再继续,可以调用:
      // UART1_DMA_WaitDone();

      // LED翻转指示
      LED_P6_0 = !LED_P6_0;
    }
}

/*------------------------------------------------------------------*/
/* 简易毫秒延时(查询方式,使用定时器0计数)                        */
/* 注意:本演示中未使用,保留备用                                    */
/*------------------------------------------------------------------*/
void delay_ms(uint16_t ms)
{
    uint16_t i, j;

    // 粗略延时 @ 40MHz
    for(i = 0; i &lt; ms; i++)
    {
      for(j = 0; j &lt; 10000; j++)
      {
            NOP();
      }
    }
}
</code></pre>
<hr />
<h3>程序说明</h3>
<h4>1. 功能概述</h4>
<ul>
<li>定时器0每 <strong>10ms</strong> 中断一次,累计 <strong>50次(500ms)</strong> 触发一次 DMA 发送</li>
<li>每次发送 <strong>22个字节</strong>,内容包括:递增计数值、固定字符串 <code>&quot;DMA:OK&quot;</code>、数字序列</li>
<li>发送完成后,P6.0 上的 LED 翻转,指示工作状态</li>
<li><strong>完全不涉及 DMA 接收</strong>,未对 <code>DMA_UR1R_*</code> 任何寄存器进行操作</li>
</ul>
<h4>2. 关键配置要点</h4>
<table>
<thead>
<tr>
<th align="left">项目</th>
<th align="left">设置</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">主时钟</td>
<td align="left">40MHz 内部IRC</td>
<td align="left">ISP下载时选择对应频率</td>
</tr>
<tr>
<td align="left">串口1</td>
<td align="left">模式1, 8N1, 115200bps</td>
<td align="left">定时器2做波特率发生器</td>
</tr>
<tr>
<td align="left">REN位</td>
<td align="left"><strong>置0</strong> (<code>SCON=0x40</code>)</td>
<td align="left">不使能接收,完全不用DMA接收</td>
</tr>
<tr>
<td align="left">发送缓冲区</td>
<td align="left"><code>xdata</code> 类型</td>
<td align="left">DMA只能访问XRAM</td>
</tr>
<tr>
<td align="left">DMA字节数</td>
<td align="left"><code>size - 1</code></td>
<td align="left">手册明确规定</td>
</tr>
<tr>
<td align="left">CKCON</td>
<td align="left"><strong>设为1</strong></td>
<td align="left">避免DMA与CPU同时访问xdata的总线竞争</td>
</tr>
</tbody>
</table>
<h4>3. 不使用 DMA 接收的要点</h4>
<ul>
<li>串口控制寄存器 <code>SCON</code> 的 <code>REN</code> 位可置0(<code>SCON = 0x40</code>),完全禁止接收</li>
<li>完全不初始化、不操作 <code>DMA_UR1R_*</code> 系列寄存器</li>
<li>如果应用中仍需要串口接收(但不用DMA接收),可将 <code>REN</code> 置1,用查询 <code>RI</code> 标志或串口中断方式接收,与 DMA 发送互不干扰</li>
</ul>
<h4>4. 关于 CKCON 的设置</h4>
<p>手册「30 DMA」章节明确指出:</p>
<blockquote>
<p>将CKCON设置为1,可完美解决硬件DMA和CPU同时访问xdata出现的仲裁错误问题。</p>
</blockquote>
<p>本程序中 <code>CKCON = 1</code>,代价是 xdata 访问多1个时钟周期(写3T,读4T),但对整体性能影响很小。</p>
<h4>5. 如何验证</h4>
<ul>
<li>将程序下载到 STC32G12K128 开发板</li>
<li>用 USB转串口工具连接 P3.0(RxD)/P3.1(TxD) 和 GND</li>
<li>打开串口助手,设置 <strong>115200, 8N1</strong></li>
<li>每500ms会收到一帧数据,格式如:<code>00 01 44 4D 41 3A 4F 4B 0D 0A 30 31 32 33 34 35 36 37 38 39 0D 0A</code></li>
<li>同时 P6.0 上的 LED 以 1Hz 频率闪烁</li>
</ul>
<h4>6. 参考来源</h4>
<ul>
<li><strong>手册</strong>:STC32G 数据手册「30 DMA」章节 —— DMA 类型说明、寄存器定义、CKCON 设置要求</li>
<li><strong>手册</strong>:STC32G 数据手册「18 2组USART-LIN/SPI,同步/异步串口通信」章节 —— 串口1 DMA 发送示例代码</li>
<li><strong>论坛</strong>:TID=23401 —— 用户反馈 DMA 发送字节数未减1导致乱码</li>
<li><strong>论坛</strong>:TID=9124 —— 用户反馈 ES 中断与 DMA 中断冲突问题</li>
</ul>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%20%E8%A6%81%E5%8C%85%E5%90%AB%E4%B8%8B%E9%9D%A2%E8%BF%99%E4%BA%9B%EF%BC%9A%0A%E4%B8%B2%E5%8F%A3DMA%E5%8F%91%E9%80%81%20%E4%B8%8D%E4%BD%BF%E7%94%A8DMA&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC【AI 助手】帮 STC32G12K128自动写一个完整的演示程序 要包含下面这些:<br />
串口DMA发送 不使用DMA&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24648&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E8%A6%81%E5%8C%85%E5%90%AB%E4%B8%8B%E9%9D%A2%E8%BF%99%E4%BA%9B%EF%BC%9A%0A%E4%B8%B2%E5%8F%A3DMA%E5%8F%91%E9%80%81%EF%BC%8C%E4%B8%8D%E4%BD%BF%E7%94%A8DMA%E6%8E%A5%E6%94%B6&amp;page=1&amp;extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24057&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E8%A6%81%E5%8C%85%E5%90%AB%E4%B8%8B%E9%9D%A2%E8%BF%99%E4%BA%9B%EF%BC%9A%0A%E4%B8%B2%E5%8F%A3DMA%E5%8F%91%E9%80%81%EF%BC%8C%E4%B8%8D%E4%BD%BF%E7%94%A8DMA%E6%8E%A5%E6%94%B6&amp;page=62&amp;extra=#pid222085">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li>串口信号产生程序——基于stc32g12k128(附串口助手习作)</li>
</ol>
<p>⏱️ 查找用时 5.8s | 总回答用时 47.2s</p>

芯LYS 发表于 2026-7-2 09:26:54

STC【AI 助手】帮AI8051U写 主时钟停振低功耗模式-专用唤醒定时器唤醒演示程序



芯LYS 发表于 2026-7-2 09:27:22

<p>现在我已经获取了所有必要的信息,可以编写完整的演示程序了。</p>
<hr />
<h2>AI8051U 主时钟停振低功耗模式 — 专用唤醒定时器唤醒演示程序</h2>
<p>以下程序基于 <strong>Ai8051U系列</strong> 手册中 <strong>&quot;10 复位、看门狗、掉电唤醒专用定时器与电源管理&quot;</strong> 章节编写。</p>
<h3>程序功能说明</h3>
<ol>
<li>上电后 LED(P1.0)以 200ms 间隔闪烁 5 次,提示程序开始运行</li>
<li>进入<strong>主时钟停振低功耗模式(掉电模式)</strong>,同时启动<strong>掉电唤醒专用定时器</strong></li>
<li>掉电唤醒定时器计数约 <strong>2 秒</strong>后自动唤醒 MCU</li>
<li>唤醒后 LED 快速闪烁 2 次作为唤醒指示,然后再次进入掉电模式</li>
<li>如此循环</li>
</ol>
<h3>完整源代码</h3>
<pre><code class="language-c">/*************************************************************
* 文件名: AI8051U_LowPower_Wakeup_Demo.c
* 功能:   AI8051U 主时钟停振低功耗模式(掉电模式)
*         - 使用专用唤醒定时器定时唤醒
*         - 唤醒后 LED(P1.0)指示
* 硬件:   AI8051U系列, LED接P1.0(低电平点亮)
* 时钟:   内部IRC 24MHz (可根据实际调整)
* 说明:   基于手册&quot;10 复位、看门狗、掉电唤醒专用定时器与电源管理&quot;
*************************************************************/

#include &quot;ai8051u.h&quot;   // AI8051U 头文件

#define MAIN_Fosc    24000000L   // 定义主时钟频率(24MHz)

// 掉电唤醒定时器计数时间计算公式(手册):
// 时间 = 10^6 ÷ Fwt × 16 × (计数值+1)微秒
// Fwt ≈ 32KHz (内部低速IRC, 实际值可从RAM区F8H/F9H读取)
//
// 常用计数值与时间对照(假设Fwt=32KHz):
//   1   → 约 1ms
//   9   → 约 5ms
//   99    → 约 50ms
//   999   → 约 0.5s
//   4095→ 约 2s
//   32766 → 约 16s
// 注意: 计数值0和32767为内部保留值, 不可使用

#define WAKEUP_TIMER_2S   4095    // 约2秒唤醒

/******************** 毫秒级延时函数 ********************/
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for(i = 0; i &lt; ms; i++)
      for(j = 0; j &lt; MAIN_Fosc / 13000; j++);
}

/******************** 配置掉电唤醒定时器 ********************/
/*
* 寄存器说明(手册&quot;掉电唤醒定时器&quot;章节):
*   WKTCL (地址: AAH) - 掉电唤醒定时器低8位
*   WKTCH (地址: ABH) - 掉电唤醒定时器高7位 + 使能位
*   WKTCH (WKTEN): 掉电唤醒定时器使能控制位
*       0: 停用掉电唤醒定时器
*       1: 启用掉电唤醒定时器
*   WKTCH: 计数值高7位
*   WKTCL: 计数值低8位
*   实际计数值 = {WKTCH, WKTCL}
*   写入值必须比实际计数值少1
*
* 注意: 写操作必须先写WKTCL, 再同时写WKTCH和WKTEN位
*       不能用读-修改-写的方式
*/
void WakeupTimer_Init(unsigned int count)
{
    // 先写低字节
    WKTCL = (unsigned char)(count &amp; 0xFF);
    // 再写高字节(含使能位WKTEN=1)
    WKTCH = (unsigned char)(((count &gt;&gt; 8) &amp; 0x7F) | 0x80);
}

/******************** 进入主时钟停振低功耗模式 ********************/
/*
* PCON寄存器(地址: 87H):
*   B7(SMOD)B6(SMOD0)B5(LVDF)B4(POF)
*   B3(GF1)   B2(GF0)    B1(PD)    B0(IDL)
*
*   PD = 1: 进入掉电模式(主时钟停振)
*   IDL = 1: 进入空闲模式(主时钟继续运行, 仅CPU停)
*
* 进入掉电模式后:
*   - 主时钟停振, 所有外设停止工作
*   - 掉电唤醒定时器(若已使能)继续计数
*   - 唤醒后从进入掉电模式的下一条指令继续执行
*   - 注意: 不会进入中断
*/
void Enter_PowerDown(void)
{
    PCON |= 0x02;   // 置位PD位, 进入掉电模式
    // 唤醒后从此处继续执行
    _nop_();      // 空操作, 稳定
    _nop_();
}

/******************** 主函数 ********************/
void main(void)
{
    unsigned char i;

    // ---- 1. 初始化I/O口 ----
    // P1.0设置为推挽输出模式(驱动LED)
    // P1M1=0, P1M0=1 → 推挽输出
    P1M0 |= 0x01;   // P1M0.0 = 1
    P1M1 &amp;= ~0x01;// P1M1.0 = 0
    P10 = 1;      // 初始关闭LED(高电平)

    // ---- 2. 上电指示: LED闪烁5次 ----
    for(i = 0; i &lt; 5; i++)
    {
      P10 = 0;    // LED亮
      delay_ms(200);
      P10 = 1;    // LED灭
      delay_ms(200);
    }

    // ---- 3. 配置掉电唤醒定时器(约2秒唤醒) ----
    WakeupTimer_Init(WAKEUP_TIMER_2S);

    // ---- 4. 主循环: 进入掉电模式 → 唤醒 → 指示 → 再进入 ----
    while(1)
    {
      // 进入主时钟停振低功耗模式(掉电模式)
      // 此时掉电唤醒定时器开始计数
      Enter_PowerDown();

      // ---- 被唤醒后从这里继续执行 ----
      // 唤醒后掉电唤醒定时器自动停止
      // 可通过读取WKTCH/WKTCL获取实际睡眠时间(手册说明)

      // 唤醒指示: LED快速闪烁2次
      P10 = 0;    // LED亮
      delay_ms(100);
      P10 = 1;    // LED灭
      delay_ms(100);
      P10 = 0;    // LED亮
      delay_ms(100);
      P10 = 1;    // LED灭
      delay_ms(100);

      // 重新使能掉电唤醒定时器, 准备下一次唤醒
      // (唤醒后WKTEN位会被硬件清零, 需重新设置)
      WakeupTimer_Init(WAKEUP_TIMER_2S);
    }
}
</code></pre>
<h3>关键寄存器配置说明(摘自手册)</h3>
<table>
<thead>
<tr>
<th align="center">寄存器</th>
<th align="center">地址</th>
<th>功能</th>
<th align="center">本程序配置</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>WKTCL</strong></td>
<td align="center">AAH</td>
<td>掉电唤醒定时器低8位计数值</td>
<td align="center">写入 <code>count &amp; 0xFF</code></td>
</tr>
<tr>
<td align="center"><strong>WKTCH</strong></td>
<td align="center">ABH</td>
<td>高7位计数值 + WKTEN使能位</td>
<td align="center">写入 <code>((count&gt;&gt;8)&amp;0x7F) | 0x80</code></td>
</tr>
<tr>
<td align="center"><strong>PCON</strong></td>
<td align="center">87H</td>
<td>电源控制,PD位控制掉电模式</td>
<td align="center"><code>PCON |= 0x02</code> 进入掉电</td>
</tr>
<tr>
<td align="center"><strong>P1M0/P1M1</strong></td>
<td align="center">92H/91H</td>
<td>P1口模式配置</td>
<td align="center">P1.0设为推挽输出</td>
</tr>
</tbody>
</table>
<h3>程序执行流程</h3>
<pre><code>上电复位

├─ LED闪烁5次 (提示程序运行)

├─ 配置掉电唤醒定时器 (约2秒)

├─ 进入掉电模式 (主时钟停振)
│   │
│   └─ 等待约2秒...
│         │
│   掉电唤醒定时器溢出
│         │
├─ 被唤醒,从进入掉电的下一条指令继续执行

├─ LED快速闪烁2次 (唤醒指示)

├─ 重新配置掉电唤醒定时器

└─ 再次进入掉电模式 → 循环
</code></pre>
<h3>注意事项</h3>
<ol>
<li><strong>写WKTCH/WKTCL的顺序</strong>:必须先写 WKTCL(低字节),再写 WKTCH(高字节+使能位),<strong>不能使用读-修改-写</strong>方式(如 <code>WKTCH |= 0x80</code> 是错误的)。</li>
<li><strong>计数值规则</strong>:写入寄存器的值必须比实际需要的计数值<strong>少1</strong>。例如需要计数4096次,则写入4095。计数值0和32767为内部保留值,不可使用。</li>
<li><strong>唤醒后WKTEN被清零</strong>:MCU被唤醒后,WKTEN位会被硬件自动清零,如需再次进入掉电模式并启用唤醒定时器,必须重新设置。</li>
<li><strong>唤醒后不进入中断</strong>:掉电唤醒定时器唤醒后,程序从进入掉电模式语句的下一条继续执行,<strong>不会产生中断</strong>。</li>
<li><strong>时钟频率</strong>:掉电唤醒定时器固定使用内部低速IRC时钟(约32KHz),该时钟无抗温漂压漂电路,误差较大。可通过读取RAM区 F8H(高字节)和 F9H(低字节)获取出厂时记录的精确频率值。</li>
<li><strong>I/O口状态</strong>:进入掉电模式前,建议将不用的I/O口设置为高阻输入或确定电平,以进一步降低功耗。</li>
</ol>
<h3>时间调整参考</h3>
<p>如需调整唤醒间隔,修改 <code>WAKEUP_TIMER_2S</code> 的宏定义值即可:</p>
<table>
<thead>
<tr>
<th align="center">写入值</th>
<th align="center">约计时间</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">1</td>
<td align="center">1 ms</td>
</tr>
<tr>
<td align="center">9</td>
<td align="center">5 ms</td>
</tr>
<tr>
<td align="center">99</td>
<td align="center">50 ms</td>
</tr>
<tr>
<td align="center">999</td>
<td align="center">0.5 s</td>
</tr>
<tr>
<td align="center"><strong>4095</strong></td>
<td align="center"><strong>2 s</strong></td>
</tr>
<tr>
<td align="center">32766</td>
<td align="center">16 s</td>
</tr>
</tbody>
</table>
<blockquote>
<p>以上时间基于手册中 <span class="language-math">​F_{wt}=32KHz</span> 的典型值计算,实际时间会因温度、芯片个体差异而有所偏差。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC%20AI8051U%20%E4%BD%8E%E5%8A%9F%E8%80%97%20%E5%94%A4%E9%86%92%E5%AE%9A%E6%97%B6%E5%99%A8%20%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC AI8051U 低功耗 唤醒定时器 演示程序&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24469&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AEAI8051U%E5%86%99%20%E4%B8%BB%E6%97%B6%E9%92%9F%E5%81%9C%E6%8C%AF%E4%BD%8E%E5%8A%9F%E8%80%97%E6%A8%A1%E5%BC%8F-%E4%B8%93%E7%94%A8%E5%94%A4%E9%86%92%E5%AE%9A%E6%97%B6%E5%99%A8%E5%94%A4%E9%86%92%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid221625">06-主时钟停振低功耗模式-专用唤醒定时器唤醒,AI8051U实验箱演示程序</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24462&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AEAI8051U%E5%86%99%20%E4%B8%BB%E6%97%B6%E9%92%9F%E5%81%9C%E6%8C%AF%E4%BD%8E%E5%8A%9F%E8%80%97%E6%A8%A1%E5%BC%8F-%E4%B8%93%E7%94%A8%E5%94%A4%E9%86%92%E5%AE%9A%E6%97%B6%E5%99%A8%E5%94%A4%E9%86%92%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid221615">07-主时钟停振低功耗模式-专用唤醒定时器唤醒,STC32G144K246实验箱演示程序</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=13489&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AEAI8051U%E5%86%99%20%E4%B8%BB%E6%97%B6%E9%92%9F%E5%81%9C%E6%8C%AF%E4%BD%8E%E5%8A%9F%E8%80%97%E6%A8%A1%E5%BC%8F-%E4%B8%93%E7%94%A8%E5%94%A4%E9%86%92%E5%AE%9A%E6%97%B6%E5%99%A8%E5%94%A4%E9%86%92%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid137237">AI8051U主时钟停振低功耗模式-外部中断 INT0唤醒咨询</a></li>
</ol>
<p>⏱️ 查找用时 23.5s | 总回答用时 45.0s</p>

32位8051 发表于 2026-7-2 09:33:24

STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序:
用ADC的DMA在主函数轮询获取AD值


32位8051 发表于 2026-7-2 09:33:34

<p>好的,现在信息已经足够。我来编写一个完整的演示程序。</p>
<hr />
<p>以下是为 <strong>STC8H8K64U</strong> 编写的 <strong>ADC + DMA 轮询获取 AD 值</strong> 的完整演示程序。</p>
<h2>程序功能说明</h2>
<ul>
<li>使用 ADC_DMA 自动扫描指定的 ADC 通道(示例使用通道 0:P1.0)</li>
<li>每个通道转换 16 次,DMA 自动计算平均值</li>
<li>主函数中轮询 DMA 完成标志 <code>ADCIF</code></li>
<li>通过串口 1(P3.1)将结果打印输出</li>
<li>时钟频率:11.0592MHz,串口波特率:115200</li>
</ul>
<pre><code class="language-c">/*---------------------------------------------------------------------*/
/* ---STC8H8K64U ADC_DMA 轮询演示程序                               */
/* ---功能:ADC_DMA 自动采样,主函数轮询获取 AD 值                  */
/* ---时钟:11.0592MHz串口:115200,8,N,1                        */
/* ---ADC通道0(P1.0),每通道转换16次,DMA自动求平均值               */
/*---------------------------------------------------------------------*/

#include &quot;STC8H.h&quot;
#include &quot;intrins.h&quot;
#include &quot;stdio.h&quot;

/*************      类型定义      **************/
typedef unsigned charu8;
typedef unsigned int   u16;
typedef unsigned longu32;

/*************      系统参数      **************/
#define MAIN_Fosc      11059200L       // 主时钟频率
#define Baudrate       115200L         // 串口波特率

/*************      ADC参数         **************/
#define ADC_CH_NUM      1               // 使用的ADC通道数量
#define ADC_SAMPLES   16            // 每个通道ADC转换次数
/* 每个通道数据总字节数 = 2*转换次数 + 4
   (2*转换次数: 每次转换结果2字节)
   (+4: 1字节通道号 + 1字节余数 + 2字节平均值) */
#define ADC_CH_DATA_SIZE   (ADC_SAMPLES * 2 + 4)

/* ADC_DMA 数据存储缓冲区 (存放在xdata区) */
u8 xdata AdcDmaBuf;

/*************      串口1初始化   **************/
void UART1_Init(void)
{
    SCON = 0x50;                        // 8位可变波特率, 无奇偶校验
    TMOD = 0x00;                        // 定时器1模式0(16位自动重载)
    TL1= (u8)(65536 - (MAIN_Fosc / 4 / Baudrate));
    TH1= (u8)((65536 - (MAIN_Fosc / 4 / Baudrate)) &gt;&gt; 8);
    TR1= 1;                           // 启动定时器1
    AUXR = 0x40;                        // 定时器1为1T模式
    ES   = 0;                           // 关闭串口中断(轮询方式)
}

/*************      串口发送字节   **************/
void UART1_SendByte(u8 dat)
{
    SBUF = dat;
    while(TI == 0);                     // 等待发送完成
    TI = 0;
}

/*************      串口发送字符串   **************/
void UART1_SendString(u8 *p)
{
    while(*p)
    {
      UART1_SendByte(*p++);
    }
}

/*************      printf支持函数   **************/
char putchar(char c)
{
    UART1_SendByte((u8)c);
    return c;
}

/*************      ADC初始化      **************/
void ADC_Init(void)
{
    /* 将ADC通道对应的IO口设置为高阻输入模式 */
    P1M0 &amp;= ~0x01;                      // P1.0
    P1M1 |=0x01;                      // 高阻输入

    /* ADC配置 */
    ADCCFG = 0x2F;                      // Bit5=1: 结果右对齐
                                        // Bit3-0: ADC时钟 = SYSclk/2/(n+1)
                                        // 0x0F =&gt; 15, ADC时钟 = 11.0592M/2/16 ≈ 345.6KHz
    ADC_CONTR = 0x80;                   // Bit7=1: 开启ADC电源, 其余位默认
    /* 注意:ADC_CONTR的详细位定义:
       Bit7: ADC_POWERADC电源控制位
       Bit6: ADC_STARTADC转换启动控制位
       Bit5: ADC_FLAG   ADC转换完成标志位
       Bit4-0: ADC_CHS 模拟通道选择位
    */
}

/*************      ADC_DMA初始化   **************/
void ADC_DMA_Init(void)
{
    /* 使能访问XFR(扩展特殊功能寄存器) */
    P_SW2 |= 0x80;

    /* 配置ADC_DMA通道使能寄存器 */
    DMA_ADC_CHSW0 = 0x01;               // 使能通道0 (P1.0)
    DMA_ADC_CHSW1 = 0x00;               // 不使能通道8~15

    /* 配置ADC_DMA转换次数
       DMA_ADC_CFG2的Bit3-0: CVTIMESEL
       转换次数 = CVTIMESEL + 1
       此处设为15 =&gt; 转换16次 */
    DMA_ADC_CFG2 = 0x0F;                // 每个通道转换16次

    /* 设置ADC_DMA数据存储首地址 */
    DMA_ADC_RXAH = (u8)(((u16)&amp;AdcDmaBuf) &gt;&gt; 8);
    DMA_ADC_RXAL = (u8)(((u16)&amp;AdcDmaBuf));

    /* 配置ADC_DMA中断优先级和总线优先级 (默认即可) */
    DMA_ADC_CFG = 0x00;               // 不使能中断(轮询方式)

    /* 清除DMA完成标志 */
    DMA_ADC_STA = 0x00;
}

/*************      启动ADC_DMA转换   **************/
void ADC_DMA_Start(void)
{
    /* 启动ADC_DMA转换
       DMA_ADC_CR:
       Bit7: ENADC - ADC_DMA使能
       Bit6: TRIG- 软件触发启动 */
    DMA_ADC_CR = 0xC0;                  // 使能ADC_DMA并触发启动
}

/*************      查询ADC_DMA是否完成 **************/
u8 ADC_DMA_IsDone(void)
{
    /* DMA_ADC_STA的Bit0: ADCIF - ADC_DMA转换完成标志 */
    if(DMA_ADC_STA &amp; 0x01)
    {
      DMA_ADC_STA = 0x00;             // 清除标志
      return 1;
    }
    return 0;
}

/*************      获取指定通道的ADC平均值 **********/
u16 ADC_GetAverage(u8 ch)
{
    u16 avg;

    /* 数据存储格式说明(以转换16次为例):
       AdcDmaBuf   = 第1次转换结果高字节
       AdcDmaBuf   = 第1次转换结果低字节
       ...
       AdcDmaBuf= 第16次转换结果高字节
       AdcDmaBuf= 第16次转换结果低字节
       AdcDmaBuf= 通道号
       AdcDmaBuf= 平均值余数
       AdcDmaBuf= 平均值高字节
       AdcDmaBuf= 平均值低字节
    */
    avg= (u16)AdcDmaBuf &lt;&lt; 8;
    avg |= (u16)AdcDmaBuf;

    return avg;
}

/*************      毫秒级延时      **************/
void Delay_ms(u16 ms)
{
    u16 i, j;
    for(i = 0; i &lt; ms; i++)
    {
      /* 粗略延时,@11.0592MHz 约1ms */
      for(j = 0; j &lt; 1200; j++);
    }
}

/*************      主函数            **************/
void main(void)
{
    u16 adc_value;
    u16 voltage_mv;                     // 电压值(mV)

    /* 关闭看门狗(如果使能了的话) */
    // WDT_CONTR = 0x00;

    /* 初始化串口 */
    UART1_Init();

    /* 初始化ADC */
    ADC_Init();

    /* 初始化ADC_DMA */
    ADC_DMA_Init();

    /* 等待系统稳定 */
    Delay_ms(100);

    /* 打印启动信息 */
    printf(&quot;\r\n&quot;);
    printf(&quot;========================================\r\n&quot;);
    printf(&quot; STC8H8K64U ADC_DMA 轮询演示程序\r\n&quot;);
    printf(&quot; 时钟: 11.0592MHz串口: 115200,8,N,1\r\n&quot;);
    printf(&quot; ADC通道0(P1.0), 每通道转换16次\r\n&quot;);
    printf(&quot;========================================\r\n\r\n&quot;);

    /* 首次启动ADC_DMA */
    ADC_DMA_Start();

    while(1)
    {
      /* 轮询查询ADC_DMA是否完成 */
      if(ADC_DMA_IsDone())
      {
            /* 获取通道0的ADC平均值 (12位分辨率, 范围0~4095) */
            adc_value = ADC_GetAverage(0);

            /* 计算电压值 (假设VRef = VCC = 3.3V)
               电压(mV) = adc_value * 3300 / 4096 */
            voltage_mv = (u16)((u32)adc_value * 3300 / 4096);

            /* 打印结果 */
            printf(&quot;ADC值: %4d(0x%03X)|电压: %d.%03dV\r\n&quot;,
                   adc_value, adc_value,
                   voltage_mv / 1000, voltage_mv % 1000);

            /* 重新启动下一轮ADC_DMA转换 */
            ADC_DMA_Start();
      }

      /* 可在此处添加其他任务代码 */
      /* ... */
    }
}
</code></pre>
<hr />
<h2>程序说明</h2>
<h3>1. 硬件连接</h3>
<ul>
<li><strong>ADC 通道 0</strong> → <strong>P1.0</strong>(模拟信号输入)</li>
<li><strong>串口 1 TXD</strong> → <strong>P3.1</strong>(接 USB 转串口模块的 RX)</li>
<li><strong>VRef</strong>:如果芯片有 <code>ADC_VRef+</code> 引脚,必须接外部参考电压或直接接 VCC,不可悬空</li>
</ul>
<h3>2. 数据存储格式(手册说明)</h3>
<p>ADC_DMA 自动将数据存入 XRAM,每个通道的数据排列如下:</p>
<table>
<thead>
<tr>
<th align="center">偏移地址</th>
<th align="center">内容</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><code> ~ </code></td>
<td align="center">n 次 ADC 转换结果(每次 2 字节,高字节在前)</td>
</tr>
<tr>
<td align="center"><code></code></td>
<td align="center">通道号</td>
</tr>
<tr>
<td align="center"><code></code></td>
<td align="center">n 次转换平均值的余数</td>
</tr>
<tr>
<td align="center"><code></code></td>
<td align="center">平均值高字节</td>
</tr>
<tr>
<td align="center"><code></code></td>
<td align="center">平均值低字节</td>
</tr>
</tbody>
</table>
<h3>3. 关键寄存器配置</h3>
<table>
<thead>
<tr>
<th align="left">寄存器</th>
<th align="center">值</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>ADCCFG</code></td>
<td align="center"><code>0x2F</code></td>
<td align="left">结果右对齐,ADC 时钟 = SYSclk/2/16</td>
</tr>
<tr>
<td align="left"><code>ADC_CONTR</code></td>
<td align="center"><code>0x80</code></td>
<td align="left">开启 ADC 电源</td>
</tr>
<tr>
<td align="left"><code>DMA_ADC_CHSW0</code></td>
<td align="center"><code>0x01</code></td>
<td align="left">使能通道 0</td>
</tr>
<tr>
<td align="left"><code>DMA_ADC_CFG2</code></td>
<td align="center"><code>0x0F</code></td>
<td align="left">每通道转换 16 次</td>
</tr>
<tr>
<td align="left"><code>DMA_ADC_CR</code></td>
<td align="center"><code>0xC0</code></td>
<td align="left">使能 ADC_DMA 并触发启动</td>
</tr>
</tbody>
</table>
<h3>4. 使用步骤</h3>
<ol>
<li>在 Keil 中新建项目,选择芯片型号 <strong>STC8H8K64U</strong></li>
<li>将上述代码复制到 <code>main.c</code></li>
<li>在 <strong>STC-ISP 下载软件</strong> 中选择:
<ul>
<li>芯片型号:STC8H8K64U</li>
<li>时钟频率:<strong>11.0592MHz</strong></li>
<li>串口波特率:115200</li>
</ul>
</li>
<li>下载程序后,打开串口助手(115200,8,N,1),即可看到 ADC 采样结果</li>
</ol>
<h3>5. 修改指南</h3>
<ul>
<li><strong>更换 ADC 通道</strong>:修改 <code>DMA_ADC_CHSW0</code> 和 <code>DMA_ADC_CHSW1</code> 的位,同时修改 <code>ADC_CH_NUM</code></li>
<li><strong>修改转换次数</strong>:修改 <code>ADC_SAMPLES</code> 和 <code>DMA_ADC_CFG2</code> 的值(转换次数 = CVTIMESEL + 1)</li>
<li><strong>修改参考电压</strong>:将 <code>voltage_mv</code> 计算中的 <code>3300</code> 改为实际 VRef 电压值(单位 mV)</li>
</ul>
<blockquote>
<p><strong>信息来源</strong>:STC8H 数据手册第 21 章「ADC模数转换」、第 33 章「DMA」;STC 官方论坛例程「54-DMA-ADC采样数据自动存储」及梁工提供的参考代码。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC8H8K64U%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%9A%0A%E7%94%A8ADC%E7%9A%84DMA%E5%9C%A8%E4%B8%BB%E5%87%BD%E6%95%B0%E8%BD%AE%E8%AF%A2%E8%8E%B7%E5%8F%96AD%E5%80%BC&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序:<br />
用ADC的DMA在主函数轮询获取AD值&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24648&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC8H8K64U%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%9A%0A%E7%94%A8ADC%E7%9A%84DMA%E5%9C%A8%E4%B8%BB%E5%87%BD%E6%95%B0%E8%BD%AE%E8%AF%A2%E8%8E%B7%E5%8F%96AD%E5%80%BC&amp;page=1&amp;extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24552&amp;highlight=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20STC8H8K64U%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%9A%0A%E7%94%A8ADC%E7%9A%84DMA%E5%9C%A8%E4%B8%BB%E5%87%BD%E6%95%B0%E8%BD%AE%E8%AF%A2%E8%8E%B7%E5%8F%96AD%E5%80%BC&amp;page=1&amp;extra=#pid221777">54-DMA-ADC采样数据自动存储,STC8H8K64U实验箱演示程序, STC8H2K32U, STC8H2K12U, ST</a></li>
<li>一起学 AiCube之 AI8H8K64U应用, printf_usb(​&quot;Hello World !\r\n​&quot;)</li>
</ol>
<p>⏱️ 查找用时 21.7s | 总回答用时 47.7s</p>

AI芯 发表于 2026-7-2 09:35:10

基于STC8H8K64U自动写一个矩阵键盘的演示程序,要包含下面这些:
1,使用 P0 口 作为 4×4 矩阵键盘接口(行:P0.0~P0.3,列:P0.4~P0.7)
2,采用行列扫描法检测按键
3,通过 UART1(P3.0/P3.1)将键值发送到串口助手显示,同时支持按键消抖和长按连发功能

页: 52 53 54 55 56 57 58 59 60 61 [62] 63 64 65 66 67 68 69 70 71
查看完整版本: 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】